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

Refactored DefaultIfEmptyContext usage. #4554

Merged
merged 5 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Refactored DefaultIfEmptyContext usage.
  • Loading branch information
sdanyliv committed Jul 1, 2024
commit c3b134179649c04f355c2dc3b737eecd98ca7552
12 changes: 6 additions & 6 deletions Source/LinqToDB/Linq/Builder/AllJoinsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,14 @@ protected override BuildSequenceResult BuildMethodCall(ExpressionBuilder builder

buildInfo.JoinType = joinType;

if (joinType == JoinType.Left || joinType == JoinType.Full)
sequence = new DefaultIfEmptyBuilder.DefaultIfEmptyContext(buildInfo.Parent, sequence, sequence, null, false);

sequence = new SubQueryContext(sequence);
var result = sequence;

if (methodCall.Arguments[conditionIndex] != null)
{
var condition = (LambdaExpression)methodCall.Arguments[conditionIndex].Unwrap();

var result = builder.BuildWhere(sequence, sequence,
result = builder.BuildWhere(result, result,
condition : condition, checkForSubQuery : false, enforceHaving : false,
isTest : buildInfo.IsTest);

Expand All @@ -82,10 +80,12 @@ protected override BuildSequenceResult BuildMethodCall(ExpressionBuilder builder
}*/

result.SetAlias(condition.Parameters[0].Name);
return BuildSequenceResult.FromContext(result);
}

return BuildSequenceResult.FromContext(sequence);
if (joinType == JoinType.Left || joinType == JoinType.Full)
sdanyliv marked this conversation as resolved.
Show resolved Hide resolved
result = new DefaultIfEmptyBuilder.DefaultIfEmptyContext(buildInfo.Parent, result, result, null, false, false);

return BuildSequenceResult.FromContext(result);
}
}
}
4 changes: 2 additions & 2 deletions Source/LinqToDB/Linq/Builder/AllJoinsLinqBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ protected override BuildSequenceResult BuildMethodCall(ExpressionBuilder builder
}

if (joinType == JoinType.Right || joinType == JoinType.Full)
outerContext = new DefaultIfEmptyBuilder.DefaultIfEmptyContext(buildInfo.Parent, outerContext, outerContext, null, false);
outerContext = new DefaultIfEmptyBuilder.DefaultIfEmptyContext(buildInfo.Parent, outerContext, outerContext, null, false, false);
sdanyliv marked this conversation as resolved.
Show resolved Hide resolved
outerContext = new SubQueryContext(outerContext);

if (joinType == JoinType.Left || joinType == JoinType.Full)
innerContext = new DefaultIfEmptyBuilder.DefaultIfEmptyContext(buildInfo.Parent, innerContext, innerContext, null, false);
innerContext = new DefaultIfEmptyBuilder.DefaultIfEmptyContext(buildInfo.Parent, innerContext, innerContext, null, false, false);
innerContext = new SubQueryContext(innerContext);

var selector = methodCall.Arguments[^1].UnwrapLambda();
Expand Down
27 changes: 17 additions & 10 deletions Source/LinqToDB/Linq/Builder/DefaultIfEmptyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ protected override BuildSequenceResult BuildMethodCall(ExpressionBuilder builder
defaultValueContext.SelectQuery.Select.AddNew(new SqlValue(1));
}

var defaultIfEmptyContext = new DefaultIfEmptyContext(buildInfo.Parent, sequence, sequence, null, true);
var defaultIfEmptyContext = new DefaultIfEmptyContext(buildInfo.Parent, sequence, sequence, null, true, false);

var notNullConditions = defaultIfEmptyContext.GetNotNullConditions();

Expand Down Expand Up @@ -126,7 +126,7 @@ protected override BuildSequenceResult BuildMethodCall(ExpressionBuilder builder
return buildResult;
var sequence = buildResult.BuildContext;

return BuildSequenceResult.FromContext(new DefaultIfEmptyContext(buildInfo.Parent, sequence, sequence, defaultValue, true));
return BuildSequenceResult.FromContext(new DefaultIfEmptyContext(buildInfo.Parent, sequence, sequence, defaultValue, true, false));
}
}

Expand All @@ -137,15 +137,17 @@ public sealed class DefaultIfEmptyContext : SequenceContextBase

ReadOnlyCollection<Expression>? _notNullConditions;

public DefaultIfEmptyContext(IBuildContext? parent, IBuildContext sequence, IBuildContext nullabilitySequence, Expression? defaultValue, bool allowNullField)
public DefaultIfEmptyContext(IBuildContext? parent, IBuildContext sequence, IBuildContext nullabilitySequence, Expression? defaultValue, bool allowNullField, bool isNullValidationDisabled)
: base(parent, sequence, null)
{
_nullabilitySequence = nullabilitySequence;
_allowNullField = allowNullField;
DefaultValue = defaultValue;
_nullabilitySequence = nullabilitySequence;
_allowNullField = allowNullField;
DefaultValue = defaultValue;
IsNullValidationDisabled = isNullValidationDisabled;
}

