JSON_TABLE

This feature allows jsonb data to be treated as a table and thus used in
a FROM clause like other tabular data. Data can be selected from the
jsonb using jsonpath expressions, and hoisted out of nested structures
in the jsonb to form multiple rows, more or less like an outer join.

Nikita Glukhov

Reviewers have included (in no particular order) Andres Freund, Alexander
Korotkov, Pavel Stehule, Andrew Alsup, Erik Rijkers, Zhihong Yu (whose
name I previously misspelled), Himanshu Upadhyaya, Daniel Gustafsson,
Justin Pryzby.

Discussion: https://postgr.es/m/7e2cb85d-24cf-4abb-30a5-1a33715959bd@postgrespro.ru
This commit is contained in:
Andrew Dunstan 2022-04-04 15:36:03 -04:00
parent c42a6fc41d
commit 4e34747c88
31 changed files with 2605 additions and 34 deletions

View File

@ -3796,7 +3796,13 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
break;
case T_TableFuncScan:
Assert(rte->rtekind == RTE_TABLEFUNC);
objectname = "xmltable";
if (rte->tablefunc)
if (rte->tablefunc->functype == TFT_XMLTABLE)
objectname = "xmltable";
else /* Must be TFT_JSON_TABLE */
objectname = "json_table";
else
objectname = NULL;
objecttag = "Table Function Name";
break;
case T_ValuesScan:

View File

@ -2635,6 +2635,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
var->typmod = exprTypmod((Node *) argexpr);
var->estate = ExecInitExpr(argexpr, state->parent);
var->econtext = NULL;
var->mcxt = NULL;
var->evaluated = false;
var->value = (Datum) 0;
var->isnull = true;

View File

@ -4602,6 +4602,7 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
case JSON_BEHAVIOR_NULL:
case JSON_BEHAVIOR_UNKNOWN:
case JSON_BEHAVIOR_EMPTY:
*is_null = true;
return (Datum) 0;
@ -4694,8 +4695,14 @@ EvalJsonPathVar(void *cxt, char *varName, int varNameLen,
if (!var->evaluated)
{
MemoryContext oldcxt = var->mcxt ?
MemoryContextSwitchTo(var->mcxt) : NULL;
var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);
var->evaluated = true;
if (oldcxt)
MemoryContextSwitchTo(oldcxt);
}
if (var->isnull)
@ -4843,6 +4850,7 @@ ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op,
PG_CATCH();
{
ErrorData *edata;
int ecategory;
/* Save error info in oldcontext */
MemoryContextSwitchTo(oldcontext);
@ -4854,8 +4862,10 @@ ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op,
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) !=
ERRCODE_DATA_EXCEPTION)
ecategory = ERRCODE_TO_CATEGORY(edata->sqlerrcode);
if (ecategory != ERRCODE_DATA_EXCEPTION && /* jsonpath and other data errors */
ecategory != ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION) /* domain errors */
ReThrowError(edata);
res = (Datum) 0;
@ -4981,6 +4991,10 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
break;
}
case JSON_TABLE_OP:
*resnull = false;
return item;
default:
elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
return (Datum) 0;

View File

@ -28,6 +28,7 @@
#include "miscadmin.h"
#include "nodes/execnodes.h"
#include "utils/builtins.h"
#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/xml.h"
@ -161,8 +162,9 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
scanstate->ss.ps.qual =
ExecInitQual(node->scan.plan.qual, &scanstate->ss.ps);
/* Only XMLTABLE is supported currently */
scanstate->routine = &XmlTableRoutine;
/* Only XMLTABLE and JSON_TABLE are supported currently */
scanstate->routine =
tf->functype == TFT_XMLTABLE ? &XmlTableRoutine : &JsonbTableRoutine;
scanstate->perTableCxt =
AllocSetContextCreate(CurrentMemoryContext,
@ -381,14 +383,17 @@ tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc)
routine->SetNamespace(tstate, ns_name, ns_uri);
}
/* Install the row filter expression into the table builder context */
value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("row filter expression must not be null")));
if (routine->SetRowFilter)
{
/* Install the row filter expression into the table builder context */
value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("row filter expression must not be null")));
routine->SetRowFilter(tstate, TextDatumGetCString(value));
routine->SetRowFilter(tstate, TextDatumGetCString(value));
}
/*
* Install the column filter expressions into the table builder context.

View File

@ -1394,6 +1394,7 @@ _copyTableFunc(const TableFunc *from)
{
TableFunc *newnode = makeNode(TableFunc);
COPY_SCALAR_FIELD(functype);
COPY_NODE_FIELD(ns_uris);
COPY_NODE_FIELD(ns_names);
COPY_NODE_FIELD(docexpr);
@ -1404,7 +1405,9 @@ _copyTableFunc(const TableFunc *from)
COPY_NODE_FIELD(colcollations);
COPY_NODE_FIELD(colexprs);
COPY_NODE_FIELD(coldefexprs);
COPY_NODE_FIELD(colvalexprs);
COPY_BITMAPSET_FIELD(notnulls);
COPY_NODE_FIELD(plan);
COPY_SCALAR_FIELD(ordinalitycol);
COPY_LOCATION_FIELD(location);
@ -2683,6 +2686,76 @@ _copyJsonArgument(const JsonArgument *from)
return newnode;
}
/*
* _copyJsonTable
*/
static JsonTable *
_copyJsonTable(const JsonTable *from)
{
JsonTable *newnode = makeNode(JsonTable);
COPY_NODE_FIELD(common);
COPY_NODE_FIELD(columns);
COPY_NODE_FIELD(on_error);
COPY_NODE_FIELD(alias);
COPY_SCALAR_FIELD(location);
return newnode;
}
/*
* _copyJsonTableColumn
*/
static JsonTableColumn *
_copyJsonTableColumn(const JsonTableColumn *from)
{
JsonTableColumn *newnode = makeNode(JsonTableColumn);
COPY_SCALAR_FIELD(coltype);
COPY_STRING_FIELD(name);
COPY_NODE_FIELD(typeName);
COPY_STRING_FIELD(pathspec);
COPY_SCALAR_FIELD(format);
COPY_SCALAR_FIELD(wrapper);
COPY_SCALAR_FIELD(omit_quotes);
COPY_NODE_FIELD(columns);
COPY_NODE_FIELD(on_empty);
COPY_NODE_FIELD(on_error);
COPY_SCALAR_FIELD(location);
return newnode;
}
/*
* _copyJsonTableParent
*/
static JsonTableParent *
_copyJsonTableParent(const JsonTableParent *from)
{
JsonTableParent *newnode = makeNode(JsonTableParent);
COPY_NODE_FIELD(path);
COPY_NODE_FIELD(child);
COPY_SCALAR_FIELD(colMin);
COPY_SCALAR_FIELD(colMax);
return newnode;
}
/*
* _copyJsonTableSibling
*/
static JsonTableSibling *
_copyJsonTableSibling(const JsonTableSibling *from)
{
JsonTableSibling *newnode = makeNode(JsonTableSibling);
COPY_NODE_FIELD(larg);
COPY_NODE_FIELD(rarg);
return newnode;
}
/* ****************************************************************
* pathnodes.h copy functions
*
@ -5850,6 +5923,18 @@ copyObjectImpl(const void *from)
case T_JsonItemCoercions:
retval = _copyJsonItemCoercions(from);
break;
case T_JsonTable:
retval = _copyJsonTable(from);
break;
case T_JsonTableColumn:
retval = _copyJsonTableColumn(from);
break;
case T_JsonTableParent:
retval = _copyJsonTableParent(from);
break;
case T_JsonTableSibling:
retval = _copyJsonTableSibling(from);
break;
/*
* RELATION NODES

View File

@ -127,6 +127,7 @@ _equalRangeVar(const RangeVar *a, const RangeVar *b)
static bool
_equalTableFunc(const TableFunc *a, const TableFunc *b)
{
COMPARE_SCALAR_FIELD(functype);
COMPARE_NODE_FIELD(ns_uris);
COMPARE_NODE_FIELD(ns_names);
COMPARE_NODE_FIELD(docexpr);
@ -137,13 +138,65 @@ _equalTableFunc(const TableFunc *a, const TableFunc *b)
COMPARE_NODE_FIELD(colcollations);
COMPARE_NODE_FIELD(colexprs);
COMPARE_NODE_FIELD(coldefexprs);
COMPARE_NODE_FIELD(colvalexprs);
COMPARE_BITMAPSET_FIELD(notnulls);
COMPARE_NODE_FIELD(plan);
COMPARE_SCALAR_FIELD(ordinalitycol);
COMPARE_LOCATION_FIELD(location);
return true;
}
static bool
_equalJsonTable(const JsonTable *a, const JsonTable *b)
{
COMPARE_NODE_FIELD(common);
COMPARE_NODE_FIELD(columns);
COMPARE_NODE_FIELD(on_error);
COMPARE_NODE_FIELD(alias);
COMPARE_SCALAR_FIELD(location);
return true;
}
static bool
_equalJsonTableColumn(const JsonTableColumn *a, const JsonTableColumn *b)
{
COMPARE_SCALAR_FIELD(coltype);
COMPARE_STRING_FIELD(name);
COMPARE_NODE_FIELD(typeName);
COMPARE_STRING_FIELD(pathspec);
COMPARE_SCALAR_FIELD(format);
COMPARE_SCALAR_FIELD(wrapper);
COMPARE_SCALAR_FIELD(omit_quotes);
COMPARE_NODE_FIELD(columns);
COMPARE_NODE_FIELD(on_empty);
COMPARE_NODE_FIELD(on_error);
COMPARE_SCALAR_FIELD(location);
return true;
}
static bool
_equalJsonTableParent(const JsonTableParent *a, const JsonTableParent *b)
{
COMPARE_NODE_FIELD(path);
COMPARE_NODE_FIELD(child);
COMPARE_SCALAR_FIELD(colMin);
COMPARE_SCALAR_FIELD(colMax);
return true;
}
static bool
_equalJsonTableSibling(const JsonTableSibling *a, const JsonTableSibling *b)
{
COMPARE_NODE_FIELD(larg);
COMPARE_NODE_FIELD(rarg);
return true;
}
static bool
_equalIntoClause(const IntoClause *a, const IntoClause *b)
{
@ -3719,6 +3772,12 @@ equal(const void *a, const void *b)
case T_JsonItemCoercions:
retval = _equalJsonItemCoercions(a, b);
break;
case T_JsonTableParent:
retval = _equalJsonTableParent(a, b);
break;
case T_JsonTableSibling:
retval = _equalJsonTableSibling(a, b);
break;
/*
* RELATION NODES
@ -4341,6 +4400,12 @@ equal(const void *a, const void *b)
case T_JsonArgument:
retval = _equalJsonArgument(a, b);
break;
case T_JsonTable:
retval = _equalJsonTable(a, b);
break;
case T_JsonTableColumn:
retval = _equalJsonTableColumn(a, b);
break;
default:
elog(ERROR, "unrecognized node type: %d",

View File

@ -2466,6 +2466,8 @@ expression_tree_walker(Node *node,
return true;
if (walker(tf->coldefexprs, context))
return true;
if (walker(tf->colvalexprs, context))
return true;
}
break;
case T_JsonValueExpr:
@ -3513,6 +3515,7 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->rowexpr, tf->rowexpr, Node *);
MUTATE(newnode->colexprs, tf->colexprs, List *);
MUTATE(newnode->coldefexprs, tf->coldefexprs, List *);
MUTATE(newnode->colvalexprs, tf->colvalexprs, List *);
return (Node *) newnode;
}
break;
@ -4530,6 +4533,30 @@ raw_expression_tree_walker(Node *node,
return true;
}
break;
case T_JsonTable:
{
JsonTable *jt = (JsonTable *) node;
if (walker(jt->common, context))
return true;
if (walker(jt->columns, context))
return true;
}
break;
case T_JsonTableColumn:
{
JsonTableColumn *jtc = (JsonTableColumn *) node;
if (walker(jtc->typeName, context))
return true;
if (walker(jtc->on_empty, context))
return true;
if (walker(jtc->on_error, context))
return true;
if (jtc->coltype == JTC_NESTED && walker(jtc->columns, context))
return true;
}
break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));

View File

@ -1092,6 +1092,7 @@ _outTableFunc(StringInfo str, const TableFunc *node)
{
WRITE_NODE_TYPE("TABLEFUNC");
WRITE_ENUM_FIELD(functype, TableFuncType);
WRITE_NODE_FIELD(ns_uris);
WRITE_NODE_FIELD(ns_names);
WRITE_NODE_FIELD(docexpr);
@ -1102,7 +1103,9 @@ _outTableFunc(StringInfo str, const TableFunc *node)
WRITE_NODE_FIELD(colcollations);
WRITE_NODE_FIELD(colexprs);
WRITE_NODE_FIELD(coldefexprs);
WRITE_NODE_FIELD(colvalexprs);
WRITE_BITMAPSET_FIELD(notnulls);
WRITE_NODE_FIELD(plan);
WRITE_INT_FIELD(ordinalitycol);
WRITE_LOCATION_FIELD(location);
}
@ -1866,6 +1869,26 @@ _outJsonItemCoercions(StringInfo str, const JsonItemCoercions *node)
WRITE_NODE_FIELD(composite);
}
static void
_outJsonTableParent(StringInfo str, const JsonTableParent *node)
{
WRITE_NODE_TYPE("JSONTABPNODE");
WRITE_NODE_FIELD(path);
WRITE_NODE_FIELD(child);
WRITE_INT_FIELD(colMin);
WRITE_INT_FIELD(colMax);
}
static void
_outJsonTableSibling(StringInfo str, const JsonTableSibling *node)
{
WRITE_NODE_TYPE("JSONTABSNODE");
WRITE_NODE_FIELD(larg);
WRITE_NODE_FIELD(rarg);
}
/*****************************************************************************
*
* Stuff from pathnodes.h.
@ -4714,6 +4737,12 @@ outNode(StringInfo str, const void *obj)
case T_JsonItemCoercions:
_outJsonItemCoercions(str, obj);
break;
case T_JsonTableParent:
_outJsonTableParent(str, obj);
break;
case T_JsonTableSibling:
_outJsonTableSibling(str, obj);
break;
default:

View File

@ -571,6 +571,7 @@ _readTableFunc(void)
{
READ_LOCALS(TableFunc);
READ_ENUM_FIELD(functype, TableFuncType);
READ_NODE_FIELD(ns_uris);
READ_NODE_FIELD(ns_names);
READ_NODE_FIELD(docexpr);
@ -581,7 +582,9 @@ _readTableFunc(void)
READ_NODE_FIELD(colcollations);
READ_NODE_FIELD(colexprs);
READ_NODE_FIELD(coldefexprs);
READ_NODE_FIELD(colvalexprs);
READ_BITMAPSET_FIELD(notnulls);
READ_NODE_FIELD(plan);
READ_INT_FIELD(ordinalitycol);
READ_LOCATION_FIELD(location);
@ -1532,6 +1535,30 @@ _readJsonExpr(void)
READ_DONE();
}
static JsonTableParent *
_readJsonTableParent(void)
{
READ_LOCALS(JsonTableParent);
READ_NODE_FIELD(path);
READ_NODE_FIELD(child);
READ_INT_FIELD(colMin);
READ_INT_FIELD(colMax);
READ_DONE();
}
static JsonTableSibling *
_readJsonTableSibling(void)
{
READ_LOCALS(JsonTableSibling);
READ_NODE_FIELD(larg);
READ_NODE_FIELD(rarg);
READ_DONE();
}
/*
* _readJsonCoercion
*/
@ -3194,6 +3221,10 @@ parseNodeString(void)
return_value = _readJsonCoercion();
else if (MATCH("JSONITEMCOERCIONS", 17))
return_value = _readJsonItemCoercions();
else if (MATCH("JSONTABPNODE", 12))
return_value = _readJsonTableParent();
else if (MATCH("JSONTABSNODE", 12))
return_value = _readJsonTableSibling();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);

View File

@ -23,6 +23,7 @@ OBJS = \
parse_enr.o \
parse_expr.o \
parse_func.o \
parse_jsontable.o \
parse_merge.o \
parse_node.o \
parse_oper.o \

View File

@ -676,15 +676,25 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_object_aggregate_constructor
json_array_aggregate_constructor
json_path_specification
json_table
json_table_column_definition
json_table_ordinality_column_definition
json_table_regular_column_definition
json_table_formatted_column_definition
json_table_exists_column_definition
json_table_nested_columns
%type <list> json_name_and_value_list
json_value_expr_list
json_array_aggregate_order_by_clause_opt
json_arguments
json_passing_clause_opt
json_table_columns_clause
json_table_column_definition_list
%type <str> json_table_path_name
json_as_path_name_clause_opt
json_table_column_path_specification_clause_opt
%type <ival> json_encoding
json_encoding_clause_opt
@ -698,6 +708,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_behavior_true
json_behavior_false
json_behavior_unknown
json_behavior_empty
json_behavior_empty_array
json_behavior_empty_object
json_behavior_default
@ -705,6 +716,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_query_behavior
json_exists_error_behavior
json_exists_error_clause_opt
json_table_error_behavior
json_table_error_clause_opt
%type <on_behavior> json_value_on_behavior_clause_opt
json_query_on_behavior_clause_opt
@ -779,7 +792,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG
JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_VALUE
JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_TABLE JSON_VALUE
KEY KEYS KEEP
@ -790,8 +803,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
MINUTE_P MINVALUE MODE MONTH_P MOVE
NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE
NORMALIZE NORMALIZED
NAME_P NAMES NATIONAL NATURAL NCHAR NESTED NEW NEXT NFC NFD NFKC NFKD NO
NONE NORMALIZE NORMALIZED
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
NULLS_P NUMERIC
@ -799,7 +812,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
ORDER ORDINALITY OTHERS OUT_P OUTER_P
OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLANS POLICY
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
@ -901,7 +914,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
*/
%nonassoc UNBOUNDED /* ideally would have same precedence as IDENT */
%nonassoc ERROR_P EMPTY_P DEFAULT ABSENT /* JSON error/empty behavior */
%nonassoc FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN UNIQUE JSON
%nonassoc FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN UNIQUE JSON COLUMNS
%nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
%left Op OPERATOR /* multi-character ops and user-defined operators */
%left '+' '-'
@ -926,6 +939,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
*/
%left JOIN CROSS LEFT FULL RIGHT INNER_P NATURAL
%nonassoc json_table_column
%nonassoc NESTED
%left PATH
%nonassoc empty_json_unique
%left WITHOUT WITH_LA_UNIQUE
@ -12697,6 +12714,19 @@ table_ref: relation_expr opt_alias_clause
$2->alias = $4;
$$ = (Node *) $2;
}
| json_table opt_alias_clause
{
JsonTable *jt = castNode(JsonTable, $1);
jt->alias = $2;
$$ = (Node *) jt;
}
| LATERAL_P json_table opt_alias_clause
{
JsonTable *jt = castNode(JsonTable, $2);
jt->alias = $3;
jt->lateral = true;
$$ = (Node *) jt;
}
;
@ -13248,6 +13278,8 @@ xmltable_column_option_el:
{ $$ = makeDefElem("is_not_null", (Node *) makeBoolean(true), @1); }
| NULL_P
{ $$ = makeDefElem("is_not_null", (Node *) makeBoolean(false), @1); }
| PATH b_expr
{ $$ = makeDefElem("path", $2, @1); }
;
xml_namespace_list:
@ -15774,6 +15806,10 @@ json_behavior_unknown:
UNKNOWN { $$ = makeJsonBehavior(JSON_BEHAVIOR_UNKNOWN, NULL); }
;
json_behavior_empty:
EMPTY_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_OBJECT, NULL); }
;
json_behavior_empty_array:
EMPTY_P ARRAY { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); }
/* non-standard, for Oracle compatibility only */
@ -15888,6 +15924,153 @@ json_query_on_behavior_clause_opt:
{ $$.on_empty = NULL; $$.on_error = NULL; }
;
json_table:
JSON_TABLE '('
json_api_common_syntax
json_table_columns_clause
json_table_error_clause_opt
')'
{
JsonTable *n = makeNode(JsonTable);
n->common = (JsonCommon *) $3;
n->columns = $4;
n->on_error = $5;
n->location = @1;
$$ = (Node *) n;
}
;
json_table_columns_clause:
COLUMNS '(' json_table_column_definition_list ')' { $$ = $3; }
;
json_table_column_definition_list:
json_table_column_definition
{ $$ = list_make1($1); }
| json_table_column_definition_list ',' json_table_column_definition
{ $$ = lappend($1, $3); }
;
json_table_column_definition:
json_table_ordinality_column_definition %prec json_table_column
| json_table_regular_column_definition %prec json_table_column
| json_table_formatted_column_definition %prec json_table_column
| json_table_exists_column_definition %prec json_table_column
| json_table_nested_columns
;
json_table_ordinality_column_definition:
ColId FOR ORDINALITY
{
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_FOR_ORDINALITY;
n->name = $1;
n->location = @1;
$$ = (Node *) n;
}
;
json_table_regular_column_definition:
ColId Typename
json_table_column_path_specification_clause_opt
json_wrapper_clause_opt
json_quotes_clause_opt
json_value_on_behavior_clause_opt
{
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_REGULAR;
n->name = $1;
n->typeName = $2;
n->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
n->wrapper = $4; /* JSW_NONE */
n->omit_quotes = $5; /* false */
n->pathspec = $3;
n->on_empty = $6.on_empty;
n->on_error = $6.on_error;
n->location = @1;
$$ = (Node *) n;
}
;
json_table_exists_column_definition:
ColId Typename
EXISTS json_table_column_path_specification_clause_opt
json_exists_error_clause_opt
{
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_EXISTS;
n->name = $1;
n->typeName = $2;
n->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
n->wrapper = JSW_NONE;
n->omit_quotes = false;
n->pathspec = $4;
n->on_empty = NULL;
n->on_error = $5;
n->location = @1;
$$ = (Node *) n;
}
;
json_table_error_behavior:
json_behavior_error
| json_behavior_empty
;
json_table_error_clause_opt:
json_table_error_behavior ON ERROR_P { $$ = $1; }
| /* EMPTY */ { $$ = NULL; }
;
json_table_column_path_specification_clause_opt:
PATH Sconst { $$ = $2; }
| /* EMPTY */ %prec json_table_column { $$ = NULL; }
;
json_table_formatted_column_definition:
ColId Typename FORMAT json_representation
json_table_column_path_specification_clause_opt
json_wrapper_clause_opt
json_quotes_clause_opt
json_query_on_behavior_clause_opt
{
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_FORMATTED;
n->name = $1;
n->typeName = $2;
n->format = castNode(JsonFormat, $4);
n->pathspec = $5;
n->wrapper = $6;
if (n->wrapper != JSW_NONE && $7 != JS_QUOTES_UNSPEC)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("SQL/JSON QUOTES behavior must not be specified when WITH WRAPPER is used"),
parser_errposition(@7)));
n->omit_quotes = $7 == JS_QUOTES_OMIT;
n->on_empty = $8.on_empty;
n->on_error = $8.on_error;
n->location = @1;
$$ = (Node *) n;
}
;
json_table_nested_columns:
NESTED path_opt Sconst json_table_columns_clause
{
JsonTableColumn *n = makeNode(JsonTableColumn);
n->coltype = JTC_NESTED;
n->pathspec = $3;
n->columns = $4;
n->location = @1;
$$ = (Node *) n;
}
;
path_opt:
PATH { }
| /* EMPTY */ { }
;
json_returning_clause_opt:
RETURNING Typename
{
@ -16733,6 +16916,7 @@ unreserved_keyword:
| MOVE
| NAME_P
| NAMES
| NESTED
| NEW
| NEXT
| NFC
@ -16766,6 +16950,7 @@ unreserved_keyword:
| PARTITION
| PASSING
| PASSWORD
| PATH
| PLANS
| POLICY
| PRECEDING
@ -16929,6 +17114,7 @@ col_name_keyword:
| JSON_QUERY
| JSON_SCALAR
| JSON_SERIALIZE
| JSON_TABLE
| JSON_VALUE
| LEAST
| NATIONAL
@ -17296,6 +17482,7 @@ bare_label_keyword:
| JSON_QUERY
| JSON_SCALAR
| JSON_SERIALIZE
| JSON_TABLE
| JSON_VALUE
| KEEP
| KEY
@ -17335,6 +17522,7 @@ bare_label_keyword:
| NATIONAL
| NATURAL
| NCHAR
| NESTED
| NEW
| NEXT
| NFC
@ -17378,6 +17566,7 @@ bare_label_keyword:
| PARTITION
| PASSING
| PASSWORD
| PATH
| PLACING
| PLANS
| POLICY

View File

@ -696,7 +696,9 @@ transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf)
char **names;
int colno;
/* Currently only XMLTABLE is supported */
/* Currently only XMLTABLE and JSON_TABLE are supported */
tf->functype = TFT_XMLTABLE;
constructName = "XMLTABLE";
docType = XMLOID;
@ -1100,13 +1102,17 @@ transformFromClauseItem(ParseState *pstate, Node *n,
rtr->rtindex = nsitem->p_rtindex;
return (Node *) rtr;
}
else if (IsA(n, RangeTableFunc))
else if (IsA(n, RangeTableFunc) || IsA(n, JsonTable))
{
/* table function is like a plain relation */
RangeTblRef *rtr;
ParseNamespaceItem *nsitem;
nsitem = transformRangeTableFunc(pstate, (RangeTableFunc *) n);
if (IsA(n, RangeTableFunc))
nsitem = transformRangeTableFunc(pstate, (RangeTableFunc *) n);
else
nsitem = transformJsonTable(pstate, (JsonTable *) n);
*top_nsitem = nsitem;
*namespace = list_make1(nsitem);
rtr = makeNode(RangeTblRef);

View File

@ -4093,7 +4093,7 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func)
Node *pathspec;
JsonFormatType format;
if (func->common->pathname)
if (func->common->pathname && func->op != JSON_TABLE_OP)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("JSON_TABLE path name is not allowed here"),
@ -4131,14 +4131,19 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func)
transformJsonPassingArgs(pstate, format, func->common->passing,
&jsexpr->passing_values, &jsexpr->passing_names);
if (func->op != JSON_EXISTS_OP)
if (func->op != JSON_EXISTS_OP && func->op != JSON_TABLE_OP)
jsexpr->on_empty = transformJsonBehavior(pstate, func->on_empty,
JSON_BEHAVIOR_NULL);
jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
func->op == JSON_EXISTS_OP ?
JSON_BEHAVIOR_FALSE :
JSON_BEHAVIOR_NULL);
if (func->op == JSON_EXISTS_OP)
jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
JSON_BEHAVIOR_FALSE);
else if (func->op == JSON_TABLE_OP)
jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
JSON_BEHAVIOR_EMPTY);
else
jsexpr->on_error = transformJsonBehavior(pstate, func->on_error,
JSON_BEHAVIOR_NULL);
return jsexpr;
}
@ -4439,6 +4444,21 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
jsexpr->result_coercion->expr = NULL;
}
break;
case JSON_TABLE_OP:
jsexpr->returning = makeNode(JsonReturning);
jsexpr->returning->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
jsexpr->returning->typid = exprType(contextItemExpr);
jsexpr->returning->typmod = -1;
if (jsexpr->returning->typid != JSONBOID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("JSON_TABLE() is not yet implemented for json type"),
errhint("Try casting the argument to jsonb"),
parser_errposition(pstate, func->location)));
break;
}
if (exprType(contextItemExpr) != JSONBOID)

View File

@ -0,0 +1,466 @@
/*-------------------------------------------------------------------------
*
* parse_jsontable.c
* parsing of JSON_TABLE
*
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/parser/parse_jsontable.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/parse_clause.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/json.h"
#include "utils/lsyscache.h"
/* Context for JSON_TABLE transformation */
typedef struct JsonTableContext
{
ParseState *pstate; /* parsing state */
JsonTable *table; /* untransformed node */
TableFunc *tablefunc; /* transformed node */
List *pathNames; /* list of all path and columns names */
Oid contextItemTypid; /* type oid of context item (json/jsonb) */
} JsonTableContext;
static JsonTableParent * transformJsonTableColumns(JsonTableContext *cxt,
List *columns,
char *pathSpec,
int location);
static Node *
makeStringConst(char *str, int location)
{
A_Const *n = makeNode(A_Const);
n->val.node.type = T_String;
n->val.sval.sval = str;
n->location = location;
return (Node *)n;
}
/*
* Transform JSON_TABLE column
* - regular column into JSON_VALUE()
* - FORMAT JSON column into JSON_QUERY()
* - EXISTS column into JSON_EXISTS()
*/
static Node *
transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
List *passingArgs, bool errorOnError)
{
JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
JsonCommon *common = makeNode(JsonCommon);
JsonOutput *output = makeNode(JsonOutput);
JsonPathSpec pathspec;
JsonFormat *default_format;
jfexpr->op =
jtc->coltype == JTC_REGULAR ? JSON_VALUE_OP :
jtc->coltype == JTC_EXISTS ? JSON_EXISTS_OP : JSON_QUERY_OP;
jfexpr->common = common;
jfexpr->output = output;
jfexpr->on_empty = jtc->on_empty;
jfexpr->on_error = jtc->on_error;
if (!jfexpr->on_error && errorOnError)
jfexpr->on_error = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL);
jfexpr->omit_quotes = jtc->omit_quotes;
jfexpr->wrapper = jtc->wrapper;
jfexpr->location = jtc->location;
output->typeName = jtc->typeName;
output->returning = makeNode(JsonReturning);
output->returning->format = jtc->format;
default_format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
common->pathname = NULL;
common->expr = makeJsonValueExpr((Expr *) contextItemExpr, default_format);
common->passing = passingArgs;
if (jtc->pathspec)
pathspec = jtc->pathspec;
else
{
/* Construct default path as '$."column_name"' */
StringInfoData path;
initStringInfo(&path);
appendStringInfoString(&path, "$.");
escape_json(&path, jtc->name);
pathspec = path.data;
}
common->pathspec = makeStringConst(pathspec, -1);
return (Node *) jfexpr;
}
static bool
isJsonTablePathNameDuplicate(JsonTableContext *cxt, const char *pathname)
{
ListCell *lc;
foreach(lc, cxt->pathNames)
{
if (!strcmp(pathname, (const char *) lfirst(lc)))
return true;
}
return false;
}
/* Register the column name in the path name list. */
static void
registerJsonTableColumn(JsonTableContext *cxt, char *colname)
{
if (isJsonTablePathNameDuplicate(cxt, colname))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_ALIAS),
errmsg("duplicate JSON_TABLE column name: %s", colname),
errhint("JSON_TABLE column names must be distinct from one another")));
cxt->pathNames = lappend(cxt->pathNames, colname);
}
/* Recursively register all nested column names in the path name list. */
static void
registerAllJsonTableColumns(JsonTableContext *cxt, List *columns)
{
ListCell *lc;
foreach(lc, columns)
{
JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
if (jtc->coltype == JTC_NESTED)
registerAllJsonTableColumns(cxt, jtc->columns);
else
registerJsonTableColumn(cxt, jtc->name);
}
}
static Node *
transformNestedJsonTableColumn(JsonTableContext *cxt, JsonTableColumn *jtc)
{
JsonTableParent *node;
node = transformJsonTableColumns(cxt, jtc->columns, jtc->pathspec,
jtc->location);
return (Node *) node;
}
static Node *
makeJsonTableSiblingJoin(Node *lnode, Node *rnode)
{
JsonTableSibling *join = makeNode(JsonTableSibling);
join->larg = lnode;
join->rarg = rnode;
return (Node *) join;
}
/*
* Recursively transform child (nested) JSON_TABLE columns.
*
* Child columns are transformed into a binary tree of union-joined
* JsonTableSiblings.
*/
static Node *
transformJsonTableChildColumns(JsonTableContext *cxt, List *columns)
{
Node *res = NULL;
ListCell *lc;
/* transform all nested columns into union join */
foreach(lc, columns)
{
JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
Node *node;
if (jtc->coltype != JTC_NESTED)
continue;
node = transformNestedJsonTableColumn(cxt, jtc);
/* join transformed node with previous sibling nodes */
res = res ? makeJsonTableSiblingJoin(res, node) : node;
}
return res;
}
/* Check whether type is json/jsonb, array, or record. */
static bool
typeIsComposite(Oid typid)
{
char typtype;
if (typid == JSONOID ||
typid == JSONBOID ||
typid == RECORDOID ||
type_is_array(typid))
return true;
typtype = get_typtype(typid);
if (typtype == TYPTYPE_COMPOSITE)
return true;
if (typtype == TYPTYPE_DOMAIN)
return typeIsComposite(getBaseType(typid));
return false;
}
/* Append transformed non-nested JSON_TABLE columns to the TableFunc node */
static void
appendJsonTableColumns(JsonTableContext *cxt, List *columns)
{
ListCell *col;
ParseState *pstate = cxt->pstate;
JsonTable *jt = cxt->table;
TableFunc *tf = cxt->tablefunc;
bool errorOnError = jt->on_error &&
jt->on_error->btype == JSON_BEHAVIOR_ERROR;
foreach(col, columns)
{
JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col));
Oid typid;
int32 typmod;
Node *colexpr;
if (rawc->name)
{
/* make sure column names are unique */
ListCell *colname;
foreach(colname, tf->colnames)
if (!strcmp((const char *) colname, rawc->name))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("column name \"%s\" is not unique",
rawc->name),
parser_errposition(pstate, rawc->location)));
tf->colnames = lappend(tf->colnames,
makeString(pstrdup(rawc->name)));
}
/*
* Determine the type and typmod for the new column. FOR
* ORDINALITY columns are INTEGER by standard; the others are
* user-specified.
*/
switch (rawc->coltype)
{
case JTC_FOR_ORDINALITY:
colexpr = NULL;
typid = INT4OID;
typmod = -1;
break;
case JTC_REGULAR:
typenameTypeIdAndMod(pstate, rawc->typeName, &typid, &typmod);
/*
* Use implicit FORMAT JSON for composite types (arrays and
* records)
*/
if (typeIsComposite(typid))
rawc->coltype = JTC_FORMATTED;
else if (rawc->wrapper != JSW_NONE)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use WITH WRAPPER clause with scalar columns"),
parser_errposition(pstate, rawc->location)));
else if (rawc->omit_quotes)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use OMIT QUOTES clause with scalar columns"),
parser_errposition(pstate, rawc->location)));
/* FALLTHROUGH */
case JTC_EXISTS:
case JTC_FORMATTED:
{
Node *je;
CaseTestExpr *param = makeNode(CaseTestExpr);
param->collation = InvalidOid;
param->typeId = cxt->contextItemTypid;
param->typeMod = -1;
je = transformJsonTableColumn(rawc, (Node *) param,
NIL, errorOnError);
colexpr = transformExpr(pstate, je, EXPR_KIND_FROM_FUNCTION);
assign_expr_collations(pstate, colexpr);
typid = exprType(colexpr);
typmod = exprTypmod(colexpr);
break;
}
case JTC_NESTED:
continue;
default:
elog(ERROR, "unknown JSON_TABLE column type: %d", rawc->coltype);
break;
}
tf->coltypes = lappend_oid(tf->coltypes, typid);
tf->coltypmods = lappend_int(tf->coltypmods, typmod);
tf->colcollations = lappend_oid(tf->colcollations,
type_is_collatable(typid)
? DEFAULT_COLLATION_OID
: InvalidOid);
tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
}
}
/*
* Create transformed JSON_TABLE parent plan node by appending all non-nested
* columns to the TableFunc node and remembering their indices in the
* colvalexprs list.
*/
static JsonTableParent *
makeParentJsonTableNode(JsonTableContext *cxt, char *pathSpec, List *columns)
{
JsonTableParent *node = makeNode(JsonTableParent);
node->path = makeConst(JSONPATHOID, -1, InvalidOid, -1,
DirectFunctionCall1(jsonpath_in,
CStringGetDatum(pathSpec)),
false, false);
/* save start of column range */
node->colMin = list_length(cxt->tablefunc->colvalexprs);
appendJsonTableColumns(cxt, columns);
/* save end of column range */
node->colMax = list_length(cxt->tablefunc->colvalexprs) - 1;
node->errorOnError =
cxt->table->on_error &&
cxt->table->on_error->btype == JSON_BEHAVIOR_ERROR;
return node;
}
static JsonTableParent *
transformJsonTableColumns(JsonTableContext *cxt, List *columns, char *pathSpec,
int location)
{
JsonTableParent *node;
/* transform only non-nested columns */
node = makeParentJsonTableNode(cxt, pathSpec, columns);
/* transform recursively nested columns */
node->child = transformJsonTableChildColumns(cxt, columns);
return node;
}
/*
* transformJsonTable -
* Transform a raw JsonTable into TableFunc.
*
* Transform the document-generating expression, the row-generating expression,
* the column-generating expressions, and the default value expressions.
*/
ParseNamespaceItem *
transformJsonTable(ParseState *pstate, JsonTable *jt)
{
JsonTableContext cxt;
TableFunc *tf = makeNode(TableFunc);
JsonFuncExpr *jfe = makeNode(JsonFuncExpr);
JsonCommon *jscommon;
char *rootPath;
bool is_lateral;
cxt.pstate = pstate;
cxt.table = jt;
cxt.tablefunc = tf;
cxt.pathNames = NIL;
registerAllJsonTableColumns(&cxt, jt->columns);
jscommon = copyObject(jt->common);
jscommon->pathspec = makeStringConst(pstrdup("$"), -1);
jfe->op = JSON_TABLE_OP;
jfe->common = jscommon;
jfe->on_error = jt->on_error;
jfe->location = jt->common->location;
/*
* We make lateral_only names of this level visible, whether or not the
* RangeTableFunc is explicitly marked LATERAL. This is needed for SQL
* spec compliance and seems useful on convenience grounds for all
* functions in FROM.
*
* (LATERAL can't nest within a single pstate level, so we don't need
* save/restore logic here.)
*/
Assert(!pstate->p_lateral_active);
pstate->p_lateral_active = true;
tf->functype = TFT_JSON_TABLE;
tf->docexpr = transformExpr(pstate, (Node *) jfe, EXPR_KIND_FROM_FUNCTION);
cxt.contextItemTypid = exprType(tf->docexpr);
if (!IsA(jt->common->pathspec, A_Const) ||
castNode(A_Const, jt->common->pathspec)->val.node.type != T_String)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("only string constants supported in JSON_TABLE path specification"),
parser_errposition(pstate,
exprLocation(jt->common->pathspec))));
rootPath = castNode(A_Const, jt->common->pathspec)->val.sval.sval;
tf->plan = (Node *) transformJsonTableColumns(&cxt, jt->columns, rootPath,
jt->common->location);
tf->ordinalitycol = -1; /* undefine ordinality column number */
tf->location = jt->location;
pstate->p_lateral_active = false;
/*
* Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
* there are any lateral cross-references in it.
*/
is_lateral = jt->lateral || contain_vars_of_level((Node *) tf, 0);
return addRangeTableEntryForTableFunc(pstate,
tf, jt->alias, is_lateral, true);
}

View File

@ -1989,7 +1989,8 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
char *refname = alias ? alias->aliasname : pstrdup("xmltable");
char *refname = alias ? alias->aliasname :
pstrdup(tf->functype == TFT_XMLTABLE ? "xmltable" : "json_table");
Alias *eref;
int numaliases;

View File

@ -1993,6 +1993,9 @@ FigureColnameInternal(Node *node, char **name)
case JSON_EXISTS_OP:
*name = "json_exists";
return 2;
case JSON_TABLE_OP:
*name = "json_table";
return 2;
}
break;
default:

View File

@ -61,9 +61,11 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "executor/execExpr.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "regex/regex.h"
#include "utils/builtins.h"
#include "utils/date.h"
@ -74,6 +76,8 @@
#include "utils/guc.h"
#include "utils/json.h"
#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
#include "utils/varlena.h"
@ -155,6 +159,57 @@ typedef struct JsonValueListIterator
ListCell *next;
} JsonValueListIterator;
/* Structures for JSON_TABLE execution */
typedef struct JsonTableScanState JsonTableScanState;
typedef struct JsonTableJoinState JsonTableJoinState;
struct JsonTableScanState
{
JsonTableScanState *parent;
JsonTableJoinState *nested;
MemoryContext mcxt;
JsonPath *path;
List *args;
JsonValueList found;
JsonValueListIterator iter;
Datum current;
int ordinal;
bool currentIsNull;
bool errorOnError;
bool advanceNested;
bool reset;
};
struct JsonTableJoinState
{
union
{
struct
{
JsonTableJoinState *left;
JsonTableJoinState *right;
bool advanceRight;
} join;
JsonTableScanState scan;
} u;
bool is_join;
};
/* random number to identify JsonTableContext */
#define JSON_TABLE_CONTEXT_MAGIC 418352867
typedef struct JsonTableContext
{
int magic;
struct
{
ExprState *expr;
JsonTableScanState *scan;
} *colexprs;
JsonTableScanState root;
bool empty;
} JsonTableContext;
/* strict/lax flags is decomposed into four [un]wrap/error flags */
#define jspStrictAbsenseOfErrors(cxt) (!(cxt)->laxMode)
#define jspAutoUnwrap(cxt) ((cxt)->laxMode)
@ -245,6 +300,7 @@ static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbValue *jb, int32 *index);
static JsonBaseObjectInfo setBaseObject(JsonPathExecContext *cxt,
JsonbValue *jbv, int32 id);
static void JsonValueListClear(JsonValueList *jvl);
static void JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv);
static int JsonValueListLength(const JsonValueList *jvl);
static bool JsonValueListIsEmpty(JsonValueList *jvl);
@ -262,6 +318,12 @@ static JsonbValue *wrapItemsInArray(const JsonValueList *items);
static int compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
bool useTz, bool *have_error);
static JsonTableJoinState *JsonTableInitPlanState(JsonTableContext *cxt,
Node *plan, JsonTableScanState *parent);
static bool JsonTableNextRow(JsonTableScanState *scan);
/****************** User interface to JsonPath executor ********************/
/*
@ -2458,6 +2520,13 @@ setBaseObject(JsonPathExecContext *cxt, JsonbValue *jbv, int32 id)
return baseObject;
}
static void
JsonValueListClear(JsonValueList *jvl)
{
jvl->singleton = NULL;
jvl->list = NULL;
}
static void
JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv)
{
@ -3067,3 +3136,370 @@ JsonItemFromDatum(Datum val, Oid typid, int32 typmod, JsonbValue *res)
"casted to supported jsonpath types.")));
}
}
/************************ JSON_TABLE functions ***************************/
/*
* Returns private data from executor state. Ensure validity by check with
* MAGIC number.
*/
static inline JsonTableContext *
GetJsonTableContext(TableFuncScanState *state, const char *fname)
{
JsonTableContext *result;
if (!IsA(state, TableFuncScanState))
elog(ERROR, "%s called with invalid TableFuncScanState", fname);
result = (JsonTableContext *) state->opaque;
if (result->magic != JSON_TABLE_CONTEXT_MAGIC)
elog(ERROR, "%s called with invalid TableFuncScanState", fname);
return result;
}
/* Recursively initialize JSON_TABLE scan state */
static void
JsonTableInitScanState(JsonTableContext *cxt, JsonTableScanState *scan,
JsonTableParent *node, JsonTableScanState *parent,
List *args, MemoryContext mcxt)
{
int i;
scan->parent = parent;
scan->errorOnError = node->errorOnError;
scan->path = DatumGetJsonPathP(node->path->constvalue);
scan->args = args;
scan->mcxt = AllocSetContextCreate(mcxt, "JsonTableContext",
ALLOCSET_DEFAULT_SIZES);
scan->nested = node->child ?
JsonTableInitPlanState(cxt, node->child, scan) : NULL;
scan->current = PointerGetDatum(NULL);
scan->currentIsNull = true;
for (i = node->colMin; i <= node->colMax; i++)
cxt->colexprs[i].scan = scan;
}
/* Recursively initialize JSON_TABLE scan state */
static JsonTableJoinState *
JsonTableInitPlanState(JsonTableContext *cxt, Node *plan,
JsonTableScanState *parent)
{
JsonTableJoinState *state = palloc0(sizeof(*state));
if (IsA(plan, JsonTableSibling))
{
JsonTableSibling *join = castNode(JsonTableSibling, plan);
state->is_join = true;
state->u.join.left = JsonTableInitPlanState(cxt, join->larg, parent);
state->u.join.right = JsonTableInitPlanState(cxt, join->rarg, parent);
}
else
{
JsonTableParent *node = castNode(JsonTableParent, plan);
state->is_join = false;
JsonTableInitScanState(cxt, &state->u.scan, node, parent,
parent->args, parent->mcxt);
}
return state;
}
/*
* JsonTableInitOpaque
* Fill in TableFuncScanState->opaque for JsonTable processor
*/
static void
JsonTableInitOpaque(TableFuncScanState *state, int natts)
{
JsonTableContext *cxt;
PlanState *ps = &state->ss.ps;
TableFuncScan *tfs = castNode(TableFuncScan, ps->plan);
TableFunc *tf = tfs->tablefunc;
JsonExpr *ci = castNode(JsonExpr, tf->docexpr);
JsonTableParent *root = castNode(JsonTableParent, tf->plan);
List *args = NIL;
ListCell *lc;
int i;
cxt = palloc0(sizeof(JsonTableContext));
cxt->magic = JSON_TABLE_CONTEXT_MAGIC;
if (ci->passing_values)
{
ListCell *exprlc;
ListCell *namelc;
forboth(exprlc, ci->passing_values,
namelc, ci->passing_names)
{
Expr *expr = (Expr *) lfirst(exprlc);
String *name = lfirst_node(String, namelc);
JsonPathVariableEvalContext *var = palloc(sizeof(*var));
var->name = pstrdup(name->sval);
var->typid = exprType((Node *) expr);
var->typmod = exprTypmod((Node *) expr);
var->estate = ExecInitExpr(expr, ps);
var->econtext = ps->ps_ExprContext;
var->mcxt = CurrentMemoryContext;
var->evaluated = false;
var->value = (Datum) 0;
var->isnull = true;
args = lappend(args, var);
}
}
cxt->colexprs = palloc(sizeof(*cxt->colexprs) *
list_length(tf->colvalexprs));
JsonTableInitScanState(cxt, &cxt->root, root, NULL, args,
CurrentMemoryContext);
i = 0;
foreach(lc, tf->colvalexprs)
{
Expr *expr = lfirst(lc);
cxt->colexprs[i].expr =
ExecInitExprWithCaseValue(expr, ps,
&cxt->colexprs[i].scan->current,
&cxt->colexprs[i].scan->currentIsNull);
i++;
}
state->opaque = cxt;
}
/* Reset scan iterator to the beginning of the item list */
static void
JsonTableRescan(JsonTableScanState *scan)
{
JsonValueListInitIterator(&scan->found, &scan->iter);
scan->current = PointerGetDatum(NULL);
scan->currentIsNull = true;
scan->advanceNested = false;
scan->ordinal = 0;
}
/* Reset context item of a scan, execute JSON path and reset a scan */
static void
JsonTableResetContextItem(JsonTableScanState *scan, Datum item)
{
MemoryContext oldcxt;
JsonPathExecResult res;
Jsonb *js = (Jsonb *) DatumGetJsonbP(item);
JsonValueListClear(&scan->found);
MemoryContextResetOnly(scan->mcxt);
oldcxt = MemoryContextSwitchTo(scan->mcxt);
res = executeJsonPath(scan->path, scan->args, EvalJsonPathVar, js,
scan->errorOnError, &scan->found, false /* FIXME */);
MemoryContextSwitchTo(oldcxt);
if (jperIsError(res))
{
Assert(!scan->errorOnError);
JsonValueListClear(&scan->found); /* EMPTY ON ERROR case */
}
JsonTableRescan(scan);
}
/*
* JsonTableSetDocument
* Install the input document
*/
static void
JsonTableSetDocument(TableFuncScanState *state, Datum value)
{
JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableSetDocument");
JsonTableResetContextItem(&cxt->root, value);
}
/*
* Fetch next row from a union joined scan.
*
* Returns false at the end of a scan, true otherwise.
*/
static bool
JsonTableNextJoinRow(JsonTableJoinState *state)
{
if (!state->is_join)
return JsonTableNextRow(&state->u.scan);
if (!state->u.join.advanceRight)
{
/* fetch next outer row */
if (JsonTableNextJoinRow(state->u.join.left))
return true;
state->u.join.advanceRight = true; /* next inner row */
}
/* fetch next inner row */
return JsonTableNextJoinRow(state->u.join.right);
}
/* Recursively set 'reset' flag of scan and its child nodes */
static void
JsonTableJoinReset(JsonTableJoinState *state)
{
if (state->is_join)
{
JsonTableJoinReset(state->u.join.left);
JsonTableJoinReset(state->u.join.right);
state->u.join.advanceRight = false;
}
else
{
state->u.scan.reset = true;
state->u.scan.advanceNested = false;
if (state->u.scan.nested)
JsonTableJoinReset(state->u.scan.nested);
}
}
/*
* Fetch next row from a simple scan with outer joined nested subscans.
*
* Returns false at the end of a scan, true otherwise.
*/
static bool
JsonTableNextRow(JsonTableScanState *scan)
{
JsonbValue *jbv;
MemoryContext oldcxt;
/* reset context item if requested */
if (scan->reset)
{
Assert(!scan->parent->currentIsNull);
JsonTableResetContextItem(scan, scan->parent->current);
scan->reset = false;
}
if (scan->advanceNested)
{
/* fetch next nested row */
if (JsonTableNextJoinRow(scan->nested))
return true;
scan->advanceNested = false;
}
/* fetch next row */
jbv = JsonValueListNext(&scan->found, &scan->iter);
if (!jbv)
{
scan->current = PointerGetDatum(NULL);
scan->currentIsNull = true;
return false; /* end of scan */
}
/* set current row item */
oldcxt = MemoryContextSwitchTo(scan->mcxt);
scan->current = JsonbPGetDatum(JsonbValueToJsonb(jbv));
scan->currentIsNull = false;
MemoryContextSwitchTo(oldcxt);
scan->ordinal++;
if (scan->nested)
{
JsonTableJoinReset(scan->nested);
scan->advanceNested = JsonTableNextJoinRow(scan->nested);
}
return true;
}
/*
* JsonTableFetchRow
* Prepare the next "current" tuple for upcoming GetValue calls.
* Returns FALSE if the row-filter expression returned no more rows.
*/
static bool
JsonTableFetchRow(TableFuncScanState *state)
{
JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableFetchRow");
if (cxt->empty)
return false;
return JsonTableNextRow(&cxt->root);
}
/*
* JsonTableGetValue
* Return the value for column number 'colnum' for the current row.
*
* This leaks memory, so be sure to reset often the context in which it's
* called.
*/
static Datum
JsonTableGetValue(TableFuncScanState *state, int colnum,
Oid typid, int32 typmod, bool *isnull)
{
JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableGetValue");
ExprContext *econtext = state->ss.ps.ps_ExprContext;
ExprState *estate = cxt->colexprs[colnum].expr;
JsonTableScanState *scan = cxt->colexprs[colnum].scan;
Datum result;
if (scan->currentIsNull) /* NULL from outer/union join */
{
result = (Datum) 0;
*isnull = true;
}
else if (estate) /* regular column */
{
result = ExecEvalExpr(estate, econtext, isnull);
}
else
{
result = Int32GetDatum(scan->ordinal); /* ordinality column */
*isnull = false;
}
return result;
}
/*
* JsonTableDestroyOpaque
*/
static void
JsonTableDestroyOpaque(TableFuncScanState *state)
{
JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableDestroyOpaque");
/* not valid anymore */
cxt->magic = 0;
state->opaque = NULL;
}
const TableFuncRoutine JsonbTableRoutine =
{
JsonTableInitOpaque,
JsonTableSetDocument,
NULL,
NULL,
NULL,
JsonTableFetchRow,
JsonTableGetValue,
JsonTableDestroyOpaque
};

View File

@ -503,6 +503,8 @@ static char *flatten_reloptions(Oid relid);
static void get_reloptions(StringInfo buf, Datum reloptions);
static void get_json_path_spec(Node *path_spec, deparse_context *context,
bool showimplicit);
static void get_json_table_columns(TableFunc *tf, JsonTableParent *node,
deparse_context *context, bool showimplicit);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@ -8516,7 +8518,8 @@ get_json_behavior(JsonBehavior *behavior, deparse_context *context,
/*
* get_json_expr_options
*
* Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS.
* Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS and
* JSON_TABLE columns.
*/
static void
get_json_expr_options(JsonExpr *jsexpr, deparse_context *context,
@ -9763,6 +9766,9 @@ get_rule_expr(Node *node, deparse_context *context,
case JSON_EXISTS_OP:
appendStringInfoString(buf, "JSON_EXISTS(");
break;
default:
elog(ERROR, "unexpected JsonExpr type: %d", jexpr->op);
break;
}
get_rule_expr(jexpr->formatted_expr, context, showimplicit);
@ -11039,16 +11045,14 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
/* ----------
* get_tablefunc - Parse back a table function
* get_xmltable - Parse back a XMLTABLE function
* ----------
*/
static void
get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit)
{
StringInfo buf = context->buf;
/* XMLTABLE is the only existing implementation. */
appendStringInfoString(buf, "XMLTABLE(");
if (tf->ns_uris != NIL)
@ -11139,6 +11143,220 @@ get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
appendStringInfoChar(buf, ')');
}
/*
* get_json_nested_columns - Parse back nested JSON_TABLE columns
*/
static void
get_json_table_nested_columns(TableFunc *tf, Node *node,
deparse_context *context, bool showimplicit,
bool needcomma)
{
if (IsA(node, JsonTableSibling))
{
JsonTableSibling *n = (JsonTableSibling *) node;
get_json_table_nested_columns(tf, n->larg, context, showimplicit,
needcomma);
get_json_table_nested_columns(tf, n->rarg, context, showimplicit, true);
}
else
{
JsonTableParent *n = castNode(JsonTableParent, node);
if (needcomma)
appendStringInfoChar(context->buf, ',');
appendStringInfoChar(context->buf, ' ');
appendContextKeyword(context, "NESTED PATH ", 0, 0, 0);
get_const_expr(n->path, context, -1);
get_json_table_columns(tf, n, context, showimplicit);
}
}
/*
* get_json_table_columns - Parse back JSON_TABLE columns
*/
static void
get_json_table_columns(TableFunc *tf, JsonTableParent *node,
deparse_context *context, bool showimplicit)
{
StringInfo buf = context->buf;
JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
ListCell *lc_colname;
ListCell *lc_coltype;
ListCell *lc_coltypmod;
ListCell *lc_colvarexpr;
int colnum = 0;
appendStringInfoChar(buf, ' ');
appendContextKeyword(context, "COLUMNS (", 0, 0, 0);
if (PRETTY_INDENT(context))
context->indentLevel += PRETTYINDENT_VAR;
forfour(lc_colname, tf->colnames,
lc_coltype, tf->coltypes,
lc_coltypmod, tf->coltypmods,
lc_colvarexpr, tf->colvalexprs)
{
char *colname = strVal(lfirst(lc_colname));
JsonExpr *colexpr;
Oid typid;
int32 typmod;
bool ordinality;
JsonBehaviorType default_behavior;
typid = lfirst_oid(lc_coltype);
typmod = lfirst_int(lc_coltypmod);
colexpr = castNode(JsonExpr, lfirst(lc_colvarexpr));
if (colnum < node->colMin)
{
colnum++;
continue;
}
if (colnum > node->colMax)
break;
if (colnum > node->colMin)
appendStringInfoString(buf, ", ");
colnum++;
ordinality = !colexpr;
appendContextKeyword(context, "", 0, 0, 0);
appendStringInfo(buf, "%s %s", quote_identifier(colname),
ordinality ? "FOR ORDINALITY" :
format_type_with_typemod(typid, typmod));
if (ordinality)
continue;
if (colexpr->op == JSON_EXISTS_OP)
{
appendStringInfoString(buf, " EXISTS");
default_behavior = JSON_BEHAVIOR_FALSE;
}
else
{
if (colexpr->op == JSON_QUERY_OP)
{
char typcategory;
bool typispreferred;
get_type_category_preferred(typid, &typcategory, &typispreferred);
if (typcategory == TYPCATEGORY_STRING)
appendStringInfoString(buf,
colexpr->format->format_type == JS_FORMAT_JSONB ?
" FORMAT JSONB" : " FORMAT JSON");
}
default_behavior = JSON_BEHAVIOR_NULL;
}
if (jexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
default_behavior = JSON_BEHAVIOR_ERROR;
appendStringInfoString(buf, " PATH ");
get_json_path_spec(colexpr->path_spec, context, showimplicit);
get_json_expr_options(colexpr, context, default_behavior);
}
if (node->child)
get_json_table_nested_columns(tf, node->child, context, showimplicit,
node->colMax >= node->colMin);
if (PRETTY_INDENT(context))
context->indentLevel -= PRETTYINDENT_VAR;
appendContextKeyword(context, ")", 0, 0, 0);
}
/* ----------
* get_json_table - Parse back a JSON_TABLE function
* ----------
*/
static void
get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
{
StringInfo buf = context->buf;
JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
JsonTableParent *root = castNode(JsonTableParent, tf->plan);
appendStringInfoString(buf, "JSON_TABLE(");
if (PRETTY_INDENT(context))
context->indentLevel += PRETTYINDENT_VAR;
appendContextKeyword(context, "", 0, 0, 0);
get_rule_expr(jexpr->formatted_expr, context, showimplicit);
appendStringInfoString(buf, ", ");
get_const_expr(root->path, context, -1);
if (jexpr->passing_values)
{
ListCell *lc1, *lc2;
bool needcomma = false;
appendStringInfoChar(buf, ' ');
appendContextKeyword(context, "PASSING ", 0, 0, 0);
if (PRETTY_INDENT(context))
context->indentLevel += PRETTYINDENT_VAR;
forboth(lc1, jexpr->passing_names,
lc2, jexpr->passing_values)
{
if (needcomma)
appendStringInfoString(buf, ", ");
needcomma = true;
appendContextKeyword(context, "", 0, 0, 0);
get_rule_expr((Node *) lfirst(lc2), context, false);
appendStringInfo(buf, " AS %s",
quote_identifier((lfirst_node(String, lc1))->sval)
);
}
if (PRETTY_INDENT(context))
context->indentLevel -= PRETTYINDENT_VAR;
}
get_json_table_columns(tf, root, context, showimplicit);
if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY)
get_json_behavior(jexpr->on_error, context, "ERROR");
if (PRETTY_INDENT(context))
context->indentLevel -= PRETTYINDENT_VAR;
appendContextKeyword(context, ")", 0, 0, 0);
}
/* ----------
* get_tablefunc - Parse back a table function
* ----------
*/
static void
get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
{
/* XMLTABLE and JSON_TABLE are the only existing implementations. */
if (tf->functype == TFT_XMLTABLE)
get_xmltable(tf, context, showimplicit);
else if (tf->functype == TFT_JSON_TABLE)
get_json_table(tf, context, showimplicit);
}
/* ----------
* get_from_clause - Parse back a FROM clause
*

View File

@ -878,9 +878,11 @@ JumbleExpr(JumbleState *jstate, Node *node)
{
TableFunc *tablefunc = (TableFunc *) node;
APP_JUMB(tablefunc->functype);
JumbleExpr(jstate, tablefunc->docexpr);
JumbleExpr(jstate, tablefunc->rowexpr);
JumbleExpr(jstate, (Node *) tablefunc->colexprs);
JumbleExpr(jstate, (Node *) tablefunc->colvalexprs);
}
break;
case T_TableSampleClause:

View File

@ -850,6 +850,10 @@ extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item,
struct JsonCoercionState **pjcstate);
extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
struct JsonCoercionsState *);
extern Datum ExecEvalExprPassingCaseValue(ExprState *estate,
ExprContext *econtext, bool *isnull,
Datum caseval_datum,
bool caseval_isnull);
extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup,
ExprContext *aggcontext);

View File

@ -212,6 +212,8 @@ typedef enum NodeTag
T_JsonExpr,
T_JsonCoercion,
T_JsonItemCoercions,
T_JsonTableParent,
T_JsonTableSibling,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@ -514,6 +516,8 @@ typedef enum NodeTag
T_JsonArrayAgg,
T_JsonFuncExpr,
T_JsonIsPredicate,
T_JsonTable,
T_JsonTableColumn,
T_JsonCommon,
T_JsonArgument,
T_JsonKeyValue,

View File

@ -1605,6 +1605,19 @@ typedef enum JsonQuotes
JS_QUOTES_OMIT /* OMIT QUOTES */
} JsonQuotes;
/*
* JsonTableColumnType -
* enumeration of JSON_TABLE column types
*/
typedef enum
{
JTC_FOR_ORDINALITY,
JTC_REGULAR,
JTC_EXISTS,
JTC_FORMATTED,
JTC_NESTED,
} JsonTableColumnType;
/*
* JsonPathSpec -
* representation of JSON path constant
@ -1664,6 +1677,41 @@ typedef struct JsonFuncExpr
int location; /* token location, or -1 if unknown */
} JsonFuncExpr;
/*
* JsonTableColumn -
* untransformed representation of JSON_TABLE column
*/
typedef struct JsonTableColumn
{
NodeTag type;
JsonTableColumnType coltype; /* column type */
char *name; /* column name */
TypeName *typeName; /* column type name */
JsonPathSpec pathspec; /* path specification, if any */
JsonFormat *format; /* JSON format clause, if specified */
JsonWrapper wrapper; /* WRAPPER behavior for formatted columns */
bool omit_quotes; /* omit or keep quotes on scalar strings? */
List *columns; /* nested columns */
JsonBehavior *on_empty; /* ON EMPTY behavior */
JsonBehavior *on_error; /* ON ERROR behavior */
int location; /* token location, or -1 if unknown */
} JsonTableColumn;
/*
* JsonTable -
* untransformed representation of JSON_TABLE
*/
typedef struct JsonTable
{
NodeTag type;
JsonCommon *common; /* common JSON path syntax fields */
List *columns; /* list of JsonTableColumn */
JsonBehavior *on_error; /* ON ERROR behavior, if specified */
Alias *alias; /* table alias in FROM clause */
bool lateral; /* does it have LATERAL prefix? */
int location; /* token location, or -1 if unknown */
} JsonTable;
/*
* JsonKeyValue -
* untransformed representation of JSON object key-value pair for

View File

@ -73,8 +73,14 @@ typedef struct RangeVar
int location; /* token location, or -1 if unknown */
} RangeVar;
typedef enum TableFuncType
{
TFT_XMLTABLE,
TFT_JSON_TABLE
} TableFuncType;
/*
* TableFunc - node for a table function, such as XMLTABLE.
* TableFunc - node for a table function, such as XMLTABLE or JSON_TABLE.
*
* Entries in the ns_names list are either String nodes containing
* literal namespace names, or NULL pointers to represent DEFAULT.
@ -82,6 +88,7 @@ typedef struct RangeVar
typedef struct TableFunc
{
NodeTag type;
TableFuncType functype; /* XMLTABLE or JSON_TABLE */
List *ns_uris; /* list of namespace URI expressions */
List *ns_names; /* list of namespace names or NULL */
Node *docexpr; /* input document expression */
@ -92,7 +99,9 @@ typedef struct TableFunc
List *colcollations; /* OID list of column collation OIDs */
List *colexprs; /* list of column filter expressions */
List *coldefexprs; /* list of column default expressions */
List *colvalexprs; /* list of column value expressions */
Bitmapset *notnulls; /* nullability flag for each output column */
Node *plan; /* JSON_TABLE plan */
int ordinalitycol; /* counts from 0; -1 if none specified */
int location; /* token location, or -1 if unknown */
} TableFunc;
@ -1241,7 +1250,8 @@ typedef enum JsonExprOp
{
JSON_VALUE_OP, /* JSON_VALUE() */
JSON_QUERY_OP, /* JSON_QUERY() */
JSON_EXISTS_OP /* JSON_EXISTS() */
JSON_EXISTS_OP, /* JSON_EXISTS() */
JSON_TABLE_OP /* JSON_TABLE() */
} JsonExprOp;
/*
@ -1455,6 +1465,31 @@ typedef struct JsonExpr
int location; /* token location, or -1 if unknown */
} JsonExpr;
/*
* JsonTableParent -
* transformed representation of parent JSON_TABLE plan node
*/
typedef struct JsonTableParent
{
NodeTag type;
Const *path; /* jsonpath constant */
Node *child; /* nested columns, if any */
int colMin; /* min column index in the resulting column list */
int colMax; /* max column index in the resulting column list */
bool errorOnError; /* ERROR/EMPTY ON ERROR behavior */
} JsonTableParent;
/*
* JsonTableSibling -
* transformed representation of joined sibling JSON_TABLE plan node
*/
typedef struct JsonTableSibling
{
NodeTag type;
Node *larg; /* left join node */
Node *rarg; /* right join node */
} JsonTableSibling;
/* ----------------
* NullTest
*

View File

@ -241,6 +241,7 @@ PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_query", JSON_QUERY, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_scalar", JSON_SCALAR, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_serialize", JSON_SERIALIZE, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_table", JSON_TABLE, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("json_value", JSON_VALUE, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("keep", KEEP, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL)
@ -283,6 +284,7 @@ PG_KEYWORD("names", NAMES, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("national", NATIONAL, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("natural", NATURAL, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("nchar", NCHAR, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("nested", NESTED, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("new", NEW, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("next", NEXT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("nfc", NFC, UNRESERVED_KEYWORD, BARE_LABEL)
@ -332,6 +334,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)

View File

@ -51,4 +51,7 @@ extern List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
extern Index assignSortGroupRef(TargetEntry *tle, List *tlist);
extern bool targetIsInSortList(TargetEntry *tle, Oid sortop, List *sortList);
/* functions in parse_jsontable.c */
extern ParseNamespaceItem *transformJsonTable(ParseState *pstate, JsonTable *jt);
#endif /* PARSE_CLAUSE_H */

