*** a/doc/src/sgml/datatype.sgml
--- b/doc/src/sgml/datatype.sgml
***************
*** 269,274 ****
--- 269,280 ----
XML data
+
+
+ json
+
+ JSON data
+
***************
*** 4169,4174 **** SET xmloption TO { DOCUMENT | CONTENT };
--- 4175,4195 ----
+
+ JSON> Type
+
+
+ JSON
+
+
+
+ The json data type can be used to store JSON data. Such
+ data can also be stored as text, but the
+ json data type has the advantage of checking that each
+ stored value is a valid JSON value.
+
+
+
&array;
&rowtypes;
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 9615,9620 **** table2-mapping
--- 9615,9678 ----
+
+ JSON functions
+
+
+ JSON
+ Functions and operators
+
+
+
+ This section descripbes the functions that are available for creating
+ JSON (see ) data.
+
+
+
+ JSON Support Functions
+
+
+
+ Function
+ Description
+ Example
+ Example Result
+
+
+
+
+
+
+ query_to_json
+
+ query_to_json(text, boolean)
+
+
+ Returns the result of running the query as JSON. If the
+ second parameter is true, there will be a line feed between records.
+
+ query_to_json('select 1 as a, $$foo$$ as b', false)
+ [{"a":1,"b":"foo"}]
+
+
+
+
+ array_to_json
+
+ array_to_json(anyarray)
+
+
+ Returns the array as JSON. A Postgres multi-dimensional array becomes a JSON
+ array of arrays.
+
+ array_to_json('{{1,5},{99,100}}'::int[])
+ [[1,5],[99,100]]
+
+
+
+
+
+
Sequence Manipulation Functions
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 24,29 ****
--- 24,30 ----
#include "rewrite/rewriteHandler.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
+ #include "utils/json.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
***************
*** 99,105 **** static void ExplainDummyGroup(const char *objtype, const char *labelname,
static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es);
static void ExplainJSONLineEnding(ExplainState *es);
static void ExplainYAMLLineStarting(ExplainState *es);
- static void escape_json(StringInfo buf, const char *str);
static void escape_yaml(StringInfo buf, const char *str);
--- 100,105 ----
***************
*** 242,248 **** ExplainResultDesc(ExplainStmt *stmt)
{
TupleDesc tupdesc;
ListCell *lc;
! bool xml = false;
/* Check for XML format option */
foreach(lc, stmt->options)
--- 242,248 ----
{
TupleDesc tupdesc;
ListCell *lc;
! Oid result_type = TEXTOID;
/* Check for XML format option */
foreach(lc, stmt->options)
***************
*** 253,259 **** ExplainResultDesc(ExplainStmt *stmt)
{
char *p = defGetString(opt);
! xml = (strcmp(p, "xml") == 0);
/* don't "break", as ExplainQuery will use the last value */
}
}
--- 253,264 ----
{
char *p = defGetString(opt);
! if (strcmp(p, "xml") == 0)
! result_type = XMLOID;
! else if (strcmp(p, "json") == 0)
! result_type = JSONOID;
! else
! result_type = TEXTOID;
/* don't "break", as ExplainQuery will use the last value */
}
}
***************
*** 261,267 **** ExplainResultDesc(ExplainStmt *stmt)
/* Need a tuple descriptor representing a single TEXT or XML column */
tupdesc = CreateTemplateTupleDesc(1, false);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
! xml ? XMLOID : TEXTOID, -1, 0);
return tupdesc;
}
--- 266,272 ----
/* Need a tuple descriptor representing a single TEXT or XML column */
tupdesc = CreateTemplateTupleDesc(1, false);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
! result_type, -1, 0);
return tupdesc;
}
***************
*** 2311,2361 **** ExplainYAMLLineStarting(ExplainState *es)
}
/*
- * Produce a JSON string literal, properly escaping characters in the text.
- */
- static void
- escape_json(StringInfo buf, const char *str)
- {
- const char *p;
-
- appendStringInfoCharMacro(buf, '\"');
- for (p = str; *p; p++)
- {
- switch (*p)
- {
- case '\b':
- appendStringInfoString(buf, "\\b");
- break;
- case '\f':
- appendStringInfoString(buf, "\\f");
- break;
- case '\n':
- appendStringInfoString(buf, "\\n");
- break;
- case '\r':
- appendStringInfoString(buf, "\\r");
- break;
- case '\t':
- appendStringInfoString(buf, "\\t");
- break;
- case '"':
- appendStringInfoString(buf, "\\\"");
- break;
- case '\\':
- appendStringInfoString(buf, "\\\\");
- break;
- default:
- if ((unsigned char) *p < ' ')
- appendStringInfo(buf, "\\u%04x", (int) *p);
- else
- appendStringInfoCharMacro(buf, *p);
- break;
- }
- }
- appendStringInfoCharMacro(buf, '\"');
- }
-
- /*
* YAML is a superset of JSON; unfortuantely, the YAML quoting rules are
* ridiculously complicated -- as documented in sections 5.3 and 7.3.3 of
* http://yaml.org/spec/1.2/spec.html -- so we chose to just quote everything.
--- 2316,2321 ----
*** a/src/backend/utils/adt/Makefile
--- b/src/backend/utils/adt/Makefile
***************
*** 18,24 **** endif
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 rangetypes.o rangetypes_gist.o \
rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
--- 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 json.o like.o lockfuncs.o \
misc.o nabstime.o name.o numeric.o numutils.o \
oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \
rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \
*** /dev/null
--- b/src/backend/utils/adt/json.c
***************
*** 0 ****
--- 1,1056 ----
+ /*-------------------------------------------------------------------------
+ *
+ * json.c
+ * JSON data type support.
+ *
+ * Portions Copyright (c) 1996-2012, 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 "executor/spi.h"
+ #include "lib/stringinfo.h"
+ #include "libpq/pqformat.h"
+ #include "mb/pg_wchar.h"
+ #include "parser/parse_coerce.h"
+ #include "utils/array.h"
+ #include "utils/builtins.h"
+ #include "utils/lsyscache.h"
+ #include "utils/json.h"
+ #include "utils/typcache.h"
+
+ typedef enum
+ {
+ JSON_VALUE_INVALID,
+ JSON_VALUE_STRING,
+ JSON_VALUE_NUMBER,
+ JSON_VALUE_OBJECT,
+ JSON_VALUE_ARRAY,
+ JSON_VALUE_TRUE,
+ JSON_VALUE_FALSE,
+ JSON_VALUE_NULL
+ } JsonValueType;
+
+ typedef struct
+ {
+ char *input;
+ char *token_start;
+ char *token_terminator;
+ JsonValueType token_type;
+ int line_number;
+ char *line_start;
+ } JsonLexContext;
+
+ typedef enum
+ {
+ JSON_PARSE_VALUE, /* expecting a value */
+ JSON_PARSE_ARRAY_START, /* saw '[', expecting value or ']' */
+ JSON_PARSE_ARRAY_NEXT, /* saw array element, expecting ',' or ']' */
+ JSON_PARSE_OBJECT_START, /* saw '{', expecting label or '}' */
+ JSON_PARSE_OBJECT_LABEL, /* saw object label, expecting ':' */
+ JSON_PARSE_OBJECT_NEXT, /* saw object value, expecting ',' or '}' */
+ JSON_PARSE_OBJECT_COMMA /* saw object ',', expecting next label */
+ } JsonParseState;
+
+ typedef struct JsonParseStack
+ {
+ JsonParseState state;
+ } JsonParseStack;
+
+ typedef enum
+ {
+ JSON_STACKOP_NONE,
+ JSON_STACKOP_PUSH,
+ JSON_STACKOP_PUSH_WITH_PUSHBACK,
+ JSON_STACKOP_POP
+ } JsonStackOp;
+
+ typedef struct
+ {
+ char *colname;
+ Oid typid;
+ TYPCATEGORY tcategory;
+ Oid toutputfunc;
+ bool tisvarlena;
+
+ } result_metadata;
+
+ static void json_validate_cstring(char *input);
+ static void json_lex(JsonLexContext *lex);
+ static void json_lex_string(JsonLexContext *lex);
+ static void json_lex_number(JsonLexContext *lex, char *s);
+ static void report_parse_error(JsonParseStack *stack, JsonLexContext *lex);
+ static void report_invalid_token(JsonLexContext *lex);
+ static char *extract_mb_char(char *s);
+ static void composite_to_json(Datum composite, StringInfo result);
+ static void array_dim_to_json(StringInfo result, int dim, int ndims,int * dims,
+ Datum *vals, int * valcount, TYPCATEGORY tcategory,
+ Oid typoutputfunc);
+ static void array_to_json_internal(Datum array, StringInfo result);
+
+ static void sql_row_to_json(int rownum, StringInfo result, result_metadata *meta);
+
+ extern Datum json_in(PG_FUNCTION_ARGS);
+
+ /*
+ * Input.
+ */
+ Datum
+ json_in(PG_FUNCTION_ARGS)
+ {
+ char *text = PG_GETARG_CSTRING(0);
+
+ json_validate_cstring(text);
+
+ PG_RETURN_TEXT_P(cstring_to_text(text));
+ }
+
+ /*
+ * Output.
+ */
+ Datum
+ json_out(PG_FUNCTION_ARGS)
+ {
+ Datum txt = PG_GETARG_DATUM(0);
+
+ PG_RETURN_CSTRING(TextDatumGetCString(txt));
+ }
+
+ /*
+ * Binary send.
+ */
+ Datum
+ json_send(PG_FUNCTION_ARGS)
+ {
+ StringInfoData buf;
+ text *t = PG_GETARG_TEXT_PP(0);
+
+ pq_begintypsend(&buf);
+ pq_sendtext(&buf, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t));
+ PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+ }
+
+ /*
+ * Binary receive.
+ */
+ Datum
+ json_recv(PG_FUNCTION_ARGS)
+ {
+ StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
+ text *result;
+ char *str;
+ int nbytes;
+
+ str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
+
+ /*
+ * We need a null-terminated string to pass to json_validate_cstring().
+ * Rather than make a separate copy, make the temporary result one byte
+ * bigger than it needs to be.
+ */
+ result = palloc(nbytes + 1 + VARHDRSZ);
+ SET_VARSIZE(result, nbytes + VARHDRSZ);
+ memcpy(VARDATA(result), str, nbytes);
+ str = VARDATA(result);
+ str[nbytes] = '\0';
+
+ /* Validate it. */
+ json_validate_cstring(str);
+
+ PG_RETURN_TEXT_P(result);
+ }
+
+ /*
+ * Check whether supplied input is valid JSON.
+ */
+ static void
+ json_validate_cstring(char *input)
+ {
+ JsonLexContext lex;
+ JsonParseStack *stack,
+ *stacktop;
+ int stacksize;
+
+ /* Set up lexing context. */
+ lex.input = input;
+ lex.token_terminator = lex.input;
+ lex.line_number = 1;
+ lex.line_start = input;
+
+ /* Set up parse stack. */
+ stacksize = 32;
+ stacktop = palloc(sizeof(JsonParseStack) * stacksize);
+ stack = stacktop;
+ stack->state = JSON_PARSE_VALUE;
+
+ /* Main parsing loop. */
+ for (;;)
+ {
+ JsonStackOp op;
+
+ /* Fetch next token. */
+ json_lex(&lex);
+
+ /* Check for unexpected end of input. */
+ if (lex.token_start == NULL)
+ report_parse_error(stack, &lex);
+
+ redo:
+ /* Figure out what to do with this token. */
+ op = JSON_STACKOP_NONE;
+ switch (stack->state)
+ {
+ case JSON_PARSE_VALUE:
+ if (lex.token_type != JSON_VALUE_INVALID)
+ op = JSON_STACKOP_POP;
+ else if (lex.token_start[0] == '[')
+ stack->state = JSON_PARSE_ARRAY_START;
+ else if (lex.token_start[0] == '{')
+ stack->state = JSON_PARSE_OBJECT_START;
+ else
+ report_parse_error(stack, &lex);
+ break;
+ case JSON_PARSE_ARRAY_START:
+ if (lex.token_type != JSON_VALUE_INVALID)
+ stack->state = JSON_PARSE_ARRAY_NEXT;
+ else if (lex.token_start[0] == ']')
+ op = JSON_STACKOP_POP;
+ else if (lex.token_start[0] == '['
+ || lex.token_start[0] == '{')
+ {
+ stack->state = JSON_PARSE_ARRAY_NEXT;
+ op = JSON_STACKOP_PUSH_WITH_PUSHBACK;
+ }
+ else
+ report_parse_error(stack, &lex);
+ break;
+ case JSON_PARSE_ARRAY_NEXT:
+ if (lex.token_type != JSON_VALUE_INVALID)
+ report_parse_error(stack, &lex);
+ else if (lex.token_start[0] == ']')
+ op = JSON_STACKOP_POP;
+ else if (lex.token_start[0] == ',')
+ op = JSON_STACKOP_PUSH;
+ else
+ report_parse_error(stack, &lex);
+ break;
+ case JSON_PARSE_OBJECT_START:
+ if (lex.token_type == JSON_VALUE_STRING)
+ stack->state = JSON_PARSE_OBJECT_LABEL;
+ else if (lex.token_type == JSON_VALUE_INVALID
+ && lex.token_start[0] == '}')
+ op = JSON_STACKOP_POP;
+ else
+ report_parse_error(stack, &lex);
+ break;
+ case JSON_PARSE_OBJECT_LABEL:
+ if (lex.token_type == JSON_VALUE_INVALID
+ && lex.token_start[0] == ':')
+ {
+ stack->state = JSON_PARSE_OBJECT_NEXT;
+ op = JSON_STACKOP_PUSH;
+ }
+ else
+ report_parse_error(stack, &lex);
+ break;
+ case JSON_PARSE_OBJECT_NEXT:
+ if (lex.token_type != JSON_VALUE_INVALID)
+ report_parse_error(stack, &lex);
+ else if (lex.token_start[0] == '}')
+ op = JSON_STACKOP_POP;
+ else if (lex.token_start[0] == ',')
+ stack->state = JSON_PARSE_OBJECT_COMMA;
+ else
+ report_parse_error(stack, &lex);
+ break;
+ case JSON_PARSE_OBJECT_COMMA:
+ if (lex.token_type == JSON_VALUE_STRING)
+ stack->state = JSON_PARSE_OBJECT_LABEL;
+ else
+ report_parse_error(stack, &lex);
+ break;
+ default:
+ elog(ERROR, "unexpected json parse state: %d",
+ (int) stack->state);
+ }
+
+ /* Push or pop the stack, if needed. */
+ switch (op)
+ {
+ case JSON_STACKOP_PUSH:
+ case JSON_STACKOP_PUSH_WITH_PUSHBACK:
+ ++stack;
+ if (stack >= &stacktop[stacksize])
+ {
+ int stackoffset = stack - stacktop;
+ stacksize = stacksize + 32;
+ stacktop = repalloc(stacktop,
+ sizeof(JsonParseStack) * stacksize);
+ stack = stacktop + stackoffset;
+ }
+ stack->state = JSON_PARSE_VALUE;
+ if (op == JSON_STACKOP_PUSH_WITH_PUSHBACK)
+ goto redo;
+ break;
+ case JSON_STACKOP_POP:
+ if (stack == stacktop)
+ {
+ /* Expect end of input. */
+ json_lex(&lex);
+ if (lex.token_start != NULL)
+ report_parse_error(NULL, &lex);
+ return;
+ }
+ --stack;
+ break;
+ case JSON_STACKOP_NONE:
+ /* nothing to do */
+ break;
+ }
+ }
+ }
+
+ /*
+ * Lex one token from the input stream.
+ */
+ static void
+ json_lex(JsonLexContext *lex)
+ {
+ char *s;
+
+ /* Skip leading whitespace. */
+ s = lex->token_terminator;
+ while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')
+ {
+ if (*s == '\n')
+ ++lex->line_number;
+ ++s;
+ }
+ lex->token_start = s;
+
+ /* Determine token type. */
+ if (strchr("{}[],:", s[0]))
+ {
+ /* strchr() doesn't return false on a NUL input. */
+ if (s[0] == '\0')
+ {
+ /* End of string. */
+ lex->token_start = NULL;
+ lex->token_terminator = NULL;
+ }
+ else
+ {
+ /* Single-character token, some kind of punctuation mark. */
+ lex->token_terminator = s + 1;
+ }
+ lex->token_type = JSON_VALUE_INVALID;
+ }
+ else if (*s == '"')
+ {
+ /* String. */
+ json_lex_string(lex);
+ lex->token_type = JSON_VALUE_STRING;
+ }
+ else if (*s == '-')
+ {
+ /* Negative number. */
+ json_lex_number(lex, s + 1);
+ lex->token_type = JSON_VALUE_NUMBER;
+ }
+ else if (*s >= '0' && *s <= '9')
+ {
+ /* Positive number. */
+ json_lex_number(lex, s);
+ lex->token_type = JSON_VALUE_NUMBER;
+ }
+ else
+ {
+ char *p;
+
+ /*
+ * We're not dealing with a string, number, legal punctuation mark,
+ * or end of string. The only legal tokens we might find here are
+ * true, false, and null, but for error reporting purposes we scan
+ * until we see a non-alphanumeric character. That way, we can report
+ * the whole word as an unexpected token, rather than just some
+ * unintuitive prefix thereof.
+ */
+ for (p = s; (*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
+ || (*p >= '0' && *p <= '9') || *p == '_' || IS_HIGHBIT_SET(*p);
+ ++p)
+ ;
+
+ /*
+ * We got some sort of unexpected punctuation or an otherwise
+ * unexpected character, so just complain about that one character.
+ */
+ if (p == s)
+ {
+ lex->token_terminator = s + 1;
+ report_invalid_token(lex);
+ }
+
+ /*
+ * We've got a real alphanumeric token here. If it happens to be
+ * true, false, or null, all is well. If not, error out.
+ */
+ lex->token_terminator = p;
+ if (p - s == 4)
+ {
+ if (memcmp(s, "true", 4) == 0)
+ lex->token_type = JSON_VALUE_TRUE;
+ else if (memcmp(s, "null", 4) == 0)
+ lex->token_type = JSON_VALUE_NULL;
+ else
+ report_invalid_token(lex);
+ }
+ else if (p - s == 5 && memcmp(s, "false", 5) == 0)
+ lex->token_type = JSON_VALUE_FALSE;
+ else
+ report_invalid_token(lex);
+ }
+ }
+
+ /*
+ * The next token in the input stream is known to be a string; lex it.
+ */
+ static void
+ json_lex_string(JsonLexContext *lex)
+ {
+ char *s = lex->token_start + 1;
+
+ for (s = lex->token_start + 1; *s != '"'; ++s)
+ {
+ /* Per RFC4627, these characters MUST be escaped. */
+ if (*s < 32)
+ {
+ /* A NUL byte marks the (premature) end of the string. */
+ if (*s == '\0')
+ {
+ lex->token_terminator = s;
+ report_invalid_token(lex);
+ }
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type json"),
+ errdetail_internal("line %d: Character \"%c\" must be escaped.",
+ lex->line_number, *s)));
+ }
+ else if (*s == '\\')
+ {
+ /* OK, we have an escape character. */
+ ++s;
+ if (*s == '\0')
+ {
+ lex->token_terminator = s;
+ report_invalid_token(lex);
+ }
+ else if (*s == 'u')
+ {
+ int i;
+ int ch = 0;
+
+ for (i = 1; i <= 4; ++i)
+ {
+ if (s[i] == '\0')
+ {
+ lex->token_terminator = s + i;
+ report_invalid_token(lex);
+ }
+ else if (s[i] >= '0' && s[i] <= '9')
+ ch = (ch * 16) + (s[i] - '0');
+ else if (s[i] >= 'a' && s[i] <= 'f')
+ ch = (ch * 16) + (s[i] - 'a') + 10;
+ else if (s[i] >= 'A' && s[i] <= 'F')
+ ch = (ch * 16) + (s[i] - 'A') + 10;
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type json"),
+ errdetail_internal("line %d: \"\\u\" must be followed by four hexadecimal digits.",
+ lex->line_number)));
+ }
+ }
+
+ /*
+ * If the database encoding is not UTF-8, we support \uXXXX
+ * escapes only for characters 0-127.
+ */
+ if (GetDatabaseEncoding() != PG_UTF8)
+ {
+ if (ch > 127)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type json"),
+ errdetail_internal("line %d: \\uXXXX escapes are supported for non-ASCII characters only under UTF-8.",
+ lex->line_number)));
+ }
+
+ /* Account for the four additional bytes we just parsed. */
+ s += 4;
+ }
+ else if (!strchr("\"\\/bfnrt", *s))
+ {
+ /* Error out. */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type json"),
+ errdetail_internal("line %d: Invalid escape \"\\%s\".",
+ lex->line_number, extract_mb_char(s))));
+ }
+ }
+ }
+
+ /* Hooray, we found the end of the string! */
+ lex->token_terminator = s + 1;
+ }
+
+ /*-------------------------------------------------------------------------
+ * The next token in the input stream is known to be a number; lex it.
+ *
+ * In JSON, a number consists of four parts:
+ *
+ * (1) An optional minus sign ('-').
+ *
+ * (2) Either a single '0', or a string of one or more digits that does not
+ * begin with a '0'.
+ *
+ * (3) An optional decimal part, consisting of a period ('.') followed by
+ * one or more digits. (Note: While this part can be omitted
+ * completely, it's not OK to have only the decimal point without
+ * any digits afterwards.)
+ *
+ * (4) An optional exponent part, consisting of 'e' or 'E', optionally
+ * followed by '+' or '-', followed by one or more digits. (Note:
+ * As with the decimal part, if 'e' or 'E' is present, it must be
+ * followed by at least one digit.)
+ *
+ * The 's' argument to this function points to the ostensible beginning
+ * of part 2 - i.e. the character after any optional minus sign, and the
+ * first character of the string if there is none.
+ *
+ *-------------------------------------------------------------------------
+ */
+ static void
+ json_lex_number(JsonLexContext *lex, char *s)
+ {
+ bool error = false;
+ char *p;
+
+ /* Part (1): leading sign indicator. */
+ /* Caller already did this for us; so do nothing. */
+
+ /* Part (2): parse main digit string. */
+ if (*s == '0')
+ ++s;
+ else if (*s >= '1' && *s <= '9')
+ {
+ do
+ {
+ ++s;
+ } while (*s >= '0' && *s <= '9');
+ }
+ else
+ error = true;
+
+ /* Part (3): parse optional decimal portion. */
+ if (*s == '.')
+ {
+ ++s;
+ if (*s < '0' && *s > '9')
+ error = true;
+ else
+ {
+ do
+ {
+ ++s;
+ } while (*s >= '0' && *s <= '9');
+ }
+ }
+
+ /* Part (4): parse optional exponent. */
+ if (*s == 'e' || *s == 'E')
+ {
+ ++s;
+ if (*s == '+' || *s == '-')
+ ++s;
+ if (*s < '0' && *s > '9')
+ error = true;
+ else
+ {
+ do
+ {
+ ++s;
+ } while (*s >= '0' && *s <= '9');
+ }
+ }
+
+ /* Check for trailing garbage. */
+ for (p = s; (*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
+ || (*p >= '0' && *p <= '9') || *p == '_' || IS_HIGHBIT_SET(*p); ++p)
+ ;
+ lex->token_terminator = p;
+ if (p > s || error)
+ report_invalid_token(lex);
+ }
+
+ /*
+ * Report a parse error.
+ */
+ static void
+ report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
+ {
+ char *detail = NULL;
+ char *token = NULL;
+ int toklen;
+
+ /* Handle case where the input ended prematurely. */
+ if (lex->token_start == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type json: \"%s\"",
+ lex->input),
+ errdetail_internal("The input string ended unexpectedly.")));
+
+ /* Work out the offending token. */
+ toklen = lex->token_terminator - lex->token_start;
+ token = palloc(toklen + 1);
+ memcpy(token, lex->token_start, toklen);
+ token[toklen] = '\0';
+
+ /* Select correct detail message. */
+ if (stack == NULL)
+ detail = "line %d: Expected end of input, but found \"%s\".";
+ else
+ {
+ switch (stack->state)
+ {
+ case JSON_PARSE_VALUE:
+ detail = "line %d: Expected string, number, object, array, true, false, or null, but found \"%s\".";
+ break;
+ case JSON_PARSE_ARRAY_START:
+ detail = "line %d: Expected array element or \"]\", but found \"%s\".";
+ break;
+ case JSON_PARSE_ARRAY_NEXT:
+ detail = "line %d: Expected \",\" or \"]\", but found \"%s\".";
+ break;
+ case JSON_PARSE_OBJECT_START:
+ detail = "line %d: Expected string or \"}\", but found \"%s\".";
+ break;
+ case JSON_PARSE_OBJECT_LABEL:
+ detail = "line %d: Expected \":\", but found \"%s\".";
+ break;
+ case JSON_PARSE_OBJECT_NEXT:
+ detail = "line %d: Expected \",\" or \"}\", but found \"%s\".";
+ break;
+ case JSON_PARSE_OBJECT_COMMA:
+ detail = "line %d: Expected string, but found \"%s\".";
+ break;
+ }
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type json: \"%s\"",
+ lex->input),
+ errdetail_internal(detail, lex->line_number, token)));
+ }
+
+ /*
+ * Report an invalid input token.
+ */
+ static void
+ report_invalid_token(JsonLexContext *lex)
+ {
+ char *token;
+ int toklen;
+
+ toklen = lex->token_terminator - lex->token_start;
+ token = palloc(toklen + 1);
+ memcpy(token, lex->token_start, toklen);
+ token[toklen] = '\0';
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type json"),
+ errdetail_internal("line %d: Token \"%s\" is invalid.",
+ lex->line_number, token)));
+ }
+
+ /*
+ * Extract a single, possibly multi-byte char from the input string.
+ */
+ static char *
+ extract_mb_char(char *s)
+ {
+ char *res;
+ int len;
+
+ len = pg_mblen(s);
+ res = palloc(len + 1);
+ memcpy(res, s, len);
+ res[len] = '\0';
+
+ return res;
+ }
+
+ static inline void
+ datum_to_json(Datum val, StringInfo result, TYPCATEGORY tcategory, Oid typoutputfunc)
+ {
+
+ char *outputstr;
+
+ if (val == (Datum) NULL)
+ {
+ appendStringInfoString(result,"null");
+ return;
+ }
+
+ switch (tcategory)
+ {
+ case TYPCATEGORY_ARRAY:
+ array_to_json_internal(val, result);
+ break;
+ case TYPCATEGORY_COMPOSITE:
+ composite_to_json(val, result);
+ break;
+ case TYPCATEGORY_BOOLEAN:
+ if (DatumGetBool(val))
+ appendStringInfoString(result,"true");
+ else
+ appendStringInfoString(result,"false");
+ break;
+ case TYPCATEGORY_NUMERIC:
+ outputstr = OidOutputFunctionCall(typoutputfunc, val);
+ appendStringInfoString(result, outputstr);
+ pfree(outputstr);
+ break;
+ default:
+ outputstr = OidOutputFunctionCall(typoutputfunc, val);
+ escape_json(result, outputstr);
+ pfree(outputstr);
+ }
+ }
+
+ static void
+ array_dim_to_json(StringInfo result, int dim, int ndims,int * dims, Datum *vals, int * valcount, TYPCATEGORY tcategory, Oid typoutputfunc)
+ {
+
+ int i;
+
+ Assert(dim < ndims);
+
+ appendStringInfoChar(result, '[');
+
+ for (i = 1; i <= dims[dim]; i++)
+ {
+ if (i > 1)
+ appendStringInfoChar(result,',');
+
+ if (dim + 1 == ndims)
+ {
+ datum_to_json(vals[*valcount],result,tcategory,typoutputfunc);
+ (*valcount)++;
+ }
+ else
+ {
+ array_dim_to_json(result,dim+1,ndims,dims,vals,valcount,tcategory,typoutputfunc);
+ }
+ }
+
+ appendStringInfoChar(result, ']');
+ }
+
+
+ static void
+ array_to_json_internal(Datum array, StringInfo result)
+ {
+ ArrayType *v = DatumGetArrayTypeP(array);
+ Oid element_type = ARR_ELEMTYPE(v);
+ int *dim;
+ int ndim;
+ int nitems;
+ int count = 0;
+ Datum *elements;
+ bool *nulls;
+
+ int16 typlen;
+ bool typbyval;
+ char typalign,
+ typdelim;
+ Oid typioparam;
+ Oid typoutputfunc;
+ TYPCATEGORY tcategory;
+
+ ndim = ARR_NDIM(v);
+ dim = ARR_DIMS(v);
+ nitems = ArrayGetNItems(ndim, dim);
+
+ if (nitems <= 0)
+ {
+ appendStringInfoString(result,"[]");
+ return;
+ }
+
+ get_type_io_data(element_type, IOFunc_output,
+ &typlen, &typbyval, &typalign,
+ &typdelim, &typioparam, &typoutputfunc);
+
+
+ deconstruct_array(v, element_type, typlen, typbyval,
+ typalign, &elements, &nulls,
+ &nitems);
+
+ /* can't have an array of arrays, so this is the only special case here */
+ if (element_type == RECORDOID)
+ tcategory = TYPCATEGORY_COMPOSITE;
+ else
+ tcategory = TypeCategory(element_type);
+
+ array_dim_to_json(result,0,ndim,dim,elements,&count,tcategory, typoutputfunc);
+
+ pfree(elements);
+ pfree(nulls);
+ }
+
+
+ static void
+ composite_to_json(Datum composite, StringInfo result)
+ {
+ HeapTupleHeader td;
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupdesc;
+ HeapTupleData tmptup, *tuple;
+ int i;
+ bool needsep = false;
+
+ td = DatumGetHeapTupleHeader(composite);
+
+ /* Extract rowtype info and find a tupdesc */
+ tupType = HeapTupleHeaderGetTypeId(td);
+ tupTypmod = HeapTupleHeaderGetTypMod(td);
+ tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+
+ /* Build a temporary HeapTuple control structure */
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+ tmptup.t_data = td;
+ tuple = &tmptup;
+
+ appendStringInfoChar(result,'{');
+
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Datum val, origval;
+ bool isnull;
+ char *attname;
+ TYPCATEGORY tcategory;
+ Oid typoutput;
+ bool typisvarlena;
+
+ if (tupdesc->attrs[i]->attisdropped)
+ continue;
+
+ if (needsep)
+ appendStringInfoChar(result,',');
+ needsep = true;
+
+ attname = NameStr(tupdesc->attrs[i]->attname);
+ escape_json(result,attname);
+ appendStringInfoChar(result,':');
+
+ origval = heap_getattr(tuple, i + 1, tupdesc, &isnull);
+
+ if (tupdesc->attrs[i]->atttypid == RECORDARRAYOID)
+ tcategory = TYPCATEGORY_ARRAY;
+ else if (tupdesc->attrs[i]->atttypid == RECORDOID)
+ tcategory = TYPCATEGORY_COMPOSITE;
+ else
+ tcategory = TypeCategory(tupdesc->attrs[i]->atttypid);
+
+ /* XXX TODO: cache this info */
+ getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
+ &typoutput, &typisvarlena);
+
+ /*
+ * If we have a toasted datum, forcibly detoast it here to avoid memory
+ * leakage inside the type's output routine.
+ */
+ if (typisvarlena)
+ val = PointerGetDatum(PG_DETOAST_DATUM(origval));
+ else
+ val = origval;
+
+ datum_to_json(val, result, tcategory, typoutput);
+
+ /* Clean up detoasted copy, if any */
+ if (val != origval)
+ pfree(DatumGetPointer(val));
+
+ }
+
+ appendStringInfoChar(result,'}');
+ ReleaseTupleDesc(tupdesc);
+
+
+ }
+
+ static void
+ sql_row_to_json(int rownum, StringInfo result, result_metadata *meta)
+ {
+ int i;
+
+ appendStringInfoChar(result, '{');
+
+ for (i = 1; i <= SPI_tuptable->tupdesc->natts; i++)
+ {
+ Datum colval;
+ bool isnull;
+
+ if (i > 1)
+ appendStringInfoChar(result, ',');
+
+ escape_json(result,meta[i-1].colname);
+ appendStringInfoChar(result,':');
+
+ /* XXX should we get a detoasted copy of this? */
+
+ colval = SPI_getbinval(SPI_tuptable->vals[rownum],
+ SPI_tuptable->tupdesc,
+ i,
+ &isnull);
+
+ datum_to_json(colval, result, meta[i-1].tcategory, meta[i-1].toutputfunc);
+ }
+
+ appendStringInfoChar(result, '}');
+
+
+ }
+
+ extern Datum
+ query_to_json(PG_FUNCTION_ARGS)
+ {
+ text *qtext = PG_GETARG_TEXT_PP(0);
+ bool lf_between_records = PG_GETARG_BOOL(1);
+ char *query = text_to_cstring(qtext);
+ StringInfo result;
+ result_metadata *meta;
+ int i;
+ char *sep;
+
+ sep = lf_between_records ? ",\n " : ",";
+
+ result = makeStringInfo();
+
+ appendStringInfoChar(result,'[');
+
+ SPI_connect();
+
+ if (SPI_execute(query, true, 0) != SPI_OK_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_EXCEPTION),
+ errmsg("invalid query")));
+
+ meta = palloc(SPI_tuptable->tupdesc->natts * sizeof(result_metadata));
+
+ for (i = 1; i <= SPI_tuptable->tupdesc->natts; i++)
+ {
+ result_metadata *m = &(meta[i-1]);
+
+ m->colname = SPI_fname(SPI_tuptable->tupdesc, i);
+ m->typid = SPI_gettypeid(SPI_tuptable->tupdesc, i);
+ /*
+ * for our purposes RECORDARRAYOID is an ARRAY
+ * and RECORDOID is a composite
+ */
+ if (m->typid == RECORDARRAYOID)
+ m->tcategory = TYPCATEGORY_ARRAY;
+ else if (m->typid == RECORDOID)
+ m->tcategory = TYPCATEGORY_COMPOSITE;
+ else
+ m->tcategory = TypeCategory(m->typid);
+ getTypeOutputInfo(m->typid, &(m->toutputfunc), &(m->tisvarlena));
+ }
+
+ for (i = 0; i < SPI_processed; i++)
+ {
+ if (i > 0)
+ appendStringInfoString(result,sep);
+
+ sql_row_to_json(i, result, meta);
+ }
+
+ SPI_finish();
+
+ appendStringInfoChar(result,']');
+
+ PG_RETURN_TEXT_P(cstring_to_text(result->data));
+
+ }
+
+ extern Datum
+ array_to_json(PG_FUNCTION_ARGS)
+ {
+ Datum array = PG_GETARG_DATUM(0);
+ StringInfo result;
+
+ result = makeStringInfo();
+
+ array_to_json_internal(array, result);
+
+ PG_RETURN_TEXT_P(cstring_to_text(result->data));
+ };
+
+ /*
+ * Produce a JSON string literal, properly escaping characters in the text.
+ */
+ void
+ escape_json(StringInfo buf, const char *str)
+ {
+ const char *p;
+
+ appendStringInfoCharMacro(buf, '\"');
+ for (p = str; *p; p++)
+ {
+ switch (*p)
+ {
+ case '\b':
+ appendStringInfoString(buf, "\\b");
+ break;
+ case '\f':
+ appendStringInfoString(buf, "\\f");
+ break;
+ case '\n':
+ appendStringInfoString(buf, "\\n");
+ break;
+ case '\r':
+ appendStringInfoString(buf, "\\r");
+ break;
+ case '\t':
+ appendStringInfoString(buf, "\\t");
+ break;
+ case '"':
+ appendStringInfoString(buf, "\\\"");
+ break;
+ case '\\':
+ appendStringInfoString(buf, "\\\\");
+ break;
+ default:
+ if ((unsigned char) *p < ' ')
+ appendStringInfo(buf, "\\u%04x", (int) *p);
+ else
+ appendStringInfoCharMacro(buf, *p);
+ break;
+ }
+ }
+ appendStringInfoCharMacro(buf, '\"');
+ }
+
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4006,4011 **** DESCR("determine if a string is well formed XML document");
--- 4006,4025 ----
DATA(insert OID = 3053 ( xml_is_well_formed_content PGNSP PGUID 12 1 0 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 */
+ DATA(insert OID = 321 ( json_in PGNSP PGUID 12 1 0 0 0 f f f t f s 1 0 114 "2275" _null_ _null_ _null_ _null_ json_in _null_ _null_ _null_ ));
+ DESCR("I/O");
+ DATA(insert OID = 322 ( json_out PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2275 "114" _null_ _null_ _null_ _null_ json_out _null_ _null_ _null_ ));
+ DESCR("I/O");
+ DATA(insert OID = 323 ( json_recv PGNSP PGUID 12 1 0 0 0 f f f t f s 1 0 114 "2281" _null_ _null_ _null_ _null_ json_recv _null_ _null_ _null_ ));
+ DESCR("I/O");
+ DATA(insert OID = 324 ( json_send PGNSP PGUID 12 1 0 0 0 f f f t f s 1 0 17 "114" _null_ _null_ _null_ _null_ json_send _null_ _null_ _null_ ));
+ DESCR("I/O");
+ DATA(insert OID = 3144 ( query_to_json PGNSP PGUID 12 1 0 0 0 f f f t f s 2 0 114 "25 16" _null_ _null_ _null_ _null_ query_to_json _null_ _null_ _null_ ));
+ DESCR("I/O");
+ DATA(insert OID = 3145 ( array_to_json PGNSP PGUID 12 1 0 0 0 f f f t f s 1 0 114 "2277" _null_ _null_ _null_ _null_ array_to_json _null_ _null_ _null_ ));
+ DESCR("I/O");
+
/* uuid */
DATA(insert OID = 2952 ( uuid_in PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2950 "2275" _null_ _null_ _null_ _null_ uuid_in _null_ _null_ _null_ ));
DESCR("I/O");
*** a/src/include/catalog/pg_type.h
--- b/src/include/catalog/pg_type.h
***************
*** 350,359 **** DATA(insert OID = 81 ( pg_proc PGNSP PGUID -1 f c C f t \054 1255 0 0 record_i
--- 350,362 ----
DATA(insert OID = 83 ( pg_class PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 0 _null_ _null_ _null_ ));
/* OIDS 100 - 199 */
+ DATA(insert OID = 114 ( json PGNSP PGUID -1 f b U f t \054 0 0 199 json_in json_out json_recv json_send - - - i x f 0 -1 0 0 _null_ _null_ _null_ ));
+ #define JSONOID 114
DATA(insert OID = 142 ( xml PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 0 _null_ _null_ _null_ ));
DESCR("XML content");
#define XMLOID 142
DATA(insert OID = 143 ( _xml PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ _null_ ));
+ DATA(insert OID = 199 ( _json PGNSP PGUID -1 f b A f t \054 0 114 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ _null_ ));
DATA(insert OID = 194 ( pg_node_tree PGNSP PGUID -1 f b S f t \054 0 0 0 pg_node_tree_in pg_node_tree_out pg_node_tree_recv pg_node_tree_send - - - i x f 0 -1 0 100 _null_ _null_ _null_ ));
DESCR("string representing an internal node tree");
*** /dev/null
--- b/src/include/utils/json.h
***************
*** 0 ****
--- 1,26 ----
+ /*-------------------------------------------------------------------------
+ *
+ * json.h
+ * Declarations for JSON data type support.
+ *
+ * Portions Copyright (c) 1996-2012, 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"
+
+ 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 query_to_json(PG_FUNCTION_ARGS);
+ extern void escape_json(StringInfo buf, const char *str);
+
+ #endif /* XML_H */
*** /dev/null
--- b/src/test/regress/expected/json.out
***************
*** 0 ****
--- 1,326 ----
+ -- Strings.
+ SELECT '""'::json; -- OK.
+ json
+ ------
+ ""
+ (1 row)
+
+ SELECT $$''$$::json; -- ERROR, single quotes are not allowed
+ ERROR: invalid input syntax for type json
+ LINE 1: SELECT $$''$$::json;
+ ^
+ DETAIL: line 1: Token "'" is invalid.
+ SELECT '"abc"'::json; -- OK
+ json
+ -------
+ "abc"
+ (1 row)
+
+ SELECT '"abc'::json; -- ERROR, quotes not closed
+ ERROR: invalid input syntax for type json
+ LINE 1: SELECT '"abc'::json;
+ ^
+ DETAIL: line 1: Token ""abc" is invalid.
+ SELECT '"abc
+ def"'::json; -- ERROR, unescaped newline in string constant
+ ERROR: invalid input syntax for type json
+ LINE 1: SELECT '"abc
+ ^
+ DETAIL: line 1: Character "
+ " must be escaped.
+ SELECT '"\n\"\\"'::json; -- OK, legal escapes
+ json
+ ----------
+ "\n\"\\"
+ (1 row)
+
+ SELECT '"\v"'::json; -- ERROR, not a valid JSON escape
+ ERROR: invalid input syntax for type json
+ LINE 1: SELECT '"\v"'::json;
+ ^
+ DETAIL: line 1: Invalid escape "\v".
+ SELECT '"\u"'::json; -- ERROR, incomplete escape
+ ERROR: invalid input syntax for type json
+ LINE 1: SELECT '"\u"'::json;
+ ^
+ DETAIL: line 1: "\u" must be followed by four hexadecimal digits.
+ SELECT '"\u00"'::json; -- ERROR, incomplete escape
+ ERROR: invalid input syntax for type json
+ LINE 1: SELECT '"\u00"'::json;
+ ^
+ DETAIL: line 1: "\u" must be followed by four hexadecimal digits.
+ SELECT '"\u000g"'::json; -- ERROR, g is not a hex digit
+ ERROR: invalid input syntax for type json
+ LINE 1: SELECT '"\u000g"'::json;
+ ^
+ DETAIL: line 1: "\u" must be followed by four hexadecimal digits.
+ SELECT '"\u0000"'::json; -- OK, legal escape
+ json
+ ----------
+ "\u0000"
+ (1 row)
+
+ SELECT '"\uaBcD"'::json; -- OK, uppercase and lower case both OK
+ json
+ ----------
+ "\uaBcD"
+ (1 row)
+
+ -- Numbers.
+ SELECT '1'::json; -- OK
+ json
+ ------
+ 1
+ (1 row)
+
+ SELECT '0'::json; -- OK
+ json
+ ------
+ 0
+ (1 row)
+
+ SELECT '01'::json; -- ERROR, not valid according to JSON spec
+ ERROR: invalid input syntax for type json
+ LINE 1: SELECT '01'::json;
+ ^
+ DETAIL: line 1: Token "01" is invalid.
+ SELECT '0.1'::json; -- OK
+ json
+ ------
+ 0.1
+ (1 row)
+
+ SELECT '9223372036854775808'::json; -- OK, even though it's too large for int8
+ json
+ ---------------------
+ 9223372036854775808
+ (1 row)
+
+ SELECT '1e100'::json; -- OK
+ json
+ -------
+ 1e100
+ (1 row)
+
+ SELECT '1.3e100'::json; -- OK
+ json
+ ---------
+ 1.3e100
+ (1 row)
+
+ SELECT '1f2'::json; -- ERROR
+ ERROR: invalid input syntax for type json
+ LINE 1: SELECT '1f2'::json;
+ ^
+ DETAIL: line 1: Token "1f2" is invalid.
+ -- Arrays.
+ SELECT '[]'::json; -- OK
+ json
+ ------
+ []
+ (1 row)
+
+ SELECT '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'::json; -- OK
+ json
+ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
+ (1 row)
+
+ SELECT '[1,2]'::json; -- OK
+ json
+ -------
+ [1,2]
+ (1 row)
+
+ SELECT '[1,2,]'::json; -- ERROR, trailing comma
+ ERROR: invalid input syntax for type json: "[1,2,]"
+ LINE 1: SELECT '[1,2,]'::json;
+ ^
+ DETAIL: line 1: Expected string, number, object, array, true, false, or null, but found "]".
+ SELECT '[1,2'::json; -- ERROR, no closing bracket
+ ERROR: invalid input syntax for type json: "[1,2"
+ LINE 1: SELECT '[1,2'::json;
+ ^
+ DETAIL: The input string ended unexpectedly.
+ SELECT '[1,[2]'::json; -- ERROR, no closing bracket
+ ERROR: invalid input syntax for type json: "[1,[2]"
+ LINE 1: SELECT '[1,[2]'::json;
+ ^
+ DETAIL: The input string ended unexpectedly.
+ -- Objects.
+ SELECT '{}'::json; -- OK
+ json
+ ------
+ {}
+ (1 row)
+
+ SELECT '{"abc"}'::json; -- ERROR, no value
+ ERROR: invalid input syntax for type json: "{"abc"}"
+ LINE 1: SELECT '{"abc"}'::json;
+ ^
+ DETAIL: line 1: Expected ":", but found "}".
+ SELECT '{"abc":1}'::json; -- OK
+ json
+ -----------
+ {"abc":1}
+ (1 row)
+
+ SELECT '{1:"abc"}'::json; -- ERROR, keys must be strings
+ ERROR: invalid input syntax for type json: "{1:"abc"}"
+ LINE 1: SELECT '{1:"abc"}'::json;
+ ^
+ DETAIL: line 1: Expected string or "}", but found "1".
+ SELECT '{"abc",1}'::json; -- ERROR, wrong separator
+ ERROR: invalid input syntax for type json: "{"abc",1}"
+ LINE 1: SELECT '{"abc",1}'::json;
+ ^
+ DETAIL: line 1: Expected ":", but found ",".
+ SELECT '{"abc"=1}'::json; -- ERROR, totally wrong separator
+ ERROR: invalid input syntax for type json
+ LINE 1: SELECT '{"abc"=1}'::json;
+ ^
+ DETAIL: line 1: Token "=" is invalid.
+ SELECT '{"abc"::1}'::json; -- ERROR, another wrong separator
+ ERROR: invalid input syntax for type json: "{"abc"::1}"
+ LINE 1: SELECT '{"abc"::1}'::json;
+ ^
+ DETAIL: line 1: Expected string, number, object, array, true, false, or null, but found ":".
+ SELECT '{"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}'::json; -- OK
+ json
+ ---------------------------------------------------------
+ {"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}
+ (1 row)
+
+ SELECT '{"abc":1:2}'::json; -- ERROR, colon in wrong spot
+ ERROR: invalid input syntax for type json: "{"abc":1:2}"
+ LINE 1: SELECT '{"abc":1:2}'::json;
+ ^
+ DETAIL: line 1: Expected "," or "}", but found ":".
+ SELECT '{"abc":1,3}'::json; -- ERROR, no value
+ ERROR: invalid input syntax for type json: "{"abc":1,3}"
+ LINE 1: SELECT '{"abc":1,3}'::json;
+ ^
+ DETAIL: line 1: Expected string, but found "3".
+ -- Miscellaneous stuff.
+ SELECT 'true'::json; -- OK
+ json
+ ------
+ true
+ (1 row)
+
+ SELECT 'false'::json; -- OK
+ json
+ -------
+ false
+ (1 row)
+
+ SELECT 'null'::json; -- OK
+ json
+ ------
+ null
+ (1 row)
+
+ SELECT ' true '::json; -- OK, even with extra whitespace
+ json
+ --------
+ true
+ (1 row)
+
+ SELECT 'true false'::json; -- ERROR, too many values
+ ERROR: invalid input syntax for type json: "true false"
+ LINE 1: SELECT 'true false'::json;
+ ^
+ DETAIL: line 1: Expected end of input, but found "false".
+ SELECT 'true, false'::json; -- ERROR, too many values
+ ERROR: invalid input syntax for type json: "true, false"
+ LINE 1: SELECT 'true, false'::json;
+ ^
+ DETAIL: line 1: Expected end of input, but found ",".
+ SELECT 'truf'::json; -- ERROR, not a keyword
+ ERROR: invalid input syntax for type json
+ LINE 1: SELECT 'truf'::json;
+ ^
+ DETAIL: line 1: Token "truf" is invalid.
+ SELECT 'trues'::json; -- ERROR, not a keyword
+ ERROR: invalid input syntax for type json
+ LINE 1: SELECT 'trues'::json;
+ ^
+ DETAIL: line 1: Token "trues" is invalid.
+ SELECT ''::json; -- ERROR, no value
+ ERROR: invalid input syntax for type json: ""
+ LINE 1: SELECT ''::json;
+ ^
+ DETAIL: The input string ended unexpectedly.
+ SELECT ' '::json; -- ERROR, no value
+ ERROR: invalid input syntax for type json: " "
+ LINE 1: SELECT ' '::json;
+ ^
+ DETAIL: The input string ended unexpectedly.
+ -- query_to_json
+ SELECT query_to_json('select 1 as a',false);
+ query_to_json
+ ---------------
+ [{"a":1}]
+ (1 row)
+
+ SELECT query_to_json('select x as b, x * 2 as c from generate_series(1,3) x',false);
+ query_to_json
+ ---------------------------------------------
+ [{"b":1,"c":2},{"b":2,"c":4},{"b":3,"c":6}]
+ (1 row)
+
+ SELECT query_to_json('select x as b, x * 2 as c from generate_series(1,3) x',true);
+ query_to_json
+ -----------------
+ [{"b":1,"c":2},+
+ {"b":2,"c":4},+
+ {"b":3,"c":6}]
+ (1 row)
+
+ SELECT query_to_json('
+ SELECT $$a$$ || x AS b,
+ y AS c,
+ ARRAY[ROW(x.*,ARRAY[1,2,3]),
+ ROW(y.*,ARRAY[4,5,6])] AS z
+ FROM generate_series(1,2) x,
+ generate_series(4,5) y',true);
+ query_to_json
+ ----------------------------------------------------------------------
+ [{"b":"a1","c":4,"z":[{"f1":1,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]},+
+ {"b":"a1","c":5,"z":[{"f1":1,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]},+
+ {"b":"a2","c":4,"z":[{"f1":2,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]},+
+ {"b":"a2","c":5,"z":[{"f1":2,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]}]
+ (1 row)
+
+ SELECT query_to_json('select array_agg(x) as d from generate_series(5,10) x',false);
+ query_to_json
+ ------------------------
+ [{"d":[5,6,7,8,9,10]}]
+ (1 row)
+
+ -- array_to_json
+ SELECT array_to_json(array_agg(x))
+ FROM generate_series(1,10) x;
+ array_to_json
+ ------------------------
+ [1,2,3,4,5,6,7,8,9,10]
+ (1 row)
+
+ SELECT array_to_json(array_agg(row(z.*)))
+ FROM (SELECT $$a$$ || x AS b,
+ y AS c,
+ ARRAY[ROW(x.*,ARRAY[1,2,3]),
+ ROW(y.*,ARRAY[4,5,6])] AS z
+ FROM generate_series(1,2) x,
+ generate_series(4,5) y) z;
+ array_to_json
+ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [{"f1":"a1","f2":4,"f3":[{"f1":1,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]},{"f1":"a1","f2":5,"f3":[{"f1":1,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]},{"f1":"a2","f2":4,"f3":[{"f1":2,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]},{"f1":"a2","f2":5,"f3":[{"f1":2,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]}]
+ (1 row)
+
+ SELECT array_to_json('{{1,5},{99,100}}'::int[]);
+ array_to_json
+ ------------------
+ [[1,5],[99,100]]
+ (1 row)
+
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
***************
*** 92,98 **** test: rules
# ----------
# Another group of parallel tests
# ----------
! test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock
# ----------
# Another group of parallel tests
--- 92,98 ----
# ----------
# Another group of parallel tests
# ----------
! test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json
# ----------
# Another group of parallel tests
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
***************
*** 109,114 **** test: window
--- 109,115 ----
test: xmlmap
test: functional_deps
test: advisory_lock
+ test: json
test: plancache
test: limit
test: plpgsql
*** /dev/null
--- b/src/test/regress/sql/json.sql
***************
*** 0 ****
--- 1,84 ----
+ -- Strings.
+ SELECT '""'::json; -- OK.
+ SELECT $$''$$::json; -- ERROR, single quotes are not allowed
+ SELECT '"abc"'::json; -- OK
+ SELECT '"abc'::json; -- ERROR, quotes not closed
+ SELECT '"abc
+ def"'::json; -- ERROR, unescaped newline in string constant
+ SELECT '"\n\"\\"'::json; -- OK, legal escapes
+ SELECT '"\v"'::json; -- ERROR, not a valid JSON escape
+ SELECT '"\u"'::json; -- ERROR, incomplete escape
+ SELECT '"\u00"'::json; -- ERROR, incomplete escape
+ SELECT '"\u000g"'::json; -- ERROR, g is not a hex digit
+ SELECT '"\u0000"'::json; -- OK, legal escape
+ SELECT '"\uaBcD"'::json; -- OK, uppercase and lower case both OK
+
+ -- Numbers.
+ SELECT '1'::json; -- OK
+ SELECT '0'::json; -- OK
+ SELECT '01'::json; -- ERROR, not valid according to JSON spec
+ SELECT '0.1'::json; -- OK
+ SELECT '9223372036854775808'::json; -- OK, even though it's too large for int8
+ SELECT '1e100'::json; -- OK
+ SELECT '1.3e100'::json; -- OK
+ SELECT '1f2'::json; -- ERROR
+
+ -- Arrays.
+ SELECT '[]'::json; -- OK
+ SELECT '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'::json; -- OK
+ SELECT '[1,2]'::json; -- OK
+ SELECT '[1,2,]'::json; -- ERROR, trailing comma
+ SELECT '[1,2'::json; -- ERROR, no closing bracket
+ SELECT '[1,[2]'::json; -- ERROR, no closing bracket
+
+ -- Objects.
+ SELECT '{}'::json; -- OK
+ SELECT '{"abc"}'::json; -- ERROR, no value
+ SELECT '{"abc":1}'::json; -- OK
+ SELECT '{1:"abc"}'::json; -- ERROR, keys must be strings
+ SELECT '{"abc",1}'::json; -- ERROR, wrong separator
+ SELECT '{"abc"=1}'::json; -- ERROR, totally wrong separator
+ SELECT '{"abc"::1}'::json; -- ERROR, another wrong separator
+ SELECT '{"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}'::json; -- OK
+ SELECT '{"abc":1:2}'::json; -- ERROR, colon in wrong spot
+ SELECT '{"abc":1,3}'::json; -- ERROR, no value
+
+ -- Miscellaneous stuff.
+ SELECT 'true'::json; -- OK
+ SELECT 'false'::json; -- OK
+ SELECT 'null'::json; -- OK
+ SELECT ' true '::json; -- OK, even with extra whitespace
+ SELECT 'true false'::json; -- ERROR, too many values
+ SELECT 'true, false'::json; -- ERROR, too many values
+ SELECT 'truf'::json; -- ERROR, not a keyword
+ SELECT 'trues'::json; -- ERROR, not a keyword
+ SELECT ''::json; -- ERROR, no value
+ SELECT ' '::json; -- ERROR, no value
+
+ -- query_to_json
+ SELECT query_to_json('select 1 as a',false);
+ SELECT query_to_json('select x as b, x * 2 as c from generate_series(1,3) x',false);
+ SELECT query_to_json('select x as b, x * 2 as c from generate_series(1,3) x',true);
+ SELECT query_to_json('
+ SELECT $$a$$ || x AS b,
+ y AS c,
+ ARRAY[ROW(x.*,ARRAY[1,2,3]),
+ ROW(y.*,ARRAY[4,5,6])] AS z
+ FROM generate_series(1,2) x,
+ generate_series(4,5) y',true);
+ SELECT query_to_json('select array_agg(x) as d from generate_series(5,10) x',false);
+
+ -- array_to_json
+ SELECT array_to_json(array_agg(x))
+ FROM generate_series(1,10) x;
+
+
+ SELECT array_to_json(array_agg(row(z.*)))
+ FROM (SELECT $$a$$ || x AS b,
+ y AS c,
+ ARRAY[ROW(x.*,ARRAY[1,2,3]),
+ ROW(y.*,ARRAY[4,5,6])] AS z
+ FROM generate_series(1,2) x,
+ generate_series(4,5) y) z;
+
+ SELECT array_to_json('{{1,5},{99,100}}'::int[]);