Kouhei Sutou
null+****@clear*****
Sat Sep 26 20:42:48 JST 2015
Kouhei Sutou 2015-09-26 20:42:48 +0900 (Sat, 26 Sep 2015) New Revision: cad23a5db5bcd410de3a10be43ae9690e5278250 https://github.com/pgroonga/pgroonga/commit/cad23a5db5bcd410de3a10be43ae9690e5278250 Message: json: support GIN compatible "@>" operator Added files: expected/jsonb/contain/array.out sql/jsonb/contain/array.sql Modified files: pgroonga.c pgroonga.h pgroonga.sql Added: expected/jsonb/contain/array.out (+25 -0) 100644 =================================================================== --- /dev/null +++ expected/jsonb/contain/array.out 2015-09-26 20:42:48 +0900 (0da477e) @@ -0,0 +1,25 @@ +CREATE TABLE logs ( + id int, + record jsonb +); +INSERT INTO logs + VALUES (1, '{"body": {"values": [100, "Hello", true]}}'); +INSERT INTO logs + VALUES (2, '{"values": [100, "Hello", true]}'); +INSERT INTO logs + VALUES (3, '{"body": {"values": [100, "Hello", true, "World"]}}'); +CREATE INDEX pgroonga_index ON logs USING pgroonga (record); +SET enable_seqscan = off; +SET enable_indexscan = on; +SET enable_bitmapscan = off; +SELECT id, record + FROM logs + WHERE record @> '{"body": {"values": ["Hello", true, 100]}}'::jsonb + ORDER BY id; + id | record +----+----------------------------------------------------- + 1 | {"body": {"values": [100, "Hello", true]}} + 3 | {"body": {"values": [100, "Hello", true, "World"]}} +(2 rows) + +DROP TABLE logs; Modified: pgroonga.c (+198 -15) =================================================================== --- pgroonga.c 2015-09-26 19:34:21 +0900 (8d5738a) +++ pgroonga.c 2015-09-26 20:42:48 +0900 (088b71c) @@ -2652,6 +2652,203 @@ PGrnSearchBuildConditionLike(PGrnSearchData *data, grn_expr_append_op(ctx, expression, GRN_OP_MATCH, 2); } +#ifdef JSONBOID +static void +PGrnSearchBuildConditionJSONQuery(PGrnSearchData *data, + grn_obj *subFilter, + grn_obj *targetColumn, + grn_obj *filter, + unsigned int *nthCondition) +{ + grn_expr_append_obj(ctx, data->expression, + subFilter, GRN_OP_PUSH, 1); + grn_expr_append_obj(ctx, data->expression, + targetColumn, GRN_OP_PUSH, 1); + grn_expr_append_const(ctx, data->expression, + filter, GRN_OP_PUSH, 1); + grn_expr_append_op(ctx, data->expression, GRN_OP_CALL, 2); + + if (*nthCondition > 0) + grn_expr_append_op(ctx, data->expression, GRN_OP_AND, 2); + + (*nthCondition)++; +} + +static void +PGrnSearchBuildConditionJSONContainValue(PGrnSearchData *data, + grn_obj *subFilter, + grn_obj *targetColumn, + grn_obj *components, + JsonbValue *value, + unsigned int *nthCondition) +{ + unsigned int i, n; + + GRN_BULK_REWIND(&buffer); + + switch (value->type) + { + case jbvNull: + GRN_TEXT_PUTS(ctx, &buffer, "type == \"null\""); + break; + case jbvString: + if (value->val.string.len == 0) { + GRN_TEXT_PUTS(ctx, &buffer, "type == \"string\" && "); + } + GRN_TEXT_PUTS(ctx, &buffer, "string == \""); + /* TODO: escape double quote and backslash. */ + GRN_TEXT_PUT(ctx, &buffer, + value->val.string.val, + value->val.string.len); + GRN_TEXT_PUTS(ctx, &buffer, "\""); + break; + case jbvNumeric: + { + Datum numericInString = + DirectFunctionCall1(numeric_out, + NumericGetDatum(value->val.numeric)); + const char *numericInCString = DatumGetCString(numericInString); + + if (strcmp(numericInCString, "0") == 0) { + GRN_TEXT_PUTS(ctx, &buffer, "type == \"number\" && "); + } + GRN_TEXT_PUTS(ctx, &buffer, "number == "); + GRN_TEXT_PUTS(ctx, &buffer, numericInCString); + break; + } + case jbvBool: + GRN_TEXT_PUTS(ctx, &buffer, "type == \"boolean\" && "); + GRN_TEXT_PUTS(ctx, &buffer, "boolean == "); + if (value->val.boolean) + GRN_TEXT_PUTS(ctx, &buffer, "true"); + else + GRN_TEXT_PUTS(ctx, &buffer, "false"); + break; + default: + return; + break; + } + + GRN_TEXT_PUTS(ctx, &buffer, "&& paths @ \"."); + n = grn_vector_size(ctx, components); + for (i = 0; i < n; i++) + { + const char *component; + unsigned int componentSize; + + componentSize = grn_vector_get_element(ctx, + components, + i, + &component, + NULL, + NULL); + if (i > 0) + GRN_TEXT_PUTS(ctx, &buffer, "."); + /* TODO: escape double quote and backslash. */ + /* TODO: use .["..."]["..."] form. */ + GRN_TEXT_PUT(ctx, &buffer, component, componentSize); + } + GRN_TEXT_PUTS(ctx, &buffer, "\""); + + PGrnSearchBuildConditionJSONQuery(data, subFilter, targetColumn, + &buffer, nthCondition); +} + +static void +PGrnSearchBuildConditionJSONContain(PGrnSearchData *data, + grn_obj *subFilter, + grn_obj *targetColumn, + Jsonb *jsonb) +{ + unsigned int nthCondition = 0; + grn_obj components; + JsonbIterator *iter; + JsonbIteratorToken token; + JsonbValue value; + + GRN_TEXT_INIT(&components, GRN_OBJ_VECTOR); + iter = JsonbIteratorInit(&(jsonb->root)); + while ((token = JsonbIteratorNext(&iter, &value, false)) != WJB_DONE) { + switch (token) + { + case WJB_KEY: + grn_vector_add_element(ctx, &components, + value.val.string.val, + value.val.string.len, + 0, + GRN_DB_SHORT_TEXT); + break; + case WJB_VALUE: + { + const char *component; + PGrnSearchBuildConditionJSONContainValue(data, + subFilter, + targetColumn, + &components, + &value, + &nthCondition); + grn_vector_pop_element(ctx, &components, &component, NULL, NULL); + break; + } + case WJB_ELEM: + PGrnSearchBuildConditionJSONContainValue(data, + subFilter, + targetColumn, + &components, + &value, + &nthCondition); + break; + case WJB_BEGIN_ARRAY: + break; + case WJB_END_ARRAY: + break; + case WJB_BEGIN_OBJECT: + break; + case WJB_END_OBJECT: + break; + default: + ereport(ERROR, + (errcode(ERRCODE_SYSTEM_ERROR), + errmsg("pgroonga: jsonb iterator returns invalid token: %d", + token))); + break; + } + } + GRN_OBJ_FIN(ctx, &components); +} + +static bool +PGrnSearchBuildConditionJSON(PGrnSearchData *data, + ScanKey key, + grn_obj *targetColumn) +{ + grn_obj *subFilter; + + subFilter = PGrnLookup("sub_filter", ERROR); + grn_obj_reinit(ctx, &buffer, GRN_DB_TEXT, 0); + + if (key->sk_strategy == PGrnQueryStrategyNumber) + { + unsigned int nthCondition = 0; + PGrnConvertDatum(key->sk_argument, TEXTOID, &buffer); + PGrnSearchBuildConditionJSONQuery(data, + subFilter, + targetColumn, + &buffer, + &nthCondition); + } + else + { + PGrnSearchBuildConditionJSONContain(data, + subFilter, + targetColumn, + DatumGetJsonb(key->sk_argument)); + } + + return true; +} +#endif + static bool PGrnSearchBuildCondition(IndexScanDesc scan, PGrnScanOpaque so, @@ -2680,21 +2877,7 @@ PGrnSearchBuildCondition(IndexScanDesc scan, #ifdef JSONBOID if (attribute->atttypid == JSONBOID) - { - grn_obj *subFilter; - - grn_obj_reinit(ctx, &buffer, GRN_DB_TEXT, 0); - PGrnConvertDatum(key->sk_argument, TEXTOID, &buffer); - subFilter = PGrnLookup("sub_filter", ERROR); - grn_expr_append_obj(ctx, data->expression, - subFilter, GRN_OP_PUSH, 1); - grn_expr_append_obj(ctx, data->expression, - targetColumn, GRN_OP_PUSH, 1); - grn_expr_append_const(ctx, data->expression, - &buffer, GRN_OP_PUSH, 1); - grn_expr_append_op(ctx, data->expression, GRN_OP_CALL, 2); - return true; - } + return PGrnSearchBuildConditionJSON(data, key, targetColumn); #endif GRN_EXPR_CREATE_FOR_QUERY(ctx, so->sourcesTable, Modified: pgroonga.h (+1 -0) =================================================================== --- pgroonga.h 2015-09-26 19:34:21 +0900 (b715273) +++ pgroonga.h 2015-09-26 20:42:48 +0900 (6e759c0) @@ -21,6 +21,7 @@ #define PGrnLikeStrategyNumber 6 /* operator ~~ (LIKE) */ #define PGrnContainStrategyNumber 7 /* operator %% (@ in Groonga) */ #define PGrnQueryStrategyNumber 8 /* operator @@ (Groonga query) */ +#define PGrnJSONContainStrategyNumber 9 /* operator @> */ /* file and table names */ #define PGrnLogBasename "pgroonga.log" Modified: pgroonga.sql (+3 -2) =================================================================== --- pgroonga.sql 2015-09-26 19:34:21 +0900 (601af79) +++ pgroonga.sql 2015-09-26 20:42:48 +0900 (189d3f1) @@ -175,7 +175,7 @@ CREATE FUNCTION pgroonga.options(internal) DELETE FROM pg_catalog.pg_am WHERE amname = 'pgroonga'; INSERT INTO pg_catalog.pg_am VALUES( 'pgroonga', -- amname - 8, -- amstrategies + 9, -- amstrategies 0, -- amsupport true, -- amcanorder true, -- amcanorderbyop @@ -325,7 +325,8 @@ BEGIN CREATE OPERATOR CLASS pgroonga.jsonb_ops DEFAULT FOR TYPE jsonb USING pgroonga AS - OPERATOR 8 @@ (jsonb, text); + OPERATOR 8 @@ (jsonb, text), + OPERATOR 9 @>; END IF; END; $$; Added: sql/jsonb/contain/array.sql (+24 -0) 100644 =================================================================== --- /dev/null +++ sql/jsonb/contain/array.sql 2015-09-26 20:42:48 +0900 (c92ed86) @@ -0,0 +1,24 @@ +CREATE TABLE logs ( + id int, + record jsonb +); + +INSERT INTO logs + VALUES (1, '{"body": {"values": [100, "Hello", true]}}'); +INSERT INTO logs + VALUES (2, '{"values": [100, "Hello", true]}'); +INSERT INTO logs + VALUES (3, '{"body": {"values": [100, "Hello", true, "World"]}}'); + +CREATE INDEX pgroonga_index ON logs USING pgroonga (record); + +SET enable_seqscan = off; +SET enable_indexscan = on; +SET enable_bitmapscan = off; + +SELECT id, record + FROM logs + WHERE record @> '{"body": {"values": ["Hello", true, 100]}}'::jsonb + ORDER BY id; + +DROP TABLE logs; -------------- next part -------------- HTML����������������������������... Download