*
* ddljson.cpp
* JSON code related to DDL command deparsing
*
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* NOTES
*
* Each JSONB object is supposed to have a "fmt" which will tell expansion
* routines how JSONB can be expanded to construct ddl command. One example
* snippet from JSONB object for 'ALTER TABLE sales ADD col1 int':
*
* { *1-level*
* "fmt": "ALTER %{objtype}s %{only}s %{identity}D %{subcmds:, }s",
* "only": "",
* "objtype": "TABLE",
* "identity": {"objname": "sales", "schemaname": "public"}
* "subcmds": [
* { *2-level*
* "fmt": "ADD %{objtype}s %{if_not_exists}s %{definition}s",
* "type": "add column",
* "objtype": "COLUMN",
* "definition": {}
* ...
* }
* ...
* }
*
* From above, we can see different key-value pairs.
* level-1 represents ROOT object with 'fmt', 'only', 'objtype','identity',
* 'subcmds' as the keys with the values appended after ":" with each key.
* Value can be string, bool, numeric, array or any nested object. As an
* example, "objtype" has string value while "subcmds" has nested-object
* as its value which can further have multiple key-value pairs.
*
* The value of "fmt" tells us how the expansion will be carried on. The
* value of "fmt" may contain zero or more %-escapes, which consist of key
* name enclosed in { }, followed by a conversion specifier which tells us
* how the value for that particular key should be expanded.
* Possible conversion specifiers are:
* % expand to a literal %
* I expand as a single, non-qualified identifier
* D expand as a possibly-qualified identifier
* T expand as a type name
* L expand as a string literal (quote using single quotes)
* s expand as a simple string (no quoting)
* n expand as a simple number (no quoting)
*
* In order to build a DDL command, it will first extract "fmt" node in
* jsonb string and will read each key name enclosed in { } in fmt-string
* and will replace it with its value. For each name mentioned in { } in
* fmt string, there must be a key-value pair, in absence of which, the
* expansion will error out. While doing this expansion, it will consider
* the conversion-specifier maintained with each key in fmt string to figure
* out how value should actually be represented. This is how DDL command can
* be constructed back from the jsonb-string.
*
* IDENTIFICATION
* src/gausskernel/optimizer/commands/ddljson.cpp
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_thread.h"
#include "tcop/ddldeparse.h"
#include "utils/builtins.h"
#include "lib/stringinfo.h"
#include "utils/jsonb.h"
#include "utils/json.h"
#define ADVANCE_PARSE_POINTER(ptr,end_ptr) \
do { \
if (++(ptr) >= (end_ptr)) \
ereport(ERROR, \
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
errmsg("unterminated format specifier"))); \
} while (0)
* Conversion specifier which determines how to expand the JSON element
* into a string.
*/
typedef enum
{
SpecDottedName,
SpecIdentifier,
SpecNumber,
SpecString,
SpecStringLiteral,
SpecTypeName
} convSpecifier;
* A ternary value that represents a boolean type JsonbValue.
*/
typedef enum
{
tv_absent,
tv_true,
tv_false
} json_trivalue;
static bool expand_one_jsonb_element(StringInfo buf, char *param,
JsonbValue *jsonval, convSpecifier specifier,
const char *fmt);
static void expand_jsonb_array(StringInfo buf, char *param,
JsonbValue *jsonarr, char *arraysep,
convSpecifier specifier, const char *fmt);
static void fmtstr_error_callback(void *arg);
static void expand_fmt_recursive(StringInfo buf, JsonbSuperHeader sheader);
* Given a JsonbContainer, find the JsonbValue with the given key name in it.
* If it's of a type other than jbvBool, an error is raised. If it doesn't
* exist, tv_absent is returned; otherwise return the actual json_trivalue.
*/
static json_trivalue
find_bool_in_jsonbcontainer(JsonbSuperHeader sheader, char *keyname)
{
JsonbValue key;
JsonbValue *value;
json_trivalue result;
key.type = jbvString;
key.string.val = keyname;
key.string.len = strlen(keyname);
value = findJsonbValueFromSuperHeader(sheader,
JB_FOBJECT, NULL, &key);
if (value == NULL)
return tv_absent;
if (value->type != jbvBool)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("element \"%s\" is not of type boolean", keyname)));
result = value->boolean ? tv_true : tv_false;
pfree(value);
return result;
}
* Given a JsonbContainer, find the JsonbValue with the given key name in it.
* If it's of a type other than jbvString, an error is raised. If it doesn't
* exist, an error is raised unless missing_ok; otherwise return NULL.
*
* If it exists and is a string, a freshly palloc'ed copy is returned.
*
* If *length is not NULL, it is set to the length of the string.
*/
static char *
find_string_in_jsonbcontainer(JsonbSuperHeader sheader, char *keyname,
bool missing_ok, int *length)
{
JsonbValue key;
JsonbValue *value;
char *str;
key.type = jbvString;
key.string.val = keyname;
key.string.len = strlen(keyname);
value = findJsonbValueFromSuperHeader(sheader,
JB_FOBJECT, NULL, &key);
if (value == NULL) {
if (missing_ok)
return NULL;
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("missing element \"%s\" in JSON object", keyname)));
}
if (value->type != jbvString)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("element \"%s\" is not of type string", keyname)));
str = pnstrdup(value->string.val, value->string.len);
if (length)
*length = value->string.len;
pfree(value);
return str;
}
* Expand a json value as a quoted identifier. The value must be of type string.
*/
static void
expand_jsonval_identifier(StringInfo buf, JsonbValue *jsonval)
{
char *str;
Assert(jsonval->type == jbvString);
str = pnstrdup(jsonval->string.val, jsonval->string.len);
appendStringInfoString(buf, quote_identifier(str));
pfree(str);
}
* Expand a json value as a dot-separated-name. The value must be of type
* binary and may contain elements "schemaname" (optional), "objname"
* (mandatory), "attrname" (optional). Double quotes are added to each element
* as necessary, and dot separators where needed.
*
* One day we might need a "catalog" element as well, but no current use case
* needs that.
*/
static void
expand_jsonval_dottedname(StringInfo buf, JsonbValue *jsonval)
{
char *str;
JsonbSuperHeader data = jsonval->binary.data;
Assert(jsonval->type == jbvBinary);
str = find_string_in_jsonbcontainer(data, "schemaname", true, NULL);
if (str) {
appendStringInfo(buf, "%s.", quote_identifier(str));
pfree(str);
}
str = find_string_in_jsonbcontainer(data, "objname", false, NULL);
appendStringInfo(buf, "%s", quote_identifier(str));
pfree(str);
str = find_string_in_jsonbcontainer(data, "attrname", true, NULL);
if (str) {
appendStringInfo(buf, ".%s", quote_identifier(str));
pfree(str);
}
}
* Expand a JSON value as a type name.
*/
static void
expand_jsonval_typename(StringInfo buf, JsonbValue *jsonval)
{
char *schema = NULL;
char *typname = NULL;
char *typmodstr = NULL;
json_trivalue is_array;
char *array_decor = NULL;
JsonbSuperHeader data = jsonval->binary.data;
* We omit schema-qualifying the output name if the schema element is
* either the empty string or NULL; the difference between those two cases
* is that in the latter we quote the type name, in the former we don't.
* This allows for types with special typmod needs, such as interval and
* timestamp (see format_type_detailed), while at the same time allowing
* for the schema name to be omitted from type names that require quotes
* but are to be obtained from a user schema.
*/
schema = find_string_in_jsonbcontainer(data, "schemaname", true, NULL);
typname = find_string_in_jsonbcontainer(data, "typename", false, NULL);
typmodstr = find_string_in_jsonbcontainer(data, "typmod", true, NULL);
is_array = find_bool_in_jsonbcontainer(data, "typarray");
switch (is_array) {
case tv_true:
array_decor = "[]";
break;
case tv_false:
array_decor = "";
break;
case tv_absent:
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("missing typarray element")));
}
if (schema == NULL)
appendStringInfo(buf, "%s", quote_identifier(typname));
else if (schema[0] == '\0')
appendStringInfo(buf, "%s", typname);
else
appendStringInfo(buf, "%s.%s", quote_identifier(schema),
quote_identifier(typname));
appendStringInfo(buf, "%s%s", typmodstr ? typmodstr : "", array_decor);
if (schema)
pfree(schema);
if (typname)
pfree(typname);
if (typmodstr)
pfree(typmodstr);
}
* Expand a JSON value as a string. The value must be of type string or of
* type Binary. In the latter case, it must contain a "fmt" element which will
* be recursively expanded; also, if the object contains an element "present"
* and it is set to false, the expansion is the empty string.
*
* Returns false if no actual expansion was made due to the "present" flag
* being set to "false".
*
* The caller is responsible to check jsonval is of type jbvString or jbvBinary.
*/
static bool
expand_jsonval_string(StringInfo buf, JsonbValue *jsonval)
{
bool expanded = false;
Assert((jsonval->type == jbvString) || (jsonval->type == jbvBinary));
if (jsonval->type == jbvString) {
appendBinaryStringInfo(buf, jsonval->string.val,
jsonval->string.len);
expanded = true;
} else if (jsonval->type == jbvBinary) {
json_trivalue present;
present = find_bool_in_jsonbcontainer((JsonbSuperHeader)jsonval->binary.data,
"present");
* If "present" is set to false, this element expands to empty;
* otherwise (either true or absent), expand "fmt".
*/
if (present != tv_false) {
expand_fmt_recursive(buf, (JsonbSuperHeader)jsonval->binary.data);
expanded = true;
}
}
return expanded;
}
* Expand a JSON value as a string literal.
*/
static void
expand_jsonval_strlit(StringInfo buf, JsonbValue *jsonval)
{
char *str;
StringInfoData dqdelim;
static const char dqsuffixes[] = "_XYZZYX_";
int dqnextchar = 0;
Assert(jsonval->type == jbvString);
str = pnstrdup(jsonval->string.val, jsonval->string.len);
if (strpbrk(str, "\'\\") == NULL) {
appendStringInfo(buf, "'%s'", str);
pfree(str);
return;
}
initStringInfo(&dqdelim);
appendStringInfoString(&dqdelim, "$");
while (strstr(str, dqdelim.data) != NULL) {
appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]);
dqnextchar = dqnextchar % (sizeof(dqsuffixes) - 1);
}
appendStringInfoChar(&dqdelim, '$');
appendStringInfo(buf, "%s%s%s", dqdelim.data, str, dqdelim.data);
pfree(dqdelim.data);
pfree(str);
}
* Expand a JSON value as an integer quantity.
*/
static void
expand_jsonval_number(StringInfo buf, JsonbValue *jsonval)
{
char *strdatum;
Assert(jsonval->type == jbvNumeric);
strdatum = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(jsonval->numeric)));
appendStringInfoString(buf, strdatum);
pfree(strdatum);
}
* Expand one JSON element into the output StringInfo according to the
* conversion specifier. The element type is validated, and an error is raised
* if it doesn't match what we expect for the conversion specifier.
*
* Returns true, except for the formatted string case if no actual expansion
* was made (due to the "present" flag being set to "false").
*/
static bool
expand_one_jsonb_element(StringInfo buf, char *param, JsonbValue *jsonval,
convSpecifier specifier, const char *fmt)
{
bool string_expanded = true;
ErrorContextCallback sqlerrcontext;
if (fmt) {
sqlerrcontext.callback = fmtstr_error_callback;
sqlerrcontext.arg = (void *) fmt;
sqlerrcontext.previous = t_thrd.log_cxt.error_context_stack;
t_thrd.log_cxt.error_context_stack = &sqlerrcontext;
}
if (!jsonval)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("element \"%s\" not found", param)));
switch (specifier) {
case SpecIdentifier:
if (jsonval->type != jbvString)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("expected JSON string for %%I element \"%s\", got %d",
param, jsonval->type)));
expand_jsonval_identifier(buf, jsonval);
break;
case SpecDottedName:
if (jsonval->type != jbvBinary)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("expected JSON struct for %%D element \"%s\", got %d",
param, jsonval->type)));
expand_jsonval_dottedname(buf, jsonval);
break;
case SpecString:
if (jsonval->type != jbvString && jsonval->type != jbvBinary)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("expected JSON string or struct for %%s element \"%s\", got %d",
param, jsonval->type)));
string_expanded = expand_jsonval_string(buf, jsonval);
break;
case SpecStringLiteral:
if (jsonval->type != jbvString)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("expected JSON string for %%L element \"%s\", got %d",
param, jsonval->type)));
expand_jsonval_strlit(buf, jsonval);
break;
case SpecTypeName:
if (jsonval->type != jbvBinary)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("expected JSON struct for %%T element \"%s\", got %d",
param, jsonval->type)));
expand_jsonval_typename(buf, jsonval);
break;
case SpecNumber:
if (jsonval->type != jbvNumeric)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("expected JSON numeric for %%n element \"%s\", got %d",
param, jsonval->type)));
expand_jsonval_number(buf, jsonval);
break;
}
if (fmt)
t_thrd.log_cxt.error_context_stack = sqlerrcontext.previous;
return string_expanded;
}
* Recursive helper for deparse_ddl_json_to_string.
*
* Find the "fmt" element in the given container, and expand it into the
* provided StringInfo.
*/
static void
expand_fmt_recursive(StringInfo buf, JsonbSuperHeader sheader)
{
JsonbValue key;
JsonbValue *value;
const char *cp;
const char *start_ptr;
const char *end_ptr;
int len;
start_ptr = find_string_in_jsonbcontainer(sheader, "fmt", false, &len);
end_ptr = start_ptr + len;
for (cp = start_ptr; cp < end_ptr; cp++) {
convSpecifier specifier = SpecString;
bool is_array = false;
char *param = NULL;
char *arraysep = NULL;
if (*cp != '%') {
appendStringInfoCharMacro(buf, *cp);
continue;
}
ADVANCE_PARSE_POINTER(cp, end_ptr);
if (*cp == '%') {
appendStringInfoCharMacro(buf, *cp);
continue;
}
* Scan the mandatory element name. Allow for an array separator
* (which may be the empty string) to be specified after a colon.
*/
if (*cp == '{') {
StringInfoData parbuf;
StringInfoData arraysepbuf;
StringInfo appendTo;
initStringInfo(&parbuf);
appendTo = &parbuf;
ADVANCE_PARSE_POINTER(cp, end_ptr);
while (cp < end_ptr) {
if (*cp == ':') {
* Found array separator delimiter; element name is now
* complete, start filling the separator.
*/
initStringInfo(&arraysepbuf);
appendTo = &arraysepbuf;
is_array = true;
ADVANCE_PARSE_POINTER(cp, end_ptr);
continue;
}
if (*cp == '}') {
ADVANCE_PARSE_POINTER(cp, end_ptr);
break;
}
appendStringInfoCharMacro(appendTo, *cp);
ADVANCE_PARSE_POINTER(cp, end_ptr);
}
param = parbuf.data;
if (is_array)
arraysep = arraysepbuf.data;
}
if (param == NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("missing conversion name in conversion specifier")));
switch (*cp) {
case 'I':
specifier = SpecIdentifier;
break;
case 'D':
specifier = SpecDottedName;
break;
case 's':
specifier = SpecString;
break;
case 'L':
specifier = SpecStringLiteral;
break;
case 'T':
specifier = SpecTypeName;
break;
case 'n':
specifier = SpecNumber;
break;
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid conversion specifier \"%c\"", *cp)));
}
* Obtain the element to be expanded.
*/
key.type = jbvString;
key.string.val = param;
key.string.len = strlen(param);
value = findJsonbValueFromSuperHeader(sheader, JB_FOBJECT, NULL, &key);
Assert(value != NULL);
* Expand the data (possibly an array) into the output StringInfo.
*/
if (is_array)
expand_jsonb_array(buf, param, value, arraysep, specifier, start_ptr);
else
expand_one_jsonb_element(buf, param, value, specifier, start_ptr);
pfree(value);
}
}
* Iterate on the elements of a JSON array, expanding each one into the output
* StringInfo per the given conversion specifier, separated by the given
* separator.
*/
static void
expand_jsonb_array(StringInfo buf, char *param,
JsonbValue *jsonarr, char *arraysep, convSpecifier specifier,
const char *fmt)
{
ErrorContextCallback sqlerrcontext;
JsonbIterator *it;
JsonbValue v;
int type;
bool first = true;
StringInfoData arrayelem;
if (fmt) {
sqlerrcontext.callback = fmtstr_error_callback;
sqlerrcontext.arg = (void *) fmt;
sqlerrcontext.previous = t_thrd.log_cxt.error_context_stack;
t_thrd.log_cxt.error_context_stack = &sqlerrcontext;
}
if (!jsonarr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("element \"%s\" not found", param)));
if (jsonarr->type != jbvBinary)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("element \"%s\" is not a JSON array", param)));
JsonbSuperHeader container = jsonarr->binary.data;
if (!((*(uint32*)container) & JB_FARRAY))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("element \"%s\" is not a JSON array", param)));
initStringInfo(&arrayelem);
it = JsonbIteratorInit(container);
while ((type = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) {
if (type == WJB_ELEM) {
resetStringInfo(&arrayelem);
if (expand_one_jsonb_element(&arrayelem, param, &v, specifier, NULL)) {
if (!first)
appendStringInfoString(buf, arraysep);
appendBinaryStringInfo(buf, arrayelem.data, arrayelem.len);
first = false;
}
}
}
if (fmt)
t_thrd.log_cxt.error_context_stack = sqlerrcontext.previous;
}
char *
deparse_ddl_json_to_string(char *json_str, char** owner)
{
Datum d;
Jsonb *jsonb;
StringInfo buf = (StringInfo) palloc0(sizeof(StringInfoData));
initStringInfo(buf);
d = DirectFunctionCall1(jsonb_in, PointerGetDatum(json_str));
jsonb = (Jsonb *) DatumGetPointer(d);
if (owner != NULL) {
JsonbValue key;
key.type = jbvString;
key.string.val = "myowner";
key.string.len = strlen(key.string.val);
JsonbValue *value;
JsonbSuperHeader data = VARDATA(jsonb);
value = findJsonbValueFromSuperHeader(data, JB_FOBJECT, NULL, &key);
if (value) {
errno_t rc = 0;
char *str = NULL;
str = (char*)palloc(value->string.len + 1);
rc = memcpy_s(str, value->string.len, value->string.val, value->string.len);
securec_check(rc, "\0", "\0");
str[value->string.len] = '\0';
*owner = str;
}
else
*owner = NULL;
}
expand_fmt_recursive(buf, VARDATA(jsonb));
return buf->data;
}
* Deparse new publication json, obtain objtype/schemaname/objname.
*
* Verbose syntax
* NEWPUB %{objtype}s %{identity}D
*/
void deparse_newpub_json_elements(char *json_str, char** objtype, char** schemaname, char** objname)
{
Datum d;
Datum obj = DirectFunctionCall1(jsonb_in, PointerGetDatum(json_str));
d = DirectFunctionCall2(jsonb_object_field_text, obj, CStringGetTextDatum("objtype"));
*objtype = TextDatumGetCString(d);
Datum identity = DirectFunctionCall2(jsonb_object_field, obj, CStringGetTextDatum("identity"));
d = DirectFunctionCall2(jsonb_object_field_text, identity, CStringGetTextDatum("schemaname"));
*schemaname = TextDatumGetCString(d);
d = DirectFunctionCall2(jsonb_object_field_text, identity, CStringGetTextDatum("objname"));
*objname = TextDatumGetCString(d);
}
* Error context callback for JSON format string expansion.
*
* XXX: indicate which element we're expanding, if applicable.
*/
static void
fmtstr_error_callback(void *arg)
{
errcontext("while expanding format string \"%s\"", (char *) arg);
}