What happens
A [*] path mapped to a to-many primitive property returns one row holding the array's text, rather than a collection.
Class wild::Row
{
id: Integer[1];
names: String[*];
}
names: extractFromSemiStructured([wild::store::DB]T.DOC, 'divisions[*].name', 'VARCHAR[]')
Against {"divisions":[{"name":"Alpha"},{"name":"Beta"}]}:
| query |
result |
project(~[id, v: r | $r.names]) |
one row, "[\"Alpha\",\"Beta\"]" |
project(~[id, v: r | $r.names->joinStrings(',')]) |
one row, "[Alpha, Beta]" |
Expected: two rows, Alpha and Beta.
Why it matters
No error is raised at compile or run time. A modeller declares String[*], sees an array-shaped value in a spot check, and ships a property that is actually a single string. The rendering differs depending on how the property is queried, which makes it easier still to mistake for a working collection.
Why it happens
Multiplicity in a relational mapping comes from row cardinality. explodeSemiStructured produces rows, so a to-many through a join works normally — meta::relational::tests::semistructured::nested::testNestedExplode maps teams: Team[*] that way and returns one row per (division, team) on DuckDB, Snowflake and Databricks.
A [*] path produces a value: one row with an array in one column. There are no rows for the to-many to range over, so the array is handed back as a single value.
What would be reasonable
Either of these would be an improvement over the current silence:
- Reject it at compile time — a to-many property mapped to an array-valued expression is not expressible today, and saying so is better than a wrong answer.
- Support it — fan the array out to rows, so
[*] bound to a to-many property behaves the way a modeller reading the mapping would expect. This is the outcome that was expected when the gap was found.
Option 2 is the useful one; option 1 is worth having in the meantime, since the failure is currently invisible.
Where this came from
Found while adding [*] wildcard path support to the relational store language (#5097). The wildcard work itself is complete and tested — this is specifically about binding the result to a to-many property.
Reproduction is one property mapping; the models in legend-engine-xt-relationalStore-emit/src/test/resources/relational-emit-models/relational-semistructured-wildcard and core_relational/relational/tests/semistructured/model/wildcardPathInStoreLanguage.legend provide the surrounding setup.
What happens
A
[*]path mapped to a to-many primitive property returns one row holding the array's text, rather than a collection.Against
{"divisions":[{"name":"Alpha"},{"name":"Beta"}]}:project(~[id, v: r | $r.names])"[\"Alpha\",\"Beta\"]"project(~[id, v: r | $r.names->joinStrings(',')])"[Alpha, Beta]"Expected: two rows,
AlphaandBeta.Why it matters
No error is raised at compile or run time. A modeller declares
String[*], sees an array-shaped value in a spot check, and ships a property that is actually a single string. The rendering differs depending on how the property is queried, which makes it easier still to mistake for a working collection.Why it happens
Multiplicity in a relational mapping comes from row cardinality.
explodeSemiStructuredproduces rows, so a to-many through a join works normally —meta::relational::tests::semistructured::nested::testNestedExplodemapsteams: Team[*]that way and returns one row per (division, team) on DuckDB, Snowflake and Databricks.A
[*]path produces a value: one row with an array in one column. There are no rows for the to-many to range over, so the array is handed back as a single value.What would be reasonable
Either of these would be an improvement over the current silence:
[*]bound to a to-many property behaves the way a modeller reading the mapping would expect. This is the outcome that was expected when the gap was found.Option 2 is the useful one; option 1 is worth having in the meantime, since the failure is currently invisible.
Where this came from
Found while adding
[*]wildcard path support to the relational store language (#5097). The wildcard work itself is complete and tested — this is specifically about binding the result to a to-many property.Reproduction is one property mapping; the models in
legend-engine-xt-relationalStore-emit/src/test/resources/relational-emit-models/relational-semistructured-wildcardandcore_relational/relational/tests/semistructured/model/wildcardPathInStoreLanguage.legendprovide the surrounding setup.