On Fri, Jul 10, 2015 at 5:16 PM, Pavel Stehule <pavel.steh...@gmail.com> wrote:
> >> Well, one could call it premature pessimization due to dynamic call >> overhead. >> >> IMO, the fact that json_out_init_context() sets the value callback to >> json_out_value is an implementation detail, the other parts of code should >> not rely on. And for the Explain output, there definitely going to be >> *some* code between context initialization and output callbacks: these are >> done in a number of different functions. >> > > Again - it is necessary? Postgres still use modular code, not OOP code. I > can understand the using of this technique, when I need a possibility to > change behave. But these function are used for printing JSON, not printing > any others. > No, it's not strictly necessary. For me it's not about procedural- vs. object- style, but rather about being able to override/extend the behavior consistently. And for that I would prefer that if I override the value callback in a JSON output context, that it would be called for every value being printed, not only for some of them. Thank you for pointing out the case of Explain format, I've totally overlooked it in my first version. Trying to apply the proposed approach in the explain printing code led me to reorganize things slightly. I've added explicit functions for printing keys vs. values, thus no need to expose that key_scalar param anymore. There are now separate before/after key and before/after value functions as well, but I believe it makes for a cleaner code. The most of the complexity is still in the code that decides whether or not to put spaces (between the values or for indentation) and newlines at certain points. Should we decide to unify the style we emit ourselves, this could be simplified, while still leaving room for great flexibility if overridden by an extension, for example. Have a nice weekend. -- Alex
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c new file mode 100644 index 7d89867..1f365f5 *** a/contrib/hstore/hstore_io.c --- b/contrib/hstore/hstore_io.c *************** hstore_to_json_loose(PG_FUNCTION_ARGS) *** 1241,1286 **** int count = HS_COUNT(in); char *base = STRPTR(in); HEntry *entries = ARRPTR(in); ! StringInfoData tmp, ! dst; if (count == 0) PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); initStringInfo(&tmp); ! initStringInfo(&dst); ! ! appendStringInfoChar(&dst, '{'); for (i = 0; i < count; i++) { resetStringInfo(&tmp); appendBinaryStringInfo(&tmp, HS_KEY(entries, base, i), HS_KEYLEN(entries, i)); ! escape_json(&dst, tmp.data); ! appendStringInfoString(&dst, ": "); if (HS_VALISNULL(entries, i)) ! appendStringInfoString(&dst, "null"); /* guess that values of 't' or 'f' are booleans */ else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 't') ! appendStringInfoString(&dst, "true"); else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 'f') ! appendStringInfoString(&dst, "false"); else { resetStringInfo(&tmp); appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i)); if (IsValidJsonNumber(tmp.data, tmp.len)) ! appendBinaryStringInfo(&dst, tmp.data, tmp.len); else ! escape_json(&dst, tmp.data); } - - if (i + 1 != count) - appendStringInfoString(&dst, ", "); } - appendStringInfoChar(&dst, '}'); ! PG_RETURN_TEXT_P(cstring_to_text(dst.data)); } PG_FUNCTION_INFO_V1(hstore_to_json); --- 1241,1293 ---- int count = HS_COUNT(in); char *base = STRPTR(in); HEntry *entries = ARRPTR(in); ! StringInfoData tmp; ! Datum num; ! JsonOutContext dst; if (count == 0) PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); initStringInfo(&tmp); ! json_out_init_context(&dst, JSON_OUT_USE_SPACES); ! dst.object_start(&dst); for (i = 0; i < count; i++) { resetStringInfo(&tmp); appendBinaryStringInfo(&tmp, HS_KEY(entries, base, i), HS_KEYLEN(entries, i)); ! json_out_cstring_key(&dst, tmp.data); ! if (HS_VALISNULL(entries, i)) ! dst.value(&dst, (Datum) 0, JSONTYPE_NULL, InvalidOid, InvalidOid); ! /* guess that values of 't' or 'f' are booleans */ else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 't') ! dst.value(&dst, BoolGetDatum(true), JSONTYPE_BOOL, ! InvalidOid, InvalidOid); ! else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 'f') ! dst.value(&dst, BoolGetDatum(false), JSONTYPE_BOOL, ! InvalidOid, InvalidOid); else { resetStringInfo(&tmp); appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i)); + if (IsValidJsonNumber(tmp.data, tmp.len)) ! { ! num = DirectFunctionCall3(numeric_in, CStringGetDatum(tmp.data), 0, -1); ! dst.value(&dst, num, JSONTYPE_NUMERIC, ! NUMERICOID, 1702 /* numeric_out */); ! pfree(DatumGetPointer(num)); ! } else ! json_out_cstring_value(&dst, tmp.data); } } ! dst.object_end(&dst); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(dst.result.data, dst.result.len)); } PG_FUNCTION_INFO_V1(hstore_to_json); *************** hstore_to_json(PG_FUNCTION_ARGS) *** 1292,1329 **** int count = HS_COUNT(in); char *base = STRPTR(in); HEntry *entries = ARRPTR(in); ! StringInfoData tmp, ! dst; if (count == 0) PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); initStringInfo(&tmp); ! initStringInfo(&dst); ! ! appendStringInfoChar(&dst, '{'); for (i = 0; i < count; i++) { resetStringInfo(&tmp); appendBinaryStringInfo(&tmp, HS_KEY(entries, base, i), HS_KEYLEN(entries, i)); ! escape_json(&dst, tmp.data); ! appendStringInfoString(&dst, ": "); if (HS_VALISNULL(entries, i)) ! appendStringInfoString(&dst, "null"); else { resetStringInfo(&tmp); appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i)); ! escape_json(&dst, tmp.data); } - - if (i + 1 != count) - appendStringInfoString(&dst, ", "); } - appendStringInfoChar(&dst, '}'); ! PG_RETURN_TEXT_P(cstring_to_text(dst.data)); } PG_FUNCTION_INFO_V1(hstore_to_jsonb); --- 1299,1332 ---- int count = HS_COUNT(in); char *base = STRPTR(in); HEntry *entries = ARRPTR(in); ! StringInfoData tmp; ! JsonOutContext dst; if (count == 0) PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); initStringInfo(&tmp); ! json_out_init_context(&dst, JSON_OUT_USE_SPACES); ! dst.object_start(&dst); for (i = 0; i < count; i++) { resetStringInfo(&tmp); appendBinaryStringInfo(&tmp, HS_KEY(entries, base, i), HS_KEYLEN(entries, i)); ! json_out_cstring_key(&dst, tmp.data); ! if (HS_VALISNULL(entries, i)) ! dst.value(&dst, (Datum) 0, JSONTYPE_NULL, InvalidOid, InvalidOid); else { resetStringInfo(&tmp); appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i)); ! json_out_cstring_value(&dst, tmp.data); } } ! dst.object_end(&dst); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(dst.result.data, dst.result.len)); } PG_FUNCTION_INFO_V1(hstore_to_jsonb); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c new file mode 100644 index 0d1ecc2..19c7db2 *** a/src/backend/commands/explain.c --- b/src/backend/commands/explain.c *************** static void ExplainCloseGroup(const char *** 126,132 **** static void ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es); static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es); - static void ExplainJSONLineEnding(ExplainState *es); static void ExplainYAMLLineStarting(ExplainState *es); static void escape_yaml(StringInfo buf, const char *str); --- 126,131 ---- *************** ExplainQuery(ExplainStmt *stmt, const ch *** 173,179 **** --- 172,189 ---- else if (strcmp(p, "xml") == 0) es->format = EXPLAIN_FORMAT_XML; else if (strcmp(p, "json") == 0) + { es->format = EXPLAIN_FORMAT_JSON; + + json_out_init_context(&es->json_cxt, + JSON_OUT_USE_SPACES | JSON_OUT_USE_LINE_FEEDS); + es->json_cxt.indent_spaces = 2; + + /* point our result buffer straight to json output context */ + pfree(es->str->data); + pfree(es->str); + es->str = &es->json_cxt.result; + } else if (strcmp(p, "yaml") == 0) es->format = EXPLAIN_FORMAT_YAML; else *************** ExplainPropertyList(const char *qlabel, *** 2711,2728 **** break; case EXPLAIN_FORMAT_JSON: ! ExplainJSONLineEnding(es); ! appendStringInfoSpaces(es->str, es->indent * 2); ! escape_json(es->str, qlabel); ! appendStringInfoString(es->str, ": ["); foreach(lc, data) { ! if (!first) ! appendStringInfoString(es->str, ", "); ! escape_json(es->str, (const char *) lfirst(lc)); ! first = false; } ! appendStringInfoChar(es->str, ']'); break; case EXPLAIN_FORMAT_YAML: --- 2721,2733 ---- break; case EXPLAIN_FORMAT_JSON: ! json_out_cstring_key(&es->json_cxt, qlabel); ! es->json_cxt.array_start(&es->json_cxt); foreach(lc, data) { ! json_out_cstring_value(&es->json_cxt, (const char *) lfirst(lc)); } ! es->json_cxt.array_end(&es->json_cxt); break; case EXPLAIN_FORMAT_YAML: *************** ExplainPropertyListNested(const char *ql *** 2757,2773 **** return; case EXPLAIN_FORMAT_JSON: ! ExplainJSONLineEnding(es); ! appendStringInfoSpaces(es->str, es->indent * 2); ! appendStringInfoChar(es->str, '['); foreach(lc, data) { ! if (!first) ! appendStringInfoString(es->str, ", "); ! escape_json(es->str, (const char *) lfirst(lc)); ! first = false; } ! appendStringInfoChar(es->str, ']'); break; case EXPLAIN_FORMAT_YAML: --- 2762,2773 ---- return; case EXPLAIN_FORMAT_JSON: ! es->json_cxt.array_start(&es->json_cxt); foreach(lc, data) { ! json_out_cstring_value(&es->json_cxt, (const char *) lfirst(lc)); } ! es->json_cxt.array_end(&es->json_cxt); break; case EXPLAIN_FORMAT_YAML: *************** ExplainProperty(const char *qlabel, cons *** 2820,2833 **** break; case EXPLAIN_FORMAT_JSON: ! ExplainJSONLineEnding(es); ! appendStringInfoSpaces(es->str, es->indent * 2); ! escape_json(es->str, qlabel); ! appendStringInfoString(es->str, ": "); ! if (numeric) ! appendStringInfoString(es->str, value); ! else ! escape_json(es->str, value); break; case EXPLAIN_FORMAT_YAML: --- 2820,2839 ---- break; case EXPLAIN_FORMAT_JSON: ! { ! Datum num; ! ! json_out_cstring_key(&es->json_cxt, qlabel); ! if (numeric) ! { ! num = DirectFunctionCall3(numeric_in, CStringGetDatum(value), 0, -1); ! es->json_cxt.value(&es->json_cxt, num, JSONTYPE_NUMERIC, ! NUMERICOID, 1702 /* numeric_out */); ! pfree(DatumGetPointer(num)); ! } ! else ! json_out_cstring_value(&es->json_cxt, value); ! } break; case EXPLAIN_FORMAT_YAML: *************** ExplainOpenGroup(const char *objtype, co *** 2913,2935 **** break; case EXPLAIN_FORMAT_JSON: - ExplainJSONLineEnding(es); - appendStringInfoSpaces(es->str, 2 * es->indent); if (labelname) ! { ! escape_json(es->str, labelname); ! appendStringInfoString(es->str, ": "); ! } ! appendStringInfoChar(es->str, labeled ? '{' : '['); ! /* ! * In JSON format, the grouping_stack is an integer list. 0 means ! * we've emitted nothing at this grouping level, 1 means we've ! * emitted something (and so the next item needs a comma). See ! * ExplainJSONLineEnding(). ! */ ! es->grouping_stack = lcons_int(0, es->grouping_stack); ! es->indent++; break; case EXPLAIN_FORMAT_YAML: --- 2919,2931 ---- break; case EXPLAIN_FORMAT_JSON: if (labelname) ! json_out_cstring_key(&es->json_cxt, labelname); ! if (labeled) ! es->json_cxt.object_start(&es->json_cxt); ! else ! es->json_cxt.array_start(&es->json_cxt); break; case EXPLAIN_FORMAT_YAML: *************** ExplainCloseGroup(const char *objtype, c *** 2976,2986 **** break; case EXPLAIN_FORMAT_JSON: ! es->indent--; ! appendStringInfoChar(es->str, '\n'); ! appendStringInfoSpaces(es->str, 2 * es->indent); ! appendStringInfoChar(es->str, labeled ? '}' : ']'); ! es->grouping_stack = list_delete_first(es->grouping_stack); break; case EXPLAIN_FORMAT_YAML: --- 2972,2981 ---- break; case EXPLAIN_FORMAT_JSON: ! if (labeled) ! es->json_cxt.object_end(&es->json_cxt); ! else ! es->json_cxt.array_end(&es->json_cxt); break; case EXPLAIN_FORMAT_YAML: *************** ExplainDummyGroup(const char *objtype, c *** 3010,3023 **** break; case EXPLAIN_FORMAT_JSON: - ExplainJSONLineEnding(es); - appendStringInfoSpaces(es->str, 2 * es->indent); if (labelname) ! { ! escape_json(es->str, labelname); ! appendStringInfoString(es->str, ": "); ! } ! escape_json(es->str, objtype); break; case EXPLAIN_FORMAT_YAML: --- 3005,3014 ---- break; case EXPLAIN_FORMAT_JSON: if (labelname) ! json_out_cstring_key(&es->json_cxt, labelname); ! ! json_out_cstring_value(&es->json_cxt, objtype); break; case EXPLAIN_FORMAT_YAML: *************** ExplainBeginOutput(ExplainState *es) *** 3059,3067 **** case EXPLAIN_FORMAT_JSON: /* top-level structure is an array of plans */ ! appendStringInfoChar(es->str, '['); ! es->grouping_stack = lcons_int(0, es->grouping_stack); ! es->indent++; break; case EXPLAIN_FORMAT_YAML: --- 3050,3056 ---- case EXPLAIN_FORMAT_JSON: /* top-level structure is an array of plans */ ! es->json_cxt.array_start(&es->json_cxt); break; case EXPLAIN_FORMAT_YAML: *************** ExplainEndOutput(ExplainState *es) *** 3088,3096 **** break; case EXPLAIN_FORMAT_JSON: ! es->indent--; ! appendStringInfoString(es->str, "\n]"); ! es->grouping_stack = list_delete_first(es->grouping_stack); break; case EXPLAIN_FORMAT_YAML: --- 3077,3083 ---- break; case EXPLAIN_FORMAT_JSON: ! es->json_cxt.array_end(&es->json_cxt); break; case EXPLAIN_FORMAT_YAML: *************** ExplainXMLTag(const char *tagname, int f *** 3150,3173 **** } /* - * Emit a JSON line ending. - * - * JSON requires a comma after each property but the last. To facilitate this, - * in JSON format, the text emitted for each property begins just prior to the - * preceding line-break (and comma, if applicable). - */ - static void - ExplainJSONLineEnding(ExplainState *es) - { - Assert(es->format == EXPLAIN_FORMAT_JSON); - if (linitial_int(es->grouping_stack) != 0) - appendStringInfoChar(es->str, ','); - else - linitial_int(es->grouping_stack) = 1; - appendStringInfoChar(es->str, '\n'); - } - - /* * Indent a YAML line. * * YAML lines are ordinarily indented by two spaces per indentation level. --- 3137,3142 ---- diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c new file mode 100644 index 26d3843..48dcc6b *** a/src/backend/utils/adt/json.c --- b/src/backend/utils/adt/json.c *************** typedef enum /* contexts of JSON par *** 53,73 **** JSON_PARSE_END /* saw the end of a document, expect nothing */ } JsonParseContext; - typedef enum /* type categories for datum_to_json */ - { - JSONTYPE_NULL, /* null, so we didn't bother to identify */ - JSONTYPE_BOOL, /* boolean (built-in types only) */ - JSONTYPE_NUMERIC, /* numeric (ditto) */ - JSONTYPE_DATE, /* we use special formatting for datetimes */ - JSONTYPE_TIMESTAMP, - JSONTYPE_TIMESTAMPTZ, - JSONTYPE_JSON, /* JSON itself (and JSONB) */ - JSONTYPE_ARRAY, /* array */ - JSONTYPE_COMPOSITE, /* composite */ - JSONTYPE_CAST, /* something with an explicit cast to JSON */ - JSONTYPE_OTHER /* all else */ - } JsonTypeCategory; - static inline void json_lex(JsonLexContext *lex); static inline void json_lex_string(JsonLexContext *lex); static inline void json_lex_number(JsonLexContext *lex, char *s, bool *num_err); --- 53,58 ---- *************** static void report_parse_error(JsonParse *** 80,102 **** static void report_invalid_token(JsonLexContext *lex); static int report_json_context(JsonLexContext *lex); static char *extract_mb_char(char *s); ! static void composite_to_json(Datum composite, StringInfo result, ! bool use_line_feeds); ! static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, Datum *vals, bool *nulls, int *valcount, ! JsonTypeCategory tcategory, Oid outfuncoid, ! bool use_line_feeds); ! static void array_to_json_internal(Datum array, StringInfo result, ! bool use_line_feeds); static void json_categorize_type(Oid typoid, JsonTypeCategory *tcategory, Oid *outfuncoid); ! static void datum_to_json(Datum val, bool is_null, StringInfo result, ! JsonTypeCategory tcategory, Oid outfuncoid, ! bool key_scalar); ! static void add_json(Datum val, bool is_null, StringInfo result, Oid val_type, bool key_scalar); - static text *catenate_stringinfo_string(StringInfo buffer, const char *addon); /* the null action object used for pure validation */ static JsonSemAction nullSemAction = --- 65,85 ---- static void report_invalid_token(JsonLexContext *lex); static int report_json_context(JsonLexContext *lex); static char *extract_mb_char(char *s); ! ! static void json_out_comma(JsonOutContext *out); ! static void json_out_indent(JsonOutContext *out); ! static void json_out_internal(JsonOutContext *out, Datum val, ! JsonTypeCategory tcategory, Oid typoid, Oid outfuncoid, bool escape); ! ! static void array_dim_to_json(JsonOutContext *out, int dim, int ndims, int *dims, Datum *vals, bool *nulls, int *valcount, ! JsonTypeCategory tcategory, Oid elemtypoid, Oid outfuncoid); ! static void array_to_json_internal(Datum array, JsonOutContext *out); static void json_categorize_type(Oid typoid, JsonTypeCategory *tcategory, Oid *outfuncoid); ! static void add_json(Datum val, bool is_null, JsonOutContext *out, Oid val_type, bool key_scalar); /* the null action object used for pure validation */ static JsonSemAction nullSemAction = *************** json_categorize_type(Oid typoid, *** 1377,1399 **** * If key_scalar is true, the value is being printed as a key, so insist * it's of an acceptable type, and force it to be quoted. */ ! static void ! datum_to_json(Datum val, bool is_null, StringInfo result, ! JsonTypeCategory tcategory, Oid outfuncoid, bool key_scalar) { - char *outputstr; - text *jsontext; - /* callers are expected to ensure that null keys are not passed in */ Assert(!(key_scalar && is_null)); - if (is_null) - { - appendStringInfoString(result, "null"); - return; - } - if (key_scalar && (tcategory == JSONTYPE_ARRAY || tcategory == JSONTYPE_COMPOSITE || --- 1360,1373 ---- * If key_scalar is true, the value is being printed as a key, so insist * it's of an acceptable type, and force it to be quoted. */ ! void ! datum_to_json(Datum val, bool is_null, JsonOutContext *out, ! JsonTypeCategory tcategory, Oid typoid, Oid outfuncoid, bool key_scalar) { /* callers are expected to ensure that null keys are not passed in */ Assert(!(key_scalar && is_null)); if (key_scalar && (tcategory == JSONTYPE_ARRAY || tcategory == JSONTYPE_COMPOSITE || *************** datum_to_json(Datum val, bool is_null, S *** 1406,1419 **** switch (tcategory) { case JSONTYPE_ARRAY: ! array_to_json_internal(val, result, false); break; case JSONTYPE_COMPOSITE: ! composite_to_json(val, result, false); break; case JSONTYPE_BOOL: outputstr = DatumGetBool(val) ? "true" : "false"; ! if (key_scalar) escape_json(result, outputstr); else appendStringInfoString(result, outputstr); --- 1380,1591 ---- switch (tcategory) { case JSONTYPE_ARRAY: ! array_to_json_internal(val, out); break; case JSONTYPE_COMPOSITE: ! composite_to_json(val, out); ! break; ! default: ! if (key_scalar) ! out->key(out, val, tcategory, typoid, outfuncoid); ! else ! out->value(out, val, tcategory, typoid, outfuncoid); ! break; ! } ! } ! ! void ! json_out_init_context(JsonOutContext *out, int flags) ! { ! out->object_start = json_out_object_start; ! out->object_end = json_out_object_end; ! out->array_start = json_out_array_start; ! out->array_end = json_out_array_end; ! out->before_key = json_out_before_key; ! out->key = json_out_key; ! out->after_key = json_out_after_key; ! out->before_value = json_out_before_value; ! out->value = json_out_value; ! out->after_value = json_out_after_value; ! ! initStringInfo(&out->result); ! out->flags = flags; ! out->indent_spaces = 0; ! out->need_comma = false; ! out->need_colon = false; ! out->depth = 0; ! ! out->agg_tcategory = JSONTYPE_OTHER; ! out->agg_outfuncoid = InvalidOid; ! } ! ! void ! json_out_object_start(JsonOutContext *out) ! { ! out->before_value(out); ! ! appendStringInfoChar(&out->result, '{'); ! if (out->indent_spaces > 0) ! appendStringInfoChar(&out->result, '\n'); ! ! out->depth++; ! } ! ! void ! json_out_object_end(JsonOutContext *out) ! { ! if (out->indent_spaces > 0) ! appendStringInfoChar(&out->result, '\n'); ! ! out->depth--; ! ! json_out_indent(out); ! appendStringInfoChar(&out->result, '}'); ! ! out->after_value(out); ! } ! ! void ! json_out_array_start(JsonOutContext *out) ! { ! out->before_value(out); ! ! appendStringInfoChar(&out->result, '['); ! if (out->indent_spaces > 0) ! appendStringInfoChar(&out->result, '\n'); ! ! out->depth++; ! } ! ! void ! json_out_array_end(JsonOutContext *out) ! { ! if (out->indent_spaces > 0) ! appendStringInfoChar(&out->result, '\n'); ! ! out->depth--; ! ! json_out_indent(out); ! appendStringInfoChar(&out->result, ']'); ! ! out->after_value(out); ! } ! ! void ! json_out_before_key(JsonOutContext *out) ! { ! Assert(!out->need_colon); ! ! json_out_comma(out); ! json_out_indent(out); ! } ! ! void ! json_out_after_key(JsonOutContext *out) ! { ! out->need_colon = true; ! out->need_comma = false; ! } ! ! void ! json_out_before_value(JsonOutContext *out) ! { ! json_out_comma(out); ! ! if (out->need_colon) ! { ! out->need_colon = false; ! appendStringInfoChar(&out->result, ':'); ! ! if ((out->flags & JSON_OUT_USE_SPACES) != 0) ! appendStringInfoChar(&out->result, ' '); ! } ! else ! json_out_indent(out); ! } ! ! void ! json_out_after_value(JsonOutContext *out) ! { ! out->need_comma = true; ! } ! ! static void ! json_out_comma(JsonOutContext *out) ! { ! if (out->need_comma) ! { ! out->need_comma = false; ! appendStringInfoChar(&out->result, ','); ! ! /* don't get into all the prettiness deep in the object structure, ! * unless indentation was required */ ! if (out->depth == 1 || out->indent_spaces > 0) ! { ! if ((out->flags & JSON_OUT_USE_SPACES) != 0 && ! (out->flags & JSON_OUT_USE_LINE_FEEDS) == 0) ! appendStringInfoChar(&out->result, ' '); ! ! if ((out->flags & JSON_OUT_USE_LINE_FEEDS) != 0) ! { ! appendStringInfoChar(&out->result, '\n'); ! ! /* this is only to support json_agg's current output style; ! * but if indent is set we don't need the extra space */ ! if (out->indent_spaces <= 0) ! appendStringInfoChar(&out->result, ' '); ! } ! } ! } ! } ! ! static void ! json_out_indent(JsonOutContext *out) ! { ! if (out->indent_spaces > 0) ! appendStringInfoSpaces(&out->result, out->depth * out->indent_spaces); ! } ! ! void ! json_out_key(JsonOutContext *out, Datum val, JsonTypeCategory tcategory, ! Oid typoid, Oid outfuncoid) ! { ! out->before_key(out); ! ! json_out_internal(out, val, tcategory, typoid, outfuncoid, true); ! ! out->after_key(out); ! } ! ! void ! json_out_value(JsonOutContext *out, Datum val, JsonTypeCategory tcategory, ! Oid typoid, Oid outfuncoid) ! { ! out->before_value(out); ! ! json_out_internal(out, val, tcategory, typoid, outfuncoid, false); ! ! out->after_value(out); ! } ! ! static void ! json_out_internal(JsonOutContext *out, Datum val, JsonTypeCategory tcategory, ! Oid typoid, Oid outfuncoid, bool escape) ! { ! char *outputstr; ! text *jsontext; ! StringInfo result = &out->result; ! ! Assert(!(tcategory == JSONTYPE_ARRAY || tcategory == JSONTYPE_COMPOSITE)); ! ! switch (tcategory) ! { ! case JSONTYPE_NULL: ! appendStringInfoString(result, "null"); break; case JSONTYPE_BOOL: outputstr = DatumGetBool(val) ? "true" : "false"; ! if (escape) escape_json(result, outputstr); else appendStringInfoString(result, outputstr); *************** datum_to_json(Datum val, bool is_null, S *** 1425,1431 **** * Don't call escape_json for a non-key if it's a valid JSON * number. */ ! if (!key_scalar && IsValidJsonNumber(outputstr, strlen(outputstr))) appendStringInfoString(result, outputstr); else escape_json(result, outputstr); --- 1597,1603 ---- * Don't call escape_json for a non-key if it's a valid JSON * number. */ ! if (!escape && IsValidJsonNumber(outputstr, strlen(outputstr))) appendStringInfoString(result, outputstr); else escape_json(result, outputstr); *************** datum_to_json(Datum val, bool is_null, S *** 1520,1581 **** pfree(jsontext); break; default: ! outputstr = OidOutputFunctionCall(outfuncoid, val); ! escape_json(result, outputstr); ! pfree(outputstr); break; } } /* * Process a single dimension of an array. * If it's the innermost dimension, output the values, otherwise call * ourselves recursively to process the next dimension. */ static void ! array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, Datum *vals, bool *nulls, int *valcount, JsonTypeCategory tcategory, ! Oid outfuncoid, bool use_line_feeds) { int i; - const char *sep; Assert(dim < ndims); ! sep = use_line_feeds ? ",\n " : ","; ! ! appendStringInfoChar(result, '['); for (i = 1; i <= dims[dim]; i++) { - if (i > 1) - appendStringInfoString(result, sep); - if (dim + 1 == ndims) { ! datum_to_json(vals[*valcount], nulls[*valcount], result, tcategory, ! outfuncoid, false); (*valcount)++; } else ! { ! /* ! * Do we want line feeds on inner dimensions of arrays? For now ! * we'll say no. ! */ ! array_dim_to_json(result, dim + 1, ndims, dims, vals, nulls, ! valcount, tcategory, outfuncoid, false); ! } } ! appendStringInfoChar(result, ']'); } /* * Turn an array into JSON. */ static void ! array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds) { ArrayType *v = DatumGetArrayTypeP(array); Oid element_type = ARR_ELEMTYPE(v); --- 1692,1760 ---- pfree(jsontext); break; default: ! if (typoid == CSTRINGOID) ! escape_json(result, DatumGetCString(val)); ! else ! { ! outputstr = OidOutputFunctionCall(outfuncoid, val); ! escape_json(result, outputstr); ! pfree(outputstr); ! } break; } } + void + json_out_cstring_key(JsonOutContext *out, const char *str) + { + out->key(out, CStringGetDatum(str), JSONTYPE_OTHER, + CSTRINGOID, 2293 /* cstring_out */); + } + + void + json_out_cstring_value(JsonOutContext *out, const char *str) + { + out->value(out, CStringGetDatum(str), JSONTYPE_OTHER, + CSTRINGOID, 2293 /* cstring_out */); + } + /* * Process a single dimension of an array. * If it's the innermost dimension, output the values, otherwise call * ourselves recursively to process the next dimension. */ static void ! array_dim_to_json(JsonOutContext *out, int dim, int ndims, int *dims, Datum *vals, bool *nulls, int *valcount, JsonTypeCategory tcategory, ! Oid elemtypoid, Oid outfuncoid) { int i; Assert(dim < ndims); ! out->array_start(out); for (i = 1; i <= dims[dim]; i++) { if (dim + 1 == ndims) { ! datum_to_json(vals[*valcount], nulls[*valcount], out, tcategory, ! elemtypoid, outfuncoid, false); (*valcount)++; } else ! array_dim_to_json(out, dim + 1, ndims, dims, vals, nulls, ! valcount, tcategory, elemtypoid, outfuncoid); } ! out->array_end(out); } /* * Turn an array into JSON. */ static void ! array_to_json_internal(Datum array, JsonOutContext *out) { ArrayType *v = DatumGetArrayTypeP(array); Oid element_type = ARR_ELEMTYPE(v); *************** array_to_json_internal(Datum array, Stri *** 1597,1603 **** if (nitems <= 0) { ! appendStringInfoString(result, "[]"); return; } --- 1776,1783 ---- if (nitems <= 0) { ! out->array_start(out); ! out->array_end(out); return; } *************** array_to_json_internal(Datum array, Stri *** 1611,1618 **** typalign, &elements, &nulls, &nitems); ! array_dim_to_json(result, 0, ndim, dim, elements, nulls, &count, tcategory, ! outfuncoid, use_line_feeds); pfree(elements); pfree(nulls); --- 1791,1798 ---- typalign, &elements, &nulls, &nitems); ! array_dim_to_json(out, 0, ndim, dim, elements, nulls, &count, tcategory, ! element_type, outfuncoid); pfree(elements); pfree(nulls); *************** array_to_json_internal(Datum array, Stri *** 1621,1628 **** /* * Turn a composite / record into JSON. */ ! static void ! composite_to_json(Datum composite, StringInfo result, bool use_line_feeds) { HeapTupleHeader td; Oid tupType; --- 1801,1808 ---- /* * Turn a composite / record into JSON. */ ! void ! composite_to_json(Datum composite, JsonOutContext *out) { HeapTupleHeader td; Oid tupType; *************** composite_to_json(Datum composite, Strin *** 1631,1640 **** HeapTupleData tmptup, *tuple; int i; - bool needsep = false; - const char *sep; - - sep = use_line_feeds ? ",\n " : ","; td = DatumGetHeapTupleHeader(composite); --- 1811,1816 ---- *************** composite_to_json(Datum composite, Strin *** 1648,1654 **** tmptup.t_data = td; tuple = &tmptup; ! appendStringInfoChar(result, '{'); for (i = 0; i < tupdesc->natts; i++) { --- 1824,1830 ---- tmptup.t_data = td; tuple = &tmptup; ! out->object_start(out); for (i = 0; i < tupdesc->natts; i++) { *************** composite_to_json(Datum composite, Strin *** 1661,1676 **** if (tupdesc->attrs[i]->attisdropped) continue; - if (needsep) - appendStringInfoString(result, sep); - needsep = true; - attname = NameStr(tupdesc->attrs[i]->attname); ! escape_json(result, attname); ! appendStringInfoChar(result, ':'); val = heap_getattr(tuple, i + 1, tupdesc, &isnull); - if (isnull) { tcategory = JSONTYPE_NULL; --- 1837,1846 ---- if (tupdesc->attrs[i]->attisdropped) continue; attname = NameStr(tupdesc->attrs[i]->attname); ! json_out_cstring_key(out, attname); val = heap_getattr(tuple, i + 1, tupdesc, &isnull); if (isnull) { tcategory = JSONTYPE_NULL; *************** composite_to_json(Datum composite, Strin *** 1680,1689 **** json_categorize_type(tupdesc->attrs[i]->atttypid, &tcategory, &outfuncoid); ! datum_to_json(val, isnull, result, tcategory, outfuncoid, false); } ! appendStringInfoChar(result, '}'); ReleaseTupleDesc(tupdesc); } --- 1850,1861 ---- json_categorize_type(tupdesc->attrs[i]->atttypid, &tcategory, &outfuncoid); ! datum_to_json(val, isnull, out, tcategory, ! tupdesc->attrs[i]->atttypid, outfuncoid, false); } ! out->object_end(out); ! ReleaseTupleDesc(tupdesc); } *************** composite_to_json(Datum composite, Strin *** 1695,1701 **** * lookups only once. */ static void ! add_json(Datum val, bool is_null, StringInfo result, Oid val_type, bool key_scalar) { JsonTypeCategory tcategory; --- 1867,1873 ---- * lookups only once. */ static void ! add_json(Datum val, bool is_null, JsonOutContext *out, Oid val_type, bool key_scalar) { JsonTypeCategory tcategory; *************** add_json(Datum val, bool is_null, String *** 1712,1721 **** outfuncoid = InvalidOid; } else ! json_categorize_type(val_type, ! &tcategory, &outfuncoid); ! datum_to_json(val, is_null, result, tcategory, outfuncoid, key_scalar); } /* --- 1884,1892 ---- outfuncoid = InvalidOid; } else ! json_categorize_type(val_type, &tcategory, &outfuncoid); ! datum_to_json(val, is_null, out, tcategory, val_type, outfuncoid, key_scalar); } /* *************** extern Datum *** 1725,1737 **** array_to_json(PG_FUNCTION_ARGS) { Datum array = PG_GETARG_DATUM(0); ! StringInfo result; ! ! result = makeStringInfo(); ! array_to_json_internal(array, result, false); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } /* --- 1896,1907 ---- array_to_json(PG_FUNCTION_ARGS) { Datum array = PG_GETARG_DATUM(0); ! JsonOutContext out; ! json_out_init_context(&out, 0); ! array_to_json_internal(array, &out); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len)); } /* *************** array_to_json_pretty(PG_FUNCTION_ARGS) *** 1742,1754 **** { Datum array = PG_GETARG_DATUM(0); bool use_line_feeds = PG_GETARG_BOOL(1); ! StringInfo result; ! ! result = makeStringInfo(); ! array_to_json_internal(array, result, use_line_feeds); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } /* --- 1912,1923 ---- { Datum array = PG_GETARG_DATUM(0); bool use_line_feeds = PG_GETARG_BOOL(1); ! JsonOutContext out; ! json_out_init_context(&out, use_line_feeds ? JSON_OUT_USE_LINE_FEEDS : 0); ! array_to_json_internal(array, &out); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len)); } /* *************** extern Datum *** 1758,1770 **** row_to_json(PG_FUNCTION_ARGS) { Datum array = PG_GETARG_DATUM(0); ! StringInfo result; ! ! result = makeStringInfo(); ! composite_to_json(array, result, false); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } /* --- 1927,1938 ---- row_to_json(PG_FUNCTION_ARGS) { Datum array = PG_GETARG_DATUM(0); ! JsonOutContext out; ! json_out_init_context(&out, 0); ! composite_to_json(array, &out); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len)); } /* *************** row_to_json_pretty(PG_FUNCTION_ARGS) *** 1775,1787 **** { Datum array = PG_GETARG_DATUM(0); bool use_line_feeds = PG_GETARG_BOOL(1); ! StringInfo result; ! ! result = makeStringInfo(); ! composite_to_json(array, result, use_line_feeds); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } /* --- 1943,1954 ---- { Datum array = PG_GETARG_DATUM(0); bool use_line_feeds = PG_GETARG_BOOL(1); ! JsonOutContext out; ! json_out_init_context(&out, use_line_feeds ? JSON_OUT_USE_LINE_FEEDS : 0); ! composite_to_json(array, &out); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len)); } /* *************** to_json(PG_FUNCTION_ARGS) *** 1792,1800 **** { Datum val = PG_GETARG_DATUM(0); Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0); - StringInfo result; JsonTypeCategory tcategory; Oid outfuncoid; if (val_type == InvalidOid) ereport(ERROR, --- 1959,1969 ---- { Datum val = PG_GETARG_DATUM(0); Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0); JsonTypeCategory tcategory; Oid outfuncoid; + JsonOutContext out; + + json_out_init_context(&out, 0); if (val_type == InvalidOid) ereport(ERROR, *************** to_json(PG_FUNCTION_ARGS) *** 1804,1814 **** json_categorize_type(val_type, &tcategory, &outfuncoid); ! result = makeStringInfo(); ! ! datum_to_json(val, false, result, tcategory, outfuncoid, false); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } /* --- 1973,1981 ---- json_categorize_type(val_type, &tcategory, &outfuncoid); ! datum_to_json(val, false, &out, tcategory, val_type, outfuncoid, false); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len)); } /* *************** json_agg_transfn(PG_FUNCTION_ARGS) *** 1822,1831 **** Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 1); MemoryContext aggcontext, oldcontext; ! StringInfo state; Datum val; - JsonTypeCategory tcategory; - Oid outfuncoid; if (val_type == InvalidOid) ereport(ERROR, --- 1989,1996 ---- Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 1); MemoryContext aggcontext, oldcontext; ! JsonOutContext *out = NULL; Datum val; if (val_type == InvalidOid) ereport(ERROR, *************** json_agg_transfn(PG_FUNCTION_ARGS) *** 1847,1891 **** * use the right context to enlarge the object if necessary. */ oldcontext = MemoryContextSwitchTo(aggcontext); ! state = makeStringInfo(); MemoryContextSwitchTo(oldcontext); ! appendStringInfoChar(state, '['); } else ! { ! state = (StringInfo) PG_GETARG_POINTER(0); ! appendStringInfoString(state, ", "); ! } /* fast path for NULLs */ if (PG_ARGISNULL(1)) { ! datum_to_json((Datum) 0, true, state, JSONTYPE_NULL, InvalidOid, false); ! PG_RETURN_POINTER(state); } val = PG_GETARG_DATUM(1); ! /* XXX we do this every time?? */ ! json_categorize_type(val_type, ! &tcategory, &outfuncoid); ! ! /* add some whitespace if structured type and not first item */ ! if (!PG_ARGISNULL(0) && ! (tcategory == JSONTYPE_ARRAY || tcategory == JSONTYPE_COMPOSITE)) ! { ! appendStringInfoString(state, "\n "); ! } ! datum_to_json(val, false, state, tcategory, outfuncoid, false); /* * The transition type for array_agg() is declared to be "internal", which * is a pass-by-value type the same size as a pointer. So we can safely ! * pass the ArrayBuildState pointer through nodeAgg.c's machinations. */ ! PG_RETURN_POINTER(state); } /* --- 2012,2049 ---- * use the right context to enlarge the object if necessary. */ oldcontext = MemoryContextSwitchTo(aggcontext); ! out = palloc(sizeof(JsonOutContext)); ! json_out_init_context(out, JSON_OUT_USE_LINE_FEEDS); MemoryContextSwitchTo(oldcontext); ! out->array_start(out); } else ! out = (JsonOutContext *) PG_GETARG_POINTER(0); /* fast path for NULLs */ if (PG_ARGISNULL(1)) { ! datum_to_json((Datum) 0, true, out, ! JSONTYPE_NULL, InvalidOid, InvalidOid, false); ! PG_RETURN_POINTER(out); } val = PG_GETARG_DATUM(1); ! if (out->agg_outfuncoid == InvalidOid) ! json_categorize_type(val_type, ! &out->agg_tcategory, &out->agg_outfuncoid); ! datum_to_json(val, false, out, out->agg_tcategory, ! val_type, out->agg_outfuncoid, false); /* * The transition type for array_agg() is declared to be "internal", which * is a pass-by-value type the same size as a pointer. So we can safely ! * pass the ArrayBuildOut pointer through nodeAgg.c's machinations. */ ! PG_RETURN_POINTER(out); } /* *************** json_agg_transfn(PG_FUNCTION_ARGS) *** 1894,1912 **** Datum json_agg_finalfn(PG_FUNCTION_ARGS) { ! StringInfo state; /* cannot be called directly because of internal-type argument */ Assert(AggCheckCallContext(fcinfo, NULL)); ! state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0); /* NULL result for no rows in, as is standard with aggregates */ ! if (state == NULL) PG_RETURN_NULL(); ! /* Else return state with appropriate array terminator added */ ! PG_RETURN_TEXT_P(catenate_stringinfo_string(state, "]")); } /* --- 2052,2071 ---- Datum json_agg_finalfn(PG_FUNCTION_ARGS) { ! JsonOutContext *out; /* cannot be called directly because of internal-type argument */ Assert(AggCheckCallContext(fcinfo, NULL)); ! out = PG_ARGISNULL(0) ? NULL : (JsonOutContext *) PG_GETARG_POINTER(0); /* NULL result for no rows in, as is standard with aggregates */ ! if (out == NULL) PG_RETURN_NULL(); ! out->array_end(out); ! ! PG_RETURN_TEXT_P(cstring_to_text_with_len(out->result.data, out->result.len)); } /* *************** json_object_agg_transfn(PG_FUNCTION_ARGS *** 1920,1926 **** Oid val_type; MemoryContext aggcontext, oldcontext; ! StringInfo state; Datum arg; if (!AggCheckCallContext(fcinfo, &aggcontext)) --- 2079,2085 ---- Oid val_type; MemoryContext aggcontext, oldcontext; ! JsonOutContext *out = NULL; Datum arg; if (!AggCheckCallContext(fcinfo, &aggcontext)) *************** json_object_agg_transfn(PG_FUNCTION_ARGS *** 1938,1953 **** * use the right context to enlarge the object if necessary. */ oldcontext = MemoryContextSwitchTo(aggcontext); ! state = makeStringInfo(); MemoryContextSwitchTo(oldcontext); ! appendStringInfoString(state, "{ "); } else ! { ! state = (StringInfo) PG_GETARG_POINTER(0); ! appendStringInfoString(state, ", "); ! } /* * Note: since json_object_agg() is declared as taking type "any", the --- 2097,2110 ---- * use the right context to enlarge the object if necessary. */ oldcontext = MemoryContextSwitchTo(aggcontext); ! out = palloc(sizeof(JsonOutContext)); ! json_out_init_context(out, JSON_OUT_USE_SPACES); MemoryContextSwitchTo(oldcontext); ! out->object_start(out); } else ! out = (JsonOutContext *) PG_GETARG_POINTER(0); /* * Note: since json_object_agg() is declared as taking type "any", the *************** json_object_agg_transfn(PG_FUNCTION_ARGS *** 1970,1978 **** arg = PG_GETARG_DATUM(1); ! add_json(arg, false, state, val_type, true); ! ! appendStringInfoString(state, " : "); val_type = get_fn_expr_argtype(fcinfo->flinfo, 2); --- 2127,2133 ---- arg = PG_GETARG_DATUM(1); ! add_json(arg, false, out, val_type, true); val_type = get_fn_expr_argtype(fcinfo->flinfo, 2); *************** json_object_agg_transfn(PG_FUNCTION_ARGS *** 1986,1994 **** else arg = PG_GETARG_DATUM(2); ! add_json(arg, PG_ARGISNULL(2), state, val_type, false); ! PG_RETURN_POINTER(state); } /* --- 2141,2149 ---- else arg = PG_GETARG_DATUM(2); ! add_json(arg, PG_ARGISNULL(2), out, val_type, false); ! PG_RETURN_POINTER(out); } /* *************** json_object_agg_transfn(PG_FUNCTION_ARGS *** 1997,2035 **** Datum json_object_agg_finalfn(PG_FUNCTION_ARGS) { ! StringInfo state; /* cannot be called directly because of internal-type argument */ Assert(AggCheckCallContext(fcinfo, NULL)); ! state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0); /* NULL result for no rows in, as is standard with aggregates */ ! if (state == NULL) PG_RETURN_NULL(); ! /* Else return state with appropriate object terminator added */ ! PG_RETURN_TEXT_P(catenate_stringinfo_string(state, " }")); ! } ! ! /* ! * Helper function for aggregates: return given StringInfo's contents plus ! * specified trailing string, as a text datum. We need this because aggregate ! * final functions are not allowed to modify the aggregate state. ! */ ! static text * ! catenate_stringinfo_string(StringInfo buffer, const char *addon) ! { ! /* custom version of cstring_to_text_with_len */ ! int buflen = buffer->len; ! int addlen = strlen(addon); ! text *result = (text *) palloc(buflen + addlen + VARHDRSZ); ! ! SET_VARSIZE(result, buflen + addlen + VARHDRSZ); ! memcpy(VARDATA(result), buffer->data, buflen); ! memcpy(VARDATA(result) + buflen, addon, addlen); ! return result; } /* --- 2152,2171 ---- Datum json_object_agg_finalfn(PG_FUNCTION_ARGS) { ! JsonOutContext *out; /* cannot be called directly because of internal-type argument */ Assert(AggCheckCallContext(fcinfo, NULL)); ! out = PG_ARGISNULL(0) ? NULL : (JsonOutContext *) PG_GETARG_POINTER(0); /* NULL result for no rows in, as is standard with aggregates */ ! if (out == NULL) PG_RETURN_NULL(); ! out->object_end(out); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(out->result.data, out->result.len)); } /* *************** json_build_object(PG_FUNCTION_ARGS) *** 2041,2048 **** int nargs = PG_NARGS(); int i; Datum arg; ! const char *sep = ""; ! StringInfo result; Oid val_type; if (nargs % 2 != 0) --- 2177,2183 ---- int nargs = PG_NARGS(); int i; Datum arg; ! JsonOutContext out; Oid val_type; if (nargs % 2 != 0) *************** json_build_object(PG_FUNCTION_ARGS) *** 2051,2059 **** errmsg("argument list must have even number of elements"), errhint("The arguments of json_build_object() must consist of alternating keys and values."))); ! result = makeStringInfo(); ! ! appendStringInfoChar(result, '{'); for (i = 0; i < nargs; i += 2) { --- 2186,2193 ---- errmsg("argument list must have even number of elements"), errhint("The arguments of json_build_object() must consist of alternating keys and values."))); ! json_out_init_context(&out, JSON_OUT_USE_SPACES); ! out.object_start(&out); for (i = 0; i < nargs; i += 2) { *************** json_build_object(PG_FUNCTION_ARGS) *** 2064,2071 **** * here as type UNKNOWN, which fortunately does not matter to us, * since unknownout() works fine. */ - appendStringInfoString(result, sep); - sep = ", "; /* process key */ val_type = get_fn_expr_argtype(fcinfo->flinfo, i); --- 2198,2203 ---- *************** json_build_object(PG_FUNCTION_ARGS) *** 2084,2092 **** arg = PG_GETARG_DATUM(i); ! add_json(arg, false, result, val_type, true); ! ! appendStringInfoString(result, " : "); /* process value */ val_type = get_fn_expr_argtype(fcinfo->flinfo, i + 1); --- 2216,2222 ---- arg = PG_GETARG_DATUM(i); ! add_json(arg, false, &out, val_type, true); /* process value */ val_type = get_fn_expr_argtype(fcinfo->flinfo, i + 1); *************** json_build_object(PG_FUNCTION_ARGS) *** 2102,2113 **** else arg = PG_GETARG_DATUM(i + 1); ! add_json(arg, PG_ARGISNULL(i + 1), result, val_type, false); } ! appendStringInfoChar(result, '}'); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } /* --- 2232,2243 ---- else arg = PG_GETARG_DATUM(i + 1); ! add_json(arg, PG_ARGISNULL(i + 1), &out, val_type, false); } ! out.object_end(&out); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len)); } /* *************** json_build_array(PG_FUNCTION_ARGS) *** 2128,2140 **** int nargs = PG_NARGS(); int i; Datum arg; ! const char *sep = ""; ! StringInfo result; Oid val_type; ! result = makeStringInfo(); ! ! appendStringInfoChar(result, '['); for (i = 0; i < nargs; i++) { --- 2258,2268 ---- int nargs = PG_NARGS(); int i; Datum arg; ! JsonOutContext out; Oid val_type; ! json_out_init_context(&out, JSON_OUT_USE_SPACES); ! out.array_start(&out); for (i = 0; i < nargs; i++) { *************** json_build_array(PG_FUNCTION_ARGS) *** 2145,2153 **** * here as type UNKNOWN, which fortunately does not matter to us, * since unknownout() works fine. */ - appendStringInfoString(result, sep); - sep = ", "; - val_type = get_fn_expr_argtype(fcinfo->flinfo, i); if (val_type == InvalidOid) --- 2273,2278 ---- *************** json_build_array(PG_FUNCTION_ARGS) *** 2161,2172 **** else arg = PG_GETARG_DATUM(i); ! add_json(arg, PG_ARGISNULL(i), result, val_type, false); } ! appendStringInfoChar(result, ']'); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } /* --- 2286,2297 ---- else arg = PG_GETARG_DATUM(i); ! add_json(arg, PG_ARGISNULL(i), &out, val_type, false); } ! out.array_end(&out); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len)); } /* *************** json_object(PG_FUNCTION_ARGS) *** 2189,2202 **** { ArrayType *in_array = PG_GETARG_ARRAYTYPE_P(0); int ndims = ARR_NDIM(in_array); ! StringInfoData result; Datum *in_datums; bool *in_nulls; int in_count, count, i; - text *rval; - char *v; switch (ndims) { --- 2314,2325 ---- { ArrayType *in_array = PG_GETARG_ARRAYTYPE_P(0); int ndims = ARR_NDIM(in_array); ! JsonOutContext out; Datum *in_datums; bool *in_nulls; int in_count, count, i; switch (ndims) { *************** json_object(PG_FUNCTION_ARGS) *** 2230,2238 **** count = in_count / 2; ! initStringInfo(&result); ! ! appendStringInfoChar(&result, '{'); for (i = 0; i < count; ++i) { --- 2353,2360 ---- count = in_count / 2; ! json_out_init_context(&out, JSON_OUT_USE_SPACES); ! out.object_start(&out); for (i = 0; i < count; ++i) { *************** json_object(PG_FUNCTION_ARGS) *** 2241,2272 **** (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value not allowed for object key"))); ! v = TextDatumGetCString(in_datums[i * 2]); ! if (i > 0) ! appendStringInfoString(&result, ", "); ! escape_json(&result, v); ! appendStringInfoString(&result, " : "); ! pfree(v); if (in_nulls[i * 2 + 1]) ! appendStringInfoString(&result, "null"); else ! { ! v = TextDatumGetCString(in_datums[i * 2 + 1]); ! escape_json(&result, v); ! pfree(v); ! } } ! appendStringInfoChar(&result, '}'); pfree(in_datums); pfree(in_nulls); ! rval = cstring_to_text_with_len(result.data, result.len); ! pfree(result.data); ! ! PG_RETURN_TEXT_P(rval); ! } /* --- 2363,2385 ---- (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value not allowed for object key"))); ! out.key(&out, in_datums[i * 2], JSONTYPE_OTHER, ! TEXTOID, 47 /* textout */); ! if (in_nulls[i * 2 + 1]) ! out.value(&out, (Datum) 0, JSONTYPE_NULL, ! InvalidOid, InvalidOid); else ! out.value(&out, in_datums[i * 2 + 1], JSONTYPE_OTHER, ! TEXTOID, 47 /* textout */); } ! out.object_end(&out); pfree(in_datums); pfree(in_nulls); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len)); } /* *************** json_object_two_arg(PG_FUNCTION_ARGS) *** 2282,2288 **** ArrayType *val_array = PG_GETARG_ARRAYTYPE_P(1); int nkdims = ARR_NDIM(key_array); int nvdims = ARR_NDIM(val_array); ! StringInfoData result; Datum *key_datums, *val_datums; bool *key_nulls, --- 2395,2401 ---- ArrayType *val_array = PG_GETARG_ARRAYTYPE_P(1); int nkdims = ARR_NDIM(key_array); int nvdims = ARR_NDIM(val_array); ! JsonOutContext out; Datum *key_datums, *val_datums; bool *key_nulls, *************** json_object_two_arg(PG_FUNCTION_ARGS) *** 2290,2297 **** int key_count, val_count, i; - text *rval; - char *v; if (nkdims > 1 || nkdims != nvdims) ereport(ERROR, --- 2403,2408 ---- *************** json_object_two_arg(PG_FUNCTION_ARGS) *** 2314,2322 **** (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("mismatched array dimensions"))); ! initStringInfo(&result); ! ! appendStringInfoChar(&result, '{'); for (i = 0; i < key_count; ++i) { --- 2425,2432 ---- (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("mismatched array dimensions"))); ! json_out_init_context(&out, JSON_OUT_USE_SPACES); ! out.object_start(&out); for (i = 0; i < key_count; ++i) { *************** json_object_two_arg(PG_FUNCTION_ARGS) *** 2325,2357 **** (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value not allowed for object key"))); ! v = TextDatumGetCString(key_datums[i]); ! if (i > 0) ! appendStringInfoString(&result, ", "); ! escape_json(&result, v); ! appendStringInfoString(&result, " : "); ! pfree(v); if (val_nulls[i]) ! appendStringInfoString(&result, "null"); else ! { ! v = TextDatumGetCString(val_datums[i]); ! escape_json(&result, v); ! pfree(v); ! } } ! appendStringInfoChar(&result, '}'); pfree(key_datums); pfree(key_nulls); pfree(val_datums); pfree(val_nulls); ! rval = cstring_to_text_with_len(result.data, result.len); ! pfree(result.data); ! ! PG_RETURN_TEXT_P(rval); } --- 2435,2459 ---- (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value not allowed for object key"))); ! out.key(&out, key_datums[i], JSONTYPE_OTHER, ! TEXTOID, 47 /* textout */); ! if (val_nulls[i]) ! out.value(&out, (Datum) 0, JSONTYPE_NULL, ! InvalidOid, InvalidOid); else ! out.value(&out, val_datums[i], JSONTYPE_OTHER, ! TEXTOID, 47 /* textout */); } ! out.object_end(&out); pfree(key_datums); pfree(key_nulls); pfree(val_datums); pfree(val_nulls); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len)); } diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h new file mode 100644 index 26fcc5b..f485d37 *** a/src/include/commands/explain.h --- b/src/include/commands/explain.h *************** *** 15,20 **** --- 15,21 ---- #include "executor/executor.h" #include "lib/stringinfo.h" + #include "utils/jsonapi.h" typedef enum ExplainFormat { *************** typedef struct ExplainState *** 42,47 **** --- 43,49 ---- int indent; /* current indentation level */ List *grouping_stack; /* format-specific grouping state */ List *deparse_cxt; /* context list for deparsing expressions */ + JsonOutContext json_cxt; /* JSON output context */ } ExplainState; /* Hook for plugins to get control in ExplainOneQuery() */ diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h new file mode 100644 index 296d20a..f31332c *** a/src/include/utils/jsonapi.h --- b/src/include/utils/jsonapi.h *************** extern JsonLexContext *makeJsonLexContex *** 124,127 **** --- 124,202 ---- */ extern bool IsValidJsonNumber(const char *str, int len); + + /* + * Generalized structures for producing JSON output. + */ + typedef enum /* type categories for datum_to_json */ + { + JSONTYPE_NULL, /* null, so we didn't bother to identify */ + JSONTYPE_BOOL, /* boolean (built-in types only) */ + JSONTYPE_NUMERIC, /* numeric (ditto) */ + JSONTYPE_DATE, /* we use special formatting for datetimes */ + JSONTYPE_TIMESTAMP, + JSONTYPE_TIMESTAMPTZ, + JSONTYPE_JSON, /* JSON itself (and JSONB) */ + JSONTYPE_ARRAY, /* array */ + JSONTYPE_COMPOSITE, /* composite */ + JSONTYPE_CAST, /* something with an explicit cast to JSON */ + JSONTYPE_OTHER /* all else */ + } JsonTypeCategory; + + struct JsonOutContext; + + typedef void (*json_out_struct_action)(struct JsonOutContext *out); + typedef void (*json_out_key_value_action)(struct JsonOutContext *out, + Datum key, JsonTypeCategory tcategory, + Oid typoid, Oid outfuncoid); + + #define JSON_OUT_USE_LINE_FEEDS 1 + #define JSON_OUT_USE_SPACES 2 + + typedef struct JsonOutContext { + json_out_struct_action object_start; + json_out_struct_action object_end; + json_out_struct_action array_start; + json_out_struct_action array_end; + json_out_struct_action before_key; + json_out_key_value_action key; + json_out_struct_action after_key; + json_out_struct_action before_value; + json_out_key_value_action value; + json_out_struct_action after_value; + + StringInfoData result; + int flags; + int indent_spaces; + bool need_comma; + bool need_colon; + int depth; + /* these are used in json_agg to cache the type information */ + JsonTypeCategory agg_tcategory; + Oid agg_outfuncoid; + } JsonOutContext; + + extern void json_out_init_context(JsonOutContext *out, int flags); + extern void json_out_object_start(JsonOutContext *out); + extern void json_out_object_end(JsonOutContext *out); + extern void json_out_array_start(JsonOutContext *out); + extern void json_out_array_end(JsonOutContext *out); + extern void json_out_before_key(JsonOutContext *out); + extern void json_out_key(JsonOutContext *out, Datum val, + JsonTypeCategory tcategory, Oid typoid, Oid outfuncoid); + extern void json_out_after_key(JsonOutContext *out); + extern void json_out_before_value(JsonOutContext *out); + extern void json_out_value(JsonOutContext *out, Datum val, + JsonTypeCategory tcategory, Oid typoid, Oid outfuncoid); + extern void json_out_after_value(JsonOutContext *out); + + /* cstring helpers */ + extern void json_out_cstring_key(JsonOutContext *out, const char *str); + extern void json_out_cstring_value(JsonOutContext *out, const char *str); + + extern void composite_to_json(Datum composite, JsonOutContext *out); + extern void datum_to_json(Datum val, bool is_null, JsonOutContext *out, + JsonTypeCategory tcategory, Oid typoid, Oid outfuncoid, + bool key_scalar); + #endif /* JSONAPI_H */ diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out new file mode 100644 index eca9690..203f334 *** a/src/test/regress/expected/insert_conflict.out --- b/src/test/regress/expected/insert_conflict.out *************** explain (costs off, format json) insert *** 190,196 **** "Relation Name": "insertconflicttest", + "Alias": "insertconflicttest", + "Conflict Resolution": "UPDATE", + ! "Conflict Arbiter Indexes": ["key_index"], + "Conflict Filter": "(insertconflicttest.fruit <> 'Lime'::text)",+ "Plans": [ + { + --- 190,198 ---- "Relation Name": "insertconflicttest", + "Alias": "insertconflicttest", + "Conflict Resolution": "UPDATE", + ! "Conflict Arbiter Indexes": [ + ! "key_index" + ! ], + "Conflict Filter": "(insertconflicttest.fruit <> 'Lime'::text)",+ "Plans": [ + { + diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out new file mode 100644 index 3942c3b..fbc9b5b *** a/src/test/regress/expected/json.out --- b/src/test/regress/expected/json.out *************** SELECT json_agg(q) *** 457,476 **** ROW(y.*,ARRAY[4,5,6])] AS z FROM generate_series(1,2) x, generate_series(4,5) y) q; ! json_agg ! ----------------------------------------------------------------------- ! [{"b":"a1","c":4,"z":[{"f1":1,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]}, + ! {"b":"a1","c":5,"z":[{"f1":1,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]}, + ! {"b":"a2","c":4,"z":[{"f1":2,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]}, + {"b":"a2","c":5,"z":[{"f1":2,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]}] (1 row) SELECT json_agg(q) FROM rows q; ! json_agg ! ----------------------- ! [{"x":1,"y":"txt1"}, + ! {"x":2,"y":"txt2"}, + {"x":3,"y":"txt3"}] (1 row) --- 457,476 ---- ROW(y.*,ARRAY[4,5,6])] AS z FROM generate_series(1,2) x, generate_series(4,5) y) q; ! json_agg ! ---------------------------------------------------------------------- ! [{"b":"a1","c":4,"z":[{"f1":1,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]},+ ! {"b":"a1","c":5,"z":[{"f1":1,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]},+ ! {"b":"a2","c":4,"z":[{"f1":2,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]},+ {"b":"a2","c":5,"z":[{"f1":2,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]}] (1 row) SELECT json_agg(q) FROM rows q; ! json_agg ! ---------------------- ! [{"x":1,"y":"txt1"},+ ! {"x":2,"y":"txt2"},+ {"x":3,"y":"txt3"}] (1 row) *************** SELECT json_build_array('a',1,'b',1.2,'c *** 1505,1522 **** (1 row) SELECT json_build_object('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}'); ! json_build_object ! ---------------------------------------------------------------------------- ! {"a" : 1, "b" : 1.2, "c" : true, "d" : null, "e" : {"x": 3, "y": [1,2,3]}} (1 row) SELECT json_build_object( 'a', json_build_object('b',false,'c',99), 'd', json_build_object('e',array[9,8,7]::int[], 'f', (select row_to_json(r) from ( select relkind, oid::regclass as name from pg_class where relname = 'pg_class') r))); ! json_build_object ! ------------------------------------------------------------------------------------------------- ! {"a" : {"b" : false, "c" : 99}, "d" : {"e" : [9,8,7], "f" : {"relkind":"r","name":"pg_class"}}} (1 row) -- empty objects/arrays --- 1505,1522 ---- (1 row) SELECT json_build_object('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}'); ! json_build_object ! ----------------------------------------------------------------------- ! {"a": 1, "b": 1.2, "c": true, "d": null, "e": {"x": 3, "y": [1,2,3]}} (1 row) SELECT json_build_object( 'a', json_build_object('b',false,'c',99), 'd', json_build_object('e',array[9,8,7]::int[], 'f', (select row_to_json(r) from ( select relkind, oid::regclass as name from pg_class where relname = 'pg_class') r))); ! json_build_object ! ------------------------------------------------------------------------------------------- ! {"a": {"b": false, "c": 99}, "d": {"e": [9,8,7], "f": {"relkind":"r","name":"pg_class"}}} (1 row) -- empty objects/arrays *************** SELECT json_build_object(); *** 1536,1542 **** SELECT json_build_object(1,2); json_build_object ------------------- ! {"1" : 2} (1 row) -- keys must be scalar and not null --- 1536,1542 ---- SELECT json_build_object(1,2); json_build_object ------------------- ! {"1": 2} (1 row) -- keys must be scalar and not null *************** INSERT INTO foo VALUES (847002,'t16','GE *** 1555,1578 **** INSERT INTO foo VALUES (847003,'sub-alpha','GESS90'); SELECT json_build_object('turbines',json_object_agg(serial_num,json_build_object('name',name,'type',type))) FROM foo; ! json_build_object ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ! {"turbines" : { "847001" : {"name" : "t15", "type" : "GE1043"}, "847002" : {"name" : "t16", "type" : "GE1043"}, "847003" : {"name" : "sub-alpha", "type" : "GESS90"} }} (1 row) -- json_object -- one dimension SELECT json_object('{a,1,b,2,3,NULL,"d e f","a b c"}'); ! json_object ! ------------------------------------------------------- ! {"a" : "1", "b" : "2", "3" : null, "d e f" : "a b c"} (1 row) -- same but with two dimensions SELECT json_object('{{a,1},{b,2},{3,NULL},{"d e f","a b c"}}'); ! json_object ! ------------------------------------------------------- ! {"a" : "1", "b" : "2", "3" : null, "d e f" : "a b c"} (1 row) -- odd number error --- 1555,1578 ---- INSERT INTO foo VALUES (847003,'sub-alpha','GESS90'); SELECT json_build_object('turbines',json_object_agg(serial_num,json_build_object('name',name,'type',type))) FROM foo; ! json_build_object ! ------------------------------------------------------------------------------------------------------------------------------------------------------------- ! {"turbines": {"847001": {"name": "t15", "type": "GE1043"}, "847002": {"name": "t16", "type": "GE1043"}, "847003": {"name": "sub-alpha", "type": "GESS90"}}} (1 row) -- json_object -- one dimension SELECT json_object('{a,1,b,2,3,NULL,"d e f","a b c"}'); ! json_object ! --------------------------------------------------- ! {"a": "1", "b": "2", "3": null, "d e f": "a b c"} (1 row) -- same but with two dimensions SELECT json_object('{{a,1},{b,2},{3,NULL},{"d e f","a b c"}}'); ! json_object ! --------------------------------------------------- ! {"a": "1", "b": "2", "3": null, "d e f": "a b c"} (1 row) -- odd number error *************** SELECT json_object('{{{a,b},{c,d}},{{b,c *** 1589,1597 **** ERROR: wrong number of array subscripts --two argument form of json_object select json_object('{a,b,c,"d e f"}','{1,2,3,"a b c"}'); ! json_object ! ------------------------------------------------------ ! {"a" : "1", "b" : "2", "c" : "3", "d e f" : "a b c"} (1 row) -- too many dimensions --- 1589,1597 ---- ERROR: wrong number of array subscripts --two argument form of json_object select json_object('{a,b,c,"d e f"}','{1,2,3,"a b c"}'); ! json_object ! -------------------------------------------------- ! {"a": "1", "b": "2", "c": "3", "d e f": "a b c"} (1 row) -- too many dimensions *************** select json_object('{a,b,NULL,"d e f"}', *** 1607,1615 **** ERROR: null value not allowed for object key -- empty key is allowed select json_object('{a,b,"","d e f"}','{1,2,3,"a b c"}'); ! json_object ! ----------------------------------------------------- ! {"a" : "1", "b" : "2", "" : "3", "d e f" : "a b c"} (1 row) -- json_to_record and json_to_recordset --- 1607,1615 ---- ERROR: null value not allowed for object key -- empty key is allowed select json_object('{a,b,"","d e f"}','{1,2,3,"a b c"}'); ! json_object ! ------------------------------------------------- ! {"a": "1", "b": "2", "": "3", "d e f": "a b c"} (1 row) -- json_to_record and json_to_recordset
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers