Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redo query column/table relationship without TableReferenceExpression #32812

Merged
merged 1 commit into from
Jan 18, 2024

Conversation

roji
Copy link
Member

@roji roji commented Jan 14, 2024

NOTE: THIS IS BASED ON TOP OF #32785 (ALIAS REDO), REVIEW ONLY THE 2ND COMMIT

  • After this, TableReferenceExpression no longer exists, and ColumnExpressions refer to their tables by their string aliases.
  • Lots of table reference book-keeping/maintenance code has been removed, including various visitation passes (compilation performance).
  • There aren't many places where a visitor actually needs to resolve a column to its table; in most cases these are on the current/top-most SelectExpression, which is trivial. In cases where columns can refer up the SQL tree, the visitor tracks the mapping as part of its visitation (this is very rare).
  • ColumnExpression is now a regular, publicly-constructible expression - there's no more private ConcreteColumnExpression.
  • Note that after this PR, table aliases are still mutable - a small upcoming PR will finish that bit off (in the interest of not cramming too much into a single PR).

Closes #32558

@roji roji requested a review from a team January 14, 2024 10:55
{
table = joinExpressionBase.Table;
}
else if (table is SetOperationBase setOperationBase)
Copy link
Member Author

Choose a reason for hiding this comment

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

@maumar unless I'm mistaken, there was a bug here when a set operation was wrapped in a join; we'd unwrap the join above but not go into the set operation because of the "else" here.

@@ -2139,6 +2144,11 @@ public IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping?

protected override Expression VisitExtension(Expression node)
{
if (node is TableExpressionBase { Alias: string tableAlias } table)
Copy link
Member Author

Choose a reason for hiding this comment

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

This is one of the few examples of a visitor which really does need to go from columns to tables, and so maintains a table map during visitation.

// TODO: recreating a new TableExpressionBase implementation with a different alias.
// TODO: Either add a mandatory, abstract non-destructive ChangeAlias to TableExpressionBase, or ideally,
// TODO: refactor things to split the alias out of TableExpressionBase altogether.
table.Alias = newAlias;
Copy link
Member Author

Choose a reason for hiding this comment

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

Note that TableExpressionBase.Alias is still mutable. It will be made immutable in a separate PR.

@@ -13,55 +13,85 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions;
/// </para>
/// </summary>
[DebuggerDisplay("{TableAlias}.{Name}")]
public abstract class ColumnExpression : SqlExpression
public class ColumnExpression : SqlExpression
Copy link
Member Author

Choose a reason for hiding this comment

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

Note that ColumnExpression is no longer abstract, with a private ConcreteColumnExpression that only SelectExpression can construct. This is now a plain, regular SqlExpression that anyone can build.

{
if (_mutable)
{
VisitList(_tables, inPlace: true, out _);
Copy link
Member Author

Choose a reason for hiding this comment

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

Note that table visitation has been moved to be the first. This aligns with actual evaluation order of SQL SELECT - tables are evaluated first, and projections last - they can reference tables (the textual layout of SQL SELECT has always seemed like a mistake; IDEs can't to Intellisense as the user is writing projections since the tables haven't been written yet... Note that in C# LINQ expression syntax the tables come first).

This is a more useful for visitors which need to visit tables first, for tracking purposes etc.

// Otherwise, if no tables have changed, we mutate the TableReferenceExpressions (this was the previous code, left it for
// a more low-risk fix). Note that updateColumns is false only if we're already being called from
// ColumnTableReferenceUpdater to replace the ColumnExpressions, in which case we avoid infinite recursion.
if (tablesChanged && updateColumns)
Copy link
Member Author

Choose a reason for hiding this comment

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

All this table reference updating/cloning is now gone 🎉

/// </remarks>
// TODO: Make this protected after SelectExpression.Prune is moved into this visitor
[EntityFrameworkInternal]
public virtual string? CurrentTableAlias { get; set; }
Copy link
Member Author

Choose a reason for hiding this comment

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

When everything is done, I plan to do a final cleanup here to move the SelectExpression.Prune functionailty into this visitor; at that point everything will be nice and self-contained. Right now it's somewhat smelly - that's a temporary situation.

/// </summary>
[EntityFrameworkInternal]
public virtual string GetRequiredAlias()
=> Alias ?? throw new InvalidOperationException($"No alias is defined on table: {ExpressionPrinter.Print(this)}");
Copy link
Contributor

Choose a reason for hiding this comment

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

add resource string

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops missed this one, will do in the #32815

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.

Look into making SelectExpression (more) immutable, getting rid of TableReferenceExpression
3 participants