Skip to content

TUR-118: Cypher: answer null for an ordering between types with no order - #1181

Merged
rjb32 merged 4 commits into
mainfrom
remyboutonnet/tur-118-query-a-cross-type-against-an-unwind-value-orders-by-type
Oct 23, 2026
Merged

rjb32 merged 4 commits into
mainfrom
remyboutonnet/tur-118-query-a-cross-type-against-an-unwind-value-orders-by-type

Conversation

@rjb32

@rjb32 rjb32 commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Answer null for <, <=, > and >= between values of types with no order between them.

UNWIND [1, 'a', [2]] AS x WITH x WHERE x < 'b' RETURN x    a
UNWIND [1, 'a'] AS x WITH x WHERE x < 5 RETURN x           1

RETURN 1 < 'a'                                             null    was ANALYZE ERROR
RETURN (1 < 2) < 3                                         null    was ANALYZE ERROR
RETURN {a: 1} < {a: 2}                                     null    was ANALYZE ERROR
MATCH (n) WHERE n.name < 5 RETURN n                        0 rows  was ANALYZE ERROR
WITH {k: 1, j: 'a'} AS m RETURN m.k < 5, m.j < 5           true, null    was ANALYZE ERROR
UNWIND [{k: 1}, {k: 'a'}] AS m UNWIND [1, 'b'] AS x
  RETURN m.k = x                                           true, false, false, false    was ANALYZE ERROR

@rjb32

rjb32 commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

Note

AI-generated summary.

Summary

Where an ordering is decided: the analyzer and codegen consult one type rule, codegen emits a null for types with no order, and the storage kernels order map values like list cells

The two TUR-118 queries already answered correctly after 52fd65e (TUR-124). They are pinned here. The rest of the PR fixes the same openCypher rule where it still failed: an ordering across types was an analyzer error instead of null.

ordersAsDisjointTypes in EvaluatedType.h is the one rule both the analyzer and codegen read:

ordersAsDisjointTypes(a, b)
  either side not a value (label, property type, ...)  -> false   analyzer still rejects
  both tagged cells (ListItem, MapValue)               -> false   runtime decides per row
  one tagged cell                                      -> the other side has no order
  otherwise                                            -> no order class, or two different ones

order classes: number (Integer, Double), string (String, Char), Bool, DateTime, Duration, List
no order:      Map, Embedding, NodePattern, EdgePattern, GraphPath
 ExprAnalyzer  < <= > >=
-  allowlist of 8 same-type pairs + 9 ListItem pairs, else ANALYZE ERROR
+  any two ordering operands -> Bool

 DBProgramGenerator::translateBinaryExpr
-  if comparesAgainstNull            -> nullConstantColumn
+  if comparesAgainstNull || ordersAsDisjointTypes -> nullConstantColumn
   else                              -> db.lt / db.gt / db.lte / db.gte

Map values were rejected by every ordering operator, even against their own type. They now go through the same kernels as list cells. The comparisonOrder bodies in ListElementOrder.cpp became templates over the existing tagOf/valueOf, so list elements and map entries share one implementation. Map-value equality also gains the pairs it was missing: a list cell, a datetime and a duration.

Evidence

  • Before (v3 shell, cc9dbee09):

    RETURN 1 < 'a'                                      Operands are not valid or compatible numeric types: 'Integer' and 'String'
    RETURN {a: 1} < {a: 2}                              ... 'Map' and 'Map'
    WITH {k: 1} AS m RETURN m.k < 5                     ... 'MapValue' and 'Integer'
    UNWIND [{k: 1}, 'a'] AS x RETURN x < {k: 2}         ... 'ListItem' and 'Map'
    UNWIND [{k: 1}] AS m UNWIND [1] AS x RETURN m.k = x Operands are not valid or compatible types: 'MapValue' and 'ListItem'
    

    test_query_ir_cross_type_ordering: 20 of 20 failed before the fix.

  • After:

    test_query_ir_cross_type_ordering      21 passed
    test_query_ir_tagged_cell_comparison   33 passed   (+2: both TUR-118 queries)
    ctest                                  864 of 864 passed
    

    Three existing expectations changed, because they pinned the old rejection:

    ExpressionTest.BinaryExpressionTest                    6 cross-type orderings: INVALID -> Bool
    return-chained-comparison-link-error.json  -> -null    RETURN 0 < 1 < 2 < 3 < 'x'   null
    return-chained-comparison-parens.json      -> -null    RETURN (1 < 2) < 3           null
    

    make run_regress was not run, since it does not exercise v3.

Merge Danger

Door: two-way

Revert the commit and the analyzer rejects these queries again. No storage format, wire format or enum ordinal changes.

Blast Radius: queries

Queries that used to fail at analysis now run and return null. A WHERE built on one of them now keeps no rows instead of raising an error. ORDER BY is untouched: it still sorts across types by openCypher orderability.

@rjb32
rjb32 merged commit f401a9f into main Oct 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant