Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  jsonb.c

  Sprache: C
 

/*-------------------------------------------------------------------------
 *
 * jsonb.c
 *  I/O routines for jsonb type
 *
 * Copyright (c) 2014-2025, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *   src/backend/utils/adt/jsonb.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/htup_details.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/json.h"
#include "utils/jsonb.h"
#include "utils/jsonfuncs.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"

typedef struct JsonbInState
{
 JsonbParseState *parseState;
 JsonbValue *res;
 bool  unique_keys;
 Node    *escontext;
} JsonbInState;

typedef struct JsonbAggState
{
 JsonbInState *res;
 JsonTypeCategory key_category;
 Oid   key_output_func;
 JsonTypeCategory val_category;
 Oid   val_output_func;
} JsonbAggState;

static inline Datum jsonb_from_cstring(char *json, int len, bool unique_keys,
            Node *escontext);
static bool checkStringLen(size_t len, Node *escontext);
static JsonParseErrorType jsonb_in_object_start(void *pstate);
static JsonParseErrorType jsonb_in_object_end(void *pstate);
static JsonParseErrorType jsonb_in_array_start(void *pstate);
static JsonParseErrorType jsonb_in_array_end(void *pstate);
static JsonParseErrorType jsonb_in_object_field_start(void *pstate, char *fname, bool isnull);
static void jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal);
static JsonParseErrorType jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype);
static void composite_to_jsonb(Datum composite, JsonbInState *result);
static void array_dim_to_jsonb(JsonbInState *result, int dim, int ndims, int *dims,
          const Datum *vals, const bool *nulls, int *valcount,
          JsonTypeCategory tcategory, Oid outfuncoid);
static void array_to_jsonb_internal(Datum array, JsonbInState *result);
static void datum_to_jsonb_internal(Datum val, bool is_null, JsonbInState *result,
         JsonTypeCategory tcategory, Oid outfuncoid,
         bool key_scalar);
static void add_jsonb(Datum val, bool is_null, JsonbInState *result,
       Oid val_type, bool key_scalar);
static JsonbParseState *clone_parse_state(JsonbParseState *state);
static char *JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_lenbool indent);
static void add_indent(StringInfo out, bool indent, int level);

/*
 * jsonb type input function
 */

Datum
jsonb_in(PG_FUNCTION_ARGS)
{
 char    *json = PG_GETARG_CSTRING(0);

 return jsonb_from_cstring(json, strlen(json), false, fcinfo->context);
}

/*
 * jsonb type recv function
 *
 * The type is sent as text in binary mode, so this is almost the same
 * as the input function, but it's prefixed with a version number so we
 * can change the binary format sent in future if necessary. For now,
 * only version 1 is supported.
 */

Datum
jsonb_recv(PG_FUNCTION_ARGS)
{
 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
 int   version = pq_getmsgint(buf, 1);
 char    *str;
 int   nbytes;

 if (version == 1)
  str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
 else
  elog(ERROR, "unsupported jsonb version number %d", version);

 return jsonb_from_cstring(str, nbytes, false, NULL);
}

/*
 * jsonb type output function
 */

Datum
jsonb_out(PG_FUNCTION_ARGS)
{
 Jsonb    *jb = PG_GETARG_JSONB_P(0);
 char    *out;

 out = JsonbToCString(NULL, &jb->root, VARSIZE(jb));

 PG_RETURN_CSTRING(out);
}

/*
 * jsonb type send function
 *
 * Just send jsonb as a version number, then a string of text
 */

Datum
jsonb_send(PG_FUNCTION_ARGS)
{
 Jsonb    *jb = PG_GETARG_JSONB_P(0);
 StringInfoData buf;
 StringInfo jtext = makeStringInfo();
 int   version = 1;

 (void) JsonbToCString(jtext, &jb->root, VARSIZE(jb));

 pq_begintypsend(&buf);
 pq_sendint8(&buf, version);
 pq_sendtext(&buf, jtext->data, jtext->len);
 destroyStringInfo(jtext);

 PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}

/*
 * jsonb_from_text
 *
 * Turns json text string into a jsonb Datum.
 */

Datum
jsonb_from_text(text *js, bool unique_keys)
{
 return jsonb_from_cstring(VARDATA_ANY(js),
         VARSIZE_ANY_EXHDR(js),
         unique_keys,
         NULL);
}

/*
 * Get the type name of a jsonb container.
 */

static const char *
JsonbContainerTypeName(JsonbContainer *jbc)
{
 JsonbValue scalar;

 if (JsonbExtractScalar(jbc, &scalar))
  return JsonbTypeName(&scalar);
 else if (JsonContainerIsArray(jbc))
  return "array";
 else if (JsonContainerIsObject(jbc))
  return "object";
 else
 {
  elog(ERROR, "invalid jsonb container type: 0x%08x", jbc->header);
  return "unknown";
 }
}

/*
 * Get the type name of a jsonb value.
 */

const char *
JsonbTypeName(JsonbValue *val)
{
 switch (val->type)
 {
  case jbvBinary:
   return JsonbContainerTypeName(val->val.binary.data);
  case jbvObject:
   return "object";
  case jbvArray:
   return "array";
  case jbvNumeric:
   return "number";
  case jbvString:
   return "string";
  case jbvBool:
   return "boolean";
  case jbvNull:
   return "null";
  case jbvDatetime:
   switch (val->val.datetime.typid)
   {
    case DATEOID:
     return "date";
    case TIMEOID:
     return "time without time zone";
    case TIMETZOID:
     return "time with time zone";
    case TIMESTAMPOID:
     return "timestamp without time zone";
    case TIMESTAMPTZOID:
     return "timestamp with time zone";
    default:
     elog(ERROR, "unrecognized jsonb value datetime type: %d",
       val->val.datetime.typid);
   }
   return "unknown";
  default:
   elog(ERROR, "unrecognized jsonb value type: %d", val->type);
   return "unknown";
 }
}

/*
 * SQL function jsonb_typeof(jsonb) -> text
 *
 * This function is here because the analog json function is in json.c, since
 * it uses the json parser internals not exposed elsewhere.
 */

Datum
jsonb_typeof(PG_FUNCTION_ARGS)
{
 Jsonb    *in = PG_GETARG_JSONB_P(0);
 const char *result = JsonbContainerTypeName(&in->root);

 PG_RETURN_TEXT_P(cstring_to_text(result));
}

/*
 * jsonb_from_cstring
 *
 * Turns json string into a jsonb Datum.
 *
 * Uses the json parser (with hooks) to construct a jsonb.
 *
 * If escontext points to an ErrorSaveContext, errors are reported there
 * instead of being thrown.
 */

static inline Datum
jsonb_from_cstring(char *json, int len, bool unique_keys, Node *escontext)
{
 JsonLexContext lex;
 JsonbInState state;
 JsonSemAction sem;

 memset(&state, 0sizeof(state));
 memset(&sem, 0sizeof(sem));
 makeJsonLexContextCstringLen(&lex, json, len, GetDatabaseEncoding(), true);

 state.unique_keys = unique_keys;
 state.escontext = escontext;
 sem.semstate = &state;

 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;

 if (!pg_parse_json_or_errsave(&lex, &sem, escontext))
  return (Datum) 0;

 /* after parsing, the item member has the composed jsonb structure */
 PG_RETURN_POINTER(JsonbValueToJsonb(state.res));
}

static bool
checkStringLen(size_t len, Node *escontext)
{
 if (len > JENTRY_OFFLENMASK)
  ereturn(escontext, false,
    (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
     errmsg("string too long to represent as jsonb string"),
     errdetail("Due to an implementation restriction, jsonb strings cannot exceed %d bytes.",
         JENTRY_OFFLENMASK)));

 return true;
}

static JsonParseErrorType
jsonb_in_object_start(void *pstate)
{
 JsonbInState *_state = (JsonbInState *) pstate;

 _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_OBJECT, NULL);
 _state->parseState->unique_keys = _state->unique_keys;

 return JSON_SUCCESS;
}

static JsonParseErrorType
jsonb_in_object_end(void *pstate)
{
 JsonbInState *_state = (JsonbInState *) pstate;

 _state->res = pushJsonbValue(&_state->parseState, WJB_END_OBJECT, NULL);

 return JSON_SUCCESS;
}

static JsonParseErrorType
jsonb_in_array_start(void *pstate)
{
 JsonbInState *_state = (JsonbInState *) pstate;

 _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_ARRAY, NULL);

 return JSON_SUCCESS;
}

static JsonParseErrorType
jsonb_in_array_end(void *pstate)
{
 JsonbInState *_state = (JsonbInState *) pstate;

 _state->res = pushJsonbValue(&_state->parseState, WJB_END_ARRAY, NULL);

 return JSON_SUCCESS;
}

static JsonParseErrorType
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 = strlen(fname);
 if (!checkStringLen(v.val.string.len, _state->escontext))
  return JSON_SEM_ACTION_FAILED;
 v.val.string.val = fname;

 _state->res = pushJsonbValue(&_state->parseState, WJB_KEY, &v);

 return JSON_SUCCESS;
}

static void
jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal)
{
 switch (scalarVal->type)
 {
  case jbvNull:
   appendBinaryStringInfo(out, "null"4);
   break;
  case jbvString:
   escape_json_with_len(out, scalarVal->val.string.val, scalarVal->val.string.len);
   break;
  case jbvNumeric:
   appendStringInfoString(out,
           DatumGetCString(DirectFunctionCall1(numeric_out,
                    PointerGetDatum(scalarVal->val.numeric))));
   break;
  case jbvBool:
   if (scalarVal->val.boolean)
    appendBinaryStringInfo(out, "true"4);
   else
    appendBinaryStringInfo(out, "false"5);
   break;
  default:
   elog(ERROR, "unknown jsonb scalar type");
 }
}

