basic_json-20100921.patch
application/octet-stream
Filename: basic_json-20100921.patch
Type: application/octet-stream
Part: 0
Message:
Re: Basic JSON support
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| src/backend/utils/adt/json.c | 0 | 0 |
| src/backend/utils/adt/json_parser.y | 0 | 0 |
| src/backend/utils/adt/json_scanner.l | 0 | 0 |
| src/backend/utils/adt/Makefile | 0 | 0 |
| src/include/catalog/pg_cast.h | 0 | 0 |
| src/include/catalog/pg_proc.h | 0 | 0 |
| src/include/catalog/pg_type.h | 0 | 0 |
| src/include/utils/json.h | 0 | 0 |
| src/test/regress/expected/json.out | 0 | 0 |
| src/test/regress/expected/opr_sanity.out | 0 | 0 |
| src/test/regress/parallel_schedule | 0 | 0 |
| src/test/regress/serial_schedule | 0 | 0 |
| src/test/regress/sql/json.sql | 0 | 0 |
| src/test/regress/sql/opr_sanity.sql | 0 | 0 |
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 <string> T_JSON_STRING
+ %token <number> T_JSON_NUMBER
+ %token <boolean> T_JSON_TRUE T_JSON_FALSE
+ %token T_JSON_NULL
+ %token T_JSON_INVALID
+
+ %type <number> number
+ %type <string> string
+ %type <node> json value array object
+ %type <list> items attrs
+ %type <attr> 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);
+ }
+ <str>([^\"\\\x00-\x1F]+|\\u[0-9A-Fa-f]{4}|\\[\"\\/bfnrt]) {
+ JsonParser *parser = getParser();
+ if (!parser->validateOnly)
+ appendStringInfoString(&parser->buf, yytext);
+ }
+ <str>\" {
+ 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 */
+ <str>.|\n { return T_JSON_INVALID; }
+ <str><<EOF>> { 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