2018-03-28 08:32:43 -04:00
|
|
|
#include "postgres.h"
|
|
|
|
|
|
2026-06-17 11:04:41 -04:00
|
|
|
#include "miscadmin.h"
|
2018-03-28 08:32:43 -04:00
|
|
|
#include "plpy_elog.h"
|
|
|
|
|
#include "plpy_typeio.h"
|
2025-04-27 11:43:02 -04:00
|
|
|
#include "plpy_util.h"
|
2018-03-28 08:32:43 -04:00
|
|
|
#include "utils/fmgrprotos.h"
|
2019-10-22 23:56:22 -04:00
|
|
|
#include "utils/jsonb.h"
|
2018-05-02 15:58:34 -04:00
|
|
|
#include "utils/numeric.h"
|
2018-03-28 08:32:43 -04:00
|
|
|
|
2025-03-26 11:11:02 -04:00
|
|
|
PG_MODULE_MAGIC_EXT(
|
|
|
|
|
.name = "jsonb_plpython",
|
|
|
|
|
.version = PG_VERSION
|
|
|
|
|
);
|
2018-03-28 08:32:43 -04:00
|
|
|
|
|
|
|
|
/* for PLyObject_AsString in plpy_typeio.c */
|
|
|
|
|
typedef char *(*PLyObject_AsString_t) (PyObject *plrv);
|
|
|
|
|
static PLyObject_AsString_t PLyObject_AsString_p;
|
|
|
|
|
|
|
|
|
|
typedef void (*PLy_elog_impl_t) (int elevel, const char *fmt, ...);
|
|
|
|
|
static PLy_elog_impl_t PLy_elog_impl_p;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* decimal_constructor is a function from python library and used
|
|
|
|
|
* for transforming strings into python decimal type
|
|
|
|
|
*/
|
|
|
|
|
static PyObject *decimal_constructor;
|
|
|
|
|
|
|
|
|
|
static PyObject *PLyObject_FromJsonbContainer(JsonbContainer *jsonb);
|
Revise APIs for pushJsonbValue() and associated routines.
Instead of passing "JsonbParseState **" to pushJsonbValue(),
pass a pointer to a JsonbInState, which will contain the
parseState stack pointer as well as other useful fields.
Also, instead of returning a JsonbValue pointer that is often
meaningless/ignored, return the top-level JsonbValue pointer
in the "result" field of the JsonbInState.
This involves a lot of (mostly mechanical) edits, but I think
the results are notationally cleaner and easier to understand.
Certainly the business with sometimes capturing the result of
pushJsonbValue() and sometimes not was bug-prone and incapable of
mechanical verification. In the new arrangement, JsonbInState.result
remains null until we've completed a valid sequence of pushes, so
that an incorrect sequence will result in a null-pointer dereference,
not mistaken use of a partial result.
However, this isn't simply an exercise in prettier notation.
The real reason for doing it is to provide a mechanism whereby
pushJsonbValue() can be told to construct the JsonbValue tree
in a context that is not CurrentMemoryContext. That happens
when a non-null "outcontext" is specified in the JsonbInState.
No callers exercise that option in this patch, but the next
patch in the series will make use of it.
I tried to improve the comments in this area too.
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/1060917.1753202222@sss.pgh.pa.us
2025-12-07 11:46:49 -05:00
|
|
|
static void PLyObject_ToJsonbValue(PyObject *obj,
|
|
|
|
|
JsonbInState *jsonb_state, bool is_elem);
|
2018-03-28 08:32:43 -04:00
|
|
|
|
|
|
|
|
typedef PyObject *(*PLyUnicode_FromStringAndSize_t)
|
|
|
|
|
(const char *s, Py_ssize_t size);
|
|
|
|
|
static PLyUnicode_FromStringAndSize_t PLyUnicode_FromStringAndSize_p;
|
|
|
|
|
|
2026-02-03 02:36:47 -05:00
|
|
|
/* Static asserts verify that typedefs above match original declarations */
|
|
|
|
|
StaticAssertVariableIsOfType(&PLyObject_AsString, PLyObject_AsString_t);
|
|
|
|
|
StaticAssertVariableIsOfType(&PLyUnicode_FromStringAndSize, PLyUnicode_FromStringAndSize_t);
|
|
|
|
|
StaticAssertVariableIsOfType(&PLy_elog_impl, PLy_elog_impl_t);
|
|
|
|
|
|
|
|
|
|
|
2018-03-28 08:32:43 -04:00
|
|
|
/*
|
|
|
|
|
* Module initialize function: fetch function pointers for cross-module calls.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
_PG_init(void)
|
|
|
|
|
{
|
|
|
|
|
PLyObject_AsString_p = (PLyObject_AsString_t)
|
|
|
|
|
load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyObject_AsString",
|
|
|
|
|
true, NULL);
|
|
|
|
|
PLyUnicode_FromStringAndSize_p = (PLyUnicode_FromStringAndSize_t)
|
|
|
|
|
load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyUnicode_FromStringAndSize",
|
|
|
|
|
true, NULL);
|
|
|
|
|
PLy_elog_impl_p = (PLy_elog_impl_t)
|
|
|
|
|
load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLy_elog_impl",
|
|
|
|
|
true, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* These defines must be after the _PG_init */
|
|
|
|
|
#define PLyObject_AsString (PLyObject_AsString_p)
|
|
|
|
|
#define PLyUnicode_FromStringAndSize (PLyUnicode_FromStringAndSize_p)
|
|
|
|
|
#undef PLy_elog
|
|
|
|
|
#define PLy_elog (PLy_elog_impl_p)
|
|
|
|
|
|
|
|
|
|
/*
|
2022-03-07 21:30:28 -05:00
|
|
|
* PLyUnicode_FromJsonbValue
|
2018-03-28 08:32:43 -04:00
|
|
|
*
|
|
|
|
|
* Transform string JsonbValue to Python string.
|
|
|
|
|
*/
|
|
|
|
|
static PyObject *
|
2022-03-07 21:30:28 -05:00
|
|
|
PLyUnicode_FromJsonbValue(JsonbValue *jbv)
|
2018-03-28 08:32:43 -04:00
|
|
|
{
|
|
|
|
|
Assert(jbv->type == jbvString);
|
|
|
|
|
|
2022-03-07 21:30:28 -05:00
|
|
|
return PLyUnicode_FromStringAndSize(jbv->val.string.val, jbv->val.string.len);
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2022-03-07 21:30:28 -05:00
|
|
|
* PLyUnicode_ToJsonbValue
|
2018-03-28 08:32:43 -04:00
|
|
|
*
|
|
|
|
|
* Transform Python string to JsonbValue.
|
|
|
|
|
*/
|
|
|
|
|
static void
|
2022-03-07 21:30:28 -05:00
|
|
|
PLyUnicode_ToJsonbValue(PyObject *obj, JsonbValue *jbvElem)
|
2018-03-28 08:32:43 -04:00
|
|
|
{
|
|
|
|
|
jbvElem->type = jbvString;
|
|
|
|
|
jbvElem->val.string.val = PLyObject_AsString(obj);
|
|
|
|
|
jbvElem->val.string.len = strlen(jbvElem->val.string.val);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* PLyObject_FromJsonbValue
|
|
|
|
|
*
|
|
|
|
|
* Transform JsonbValue to PyObject.
|
|
|
|
|
*/
|
|
|
|
|
static PyObject *
|
|
|
|
|
PLyObject_FromJsonbValue(JsonbValue *jsonbValue)
|
|
|
|
|
{
|
|
|
|
|
switch (jsonbValue->type)
|
|
|
|
|
{
|
|
|
|
|
case jbvNull:
|
|
|
|
|
Py_RETURN_NONE;
|
|
|
|
|
|
|
|
|
|
case jbvBinary:
|
|
|
|
|
return PLyObject_FromJsonbContainer(jsonbValue->val.binary.data);
|
|
|
|
|
|
|
|
|
|
case jbvNumeric:
|
|
|
|
|
{
|
|
|
|
|
Datum num;
|
|
|
|
|
char *str;
|
|
|
|
|
|
|
|
|
|
num = NumericGetDatum(jsonbValue->val.numeric);
|
|
|
|
|
str = DatumGetCString(DirectFunctionCall1(numeric_out, num));
|
|
|
|
|
|
|
|
|
|
return PyObject_CallFunction(decimal_constructor, "s", str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case jbvString:
|
2022-03-07 21:30:28 -05:00
|
|
|
return PLyUnicode_FromJsonbValue(jsonbValue);
|
2018-03-28 08:32:43 -04:00
|
|
|
|
|
|
|
|
case jbvBool:
|
|
|
|
|
if (jsonbValue->val.boolean)
|
|
|
|
|
Py_RETURN_TRUE;
|
|
|
|
|
else
|
|
|
|
|
Py_RETURN_FALSE;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
elog(ERROR, "unexpected jsonb value type: %d", jsonbValue->type);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2019-06-17 03:13:16 -04:00
|
|
|
* PLyObject_FromJsonbContainer
|
2018-03-28 08:32:43 -04:00
|
|
|
*
|
|
|
|
|
* Transform JsonbContainer to PyObject.
|
|
|
|
|
*/
|
|
|
|
|
static PyObject *
|
|
|
|
|
PLyObject_FromJsonbContainer(JsonbContainer *jsonb)
|
|
|
|
|
{
|
|
|
|
|
JsonbIteratorToken r;
|
|
|
|
|
JsonbValue v;
|
|
|
|
|
JsonbIterator *it;
|
|
|
|
|
PyObject *result;
|
|
|
|
|
|
2026-06-17 11:04:41 -04:00
|
|
|
/* this can recurse via PLyObject_FromJsonbValue() */
|
|
|
|
|
check_stack_depth();
|
|
|
|
|
|
2018-03-28 08:32:43 -04:00
|
|
|
it = JsonbIteratorInit(jsonb);
|
|
|
|
|
r = JsonbIteratorNext(&it, &v, true);
|
|
|
|
|
|
|
|
|
|
switch (r)
|
|
|
|
|
{
|
|
|
|
|
case WJB_BEGIN_ARRAY:
|
|
|
|
|
if (v.val.array.rawScalar)
|
|
|
|
|
{
|
|
|
|
|
JsonbValue tmp;
|
|
|
|
|
|
|
|
|
|
if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_ELEM ||
|
|
|
|
|
(r = JsonbIteratorNext(&it, &tmp, true)) != WJB_END_ARRAY ||
|
|
|
|
|
(r = JsonbIteratorNext(&it, &tmp, true)) != WJB_DONE)
|
|
|
|
|
elog(ERROR, "unexpected jsonb token: %d", r);
|
|
|
|
|
|
|
|
|
|
result = PLyObject_FromJsonbValue(&v);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-04-06 17:54:29 -04:00
|
|
|
PyObject *volatile elem = NULL;
|
|
|
|
|
|
2018-03-28 08:32:43 -04:00
|
|
|
result = PyList_New(0);
|
|
|
|
|
if (!result)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
2019-04-06 17:54:29 -04:00
|
|
|
PG_TRY();
|
2018-03-28 08:32:43 -04:00
|
|
|
{
|
2019-04-06 17:54:29 -04:00
|
|
|
while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
|
2018-03-28 08:32:43 -04:00
|
|
|
{
|
2019-04-06 17:54:29 -04:00
|
|
|
if (r != WJB_ELEM)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
elem = PLyObject_FromJsonbValue(&v);
|
2018-03-28 08:32:43 -04:00
|
|
|
|
|
|
|
|
PyList_Append(result, elem);
|
|
|
|
|
Py_XDECREF(elem);
|
2019-04-06 17:54:29 -04:00
|
|
|
elem = NULL;
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
|
|
|
|
}
|
2019-04-06 17:54:29 -04:00
|
|
|
PG_CATCH();
|
|
|
|
|
{
|
|
|
|
|
Py_XDECREF(elem);
|
|
|
|
|
Py_XDECREF(result);
|
|
|
|
|
PG_RE_THROW();
|
|
|
|
|
}
|
|
|
|
|
PG_END_TRY();
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case WJB_BEGIN_OBJECT:
|
|
|
|
|
{
|
2019-04-06 17:54:29 -04:00
|
|
|
PyObject *volatile result_v = PyDict_New();
|
|
|
|
|
PyObject *volatile key = NULL;
|
|
|
|
|
PyObject *volatile val = NULL;
|
2018-03-28 08:32:43 -04:00
|
|
|
|
2019-04-06 17:54:29 -04:00
|
|
|
if (!result_v)
|
|
|
|
|
return NULL;
|
2018-03-28 08:32:43 -04:00
|
|
|
|
2019-04-06 17:54:29 -04:00
|
|
|
PG_TRY();
|
|
|
|
|
{
|
|
|
|
|
while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
|
2018-03-28 08:32:43 -04:00
|
|
|
{
|
2019-04-06 17:54:29 -04:00
|
|
|
if (r != WJB_KEY)
|
|
|
|
|
continue;
|
2018-03-28 08:32:43 -04:00
|
|
|
|
2022-03-07 21:30:28 -05:00
|
|
|
key = PLyUnicode_FromJsonbValue(&v);
|
2019-04-06 17:54:29 -04:00
|
|
|
if (!key)
|
|
|
|
|
{
|
|
|
|
|
Py_XDECREF(result_v);
|
|
|
|
|
result_v = NULL;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_VALUE)
|
|
|
|
|
elog(ERROR, "unexpected jsonb token: %d", r);
|
|
|
|
|
|
|
|
|
|
val = PLyObject_FromJsonbValue(&v);
|
|
|
|
|
if (!val)
|
2018-03-28 08:32:43 -04:00
|
|
|
{
|
|
|
|
|
Py_XDECREF(key);
|
2019-04-06 17:54:29 -04:00
|
|
|
key = NULL;
|
|
|
|
|
Py_XDECREF(result_v);
|
|
|
|
|
result_v = NULL;
|
|
|
|
|
break;
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
|
|
|
|
|
2019-04-06 17:54:29 -04:00
|
|
|
PyDict_SetItem(result_v, key, val);
|
2018-03-28 08:32:43 -04:00
|
|
|
|
2019-04-06 17:54:29 -04:00
|
|
|
Py_XDECREF(key);
|
|
|
|
|
key = NULL;
|
|
|
|
|
Py_XDECREF(val);
|
|
|
|
|
val = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PG_CATCH();
|
|
|
|
|
{
|
|
|
|
|
Py_XDECREF(result_v);
|
2018-03-28 08:32:43 -04:00
|
|
|
Py_XDECREF(key);
|
2019-04-06 17:54:29 -04:00
|
|
|
Py_XDECREF(val);
|
|
|
|
|
PG_RE_THROW();
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
2019-04-06 17:54:29 -04:00
|
|
|
PG_END_TRY();
|
|
|
|
|
|
|
|
|
|
result = result_v;
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
elog(ERROR, "unexpected jsonb token: %d", r);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* PLyMapping_ToJsonbValue
|
|
|
|
|
*
|
|
|
|
|
* Transform Python dict to JsonbValue.
|
|
|
|
|
*/
|
Revise APIs for pushJsonbValue() and associated routines.
Instead of passing "JsonbParseState **" to pushJsonbValue(),
pass a pointer to a JsonbInState, which will contain the
parseState stack pointer as well as other useful fields.
Also, instead of returning a JsonbValue pointer that is often
meaningless/ignored, return the top-level JsonbValue pointer
in the "result" field of the JsonbInState.
This involves a lot of (mostly mechanical) edits, but I think
the results are notationally cleaner and easier to understand.
Certainly the business with sometimes capturing the result of
pushJsonbValue() and sometimes not was bug-prone and incapable of
mechanical verification. In the new arrangement, JsonbInState.result
remains null until we've completed a valid sequence of pushes, so
that an incorrect sequence will result in a null-pointer dereference,
not mistaken use of a partial result.
However, this isn't simply an exercise in prettier notation.
The real reason for doing it is to provide a mechanism whereby
pushJsonbValue() can be told to construct the JsonbValue tree
in a context that is not CurrentMemoryContext. That happens
when a non-null "outcontext" is specified in the JsonbInState.
No callers exercise that option in this patch, but the next
patch in the series will make use of it.
I tried to improve the comments in this area too.
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/1060917.1753202222@sss.pgh.pa.us
2025-12-07 11:46:49 -05:00
|
|
|
static void
|
|
|
|
|
PLyMapping_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state)
|
2018-03-28 08:32:43 -04:00
|
|
|
{
|
|
|
|
|
Py_ssize_t pcount;
|
2019-04-06 17:54:29 -04:00
|
|
|
PyObject *volatile items;
|
2018-03-28 08:32:43 -04:00
|
|
|
|
|
|
|
|
pcount = PyMapping_Size(obj);
|
plpython: Fix NULL pointer dereferences for broken sequence and mapping objects
PL/Python and its hstore and jsonb transforms build SQL values from
Python containers by calling Python C API functions that can return
NULL, and in several places the result was used without first checking
it.
On the sequence side, PySequence_GetItem() is used when converting a
returned sequence into a SQL array or composite value, when reading
the argument list passed to plpy.execute() or plpy.cursor(), and when
reading the list of type names given to plpy.prepare(). On the
mapping side, the hstore and jsonb transforms call PyMapping_Size()
and PyMapping_Items() and then index the result with PyList_GetItem()
and PyTuple_GetItem().
All of these return NULL (or -1), with a Python exception set, for a
broken object: for example one whose __getitem__() or items() raises,
or which reports a length that disagrees with what it actually yields.
The unchecked result was then dereferenced, crashing the backend.
Fix this by checking the result of each call and reporting a regular
error if it failed, so that the underlying Python exception is
surfaced instead of taking down the session.
Author: Richard Guo <guofenglinux@gmail.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Discussion: https://postgr.es/m/CAMbWs49BKM9wP6m8bCXEpHwQKp7usvOGV6Jf=J7FYr_BCpxLqg@mail.gmail.com
Backpatch-through: 14
2026-06-28 22:38:39 -04:00
|
|
|
if (pcount < 0)
|
|
|
|
|
PLy_elog(ERROR, "could not get size of Python mapping");
|
|
|
|
|
|
2019-03-14 03:25:25 -04:00
|
|
|
items = PyMapping_Items(obj);
|
plpython: Fix NULL pointer dereferences for broken sequence and mapping objects
PL/Python and its hstore and jsonb transforms build SQL values from
Python containers by calling Python C API functions that can return
NULL, and in several places the result was used without first checking
it.
On the sequence side, PySequence_GetItem() is used when converting a
returned sequence into a SQL array or composite value, when reading
the argument list passed to plpy.execute() or plpy.cursor(), and when
reading the list of type names given to plpy.prepare(). On the
mapping side, the hstore and jsonb transforms call PyMapping_Size()
and PyMapping_Items() and then index the result with PyList_GetItem()
and PyTuple_GetItem().
All of these return NULL (or -1), with a Python exception set, for a
broken object: for example one whose __getitem__() or items() raises,
or which reports a length that disagrees with what it actually yields.
The unchecked result was then dereferenced, crashing the backend.
Fix this by checking the result of each call and reporting a regular
error if it failed, so that the underlying Python exception is
surfaced instead of taking down the session.
Author: Richard Guo <guofenglinux@gmail.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Discussion: https://postgr.es/m/CAMbWs49BKM9wP6m8bCXEpHwQKp7usvOGV6Jf=J7FYr_BCpxLqg@mail.gmail.com
Backpatch-through: 14
2026-06-28 22:38:39 -04:00
|
|
|
if (items == NULL)
|
|
|
|
|
PLy_elog(ERROR, "could not get items from Python mapping");
|
2018-03-28 08:32:43 -04:00
|
|
|
|
|
|
|
|
PG_TRY();
|
|
|
|
|
{
|
|
|
|
|
Py_ssize_t i;
|
|
|
|
|
|
|
|
|
|
pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL);
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < pcount; i++)
|
|
|
|
|
{
|
|
|
|
|
JsonbValue jbvKey;
|
|
|
|
|
PyObject *item = PyList_GetItem(items, i);
|
plpython: Fix NULL pointer dereferences for broken sequence and mapping objects
PL/Python and its hstore and jsonb transforms build SQL values from
Python containers by calling Python C API functions that can return
NULL, and in several places the result was used without first checking
it.
On the sequence side, PySequence_GetItem() is used when converting a
returned sequence into a SQL array or composite value, when reading
the argument list passed to plpy.execute() or plpy.cursor(), and when
reading the list of type names given to plpy.prepare(). On the
mapping side, the hstore and jsonb transforms call PyMapping_Size()
and PyMapping_Items() and then index the result with PyList_GetItem()
and PyTuple_GetItem().
All of these return NULL (or -1), with a Python exception set, for a
broken object: for example one whose __getitem__() or items() raises,
or which reports a length that disagrees with what it actually yields.
The unchecked result was then dereferenced, crashing the backend.
Fix this by checking the result of each call and reporting a regular
error if it failed, so that the underlying Python exception is
surfaced instead of taking down the session.
Author: Richard Guo <guofenglinux@gmail.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Discussion: https://postgr.es/m/CAMbWs49BKM9wP6m8bCXEpHwQKp7usvOGV6Jf=J7FYr_BCpxLqg@mail.gmail.com
Backpatch-through: 14
2026-06-28 22:38:39 -04:00
|
|
|
PyObject *key;
|
|
|
|
|
PyObject *value;
|
|
|
|
|
|
|
|
|
|
/* The mapping's items() must yield key/value pairs */
|
|
|
|
|
if (item == NULL || !PyTuple_Check(item) || PyTuple_Size(item) < 2)
|
|
|
|
|
PLy_elog(ERROR, "items() of a Python mapping must return key/value pairs");
|
|
|
|
|
|
|
|
|
|
key = PyTuple_GetItem(item, 0);
|
|
|
|
|
value = PyTuple_GetItem(item, 1);
|
2018-03-28 08:32:43 -04:00
|
|
|
|
|
|
|
|
/* Python dictionary can have None as key */
|
|
|
|
|
if (key == Py_None)
|
|
|
|
|
{
|
|
|
|
|
jbvKey.type = jbvString;
|
|
|
|
|
jbvKey.val.string.len = 0;
|
|
|
|
|
jbvKey.val.string.val = "";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* All others types of keys we serialize to string */
|
2022-03-07 21:30:28 -05:00
|
|
|
PLyUnicode_ToJsonbValue(key, &jbvKey);
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
|
|
|
|
|
Revise APIs for pushJsonbValue() and associated routines.
Instead of passing "JsonbParseState **" to pushJsonbValue(),
pass a pointer to a JsonbInState, which will contain the
parseState stack pointer as well as other useful fields.
Also, instead of returning a JsonbValue pointer that is often
meaningless/ignored, return the top-level JsonbValue pointer
in the "result" field of the JsonbInState.
This involves a lot of (mostly mechanical) edits, but I think
the results are notationally cleaner and easier to understand.
Certainly the business with sometimes capturing the result of
pushJsonbValue() and sometimes not was bug-prone and incapable of
mechanical verification. In the new arrangement, JsonbInState.result
remains null until we've completed a valid sequence of pushes, so
that an incorrect sequence will result in a null-pointer dereference,
not mistaken use of a partial result.
However, this isn't simply an exercise in prettier notation.
The real reason for doing it is to provide a mechanism whereby
pushJsonbValue() can be told to construct the JsonbValue tree
in a context that is not CurrentMemoryContext. That happens
when a non-null "outcontext" is specified in the JsonbInState.
No callers exercise that option in this patch, but the next
patch in the series will make use of it.
I tried to improve the comments in this area too.
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/1060917.1753202222@sss.pgh.pa.us
2025-12-07 11:46:49 -05:00
|
|
|
pushJsonbValue(jsonb_state, WJB_KEY, &jbvKey);
|
|
|
|
|
PLyObject_ToJsonbValue(value, jsonb_state, false);
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
|
|
|
|
|
Revise APIs for pushJsonbValue() and associated routines.
Instead of passing "JsonbParseState **" to pushJsonbValue(),
pass a pointer to a JsonbInState, which will contain the
parseState stack pointer as well as other useful fields.
Also, instead of returning a JsonbValue pointer that is often
meaningless/ignored, return the top-level JsonbValue pointer
in the "result" field of the JsonbInState.
This involves a lot of (mostly mechanical) edits, but I think
the results are notationally cleaner and easier to understand.
Certainly the business with sometimes capturing the result of
pushJsonbValue() and sometimes not was bug-prone and incapable of
mechanical verification. In the new arrangement, JsonbInState.result
remains null until we've completed a valid sequence of pushes, so
that an incorrect sequence will result in a null-pointer dereference,
not mistaken use of a partial result.
However, this isn't simply an exercise in prettier notation.
The real reason for doing it is to provide a mechanism whereby
pushJsonbValue() can be told to construct the JsonbValue tree
in a context that is not CurrentMemoryContext. That happens
when a non-null "outcontext" is specified in the JsonbInState.
No callers exercise that option in this patch, but the next
patch in the series will make use of it.
I tried to improve the comments in this area too.
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/1060917.1753202222@sss.pgh.pa.us
2025-12-07 11:46:49 -05:00
|
|
|
pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
2019-11-01 06:09:52 -04:00
|
|
|
PG_FINALLY();
|
2018-03-28 08:32:43 -04:00
|
|
|
{
|
2019-03-14 03:25:25 -04:00
|
|
|
Py_DECREF(items);
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
|
|
|
|
PG_END_TRY();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* PLySequence_ToJsonbValue
|
|
|
|
|
*
|
|
|
|
|
* Transform python list to JsonbValue. Expects transformed PyObject and
|
|
|
|
|
* a state required for jsonb construction.
|
|
|
|
|
*/
|
Revise APIs for pushJsonbValue() and associated routines.
Instead of passing "JsonbParseState **" to pushJsonbValue(),
pass a pointer to a JsonbInState, which will contain the
parseState stack pointer as well as other useful fields.
Also, instead of returning a JsonbValue pointer that is often
meaningless/ignored, return the top-level JsonbValue pointer
in the "result" field of the JsonbInState.
This involves a lot of (mostly mechanical) edits, but I think
the results are notationally cleaner and easier to understand.
Certainly the business with sometimes capturing the result of
pushJsonbValue() and sometimes not was bug-prone and incapable of
mechanical verification. In the new arrangement, JsonbInState.result
remains null until we've completed a valid sequence of pushes, so
that an incorrect sequence will result in a null-pointer dereference,
not mistaken use of a partial result.
However, this isn't simply an exercise in prettier notation.
The real reason for doing it is to provide a mechanism whereby
pushJsonbValue() can be told to construct the JsonbValue tree
in a context that is not CurrentMemoryContext. That happens
when a non-null "outcontext" is specified in the JsonbInState.
No callers exercise that option in this patch, but the next
patch in the series will make use of it.
I tried to improve the comments in this area too.
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/1060917.1753202222@sss.pgh.pa.us
2025-12-07 11:46:49 -05:00
|
|
|
static void
|
|
|
|
|
PLySequence_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state)
|
2018-03-28 08:32:43 -04:00
|
|
|
{
|
|
|
|
|
Py_ssize_t i;
|
|
|
|
|
Py_ssize_t pcount;
|
2019-04-06 17:54:29 -04:00
|
|
|
PyObject *volatile value = NULL;
|
2018-03-28 08:32:43 -04:00
|
|
|
|
|
|
|
|
pcount = PySequence_Size(obj);
|
|
|
|
|
|
|
|
|
|
pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL);
|
|
|
|
|
|
2019-04-06 17:54:29 -04:00
|
|
|
PG_TRY();
|
2018-03-28 08:32:43 -04:00
|
|
|
{
|
2019-04-06 17:54:29 -04:00
|
|
|
for (i = 0; i < pcount; i++)
|
|
|
|
|
{
|
|
|
|
|
value = PySequence_GetItem(obj, i);
|
plpython: Fix NULL pointer dereferences for broken sequence and mapping objects
PL/Python and its hstore and jsonb transforms build SQL values from
Python containers by calling Python C API functions that can return
NULL, and in several places the result was used without first checking
it.
On the sequence side, PySequence_GetItem() is used when converting a
returned sequence into a SQL array or composite value, when reading
the argument list passed to plpy.execute() or plpy.cursor(), and when
reading the list of type names given to plpy.prepare(). On the
mapping side, the hstore and jsonb transforms call PyMapping_Size()
and PyMapping_Items() and then index the result with PyList_GetItem()
and PyTuple_GetItem().
All of these return NULL (or -1), with a Python exception set, for a
broken object: for example one whose __getitem__() or items() raises,
or which reports a length that disagrees with what it actually yields.
The unchecked result was then dereferenced, crashing the backend.
Fix this by checking the result of each call and reporting a regular
error if it failed, so that the underlying Python exception is
surfaced instead of taking down the session.
Author: Richard Guo <guofenglinux@gmail.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Discussion: https://postgr.es/m/CAMbWs49BKM9wP6m8bCXEpHwQKp7usvOGV6Jf=J7FYr_BCpxLqg@mail.gmail.com
Backpatch-through: 14
2026-06-28 22:38:39 -04:00
|
|
|
|
|
|
|
|
/* PySequence_GetItem() can return NULL, with an exception set */
|
|
|
|
|
if (value == NULL)
|
|
|
|
|
PLy_elog(ERROR, "could not get element %d from sequence", (int) i);
|
2018-06-15 08:01:46 -04:00
|
|
|
|
Revise APIs for pushJsonbValue() and associated routines.
Instead of passing "JsonbParseState **" to pushJsonbValue(),
pass a pointer to a JsonbInState, which will contain the
parseState stack pointer as well as other useful fields.
Also, instead of returning a JsonbValue pointer that is often
meaningless/ignored, return the top-level JsonbValue pointer
in the "result" field of the JsonbInState.
This involves a lot of (mostly mechanical) edits, but I think
the results are notationally cleaner and easier to understand.
Certainly the business with sometimes capturing the result of
pushJsonbValue() and sometimes not was bug-prone and incapable of
mechanical verification. In the new arrangement, JsonbInState.result
remains null until we've completed a valid sequence of pushes, so
that an incorrect sequence will result in a null-pointer dereference,
not mistaken use of a partial result.
However, this isn't simply an exercise in prettier notation.
The real reason for doing it is to provide a mechanism whereby
pushJsonbValue() can be told to construct the JsonbValue tree
in a context that is not CurrentMemoryContext. That happens
when a non-null "outcontext" is specified in the JsonbInState.
No callers exercise that option in this patch, but the next
patch in the series will make use of it.
I tried to improve the comments in this area too.
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/1060917.1753202222@sss.pgh.pa.us
2025-12-07 11:46:49 -05:00
|
|
|
PLyObject_ToJsonbValue(value, jsonb_state, true);
|
2019-04-06 17:54:29 -04:00
|
|
|
Py_XDECREF(value);
|
|
|
|
|
value = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PG_CATCH();
|
|
|
|
|
{
|
2018-06-15 08:01:46 -04:00
|
|
|
Py_XDECREF(value);
|
2019-04-06 17:54:29 -04:00
|
|
|
PG_RE_THROW();
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
2019-04-06 17:54:29 -04:00
|
|
|
PG_END_TRY();
|
2018-03-28 08:32:43 -04:00
|
|
|
|
Revise APIs for pushJsonbValue() and associated routines.
Instead of passing "JsonbParseState **" to pushJsonbValue(),
pass a pointer to a JsonbInState, which will contain the
parseState stack pointer as well as other useful fields.
Also, instead of returning a JsonbValue pointer that is often
meaningless/ignored, return the top-level JsonbValue pointer
in the "result" field of the JsonbInState.
This involves a lot of (mostly mechanical) edits, but I think
the results are notationally cleaner and easier to understand.
Certainly the business with sometimes capturing the result of
pushJsonbValue() and sometimes not was bug-prone and incapable of
mechanical verification. In the new arrangement, JsonbInState.result
remains null until we've completed a valid sequence of pushes, so
that an incorrect sequence will result in a null-pointer dereference,
not mistaken use of a partial result.
However, this isn't simply an exercise in prettier notation.
The real reason for doing it is to provide a mechanism whereby
pushJsonbValue() can be told to construct the JsonbValue tree
in a context that is not CurrentMemoryContext. That happens
when a non-null "outcontext" is specified in the JsonbInState.
No callers exercise that option in this patch, but the next
patch in the series will make use of it.
I tried to improve the comments in this area too.
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/1060917.1753202222@sss.pgh.pa.us
2025-12-07 11:46:49 -05:00
|
|
|
pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL);
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* PLyNumber_ToJsonbValue(PyObject *obj)
|
|
|
|
|
*
|
|
|
|
|
* Transform python number to JsonbValue.
|
|
|
|
|
*/
|
|
|
|
|
static JsonbValue *
|
|
|
|
|
PLyNumber_ToJsonbValue(PyObject *obj, JsonbValue *jbvNum)
|
|
|
|
|
{
|
|
|
|
|
Numeric num;
|
|
|
|
|
char *str = PLyObject_AsString(obj);
|
|
|
|
|
|
|
|
|
|
PG_TRY();
|
|
|
|
|
{
|
2018-05-02 15:52:54 -04:00
|
|
|
Datum numd;
|
|
|
|
|
|
|
|
|
|
numd = DirectFunctionCall3(numeric_in,
|
|
|
|
|
CStringGetDatum(str),
|
|
|
|
|
ObjectIdGetDatum(InvalidOid),
|
|
|
|
|
Int32GetDatum(-1));
|
|
|
|
|
num = DatumGetNumeric(numd);
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
|
|
|
|
PG_CATCH();
|
|
|
|
|
{
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
2020-01-30 11:32:04 -05:00
|
|
|
errmsg("could not convert value \"%s\" to jsonb", str)));
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
|
|
|
|
PG_END_TRY();
|
|
|
|
|
|
|
|
|
|
pfree(str);
|
|
|
|
|
|
2018-05-02 15:58:34 -04:00
|
|
|
/*
|
2020-07-22 19:19:44 -04:00
|
|
|
* jsonb doesn't allow NaN or infinity (per JSON specification), so we
|
|
|
|
|
* have to reject those here explicitly.
|
2018-05-02 15:58:34 -04:00
|
|
|
*/
|
|
|
|
|
if (numeric_is_nan(num))
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
2020-01-30 11:32:04 -05:00
|
|
|
errmsg("cannot convert NaN to jsonb")));
|
2020-07-22 19:19:44 -04:00
|
|
|
if (numeric_is_inf(num))
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
|
|
|
errmsg("cannot convert infinity to jsonb")));
|
2018-05-02 15:58:34 -04:00
|
|
|
|
2018-03-28 08:32:43 -04:00
|
|
|
jbvNum->type = jbvNumeric;
|
|
|
|
|
jbvNum->val.numeric = num;
|
|
|
|
|
|
|
|
|
|
return jbvNum;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* PLyObject_ToJsonbValue(PyObject *obj)
|
|
|
|
|
*
|
|
|
|
|
* Transform python object to JsonbValue.
|
|
|
|
|
*/
|
Revise APIs for pushJsonbValue() and associated routines.
Instead of passing "JsonbParseState **" to pushJsonbValue(),
pass a pointer to a JsonbInState, which will contain the
parseState stack pointer as well as other useful fields.
Also, instead of returning a JsonbValue pointer that is often
meaningless/ignored, return the top-level JsonbValue pointer
in the "result" field of the JsonbInState.
This involves a lot of (mostly mechanical) edits, but I think
the results are notationally cleaner and easier to understand.
Certainly the business with sometimes capturing the result of
pushJsonbValue() and sometimes not was bug-prone and incapable of
mechanical verification. In the new arrangement, JsonbInState.result
remains null until we've completed a valid sequence of pushes, so
that an incorrect sequence will result in a null-pointer dereference,
not mistaken use of a partial result.
However, this isn't simply an exercise in prettier notation.
The real reason for doing it is to provide a mechanism whereby
pushJsonbValue() can be told to construct the JsonbValue tree
in a context that is not CurrentMemoryContext. That happens
when a non-null "outcontext" is specified in the JsonbInState.
No callers exercise that option in this patch, but the next
patch in the series will make use of it.
I tried to improve the comments in this area too.
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/1060917.1753202222@sss.pgh.pa.us
2025-12-07 11:46:49 -05:00
|
|
|
static void
|
|
|
|
|
PLyObject_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state, bool is_elem)
|
2018-03-28 08:32:43 -04:00
|
|
|
{
|
|
|
|
|
JsonbValue *out;
|
|
|
|
|
|
2026-06-17 11:04:41 -04:00
|
|
|
/* this can recurse via PLyMapping_ToJsonbValue() */
|
|
|
|
|
check_stack_depth();
|
|
|
|
|
|
2022-03-07 21:30:28 -05:00
|
|
|
if (!PyUnicode_Check(obj))
|
2018-03-28 08:32:43 -04:00
|
|
|
{
|
|
|
|
|
if (PySequence_Check(obj))
|
Revise APIs for pushJsonbValue() and associated routines.
Instead of passing "JsonbParseState **" to pushJsonbValue(),
pass a pointer to a JsonbInState, which will contain the
parseState stack pointer as well as other useful fields.
Also, instead of returning a JsonbValue pointer that is often
meaningless/ignored, return the top-level JsonbValue pointer
in the "result" field of the JsonbInState.
This involves a lot of (mostly mechanical) edits, but I think
the results are notationally cleaner and easier to understand.
Certainly the business with sometimes capturing the result of
pushJsonbValue() and sometimes not was bug-prone and incapable of
mechanical verification. In the new arrangement, JsonbInState.result
remains null until we've completed a valid sequence of pushes, so
that an incorrect sequence will result in a null-pointer dereference,
not mistaken use of a partial result.
However, this isn't simply an exercise in prettier notation.
The real reason for doing it is to provide a mechanism whereby
pushJsonbValue() can be told to construct the JsonbValue tree
in a context that is not CurrentMemoryContext. That happens
when a non-null "outcontext" is specified in the JsonbInState.
No callers exercise that option in this patch, but the next
patch in the series will make use of it.
I tried to improve the comments in this area too.
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/1060917.1753202222@sss.pgh.pa.us
2025-12-07 11:46:49 -05:00
|
|
|
{
|
|
|
|
|
PLySequence_ToJsonbValue(obj, jsonb_state);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-03-28 08:32:43 -04:00
|
|
|
else if (PyMapping_Check(obj))
|
Revise APIs for pushJsonbValue() and associated routines.
Instead of passing "JsonbParseState **" to pushJsonbValue(),
pass a pointer to a JsonbInState, which will contain the
parseState stack pointer as well as other useful fields.
Also, instead of returning a JsonbValue pointer that is often
meaningless/ignored, return the top-level JsonbValue pointer
in the "result" field of the JsonbInState.
This involves a lot of (mostly mechanical) edits, but I think
the results are notationally cleaner and easier to understand.
Certainly the business with sometimes capturing the result of
pushJsonbValue() and sometimes not was bug-prone and incapable of
mechanical verification. In the new arrangement, JsonbInState.result
remains null until we've completed a valid sequence of pushes, so
that an incorrect sequence will result in a null-pointer dereference,
not mistaken use of a partial result.
However, this isn't simply an exercise in prettier notation.
The real reason for doing it is to provide a mechanism whereby
pushJsonbValue() can be told to construct the JsonbValue tree
in a context that is not CurrentMemoryContext. That happens
when a non-null "outcontext" is specified in the JsonbInState.
No callers exercise that option in this patch, but the next
patch in the series will make use of it.
I tried to improve the comments in this area too.
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/1060917.1753202222@sss.pgh.pa.us
2025-12-07 11:46:49 -05:00
|
|
|
{
|
|
|
|
|
PLyMapping_ToJsonbValue(obj, jsonb_state);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
|
|
|
|
|
2025-12-05 02:40:26 -05:00
|
|
|
out = palloc_object(JsonbValue);
|
2018-03-28 08:32:43 -04:00
|
|
|
|
|
|
|
|
if (obj == Py_None)
|
|
|
|
|
out->type = jbvNull;
|
2022-03-07 21:30:28 -05:00
|
|
|
else if (PyUnicode_Check(obj))
|
|
|
|
|
PLyUnicode_ToJsonbValue(obj, out);
|
2018-04-26 14:47:16 -04:00
|
|
|
|
2018-03-28 08:32:43 -04:00
|
|
|
/*
|
|
|
|
|
* PyNumber_Check() returns true for booleans, so boolean check should
|
|
|
|
|
* come first.
|
|
|
|
|
*/
|
|
|
|
|
else if (PyBool_Check(obj))
|
|
|
|
|
{
|
|
|
|
|
out->type = jbvBool;
|
|
|
|
|
out->val.boolean = (obj == Py_True);
|
|
|
|
|
}
|
|
|
|
|
else if (PyNumber_Check(obj))
|
|
|
|
|
out = PLyNumber_ToJsonbValue(obj, out);
|
|
|
|
|
else
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2020-01-30 11:32:04 -05:00
|
|
|
errmsg("Python type \"%s\" cannot be transformed to jsonb",
|
|
|
|
|
PLyObject_AsString((PyObject *) obj->ob_type))));
|
2018-03-28 08:32:43 -04:00
|
|
|
|
Revise APIs for pushJsonbValue() and associated routines.
Instead of passing "JsonbParseState **" to pushJsonbValue(),
pass a pointer to a JsonbInState, which will contain the
parseState stack pointer as well as other useful fields.
Also, instead of returning a JsonbValue pointer that is often
meaningless/ignored, return the top-level JsonbValue pointer
in the "result" field of the JsonbInState.
This involves a lot of (mostly mechanical) edits, but I think
the results are notationally cleaner and easier to understand.
Certainly the business with sometimes capturing the result of
pushJsonbValue() and sometimes not was bug-prone and incapable of
mechanical verification. In the new arrangement, JsonbInState.result
remains null until we've completed a valid sequence of pushes, so
that an incorrect sequence will result in a null-pointer dereference,
not mistaken use of a partial result.
However, this isn't simply an exercise in prettier notation.
The real reason for doing it is to provide a mechanism whereby
pushJsonbValue() can be told to construct the JsonbValue tree
in a context that is not CurrentMemoryContext. That happens
when a non-null "outcontext" is specified in the JsonbInState.
No callers exercise that option in this patch, but the next
patch in the series will make use of it.
I tried to improve the comments in this area too.
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/1060917.1753202222@sss.pgh.pa.us
2025-12-07 11:46:49 -05:00
|
|
|
if (jsonb_state->parseState)
|
|
|
|
|
{
|
|
|
|
|
/* We're in an array or object, so push value as element or field. */
|
|
|
|
|
pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, out);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* We are at top level, so it's a raw scalar. If we just shove the
|
|
|
|
|
* scalar value into jsonb_state->result, JsonbValueToJsonb will take
|
|
|
|
|
* care of wrapping it into a dummy array.
|
|
|
|
|
*/
|
|
|
|
|
jsonb_state->result = out;
|
|
|
|
|
}
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* plpython_to_jsonb
|
|
|
|
|
*
|
|
|
|
|
* Transform python object to Jsonb datum
|
|
|
|
|
*/
|
|
|
|
|
PG_FUNCTION_INFO_V1(plpython_to_jsonb);
|
|
|
|
|
Datum
|
|
|
|
|
plpython_to_jsonb(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
Revise APIs for pushJsonbValue() and associated routines.
Instead of passing "JsonbParseState **" to pushJsonbValue(),
pass a pointer to a JsonbInState, which will contain the
parseState stack pointer as well as other useful fields.
Also, instead of returning a JsonbValue pointer that is often
meaningless/ignored, return the top-level JsonbValue pointer
in the "result" field of the JsonbInState.
This involves a lot of (mostly mechanical) edits, but I think
the results are notationally cleaner and easier to understand.
Certainly the business with sometimes capturing the result of
pushJsonbValue() and sometimes not was bug-prone and incapable of
mechanical verification. In the new arrangement, JsonbInState.result
remains null until we've completed a valid sequence of pushes, so
that an incorrect sequence will result in a null-pointer dereference,
not mistaken use of a partial result.
However, this isn't simply an exercise in prettier notation.
The real reason for doing it is to provide a mechanism whereby
pushJsonbValue() can be told to construct the JsonbValue tree
in a context that is not CurrentMemoryContext. That happens
when a non-null "outcontext" is specified in the JsonbInState.
No callers exercise that option in this patch, but the next
patch in the series will make use of it.
I tried to improve the comments in this area too.
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/1060917.1753202222@sss.pgh.pa.us
2025-12-07 11:46:49 -05:00
|
|
|
PyObject *obj = (PyObject *) PG_GETARG_POINTER(0);
|
|
|
|
|
JsonbInState jsonb_state = {0};
|
2018-03-28 08:32:43 -04:00
|
|
|
|
Revise APIs for pushJsonbValue() and associated routines.
Instead of passing "JsonbParseState **" to pushJsonbValue(),
pass a pointer to a JsonbInState, which will contain the
parseState stack pointer as well as other useful fields.
Also, instead of returning a JsonbValue pointer that is often
meaningless/ignored, return the top-level JsonbValue pointer
in the "result" field of the JsonbInState.
This involves a lot of (mostly mechanical) edits, but I think
the results are notationally cleaner and easier to understand.
Certainly the business with sometimes capturing the result of
pushJsonbValue() and sometimes not was bug-prone and incapable of
mechanical verification. In the new arrangement, JsonbInState.result
remains null until we've completed a valid sequence of pushes, so
that an incorrect sequence will result in a null-pointer dereference,
not mistaken use of a partial result.
However, this isn't simply an exercise in prettier notation.
The real reason for doing it is to provide a mechanism whereby
pushJsonbValue() can be told to construct the JsonbValue tree
in a context that is not CurrentMemoryContext. That happens
when a non-null "outcontext" is specified in the JsonbInState.
No callers exercise that option in this patch, but the next
patch in the series will make use of it.
I tried to improve the comments in this area too.
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/1060917.1753202222@sss.pgh.pa.us
2025-12-07 11:46:49 -05:00
|
|
|
PLyObject_ToJsonbValue(obj, &jsonb_state, true);
|
|
|
|
|
PG_RETURN_POINTER(JsonbValueToJsonb(jsonb_state.result));
|
2018-03-28 08:32:43 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* jsonb_to_plpython
|
|
|
|
|
*
|
|
|
|
|
* Transform Jsonb datum to PyObject and return it as internal.
|
|
|
|
|
*/
|
|
|
|
|
PG_FUNCTION_INFO_V1(jsonb_to_plpython);
|
|
|
|
|
Datum
|
|
|
|
|
jsonb_to_plpython(PG_FUNCTION_ARGS)
|
|
|
|
|
{
|
|
|
|
|
PyObject *result;
|
|
|
|
|
Jsonb *in = PG_GETARG_JSONB_P(0);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Initialize pointer to Decimal constructor. First we try "cdecimal", C
|
|
|
|
|
* version of decimal library. In case of failure we use slower "decimal"
|
|
|
|
|
* module.
|
|
|
|
|
*/
|
|
|
|
|
if (!decimal_constructor)
|
|
|
|
|
{
|
|
|
|
|
PyObject *decimal_module = PyImport_ImportModule("cdecimal");
|
|
|
|
|
|
|
|
|
|
if (!decimal_module)
|
|
|
|
|
{
|
|
|
|
|
PyErr_Clear();
|
|
|
|
|
decimal_module = PyImport_ImportModule("decimal");
|
|
|
|
|
}
|
|
|
|
|
Assert(decimal_module);
|
|
|
|
|
decimal_constructor = PyObject_GetAttrString(decimal_module, "Decimal");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = PLyObject_FromJsonbContainer(&in->root);
|
|
|
|
|
if (!result)
|
|
|
|
|
PLy_elog(ERROR, "transformation from jsonb to Python failed");
|
|
|
|
|
|
|
|
|
|
return PointerGetDatum(result);
|
|
|
|
|
}
|