/*
 * For jsonb we always want the de-escaped value - that's what's in token
 */

static JsonParseErrorType
jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype)
{
 JsonbInState *_state = (JsonbInState *) pstate;
 JsonbValue v;
 Datum  numd;

 switch (tokentype)
 {

  case JSON_TOKEN_STRING:
   Assert(token != NULL);
   v.type = jbvString;
   v.val.string.len = strlen(token);
   if (!checkStringLen(v.val.string.len, _state->escontext))
    return JSON_SEM_ACTION_FAILED;
   v.val.string.val = token;
   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;
   if (!DirectInputFunctionCallSafe(numeric_in, token,
            InvalidOid, -1,
            _state->escontext,
            &numd))
    return JSON_SEM_ACTION_FAILED;
   v.val.numeric = DatumGetNumeric(numd);
   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");
  }
 }

 return JSON_SUCCESS;
}

/*
 * JsonbToCString
 *    Converts jsonb value to a C-string.
 *
 * If 'out' argument is non-null, the resulting C-string is stored inside the
 * StringBuffer.  The resulting string is always returned.
 *
 * A typical case for passing the StringInfo in rather than NULL is where the
 * caller wants access to the len attribute without having to call strlen, e.g.
 * if they are converting it to a text* object.
 */

char *
JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len)
{
 return JsonbToCStringWorker(out, in, estimated_len, false);
}

/*
 * same thing but with indentation turned on
 */

char *
JsonbToCStringIndent(StringInfo out, JsonbContainer *in, int estimated_len)
{
 return JsonbToCStringWorker(out, in, estimated_len, true);
}

/*
 * common worker for above two functions
 */

static char *
JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool indent)
{
 bool  first = true;
 JsonbIterator *it;
 JsonbValue v;
 JsonbIteratorToken type = WJB_DONE;
 int   level = 0;
 bool  redo_switch = false;

 /* If we are indenting, don't add a space after a comma */
 int   ispaces = indent ? 1 : 2;

 /*
  * Don't indent the very first item. This gets set to the indent flag at
  * the bottom of the loop.
 */

 bool  use_indent = false;
 bool  raw_scalar = false;
 bool  last_was_key = false;

 if (out == NULL)
  out = makeStringInfo();

 enlargeStringInfo(out, (estimated_len >= 0) ? estimated_len : 64);

 it = JsonbIteratorInit(in);

 while (redo_switch ||
     ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE))
 {
  redo_switch = false;
  switch (type)
  {
   case WJB_BEGIN_ARRAY:
    if (!first)
     appendBinaryStringInfo(out, ", ", ispaces);

    if (!v.val.array.rawScalar)
    {
     add_indent(out, use_indent && !last_was_key, level);
     appendStringInfoCharMacro(out, '[');
    }
    else
     raw_scalar = true;

    first = true;
    level++;
    break;
   case WJB_BEGIN_OBJECT:
    if (!first)
     appendBinaryStringInfo(out, ", ", ispaces);

    add_indent(out, use_indent && !last_was_key, level);
    appendStringInfoCharMacro(out, '{');

    first = true;
    level++;
    break;
   case WJB_KEY:
    if (!first)
     appendBinaryStringInfo(out, ", ", ispaces);
    first = true;

    add_indent(out, use_indent, level);

    /* json rules guarantee this is a string */
    jsonb_put_escaped_value(out, &v);
    appendBinaryStringInfo(out, ": "2);

    type = JsonbIteratorNext(&it, &v, false);
    if (type == WJB_VALUE)
    {
     first = false;
     jsonb_put_escaped_value(out, &v);
    }
    else
    {
     Assert(type == WJB_BEGIN_OBJECT || type == WJB_BEGIN_ARRAY);

     /*
      * We need to rerun the current switch() since we need to
      * output the object which we just got from the iterator
      * before calling the iterator again.
 */

     redo_switch = true;
    }
    break;
   case WJB_ELEM:
    if (!first)
     appendBinaryStringInfo(out, ", ", ispaces);
    first = false;

    if (!raw_scalar)
     add_indent(out, use_indent, level);
    jsonb_put_escaped_value(out, &v);
    break;
   case WJB_END_ARRAY:
    level--;
    if (!raw_scalar)
    {
     add_indent(out, use_indent, level);
     appendStringInfoCharMacro(out, ']');
    }
    first = false;
    break;
   case WJB_END_OBJECT:
    level--;
    add_indent(out, use_indent, level);
    appendStringInfoCharMacro(out, '}');
    first = false;
    break;
   default:
    elog(ERROR, "unknown jsonb iterator token type");
  }
  use_indent = indent;
  last_was_key = redo_switch;
 }

 Assert(level == 0);

 return out->data;
}

static void
add_indent(StringInfo out, bool indent, int level)
{
 if (indent)
 {
  appendStringInfoCharMacro(out, '\n');
  appendStringInfoSpaces(out, level * 4);
 }
}


