From 8b9f9562f10bfd7028fcbf8fe1204c2ffd0a2cc3 Mon Sep 17 00:00:00 2001 From: Muhammad Taha Naveed Date: Fri, 8 Sep 2023 12:16:14 +0500 Subject: [PATCH] Add exist(?, ?|, ?&) operators for agtype - Added exist(?, ?|, ?&) operators for agtype similar to jsonb operators in postgresql. - Allow exist(?, ?|, ?&) operators to be used inside cypher queries. - Added regression tests. --- age--1.4.0.sql | 55 +- regress/expected/agtype.out | 72 -- regress/expected/jsonb_operators.out | 1083 ++++++++++++++++++++++++++ regress/sql/agtype.sql | 19 - regress/sql/jsonb_operators.sql | 248 ++++++ src/backend/parser/ag_scanner.l | 36 +- src/backend/parser/cypher_gram.y | 14 + src/backend/parser/cypher_parser.c | 4 + src/backend/utils/adt/agtype.c | 61 +- src/backend/utils/adt/agtype_gin.c | 43 +- src/backend/utils/adt/agtype_ops.c | 159 ++++ src/include/parser/ag_scanner.h | 2 + src/include/utils/agtype.h | 4 + 13 files changed, 1676 insertions(+), 124 deletions(-) diff --git a/age--1.4.0.sql b/age--1.4.0.sql index 213b397fd..1d5116637 100644 --- a/age--1.4.0.sql +++ b/age--1.4.0.sql @@ -2981,6 +2981,23 @@ CREATE OPERATOR ? ( JOIN = contjoinsel ); +CREATE FUNCTION ag_catalog.agtype_exists_agtype(agtype, agtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ? ( + LEFTARG = agtype, + RIGHTARG = agtype, + FUNCTION = ag_catalog.agtype_exists_agtype, + COMMUTATOR = '?', + RESTRICT = contsel, + JOIN = contjoinsel +); + CREATE FUNCTION ag_catalog.agtype_exists_any(agtype, text[]) RETURNS boolean LANGUAGE c @@ -2997,6 +3014,22 @@ CREATE OPERATOR ?| ( JOIN = contjoinsel ); +CREATE FUNCTION ag_catalog.agtype_exists_any_agtype(agtype, agtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ?| ( + LEFTARG = agtype, + RIGHTARG = agtype, + FUNCTION = ag_catalog.agtype_exists_any_agtype, + RESTRICT = contsel, + JOIN = contjoinsel +); + CREATE FUNCTION ag_catalog.agtype_exists_all(agtype, text[]) RETURNS boolean LANGUAGE c @@ -3013,6 +3046,22 @@ CREATE OPERATOR ?& ( JOIN = contjoinsel ); +CREATE FUNCTION ag_catalog.agtype_exists_all_agtype(agtype, agtype) +RETURNS boolean +LANGUAGE c +IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR ?& ( + LEFTARG = agtype, + RIGHTARG = agtype, + FUNCTION = ag_catalog.agtype_exists_all_agtype, + RESTRICT = contsel, + JOIN = contjoinsel +); + -- -- agtype GIN support -- @@ -3062,9 +3111,9 @@ PARALLEL SAFE; CREATE OPERATOR CLASS ag_catalog.gin_agtype_ops DEFAULT FOR TYPE agtype USING gin AS OPERATOR 7 @>, - OPERATOR 9 ?(agtype, text), - OPERATOR 10 ?|(agtype, text[]), - OPERATOR 11 ?&(agtype, text[]), + OPERATOR 9 ?(agtype, agtype), + OPERATOR 10 ?|(agtype, agtype), + OPERATOR 11 ?&(agtype, agtype), FUNCTION 1 ag_catalog.gin_compare_agtype(text,text), FUNCTION 2 ag_catalog.gin_extract_agtype(agtype, internal), FUNCTION 3 ag_catalog.gin_extract_agtype_query(agtype, internal, int2, diff --git a/regress/expected/agtype.out b/regress/expected/agtype.out index cf451b433..c418b69de 100644 --- a/regress/expected/agtype.out +++ b/regress/expected/agtype.out @@ -2762,78 +2762,6 @@ SELECT '{"id": 1}'::agtype @> '{"id": 2}'; f (1 row) -SELECT agtype_exists('{"id": 1}','id'); - agtype_exists ---------------- - t -(1 row) - -SELECT agtype_exists('{"id": 1}','not_id'); - agtype_exists ---------------- - f -(1 row) - -SELECT '{"id": 1}'::agtype ? 'id'; - ?column? ----------- - t -(1 row) - -SELECT '{"id": 1}'::agtype ? 'not_id'; - ?column? ----------- - f -(1 row) - -SELECT agtype_exists_any('{"id": 1}', array['id']); - agtype_exists_any -------------------- - t -(1 row) - -SELECT agtype_exists_any('{"id": 1}', array['not_id']); - agtype_exists_any -------------------- - f -(1 row) - -SELECT '{"id": 1}'::agtype ?| array['id']; - ?column? ----------- - t -(1 row) - -SELECT '{"id": 1}'::agtype ?| array['not_id']; - ?column? ----------- - f -(1 row) - -SELECT agtype_exists_all('{"id": 1}', array['id']); - agtype_exists_all -------------------- - t -(1 row) - -SELECT agtype_exists_all('{"id": 1}', array['not_id']); - agtype_exists_all -------------------- - f -(1 row) - -SELECT '{"id": 1}'::agtype ?& array['id']; - ?column? ----------- - t -(1 row) - -SELECT '{"id": 1}'::agtype ?& array['not_id']; - ?column? ----------- - f -(1 row) - -- -- Test STARTS WITH, ENDS WITH, and CONTAINS -- diff --git a/regress/expected/jsonb_operators.out b/regress/expected/jsonb_operators.out index 71e4fe1a6..78b2f8970 100644 --- a/regress/expected/jsonb_operators.out +++ b/regress/expected/jsonb_operators.out @@ -22,6 +22,789 @@ SET search_path TO ag_catalog; -- jsonb operators in AGE (?, ?&, ?|, ->, ->>, #>, #>>, ||) -- -- +-- Agtype exists operator +-- +-- exists (?) +-- should return 't' +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '"n"'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '"a"'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '"b"'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '"d"'; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ? '"label"'; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ? '"n"'; + ?column? +---------- + t +(1 row) + +SELECT '["1","2"]'::agtype ? '"1"'; + ?column? +---------- + t +(1 row) + +SELECT '["hello", "world"]'::agtype ? '"hello"'; + ?column? +---------- + t +(1 row) + +SELECT agtype_exists('{"id": 1}','id'::text); + agtype_exists +--------------- + t +(1 row) + +SELECT '{"id": 1}'::agtype ? 'id'::text; + ?column? +---------- + t +(1 row) + +-- should return 'f' +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '"e"'; + ?column? +---------- + f +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '"e1"'; + ?column? +---------- + f +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '"1"'; + ?column? +---------- + f +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '1'; + ?column? +---------- + f +(1 row) + +SELECT '[{"id": 281474976710658, "label": "", "properties": {"n": 100}}]'::agtype ? '"id"'; + ?column? +---------- + f +(1 row) + +SELECT '[{"id": 281474976710658, "label": "", "properties": {"n": 100}}]'::agtype ? 'null'; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ? 'null'; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ? 'null'; + ?column? +---------- + f +(1 row) + +SELECT '["hello", "world"]'::agtype ? '"hell"'; + ?column? +---------- + f +(1 row) + +SELECT agtype_exists('{"id": 1}','not_id'::text); + agtype_exists +--------------- + f +(1 row) + +SELECT '{"id": 1}'::agtype ? 'not_id'::text; + ?column? +---------- + f +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '["e1", "n"]'; + ?column? +---------- + f +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '["n"]'; + ?column? +---------- + f +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '["n", "a", "e"]'; + ?column? +---------- + f +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '{"n": null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '{"n": null, "b": true}'; + ?column? +---------- + f +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '["e1"]'; + ?column? +---------- + f +(1 row) + +-- errors out +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? 'e1'; +ERROR: invalid input syntax for type agtype +LINE 1: ...a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? 'e1'; + ^ +DETAIL: Expected agtype value, but found "e1". +CONTEXT: agtype data, line 1: e1 +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? 'e'; +ERROR: invalid input syntax for type agtype +LINE 1: ..."a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? 'e'; + ^ +DETAIL: Expected agtype value, but found "e". +CONTEXT: agtype data, line 1: e +-- Exists any (?|) +-- should return 't' +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["a","b"]'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["b","a"]'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["c","a"]'; + ?column? +---------- + t +(1 row) + +SELECT '{"1":null, "b":"qq"}'::agtype ?| '["c","1"]'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["a","a", "b", "b", "b"]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '[1,2,3]'::agtype ?| '[1,2,3,4]'; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[null,"id"]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '["id",null]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[true,"id"]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[1,"id"]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '[null,null,"n"]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '["n",null]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT agtype_exists_any('{"id": 1}', array['id']); + agtype_exists_any +------------------- + t +(1 row) + +SELECT '{"id": 1}'::agtype ?| array['id']; + ?column? +---------- + t +(1 row) + +-- should return 'f' +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["c","d"]'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["1","2"]'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["c","1"]'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?| '[]'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["c","d"]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[null]'; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[]'; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[null,null]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[null, "idk"]'; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[""]'; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex' ?| '[null,"idk"]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?| '[null,null,"idk"]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?| '["idk",null]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '[null]'; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '[]'; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '[null,null]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '[[""]]'; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '[null,null, "idk"]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '[null,null,"idk"]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '["start_idk",null]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?| '[["a"], ["b"]]'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?| '[["a"], ["b"], ["c"]]'; + ?column? +---------- + f +(1 row) + +SELECT '[null]'::agtype ?| '[null]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT agtype_exists_any('{"id": 1}', array['not_id']); + agtype_exists_any +------------------- + f +(1 row) + +SELECT '{"id": 1}'::agtype ?| array['not_id']; + ?column? +---------- + f +(1 row) + +-- errors out +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ?| '"b"'; +ERROR: invalid agtype value for right operand +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ?| '"d"'; +ERROR: invalid agtype value for right operand +SELECT '{"a":null, "b":"qq"}'::agtype ?| '{"a", "b"}'; +ERROR: invalid input syntax for type agtype +LINE 1: SELECT '{"a":null, "b":"qq"}'::agtype ?| '{"a", "b"}'; + ^ +DETAIL: Expected ":", but found ",". +CONTEXT: agtype data, line 1: {"a",... +SELECT '{"a":null, "b":"qq"}'::agtype ?| ''; +ERROR: invalid input syntax for type agtype +LINE 1: SELECT '{"a":null, "b":"qq"}'::agtype ?| ''; + ^ +DETAIL: The input string ended unexpectedly. +CONTEXT: agtype data, line 1: +SELECT '{"a":null, "b":"qq"}'::agtype ?| '""'; +ERROR: invalid agtype value for right operand +SELECT '{"a":null, "b":"qq"}'::agtype ?| '{""}'; +ERROR: invalid input syntax for type agtype +LINE 1: SELECT '{"a":null, "b":"qq"}'::agtype ?| '{""}'; + ^ +DETAIL: Expected ":", but found "}". +CONTEXT: agtype data, line 1: {""} +SELECT '{"a":null, "b":"qq"}'::agtype ?| '{}'; +ERROR: invalid agtype value for right operand +SELECT '{"a":null, "b":"qq"}'::agtype ?| '0'::agtype; +ERROR: invalid agtype value for right operand +SELECT '{"a":null, "b":"qq"}'::agtype ?| '0'; +ERROR: invalid agtype value for right operand +-- Exists all (?&) +-- should return 't' +SELECT '{"a":null, "b":"qq"}'::agtype ?& '["a","b"]'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?& '["b","a"]'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?& '["a","a", "b", "b", "b"]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[null]'; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[]'; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[null,null]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[null,null,"id"]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '["id",null]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?& '[null]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?& '[]'; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?& '[null,null]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?& '[null,null,"n"]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?& '["n",null]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[null]'; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[]'; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[null,null]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[null,null,"n"]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '["n",null]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT '[1,2,3]'::agtype ?& '[1,2,3]'; + ?column? +---------- + t +(1 row) + +SELECT '[1,2,3]'::agtype ?& '[1,2,3,null]'; + ?column? +---------- + t +(1 row) + +SELECT '[1,2,3]'::agtype ?& '[null, null]'; + ?column? +---------- + t +(1 row) + +SELECT '[1,2,3]'::agtype ?& '[null, null, null]'; + ?column? +---------- + t +(1 row) + +SELECT '[1,2,3]'::agtype ?& '[]'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?& '[]'; + ?column? +---------- + t +(1 row) + +SELECT '[null]'::agtype ?& '[null]'::agtype; + ?column? +---------- + t +(1 row) + +SELECT agtype_exists_all('{"id": 1}', array['id']); + agtype_exists_all +------------------- + t +(1 row) + +SELECT '{"id": 1}'::agtype ?& array['id']; + ?column? +---------- + t +(1 row) + +-- should return 'f' +SELECT '{"a":null, "b":"qq"}'::agtype ?& '["c","a"]'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?& '["a","b", "c"]'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?& '["c","d"]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '[1,2,3]'::agtype ?& '[1,2,3,4]'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?& '[["a"]]'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?& '[["a"], ["b"]]'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?& '[["a"], ["b"], ["c"]]'; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[null, "idk"]'; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[""]'; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex' ?& '[null,"idk"]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?& '[null,null,"idk"]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?& '["idk",null]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[1,"id"]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[true,"id"]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[null, "idk"]'; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[[""]]'; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[null,null, "idk"]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[null,null,"idk"]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '["start_idk",null]'::agtype; + ?column? +---------- + f +(1 row) + +SELECT '{"a":null, "b":"qq"}'::agtype ?& '[null, "c", "a"]'; + ?column? +---------- + f +(1 row) + +SELECT agtype_exists_all('{"id": 1}', array['not_id']); + agtype_exists_all +------------------- + f +(1 row) + +SELECT '{"id": 1}'::agtype ?& array['not_id']; + ?column? +---------- + f +(1 row) + +-- errors out +SELECT '{"a":null, "b":"qq"}'::agtype ?& '"d"'; +ERROR: invalid agtype value for right operand +SELECT '{"a":null, "b":"qq"}'::agtype ?& '"a"'; +ERROR: invalid agtype value for right operand +SELECT '{"a":null, "b":"qq"}'::agtype ?& '" "'; +ERROR: invalid agtype value for right operand +SELECT '{"a":null, "b":"qq"}'::agtype ?& '""'; +ERROR: invalid agtype value for right operand +SELECT '{"a":null, "b":"qq"}'::agtype ?& '"null"'; +ERROR: invalid agtype value for right operand +SELECT '{"a":null, "b":"qq"}'::agtype ?& '{"a", "b", "c"}'; +ERROR: invalid input syntax for type agtype +LINE 1: SELECT '{"a":null, "b":"qq"}'::agtype ?& '{"a", "b", "c"}'; + ^ +DETAIL: Expected ":", but found ",". +CONTEXT: agtype data, line 1: {"a",... +SELECT '{"a":null, "b":"qq"}'::agtype ?& '{}'; +ERROR: invalid agtype value for right operand +SELECT '{"a":null, "b":"qq"}'::agtype ?& '{""}'; +ERROR: invalid input syntax for type agtype +LINE 1: SELECT '{"a":null, "b":"qq"}'::agtype ?& '{""}'; + ^ +DETAIL: Expected ":", but found "}". +CONTEXT: agtype data, line 1: {""} +SELECT '{"a":null, "b":"qq"}'::agtype ?& '{null}'; +ERROR: invalid input syntax for type agtype +LINE 1: SELECT '{"a":null, "b":"qq"}'::agtype ?& '{null}'; + ^ +DETAIL: Expected string or "}", but found "null". +CONTEXT: agtype data, line 1: {null... +SELECT '{"a":null, "b":"qq"}'::agtype ?& '{"null"}'; +ERROR: invalid input syntax for type agtype +LINE 1: SELECT '{"a":null, "b":"qq"}'::agtype ?& '{"null"}'; + ^ +DETAIL: Expected ":", but found "}". +CONTEXT: agtype data, line 1: {"null"} +-- -- concat || operator -- SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || '[0, 1]'::agtype as i) a; @@ -464,6 +1247,306 @@ SELECT * FROM cypher('jsonb_operators',$$CREATE ({list:['a', 'b', 'c'], json:{a: --- (0 rows) +/* + * ?, ?|, ?& key existence operators + */ +-- Exists (?) +-- should return true +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ? 'list' $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json ? 'a' $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.list ? 'c' $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ? 'a' $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.c ? 'd' $$) as (a agtype); + a +------ + true +(1 row) + +-- should return false +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ? 'a' $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json ? 'd' $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.list ? 'd' $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ? 'c' $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.c ? 'e' $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ? [] $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ? ['d'] $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ? {d: 'e'} $$) as (a agtype); + a +------- + false +(1 row) + +-- Exists (?|) +-- should return true +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| ['list'] $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| ['list', 'd'] $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| ['json', 'a'] $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| ['list', 'json'] $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ?| ['a'] $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ?| ['a', 'b'] $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.c ?| ['d'] $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.c ?| ['d', 'e'] $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| keys(n) $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json ?| keys(n.json) $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return [n.json ?| keys(n.json)] ?| [true] $$) as (a agtype); + a +------ + true +(1 row) + +-- should return false +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| [] $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| ['a', 'b'] $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ?| [] $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.c ?| ['c'] $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| [['list']] $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| keys(n.json) $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json ?| keys(n) $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return [n.json ?| keys(n.json)] ?| [false] $$) as (a agtype); + a +------- + false +(1 row) + +-- errors out +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| 'list' $$) as (a agtype); +ERROR: invalid agtype value for right operand +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| n $$) as (a agtype); +ERROR: invalid agtype value for right operand +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& 1 $$) as (a agtype); +ERROR: invalid agtype value for right operand +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& '' $$) as (a agtype); +ERROR: invalid agtype value for right operand +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& '1' $$) as (a agtype); +ERROR: invalid agtype value for right operand +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& '{}' $$) as (a agtype); +ERROR: invalid agtype value for right operand +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& n.json $$) as (a agtype); +ERROR: invalid agtype value for right operand +-- Exists (?&) +-- should return true +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& ['list', 'json'] $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ?& ['a', 'b'] $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.c ?& ['d'] $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& keys(n) $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json ?& keys(n.json) $$) as (a agtype); + a +------ + true +(1 row) + +-- should return false +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& [] $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& ['a', 'b'] $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ?& [] $$) as (a agtype); + a +------ + true +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ?& ['a', 'b', 'c'] $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.c ?& ['d', 'e'] $$) as (a agtype); + a +------- + false +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& [['list']] $$) as (a agtype); + a +------- + false +(1 row) + +-- errors out +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& 'list' $$) as (a agtype); +ERROR: invalid agtype value for right operand +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& 1 $$) as (a agtype); +ERROR: invalid agtype value for right operand +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& '' $$) as (a agtype); +ERROR: invalid agtype value for right operand +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& '1' $$) as (a agtype); +ERROR: invalid agtype value for right operand +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& n $$) as (a agtype); +ERROR: invalid agtype value for right operand +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& '{}' $$) as (a agtype); +ERROR: invalid agtype value for right operand +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& n.json $$) as (a agtype); +ERROR: invalid agtype value for right operand -- -- concat || operator -- diff --git a/regress/sql/agtype.sql b/regress/sql/agtype.sql index 0e53df2c1..b8f08d9e2 100644 --- a/regress/sql/agtype.sql +++ b/regress/sql/agtype.sql @@ -803,25 +803,6 @@ SELECT agtype_contains('{"id": 1}','{"id": 2}'); SELECT '{"id": 1}'::agtype @> '{"id": 1}'; SELECT '{"id": 1}'::agtype @> '{"id": 2}'; -SELECT agtype_exists('{"id": 1}','id'); -SELECT agtype_exists('{"id": 1}','not_id'); - -SELECT '{"id": 1}'::agtype ? 'id'; -SELECT '{"id": 1}'::agtype ? 'not_id'; - -SELECT agtype_exists_any('{"id": 1}', array['id']); -SELECT agtype_exists_any('{"id": 1}', array['not_id']); - -SELECT '{"id": 1}'::agtype ?| array['id']; -SELECT '{"id": 1}'::agtype ?| array['not_id']; - - -SELECT agtype_exists_all('{"id": 1}', array['id']); -SELECT agtype_exists_all('{"id": 1}', array['not_id']); - -SELECT '{"id": 1}'::agtype ?& array['id']; -SELECT '{"id": 1}'::agtype ?& array['not_id']; - -- -- Test STARTS WITH, ENDS WITH, and CONTAINS -- diff --git a/regress/sql/jsonb_operators.sql b/regress/sql/jsonb_operators.sql index 6fbb872a1..689c148aa 100644 --- a/regress/sql/jsonb_operators.sql +++ b/regress/sql/jsonb_operators.sql @@ -24,6 +24,170 @@ SET search_path TO ag_catalog; -- jsonb operators in AGE (?, ?&, ?|, ->, ->>, #>, #>>, ||) -- +-- +-- Agtype exists operator +-- + +-- exists (?) + +-- should return 't' +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '"n"'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '"a"'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '"b"'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '"d"'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ? '"label"'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ? '"n"'; +SELECT '["1","2"]'::agtype ? '"1"'; +SELECT '["hello", "world"]'::agtype ? '"hello"'; +SELECT agtype_exists('{"id": 1}','id'::text); +SELECT '{"id": 1}'::agtype ? 'id'::text; + +-- should return 'f' +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '"e"'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '"e1"'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '"1"'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '1'; +SELECT '[{"id": 281474976710658, "label": "", "properties": {"n": 100}}]'::agtype ? '"id"'; +SELECT '[{"id": 281474976710658, "label": "", "properties": {"n": 100}}]'::agtype ? 'null'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ? 'null'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ? 'null'; +SELECT '["hello", "world"]'::agtype ? '"hell"'; +SELECT agtype_exists('{"id": 1}','not_id'::text); +SELECT '{"id": 1}'::agtype ? 'not_id'::text; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '["e1", "n"]'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '["n"]'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '["n", "a", "e"]'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '{"n": null}'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '{"n": null, "b": true}'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? '["e1"]'; + +-- errors out +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? 'e1'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ? 'e'; + +-- Exists any (?|) + +-- should return 't' +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["a","b"]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["b","a"]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["c","a"]'; +SELECT '{"1":null, "b":"qq"}'::agtype ?| '["c","1"]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["a","a", "b", "b", "b"]'::agtype; +SELECT '[1,2,3]'::agtype ?| '[1,2,3,4]'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[null,"id"]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '["id",null]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[true,"id"]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[1,"id"]'::agtype; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '[null,null,"n"]'::agtype; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '["n",null]'::agtype; +SELECT agtype_exists_any('{"id": 1}', array['id']); +SELECT '{"id": 1}'::agtype ?| array['id']; + +-- should return 'f' +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["c","d"]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["1","2"]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["c","1"]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '[]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '["c","d"]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[null]'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[]'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[null,null]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[null, "idk"]'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?| '[""]'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex' ?| '[null,"idk"]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?| '[null,null,"idk"]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?| '["idk",null]'::agtype; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '[null]'; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '[]'; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '[null,null]'::agtype; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '[[""]]'; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '[null,null, "idk"]'::agtype; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '[null,null,"idk"]'::agtype; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?| '["start_idk",null]'::agtype; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '[["a"], ["b"]]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '[["a"], ["b"], ["c"]]'; +SELECT '[null]'::agtype ?| '[null]'::agtype; +SELECT agtype_exists_any('{"id": 1}', array['not_id']); +SELECT '{"id": 1}'::agtype ?| array['not_id']; + +-- errors out +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ?| '"b"'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::agtype ?| '"d"'; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '{"a", "b"}'; +SELECT '{"a":null, "b":"qq"}'::agtype ?| ''; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '""'; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '{""}'; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '{}'; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '0'::agtype; +SELECT '{"a":null, "b":"qq"}'::agtype ?| '0'; + +-- Exists all (?&) + +-- should return 't' +SELECT '{"a":null, "b":"qq"}'::agtype ?& '["a","b"]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '["b","a"]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '["a","a", "b", "b", "b"]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[null]'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[]'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[null,null]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[null,null,"id"]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '["id",null]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?& '[null]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?& '[]'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?& '[null,null]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?& '[null,null,"n"]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?& '["n",null]'::agtype; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[null]'; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[]'; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[null,null]'::agtype; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[null,null,"n"]'::agtype; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '["n",null]'::agtype; +SELECT '[1,2,3]'::agtype ?& '[1,2,3]'; +SELECT '[1,2,3]'::agtype ?& '[1,2,3,null]'; +SELECT '[1,2,3]'::agtype ?& '[null, null]'; +SELECT '[1,2,3]'::agtype ?& '[null, null, null]'; +SELECT '[1,2,3]'::agtype ?& '[]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '[]'; +SELECT '[null]'::agtype ?& '[null]'::agtype; +SELECT agtype_exists_all('{"id": 1}', array['id']); +SELECT '{"id": 1}'::agtype ?& array['id']; + +-- should return 'f' +SELECT '{"a":null, "b":"qq"}'::agtype ?& '["c","a"]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '["a","b", "c"]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '["c","d"]'::agtype; +SELECT '[1,2,3]'::agtype ?& '[1,2,3,4]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '[["a"]]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '[["a"], ["b"]]'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '[["a"], ["b"], ["c"]]'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[null, "idk"]'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[""]'; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex' ?& '[null,"idk"]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?& '[null,null,"idk"]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex'::agtype ?& '["idk",null]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[1,"id"]'::agtype; +SELECT '{"id": 281474976710658, "label": "", "properties": {"n": 100}}'::agtype ?& '[true,"id"]'::agtype; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[null, "idk"]'; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[[""]]'; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[null,null, "idk"]'::agtype; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '[null,null,"idk"]'::agtype; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"n": 100}}::edge'::agtype ?& '["start_idk",null]'::agtype; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '[null, "c", "a"]'; +SELECT agtype_exists_all('{"id": 1}', array['not_id']); +SELECT '{"id": 1}'::agtype ?& array['not_id']; + +-- errors out +SELECT '{"a":null, "b":"qq"}'::agtype ?& '"d"'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '"a"'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '" "'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '""'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '"null"'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '{"a", "b", "c"}'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '{}'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '{""}'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '{null}'; +SELECT '{"a":null, "b":"qq"}'::agtype ?& '{"null"}'; + -- -- concat || operator -- @@ -129,6 +293,90 @@ SELECT create_graph('jsonb_operators'); SELECT * FROM cypher('jsonb_operators',$$CREATE ({list:['a', 'b', 'c'], json:{a:1, b:['a', 'b'], c:{d:'a'}}})$$) as (a agtype); +/* + * ?, ?|, ?& key existence operators + */ + +-- Exists (?) + +-- should return true +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ? 'list' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json ? 'a' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.list ? 'c' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ? 'a' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.c ? 'd' $$) as (a agtype); + +-- should return false +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ? 'a' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json ? 'd' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.list ? 'd' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ? 'c' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.c ? 'e' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ? [] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ? ['d'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ? {d: 'e'} $$) as (a agtype); + +-- Exists (?|) + +-- should return true +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| ['list'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| ['list', 'd'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| ['json', 'a'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| ['list', 'json'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ?| ['a'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ?| ['a', 'b'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.c ?| ['d'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.c ?| ['d', 'e'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| keys(n) $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json ?| keys(n.json) $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return [n.json ?| keys(n.json)] ?| [true] $$) as (a agtype); + +-- should return false +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| [] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| ['a', 'b'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ?| [] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.c ?| ['c'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| [['list']] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| keys(n.json) $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json ?| keys(n) $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return [n.json ?| keys(n.json)] ?| [false] $$) as (a agtype); + +-- errors out +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| 'list' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?| n $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& 1 $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& '' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& '1' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& '{}' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& n.json $$) as (a agtype); + + +-- Exists (?&) + +-- should return true +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& ['list', 'json'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ?& ['a', 'b'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.c ?& ['d'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& keys(n) $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json ?& keys(n.json) $$) as (a agtype); + +-- should return false +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& [] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& ['a', 'b'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ?& [] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.b ?& ['a', 'b', 'c'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n.json.c ?& ['d', 'e'] $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& [['list']] $$) as (a agtype); + +-- errors out +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& 'list' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& 1 $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& '' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& '1' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& n $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& '{}' $$) as (a agtype); +SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& n.json $$) as (a agtype); + -- -- concat || operator -- diff --git a/src/backend/parser/ag_scanner.l b/src/backend/parser/ag_scanner.l index ffc6673cb..74887ba4a 100644 --- a/src/backend/parser/ag_scanner.l +++ b/src/backend/parser/ag_scanner.l @@ -227,15 +227,17 @@ param \${id} * These are tokens that are used as operators and language constructs in * Cypher, and some of them are structural characters in JSON. */ -concat "||" -lt_gt "<>" -lt_eq "<=" -gt_eq ">=" -dot_dot ".." -plus_eq "+=" -eq_tilde "=~" -typecast "::" -self [%()*+,\-./:;<=>[\]^{|}] +any_exists "?|" +all_exists "?&" +concat "||" +lt_gt "<>" +lt_eq "<=" +gt_eq ">=" +dot_dot ".." +plus_eq "+=" +eq_tilde "=~" +typecast "::" +self [?%()*+,\-./:;<=>[\]^{|}] other . @@ -651,6 +653,22 @@ ag_token token; return token; } +{any_exists} { + update_location(); + token.type = AG_TOKEN_ANY_EXISTS; + token.value.s = yytext; + token.location = get_location(); + return token; +} + +{all_exists} { + update_location(); + token.type = AG_TOKEN_ALL_EXISTS; + token.value.s = yytext; + token.location = get_location(); + return token; +} + {lt_gt} { update_location(); token.type = AG_TOKEN_LT_GT; diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y index 130b56f9a..884af4552 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -76,6 +76,7 @@ /* operators that have more than 1 character */ %token NOT_EQ LT_EQ GT_EQ DOT_DOT TYPECAST PLUS_EQ EQ_TILDE CONCAT +%token ANY_EXISTS ALL_EXISTS /* keywords in alphabetical order */ %token ALL ANALYZE AND AS ASC ASCENDING @@ -169,6 +170,7 @@ %left XOR %right NOT %left '=' NOT_EQ '<' LT_EQ '>' GT_EQ +%left '|' '&' '?' ANY_EXISTS ALL_EXISTS %left '+' '-' CONCAT %left '*' '/' '%' %left '^' @@ -1324,6 +1326,18 @@ expr: { $$ = build_comparison_expression($1, $3, ">=", @2); } + | expr '?' expr %prec '.' + { + $$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "?", $1, $3, @2); + } + | expr ANY_EXISTS expr + { + $$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "?|", $1, $3, @2); + } + | expr ALL_EXISTS expr + { + $$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "?&", $1, $3, @2); + } | expr CONCAT expr { $$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "||", $1, $3, @2); diff --git a/src/backend/parser/cypher_parser.c b/src/backend/parser/cypher_parser.c index 407d6c07a..f9dfae71b 100644 --- a/src/backend/parser/cypher_parser.c +++ b/src/backend/parser/cypher_parser.c @@ -47,6 +47,8 @@ int cypher_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, ag_scanner_t scanner) TYPECAST, PLUS_EQ, EQ_TILDE, + ANY_EXISTS, + ALL_EXISTS, CONCAT }; @@ -99,6 +101,8 @@ int cypher_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, ag_scanner_t scanner) case AG_TOKEN_DOT_DOT: case AG_TOKEN_PLUS_EQ: case AG_TOKEN_EQ_TILDE: + case AG_TOKEN_ALL_EXISTS: + case AG_TOKEN_ANY_EXISTS: case AG_TOKEN_CONCAT: break; case AG_TOKEN_TYPECAST: diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 2d025a8f0..2f93983e3 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -175,9 +175,6 @@ static int64 get_int64_from_int_datums(Datum d, Oid type, char *funcname, static agtype_iterator *get_next_object_key(agtype_iterator *it, agtype_container *agtc, agtype_value *key); -static agtype_iterator *get_next_list_element(agtype_iterator *it, - agtype_container *agtc, - agtype_value *elem); static int extract_variadic_args_min(FunctionCallInfo fcinfo, int variadic_start, bool convert_unknown, Datum **args, Oid **types, bool **nulls, @@ -6411,7 +6408,7 @@ Datum age_tostringlist(PG_FUNCTION_ARGS) PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); } -static agtype_iterator *get_next_list_element(agtype_iterator *it, +agtype_iterator *get_next_list_element(agtype_iterator *it, agtype_container *agtc, agtype_value *elem) { agtype_iterator_token itok; @@ -10163,6 +10160,62 @@ agtype_value *get_agtype_value(char *funcname, agtype *agt_arg, return agtv_value; } +/* + * Returns properties of an entity (vertex or edge) or NULL if there are none. + * If the object passed is not a scalar, an error is thrown. + * If the object is a scalar and error_on_scalar is false, the scalar is + * returned, otherwise an error is thrown. + */ +agtype_value *extract_entity_properties(agtype *object, bool error_on_scalar) +{ + agtype_value *scalar_value = NULL; + agtype_value *return_value = NULL; + + if (!AGT_ROOT_IS_SCALAR(object)) + { + ereport(ERROR,(errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected a scalar value"))); + } + + /* unpack the scalar */ + scalar_value = get_ith_agtype_value_from_container(&object->root, 0); + + /* get the properties depending on the type or fail */ + if (scalar_value->type == AGTV_VERTEX) + { + return_value = &scalar_value->val.object.pairs[2].value; + } + else if (scalar_value->type == AGTV_EDGE) + { + return_value = &scalar_value->val.object.pairs[4].value; + } + else if (scalar_value->type == AGTV_PATH) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot extract properties from an agtype path"))); + } + else if (error_on_scalar) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("scalar object must be a vertex or edge"))); + } + else + { + return_value = scalar_value; + } + + /* if the properties are NULL, return NULL */ + if (return_value == NULL || return_value->type == AGTV_NULL) + { + return NULL; + } + + /* set the object_value to the property_value. */ + return return_value; +} + PG_FUNCTION_INFO_V1(age_eq_tilde); /* * Execution function for =~ aka regular expression comparisons diff --git a/src/backend/utils/adt/agtype_gin.c b/src/backend/utils/adt/agtype_gin.c index c0a2a028d..1a1f267af 100644 --- a/src/backend/utils/adt/agtype_gin.c +++ b/src/backend/utils/adt/agtype_gin.c @@ -224,33 +224,42 @@ Datum gin_extract_agtype_query(PG_FUNCTION_ARGS) strategy == AGTYPE_EXISTS_ALL_STRATEGY_NUMBER) { /* Query is a text array; each element is treated as a key */ - ArrayType *query = PG_GETARG_ARRAYTYPE_P(0); - Datum *key_datums; - bool *key_nulls; - int key_count; - int i, j; - - deconstruct_array(query, TEXTOID, -1, false, 'i', - &key_datums, &key_nulls, &key_count); + agtype *agt = AG_GET_ARG_AGTYPE_P(0); + agtype_iterator *it = NULL; + agtype_value elem; + agtype_iterator_token itok; + int key_count = AGTYPE_CONTAINER_SIZE(&agt->root); + int index = 0; + + if (AGTYPE_CONTAINER_IS_SCALAR(&agt->root) || + !AGTYPE_CONTAINER_IS_ARRAY(&agt->root)) + { + elog(ERROR, "GIN query requires an agtype array"); + } entries = (Datum *) palloc(sizeof(Datum) * key_count); + it = agtype_iterator_init(&agt->root); - for (i = 0, j = 0; i < key_count; i++) + /* it should be WAGT_BEGIN_ARRAY */ + itok = agtype_iterator_next(&it, &elem, true); + Assert(itok == WAGT_BEGIN_ARRAY); + + while (WAGT_END_ARRAY != agtype_iterator_next(&it, &elem, true)) { - /* Nulls in the array are ignored */ - if (key_nulls[i]) + if (elem.type != AGTV_STRING) { - continue; + elog(ERROR, "unsupport agtype for GIN lookup: %d", elem.type); } - entries[j++] = make_text_key(AGT_GIN_FLAG_KEY, - VARDATA(key_datums[i]), - VARSIZE(key_datums[i]) - VARHDRSZ); + entries[index++] = make_text_key(AGT_GIN_FLAG_KEY, + elem.val.string.val, + elem.val.string.len); } - *nentries = j; + *nentries = index; + /* ExistsAll with no keys should match everything */ - if (j == 0 && strategy == AGTYPE_EXISTS_ALL_STRATEGY_NUMBER) + if (index == 0 && strategy == AGTYPE_EXISTS_ALL_STRATEGY_NUMBER) { *searchMode = GIN_SEARCH_MODE_ALL; } diff --git a/src/backend/utils/adt/agtype_ops.c b/src/backend/utils/adt/agtype_ops.c index 829483a49..238259af7 100644 --- a/src/backend/utils/adt/agtype_ops.c +++ b/src/backend/utils/adt/agtype_ops.c @@ -996,6 +996,165 @@ Datum agtype_any_ge(PG_FUNCTION_ARGS) PG_RETURN_BOOL(result); } +PG_FUNCTION_INFO_V1(agtype_exists_agtype); +/* + * ? operator for agtype. Returns true if the string exists as top-level keys + */ +Datum agtype_exists_agtype(PG_FUNCTION_ARGS) +{ + agtype *agt = AG_GET_ARG_AGTYPE_P(0); + agtype *key = AG_GET_ARG_AGTYPE_P(1); + agtype_value *aval; + agtype_value *v = NULL; + + if (AGT_ROOT_IS_SCALAR(agt)) + { + agt = agtype_value_to_agtype(extract_entity_properties(agt, false)); + } + + if (AGT_ROOT_IS_SCALAR(key)) + { + aval = get_ith_agtype_value_from_container(&key->root, 0); + } + else + { + PG_RETURN_BOOL(false); + } + + if (AGT_ROOT_IS_OBJECT(agt) && + aval->type == AGTV_STRING) + { + v = find_agtype_value_from_container(&agt->root, + AGT_FOBJECT, + aval); + } + else if (AGT_ROOT_IS_ARRAY(agt) && + !aval->type == AGTV_NULL) + { + v = find_agtype_value_from_container(&agt->root, + AGT_FARRAY, + aval); + } + + PG_RETURN_BOOL(v != NULL); +} + +PG_FUNCTION_INFO_V1(agtype_exists_any_agtype); +/* + * ?| operator for agtype. Returns true if any of the array strings exist as + * top-level keys + */ +Datum agtype_exists_any_agtype(PG_FUNCTION_ARGS) +{ + agtype *agt = AG_GET_ARG_AGTYPE_P(0); + agtype *keys = AG_GET_ARG_AGTYPE_P(1); + agtype_value elem; + agtype_iterator *it = NULL; + + if (AGT_ROOT_IS_SCALAR(agt)) + { + agt = agtype_value_to_agtype(extract_entity_properties(agt, true)); + } + + if (!AGT_ROOT_IS_SCALAR(keys) && !AGT_ROOT_IS_OBJECT(keys)) + { + while ((it = get_next_list_element(it, &keys->root, &elem))) + { + if (IS_A_AGTYPE_SCALAR(&elem)) + { + if (AGT_ROOT_IS_OBJECT(agt) && + (&elem)->type == AGTV_STRING && + find_agtype_value_from_container(&agt->root, + AGT_FOBJECT, + &elem)) + { + PG_RETURN_BOOL(true); + } + else if (AGT_ROOT_IS_ARRAY(agt) && + !(&elem)->type == AGTV_NULL && + find_agtype_value_from_container(&agt->root, + AGT_FARRAY, + &elem)) + { + PG_RETURN_BOOL(true); + } + } + else + { + PG_RETURN_BOOL(false); + } + } + } + else + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid agtype value for right operand"))); + } + + PG_RETURN_BOOL(false); +} + +PG_FUNCTION_INFO_V1(agtype_exists_all_agtype); +/* + * ?& operator for agtype. Returns true if all of the array strings exist as + * top-level keys + */ +Datum agtype_exists_all_agtype(PG_FUNCTION_ARGS) +{ + agtype *agt = AG_GET_ARG_AGTYPE_P(0); + agtype *keys = AG_GET_ARG_AGTYPE_P(1); + agtype_value elem; + agtype_iterator *it = NULL; + + if (AGT_ROOT_IS_SCALAR(agt)) + { + agt = agtype_value_to_agtype(extract_entity_properties(agt, true)); + } + + if (!AGT_ROOT_IS_SCALAR(keys) && !AGT_ROOT_IS_OBJECT(keys)) + { + while ((it = get_next_list_element(it, &keys->root, &elem))) + { + if (IS_A_AGTYPE_SCALAR(&elem)) + { + if ((&elem)->type == AGTV_NULL) + { + continue; + } + else if (AGT_ROOT_IS_OBJECT(agt) && + (&elem)->type == AGTV_STRING && + find_agtype_value_from_container(&agt->root, + AGT_FOBJECT, + &elem)) + { + continue; + } + else if (AGT_ROOT_IS_ARRAY(agt) && + find_agtype_value_from_container(&agt->root, + AGT_FARRAY, + &elem)) + { + continue; + } + else + { + PG_RETURN_BOOL(false); + } + } + else + { + PG_RETURN_BOOL(false); + } + } + } + else + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid agtype value for right operand"))); + } + + PG_RETURN_BOOL(true); +} PG_FUNCTION_INFO_V1(agtype_contains); /* diff --git a/src/include/parser/ag_scanner.h b/src/include/parser/ag_scanner.h index 01087dc1f..cb11b4450 100644 --- a/src/include/parser/ag_scanner.h +++ b/src/include/parser/ag_scanner.h @@ -46,6 +46,8 @@ typedef enum ag_token_type AG_TOKEN_TYPECAST, AG_TOKEN_PLUS_EQ, AG_TOKEN_EQ_TILDE, + AG_TOKEN_ANY_EXISTS, + AG_TOKEN_ALL_EXISTS, AG_TOKEN_CONCAT, AG_TOKEN_CHAR, } ag_token_type; diff --git a/src/include/utils/agtype.h b/src/include/utils/agtype.h index 7020ac8e4..75712dd70 100644 --- a/src/include/utils/agtype.h +++ b/src/include/utils/agtype.h @@ -550,6 +550,10 @@ agtype_value *string_to_agtype_value(char *s); agtype_value *integer_to_agtype_value(int64 int_value); void add_agtype(Datum val, bool is_null, agtype_in_state *result, Oid val_type, bool key_scalar); +agtype_value *extract_entity_properties(agtype *object, bool error_on_scalar); +agtype_iterator *get_next_list_element(agtype_iterator *it, + agtype_container *agtc, + agtype_value *elem); void pfree_agtype_value(agtype_value* value); void pfree_agtype_value_content(agtype_value* value); void pfree_agtype_in_state(agtype_in_state* value);