diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs
index 028d05dc1db..4d7f1c8d1b7 100644
--- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs
+++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs
@@ -6,6 +6,7 @@
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
+using System.Threading;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
@@ -26,9 +27,6 @@ namespace Microsoft.EntityFrameworkCore.Query
///
public class QuerySqlGenerator : SqlExpressionVisitor
{
- private static readonly Regex _composableSql
- = new Regex(@"^\s*?SELECT\b", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(value: 1000.0));
-
private readonly IRelationalCommandBuilderFactory _relationalCommandBuilderFactory;
private readonly ISqlGenerationHelper _sqlGenerationHelper;
private IRelationalCommandBuilder _relationalCommandBuilder;
@@ -437,10 +435,53 @@ protected virtual void CheckComposableSql([NotNull] string sql)
{
Check.NotNull(sql, nameof(sql));
- if (!_composableSql.IsMatch(sql))
+ var pos = -1;
+ char c;
+
+ do
{
+ c = NextChar();
+
+ if (char.IsWhiteSpace(c))
+ {
+ continue;
+ }
+
+ if (c == '-')
+ {
+ if (NextChar() != '-')
+ {
+ throw new InvalidOperationException(RelationalStrings.FromSqlNonComposable);
+ }
+
+ while (NextChar() != '\n') { }
+
+ continue;
+ }
+
+ if (char.ToLowerInvariant(c) == 's' &&
+ char.ToLowerInvariant(NextChar()) == 'e' &&
+ char.ToLowerInvariant(NextChar()) == 'l' &&
+ char.ToLowerInvariant(NextChar()) == 'e' &&
+ char.ToLowerInvariant(NextChar()) == 'c' &&
+ char.ToLowerInvariant(NextChar()) == 't')
+ {
+ c = NextChar();
+ if (char.IsWhiteSpace(c)
+ || c == '-' && NextChar() == '-')
+ {
+ return;
+ }
+ }
+
throw new InvalidOperationException(RelationalStrings.FromSqlNonComposable);
}
+ while (true);
+
+ char NextChar()
+ => ++pos < sql.Length
+ ? sql[pos]
+ : throw new InvalidOperationException(RelationalStrings.FromSqlNonComposable);
}
///
diff --git a/test/EFCore.Relational.Tests/Query/Internal/QuerySqlGeneratorTest.cs b/test/EFCore.Relational.Tests/Query/Internal/QuerySqlGeneratorTest.cs
new file mode 100644
index 00000000000..63e7ee25ac2
--- /dev/null
+++ b/test/EFCore.Relational.Tests/Query/Internal/QuerySqlGeneratorTest.cs
@@ -0,0 +1,60 @@
+// 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 JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Xunit;
+
+namespace Microsoft.EntityFrameworkCore.Query.Internal
+{
+ public class QuerySqlGeneratorTest
+ {
+ [Theory]
+ [InlineData("INSERT something")]
+ [InlineData("SELECTANDSOMEOTHERSTUFF")]
+ [InlineData("SELECT")]
+ [InlineData("SELEC")]
+ [InlineData("- bad comment\nSELECT something")]
+ [InlineData("SELECT-\n1")]
+ [InlineData("")]
+ public void CheckComposableSql_throws(string sql)
+ => Assert.Equal(
+ RelationalStrings.FromSqlNonComposable,
+ Assert.Throws(
+ () => CreateDummyQuerySqlGenerator().CheckComposableSql(sql)).Message);
+
+ [Theory]
+ [InlineData("SELECT something")]
+ [InlineData(" SELECT something")]
+ [InlineData("-- comment\n SELECT something")]
+ [InlineData("-- comment1\r\n --\t\rcomment2\r\nSELECT something")]
+ [InlineData("SELECT--\n1")]
+ public void CheckComposableSql_does_not_throw(string sql)
+ => CreateDummyQuerySqlGenerator().CheckComposableSql(sql);
+
+ private DummyQuerySqlGenerator CreateDummyQuerySqlGenerator()
+ => new DummyQuerySqlGenerator(
+ new QuerySqlGeneratorDependencies(
+ new RelationalCommandBuilderFactory(
+ new RelationalCommandBuilderDependencies(
+ new TestRelationalTypeMappingSource(
+ TestServiceFactory.Instance.Create(),
+ TestServiceFactory.Instance.Create()))),
+ new RelationalSqlGenerationHelper(
+ new RelationalSqlGenerationHelperDependencies())));
+
+ class DummyQuerySqlGenerator : QuerySqlGenerator
+ {
+ public DummyQuerySqlGenerator([NotNull] QuerySqlGeneratorDependencies dependencies)
+ : base(dependencies)
+ {
+ }
+
+ public new void CheckComposableSql(string sql)
+ => base.CheckComposableSql(sql);
+ }
+ }
+}