/*
 * 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.
 *
 * Note: currently, we assume that result->escontext is NULL and errors
 * will be thrown.
 */

static void
datum_to_jsonb_internal(Datum val, bool is_null, JsonbInState *result,
      JsonTypeCategory 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 == JSONTYPE_ARRAY ||
     tcategory == JSONTYPE_COMPOSITE ||
     tcategory == JSONTYPE_JSON ||
     tcategory == JSONTYPE_JSONB ||
     tcategory == JSONTYPE_CAST))
 {
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     errmsg("key value must be scalar, not array, composite, or json")));
 }
 else
 {
  if (tcategory == JSONTYPE_CAST)
   val = OidFunctionCall1(outfuncoid, val);

  switch (tcategory)
  {
   case JSONTYPE_ARRAY:
    array_to_jsonb_internal(val, result);
    break;
   case JSONTYPE_COMPOSITE:
    composite_to_jsonb(val, result);
    break;
   case JSONTYPE_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 JSONTYPE_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)
     {
      Datum  numd;

      jb.type = jbvNumeric;
      numd = DirectFunctionCall3(numeric_in,
               CStringGetDatum(outputstr),
               ObjectIdGetDatum(InvalidOid),
               Int32GetDatum(-1));
      jb.val.numeric = DatumGetNumeric(numd);
      pfree(outputstr);
     }
     else
     {
      jb.type = jbvString;
      jb.val.string.len = strlen(outputstr);
      jb.val.string.val = outputstr;
     }
    }
    break;
   case JSONTYPE_DATE:
    jb.type = jbvString;
    jb.val.string.val = JsonEncodeDateTime(NULL, val,
                DATEOID, NULL);
    jb.val.string.len = strlen(jb.val.string.val);
    break;
   case JSONTYPE_TIMESTAMP:
    jb.type = jbvString;
    jb.val.string.val = JsonEncodeDateTime(NULL, val,
                TIMESTAMPOID, NULL);
    jb.val.string.len = strlen(jb.val.string.val);
    break;
   case JSONTYPE_TIMESTAMPTZ:
    jb.type = jbvString;
    jb.val.string.val = JsonEncodeDateTime(NULL, val,
                TIMESTAMPTZOID, NULL);
    jb.val.string.len = strlen(jb.val.string.val);
    break;
   case JSONTYPE_CAST:
   case JSONTYPE_JSON:
    {
     /* parse the json right into the existing result object */
     JsonLexContext lex;
     JsonSemAction sem;
     text    *json = DatumGetTextPP(val);

     makeJsonLexContext(&lex, json, true);

     memset(&sem, 0sizeof(sem));

     sem.semstate = 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_or_ereport(&lex, &sem);
     freeJsonLexContext(&lex);
    }
    break;
   case JSONTYPE_JSONB:
    {
     Jsonb    *jsonb = DatumGetJsonbP(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 = strlen(outputstr);
    (void) checkStringLen(jb.val.string.len, NULL);
    jb.val.string.val = outputstr;
    break;
  }
 }

 /* Now insert jb into result, unless we did it recursively */
 if (!is_null && !scalar_jsonb &&
  tcategory >= JSONTYPE_JSON && tcategory <= JSONTYPE_CAST)
 {
  /* 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");
  }
 }
}

/*
 * 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, const Datum *vals,
       const bool *nulls, int *valcount, JsonTypeCategory 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_internal(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);
}

/*
 * 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;
 JsonTypeCategory 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);

 json_categorize_type(element_type, true,
       &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);
}

/*
 * 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;
  JsonTypeCategory tcategory;
  Oid   outfuncoid;
  JsonbValue v;
  Form_pg_attribute att = TupleDescAttr(tupdesc, i);

  if (att->attisdropped)
   continue;

  attname = NameStr(att->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 = JSONTYPE_NULL;
   outfuncoid = InvalidOid;
  }
  else
   json_categorize_type(att->atttypid, true, &tcategory,
         &outfuncoid);

  datum_to_jsonb_internal(val, isnull, result, tcategory, outfuncoid,
        false);
 }

 result->res = pushJsonbValue(&result->parseState, WJB_END_OBJECT, NULL);
 ReleaseTupleDesc(tupdesc);
}

/*
 * Append JSON text for "val" to "result".
 *
 * This is just a thin wrapper around datum_to_jsonb.  If the same type will be
 * printed many times, avoid using this; better to do the json_categorize_type
 * lookups only once.
 */


static void
add_jsonb(Datum val, bool is_null, JsonbInState *result,
    Oid val_type, bool key_scalar)
{
 JsonTypeCategory tcategory;
 Oid   outfuncoid;

 if (val_type == InvalidOid)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     errmsg("could not determine input data type")));

 if (is_null)
 {
  tcategory = JSONTYPE_NULL;
  outfuncoid = InvalidOid;
 }
 else
  json_categorize_type(val_type, true,
        &tcategory, &outfuncoid);

 datum_to_jsonb_internal(val, is_null, result, tcategory, outfuncoid,
       key_scalar);
}


/*
 * Is the given type immutable when coming out of a JSONB context?
 *
 * At present, datetimes are all considered mutable, because they
 * depend on timezone.  XXX we should also drill down into objects and
 * arrays, but do not.
 */

bool
to_jsonb_is_immutable(Oid typoid)
{
 JsonTypeCategory tcategory;
 Oid   outfuncoid;

 json_categorize_type(typoid, true, &tcategory, &outfuncoid);

 switch (tcategory)
 {
  case JSONTYPE_NULL:
  case JSONTYPE_BOOL:
  case JSONTYPE_JSON:
  case JSONTYPE_JSONB:
   return true;

  case JSONTYPE_DATE:
  case JSONTYPE_TIMESTAMP:
  case JSONTYPE_TIMESTAMPTZ:
   return false;

  case JSONTYPE_ARRAY:
   return false;  /* TODO recurse into elements */

  case JSONTYPE_COMPOSITE:
   return false;  /* TODO recurse into fields */

  case JSONTYPE_NUMERIC:
  case JSONTYPE_CAST:
  case JSONTYPE_OTHER:
   return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE;
 }

 return false;    /* not reached */
}

/*
 * SQL function to_jsonb(anyvalue)
 */

Datum
to_jsonb(PG_FUNCTION_ARGS)
{
 Datum  val = PG_GETARG_DATUM(0);
 Oid   val_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
 JsonTypeCategory tcategory;
 Oid   outfuncoid;

 if (val_type == InvalidOid)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     errmsg("could not determine input data type")));

 json_categorize_type(val_type, true,
       &tcategory, &outfuncoid);

 PG_RETURN_DATUM(datum_to_jsonb(val, tcategory, outfuncoid));
}

/*
 * Turn a Datum into jsonb.
 *
 * tcategory and outfuncoid are from a previous call to json_categorize_type.
 */

Datum
datum_to_jsonb(Datum val, JsonTypeCategory tcategory, Oid outfuncoid)
{
 JsonbInState result;

 memset(&result, 0sizeof(JsonbInState));

 datum_to_jsonb_internal(val, false, &result, tcategory, outfuncoid,
       false);

 return JsonbPGetDatum(JsonbValueToJsonb(result.res));
}

Datum
jsonb_build_object_worker(int nargs, const Datum *args, const bool *nulls, const Oid *types,
        bool absent_on_null, bool unique_keys)
{
 int   i;
 JsonbInState result;

 if (nargs % 2 != 0)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     errmsg("argument list must have even number of elements"),
  /* translator: %s is a SQL function name */
     errhint("The arguments of %s must consist of alternating keys and values.",
       "jsonb_build_object()")));

 memset(&result, 0sizeof(JsonbInState));

 result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL);
 result.parseState->unique_keys = unique_keys;
 result.parseState->skip_nulls = absent_on_null;

 for (i = 0; i < nargs; i += 2)
 {
  /* process key */
  bool  skip;

  if (nulls[i])
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      errmsg("argument %d: key must not be null", i + 1)));

  /* skip null values if absent_on_null */
  skip = absent_on_null && nulls[i + 1];

  /* we need to save skipped keys for the key uniqueness check */
  if (skip && !unique_keys)
   continue;

  add_jsonb(args[i], false, &result, types[i], true);

  /* process value */
  add_jsonb(args[i + 1], nulls[i + 1], &result, types[i + 1], false);
 }

 result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL);

 return JsonbPGetDatum(JsonbValueToJsonb(result.res));
}

/*
 * SQL function jsonb_build_object(variadic "any")
 */

Datum
jsonb_build_object(PG_FUNCTION_ARGS)
{
 Datum    *args;
 bool    *nulls;
 Oid     *types;

 /* build argument values to build the object */
 int   nargs = extract_variadic_args(fcinfo, 0true,
             &args, &types, &nulls);

 if (nargs < 0)
  PG_RETURN_NULL();

 PG_RETURN_DATUM(jsonb_build_object_worker(nargs, args, nulls, types, falsefalse));
}

/*
 * degenerate case of jsonb_build_object where it gets 0 arguments.
 */

Datum
jsonb_build_object_noargs(PG_FUNCTION_ARGS)
{
 JsonbInState result;

 memset(&result, 0sizeof(JsonbInState));

 (void) pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL);
 result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL);

 PG_RETURN_POINTER(JsonbValueToJsonb(result.res));
}

Datum
jsonb_build_array_worker(int nargs, const Datum *args, const bool *nulls, const Oid *types,
       bool absent_on_null)
{
 int   i;
 JsonbInState result;

 memset(&result, 0sizeof(JsonbInState));

 result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_ARRAY, NULL);

 for (i = 0; i < nargs; i++)
 {
  if (absent_on_null && nulls[i])
   continue;

  add_jsonb(args[i], nulls[i], &result, types[i], false);
 }

 result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL);

 return JsonbPGetDatum(JsonbValueToJsonb(result.res));
}

