Skip to content

Commit

Permalink
In Subquery tests (#4327)
Browse files Browse the repository at this point in the history
* Use nullable columns in tests

* Working on #4327.

* #4327 Fixed tests.

* Working on #4327.

* Working on #4327.

* Working on #4327.

* Update System.Data.SqlClient.

* Update Microsoft.Data.SqlClient.

* Update Microsoft.Data.SqlClient.

* Working on #4327.

* Review updates.

---------

Co-authored-by: Igor Tkachev <igor.tkachev@gmail.com>
  • Loading branch information
jods4 and igor-tkachev committed Jan 27, 2024
1 parent 693e871 commit 7b26d88
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 208 deletions.
6 changes: 3 additions & 3 deletions Source/LinqToDB/Linq/Builder/ContainsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ namespace LinqToDB.Linq.Builder

sealed class ContainsBuilder : MethodCallBuilder
{
private static readonly string[] MethodNames = { "Contains" };
private static readonly string[] MethodNamesAsync = { "ContainsAsync" };
private static readonly string[] MethodNames = ["Contains"];
private static readonly string[] MethodNamesAsync = ["ContainsAsync"];

protected override bool CanBuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo)
{
Expand Down Expand Up @@ -92,7 +92,7 @@ public override SqlInfo[] ConvertToSql(Expression? expression, int level, Conver
if (Parent != null)
query = Parent.SelectQuery;

return new[] { new SqlInfo(sql, query) };
return [new SqlInfo(sql, query)];
}

throw new InvalidOperationException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,9 +441,9 @@ Expression CorrectConditional(IBuildContext context, Expression expr, bool enfor

var cond = (ConditionalExpression)expr;

if (cond.Test.NodeType == ExpressionType.Equal || cond.Test.NodeType == ExpressionType.NotEqual)
{
var b = (BinaryExpression)cond.Test;
if (cond.Test.NodeType == ExpressionType.Equal || cond.Test.NodeType == ExpressionType.NotEqual)
{
var b = (BinaryExpression)cond.Test;

Expression? cnt = null;
Expression? obj = null;
Expand Down
23 changes: 9 additions & 14 deletions Source/LinqToDB/Linq/Builder/SelectContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class SelectContext : IBuildContext
public SelectContext(IBuildContext? parent, ExpressionBuilder builder, LambdaExpression lambda, SelectQuery selectQuery)
{
Parent = parent;
Sequence = Array<IBuildContext>.Empty;
Sequence = [];
Builder = builder;
Lambda = lambda;
Body = lambda.Body;
Expand Down Expand Up @@ -112,7 +112,7 @@ Expression BuildExpressionInternal(Expression? expression, int level, bool enfor
#endif
{
{
var key = Tuple.Create(expression, level, ConvertFlags.Field);
var key = (expression, level, ConvertFlags.Field);

if (_expressionIndex.TryGetValue(key, out var info))
{
Expand Down Expand Up @@ -177,7 +177,7 @@ Expression BuildExpressionInternal(Expression? expression, int level, bool enfor
}

switch (levelExpression.NodeType)
{
{
case ExpressionType.MemberAccess :
{
var memberInfo = ((MemberExpression)levelExpression).Member;
Expand Down Expand Up @@ -278,7 +278,7 @@ public virtual SqlInfo[] ConvertToSql(Expression? expression, int level, Convert

if (e.Method.DeclaringType == typeof(Enumerable) && !typeof(IGrouping<,>).IsSameOrParentOf(e.Arguments[0].Type))
{
return new[] { new SqlInfo(Builder.SubQueryToSql(this, e)) };
return [new SqlInfo(Builder.SubQueryToSql(this, e))];
}
}

Expand Down Expand Up @@ -307,7 +307,7 @@ public virtual SqlInfo[] ConvertToSql(Expression? expression, int level, Convert
expression,
level,
static (context, ctx, ex, l) => ctx!.ConvertToSql(ex, l, context.flags),
static context => new[] { new SqlInfo(context.context.Builder.ConvertToSql(context.context, context.expression)) }, true);
static context => [new SqlInfo(context.context.Builder.ConvertToSql(context.context, context.expression))], true);
}
}
}
Expand All @@ -318,6 +318,7 @@ public virtual SqlInfo[] ConvertToSql(Expression? expression, int level, Convert
if (flags != ConvertFlags.Field)
{
var list = new List<SqlInfo>();

foreach (var mi in Members)
if (!(mi.Key is MethodInfo || flags == ConvertFlags.Key && EagerLoading.IsDetailsMember(this, mi.Value)))
list.AddRange(ConvertMember(mi.Key, mi.Value, flags));
Expand Down Expand Up @@ -441,11 +442,11 @@ SqlInfo[] ConvertExpressions(Expression expression, ConvertFlags flags, ColumnDe

#region ConvertToIndex

readonly Dictionary<Tuple<Expression?,int,ConvertFlags>,SqlInfo[]> _expressionIndex = new ();
readonly Dictionary<(Expression?,int,ConvertFlags),SqlInfo[]> _expressionIndex = new ();

public virtual SqlInfo[] ConvertToIndex(Expression? expression, int level, ConvertFlags flags)
{
var key = Tuple.Create(expression, level, flags);
var key = (expression, level, flags);

if (!_expressionIndex.TryGetValue(key, out var info))
{
Expand Down Expand Up @@ -478,12 +479,6 @@ public virtual SqlInfo[] ConvertToIndex(Expression? expression, int level, Conve

readonly Dictionary<Tuple<MemberInfo?,ConvertFlags>,SqlInfo[]> _memberIndex = new ();

sealed class SqlData
{
public SqlInfo[] Sql = null!;
public MemberInfo Member = null!;
}

SqlInfo[] ConvertToIndexInternal(Expression? expression, int level, ConvertFlags flags)
{
if (IsScalar)
Expand Down Expand Up @@ -547,7 +542,7 @@ SqlInfo[] ConvertToIndexInternal(Expression? expression, int level, ConvertFlags
}
}

return list?.ToArray() ?? Array<SqlInfo>.Empty;
return list?.ToArray() ?? [];
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions Source/LinqToDB/Linq/Builder/TableBuilder.TableContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ void SetLoadWithBindings(Type objectType, ParameterExpression parentObject, List

ParameterExpression? _variable;

Expression BuildTableExpression(bool buildBlock, Type objectType, Tuple<int, SqlField?>[] index)
Expression BuildTableExpression(bool buildBlock, Type objectType, (int, SqlField?)[] index)
{
if (buildBlock && _variable != null)
return _variable;
Expand Down Expand Up @@ -323,7 +323,7 @@ Expression BuildCalculatedColumns(EntityDescriptor entityDescriptor, Expression
return Expression.Block(new[] { variable }, expressions);
}

Expression BuildDefaultConstructor(EntityDescriptor entityDescriptor, Type objectType, Tuple<int, SqlField?>[] index)
Expression BuildDefaultConstructor(EntityDescriptor entityDescriptor, Type objectType, (int, SqlField?)[] index)
{
var members = new List<(ColumnDescriptor column, MemberInfo storage, ConvertFromDataReaderExpression expr)>();

Expand Down Expand Up @@ -564,7 +564,7 @@ static ConstructorInfo SelectParameterizedConstructor(Type objectType)
return expr;
}

Expression BuildRecordConstructor(EntityDescriptor entityDescriptor, Type objectType, Tuple<int, SqlField?>[] index, RecordType recordType)
Expression BuildRecordConstructor(EntityDescriptor entityDescriptor, Type objectType, (int, SqlField?)[] index, RecordType recordType)
{
var columns = new List<ColumnInfo>();
foreach (var idx in index)
Expand Down Expand Up @@ -597,7 +597,7 @@ protected virtual Expression ProcessExpression(Expression expression)
return expression;
}

Tuple<int, SqlField?>[] BuildIndex(Tuple<int, SqlField?>[] index, Type objectType)
(int, SqlField?)[] BuildIndex((int, SqlField?)[] index, Type objectType)
{
var names = new Dictionary<string,int>();
var n = 0;
Expand All @@ -616,7 +616,7 @@ protected virtual Expression ProcessExpression(Expression expression)
}
}

var result = new Tuple<int, SqlField?>[q.Count];
var result = new (int, SqlField?)[q.Count];

var idx = 0;
foreach (var r in q.OrderBy(static r => r.sort))
Expand Down Expand Up @@ -677,10 +677,10 @@ protected virtual Expression BuildQuery(Type tableType, TableContext tableContex
info = matchedFields.ToArray();
}

var index = new Tuple<int, SqlField?>[info.Length];
var index = new (int, SqlField?)[info.Length];

for (var i = 0; i < info.Length; i++)
index[i] = Tuple.Create(ConvertToParentIndex(info[i].Index, this), QueryHelper.GetUnderlyingField(info[i].Sql));
index[i] = (ConvertToParentIndex(info[i].Index, this), QueryHelper.GetUnderlyingField(info[i].Sql));

if (ObjectType != tableType || InheritanceMapping.Count == 0)
return BuildTableExpression(!Builder.IsBlockDisable, tableType, index);
Expand Down
2 changes: 1 addition & 1 deletion Source/LinqToDB/LinqOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ namespace LinqToDB
/// </param>
/// <param name="EnableContextSchemaEdit">
/// If <c>true</c>, user could add new mappings to context mapping schems (<see cref="IDataContext.MappingSchema"/>).
/// Otherwise <see cref="LinqToDBException"/> will be generated on locked mapping schema edit attempt.
/// Otherwise, <see cref="LinqToDBException"/> will be generated on locked mapping schema edit attempt.
/// It is not recommended to enable this option as it has performance implications.
/// Proper approach is to create single <see cref="Mapping.MappingSchema"/> instance once, configure mappings for it and use this <see cref="Mapping.MappingSchema"/> instance for all context instances.
/// Default value: <c>false</c>.
Expand Down
2 changes: 1 addition & 1 deletion Source/LinqToDB/SqlProvider/BasicSqlBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2931,7 +2931,7 @@ protected virtual void BuildLikePredicate(SqlPredicate.Like predicate)

case QueryElementType.SqlQuery:
{
var hasParentheses = checkParentheses && StringBuilder[StringBuilder.Length - 1] == '(';
var hasParentheses = checkParentheses && StringBuilder[^1] == '(';

if (!hasParentheses)
StringBuilder.AppendLine(OpenParens);
Expand Down
114 changes: 56 additions & 58 deletions Source/LinqToDB/SqlProvider/BasicSqlOptimizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2137,90 +2137,88 @@ protected virtual ISqlExpression ConvertFunction(SqlFunction func)
if (optimizationContext.IsOptimized(element, out var newElement))
return newElement!;

newElement = RunOptimization(element, optimizationContext, this, mappingSchema, dataOptions, !withConversion,
static (visitor, e) =>
{
var ne = e;
newElement = RunOptimization(element, optimizationContext, this, mappingSchema, dataOptions, !withConversion, Optimize);

static IQueryElement Optimize(ConvertVisitor<RunOptimizationContext> visitor, IQueryElement e)
{
var ne = e;

if (ne is ISqlExpression expr1)
ne = visitor.Context.Optimizer.OptimizeExpression(expr1, visitor);
if (ne is ISqlExpression expr1)
ne = visitor.Context.Optimizer.OptimizeExpression(expr1, visitor);

switch (ne)
if (ne is ISqlPredicate pred1)
{
ne = visitor.Context.Optimizer.OptimizePredicate(pred1, visitor.Context.OptimizationContext.Context, visitor.Context.DataOptions);
}
else if (ne is SqlCondition(var isNot, var predicate, var isOr))
{
switch (
visitor.Context.DataOptions.LinqOptions.CompareNullsAsValues,
isNot,
predicate)
{
case ISqlPredicate pred1:
ne = visitor.Context.Optimizer.OptimizePredicate(pred1, visitor.Context.OptimizationContext.Context, visitor.Context.DataOptions);
break;
// NOT IN NULL (value) NOT NULL (subquery)
case (true, true, SqlSearchCondition([(false, SqlPredicate.InSubQuery({CanBeNull: true}, false, {Select.Columns : [{CanBeNull: false}]}) inSubQuery, var isOr1)]))
:
return new SqlCondition(false, new SqlSearchCondition().Expr(inSubQuery.Expr1).IsNull.Or.Add(new SqlCondition(true, inSubQuery, isOr1)), isOr);

case SqlCondition(true, SqlSearchCondition([(false, SqlPredicate.InSubQuery({CanBeNull: true}, false, {Select.Columns : [{CanBeNull: false}]}) insq, var isOr1) cond]), var isOr)
when visitor.Context.Optimizer.SqlProviderFlags.DoesNotSupportCorrelatedSubquery:
return new SqlCondition(false, new SqlSearchCondition().Expr(insq.Expr1).IsNull.Or.Add(new (true, insq, isOr1)), isOr);
// IN NULL (value) NULL (subquery)
case (true, false, SqlSearchCondition([(false, SqlPredicate.InSubQuery({CanBeNull: true}, false, {Select.Columns : [{CanBeNull: true} col]} subQuery) inSubQuery, _) cond]))
:
return ConvertNullInNullSubquery(subQuery, col, inSubQuery, cond, isOr);

case SqlCondition(false, SqlSearchCondition([(false, SqlPredicate.InSubQuery({CanBeNull: true}, false, {Select.Columns : [{CanBeNull: true} col]} subQuery) insq, _) cond]), var isOr)
when visitor.Context.Optimizer.SqlProviderFlags.DoesNotSupportCorrelatedSubquery :
// NOT IN for previous case.
case (true, true, SqlSearchCondition([{ OptimizationTag:1 } cond]))
:
{
var newQuery = subQuery.Convert((subQuery,col.Expression), static (v, e) =>
{
if (ReferenceEquals(e, v.Context.Expression))
return new SqlValue(1);
if (e is SqlWhereClause w && w == v.Context.subQuery.Where)
{
var wc = new SqlWhereClause(new SqlSearchCondition(w.SearchCondition.Conditions));
wc.SearchCondition.Conditions.Add(new(
false,
new SqlPredicate.IsNull(v.Context.subQuery.Select.Columns[0].Expression, false)));
return wc;
}
var f = new SqlFunction(typeof(bool), "CASE", [(SqlSearchCondition)cond.Predicate, new SqlValue(true), new SqlValue(false)]) { DoNotOptimize = true };

return e;
});
var sc = new SqlSearchCondition(
new SqlCondition(false, new SqlPredicate.ExprExpr(f, SqlPredicate.Operator.Equal, new SqlValue(false), false), false));

return new SqlCondition(
false,
new SqlSearchCondition()
.Expr(insq.Expr1).IsNull. And.Expr(new SqlValue(1)).InSubQuery(newQuery).Or
.Expr(insq.Expr1).IsNotNull.And.Add(cond), isOr);
return new SqlCondition(false, sc, isOr);
}
case SqlCondition(false, SqlSearchCondition([(false, SqlPredicate.InSubQuery({CanBeNull: true}, true, {Select.Columns : [{CanBeNull: false}]}) insq, _) cond]), var isOr)
when visitor.Context.Optimizer.SqlProviderFlags.DoesNotSupportCorrelatedSubquery :
return new SqlCondition(false, new SqlSearchCondition().Expr(insq.Expr1).IsNull.Or.Add(cond), isOr);
case SqlCondition(false, SqlPredicate.InSubQuery({CanBeNull: true} ex1, false, {Select.Columns : [{CanBeNull: true} col]} subQuery), var isOr)
when !visitor.Context.Optimizer.SqlProviderFlags.DoesNotSupportCorrelatedSubquery :
return ConvertInSubquery(ex1, false, col, subQuery, isOr);
case SqlCondition(false, SqlPredicate.InSubQuery({CanBeNull: true} ex1, true, {Select.Columns : [{CanBeNull: false} col]} subQuery), var isOr)
when !visitor.Context.Optimizer.SqlProviderFlags.DoesNotSupportCorrelatedSubquery :
return ConvertInSubquery(ex1, true, col, subQuery, isOr);
}

static IQueryElement ConvertInSubquery(ISqlExpression ex1, bool isNot, SqlColumn col, SelectQuery subQuery, bool isOr)
static SqlCondition ConvertNullInNullSubquery(
SelectQuery subQuery, SqlColumn col, SqlPredicate.InSubQuery inSubQuery, SqlCondition cond, bool isOr)
{
var newQuery = subQuery.Convert((subQuery,col,ex1), static (v, e) =>
var newQuery = subQuery.Convert((subQuery,col.Expression), static (v, e) =>
{
if (ReferenceEquals(e, v.Context.Expression))
return new SqlValue(1);
if (e is SqlWhereClause w && w == v.Context.subQuery.Where)
{
var wc = new SqlWhereClause(new SqlSearchCondition(w.SearchCondition.Conditions));
wc.SearchCondition.Conditions.Add(new(
false,
new SqlPredicate.ExprExpr(v.Context.col.Expression, SqlPredicate.Operator.Equal, v.Context.ex1, true)));
new SqlPredicate.IsNull(v.Context.subQuery.Select.Columns[0].Expression, false)));
return wc;
}
return e;
});

return new SqlCondition(isNot, new SqlPredicate.FuncLike(SqlFunction.CreateExists(newQuery)), isOr);
var sc = new SqlSearchCondition(
[
new (false, new SqlPredicate.IsNull (inSubQuery.Expr1, false), false),
new (false, new SqlPredicate.InSubQuery(new SqlValue(1), false, newQuery), true),
new (false, new SqlPredicate.IsNull (inSubQuery.Expr1, true), false),
cond
]);

return new SqlCondition(false, sc, isOr) { OptimizationTag = 1 };
}
}

if (!ReferenceEquals(ne, e))
return ne;
if (!ReferenceEquals(ne, e))
return ne;

ne = visitor.Context.Optimizer.OptimizeQueryElement(visitor, ne);
ne = visitor.Context.Optimizer.OptimizeQueryElement(visitor, ne);

return ne;
});
return ne;
}

if (withConversion)
{
Expand All @@ -2238,8 +2236,8 @@ static IQueryElement ConvertInSubquery(ISqlExpression ex1, bool isNot, SqlColumn
if (!ReferenceEquals(ne, e))
return ne;
if (ne is ISqlPredicate pred3)
ne = visitor.Context.Optimizer.ConvertPredicateImpl(pred3, visitor);
if (ne is ISqlPredicate predicate)
ne = visitor.Context.Optimizer.ConvertPredicateImpl(predicate, visitor);
return ne;
});
Expand Down
2 changes: 2 additions & 0 deletions Source/LinqToDB/SqlQuery/SqlCondition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public SqlCondition(bool isNot, ISqlPredicate predicate, bool isOr)
public ISqlPredicate Predicate { get; set; }
public bool IsOr { get; set; }

internal int OptimizationTag;

public int Precedence =>
IsNot ? SqlQuery.Precedence.LogicalNegation :
IsOr ? SqlQuery.Precedence.LogicalDisjunction :
Expand Down

0 comments on commit 7b26d88

Please sign in to comment.