Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 5264d91

Browse files
committed
Add json_array_elements_text function.
This was a notable omission from the json functions added in 9.3 and there have been numerous complaints about its absence. Laurence Rowe.
1 parent 699b1f4 commit 5264d91

File tree

7 files changed

+143
-22
lines changed

7 files changed

+143
-22
lines changed

doc/src/sgml/func.sgml

+21
Original file line numberDiff line numberDiff line change
@@ -10280,6 +10280,27 @@ table2-mapping
1028010280
1
1028110281
true
1028210282
[2,false]
10283+
</programlisting>
10284+
</entry>
10285+
</row>
10286+
<row>
10287+
<entry>
10288+
<indexterm>
10289+
<primary>json_array_elements_text</primary>
10290+
</indexterm>
10291+
<literal>json_array_elements_text(json)</literal>
10292+
</entry>
10293+
<entry><type>SETOF json</type></entry>
10294+
<entry>
10295+
Expands a JSON array to a set of text values.
10296+
</entry>
10297+
<entry><literal>json_array_elements_text('["foo", "bar"]')</literal></entry>
10298+
<entry>
10299+
<programlisting>
10300+
value
10301+
-----------
10302+
foo
10303+
bar
1028310304
</programlisting>
1028410305
</entry>
1028510306
</row>

src/backend/utils/adt/jsonfuncs.c

+55-12
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ static void each_object_field_end(void *state, char *fname, bool isnull);
6666
static void each_array_start(void *state);
6767
static void each_scalar(void *state, char *token, JsonTokenType tokentype);
6868

69+
/* common worker for json_each* functions */
70+
static inline Datum elements_worker(PG_FUNCTION_ARGS, bool as_text);
71+
6972
/* semantic action functions for json_array_elements */
7073
static void elements_object_start(void *state);
7174
static void elements_array_element_start(void *state, bool isnull);
@@ -165,6 +168,9 @@ typedef struct ElementsState
165168
TupleDesc ret_tdesc;
166169
MemoryContext tmp_cxt;
167170
char *result_start;
171+
bool normalize_results;
172+
bool next_scalar;
173+
char *normalized_scalar;
168174
} ElementsState;
169175

170176
/* state for get_json_object_as_hash */
@@ -1069,19 +1075,31 @@ each_scalar(void *state, char *token, JsonTokenType tokentype)
10691075
}
10701076

10711077
/*
1072-
* SQL function json_array_elements
1078+
* SQL functions json_array_elements and json_array_elements_text
10731079
*
10741080
* get the elements from a json array
10751081
*
10761082
* a lot of this processing is similar to the json_each* functions
10771083
*/
10781084
Datum
10791085
json_array_elements(PG_FUNCTION_ARGS)
1086+
{
1087+
return elements_worker(fcinfo, false);
1088+
}
1089+
1090+
Datum
1091+
json_array_elements_text(PG_FUNCTION_ARGS)
1092+
{
1093+
return elements_worker(fcinfo, true);
1094+
}
1095+
1096+
static inline Datum
1097+
elements_worker(PG_FUNCTION_ARGS, bool as_text)
10801098
{
10811099
text *json = PG_GETARG_TEXT_P(0);
10821100

1083-
/* elements doesn't need any escaped strings, so use false here */
1084-
JsonLexContext *lex = makeJsonLexContext(json, false);
1101+
/* elements only needs escaped strings when as_text */
1102+
JsonLexContext *lex = makeJsonLexContext(json, as_text);
10851103
JsonSemAction *sem;
10861104
ReturnSetInfo *rsi;
10871105
MemoryContext old_cxt;
@@ -1124,6 +1142,9 @@ json_array_elements(PG_FUNCTION_ARGS)
11241142
sem->array_element_start = elements_array_element_start;
11251143
sem->array_element_end = elements_array_element_end;
11261144

1145+
state->normalize_results = as_text;
1146+
state->next_scalar = false;
1147+
11271148
state->lex = lex;
11281149
state->tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
11291150
"json_array_elements temporary cxt",
@@ -1146,7 +1167,17 @@ elements_array_element_start(void *state, bool isnull)
11461167

11471168
/* save a pointer to where the value starts */
11481169
if (_state->lex->lex_level == 1)
1149-
_state->result_start = _state->lex->token_start;
1170+
{
1171+
/*
1172+
* next_scalar will be reset in the array_element_end handler, and
1173+
* since we know the value is a scalar there is no danger of it being
1174+
* on while recursing down the tree.
1175+
*/
1176+
if (_state->normalize_results && _state->lex->token_type == JSON_TOKEN_STRING)
1177+
_state->next_scalar = true;
1178+
else
1179+
_state->result_start = _state->lex->token_start;
1180+
}
11501181
}
11511182

