From 91f7fc0e4a4eb6be3a5d7a356badbb47fbea9a3e Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Wed, 14 May 2014 00:26:31 +0400 Subject: [PATCH 1/7] Vodka opclass. --- Makefile | 3 +- jsonb_gin_ops.c | 1 - jsonb_vodka_ops.c | 596 ++++++++++++++++++++++++++++++++++++++++++++++ jsquery--1.0.sql | 37 +++ jsquery.h | 2 + 5 files changed, 637 insertions(+), 2 deletions(-) create mode 100644 jsonb_vodka_ops.c diff --git a/Makefile b/Makefile index cfee7be..90fc8a1 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,8 @@ MODULE_big = jsquery OBJS = jsquery_io.o jsquery_gram.o jsquery_op.o \ - jsquery_constr.o jsquery_extract.o jsonb_gin_ops.o + jsquery_constr.o jsquery_extract.o jsonb_gin_ops.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 be93f31..82a1892 100644 --- a/jsonb_gin_ops.c +++ b/jsonb_gin_ops.c @@ -36,7 +36,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..5269d87 --- /dev/null +++ b/jsonb_vodka_ops.c @@ -0,0 +1,596 @@ +/*------------------------------------------------------------------------- + * + * 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_TRUE 0x08 +#define JSONB_VODKA_FLAG_NAN 0x08 +#define JSONB_VODKA_FLAG_NEGATIVE 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 int +get_ndigits(Numeric val) +{ + const NumericDigit *digits; + int ndigits; + + ndigits = NUMERIC_NDIGITS(val); + digits = NUMERIC_DIGITS(val); + + while (ndigits > 0 && *digits == 0) + { + ndigits--; + digits++; + } + return ndigits; +} + +static void +write_numeric_key(Pointer ptr, Numeric val) +{ + *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_NUMERIC; + if (NUMERIC_IS_NAN(val)) + { + *ptr |= JSONB_VODKA_FLAG_NAN; + } + else + { + const NumericDigit *digits = NUMERIC_DIGITS(val); + int ndigits = NUMERIC_NDIGITS(val); + int weight = NUMERIC_WEIGHT(val); + int sign = NUMERIC_SIGN(val); + + if (sign == NUMERIC_NEG) + *ptr |= JSONB_VODKA_FLAG_NEGATIVE; + ptr++; + + while (ndigits > 0 && *digits == 0) + { + ndigits--; + digits++; + } + + memcpy(ptr, &weight, sizeof(weight)); + ptr += sizeof(weight); + + memcpy(ptr, digits, sizeof(NumericDigit) * ndigits); + ptr += sizeof(NumericDigit) * ndigits; + + *ptr = 0; + } +} + +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: + vallen = 1; + break; + case jbvString: + vallen = 5; + break; + case jbvNumeric: + if (NUMERIC_IS_NAN(val->val.numeric)) + vallen = 1; + else + vallen = get_ndigits(val->val.numeric) + 6; + 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 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_STRING; + write_numeric_key(ptr, val->val.numeric); + 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; + int totallen = VARHDRSZ, vallen, jqPos, len; + Pointer ptr; + JsQueryValue *value; + Numeric numeric = NULL; + char *jqBase; + uint32 hash; + bytea *result; + + Assert(node->type == eScalar); + + if (node->bounds.inequality || node->bounds.exact->type == jqiAny) + return -1; + + item = node->path; + while (item) + { + if (item->type == iKey) + totallen += item->len + 2; + else if (item->type == iAnyArray) + totallen += 1; + else + return -1; + item = item->parent; + } + + value = node->bounds.exact; + switch(value->type) + { + case jqiNull: + case jqiBool: + vallen = 1; + break; + case jqiString: + vallen = 5; + break; + case jqiNumeric: + numeric = (Numeric)(value->jqBase + value->jqPos); + if (NUMERIC_IS_NAN(numeric)) + vallen = 1; + else + vallen = get_ndigits(numeric) + 6; + break; + default: + elog(ERROR,"Wrong state"); + } + + totallen += vallen; + result = (bytea *)palloc(totallen); + SET_VARSIZE(result, totallen); + ptr = (Pointer)result + totallen - vallen; + + item = node->path; + while (item) + { + if (item->type == iKey) + { + ptr -= item->len + 2; + ptr[0] = 0; + memcpy(ptr + 1, item->s, item->len); + ptr[item->len + 1] = 0; + } + else if (item->type == iAnyArray) + { + ptr--; + *ptr = JSONB_VODKA_FLAG_ARRAY; + } + else + { + return -1; + } + item = item->parent; + } + + ptr = (Pointer)result + totallen - vallen; + jqBase = value->jqBase; + jqPos = value->jqPos; + + switch (value->type) + { + case jbvNull: + *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_NULL; + break; + case jbvBool: + *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_BOOL; + read_byte(len, jqBase, jqPos); + if (len) + *ptr |= JSONB_VODKA_FLAG_TRUE; + break; + case jbvString: + *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_STRING; + read_int32(len, jqBase, jqPos); + hash = hash_any((unsigned char *)jqBase + jqPos, len); + memcpy(ptr + 1, &hash, sizeof(hash)); + break; + case jbvNumeric: + *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_STRING; + write_numeric_key(ptr, numeric); + break; + default: + elog(ERROR, "invalid jsonb scalar type"); + } + + return add_entry(e, PointerGetDatum(result), NULL, ByteaEqualOperator, 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: + case WJB_BEGIN_OBJECT: + 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); + + *nentries = e.count; + keys = e.entries; + for (i = 0; i < e.count; i++) + keys[i].extra = (Pointer)root; + break; + + + 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); + bool res; + int32 i; + + switch (strategy) + { + case JsonbContainsStrategyNumber: + /* result is not lossy */ + *recheck = false; + /* must have all elements in check[] true, and no nulls */ + res = true; + for (i = 0; i < nkeys; i++) + { + if (!check[i] || queryKeys[i].isnull) + { + res = false; + break; + } + } + break; + + case JsQueryMatchStrategyNumber: + if (nkeys == 0) + res = true; + else + res = execRecursive((ExtractedNode *)queryKeys[0].extra, 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); + 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: + if (nkeys == 0) + res = GIN_MAYBE; + else + res = execRecursiveTristate((ExtractedNode *)queryKeys[0].extra, check); + + if (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 50a171e..e3302fd 100644 --- a/jsquery--1.0.sql +++ b/jsquery--1.0.sql @@ -281,3 +281,40 @@ CREATE OPERATOR CLASS jsonb_hash_value_ops FUNCTION 5 gin_compare_partial_jsonb_hash_value(bytea, bytea, smallint, internal), FUNCTION 6 gin_triconsistent_jsonb_hash_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; + \ No newline at end of file diff --git a/jsquery.h b/jsquery.h index 2c016ac..bf8ef80 100644 --- a/jsquery.h +++ b/jsquery.h @@ -165,6 +165,8 @@ struct ExtractedNode typedef int (*MakeEntryHandler)(ExtractedNode *node, Pointer extra); +#define JsQueryMatchStrategyNumber 14 + ExtractedNode *extractJsQuery(JsQuery *jq, MakeEntryHandler handler, Pointer extra); bool execRecursive(ExtractedNode *node, bool *check); bool execRecursiveTristate(ExtractedNode *node, GinTernaryValue *check); From 390f7df5012797b90d853f301304fef7415bee19 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Thu, 15 May 2014 18:51:28 +0400 Subject: [PATCH 2/7] Partly working vodka opclass... --- jsonb_vodka_ops.c | 169 ++++++++++++++++++++-------------------------- 1 file changed, 75 insertions(+), 94 deletions(-) diff --git a/jsonb_vodka_ops.c b/jsonb_vodka_ops.c index 5269d87..9142512 100644 --- a/jsonb_vodka_ops.c +++ b/jsonb_vodka_ops.c @@ -37,6 +37,7 @@ typedef struct #define JSONB_VODKA_FLAG_STRING 0x02 #define JSONB_VODKA_FLAG_NUMERIC 0x04 #define JSONB_VODKA_FLAG_BOOL 0x06 +#define JSONB_VODKA_FLAG_TYPE 0x06 #define JSONB_VODKA_FLAG_TRUE 0x08 #define JSONB_VODKA_FLAG_NAN 0x08 #define JSONB_VODKA_FLAG_NEGATIVE 0x10 @@ -184,10 +185,7 @@ get_vodka_key(PathStack *stack, const JsonbValue *val) vallen = 5; break; case jbvNumeric: - if (NUMERIC_IS_NAN(val->val.numeric)) - vallen = 1; - else - vallen = get_ndigits(val->val.numeric) + 6; + vallen = 1 + VARSIZE_ANY(val->val.numeric); break; default: elog(ERROR, "invalid jsonb scalar type"); @@ -234,8 +232,8 @@ get_vodka_key(PathStack *stack, const JsonbValue *val) memcpy(ptr + 1, &hash, sizeof(hash)); break; case jbvNumeric: - *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_STRING; - write_numeric_key(ptr, val->val.numeric); + *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"); @@ -244,116 +242,103 @@ get_vodka_key(PathStack *stack, const JsonbValue *val) return result; } -static int -make_entry_handler(ExtractedNode *node, Pointer extra) +typedef struct { - Entries *e = (Entries *)extra; - PathItem *item; - int totallen = VARHDRSZ, vallen, jqPos, len; - Pointer ptr; - JsQueryValue *value; - Numeric numeric = NULL; - char *jqBase; - uint32 hash; - bytea *result; - - Assert(node->type == eScalar); - - if (node->bounds.inequality || node->bounds.exact->type == jqiAny) - return -1; - - item = node->path; - while (item) - { - if (item->type == iKey) - totallen += item->len + 2; - else if (item->type == iAnyArray) - totallen += 1; - else - return -1; - item = item->parent; - } + uint8 type; + uint32 hash; + Numeric n; +} JsonbVodkaValue; - value = node->bounds.exact; - switch(value->type) - { - case jqiNull: - case jqiBool: - vallen = 1; - break; - case jqiString: - vallen = 5; - break; - case jqiNumeric: - numeric = (Numeric)(value->jqBase + value->jqPos); - if (NUMERIC_IS_NAN(numeric)) - vallen = 1; - else - vallen = get_ndigits(numeric) + 6; - break; - default: - elog(ERROR,"Wrong state"); - } +typedef struct +{ + int pathLength; + PathItem *path; + JsonbVodkaValue *exact, *leftBound, *rightBound; + bool inequality, leftInclusive, rightInclusive; +} JsonbVodkaKey; + +static JsonbVodkaValue * +make_vodka_value(JsQueryValue *value) +{ + JsonbVodkaValue *result; + int32 len, jqPos; + char *jqBase; - totallen += vallen; - result = (bytea *)palloc(totallen); - SET_VARSIZE(result, totallen); - ptr = (Pointer)result + totallen - vallen; + if (!value) + return NULL; - item = node->path; - while (item) - { - if (item->type == iKey) - { - ptr -= item->len + 2; - ptr[0] = 0; - memcpy(ptr + 1, item->s, item->len); - ptr[item->len + 1] = 0; - } - else if (item->type == iAnyArray) - { - ptr--; - *ptr = JSONB_VODKA_FLAG_ARRAY; - } - else - { - return -1; - } - item = item->parent; - } + result = (JsonbVodkaValue *)palloc(sizeof(JsonbVodkaValue)); - ptr = (Pointer)result + totallen - vallen; jqBase = value->jqBase; jqPos = value->jqPos; switch (value->type) { case jbvNull: - *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_NULL; + result->type = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_NULL; break; case jbvBool: - *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_BOOL; + result->type = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_BOOL; read_byte(len, jqBase, jqPos); if (len) - *ptr |= JSONB_VODKA_FLAG_TRUE; + result->type |= JSONB_VODKA_FLAG_TRUE; break; case jbvString: - *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_STRING; + result->type = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_STRING; read_int32(len, jqBase, jqPos); - hash = hash_any((unsigned char *)jqBase + jqPos, len); - memcpy(ptr + 1, &hash, sizeof(hash)); + result->hash = hash_any((unsigned char *)jqBase + jqPos, len); break; case jbvNumeric: - *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_STRING; - write_numeric_key(ptr, numeric); + result->type = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_NUMERIC; + result->n = (Numeric)(jqBase + jqPos); break; default: elog(ERROR, "invalid jsonb scalar type"); } - - return add_entry(e, PointerGetDatum(result), NULL, ByteaEqualOperator, false); + return result; } +static int +make_entry_handler(ExtractedNode *node, Pointer extra) +{ + Entries *e = (Entries *)extra; + PathItem *item; + JsonbVodkaKey *key = (JsonbVodkaKey *)palloc(sizeof(JsonbVodkaKey)); + int length = 0, i; + + item = node->path; + while (item) + { + length++; + item = item->parent; + } + key->pathLength = length; + key->path = (PathItem *)palloc(sizeof(PathItem) * length); + + i = length - 1; + item = node->path; + 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 @@ -474,15 +459,11 @@ vodkaqueryjsonbextract(PG_FUNCTION_ARGS) keys[i].extra = (Pointer)root; break; - - 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; From 14ecaa2ebce52acd9d2e065a069ad59689bfc247 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Thu, 15 May 2014 21:25:36 +0400 Subject: [PATCH 3/7] Bug fixes and * support. --- jsonb_vodka_ops.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jsonb_vodka_ops.c b/jsonb_vodka_ops.c index 9142512..0ec479b 100644 --- a/jsonb_vodka_ops.c +++ b/jsonb_vodka_ops.c @@ -264,7 +264,7 @@ make_vodka_value(JsQueryValue *value) int32 len, jqPos; char *jqBase; - if (!value) + if (!value || value->type == jqiAny) return NULL; result = (JsonbVodkaValue *)palloc(sizeof(JsonbVodkaValue)); @@ -274,21 +274,21 @@ make_vodka_value(JsQueryValue *value) switch (value->type) { - case jbvNull: + case jqiNull: result->type = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_NULL; break; - case jbvBool: + case jqiBool: result->type = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_BOOL; read_byte(len, jqBase, jqPos); if (len) result->type |= JSONB_VODKA_FLAG_TRUE; break; - case jbvString: + case jqiString: result->type = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_STRING; read_int32(len, jqBase, jqPos); result->hash = hash_any((unsigned char *)jqBase + jqPos, len); break; - case jbvNumeric: + case jqiNumeric: result->type = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_NUMERIC; result->n = (Numeric)(jqBase + jqPos); break; From d87a1e138b0ac9718a1f8a86c08e9e18ccb4947b Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Mon, 19 May 2014 20:59:16 +0400 Subject: [PATCH 4/7] Fix = * for arrays and objects. --- jsonb_vodka_ops.c | 101 +++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 63 deletions(-) diff --git a/jsonb_vodka_ops.c b/jsonb_vodka_ops.c index 0ec479b..59259ed 100644 --- a/jsonb_vodka_ops.c +++ b/jsonb_vodka_ops.c @@ -33,14 +33,15 @@ typedef struct #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_TYPE 0x06 -#define JSONB_VODKA_FLAG_TRUE 0x08 -#define JSONB_VODKA_FLAG_NAN 0x08 -#define JSONB_VODKA_FLAG_NEGATIVE 0x10 +#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 @@ -100,58 +101,6 @@ typedef struct PathStack struct PathStack *parent; } PathStack; -static int -get_ndigits(Numeric val) -{ - const NumericDigit *digits; - int ndigits; - - ndigits = NUMERIC_NDIGITS(val); - digits = NUMERIC_DIGITS(val); - - while (ndigits > 0 && *digits == 0) - { - ndigits--; - digits++; - } - return ndigits; -} - -static void -write_numeric_key(Pointer ptr, Numeric val) -{ - *ptr = JSONB_VODKA_FLAG_VALUE | JSONB_VODKA_FLAG_NUMERIC; - if (NUMERIC_IS_NAN(val)) - { - *ptr |= JSONB_VODKA_FLAG_NAN; - } - else - { - const NumericDigit *digits = NUMERIC_DIGITS(val); - int ndigits = NUMERIC_NDIGITS(val); - int weight = NUMERIC_WEIGHT(val); - int sign = NUMERIC_SIGN(val); - - if (sign == NUMERIC_NEG) - *ptr |= JSONB_VODKA_FLAG_NEGATIVE; - ptr++; - - while (ndigits > 0 && *digits == 0) - { - ndigits--; - digits++; - } - - memcpy(ptr, &weight, sizeof(weight)); - ptr += sizeof(weight); - - memcpy(ptr, digits, sizeof(NumericDigit) * ndigits); - ptr += sizeof(NumericDigit) * ndigits; - - *ptr = 0; - } -} - static bytea * get_vodka_key(PathStack *stack, const JsonbValue *val) { @@ -179,6 +128,8 @@ get_vodka_key(PathStack *stack, const JsonbValue *val) { case jbvNull: case jbvBool: + case jbvObject: + case jbvArray: vallen = 1; break; case jbvString: @@ -226,6 +177,12 @@ get_vodka_key(PathStack *stack, const JsonbValue *val) 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); @@ -302,11 +259,19 @@ static int make_entry_handler(ExtractedNode *node, Pointer extra) { Entries *e = (Entries *)extra; - PathItem *item; + PathItem *item, *leaf = node->path; JsonbVodkaKey *key = (JsonbVodkaKey *)palloc(sizeof(JsonbVodkaKey)); int length = 0, i; - item = node->path; + 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++; @@ -316,7 +281,7 @@ make_entry_handler(ExtractedNode *node, Pointer extra) key->path = (PathItem *)palloc(sizeof(PathItem) * length); i = length - 1; - item = node->path; + item = leaf; while (item) { key->path[i] = *item; @@ -380,7 +345,17 @@ vodkajsonbextract(PG_FUNCTION_ARGS) 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; From 2aeacb9a5d38694ba63453e3a215226e082b6065 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Wed, 21 May 2014 17:49:26 +0400 Subject: [PATCH 5/7] Correct fallback if not positive quals for vodka opclass. --- jsonb_vodka_ops.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/jsonb_vodka_ops.c b/jsonb_vodka_ops.c index 59259ed..ef1aba1 100644 --- a/jsonb_vodka_ops.c +++ b/jsonb_vodka_ops.c @@ -427,11 +427,18 @@ vodkaqueryjsonbextract(PG_FUNCTION_ARGS) case JsQueryMatchStrategyNumber: jq = PG_GETARG_JSQUERY(0); root = extractJsQuery(jq, make_entry_handler, (Pointer)&e); - - *nentries = e.count; - keys = e.entries; - for (i = 0; i < e.count; i++) - keys[i].extra = (Pointer)root; + if (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: From 61dd5f6340bcabd9ff837c2a412b82e46949377c Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Wed, 21 May 2014 20:08:02 +0400 Subject: [PATCH 6/7] Use recheck only when it's needed. --- jsonb_vodka_ops.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/jsonb_vodka_ops.c b/jsonb_vodka_ops.c index ef1aba1..f4804bc 100644 --- a/jsonb_vodka_ops.c +++ b/jsonb_vodka_ops.c @@ -466,19 +466,18 @@ vodkajsonbconsistent(PG_FUNCTION_ARGS) 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: - /* result is not lossy */ - *recheck = false; - /* must have all elements in check[] true, and no nulls */ + *recheck = true; res = true; for (i = 0; i < nkeys; i++) { - if (!check[i] || queryKeys[i].isnull) + if (!check[i]) { res = false; break; @@ -487,10 +486,12 @@ vodkajsonbconsistent(PG_FUNCTION_ARGS) break; case JsQueryMatchStrategyNumber: + root = (ExtractedNode *)queryKeys[0].extra; + *recheck = root->indirect; if (nkeys == 0) res = true; else - res = execRecursive((ExtractedNode *)queryKeys[0].extra, check); + res = execRecursive(root, check); break; default: @@ -516,6 +517,7 @@ vodkajsonbtriconsistent(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ VodkaKey *queryKeys = (VodkaKey *) PG_GETARG_POINTER(4); + ExtractedNode *root; VodkaTernaryValue res; int32 i; @@ -539,12 +541,13 @@ vodkajsonbtriconsistent(PG_FUNCTION_ARGS) break; case JsQueryMatchStrategyNumber: + root = (ExtractedNode *)queryKeys[0].extra; if (nkeys == 0) res = GIN_MAYBE; else - res = execRecursiveTristate((ExtractedNode *)queryKeys[0].extra, check); + res = execRecursiveTristate(root, check); - if (res == GIN_TRUE) + if (root->indirect && res == GIN_TRUE) res = GIN_MAYBE; break; From eaaf5da50525908e83aec5c43117d9db7dc7328a Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Fri, 30 May 2014 22:41:32 +0400 Subject: [PATCH 7/7] Code alignment fix. --- jsonb_vodka_ops.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsonb_vodka_ops.c b/jsonb_vodka_ops.c index 437374f..5121057 100644 --- a/jsonb_vodka_ops.c +++ b/jsonb_vodka_ops.c @@ -31,7 +31,7 @@ typedef struct int count, total; } Entries; -#define JSONB_VODKA_FLAG_VALUE 0x01 +#define JSONB_VODKA_FLAG_VALUE 0x01 #define JSONB_VODKA_FLAG_NULL 0x00 #define JSONB_VODKA_FLAG_STRING 0x02 @@ -43,7 +43,7 @@ typedef struct #define JSONB_VODKA_FLAG_TRUE 0x10 #define JSONB_VODKA_FLAG_NAN 0x10 -#define JSONB_VODKA_FLAG_ARRAY 0x02 +#define JSONB_VODKA_FLAG_ARRAY 0x02 PG_FUNCTION_INFO_V1(vodkajsonbconfig); PG_FUNCTION_INFO_V1(vodkajsonbextract);