Skip to content

Commit c1ce37e

Browse files
gregfeliceclaude
andcommitted
Address Copilot review: fix comment, add regression tests for jsonb casts
- Fix comment in agtype_coercions.sql: document json-intermediate path (agtype_to_json -> json::jsonb) instead of incorrect "text intermediate" - Add agtype_jsonb_cast regression test covering: string/null/array/object agtype->jsonb, all jsonb scalar types->agtype, roundtrips, vertex/edge ->jsonb with structural key checks, NULL handling - Register agtype_jsonb_cast in Makefile REGRESS list Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 600c82f commit c1ce37e

File tree

4 files changed

+402
-3
lines changed

4 files changed

+402
-3
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ REGRESS = scan \
114114
pattern_expression \
115115
map_projection \
116116
direct_field_access \
117+
agtype_jsonb_cast \
117118
security
118119

119120
ifneq ($(EXTRA_TESTS),)
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
LOAD 'age';
20+
SET search_path TO ag_catalog;
21+
--
22+
-- agtype -> jsonb casts
23+
--
24+
-- String scalar
25+
SELECT '"hello"'::agtype::jsonb;
26+
jsonb
27+
---------
28+
"hello"
29+
(1 row)
30+
31+
-- Null
32+
SELECT 'null'::agtype::jsonb;
33+
jsonb
34+
-------
35+
null
36+
(1 row)
37+
38+
-- Array
39+
SELECT '[1, "two", null]'::agtype::jsonb;
40+
jsonb
41+
------------------
42+
[1, "two", null]
43+
(1 row)
44+
45+
-- Nested array
46+
SELECT '[[1, 2], [3, 4]]'::agtype::jsonb;
47+
jsonb
48+
------------------
49+
[[1, 2], [3, 4]]
50+
(1 row)
51+
52+
-- Empty array
53+
SELECT '[]'::agtype::jsonb;
54+
jsonb
55+
-------
56+
[]
57+
(1 row)
58+
59+
-- Object
60+
SELECT '{"name": "Alice", "age": 30}'::agtype::jsonb;
61+
jsonb
62+
------------------------------
63+
{"age": 30, "name": "Alice"}
64+
(1 row)
65+
66+
-- Nested object
67+
SELECT '{"a": {"b": {"c": 1}}}'::agtype::jsonb;
68+
jsonb
69+
------------------------
70+
{"a": {"b": {"c": 1}}}
71+
(1 row)
72+
73+
-- Object with array values
74+
SELECT '{"tags": ["a", "b"], "count": 2}'::agtype::jsonb;
75+
jsonb
76+
----------------------------------
77+
{"tags": ["a", "b"], "count": 2}
78+
(1 row)
79+
80+
-- Empty object
81+
SELECT '{}'::agtype::jsonb;
82+
jsonb
83+
-------
84+
{}
85+
(1 row)
86+
87+
--
88+
-- jsonb -> agtype casts
89+
--
90+
-- String scalar
91+
SELECT '"hello"'::jsonb::agtype;
92+
agtype
93+
---------
94+
"hello"
95+
(1 row)
96+
97+
-- Numeric scalar
98+
SELECT '42'::jsonb::agtype;
99+
agtype
100+
--------
101+
42
102+
(1 row)
103+
104+
-- Float scalar
105+
SELECT '3.14'::jsonb::agtype;
106+
agtype
107+
--------
108+
3.14
109+
(1 row)
110+
111+
-- Boolean
112+
SELECT 'true'::jsonb::agtype;
113+
agtype
114+
--------
115+
true
116+
(1 row)
117+
118+
-- Null
119+
SELECT 'null'::jsonb::agtype;
120+
agtype
121+
--------
122+
null
123+
(1 row)
124+
125+
-- Array
126+
SELECT '[1, "two", null]'::jsonb::agtype;
127+
agtype
128+
------------------
129+
[1, "two", null]
130+
(1 row)
131+
132+
-- Nested array
133+
SELECT '[[1, 2], [3, 4]]'::jsonb::agtype;
134+
agtype
135+
------------------
136+
[[1, 2], [3, 4]]
137+
(1 row)
138+
139+
-- Empty array
140+
SELECT '[]'::jsonb::agtype;
141+
agtype
142+
--------
143+
[]
144+
(1 row)
145+
146+
-- Object
147+
SELECT '{"name": "Alice", "age": 30}'::jsonb::agtype;
148+
agtype
149+
------------------------------
150+
{"age": 30, "name": "Alice"}
151+
(1 row)
152+
153+
-- Nested object
154+
SELECT '{"a": {"b": {"c": 1}}}'::jsonb::agtype;
155+
agtype
156+
------------------------
157+
{"a": {"b": {"c": 1}}}
158+
(1 row)
159+
160+
-- Empty object
161+
SELECT '{}'::jsonb::agtype;
162+
agtype
163+
--------
164+
{}
165+
(1 row)
166+
167+
--
168+
-- Roundtrip: jsonb -> agtype -> jsonb
169+
--
170+
SELECT ('{"key": "value"}'::jsonb::agtype)::jsonb;
171+
jsonb
172+
------------------
173+
{"key": "value"}
174+
(1 row)
175+
176+
SELECT ('[1, 2, 3]'::jsonb::agtype)::jsonb;
177+
jsonb
178+
-----------
179+
[1, 2, 3]
180+
(1 row)
181+
182+
SELECT ('null'::jsonb::agtype)::jsonb;
183+
jsonb
184+
-------
185+
null
186+
(1 row)
187+
188+
--
189+
-- Graph data -> jsonb (vertex and edge)
190+
--
191+
SELECT create_graph('agtype_jsonb_test');
192+
NOTICE: graph "agtype_jsonb_test" has been created
193+
create_graph
194+
--------------
195+
196+
(1 row)
197+
198+
SELECT * FROM cypher('agtype_jsonb_test', $$
199+
CREATE (a:Person {name: 'Alice', age: 30})-[:KNOWS {since: 2020}]->(b:Person {name: 'Bob', age: 25})
200+
RETURN a, b
201+
$$) AS (a agtype, b agtype);
202+
a | b
203+
------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------
204+
{"id": 844424930131969, "label": "Person", "properties": {"age": 30, "name": "Alice"}}::vertex | {"id": 844424930131970, "label": "Person", "properties": {"age": 25, "name": "Bob"}}::vertex
205+
(1 row)
206+
207+
-- Vertex to jsonb: check structure
208+
SELECT v::jsonb ? 'label' AS has_label,
209+
v::jsonb ? 'properties' AS has_properties,
210+
(v::jsonb -> 'properties' ->> 'name') AS name
211+
FROM cypher('agtype_jsonb_test', $$
212+
MATCH (n:Person) RETURN n ORDER BY n.name
213+
$$) AS (v agtype);
214+
has_label | has_properties | name
215+
-----------+----------------+-------
216+
t | t | Alice
217+
t | t | Bob
218+
(2 rows)
219+
220+
-- Edge to jsonb: check structure
221+
SELECT e::jsonb ? 'label' AS has_label,
222+
(e::jsonb ->> 'label') AS label,
223+
(e::jsonb -> 'properties' ->> 'since') AS since
224+
FROM cypher('agtype_jsonb_test', $$
225+
MATCH ()-[r:KNOWS]->() RETURN r
226+
$$) AS (e agtype);
227+
has_label | label | since
228+
-----------+-------+-------
229+
t | KNOWS | 2020
230+
(1 row)
231+
232+
--
233+
-- NULL handling
234+
--
235+
SELECT NULL::agtype::jsonb;
236+
jsonb
237+
-------
238+
239+
(1 row)
240+
241+
SELECT NULL::jsonb::agtype;
242+
agtype
243+
--------
244+
245+
(1 row)
246+
247+
--
248+
-- Cleanup
249+
--
250+
SELECT drop_graph('agtype_jsonb_test', true);
251+
NOTICE: drop cascades to 4 other objects
252+
DETAIL: drop cascades to table agtype_jsonb_test._ag_label_vertex
253+
drop cascades to table agtype_jsonb_test._ag_label_edge
254+
drop cascades to table agtype_jsonb_test."Person"
255+
drop cascades to table agtype_jsonb_test."KNOWS"
256+
NOTICE: graph "agtype_jsonb_test" has been dropped
257+
drop_graph
258+
------------
259+
260+
(1 row)
261+

0 commit comments

Comments
 (0)