11521183
static void
@@ -1158,7 +1189,7 @@ elements_array_element_end(void *state, bool isnull)
11581189
text *val;
11591190
HeapTuple tuple;
11601191
Datum values[1];
1161-
static bool nulls[1] = {false};
1192+
bool nulls[1] = {false};
11621193

11631194
/* skip over nested objects */
11641195
if (_state->lex->lex_level != 1)
@@ -1167,10 +1198,23 @@ elements_array_element_end(void *state, bool isnull)
11671198
/* use the tmp context so we can clean up after each tuple is done */
11681199
old_cxt = MemoryContextSwitchTo(_state->tmp_cxt);
11691200

1170-
len = _state->lex->prev_token_terminator - _state->result_start;
1171-
val = cstring_to_text_with_len(_state->result_start, len);
1201+
if (isnull && _state->normalize_results)
1202+
{
1203+
nulls[0] = true;
1204+
values[0] = (Datum) NULL;
1205+
}
1206+
else if (_state->next_scalar)
1207+
{
1208+
values[0] = CStringGetTextDatum(_state->normalized_scalar);
1209+
_state->next_scalar = false;
1210+
}
1211+
else
1212+
{
1213+
len = _state->lex->prev_token_terminator - _state->result_start;
1214+
val = cstring_to_text_with_len(_state->result_start, len);
1215+
values[0] = PointerGetDatum(val);
1216+
}
11721217

1173-
values[0] = PointerGetDatum(val);
11741218

11751219
tuple = heap_form_tuple(_state->ret_tdesc, values, nulls);
11761220

@@ -1204,10 +1248,9 @@ elements_scalar(void *state, char *token, JsonTokenType tokentype)
12041248
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
12051249
errmsg("cannot call json_array_elements on a scalar")));
12061250

1207-
/*
1208-
* json_array_elements always returns json, so there's no need to think
1209-
* about de-escaped values here.
1210-
*/
1251+
/* supply de-escaped value if required */
1252+
if (_state->next_scalar)
1253+
_state->normalized_scalar = token;
12111254
}
12121255

12131256
/*

src/include/catalog/pg_proc.h

+2
Original file line numberDiff line numberDiff line change
@@ -4185,6 +4185,8 @@ DATA(insert OID = 3205 ( json_to_recordset PGNSP PGUID 12 1 100 0 0 f f f f f
41854185
DESCR("get set of records with fields from a json array of objects");
41864186
DATA(insert OID = 3968 ( json_typeof PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "114" _null_ _null_ _null_ _null_ json_typeof _null_ _null_ _null_ ));
41874187
DESCR("get the type of a json value");
4188+
DATA(insert OID = 3969 ( json_array_elements_text PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "114" "{114,25}" "{i,o}" "{from_json,value}" _null_ json_array_elements_text _null_ _null_ _null_ ));
4189+
DESCR("elements of json array");
41884190

41894191
/* uuid */
41904192
DATA(insert OID = 2952 ( uuid_in PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2950 "2275" _null_ _null_ _null_ _null_ uuid_in _null_ _null_ _null_ ));

