diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 70e21b9..483abb6 100644 *** a/src/backend/utils/adt/Makefile --- b/src/backend/utils/adt/Makefile *************** endif *** 18,24 **** OBJS = acl.o arrayfuncs.o array_userfuncs.o arrayutils.o bool.o \ cash.o char.o date.o datetime.o datum.o domains.o \ enum.o float.o format_type.o \ ! geo_ops.o geo_selfuncs.o int.o int8.o like.o lockfuncs.o \ misc.o nabstime.o name.o numeric.o numutils.o \ oid.o oracle_compat.o pseudotypes.o rowtypes.o \ regexp.o regproc.o ruleutils.o selfuncs.o \ --- 18,25 ---- OBJS = acl.o arrayfuncs.o array_userfuncs.o arrayutils.o bool.o \ cash.o char.o date.o datetime.o datum.o domains.o \ enum.o float.o format_type.o \ ! geo_ops.o geo_selfuncs.o json.o json_parser.o json_scanner.o \ ! int.o int8.o like.o lockfuncs.o \ misc.o nabstime.o name.o numeric.o numutils.o \ oid.o oracle_compat.o pseudotypes.o rowtypes.o \ regexp.o regproc.o ruleutils.o selfuncs.o \ *************** OBJS = acl.o arrayfuncs.o array_userfunc *** 33,36 **** --- 34,62 ---- like.o: like.c like_match.c + json_parser.o: json_scanner.c + + # Latest flex causes warnings in this file. + #ifeq ($(GCC),yes) + #json_parser.o: CFLAGS += -Wno-error + #endif + + json_parser.h: json_parser.c ; + + json_parser.c: json_parser.y + ifdef BISON + $(BISON) -d $(BISONFLAGS) -o $@ $< + else + @$(missing) bison $< $@ + endif + + json_scanner.c: json_scanner.l + ifdef FLEX + $(FLEX) $(FLEXFLAGS) -o'$@' $< + else + @$(missing) flex $< $@ + endif + + json_parser.o keywords.o parser.o: json_parser.h + include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index ...13e5391 . *** a/src/backend/utils/adt/json.c --- b/src/backend/utils/adt/json.c *************** *** 0 **** --- 1,307 ---- + /*------------------------------------------------------------------------- + * + * json.c + * JSON data type support. + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL$ + * + *------------------------------------------------------------------------- + */ + + #include "postgres.h" + + #include "libpq/pqformat.h" + #include "utils/builtins.h" + #include "utils/json.h" + + #include "json_parser.h" + #include "json_scanner.h" + + static Json *json_parse(const char *str, int len, bool validateOnly); + static void json_free(Json *node); + static void append_json(StringInfo buf, const Json *node, int indent); + static void append_indent(StringInfo buf, int indent); + + extern int json_yyparse(yyscan_t scanner); + extern void json_yyerror(yyscan_t scanner, const char *error); + extern void *json_yyalloc(size_t bytes, void *yyscanner); + extern void *json_yyrealloc(void *ptr, size_t bytes, void *yyscanner); + extern void json_yyfree(void *ptr, void *yyscanner); + + /* indent with 4 spaces */ + #define JSON_INDENT_STRING " " + + + Datum + json_in(PG_FUNCTION_ARGS) + { + char *s = PG_GETARG_CSTRING(0); + jsontype *json; + + json = (jsontype *) cstring_to_text(s); + + /* + * Parse the data to check if it is a JSON data. Assume that + * ERROR occurred if parsing failed. + */ + json_parse(VARDATA_ANY(json), VARSIZE_ANY_EXHDR(json), true); + + PG_RETURN_JSON_P(json); + } + + Datum + json_out(PG_FUNCTION_ARGS) + { + /* text and jsontype are binary-compatible */ + return textout(fcinfo); + } + + Datum + json_recv(PG_FUNCTION_ARGS) + { + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + jsontype *result; + char *str; + int nbytes; + + str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + json_parse(str, nbytes, true); + + result = (jsontype *) cstring_to_text_with_len(str, nbytes); + pfree(str); + PG_RETURN_JSON_P(result); + } + + Datum + json_send(PG_FUNCTION_ARGS) + { + /* text and jsontype are binary-compatible */ + return textsend(fcinfo); + } + + Datum + text_to_json(PG_FUNCTION_ARGS) + { + text *t = PG_GETARG_TEXT_PP(0); + + json_parse(VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t), true); + + PG_RETURN_JSON_P((jsontype *) t); + } + + Datum + json_to_text(PG_FUNCTION_ARGS) + { + /* text and jsontype are binary-compatible */ + PG_RETURN_DATUM(PG_GETARG_DATUM(0)); + } + + Datum + json_pretty(PG_FUNCTION_ARGS) + { + jsontype *json = PG_GETARG_JSON_PP(0); + Json *node; + StringInfoData buf; + + node = json_parse(VARDATA_ANY(json), VARSIZE_ANY_EXHDR(json), false); + initStringInfo(&buf); + append_json(&buf, node, 0); + json_free(node); + + return CStringGetTextDatum(buf.data); + } + + /* + * json_parse -- parse and validate the JSON text. + * + * Only validates the input when validateOnly is true, or returns JSON + * tree on false. + */ + static Json * + json_parse(const char *str, int len, bool validateOnly) + { + yyscan_t scanner; + char *scanbuf; + JsonParser parser; + + scanbuf = (char *) palloc(len + 2); + memcpy(scanbuf, str, len); + scanbuf[len] = scanbuf[len + 1] = '\0'; + + /* initialize parser and scanner */ + memset(&parser, 0, sizeof(parser)); + parser.validateOnly = validateOnly; + if (!validateOnly) + initStringInfo(&parser.buf); + + json_yylex_init(&scanner); + json_yyset_extra(&parser, scanner); + json_yy_scan_buffer(scanbuf, len + 2, scanner); + + /* parse the JSON text */ + json_yyparse(scanner); + + /* cleanup */ + json_yylex_destroy(scanner); + pfree(scanbuf); + if (parser.buf.data) + pfree(parser.buf.data); + + return parser.json; + } + + /* + * json_free -- free JSON node tree recursively + */ + static void + json_free(Json *node) + { + ListCell *cell; + + switch (node->type) + { + case JSON_NULL: + case JSON_BOOL: + break; + case JSON_NUMBER: + pfree(node->value.number); + break; + case JSON_STRING: + pfree(node->value.string); + break; + case JSON_ARRAY: + foreach (cell, node->value.array) + { + Json *item = (Json *) lfirst(cell); + + json_free(item); + } + list_free(node->value.array); + break; + case JSON_OBJECT: + foreach (cell, node->value.object) + { + JsonAttr *attr = (JsonAttr *) lfirst(cell); + + pfree(attr->key); + json_free(attr->value); + pfree(attr); + } + list_free(node->value.object); + break; + default: + elog(ERROR, "unexpected json type: %d", node->type); + } + + pfree(node); + } + + /* + * append_json -- append JSON node tree to string with indentation + */ + static void + append_json(StringInfo buf, const Json *node, int indent) + { + ListCell *cell; + bool sep; + + switch (node->type) + { + case JSON_NULL: + appendStringInfoString(buf, "null"); + break; + case JSON_BOOL: + appendStringInfoString(buf, node->value.boolean ? "true" : "false"); + break; + case JSON_NUMBER: + appendStringInfoString(buf, node->value.number); + break; + case JSON_STRING: + appendStringInfoString(buf, node->value.string); + break; + case JSON_ARRAY: + appendStringInfoString(buf, "[\n"); + sep = false; + foreach (cell, node->value.array) + { + const Json *value = (const Json *) lfirst(cell); + + if (sep) + appendStringInfoString(buf, ",\n"); + else + sep = true; + append_indent(buf, indent + 1); + append_json(buf, value, indent + 1); + } + if (sep) + appendStringInfoChar(buf, '\n'); + append_indent(buf, indent); + appendStringInfoChar(buf, ']'); + break; + case JSON_OBJECT: + appendStringInfoString(buf, "{\n"); + sep = false; + foreach (cell, node->value.object) + { + const JsonAttr *attr = (const JsonAttr *) lfirst(cell); + const char *key = attr->key; + const Json *value = attr->value; + + if (sep) + appendStringInfoString(buf, ",\n"); + else + sep = true; + append_indent(buf, indent + 1); + appendStringInfo(buf, "%s: ", key); + append_json(buf, value, indent + 1); + } + if (sep) + appendStringInfoChar(buf, '\n'); + append_indent(buf, indent); + appendStringInfoChar(buf, '}'); + break; + default: + elog(ERROR, "unexpected json type: %d", node->type); + } + } + + static void + append_indent(StringInfo buf, int indent) + { + int i; + for (i = 0; i < indent; i++) + appendStringInfoString(buf, JSON_INDENT_STRING); + } + + void + json_yyerror(yyscan_t scanner, const char *error) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error in json: %s", error))); + } + + void * + json_yyalloc(size_t bytes, void *yyscanner) + { + return palloc(bytes); + } + + void * + json_yyrealloc(void *ptr, size_t bytes, void *yyscanner) + { + if (ptr != NULL) + return repalloc(ptr, bytes); + else + return palloc(bytes); + } + + void + json_yyfree(void *ptr, void *yyscanner) + { + if (ptr != NULL) + pfree(ptr); + } diff --git a/src/backend/utils/adt/json_parser.y b/src/backend/utils/adt/json_parser.y index ...fb769e2 . *** a/src/backend/utils/adt/json_parser.y --- b/src/backend/utils/adt/json_parser.y *************** *** 0 **** --- 1,221 ---- + %{ + /*------------------------------------------------------------------------- + * + * json_parser.y + * Parser for JSON data types. + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL$ + * + *------------------------------------------------------------------------- + */ + + #include "postgres.h" + #include "nodes/pg_list.h" + #include "utils/json.h" + + static Json *makeJsonNode(JsonType type); + static Json *makeJsonNull(JsonParser *parser); + static Json *makeJsonBool(JsonParser *parser, bool value); + static Json *makeJsonNumber(JsonParser *parser, char *value); + static Json *makeJsonString(JsonParser *parser, char *value); + static Json *makeJsonArray(JsonParser *parser, List *items); + static Json *makeJsonObject(JsonParser *parser, List *attrs); + static JsonAttr *makeJsonAttr(JsonParser *parser, char *key, Json *value); + static List *makeList(JsonParser *parser, void *elem); + static List *lconsList(JsonParser *parser, void *elem, List *list); + + #include "json_parser.h" + #include "json_scanner.h" + + #define getParser() ((JsonParser *) json_yyget_extra(scanner)) + + extern void json_yyerror(yyscan_t, const char *); + %} + + %defines + %expect 0 + %name-prefix="json_yy" + %pure-parser + %parse-param {yyscan_t scanner} + %lex-param {yyscan_t scanner} + + %union { + bool boolean; + char *number; + char *string; + Json *node; + JsonAttr *attr; + List *list; + } + + %token T_JSON_STRING + %token T_JSON_NUMBER + %token T_JSON_TRUE T_JSON_FALSE + %token T_JSON_NULL + %token T_JSON_INVALID + + %type number + %type string + %type json value array object + %type items attrs + %type attr + + %% + json: + value { getParser()->json = $1; } + ; + + array: + '[' ']' { $$ = makeJsonArray(getParser(), NIL); } + | '[' items ']' { $$ = makeJsonArray(getParser(), $2);; } + ; + + items: + value { $$ = makeList(getParser(), $1); } + | value ',' items { $$ = lconsList(getParser(), $1, $3); } + ; + + object: + '{' '}' { $$ = makeJsonObject(getParser(), NIL); } + | '{' attrs '}' { $$ = makeJsonObject(getParser(), $2); } + ; + + attrs: + attr { $$ = makeList(getParser(), $1); } + | attr ',' attrs { $$ = lconsList(getParser(), $1, $3); } + ; + + attr: + string ':' value { $$ = makeJsonAttr(getParser(), $1, $3); } + ; + + value: + T_JSON_NULL { $$ = makeJsonNull(getParser()); } + | T_JSON_TRUE { $$ = makeJsonBool(getParser(), true); } + | T_JSON_FALSE { $$ = makeJsonBool(getParser(), false); } + | number { $$ = makeJsonNumber(getParser(), $1); } + | string { $$ = makeJsonString(getParser(), $1); } + | array { $$ = $1; } + | object { $$ = $1; } + ; + + number: T_JSON_NUMBER { $$ = $1; }; + string: T_JSON_STRING { $$ = $1; }; + + %% + static Json * + makeJsonNode(JsonType type) + { + Json *node = (Json *) palloc(sizeof(Json)); + node->type = type; + return node; + } + + static Json * + makeJsonNull(JsonParser *parser) + { + if (parser->validateOnly) + return NULL; + else + return makeJsonNode(JSON_NULL); + } + + static Json * + makeJsonBool(JsonParser *parser, bool value) + { + if (parser->validateOnly) + return NULL; + else + { + Json *node = makeJsonNode(JSON_BOOL); + node->value.boolean = value; + return node; + } + } + + static Json * + makeJsonNumber(JsonParser *parser, char *value) + { + if (parser->validateOnly) + return NULL; + else + { + Json *node = makeJsonNode(JSON_NUMBER); + node->value.number = value; + return node; + } + } + + static Json * + makeJsonString(JsonParser *parser, char *value) + { + if (parser->validateOnly) + return NULL; + else + { + Json *node = makeJsonNode(JSON_STRING); + node->value.string = value; + return node; + } + } + + static Json * + makeJsonArray(JsonParser *parser, List *items) + { + if (parser->validateOnly) + return NULL; + else + { + Json *node = makeJsonNode(JSON_ARRAY); + node->value.array = items; + return node; + } + } + + static Json * + makeJsonObject(JsonParser *parser, List *attrs) + { + if (parser->validateOnly) + return NULL; + else + { + Json *node = makeJsonNode(JSON_OBJECT); + node->value.object = attrs; + return node; + } + } + + static JsonAttr * + makeJsonAttr(JsonParser *parser, char *key, Json *value) + { + if (parser->validateOnly) + return NULL; + else + { + JsonAttr *attr = (JsonAttr *) palloc(sizeof(JsonAttr)); + attr->key = key; + attr->value = value; + return attr; + } + } + + static List * + makeList(JsonParser *parser, void *elem) + { + if (parser->validateOnly) + return NIL; + else + return list_make1(elem); + } + + static List * + lconsList(JsonParser *parser, void *elem, List *list) + { + if (parser->validateOnly) + return NIL; + else + return lcons(elem, list); + } diff --git a/src/backend/utils/adt/json_scanner.l b/src/backend/utils/adt/json_scanner.l index ...f4ed64f . *** a/src/backend/utils/adt/json_scanner.l --- b/src/backend/utils/adt/json_scanner.l *************** *** 0 **** --- 1,88 ---- + %{ + /*------------------------------------------------------------------------- + * + * json_snanner.l + * Lexical scanner for JSON data types. + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL$ + * + *------------------------------------------------------------------------- + */ + + #include "postgres.h" + #include "nodes/pg_list.h" + #include "utils/json.h" + #include "json_parser.h" + + #define getParser() ((JsonParser *) json_yyget_extra(yyscanner)) + %} + + %option reentrant + %option bison-bridge + %option 8bit + %option never-interactive + %option nodefault + %option noinput + %option nounput + %option noyywrap + %option noyyalloc + %option noyyrealloc + %option noyyfree + %option warn + %option prefix="json_yy" + %option outfile="json_scanner.c" header-file="json_scanner.h" + + %x str + + %% + /* Whitespace */ + [ \t\r\n] + + /* Symbol */ + [\[\]\{\}:,] return yytext[0]; + + /* Null */ + null { return T_JSON_NULL; } + + /* Boolean */ + true { return T_JSON_TRUE; } + false { return T_JSON_FALSE; } + + /* Number */ + 0|-?([1-9][0-9]*(\.[0-9]+)?|0\.[0-9]+)([Ee][+-]?[0-9]+)? { + JsonParser *parser = getParser(); + if (!parser->validateOnly) + yylval->number = pstrdup(yytext); + return T_JSON_NUMBER; + } + + /* String */ + \" { + JsonParser *parser = getParser(); + BEGIN str; + if (!parser->validateOnly) + appendStringInfoString(&parser->buf, yytext); + } + ([^\"\\[:cntrl:]]+|\\u[0-9A-Fa-f]{4}|\\[\"\\/bfnrt]) { + JsonParser *parser = getParser(); + if (!parser->validateOnly) + appendStringInfoString(&parser->buf, yytext); + } + \" { + JsonParser *parser = getParser(); + if (!parser->validateOnly) + { + appendStringInfoString(&parser->buf, yytext); + yylval->string = pstrdup(parser->buf.data); + resetStringInfo(&parser->buf); + } + BEGIN(INITIAL); + return T_JSON_STRING; + } + .|\n { return T_JSON_INVALID; } /* unterminated string */ + + /* Invalid */ + . return T_JSON_INVALID; diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h index f53a39a..569dcaa 100644 *** a/src/include/catalog/pg_cast.h --- b/src/include/catalog/pg_cast.h *************** DATA(insert ( 869 25 730 a f )); *** 320,325 **** --- 320,327 ---- DATA(insert ( 16 25 2971 a f )); DATA(insert ( 142 25 0 a b )); DATA(insert ( 25 142 2896 e f )); + DATA(insert ( 321 25 0 a b )); + DATA(insert ( 25 321 3830 e f )); /* * Cross-category casts to and from VARCHAR *************** DATA(insert ( 869 1043 730 a f )); *** 331,336 **** --- 333,340 ---- DATA(insert ( 16 1043 2971 a f )); DATA(insert ( 142 1043 0 a b )); DATA(insert ( 1043 142 2896 e f )); + DATA(insert ( 321 1043 0 a b )); + DATA(insert ( 1043 321 3830 e f )); /* * Cross-category casts to and from BPCHAR *************** DATA(insert ( 869 1042 730 a f )); *** 342,347 **** --- 346,353 ---- DATA(insert ( 16 1042 2971 a f )); DATA(insert ( 142 1042 0 a b )); DATA(insert ( 1042 142 2896 e f )); + DATA(insert ( 321 1042 0 a b )); + DATA(insert ( 1042 321 3830 e f )); /* * Length-coercion functions diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 427f5ea..b3d3da5 100644 *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** DESCR("determine if a string is well for *** 4457,4462 **** --- 4457,4478 ---- DATA(insert OID = 3053 ( xml_is_well_formed_content PGNSP PGUID 12 1 0 0 f f f t f i 1 0 16 "25" _null_ _null_ _null_ _null_ xml_is_well_formed_content _null_ _null_ _null_ )); DESCR("determine if a string is well formed XML content"); + /* JSON support */ + DATA(insert OID = 3826 ( json_in PGNSP PGUID 12 1 0 0 f f f t f i 1 0 321 "2275" _null_ _null_ _null_ _null_ json_in _null_ _null_ _null_ )); + DESCR("I/O"); + DATA(insert OID = 3827 ( json_out PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2275 "321" _null_ _null_ _null_ _null_ json_out _null_ _null_ _null_ )); + DESCR("I/O"); + DATA(insert OID = 3828 ( json_recv PGNSP PGUID 12 1 0 0 f f f t f i 1 0 321 "2281" _null_ _null_ _null_ _null_ json_recv _null_ _null_ _null_ )); + DESCR("I/O"); + DATA(insert OID = 3829 ( json_send PGNSP PGUID 12 1 0 0 f f f t f i 1 0 17 "321" _null_ _null_ _null_ _null_ json_send _null_ _null_ _null_ )); + DESCR("I/O"); + DATA(insert OID = 3830 ( json PGNSP PGUID 12 1 0 0 f f f t f s 1 0 321 "25" _null_ _null_ _null_ _null_ text_to_json _null_ _null_ _null_ )); + DESCR("perform a non-validating parse of a character string to produce a JSON value"); + DATA(insert OID = 3831 ( text PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "321" _null_ _null_ _null_ _null_ json_to_text _null_ _null_ _null_ )); + DESCR("serialize a JSON value to a character string"); + DATA(insert OID = 3832 ( json_pretty PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "321" _null_ _null_ _null_ _null_ json_pretty _null_ _null_ _null_ )); + DESCR("convert a JSON to a human readable text"); + /* uuid */ DATA(insert OID = 2952 ( uuid_in PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2950 "2275" _null_ _null_ _null_ _null_ uuid_in _null_ _null_ _null_ )); DESCR("I/O"); diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 05fc974..6b110bf 100644 *** a/src/include/catalog/pg_type.h --- b/src/include/catalog/pg_type.h *************** DESCR("storage manager"); *** 354,359 **** --- 354,364 ---- /* OIDS 300 - 399 */ + DATA(insert OID = 321 ( json PGNSP PGUID -1 f b U f t \054 0 0 322 json_in json_out json_recv json_send - - - i x f 0 -1 0 _null_ _null_ )); + DESCR("JSON content"); + #define JSONOID 321 + DATA(insert OID = 322 ( _json PGNSP PGUID -1 f b A f t \054 0 321 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); + /* OIDS 400 - 499 */ /* OIDS 500 - 599 */ diff --git a/src/include/utils/json.h b/src/include/utils/json.h index ...952d1bc . *** a/src/include/utils/json.h --- b/src/include/utils/json.h *************** *** 0 **** --- 1,75 ---- + /*------------------------------------------------------------------------- + * + * json.h + * Declarations for JSON data type support. + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL$ + * + *------------------------------------------------------------------------- + */ + + #ifndef JSON_H + #define JSON_H + + #include "fmgr.h" + #include "nodes/execnodes.h" + #include "nodes/primnodes.h" + + typedef struct varlena jsontype; + + #define DatumGetJsonP(X) ((jsontype *) PG_DETOAST_DATUM(X)) + #define DatumGetJsonPP(X) ((jsontype *) PG_DETOAST_DATUM_PACKED(X)) + #define JsonPGetDatum(X) PointerGetDatum(X) + + #define PG_GETARG_JSON_P(n) DatumGetJsonP(PG_GETARG_DATUM(n)) + #define PG_GETARG_JSON_PP(n) DatumGetJsonPP(PG_GETARG_DATUM(n)) + #define PG_RETURN_JSON_P(x) PG_RETURN_POINTER(x) + + extern Datum json_in(PG_FUNCTION_ARGS); + extern Datum json_out(PG_FUNCTION_ARGS); + extern Datum json_recv(PG_FUNCTION_ARGS); + extern Datum json_send(PG_FUNCTION_ARGS); + extern Datum text_to_json(PG_FUNCTION_ARGS); + extern Datum json_to_text(PG_FUNCTION_ARGS); + extern Datum json_pretty(PG_FUNCTION_ARGS); + + typedef enum JsonType + { + JSON_NULL, + JSON_BOOL, + JSON_NUMBER, + JSON_STRING, + JSON_ARRAY, + JSON_OBJECT + } JsonType; + + typedef struct Json + { + JsonType type; + union + { + bool boolean; + char *number; + char *string; + List *array; /* a list of Json */ + List *object; /* a list of JsonAttr */ + } value; + } Json; + + typedef struct JsonAttr + { + char *key; + Json *value; + } JsonAttr; + + typedef struct JsonParser + { + Json *json; + StringInfoData buf; + bool validateOnly; + } JsonParser; + + #endif /* JSON_H */ diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index ...1237714 . *** a/src/test/regress/expected/json.out --- b/src/test/regress/expected/json.out *************** *** 0 **** --- 1,39 ---- + CREATE TABLE jsontest ( + id int, + data json + ); + INSERT INTO jsontest VALUES (1, 'null'); + INSERT INTO jsontest VALUES (2, '{"booleans":[true,false],"null":null}'); + INSERT INTO jsontest VALUES (3, E'[\n123.456,\n"string",\n{"name":"object"}\n]'); + INSERT INTO jsontest VALUES (101, 'wrong'); + ERROR: syntax error in json: syntax error + LINE 1: INSERT INTO jsontest VALUES (101, 'wrong'); + ^ + INSERT INTO jsontest VALUES (101, '[wrong'); + ERROR: syntax error in json: syntax error + LINE 1: INSERT INTO jsontest VALUES (101, '[wrong'); + ^ + INSERT INTO jsontest VALUES (101, '{"wrong":123}}'); + ERROR: syntax error in json: syntax error + LINE 1: INSERT INTO jsontest VALUES (101, '{"wrong":123}}'); + ^ + SELECT *, json_pretty(data) FROM jsontest; + id | data | json_pretty + ----+---------------------------------------+-------------------------- + 1 | null | null + 2 | {"booleans":[true,false],"null":null} | { + + | | "booleans": [ + + | | true, + + | | false + + | | ], + + | | "null": null + + | | } + 3 | [ +| [ + + | 123.456, +| 123.456, + + | "string", +| "string", + + | {"name":"object"} +| { + + | ] | "name": "object"+ + | | } + + | | ] + (3 rows) + diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 4703d49..ca6ff28 100644 *** a/src/test/regress/expected/opr_sanity.out --- b/src/test/regress/expected/opr_sanity.out *************** WHERE c.castfunc = p.oid AND *** 402,407 **** --- 402,409 ---- -- texttoxml(), which does an XML syntax check. -- As of 9.1, this finds the cast from pg_node_tree to text, which we -- intentionally do not provide a reverse pathway for. + -- Also, this finds the casts from json to text, varchar, and bpchar, + -- because of the same reason as xml. SELECT castsource::regtype, casttarget::regtype, castfunc, castcontext FROM pg_cast c WHERE c.castmethod = 'b' AND *************** WHERE c.castmethod = 'b' AND *** 416,424 **** pg_node_tree | text | 0 | i cidr | inet | 0 | i xml | text | 0 | a xml | character varying | 0 | a xml | character | 0 | a ! (7 rows) -- **************** pg_operator **************** -- Look for illegal values in pg_operator fields. --- 418,429 ---- pg_node_tree | text | 0 | i cidr | inet | 0 | i xml | text | 0 | a + json | text | 0 | a xml | character varying | 0 | a + json | character varying | 0 | a xml | character | 0 | a ! json | character | 0 | a ! (10 rows) -- **************** pg_operator **************** -- Look for illegal values in pg_operator fields. diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 191d1fe..e056bd2 100644 *** a/src/test/regress/parallel_schedule --- b/src/test/regress/parallel_schedule *************** test: select_views portals_p2 foreign_ke *** 91,97 **** # NB: temp.sql does a reconnect which transiently uses 2 connections, # so keep this parallel group to at most 19 tests # ---------- ! test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml # run stats by itself because its delay may be insufficient under heavy load test: stats --- 91,97 ---- # NB: temp.sql does a reconnect which transiently uses 2 connections, # so keep this parallel group to at most 19 tests # ---------- ! test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml json # run stats by itself because its delay may be insufficient under heavy load test: stats diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 80a9881..3965bef 100644 *** a/src/test/regress/serial_schedule --- b/src/test/regress/serial_schedule *************** test: returning *** 123,126 **** --- 123,127 ---- test: largeobject test: with test: xml + text: json test: stats diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index ...76a1d06 . *** a/src/test/regress/sql/json.sql --- b/src/test/regress/sql/json.sql *************** *** 0 **** --- 1,13 ---- + CREATE TABLE jsontest ( + id int, + data json + ); + + INSERT INTO jsontest VALUES (1, 'null'); + INSERT INTO jsontest VALUES (2, '{"booleans":[true,false],"null":null}'); + INSERT INTO jsontest VALUES (3, E'[\n123.456,\n"string",\n{"name":"object"}\n]'); + INSERT INTO jsontest VALUES (101, 'wrong'); + INSERT INTO jsontest VALUES (101, '[wrong'); + INSERT INTO jsontest VALUES (101, '{"wrong":123}}'); + + SELECT *, json_pretty(data) FROM jsontest; diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 0d084a1..fb71bb2 100644 *** a/src/test/regress/sql/opr_sanity.sql --- b/src/test/regress/sql/opr_sanity.sql *************** WHERE c.castfunc = p.oid AND *** 320,325 **** --- 320,327 ---- -- As of 9.1, this finds the cast from pg_node_tree to text, which we -- intentionally do not provide a reverse pathway for. + -- Also, this finds the casts from json to text, varchar, and bpchar, + -- because of the same reason as xml. SELECT castsource::regtype, casttarget::regtype, castfunc, castcontext FROM pg_cast c