diff --git a/Makefile b/Makefile index 6f8561c..cd3985a 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,8 @@ MODULE_big = jsquery OBJS = jsonb_gin_ops.o jsquery_constr.o jsquery_extract.o \ - jsquery_gram.o jsquery_io.o jsquery_op.o jsquery_support.o + jsquery_gram.o jsquery_io.o jsquery_op.o jsquery_support.o \ + jsonb_vodka_ops.o EXTENSION = jsquery DATA = jsquery--1.0.sql diff --git a/jsonb_gin_ops.c b/jsonb_gin_ops.c index 757002f..764bdd4 100644 --- a/jsonb_gin_ops.c +++ b/jsonb_gin_ops.c @@ -50,7 +50,6 @@ typedef struct #define BLOOM_BITS 2 #define JsonbNestedContainsStrategyNumber 13 -#define JsQueryMatchStrategyNumber 14 typedef struct { diff --git a/jsonb_vodka_ops.c b/jsonb_vodka_ops.c new file mode 100644 index 0000000..9bd1b87 --- /dev/null +++ b/jsonb_vodka_ops.c @@ -0,0 +1,558 @@ +/*------------------------------------------------------------------------- + * + * vodkaarrayproc.c + * support functions for VODKA's indexing of any array + * + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/access/vodka/vodkaarrayproc.c + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/hash.h" +#include "access/vodka.h" +#include "access/skey.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_opclass.h" +#include "utils/builtins.h" +#include "utils/jsonb.h" +#include "utils/numeric.h" +#include "utils/lsyscache.h" + +#include "jsquery.h" + +typedef struct +{ + VodkaKey *entries; + int count, total; +} Entries; + +#define JSONB_VODKA_FLAG_VALUE 0x01 + +#define JSONB_VODKA_FLAG_NULL 0x00 +#define JSONB_VODKA_FLAG_STRING 0x02 +#define JSONB_VODKA_FLAG_NUMERIC 0x04 +#define JSONB_VODKA_FLAG_BOOL 0x06 +#define JSONB_VODKA_FLAG_EMPTY_ARRAY 0x08 +#define JSONB_VODKA_FLAG_EMPTY_OBJECT 0x0A +#define JSONB_VODKA_FLAG_TYPE 0x0E +#define JSONB_VODKA_FLAG_TRUE 0x10 +#define JSONB_VODKA_FLAG_NAN 0x10 + +#define JSONB_VODKA_FLAG_ARRAY 0x02 + +PG_FUNCTION_INFO_V1(vodkajsonbconfig); +PG_FUNCTION_INFO_V1(vodkajsonbextract); +PG_FUNCTION_INFO_V1(vodkaqueryjsonbextract); +PG_FUNCTION_INFO_V1(vodkajsonbconsistent); +PG_FUNCTION_INFO_V1(vodkajsonbtriconsistent); + +Datum vodkajsonbconfig(PG_FUNCTION_ARGS); +Datum vodkajsonbextract(PG_FUNCTION_ARGS); +Datum vodkaqueryjsonbextract(PG_FUNCTION_ARGS); +Datum vodkajsonbconsistent(PG_FUNCTION_ARGS); +Datum vodkajsonbtriconsistent(PG_FUNCTION_ARGS); + +static int +add_entry(Entries *e, Datum value, Pointer extra, Oid operator, bool isnull) +{ + VodkaKey *key; + int entryNum; + if (!e->entries) + { + e->total = 16; + e->entries = (VodkaKey *)palloc(e->total * sizeof(VodkaKey)); + } + if (e->count + 1 > e->total) + { + e->total *= 2; + e->entries = (VodkaKey *)repalloc(e->entries, e->total * sizeof(VodkaKey)); + } + entryNum = e->count; + e->count++; + key = &e->entries[entryNum]; + key->value = value; + key->extra = extra; + key->operator = operator; + key->isnull = isnull; + return entryNum; +} + + +Datum +vodkajsonbconfig(PG_FUNCTION_ARGS) +{ + /* VodkaConfigIn *in = (VodkaConfigIn *)PG_GETARG_POINTER(0); */ + VodkaConfigOut *out = (VodkaConfigOut *)PG_GETARG_POINTER(1); + + out->entryOpclass = BYTEA_SPGIST_OPS_OID; + out->entryEqualOperator = ByteaEqualOperator; + PG_RETURN_VOID(); +} + +typedef struct PathStack +{ + char *s; + int len; + struct PathStack *parent; +} PathStack; + +static bytea * +get_vodka_key(PathStack *stack, const JsonbValue *val) +{ + bytea *result; + int totallen = VARHDRSZ, vallen; + PathStack *tmp; + Pointer ptr; + uint32 hash; + + tmp = stack; + while (tmp) + { + if (tmp->s) + { + totallen += tmp->len + 2; + } + else + { + totallen++; + } + tmp = tmp->parent; + } + + switch (val->type) + { + case jbvNull: + case jbvBool: + case jbvObject: + case jbvArray: + vallen = 1; + break; + case jbvString: + vallen = 5; + break; + case jbvNumeric: + vallen = 1 + VARSIZE_ANY(val->val.numeric); + break; + default: + elog(ERROR, "invalid jsonb scalar type"); + } + + totallen += vallen; + result = (bytea *)palloc(totallen); + SET_VARSIZE(result, totallen); + ptr = (Pointer)result + totallen - vallen; + + tmp = stack; + while (tmp) + { + if (tmp->s) + { + ptr -= tmp->len + 2; + ptr[0] = 0; + memcpy(ptr + 1, tmp->s, tmp->len); + ptr[tmp->len + 1] = 0; + } + else + { + ptr--; + *ptr = JSONB_VODKA_FLAG_ARRAY; + } + tmp = tmp->parent; + } + + ptr = (Pointer)result + totallen - vallen; + + switch (val->type) + { + case jbvNull: + *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_NULL; + break; + case jbvBool: + *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_BOOL; + if (val->val.boolean) + *ptr |= JSONB_VODKA_FLAG_TRUE; + break; + case jbvArray: + *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_EMPTY_ARRAY; + break; + case jbvObject: + *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_EMPTY_OBJECT; + break; + case jbvString: + *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_STRING; + hash = hash_any((unsigned char *)val->val.string.val, val->val.string.len); + memcpy(ptr + 1, &hash, sizeof(hash)); + break; + case jbvNumeric: + *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_NUMERIC; + memcpy(ptr + 1, val->val.numeric, VARSIZE_ANY(val->val.numeric)); + break; + default: + elog(ERROR, "invalid jsonb scalar type"); + } + + return result; +} + +typedef struct +{ + uint8 type; + uint32 hash; + Numeric n; +} JsonbVodkaValue; + +typedef struct +{ + int pathLength; + PathItem *path; + JsonbVodkaValue *exact, *leftBound, *rightBound; + bool inequality, leftInclusive, rightInclusive; +} JsonbVodkaKey; + +static JsonbVodkaValue * +make_vodka_value(JsQueryItem *value) +{ + JsonbVodkaValue *result; + int32 len; + char *s; + + if (!value || value->type == jqiAny) + return NULL; + + result = (JsonbVodkaValue *)palloc(sizeof(JsonbVodkaValue)); + switch (value->type) + { + case jqiNull: + result->type = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_NULL; + break; + case jqiBool: + result->type = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_BOOL; + if (jsqGetBool(value)) + result->type |= JSONB_VODKA_FLAG_TRUE; + break; + case jqiString: + result->type = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_STRING; + s = jsqGetString(value, &len); + result->hash = hash_any((unsigned char *)s, len); + break; + case jqiNumeric: + result->type = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_NUMERIC; + result->n = jsqGetNumeric(value); + break; + default: + elog(ERROR, "invalid jsonb scalar type"); + } + return result; +} + +static int +make_entry_handler(ExtractedNode *node, Pointer extra) +{ + Entries *e = (Entries *)extra; + PathItem *item, *leaf = node->path; + JsonbVodkaKey *key = (JsonbVodkaKey *)palloc(sizeof(JsonbVodkaKey)); + int length = 0, i; + + if (!node->bounds.inequality && node->bounds.exact && node->bounds.exact->type == jqiAny) + { + item = (PathItem *)palloc(sizeof(PathItem)); + item->type = iAny; + item->parent = leaf; + leaf = item; + } + + item = leaf; + while (item) + { + length++; + item = item->parent; + } + key->pathLength = length; + key->path = (PathItem *)palloc(sizeof(PathItem) * length); + + i = length - 1; + item = leaf; + while (item) + { + key->path[i] = *item; + i--; + item = item->parent; + } + + key->inequality = node->bounds.inequality; + key->leftInclusive = node->bounds.leftInclusive; + key->rightInclusive = node->bounds.rightInclusive; + if (key->inequality) + { + key->leftBound = make_vodka_value(node->bounds.leftBound); + key->rightBound = make_vodka_value(node->bounds.rightBound); + } + else + { + key->exact = make_vodka_value(node->bounds.exact); + } + + return add_entry(e, PointerGetDatum(key), NULL, VodkaMatchOperator, false); +} + +/* + * extractValue support function + */ +Datum +vodkajsonbextract(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; + PathStack *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(&jb->root); + stack = NULL; + + while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + { + PathStack *tmp; + + if (i >= total) + { + total *= 2; + entries = (Datum *) repalloc(entries, sizeof(Datum) * total); + } + + switch (r) + { + case WJB_BEGIN_ARRAY: + if (v.val.array.nElems == 0) + entries[i++] = PointerGetDatum(get_vodka_key(stack, &v)); + tmp = stack; + stack = (PathStack *) palloc(sizeof(PathStack)); + stack->s = NULL; + stack->len = 0; + stack->parent = tmp; + break; + case WJB_BEGIN_OBJECT: + if (v.val.object.nPairs == 0) + entries[i++] = PointerGetDatum(get_vodka_key(stack, &v)); + tmp = stack; + stack = (PathStack *) palloc(sizeof(PathStack)); + stack->s = NULL; + stack->len = 0; + stack->parent = tmp; + break; + case WJB_KEY: + /* Initialize hash from parent */ + stack->s = v.val.string.val; + stack->len = v.val.string.len; + break; + case WJB_ELEM: + case WJB_VALUE: + entries[i++] = PointerGetDatum(get_vodka_key(stack, &v)); + 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); +} + +/* + * extractQuery support function + */ +Datum +vodkaqueryjsonbextract(PG_FUNCTION_ARGS) +{ + int32 *nentries = (int32 *) PG_GETARG_POINTER(1); + StrategyNumber strategy = PG_GETARG_UINT16(2); + int32 *searchMode = (int32 *) PG_GETARG_POINTER(3); + Datum *entries; + VodkaKey *keys; + Entries e = {0}; + int i; + JsQuery *jq; + ExtractedNode *root; + + switch (strategy) + { + case JsonbContainsStrategyNumber: + /* Query is a jsonb, so just apply gin_extract_jsonb... */ + entries = (Datum *) + DatumGetPointer(DirectFunctionCall2(vodkajsonbextract, + PG_GETARG_DATUM(0), + PointerGetDatum(nentries))); + + keys = (VodkaKey *)palloc(sizeof(VodkaKey) * (*nentries)); + + for (i = 0; i < *nentries; i++) + { + keys[i].value = entries[i]; + keys[i].isnull = false; + keys[i].extra = NULL; + keys[i].operator = ByteaEqualOperator; + } + break; + + case JsQueryMatchStrategyNumber: + jq = PG_GETARG_JSQUERY(0); + root = extractJsQuery(jq, make_entry_handler, (Pointer)&e); + if (root) + { + root->indirect = queryNeedRecheck(root); + *nentries = e.count; + keys = e.entries; + for (i = 0; i < e.count; i++) + keys[i].extra = (Pointer)root; + } + else + { + *nentries = 0; + keys = NULL; + } + break; + + default: + elog(ERROR, "unrecognized strategy number: %d", strategy); + break; + } + + /* ...although "contains {}" requires a full index scan */ + if (*nentries == 0) + *searchMode = VODKA_SEARCH_MODE_ALL; + + PG_RETURN_POINTER(keys); +} + +/* + * consistent support function + */ +Datum +vodkajsonbconsistent(PG_FUNCTION_ARGS) +{ + bool *check = (bool *) PG_GETARG_POINTER(0); + StrategyNumber strategy = PG_GETARG_UINT16(1); + + /* ArrayType *query = PG_GETARG_ARRAYTYPE_P(2); */ + int32 nkeys = PG_GETARG_INT32(3); + bool *recheck = (bool *) PG_GETARG_POINTER(4); + VodkaKey *queryKeys = (VodkaKey *) PG_GETARG_POINTER(5); + ExtractedNode *root; + bool res; + int32 i; + + switch (strategy) + { + case JsonbContainsStrategyNumber: + *recheck = true; + res = true; + for (i = 0; i < nkeys; i++) + { + if (!check[i]) + { + res = false; + break; + } + } + break; + + case JsQueryMatchStrategyNumber: + root = (ExtractedNode *)queryKeys[0].extra; + *recheck = root->indirect; + if (nkeys == 0) + res = true; + else + res = execRecursive(root, check); + break; + + default: + elog(ERROR, "vodkajsonbconsistent: unknown strategy number: %d", + strategy); + res = false; + } + + PG_RETURN_BOOL(res); +} + +/* + * triconsistent support function + */ +Datum +vodkajsonbtriconsistent(PG_FUNCTION_ARGS) +{ + VodkaTernaryValue *check = (VodkaTernaryValue *) PG_GETARG_POINTER(0); + StrategyNumber strategy = PG_GETARG_UINT16(1); + + /* ArrayType *query = PG_GETARG_ARRAYTYPE_P(2); */ + int32 nkeys = PG_GETARG_INT32(3); + + /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ + VodkaKey *queryKeys = (VodkaKey *) PG_GETARG_POINTER(4); + ExtractedNode *root; + VodkaTernaryValue res; + int32 i; + + switch (strategy) + { + case JsonbContainsStrategyNumber: + /* must have all elements in check[] true, and no nulls */ + res = VODKA_TRUE; + for (i = 0; i < nkeys; i++) + { + if (check[i] == VODKA_FALSE || queryKeys[i].isnull) + { + res = VODKA_FALSE; + break; + } + if (check[i] == VODKA_MAYBE) + { + res = VODKA_MAYBE; + } + } + break; + + case JsQueryMatchStrategyNumber: + root = (ExtractedNode *)queryKeys[0].extra; + if (nkeys == 0) + res = GIN_MAYBE; + else + res = execRecursiveTristate(root, check); + + if (root->indirect && res == GIN_TRUE) + res = GIN_MAYBE; + + break; + + default: + elog(ERROR, "vodkajsonbconsistent: unknown strategy number: %d", + strategy); + res = false; + } + + PG_RETURN_VODKA_TERNARY_VALUE(res); +} diff --git a/jsquery--1.0.sql b/jsquery--1.0.sql index 8bae88e..1c5215f 100644 --- a/jsquery--1.0.sql +++ b/jsquery--1.0.sql @@ -281,3 +281,40 @@ CREATE OPERATOR CLASS jsonb_path_value_ops FUNCTION 5 gin_compare_partial_jsonb_path_value(bytea, bytea, smallint, internal), FUNCTION 6 gin_triconsistent_jsonb_path_value(internal, smallint, anyarray, integer, internal, internal, internal), STORAGE bytea; + +CREATE OR REPLACE FUNCTION vodkajsonbconfig(internal, internal) + RETURNS void + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT IMMUTABLE; + +CREATE OR REPLACE FUNCTION vodkajsonbextract(anyarray, internal, internal) + RETURNS internal + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT IMMUTABLE; + +CREATE OR REPLACE FUNCTION vodkaqueryjsonbextract(anyarray, internal, smallint, internal) + RETURNS internal + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT IMMUTABLE; + +CREATE OR REPLACE FUNCTION vodkajsonbconsistent(internal, smallint, anyarray, integer, internal, internal, internal) + RETURNS boolean + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT IMMUTABLE; + +CREATE OR REPLACE FUNCTION vodkajsonbtriconsistent(internal, smallint, anyarray, integer, internal, internal) + RETURNS boolean + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS jsonb_ops DEFAULT FOR TYPE jsonb USING vodka AS + OPERATOR 7 @>, + OPERATOR 14 @@ (jsonb, jsquery), + FUNCTION 1 vodkajsonbconfig(internal, internal), + FUNCTION 2 byteacmp(bytea, bytea), + FUNCTION 3 vodkajsonbextract(anyarray, internal, internal), + FUNCTION 4 vodkaqueryjsonbextract(anyarray, internal, smallint, internal), + FUNCTION 5 vodkajsonbconsistent(internal, smallint, anyarray, integer, internal, internal, internal), + FUNCTION 6 vodkajsonbtriconsistent(internal, smallint, anyarray, integer, internal, internal), + STORAGE bytea; + diff --git a/jsquery.h b/jsquery.h index e6b62e0..c659b07 100644 --- a/jsquery.h +++ b/jsquery.h @@ -194,6 +194,8 @@ struct ExtractedNode typedef int (*MakeEntryHandler)(ExtractedNode *node, Pointer extra); +#define JsQueryMatchStrategyNumber 14 + ExtractedNode *extractJsQuery(JsQuery *jq, MakeEntryHandler handler, Pointer extra); bool queryNeedRecheck(ExtractedNode *node); bool execRecursive(ExtractedNode *node, bool *check); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy