Skip to content

Commit

Permalink
Progress
Browse files Browse the repository at this point in the history
  • Loading branch information
cincuranet committed Aug 1, 2024
1 parent 3d1d324 commit 8c9275a
Show file tree
Hide file tree
Showing 37 changed files with 720 additions and 270 deletions.
6 changes: 6 additions & 0 deletions src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.Cosmos/Properties/CosmosStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@
<data name="DuplicateDiscriminatorValue" xml:space="preserve">
<value>The discriminator value for '{entityType1}' is '{discriminatorValue}' which is the same for '{entityType2}'. Every concrete entity type mapped to the container '{container}' must have a unique discriminator value.</value>
</data>
<data name="EFConstantNotSupported" xml:space="preserve">
<value>The 'EF.Constant()' isn't supported by Cosmos.</value>
</data>
<data name="ElementWithValueConverter" xml:space="preserve">
<value>The property '{propertyType} {structuralType}.{property}' has element type '{elementType}', which requires a value converter. Elements types requiring value converters are not currently supported with the Azure Cosmos DB database provider.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ protected CosmosQueryableMethodTranslatingExpressionVisitor(
/// </summary>
public override Expression Translate(Expression expression)
{
if (_queryCompilationContext.ExpressionsToConstantize.Count != 0)
{
throw new NotSupportedException(CosmosStrings.EFConstantNotSupported);
}

// Handle ToPageAsync(), which can only ever be the top-level node in the query tree.
if (expression is MethodCallExpression { Method: var method, Arguments: var arguments }
&& method.DeclaringType == typeof(CosmosQueryableExtensions)
Expand Down
21 changes: 21 additions & 0 deletions src/EFCore.Design/Query/Internal/LinqToCSharpSyntaxTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,8 @@ IEqualityComparer c
Generate(typeof(Encoding)),
IdentifierName(nameof(Encoding.Default))),

string[] arr => HandleStringArray(arr),

_ => GenerateUnknownValue(value)
};

Expand Down Expand Up @@ -1159,6 +1161,25 @@ ExpressionSyntax HandleValueTuple(ITuple tuple)

return TupleExpression(SeparatedList(arguments));
}

ExpressionSyntax HandleStringArray(string[] arr)
{
return ArrayCreationExpression(ArrayType(PredefinedType(Token(SyntaxKind.StringKeyword)))
.WithRankSpecifiers(SingletonList(ArrayRankSpecifier(SingletonSeparatedList<ExpressionSyntax>(OmittedArraySizeExpression())))))
.WithInitializer(CreateInitializer());

InitializerExpressionSyntax CreateInitializer()
{
if (arr.Length == 0)
{
return InitializerExpression(SyntaxKind.ArrayInitializerExpression);
}

var items = arr.Select(x => LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(x)));
var separators = Enumerable.Range(0, arr.Length - 1).Select(_ => Token(SyntaxKind.CommaToken));
return InitializerExpression(SyntaxKind.ArrayInitializerExpression, SeparatedList<ExpressionSyntax>(items, separators));
}
}
}

