This is an automated email from the ASF dual-hosted git repository.
jgemignani pushed a commit to branch PG12
in repository https://gitbox.apache.org/repos/asf/age.git
The following commit(s) were added to refs/heads/PG12 by this push:
new b28d06a6 Add concat || operator to agtype (#1215)
b28d06a6 is described below
commit b28d06a6978a6d8cd94fbe9a5eb22b1ea19e54ec
Author: Zainab Saad <[email protected]>
AuthorDate: Fri Sep 8 04:27:28 2023 +0500
Add concat || operator to agtype (#1215)
- 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 | 13 +
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, 911 insertions(+), 30 deletions(-)
diff --git a/Makefile b/Makefile
index facf7b46..5d1dac43 100644
--- a/Makefile
+++ b/Makefile
@@ -98,6 +98,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 4bfca4fb..0a6a07be 100644
--- a/age--1.4.0.sql
+++ b/age--1.4.0.sql
@@ -1390,6 +1390,19 @@ 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
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 68b15a22..a6439bc4 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 ">="
@@ -644,6 +645,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 fd01174b..9c5777b9 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
@@ -1326,6 +1326,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;
/*