View File

@ -15,6 +15,7 @@
#define JSONPATH_H
#include "fmgr.h"
#include "executor/tablefunc.h"
#include "nodes/pg_list.h"
#include "nodes/primnodes.h"
#include "utils/jsonb.h"
@ -263,6 +264,7 @@ typedef struct JsonPathVariableEvalContext
int32 typmod;
struct ExprContext *econtext;
struct ExprState *estate;
MemoryContext mcxt; /* memory context for cached value */
Datum value;
bool isnull;
bool evaluated;
@ -281,4 +283,6 @@ extern JsonbValue *JsonPathValue(Datum jb, JsonPath *jp, bool *empty,
extern int EvalJsonPathVar(void *vars, char *varName, int varNameLen,
JsonbValue *val, JsonbValue *baseObject);
extern const TableFuncRoutine JsonbTableRoutine;
#endif

View File

@ -13,3 +13,9 @@ SELECT JSON_QUERY(NULL FORMAT JSON, '$');
ERROR: JSON_QUERY() is not yet implemented for json type
LINE 1: SELECT JSON_QUERY(NULL FORMAT JSON, '$');
^
-- JSON_TABLE
SELECT * FROM JSON_TABLE(NULL FORMAT JSON, '$' COLUMNS (foo text));
ERROR: JSON_TABLE() is not yet implemented for json type
LINE 1: SELECT * FROM JSON_TABLE(NULL FORMAT JSON, '$' COLUMNS (foo ...
^
HINT: Try casting the argument to jsonb

View File

@ -1020,3 +1020,565 @@ CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.dateti
ERROR: functions in index expression must be marked IMMUTABLE
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x));
DROP TABLE test_jsonb_mutability;
-- JSON_TABLE
-- Should fail (JSON_TABLE can be used only in FROM clause)
SELECT JSON_TABLE('[]', '$');
ERROR: syntax error at or near "("
LINE 1: SELECT JSON_TABLE('[]', '$');
^
-- Should fail (no columns)
SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ());
ERROR: syntax error at or near ")"
LINE 1: SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ());
^
-- NULL => empty table
SELECT * FROM JSON_TABLE(NULL::jsonb, '$' COLUMNS (foo int)) bar;
foo
-----
(0 rows)
--
SELECT * FROM JSON_TABLE(jsonb '123', '$'
COLUMNS (item int PATH '$', foo int)) bar;
item | foo
------+-----
123 |
(1 row)
-- JSON_TABLE: basic functionality
CREATE DOMAIN jsonb_test_domain AS text CHECK (value <> 'foo');
SELECT *
FROM
(VALUES
('1'),
('[]'),
('{}'),
('[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""]')
) vals(js)
LEFT OUTER JOIN
-- JSON_TABLE is implicitly lateral
JSON_TABLE(
vals.js::jsonb, 'lax $[*]'
COLUMNS (
id FOR ORDINALITY,
id2 FOR ORDINALITY, -- allowed additional ordinality columns
"int" int PATH '$',
"text" text PATH '$',
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
"domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
jsc char(4) FORMAT JSON PATH '$',
jsv varchar(4) FORMAT JSON PATH '$',
jsb jsonb FORMAT JSON PATH '$',
jsbq jsonb FORMAT JSON PATH '$' OMIT QUOTES,
aaa int, -- implicit path '$."aaa"',
aaa1 int PATH '$.aaa',
exists1 bool EXISTS PATH '$.aaa',
exists2 int EXISTS PATH '$.aaa',
exists3 int EXISTS PATH 'strict $.aaa' UNKNOWN ON ERROR,
exists4 text EXISTS PATH 'strict $.aaa' FALSE ON ERROR,
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
ia int[] PATH '$',
ta text[] PATH '$',
jba jsonb[] PATH '$'
)
) jt
ON true;
js | id | id2 | int | text | char(4) | bool | numeric | domain | js | jb | jst | jsc | jsv | jsb | jsbq | aaa | aaa1 | exists1 | exists2 | exists3 | exists4 | js2 | jsb2w | jsb2q | ia | ta | jba
---------------------------------------------------------------------------------------+----+-----+-----+---------+---------+------+---------+---------+--------------+--------------+--------------+------+------+--------------+--------------+-----+------+---------+---------+---------+---------+--------------+----------------+--------------+----+----+-----
1 | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | f | 0 | | false | 1 | [1] | 1 | | |
[] | | | | | | | | | | | | | | | | | | | | | | | | | | |
{} | 1 | 1 | | | | | | | {} | {} | {} | {} | {} | {} | {} | | | f | 0 | | false | {} | [{}] | {} | | |
[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | f | 0 | | false | 1 | [1] | 1 | | |
[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 2 | 2 | 1 | 1.23 | 1.23 | | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | | | f | 0 | | false | 1.23 | [1.23] | 1.23 | | |
[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 3 | 3 | 2 | 2 | 2 | | 2 | 2 | "2" | "2" | "2" | "2" | "2" | "2" | 2 | | | f | 0 | | false | "2" | ["2"] | 2 | | |
[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 4 | 4 | | aaaaaaa | aaaa | | | aaaaaaa | "aaaaaaa" | "aaaaaaa" | "aaaaaaa" | "aaa | "aaa | "aaaaaaa" | | | | f | 0 | | false | "aaaaaaa" | ["aaaaaaa"] | | | |
[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 5 | 5 | | foo | foo | | | | "foo" | "foo" | "foo" | "foo | "foo | "foo" | | | | f | 0 | | false | "foo" | ["foo"] | | | |
[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 6 | 6 | | | | | | | null | null | null | null | null | null | null | | | f | 0 | | false | null | [null] | null | | |
[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 7 | 7 | 0 | false | fals | f | | false | false | false | false | fals | fals | false | false | | | f | 0 | | false | false | [false] | false | | |
[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 8 | 8 | 1 | true | true | t | | true | true | true | true | true | true | true | true | | | f | 0 | | false | true | [true] | true | | |
[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 9 | 9 | | | | | | | {"aaa": 123} | {"aaa": 123} | {"aaa": 123} | {"aa | {"aa | {"aaa": 123} | {"aaa": 123} | 123 | 123 | t | 1 | 1 | true | {"aaa": 123} | [{"aaa": 123}] | {"aaa": 123} | | |
[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 10 | 10 | | [1,2] | [1,2 | | | [1,2] | "[1,2]" | "[1,2]" | "[1,2]" | "[1, | "[1, | "[1,2]" | [1, 2] | | | f | 0 | | false | "[1,2]" | ["[1,2]"] | [1, 2] | | |
[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 11 | 11 | | "str" | "str | | | "str" | "\"str\"" | "\"str\"" | "\"str\"" | "\"s | "\"s | "\"str\"" | "str" | | | f | 0 | | false | "\"str\"" | ["\"str\""] | "str" | | |
(14 rows)
-- JSON_TABLE: Test backward parsing
CREATE VIEW jsonb_table_view AS
SELECT * FROM
JSON_TABLE(
jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
COLUMNS (
id FOR ORDINALITY,
id2 FOR ORDINALITY, -- allowed additional ordinality columns
"int" int PATH '$',
"text" text PATH '$',
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
"domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
jsc char(4) FORMAT JSON PATH '$',
jsv varchar(4) FORMAT JSON PATH '$',
jsb jsonb FORMAT JSON PATH '$',
jsbq jsonb FORMAT JSON PATH '$' OMIT QUOTES,
aaa int, -- implicit path '$."aaa"',
aaa1 int PATH '$.aaa',
exists1 bool EXISTS PATH '$.aaa',
exists2 int EXISTS PATH '$.aaa' TRUE ON ERROR,
exists3 text EXISTS PATH 'strict $.aaa' UNKNOWN ON ERROR,
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
ia int[] PATH '$',
ta text[] PATH '$',
jba jsonb[] PATH '$',
NESTED PATH '$[1]' COLUMNS (
a1 int,
NESTED PATH '$[*]' COLUMNS (
a11 text
),
b1 text
),
NESTED PATH '$[2]' COLUMNS (
NESTED PATH '$[*]' COLUMNS (
a21 text
),
NESTED PATH '$[*]' COLUMNS (
a22 text
)
)
)
);
\sv jsonb_table_view
CREATE OR REPLACE VIEW public.jsonb_table_view AS
SELECT "json_table".id,
"json_table".id2,
"json_table"."int",
"json_table".text,
"json_table"."char(4)",
"json_table".bool,
"json_table"."numeric",
"json_table".domain,
"json_table".js,
"json_table".jb,
"json_table".jst,
"json_table".jsc,
"json_table".jsv,
"json_table".jsb,
"json_table".jsbq,
"json_table".aaa,
"json_table".aaa1,
"json_table".exists1,
"json_table".exists2,
"json_table".exists3,
"json_table".js2,
"json_table".jsb2w,
"json_table".jsb2q,
"json_table".ia,
"json_table".ta,
"json_table".jba,
"json_table".a1,
"json_table".b1,
"json_table".a11,
"json_table".a21,
"json_table".a22
FROM JSON_TABLE(
'null'::jsonb, '$[*]'
PASSING
1 + 2 AS a,
'"foo"'::json AS "b c"
COLUMNS (
id FOR ORDINALITY,
id2 FOR ORDINALITY,
"int" integer PATH '$',
text text PATH '$',
"char(4)" character(4) PATH '$',
bool boolean PATH '$',
"numeric" numeric PATH '$',
domain jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
jsc character(4) FORMAT JSON PATH '$',
jsv character varying(4) FORMAT JSON PATH '$',
jsb jsonb PATH '$',
jsbq jsonb PATH '$' OMIT QUOTES,
aaa integer PATH '$."aaa"',
aaa1 integer PATH '$."aaa"',
exists1 boolean EXISTS PATH '$."aaa"',
exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR,
exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR,
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
ia integer[] PATH '$',
ta text[] PATH '$',
jba jsonb[] PATH '$',
NESTED PATH '$[1]'
COLUMNS (
a1 integer PATH '$."a1"',
b1 text PATH '$."b1"',
NESTED PATH '$[*]'
COLUMNS (
a11 text PATH '$."a11"'
)
),
NESTED PATH '$[2]'
COLUMNS (
NESTED PATH '$[*]'
COLUMNS (
a21 text PATH '$."a21"'
),
NESTED PATH '$[*]'
COLUMNS (
a22 text PATH '$."a22"'
)
)
)
)
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Table Function Scan on "json_table"
Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".domain, "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".jsbq, "json_table".aaa, "json_table".aaa1, "json_table".exists1, "json_table".exists2, "json_table".exists3, "json_table".js2, "json_table".jsb2w, "json_table".jsb2q, "json_table".ia, "json_table".ta, "json_table".jba, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22
Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', domain jsonb_test_domain PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb PATH '$', jsbq jsonb PATH '$' OMIT QUOTES, aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', exists1 boolean EXISTS PATH '$."aaa"', exists2 integer EXISTS PATH '$."aaa"' TRUE ON ERROR, exists3 text EXISTS PATH 'strict $."aaa"' UNKNOWN ON ERROR, js2 json PATH '$', jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER, jsb2q jsonb PATH '$' OMIT QUOTES, ia integer[] PATH '$', ta text[] PATH '$', jba jsonb[] PATH '$', NESTED PATH '$[1]' COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' COLUMNS ( NESTED PATH '$[*]' COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' COLUMNS (a22 text PATH '$."a22"'))))
(3 rows)
DROP VIEW jsonb_table_view;
DROP DOMAIN jsonb_test_domain;
-- JSON_TABLE: ON EMPTY/ON ERROR behavior
SELECT *
FROM
(VALUES ('1'), ('"err"')) vals(js),
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$')) jt;
js | a
-------+---
1 | 1
"err" |
(2 rows)
SELECT *
FROM
(VALUES ('1'), ('"err"')) vals(js)
LEFT OUTER JOIN
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt
ON true;
ERROR: invalid input syntax for type integer: "err"
SELECT *
FROM
(VALUES ('1'), ('"err"')) vals(js)
LEFT OUTER JOIN
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
ON true;
ERROR: invalid input syntax for type integer: "err"
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
a
---
(1 row)
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
ERROR: jsonpath member accessor can only be applied to an object
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
ERROR: no SQL/JSON item
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
a
---
2
(1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
a
---
2
(1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
a
---
1
(1 row)
-- JSON_TABLE: EXISTS PATH types
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int4 EXISTS PATH '$.a'));
a
---
0
(1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXISTS PATH '$.a'));
ERROR: cannot cast type boolean to smallint
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXISTS PATH '$.a'));
ERROR: cannot cast type boolean to bigint
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 EXISTS PATH '$.a'));
ERROR: cannot cast type boolean to real
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 E...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH '$.a'));
a
-----
fal
(1 row)
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXISTS PATH '$.a'));
ERROR: cannot cast type boolean to json
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXI...
^
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EXISTS PATH '$.a'));
ERROR: cannot cast type boolean to jsonb
LINE 1: ...ELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EX...
^
-- JSON_TABLE: nested paths and plans
-- Should fail (column names must be distinct)
SELECT * FROM JSON_TABLE(
jsonb '[]', '$'
COLUMNS (
a int,
b text,
a jsonb
)
) jt;
ERROR: duplicate JSON_TABLE column name: a
HINT: JSON_TABLE column names must be distinct from one another
SELECT * FROM JSON_TABLE(
jsonb '[]', '$'
COLUMNS (
b int,
NESTED PATH '$'
COLUMNS (
c int,
b text
)
)
) jt;
ERROR: duplicate JSON_TABLE column name: b
HINT: JSON_TABLE column names must be distinct from one another
SELECT * FROM JSON_TABLE(
jsonb '[]', '$'
COLUMNS (
NESTED PATH '$'
COLUMNS (
b int
),
NESTED PATH '$'
COLUMNS (
NESTED PATH '$'
COLUMNS (
c int,
b text
)
)
)
) jt;
ERROR: duplicate JSON_TABLE column name: b
HINT: JSON_TABLE column names must be distinct from one another
-- JSON_TABLE: plan execution
CREATE TEMP TABLE jsonb_table_test (js jsonb);
INSERT INTO jsonb_table_test
VALUES (
'[
{"a": 1, "b": [], "c": []},
{"a": 2, "b": [1, 2, 3], "c": [10, null, 20]},
{"a": 3, "b": [1, 2], "c": []},
{"x": "4", "b": [1, 2], "c": 123}
]'
);
-- unspecified plan (outer, union)
select
jt.*
from
jsonb_table_test jtt,
json_table (
jtt.js,'strict $[*]'
columns (
n for ordinality,
a int path 'lax $.a' default -1 on empty,
nested path 'strict $.b[*]' columns ( b int path '$' ),
nested path 'strict $.c[*]' columns ( c int path '$' )
)
) jt;
n | a | b | c
---+----+---+----
1 | 1 | |
2 | 2 | 1 |
2 | 2 | 2 |
2 | 2 | 3 |
2 | 2 | | 10
2 | 2 | |
2 | 2 | | 20
3 | 3 | 1 |
3 | 3 | 2 |
4 | -1 | 1 |
4 | -1 | 2 |
(11 rows)
-- Should succeed (JSON arguments are passed to root and nested paths)
SELECT *
FROM
generate_series(1, 4) x,
generate_series(1, 3) y,
JSON_TABLE(jsonb
'[[1,2,3],[2,3,4,5],[3,4,5,6]]',
'strict $[*] ? (@[*] < $x)'
PASSING x AS x, y AS y
COLUMNS (
y text FORMAT JSON PATH '$',
NESTED PATH 'strict $[*] ? (@ >= $y)'
COLUMNS (
z int PATH '$'
)
)
) jt;
x | y | y | z
---+---+--------------+---
2 | 1 | [1, 2, 3] | 1
2 | 1 | [1, 2, 3] | 2
2 | 1 | [1, 2, 3] | 3
3 | 1 | [1, 2, 3] | 1
3 | 1 | [1, 2, 3] | 2
3 | 1 | [1, 2, 3] | 3
3 | 1 | [2, 3, 4, 5] | 2
3 | 1 | [2, 3, 4, 5] | 3
3 | 1 | [2, 3, 4, 5] | 4
3 | 1 | [2, 3, 4, 5] | 5
4 | 1 | [1, 2, 3] | 1
4 | 1 | [1, 2, 3] | 2
4 | 1 | [1, 2, 3] | 3
4 | 1 | [2, 3, 4, 5] | 2
4 | 1 | [2, 3, 4, 5] | 3
4 | 1 | [2, 3, 4, 5] | 4
4 | 1 | [2, 3, 4, 5] | 5
4 | 1 | [3, 4, 5, 6] | 3
4 | 1 | [3, 4, 5, 6] | 4
4 | 1 | [3, 4, 5, 6] | 5
4 | 1 | [3, 4, 5, 6] | 6
2 | 2 | [1, 2, 3] | 2
2 | 2 | [1, 2, 3] | 3
3 | 2 | [1, 2, 3] | 2
3 | 2 | [1, 2, 3] | 3
3 | 2 | [2, 3, 4, 5] | 2
3 | 2 | [2, 3, 4, 5] | 3
3 | 2 | [2, 3, 4, 5] | 4
3 | 2 | [2, 3, 4, 5] | 5
4 | 2 | [1, 2, 3] | 2
4 | 2 | [1, 2, 3] | 3
4 | 2 | [2, 3, 4, 5] | 2
4 | 2 | [2, 3, 4, 5] | 3
4 | 2 | [2, 3, 4, 5] | 4
4 | 2 | [2, 3, 4, 5] | 5
4 | 2 | [3, 4, 5, 6] | 3
4 | 2 | [3, 4, 5, 6] | 4
4 | 2 | [3, 4, 5, 6] | 5
4 | 2 | [3, 4, 5, 6] | 6
2 | 3 | [1, 2, 3] | 3
3 | 3 | [1, 2, 3] | 3
3 | 3 | [2, 3, 4, 5] | 3
3 | 3 | [2, 3, 4, 5] | 4
3 | 3 | [2, 3, 4, 5] | 5
4 | 3 | [1, 2, 3] | 3
4 | 3 | [2, 3, 4, 5] | 3
4 | 3 | [2, 3, 4, 5] | 4
4 | 3 | [2, 3, 4, 5] | 5
4 | 3 | [3, 4, 5, 6] | 3
4 | 3 | [3, 4, 5, 6] | 4
4 | 3 | [3, 4, 5, 6] | 5
4 | 3 | [3, 4, 5, 6] | 6
(52 rows)
-- Should fail (JSON arguments are not passed to column paths)
SELECT *
FROM JSON_TABLE(
jsonb '[1,2,3]',
'$[*] ? (@ < $x)'
PASSING 10 AS x
COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
) jt;
ERROR: could not find jsonpath variable "x"
-- Extension: non-constant JSON path
SELECT JSON_EXISTS(jsonb '{"a": 123}', '$' || '.' || 'a');
json_exists
-------------
t
(1 row)
SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a');
json_value
------------
123
(1 row)
SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY);
json_value
------------
foo
(1 row)
SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a');
json_query
------------
123
(1 row)
SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER);
json_query
------------
[123]
(1 row)
-- Should fail (invalid path)
SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error');
ERROR: syntax error, unexpected IDENT_P at or near " " of jsonpath input
-- Should fail (not supported)
SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int));
ERROR: only string constants supported in JSON_TABLE path specification
LINE 1: SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || '...
^
-- Test parallel JSON_VALUE()
CREATE TABLE test_parallel_jsonb_value AS
SELECT i::text::jsonb AS js
FROM generate_series(1, 1000000) i;
-- Should be non-parallel due to subtransactions
EXPLAIN (COSTS OFF)
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
QUERY PLAN
---------------------------------------------
Aggregate
-> Seq Scan on test_parallel_jsonb_value
(2 rows)
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
sum
--------------
500000500000
(1 row)
-- Should be parallel
EXPLAIN (COSTS OFF)
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value;
QUERY PLAN
------------------------------------------------------------------
Finalize Aggregate
-> Gather
Workers Planned: 2
-> Partial Aggregate
-> Parallel Seq Scan on test_parallel_jsonb_value
(5 rows)
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value;
sum
--------------
500000500000
(1 row)

