diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index be272b5..796fcac 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,66 ---- 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.h: json_scanner.c ; + + 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 + + json.c: json_parser.h json_scanner.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 ...864bc87 . *** a/src/backend/utils/adt/json.c --- b/src/backend/utils/adt/json.c *************** *** 0 **** --- 1,460 ---- + /*------------------------------------------------------------------------- + * + * 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 + * + * + * IDENTIFICATION + * src/backend/utils/adt/json.c + * + *------------------------------------------------------------------------- + */ + + #include "postgres.h" + + #include "catalog/pg_type.h" + #include "libpq/pqformat.h" + #include "utils/array.h" + #include "utils/builtins.h" + #include "utils/json.h" + + #include "json_parser.h" + #include "json_scanner.h" + + static Json *json_to_node(const char *str, int len); + static bool json_validate(const char *str, int len); + static Json *json_parse_internal(const char *str, int len, bool *validate); + static void append_json(StringInfo buf, const Json *node, const text *space, + int indent); + static void append_indent(StringInfo buf, const text *space, int indent); + #ifdef NOT_USED + static void json_free(Json *node); + #endif + + 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); + + + Datum + json_in(PG_FUNCTION_ARGS) + { + char *s = PG_GETARG_CSTRING(0); + jsontype *json; + + json = (jsontype *) cstring_to_text(s); + + if (!json_validate(VARDATA_ANY(json), VARSIZE_ANY_EXHDR(json))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for JSON"))); + + 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); + if (!json_validate(str, nbytes)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid input syntax for JSON"))); + + 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); + } + + /* + * json_is_well_formed(text) RETURNS bool + */ + Datum + json_is_well_formed(PG_FUNCTION_ARGS) + { + text *t = PG_GETARG_TEXT_PP(0); + bool ret; + + ret = json_validate(VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t)); + PG_RETURN_BOOL(ret); + } + + Datum + json_parse(PG_FUNCTION_ARGS) + { + text *t = PG_GETARG_TEXT_PP(0); + + if (!json_validate(VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for JSON"))); + + PG_RETURN_JSON_P((jsontype *) t); + } + + Datum + json_stringify(PG_FUNCTION_ARGS) + { + /* text and jsontype are binary-compatible */ + PG_RETURN_DATUM(PG_GETARG_DATUM(0)); + } + + /* + * json_stringify(json, space) -- Format a JSON value into text with or + * without indentation. If 'space' is NULL, all of the unneeded whitespaces + * are removed. + * + * XXX: The whitespace-trimmer might be confusable because json_stringify(x) + * and json_stringify(x, NULL) behave differently. We could move the trimmer + * into json_in and json_parse. If do so, we cannot return the input as-is, + * but the above two calls can return the same result. + */ + Datum + json_stringify_space(PG_FUNCTION_ARGS) + { + jsontype *json; + text *space; + Json *node; + StringInfoData buf; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + json = PG_GETARG_JSON_PP(0); + space = (PG_ARGISNULL(1) ? NULL : PG_GETARG_TEXT_PP(1)); + + /* This should be succeeded because it have been validated on input. */ + node = json_to_node(VARDATA_ANY(json), VARSIZE_ANY_EXHDR(json)); + if (node == NULL) + elog(ERROR, "unexpected json value"); + + initStringInfo(&buf); + append_json(&buf, node, space, 0); + + #ifdef NOT_USED + json_free(node); + #endif + + return CStringGetTextDatum(buf.data); + } + + /* + * json_to_node -- parse a JSON text into a node tree. + * + * Returns NULL if the input is not valid JSON. + */ + static Json * + json_to_node(const char *str, int len) + { + return json_parse_internal(str, len, NULL); + } + + /* + * json_validate -- validate a JSON text. + */ + static bool + json_validate(const char *str, int len) + { + bool valid; + (void) json_parse_internal(str, len, &valid); + return valid; + } + + /* + * json_to_array -- split a JSON array to array of JSON values. + */ + Datum + json_to_array(PG_FUNCTION_ARGS) + { + jsontype *json = PG_GETARG_JSON_PP(0); + Json *node; + ListCell *cell; + int nelems; + ArrayType *array; + + /* This should be succeeded because it have been validated on input. */ + node = json_to_node(VARDATA_ANY(json), VARSIZE_ANY_EXHDR(json)); + if (node == NULL) + elog(ERROR, "unexpected json value"); + + /* json null to SQL NULL */ + if (node->type == JSON_NULL) + PG_RETURN_NULL(); + + if (node->type != JSON_ARRAY) + elog(ERROR, "not a json array"); + + nelems = list_length(node->value.array); + if (nelems == 0) + { + /* fast path for empty array */ + array = construct_empty_array(JSONOID); + } + else + { + Datum *elems; + StringInfoData buf; + int i; + + elems = (Datum *) palloc(sizeof(Datum) * nelems); + initStringInfo(&buf); + + i = 0; + foreach (cell, node->value.array) + { + Json *item = (Json *) lfirst(cell); + + resetStringInfo(&buf); + append_json(&buf, item, NULL, 0); + elems[i++] = PointerGetDatum( + cstring_to_text_with_len(buf.data, buf.len)); + } + array = construct_array(elems, nelems, JSONOID, -1, false, 'i'); + } + + PG_RETURN_ARRAYTYPE_P(array); + } + + /* + * Helper for json_to_node and json_validate. + * + * If validate is NULL, this function returns a parse tree, or NULL on a + * parse error. If validate is not NULL, this function sets a boolean to the + * variable indicating whether the JSON was valid and always return NULL. + */ + static Json * + json_parse_internal(const char *str, int len, bool *validate) + { + yyscan_t scanner; + char *scanbuf; + JsonParser parser; + int rc; + + 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 = (validate != NULL); + if (!parser.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 */ + rc = json_yyparse(scanner); + + /* cleanup */ + json_yylex_destroy(scanner); + pfree(scanbuf); + if (parser.buf.data) + pfree(parser.buf.data); + + if (validate != NULL) + *validate = (rc == 0); + + return rc == 0 ? parser.json : NULL; + } + + /* + * append_json -- append JSON node tree to string with or without indentation + */ + static void + append_json(StringInfo buf, const Json *node, const text *space, 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: + appendStringInfoChar(buf, '['); + if (space != NULL) + appendStringInfoChar(buf, '\n'); + sep = false; + foreach (cell, node->value.array) + { + const Json *value = (const Json *) lfirst(cell); + + if (sep) + { + appendStringInfoChar(buf, ','); + if (space != NULL) + appendStringInfoChar(buf, '\n'); + } + else + sep = true; + append_indent(buf, space, indent + 1); + append_json(buf, value, space, indent + 1); + } + if (sep && space) + appendStringInfoChar(buf, '\n'); + append_indent(buf, space, indent); + appendStringInfoChar(buf, ']'); + break; + case JSON_OBJECT: + appendStringInfoChar(buf, '{'); + if (space != NULL) + appendStringInfoChar(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) + { + appendStringInfoChar(buf, ','); + if (space != NULL) + appendStringInfoChar(buf, '\n'); + } + else + sep = true; + append_indent(buf, space, indent + 1); + appendStringInfoString(buf, key); + appendStringInfoChar(buf, ':'); + if (space != NULL) + appendStringInfoChar(buf, ' '); + append_json(buf, value, space, indent + 1); + } + if (sep && space != NULL) + appendStringInfoChar(buf, '\n'); + append_indent(buf, space, indent); + appendStringInfoChar(buf, '}'); + break; + default: + elog(ERROR, "unexpected json type: %d", node->type); + } + } + + static void + append_indent(StringInfo buf, const text *space, int indent) + { + int i; + + if (space == NULL) + return; /* do nothing */ + + for (i = 0; i < indent; i++) + appendBinaryStringInfo(buf, + VARDATA_ANY(space), VARSIZE_ANY_EXHDR(space)); + } + + #ifdef NOT_USED + /* + * 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); + } + #endif + + void + json_yyerror(yyscan_t scanner, const char *error) + { + /* + * No error report here. Instead, the caller will receives NULL node + * tree on 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 ...dd3914d . *** a/src/backend/utils/adt/json_parser.y --- b/src/backend/utils/adt/json_parser.y *************** *** 0 **** --- 1,223 ---- + %{ + /*------------------------------------------------------------------------- + * + * 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 + * + * + * IDENTIFICATION + * src/backend/utils/adt/json_parser.y + * + *------------------------------------------------------------------------- + */ + + #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 ...cdf2a6a . *** a/src/backend/utils/adt/json_scanner.l --- b/src/backend/utils/adt/json_scanner.l *************** *** 0 **** --- 1,92 ---- + %{ + /*------------------------------------------------------------------------- + * + * 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 + * + * + * IDENTIFICATION + * src/backend/utils/adt/json_snanner.l + * + *------------------------------------------------------------------------- + */ + + #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]+)?([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); + } + ([^\"\\\x00-\x1F]+|\\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; + } + /* unterminated string */ + .|\n { return T_JSON_INVALID; } + <> { return T_JSON_INVALID; } + + /* Invalid */ + . return T_JSON_INVALID; diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h index 7e33b94..85b833f 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 61c6b27..76d95b7 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,4482 ---- 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_parse PGNSP PGUID 12 1 0 0 f f f t f s 1 0 321 "25" _null_ _null_ _null_ _null_ json_parse _null_ _null_ _null_ )); + DESCR("parse a character string to produce a JSON value"); + DATA(insert OID = 3831 ( json_is_well_formed PGNSP PGUID 12 1 0 0 f f f t f i 1 0 16 "25" _null_ _null_ _null_ _null_ json_is_well_formed _null_ _null_ _null_ )); + DESCR("determine if a string is well formed JSON"); + DATA(insert OID = 3832 ( json_stringify PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "321" _null_ _null_ _null_ _null_ json_stringify _null_ _null_ _null_ )); + DESCR("serialize a JSON value to a character string"); + DATA(insert OID = 3833 ( json_stringify PGNSP PGUID 12 1 0 0 f f f f f i 2 0 25 "321 25" _null_ _null_ _null_ _null_ json_stringify_space _null_ _null_ _null_ )); + DESCR("serialize a JSON value to a character string with space option"); + DATA(insert OID = 3834 ( json_to_array PGNSP PGUID 12 1 0 0 f f f t f i 1 0 322 "321" _null_ _null_ _null_ _null_ json_to_array _null_ _null_ _null_ )); + DESCR("convert a JSON array to an array of JSON values"); + /* 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 fc2c306..6034cc3 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 ...55113b3 . *** a/src/include/utils/json.h --- b/src/include/utils/json.h *************** *** 0 **** --- 1,77 ---- + /*------------------------------------------------------------------------- + * + * 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 + * + * src/include/utils/json.h + * + *------------------------------------------------------------------------- + */ + + #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 json_parse(PG_FUNCTION_ARGS); + extern Datum json_is_well_formed(PG_FUNCTION_ARGS); + extern Datum json_stringify(PG_FUNCTION_ARGS); + extern Datum json_stringify_space(PG_FUNCTION_ARGS); + extern Datum json_to_array(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 ...fc87cd4 . *** a/src/test/regress/expected/json.out --- b/src/test/regress/expected/json.out *************** *** 0 **** --- 1,107 ---- + CREATE TABLE jsontest (t text); + -- Control characters not allowed in JSON strings. + -- Can't test \x00 because it's not even a valid TEXT. + -- Although \x7F is a control character, the JSON RFC only says + -- "..., and the control characters (U+0000 through U+001F)". + -- However, whether to trust the RFC here or not is a tough call. + copy jsontest from stdin; + SELECT t, json_is_well_formed(t)::text FROM jsontest; + t | json_is_well_formed + -----------------------------------+--------------------- + | false + null | true + {"booleans":[true,false]} | true + 0 | true + +0 | false + -0 | true + 3 | true + -3 | true + 3. | false + 3.1 | true + .3 | false + 0.3 | true + 123.456e14234 | true + 123.456e-14234 | true + 123.456e+14234 | true + 0.3e | false + 0.3e+ | false + 0.3e+5 | true + 0.3e-5 | true + 3e+1 | true + 5e5 | true + 5.e5 | false + 3.2e+1 | true + 3.2e+1.5 | false + "" | true + "hello" | true + 'hello' | false + hello | false + "hello | false + "hello""" | false + "\"hello\"" | true + "newline +| false + " | + "\u00000" | true + "\u99999" | true + "\uDB00" | true + "\uDB00\uDBFF" | true + "\u1234\uD800" | true + "\ud800\udc00" | true + "\uFFFE\uFFFF" | true + "\UD834\UDD1E" | false + "\n\r\t\b" | true + "\n\r\t\v" | false + "\x01" | false + "\x1F" | false + "\x7F" | false + [] | true + [,] | false + [,2] | false + [) | false + []] | false + [} | false + [1] | true + ["1":2] | false + [1,2,] | false + [1:2} | false + [3, [4, [5], 6]] | true + ["hello"] | true + ["hello", "bye", 3] | true + {} | true + {] | false + {,} | false + {"1":{}} | true + {"1":2} | true + {"1":2,} | false + {1:2} | false + {"1":2, "3":4} | true + {"1":2, "3":4 | false + {"1":2, "3" : [4, {}, ["5"]} | false + {"no value"} | false + {"non-string key":null, 123:null} | false + (70 rows) + + SELECT json_stringify('{"null":null, "boolean":true, "array":[1,2,3], "empty array":[], "empty object":{}}', ' '); + json_stringify + --------------------- + { + + "null": null, + + "boolean": true, + + "array": [ + + 1, + + 2, + + 3 + + ], + + "empty array": [ + + ], + + "empty object": {+ + } + + } + (1 row) + + SELECT json_to_array('[1, 2, 3, "string", [ "array" ], { "object":null }]'); + json_to_array + -------------------------------------------------------- + {1,2,3,"\"string\"","[\"array\"]","{\"object\":null}"} + (1 row) + 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 a05bfeb..286fd4b 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 e2f8351..d7d6e45 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 ...a7e283d . *** a/src/test/regress/sql/json.sql --- b/src/test/regress/sql/json.sql *************** *** 0 **** --- 1,85 ---- + CREATE TABLE jsontest (t text); + + -- Control characters not allowed in JSON strings. + -- Can't test \x00 because it's not even a valid TEXT. + -- Although \x7F is a control character, the JSON RFC only says + -- "..., and the control characters (U+0000 through U+001F)". + -- However, whether to trust the RFC here or not is a tough call. + + copy jsontest from stdin; + + null + {"booleans":[true,false]} + 0 + +0 + -0 + 3 + -3 + 3. + 3.1 + .3 + 0.3 + 123.456e14234 + 123.456e-14234 + 123.456e+14234 + 0.3e + 0.3e+ + 0.3e+5 + 0.3e-5 + 3e+1 + 5e5 + 5.e5 + 3.2e+1 + 3.2e+1.5 + "" + "hello" + 'hello' + hello + "hello + "hello""" + "\\"hello\\"" + "newline\n" + "\\u00000" + "\\u99999" + "\\uDB00" + "\\uDB00\\uDBFF" + "\\u1234\\uD800" + "\\ud800\\udc00" + "\\uFFFE\\uFFFF" + "\\UD834\\UDD1E" + "\\n\\r\\t\\b" + "\\n\\r\\t\\v" + "\\x01" + "\\x1F" + "\\x7F" + [] + [,] + [,2] + [) + []] + [} + [1] + ["1":2] + [1,2,] + [1:2} + [3, [4, [5], 6]] + ["hello"] + ["hello", "bye", 3] + {} + {] + {,} + {"1":{}} + {"1":2} + {"1":2,} + {1:2} + {"1":2, "3":4} + {"1":2, "3":4 + {"1":2, "3" : [4, {}, ["5"]} + {"no value"} + {"non-string key":null, 123:null} + \. + + SELECT t, json_is_well_formed(t)::text FROM jsontest; + + SELECT json_stringify('{"null":null, "boolean":true, "array":[1,2,3], "empty array":[], "empty object":{}}', ' '); + SELECT json_to_array('[1, 2, 3, "string", [ "array" ], { "object":null }]'); 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