/*
 * SQL function jsonb_build_array(variadic "any")
 */

Datum
jsonb_build_array(PG_FUNCTION_ARGS)
{
 Datum    *args;
 bool    *nulls;
 Oid     *types;

 /* build argument values to build the object */
 int   nargs = extract_variadic_args(fcinfo, 0true,
             &args, &types, &nulls);

 if (nargs < 0)
  PG_RETURN_NULL();

 PG_RETURN_DATUM(jsonb_build_array_worker(nargs, args, nulls, types, false));
}


/*
 * degenerate case of jsonb_build_array where it gets 0 arguments.
 */

Datum
jsonb_build_array_noargs(PG_FUNCTION_ARGS)
{
 JsonbInState result;

 memset(&result, 0sizeof(JsonbInState));

 (void) pushJsonbValue(&result.parseState, WJB_BEGIN_ARRAY, NULL);
 result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL);

 PG_RETURN_POINTER(JsonbValueToJsonb(result.res));
}


/*
 * 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, 0sizeof(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_builtin(in_array, TEXTOID, &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));
}

/*
 * 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, 0sizeof(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_builtin(key_array, TEXTOID, &key_datums, &key_nulls, &key_count);
 deconstruct_array_builtin(val_array, TEXTOID, &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));
}


/*
 * shallow clone of a parse state, suitable for use in aggregate
 * final functions that will only append to the values rather than
 * change them.
 */

static JsonbParseState *
clone_parse_state(JsonbParseState *state)
{
 JsonbParseState *result,
      *icursor,
      *ocursor;

 if (state == NULL)
  return NULL;

 result = palloc(sizeof(JsonbParseState));
 icursor = state;
 ocursor = result;
 for (;;)
 {
  ocursor->contVal = icursor->contVal;
  ocursor->size = icursor->size;
  ocursor->unique_keys = icursor->unique_keys;
  ocursor->skip_nulls = icursor->skip_nulls;
  icursor = icursor->next;
  if (icursor == NULL)
   break;
  ocursor->next = palloc(sizeof(JsonbParseState));
  ocursor = ocursor->next;
 }
 ocursor->next = NULL;

 return result;
}

static Datum
jsonb_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null)
{
 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);

  json_categorize_type(arg_type, true, &state->val_category,
        &state->val_output_func);
 }
 else
 {
  state = (JsonbAggState *) PG_GETARG_POINTER(0);
  result = state->res;
 }

 if (absent_on_null && PG_ARGISNULL(1))
  PG_RETURN_POINTER(state);

 /* turn the argument into jsonb in the normal function context */

 val = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1);

 memset(&elem, 0sizeof(JsonbInState));

 datum_to_jsonb_internal(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);
}

/*
 * jsonb_agg aggregate function
 */

Datum
jsonb_agg_transfn(PG_FUNCTION_ARGS)
{
 return jsonb_agg_transfn_worker(fcinfo, false);
}

/*
 * jsonb_agg_strict aggregate function
 */

Datum
jsonb_agg_strict_transfn(PG_FUNCTION_ARGS)
{
 return jsonb_agg_transfn_worker(fcinfo, true);
}

Datum
jsonb_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 in case the final
  * function is called more than once, so we avoid changing the argument. A
  * shallow clone is sufficient as we aren't going to change any of the
  * values, just add the final array end marker.
 */

 memset(&result, 0sizeof(JsonbInState));

 result.parseState = clone_parse_state(arg->res->parseState);

 result.res = pushJsonbValue(&result.parseState,
        WJB_END_ARRAY, NULL);

 out = JsonbValueToJsonb(result.res);

 PG_RETURN_POINTER(out);
}