public Expression? DefaultValue { get; }
public bool IsNullValidationDisabled { get; set; }
public Expression? DefaultValue { get; }

public const string NotNullPropName = "not_null";

Expand Down Expand Up @@ -179,7 +181,7 @@ public override Expression MakeExpression(Expression path, ProjectFlags flags)
return placeholder;
}

if (DefaultValue != null)
if (!IsNullValidationDisabled && DefaultValue != null)
{
var notNullConditions = GetNotNullConditions();

Expand Down Expand Up @@ -208,7 +210,7 @@ public override Expression MakeExpression(Expression path, ProjectFlags flags)

expr = SequenceHelper.CorrectTrackingPath(Builder, expr, path);

if (/*!flags.IsKeys() && */expr.UnwrapConvert() is not SqlEagerLoadExpression)
if (!IsNullValidationDisabled && /*!flags.IsKeys() && */expr.UnwrapConvert() is not SqlEagerLoadExpression)
{
if (expr is SqlPlaceholderExpression placeholder)
{
Expand Down Expand Up @@ -243,7 +245,12 @@ public override Expression MakeExpression(Expression path, ProjectFlags flags)

public override IBuildContext Clone(CloningContext context)
{
return new DefaultIfEmptyContext(null, context.CloneContext(Sequence), context.CloneContext(_nullabilitySequence), context.CloneExpression(DefaultValue), _allowNullField);
return new DefaultIfEmptyContext(null,
context.CloneContext(Sequence),
context.CloneContext(_nullabilitySequence),
context.CloneExpression(DefaultValue),
_allowNullField,
IsNullValidationDisabled);
}

public override IBuildContext? GetContext(Expression expression, BuildInfo buildInfo)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1898,7 +1898,7 @@ Expression GenerateNullComparison(Expression placeholdersExpression, bool isNot)
var searchCondition = new SqlSearchCondition(isNot);
foreach (var placeholder in notNull)
{
var sql = SqlNullabilityExpression.ApplyNullability(placeholder.Sql, true);
var sql = placeholder.Sql;
searchCondition.Predicates.Add(new SqlPredicate.IsNull(sql, isNot));
}

Expand Down
2 changes: 1 addition & 1 deletion Source/LinqToDB/Linq/Builder/FirstSingleBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ protected override BuildSequenceResult BuildMethodCall(ExpressionBuilder builder

if (buildInfo.Parent != null && (cardinality & SourceCardinality.Zero) != 0)
{
sequence = new DefaultIfEmptyBuilder.DefaultIfEmptyContext(buildInfo.Parent, sequence, sequence, null, allowNullField: true);
sequence = new DefaultIfEmptyBuilder.DefaultIfEmptyContext(buildInfo.Parent, sequence, sequence, null, allowNullField: true, isNullValidationDisabled: false);
canBeWeak = true;
}

Expand Down
51 changes: 33 additions & 18 deletions Source/LinqToDB/Linq/Builder/SelectManyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,46 @@ protected override BuildSequenceResult BuildMethodCall(ExpressionBuilder builder
//
if (collectionInfo.JoinType == JoinType.Full || collectionInfo.JoinType == JoinType.Right)
{
sequence = new DefaultIfEmptyBuilder.DefaultIfEmptyContext(buildInfo.Parent, sequence, collection, null, false);
sequence = new DefaultIfEmptyBuilder.DefaultIfEmptyContext(buildInfo.Parent, sequence, collection, null, false, false);
}

var collectionDefaultIfEmptyContext = SequenceHelper.GetDefaultIfEmptyContext(collection);
if (collectionDefaultIfEmptyContext != null)
{
collectionDefaultIfEmptyContext.IsNullValidationDisabled = true;
}

var isLeftJoin =
collectionDefaultIfEmptyContext != null ||
collectionInfo.JoinType == JoinType.Left;

var joinType = collectionInfo.JoinType;
joinType = joinType switch
{
JoinType.Inner => isLeftJoin ? JoinType.OuterApply : JoinType.CrossApply,
JoinType.Auto => isLeftJoin ? JoinType.OuterApply : JoinType.CrossApply,
JoinType.Left => JoinType.OuterApply,
JoinType.Full => JoinType.FullApply,
JoinType.Right => JoinType.RightApply,
_ => joinType
};

var projected = builder.BuildSqlExpression(collection,
new ContextRefExpression(collection.ElementType, collection), buildInfo.GetFlags(),
buildFlags : ExpressionBuilder.BuildFlags.ForceAssignments);

var expanded = builder.MakeExpression(sequence, new ContextRefExpression(collection.ElementType, collection), ProjectFlags.ExtractProjection);

collection = new SubQueryContext(collection);

projected = builder.UpdateNesting(collection, projected);
if (collectionDefaultIfEmptyContext != null)
{
var collectionSelectContext = new SelectContext(buildInfo.Parent, builder, null, expanded, collection.SelectQuery, buildInfo.IsSubQuery);

collection = new DefaultIfEmptyBuilder.DefaultIfEmptyContext(sequence, collectionSelectContext, collection, collectionDefaultIfEmptyContext.DefaultValue,
allowNullField: joinType is not (JoinType.Right or JoinType.RightApply or JoinType.Full or JoinType.FullApply),
isNullValidationDisabled: false);
}

if (resultSelector == null)
{
Expand All @@ -83,7 +113,7 @@ protected override BuildSequenceResult BuildMethodCall(ExpressionBuilder builder
resultExpression = SequenceHelper.ReplaceBody(resultSelector.Body, resultSelector.Parameters[0], sequence);
if (resultSelector.Parameters.Count > 1)
{
resultExpression = SequenceHelper.ReplaceBody(resultExpression, resultSelector.Parameters[1], new ScopeContext(collection, sequence));
resultExpression = SequenceHelper.ReplaceBody(resultExpression, resultSelector.Parameters[1], collection);
}
}

Expand All @@ -98,21 +128,6 @@ protected override BuildSequenceResult BuildMethodCall(ExpressionBuilder builder
collection.SetAlias(collectionAlias);
}

var isLeftJoin =
SequenceHelper.IsDefaultIfEmpty(collection) ||
collectionInfo.JoinType == JoinType.Left;

var joinType = collectionInfo.JoinType;
joinType = joinType switch
{
JoinType.Inner => isLeftJoin ? JoinType.OuterApply : JoinType.CrossApply,
JoinType.Auto => isLeftJoin ? JoinType.OuterApply : JoinType.CrossApply,
JoinType.Left => JoinType.OuterApply,
JoinType.Full => JoinType.FullApply,
JoinType.Right => JoinType.RightApply,
_ => joinType
};

var join = new SqlFromClause.Join(joinType, collection.SelectQuery, collectionAlias, false, null);
sequence.SelectQuery.From.Tables[0].Joins.Add(join.JoinedTable);

Expand Down
4 changes: 2 additions & 2 deletions Source/LinqToDB/Linq/Builder/SequenceHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -913,9 +913,9 @@ static IBuildContext UnwrapSubqueryContext(IBuildContext context)
return current;
}

public static bool IsDefaultIfEmpty(IBuildContext context)
public static DefaultIfEmptyBuilder.DefaultIfEmptyContext? GetDefaultIfEmptyContext(IBuildContext context)
{
return UnwrapSubqueryContext(context) is DefaultIfEmptyBuilder.DefaultIfEmptyContext;
return UnwrapSubqueryContext(context) as DefaultIfEmptyBuilder.DefaultIfEmptyContext;
}

public static Expression UnwrapDefaultIfEmpty(Expression expression)
Expand Down
2 changes: 1 addition & 1 deletion Source/LinqToDB/SqlProvider/SqlOptimizerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static SqlStatement PrepareStatementForRemoting(this ISqlOptimizer optimi
isAlreadyOptimizedAndConverted: false,
static () => NoopQueryParametersNormalizer.Instance);

var nullability = NullabilityContext.GetContext(statement.SelectQuery);
var nullability = NullabilityContext.NonQuery;

var newStatement = optimizationContext.OptimizeAndConvertAll(statement, nullability);

Expand Down
18 changes: 3 additions & 15 deletions Tests/Linq/Linq/DefaultIfEmptyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,26 +80,14 @@ public void WithDefaultInSelectMany([DataSources(TestProvName.AllClickHouse)] st
using var db = GetDataContext(context);

var query =
from p in db.Parent
from p in db.Parent.LoadWith(p => p.Children)
from c in p.Children.DefaultIfEmpty(new Child { ChildID = -100 })
select new { Parent = p, Child = c }
select new { Parent = p.ParentID, Child = c }
into s
where s.Child.ChildID < 0
select s;

var xx = query.ToList();

/*var exptected =
from p in Parent
select new
{
Sum = p.Children.DefaultIfEmpty(new Child { ParentID = -100 })
.Select(c => c.ParentID)
.Sum()
};


AreEqual(exptected, query);*/
AssertQuery(query);
}

}
Expand Down
Loading