/* * Process a single dimension of an array. * If it's the innermost dimension, output the values, otherwise call * ourselves recursively to process the next dimension. */ static void array_dim_to_jsonb(JsonbInState *result, int dim, int ndims, int *dims, Datum *vals, bool *nulls, int *valcount, JsonbTypeCategory tcategory, Oid outfuncoid) { int i; Assert(dim < ndims); result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_ARRAY, NULL); for (i = 1; i <= dims[dim]; i++) { if (dim + 1 == ndims) { datum_to_jsonb(vals[*valcount], nulls[*valcount], result, tcategory, outfuncoid, false); (*valcount)++; } else { array_dim_to_jsonb(result, dim + 1, ndims, dims, vals, nulls, valcount, tcategory, outfuncoid); } } result->res = pushJsonbValue(&result->parseState, WJB_END_ARRAY, NULL); }
/* * PLyMapping_ToJsonbValue * * Transform Python dict to JsonbValue. */ static JsonbValue * PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state) { Py_ssize_t pcount; JsonbValue *out = NULL; /* We need it volatile, since we use it after longjmp */ volatile PyObject *items_v = NULL; pcount = PyMapping_Size(obj); items_v = PyMapping_Items(obj); PG_TRY(); { Py_ssize_t i; PyObject *items; items = (PyObject *) items_v; pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL); for (i = 0; i < pcount; i++) { JsonbValue jbvKey; PyObject *item = PyList_GetItem(items, i); PyObject *key = PyTuple_GetItem(item, 0); PyObject *value = PyTuple_GetItem(item, 1); /* 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 */ PLyString_ToJsonbValue(key, &jbvKey); } (void) pushJsonbValue(jsonb_state, WJB_KEY, &jbvKey); (void) PLyObject_ToJsonbValue(value, jsonb_state, false); } out = pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL); } PG_CATCH(); { Py_DECREF(items_v); PG_RE_THROW(); } PG_END_TRY(); return out; }
/* * degenerate case of jsonb_build_array where it gets 0 arguments. */ Datum jsonb_build_array_noargs(PG_FUNCTION_ARGS) { JsonbInState result; memset(&result, 0, sizeof(JsonbInState)); (void) pushJsonbValue(&result.parseState, WJB_BEGIN_ARRAY, NULL); result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL); PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); }
static void jsonb_in_array_end(void *pstate) { JsonbInState *_state = (JsonbInState *) pstate; _state->res = pushJsonbValue(&_state->parseState, WJB_END_ARRAY, NULL); }
Datum jsonb_object_agg_finalfn(PG_FUNCTION_ARGS) { JsonbAggState *arg; JsonbInState result; Jsonb *out; /* cannot be called directly because of internal-type argument */ Assert(AggCheckCallContext(fcinfo, NULL)); if (PG_ARGISNULL(0)) PG_RETURN_NULL(); /* returns null iff no input values */ arg = (JsonbAggState *) PG_GETARG_POINTER(0); /* * We need to do a shallow clone of the argument's res field in case the * final function is called more than once, so we avoid changing the * aggregate state value. A shallow clone is sufficient as we aren't * going to change any of the values, just add the final object end * marker. */ result.parseState = clone_parse_state(arg->res->parseState); result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); out = JsonbValueToJsonb(result.res); PG_RETURN_POINTER(out); }
static void jsonb_in_object_end(void *pstate) { JsonbInState *_state = (JsonbInState *) pstate; _state->res = pushJsonbValue(&_state->parseState, WJB_END_OBJECT, NULL); }
Datum hstore_to_jsonb(PG_FUNCTION_ARGS) { HStore *in = PG_GETARG_HS(0); int i; int count = HS_COUNT(in); char *base = STRPTR(in); HEntry *entries = ARRPTR(in); JsonbParseState *state = NULL; JsonbValue *res; res = pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); for (i = 0; i < count; i++) { JsonbValue key, val; key.estSize = sizeof(JEntry); key.type = jbvString; key.val.string.len = HS_KEYLEN(entries, i); key.val.string.val = pnstrdup(HS_KEY(entries, base, i), key.val.string.len); key.estSize += key.val.string.len; res = pushJsonbValue(&state, WJB_KEY, &key); if (HS_VALISNULL(entries, i)) { val.estSize = sizeof(JEntry); val.type = jbvNull; } else { val.estSize = sizeof(JEntry); val.type = jbvString; val.val.string.len = HS_VALLEN(entries, i); val.val.string.val = pnstrdup(HS_VAL(entries, base, i), val.val.string.len); val.estSize += val.val.string.len; } res = pushJsonbValue(&state, WJB_VALUE, &val); } res = pushJsonbValue(&state, WJB_END_OBJECT, NULL); PG_RETURN_POINTER(JsonbValueToJsonb(res)); }
/* * Turn an array into JSON. */ static void array_to_jsonb_internal(Datum array, JsonbInState *result) { ArrayType *v = DatumGetArrayTypeP(array); Oid element_type = ARR_ELEMTYPE(v); int *dim; int ndim; int nitems; int count = 0; Datum *elements; bool *nulls; int16 typlen; bool typbyval; char typalign; JsonbTypeCategory tcategory; Oid outfuncoid; ndim = ARR_NDIM(v); dim = ARR_DIMS(v); nitems = ArrayGetNItems(ndim, dim); if (nitems <= 0) { result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_ARRAY, NULL); result->res = pushJsonbValue(&result->parseState, WJB_END_ARRAY, NULL); return; } get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign); jsonb_categorize_type(element_type, &tcategory, &outfuncoid); deconstruct_array(v, element_type, typlen, typbyval, typalign, &elements, &nulls, &nitems); array_dim_to_jsonb(result, 0, ndim, dim, elements, nulls, &count, tcategory, outfuncoid); pfree(elements); pfree(nulls); }
/* * PLySequence_ToJsonbValue * * Transform python list to JsonbValue. Expects transformed PyObject and * a state required for jsonb construction. */ static JsonbValue * PLySequence_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state) { Py_ssize_t i; Py_ssize_t pcount; pcount = PySequence_Size(obj); pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL); for (i = 0; i < pcount; i++) { PyObject *value = PySequence_GetItem(obj, i); (void) PLyObject_ToJsonbValue(value, jsonb_state, true); } return pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL); }
/* * SQL function jsonb_build_array(variadic "any") */ Datum jsonb_build_array(PG_FUNCTION_ARGS) { int nargs = PG_NARGS(); int i; Datum arg; Oid val_type; JsonbInState result; memset(&result, 0, sizeof(JsonbInState)); result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_ARRAY, NULL); for (i = 0; i < nargs; i++) { val_type = get_fn_expr_argtype(fcinfo->flinfo, i); arg = PG_GETARG_DATUM(i + 1); /* see comments in jsonb_build_object above */ if (val_type == UNKNOWNOID && get_fn_expr_arg_stable(fcinfo->flinfo, i)) { val_type = TEXTOID; if (PG_ARGISNULL(i)) arg = (Datum) 0; else arg = CStringGetTextDatum(PG_GETARG_POINTER(i)); } else { arg = PG_GETARG_DATUM(i); } if (val_type == InvalidOid || val_type == UNKNOWNOID) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("arg %d: could not determine data type", i + 1))); add_jsonb(arg, PG_ARGISNULL(i), &result, val_type, false); } result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL); PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); }
static void jsonb_in_object_field_start(void *pstate, char *fname, bool isnull) { JsonbInState *_state = (JsonbInState *) pstate; JsonbValue v; Assert(fname != NULL); v.type = jbvString; v.val.string.len = checkStringLen(strlen(fname)); v.val.string.val = fname; _state->res = pushJsonbValue(&_state->parseState, WJB_KEY, &v); }
/* * jsonb_delete: * Return copy of jsonb with the specified item removed. * Item is a one key or element from jsonb, specified by name. * If there are many keys or elements with than name, * the first one will be removed. */ Datum jsonb_delete(PG_FUNCTION_ARGS) { Jsonb *in = PG_GETARG_JSONB(0); text *key = PG_GETARG_TEXT_PP(1); char *keyptr = VARDATA_ANY(key); int keylen = VARSIZE_ANY_EXHDR(key); JsonbParseState *state = NULL; JsonbIterator *it; uint32 r; JsonbValue v, *res = NULL; bool skipped = false; if (JB_ROOT_IS_SCALAR(in)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot delete from scalar"))); if (JB_ROOT_COUNT(in) == 0) { PG_RETURN_JSONB(in); } it = JsonbIteratorInit(&in->root); while((r = JsonbIteratorNext(&it, &v, false)) != 0) { if (!skipped && (r == WJB_ELEM || r == WJB_KEY) && (v.type == jbvString && keylen == v.val.string.len && memcmp(keyptr, v.val.string.val, keylen) == 0)) { /* we should delete only one key/element */ skipped = true; if (r == WJB_KEY) { /* skip corresponding value */ JsonbIteratorNext(&it, &v, true); } continue; } res = pushJsonbValue(&state, r, r < WJB_BEGIN_ARRAY ? &v : NULL); } Assert(res != NULL); PG_RETURN_JSONB(JsonbValueToJsonb(res)); }
/* * PLyObject_ToJsonbValue(PyObject *obj) * * Transform python object to JsonbValue. */ static JsonbValue * PLyObject_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state, bool is_elem) { JsonbValue buf; JsonbValue *out; if (!(PyString_Check(obj) || PyUnicode_Check(obj))) { if (PySequence_Check(obj)) return PLySequence_ToJsonbValue(obj, jsonb_state); else if (PyMapping_Check(obj)) return PLyMapping_ToJsonbValue(obj, jsonb_state); } /* Allocate JsonbValue in heap only if it is raw scalar value. */ if (*jsonb_state) out = &buf; else out = palloc(sizeof(JsonbValue)); if (obj == Py_None) out->type = jbvNull; else if (PyString_Check(obj) || PyUnicode_Check(obj)) PLyString_ToJsonbValue(obj, out); /* * PyNumber_Check() returns true for booleans, so boolean check should * come first. */ else if (PyBool_Check(obj)) { out = palloc(sizeof(JsonbValue)); 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), (errmsg("Python type \"%s\" cannot be transformed to jsonb", PLyObject_AsString((PyObject *) obj->ob_type))))); /* Push result into 'jsonb_state' unless it is raw scalar value. */ return (*jsonb_state ? pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, out) : out); }
/* * SQL function jsonb_object(text[], text[]) * * take separate name and value arrays of text to construct a jsonb object * pairwise. */ Datum jsonb_object_two_arg(PG_FUNCTION_ARGS) { ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(0); ArrayType *val_array = PG_GETARG_ARRAYTYPE_P(1); int nkdims = ARR_NDIM(key_array); int nvdims = ARR_NDIM(val_array); Datum *key_datums, *val_datums; bool *key_nulls, *val_nulls; int key_count, val_count, i; JsonbInState result; memset(&result, 0, sizeof(JsonbInState)); (void) pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); if (nkdims > 1 || nkdims != nvdims) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("wrong number of array subscripts"))); if (nkdims == 0) goto close_object; deconstruct_array(key_array, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, &key_count); deconstruct_array(val_array, TEXTOID, -1, false, 'i', &val_datums, &val_nulls, &val_count); if (key_count != val_count) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("mismatched array dimensions"))); for (i = 0; i < key_count; ++i) { JsonbValue v; char *str; int len; if (key_nulls[i]) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value not allowed for object key"))); str = TextDatumGetCString(key_datums[i]); len = strlen(str); v.type = jbvString; v.val.string.len = len; v.val.string.val = str; (void) pushJsonbValue(&result.parseState, WJB_KEY, &v); if (val_nulls[i]) { v.type = jbvNull; } else { str = TextDatumGetCString(val_datums[i]); len = strlen(str); v.type = jbvString; v.val.string.len = len; v.val.string.val = str; } (void) pushJsonbValue(&result.parseState, WJB_VALUE, &v); } pfree(key_datums); pfree(key_nulls); pfree(val_datums); pfree(val_nulls); close_object: result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); }
Datum hstore_to_jsonb_loose(PG_FUNCTION_ARGS) { HStore *in = PG_GETARG_HS(0); int i; int count = HS_COUNT(in); char *base = STRPTR(in); HEntry *entries = ARRPTR(in); JsonbParseState *state = NULL; JsonbValue *res; StringInfoData tmp; bool is_number; initStringInfo(&tmp); res = pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); for (i = 0; i < count; i++) { JsonbValue key, val; key.estSize = sizeof(JEntry); key.type = jbvString; key.val.string.len = HS_KEYLEN(entries, i); key.val.string.val = pnstrdup(HS_KEY(entries, base, i), key.val.string.len); key.estSize += key.val.string.len; res = pushJsonbValue(&state, WJB_KEY, &key); val.estSize = sizeof(JEntry); if (HS_VALISNULL(entries, i)) { val.type = jbvNull; } /* guess that values of 't' or 'f' are booleans */ else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 't') { val.type = jbvBool; val.val.boolean = true; } else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 'f') { val.type = jbvBool; val.val.boolean = false; } else { is_number = false; resetStringInfo(&tmp); appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i)); /* * don't treat something with a leading zero followed by another * digit as numeric - could be a zip code or similar */ if (tmp.len > 0 && !(tmp.data[0] == '0' && isdigit((unsigned char) tmp.data[1])) && strspn(tmp.data, "+-0123456789Ee.") == tmp.len) { /* * might be a number. See if we can input it as a numeric * value. Ignore any actual parsed value. */ char *endptr = "junk"; long lval; lval = strtol(tmp.data, &endptr, 10); (void) lval; if (*endptr == '\0') { /* * strol man page says this means the whole string is * valid */ is_number = true; } else { /* not an int - try a double */ double dval; dval = strtod(tmp.data, &endptr); (void) dval; if (*endptr == '\0') is_number = true; } } if (is_number) { val.type = jbvNumeric; val.val.numeric = DatumGetNumeric( DirectFunctionCall3(numeric_in, CStringGetDatum(tmp.data), 0, -1)); val.estSize += VARSIZE_ANY(val.val.numeric) +sizeof(JEntry); } else { val.estSize = sizeof(JEntry); val.type = jbvString; val.val.string.len = HS_VALLEN(entries, i); val.val.string.val = pnstrdup(HS_VAL(entries, base, i), val.val.string.len); val.estSize += val.val.string.len; } } res = pushJsonbValue(&state, WJB_VALUE, &val); } res = pushJsonbValue(&state, WJB_END_OBJECT, NULL); PG_RETURN_POINTER(JsonbValueToJsonb(res)); }
/* * Turn a composite / record into JSON. */ static void composite_to_jsonb(Datum composite, JsonbInState *result) { HeapTupleHeader td; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tmptup, *tuple; int i; td = DatumGetHeapTupleHeader(composite); /* Extract rowtype info and find a tupdesc */ tupType = HeapTupleHeaderGetTypeId(td); tupTypmod = HeapTupleHeaderGetTypMod(td); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); /* Build a temporary HeapTuple control structure */ tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; tuple = &tmptup; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_OBJECT, NULL); for (i = 0; i < tupdesc->natts; i++) { Datum val; bool isnull; char *attname; JsonbTypeCategory tcategory; Oid outfuncoid; JsonbValue v; if (tupdesc->attrs[i]->attisdropped) continue; attname = NameStr(tupdesc->attrs[i]->attname); v.type = jbvString; /* don't need checkStringLen here - can't exceed maximum name length */ v.val.string.len = strlen(attname); v.val.string.val = attname; result->res = pushJsonbValue(&result->parseState, WJB_KEY, &v); val = heap_getattr(tuple, i + 1, tupdesc, &isnull); if (isnull) { tcategory = JSONBTYPE_NULL; outfuncoid = InvalidOid; } else jsonb_categorize_type(tupdesc->attrs[i]->atttypid, &tcategory, &outfuncoid); datum_to_jsonb(val, isnull, result, tcategory, outfuncoid, false); } result->res = pushJsonbValue(&result->parseState, WJB_END_OBJECT, NULL); ReleaseTupleDesc(tupdesc); }
/* * SQL function jsonb_build_object(variadic "any") */ Datum jsonb_build_object(PG_FUNCTION_ARGS) { int nargs = PG_NARGS(); int i; Datum arg; Oid val_type; JsonbInState result; if (nargs % 2 != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid number of arguments: object must be matched key value pairs"))); memset(&result, 0, sizeof(JsonbInState)); result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); for (i = 0; i < nargs; i += 2) { /* process key */ if (PG_ARGISNULL(i)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument %d: key must not be null", i + 1))); val_type = get_fn_expr_argtype(fcinfo->flinfo, i); /* * turn a constant (more or less literal) value that's of unknown type * into text. Unknowns come in as a cstring pointer. */ if (val_type == UNKNOWNOID && get_fn_expr_arg_stable(fcinfo->flinfo, i)) { val_type = TEXTOID; arg = CStringGetTextDatum(PG_GETARG_POINTER(i)); } else { arg = PG_GETARG_DATUM(i); } if (val_type == InvalidOid || val_type == UNKNOWNOID) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument %d: could not determine data type", i + 1))); add_jsonb(arg, false, &result, val_type, true); /* process value */ val_type = get_fn_expr_argtype(fcinfo->flinfo, i + 1); /* see comments above */ if (val_type == UNKNOWNOID && get_fn_expr_arg_stable(fcinfo->flinfo, i + 1)) { val_type = TEXTOID; if (PG_ARGISNULL(i + 1)) arg = (Datum) 0; else arg = CStringGetTextDatum(PG_GETARG_POINTER(i + 1)); } else { arg = PG_GETARG_DATUM(i + 1); } if (val_type == InvalidOid || val_type == UNKNOWNOID) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument %d: could not determine data type", i + 2))); add_jsonb(arg, PG_ARGISNULL(i + 1), &result, val_type, false); } result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); }
/* * Turn a Datum into jsonb, adding it to the result JsonbInState. * * tcategory and outfuncoid are from a previous call to json_categorize_type, * except that if is_null is true then they can be invalid. * * If key_scalar is true, the value is stored as a key, so insist * it's of an acceptable type, and force it to be a jbvString. */ static void datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, JsonbTypeCategory tcategory, Oid outfuncoid, bool key_scalar) { char *outputstr; bool numeric_error; JsonbValue jb; bool scalar_jsonb = false; check_stack_depth(); /* Convert val to a JsonbValue in jb (in most cases) */ if (is_null) { Assert(!key_scalar); jb.type = jbvNull; } else if (key_scalar && (tcategory == JSONBTYPE_ARRAY || tcategory == JSONBTYPE_COMPOSITE || tcategory == JSONBTYPE_JSON || tcategory == JSONBTYPE_JSONB || tcategory == JSONBTYPE_JSONCAST)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("key value must be scalar, not array, composite, or json"))); } else { if (tcategory == JSONBTYPE_JSONCAST) val = OidFunctionCall1(outfuncoid, val); switch (tcategory) { case JSONBTYPE_ARRAY: array_to_jsonb_internal(val, result); break; case JSONBTYPE_COMPOSITE: composite_to_jsonb(val, result); break; case JSONBTYPE_BOOL: if (key_scalar) { outputstr = DatumGetBool(val) ? "true" : "false"; jb.type = jbvString; jb.val.string.len = strlen(outputstr); jb.val.string.val = outputstr; } else { jb.type = jbvBool; jb.val.boolean = DatumGetBool(val); } break; case JSONBTYPE_NUMERIC: outputstr = OidOutputFunctionCall(outfuncoid, val); if (key_scalar) { /* always quote keys */ jb.type = jbvString; jb.val.string.len = strlen(outputstr); jb.val.string.val = outputstr; } else { /* * Make it numeric if it's a valid JSON number, otherwise * a string. Invalid numeric output will always have an * 'N' or 'n' in it (I think). */ numeric_error = (strchr(outputstr, 'N') != NULL || strchr(outputstr, 'n') != NULL); if (!numeric_error) { jb.type = jbvNumeric; jb.val.numeric = DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(outputstr), 0, -1)); pfree(outputstr); } else { jb.type = jbvString; jb.val.string.len = strlen(outputstr); jb.val.string.val = outputstr; } } break; case JSONBTYPE_DATE: { DateADT date; struct pg_tm tm; char buf[MAXDATELEN + 1]; date = DatumGetDateADT(val); /* Same as date_out(), but forcing DateStyle */ if (DATE_NOT_FINITE(date)) EncodeSpecialDate(date, buf); else { j2date(date + POSTGRES_EPOCH_JDATE, &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday)); EncodeDateOnly(&tm, USE_XSD_DATES, buf); } jb.type = jbvString; jb.val.string.len = strlen(buf); jb.val.string.val = pstrdup(buf); } break; case JSONBTYPE_TIMESTAMP: { Timestamp timestamp; struct pg_tm tm; fsec_t fsec; char buf[MAXDATELEN + 1]; timestamp = DatumGetTimestamp(val); /* Same as timestamp_out(), but forcing DateStyle */ if (TIMESTAMP_NOT_FINITE(timestamp)) EncodeSpecialTimestamp(timestamp, buf); else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0) EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, buf); else ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); jb.type = jbvString; jb.val.string.len = strlen(buf); jb.val.string.val = pstrdup(buf); } break; case JSONBTYPE_TIMESTAMPTZ: { TimestampTz timestamp; struct pg_tm tm; int tz; fsec_t fsec; const char *tzn = NULL; char buf[MAXDATELEN + 1]; timestamp = DatumGetTimestampTz(val); /* Same as timestamptz_out(), but forcing DateStyle */ if (TIMESTAMP_NOT_FINITE(timestamp)) EncodeSpecialTimestamp(timestamp, buf); else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0) EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf); else ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); jb.type = jbvString; jb.val.string.len = strlen(buf); jb.val.string.val = pstrdup(buf); } break; case JSONBTYPE_JSONCAST: case JSONBTYPE_JSON: { /* parse the json right into the existing result object */ JsonLexContext *lex; JsonSemAction sem; text *json = DatumGetTextP(val); lex = makeJsonLexContext(json, true); memset(&sem, 0, sizeof(sem)); sem.semstate = (void *) result; sem.object_start = jsonb_in_object_start; sem.array_start = jsonb_in_array_start; sem.object_end = jsonb_in_object_end; sem.array_end = jsonb_in_array_end; sem.scalar = jsonb_in_scalar; sem.object_field_start = jsonb_in_object_field_start; pg_parse_json(lex, &sem); } break; case JSONBTYPE_JSONB: { Jsonb *jsonb = DatumGetJsonb(val); JsonbIterator *it; it = JsonbIteratorInit(&jsonb->root); if (JB_ROOT_IS_SCALAR(jsonb)) { (void) JsonbIteratorNext(&it, &jb, true); Assert(jb.type == jbvArray); (void) JsonbIteratorNext(&it, &jb, true); scalar_jsonb = true; } else { JsonbIteratorToken type; while ((type = JsonbIteratorNext(&it, &jb, false)) != WJB_DONE) { if (type == WJB_END_ARRAY || type == WJB_END_OBJECT || type == WJB_BEGIN_ARRAY || type == WJB_BEGIN_OBJECT) result->res = pushJsonbValue(&result->parseState, type, NULL); else result->res = pushJsonbValue(&result->parseState, type, &jb); } } } break; default: outputstr = OidOutputFunctionCall(outfuncoid, val); jb.type = jbvString; jb.val.string.len = checkStringLen(strlen(outputstr)); jb.val.string.val = outputstr; break; } } /* Now insert jb into result, unless we did it recursively */ if (!is_null && !scalar_jsonb && tcategory >= JSONBTYPE_JSON && tcategory <= JSONBTYPE_JSONCAST) { /* work has been done recursively */ return; } else if (result->parseState == NULL) { /* single root scalar */ JsonbValue va; va.type = jbvArray; va.val.array.rawScalar = true; va.val.array.nElems = 1; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_ARRAY, &va); result->res = pushJsonbValue(&result->parseState, WJB_ELEM, &jb); result->res = pushJsonbValue(&result->parseState, WJB_END_ARRAY, NULL); } else { JsonbValue *o = &result->parseState->contVal; switch (o->type) { case jbvArray: result->res = pushJsonbValue(&result->parseState, WJB_ELEM, &jb); break; case jbvObject: result->res = pushJsonbValue(&result->parseState, key_scalar ? WJB_KEY : WJB_VALUE, &jb); break; default: elog(ERROR, "unexpected parent of nested structure"); } } }
/* * SQL function jsonb_object(text[]) * * take a one or two dimensional array of text as name value pairs * for a jsonb object. * */ Datum jsonb_object(PG_FUNCTION_ARGS) { ArrayType *in_array = PG_GETARG_ARRAYTYPE_P(0); int ndims = ARR_NDIM(in_array); Datum *in_datums; bool *in_nulls; int in_count, count, i; JsonbInState result; memset(&result, 0, sizeof(JsonbInState)); (void) pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); switch (ndims) { case 0: goto close_object; break; case 1: if ((ARR_DIMS(in_array)[0]) % 2) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("array must have even number of elements"))); break; case 2: if ((ARR_DIMS(in_array)[1]) != 2) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("array must have two columns"))); break; default: ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("wrong number of array subscripts"))); } deconstruct_array(in_array, TEXTOID, -1, false, 'i', &in_datums, &in_nulls, &in_count); count = in_count / 2; for (i = 0; i < count; ++i) { JsonbValue v; char *str; int len; if (in_nulls[i * 2]) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value not allowed for object key"))); str = TextDatumGetCString(in_datums[i * 2]); len = strlen(str); v.type = jbvString; v.val.string.len = len; v.val.string.val = str; (void) pushJsonbValue(&result.parseState, WJB_KEY, &v); if (in_nulls[i * 2 + 1]) { v.type = jbvNull; } else { str = TextDatumGetCString(in_datums[i * 2 + 1]); len = strlen(str); v.type = jbvString; v.val.string.len = len; v.val.string.val = str; } (void) pushJsonbValue(&result.parseState, WJB_VALUE, &v); } pfree(in_datums); pfree(in_nulls); close_object: result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); }
/* * jsonb_delete_idx: * Return a copy of jsonb withour specified items. * Delete key (only from the top level of object) or element from jsonb by index (idx). * Negative idx value is supported, and it implies the countdown from the last key/element. * If idx is more, than numbers of keys/elements, or equal - nothing will be deleted. * If idx is negative and -idx is more, than number of keys/elements - the last one will be deleted. * * TODO: take care about nesting values. */ Datum jsonb_delete_idx(PG_FUNCTION_ARGS) { Jsonb *in = PG_GETARG_JSONB(0); int idx = PG_GETARG_INT32(1); JsonbParseState *state = NULL; JsonbIterator *it; uint32 r, i = 0, n; JsonbValue v, *res = NULL; if (JB_ROOT_IS_SCALAR(in)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot delete from scalar"))); if (JB_ROOT_COUNT(in) == 0) { PG_RETURN_JSONB(in); } it = JsonbIteratorInit(&in->root); r = JsonbIteratorNext(&it, &v, false); if (r == WJB_BEGIN_ARRAY) n = v.val.array.nElems; else n = v.val.object.nPairs; if (idx < 0) { if (-idx > n) idx = n; else idx = n + idx; } if (idx >= n) { PG_RETURN_JSONB(in); } pushJsonbValue(&state, r, r < WJB_BEGIN_ARRAY ? &v : NULL); while((r = JsonbIteratorNext(&it, &v, true)) != 0) { if (r == WJB_ELEM || r == WJB_KEY) { if (i++ == idx) { if (r == WJB_KEY) JsonbIteratorNext(&it, &v, true); /* skip value */ continue; } } res = pushJsonbValue(&state, r, r < WJB_BEGIN_ARRAY ? &v : NULL); } Assert (res != NULL); PG_RETURN_JSONB(JsonbValueToJsonb(res)); }
/* * jsonb_object_agg aggregate function */ Datum jsonb_object_agg_transfn(PG_FUNCTION_ARGS) { MemoryContext oldcontext, aggcontext; JsonbInState elem; JsonbAggState *state; Datum val; JsonbInState *result; bool single_scalar; JsonbIterator *it; Jsonb *jbkey, *jbval; JsonbValue v; JsonbIteratorToken type; if (!AggCheckCallContext(fcinfo, &aggcontext)) { /* cannot be called directly because of internal-type argument */ elog(ERROR, "jsonb_object_agg_transfn called in non-aggregate context"); } /* set up the accumulator on the first go round */ if (PG_ARGISNULL(0)) { Oid arg_type; oldcontext = MemoryContextSwitchTo(aggcontext); state = palloc(sizeof(JsonbAggState)); result = palloc0(sizeof(JsonbInState)); state->res = result; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_OBJECT, NULL); MemoryContextSwitchTo(oldcontext); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); if (arg_type == InvalidOid) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine input data type"))); jsonb_categorize_type(arg_type, &state->key_category, &state->key_output_func); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 2); if (arg_type == InvalidOid) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine input data type"))); jsonb_categorize_type(arg_type, &state->val_category, &state->val_output_func); } else { state = (JsonbAggState *) PG_GETARG_POINTER(0); result = state->res; } /* turn the argument into jsonb in the normal function context */ if (PG_ARGISNULL(1)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("field name must not be null"))); val = PG_GETARG_DATUM(1); memset(&elem, 0, sizeof(JsonbInState)); datum_to_jsonb(val, false, &elem, state->key_category, state->key_output_func, true); jbkey = JsonbValueToJsonb(elem.res); val = PG_ARGISNULL(2) ? (Datum) 0 : PG_GETARG_DATUM(2); memset(&elem, 0, sizeof(JsonbInState)); datum_to_jsonb(val, PG_ARGISNULL(2), &elem, state->val_category, state->val_output_func, false); jbval = JsonbValueToJsonb(elem.res); it = JsonbIteratorInit(&jbkey->root); /* switch to the aggregate context for accumulation operations */ oldcontext = MemoryContextSwitchTo(aggcontext); /* * keys should be scalar, and we should have already checked for that * above when calling datum_to_jsonb, so we only need to look for these * things. */ while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { switch (type) { case WJB_BEGIN_ARRAY: if (!v.val.array.rawScalar) elog(ERROR, "unexpected structure for key"); break; case WJB_ELEM: if (v.type == jbvString) { /* copy string values in the aggregate context */ char *buf = palloc(v.val.string.len + 1); snprintf(buf, v.val.string.len + 1, "%s", v.val.string.val); v.val.string.val = buf; } else { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("object keys must be strings"))); } result->res = pushJsonbValue(&result->parseState, WJB_KEY, &v); break; case WJB_END_ARRAY: break; default: elog(ERROR, "unexpected structure for key"); break; } } it = JsonbIteratorInit(&jbval->root); single_scalar = false; /* * values can be anything, including structured and null, so we treat them * as in json_agg_transfn, except that single scalars are always pushed as * WJB_VALUE items. */ while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { switch (type) { case WJB_BEGIN_ARRAY: if (v.val.array.rawScalar) single_scalar = true; else result->res = pushJsonbValue(&result->parseState, type, NULL); break; case WJB_END_ARRAY: if (!single_scalar) result->res = pushJsonbValue(&result->parseState, type, NULL); break; case WJB_BEGIN_OBJECT: case WJB_END_OBJECT: result->res = pushJsonbValue(&result->parseState, type, NULL); break; case WJB_ELEM: case WJB_KEY: case WJB_VALUE: if (v.type == jbvString) { /* copy string values in the aggregate context */ char *buf = palloc(v.val.string.len + 1); snprintf(buf, v.val.string.len + 1, "%s", v.val.string.val); v.val.string.val = buf; } else if (v.type == jbvNumeric) { /* same for numeric */ v.val.numeric = DatumGetNumeric(DirectFunctionCall1(numeric_uplus, NumericGetDatum(v.val.numeric))); } result->res = pushJsonbValue(&result->parseState, single_scalar ? WJB_VALUE : type, &v); break; default: elog(ERROR, "unknown jsonb iterator token type"); } } MemoryContextSwitchTo(oldcontext); PG_RETURN_POINTER(state); }
/* * For jsonb we always want the de-escaped value - that's what's in token */ static void jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype) { JsonbInState *_state = (JsonbInState *) pstate; JsonbValue v; v.estSize = sizeof(JEntry); switch (tokentype) { case JSON_TOKEN_STRING: Assert(token != NULL); v.type = jbvString; v.val.string.len = checkStringLen(strlen(token)); v.val.string.val = pnstrdup(token, v.val.string.len); v.estSize += v.val.string.len; break; case JSON_TOKEN_NUMBER: /* * No need to check size of numeric values, because maximum * numeric size is well below the JsonbValue restriction */ Assert(token != NULL); v.type = jbvNumeric; v.val.numeric = DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(token), 0, -1)); v.estSize += VARSIZE_ANY(v.val.numeric) +sizeof(JEntry) /* alignment */ ; break; case JSON_TOKEN_TRUE: v.type = jbvBool; v.val.boolean = true; break; case JSON_TOKEN_FALSE: v.type = jbvBool; v.val.boolean = false; break; case JSON_TOKEN_NULL: v.type = jbvNull; break; default: /* should not be possible */ elog(ERROR, "invalid json token type"); break; } if (_state->parseState == NULL) { /* single scalar */ JsonbValue va; va.type = jbvArray; va.val.array.rawScalar = true; va.val.array.nElems = 1; _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_ARRAY, &va); _state->res = pushJsonbValue(&_state->parseState, WJB_ELEM, &v); _state->res = pushJsonbValue(&_state->parseState, WJB_END_ARRAY, NULL); } else { JsonbValue *o = &_state->parseState->contVal; switch (o->type) { case jbvArray: _state->res = pushJsonbValue(&_state->parseState, WJB_ELEM, &v); break; case jbvObject: _state->res = pushJsonbValue(&_state->parseState, WJB_VALUE, &v); break; default: elog(ERROR, "unexpected parent of nested structure"); } } }
/* * jsonb_agg aggregate function */ Datum jsonb_agg_transfn(PG_FUNCTION_ARGS) { MemoryContext oldcontext, aggcontext; JsonbAggState *state; JsonbInState elem; Datum val; JsonbInState *result; bool single_scalar = false; JsonbIterator *it; Jsonb *jbelem; JsonbValue v; JsonbIteratorToken type; if (!AggCheckCallContext(fcinfo, &aggcontext)) { /* cannot be called directly because of internal-type argument */ elog(ERROR, "jsonb_agg_transfn called in non-aggregate context"); } /* set up the accumulator on the first go round */ if (PG_ARGISNULL(0)) { Oid arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); if (arg_type == InvalidOid) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine input data type"))); oldcontext = MemoryContextSwitchTo(aggcontext); state = palloc(sizeof(JsonbAggState)); result = palloc0(sizeof(JsonbInState)); state->res = result; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_ARRAY, NULL); MemoryContextSwitchTo(oldcontext); jsonb_categorize_type(arg_type, &state->val_category, &state->val_output_func); } else { state = (JsonbAggState *) PG_GETARG_POINTER(0); result = state->res; } /* turn the argument into jsonb in the normal function context */ val = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1); memset(&elem, 0, sizeof(JsonbInState)); datum_to_jsonb(val, PG_ARGISNULL(1), &elem, state->val_category, state->val_output_func, false); jbelem = JsonbValueToJsonb(elem.res); /* switch to the aggregate context for accumulation operations */ oldcontext = MemoryContextSwitchTo(aggcontext); it = JsonbIteratorInit(&jbelem->root); while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { switch (type) { case WJB_BEGIN_ARRAY: if (v.val.array.rawScalar) single_scalar = true; else result->res = pushJsonbValue(&result->parseState, type, NULL); break; case WJB_END_ARRAY: if (!single_scalar) result->res = pushJsonbValue(&result->parseState, type, NULL); break; case WJB_BEGIN_OBJECT: case WJB_END_OBJECT: result->res = pushJsonbValue(&result->parseState, type, NULL); break; case WJB_ELEM: case WJB_KEY: case WJB_VALUE: if (v.type == jbvString) { /* copy string values in the aggregate context */ char *buf = palloc(v.val.string.len + 1); snprintf(buf, v.val.string.len + 1, "%s", v.val.string.val); v.val.string.val = buf; } else if (v.type == jbvNumeric) { /* same for numeric */ v.val.numeric = DatumGetNumeric(DirectFunctionCall1(numeric_uplus, NumericGetDatum(v.val.numeric))); } result->res = pushJsonbValue(&result->parseState, type, &v); break; default: elog(ERROR, "unknown jsonb iterator token type"); } } MemoryContextSwitchTo(oldcontext); PG_RETURN_POINTER(state); }