From 5bfb114a1c516588b6ebe9c8f9e0b81e348821d9 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Mon, 23 Dec 2019 15:55:27 -0800 Subject: [PATCH] Use SQLite CLI parameter syntax for SQLite query string Part of #6482 See also https://github.com/aspnet/EntityFrameworkCore/pull/19368#issuecomment-567692210 and https://github.com/aspnet/EntityFrameworkCore/pull/19368#issuecomment-568606361 --- .../Internal/SqlServerQueryStringFactory.cs | 7 +- .../SqliteServiceCollectionExtensions.cs | 1 + .../Internal/SqliteQueryStringFactory.cs | 67 +++++++++++++++++++ .../Query/FromSqlQuerySqliteTest.cs | 32 +++++++++ .../Query/NorthwindWhereQuerySqliteTest.cs | 3 +- 5 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryStringFactory.cs diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryStringFactory.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryStringFactory.cs index 4ce8381fea8..6b6e38bc2c7 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryStringFactory.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryStringFactory.cs @@ -25,7 +25,12 @@ public class SqlServerQueryStringFactory : IRelationalQueryStringFactory { private readonly IRelationalTypeMappingSource _typeMapper; - /// Initializes a new instance of the class. + /// + /// 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. + /// public SqlServerQueryStringFactory([NotNull] IRelationalTypeMappingSource typeMapper) { _typeMapper = typeMapper; diff --git a/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs b/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs index b81ba55c461..f3b405259ff 100644 --- a/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs +++ b/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs @@ -63,6 +63,7 @@ public static IServiceCollection AddEntityFrameworkSqlite([NotNull] this IServic .TryAdd() .TryAdd() .TryAdd() + .TryAdd() // New Query Pipeline .TryAdd() diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryStringFactory.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryStringFactory.cs new file mode 100644 index 00000000000..45a8cf8f44c --- /dev/null +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryStringFactory.cs @@ -0,0 +1,67 @@ +// 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.Data.Common; +using System.Text; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal +{ + /// + /// 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. + /// + public class SqliteQueryStringFactory : IRelationalQueryStringFactory + { + private readonly IRelationalTypeMappingSource _typeMapper; + + /// + /// 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. + /// + public SqliteQueryStringFactory([NotNull] IRelationalTypeMappingSource typeMapper) + { + _typeMapper = typeMapper; + } + + /// + /// 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. + /// + public virtual string Create(DbCommand command) + { + if (command.Parameters.Count == 0) + { + return command.CommandText; + } + + var builder = new StringBuilder(); + foreach (DbParameter parameter in command.Parameters) + { + var value = parameter.Value; + builder + .Append(".param set ") + .Append(parameter.ParameterName) + .Append(' ') + .AppendLine( + value == null || value == DBNull.Value + ? "NULL" + : _typeMapper.FindMapping(value.GetType())?.GenerateSqlLiteral(value) + ?? value.ToString()); + } + + return builder + .AppendLine() + .Append(command.CommandText).ToString(); + } + } +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/FromSqlQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/FromSqlQuerySqliteTest.cs index 31b64c2b4d1..fd722aaf7fb 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/FromSqlQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/FromSqlQuerySqliteTest.cs @@ -4,6 +4,7 @@ using System.Data.Common; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; using Xunit.Abstractions; namespace Microsoft.EntityFrameworkCore.Query @@ -16,6 +17,37 @@ public FromSqlQuerySqliteTest(NorthwindQuerySqliteFixture f fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } + public override string FromSqlRaw_queryable_composed() + { + var queryString = base.FromSqlRaw_queryable_composed(); + + var expected = @"SELECT ""c"".""CustomerID"", ""c"".""Address"", ""c"".""City"", ""c"".""CompanyName"", ""c"".""ContactName"", ""c"".""ContactTitle"", ""c"".""Country"", ""c"".""Fax"", ""c"".""Phone"", ""c"".""PostalCode"", ""c"".""Region"" +FROM ( + SELECT * FROM ""Customers"" +) AS ""c"" +WHERE ('z' = '') OR (instr(""c"".""ContactName"", 'z') > 0)"; + + Assert.Equal(expected, queryString); + + return null; + } + + public override string FromSqlRaw_queryable_with_parameters_and_closure() + { + var queryString = base.FromSqlRaw_queryable_with_parameters_and_closure(); + + Assert.Equal(@".param set p0 'London' +.param set @__contactTitle_1 'Sales Representative' + +SELECT ""c"".""CustomerID"", ""c"".""Address"", ""c"".""City"", ""c"".""CompanyName"", ""c"".""ContactName"", ""c"".""ContactTitle"", ""c"".""Country"", ""c"".""Fax"", ""c"".""Phone"", ""c"".""PostalCode"", ""c"".""Region"" +FROM ( + SELECT * FROM ""Customers"" WHERE ""City"" = @p0 +) AS ""c"" +WHERE ""c"".""ContactTitle"" = @__contactTitle_1", queryString); + + return null; + } + public override void Bad_data_error_handling_invalid_cast_key() { // Not supported on SQLite diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs index 189df6c98fe..5961d7d6b89 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs @@ -36,7 +36,8 @@ public override async Task Where_simple_closure(bool async) WHERE ""c"".""City"" = @__city_0"); Assert.Equal( - @"-- @__city_0='London' (Size = 6) + @".param set @__city_0 'London' + SELECT ""c"".""CustomerID"", ""c"".""Address"", ""c"".""City"", ""c"".""CompanyName"", ""c"".""ContactName"", ""c"".""ContactTitle"", ""c"".""Country"", ""c"".""Fax"", ""c"".""Phone"", ""c"".""PostalCode"", ""c"".""Region"" FROM ""Customers"" AS ""c"" WHERE ""c"".""City"" = @__city_0", queryString, ignoreLineEndingDifferences: true, ignoreWhiteSpaceDifferences: true);