Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public SqliteMemberTranslatorProvider(RelationalMemberTranslatorProviderDependen
[
new SqliteDateTimeMemberTranslator(sqlExpressionFactory),
new SqliteStringLengthTranslator(sqlExpressionFactory),
new SqliteDateOnlyMemberTranslator(sqlExpressionFactory)
new SqliteDateOnlyMemberTranslator(sqlExpressionFactory),
new SqliteTimeSpanMemberTranslator(sqlExpressionFactory)
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public SqliteMethodCallTranslatorProvider(RelationalMethodCallTranslatorProvider
new SqliteRandomTranslator(sqlExpressionFactory),
new SqliteRegexMethodTranslator(sqlExpressionFactory),
new SqliteStringMethodTranslator(sqlExpressionFactory),
new SqliteSubstrMethodTranslator(sqlExpressionFactory)
new SqliteSubstrMethodTranslator(sqlExpressionFactory),
new SqliteTimeSpanMethodTranslator(sqlExpressionFactory)
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,35 @@ public virtual SqlExpression Date(
typeMapping);
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// 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 SqlExpression EfDays(SqlExpression timeSpanExpression)
=> Function(
"ef_days",
[timeSpanExpression],
nullable: true,
argumentsPropagateNullability: [true],
typeof(double));

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// 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 SqlExpression EfTimespan(SqlExpression daysExpression)
=> Function(
"ef_timespan",
[daysExpression],
nullable: true,
argumentsPropagateNullability: [true],
typeof(TimeSpan),
Dependencies.TypeMappingSource.FindMapping(typeof(TimeSpan), Dependencies.Model));

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,53 +49,45 @@ private static readonly IReadOnlyDictionary<ExpressionType, IReadOnlyCollection<
typeof(DateOnly),
typeof(DateTime),
typeof(DateTimeOffset),
typeof(TimeOnly),
typeof(TimeSpan)
typeof(TimeOnly)
},
[ExpressionType.Divide] = new HashSet<Type>
{
typeof(TimeOnly),
typeof(TimeSpan),
typeof(ulong)
},
[ExpressionType.GreaterThan] = new HashSet<Type>
{
typeof(DateTimeOffset),
typeof(TimeSpan),
typeof(ulong)
},
[ExpressionType.GreaterThanOrEqual] = new HashSet<Type>
{
typeof(DateTimeOffset),
typeof(TimeSpan),
typeof(ulong)
},
[ExpressionType.LessThan] = new HashSet<Type>
{
typeof(DateTimeOffset),
typeof(TimeSpan),
typeof(ulong)
},
[ExpressionType.LessThanOrEqual] = new HashSet<Type>
{
typeof(DateTimeOffset),
typeof(TimeSpan),
typeof(ulong)
},
[ExpressionType.Modulo] = new HashSet<Type> { typeof(ulong) },
[ExpressionType.Multiply] = new HashSet<Type>
{
typeof(TimeOnly),
typeof(TimeSpan),
typeof(ulong)
},
[ExpressionType.Subtract] = new HashSet<Type>
{
typeof(DateOnly),
typeof(DateTime),
typeof(DateTimeOffset),
typeof(TimeOnly),
typeof(TimeSpan)
typeof(TimeOnly)
}
};

Expand Down Expand Up @@ -128,9 +120,13 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression)
[true],
translation.Type),

var t when t == typeof(TimeOnly) || t == typeof(TimeSpan)
var t when t == typeof(TimeOnly)
=> QueryCompilationContext.NotTranslatedExpression,

var t when t == typeof(TimeSpan)
&& _sqlExpressionFactory is SqliteSqlExpressionFactory sqliteFactory
=> sqliteFactory.EfTimespan(Dependencies.SqlExpressionFactory.Negate(sqliteFactory.EfDays(sqlUnary.Operand))),

_ => translation
};
}
Expand Down Expand Up @@ -191,6 +187,11 @@ when ModuloFunctions.TryGetValue(GetProviderType(sqlBinary.Left), out var functi
case { } when AttemptDecimalArithmetic(sqlBinary):
return DoDecimalArithmetics(translation, sqlBinary.OperatorType, sqlBinary.Left, sqlBinary.Right);

case { } when _sqlExpressionFactory is SqliteSqlExpressionFactory sqliteFactory
&& TryTranslateTimeSpanDateTimeBinary(sqlBinary, sqliteFactory, out var timeSpanDateTimeResult)
&& timeSpanDateTimeResult != null:
return timeSpanDateTimeResult;

case { }
when RestrictedBinaryExpressions.TryGetValue(sqlBinary.OperatorType, out var restrictedTypes)
&& (restrictedTypes.Contains(GetProviderType(sqlBinary.Left))
Expand All @@ -204,6 +205,145 @@ when RestrictedBinaryExpressions.TryGetValue(sqlBinary.OperatorType, out var res
return translation;
}

private static bool TryTranslateTimeSpanDateTimeBinary(
SqlBinaryExpression sqlBinary,
SqliteSqlExpressionFactory sqliteFactory,
out SqlExpression? result)
{
result = null;
var leftType = GetProviderType(sqlBinary.Left);
var rightType = GetProviderType(sqlBinary.Right);
var operatorType = sqlBinary.OperatorType;

if (leftType == typeof(TimeSpan) && rightType == typeof(TimeSpan))
{
var leftDays = sqliteFactory.EfDays(sqlBinary.Left);
var rightDays = sqliteFactory.EfDays(sqlBinary.Right);
switch (operatorType)
{
case ExpressionType.Add:
result = sqliteFactory.EfTimespan(
sqliteFactory.Add(leftDays, rightDays));
return true;
case ExpressionType.Subtract:
result = sqliteFactory.EfTimespan(
sqliteFactory.Subtract(leftDays, rightDays));
return true;
case ExpressionType.Divide:
result = sqliteFactory.Divide(leftDays, rightDays);
return true;
case ExpressionType.GreaterThan:
result = sqliteFactory.GreaterThan(leftDays, rightDays);
return true;
case ExpressionType.GreaterThanOrEqual:
result = sqliteFactory.GreaterThanOrEqual(leftDays, rightDays);
return true;
case ExpressionType.LessThan:
result = sqliteFactory.LessThan(leftDays, rightDays);
return true;
case ExpressionType.LessThanOrEqual:
result = sqliteFactory.LessThanOrEqual(leftDays, rightDays);
return true;
}
}

if (operatorType == ExpressionType.Divide && leftType == typeof(TimeSpan) && (rightType == typeof(double) || rightType == typeof(float)))
{
result = sqliteFactory.EfTimespan(
sqliteFactory.Divide(sqliteFactory.EfDays(sqlBinary.Left), sqlBinary.Right));
return true;
}

if (operatorType == ExpressionType.Multiply)
{
if ((leftType == typeof(double) || leftType == typeof(float)) && rightType == typeof(TimeSpan))
{
result = sqliteFactory.EfTimespan(
sqliteFactory.Multiply(sqlBinary.Left, sqliteFactory.EfDays(sqlBinary.Right)));
return true;
}
if (leftType == typeof(TimeSpan) && (rightType == typeof(double) || rightType == typeof(float)))
{
result = sqliteFactory.EfTimespan(
sqliteFactory.Multiply(sqliteFactory.EfDays(sqlBinary.Left), sqlBinary.Right));
return true;
}
}

if (leftType == typeof(DateTime) && rightType == typeof(TimeSpan))
{
if (operatorType == ExpressionType.Add)
{
var juliandayLeft = sqliteFactory.Function(
"julianday",
[sqlBinary.Left],
nullable: true,
argumentsPropagateNullability: Statics.TrueArrays[1],
typeof(double));
var sumDays = sqliteFactory.Add(juliandayLeft, sqliteFactory.EfDays(sqlBinary.Right));
result = MakeDateTimeFromJulianDay(sqliteFactory, sumDays);
return true;
}
if (operatorType == ExpressionType.Subtract)
{
var juliandayLeft = sqliteFactory.Function(
"julianday",
[sqlBinary.Left],
nullable: true,
argumentsPropagateNullability: Statics.TrueArrays[1],
typeof(double));
var diffDays = sqliteFactory.Subtract(juliandayLeft, sqliteFactory.EfDays(sqlBinary.Right));
result = MakeDateTimeFromJulianDay(sqliteFactory, diffDays);
return true;
}
}

if (leftType == typeof(DateTime) && rightType == typeof(DateTime) && operatorType == ExpressionType.Subtract)
{
var juliandayLeft = sqliteFactory.Function(
"julianday",
[sqlBinary.Left],
nullable: true,
argumentsPropagateNullability: Statics.TrueArrays[1],
typeof(double));
var juliandayRight = sqliteFactory.Function(
"julianday",
[sqlBinary.Right],
nullable: true,
argumentsPropagateNullability: Statics.TrueArrays[1],
typeof(double));
result = sqliteFactory.EfTimespan(sqliteFactory.Subtract(juliandayLeft, juliandayRight));
return true;
}

return false;
}

private static SqlExpression MakeDateTimeFromJulianDay(SqliteSqlExpressionFactory sqliteFactory, SqlExpression julianDayExpression)
{
var strftimeResult = sqliteFactory.Strftime(
typeof(DateTime),
"%Y-%m-%d %H:%M:%f",
julianDayExpression);
return sqliteFactory.Function(
"rtrim",
[
sqliteFactory.Function(
"rtrim",
[
strftimeResult,
sqliteFactory.Constant("0")
],
nullable: true,
argumentsPropagateNullability: Statics.TrueFalse,
typeof(DateTime)),
sqliteFactory.Constant(".")
],
nullable: true,
argumentsPropagateNullability: Statics.TrueFalse,
typeof(DateTime));
}

/// <inheritdoc />
protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,26 @@ public class SqliteQueryableAggregateMethodTranslator(ISqlExpressionFactory sqlE
&& source.Selector is SqlExpression maxSqlExpression:
var maxArgumentType = GetProviderType(maxSqlExpression);
if (maxArgumentType == typeof(DateTimeOffset)
|| maxArgumentType == typeof(TimeSpan)
|| maxArgumentType == typeof(ulong))
{
throw new NotSupportedException(
SqliteStrings.AggregateOperationNotSupported(nameof(Queryable.Max), maxArgumentType.ShortDisplayName()));
}

if (maxArgumentType == typeof(TimeSpan)
&& sqlExpressionFactory is SqliteSqlExpressionFactory maxSqliteFactory)
{
maxSqlExpression = CombineTerms(source, maxSqlExpression);
var maxDaysExpression = maxSqliteFactory.EfDays(maxSqlExpression);
var maxAggregate = sqlExpressionFactory.Function(
"max",
[maxDaysExpression],
nullable: true,
argumentsPropagateNullability: Statics.FalseArrays[1],
typeof(double));
return maxSqliteFactory.EfTimespan(maxAggregate);
}

if (maxArgumentType == typeof(decimal))
{
maxSqlExpression = CombineTerms(source, maxSqlExpression);
Expand All @@ -86,13 +99,26 @@ public class SqliteQueryableAggregateMethodTranslator(ISqlExpressionFactory sqlE
&& source.Selector is SqlExpression minSqlExpression:
var minArgumentType = GetProviderType(minSqlExpression);
if (minArgumentType == typeof(DateTimeOffset)
|| minArgumentType == typeof(TimeSpan)
|| minArgumentType == typeof(ulong))
{
throw new NotSupportedException(
SqliteStrings.AggregateOperationNotSupported(nameof(Queryable.Min), minArgumentType.ShortDisplayName()));
}

if (minArgumentType == typeof(TimeSpan)
&& sqlExpressionFactory is SqliteSqlExpressionFactory minSqliteFactory)
{
minSqlExpression = CombineTerms(source, minSqlExpression);
var minDaysExpression = minSqliteFactory.EfDays(minSqlExpression);
var minAggregate = sqlExpressionFactory.Function(
"min",
[minDaysExpression],
nullable: true,
argumentsPropagateNullability: Statics.FalseArrays[1],
typeof(double));
return minSqliteFactory.EfTimespan(minAggregate);
}

if (minArgumentType == typeof(decimal))
{
minSqlExpression = CombineTerms(source, minSqlExpression);
Expand Down
Loading
Loading