From 2f8e75cb7c6ee0e6a6d9a9381eebe7bd769ee968 Mon Sep 17 00:00:00 2001 From: Philipp Zech Date: Fri, 6 Mar 2020 15:46:40 +0100 Subject: [PATCH 01/13] add ef_negate --- .../SqliteSqlTranslatingExpressionVisitor.cs | 13 +++++++++++-- .../Storage/Internal/SqliteRelationalConnection.cs | 7 +++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index 4a1a253afc8..aaee4f3c04e 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -113,8 +113,17 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) && sqlUnary.OperatorType == ExpressionType.Negate) { var operandType = GetProviderType(sqlUnary.Operand); - if (operandType == typeof(decimal) - || operandType == typeof(TimeSpan)) + if (operandType == typeof(decimal)) + { + return SqlExpressionFactory.Function( + name: "ef_negate", + new[] { sqlUnary.Operand }, + nullable: true, + new[] { true }, + visitedExpression.Type); + } + + if (operandType == typeof(TimeSpan)) { return null; } diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs index d54abd97374..7061bc7a28e 100644 --- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs @@ -141,6 +141,13 @@ private void InitializeDbConnection(DbConnection connection) ? decimal.Compare(left.Value, right.Value) : default(int?), isDeterministic: true); + + sqliteConnection.CreateFunction( + name: "ef_negate", + (decimal? m) => m.HasValue + ? decimal.Negate(m.Value) + : default(decimal?), + isDeterministic: true); } else { From d358ee39e51a4a113bcae42135f3250060f1ff1a Mon Sep 17 00:00:00 2001 From: Philipp Zech Date: Sat, 7 Mar 2020 10:18:03 +0100 Subject: [PATCH 02/13] add ef_add --- .../SqliteSqlTranslatingExpressionVisitor.cs | 13 ++++++++++++- .../Storage/Internal/SqliteRelationalConnection.cs | 8 +++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index aaee4f3c04e..da6c5c1da64 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -22,7 +22,6 @@ private static readonly IReadOnlyDictionary @@ -158,6 +157,18 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) visitedExpression.TypeMapping); } + if (sqlBinary.OperatorType == ExpressionType.Add + && GetProviderType(sqlBinary.Left) == typeof(decimal) + && (GetProviderType(sqlBinary.Right) == typeof(decimal))) + { + return SqlExpressionFactory.Function( + "ef_add", + new[] { sqlBinary.Left, sqlBinary.Right }, + nullable: true, + new[] { true, true }, + visitedExpression.Type); + } + if (AttemptDecimalCompare(sqlBinary)) { return DoDecimalCompare(visitedExpression, sqlBinary.OperatorType, sqlBinary.Left, sqlBinary.Right); diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs index 7061bc7a28e..1b0425a2745 100644 --- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs @@ -9,7 +9,6 @@ using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Internal; using Microsoft.EntityFrameworkCore.Storage; @@ -135,6 +134,13 @@ private void InitializeDbConnection(DbConnection connection) % Convert.ToDouble(divisor, CultureInfo.InvariantCulture); }); + sqliteConnection.CreateFunction( + name: "ef_add", + (decimal? left, decimal? right) => left.HasValue && right.HasValue + ? decimal.Add(left.Value, right.Value) + : default(decimal?), + isDeterministic: true); + sqliteConnection.CreateFunction( name: "ef_compare", (decimal? left, decimal? right) => left.HasValue && right.HasValue From 37b47529c107adfc082536ea3dae1458478a5ffd Mon Sep 17 00:00:00 2001 From: Philipp Zech Date: Sat, 7 Mar 2020 10:38:59 +0100 Subject: [PATCH 03/13] add ef_divide --- .../SqliteSqlTranslatingExpressionVisitor.cs | 13 +++++++++++- .../Internal/SqliteRelationalConnection.cs | 20 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index da6c5c1da64..c957528db56 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -26,7 +26,6 @@ private static readonly IReadOnlyDictionary { - typeof(decimal), typeof(TimeSpan), typeof(ulong) }, @@ -169,6 +168,18 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) visitedExpression.Type); } + if (sqlBinary.OperatorType == ExpressionType.Divide + && GetProviderType(sqlBinary.Left) == typeof(decimal) + && (GetProviderType(sqlBinary.Right) == typeof(decimal))) + { + return SqlExpressionFactory.Function( + "ef_divide", + new[] { sqlBinary.Left, sqlBinary.Right }, + nullable: true, + new[] { true, true }, + visitedExpression.Type); + } + if (AttemptDecimalCompare(sqlBinary)) { return DoDecimalCompare(visitedExpression, sqlBinary.OperatorType, sqlBinary.Left, sqlBinary.Right); diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs index 1b0425a2745..97aa15e29cb 100644 --- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs @@ -141,6 +141,25 @@ private void InitializeDbConnection(DbConnection connection) : default(decimal?), isDeterministic: true); + sqliteConnection.CreateFunction( + name: "ef_divide", + (decimal? dividend, decimal? divisor) => + { + if (dividend == null + || divisor == null) + { + return new decimal(null); + } + + if (divisor.Value != 0) + { + return decimal.Divide(dividend.Value, divisor.Value); + } + + throw new DivideByZeroException(); + }, + isDeterministic: true); + sqliteConnection.CreateFunction( name: "ef_compare", (decimal? left, decimal? right) => left.HasValue && right.HasValue @@ -156,6 +175,7 @@ private void InitializeDbConnection(DbConnection connection) isDeterministic: true); } else + { _logger.UnexpectedConnectionTypeWarning(connection.GetType()); } From 729af46862ba3a9594842a579aef3896105c4271 Mon Sep 17 00:00:00 2001 From: Philipp Zech Date: Sat, 7 Mar 2020 10:42:25 +0100 Subject: [PATCH 04/13] add ef_multiply --- .../SqliteSqlTranslatingExpressionVisitor.cs | 18 ++++++++++++++++-- .../Internal/SqliteRelationalConnection.cs | 7 +++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index c957528db56..50e0431d509 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -53,10 +53,12 @@ private static readonly IReadOnlyDictionary { typeof(ulong) }, + [ExpressionType.Modulo] = new HashSet + { + typeof(ulong) + }, [ExpressionType.Multiply] = new HashSet { - typeof(decimal), typeof(TimeSpan), typeof(ulong) }, @@ -185,6 +187,18 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) return DoDecimalCompare(visitedExpression, sqlBinary.OperatorType, sqlBinary.Left, sqlBinary.Right); } + if (sqlBinary.OperatorType == ExpressionType.Multiply + && GetProviderType(sqlBinary.Left) == typeof(decimal) + && (GetProviderType(sqlBinary.Right) == typeof(decimal))) + { + return SqlExpressionFactory.Function( + "ef_multiply", + new[] { sqlBinary.Left, sqlBinary.Right }, + nullable: true, + new[] { true, true }, + visitedExpression.Type); + } + if (_restrictedBinaryExpressions.TryGetValue(sqlBinary.OperatorType, out var restrictedTypes) && (restrictedTypes.Contains(GetProviderType(sqlBinary.Left)) || restrictedTypes.Contains(GetProviderType(sqlBinary.Right)))) diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs index 97aa15e29cb..02b19f2006b 100644 --- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs @@ -167,6 +167,13 @@ private void InitializeDbConnection(DbConnection connection) : default(int?), isDeterministic: true); + sqliteConnection.CreateFunction( + name: "ef_multiply", + (decimal? left, decimal? right) => left.HasValue && right.HasValue + ? decimal.Multiply(left.Value, right.Value) + : default(decimal?), + isDeterministic: true); + sqliteConnection.CreateFunction( name: "ef_negate", (decimal? m) => m.HasValue From 430693ac7505324ce9e1650a6c7a5a77d5322fdc Mon Sep 17 00:00:00 2001 From: Philipp Zech Date: Sat, 7 Mar 2020 10:52:06 +0100 Subject: [PATCH 05/13] add "ef_subtract" by ef_add and negated subtrahend --- .../SqliteSqlTranslatingExpressionVisitor.cs | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index 50e0431d509..e0fab54250d 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -24,11 +24,7 @@ private static readonly IReadOnlyDictionary - { - typeof(TimeSpan), - typeof(ulong) - }, + [ExpressionType.Divide] = new HashSet { typeof(TimeSpan), typeof(ulong) }, [ExpressionType.GreaterThan] = new HashSet { typeof(DateTimeOffset), @@ -53,20 +49,12 @@ private static readonly IReadOnlyDictionary - { - typeof(ulong) - }, - [ExpressionType.Multiply] = new HashSet - { - typeof(TimeSpan), - typeof(ulong) - }, + [ExpressionType.Modulo] = new HashSet { typeof(ulong) }, + [ExpressionType.Multiply] = new HashSet { typeof(TimeSpan), typeof(ulong) }, [ExpressionType.Subtract] = new HashSet { typeof(DateTime), typeof(DateTimeOffset), - typeof(decimal), typeof(TimeSpan) } }; @@ -199,6 +187,24 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) visitedExpression.Type); } + if (sqlBinary.OperatorType == ExpressionType.Subtract + && GetProviderType(sqlBinary.Left) == typeof(decimal) + && (GetProviderType(sqlBinary.Right) == typeof(decimal))) + { + var subtrahend = SqlExpressionFactory.Function( + "ef_negate", + new[] { sqlBinary.Right }, + nullable: true, + new[] { true }, + visitedExpression.Type); + return SqlExpressionFactory.Function( + "ef_add", + new[] { sqlBinary.Left, subtrahend }, + nullable: true, + new[] { true, true }, + visitedExpression.Type); + } + if (_restrictedBinaryExpressions.TryGetValue(sqlBinary.OperatorType, out var restrictedTypes) && (restrictedTypes.Contains(GetProviderType(sqlBinary.Left)) || restrictedTypes.Contains(GetProviderType(sqlBinary.Right)))) From 4737cc9a7f35189a77184861d61af84aa5af98e9 Mon Sep 17 00:00:00 2001 From: Philipp Zech Date: Sat, 7 Mar 2020 16:28:37 +0100 Subject: [PATCH 06/13] refactoring --- .../SqliteSqlTranslatingExpressionVisitor.cs | 96 +++++++++---------- 1 file changed, 43 insertions(+), 53 deletions(-) diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index e0fab54250d..e248deb621a 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -146,63 +146,14 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) visitedExpression.TypeMapping); } - if (sqlBinary.OperatorType == ExpressionType.Add - && GetProviderType(sqlBinary.Left) == typeof(decimal) - && (GetProviderType(sqlBinary.Right) == typeof(decimal))) - { - return SqlExpressionFactory.Function( - "ef_add", - new[] { sqlBinary.Left, sqlBinary.Right }, - nullable: true, - new[] { true, true }, - visitedExpression.Type); - } - - if (sqlBinary.OperatorType == ExpressionType.Divide - && GetProviderType(sqlBinary.Left) == typeof(decimal) - && (GetProviderType(sqlBinary.Right) == typeof(decimal))) - { - return SqlExpressionFactory.Function( - "ef_divide", - new[] { sqlBinary.Left, sqlBinary.Right }, - nullable: true, - new[] { true, true }, - visitedExpression.Type); - } - if (AttemptDecimalCompare(sqlBinary)) { return DoDecimalCompare(visitedExpression, sqlBinary.OperatorType, sqlBinary.Left, sqlBinary.Right); } - if (sqlBinary.OperatorType == ExpressionType.Multiply - && GetProviderType(sqlBinary.Left) == typeof(decimal) - && (GetProviderType(sqlBinary.Right) == typeof(decimal))) - { - return SqlExpressionFactory.Function( - "ef_multiply", - new[] { sqlBinary.Left, sqlBinary.Right }, - nullable: true, - new[] { true, true }, - visitedExpression.Type); - } - - if (sqlBinary.OperatorType == ExpressionType.Subtract - && GetProviderType(sqlBinary.Left) == typeof(decimal) - && (GetProviderType(sqlBinary.Right) == typeof(decimal))) + if (AttemptDecimalArithmetic(sqlBinary)) { - var subtrahend = SqlExpressionFactory.Function( - "ef_negate", - new[] { sqlBinary.Right }, - nullable: true, - new[] { true }, - visitedExpression.Type); - return SqlExpressionFactory.Function( - "ef_add", - new[] { sqlBinary.Left, subtrahend }, - nullable: true, - new[] { true, true }, - visitedExpression.Type); + return DoDecimalArithmetics(visitedExpression, sqlBinary.OperatorType, sqlBinary.Left, sqlBinary.Right); } if (_restrictedBinaryExpressions.TryGetValue(sqlBinary.OperatorType, out var restrictedTypes) @@ -283,9 +234,11 @@ private static Type GetProviderType(SqlExpression expression) ?? expression.TypeMapping?.ClrType ?? expression.Type).UnwrapNullableType(); + private static bool AreOperandsDecimals(SqlBinaryExpression sqlExpression) => GetProviderType(sqlExpression.Left) == typeof(decimal) + && GetProviderType(sqlExpression.Right) == typeof(decimal); + private static bool AttemptDecimalCompare(SqlBinaryExpression sqlBinary) => - GetProviderType(sqlBinary.Left) == typeof(decimal) - && GetProviderType(sqlBinary.Right) == typeof(decimal) + AreOperandsDecimals(sqlBinary) && new[] { ExpressionType.GreaterThan, ExpressionType.GreaterThanOrEqual, ExpressionType.LessThan, ExpressionType.LessThanOrEqual @@ -310,5 +263,42 @@ private Expression DoDecimalCompare(SqlExpression visitedExpression, ExpressionT _ => visitedExpression }; } + + private static bool AttemptDecimalArithmetic(SqlBinaryExpression sqlBinary) => + AreOperandsDecimals(sqlBinary) + && new[] { ExpressionType.Add, ExpressionType.Subtract, ExpressionType.Multiply, ExpressionType.Divide }.Contains( + sqlBinary.OperatorType); + + private Expression DoDecimalArithmetics(SqlExpression visitedExpression, ExpressionType op, SqlExpression left, SqlExpression right) + { + return op switch + { + ExpressionType.Add => ArithmeticExpressionFactoryFunction("ef_add", left, right), + ExpressionType.Divide => ArithmeticExpressionFactoryFunction("ef_divide", left, right), + ExpressionType.Multiply => ArithmeticExpressionFactoryFunction("ef_multiply", left, right), + ExpressionType.Subtract => SubtractExpressionFactoryFunction(left, right), + _ => visitedExpression + }; + + Expression ArithmeticExpressionFactoryFunction(string name, SqlExpression left, SqlExpression right) => + SqlExpressionFactory.Function( + name, + new[] { left, right }, + nullable: true, + new[] { true, true }, + visitedExpression.Type); + + Expression SubtractExpressionFactoryFunction(SqlExpression left, SqlExpression right) + { + var subtrahend = SqlExpressionFactory.Function( + "ef_negate", + new[] { right }, + nullable: true, + new[] { true }, + visitedExpression.Type); + + return ArithmeticExpressionFactoryFunction("ef_add", left, subtrahend); + } + } } } From 92818288e0f5c4d7bb1c12d287416cfe130d3253 Mon Sep 17 00:00:00 2001 From: Philipp Zech Date: Sat, 7 Mar 2020 16:34:08 +0100 Subject: [PATCH 07/13] renaming --- .../SqliteSqlTranslatingExpressionVisitor.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index e248deb621a..f5226d82ce9 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -273,22 +273,24 @@ private Expression DoDecimalArithmetics(SqlExpression visitedExpression, Express { return op switch { - ExpressionType.Add => ArithmeticExpressionFactoryFunction("ef_add", left, right), - ExpressionType.Divide => ArithmeticExpressionFactoryFunction("ef_divide", left, right), - ExpressionType.Multiply => ArithmeticExpressionFactoryFunction("ef_multiply", left, right), - ExpressionType.Subtract => SubtractExpressionFactoryFunction(left, right), + ExpressionType.Add => DecimalArithmeticExpressionFactoryFunction("ef_add", left, right), + ExpressionType.Divide => DecimalArithmeticExpressionFactoryFunction("ef_divide", left, right), + ExpressionType.Multiply => DecimalArithmeticExpressionFactoryFunction("ef_multiply", left, right), + ExpressionType.Subtract => DecimalSubtractExpressionFactoryFunction(left, right), _ => visitedExpression }; - Expression ArithmeticExpressionFactoryFunction(string name, SqlExpression left, SqlExpression right) => - SqlExpressionFactory.Function( + Expression DecimalArithmeticExpressionFactoryFunction(string name, SqlExpression left, SqlExpression right) + { + return SqlExpressionFactory.Function( name, new[] { left, right }, nullable: true, new[] { true, true }, visitedExpression.Type); + } - Expression SubtractExpressionFactoryFunction(SqlExpression left, SqlExpression right) + Expression DecimalSubtractExpressionFactoryFunction(SqlExpression left, SqlExpression right) { var subtrahend = SqlExpressionFactory.Function( "ef_negate", @@ -297,7 +299,7 @@ Expression SubtractExpressionFactoryFunction(SqlExpression left, SqlExpression r new[] { true }, visitedExpression.Type); - return ArithmeticExpressionFactoryFunction("ef_add", left, subtrahend); + return DecimalArithmeticExpressionFactoryFunction("ef_add", left, subtrahend); } } } From 76185deb2969e5c244af2fd8da3becfd9c3c7a19 Mon Sep 17 00:00:00 2001 From: Philipp Zech Date: Sat, 7 Mar 2020 18:10:41 +0100 Subject: [PATCH 08/13] refactoring --- .../SqliteSqlTranslatingExpressionVisitor.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index f5226d82ce9..3dee7b5768a 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -273,13 +273,25 @@ private Expression DoDecimalArithmetics(SqlExpression visitedExpression, Express { return op switch { - ExpressionType.Add => DecimalArithmeticExpressionFactoryFunction("ef_add", left, right), - ExpressionType.Divide => DecimalArithmeticExpressionFactoryFunction("ef_divide", left, right), - ExpressionType.Multiply => DecimalArithmeticExpressionFactoryFunction("ef_multiply", left, right), + ExpressionType.Add => DecimalArithmeticExpressionFactoryFunction(ResolveFunctionNameFromExpressionType(op), left, right), + ExpressionType.Divide => DecimalArithmeticExpressionFactoryFunction(ResolveFunctionNameFromExpressionType(op), left, right), + ExpressionType.Multiply => DecimalArithmeticExpressionFactoryFunction(ResolveFunctionNameFromExpressionType(op), left, right), ExpressionType.Subtract => DecimalSubtractExpressionFactoryFunction(left, right), _ => visitedExpression }; + string ResolveFunctionNameFromExpressionType(ExpressionType expressionType) + { + return expressionType switch + { + ExpressionType.Add => "ef_add", + ExpressionType.Divide => "ef_divide", + ExpressionType.Multiply => "ef_multiply", + ExpressionType.Subtract => "ef_add", + _ => throw new InvalidOperationException() + }; + } + Expression DecimalArithmeticExpressionFactoryFunction(string name, SqlExpression left, SqlExpression right) { return SqlExpressionFactory.Function( @@ -299,7 +311,7 @@ Expression DecimalSubtractExpressionFactoryFunction(SqlExpression left, SqlExpre new[] { true }, visitedExpression.Type); - return DecimalArithmeticExpressionFactoryFunction("ef_add", left, subtrahend); + return DecimalArithmeticExpressionFactoryFunction(ResolveFunctionNameFromExpressionType(op), left, subtrahend); } } } From f72fcb5e58061127966a8d321dec2009892df1ec Mon Sep 17 00:00:00 2001 From: Philipp Zech Date: Sat, 7 Mar 2020 18:21:42 +0100 Subject: [PATCH 09/13] renaming --- .../SqliteSqlTranslatingExpressionVisitor.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index 3dee7b5768a..88fcc4bca81 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -273,10 +273,10 @@ private Expression DoDecimalArithmetics(SqlExpression visitedExpression, Express { return op switch { - ExpressionType.Add => DecimalArithmeticExpressionFactoryFunction(ResolveFunctionNameFromExpressionType(op), left, right), - ExpressionType.Divide => DecimalArithmeticExpressionFactoryFunction(ResolveFunctionNameFromExpressionType(op), left, right), - ExpressionType.Multiply => DecimalArithmeticExpressionFactoryFunction(ResolveFunctionNameFromExpressionType(op), left, right), - ExpressionType.Subtract => DecimalSubtractExpressionFactoryFunction(left, right), + ExpressionType.Add => DecimalArithmeticExpressionFactoryMethod(ResolveFunctionNameFromExpressionType(op), left, right), + ExpressionType.Divide => DecimalArithmeticExpressionFactoryMethod(ResolveFunctionNameFromExpressionType(op), left, right), + ExpressionType.Multiply => DecimalArithmeticExpressionFactoryMethod(ResolveFunctionNameFromExpressionType(op), left, right), + ExpressionType.Subtract => DecimalSubtractExpressionFactoryMethod(left, right), _ => visitedExpression }; @@ -292,7 +292,7 @@ string ResolveFunctionNameFromExpressionType(ExpressionType expressionType) }; } - Expression DecimalArithmeticExpressionFactoryFunction(string name, SqlExpression left, SqlExpression right) + Expression DecimalArithmeticExpressionFactoryMethod(string name, SqlExpression left, SqlExpression right) { return SqlExpressionFactory.Function( name, @@ -302,7 +302,7 @@ Expression DecimalArithmeticExpressionFactoryFunction(string name, SqlExpression visitedExpression.Type); } - Expression DecimalSubtractExpressionFactoryFunction(SqlExpression left, SqlExpression right) + Expression DecimalSubtractExpressionFactoryMethod(SqlExpression left, SqlExpression right) { var subtrahend = SqlExpressionFactory.Function( "ef_negate", @@ -311,7 +311,7 @@ Expression DecimalSubtractExpressionFactoryFunction(SqlExpression left, SqlExpre new[] { true }, visitedExpression.Type); - return DecimalArithmeticExpressionFactoryFunction(ResolveFunctionNameFromExpressionType(op), left, subtrahend); + return DecimalArithmeticExpressionFactoryMethod(ResolveFunctionNameFromExpressionType(op), left, subtrahend); } } } From e914ab9ff1caebb92c8d422d421fd9bd43d97046 Mon Sep 17 00:00:00 2001 From: Philipp Zech Date: Sat, 14 Mar 2020 06:55:42 +0100 Subject: [PATCH 10/13] fix: Apply bricelam's comments. --- .../Internal/SqliteRelationalConnection.cs | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs index 02b19f2006b..eb6fe5281e3 100644 --- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs @@ -136,28 +136,12 @@ private void InitializeDbConnection(DbConnection connection) sqliteConnection.CreateFunction( name: "ef_add", - (decimal? left, decimal? right) => left.HasValue && right.HasValue - ? decimal.Add(left.Value, right.Value) - : default(decimal?), + (decimal? left, decimal? right) => left + right, isDeterministic: true); sqliteConnection.CreateFunction( name: "ef_divide", - (decimal? dividend, decimal? divisor) => - { - if (dividend == null - || divisor == null) - { - return new decimal(null); - } - - if (divisor.Value != 0) - { - return decimal.Divide(dividend.Value, divisor.Value); - } - - throw new DivideByZeroException(); - }, + (decimal? dividend, decimal? divisor) => dividend / divisor, isDeterministic: true); sqliteConnection.CreateFunction( @@ -169,16 +153,12 @@ private void InitializeDbConnection(DbConnection connection) sqliteConnection.CreateFunction( name: "ef_multiply", - (decimal? left, decimal? right) => left.HasValue && right.HasValue - ? decimal.Multiply(left.Value, right.Value) - : default(decimal?), + (decimal? left, decimal? right) => left * right, isDeterministic: true); sqliteConnection.CreateFunction( name: "ef_negate", - (decimal? m) => m.HasValue - ? decimal.Negate(m.Value) - : default(decimal?), + (decimal? m) => -m, isDeterministic: true); } else From 18afdffe7b933ae86b5aa6b72806b0fc1f74a742 Mon Sep 17 00:00:00 2001 From: Philipp Zech Date: Thu, 23 Apr 2020 18:27:11 +0200 Subject: [PATCH 11/13] new: --- .../Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index 598f10a3b67..0e8efe76efa 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -277,7 +277,7 @@ private Expression DoDecimalArithmetics(SqlExpression visitedExpression, Express _ => visitedExpression }; - string ResolveFunctionNameFromExpressionType(ExpressionType expressionType) + static string ResolveFunctionNameFromExpressionType(ExpressionType expressionType) { return expressionType switch { From 531e1e69c3daa0f591c70fafa28772e2cea2cd51 Mon Sep 17 00:00:00 2001 From: Philipp Zech Date: Sun, 3 May 2020 11:55:55 +0200 Subject: [PATCH 12/13] fix: call SqlExpressionFactory from Dependencies --- .../Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index f73a5c86ba0..af8f60e90c9 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -120,7 +120,7 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) var operandType = GetProviderType(sqlUnary.Operand); if (operandType == typeof(decimal)) { - return SqlExpressionFactory.Function( + return Dependencies.SqlExpressionFactory.Function( name: "ef_negate", new[] { sqlUnary.Operand }, nullable: true, @@ -345,7 +345,7 @@ static string ResolveFunctionNameFromExpressionType(ExpressionType expressionTyp Expression DecimalArithmeticExpressionFactoryMethod(string name, SqlExpression left, SqlExpression right) { - return SqlExpressionFactory.Function( + return Dependencies.SqlExpressionFactory.Function( name, new[] { left, right }, nullable: true, @@ -355,7 +355,7 @@ Expression DecimalArithmeticExpressionFactoryMethod(string name, SqlExpression l Expression DecimalSubtractExpressionFactoryMethod(SqlExpression left, SqlExpression right) { - var subtrahend = SqlExpressionFactory.Function( + var subtrahend = Dependencies.SqlExpressionFactory.Function( "ef_negate", new[] { right }, nullable: true, From 35822f8103ae6ec6707051e482937a8245ce93f0 Mon Sep 17 00:00:00 2001 From: Philipp Zech Date: Thu, 9 Jul 2020 21:19:57 +0200 Subject: [PATCH 13/13] [new] update tests --- .../BuiltInDataTypesSqliteTest.cs | 47 +++++++++++++++++++ ...thwindAggregateOperatorsQuerySqliteTest.cs | 10 ++-- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs index 9fbcc0e87d8..1d22abb7385 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs @@ -1505,6 +1505,53 @@ public override void Object_to_string_conversion() WHERE ""b"".""Id"" = 13"); } + [ConditionalFact] + public virtual void Projecting_aritmetic_operations_on_decimals() + { + using var context = CreateContext(); + var expected = (from dt1 in context.Set().ToList() + from dt2 in context.Set().ToList() + orderby dt1.Id, dt2.Id + select new + { + add = dt1.TestDecimal + dt2.TestDecimal, + subtract = dt1.TestDecimal - dt2.TestDecimal, + multiply = dt1.TestDecimal * dt2.TestDecimal, + divide = dt1.TestDecimal / dt2.TestDecimal, + negate = -dt1.TestDecimal + }).ToList(); + + Fixture.TestSqlLoggerFactory.Clear(); + + var actual = (from dt1 in context.Set() + from dt2 in context.Set() + orderby dt1.Id, dt2.Id + select new + { + add = dt1.TestDecimal + dt2.TestDecimal, + subtract = dt1.TestDecimal - dt2.TestDecimal, + multiply = dt1.TestDecimal * dt2.TestDecimal, + divide = dt1.TestDecimal / dt2.TestDecimal, + negate = -dt1.TestDecimal + }).ToList(); + + Assert.Equal(expected.Count, actual.Count); + for (var i = 0; i < expected.Count; i++) + { + Assert.Equal(expected[i].add, actual[i].add); + Assert.Equal(expected[i].subtract, actual[i].subtract); + Assert.Equal(expected[i].multiply, actual[i].multiply); + Assert.Equal(expected[i].divide, actual[i].divide); + Assert.Equal(expected[i].negate, actual[i].negate); + } + + AssertSql( + @"SELECT ef_add(""b"".""TestDecimal"", ""b0"".""TestDecimal"") AS ""add"", ef_add(""b"".""TestDecimal"", ef_negate(""b0"".""TestDecimal"")) AS ""subtract"", ef_multiply(""b"".""TestDecimal"", ""b0"".""TestDecimal"") AS ""multiply"", ef_divide(""b"".""TestDecimal"", ""b0"".""TestDecimal"") AS ""divide"", ef_negate(""b"".""TestDecimal"") AS ""negate"" +FROM ""BuiltInDataTypes"" AS ""b"" +CROSS JOIN ""BuiltInDataTypes"" AS ""b0"" +ORDER BY ""b"".""Id"", ""b0"".""Id"""); + } + private void AssertTranslationFailed(Action testCode) => Assert.Contains( CoreStrings.TranslationFailed("").Substring(21), diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqliteTest.cs index c4add4c207f..7596ed52576 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqliteTest.cs @@ -1,8 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; using Xunit.Abstractions; namespace Microsoft.EntityFrameworkCore.Query @@ -17,15 +19,15 @@ public NorthwindAggregateOperatorsQuerySqliteTest(NorthwindQuerySqliteFixture AssertTranslationFailed(() => base.Sum_with_division_on_decimal(async)); + => Assert.ThrowsAsync(() => base.Sum_with_division_on_decimal(async)); public override Task Sum_with_division_on_decimal_no_significant_digits(bool async) - => AssertTranslationFailed(() => base.Sum_with_division_on_decimal_no_significant_digits(async)); + => Assert.ThrowsAsync(() => base.Sum_with_division_on_decimal_no_significant_digits(async)); public override Task Average_with_division_on_decimal(bool async) - => AssertTranslationFailed(() => base.Average_with_division_on_decimal(async)); + => Assert.ThrowsAsync(() => base.Average_with_division_on_decimal(async)); public override Task Average_with_division_on_decimal_no_significant_digits(bool async) - => AssertTranslationFailed(() => base.Average_with_division_on_decimal_no_significant_digits(async)); + => Assert.ThrowsAsync(() => base.Average_with_division_on_decimal_no_significant_digits(async)); } }