This is an automated email from the ASF dual-hosted git repository. jgemignani pushed a commit to branch PG14 in repository https://gitbox.apache.org/repos/asf/age.git
The following commit(s) were added to refs/heads/PG14 by this push: new 6f316221 Add concat || operator to agtype (#1213) 6f316221 is described below commit 6f316221b1fcc04cfa22936e7b371424206fb1d3 Author: Zainab Saad <105385638+zainab-s...@users.noreply.github.com> AuthorDate: Fri Sep 8 03:42:23 2023 +0500 Add concat || operator to agtype (#1213) - Implement the concatenation operator for agtype as operands, similar to the one in postgres where this operator works with jsonb operands - Allow using concat operator inside cypher queries - Add jsonb_operators.sql and jsonb_operators.out files for containing regression tests related to jsonb operators, i.e., (?,?|,?&,->,->>,#>,#>>,||) --- Makefile | 1 + age--1.4.0.sql | 14 + regress/expected/jsonb_operators.out | 577 +++++++++++++++++++++++++++++++++++ regress/sql/jsonb_operators.sql | 159 ++++++++++ src/backend/parser/ag_scanner.l | 9 + src/backend/parser/cypher_gram.y | 8 +- src/backend/parser/cypher_parser.c | 4 +- src/backend/utils/adt/agtype_ops.c | 167 ++++++++-- src/include/parser/ag_scanner.h | 3 +- 9 files changed, 912 insertions(+), 30 deletions(-) diff --git a/Makefile b/Makefile index ab98665e..fbd3f3b8 100644 --- a/Makefile +++ b/Makefile @@ -99,6 +99,7 @@ REGRESS = scan \ analyze \ graph_generation \ name_validation \ + jsonb_operators \ drop srcdir=`pwd` diff --git a/age--1.4.0.sql b/age--1.4.0.sql index 3a911627..213b397f 100644 --- a/age--1.4.0.sql +++ b/age--1.4.0.sql @@ -1391,6 +1391,20 @@ CREATE OPERATOR ^ ( RIGHTARG = agtype ); +CREATE FUNCTION ag_catalog.agtype_concat(agtype, agtype) +RETURNS agtype +LANGUAGE c +STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE OPERATOR || ( + FUNCTION = ag_catalog.agtype_concat, + LEFTARG = agtype, + RIGHTARG = agtype +); + CREATE FUNCTION ag_catalog.graphid_hash_cmp(graphid) RETURNS INTEGER LANGUAGE c diff --git a/regress/expected/jsonb_operators.out b/regress/expected/jsonb_operators.out new file mode 100644 index 00000000..71e4fe1a --- /dev/null +++ b/regress/expected/jsonb_operators.out @@ -0,0 +1,577 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +LOAD 'age'; +SET search_path TO ag_catalog; +-- +-- jsonb operators in AGE (?, ?&, ?|, ->, ->>, #>, #>>, ||) +-- +-- +-- concat || operator +-- +SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || '[0, 1]'::agtype as i) a; + i | pg_typeof +--------------+----------- + [0, 1, 0, 1] | agtype +(1 row) + +SELECT i, pg_typeof(i) FROM (SELECT '2'::agtype || '[0, 1]'::agtype as i) a; + i | pg_typeof +-----------+----------- + [2, 0, 1] | agtype +(1 row) + +SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || '2'::agtype as i) a; + i | pg_typeof +-----------+----------- + [0, 1, 2] | agtype +(1 row) + +SELECT i, pg_typeof(i) FROM (SELECT '{"a": 1}'::agtype || '[0, 1]'::agtype as i) a; + i | pg_typeof +------------------+----------- + [{"a": 1}, 0, 1] | agtype +(1 row) + +SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || '{"a": 1}'::agtype as i) a; + i | pg_typeof +------------------+----------- + [0, 1, {"a": 1}] | agtype +(1 row) + +SELECT i, pg_typeof(i) FROM (SELECT '[]'::agtype || '[0, 1]'::agtype as i) a; + i | pg_typeof +--------+----------- + [0, 1] | agtype +(1 row) + +SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || '[]'::agtype as i) a; + i | pg_typeof +--------+----------- + [0, 1] | agtype +(1 row) + +SELECT i, pg_typeof(i) FROM (SELECT 'null'::agtype || '[0, 1]'::agtype as i) a; + i | pg_typeof +--------------+----------- + [null, 0, 1] | agtype +(1 row) + +SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || 'null'::agtype as i) a; + i | pg_typeof +--------------+----------- + [0, 1, null] | agtype +(1 row) + +SELECT i, pg_typeof(i) FROM (SELECT '[null]'::agtype || '[0, 1]'::agtype as i) a; + i | pg_typeof +--------------+----------- + [null, 0, 1] | agtype +(1 row) + +SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || '[null]'::agtype as i) a; + i | pg_typeof +--------------+----------- + [0, 1, null] | agtype +(1 row) + +SELECT i, pg_typeof(i) FROM (SELECT NULL || '[0, 1]'::agtype as i) a; + i | pg_typeof +---+----------- + | agtype +(1 row) + +SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || NULL as i) a; + i | pg_typeof +---+----------- + | agtype +(1 row) + +-- both operands are objects +SELECT '{"aa":1 , "b":2, "cq":3}'::agtype || '{"cq":"l", "b":"g", "fg":false}'; + ?column? +--------------------------------------------- + {"b": "g", "aa": 1, "cq": "l", "fg": false} +(1 row) + +SELECT '{"aa":1 , "b":2, "cq":3}'::agtype || '{"aq":"l"}'; + ?column? +--------------------------------------- + {"b": 2, "aa": 1, "aq": "l", "cq": 3} +(1 row) + +SELECT '{"aa":1 , "b":2, "cq":3}'::agtype || '{"aa":"l"}'; + ?column? +------------------------------ + {"b": 2, "aa": "l", "cq": 3} +(1 row) + +SELECT '{"aa":1 , "b":2, "cq":3}'::agtype || '{}'; + ?column? +---------------------------- + {"b": 2, "aa": 1, "cq": 3} +(1 row) + +SELECT '{"aa":1 , "b":2, "cq":3, "cj": {"fg": true}}'::agtype || '{"cq":"l", "b":"g", "fg":false}'; + ?column? +----------------------------------------------------------------- + {"b": "g", "aa": 1, "cj": {"fg": true}, "cq": "l", "fg": false} +(1 row) + +SELECT '{"a": 13}'::agtype || '{"a": 13}'::agtype; + ?column? +----------- + {"a": 13} +(1 row) + +SELECT '{}'::agtype || '{"a":"b"}'::agtype; + ?column? +------------ + {"a": "b"} +(1 row) + +SELECT '{}'::agtype || '{}'::agtype; + ?column? +---------- + {} +(1 row) + +-- both operands are arrays +SELECT '["a", "b"]'::agtype || '["c"]'; + ?column? +----------------- + ["a", "b", "c"] +(1 row) + +SELECT '["a", "b"]'::agtype || '["c", "d"]'; + ?column? +---------------------- + ["a", "b", "c", "d"] +(1 row) + +SELECT '["a", "b"]'::agtype || '["c", "d", "d"]'; + ?column? +--------------------------- + ["a", "b", "c", "d", "d"] +(1 row) + +SELECT '["c"]' || '["a", "b"]'::agtype; + ?column? +----------------- + ["c", "a", "b"] +(1 row) + +SELECT '[]'::agtype || '["a"]'::agtype; + ?column? +---------- + ["a"] +(1 row) + +SELECT '[]'::agtype || '[]'::agtype; + ?column? +---------- + [] +(1 row) + +SELECT '["a", "b"]'::agtype || '"c"'; + ?column? +----------------- + ["a", "b", "c"] +(1 row) + +SELECT '"c"' || '["a", "b"]'::agtype; + ?column? +----------------- + ["c", "a", "b"] +(1 row) + +SELECT '[]'::agtype || '"a"'::agtype; + ?column? +---------- + ["a"] +(1 row) + +SELECT '"b"'::agtype || '"a"'::agtype; + ?column? +------------ + ["b", "a"] +(1 row) + +SELECT '3'::agtype || '[]'::agtype; + ?column? +---------- + [3] +(1 row) + +SELECT '3'::agtype || '4'::agtype; + ?column? +---------- + [3, 4] +(1 row) + +SELECT '3'::agtype || '[4]'; + ?column? +---------- + [3, 4] +(1 row) + +SELECT '3::numeric'::agtype || '[[]]'::agtype; + ?column? +------------------ + [3::numeric, []] +(1 row) + +SELECT null::agtype || null::agtype; + ?column? +---------- + +(1 row) + +-- array and object as operands +SELECT '{"aa":1 , "b":2, "cq":3}'::agtype || '[{"aa":"l"}]'; + ?column? +------------------------------------------- + [{"b": 2, "aa": 1, "cq": 3}, {"aa": "l"}] +(1 row) + +SELECT '{"aa":1 , "b":2, "cq":3}'::agtype || '[{"aa":"l", "aa": "k"}]'; + ?column? +------------------------------------------- + [{"b": 2, "aa": 1, "cq": 3}, {"aa": "k"}] +(1 row) + +SELECT '{"a": 13}'::agtype || '[{"a": 13}]'::agtype; + ?column? +------------------------ + [{"a": 13}, {"a": 13}] +(1 row) + +SELECT '[]'::agtype || '{"a":"b"}'::agtype; + ?column? +-------------- + [{"a": "b"}] +(1 row) + +SELECT '{"a":"b"}'::agtype || '[]'::agtype; + ?column? +-------------- + [{"a": "b"}] +(1 row) + +SELECT '[]'::agtype || '{}'::agtype; + ?column? +---------- + [{}] +(1 row) + +SELECT '[3]'::agtype || '{}'::agtype; + ?column? +---------- + [3, {}] +(1 row) + +SELECT '{}'::agtype || '[null]'::agtype; + ?column? +------------ + [{}, null] +(1 row) + +SELECT '[null]'::agtype || '{"a": null}'::agtype; + ?column? +--------------------- + [null, {"a": null}] +(1 row) + +SELECT '""'::agtype || '[]'::agtype; + ?column? +---------- + [""] +(1 row) + +-- vertex/edge/path as operand(s) +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b" : true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge'::agtype || '"id"'; + ?column? +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + [{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge, "id"] +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b" : true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge'::agtype || '"m"'; + ?column? +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + [{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge, "m"] +(1 row) + +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b" : true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge'::agtype || '{"m": []}'; + ?column? +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + [{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge, {"m": []}] +(1 row) + +SELECT '{"id": 844424930131969, "label": "v", "properties": {}}::vertex'::agtype || '{"id": 844424930131971, "label": "v", "properties": {"key": "value"}}::vertex'::agtype; + ?column? +-------------------------------------------------------------------------------------------------------------------------------------------------- + [{"id": 844424930131969, "label": "v", "properties": {}}::vertex, {"id": 844424930131971, "label": "v", "properties": {"key": "value"}}::vertex] +(1 row) + +SELECT '{"id": 844424930131969, "label": "v", "properties": {}}::vertex'::agtype || '[]'::agtype; + ?column? +------------------------------------------------------------------- + [{"id": 844424930131969, "label": "v", "properties": {}}::vertex] +(1 row) + +SELECT '{"id": 844424930131969, "label": "v", "properties": {}}::vertex'::agtype || '{}'::agtype; + ?column? +----------------------------------------------------------------------- + [{"id": 844424930131969, "label": "v", "properties": {}}::vertex, {}] +(1 row) + +SELECT '{}'::agtype || '{"id": 844424930131969, "label": "v", "properties": {}}::vertex'::agtype; + ?column? +----------------------------------------------------------------------- + [{}, {"id": 844424930131969, "label": "v", "properties": {}}::vertex] +(1 row) + +SELECT '"id"'::agtype || '{"id": 844424930131969, "label": "v", "properties": {}}::vertex'::agtype; + ?column? +------------------------------------------------------------------------- + ["id", {"id": 844424930131969, "label": "v", "properties": {}}::vertex] +(1 row) + +SELECT '{"id": 844424930131969, "label": "v", "properties": {}}::vertex'::agtype || '{"id": 1688849860263950, "label": "e_var", "end_id": 281474976710662, "start_id": 281474976710661, "properties": {}}::edge'::agtype; + ?column? +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + [{"id": 844424930131969, "label": "v", "properties": {}}::vertex, {"id": 1688849860263950, "label": "e_var", "end_id": 281474976710662, "start_id": 281474976710661, "properties": {}}::edge] +(1 row) + +SELECT '[{"id": 281474976710672, "label": "", "properties": {}}::vertex, {"id": 1688849860263960, "label": "e_var", "end_id": 281474976710673, "start_id": 281474976710672, "properties": {}}::edge, {"id": 281474976710673, "label": "", "properties": {}}::vertex]::path'::agtype || '{"id": 844424930131969, "label": "v", "properties": {}}::vertex'::agtype; + ?column? +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + [[{"id": 281474976710672, "label": "", "properties": {}}::vertex, {"id": 1688849860263960, "label": "e_var", "end_id": 281474976710673, "start_id": 281474976710672, "properties": {}}::edge, {"id": 281474976710673, "label": "", "properties": {}}::vertex]::path, {"id": 844424930131969, "label": "v", "properties": {}}::vertex] +(1 row) + +SELECT '[{"id": 281474976710672, "label": "", "properties": {}}::vertex, {"id": 1688849860263960, "label": "e_var", "end_id": 281474976710673, "start_id": 281474976710672, "properties": {}}::edge, {"id": 281474976710673, "label": "", "properties": {}}::vertex]::path'::agtype || '[{"id": 281474976710672, "label": "", "properties": {}}::vertex, {"id": 1688849860263960, "label": "e_var", "end_id": 281474976710673, "start_id": 281474976710672, "properties": {}}::edge, {"id": 281474976710673, [...] + ?column? [...] +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ [...] + [[{"id": 281474976710672, "label": "", "properties": {}}::vertex, {"id": 1688849860263960, "label": "e_var", "end_id": 281474976710673, "start_id": 281474976710672, "properties": {}}::edge, {"id": 281474976710673, "label": "", "properties": {}}::vertex]::path, [{"id": 281474976710672, "label": "", "properties": {}}::vertex, {"id": 1688849860263960, "label": "e_var", "end_id": 281474976710673, "start_id": 281474976710672, "properties": {}}::edge, {"id": 281474976710673, "label": "", "pro [...] +(1 row) + +-- using concat more than once in a query +SELECT '{}'::agtype || '{}'::agtype || '[{}]'::agtype; + ?column? +---------- + [{}, {}] +(1 row) + +SELECT '{"y": {}}'::agtype || '{"b": "5"}'::agtype || '{"a": {}}'::agtype || '{"z": []}'::agtype; + ?column? +--------------------------------------- + {"a": {}, "b": "5", "y": {}, "z": []} +(1 row) + +SELECT '{"y": {}}'::agtype || '{"b": "5"}'::agtype || '{"a": {}}'::agtype || '{"z": []}'::agtype || '[]'::agtype; + ?column? +----------------------------------------- + [{"a": {}, "b": "5", "y": {}, "z": []}] +(1 row) + +SELECT '{"y": {}}'::agtype || '{"b": "5"}'::agtype || '{"a": {}}'::agtype || '{"z": []}'::agtype || '[]'::agtype || '{}'; + ?column? +--------------------------------------------- + [{"a": {}, "b": "5", "y": {}, "z": []}, {}] +(1 row) + +SELECT '"e"'::agtype || '1'::agtype || '{}'::agtype; + ?column? +-------------- + ["e", 1, {}] +(1 row) + +SELECT ('"e"'::agtype || '1'::agtype) || '{"[]": "p"}'::agtype; + ?column? +----------------------- + ["e", 1, {"[]": "p"}] +(1 row) + +SELECT '{"{}": {"a": []}}'::agtype || '{"{}": {"[]": []}}'::agtype || '{"{}": {}}'::agtype; + ?column? +------------ + {"{}": {}} +(1 row) + +SELECT '{}'::agtype || '{}'::agtype || '[{}]'::agtype || '[{}]'::agtype || '{}'::agtype; + ?column? +------------------ + [{}, {}, {}, {}] +(1 row) + +-- should give an error +SELECT '{"a": 13}'::agtype || 'null'::agtype; +ERROR: invalid right operand for agtype concatenation +SELECT '"a"'::agtype || '{"a":1}'; +ERROR: invalid left operand for agtype concatenation +SELECT '3'::agtype || '{}'::agtype; +ERROR: invalid left operand for agtype concatenation +SELECT '{"a":1}' || '"a"'::agtype; +ERROR: invalid right operand for agtype concatenation +SELECT '{"b": [1, 2, {"[{}, {}]": "a"}, {"1": {}}]}'::agtype || true::agtype; +ERROR: invalid right operand for agtype concatenation +SELECT '{"b": [1, 2, {"[{}, {}]": "a"}, {"1": {}}]}'::agtype || 'true'::agtype; +ERROR: invalid right operand for agtype concatenation +SELECT '{"b": [1, 2, {"[{}, {}]": "a"}, {"1": {}}]}'::agtype || age_agtype_sum('1', '2'); +ERROR: invalid right operand for agtype concatenation +SELECT ('{"a": "5"}'::agtype || '{"a": {}}'::agtype) || '5'::agtype; +ERROR: invalid right operand for agtype concatenation +SELECT ('{"a": "5"}'::agtype || '{"a": {}}'::agtype || '5') || '[5]'::agtype; +ERROR: invalid right operand for agtype concatenation +-- both operands have to be of agtype +SELECT '3'::agtype || 4; +ERROR: operator does not exist: agtype || integer +LINE 1: SELECT '3'::agtype || 4; + ^ +HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +SELECT '3'::agtype || true; +ERROR: operator does not exist: agtype || boolean +LINE 1: SELECT '3'::agtype || true; + ^ +HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +-- +-- jsonb operators inside cypher queries +-- +SELECT create_graph('jsonb_operators'); +NOTICE: graph "jsonb_operators" has been created + create_graph +-------------- + +(1 row) + +SELECT * FROM cypher('jsonb_operators',$$CREATE ({list:['a', 'b', 'c'], json:{a:1, b:['a', 'b'], c:{d:'a'}}})$$) as (a agtype); + a +--- +(0 rows) + +-- +-- concat || operator +-- +SELECT * FROM cypher('jsonb_operators', $$ RETURN [1,2] || 2 $$) AS (result agtype); + result +----------- + [1, 2, 2] +(1 row) + +SELECT * FROM cypher('jsonb_operators', $$ RETURN true || false $$) AS (result agtype); + result +--------------- + [true, false] +(1 row) + +SELECT * FROM cypher('jsonb_operators', $$ RETURN true || false || {a: 'string'} $$) AS (result agtype); + result +-------------------------------- + [true, false, {"a": "string"}] +(1 row) + +SELECT * FROM cypher('jsonb_operators', $$ RETURN true || false || {a: 'string'} || true $$) AS (result agtype); + result +-------------------------------------- + [true, false, {"a": "string"}, true] +(1 row) + +SELECT * FROM cypher('jsonb_operators', $$ WITH [1,2,3] AS m WITH m, m || 'string' AS n RETURN n $$) AS (result agtype); + result +--------------------- + [1, 2, 3, "string"] +(1 row) + +SELECT * FROM cypher('jsonb_operators', $$ WITH [1,2,3] AS m WITH m, m || {a: 1::numeric} AS n RETURN n $$) AS (result agtype); + result +------------------------------ + [1, 2, 3, {"a": 1::numeric}] +(1 row) + +SELECT * FROM cypher('jsonb_operators', $$ WITH {a: [1,2,3]} AS m WITH m, m || {a: 1::numeric} AS n RETURN n $$) AS (result agtype); + result +------------------- + {"a": 1::numeric} +(1 row) + +SELECT * FROM cypher('jsonb_operators', $$ WITH {b: [1,2,3]} AS m WITH m, m || {a: 1::numeric} AS n RETURN n $$) AS (result agtype); + result +----------------------------------- + {"a": 1::numeric, "b": [1, 2, 3]} +(1 row) + +SELECT * FROM cypher('jsonb_operators', $$ MATCH(n) RETURN n || 1 || 'string' $$) AS (result agtype); + result +---------------------------------------------------------------------------------------------------------------------------------------------------------- + [{"id": 281474976710657, "label": "", "properties": {"json": {"a": 1, "b": ["a", "b"], "c": {"d": "a"}}, "list": ["a", "b", "c"]}}::vertex, 1, "string"] +(1 row) + +SELECT * FROM cypher('jsonb_operators', $$ MATCH(n) RETURN n || {list: [true, null]} $$) AS (result agtype); + result +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- + [{"id": 281474976710657, "label": "", "properties": {"json": {"a": 1, "b": ["a", "b"], "c": {"d": "a"}}, "list": ["a", "b", "c"]}}::vertex, {"list": [true, null]}] +(1 row) + +SELECT * FROM cypher('jsonb_operators', $$ MATCH (n) MATCH(m) RETURN n || m $$) AS (result agtype); + result +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + [{"id": 281474976710657, "label": "", "properties": {"json": {"a": 1, "b": ["a", "b"], "c": {"d": "a"}}, "list": ["a", "b", "c"]}}::vertex, {"id": 281474976710657, "label": "", "properties": {"json": {"a": 1, "b": ["a", "b"], "c": {"d": "a"}}, "list": ["a", "b", "c"]}}::vertex] +(1 row) + +SELECT * FROM cypher('jsonb_operators', $$ MATCH (n) RETURN n.list || [1, 2, 3] $$) AS (result agtype); + result +-------------------------- + ["a", "b", "c", 1, 2, 3] +(1 row) + +SELECT * FROM cypher('jsonb_operators', $$ MATCH (n) RETURN n.json || [1, 2, 3] $$) AS (result agtype); + result +------------------------------------------------------- + [{"a": 1, "b": ["a", "b"], "c": {"d": "a"}}, 1, 2, 3] +(1 row) + +SELECT * FROM cypher('jsonb_operators', $$ MATCH (n) RETURN n.json || n.json $$) AS (result agtype); + result +-------------------------------------------- + {"a": 1, "b": ["a", "b"], "c": {"d": "a"}} +(1 row) + +SELECT * FROM cypher('jsonb_operators', $$ MATCH (n) RETURN n.json || n $$) AS (result agtype); + result +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + [{"a": 1, "b": ["a", "b"], "c": {"d": "a"}}, {"id": 281474976710657, "label": "", "properties": {"json": {"a": 1, "b": ["a", "b"], "c": {"d": "a"}}, "list": ["a", "b", "c"]}}::vertex] +(1 row) + +-- should give an error +SELECT * FROM cypher('jsonb_operators', $$ RETURN true || {a: 'string'} || true $$) AS (result agtype); +ERROR: invalid left operand for agtype concatenation +SELECT * FROM cypher('jsonb_operators', $$ WITH 'b' AS m WITH m, m || {a: 1} AS n RETURN n $$) AS (result agtype); +ERROR: invalid left operand for agtype concatenation +SELECT * FROM cypher('jsonb_operators', $$ MATCH (n) RETURN n.json || 1 $$) AS (result agtype); +ERROR: invalid right operand for agtype concatenation +-- clean up +SELECT drop_graph('jsonb_operators', true); +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table jsonb_operators._ag_label_vertex +drop cascades to table jsonb_operators._ag_label_edge +NOTICE: graph "jsonb_operators" has been dropped + drop_graph +------------ + +(1 row) + diff --git a/regress/sql/jsonb_operators.sql b/regress/sql/jsonb_operators.sql new file mode 100644 index 00000000..6fbb872a --- /dev/null +++ b/regress/sql/jsonb_operators.sql @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +LOAD 'age'; +SET search_path TO ag_catalog; + +-- +-- jsonb operators in AGE (?, ?&, ?|, ->, ->>, #>, #>>, ||) +-- + +-- +-- concat || operator +-- +SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || '[0, 1]'::agtype as i) a; + +SELECT i, pg_typeof(i) FROM (SELECT '2'::agtype || '[0, 1]'::agtype as i) a; +SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || '2'::agtype as i) a; + +SELECT i, pg_typeof(i) FROM (SELECT '{"a": 1}'::agtype || '[0, 1]'::agtype as i) a; +SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || '{"a": 1}'::agtype as i) a; + +SELECT i, pg_typeof(i) FROM (SELECT '[]'::agtype || '[0, 1]'::agtype as i) a; +SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || '[]'::agtype as i) a; +SELECT i, pg_typeof(i) FROM (SELECT 'null'::agtype || '[0, 1]'::agtype as i) a; +SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || 'null'::agtype as i) a; +SELECT i, pg_typeof(i) FROM (SELECT '[null]'::agtype || '[0, 1]'::agtype as i) a; +SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || '[null]'::agtype as i) a; + +SELECT i, pg_typeof(i) FROM (SELECT NULL || '[0, 1]'::agtype as i) a; +SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || NULL as i) a; + +-- both operands are objects +SELECT '{"aa":1 , "b":2, "cq":3}'::agtype || '{"cq":"l", "b":"g", "fg":false}'; +SELECT '{"aa":1 , "b":2, "cq":3}'::agtype || '{"aq":"l"}'; +SELECT '{"aa":1 , "b":2, "cq":3}'::agtype || '{"aa":"l"}'; +SELECT '{"aa":1 , "b":2, "cq":3}'::agtype || '{}'; +SELECT '{"aa":1 , "b":2, "cq":3, "cj": {"fg": true}}'::agtype || '{"cq":"l", "b":"g", "fg":false}'; +SELECT '{"a": 13}'::agtype || '{"a": 13}'::agtype; +SELECT '{}'::agtype || '{"a":"b"}'::agtype; +SELECT '{}'::agtype || '{}'::agtype; + +-- both operands are arrays +SELECT '["a", "b"]'::agtype || '["c"]'; +SELECT '["a", "b"]'::agtype || '["c", "d"]'; +SELECT '["a", "b"]'::agtype || '["c", "d", "d"]'; +SELECT '["c"]' || '["a", "b"]'::agtype; +SELECT '[]'::agtype || '["a"]'::agtype; +SELECT '[]'::agtype || '[]'::agtype; + +SELECT '["a", "b"]'::agtype || '"c"'; +SELECT '"c"' || '["a", "b"]'::agtype; +SELECT '[]'::agtype || '"a"'::agtype; +SELECT '"b"'::agtype || '"a"'::agtype; +SELECT '3'::agtype || '[]'::agtype; +SELECT '3'::agtype || '4'::agtype; +SELECT '3'::agtype || '[4]'; +SELECT '3::numeric'::agtype || '[[]]'::agtype; +SELECT null::agtype || null::agtype; + +-- array and object as operands +SELECT '{"aa":1 , "b":2, "cq":3}'::agtype || '[{"aa":"l"}]'; +SELECT '{"aa":1 , "b":2, "cq":3}'::agtype || '[{"aa":"l", "aa": "k"}]'; +SELECT '{"a": 13}'::agtype || '[{"a": 13}]'::agtype; +SELECT '[]'::agtype || '{"a":"b"}'::agtype; +SELECT '{"a":"b"}'::agtype || '[]'::agtype; +SELECT '[]'::agtype || '{}'::agtype; +SELECT '[3]'::agtype || '{}'::agtype; +SELECT '{}'::agtype || '[null]'::agtype; +SELECT '[null]'::agtype || '{"a": null}'::agtype; +SELECT '""'::agtype || '[]'::agtype; + +-- vertex/edge/path as operand(s) +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b" : true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge'::agtype || '"id"'; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b" : true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge'::agtype || '"m"'; +SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b" : true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge'::agtype || '{"m": []}'; +SELECT '{"id": 844424930131969, "label": "v", "properties": {}}::vertex'::agtype || '{"id": 844424930131971, "label": "v", "properties": {"key": "value"}}::vertex'::agtype; +SELECT '{"id": 844424930131969, "label": "v", "properties": {}}::vertex'::agtype || '[]'::agtype; +SELECT '{"id": 844424930131969, "label": "v", "properties": {}}::vertex'::agtype || '{}'::agtype; +SELECT '{}'::agtype || '{"id": 844424930131969, "label": "v", "properties": {}}::vertex'::agtype; +SELECT '"id"'::agtype || '{"id": 844424930131969, "label": "v", "properties": {}}::vertex'::agtype; +SELECT '{"id": 844424930131969, "label": "v", "properties": {}}::vertex'::agtype || '{"id": 1688849860263950, "label": "e_var", "end_id": 281474976710662, "start_id": 281474976710661, "properties": {}}::edge'::agtype; +SELECT '[{"id": 281474976710672, "label": "", "properties": {}}::vertex, {"id": 1688849860263960, "label": "e_var", "end_id": 281474976710673, "start_id": 281474976710672, "properties": {}}::edge, {"id": 281474976710673, "label": "", "properties": {}}::vertex]::path'::agtype || '{"id": 844424930131969, "label": "v", "properties": {}}::vertex'::agtype; +SELECT '[{"id": 281474976710672, "label": "", "properties": {}}::vertex, {"id": 1688849860263960, "label": "e_var", "end_id": 281474976710673, "start_id": 281474976710672, "properties": {}}::edge, {"id": 281474976710673, "label": "", "properties": {}}::vertex]::path'::agtype || '[{"id": 281474976710672, "label": "", "properties": {}}::vertex, {"id": 1688849860263960, "label": "e_var", "end_id": 281474976710673, "start_id": 281474976710672, "properties": {}}::edge, {"id": 281474976710673, [...] + +-- using concat more than once in a query +SELECT '{}'::agtype || '{}'::agtype || '[{}]'::agtype; +SELECT '{"y": {}}'::agtype || '{"b": "5"}'::agtype || '{"a": {}}'::agtype || '{"z": []}'::agtype; +SELECT '{"y": {}}'::agtype || '{"b": "5"}'::agtype || '{"a": {}}'::agtype || '{"z": []}'::agtype || '[]'::agtype; +SELECT '{"y": {}}'::agtype || '{"b": "5"}'::agtype || '{"a": {}}'::agtype || '{"z": []}'::agtype || '[]'::agtype || '{}'; +SELECT '"e"'::agtype || '1'::agtype || '{}'::agtype; +SELECT ('"e"'::agtype || '1'::agtype) || '{"[]": "p"}'::agtype; +SELECT '{"{}": {"a": []}}'::agtype || '{"{}": {"[]": []}}'::agtype || '{"{}": {}}'::agtype; +SELECT '{}'::agtype || '{}'::agtype || '[{}]'::agtype || '[{}]'::agtype || '{}'::agtype; + +-- should give an error +SELECT '{"a": 13}'::agtype || 'null'::agtype; +SELECT '"a"'::agtype || '{"a":1}'; +SELECT '3'::agtype || '{}'::agtype; +SELECT '{"a":1}' || '"a"'::agtype; +SELECT '{"b": [1, 2, {"[{}, {}]": "a"}, {"1": {}}]}'::agtype || true::agtype; +SELECT '{"b": [1, 2, {"[{}, {}]": "a"}, {"1": {}}]}'::agtype || 'true'::agtype; +SELECT '{"b": [1, 2, {"[{}, {}]": "a"}, {"1": {}}]}'::agtype || age_agtype_sum('1', '2'); +SELECT ('{"a": "5"}'::agtype || '{"a": {}}'::agtype) || '5'::agtype; +SELECT ('{"a": "5"}'::agtype || '{"a": {}}'::agtype || '5') || '[5]'::agtype; +-- both operands have to be of agtype +SELECT '3'::agtype || 4; +SELECT '3'::agtype || true; + +-- +-- jsonb operators inside cypher queries +-- +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); + +-- +-- concat || operator +-- +SELECT * FROM cypher('jsonb_operators', $$ RETURN [1,2] || 2 $$) AS (result agtype); +SELECT * FROM cypher('jsonb_operators', $$ RETURN true || false $$) AS (result agtype); +SELECT * FROM cypher('jsonb_operators', $$ RETURN true || false || {a: 'string'} $$) AS (result agtype); +SELECT * FROM cypher('jsonb_operators', $$ RETURN true || false || {a: 'string'} || true $$) AS (result agtype); + +SELECT * FROM cypher('jsonb_operators', $$ WITH [1,2,3] AS m WITH m, m || 'string' AS n RETURN n $$) AS (result agtype); +SELECT * FROM cypher('jsonb_operators', $$ WITH [1,2,3] AS m WITH m, m || {a: 1::numeric} AS n RETURN n $$) AS (result agtype); +SELECT * FROM cypher('jsonb_operators', $$ WITH {a: [1,2,3]} AS m WITH m, m || {a: 1::numeric} AS n RETURN n $$) AS (result agtype); +SELECT * FROM cypher('jsonb_operators', $$ WITH {b: [1,2,3]} AS m WITH m, m || {a: 1::numeric} AS n RETURN n $$) AS (result agtype); + +SELECT * FROM cypher('jsonb_operators', $$ MATCH(n) RETURN n || 1 || 'string' $$) AS (result agtype); +SELECT * FROM cypher('jsonb_operators', $$ MATCH(n) RETURN n || {list: [true, null]} $$) AS (result agtype); +SELECT * FROM cypher('jsonb_operators', $$ MATCH (n) MATCH(m) RETURN n || m $$) AS (result agtype); +SELECT * FROM cypher('jsonb_operators', $$ MATCH (n) RETURN n.list || [1, 2, 3] $$) AS (result agtype); +SELECT * FROM cypher('jsonb_operators', $$ MATCH (n) RETURN n.json || [1, 2, 3] $$) AS (result agtype); +SELECT * FROM cypher('jsonb_operators', $$ MATCH (n) RETURN n.json || n.json $$) AS (result agtype); +SELECT * FROM cypher('jsonb_operators', $$ MATCH (n) RETURN n.json || n $$) AS (result agtype); + +-- should give an error +SELECT * FROM cypher('jsonb_operators', $$ RETURN true || {a: 'string'} || true $$) AS (result agtype); +SELECT * FROM cypher('jsonb_operators', $$ WITH 'b' AS m WITH m, m || {a: 1} AS n RETURN n $$) AS (result agtype); +SELECT * FROM cypher('jsonb_operators', $$ MATCH (n) RETURN n.json || 1 $$) AS (result agtype); + +-- clean up +SELECT drop_graph('jsonb_operators', true); \ No newline at end of file diff --git a/src/backend/parser/ag_scanner.l b/src/backend/parser/ag_scanner.l index 18ce042c..ffc6673c 100644 --- a/src/backend/parser/ag_scanner.l +++ b/src/backend/parser/ag_scanner.l @@ -227,6 +227,7 @@ 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 ">=" @@ -642,6 +643,14 @@ ag_token token; return token; } +{concat} { + update_location(); + token.type = AG_TOKEN_CONCAT; + 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 26908122..5d671624 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -75,7 +75,7 @@ %token <string> PARAMETER /* operators that have more than 1 character */ -%token NOT_EQ LT_EQ GT_EQ DOT_DOT TYPECAST PLUS_EQ EQ_TILDE +%token NOT_EQ LT_EQ GT_EQ DOT_DOT TYPECAST PLUS_EQ EQ_TILDE CONCAT /* keywords in alphabetical order */ %token <keyword> ALL ANALYZE AND AS ASC ASCENDING @@ -169,7 +169,7 @@ %left XOR %right NOT %left '=' NOT_EQ '<' LT_EQ '>' GT_EQ -%left '+' '-' +%left '+' '-' CONCAT %left '*' '/' '%' %left '^' %nonassoc IN IS @@ -1324,6 +1324,10 @@ expr: { $$ = build_comparison_expression($1, $3, ">=", @2); } + | expr CONCAT expr + { + $$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "||", $1, $3, @2); + } | expr '+' 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 487f5607..407d6c07 100644 --- a/src/backend/parser/cypher_parser.c +++ b/src/backend/parser/cypher_parser.c @@ -46,7 +46,8 @@ int cypher_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, ag_scanner_t scanner) DOT_DOT, TYPECAST, PLUS_EQ, - EQ_TILDE + EQ_TILDE, + CONCAT }; ag_token token; @@ -98,6 +99,7 @@ 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_CONCAT: break; case AG_TOKEN_TYPECAST: break; diff --git a/src/backend/utils/adt/agtype_ops.c b/src/backend/utils/adt/agtype_ops.c index 9ee2180b..829483a4 100644 --- a/src/backend/utils/adt/agtype_ops.c +++ b/src/backend/utils/adt/agtype_ops.c @@ -33,7 +33,7 @@ #include "utils/agtype.h" static void ereport_op_str(const char *op, agtype *lhs, agtype *rhs); -static agtype *agtype_concat(agtype *agt1, agtype *agt2); +static agtype *agtype_concat_impl(agtype *agt1, agtype *agt2); static agtype_value *iterator_concat(agtype_iterator **it1, agtype_iterator **it2, agtype_parse_state **state); @@ -162,7 +162,7 @@ Datum agtype_add(PG_FUNCTION_ARGS) (AGT_ROOT_IS_OBJECT(lhs) && AGT_ROOT_IS_OBJECT(rhs))) ereport_op_str("+", lhs, rhs); - agt = AGTYPE_P_GET_DATUM(agtype_concat(lhs, rhs)); + agt = AGTYPE_P_GET_DATUM(agtype_concat_impl(lhs, rhs)); PG_RETURN_DATUM(agt); } @@ -1156,7 +1156,26 @@ Datum agtype_exists_all(PG_FUNCTION_ARGS) PG_RETURN_BOOL(true); } -static agtype *agtype_concat(agtype *agt1, agtype *agt2) +PG_FUNCTION_INFO_V1(agtype_concat); + +Datum agtype_concat(PG_FUNCTION_ARGS) +{ + agtype *agt_lhs = AG_GET_ARG_AGTYPE_P(0); + agtype *agt_rhs = AG_GET_ARG_AGTYPE_P(1); + + /* + * Jsonb returns NULL for PG Null, but not for jsonb's NULL value, + * so we do the same. + */ + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + AG_RETURN_AGTYPE_P(agtype_concat_impl(agt_lhs, agt_rhs)); +} + +static agtype *agtype_concat_impl(agtype *agt1, agtype *agt2) { agtype_parse_state *state = NULL; agtype_value *res; @@ -1172,9 +1191,13 @@ static agtype *agtype_concat(agtype *agt1, agtype *agt2) if (AGT_ROOT_IS_OBJECT(agt1) == AGT_ROOT_IS_OBJECT(agt2)) { if (AGT_ROOT_COUNT(agt1) == 0 && !AGT_ROOT_IS_SCALAR(agt2)) + { return agt2; + } else if (AGT_ROOT_COUNT(agt2) == 0 && !AGT_ROOT_IS_SCALAR(agt1)) + { return agt1; + } } it1 = agtype_iterator_init(&agt1->root); @@ -1210,22 +1233,31 @@ static agtype_value *iterator_concat(agtype_iterator **it1, if (rk1 == WAGT_BEGIN_OBJECT && rk2 == WAGT_BEGIN_OBJECT) { /* - * Append the all tokens from v1 to res, except last WAGT_END_OBJECT + * Append all tokens from v1 to res, except last WAGT_END_OBJECT * (because res will not be finished yet). */ push_agtype_value(state, r1, NULL); + while ((r1 = agtype_iterator_next(it1, &v1, true)) != WAGT_END_OBJECT) + { + Assert(r1 == WAGT_KEY || r1 == WAGT_VALUE); push_agtype_value(state, r1, &v1); + } /* - * Append the all tokens from v2 to res, include last WAGT_END_OBJECT - * (the concatenation will be completed). + * Append all tokens from v2 to res, except last WAGT_END_OBJECT */ - while ((r2 = agtype_iterator_next(it2, &v2, true)) != 0) - res = push_agtype_value(state, r2, - r2 != WAGT_END_OBJECT ? &v2 : NULL); - } + while ((r2 = agtype_iterator_next(it2, &v2, true)) != WAGT_END_OBJECT) + { + Assert(r2 == WAGT_KEY || r2 == WAGT_VALUE); + push_agtype_value(state, r2, &v2); + } + /* + * Append the last token WAGT_END_OBJECT to complete res + */ + res = push_agtype_value(state, WAGT_END_OBJECT, NULL); + } /* * Both elements are arrays (either can be scalar). */ @@ -1242,11 +1274,10 @@ static agtype_value *iterator_concat(agtype_iterator **it1, while ((r2 = agtype_iterator_next(it2, &v2, true)) != WAGT_END_ARRAY) { Assert(r2 == WAGT_ELEM); - push_agtype_value(state, WAGT_ELEM, &v2); + push_agtype_value(state, r2, &v2); } - res = push_agtype_value(state, WAGT_END_ARRAY, - NULL /* signal to sort */); + res = push_agtype_value(state, WAGT_END_ARRAY, NULL); } /* have we got array || object or object || array? */ else if (((rk1 == WAGT_BEGIN_ARRAY && !(*it1)->is_scalar) && @@ -1264,36 +1295,120 @@ static agtype_value *iterator_concat(agtype_iterator **it1, if (prepend) { push_agtype_value(state, WAGT_BEGIN_OBJECT, NULL); - while ((r1 = agtype_iterator_next(it_object, &v1, true)) != 0) - push_agtype_value(state, r1, - r1 != WAGT_END_OBJECT ? &v1 : NULL); - while ((r2 = agtype_iterator_next(it_array, &v2, true)) != 0) - res = push_agtype_value(state, r2, - r2 != WAGT_END_ARRAY ? &v2 : NULL); + while ((r1 = agtype_iterator_next(it_object, &v1, true)) != + WAGT_END_OBJECT) + { + Assert(r1 == WAGT_KEY || r1 == WAGT_VALUE); + push_agtype_value(state, r1, &v1); + } + + push_agtype_value(state, WAGT_END_OBJECT, NULL); + + while ((r2 = agtype_iterator_next(it_array, &v2, true)) != + WAGT_END_ARRAY) + { + Assert(r2 == WAGT_ELEM); + push_agtype_value(state, r2, &v2); + } + + res = push_agtype_value(state, WAGT_END_ARRAY, NULL); } else { while ((r1 = agtype_iterator_next(it_array, &v1, true)) != WAGT_END_ARRAY) + { + Assert(r1 == WAGT_ELEM); push_agtype_value(state, r1, &v1); + } push_agtype_value(state, WAGT_BEGIN_OBJECT, NULL); - while ((r2 = agtype_iterator_next(it_object, &v2, true)) != 0) - push_agtype_value(state, r2, - r2 != WAGT_END_OBJECT ? &v2 : NULL); + + while ((r2 = agtype_iterator_next(it_object, &v2, true)) != + WAGT_END_OBJECT) + { + Assert(r2 == WAGT_KEY || r2 == WAGT_VALUE); + push_agtype_value(state, r2,&v2); + } + + push_agtype_value(state, WAGT_END_OBJECT, NULL); res = push_agtype_value(state, WAGT_END_ARRAY, NULL); } } + else if (rk1 == WAGT_BEGIN_OBJECT) + { + /* + * We have object || array. + */ + Assert(rk1 == WAGT_BEGIN_OBJECT); + Assert(rk2 == WAGT_BEGIN_ARRAY); + + push_agtype_value(state, WAGT_BEGIN_ARRAY, NULL); + push_agtype_value(state, WAGT_BEGIN_OBJECT, NULL); + + while ((r1 = agtype_iterator_next(it1, &v1, true)) != WAGT_END_OBJECT) + { + Assert(r1 == WAGT_KEY || r1 == WAGT_VALUE); + push_agtype_value(state, r1, &v1); + } + + push_agtype_value(state, WAGT_END_OBJECT, NULL); + + while ((r2 = agtype_iterator_next(it2, &v2, true)) != WAGT_END_ARRAY) + { + if (v2.type < AGTV_VERTEX || v2.type > AGTV_PATH) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid right operand for agtype " + "concatenation"))); + } + + Assert(r2 == WAGT_ELEM); + + push_agtype_value(state, r2, &v2); + } + + res = push_agtype_value(state, WAGT_END_ARRAY, NULL); + } else { /* - * This must be scalar || object or object || scalar, as that's all - * that's left. Both of these make no sense, so error out. + * We have array || object. */ - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid concatenation of agtype objects"))); + Assert(rk1 == WAGT_BEGIN_ARRAY); + Assert(rk2 == WAGT_BEGIN_OBJECT); + + push_agtype_value(state, WAGT_BEGIN_ARRAY, NULL); + + while ((r1 = agtype_iterator_next(it1, &v1, true)) != WAGT_END_ARRAY) + { + if (v1.type < AGTV_VERTEX || v1.type > AGTV_PATH) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid left operand for agtype " + "concatenation"))); + } + + Assert(r1 == WAGT_ELEM); + + push_agtype_value(state, r1, &v1); + } + + push_agtype_value(state, WAGT_BEGIN_OBJECT, NULL); + + while ((r2 = agtype_iterator_next(it2, &v2, true)) != WAGT_END_OBJECT) + { + Assert(r2 == WAGT_KEY || r2 == WAGT_VALUE); + push_agtype_value(state, r2, &v2); + } + + push_agtype_value(state, WAGT_END_OBJECT, NULL); + + res = push_agtype_value(state, WAGT_END_ARRAY, NULL); } return res; diff --git a/src/include/parser/ag_scanner.h b/src/include/parser/ag_scanner.h index d5ce9e9f..01087dc1 100644 --- a/src/include/parser/ag_scanner.h +++ b/src/include/parser/ag_scanner.h @@ -46,7 +46,8 @@ typedef enum ag_token_type AG_TOKEN_TYPECAST, AG_TOKEN_PLUS_EQ, AG_TOKEN_EQ_TILDE, - AG_TOKEN_CHAR + AG_TOKEN_CONCAT, + AG_TOKEN_CHAR, } ag_token_type; /*