aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/commands/explain.c11
-rw-r--r--src/backend/utils/adt/Makefile2
-rw-r--r--src/backend/utils/adt/json.c665
-rw-r--r--src/include/catalog/pg_proc.h10
-rw-r--r--src/include/catalog/pg_type.h3
-rw-r--r--src/include/utils/json.h24
-rw-r--r--src/test/regress/expected/json.out258
-rw-r--r--src/test/regress/parallel_schedule2
-rw-r--r--src/test/regress/serial_schedule1
-rw-r--r--src/test/regress/sql/json.sql56
10 files changed, 1027 insertions, 5 deletions
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index e297e9cfb90..e35ac590301 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -242,7 +242,7 @@ ExplainResultDesc(ExplainStmt *stmt)
{
TupleDesc tupdesc;
ListCell *lc;
- bool xml = false;
+ Oid result_type = TEXTOID;
/* Check for XML format option */
foreach(lc, stmt->options)
@@ -253,7 +253,12 @@ ExplainResultDesc(ExplainStmt *stmt)
{
char *p = defGetString(opt);
- xml = (strcmp(p, "xml") == 0);
+ 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,7 +266,7 @@ 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);
+ result_type, -1, 0);
return tupdesc;
}
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 5f968b011fc..c635c38f5b8 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -18,7 +18,7 @@ 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 \
+ 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 \
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
new file mode 100644
index 00000000000..cbb81d1bf37
--- /dev/null
+++ b/src/backend/utils/adt/json.c
@@ -0,0 +1,665 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "lib/stringinfo.h"
+#include "libpq/pqformat.h"
+#include "mb/pg_wchar.h"
+#include "utils/builtins.h"
+#include "utils/json.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;
+
+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);
+
+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)));
+ }
+ }
+
+ /* 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;
+}
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 96551ccae9e..5dc6d05478f 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4022,6 +4022,16 @@ DESCR("determine if a string is well formed XML document");
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");
+
/* 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");
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index d9855a79f86..e4bca552973 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -352,10 +352,13 @@ DATA(insert OID = 81 ( pg_proc PGNSP PGUID -1 f c C f t \054 1255 0 0 record_i
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");
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
new file mode 100644
index 00000000000..ee87fd61d7c
--- /dev/null
+++ b/src/include/utils/json.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+
+#endif /* XML_H */
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
new file mode 100644
index 00000000000..5ef65f7c972
--- /dev/null
+++ b/src/test/regress/expected/json.out
@@ -0,0 +1,258 @@
+-- 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.
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 3bedad07c73..d6105f85678 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -92,7 +92,7 @@ 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
+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
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 0b64569849c..90726ce97b3 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -109,6 +109,7 @@ test: window
test: xmlmap
test: functional_deps
test: advisory_lock
+test: json
test: plancache
test: limit
test: plpgsql
diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql
new file mode 100644
index 00000000000..440398b9b9f
--- /dev/null
+++ b/src/test/regress/sql/json.sql
@@ -0,0 +1,56 @@
+-- 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