Skip to content

Commit b9d35d5

Browse files
jrgemignaniMuhammadTahaNaveed
authored andcommitted
Fix ORDER BY alias resolution with AS in Cypher queries (#2269)
NOTE: This PR was partially created with AI tools and reviewed by a human. ORDER BY clauses failed when referencing column aliases from RETURN: MATCH (p:Person) RETURN p.age AS age ORDER BY age DESC ERROR: could not find rte for age Added SQL-99 compliant alias matching to find_target_list_entry() that checks if ORDER BY identifier matches a target list alias before attempting expression transformation. This enables standard SQL behavior for sorting by aliased columns with DESC/DESCENDING/ASC/ASCENDING. Updated regression tests. Added regression tests. modified: regress/expected/cypher_match.out modified: regress/expected/expr.out modified: regress/sql/expr.sql modified: src/backend/parser/cypher_clause.c
1 parent 419eb13 commit b9d35d5

File tree

4 files changed

+156
-12
lines changed

4 files changed

+156
-12
lines changed

regress/expected/cypher_match.out

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1655,10 +1655,10 @@ SELECT * FROM cypher('cypher_match', $$
16551655
$$) AS (u agtype, m agtype, l agtype);
16561656
u | m | l
16571657
------------+---------------+------------
1658-
"someone" | "opt_match_e" | "somebody"
1659-
"somebody" | "opt_match_e" | "someone"
16601658
"anybody" | "opt_match_e" | "nobody"
16611659
"nobody" | "opt_match_e" | "anybody"
1660+
"somebody" | "opt_match_e" | "someone"
1661+
"someone" | "opt_match_e" | "somebody"
16621662
(4 rows)
16631663

16641664
SELECT * FROM cypher('cypher_match', $$
@@ -1670,8 +1670,8 @@ SELECT * FROM cypher('cypher_match', $$
16701670
$$) AS (n agtype, r agtype, p agtype, m agtype, s agtype, q agtype);
16711671
n | r | p | m | s | q
16721672
-----------+---------------+------------+-----------+---------------+------------
1673-
"someone" | "opt_match_e" | "somebody" | "anybody" | "opt_match_e" | "nobody"
16741673
"anybody" | "opt_match_e" | "nobody" | "someone" | "opt_match_e" | "somebody"
1674+
"someone" | "opt_match_e" | "somebody" | "anybody" | "opt_match_e" | "nobody"
16751675
(2 rows)
16761676

16771677
SELECT * FROM cypher('cypher_match', $$
@@ -1684,18 +1684,18 @@ SELECT * FROM cypher('cypher_match', $$
16841684
$$) AS (n agtype, r agtype, p agtype, m agtype, s agtype, q agtype);
16851685
n | r | p | m | s | q
16861686
------------+---------------+------------+------------+---------------+------------
1687-
"someone" | "opt_match_e" | "somebody" | "anybody" | "opt_match_e" | "nobody"
1688-
"someone" | | | "somebody" | |
1689-
"someone" | | | "nobody" | |
1690-
"somebody" | | | "someone" | |
1691-
"somebody" | | | "anybody" | |
1692-
"somebody" | | | "nobody" | |
16931687
"anybody" | "opt_match_e" | "nobody" | "someone" | "opt_match_e" | "somebody"
1694-
"anybody" | | | "somebody" | |
16951688
"anybody" | | | "nobody" | |
1696-
"nobody" | | | "someone" | |
1697-
"nobody" | | | "somebody" | |
1689+
"anybody" | | | "somebody" | |
16981690
"nobody" | | | "anybody" | |
1691+
"nobody" | | | "somebody" | |
1692+
"nobody" | | | "someone" | |
1693+
"somebody" | | | "anybody" | |
1694+
"somebody" | | | "nobody" | |
1695+
"somebody" | | | "someone" | |
1696+
"someone" | "opt_match_e" | "somebody" | "anybody" | "opt_match_e" | "nobody"
1697+
"someone" | | | "nobody" | |
1698+
"someone" | | | "somebody" | |
16991699
(12 rows)
17001700

17011701
-- Tests to catch match following optional match logic

regress/expected/expr.out

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6949,6 +6949,94 @@ $$) AS (i agtype);
69496949
{"key": "value"}
69506950
(9 rows)
69516951

6952+
--
6953+
-- Test ORDER BY with AS
6954+
--
6955+
SELECT * FROM cypher('order_by', $$ CREATE ({name: 'John', age: 38}) $$) AS (result agtype);
6956+
result
6957+
--------
6958+
(0 rows)
6959+
6960+
SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Jill', age: 23}) $$) AS (result agtype);
6961+
result
6962+
--------
6963+
(0 rows)
6964+
6965+
SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Ion', age: 34}) $$) AS (result agtype);
6966+
result
6967+
--------
6968+
(0 rows)
6969+
6970+
SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Mary', age: 57}) $$) AS (result agtype);
6971+
result
6972+
--------
6973+
(0 rows)
6974+
6975+
SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Jerry', age: 34}) $$) AS (result agtype);
6976+
result
6977+
--------
6978+
(0 rows)
6979+
6980+
SELECT * FROM cypher('order_by', $$
6981+
MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY name
6982+
$$) AS (name agtype, age agtype);
6983+
name | age
6984+
---------+-----
6985+
"Ion" | 34
6986+
"Jerry" | 34
6987+
"Jill" | 23
6988+
"John" | 38
6989+
"Mary" | 57
6990+
(5 rows)
6991+
6992+
SELECT * FROM cypher('order_by', $$
6993+
MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY name ASC
6994+
$$) AS (name agtype, age agtype);
6995+
name | age
6996+
---------+-----
6997+
"Ion" | 34
6998+
"Jerry" | 34
6999+
"Jill" | 23
7000+
"John" | 38
7001+
"Mary" | 57
7002+
(5 rows)
7003+
7004+
SELECT * FROM cypher('order_by', $$
7005+
MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY name DESC
7006+
$$) AS (name agtype, age agtype);
7007+
name | age
7008+
---------+-----
7009+
"Mary" | 57
7010+
"John" | 38
7011+
"Jill" | 23
7012+
"Jerry" | 34
7013+
"Ion" | 34
7014+
(5 rows)
7015+
7016+
SELECT * FROM cypher('order_by', $$
7017+
MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY age ASC, name DESCENDING
7018+
$$) AS (name agtype, age agtype);
7019+
name | age
7020+
---------+-----
7021+
"Jill" | 23
7022+
"Jerry" | 34
7023+
"Ion" | 34
7024+
"John" | 38
7025+
"Mary" | 57
7026+
(5 rows)
7027+
7028+
SELECT * FROM cypher('order_by', $$
7029+
MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY age DESC, name ASCENDING
7030+
$$) AS (name agtype, age agtype);
7031+
name | age
7032+
---------+-----
7033+
"Mary" | 57
7034+
"John" | 38
7035+
"Ion" | 34
7036+
"Jerry" | 34
7037+
"Jill" | 23
7038+
(5 rows)
7039+
69527040
--CASE
69537041
SELECT create_graph('case_statement');
69547042
NOTICE: graph "case_statement" has been created

regress/sql/expr.sql

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2823,6 +2823,7 @@ SELECT * FROM cypher('order_by', $$CREATE ({i: false})$$) AS (result agtype);
28232823
SELECT * FROM cypher('order_by', $$CREATE ({i: {key: 'value'}})$$) AS (result agtype);
28242824
SELECT * FROM cypher('order_by', $$CREATE ({i: [1]})$$) AS (result agtype);
28252825

2826+
28262827
SELECT * FROM cypher('order_by', $$
28272828
MATCH (u)
28282829
RETURN u.i
@@ -2835,6 +2836,35 @@ SELECT * FROM cypher('order_by', $$
28352836
ORDER BY u.i DESC
28362837
$$) AS (i agtype);
28372838

2839+
--
2840+
-- Test ORDER BY with AS
2841+
--
2842+
SELECT * FROM cypher('order_by', $$ CREATE ({name: 'John', age: 38}) $$) AS (result agtype);
2843+
SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Jill', age: 23}) $$) AS (result agtype);
2844+
SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Ion', age: 34}) $$) AS (result agtype);
2845+
SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Mary', age: 57}) $$) AS (result agtype);
2846+
SELECT * FROM cypher('order_by', $$ CREATE ({name: 'Jerry', age: 34}) $$) AS (result agtype);
2847+
2848+
SELECT * FROM cypher('order_by', $$
2849+
MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY name
2850+
$$) AS (name agtype, age agtype);
2851+
2852+
SELECT * FROM cypher('order_by', $$
2853+
MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY name ASC
2854+
$$) AS (name agtype, age agtype);
2855+
2856+
SELECT * FROM cypher('order_by', $$
2857+
MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY name DESC
2858+
$$) AS (name agtype, age agtype);
2859+
2860+
SELECT * FROM cypher('order_by', $$
2861+
MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY age ASC, name DESCENDING
2862+
$$) AS (name agtype, age agtype);
2863+
2864+
SELECT * FROM cypher('order_by', $$
2865+
MATCH (u) WHERE EXISTS(u.name) RETURN u.name AS name, u.age AS age ORDER BY age DESC, name ASCENDING
2866+
$$) AS (name agtype, age agtype);
2867+
28382868
--CASE
28392869
SELECT create_graph('case_statement');
28402870
SELECT * FROM cypher('case_statement', $$CREATE ({id: 1, i: 1, j: null})-[:connected_to {id: 1, k:0}]->({id: 2, i: 'a', j: 'b'})$$) AS (result agtype);

src/backend/parser/cypher_clause.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2296,6 +2296,32 @@ static TargetEntry *find_target_list_entry(cypher_parsestate *cpstate,
22962296
ListCell *lt;
22972297
TargetEntry *te;
22982298

2299+
/*
2300+
* If the ORDER BY item is a simple identifier, check if it matches
2301+
* an alias in the target list. This implements SQL99-compliant
2302+
* alias matching for ORDER BY clauses.
2303+
*/
2304+
if (IsA(node, ColumnRef))
2305+
{
2306+
ColumnRef *cref = (ColumnRef *)node;
2307+
2308+
if (list_length(cref->fields) == 1)
2309+
{
2310+
char *name = strVal(linitial(cref->fields));
2311+
2312+
/* Try to match an alias in the target list */
2313+
foreach (lt, *target_list)
2314+
{
2315+
te = lfirst(lt);
2316+
2317+
if (te->resname != NULL && strcmp(te->resname, name) == 0)
2318+
{
2319+
return te;
2320+
}
2321+
}
2322+
}
2323+
}
2324+
22992325
expr = transform_cypher_expr(cpstate, node, expr_kind);
23002326

23012327
foreach (lt, *target_list)

0 commit comments

Comments
 (0)