Skip to content

Commit

Permalink
Fix to #26858 - Query: improve TableExpressionBase extensibility by a…
Browse files Browse the repository at this point in the history
…dding annotations

Added annotation infra for TableExpressionBase and annotations for temporal table information. Removed (now unnecessary) temporal specific table expressions.

Fixes #26858
  • Loading branch information
maumar committed Dec 1, 2021
1 parent fe1f86d commit 9f2a303
Show file tree
Hide file tree
Showing 20 changed files with 433 additions and 439 deletions.
12 changes: 11 additions & 1 deletion src/EFCore.Relational/Query/SqlExpressions/FromSqlExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions;
/// </summary>
public class FromSqlExpression : TableExpressionBase, IClonableTableExpressionBase
{
private FromSqlExpression(
string alias,
string sql,
Expression arguments,
IEnumerable<ISqlExpressionAnnotation>? annotations)
: this(alias, sql, arguments)
{
SetAnnotations(annotations);
}

/// <summary>
/// Creates a new instance of the <see cref="FromSqlExpression" /> class.
/// </summary>
Expand Down Expand Up @@ -66,7 +76,7 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)

/// <inheritdoc />
public virtual TableExpressionBase Clone()
=> new FromSqlExpression(Alias, Sql, Arguments);
=> new FromSqlExpression(Alias, Sql, Arguments, GetAnnotations());

/// <inheritdoc />
protected override void Print(ExpressionPrinter expressionPrinter)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions;

