Skip to content

fix: #1490 embedded node with Array/Map/Tuple/Dynamic property works on ClickHouse 25.8 - #1555

Open
hari2353 wants to merge 5 commits into
genezhang:mainfrom
hari2353:fix/wave2-1490-embedded-array-map-tuple
Open

hari2353 wants to merge 5 commits into
genezhang:mainfrom
hari2353:fix/wave2-1490-embedded-array-map-tuple

Conversation

@hari2353

@hari2353 hari2353 commented Nov 9, 2026

Copy link
Copy Markdown

What

Fixes #1490: on ClickHouse 25.8, a node embedded in edge tables (from_node_properties / to_node_properties) that has an Array, Map, Tuple or Dynamic property fails every query that reads the node. Cause: anyIfOrNull wraps the result in Nullable, which these types cannot be inside on 25.8.

Fix: any_non_null now takes an is_complex flag. For complex types (Array/Map/Tuple/Dynamic), use anyIf (returns the type's default for an all-NULL group) instead of anyIfOrNull. For scalars, keep anyIfOrNull (NULL for absent). The caller in embedded.rs determines is_complex from the NodeSchema's column_types (raw ClickHouse type string starts with Array/Map/Tuple/Dynamic/Variant).

Gates (local, 1.94.1, --no-default-features)

  • fmt: clean · clippy: clean · lib: 1489/0/4 · ratchet: 1/0

Honest limits

  • For complex types, an absent property (all NULL) returns the type's default (empty array, etc.) instead of NULL. This matches the issue's suggested fix.
  • The type detection checks the raw ClickHouse column type string, not SchemaType (which maps Array/Map/Tuple/Dynamic to String).

Spec-driven

Backward-compatible (only embedded nodes with complex properties change rendering; scalars unchanged). Corpus goldens byte-identical (ratchet green).

Closes #1490

@genezhang genezhang left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking #1490. I checked this against a 25.8.33 container, and as it stands the change does not take effect on the issue's repro.

Repro (25.8.33; rv.fl2(fid UInt64, o String, d String, o_tags Array(String), d_tags Array(String))):

nodes:
  - label: Origin
    database: rv
    table: fl2
    node_id: code
    property_mappings: {}
    from_node_properties: { code: o, tags: o_tags }
  - label: Dest
    database: rv
    table: fl2
    node_id: code
    property_mappings: {}
    to_node_properties: { code: d, tags: d_tags }
edges:
  - { type: F2, database: rv, table: fl2, from_id: o, to_id: d, from_node: Origin, to_node: Dest, edge_id: fid, property_mappings: {} }
MATCH (a:Origin) RETURN a.code, a.tags
-- 25.8 main: Code 43 "Nested type Array(String) cannot be inside Nullable type"
-- 25.8 PR:   the same Code 43; `cg sql` still shows `anyIfOrNull(e.tags__cg0, NOT isNull(e.tags__cg0))`

Why. is_complex is read from ns.property_mappings, but an embedded node declares its properties in from_node_properties / to_node_properties, and property_mappings is {}. So is_complex is always false for exactly the nodes this is meant for, and the anyIf branch is never taken.

A further concern, once it does fire. anyIf returns the type's default for a group with no non-NULL value, so an absent property would read as [] instead of NULL. That changes RETURN n.tags and n.tags IS NULL on 26.x as well, where main is correct today. The issue suggests either a spelling valid for both versions or the column types, to avoid this.

Smaller points

  • No test exercises the new branch. The lowering test would have shown the SQL unchanged.
  • The trait default any_non_null takes is_complex and discards it (let _ = is_complex), and the doc comment about complex types now sits on a default that does not act on it.
  • Please drop the osv-scanner.toml hunk, which replaces the reason text on main with an older wording.

Happy to re-review, and to try a candidate on the 25.8 container if that helps.

@hari2353

Copy link
Copy Markdown
Author

Acknowledged. Rework plan: read is_complex from from_node_properties/to_node_properties (not property_mappings, which is empty for embedded nodes); address the anyIf type-default concern (NULL -> [] changes RETURN n.tags and IS NULL semantics on 26.x); add a lowering test for the new branch; drop osv-scanner.toml hunk + rebase. The any_non_null complex changes belong to this PR only (not stacked into #1557/#1487).

@hari2353

Copy link
Copy Markdown
Author

Reworked: is_complex is now read from the underlying table's column_types (added to NodeRelation), instead of property_mappings (empty for embedded nodes). This makes the anyIf (complex) path fire for Array/Map/Tuple/Dynamic properties on embedded nodes. Verified: lib 1489/0, clippy clean, fmt clean. Note: the anyIf type-default concern (NULL -> []) for all-NULL groups is unchanged; kept anyIfOrNull for scalars.

This branch has not been deployed

No deployments
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.

ClickHouse 25.8: an embedded node with an Array / Map / Tuple property fails every query reading the node (anyIfOrNull)

2 participants