From 59644b2b1ee8d762133626ebde9176751c4e9171 Mon Sep 17 00:00:00 2001 From: Austin Drenski Date: Sat, 29 Sep 2018 23:45:47 -0400 Subject: [PATCH] Updates split off from PR 541 --- ...qlCompositeExpressionFragmentTranslator.cs | 3 +- .../NpgsqlCompositeMemberTranslator.cs | 2 +- .../NpgsqlCompositeMethodCallTranslator.cs | 6 +- .../NpgsqlSqlTranslatingExpressionVisitor.cs | 2 +- .../Sql/Internal/NpgsqlQuerySqlGenerator.cs | 34 ++++--- .../Query/ArrayQueryTest.cs | 94 +++++++++---------- 6 files changed, 73 insertions(+), 68 deletions(-) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCompositeExpressionFragmentTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCompositeExpressionFragmentTranslator.cs index ab22a1a40..d43d8a1d5 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCompositeExpressionFragmentTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCompositeExpressionFragmentTranslator.cs @@ -2,7 +2,7 @@ // The PostgreSQL License // -// Copyright (C) 2016 The Npgsql Development Team +// Copyright (C) 2018 The Npgsql Development Team // // Permission to use, copy, modify, and distribute this software and its // documentation for any purpose, without fee, and without a written @@ -49,6 +49,7 @@ public NpgsqlCompositeExpressionFragmentTranslator( AddTranslators(ExpressionFragmentTranslators); } + // ReSharper disable once MemberCanBeProtected.Global /// /// Adds additional dispatches to the translators list. /// diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCompositeMemberTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCompositeMemberTranslator.cs index a95d5460a..2b272323f 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCompositeMemberTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCompositeMemberTranslator.cs @@ -2,7 +2,7 @@ // The PostgreSQL License // -// Copyright (C) 2016 The Npgsql Development Team +// Copyright (C) 2018 The Npgsql Development Team // // Permission to use, copy, modify, and distribute this software and its // documentation for any purpose, without fee, and without a written diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCompositeMethodCallTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCompositeMethodCallTranslator.cs index ee123c573..402487960 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCompositeMethodCallTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCompositeMethodCallTranslator.cs @@ -2,7 +2,7 @@ // The PostgreSQL License // -// Copyright (C) 2016 The Npgsql Development Team +// Copyright (C) 2018 The Npgsql Development Team // // Permission to use, copy, modify, and distribute this software and its // documentation for any purpose, without fee, and without a written @@ -68,7 +68,7 @@ public NpgsqlCompositeMethodCallTranslator( [NotNull] INpgsqlOptions npgsqlOptions) : base(dependencies) { - var instanceTranslators = new IMethodCallTranslator[] + var versionDependentTranslators = new IMethodCallTranslator[] { new NpgsqlDateAddTranslator(npgsqlOptions.PostgresVersion), new NpgsqlMathTranslator(npgsqlOptions.PostgresVersion) @@ -78,7 +78,7 @@ public NpgsqlCompositeMethodCallTranslator( AddTranslators(MethodCallTranslators); // ReSharper disable once DoNotCallOverridableMethodsInConstructor - AddTranslators(instanceTranslators); + AddTranslators(versionDependentTranslators); foreach (var plugin in npgsqlOptions.Plugins) plugin.AddMethodCallTranslators(this); diff --git a/src/EFCore.PG/Query/ExpressionVisitors/NpgsqlSqlTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/ExpressionVisitors/NpgsqlSqlTranslatingExpressionVisitor.cs index e150e88c2..036573158 100644 --- a/src/EFCore.PG/Query/ExpressionVisitors/NpgsqlSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/ExpressionVisitors/NpgsqlSqlTranslatingExpressionVisitor.cs @@ -2,7 +2,7 @@ // The PostgreSQL License // -// Copyright (C) 2016 The Npgsql Development Team +// Copyright (C) 2018 The Npgsql Development Team // // Permission to use, copy, modify, and distribute this software and its // documentation for any purpose, without fee, and without a written diff --git a/src/EFCore.PG/Query/Sql/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Sql/Internal/NpgsqlQuerySqlGenerator.cs index 48373a059..4aa2a3e57 100644 --- a/src/EFCore.PG/Query/Sql/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Sql/Internal/NpgsqlQuerySqlGenerator.cs @@ -2,7 +2,7 @@ // The PostgreSQL License // -// Copyright (C) 2016 The Npgsql Development Team +// Copyright (C) 2018 The Npgsql Development Team // // Permission to use, copy, modify, and distribute this software and its // documentation for any purpose, without fee, and without a written @@ -195,8 +195,7 @@ protected override Expression VisitBinary(BinaryExpression expression) } case ExpressionType.ArrayIndex: - VisitArrayIndex(expression); - return expression; + return VisitArrayIndex(expression); default: return base.VisitBinary(expression); @@ -228,26 +227,29 @@ protected override Expression VisitUnary(UnaryExpression expression) return base.VisitUnary(expression); } - protected virtual void VisitArrayIndex([NotNull] BinaryExpression expression) + [NotNull] + protected virtual Expression VisitArrayIndex([NotNull] BinaryExpression expression) { Debug.Assert(expression.NodeType == ExpressionType.ArrayIndex); if (expression.Left.Type == typeof(byte[])) { - // bytea cannot be subscripted, but there's get_byte - VisitSqlFunction(new SqlFunctionExpression("get_byte", typeof(byte), - new[] { expression.Left, expression.Right })); - return; + // bytea cannot be subscripted, but there's get_byte. + return VisitSqlFunction( + new SqlFunctionExpression( + "get_byte", + typeof(byte), + new[] { expression.Left, expression.Right })); } if (expression.Left.Type == typeof(string)) { - // text cannot be subscripted, use substr - // PostgreSQL substr() is 1-based. - - VisitSqlFunction(new SqlFunctionExpression("substr", typeof(char), - new[] { expression.Left, expression.Right, Expression.Constant(1) })); - return; + // text cannot be subscripted, use substr. PostgreSQL substr() is 1-based. + return VisitSqlFunction( + new SqlFunctionExpression( + "substr", + typeof(char), + new[] { expression.Left, expression.Right, Expression.Constant(1) })); } // Regular array from here @@ -255,11 +257,13 @@ protected virtual void VisitArrayIndex([NotNull] BinaryExpression expression) Sql.Append('['); Visit(GenerateOneBasedIndexExpression(expression.Right)); Sql.Append(']'); + return expression; } /// /// Produces expressions like: 1 = ANY ('{0,1,2}') or 'cat' LIKE ANY ('{a%,b%,c%}'). /// + [NotNull] public virtual Expression VisitArrayAnyAll([NotNull] ArrayAnyAllExpression expression) { Visit(expression.Operand); @@ -291,7 +295,7 @@ public virtual Expression VisitRegexMatch([NotNull] RegexMatchExpression express Visit(expression.Match); Sql.Append(" ~ "); - // PG regexps are singleline by default + // PG regexps are single-line by default if (options == RegexOptions.Singleline) { Visit(expression.Pattern); diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs index eb69eab25..41863ba5b 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs @@ -19,7 +19,7 @@ public class ArrayQueryTest : IClassFixture [Fact] public void Roundtrip() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { var x = ctx.SomeEntities.Single(e => e.Id == 1); Assert.Equal(new[] { 3, 4 }, x.SomeArray); @@ -34,7 +34,7 @@ public void Roundtrip() [Fact] public void Index_with_constant() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { var actual = ctx.SomeEntities.Where(e => e.SomeArray[0] == 3).ToList(); Assert.Equal(1, actual.Count); @@ -45,7 +45,7 @@ public void Index_with_constant() [Fact] public void Index_with_non_constant() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { // ReSharper disable once ConvertToConstant.Local var x = 0; @@ -58,7 +58,7 @@ public void Index_with_non_constant() [Fact] public void Index_bytea_with_constant() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { var actual = ctx.SomeEntities.Where(e => e.SomeBytea[0] == 3).ToList(); Assert.Equal(1, actual.Count); @@ -69,7 +69,7 @@ public void Index_bytea_with_constant() [Fact] public void Index_multidimensional() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { // Operations on multidimensional arrays aren't mapped to SQL yet var actual = ctx.SomeEntities.Where(e => e.SomeMatrix[0, 0] == 5).ToList(); @@ -84,7 +84,7 @@ public void Index_multidimensional() [Fact] public void SequenceEqual_with_parameter() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { var arr = new[] { 3, 4 }; var x = ctx.SomeEntities.Single(e => e.SomeArray.SequenceEqual(arr)); @@ -96,7 +96,7 @@ public void SequenceEqual_with_parameter() [Fact] public void SequenceEqual_with_array_literal() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { var x = ctx.SomeEntities.Single(e => e.SomeArray.SequenceEqual(new[] { 3, 4 })); Assert.Equal(new[] { 3, 4 }, x.SomeArray); @@ -111,7 +111,7 @@ public void SequenceEqual_with_array_literal() [Fact] public void Contains_with_literal() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { var x = ctx.SomeEntities.Single(e => e.SomeArray.Contains(3)); Assert.Equal(new[] { 3, 4 }, x.SomeArray); @@ -122,7 +122,7 @@ public void Contains_with_literal() [Fact] public void Contains_with_parameter() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { // ReSharper disable once ConvertToConstant.Local var p = 3; @@ -135,7 +135,7 @@ public void Contains_with_parameter() [Fact] public void Contains_with_column() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { var x = ctx.SomeEntities.Single(e => e.SomeArray.Contains(e.Id + 2)); Assert.Equal(new[] { 3, 4 }, x.SomeArray); @@ -150,7 +150,7 @@ public void Contains_with_column() [Fact] public void Length() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { var x = ctx.SomeEntities.Single(e => e.SomeArray.Length == 2); Assert.Equal(new[] { 3, 4 }, x.SomeArray); @@ -161,7 +161,7 @@ public void Length() [Fact(Skip = "https://github.com/aspnet/EntityFramework/issues/9242")] public void Length_on_EF_Property() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { // TODO: This fails var x = ctx.SomeEntities.Single(e => EF.Property(e, nameof(SomeArrayEntity.SomeArray)).Length == 2); @@ -173,7 +173,7 @@ public void Length_on_EF_Property() [Fact] public void Length_on_literal_not_translated() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { var _ = ctx.SomeEntities.Where(e => new[] { 1, 2, 3 }.Length == e.Id).ToList(); AssertDoesNotContainInSql("array_length"); @@ -187,7 +187,7 @@ public void Length_on_literal_not_translated() [Fact] public void Array_like_any_when_match_expression_is_column() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { var patterns = new[] { "a", "b", "c" }; @@ -210,7 +210,7 @@ public void Array_like_any_when_match_expression_is_column() [Fact] public void Array_like_any_not_translated_when_match_expression_is_qsre() { - using (var ctx = CreateContext()) + using (var ctx = Fixture.CreateContext()) { var matches = new[] { "a", "b", "c" }; @@ -246,8 +246,6 @@ public ArrayQueryTest(ArrayFixture fixture) Fixture.TestSqlLoggerFactory.Clear(); } - ArrayContext CreateContext() => Fixture.CreateContext(); - void AssertContainsInSql(string expected) => Assert.Contains(expected, Fixture.TestSqlLoggerFactory.Sql); @@ -257,8 +255,8 @@ void AssertDoesNotContainInSql(string expected) public class ArrayContext : DbContext { public DbSet SomeEntities { get; set; } + public ArrayContext(DbContextOptions options) : base(options) {} - protected override void OnModelCreating(ModelBuilder builder) {} } public class SomeArrayEntity @@ -268,54 +266,56 @@ public class SomeArrayEntity public int[,] SomeMatrix { get; set; } public List SomeList { get; set; } public byte[] SomeBytea { get; set; } - - // ReSharper disable once UnusedMember.Global public string SomeText { get; set; } } public class ArrayFixture : IDisposable { - readonly DbContextOptions _options; + readonly NpgsqlTestStore _testStore; public TestSqlLoggerFactory TestSqlLoggerFactory { get; } = new TestSqlLoggerFactory(); public ArrayFixture() { _testStore = NpgsqlTestStore.CreateScratch(); - _options = new DbContextOptionsBuilder() - .UseNpgsql(_testStore.ConnectionString, b => b.ApplyConfiguration()) - .UseInternalServiceProvider( - new ServiceCollection() - .AddEntityFrameworkNpgsql() - .AddSingleton(TestSqlLoggerFactory) - .BuildServiceProvider()) - .Options; using (var ctx = CreateContext()) { ctx.Database.EnsureCreated(); - ctx.SomeEntities.Add(new SomeArrayEntity - { - Id = 1, - SomeArray = new[] { 3, 4 }, - SomeBytea = new byte[] { 3, 4 }, - SomeMatrix = new[,] { { 5, 6 }, { 7, 8 } }, - SomeList = new List { 3, 4 } - }); - ctx.SomeEntities.Add(new SomeArrayEntity - { - Id = 2, - SomeArray = new[] { 5, 6, 7 }, - SomeBytea = new byte[] { 5, 6, 7 }, - SomeMatrix = new[,] { { 10, 11 }, { 12, 13 } }, - SomeList = new List { 3, 4 } - }); + ctx.SomeEntities.AddRange( + new SomeArrayEntity + { + Id = 1, + SomeArray = new[] { 3, 4 }, + SomeBytea = new byte[] { 3, 4 }, + SomeMatrix = new[,] { { 5, 6 }, { 7, 8 } }, + SomeList = new List { 3, 4 } + }, + new SomeArrayEntity + { + Id = 2, + SomeArray = new[] { 5, 6, 7 }, + SomeBytea = new byte[] { 5, 6, 7 }, + SomeMatrix = new[,] { { 10, 11 }, { 12, 13 } }, + SomeList = new List { 3, 4 } + }); ctx.SaveChanges(); } } - readonly NpgsqlTestStore _testStore; - public ArrayContext CreateContext() => new ArrayContext(_options); + public ArrayContext CreateContext(Version postgresVersion = default) + => new ArrayContext(CreateOptions(postgresVersion)); + public void Dispose() => _testStore.Dispose(); + + DbContextOptions CreateOptions(Version postgresVersion = null) + => new DbContextOptionsBuilder() + .UseNpgsql(_testStore.ConnectionString, b => b.ApplyConfiguration().SetPostgresVersion(postgresVersion)) + .UseInternalServiceProvider( + new ServiceCollection() + .AddEntityFrameworkNpgsql() + .AddSingleton(TestSqlLoggerFactory) + .BuildServiceProvider()) + .Options; } #endregion