/// <summary>
/// <para>
/// A class that exposes sql expression annotations. Annotations allow for arbitrary metadata to be stored on an object.
/// </para>
/// <para>
/// This interface is typically used by database providers (and other extensions). It is generally
/// not used in application code.
/// </para>
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-providers">Implementation of database providers and extensions</see>
/// for more information and examples.
/// </remarks>
public interface ISqlExpressionAnnotatable
{
/// <summary>
/// Gets the value of the annotation with the given name, returning <see langword="null" /> if it does not exist.
/// </summary>
/// <param name="name">The name of the annotation to find.</param>
/// <returns>
/// The value of the existing annotation if an annotation with the specified name already exists. Otherwise, <see langword="null" />.
/// </returns>
object? this[string name] { get; }

/// <summary>
/// Gets the annotation with the given name, returning <see langword="null" /> if it does not exist.
/// </summary>
/// <param name="name">The name of the annotation to find.</param>
/// <returns>
/// The existing annotation if an annotation with the specified name already exists. Otherwise, <see langword="null" />.
/// </returns>
ISqlExpressionAnnotation? FindAnnotation(string name);

/// <summary>
/// Gets all annotations on the current object.
/// </summary>
IEnumerable<ISqlExpressionAnnotation> GetAnnotations();

/// <summary>
/// Removes the given annotation from this object.
/// </summary>
/// <param name="name">The annotation to remove.</param>
/// <returns>The annotation that was removed.</returns>
ISqlExpressionAnnotation? RemoveAnnotation(string name);

/// <summary>
/// Sets the annotation stored under the given key. Overwrites the existing annotation if an
/// annotation with the specified name already exists.
/// </summary>
/// <param name="name">The key of the annotation to be added.</param>
/// <param name="value">The value to be stored in the annotation.</param>
void SetAnnotation(string name, object? value);

///// <summary>
///// Sets the annotation stored under the given key. Overwrites the existing annotation if an
///// annotation with the specified name already exists.
///// </summary>
///// <param name="name">The key of the annotation to be added.</param>
///// <param name="annotation">The annotation to be set.</param>
///// <param name="oldAnnotation">The annotation being replaced.</param>
///// <returns>The annotation that was set.</returns>
//ISqlExpressionAnnotation? SetAnnotation(
// string name,
// ISqlExpressionAnnotation annotation,
// ISqlExpressionAnnotation? oldAnnotation);

///// <summary>
///// Gets the annotation with the given name, throwing if it does not exist.
///// </summary>
///// <param name="annotationName">The key of the annotation to find.</param>
///// <returns>The annotation with the specified name.</returns>
//IAnnotation GetAnnotation(string annotationName)
// => AnnotatableBase.GetAnnotation(this, annotationName);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions;

/// <summary>
/// <para>
/// An arbitrary piece of metadata that can be stored on an object.
/// </para>
/// <para>
/// This interface is typically used by database providers (and other extensions). It is generally
/// not used in application code.
/// </para>
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-providers">Implementation of database providers and extensions</see>
/// for more information and examples.
/// </remarks>
public interface ISqlExpressionAnnotation
{
/// <summary>
/// Gets the key of this annotation.
/// </summary>
string Name { get; }

/// <summary>
/// Gets the value assigned to this annotation.
/// </summary>
object? Value { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,7 @@ private sealed class CloningExpressionVisitor : ExpressionVisitor
var limit = (SqlExpression?)Visit(selectExpression.Limit);

var newSelectExpression = new SelectExpression(
selectExpression.Alias, newProjections, newTables, newTableReferences, newGroupBy, newOrderings)
selectExpression.Alias, newProjections, newTables, newTableReferences, newGroupBy, newOrderings, selectExpression.GetAnnotations())
{
Predicate = predicate,
Having = havingExpression,
Expand Down
16 changes: 10 additions & 6 deletions src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,17 @@ private SelectExpression(
List<TableExpressionBase> tables,
List<TableReferenceExpression> tableReferences,
List<SqlExpression> groupBy,
List<OrderingExpression> orderings)
List<OrderingExpression> orderings,
IEnumerable<ISqlExpressionAnnotation>? annotations)
: base(alias)
{
_projection = projections;
_tables = tables;
_tableReferences = tableReferences;
_groupBy = groupBy;
_orderings = orderings;

SetAnnotations(annotations);
}

internal SelectExpression(SqlExpression? projection)
Expand Down Expand Up @@ -1429,7 +1432,7 @@ private void ApplySetOperation(SetOperationType setOperationType, SelectExpressi
{
// TODO: Introduce clone method? See issue#24460
var select1 = new SelectExpression(
null, new List<ProjectionExpression>(), _tables.ToList(), _tableReferences.ToList(), _groupBy.ToList(), _orderings.ToList())
null, new List<ProjectionExpression>(), _tables.ToList(), _tableReferences.ToList(), _groupBy.ToList(), _orderings.ToList(), GetAnnotations())
{
IsDistinct = IsDistinct,
Predicate = Predicate,
Expand Down Expand Up @@ -1727,7 +1730,8 @@ public void ApplyDefaultIfEmpty(ISqlExpressionFactory sqlExpressionFactory)
new List<TableExpressionBase>(),
new List<TableReferenceExpression>(),
new List<SqlExpression>(),
new List<OrderingExpression>());
new List<OrderingExpression>(),
annotations: null);

if (Orderings.Any()
|| Limit != null
Expand Down Expand Up @@ -2580,7 +2584,7 @@ private SqlRemappingVisitor PushdownIntoSubqueryInternal()
var subqueryAlias = GenerateUniqueAlias(_usedAliases, "t");
var subquery = new SelectExpression(
subqueryAlias, new List<ProjectionExpression>(), _tables.ToList(), _tableReferences.ToList(), _groupBy.ToList(),
_orderings.ToList())
_orderings.ToList(), GetAnnotations())
{
IsDistinct = IsDistinct,
Predicate = Predicate,
Expand Down Expand Up @@ -3221,7 +3225,7 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
{
var newTableReferences = _tableReferences.ToList();
var newSelectExpression = new SelectExpression(
Alias, newProjections, newTables, newTableReferences, newGroupBy, newOrderings)
Alias, newProjections, newTables, newTableReferences, newGroupBy, newOrderings, GetAnnotations())
{
_clientProjections = _clientProjections,
_projectionMapping = _projectionMapping,
Expand Down Expand Up @@ -3330,7 +3334,7 @@ public SelectExpression Update(

var newTableReferences = _tableReferences.ToList();
var newSelectExpression = new SelectExpression(
Alias, projections.ToList(), tables.ToList(), newTableReferences, groupBy.ToList(), orderings.ToList())
Alias, projections.ToList(), tables.ToList(), newTableReferences, groupBy.ToList(), orderings.ToList(), GetAnnotations())
{
_projectionMapping = projectionMapping,
_clientProjections = _clientProjections.ToList(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions;

/// <summary>
/// <para>
/// An arbitrary piece of metadata that can be stored on an object.
/// </para>
/// <para>
/// This interface is typically used by database providers (and other extensions). It is generally
/// not used in application code.
/// </para>
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-providers">Implementation of database providers and extensions</see>
/// for more information and examples.
/// </remarks>
public class SqlExpressionAnnotation : ISqlExpressionAnnotation
{
/// <summary>
/// Initializes a new instance of the <see cref="SqlExpressionAnnotation" /> class.
/// </summary>
/// <param name="name">The key of this annotation.</param>
/// <param name="value">The value assigned to this annotation.</param>
public SqlExpressionAnnotation(string name, object? value)
{
Check.NotEmpty(name, nameof(name));

Name = name;
Value = value;
}

/// <summary>
/// Gets the key of this annotation.
/// </summary>
public virtual string Name { get; }

/// <summary>
/// Gets the value assigned to this annotation.
/// </summary>
public virtual object? Value { get; }
}
19 changes: 17 additions & 2 deletions src/EFCore.Relational/Query/SqlExpressions/TableExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions;
/// </remarks>
public sealed class TableExpression : TableExpressionBase, IClonableTableExpressionBase
{
private TableExpression(ITableBase table, IEnumerable<ISqlExpressionAnnotation>? annotations)
: this(table)
{
SetAnnotations(annotations);
}

internal TableExpression(ITableBase table)
: base(table.Name.Substring(0, 1).ToLowerInvariant())
{
Expand All @@ -31,7 +37,16 @@ protected override void Print(ExpressionPrinter expressionPrinter)
expressionPrinter.Append(Schema).Append(".");
}

expressionPrinter.Append(Name).Append(" AS ").Append(Alias);
expressionPrinter.Append(Name);
var annotations = GetAnnotations();
if (annotations.Any())
{
expressionPrinter.Append("[");
expressionPrinter.Append(annotations.Select(a => a.Name + "=" + a.Value).Join(" | "));
expressionPrinter.Append("]");
}

expressionPrinter.Append(" AS ").Append(Alias);
}

/// <summary>
Expand Down Expand Up @@ -61,7 +76,7 @@ public override string? Alias

/// <inheritdoc />
public TableExpressionBase Clone()
=> new TableExpression(Table) { Alias = Alias };
=> new TableExpression(Table, GetAnnotations()) { Alias = Alias };

/// <inheritdoc />
public override bool Equals(object? obj)
Expand Down
Loading

0 comments on commit 9f2a303

Please sign in to comment.