diff --git a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerByteArrayMethodTranslator.cs b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerByteArrayMethodTranslator.cs index c50f5420e35..335b2b1c299 100644 --- a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerByteArrayMethodTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerByteArrayMethodTranslator.cs @@ -14,6 +14,12 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; /// public class SqlServerByteArrayMethodTranslator : IMethodCallTranslator { + private static readonly MethodInfo ArrayIndexOf + = typeof(Array).GetMethod(nameof(Array.IndexOf), 1, BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly, null, CallingConventions.Any, [Type.MakeGenericMethodParameter(0).MakeArrayType(), Type.MakeGenericMethodParameter(0)], null)!; + + private static readonly MethodInfo ArrayIndexOfWithStartIndex + = typeof(Array).GetMethod(nameof(Array.IndexOf), 1, BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly, null, CallingConventions.Any, [Type.MakeGenericMethodParameter(0).MakeArrayType(), Type.MakeGenericMethodParameter(0), typeof(int)], null)!; + private readonly ISqlExpressionFactory _sqlExpressionFactory; /// @@ -38,31 +44,32 @@ public SqlServerByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFac IDiagnosticsLogger logger) { if (method.IsGenericMethod - && method.GetGenericMethodDefinition().Equals(EnumerableMethods.Contains) + && arguments.Count >= 1 && arguments[0].Type == typeof(byte[])) { - var source = arguments[0]; - var sourceTypeMapping = source.TypeMapping; + var methodDefinition = method.GetGenericMethodDefinition(); + if (methodDefinition.Equals(EnumerableMethods.Contains)) + { + var source = arguments[0]; + var sourceTypeMapping = source.TypeMapping; - var value = arguments[1] is SqlConstantExpression constantValue - ? _sqlExpressionFactory.Constant(new[] { (byte)constantValue.Value! }, sourceTypeMapping) - : _sqlExpressionFactory.Convert(arguments[1], typeof(byte[]), sourceTypeMapping); + var value = arguments[1] is SqlConstantExpression constantValue + ? _sqlExpressionFactory.Constant(new[] { (byte)constantValue.Value! }, sourceTypeMapping) + : _sqlExpressionFactory.Convert(arguments[1], typeof(byte[]), sourceTypeMapping); - return _sqlExpressionFactory.GreaterThan( - _sqlExpressionFactory.Function( - "CHARINDEX", - [value, source], - nullable: true, - argumentsPropagateNullability: [true, true], - typeof(int)), - _sqlExpressionFactory.Constant(0)); - } + return _sqlExpressionFactory.GreaterThan( + _sqlExpressionFactory.Function( + "CHARINDEX", + [value, source], + nullable: true, + argumentsPropagateNullability: [true, true], + typeof(int)), + _sqlExpressionFactory.Constant(0)); + } - if (method.IsGenericMethod - && method.GetGenericMethodDefinition().Equals(EnumerableMethods.FirstWithoutPredicate) - && arguments[0].Type == typeof(byte[])) - { - return _sqlExpressionFactory.Convert( + if (methodDefinition.Equals(EnumerableMethods.FirstWithoutPredicate)) + { + return _sqlExpressionFactory.Convert( _sqlExpressionFactory.Function( "SUBSTRING", [arguments[0], _sqlExpressionFactory.Constant(1), _sqlExpressionFactory.Constant(1)], @@ -70,8 +77,69 @@ public SqlServerByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFac argumentsPropagateNullability: [true, true, true], typeof(byte[])), method.ReturnType); + } + + if (methodDefinition.Equals(ArrayIndexOf)) + { + return TranslateByteArrayIndexOf(method, arguments[0], arguments[1], null); + } + + if (methodDefinition.Equals(ArrayIndexOfWithStartIndex)) + { + return TranslateByteArrayIndexOf(method, arguments[0], arguments[1], arguments[2]); + } } return null; } + + private SqlExpression TranslateByteArrayIndexOf( + MethodInfo method, + SqlExpression source, + SqlExpression valueToSearch, + SqlExpression? startIndex) + { + var sourceTypeMapping = source.TypeMapping; + var sqlArguments = new List + { + valueToSearch is SqlConstantExpression { Value: byte constantValue } + ? _sqlExpressionFactory.Constant(new byte[] { constantValue }, sourceTypeMapping) + : _sqlExpressionFactory.Convert(valueToSearch, typeof(byte[]), sourceTypeMapping), + source + }; + + if (startIndex is not null) + { + sqlArguments.Add( + startIndex is SqlConstantExpression { Value : int index } + ? _sqlExpressionFactory.Constant(index + 1, typeof(int)) + : _sqlExpressionFactory.Add(startIndex, _sqlExpressionFactory.Constant(1))); + } + + var argumentsPropagateNullability = Enumerable.Repeat(true, sqlArguments.Count); + + SqlExpression charIndexExpr; + var storeType = sourceTypeMapping?.StoreType; + if (storeType == "varbinary(max)") + { + charIndexExpr = GetCharIndexSqlFunctionExpression(sqlArguments, argumentsPropagateNullability, typeof(long)); + charIndexExpr = _sqlExpressionFactory.Convert(charIndexExpr, typeof(int)); + } + else + { + charIndexExpr = GetCharIndexSqlFunctionExpression(sqlArguments, argumentsPropagateNullability, method.ReturnType); + } + + return _sqlExpressionFactory.Subtract(charIndexExpr, _sqlExpressionFactory.Constant(1)); + + SqlExpression GetCharIndexSqlFunctionExpression(List sqlArguments, IEnumerable argumentsPropagateNullability, Type returnType) + { + return _sqlExpressionFactory.Function( + "CHARINDEX", + sqlArguments, + nullable: true, + argumentsPropagateNullability: argumentsPropagateNullability, + returnType); + } + } } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteByteArrayMethodTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteByteArrayMethodTranslator.cs index 195726644e3..2a5becdbae9 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteByteArrayMethodTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteByteArrayMethodTranslator.cs @@ -14,6 +14,9 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; /// public class SqliteByteArrayMethodTranslator : IMethodCallTranslator { + private static readonly MethodInfo ArrayIndexOf + = typeof(Array).GetMethod(nameof(Array.IndexOf), 1, BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly, null, CallingConventions.Any, [Type.MakeGenericMethodParameter(0).MakeArrayType(), Type.MakeGenericMethodParameter(0)], null)!; + private readonly ISqlExpressionFactory _sqlExpressionFactory; /// @@ -38,28 +41,26 @@ public SqliteByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFactor IDiagnosticsLogger logger) { if (method.IsGenericMethod - && method.GetGenericMethodDefinition().Equals(EnumerableMethods.Contains) + && arguments.Count >= 1 && arguments[0].Type == typeof(byte[])) { - var source = arguments[0]; + var genericMethodDefinition = method.GetGenericMethodDefinition(); + if (genericMethodDefinition.Equals(EnumerableMethods.Contains)) + { + return _sqlExpressionFactory.GreaterThan( + GetInStrSqlFunctionExpression(arguments[0], arguments[1]), + _sqlExpressionFactory.Constant(0)); - var value = arguments[1] is SqlConstantExpression constantValue - ? (SqlExpression)_sqlExpressionFactory.Constant(new[] { (byte)constantValue.Value! }, source.TypeMapping) - : _sqlExpressionFactory.Function( - "char", - new[] { arguments[1] }, - nullable: false, - argumentsPropagateNullability: new[] { false }, - typeof(string)); + } + + if (genericMethodDefinition.Equals(ArrayIndexOf)) + { + return _sqlExpressionFactory.Subtract( + GetInStrSqlFunctionExpression(arguments[0], arguments[1]), + _sqlExpressionFactory.Constant(1)); + } - return _sqlExpressionFactory.GreaterThan( - _sqlExpressionFactory.Function( - "instr", - new[] { source, value }, - nullable: true, - argumentsPropagateNullability: new[] { true, true }, - typeof(int)), - _sqlExpressionFactory.Constant(0)); + // NOTE: IndexOf Method with a starting position is not supported by SQLite } // See issue#16428 @@ -89,5 +90,24 @@ public SqliteByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFactor //} return null; + + SqlExpression GetInStrSqlFunctionExpression(SqlExpression source, SqlExpression valueToSearch) + { + var value = valueToSearch is SqlConstantExpression { Value: byte constantValue } + ? _sqlExpressionFactory.Constant(new byte[] { constantValue }, source.TypeMapping) + : _sqlExpressionFactory.Function( + "char", + [valueToSearch], + nullable: false, + argumentsPropagateNullability: [false], + typeof(string)); + + return _sqlExpressionFactory.Function( + "instr", + [source, value], + nullable: true, + argumentsPropagateNullability: [true, true], + typeof(int)); + } } } diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index ec8180be84c..dcc7b7c2bce 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -6258,6 +6258,88 @@ public virtual Task Byte_array_filter_by_length_parameter(bool async) ss => ss.Set().Where(w => w.Banner != null && w.Banner.Length == someByteArr.Length)); } + #region Byte Array IndexOf + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Byte_array_IndexOf_with_literal(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(w => Array.IndexOf(w.Banner, (byte)1) == 1), + ss => ss.Set().Where(w => w.Banner != null && Array.IndexOf(w.Banner, (byte)1) == 1)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Byte_array_IndexOf_with_parameter(bool async) + { + byte b = 0; + return AssertQuery( + async, + ss => ss.Set().Where(w => Array.IndexOf(w.Banner, b) == 0), + ss => ss.Set().Where(w => w.Banner != null && Array.IndexOf(w.Banner, b) == 0)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Byte_array_with_length_IndexOf_with_literal(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(w => Array.IndexOf(w.Banner5, (byte)5) == 1), + ss => ss.Set().Where(w => w.Banner != null && Array.IndexOf(w.Banner5, (byte)5) == 1)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Byte_array_with_length_IndexOf_with_parameter(bool async) + { + byte b = 4; + return AssertQuery( + async, + ss => ss.Set().Where(w => Array.IndexOf(w.Banner5, b) == 0), + ss => ss.Set().Where(w => w.Banner != null && Array.IndexOf(w.Banner5, b) == 0)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Byte_array_IndexOf_with_startIndex_with_literals(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(w => Array.IndexOf(w.Banner, (byte)1, 1) == 1), + ss => ss.Set().Where(w => w.Banner != null && Array.IndexOf(w.Banner, (byte)1, 1) == 1)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Byte_array_IndexOf_with_startIndex_with_parameters(bool async) + { + byte b = 0; + var startPos = 0; + return AssertQuery( + async, + ss => ss.Set().Where(w => Array.IndexOf(w.Banner, b, startPos) == 0), + ss => ss.Set().Where(w => w.Banner != null && Array.IndexOf(w.Banner, b, startPos) == 0)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Byte_array_with_length_IndexOf_with_startIndex_with_literals(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(w => Array.IndexOf(w.Banner5, (byte)5, 1) == 1), + ss => ss.Set().Where(w => w.Banner != null && Array.IndexOf(w.Banner5, (byte)5, 1) == 1)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Byte_array_with_length_IndexOf_with_startIndex_with_parameters(bool async) + { + byte b = 4; + var startPos = 0; + return AssertQuery( + async, + ss => ss.Set().Where(w => Array.IndexOf(w.Banner5, b, startPos) == 0), + ss => ss.Set().Where(w => w.Banner != null && Array.IndexOf(w.Banner5, b, startPos) == 0)); + } + + #endregion + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task OrderBy_bool_coming_from_optional_navigation(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 14db73c59cb..5185ccf7ed8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -7610,6 +7610,20 @@ WHERE CHARINDEX(0x01, [s].[Banner]) > 0 """); } + public override async Task Byte_array_contains_parameter(bool async) + { + await base.Byte_array_contains_parameter(async); + + AssertSql( + """ +@__someByte_0='1' (Size = 1) + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CHARINDEX(CAST(@__someByte_0 AS varbinary(max)), [s].[Banner]) > 0 +"""); + } + public override async Task Byte_array_filter_by_length_literal(bool async) { await base.Byte_array_filter_by_length_literal(async); @@ -7650,32 +7664,128 @@ WHERE CAST(DATALENGTH([s].[Banner]) AS int) = CAST(DATALENGTH(@__byteArrayParam) """); } - public override async Task Byte_array_contains_parameter(bool async) + public override async Task Byte_array_filter_by_length_literal_does_not_cast_on_varbinary_n(bool async) { - await base.Byte_array_contains_parameter(async); + await base.Byte_array_filter_by_length_literal_does_not_cast_on_varbinary_n(async); AssertSql( """ -@__someByte_0='1' (Size = 1) +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE DATALENGTH([s].[Banner5]) = 5 +"""); + } + + #region Byte Array IndexOf Translation + + public override async Task Byte_array_IndexOf_with_literal(bool async) + { + await base.Byte_array_IndexOf_with_literal(async); + AssertSql( + """ SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] FROM [Squads] AS [s] -WHERE CHARINDEX(CAST(@__someByte_0 AS varbinary(max)), [s].[Banner]) > 0 +WHERE CAST(CHARINDEX(0x01, [s].[Banner]) AS int) - 1 = 1 """); } - public override async Task Byte_array_filter_by_length_literal_does_not_cast_on_varbinary_n(bool async) + public override async Task Byte_array_IndexOf_with_parameter(bool async) { - await base.Byte_array_filter_by_length_literal_does_not_cast_on_varbinary_n(async); + await base.Byte_array_IndexOf_with_parameter(async); AssertSql( """ +@__b_0='0' (Size = 1) + SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] FROM [Squads] AS [s] -WHERE DATALENGTH([s].[Banner5]) = 5 +WHERE CAST(CHARINDEX(CAST(@__b_0 AS varbinary(max)), [s].[Banner]) AS int) - 1 = 0 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_literal(bool async) + { + await base.Byte_array_with_length_IndexOf_with_literal(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CHARINDEX(0x05, [s].[Banner5]) - 1 = 1 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_parameter(bool async) + { + await base.Byte_array_with_length_IndexOf_with_parameter(async); + + AssertSql( + """ +@__b_0='4' (Size = 1) + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CHARINDEX(CAST(@__b_0 AS varbinary(5)), [s].[Banner5]) - 1 = 0 +"""); + } + + public override async Task Byte_array_IndexOf_with_startIndex_with_literals(bool async) + { + await base.Byte_array_IndexOf_with_startIndex_with_literals(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CAST(CHARINDEX(0x01, [s].[Banner], 2) AS int) - 1 = 1 +"""); + } + + public override async Task Byte_array_IndexOf_with_startIndex_with_parameters(bool async) + { + await base.Byte_array_IndexOf_with_startIndex_with_parameters(async); + + AssertSql( + """ +@__b_0='0' (Size = 1) +@__startPos_1='0' + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CAST(CHARINDEX(CAST(@__b_0 AS varbinary(max)), [s].[Banner], @__startPos_1 + 1) AS int) - 1 = 0 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_startIndex_with_literals(bool async) + { + await base.Byte_array_with_length_IndexOf_with_startIndex_with_literals(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CHARINDEX(0x05, [s].[Banner5], 2) - 1 = 1 """); } + public override async Task Byte_array_with_length_IndexOf_with_startIndex_with_parameters(bool async) + { + await base.Byte_array_with_length_IndexOf_with_startIndex_with_parameters(async); + + AssertSql( + """ +@__b_0='4' (Size = 1) +@__startPos_1='0' + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CHARINDEX(CAST(@__b_0 AS varbinary(5)), [s].[Banner5], @__startPos_1 + 1) - 1 = 0 +"""); + } + + #endregion + public override async Task Conditional_expression_with_test_being_simplified_to_constant_simple(bool isAsync) { await base.Conditional_expression_with_test_being_simplified_to_constant_simple(isAsync); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs index f8d41c87f63..cac26b7d022 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs @@ -10220,6 +10220,116 @@ WHERE DATALENGTH([s].[Banner5]) = 5 """); } + #region Byte Array IndexOf Translation + + public override async Task Byte_array_IndexOf_with_literal(bool async) + { + await base.Byte_array_IndexOf_with_literal(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CAST(CHARINDEX(0x01, [s].[Banner]) AS int) - 1 = 1 +"""); + } + + public override async Task Byte_array_IndexOf_with_parameter(bool async) + { + await base.Byte_array_IndexOf_with_parameter(async); + + AssertSql( + """ +@__b_0='0' (Size = 1) + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CAST(CHARINDEX(CAST(@__b_0 AS varbinary(max)), [s].[Banner]) AS int) - 1 = 0 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_literal(bool async) + { + await base.Byte_array_with_length_IndexOf_with_literal(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CHARINDEX(0x05, [s].[Banner5]) - 1 = 1 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_parameter(bool async) + { + await base.Byte_array_with_length_IndexOf_with_parameter(async); + + AssertSql( + """ +@__b_0='4' (Size = 1) + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CHARINDEX(CAST(@__b_0 AS varbinary(5)), [s].[Banner5]) - 1 = 0 +"""); + } + + public override async Task Byte_array_IndexOf_with_startIndex_with_literals(bool async) + { + await base.Byte_array_IndexOf_with_startIndex_with_literals(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CAST(CHARINDEX(0x01, [s].[Banner], 2) AS int) - 1 = 1 +"""); + } + + public override async Task Byte_array_IndexOf_with_startIndex_with_parameters(bool async) + { + await base.Byte_array_IndexOf_with_startIndex_with_parameters(async); + + AssertSql( + """ +@__b_0='0' (Size = 1) +@__startPos_1='0' + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CAST(CHARINDEX(CAST(@__b_0 AS varbinary(max)), [s].[Banner], @__startPos_1 + 1) AS int) - 1 = 0 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_startIndex_with_literals(bool async) + { + await base.Byte_array_with_length_IndexOf_with_startIndex_with_literals(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CHARINDEX(0x05, [s].[Banner5], 2) - 1 = 1 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_startIndex_with_parameters(bool async) + { + await base.Byte_array_with_length_IndexOf_with_startIndex_with_parameters(async); + + AssertSql( + """ +@__b_0='4' (Size = 1) +@__startPos_1='0' + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CHARINDEX(CAST(@__b_0 AS varbinary(5)), [s].[Banner5], @__startPos_1 + 1) - 1 = 0 +"""); + } + + #endregion + public override async Task Conditional_expression_with_test_being_simplified_to_constant_simple(bool isAsync) { await base.Conditional_expression_with_test_being_simplified_to_constant_simple(isAsync); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index e2260d6cc08..46557412d91 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -8686,6 +8686,116 @@ WHERE DATALENGTH([s].[Banner5]) = 5 """); } + #region Byte Array IndexOf Translation + + public override async Task Byte_array_IndexOf_with_literal(bool async) + { + await base.Byte_array_IndexOf_with_literal(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CAST(CHARINDEX(0x01, [s].[Banner]) AS int) - 1 = 1 +"""); + } + + public override async Task Byte_array_IndexOf_with_parameter(bool async) + { + await base.Byte_array_IndexOf_with_parameter(async); + + AssertSql( + """ +@__b_0='0' (Size = 1) + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CAST(CHARINDEX(CAST(@__b_0 AS varbinary(max)), [s].[Banner]) AS int) - 1 = 0 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_literal(bool async) + { + await base.Byte_array_with_length_IndexOf_with_literal(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CHARINDEX(0x05, [s].[Banner5]) - 1 = 1 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_parameter(bool async) + { + await base.Byte_array_with_length_IndexOf_with_parameter(async); + + AssertSql( + """ +@__b_0='4' (Size = 1) + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CHARINDEX(CAST(@__b_0 AS varbinary(5)), [s].[Banner5]) - 1 = 0 +"""); + } + + public override async Task Byte_array_IndexOf_with_startIndex_with_literals(bool async) + { + await base.Byte_array_IndexOf_with_startIndex_with_literals(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CAST(CHARINDEX(0x01, [s].[Banner], 2) AS int) - 1 = 1 +"""); + } + + public override async Task Byte_array_IndexOf_with_startIndex_with_parameters(bool async) + { + await base.Byte_array_IndexOf_with_startIndex_with_parameters(async); + + AssertSql( + """ +@__b_0='0' (Size = 1) +@__startPos_1='0' + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CAST(CHARINDEX(CAST(@__b_0 AS varbinary(max)), [s].[Banner], @__startPos_1 + 1) AS int) - 1 = 0 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_startIndex_with_literals(bool async) + { + await base.Byte_array_with_length_IndexOf_with_startIndex_with_literals(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CHARINDEX(0x05, [s].[Banner5], 2) - 1 = 1 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_startIndex_with_parameters(bool async) + { + await base.Byte_array_with_length_IndexOf_with_startIndex_with_parameters(async); + + AssertSql( + """ +@__b_0='4' (Size = 1) +@__startPos_1='0' + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE CHARINDEX(CAST(@__b_0 AS varbinary(5)), [s].[Banner5], @__startPos_1 + 1) - 1 = 0 +"""); + } + + #endregion + public override async Task Conditional_expression_with_test_being_simplified_to_constant_simple(bool isAsync) { await base.Conditional_expression_with_test_being_simplified_to_constant_simple(isAsync); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs index 6895ee561b1..cef85fd94b6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs @@ -3262,6 +3262,116 @@ WHEN [t].[GearNickName] IS NOT NULL THEN [g].[Nickname] """); } + #region Byte Array IndexOf Translation + + public override async Task Byte_array_IndexOf_with_literal(bool async) + { + await base.Byte_array_IndexOf_with_literal(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name], [s].[PeriodEnd], [s].[PeriodStart] +FROM [Squads] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] +WHERE CAST(CHARINDEX(0x01, [s].[Banner]) AS int) - 1 = 1 +"""); + } + + public override async Task Byte_array_IndexOf_with_parameter(bool async) + { + await base.Byte_array_IndexOf_with_parameter(async); + + AssertSql( + """ +@__b_0='0' (Size = 1) + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name], [s].[PeriodEnd], [s].[PeriodStart] +FROM [Squads] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] +WHERE CAST(CHARINDEX(CAST(@__b_0 AS varbinary(max)), [s].[Banner]) AS int) - 1 = 0 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_literal(bool async) + { + await base.Byte_array_with_length_IndexOf_with_literal(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name], [s].[PeriodEnd], [s].[PeriodStart] +FROM [Squads] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] +WHERE CHARINDEX(0x05, [s].[Banner5]) - 1 = 1 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_parameter(bool async) + { + await base.Byte_array_with_length_IndexOf_with_parameter(async); + + AssertSql( + """ +@__b_0='4' (Size = 1) + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name], [s].[PeriodEnd], [s].[PeriodStart] +FROM [Squads] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] +WHERE CHARINDEX(CAST(@__b_0 AS varbinary(5)), [s].[Banner5]) - 1 = 0 +"""); + } + + public override async Task Byte_array_IndexOf_with_startIndex_with_literals(bool async) + { + await base.Byte_array_IndexOf_with_startIndex_with_literals(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name], [s].[PeriodEnd], [s].[PeriodStart] +FROM [Squads] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] +WHERE CAST(CHARINDEX(0x01, [s].[Banner], 2) AS int) - 1 = 1 +"""); + } + + public override async Task Byte_array_IndexOf_with_startIndex_with_parameters(bool async) + { + await base.Byte_array_IndexOf_with_startIndex_with_parameters(async); + + AssertSql( + """ +@__b_0='0' (Size = 1) +@__startPos_1='0' + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name], [s].[PeriodEnd], [s].[PeriodStart] +FROM [Squads] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] +WHERE CAST(CHARINDEX(CAST(@__b_0 AS varbinary(max)), [s].[Banner], @__startPos_1 + 1) AS int) - 1 = 0 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_startIndex_with_literals(bool async) + { + await base.Byte_array_with_length_IndexOf_with_startIndex_with_literals(async); + + AssertSql( + """ +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name], [s].[PeriodEnd], [s].[PeriodStart] +FROM [Squads] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] +WHERE CHARINDEX(0x05, [s].[Banner5], 2) - 1 = 1 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_startIndex_with_parameters(bool async) + { + await base.Byte_array_with_length_IndexOf_with_startIndex_with_parameters(async); + + AssertSql( + """ +@__b_0='4' (Size = 1) +@__startPos_1='0' + +SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name], [s].[PeriodEnd], [s].[PeriodStart] +FROM [Squads] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] +WHERE CHARINDEX(CAST(@__b_0 AS varbinary(5)), [s].[Banner5], @__startPos_1 + 1) - 1 = 0 +"""); + } + + #endregion + public override async Task Byte_array_filter_by_length_literal_does_not_cast_on_varbinary_n(bool async) { await base.Byte_array_filter_by_length_literal_does_not_cast_on_varbinary_n(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 6fff187cf93..194d99a922a 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -436,6 +436,74 @@ WHERE length("s"."Banner") = length(@__byteArrayParam) """); } + #region Byte Array IndexOf Translation + + public override async Task Byte_array_IndexOf_with_literal(bool async) + { + await base.Byte_array_IndexOf_with_literal(async); + + AssertSql( + """ +SELECT "s"."Id", "s"."Banner", "s"."Banner5", "s"."InternalNumber", "s"."Name" +FROM "Squads" AS "s" +WHERE instr("s"."Banner", X'01') - 1 = 1 +"""); + } + + public override async Task Byte_array_IndexOf_with_parameter(bool async) + { + await base.Byte_array_IndexOf_with_parameter(async); + + AssertSql( + """ +@__b_0='0' + +SELECT "s"."Id", "s"."Banner", "s"."Banner5", "s"."InternalNumber", "s"."Name" +FROM "Squads" AS "s" +WHERE instr("s"."Banner", char(@__b_0)) - 1 = 0 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_literal(bool async) + { + await base.Byte_array_with_length_IndexOf_with_literal(async); + + AssertSql( + """ +SELECT "s"."Id", "s"."Banner", "s"."Banner5", "s"."InternalNumber", "s"."Name" +FROM "Squads" AS "s" +WHERE instr("s"."Banner5", X'05') - 1 = 1 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_parameter(bool async) + { + await base.Byte_array_with_length_IndexOf_with_parameter(async); + + AssertSql( + """ +@__b_0='4' + +SELECT "s"."Id", "s"."Banner", "s"."Banner5", "s"."InternalNumber", "s"."Name" +FROM "Squads" AS "s" +WHERE instr("s"."Banner5", char(@__b_0)) - 1 = 0 +"""); + } + + public override Task Byte_array_IndexOf_with_startIndex_with_literals(bool async) + => AssertTranslationFailed(() => base.Byte_array_IndexOf_with_startIndex_with_literals(async)); + + public override Task Byte_array_IndexOf_with_startIndex_with_parameters(bool async) + => AssertTranslationFailed(() => base.Byte_array_IndexOf_with_startIndex_with_parameters(async)); + + public override Task Byte_array_with_length_IndexOf_with_startIndex_with_literals(bool async) + => AssertTranslationFailed(() => base.Byte_array_with_length_IndexOf_with_startIndex_with_literals(async)); + + public override Task Byte_array_with_length_IndexOf_with_startIndex_with_parameters(bool async) + => AssertTranslationFailed(() => base.Byte_array_with_length_IndexOf_with_startIndex_with_parameters(async)); + + #endregion + public override async Task Byte_array_filter_by_SequenceEqual(bool async) { await base.Byte_array_filter_by_SequenceEqual(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/TPCGearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/TPCGearsOfWarQuerySqliteTest.cs index 07174a52b7b..a5527586844 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/TPCGearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/TPCGearsOfWarQuerySqliteTest.cs @@ -301,6 +301,74 @@ public override async Task Byte_array_filter_by_SequenceEqual(bool async) """); } + #region Byte Array IndexOf Translation + + public override async Task Byte_array_IndexOf_with_literal(bool async) + { + await base.Byte_array_IndexOf_with_literal(async); + + AssertSql( + """ +SELECT "s"."Id", "s"."Banner", "s"."Banner5", "s"."InternalNumber", "s"."Name" +FROM "Squads" AS "s" +WHERE instr("s"."Banner", X'01') - 1 = 1 +"""); + } + + public override async Task Byte_array_IndexOf_with_parameter(bool async) + { + await base.Byte_array_IndexOf_with_parameter(async); + + AssertSql( + """ +@__b_0='0' + +SELECT "s"."Id", "s"."Banner", "s"."Banner5", "s"."InternalNumber", "s"."Name" +FROM "Squads" AS "s" +WHERE instr("s"."Banner", char(@__b_0)) - 1 = 0 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_literal(bool async) + { + await base.Byte_array_with_length_IndexOf_with_literal(async); + + AssertSql( + """ +SELECT "s"."Id", "s"."Banner", "s"."Banner5", "s"."InternalNumber", "s"."Name" +FROM "Squads" AS "s" +WHERE instr("s"."Banner5", X'05') - 1 = 1 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_parameter(bool async) + { + await base.Byte_array_with_length_IndexOf_with_parameter(async); + + AssertSql( + """ +@__b_0='4' + +SELECT "s"."Id", "s"."Banner", "s"."Banner5", "s"."InternalNumber", "s"."Name" +FROM "Squads" AS "s" +WHERE instr("s"."Banner5", char(@__b_0)) - 1 = 0 +"""); + } + + public override Task Byte_array_IndexOf_with_startIndex_with_literals(bool async) + => AssertTranslationFailed(() => base.Byte_array_IndexOf_with_startIndex_with_literals(async)); + + public override Task Byte_array_IndexOf_with_startIndex_with_parameters(bool async) + => AssertTranslationFailed(() => base.Byte_array_IndexOf_with_startIndex_with_parameters(async)); + + public override Task Byte_array_with_length_IndexOf_with_startIndex_with_literals(bool async) + => AssertTranslationFailed(() => base.Byte_array_with_length_IndexOf_with_startIndex_with_literals(async)); + + public override Task Byte_array_with_length_IndexOf_with_startIndex_with_parameters(bool async) + => AssertTranslationFailed(() => base.Byte_array_with_length_IndexOf_with_startIndex_with_parameters(async)); + + #endregion + public override Task Where_TimeSpan_Hours(bool async) // TimeSpan. Issue #18844. => AssertTranslationFailed(() => base.Where_TimeSpan_Hours(async)); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs index bef3059af39..474b99405f2 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs @@ -301,6 +301,74 @@ public override async Task Byte_array_filter_by_SequenceEqual(bool async) """); } + #region Byte Array IndexOf Translation + + public override async Task Byte_array_IndexOf_with_literal(bool async) + { + await base.Byte_array_IndexOf_with_literal(async); + + AssertSql( + """ +SELECT "s"."Id", "s"."Banner", "s"."Banner5", "s"."InternalNumber", "s"."Name" +FROM "Squads" AS "s" +WHERE instr("s"."Banner", X'01') - 1 = 1 +"""); + } + + public override async Task Byte_array_IndexOf_with_parameter(bool async) + { + await base.Byte_array_IndexOf_with_parameter(async); + + AssertSql( + """ +@__b_0='0' + +SELECT "s"."Id", "s"."Banner", "s"."Banner5", "s"."InternalNumber", "s"."Name" +FROM "Squads" AS "s" +WHERE instr("s"."Banner", char(@__b_0)) - 1 = 0 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_literal(bool async) + { + await base.Byte_array_with_length_IndexOf_with_literal(async); + + AssertSql( + """ +SELECT "s"."Id", "s"."Banner", "s"."Banner5", "s"."InternalNumber", "s"."Name" +FROM "Squads" AS "s" +WHERE instr("s"."Banner5", X'05') - 1 = 1 +"""); + } + + public override async Task Byte_array_with_length_IndexOf_with_parameter(bool async) + { + await base.Byte_array_with_length_IndexOf_with_parameter(async); + + AssertSql( + """ +@__b_0='4' + +SELECT "s"."Id", "s"."Banner", "s"."Banner5", "s"."InternalNumber", "s"."Name" +FROM "Squads" AS "s" +WHERE instr("s"."Banner5", char(@__b_0)) - 1 = 0 +"""); + } + + public override Task Byte_array_IndexOf_with_startIndex_with_literals(bool async) + => AssertTranslationFailed(() => base.Byte_array_IndexOf_with_startIndex_with_literals(async)); + + public override Task Byte_array_IndexOf_with_startIndex_with_parameters(bool async) + => AssertTranslationFailed(() => base.Byte_array_IndexOf_with_startIndex_with_parameters(async)); + + public override Task Byte_array_with_length_IndexOf_with_startIndex_with_literals(bool async) + => AssertTranslationFailed(() => base.Byte_array_with_length_IndexOf_with_startIndex_with_literals(async)); + + public override Task Byte_array_with_length_IndexOf_with_startIndex_with_parameters(bool async) + => AssertTranslationFailed(() => base.Byte_array_with_length_IndexOf_with_startIndex_with_parameters(async)); + + #endregion + public override Task Where_TimeSpan_Hours(bool async) // TimeSpan. Issue #18844. => AssertTranslationFailed(() => base.Where_TimeSpan_Hours(async));