static Datum
jsonb_object_agg_transfn_worker(FunctionCallInfo fcinfo,
        bool absent_on_null, bool unique_keys)
{
 MemoryContext oldcontext,
    aggcontext;
 JsonbInState elem;
 JsonbAggState *state;
 Datum  val;
 JsonbInState *result;
 bool  single_scalar;
 JsonbIterator *it;
 Jsonb    *jbkey,
      *jbval;
 JsonbValue v;
 JsonbIteratorToken type;
 bool  skip;

 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);
  result->parseState->unique_keys = unique_keys;
  result->parseState->skip_nulls = absent_on_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")));

  json_categorize_type(arg_type, true, &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")));

  json_categorize_type(arg_type, true, &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")));

 /*
  * Skip null values if absent_on_null unless key uniqueness check is
  * needed (because we must save keys in this case).
 */

 skip = absent_on_null && PG_ARGISNULL(2);

 if (skip && !unique_keys)
  PG_RETURN_POINTER(state);

 val = PG_GETARG_DATUM(1);

 memset(&elem, 0sizeof(JsonbInState));

 datum_to_jsonb_internal(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, 0sizeof(JsonbInState));

 datum_to_jsonb_internal(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);

    if (skip)
    {
     v.type = jbvNull;
     result->res = pushJsonbValue(&result->parseState,
             WJB_VALUE, &v);
     MemoryContextSwitchTo(oldcontext);
     PG_RETURN_POINTER(state);
    }

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

/*
 * jsonb_object_agg aggregate function
 */

Datum
jsonb_object_agg_transfn(PG_FUNCTION_ARGS)
{
 return jsonb_object_agg_transfn_worker(fcinfo, falsefalse);
}


/*
 * jsonb_object_agg_strict aggregate function
 */

Datum
jsonb_object_agg_strict_transfn(PG_FUNCTION_ARGS)
{
 return jsonb_object_agg_transfn_worker(fcinfo, truefalse);
}

/*
 * jsonb_object_agg_unique aggregate function
 */

Datum
jsonb_object_agg_unique_transfn(PG_FUNCTION_ARGS)
{
 return jsonb_object_agg_transfn_worker(fcinfo, falsetrue);
}

/*
 * jsonb_object_agg_unique_strict aggregate function
 */

Datum
jsonb_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS)
{
 return jsonb_object_agg_transfn_worker(fcinfo, truetrue);
}

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

 memset(&result, 0sizeof(JsonbInState));

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


/*
 * Extract scalar value from raw-scalar pseudo-array jsonb.
 */

bool
JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
{
 JsonbIterator *it;
 JsonbIteratorToken tok PG_USED_FOR_ASSERTS_ONLY;
 JsonbValue tmp;

 if (!JsonContainerIsArray(jbc) || !JsonContainerIsScalar(jbc))
 {
  /* inform caller about actual type of container */
  res->type = (JsonContainerIsArray(jbc)) ? jbvArray : jbvObject;
  return false;
 }

 /*
  * A root scalar is stored as an array of one element, so we get the array
  * and then its first (and only) member.
 */

 it = JsonbIteratorInit(jbc);

 tok = JsonbIteratorNext(&it, &tmp, true);
 Assert(tok == WJB_BEGIN_ARRAY);
 Assert(tmp.val.array.nElems == 1 && tmp.val.array.rawScalar);

 tok = JsonbIteratorNext(&it, res, true);
 Assert(tok == WJB_ELEM);
 Assert(IsAJsonbScalar(res));

 tok = JsonbIteratorNext(&it, &tmp, true);
 Assert(tok == WJB_END_ARRAY);

 tok = JsonbIteratorNext(&it, &tmp, true);
 Assert(tok == WJB_DONE);

 return true;
}

/*
 * Emit correct, translatable cast error message
 */

static void
cannotCastJsonbValue(enum jbvType type, const char *sqltype)
{
 static const struct
 {
  enum jbvType type;
  const char *msg;
 }
    messages[] =
 {
  {jbvNull, gettext_noop("cannot cast jsonb null to type %s")},
  {jbvString, gettext_noop("cannot cast jsonb string to type %s")},
  {jbvNumeric, gettext_noop("cannot cast jsonb numeric to type %s")},
  {jbvBool, gettext_noop("cannot cast jsonb boolean to type %s")},
  {jbvArray, gettext_noop("cannot cast jsonb array to type %s")},
  {jbvObject, gettext_noop("cannot cast jsonb object to type %s")},
  {jbvBinary, gettext_noop("cannot cast jsonb array or object to type %s")}
 };
 int   i;

 for (i = 0; i < lengthof(messages); i++)
  if (messages[i].type == type)
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      errmsg(messages[i].msg, sqltype)));

 /* should be unreachable */
 elog(ERROR, "unknown jsonb type: %d", (int) type);
}

Datum
jsonb_bool(PG_FUNCTION_ARGS)
{
 Jsonb    *in = PG_GETARG_JSONB_P(0);
 JsonbValue v;

 if (!JsonbExtractScalar(&in->root, &v))
  cannotCastJsonbValue(v.type, "boolean");

 if (v.type == jbvNull)
 {
  PG_FREE_IF_COPY(in, 0);
  PG_RETURN_NULL();
 }

 if (v.type != jbvBool)
  cannotCastJsonbValue(v.type, "boolean");

 PG_FREE_IF_COPY(in, 0);

 PG_RETURN_BOOL(v.val.boolean);
}

Datum
jsonb_numeric(PG_FUNCTION_ARGS)
{
 Jsonb    *in = PG_GETARG_JSONB_P(0);
 JsonbValue v;
 Numeric  retValue;

 if (!JsonbExtractScalar(&in->root, &v))
  cannotCastJsonbValue(v.type, "numeric");

 if (v.type == jbvNull)
 {
  PG_FREE_IF_COPY(in, 0);
  PG_RETURN_NULL();
 }

 if (v.type != jbvNumeric)
  cannotCastJsonbValue(v.type, "numeric");

 /*
  * v.val.numeric points into jsonb body, so we need to make a copy to
  * return
 */

 retValue = DatumGetNumericCopy(NumericGetDatum(v.val.numeric));

 PG_FREE_IF_COPY(in, 0);

 PG_RETURN_NUMERIC(retValue);
}

Datum
jsonb_int2(PG_FUNCTION_ARGS)
{
 Jsonb    *in = PG_GETARG_JSONB_P(0);
 JsonbValue v;
 Datum  retValue;

 if (!JsonbExtractScalar(&in->root, &v))
  cannotCastJsonbValue(v.type, "smallint");

 if (v.type == jbvNull)
 {
  PG_FREE_IF_COPY(in, 0);
  PG_RETURN_NULL();
 }

 if (v.type != jbvNumeric)
  cannotCastJsonbValue(v.type, "smallint");

 retValue = DirectFunctionCall1(numeric_int2,
           NumericGetDatum(v.val.numeric));

 PG_FREE_IF_COPY(in, 0);

 PG_RETURN_DATUM(retValue);
}

Datum
jsonb_int4(PG_FUNCTION_ARGS)
{
 Jsonb    *in = PG_GETARG_JSONB_P(0);
 JsonbValue v;
 Datum  retValue;

 if (!JsonbExtractScalar(&in->root, &v))
  cannotCastJsonbValue(v.type, "integer");

 if (v.type == jbvNull)
 {
  PG_FREE_IF_COPY(in, 0);
  PG_RETURN_NULL();
 }

 if (v.type != jbvNumeric)
  cannotCastJsonbValue(v.type, "integer");

 retValue = DirectFunctionCall1(numeric_int4,
           NumericGetDatum(v.val.numeric));

 PG_FREE_IF_COPY(in, 0);

 PG_RETURN_DATUM(retValue);
}

Datum
jsonb_int8(PG_FUNCTION_ARGS)
{
 Jsonb    *in = PG_GETARG_JSONB_P(0);
 JsonbValue v;
 Datum  retValue;

 if (!JsonbExtractScalar(&in->root, &v))
  cannotCastJsonbValue(v.type, "bigint");

 if (v.type == jbvNull)
 {
  PG_FREE_IF_COPY(in, 0);
  PG_RETURN_NULL();
 }

 if (v.type != jbvNumeric)
  cannotCastJsonbValue(v.type, "bigint");

 retValue = DirectFunctionCall1(numeric_int8,
           NumericGetDatum(v.val.numeric));

 PG_FREE_IF_COPY(in, 0);

 PG_RETURN_DATUM(retValue);
}

Datum
jsonb_float4(PG_FUNCTION_ARGS)
{
 Jsonb    *in = PG_GETARG_JSONB_P(0);
 JsonbValue v;
 Datum  retValue;

 if (!JsonbExtractScalar(&in->root, &v))
  cannotCastJsonbValue(v.type, "real");

 if (v.type == jbvNull)
 {
  PG_FREE_IF_COPY(in, 0);
  PG_RETURN_NULL();
 }

 if (v.type != jbvNumeric)
  cannotCastJsonbValue(v.type, "real");

 retValue = DirectFunctionCall1(numeric_float4,
           NumericGetDatum(v.val.numeric));

 PG_FREE_IF_COPY(in, 0);

 PG_RETURN_DATUM(retValue);
}

Datum
jsonb_float8(PG_FUNCTION_ARGS)
{
 Jsonb    *in = PG_GETARG_JSONB_P(0);
 JsonbValue v;
 Datum  retValue;

 if (!JsonbExtractScalar(&in->root, &v))
  cannotCastJsonbValue(v.type, "double precision");

 if (v.type == jbvNull)
 {
  PG_FREE_IF_COPY(in, 0);
  PG_RETURN_NULL();
 }

 if (v.type != jbvNumeric)
  cannotCastJsonbValue(v.type, "double precision");

 retValue = DirectFunctionCall1(numeric_float8,
           NumericGetDatum(v.val.numeric));

 PG_FREE_IF_COPY(in, 0);

 PG_RETURN_DATUM(retValue);
}

/*
 * Convert jsonb to a C-string stripping quotes from scalar strings.
 */

char *
JsonbUnquote(Jsonb *jb)
{
 if (JB_ROOT_IS_SCALAR(jb))
 {
  JsonbValue v;

  (void) JsonbExtractScalar(&jb->root, &v);

  if (v.type == jbvString)
   return pnstrdup(v.val.string.val, v.val.string.len);
  else if (v.type == jbvBool)
   return pstrdup(v.val.boolean ? "true" : "false");
  else if (v.type == jbvNumeric)
   return DatumGetCString(DirectFunctionCall1(numeric_out,
                PointerGetDatum(v.val.numeric)));
  else if (v.type == jbvNull)
   return pstrdup("null");
  else
  {
   elog(ERROR, "unrecognized jsonb value type %d", v.type);
   return NULL;
  }
 }
 else
  return JsonbToCString(NULL, &jb->root, VARSIZE(jb));
}

Messung V0.5 in Prozent
C=94 H=94 G=93

¤ Dauer der Verarbeitung: 0.26 Sekunden  (vorverarbeitet am  2026-08-08) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=141584
#Domains=752002