/// <summary>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,9 @@
<data name="OneOfThreeValuesMustBeSet" xml:space="preserve">
<value>Exactly one of '{param1}', '{param2}' or '{param3}' must be set.</value>
</data>
<data name="OneOfTwoValuesMustBeSet" xml:space="preserve">
<value>Exactly one of '{param1}' or '{param2}' must be set.</value>
</data>
<data name="OnlyConstantsSupportedInInlineCollectionQueryRoots" xml:space="preserve">
<value>Only constants are supported inside inline collection query roots.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public interface IRelationalParameterBasedSqlProcessorFactory
/// Creates a new <see cref="RelationalParameterBasedSqlProcessor" />.
/// </summary>
/// <param name="useRelationalNulls">A bool value indicating if relational nulls should be used.</param>
/// <param name="parametersToConstantize">A collection of parameter names to constantize.</param>
/// <returns>A relational parameter based sql processor.</returns>
RelationalParameterBasedSqlProcessor Create(bool useRelationalNulls);
RelationalParameterBasedSqlProcessor Create(bool useRelationalNulls, string[] parametersToConstantize);
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ public RelationalCommandCache(
IQuerySqlGeneratorFactory querySqlGeneratorFactory,
IRelationalParameterBasedSqlProcessorFactory relationalParameterBasedSqlProcessorFactory,
Expression queryExpression,
bool useRelationalNulls)
bool useRelationalNulls,
string[] parametersToConstantize)
{
_memoryCache = memoryCache;
_querySqlGeneratorFactory = querySqlGeneratorFactory;
_queryExpression = queryExpression;
_relationalParameterBasedSqlProcessor = relationalParameterBasedSqlProcessorFactory.Create(useRelationalNulls);
_relationalParameterBasedSqlProcessor = relationalParameterBasedSqlProcessorFactory.Create(useRelationalNulls, parametersToConstantize);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ public RelationalParameterBasedSqlProcessorFactory(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual RelationalParameterBasedSqlProcessor Create(bool useRelationalNulls)
=> new(Dependencies, useRelationalNulls);
public virtual RelationalParameterBasedSqlProcessor Create(bool useRelationalNulls, string[] parametersToConstantize)
=> new(Dependencies, useRelationalNulls, parametersToConstantize);
}
5 changes: 5 additions & 0 deletions src/EFCore.Relational/Query/QuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,11 @@ protected override Expression VisitValues(ValuesExpression valuesExpression)
/// <param name="valuesExpression">The <see cref="ValuesExpression" /> for which to generate SQL.</param>
protected virtual void GenerateValues(ValuesExpression valuesExpression)
{
if (valuesExpression.RowValues is null)
{
throw new UnreachableException();
}

if (valuesExpression.RowValues.Count == 0)
{
throw new InvalidOperationException(RelationalStrings.EmptyCollectionNotSupportedAsInlineQueryRoot);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ public class RelationalParameterBasedSqlProcessor
/// </summary>
/// <param name="dependencies">Parameter object containing dependencies for this class.</param>
/// <param name="useRelationalNulls">A bool value indicating if relational nulls should be used.</param>
/// <param name="parametersToConstantize">A collection of parameter names to constantize.</param>
public RelationalParameterBasedSqlProcessor(
RelationalParameterBasedSqlProcessorDependencies dependencies,
bool useRelationalNulls)
bool useRelationalNulls,
string[] parametersToConstantize)
{
Dependencies = dependencies;
UseRelationalNulls = useRelationalNulls;
ParametersToConstantize = parametersToConstantize;
}

/// <summary>
Expand All @@ -40,6 +43,11 @@ public RelationalParameterBasedSqlProcessor(
/// </summary>
protected virtual bool UseRelationalNulls { get; }

/// <summary>
/// A collection of parameter names to constantize.
/// </summary>
protected virtual string[] ParametersToConstantize { get; }

/// <summary>
/// Optimizes the query expression for given parameter values.
/// </summary>
Expand Down Expand Up @@ -74,7 +82,7 @@ protected virtual Expression ProcessSqlNullability(
Expression queryExpression,
IReadOnlyDictionary<string, object?> parametersValues,
out bool canCache)
=> new SqlNullabilityProcessor(Dependencies, UseRelationalNulls).Process(queryExpression, parametersValues, out canCache);
=> new SqlNullabilityProcessor(Dependencies, UseRelationalNulls, ParametersToConstantize).Process(queryExpression, parametersValues, out canCache);

/// <summary>
/// Expands the parameters to <see cref="FromSqlExpression" /> inside the query expression for given parameter values.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,15 +374,23 @@ JsonScalarExpression jsonScalar
}

// Second pass: create the VALUES expression's row value expressions.
var rowExpressions = new RowValueExpression[sqlExpressions.Length];
for (var i = 0; i < sqlExpressions.Length; i++)
var alias = _sqlAliasManager.GenerateTableAlias("values");
ValuesExpression valuesExpression;
if (sqlExpressions is [var single] && single is SqlParameterExpression parameter)
{
var sqlExpression = sqlExpressions[i];

rowExpressions[i] =
new RowValueExpression(
new[]
{
parameter = (SqlParameterExpression)parameter.ApplyTypeMapping(_typeMappingSource.FindMapping(elementType, RelationalDependencies.Model));
valuesExpression = new ValuesExpression(alias, parameter, new[] { ValuesOrderingColumnName, ValuesValueColumnName });
}
else
{
var rowExpressions = new RowValueExpression[sqlExpressions.Length];
for (var i = 0; i < sqlExpressions.Length; i++)
{
var sqlExpression = sqlExpressions[i];
rowExpressions[i] =
new RowValueExpression(
new[]
{
// Since VALUES may not guarantee row ordering, we add an _ord value by which we'll order.
_sqlExpressionFactory.Constant(i, intTypeMapping),
// If no type mapping was inferred (i.e. no column in the inline collection), it's left null, to allow it to get
Expand All @@ -392,12 +400,11 @@ JsonScalarExpression jsonScalar
sqlExpression.TypeMapping is null && inferredTypeMaping is not null
? _sqlExpressionFactory.ApplyTypeMapping(sqlExpression, inferredTypeMaping)
: sqlExpression
});
});
}
valuesExpression = new ValuesExpression(alias, rowExpressions, new[] { ValuesOrderingColumnName, ValuesValueColumnName });
}

var alias = _sqlAliasManager.GenerateTableAlias("values");
var valuesExpression = new ValuesExpression(alias, rowExpressions, new[] { ValuesOrderingColumnName, ValuesValueColumnName });

// Note: we leave the element type mapping null, to allow it to get inferred based on queryable operators composed on top.
var valueColumn = new ColumnExpression(
ValuesValueColumnName,
Expand Down Expand Up @@ -577,7 +584,9 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s
// Pattern-match Contains over ValuesExpression, translating to simplified 'item IN (1, 2, 3)' with constant elements
if (TryExtractBareInlineCollectionValues(source, out var values))
{
var inExpression = _sqlExpressionFactory.In(translatedItem, values);
var inExpression = values is [var single] && single is SqlParameterExpression parameter
? _sqlExpressionFactory.In(translatedItem, parameter)
: _sqlExpressionFactory.In(translatedItem, values);
return source.Update(new SelectExpression(inExpression, _sqlAliasManager), source.ShaperExpression);
}

Expand Down Expand Up @@ -2100,15 +2109,24 @@ private bool TryExtractBareInlineCollectionValues(ShapedQueryExpression shapedQu
&& projection is ColumnExpression { TableAlias: var tableAlias }
&& tableAlias == valuesExpression.Alias)
{
values = new SqlExpression[valuesExpression.RowValues.Count];

for (var i = 0; i < values.Length; i++)
if (valuesExpression.RowValues is not null)
{
// Skip the first value (_ord) - this function assumes ordering doesn't matter
values[i] = valuesExpression.RowValues[i].Values[1];
values = new SqlExpression[valuesExpression.RowValues.Count];

for (var i = 0; i < values.Length; i++)
{
// Skip the first value (_ord) - this function assumes ordering doesn't matter
values[i] = valuesExpression.RowValues[i].Values[1];
}

return true;
}
if (valuesExpression.ValuesParameter is not null)
{
values = [valuesExpression.ValuesParameter];

return true;
return true;
}
}

values = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Query;
/// <inheritdoc />
public partial class RelationalShapedQueryCompilingExpressionVisitor : ShapedQueryCompilingExpressionVisitor
{
private readonly string[] _parametersToConstantize;
private readonly Type _contextType;
private readonly ISet<string> _tags;
private readonly bool _threadSafetyChecksEnabled;
Expand Down Expand Up @@ -42,8 +43,11 @@ public RelationalShapedQueryCompilingExpressionVisitor(
: base(dependencies, queryCompilationContext)
{
RelationalDependencies = relationalDependencies;

_parametersToConstantize = QueryCompilationContext.ExpressionsToConstantize.Select(x => x.Name!).ToArray();

_relationalParameterBasedSqlProcessor =
relationalDependencies.RelationalParameterBasedSqlProcessorFactory.Create(_useRelationalNulls);
relationalDependencies.RelationalParameterBasedSqlProcessorFactory.Create(_useRelationalNulls, _parametersToConstantize);
_querySqlGeneratorFactory = relationalDependencies.QuerySqlGeneratorFactory;

_contextType = queryCompilationContext.ContextType;
Expand Down Expand Up @@ -498,7 +502,8 @@ private Expression CreateRelationalCommandResolverExpression(Expression queryExp
RelationalDependencies.QuerySqlGeneratorFactory,
RelationalDependencies.RelationalParameterBasedSqlProcessorFactory,
queryExpression,
_useRelationalNulls);
_useRelationalNulls,
_parametersToConstantize);

var commandLiftableConstant = RelationalDependencies.RelationalLiftableConstantFactory.CreateLiftableConstant(
relationalCommandCache,
Expand All @@ -507,7 +512,8 @@ private Expression CreateRelationalCommandResolverExpression(Expression queryExp
c.RelationalDependencies.QuerySqlGeneratorFactory,
c.RelationalDependencies.RelationalParameterBasedSqlProcessorFactory,
queryExpression,
_useRelationalNulls),
_useRelationalNulls,
_parametersToConstantize),
"relationalCommandCache",
typeof(RelationalCommandCache));

Expand Down
50 changes: 27 additions & 23 deletions src/EFCore.Relational/Query/RelationalTypeMappingPostprocessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,40 +162,44 @@ protected virtual ValuesExpression ApplyTypeMappingsOnValuesExpression(ValuesExp
? valuesExpression.ColumnNames.Skip(1).ToArray()
: valuesExpression.ColumnNames;

var newRowValues = new RowValueExpression[valuesExpression.RowValues.Count];
for (var i = 0; i < newRowValues.Length; i++)
RowValueExpression[]? newRowValues = null;
if (valuesExpression.RowValues is not null)
{
var rowValue = valuesExpression.RowValues[i];
var newValues = new SqlExpression[newColumnNames.Count];
for (var j = 0; j < valuesExpression.ColumnNames.Count; j++)
newRowValues = new RowValueExpression[valuesExpression.RowValues.Count];
for (var i = 0; i < newRowValues.Length; i++)
{
if (j == 0 && stripOrdering)
var rowValue = valuesExpression.RowValues[i];
var newValues = new SqlExpression[newColumnNames.Count];
for (var j = 0; j < valuesExpression.ColumnNames.Count; j++)
{
continue;
}
if (j == 0 && stripOrdering)
{
continue;
}

var value = rowValue.Values[j];
var value = rowValue.Values[j];

if (value.TypeMapping is null
&& inferredTypeMappings[j] is RelationalTypeMapping inferredTypeMapping)
{
value = _sqlExpressionFactory.ApplyTypeMapping(value, inferredTypeMapping);
}
if (value.TypeMapping is null
&& inferredTypeMappings[j] is RelationalTypeMapping inferredTypeMapping)
{
value = _sqlExpressionFactory.ApplyTypeMapping(value, inferredTypeMapping);
}

// We currently add explicit conversions on the first row (but not to the _ord column), to ensure that the inferred types
// are properly typed. See #30605 for removing that when not needed.
if (i == 0 && j > 0 && value is not ColumnExpression)
{
value = new SqlUnaryExpression(ExpressionType.Convert, value, value.Type, value.TypeMapping);
// We currently add explicit conversions on the first row (but not to the _ord column), to ensure that the inferred types
// are properly typed. See #30605 for removing that when not needed.
if (i == 0 && j > 0 && value is not ColumnExpression)
{
value = new SqlUnaryExpression(ExpressionType.Convert, value, value.Type, value.TypeMapping);
}

newValues[j - (stripOrdering ? 1 : 0)] = value;
}

newValues[j - (stripOrdering ? 1 : 0)] = value;
newRowValues[i] = new RowValueExpression(newValues);
}

newRowValues[i] = new RowValueExpression(newValues);
}

return new ValuesExpression(valuesExpression.Alias, newRowValues, newColumnNames);
return new ValuesExpression(valuesExpression.Alias, newRowValues, valuesExpression.ValuesParameter, newColumnNames);
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore.Relational/Query/SqlExpressions/InExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,9 @@ public override int GetHashCode()

if (Values is not null)
{
for (var i = 0; i < Values.Count; i++)
foreach (var value in Values)
{
hash.Add(Values[i]);
hash.Add(value);
}
}

Expand Down
Loading

0 comments on commit 8c9275a

Please sign in to comment.