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); + } + } +}