View File

@ -9,3 +9,7 @@ SELECT JSON_VALUE(NULL FORMAT JSON, '$');
-- JSON_QUERY
SELECT JSON_QUERY(NULL FORMAT JSON, '$');
-- JSON_TABLE
SELECT * FROM JSON_TABLE(NULL FORMAT JSON, '$' COLUMNS (foo text));

View File

@ -319,3 +319,287 @@ CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime()
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x));
DROP TABLE test_jsonb_mutability;
-- JSON_TABLE
-- Should fail (JSON_TABLE can be used only in FROM clause)
SELECT JSON_TABLE('[]', '$');
-- Should fail (no columns)
SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ());
-- NULL => empty table
SELECT * FROM JSON_TABLE(NULL::jsonb, '$' COLUMNS (foo int)) bar;
--
SELECT * FROM JSON_TABLE(jsonb '123', '$'
COLUMNS (item int PATH '$', foo int)) bar;
-- JSON_TABLE: basic functionality
CREATE DOMAIN jsonb_test_domain AS text CHECK (value <> 'foo');
SELECT *
FROM
(VALUES
('1'),
('[]'),
('{}'),
('[1, 1.23, "2", "aaaaaaa", "foo", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""]')
) vals(js)
LEFT OUTER JOIN
-- JSON_TABLE is implicitly lateral
JSON_TABLE(
vals.js::jsonb, 'lax $[*]'
COLUMNS (
id FOR ORDINALITY,
id2 FOR ORDINALITY, -- allowed additional ordinality columns
"int" int PATH '$',
"text" text PATH '$',
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
"domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
jsc char(4) FORMAT JSON PATH '$',
jsv varchar(4) FORMAT JSON PATH '$',
jsb jsonb FORMAT JSON PATH '$',
jsbq jsonb FORMAT JSON PATH '$' OMIT QUOTES,
aaa int, -- implicit path '$."aaa"',
aaa1 int PATH '$.aaa',
exists1 bool EXISTS PATH '$.aaa',
exists2 int EXISTS PATH '$.aaa',
exists3 int EXISTS PATH 'strict $.aaa' UNKNOWN ON ERROR,
exists4 text EXISTS PATH 'strict $.aaa' FALSE ON ERROR,
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
ia int[] PATH '$',
ta text[] PATH '$',
jba jsonb[] PATH '$'
)
) jt
ON true;
-- JSON_TABLE: Test backward parsing
CREATE VIEW jsonb_table_view AS
SELECT * FROM
JSON_TABLE(
jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c"
COLUMNS (
id FOR ORDINALITY,
id2 FOR ORDINALITY, -- allowed additional ordinality columns
"int" int PATH '$',
"text" text PATH '$',
"char(4)" char(4) PATH '$',
"bool" bool PATH '$',
"numeric" numeric PATH '$',
"domain" jsonb_test_domain PATH '$',
js json PATH '$',
jb jsonb PATH '$',
jst text FORMAT JSON PATH '$',
jsc char(4) FORMAT JSON PATH '$',
jsv varchar(4) FORMAT JSON PATH '$',
jsb jsonb FORMAT JSON PATH '$',
jsbq jsonb FORMAT JSON PATH '$' OMIT QUOTES,
aaa int, -- implicit path '$."aaa"',
aaa1 int PATH '$.aaa',
exists1 bool EXISTS PATH '$.aaa',
exists2 int EXISTS PATH '$.aaa' TRUE ON ERROR,
exists3 text EXISTS PATH 'strict $.aaa' UNKNOWN ON ERROR,
js2 json PATH '$',
jsb2w jsonb PATH '$' WITH WRAPPER,
jsb2q jsonb PATH '$' OMIT QUOTES,
ia int[] PATH '$',
ta text[] PATH '$',
jba jsonb[] PATH '$',
NESTED PATH '$[1]' COLUMNS (
a1 int,
NESTED PATH '$[*]' COLUMNS (
a11 text
),
b1 text
),
NESTED PATH '$[2]' COLUMNS (
NESTED PATH '$[*]' COLUMNS (
a21 text
),
NESTED PATH '$[*]' COLUMNS (
a22 text
)
)
)
);
\sv jsonb_table_view
EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view;
DROP VIEW jsonb_table_view;
DROP DOMAIN jsonb_test_domain;
-- JSON_TABLE: ON EMPTY/ON ERROR behavior
SELECT *
FROM
(VALUES ('1'), ('"err"')) vals(js),
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$')) jt;
SELECT *
FROM
(VALUES ('1'), ('"err"')) vals(js)
LEFT OUTER JOIN
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt
ON true;
SELECT *
FROM
(VALUES ('1'), ('"err"')) vals(js)
LEFT OUTER JOIN
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
ON true;
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
-- JSON_TABLE: EXISTS PATH types
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int4 EXISTS PATH '$.a'));
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int2 EXISTS PATH '$.a'));
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int8 EXISTS PATH '$.a'));
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a float4 EXISTS PATH '$.a'));
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a char(3) EXISTS PATH '$.a'));
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a json EXISTS PATH '$.a'));
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a jsonb EXISTS PATH '$.a'));
-- JSON_TABLE: nested paths and plans
-- Should fail (column names must be distinct)
SELECT * FROM JSON_TABLE(
jsonb '[]', '$'
COLUMNS (
a int,
b text,
a jsonb
)
) jt;
SELECT * FROM JSON_TABLE(
jsonb '[]', '$'
COLUMNS (
b int,
NESTED PATH '$'
COLUMNS (
c int,
b text
)
)
) jt;
SELECT * FROM JSON_TABLE(
jsonb '[]', '$'
COLUMNS (
NESTED PATH '$'
COLUMNS (
b int
),
NESTED PATH '$'
COLUMNS (
NESTED PATH '$'
COLUMNS (
c int,
b text
)
)
)
) jt;
-- JSON_TABLE: plan execution
CREATE TEMP TABLE jsonb_table_test (js jsonb);
INSERT INTO jsonb_table_test
VALUES (
'[
{"a": 1, "b": [], "c": []},
{"a": 2, "b": [1, 2, 3], "c": [10, null, 20]},
{"a": 3, "b": [1, 2], "c": []},
{"x": "4", "b": [1, 2], "c": 123}
]'
);
-- unspecified plan (outer, union)
select
jt.*
from
jsonb_table_test jtt,
json_table (
jtt.js,'strict $[*]'
columns (
n for ordinality,
a int path 'lax $.a' default -1 on empty,
nested path 'strict $.b[*]' columns ( b int path '$' ),
nested path 'strict $.c[*]' columns ( c int path '$' )
)
) jt;
-- Should succeed (JSON arguments are passed to root and nested paths)
SELECT *
FROM
generate_series(1, 4) x,
generate_series(1, 3) y,
JSON_TABLE(jsonb
'[[1,2,3],[2,3,4,5],[3,4,5,6]]',
'strict $[*] ? (@[*] < $x)'
PASSING x AS x, y AS y
COLUMNS (
y text FORMAT JSON PATH '$',
NESTED PATH 'strict $[*] ? (@ >= $y)'
COLUMNS (
z int PATH '$'
)
)
) jt;
-- Should fail (JSON arguments are not passed to column paths)
SELECT *
FROM JSON_TABLE(
jsonb '[1,2,3]',
'$[*] ? (@ < $x)'
PASSING 10 AS x
COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)')
) jt;
-- Extension: non-constant JSON path
SELECT JSON_EXISTS(jsonb '{"a": 123}', '$' || '.' || 'a');
SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a');
SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY);
SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a');
SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER);
-- Should fail (invalid path)
SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error');
-- Should fail (not supported)
SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int));
-- Test parallel JSON_VALUE()
CREATE TABLE test_parallel_jsonb_value AS
SELECT i::text::jsonb AS js
FROM generate_series(1, 1000000) i;
-- Should be non-parallel due to subtransactions
EXPLAIN (COSTS OFF)
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value;
-- Should be parallel
EXPLAIN (COSTS OFF)
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value;
SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value;

View File

@ -1238,6 +1238,14 @@ JsonPathParseResult
JsonPathPredicateCallback
JsonPathString
JsonSemAction
JsonTable
JsonTableColumn
JsonTableColumnType
JsonTableContext
JsonTableJoinState
JsonTableParent
JsonTableScanState
JsonTableSibling
JsonTokenType
JsonTransformStringValuesAction
JsonTypeCategory
@ -2645,6 +2653,7 @@ TableFunc
TableFuncRoutine
TableFuncScan
TableFuncScanState
TableFuncType
TableInfo
TableLikeClause
TableSampleClause