Improve relational model integration in Query#38346
Conversation
| // Preserve the underlying model column through subquery boundaries so JsonQueryExpression.FindJsonElement keeps | ||
| // matching after lifts (e.g. LiftJsonQueryFromSubquery wraps the json column in a JsonScalarExpression). | ||
| column: subqueryProjection.Expression switch | ||
| { | ||
| ColumnExpression { Column: { } column } => column, | ||
| JsonScalarExpression { Json: ColumnExpression { Column: { } jsonColumn } } => jsonColumn, | ||
| _ => null | ||
| }, |
There was a problem hiding this comment.
This looks a bit odd... The idea here was for ColumnExpression to reference the relational model IColumn it refers to, similar to how TableExpression references its the relational model ITableBase; IIRC I did this specifically so that e.g. the translation can look at a ColumnExpression and know whether it has a database index, since that can affect the translation strategy.
This change makes it so that a ColumnExpression referring to a JsonScalarExpression will point the the relational model column which contains that JSON scalar, which doesn't seem right (or at least changes the meaning of ColumnExpression.Column significantly). I'd propose leaving this null at least for now - ColumnExpression.Column is optional anyway, and doesn't have to be populated.
There was a problem hiding this comment.
Ok, changed this to only set the containing column when the result is going to be JsonQueryExpression.JsonColumn to preserve the semantics
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Fixes #38060
Fixes #35466
Fixes #32853
The query pipeline previously fell back to model‑wide accessors (
GetJsonPropertyName, container‑column‑by‑name scans) at translation time, which produced wrong/ambiguous results under entity splitting, TPT, and JSON‑on‑view scenarios. This change threads the actual table set and JSON element metadata through the existing relational query expressions and consolidates priority logic in one place.Changes
GetQueryMappings()internal helper (RelationalTypeBaseExtensions) returns storage mappings in priority order — default SQL query → default function → view → table — and replaces allGetViewOrTableMappings()call sites in the query pipeline. Casts directly to the concreteList<TMapping>runtime type and filters in place to avoid LINQ allocations.StructuralTypeProjectionExpression.TableMap(optionalIReadOnlyDictionary<ITableBase, string>) — threaded through TPT, single‑table, entity‑splitting and TPCCreateSelectbranches, preserved acrossMakeNullable,UpdateEntityType,VisitChildren, set‑op merge, and subquery lift.BindProperty,TryRewriteEntityEquality, andGenerateMaterializationConditionnow scope their principal‑split‑table lookup to the actually‑projected tables.JsonQueryExpression.FindJsonElement(IPropertyBase)— returns theIRelationalJsonElementwhose containing column matchesJsonColumnwhen available, otherwise the first element from the property's mappings.BindProperty/BindStructuralPropertyand the OPENJSON/json_each translators inRelationalQueryableMethodTranslatingExpressionVisitor,SqlServer, andSqlitenow use this instead of model‑wideGetJsonPropertyName().JsonProjectionInfo.JsonColumn+ColumnExpression.Columnpreservation inMakeNullable/ApplyTypeMapping— the shaper now uses the column resolved at CreateSelect time, falling back to a model scan only when there's no underlyingIColumnBase(synthetic JSON columns over OPENJSON/json_each).