/* Combine existing path hash with next key hash (jsonb_path_ops) */ static bool jsonb_path_ops__add_path_item(JsonPathGinPath *path, JsonPathItem *jsp) { switch (jsp->type) { case jpiRoot: path->hash = 0; /* reset path hash */ return true; case jpiKey: { JsonbValue jbv; jbv.type = jbvString; jbv.val.string.val = jspGetString(jsp, &jbv.val.string.len); JsonbHashScalarValue(&jbv, &path->hash); return true; } case jpiIndexArray: case jpiAnyArray: return true; /* path hash is unchanged */ default: /* other items (wildcard paths, item methods) are not supported */ return false; } }
/* Append a list of nodes from the jsonpath (jsonb_path_ops). */ static List * jsonb_path_ops__extract_nodes(JsonPathGinContext *cxt, JsonPathGinPath path, JsonbValue *scalar, List *nodes) { if (scalar) { /* append path hash node for equality queries */ uint32 hash = path.hash; JsonbHashScalarValue(scalar, &hash); return lappend(nodes, make_jsp_entry_node(UInt32GetDatum(hash))); } else { /* jsonb_path_ops doesn't support EXISTS queries => nothing to append */ return nodes; } }
/* * Hash operator class jsonb hashing function */ Datum jsonb_hash(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB(0); JsonbIterator *it; JsonbValue v; JsonbIteratorToken r; uint32 hash = 0; if (JB_ROOT_COUNT(jb) == 0) PG_RETURN_INT32(0); it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { switch (r) { /* Rotation is left to JsonbHashScalarValue() */ case WJB_BEGIN_ARRAY: hash ^= JB_FARRAY; break; case WJB_BEGIN_OBJECT: hash ^= JB_FOBJECT; break; case WJB_KEY: case WJB_VALUE: case WJB_ELEM: JsonbHashScalarValue(&v, &hash); break; case WJB_END_ARRAY: case WJB_END_OBJECT: break; default: elog(ERROR, "invalid JsonbIteratorNext rc: %d", (int) r); } } PG_FREE_IF_COPY(jb, 0); PG_RETURN_INT32(hash); }
Datum gin_extract_jsonb_path(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB_P(0); int32 *nentries = (int32 *) PG_GETARG_POINTER(1); int total = 2 * JB_ROOT_COUNT(jb); JsonbIterator *it; JsonbValue v; JsonbIteratorToken r; PathHashStack tail; PathHashStack *stack; int i = 0; Datum *entries; /* If the root level is empty, we certainly have no keys */ if (total == 0) { *nentries = 0; PG_RETURN_POINTER(NULL); } /* Otherwise, use 2 * root count as initial estimate of result size */ entries = (Datum *) palloc(sizeof(Datum) * total); /* We keep a stack of partial hashes corresponding to parent key levels */ tail.parent = NULL; tail.hash = 0; stack = &tail; it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { PathHashStack *parent; /* Since we recurse into the object, we might need more space */ if (i >= total) { total *= 2; entries = (Datum *) repalloc(entries, sizeof(Datum) * total); } switch (r) { case WJB_BEGIN_ARRAY: case WJB_BEGIN_OBJECT: /* Push a stack level for this object */ parent = stack; stack = (PathHashStack *) palloc(sizeof(PathHashStack)); /* * We pass forward hashes from outer nesting levels so that * the hashes for nested values will include outer keys as * well as their own keys. * * Nesting an array within another array will not alter * innermost scalar element hash values, but that seems * inconsequential. */ stack->hash = parent->hash; stack->parent = parent; break; case WJB_KEY: /* mix this key into the current outer hash */ JsonbHashScalarValue(&v, &stack->hash); /* hash is now ready to incorporate the value */ break; case WJB_ELEM: case WJB_VALUE: /* mix the element or value's hash into the prepared hash */ JsonbHashScalarValue(&v, &stack->hash); /* and emit an index entry */ entries[i++] = UInt32GetDatum(stack->hash); /* reset hash for next key, value, or sub-object */ stack->hash = stack->parent->hash; break; case WJB_END_ARRAY: case WJB_END_OBJECT: /* Pop the stack */ parent = stack->parent; pfree(stack); stack = parent; /* reset hash for next key, value, or sub-object */ if (stack->parent) stack->hash = stack->parent->hash; else stack->hash = 0; break; default: elog(ERROR, "invalid JsonbIteratorNext rc: %d", (int) r); } } *nentries = i; PG_RETURN_POINTER(entries); }
Datum gin_extract_jsonb_hash(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB(0); int32 *nentries = (int32 *) PG_GETARG_POINTER(1); int total = 2 * JB_ROOT_COUNT(jb); JsonbIterator *it; JsonbValue v; PathHashStack tail; PathHashStack *stack; int i = 0, r; Datum *entries = NULL; if (total == 0) { *nentries = 0; PG_RETURN_POINTER(NULL); } entries = (Datum *) palloc(sizeof(Datum) * total); it = JsonbIteratorInit(VARDATA(jb)); tail.parent = NULL; tail.hash = 0; stack = &tail; while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { PathHashStack *tmp; if (i >= total) { total *= 2; entries = (Datum *) repalloc(entries, sizeof(Datum) * total); } switch (r) { case WJB_BEGIN_ARRAY: case WJB_BEGIN_OBJECT: tmp = stack; stack = (PathHashStack *) palloc(sizeof(PathHashStack)); /* * Nesting an array within another array will not alter * innermost scalar element hash values, but that seems * inconsequential */ if (tmp->parent) { /* * We pass forward hashes from previous container nesting * levels so that nested arrays with an outermost nested * object will have element hashes mixed with the outermost * key. It's also somewhat useful to have nested objects * innermost values have hashes that are a function of not * just their own key, but outer keys too. */ stack->hash = tmp->hash; } else { /* * At least nested level, initialize with stable container * type proxy value */ stack->hash = (r == WJB_BEGIN_ARRAY)? JB_FARRAY:JB_FOBJECT; } stack->parent = tmp; break; case WJB_KEY: /* Initialize hash from parent */ stack->hash = stack->parent->hash; JsonbHashScalarValue(&v, &stack->hash); break; case WJB_ELEM: /* Elements have parent hash mixed in separately */ stack->hash = stack->parent->hash; case WJB_VALUE: /* Element/value case */ JsonbHashScalarValue(&v, &stack->hash); entries[i++] = stack->hash; break; case WJB_END_ARRAY: case WJB_END_OBJECT: /* Pop the stack */ tmp = stack->parent; pfree(stack); stack = tmp; break; default: elog(ERROR, "invalid JsonbIteratorNext rc: %d", r); } } *nentries = i; PG_RETURN_POINTER(entries); }
Datum gin_extract_jsonb_path(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB(0); int32 *nentries = (int32 *) PG_GETARG_POINTER(1); int total = 2 * JB_ROOT_COUNT(jb); JsonbIterator *it; JsonbValue v; JsonbIteratorToken r; PathHashStack tail; PathHashStack *stack; int i = 0; Datum *entries; /* If the root level is empty, we certainly have no keys */ if (total == 0) { *nentries = 0; PG_RETURN_POINTER(NULL); } /* Otherwise, use 2 * root count as initial estimate of result size */ entries = (Datum *) palloc(sizeof(Datum) * total); /* We keep a stack of partial hashes corresponding to parent key levels */ tail.parent = NULL; tail.hash = 0; stack = &tail; it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { PathHashStack *parent; /* Since we recurse into the object, we might need more space */ if (i >= total) { total *= 2; entries = (Datum *) repalloc(entries, sizeof(Datum) * total); } switch (r) { case WJB_BEGIN_ARRAY: case WJB_BEGIN_OBJECT: /* Push a stack level for this object */ parent = stack; stack = (PathHashStack *) palloc(sizeof(PathHashStack)); if (parent->parent) { /* * We pass forward hashes from previous container nesting * levels so that nested arrays with an outermost nested * object will have element hashes mixed with the * outermost key. It's also somewhat useful to have * nested objects' innermost values have hashes that are a * function of not just their own key, but outer keys too. * * Nesting an array within another array will not alter * innermost scalar element hash values, but that seems * inconsequential. */ stack->hash = parent->hash; } else { /* * At the outermost level, initialize hash with container * type proxy value. Note that this makes JB_FARRAY and * JB_FOBJECT part of the on-disk representation, but they * are that in the base jsonb object storage already. */ stack->hash = (r == WJB_BEGIN_ARRAY) ? JB_FARRAY : JB_FOBJECT; } stack->parent = parent; break; case WJB_KEY: /* initialize hash from parent */ stack->hash = stack->parent->hash; /* and mix in this key */ JsonbHashScalarValue(&v, &stack->hash); /* hash is now ready to incorporate the value */ break; case WJB_ELEM: /* array elements use parent hash mixed with element's hash */ stack->hash = stack->parent->hash; /* FALL THRU */ case WJB_VALUE: /* mix the element or value's hash into the prepared hash */ JsonbHashScalarValue(&v, &stack->hash); /* and emit an index entry */ entries[i++] = UInt32GetDatum(stack->hash); /* Note: we assume we'll see KEY before another VALUE */ break; case WJB_END_ARRAY: case WJB_END_OBJECT: /* Pop the stack */ parent = stack->parent; pfree(stack); stack = parent; break; default: elog(ERROR, "invalid JsonbIteratorNext rc: %d", (int) r); } } *nentries = i; PG_RETURN_POINTER(entries); }