src/include/utils/json.h

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ extern Datum json_array_length(PG_FUNCTION_ARGS);
5858
extern Datum json_each(PG_FUNCTION_ARGS);
5959
extern Datum json_each_text(PG_FUNCTION_ARGS);
6060
extern Datum json_array_elements(PG_FUNCTION_ARGS);
61+
extern Datum json_array_elements_text(PG_FUNCTION_ARGS);
6162
extern Datum json_populate_record(PG_FUNCTION_ARGS);
6263
extern Datum json_populate_recordset(PG_FUNCTION_ARGS);
6364
extern Datum json_to_record(PG_FUNCTION_ARGS);

src/test/regress/expected/json.out

+30-4
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>'{f2,1}';
801801
(1 row)
802802

803803
-- array_elements
804-
select json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]');
804+
select json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]');
805805
json_array_elements
806806
-----------------------
807807
1
@@ -810,9 +810,10 @@ select json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]'
810810
null
811811
{"f1":1,"f2":[7,8,9]}
812812
false
813-
(6 rows)
813+
"stringy"
814+
(7 rows)
814815

815-
select * from json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q;
816+
select * from json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q;
816817
value
817818
-----------------------
818819
1
@@ -821,7 +822,32 @@ select * from json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},
821822
null
822823
{"f1":1,"f2":[7,8,9]}
823824
false
824-
(6 rows)
825+
"stringy"
826+
(7 rows)
827+
828+
select json_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]');
829+
json_array_elements_text
830+
--------------------------
831+
1
832+
true
833+
[1,[2,3]]
834+
835+
{"f1":1,"f2":[7,8,9]}
836+
false
837+
stringy
838+
(7 rows)
839+
840+
select * from json_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q;
841+
value
842+
-----------------------
843+
1
844+
true
845+
[1,[2,3]]
846+
847+
{"f1":1,"f2":[7,8,9]}
848+
false
849+
stringy
850+
(7 rows)
825851

826852
-- populate_record
827853
create type jpop as (a text, b int, c timestamp);

src/test/regress/expected/json_1.out

+30-4
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>'{f2,1}';
801801
(1 row)
802802

803803
-- array_elements
804-
select json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]');
804+
select json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]');
805805
json_array_elements
806806
-----------------------
807807
1
@@ -810,9 +810,10 @@ select json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]'
810810
null
811811
{"f1":1,"f2":[7,8,9]}
812812
false
813-
(6 rows)
813+
"stringy"
814+
(7 rows)
814815

815-
select * from json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q;
816+
select * from json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q;
816817
value
817818
-----------------------
818819
1
@@ -821,7 +822,32 @@ select * from json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},
821822
null
822823
{"f1":1,"f2":[7,8,9]}
823824
false
824-
(6 rows)
825+
"stringy"
826+
(7 rows)
827+
828+
select json_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]');
829+
json_array_elements_text
830+
--------------------------
831+
1
832+
true
833+
[1,[2,3]]
834+
835+
{"f1":1,"f2":[7,8,9]}
836+
false
837+
stringy
838+
(7 rows)
839+
840+
select * from json_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q;
841+
value
842+
-----------------------
843+
1
844+
true
845+
[1,[2,3]]
846+
847+
{"f1":1,"f2":[7,8,9]}
848+
false
849+
stringy
850+
(7 rows)
825851

826852
-- populate_record
827853
create type jpop as (a text, b int, c timestamp);

src/test/regress/sql/json.sql

+4-2
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,10 @@ select '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::json#>>'{f2,1}';
265265

266266
-- array_elements
267267

268-
select json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]');
269-
select * from json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q;
268+
select json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]');
269+
select * from json_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q;
270+
select json_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]');
271+
select * from json_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q;
270272

271273
-- populate_record
272274
create type jpop as (a text, b int, c timestamp);

0 commit comments

Comments
 (0)