Built-in JSON data type.

Like the XML data type, we simply store JSON data as text, after checking
that it is valid.  More complex operations such as canonicalization and
comparison may come later, but this is enough for not.

There are a few open issues here, such as whether we should attempt to
detect UTF-8 surrogate pairs represented as \uXXXX\uYYYY, but this gets
the basic framework in place.
This commit is contained in:
Robert Haas 2012-01-31 11:48:23 -05:00
parent 4c6cedd1b0
commit 5384a73f98
11 changed files with 1059 additions and 5 deletions

View File

@ -269,6 +269,12 @@
<entry></entry>
<entry>XML data</entry>
</row>
<row>
<entry><type>json</type></entry>
<entry></entry>
<entry>JSON data</entry>
</row>
</tbody>
</tgroup>
</table>
@ -4169,6 +4175,32 @@ SET xmloption TO { DOCUMENT | CONTENT };
</sect2>
</sect1>
<sect1 id="datatype-json">
<title><acronym>JSON</> Type</title>
<indexterm zone="datatype-json">
<primary>JSON</primary>
</indexterm>
<para>
The <type>json</type> data type can be used to store JSON data. Such
data can also be stored as <type>text</type>, but the
<type>json</type> data type has the advantage of checking that each
stored value is a valid JSON value.
</para>
<para>
<productname>PostgreSQL</productname> allows only one server encoding
per database. It is therefore not possible for JSON to conform rigidly
to the specification unless the server encoding is UTF-8. Attempts to
directly include characters which cannot be represented in the server
encoding will fail; conversely, characters which can be represented in
the server encoding but not in UTF-8 will be allowed.
<literal>\uXXXX</literal> escapes are allowed regardless of the server
encoding, and are checked only for syntactic correctness.
</para>
</sect1>
&array;
&rowtypes;

View File

@ -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;
}

View File

@ -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 \

View File

@ -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;
}

View File

@ -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");

View File

@ -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");

24
src/include/utils/json.h Normal file
View File

@ -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 */

View File

@ -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.

View File

@ -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

View File

@ -109,6 +109,7 @@ test: window
test: xmlmap
test: functional_deps
test: advisory_lock
test: json
test: plancache
test: limit
test: plpgsql

View File

@ -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