diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 0dcb5e9e6..cabcf202d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -11,7 +11,7 @@ on:
pull_request:
env:
- dotnet_sdk_version: '8.0.100'
+ dotnet_sdk_version: '9.0.100-preview.3.24204.13'
postgis_version: 3
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 6eed22bfd..e4a10226f 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -27,7 +27,7 @@ on:
- cron: '30 22 * * 6'
env:
- dotnet_sdk_version: '8.0.100'
+ dotnet_sdk_version: '9.0.100-preview.3.24204.13'
jobs:
analyze:
diff --git a/Directory.Build.props b/Directory.Build.props
index b913e4b87..5fb8849de 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -12,7 +12,7 @@
true
true
- Copyright 2023 © The Npgsql Development Team
+ Copyright 2024 © The Npgsql Development Team
Npgsql
true
PostgreSQL
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 93aecba8c..0fb184004 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,7 +1,7 @@
- 9.0.0-preview.2.24128.4
- 9.0.0-preview.2.24128.5
+ 9.0.0-preview.3.24172.4
+ 9.0.0-preview.3.24172.9
8.0.2
@@ -23,8 +23,12 @@
-
-
+
+
+
+
+
+
diff --git a/EFCore.PG.sln.DotSettings b/EFCore.PG.sln.DotSettings
index 0ae73aa7c..9055198e9 100644
--- a/EFCore.PG.sln.DotSettings
+++ b/EFCore.PG.sln.DotSettings
@@ -182,216 +182,11 @@
True
True
-
- True
- True
- True
- Side by side
- Side by side
- False
- False
- False
- True
- False
- False
- True
- False
- False
- False
- True
- <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
- True
- FK
- MARS
- $object$_On$event$
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Public" Description="Test Methods"><ElementKinds><Kind Name="TEST_MEMBER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="Aa_bb" /></Policy>
- EF
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" />
- $object$_On$event$
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
-
-
- True
- C:\repos\EntityFrameworkCore\All.sln.DotSettings
-
-
-
- True
- 2
- DO_NOTHING
- True
- True
True
- True
True
- True
True
- True
True
True
- True
- True
- True
- True
True
-<<<<<<< HEAD
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
-
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
-
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
-
+
\ No newline at end of file
diff --git a/global.json b/global.json
index 817f0c380..50d6cea52 100644
--- a/global.json
+++ b/global.json
@@ -1,7 +1,7 @@
{
"sdk": {
- "version": "8.0.100-rc.2.23502.2",
+ "version": "9.0.100-preview.3.24204.13",
"rollForward": "latestMajor",
- "allowPrerelease": "true"
+ "allowPrerelease": true
}
}
diff --git a/src/EFCore.PG.NodaTime/Query/Internal/PendingDateTimeZoneProviderExpression.cs b/src/EFCore.PG.NodaTime/Query/Internal/PendingDateTimeZoneProviderExpression.cs
index 11879f214..7c2ee5ef9 100644
--- a/src/EFCore.PG.NodaTime/Query/Internal/PendingDateTimeZoneProviderExpression.cs
+++ b/src/EFCore.PG.NodaTime/Query/Internal/PendingDateTimeZoneProviderExpression.cs
@@ -9,6 +9,9 @@ private PendingDateTimeZoneProviderExpression()
{
}
+ public override Expression Quote()
+ => throw new UnreachableException("PendingDateTimeZoneProviderExpression is a temporary tree representation and should never be quoted");
+
protected override void Print(ExpressionPrinter expressionPrinter)
=> expressionPrinter.Append("TZDB");
}
diff --git a/src/EFCore.PG.NodaTime/Query/Internal/PendingZonedDateTimeExpression.cs b/src/EFCore.PG.NodaTime/Query/Internal/PendingZonedDateTimeExpression.cs
index 35c8affee..41739d713 100644
--- a/src/EFCore.PG.NodaTime/Query/Internal/PendingZonedDateTimeExpression.cs
+++ b/src/EFCore.PG.NodaTime/Query/Internal/PendingZonedDateTimeExpression.cs
@@ -12,6 +12,9 @@ internal PendingZonedDateTimeExpression(SqlExpression operand, SqlExpression tim
internal SqlExpression TimeZoneId { get; }
+ public override Expression Quote()
+ => throw new UnreachableException("PendingDateTimeZoneProviderExpression is a temporary tree representation and should never be quoted");
+
protected override void Print(ExpressionPrinter expressionPrinter)
{
expressionPrinter.Visit(Operand);
diff --git a/src/EFCore.PG/EFCore.PG.csproj b/src/EFCore.PG/EFCore.PG.csproj
index 0ab1ace12..1fb3139f4 100644
--- a/src/EFCore.PG/EFCore.PG.csproj
+++ b/src/EFCore.PG/EFCore.PG.csproj
@@ -9,6 +9,7 @@
PostgreSQL/Npgsql provider for Entity Framework Core.
npgsql;postgresql;postgres;Entity Framework Core;entity-framework-core;ef;efcore;orm;sql
README.md
+ EF1003
diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs
index 8478cae09..377939774 100644
--- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs
+++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs
@@ -78,7 +78,7 @@ public NpgsqlObjectToStringTranslator(IRelationalTypeMappingSource typeMappingSo
_sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(true)),
_sqlExpressionFactory.Constant(true.ToString()))
},
- _sqlExpressionFactory.Constant(null))
+ _sqlExpressionFactory.Constant(null, typeof(string)))
: _sqlExpressionFactory.Case(
new[]
{
diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgAllExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgAllExpression.cs
index 714b895ac..0ee27abd3 100644
--- a/src/EFCore.PG/Query/Expressions/Internal/PgAllExpression.cs
+++ b/src/EFCore.PG/Query/Expressions/Internal/PgAllExpression.cs
@@ -8,6 +8,8 @@
///
public class PgAllExpression : SqlExpression, IEquatable
{
+ private static ConstructorInfo? _quotingConstructor;
+
///
public override Type Type
=> typeof(bool);
@@ -63,6 +65,16 @@ public virtual PgAllExpression Update(SqlExpression item, SqlExpression array)
? new PgAllExpression(item, array, OperatorType, TypeMapping)
: this;
+ ///
+ public override Expression Quote()
+ => New(
+ _quotingConstructor ??= typeof(PgAllExpression).GetConstructor(
+ [typeof(SqlExpression), typeof(SqlExpression), typeof(PgAllOperatorType), typeof(RelationalTypeMapping)])!,
+ Item.Quote(),
+ Array.Quote(),
+ Constant(OperatorType),
+ RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping));
+
///
protected override Expression VisitChildren(ExpressionVisitor visitor)
=> Update((SqlExpression)visitor.Visit(Item), (SqlExpression)visitor.Visit(Array));
diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgAnyExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgAnyExpression.cs
index 94c2330dd..3d57bc228 100644
--- a/src/EFCore.PG/Query/Expressions/Internal/PgAnyExpression.cs
+++ b/src/EFCore.PG/Query/Expressions/Internal/PgAnyExpression.cs
@@ -11,6 +11,8 @@
///
public class PgAnyExpression : SqlExpression, IEquatable
{
+ private static ConstructorInfo? _quotingConstructor;
+
///
public override Type Type
=> typeof(bool);
@@ -74,6 +76,16 @@ public virtual PgAnyExpression Update(SqlExpression item, SqlExpression array)
? new PgAnyExpression(item, array, OperatorType, TypeMapping)
: this;
+ ///
+ public override Expression Quote()
+ => New(
+ _quotingConstructor ??= typeof(PgAnyExpression).GetConstructor(
+ [typeof(SqlExpression), typeof(SqlExpression), typeof(PgAllOperatorType), typeof(RelationalTypeMapping)])!,
+ Item.Quote(),
+ Array.Quote(),
+ Constant(OperatorType),
+ RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping));
+
///
protected override Expression VisitChildren(ExpressionVisitor visitor)
=> Update((SqlExpression)visitor.Visit(Item), (SqlExpression)visitor.Visit(Array));
diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgArrayIndexExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgArrayIndexExpression.cs
index 4a4f3d51b..d3e676fb7 100644
--- a/src/EFCore.PG/Query/Expressions/Internal/PgArrayIndexExpression.cs
+++ b/src/EFCore.PG/Query/Expressions/Internal/PgArrayIndexExpression.cs
@@ -9,6 +9,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal;
///
public class PgArrayIndexExpression : SqlExpression, IEquatable
{
+ private static ConstructorInfo? _quotingConstructor;
+
///
/// The array being indexed.
///
@@ -75,6 +77,17 @@ public virtual PgArrayIndexExpression Update(SqlExpression array, SqlExpression
? this
: new PgArrayIndexExpression(array, index, IsNullable, Type, TypeMapping);
+ ///
+ public override Expression Quote()
+ => New(
+ _quotingConstructor ??= typeof(PgArrayIndexExpression).GetConstructor(
+ [typeof(SqlExpression), typeof(SqlExpression), typeof(bool), typeof(Type), typeof(RelationalTypeMapping)])!,
+ Array.Quote(),
+ Index.Quote(),
+ Constant(IsNullable),
+ Constant(Type),
+ RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping));
+
///
protected override Expression VisitChildren(ExpressionVisitor visitor)
=> Update((SqlExpression)visitor.Visit(Array), (SqlExpression)visitor.Visit(Index));
diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs
index 3d03488f1..9c6d56fbe 100644
--- a/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs
+++ b/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs
@@ -8,6 +8,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal;
///
public class PgArraySliceExpression : SqlExpression, IEquatable
{
+ private static ConstructorInfo? _quotingConstructor;
+
///
/// The array being sliced.
///
@@ -72,6 +74,18 @@ public virtual PgArraySliceExpression Update(SqlExpression array, SqlExpression?
? this
: new PgArraySliceExpression(array, lowerBound, upperBound, IsNullable, Type, TypeMapping);
+ ///
+ public override Expression Quote()
+ => New(
+ _quotingConstructor ??= typeof(PgArraySliceExpression).GetConstructor(
+ [typeof(SqlExpression), typeof(SqlExpression), typeof(SqlExpression), typeof(bool), typeof(Type), typeof(RelationalTypeMapping)])!,
+ Array.Quote(),
+ RelationalExpressionQuotingUtilities.VisitOrNull(LowerBound),
+ RelationalExpressionQuotingUtilities.VisitOrNull(UpperBound),
+ Constant(IsNullable),
+ Constant(Type),
+ RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping));
+
///
protected override Expression VisitChildren(ExpressionVisitor visitor)
=> Update(
diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgBinaryExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgBinaryExpression.cs
index 0cbee2c1e..03387b988 100644
--- a/src/EFCore.PG/Query/Expressions/Internal/PgBinaryExpression.cs
+++ b/src/EFCore.PG/Query/Expressions/Internal/PgBinaryExpression.cs
@@ -7,6 +7,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal;
///
public class PgBinaryExpression : SqlExpression
{
+ private static ConstructorInfo? _quotingConstructor;
+
///
/// Creates a new instance of the class.
///
@@ -74,6 +76,17 @@ public virtual PgBinaryExpression Update(SqlExpression left, SqlExpression right
: this;
}
+ ///
+ public override Expression Quote()
+ => New(
+ _quotingConstructor ??= typeof(PgBinaryExpression).GetConstructor(
+ [typeof(PgExpressionType), typeof(SqlExpression), typeof(SqlExpression), typeof(Type), typeof(RelationalTypeMapping)])!,
+ Constant(OperatorType),
+ Left.Quote(),
+ Right.Quote(),
+ Constant(Type),
+ RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping));
+
///
protected override void Print(ExpressionPrinter expressionPrinter)
{
diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs
index 850b1f358..2304618ec 100644
--- a/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs
+++ b/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs
@@ -6,6 +6,8 @@
// ReSharper disable once InconsistentNaming
public class PgILikeExpression : SqlExpression, IEquatable
{
+ private static ConstructorInfo? _quotingConstructor;
+
///
/// The match expression.
///
@@ -60,6 +62,16 @@ public virtual PgILikeExpression Update(
? this
: new PgILikeExpression(match, pattern, escapeChar, TypeMapping);
+ ///
+ public override Expression Quote()
+ => New(
+ _quotingConstructor ??= typeof(PgILikeExpression).GetConstructor(
+ [typeof(SqlExpression), typeof(SqlExpression), typeof(SqlExpression), typeof(RelationalTypeMapping)])!,
+ Match.Quote(),
+ Pattern.Quote(),
+ RelationalExpressionQuotingUtilities.VisitOrNull(EscapeChar),
+ RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping));
+
///
public override bool Equals(object? obj)
=> obj is PgILikeExpression other && Equals(other);
diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgJsonTraversalExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgJsonTraversalExpression.cs
index 076c8caea..61871b567 100644
--- a/src/EFCore.PG/Query/Expressions/Internal/PgJsonTraversalExpression.cs
+++ b/src/EFCore.PG/Query/Expressions/Internal/PgJsonTraversalExpression.cs
@@ -5,6 +5,8 @@
///
public class PgJsonTraversalExpression : SqlExpression, IEquatable
{
+ private static ConstructorInfo? _quotingConstructor;
+
///
/// The match expression.
///
@@ -56,6 +58,17 @@ public virtual PgJsonTraversalExpression Update(SqlExpression expression, IReadO
? this
: new PgJsonTraversalExpression(expression, path, ReturnsText, Type, TypeMapping);
+ ///
+ public override Expression Quote()
+ => New(
+ _quotingConstructor ??= typeof(PgJsonTraversalExpression).GetConstructor(
+ [typeof(SqlExpression), typeof(IReadOnlyList), typeof(bool), typeof(Type), typeof(RelationalTypeMapping)])!,
+ Expression.Quote(),
+ NewArrayInit(typeof(SqlExpression), initializers: Path.Select(a => a.Quote())),
+ Constant(ReturnsText),
+ Constant(Type),
+ RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping));
+
///
/// Appends an additional path component to this and returns the result.
///
diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgNewArrayExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgNewArrayExpression.cs
index 5aafba511..22cfe208d 100644
--- a/src/EFCore.PG/Query/Expressions/Internal/PgNewArrayExpression.cs
+++ b/src/EFCore.PG/Query/Expressions/Internal/PgNewArrayExpression.cs
@@ -5,6 +5,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal;
///
public class PgNewArrayExpression : SqlExpression
{
+ private static ConstructorInfo? _quotingConstructor;
+
///
/// Creates a new instance of the class.
///
@@ -74,6 +76,15 @@ public virtual PgNewArrayExpression Update(IReadOnlyList expressi
: new PgNewArrayExpression(expressions, Type, TypeMapping);
}
+ ///
+ public override Expression Quote()
+ => New(
+ _quotingConstructor ??= typeof(PgNewArrayExpression).GetConstructor(
+ [typeof(IReadOnlyList), typeof(Type), typeof(RelationalTypeMapping)])!,
+ NewArrayInit(typeof(SqlExpression), initializers: Expressions.Select(a => a.Quote())),
+ Constant(Type),
+ RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping));
+
///
protected override void Print(ExpressionPrinter expressionPrinter)
{
diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgRegexMatchExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgRegexMatchExpression.cs
index fa2a1a34b..f907e7b42 100644
--- a/src/EFCore.PG/Query/Expressions/Internal/PgRegexMatchExpression.cs
+++ b/src/EFCore.PG/Query/Expressions/Internal/PgRegexMatchExpression.cs
@@ -7,6 +7,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal;
///
public class PgRegexMatchExpression : SqlExpression, IEquatable
{
+ private static ConstructorInfo? _quotingConstructor;
+
///
public override Type Type
=> typeof(bool);
@@ -58,6 +60,16 @@ public virtual PgRegexMatchExpression Update(SqlExpression match, SqlExpression
? new PgRegexMatchExpression(match, pattern, Options, TypeMapping)
: this;
+ ///
+ public override Expression Quote()
+ => New(
+ _quotingConstructor ??= typeof(PgRegexMatchExpression).GetConstructor(
+ [typeof(SqlExpression), typeof(SqlExpression), typeof(RegexOptions), typeof(RelationalTypeMapping)])!,
+ Match.Quote(),
+ Pattern.Quote(),
+ Constant(Options),
+ RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping));
+
///
public virtual bool Equals(PgRegexMatchExpression? other)
=> ReferenceEquals(this, other)
diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgRowValueExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgRowValueExpression.cs
index 19d8c6b22..0d0e6f305 100644
--- a/src/EFCore.PG/Query/Expressions/Internal/PgRowValueExpression.cs
+++ b/src/EFCore.PG/Query/Expressions/Internal/PgRowValueExpression.cs
@@ -11,13 +11,18 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal;
///
public class PgRowValueExpression : SqlExpression, IEquatable
{
+ private static ConstructorInfo? _quotingConstructor;
+
///
/// The values of this PostgreSQL row value expression.
///
public virtual IReadOnlyList Values { get; }
///
- public PgRowValueExpression(IReadOnlyList values, Type type, RelationalTypeMapping? typeMapping = null)
+ public PgRowValueExpression(
+ IReadOnlyList values,
+ Type type,
+ RelationalTypeMapping? typeMapping = null)
: base(type, typeMapping)
{
Check.NotNull(values, nameof(values));
@@ -64,6 +69,15 @@ public virtual PgRowValueExpression Update(IReadOnlyList values)
? this
: new PgRowValueExpression(values, Type);
+ ///
+ public override Expression Quote()
+ => New(
+ _quotingConstructor ??= typeof(PgRowValueExpression).GetConstructor(
+ [typeof(IReadOnlyList), typeof(Type), typeof(RelationalTypeMapping)])!,
+ NewArrayInit(typeof(SqlExpression), initializers: Values.Select(a => a.Quote())),
+ Constant(Type),
+ RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping));
+
///
protected override void Print(ExpressionPrinter expressionPrinter)
{
diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgUnknownBinaryExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgUnknownBinaryExpression.cs
index bf2b89126..2d8dd0ee7 100644
--- a/src/EFCore.PG/Query/Expressions/Internal/PgUnknownBinaryExpression.cs
+++ b/src/EFCore.PG/Query/Expressions/Internal/PgUnknownBinaryExpression.cs
@@ -9,6 +9,8 @@
///
public class PgUnknownBinaryExpression : SqlExpression, IEquatable
{
+ private static ConstructorInfo? _quotingConstructor;
+
///
/// The left-hand expression.
///
@@ -59,6 +61,17 @@ public virtual PgUnknownBinaryExpression Update(SqlExpression left, SqlExpressio
? this
: new PgUnknownBinaryExpression(left, right, Operator, Type, TypeMapping);
+ ///
+ public override Expression Quote()
+ => New(
+ _quotingConstructor ??= typeof(PgUnknownBinaryExpression).GetConstructor(
+ [typeof(SqlExpression), typeof(SqlExpression), typeof(string), typeof(Type), typeof(RelationalTypeMapping)])!,
+ Left.Quote(),
+ Right.Quote(),
+ Constant(Operator),
+ Constant(Type),
+ RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping));
+
///
public virtual bool Equals(PgUnknownBinaryExpression? other)
=> ReferenceEquals(this, other)
diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs
index 3afb677bf..5eba3759a 100644
--- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs
+++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs
@@ -1088,8 +1088,7 @@ void GenerateJsonPath(bool returnsText)
s => s switch
{
{ PropertyName: string propertyName }
- => new SqlConstantExpression(
- Expression.Constant(propertyName), _textTypeMapping ??= _typeMappingSource.FindMapping(typeof(string))),
+ => new SqlConstantExpression(propertyName, _textTypeMapping ??= _typeMappingSource.FindMapping(typeof(string))),
{ ArrayIndex: SqlExpression arrayIndex } => arrayIndex,
_ => throw new UnreachableException()
}).ToList());
diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs
index af2cb57ea..0acc8a795 100644
--- a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs
+++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs
@@ -40,6 +40,15 @@ public override Expression Process(Expression query)
return result;
}
+ ///
+ /// 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.
+ ///
+ protected override Expression ProcessTypeMappings(Expression expression)
+ => new NpgsqlTypeMappingPostprocessor(Dependencies, RelationalDependencies, RelationalQueryCompilationContext).Process(expression);
+
///
/// 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
diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs
index 84ec8668a..0c4f030cb 100644
--- a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs
@@ -266,18 +266,6 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT
.SelectMany(t => t.GetDeclaredNavigations());
}
- ///
- /// 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.
- ///
- protected override Expression ApplyInferredTypeMappings(
- Expression expression,
- IReadOnlyDictionary<(string, string), RelationalTypeMapping?> inferredTypeMappings)
- => new NpgsqlInferredTypeMappingApplier(
- RelationalDependencies.Model, _typeMappingSource, _sqlExpressionFactory, inferredTypeMappings).Visit(expression);
-
///
/// 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
@@ -1310,62 +1298,4 @@ public bool ContainsReferenceToMainTable(TableExpressionBase tableExpression)
return base.Visit(expression);
}
}
-
- ///
- /// 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.
- ///
- protected class NpgsqlInferredTypeMappingApplier : RelationalInferredTypeMappingApplier
- {
- private readonly NpgsqlTypeMappingSource _typeMappingSource;
- private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory;
-
- ///
- /// 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 NpgsqlInferredTypeMappingApplier(
- IModel model,
- NpgsqlTypeMappingSource typeMappingSource,
- NpgsqlSqlExpressionFactory sqlExpressionFactory,
- IReadOnlyDictionary<(string, string), RelationalTypeMapping?> inferredTypeMappings)
- : base(model, sqlExpressionFactory, inferredTypeMappings)
- {
- _typeMappingSource = typeMappingSource;
- _sqlExpressionFactory = sqlExpressionFactory;
- }
-
- ///
- /// 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.
- ///
- protected override Expression VisitExtension(Expression expression)
- {
- switch (expression)
- {
- case PgUnnestExpression unnestExpression
- when TryGetInferredTypeMapping(unnestExpression.Alias, unnestExpression.ColumnName, out var elementTypeMapping):
- {
- var collectionTypeMapping = _typeMappingSource.FindMapping(unnestExpression.Array.Type, Model, elementTypeMapping);
-
- if (collectionTypeMapping is null)
- {
- throw new InvalidOperationException(RelationalStrings.NullTypeMappingInSqlTree(expression.Print()));
- }
-
- return unnestExpression.Update(
- _sqlExpressionFactory.ApplyTypeMapping(unnestExpression.Array, collectionTypeMapping));
- }
-
- default:
- return base.VisitExtension(expression);
- }
- }
- }
}
diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs
index 056e73f0c..c309220f8 100644
--- a/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs
+++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs
@@ -261,7 +261,7 @@ protected virtual SqlExpression VisitAny(PgAnyExpression anyExpression, bool all
_sqlExpressionFactory.IsNotNull(
_sqlExpressionFactory.Function(
"array_position",
- new[] { array, _sqlExpressionFactory.Constant(null, item.TypeMapping) },
+ new[] { array, _sqlExpressionFactory.Constant(null, item.Type, item.TypeMapping) },
nullable: true,
argumentsPropagateNullability: FalseArrays[2],
typeof(int)))));
diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs
index e6194f099..6234f7097 100644
--- a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs
+++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs
@@ -411,7 +411,9 @@ private bool TryTranslateStartsEndsWithContains(
// simple LIKE
translation = patternConstant.Value switch
{
- null => _sqlExpressionFactory.Like(translatedInstance, _sqlExpressionFactory.Constant(null, stringTypeMapping)),
+ null => _sqlExpressionFactory.Like(
+ translatedInstance,
+ _sqlExpressionFactory.Constant(null, typeof(string), stringTypeMapping)),
// In .NET, all strings start with/end with/contain the empty string, but SQL LIKE return false for empty patterns.
// Return % which always matches instead.
diff --git a/src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs
new file mode 100644
index 000000000..ba50c0577
--- /dev/null
+++ b/src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs
@@ -0,0 +1,60 @@
+using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal;
+
+namespace Npgsql.EntityFrameworkCore.PostgreSQL.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 NpgsqlTypeMappingPostprocessor : RelationalTypeMappingPostprocessor
+{
+ private readonly IRelationalTypeMappingSource _typeMappingSource;
+ private readonly ISqlExpressionFactory _sqlExpressionFactory;
+
+ ///
+ /// 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 NpgsqlTypeMappingPostprocessor(
+ QueryTranslationPostprocessorDependencies dependencies,
+ RelationalQueryTranslationPostprocessorDependencies relationalDependencies,
+ RelationalQueryCompilationContext queryCompilationContext)
+ : base(dependencies, relationalDependencies, queryCompilationContext)
+ {
+ _typeMappingSource = relationalDependencies.TypeMappingSource;
+ _sqlExpressionFactory = relationalDependencies.SqlExpressionFactory;
+ }
+
+ ///
+ /// 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.
+ ///
+ protected override Expression VisitExtension(Expression expression)
+ {
+ switch (expression)
+ {
+ case PgUnnestExpression unnestExpression
+ when TryGetInferredTypeMapping(unnestExpression.Alias, unnestExpression.ColumnName, out var elementTypeMapping):
+ {
+ var collectionTypeMapping = _typeMappingSource.FindMapping(unnestExpression.Array.Type, Model, elementTypeMapping);
+
+ if (collectionTypeMapping is null)
+ {
+ throw new InvalidOperationException(RelationalStrings.NullTypeMappingInSqlTree(expression.Print()));
+ }
+
+ return unnestExpression.Update(
+ _sqlExpressionFactory.ApplyTypeMapping(unnestExpression.Array, collectionTypeMapping));
+ }
+
+ default:
+ return base.VisitExtension(expression);
+ }
+ }
+}
diff --git a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs
index 91d898672..368ddc238 100644
--- a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs
+++ b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs
@@ -540,7 +540,7 @@ bool TryGetRowValueValues(SqlExpression e, [NotNullWhen(true)] out IReadOnlyList
for (var i = 0; i < v.Length; i++)
{
- v[i] = Constant(constantTuple[i]);
+ v[i] = Constant(constantTuple[i], typeof(object));
}
values = v;
@@ -651,7 +651,7 @@ private SqlExpression ApplyTypeMappingOnArrayIndex(
// If a (non-null) type mapping is being applied, it's to the element being indexed.
// Infer the array's mapping from that.
var (_, array) = typeMapping is not null
- ? ApplyTypeMappingsOnItemAndArray(Constant(null, typeMapping), pgArrayIndexExpression.Array)
+ ? ApplyTypeMappingsOnItemAndArray(Constant(null, typeMapping.ClrType, typeMapping), pgArrayIndexExpression.Array)
: (null, ApplyDefaultTypeMapping(pgArrayIndexExpression.Array));
return new PgArrayIndexExpression(
diff --git a/test/Directory.Build.props b/test/Directory.Build.props
index e4c47baca..8648a51f4 100644
--- a/test/Directory.Build.props
+++ b/test/Directory.Build.props
@@ -3,7 +3,7 @@
- net8.0
+ net9.0
false
false
diff --git a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj
index d842ad664..421d004c8 100644
--- a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj
+++ b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj
@@ -17,6 +17,10 @@
+
+
+
+
diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs
index 190860809..2277793c9 100644
--- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs
+++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs
@@ -391,40 +391,35 @@ public override async Task Drop_table()
{
await base.Drop_table();
- AssertSql(
- @"DROP TABLE ""People"";");
+ AssertSql("""DROP TABLE "People";""");
}
public override async Task Alter_table_add_comment()
{
await base.Alter_table_add_comment();
- AssertSql(
- @"COMMENT ON TABLE ""People"" IS 'Table comment';");
+ AssertSql("""COMMENT ON TABLE "People" IS 'Table comment';""");
}
public override async Task Alter_table_add_comment_non_default_schema()
{
await base.Alter_table_add_comment_non_default_schema();
- AssertSql(
- @"COMMENT ON TABLE ""SomeOtherSchema"".""People"" IS 'Table comment';");
+ AssertSql("""COMMENT ON TABLE "SomeOtherSchema"."People" IS 'Table comment';""");
}
public override async Task Alter_table_change_comment()
{
await base.Alter_table_change_comment();
- AssertSql(
- @"COMMENT ON TABLE ""People"" IS 'Table comment2';");
+ AssertSql("""COMMENT ON TABLE "People" IS 'Table comment2';""");
}
public override async Task Alter_table_remove_comment()
{
await base.Alter_table_remove_comment();
- AssertSql(
- @"COMMENT ON TABLE ""People"" IS NULL;");
+ AssertSql("""COMMENT ON TABLE "People" IS NULL;""");
}
[Fact]
@@ -478,8 +473,7 @@ await Test(
builder => builder.Entity("People").IsUnlogged(),
asserter: null); // We don't scaffold unlogged
- AssertSql(
- @"ALTER TABLE ""People"" SET UNLOGGED;");
+ AssertSql("""ALTER TABLE "People" SET UNLOGGED;""");
}
[Fact]
@@ -496,8 +490,7 @@ await Test(
_ => { },
asserter: null); // We don't scaffold unlogged
- AssertSql(
- @"ALTER TABLE ""People"" SET LOGGED;");
+ AssertSql("""ALTER TABLE "People" SET LOGGED;""");
}
public override async Task Rename_table()
@@ -505,11 +498,11 @@ public override async Task Rename_table()
await base.Rename_table();
AssertSql(
- @"ALTER TABLE ""People"" DROP CONSTRAINT ""PK_People"";",
+ """ALTER TABLE "People" DROP CONSTRAINT "PK_People";""",
//
- @"ALTER TABLE ""People"" RENAME TO ""Persons"";",
+ """ALTER TABLE "People" RENAME TO "Persons";""",
//
- @"ALTER TABLE ""Persons"" ADD CONSTRAINT ""PK_Persons"" PRIMARY KEY (""Id"");");
+ """ALTER TABLE "Persons" ADD CONSTRAINT "PK_Persons" PRIMARY KEY ("Id");""");
}
public override async Task Rename_table_with_primary_key()
@@ -517,11 +510,11 @@ public override async Task Rename_table_with_primary_key()
await base.Rename_table_with_primary_key();
AssertSql(
- @"ALTER TABLE ""People"" DROP CONSTRAINT ""PK_People"";",
+ """ALTER TABLE "People" DROP CONSTRAINT "PK_People";""",
//
- @"ALTER TABLE ""People"" RENAME TO ""Persons"";",
+ """ALTER TABLE "People" RENAME TO "Persons";""",
//
- @"ALTER TABLE ""Persons"" ADD CONSTRAINT ""PK_Persons"" PRIMARY KEY (""Id"");");
+ """ALTER TABLE "Persons" ADD CONSTRAINT "PK_Persons" PRIMARY KEY ("Id");""");
}
public override async Task Move_table()
@@ -594,8 +587,7 @@ public override async Task Add_column_with_defaultValue_string()
{
await base.Add_column_with_defaultValue_string();
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""Name"" text NOT NULL DEFAULT 'John Doe';");
+ AssertSql("""ALTER TABLE "People" ADD "Name" text NOT NULL DEFAULT 'John Doe';""");
}
public override async Task Add_column_with_defaultValue_datetime()
@@ -614,8 +606,7 @@ await Test(
Assert.False(birthdayColumn.IsNullable);
});
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""Birthday"" timestamp with time zone NOT NULL DEFAULT TIMESTAMPTZ '2015-04-12T17:05:00Z';");
+ AssertSql("""ALTER TABLE "People" ADD "Birthday" timestamp with time zone NOT NULL DEFAULT TIMESTAMPTZ '2015-04-12T17:05:00Z';""");
}
[Fact]
@@ -623,8 +614,7 @@ public override async Task Add_column_with_defaultValueSql()
{
await base.Add_column_with_defaultValueSql();
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""Sum"" integer NOT NULL DEFAULT (1 + 2);");
+ AssertSql("""ALTER TABLE "People" ADD "Sum" integer NOT NULL DEFAULT (1 + 2);""");
}
public override async Task Add_column_with_computedSql(bool? stored)
@@ -644,40 +634,35 @@ public override async Task Add_column_with_computedSql(bool? stored)
await base.Add_column_with_computedSql(stored: true);
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""Sum"" text GENERATED ALWAYS AS (""X"" + ""Y"") STORED;");
+ AssertSql("""ALTER TABLE "People" ADD "Sum" text GENERATED ALWAYS AS ("X" + "Y") STORED;""");
}
public override async Task Add_column_with_required()
{
await base.Add_column_with_required();
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""Name"" text NOT NULL DEFAULT '';");
+ AssertSql("""ALTER TABLE "People" ADD "Name" text NOT NULL DEFAULT '';""");
}
public override async Task Add_column_with_ansi()
{
await base.Add_column_with_ansi();
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""Name"" text;");
+ AssertSql("""ALTER TABLE "People" ADD "Name" text;""");
}
public override async Task Add_column_with_max_length()
{
await base.Add_column_with_max_length();
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""Name"" character varying(30);");
+ AssertSql("""ALTER TABLE "People" ADD "Name" character varying(30);""");
}
public override async Task Add_column_with_unbounded_max_length()
{
await base.Add_column_with_unbounded_max_length();
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""Name"" text;");
+ AssertSql("""ALTER TABLE "People" ADD "Name" text;""");
}
public override async Task Add_column_with_max_length_on_derived()
@@ -691,7 +676,7 @@ public override async Task Add_column_with_fixed_length()
{
await base.Add_column_with_fixed_length();
- AssertSql(@"ALTER TABLE ""People"" ADD ""Name"" character(100);");
+ AssertSql("""ALTER TABLE "People" ADD "Name" character(100);""");
}
public override async Task Add_column_with_comment()
@@ -709,8 +694,7 @@ public override async Task Add_column_with_collation()
{
await base.Add_column_with_collation();
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""Name"" text COLLATE ""POSIX"";");
+ AssertSql("""ALTER TABLE "People" ADD "Name" text COLLATE "POSIX";""");
}
public override async Task Add_column_computed_with_collation(bool stored)
@@ -730,8 +714,7 @@ public override async Task Add_column_computed_with_collation(bool stored)
await base.Add_column_computed_with_collation(stored);
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""Name"" text COLLATE ""POSIX"" GENERATED ALWAYS AS ('hello') STORED;");
+ AssertSql("""ALTER TABLE "People" ADD "Name" text COLLATE "POSIX" GENERATED ALWAYS AS ('hello') STORED;""");
}
#pragma warning disable CS0618
@@ -754,8 +737,7 @@ await Test(
Assert.Equal("POSIX", nameColumn.Collation);
});
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""Name"" text COLLATE ""POSIX"";");
+ AssertSql("""ALTER TABLE "People" ADD "Name" text COLLATE "POSIX";""");
}
[ConditionalFact]
@@ -778,8 +760,7 @@ await Test(
Assert.Equal("C", nameColumn.Collation);
});
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""Name"" text COLLATE ""C"";");
+ AssertSql("""ALTER TABLE "People" ADD "Name" text COLLATE "C";""");
}
#pragma warning restore CS0618
@@ -834,8 +815,7 @@ await Test(
Assert.Null(column[NpgsqlAnnotationNames.IdentityOptions]);
});
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""SomeColumn"" integer GENERATED BY DEFAULT AS IDENTITY;");
+ AssertSql("""ALTER TABLE "People" ADD "SomeColumn" integer GENERATED BY DEFAULT AS IDENTITY;""");
}
[Fact]
@@ -859,8 +839,7 @@ await Test(
Assert.Null(column[NpgsqlAnnotationNames.IdentityOptions]);
});
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""SomeColumn"" integer GENERATED ALWAYS AS IDENTITY;");
+ AssertSql("""ALTER TABLE "People" ADD "SomeColumn" integer GENERATED ALWAYS AS IDENTITY;""");
}
[Fact]
@@ -898,7 +877,9 @@ await Test(
});
AssertSql(
- @"ALTER TABLE ""People"" ADD ""SomeColumn"" integer GENERATED BY DEFAULT AS IDENTITY (START WITH 5 INCREMENT BY 2 MINVALUE 3 MAXVALUE 2000 CYCLE CACHE 10);");
+ """
+ALTER TABLE "People" ADD "SomeColumn" integer GENERATED BY DEFAULT AS IDENTITY (START WITH 5 INCREMENT BY 2 MINVALUE 3 MAXVALUE 2000 CYCLE CACHE 10);
+""");
}
[Fact]
@@ -935,8 +916,7 @@ await Test(
Assert.Null(column[NpgsqlAnnotationNames.IdentityOptions]);
});
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""SomeColumn"" serial NOT NULL;");
+ AssertSql("""ALTER TABLE "People" ADD "SomeColumn" serial NOT NULL;""");
}
[Fact]
@@ -960,8 +940,7 @@ await Test(
Assert.Null(column[NpgsqlAnnotationNames.IdentityOptions]);
});
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""SomeColumn"" integer GENERATED BY DEFAULT AS IDENTITY;");
+ AssertSql("""ALTER TABLE "People" ADD "SomeColumn" integer GENERATED BY DEFAULT AS IDENTITY;""");
}
[Fact]
@@ -1008,8 +987,7 @@ await Test(
Assert.Equal("text", column.StoreType);
});
- AssertSql(
- @"ALTER TABLE ""People"" ADD ""Name"" text;");
+ AssertSql("""ALTER TABLE "People" ADD "Name" text;""");
}
[Fact]
@@ -1031,20 +1009,14 @@ await Test(
Assert.Equal("pglz", column[NpgsqlAnnotationNames.CompressionMethod]);
});
- AssertSql(
- """
-ALTER TABLE "Blogs" ADD "Title" text COMPRESSION pglz;
-""");
+ AssertSql("""ALTER TABLE "Blogs" ADD "Title" text COMPRESSION pglz;""");
}
public override async Task Alter_column_change_type()
{
await base.Alter_column_change_type();
- AssertSql(
- """
-ALTER TABLE "People" ALTER COLUMN "SomeColumn" TYPE bigint;
-""");
+ AssertSql("""ALTER TABLE "People" ALTER COLUMN "SomeColumn" TYPE bigint;""");
}
public override async Task Alter_column_make_required()
@@ -1113,9 +1085,9 @@ public override async Task Alter_column_make_computed(bool? stored)
await base.Alter_column_make_computed(stored);
AssertSql(
- @"ALTER TABLE ""People"" DROP COLUMN ""Sum"";",
+ """ALTER TABLE "People" DROP COLUMN "Sum";""",
//
- @"ALTER TABLE ""People"" ADD ""Sum"" integer GENERATED ALWAYS AS (""X"" + ""Y"") STORED NOT NULL;");
+ """ALTER TABLE "People" ADD "Sum" integer GENERATED ALWAYS AS ("X" + "Y") STORED NOT NULL;""");
}
public override async Task Alter_column_change_computed()
@@ -1150,9 +1122,9 @@ await Test(
});
AssertSql(
- @"ALTER TABLE ""People"" DROP COLUMN ""Sum"";",
+ """ALTER TABLE "People" DROP COLUMN "Sum";""",
//
- @"ALTER TABLE ""People"" ADD ""Sum"" integer GENERATED ALWAYS AS (""X"" - ""Y"") STORED NOT NULL;");
+ """ALTER TABLE "People" ADD "Sum" integer GENERATED ALWAYS AS ("X" - "Y") STORED NOT NULL;""");
}
public override async Task Alter_column_change_computed_recreates_indexes()
@@ -1195,11 +1167,11 @@ await Test(
});
AssertSql(
- @"ALTER TABLE ""People"" DROP COLUMN ""Sum"";",
+ """ALTER TABLE "People" DROP COLUMN "Sum";""",
//
- @"ALTER TABLE ""People"" ADD ""Sum"" integer GENERATED ALWAYS AS (""X"" - ""Y"") STORED NOT NULL;",
+ """ALTER TABLE "People" ADD "Sum" integer GENERATED ALWAYS AS ("X" - "Y") STORED NOT NULL;""",
//
- @"CREATE INDEX ""IX_People_Sum"" ON ""People"" (""Sum"");");
+ """CREATE INDEX "IX_People_Sum" ON "People" ("Sum");""");
}
public override Task Alter_column_change_computed_type()
@@ -1233,17 +1205,16 @@ await Test(
});
AssertSql(
- @"ALTER TABLE ""People"" DROP COLUMN ""Sum"";",
+ """ALTER TABLE "People" DROP COLUMN "Sum";""",
//
- @"ALTER TABLE ""People"" ADD ""Sum"" integer NOT NULL;");
+ """ALTER TABLE "People" ADD "Sum" integer NOT NULL;""");
}
public override async Task Alter_column_add_comment()
{
await base.Alter_column_add_comment();
- AssertSql(
- @"COMMENT ON COLUMN ""People"".""Id"" IS 'Some comment';");
+ AssertSql("""COMMENT ON COLUMN "People"."Id" IS 'Some comment';""");
}
public override async Task Alter_computed_column_add_comment()
@@ -1273,16 +1244,14 @@ await Test(
}
});
- AssertSql(
- @"COMMENT ON COLUMN ""People"".""SomeColumn"" IS 'Some comment';");
+ AssertSql("""COMMENT ON COLUMN "People"."SomeColumn" IS 'Some comment';""");
}
public override async Task Alter_column_change_comment()
{
await base.Alter_column_change_comment();
- AssertSql(
- @"COMMENT ON COLUMN ""People"".""Id"" IS 'Some comment2';");
+ AssertSql("""COMMENT ON COLUMN "People"."Id" IS 'Some comment2';""");
}
public override async Task Alter_column_remove_comment()
@@ -1290,7 +1259,7 @@ public override async Task Alter_column_remove_comment()
await base.Alter_column_remove_comment();
AssertSql(
- @"COMMENT ON COLUMN ""People"".""Id"" IS NULL;");
+ """COMMENT ON COLUMN "People"."Id" IS NULL;""");
}
[Fact]
@@ -1337,8 +1306,7 @@ await Test(
Assert.Null(column[NpgsqlAnnotationNames.IdentityOptions]);
});
- AssertSql(
- @"ALTER TABLE ""People"" ALTER COLUMN ""Id"" SET GENERATED ALWAYS;");
+ AssertSql("""ALTER TABLE "People" ALTER COLUMN "Id" SET GENERATED ALWAYS;""");
}
[Fact]
@@ -1610,7 +1578,7 @@ await Test(
});
AssertSql(
- @"ALTER TABLE ""People"" ALTER COLUMN ""Id"" SET GENERATED ALWAYS;");
+ """ALTER TABLE "People" ALTER COLUMN "Id" SET GENERATED ALWAYS;""");
}
[Fact]
@@ -1668,8 +1636,7 @@ await Test(
Assert.Null(column[NpgsqlAnnotationNames.IdentityOptions]);
});
- AssertSql(
- @"ALTER TABLE ""People"" ALTER COLUMN ""Id"" TYPE bigint;");
+ AssertSql("""ALTER TABLE "People" ALTER COLUMN "Id" TYPE bigint;""");
}
[Fact]
@@ -1693,8 +1660,7 @@ await Test(
Assert.Equal(10, options.StartValue); // Restarting doesn't change the scaffolded start value
});
- AssertSql(
- @"ALTER TABLE ""People"" ALTER COLUMN ""Id"" RESTART WITH 20;");
+ AssertSql("""ALTER TABLE "People" ALTER COLUMN "Id" RESTART WITH 20;""");
}
[Fact]
@@ -1702,8 +1668,7 @@ public override async Task Alter_column_set_collation()
{
await base.Alter_column_set_collation();
- AssertSql(
- @"ALTER TABLE ""People"" ALTER COLUMN ""Name"" TYPE text COLLATE ""POSIX"";");
+ AssertSql("""ALTER TABLE "People" ALTER COLUMN "Name" TYPE text COLLATE "POSIX";""");
}
[Fact]
@@ -1711,8 +1676,7 @@ public override async Task Alter_column_reset_collation()
{
await base.Alter_column_reset_collation();
- AssertSql(
- @"ALTER TABLE ""People"" ALTER COLUMN ""Name"" TYPE text COLLATE ""default"";");
+ AssertSql("""ALTER TABLE "People" ALTER COLUMN "Name" TYPE text COLLATE "default";""");
}
public override async Task Convert_string_column_to_a_json_column_containing_reference()
@@ -1759,8 +1723,7 @@ await Test(
// Assert.Equal("C", nameColumn.Collation);
});
- AssertSql(
- @"ALTER TABLE ""People"" ALTER COLUMN ""Name"" TYPE text COLLATE ""C"";");
+ AssertSql("""ALTER TABLE "People" ALTER COLUMN "Name" TYPE text COLLATE "C";""");
}
#pragma warning restore CS0618
@@ -1789,8 +1752,7 @@ await Test(
Assert.Equal(NonDefaultCollation, computedColumn.Collation);
});
- AssertSql(
- @"ALTER TABLE ""People"" ALTER COLUMN ""Name2"" TYPE text COLLATE ""POSIX"";");
+ AssertSql("""ALTER TABLE "People" ALTER COLUMN "Name2" TYPE text COLLATE "POSIX";""");
}
[Fact]
@@ -1812,8 +1774,7 @@ await Test(
Assert.Equal("pglz", column[NpgsqlAnnotationNames.CompressionMethod]);
});
- AssertSql(
- @"ALTER TABLE ""Blogs"" ALTER COLUMN ""Title"" SET COMPRESSION pglz");
+ AssertSql("""ALTER TABLE "Blogs" ALTER COLUMN "Title" SET COMPRESSION pglz""");
}
[Fact]
@@ -1835,16 +1796,14 @@ await Test(
Assert.Null(column[NpgsqlAnnotationNames.CompressionMethod]);
});
- AssertSql(
- @"ALTER TABLE ""Blogs"" ALTER COLUMN ""Title"" SET COMPRESSION default");
+ AssertSql("""ALTER TABLE "Blogs" ALTER COLUMN "Title" SET COMPRESSION default""");
}
public override async Task Drop_column()
{
await base.Drop_column();
- AssertSql(
- @"ALTER TABLE ""People"" DROP COLUMN ""SomeColumn"";");
+ AssertSql("""ALTER TABLE "People" DROP COLUMN "SomeColumn";""");
}
public override async Task Drop_column_primary_key()
@@ -1852,9 +1811,9 @@ public override async Task Drop_column_primary_key()
await base.Drop_column_primary_key();
AssertSql(
- @"ALTER TABLE ""People"" DROP CONSTRAINT ""PK_People"";",
+ """ALTER TABLE "People" DROP CONSTRAINT "PK_People";""",
//
- @"ALTER TABLE ""People"" DROP COLUMN ""Id"";");
+ """ALTER TABLE "People" DROP COLUMN "Id";""");
}
public override async Task Drop_column_computed_and_non_computed_with_dependency()
@@ -1880,9 +1839,9 @@ await Test(
});
AssertSql(
- @"ALTER TABLE ""People"" DROP COLUMN ""Y"";",
+ """ALTER TABLE "People" DROP COLUMN "Y";""",
//
- @"ALTER TABLE ""People"" DROP COLUMN ""X"";");
+ """ALTER TABLE "People" DROP COLUMN "X";""");
}
[Fact]
@@ -1918,8 +1877,7 @@ public override async Task Rename_column()
{
await base.Rename_column();
- AssertSql(
- @"ALTER TABLE ""People"" RENAME COLUMN ""SomeColumn"" TO ""SomeOtherColumn"";");
+ AssertSql("""ALTER TABLE "People" RENAME COLUMN "SomeColumn" TO "SomeOtherColumn";""");
}
#endregion
@@ -1930,24 +1888,21 @@ public override async Task Create_index_unique()
{
await base.Create_index_unique();
- AssertSql(
- @"CREATE UNIQUE INDEX ""IX_People_FirstName_LastName"" ON ""People"" (""FirstName"", ""LastName"");");
+ AssertSql("""CREATE UNIQUE INDEX "IX_People_FirstName_LastName" ON "People" ("FirstName", "LastName");""");
}
public override async Task Create_index_descending()
{
await base.Create_index_descending();
- AssertSql(
- @"CREATE INDEX ""IX_People_X"" ON ""People"" (""X"" DESC);");
+ AssertSql("""CREATE INDEX "IX_People_X" ON "People" ("X" DESC);""");
}
public override async Task Create_index_descending_mixed()
{
await base.Create_index_descending_mixed();
- AssertSql(
- @"CREATE INDEX ""IX_People_X_Y_Z"" ON ""People"" (""X"", ""Y"" DESC, ""Z"");");
+ AssertSql("""CREATE INDEX "IX_People_X_Y_Z" ON "People" ("X", "Y" DESC, "Z");""");
}
#pragma warning disable CS0618 // HasSortOrder is obsolete
@@ -1974,8 +1929,7 @@ await Test(
Assert.Collection(index.IsDescending, Assert.False, Assert.True, Assert.False);
});
- AssertSql(
- @"CREATE INDEX ""IX_People_X_Y_Z"" ON ""People"" (""X"", ""Y"" DESC, ""Z"");");
+ AssertSql("""CREATE INDEX "IX_People_X_Y_Z" ON "People" ("X", "Y" DESC, "Z");""");
}
#pragma warning restore CS0618
@@ -2002,24 +1956,21 @@ await Test(
Assert.Collection(index.IsDescending, Assert.False, Assert.True, Assert.False);
});
- AssertSql(
- @"CREATE INDEX ""IX_People_X_Y_Z"" ON ""People"" (""X"", ""Y"" DESC, ""Z"");");
+ AssertSql("""CREATE INDEX "IX_People_X_Y_Z" ON "People" ("X", "Y" DESC, "Z");""");
}
public override async Task Create_index_with_filter()
{
await base.Create_index_with_filter();
- AssertSql(
- @"CREATE INDEX ""IX_People_Name"" ON ""People"" (""Name"") WHERE ""Name"" IS NOT NULL;");
+ AssertSql("""CREATE INDEX "IX_People_Name" ON "People" ("Name") WHERE "Name" IS NOT NULL;""");
}
public override async Task Create_unique_index_with_filter()
{
await base.Create_unique_index_with_filter();
- AssertSql(
- @"CREATE UNIQUE INDEX ""IX_People_Name"" ON ""People"" (""Name"") WHERE ""Name"" IS NOT NULL AND ""Name"" <> '';");
+ AssertSql("""CREATE UNIQUE INDEX "IX_People_Name" ON "People" ("Name") WHERE "Name" IS NOT NULL AND "Name" <> '';""");
}
[Fact]
@@ -2061,8 +2012,8 @@ await Test(
AssertSql(
TestEnvironment.PostgresVersion.AtLeast(11)
- ? @"CREATE INDEX ""IX_People_Name"" ON ""People"" (""Name"") INCLUDE (""FirstName"", last_name);"
- : @"CREATE INDEX ""IX_People_Name"" ON ""People"" (""Name"");");
+ ? """CREATE INDEX "IX_People_Name" ON "People" ("Name") INCLUDE ("FirstName", last_name);"""
+ : """CREATE INDEX "IX_People_Name" ON "People" ("Name");""");
}
[Fact]
@@ -2106,8 +2057,8 @@ await Test(
AssertSql(
TestEnvironment.PostgresVersion.AtLeast(11)
- ? @"CREATE INDEX ""IX_People_Name"" ON ""People"" (""Name"") INCLUDE (""FirstName"", ""LastName"") WHERE ""Name"" IS NOT NULL;"
- : @"CREATE INDEX ""IX_People_Name"" ON ""People"" (""Name"") WHERE ""Name"" IS NOT NULL;");
+ ? """CREATE INDEX "IX_People_Name" ON "People" ("Name") INCLUDE ("FirstName", "LastName") WHERE "Name" IS NOT NULL;"""
+ : """CREATE INDEX "IX_People_Name" ON "People" ("Name") WHERE "Name" IS NOT NULL;""");
}
[Fact]
@@ -2151,8 +2102,8 @@ await Test(
AssertSql(
TestEnvironment.PostgresVersion.AtLeast(11)
- ? @"CREATE UNIQUE INDEX ""IX_People_Name"" ON ""People"" (""Name"") INCLUDE (""FirstName"", ""LastName"");"
- : @"CREATE UNIQUE INDEX ""IX_People_Name"" ON ""People"" (""Name"");");
+ ? """CREATE UNIQUE INDEX "IX_People_Name" ON "People" ("Name") INCLUDE ("FirstName", "LastName");"""
+ : """CREATE UNIQUE INDEX "IX_People_Name" ON "People" ("Name");""");
}
[Fact]
@@ -2198,8 +2149,8 @@ await Test(
AssertSql(
TestEnvironment.PostgresVersion.AtLeast(11)
- ? @"CREATE UNIQUE INDEX ""IX_People_Name"" ON ""People"" (""Name"") INCLUDE (""FirstName"", ""LastName"") WHERE ""Name"" IS NOT NULL;"
- : @"CREATE UNIQUE INDEX ""IX_People_Name"" ON ""People"" (""Name"") WHERE ""Name"" IS NOT NULL;");
+ ? """CREATE UNIQUE INDEX "IX_People_Name" ON "People" ("Name") INCLUDE ("FirstName", "LastName") WHERE "Name" IS NOT NULL;"""
+ : """CREATE UNIQUE INDEX "IX_People_Name" ON "People" ("Name") WHERE "Name" IS NOT NULL;""");
}
[Fact]
@@ -2217,8 +2168,7 @@ await Test(
.IsCreatedConcurrently(),
asserter: null); // No scaffolding for IsCreatedConcurrently
- AssertSql(
- @"CREATE INDEX CONCURRENTLY ""IX_People_Age"" ON ""People"" (""Age"");");
+ AssertSql("""CREATE INDEX CONCURRENTLY "IX_People_Age" ON "People" ("Age");""");
}
[Fact]
@@ -2241,8 +2191,7 @@ await Test(
Assert.Equal("hash", index[NpgsqlAnnotationNames.IndexMethod]);
});
- AssertSql(
- @"CREATE INDEX ""IX_People_Age"" ON ""People"" USING hash (""Age"");");
+ AssertSql("""CREATE INDEX "IX_People_Age" ON "People" USING hash ("Age");""");
}
[Fact]
@@ -2266,8 +2215,7 @@ await Test(
Assert.Equal(new[] { "text_pattern_ops", null }, index[NpgsqlAnnotationNames.IndexOperators]);
});
- AssertSql(
- @"CREATE INDEX ""IX_People_FirstName_LastName"" ON ""People"" (""FirstName"" text_pattern_ops, ""LastName"");");
+ AssertSql("""CREATE INDEX "IX_People_FirstName_LastName" ON "People" ("FirstName" text_pattern_ops, "LastName");""");
}
[Fact]
@@ -2288,10 +2236,7 @@ await Test(
Assert.Equal("some_collation", Assert.Single((IReadOnlyList)index[RelationalAnnotationNames.Collation]!));
});
- AssertSql(
- """
-CREATE INDEX "IX_People_Name" ON "People" ("Name" COLLATE some_collation);
-""");
+ AssertSql("""CREATE INDEX "IX_People_Name" ON "People" ("Name" COLLATE some_collation);""");
}
[Fact] // #3027
@@ -2315,10 +2260,7 @@ await Test(
Assert.Equal("some_collation", Assert.Single((IReadOnlyList)index[RelationalAnnotationNames.Collation]!));
});
- AssertSql(
- """
-CREATE INDEX "IX_People_Name" ON "People" ("Name" COLLATE some_collation text_pattern_ops);
-""");
+ AssertSql("""CREATE INDEX "IX_People_Name" ON "People" ("Name" COLLATE some_collation text_pattern_ops);""");
}
[Fact]
@@ -2346,7 +2288,7 @@ await Test(
});
AssertSql(
- @"CREATE INDEX ""IX_People_FirstName_MiddleName_LastName"" ON ""People"" (""FirstName"" NULLS FIRST, ""MiddleName"", ""LastName"" NULLS LAST);");
+ """CREATE INDEX "IX_People_FirstName_MiddleName_LastName" ON "People" ("FirstName" NULLS FIRST, "MiddleName", "LastName" NULLS LAST);""");
}
[Fact]
@@ -2366,7 +2308,7 @@ await Test(
_ => { });
AssertSql(
- @"CREATE INDEX ""IX_Blogs_Title_Description"" ON ""Blogs"" (to_tsvector('simple', ""Title"" || ' ' || coalesce(""Description"", '')));");
+ """CREATE INDEX "IX_Blogs_Title_Description" ON "Blogs" (to_tsvector('simple', "Title" || ' ' || coalesce("Description", '')));""");
}
[Fact]
@@ -2387,7 +2329,7 @@ await Test(
_ => { });
AssertSql(
- @"CREATE INDEX ""IX_Blogs_Title_Description"" ON ""Blogs"" USING GIN (to_tsvector('simple', ""Title"" || ' ' || coalesce(""Description"", '')));");
+ """CREATE INDEX "IX_Blogs_Title_Description" ON "Blogs" USING GIN (to_tsvector('simple', "Title" || ' ' || coalesce("Description", '')));""");
}
[ConditionalFact]
@@ -2425,13 +2367,9 @@ await Test(
});
AssertSql(
- """
-CREATE UNIQUE INDEX "IX_NullsDistinct" ON "People" ("Age");
-""",
+ """CREATE UNIQUE INDEX "IX_NullsDistinct" ON "People" ("Age");""",
//
- """
-CREATE UNIQUE INDEX "IX_NullsNotDistinct" ON "People" ("Age") NULLS NOT DISTINCT;
-""");
+ """CREATE UNIQUE INDEX "IX_NullsNotDistinct" ON "People" ("Age") NULLS NOT DISTINCT;""");
}
[Fact]
@@ -2460,8 +2398,7 @@ await Test(
Assert.Equal("70", storageParameter.Value);
});
- AssertSql(
- @"CREATE INDEX ""IX_People_Age"" ON ""People"" (""Age"") WITH (fillfactor=70);");
+ AssertSql("""CREATE INDEX "IX_People_Age" ON "People" ("Age") WITH (fillfactor=70);""");
}
public override async Task Alter_index_change_sort_order()
@@ -2469,25 +2406,23 @@ public override async Task Alter_index_change_sort_order()
await base.Alter_index_change_sort_order();
AssertSql(
- @"DROP INDEX ""IX_People_X_Y_Z"";",
+ """DROP INDEX "IX_People_X_Y_Z";""",
//
- @"CREATE INDEX ""IX_People_X_Y_Z"" ON ""People"" (""X"", ""Y"" DESC, ""Z"");");
+ """CREATE INDEX "IX_People_X_Y_Z" ON "People" ("X", "Y" DESC, "Z");""");
}
public override async Task Drop_index()
{
await base.Drop_index();
- AssertSql(
- @"DROP INDEX ""IX_People_SomeField"";");
+ AssertSql("""DROP INDEX "IX_People_SomeField";""");
}
public override async Task Rename_index()
{
await base.Rename_index();
- AssertSql(
- @"ALTER INDEX ""Foo"" RENAME TO foo;");
+ AssertSql("""ALTER INDEX "Foo" RENAME TO foo;""");
}
#endregion
@@ -2504,14 +2439,16 @@ public override async Task Add_primary_key_int()
ALTER TABLE "People" ALTER COLUMN "SomeField" ADD GENERATED BY DEFAULT AS IDENTITY;
""",
//
- @"ALTER TABLE ""People"" ADD CONSTRAINT ""PK_People"" PRIMARY KEY (""SomeField"");");
+ """
+ALTER TABLE "People" ADD CONSTRAINT "PK_People" PRIMARY KEY ("SomeField");
+""");
}
public override async Task Add_primary_key_string()
{
await base.Add_primary_key_string();
- AssertSql(@"ALTER TABLE ""People"" ADD CONSTRAINT ""PK_People"" PRIMARY KEY (""SomeField"");");
+ AssertSql("""ALTER TABLE "People" ADD CONSTRAINT "PK_People" PRIMARY KEY ("SomeField");""");
}
public override async Task Add_primary_key_with_name()
@@ -2525,15 +2462,16 @@ public override async Task Add_primary_key_with_name()
ALTER TABLE "People" ALTER COLUMN "SomeField" SET DEFAULT '';
""",
//
- @"ALTER TABLE ""People"" ADD CONSTRAINT ""PK_Foo"" PRIMARY KEY (""SomeField"");");
+ """
+ALTER TABLE "People" ADD CONSTRAINT "PK_Foo" PRIMARY KEY ("SomeField");
+""");
}
public override async Task Add_primary_key_composite_with_name()
{
await base.Add_primary_key_composite_with_name();
- AssertSql(
- @"ALTER TABLE ""People"" ADD CONSTRAINT ""PK_Foo"" PRIMARY KEY (""SomeField1"", ""SomeField2"");");
+ AssertSql("""ALTER TABLE "People" ADD CONSTRAINT "PK_Foo" PRIMARY KEY ("SomeField1", "SomeField2");""");
}
public override async Task Drop_primary_key_int()
@@ -2541,17 +2479,16 @@ public override async Task Drop_primary_key_int()
await base.Drop_primary_key_int();
AssertSql(
- @"ALTER TABLE ""People"" DROP CONSTRAINT ""PK_People"";",
+ """ALTER TABLE "People" DROP CONSTRAINT "PK_People";""",
//
- @"ALTER TABLE ""People"" ALTER COLUMN ""SomeField"" DROP IDENTITY;");
+ """ALTER TABLE "People" ALTER COLUMN "SomeField" DROP IDENTITY;""");
}
public override async Task Drop_primary_key_string()
{
await base.Drop_primary_key_string();
- AssertSql(
- @"ALTER TABLE ""People"" DROP CONSTRAINT ""PK_People"";");
+ AssertSql("""ALTER TABLE "People" DROP CONSTRAINT "PK_People";""");
}
public override Task Add_foreign_key()
@@ -2562,9 +2499,9 @@ public override async Task Add_foreign_key_with_name()
await base.Add_foreign_key_with_name();
AssertSql(
- @"CREATE INDEX ""IX_Orders_CustomerId"" ON ""Orders"" (""CustomerId"");",
+ """CREATE INDEX "IX_Orders_CustomerId" ON "Orders" ("CustomerId");""",
//
- @"ALTER TABLE ""Orders"" ADD CONSTRAINT ""FK_Foo"" FOREIGN KEY (""CustomerId"") REFERENCES ""Customers"" (""Id"") ON DELETE CASCADE;");
+ """ALTER TABLE "Orders" ADD CONSTRAINT "FK_Foo" FOREIGN KEY ("CustomerId") REFERENCES "Customers" ("Id") ON DELETE CASCADE;""");
}
public override async Task Drop_foreign_key()
@@ -2572,49 +2509,44 @@ public override async Task Drop_foreign_key()
await base.Drop_foreign_key();
AssertSql(
- @"ALTER TABLE ""Orders"" DROP CONSTRAINT ""FK_Orders_Customers_CustomerId"";",
+ """ALTER TABLE "Orders" DROP CONSTRAINT "FK_Orders_Customers_CustomerId";""",
//
- @"DROP INDEX ""IX_Orders_CustomerId"";");
+ """DROP INDEX "IX_Orders_CustomerId";""");
}
public override async Task Add_unique_constraint()
{
await base.Add_unique_constraint();
- AssertSql(
- @"ALTER TABLE ""People"" ADD CONSTRAINT ""AK_People_AlternateKeyColumn"" UNIQUE (""AlternateKeyColumn"");");
+ AssertSql("""ALTER TABLE "People" ADD CONSTRAINT "AK_People_AlternateKeyColumn" UNIQUE ("AlternateKeyColumn");""");
}
public override async Task Add_unique_constraint_composite_with_name()
{
await base.Add_unique_constraint_composite_with_name();
- AssertSql(
- @"ALTER TABLE ""People"" ADD CONSTRAINT ""AK_Foo"" UNIQUE (""AlternateKeyColumn1"", ""AlternateKeyColumn2"");");
+ AssertSql("""ALTER TABLE "People" ADD CONSTRAINT "AK_Foo" UNIQUE ("AlternateKeyColumn1", "AlternateKeyColumn2");""");
}
public override async Task Drop_unique_constraint()
{
await base.Drop_unique_constraint();
- AssertSql(
- @"ALTER TABLE ""People"" DROP CONSTRAINT ""AK_People_AlternateKeyColumn"";");
+ AssertSql("""ALTER TABLE "People" DROP CONSTRAINT "AK_People_AlternateKeyColumn";""");
}
public override async Task Add_check_constraint_with_name()
{
await base.Add_check_constraint_with_name();
- AssertSql(
- @"ALTER TABLE ""People"" ADD CONSTRAINT ""CK_People_Foo"" CHECK (""DriverLicense"" > 0);");
+ AssertSql("""ALTER TABLE "People" ADD CONSTRAINT "CK_People_Foo" CHECK ("DriverLicense" > 0);""");
}
public override async Task Drop_check_constraint()
{
await base.Drop_check_constraint();
- AssertSql(
- @"ALTER TABLE ""People"" DROP CONSTRAINT ""CK_People_Foo"";");
+ AssertSql("""ALTER TABLE "People" DROP CONSTRAINT "CK_People_Foo";""");
}
#endregion
@@ -2625,10 +2557,7 @@ public override async Task Create_sequence()
{
await base.Create_sequence();
- AssertSql(
- """
-CREATE SEQUENCE "TestSequence" AS integer START WITH 1 INCREMENT BY 1 NO CYCLE;
-""");
+ AssertSql("""CREATE SEQUENCE "TestSequence" AS integer START WITH 1 INCREMENT BY 1 NO CYCLE;""");
}
public override async Task Create_sequence_all_settings()
@@ -2645,7 +2574,9 @@ IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'dbo2') THEN
END $EF$;
""",
//
- @"CREATE SEQUENCE dbo2.""TestSequence"" START WITH 3 INCREMENT BY 2 MINVALUE 2 MAXVALUE 916 CYCLE CACHE 20;");
+ """
+CREATE SEQUENCE dbo2."TestSequence" START WITH 3 INCREMENT BY 2 MINVALUE 2 MAXVALUE 916 CYCLE CACHE 20;
+""");
}
public override async Task Create_sequence_nocache()
@@ -2693,10 +2624,7 @@ await Test(
Assert.Equal("smallint", sequence.StoreType);
});
- AssertSql(
- """
-CREATE SEQUENCE "TestSequence" AS smallint START WITH 1 INCREMENT BY 1 NO CYCLE;
-""");
+ AssertSql("""CREATE SEQUENCE "TestSequence" AS smallint START WITH 1 INCREMENT BY 1 NO CYCLE;""");
}
[Fact]
@@ -2719,32 +2647,28 @@ public override async Task Alter_sequence_increment_by()
{
await base.Alter_sequence_increment_by();
- AssertSql(
- @"ALTER SEQUENCE foo INCREMENT BY 2 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;");
+ AssertSql("ALTER SEQUENCE foo INCREMENT BY 2 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;");
}
public override async Task Alter_sequence_default_cache_to_cache()
{
await base.Alter_sequence_default_cache_to_cache();
- AssertSql(
- """ALTER SEQUENCE "Delta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20;""");
+ AssertSql("""ALTER SEQUENCE "Delta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20;""");
}
public override async Task Alter_sequence_default_cache_to_nocache()
{
await base.Alter_sequence_default_cache_to_nocache();
- AssertSql(
- """ALTER SEQUENCE "Epsilon" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;""");
+ AssertSql("""ALTER SEQUENCE "Epsilon" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;""");
}
public override async Task Alter_sequence_cache_to_nocache()
{
await base.Alter_sequence_cache_to_nocache();
- AssertSql(
- """ALTER SEQUENCE "Zeta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;""");
+ AssertSql("""ALTER SEQUENCE "Zeta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;""");
}
public override async Task Alter_sequence_cache_to_default_cache()
@@ -2762,18 +2686,14 @@ await Test(
Assert.Null(sequence.CacheSize);
});
- AssertSql(
- """ALTER SEQUENCE "Eta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;""");
+ AssertSql("""ALTER SEQUENCE "Eta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;""");
}
public override async Task Alter_sequence_nocache_to_cache()
{
await base.Alter_sequence_nocache_to_cache();
- AssertSql(
- """
-ALTER SEQUENCE "Theta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20;
-""");
+ AssertSql("""ALTER SEQUENCE "Theta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20;""");
}
public override async Task Alter_sequence_nocache_to_default_cache()
@@ -2791,8 +2711,7 @@ await Test(
Assert.Null(sequence.CacheSize);
});
- AssertSql(
- """ALTER SEQUENCE "Iota" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;""");
+ AssertSql("""ALTER SEQUENCE "Iota" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;""");
}
public override async Task Alter_sequence_restart_with()
@@ -2810,16 +2729,14 @@ public override async Task Drop_sequence()
{
await base.Drop_sequence();
- AssertSql(
- @"DROP SEQUENCE ""TestSequence"";");
+ AssertSql("""DROP SEQUENCE "TestSequence";""");
}
public override async Task Rename_sequence()
{
await base.Rename_sequence();
- AssertSql(
- @"ALTER SEQUENCE ""TestSequence"" RENAME TO testsequence;");
+ AssertSql("""ALTER SEQUENCE "TestSequence" RENAME TO testsequence;""");
}
public override async Task Move_sequence()
@@ -2836,7 +2753,9 @@ IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'TestSequenceSchema') T
END $EF$;
""",
//
- @"ALTER SEQUENCE ""TestSequence"" SET SCHEMA ""TestSequenceSchema"";");
+ """
+ALTER SEQUENCE "TestSequence" SET SCHEMA "TestSequenceSchema";
+""");
}
#endregion
@@ -2993,7 +2912,6 @@ SELECT setval(
#endregion Data seeding
- [ConditionalFact]
public override async Task Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table()
{
await base.Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table_core("ARRAY[3, 2, 1]");
@@ -3016,8 +2934,7 @@ await Test(
Assert.Equal("public", citext.Schema);
});
- AssertSql(
- @"CREATE EXTENSION IF NOT EXISTS citext;");
+ AssertSql("CREATE EXTENSION IF NOT EXISTS citext;");
}
[Fact]
@@ -3065,8 +2982,7 @@ await Test(
l => Assert.Equal("Sad", l));
});
- AssertSql(
- @"CREATE TYPE ""Mood"" AS ENUM ('Happy', 'Sad');");
+ AssertSql("""CREATE TYPE "Mood" AS ENUM ('Happy', 'Sad');""");
}
[Fact]
@@ -3096,7 +3012,9 @@ IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'some_schema') THEN
END $EF$;
""",
//
- @"CREATE TYPE some_schema.""Mood"" AS ENUM ('Happy', 'Sad');");
+ """
+CREATE TYPE some_schema."Mood" AS ENUM ('Happy', 'Sad');
+""");
}
[Fact]
@@ -3107,8 +3025,7 @@ await Test(
_ => { },
model => Assert.Empty(model.GetPostgresEnums()));
- AssertSql(
- @"DROP TYPE ""Mood"";");
+ AssertSql("""DROP TYPE "Mood";""");
}
[Fact] // #979
@@ -3120,8 +3037,7 @@ await Test(
builder => builder.HasPostgresEnum("Enum2", ["X", "Y"]),
model => Assert.Equal(2, model.GetPostgresEnums().Count()));
- AssertSql(
- @"CREATE TYPE ""Enum2"" AS ENUM ('X', 'Y');");
+ AssertSql("""CREATE TYPE "Enum2" AS ENUM ('X', 'Y');""");
}
[Fact]
@@ -3140,8 +3056,7 @@ await Test(
l => Assert.Equal("Angry", l));
});
- AssertSql(
- @"ALTER TYPE ""Mood"" ADD VALUE 'Angry';");
+ AssertSql("""ALTER TYPE "Mood" ADD VALUE 'Angry';""");
}
[Fact]
@@ -3160,8 +3075,7 @@ await Test(
l => Assert.Equal("Sad", l));
});
- AssertSql(
- @"ALTER TYPE ""Mood"" ADD VALUE 'Angry' BEFORE 'Sad';");
+ AssertSql("""ALTER TYPE "Mood" ADD VALUE 'Angry' BEFORE 'Sad';""");
}
[Fact]
@@ -3240,8 +3154,7 @@ await Test(
_ => { },
model => Assert.Empty(PostgresCollation.GetCollations(model)));
- AssertSql(
- @"DROP COLLATION dummy;");
+ AssertSql("""DROP COLLATION dummy;""");
}
[Fact]
@@ -3273,8 +3186,7 @@ await Test(
Assert.Equal("tsvector", column.StoreType);
});
- AssertSql(
- @"ALTER TABLE ""Blogs"" ADD ""SearchColumn"" tsvector GENERATED ALWAYS AS (to_tsvector('english', ""TextColumn"")) STORED;");
+ AssertSql("""ALTER TABLE "Blogs" ADD "SearchColumn" tsvector GENERATED ALWAYS AS (to_tsvector('english', "TextColumn")) STORED;""");
}
[Fact]
@@ -3298,7 +3210,7 @@ await Test(
});
AssertSql(
- @"ALTER TABLE ""People"" ADD ""SearchColumn"" tsvector GENERATED ALWAYS AS (jsonb_to_tsvector('english', ""JsonbColumn"", '""all""')) STORED;");
+ """ALTER TABLE "People" ADD "SearchColumn" tsvector GENERATED ALWAYS AS (jsonb_to_tsvector('english', "JsonbColumn", '"all"')) STORED;""");
}
[Fact]
@@ -3329,7 +3241,7 @@ await Test(
});
AssertSql(
- @"ALTER TABLE ""People"" ADD ""SearchColumn"" tsvector GENERATED ALWAYS AS (to_tsvector('english', ""RequiredTextColumn"" || ' ' || coalesce(""OptionalTextColumn"", '')) || jsonb_to_tsvector('english', ""RequiredJsonbColumn"", '""all""') || json_to_tsvector('english', coalesce(""OptionalJsonColumn"", '{}'), '""all""')) STORED;");
+ """ALTER TABLE "People" ADD "SearchColumn" tsvector GENERATED ALWAYS AS (to_tsvector('english', "RequiredTextColumn" || ' ' || coalesce("OptionalTextColumn", '')) || jsonb_to_tsvector('english', "RequiredJsonbColumn", '"all"') || json_to_tsvector('english', coalesce("OptionalJsonColumn", '{}'), '"all"')) STORED;""");
}
[Fact]
@@ -3362,13 +3274,138 @@ await Test(
});
AssertSql(
- @"ALTER TABLE ""Blogs"" DROP COLUMN ""TsVector"";",
+ """ALTER TABLE "Blogs" DROP COLUMN "TsVector";""",
//
- @"ALTER TABLE ""Blogs"" ADD ""TsVector"" tsvector GENERATED ALWAYS AS (to_tsvector('english', ""Title"" || ' ' || coalesce(""Description"", ''))) STORED;");
+ """ALTER TABLE "Blogs" ADD "TsVector" tsvector GENERATED ALWAYS AS (to_tsvector('english', "Title" || ' ' || coalesce("Description", ''))) STORED;""");
}
#endregion PostgreSQL full-text search
+ public override async Task Add_required_primitive_collection_to_existing_table()
+ {
+ await base.Add_required_primitive_collection_to_existing_table();
+
+ AssertSql("""ALTER TABLE "Customers" ADD "Numbers" integer[] NOT NULL;""");
+ }
+
+ public override async Task Add_required_primitive_collection_with_custom_default_value_to_existing_table()
+ {
+ await base.Add_required_primitive_collection_with_custom_default_value_to_existing_table();
+
+ AssertSql("""ALTER TABLE "Customers" ADD "Numbers" integer[] NOT NULL DEFAULT ARRAY[1,2,3]::integer[];""");
+ }
+
+ public override async Task Add_required_primitive_collection_with_custom_default_value_sql_to_existing_table()
+ {
+ await base.Add_required_primitive_collection_with_custom_default_value_sql_to_existing_table_core("ARRAY[1,2,3]");
+
+ AssertSql("""ALTER TABLE "Customers" ADD "Numbers" integer[] NOT NULL DEFAULT (ARRAY[1,2,3]);""");
+ }
+
+ [ConditionalFact(Skip = "issue #33038")]
+ public override async Task Add_required_primitive_collection_with_custom_converter_to_existing_table()
+ {
+ await base.Add_required_primitive_collection_with_custom_converter_to_existing_table();
+
+ AssertSql(
+ """
+ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'nothing';
+""");
+ }
+
+ public override async Task Add_required_primitive_collection_with_custom_converter_and_custom_default_value_to_existing_table()
+ {
+ await base.Add_required_primitive_collection_with_custom_converter_and_custom_default_value_to_existing_table();
+
+ AssertSql("""ALTER TABLE "Customers" ADD "Numbers" text NOT NULL DEFAULT 'some numbers';""");
+ }
+
+ public override async Task Add_optional_primitive_collection_to_existing_table()
+ {
+ await base.Add_optional_primitive_collection_to_existing_table();
+
+ AssertSql("""ALTER TABLE "Customers" ADD "Numbers" integer[];""");
+ }
+
+ public override async Task Create_table_with_required_primitive_collection()
+ {
+ await base.Create_table_with_required_primitive_collection();
+
+ AssertSql(
+ """
+CREATE TABLE "Customers" (
+ "Id" integer GENERATED BY DEFAULT AS IDENTITY,
+ "Name" text,
+ "Numbers" integer[] NOT NULL,
+ CONSTRAINT "PK_Customers" PRIMARY KEY ("Id")
+);
+""");
+ }
+
+ public override async Task Create_table_with_optional_primitive_collection()
+ {
+ await base.Create_table_with_optional_primitive_collection();
+
+ AssertSql(
+ """
+CREATE TABLE "Customers" (
+ "Id" integer GENERATED BY DEFAULT AS IDENTITY,
+ "Name" text,
+ "Numbers" integer[],
+ CONSTRAINT "PK_Customers" PRIMARY KEY ("Id")
+);
+""");
+ }
+
+ public override async Task Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH()
+ {
+ await base.Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH();
+
+ AssertSql(
+ """
+CREATE TABLE "Contacts" (
+ "Id" integer GENERATED BY DEFAULT AS IDENTITY,
+ "Discriminator" character varying(8) NOT NULL,
+ "Name" text,
+ "Number" integer,
+ "MyComplex_Prop" text,
+ "MyComplex_MyNestedComplex_Bar" timestamp with time zone,
+ "MyComplex_MyNestedComplex_Foo" integer,
+ CONSTRAINT "PK_Contacts" PRIMARY KEY ("Id")
+);
+""");
+ }
+
+ public override async Task Add_required_primitve_collection_to_existing_table()
+ {
+ await base.Add_required_primitve_collection_to_existing_table();
+
+ AssertSql("""ALTER TABLE "Customers" ADD "Numbers" integer[] NOT NULL;""");
+ }
+
+ public override async Task Add_required_primitve_collection_with_custom_default_value_to_existing_table()
+ {
+ await base.Add_required_primitve_collection_with_custom_default_value_to_existing_table();
+
+ AssertSql("""ALTER TABLE "Customers" ADD "Numbers" integer[] NOT NULL DEFAULT ARRAY[1,2,3]::integer[];""");
+ }
+
+ [ConditionalFact(Skip = "issue #33038")]
+ public override async Task Add_required_primitve_collection_with_custom_converter_to_existing_table()
+ {
+ await base.Add_required_primitve_collection_with_custom_converter_to_existing_table();
+
+ AssertSql("ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'nothing';");
+ }
+
+ public override async Task Add_required_primitve_collection_with_custom_converter_and_custom_default_value_to_existing_table()
+ {
+ await base.Add_required_primitve_collection_with_custom_converter_and_custom_default_value_to_existing_table();
+
+ AssertSql("""ALTER TABLE "Customers" ADD "Numbers" text NOT NULL DEFAULT 'some numbers';""");
+ }
+
+
protected override string NonDefaultCollation
=> "POSIX";
diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs
index dd80b867a..0a2e11cce 100644
--- a/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs
+++ b/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs
@@ -16,4 +16,9 @@ public override Task Query_generates_correct_datetime2_parameter_definition(int?
public override Task Query_generates_correct_datetimeoffset_parameter_definition(int? fractionalSeconds, string postfix)
=> Assert.ThrowsAsync(
() => base.Query_generates_correct_datetime2_parameter_definition(fractionalSeconds, postfix));
+
+ // Cannot write DateTimeOffset with Offset=10:00:00 to PostgreSQL type 'timestamp with time zone', only offset 0 (UTC) is supported.
+ public override Task Projecting_one_of_two_similar_complex_types_picks_the_correct_one()
+ => Assert.ThrowsAsync(
+ () => base.Projecting_one_of_two_similar_complex_types_picks_the_correct_one());
}
diff --git a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs
index 6cedca28b..9ca70651d 100644
--- a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs
+++ b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs
@@ -1023,6 +1023,29 @@ public override async Task Same_complex_type_projected_twice_with_pushdown_as_pa
AssertSql("");
}
+ public override async Task Entity_with_complex_type_with_group_by_and_first(bool async)
+ {
+ await base.Entity_with_complex_type_with_group_by_and_first(async);
+
+ AssertSql(
+ """
+SELECT c3."Id", c3."Name", c3."BillingAddress_AddressLine1", c3."BillingAddress_AddressLine2", c3."BillingAddress_Tags", c3."BillingAddress_ZipCode", c3."BillingAddress_Country_Code", c3."BillingAddress_Country_FullName", c3."ShippingAddress_AddressLine1", c3."ShippingAddress_AddressLine2", c3."ShippingAddress_Tags", c3."ShippingAddress_ZipCode", c3."ShippingAddress_Country_Code", c3."ShippingAddress_Country_FullName"
+FROM (
+ SELECT c."Id"
+ FROM "Customer" AS c
+ GROUP BY c."Id"
+) AS c1
+LEFT JOIN (
+ SELECT c2."Id", c2."Name", c2."BillingAddress_AddressLine1", c2."BillingAddress_AddressLine2", c2."BillingAddress_Tags", c2."BillingAddress_ZipCode", c2."BillingAddress_Country_Code", c2."BillingAddress_Country_FullName", c2."ShippingAddress_AddressLine1", c2."ShippingAddress_AddressLine2", c2."ShippingAddress_Tags", c2."ShippingAddress_ZipCode", c2."ShippingAddress_Country_Code", c2."ShippingAddress_Country_FullName"
+ FROM (
+ SELECT c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_Tags", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName", ROW_NUMBER() OVER(PARTITION BY c0."Id" ORDER BY c0."Id" NULLS FIRST) AS row
+ FROM "Customer" AS c0
+ ) AS c2
+ WHERE c2.row <= 1
+) AS c3 ON c1."Id" = c3."Id"
+""");
+ }
+
[ConditionalFact]
public virtual void Check_all_tests_overridden()
=> TestHelpers.AssertAllMethodsOverridden(GetType());
diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs
index 84b0e0a29..c6acdf2f5 100644
--- a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs
+++ b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs
@@ -4,10 +4,12 @@
namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities;
+#nullable enable
+
public class NpgsqlTestStore : RelationalTestStore
{
- private readonly string _scriptPath;
- private readonly string _additionalSql;
+ private readonly string? _scriptPath;
+ private readonly string? _additionalSql;
private const string Northwind = "Northwind";
@@ -17,55 +19,53 @@ public class NpgsqlTestStore : RelationalTestStore
public static NpgsqlTestStore GetNorthwindStore()
=> (NpgsqlTestStore)NpgsqlNorthwindTestStoreFactory.Instance
- .GetOrCreate(NpgsqlNorthwindTestStoreFactory.Name).Initialize(null, (Func)null);
+ .GetOrCreate(NpgsqlNorthwindTestStoreFactory.Name).Initialize(null, (Func?)null);
// ReSharper disable once UnusedMember.Global
public static NpgsqlTestStore GetOrCreateInitialized(string name)
- => new NpgsqlTestStore(name).InitializeNpgsql(null, (Func)null, null);
+ => new NpgsqlTestStore(name).InitializeNpgsql(null, (Func?)null, null);
public static NpgsqlTestStore GetOrCreate(
string name,
- string scriptPath = null,
- string additionalSql = null,
- string connectionStringOptions = null)
+ string? scriptPath = null,
+ string? additionalSql = null,
+ string? connectionStringOptions = null)
=> new(name, scriptPath, additionalSql, connectionStringOptions);
- public static NpgsqlTestStore Create(string name, string connectionStringOptions = null)
+ public static NpgsqlTestStore Create(string name, string? connectionStringOptions = null)
=> new(name, connectionStringOptions: connectionStringOptions, shared: false);
public static NpgsqlTestStore CreateInitialized(string name)
=> new NpgsqlTestStore(name, shared: false)
- .InitializeNpgsql(null, (Func)null, null);
+ .InitializeNpgsql(null, (Func?)null, null);
private NpgsqlTestStore(
string name,
- string scriptPath = null,
- string additionalSql = null,
- string connectionStringOptions = null,
+ string? scriptPath = null,
+ string? additionalSql = null,
+ string? connectionStringOptions = null,
bool shared = true)
- : base(name, shared)
+ : base(name, shared, CreateConnection(name, connectionStringOptions))
{
Name = name;
if (scriptPath is not null)
{
// ReSharper disable once AssignNullToNotNullAttribute
- _scriptPath = Path.Combine(Path.GetDirectoryName(typeof(NpgsqlTestStore).GetTypeInfo().Assembly.Location), scriptPath);
+ _scriptPath = Path.Combine(Path.GetDirectoryName(typeof(NpgsqlTestStore).GetTypeInfo().Assembly.Location)!, scriptPath);
}
_additionalSql = additionalSql;
-
- // ReSharper disable VirtualMemberCallInConstructor
- ConnectionString = CreateConnectionString(Name, connectionStringOptions);
- Connection = new NpgsqlConnection(ConnectionString);
- // ReSharper restore VirtualMemberCallInConstructor
}
+ private static NpgsqlConnection CreateConnection(string name, string? connectionStringOptions)
+ => new(CreateConnectionString(name, connectionStringOptions));
+
// ReSharper disable once MemberCanBePrivate.Global
public NpgsqlTestStore InitializeNpgsql(
- IServiceProvider serviceProvider,
- Func createContext,
- Action seed)
+ IServiceProvider? serviceProvider,
+ Func? createContext,
+ Action? seed)
=> (NpgsqlTestStore)Initialize(serviceProvider, createContext, seed);
// ReSharper disable once UnusedMember.Global
@@ -75,7 +75,7 @@ public NpgsqlTestStore InitializeNpgsql(
Action seed)
=> InitializeNpgsql(serviceProvider, () => createContext(this), seed);
- protected override void Initialize(Func createContext, Action seed, Action clean)
+ protected override void Initialize(Func createContext, Action? seed, Action? clean)
{
if (CreateDatabase(clean))
{
@@ -123,7 +123,7 @@ private static string GetScratchDbName()
return name;
}
- private bool CreateDatabase(Action clean)
+ private bool CreateDatabase(Action? clean)
{
using (var master = new NpgsqlConnection(CreateAdminConnectionString()))
{
@@ -273,34 +273,34 @@ public T ExecuteScalar(string sql, params object[] parameters)
=> ExecuteScalar(Connection, sql, parameters);
private static T ExecuteScalar(DbConnection connection, string sql, params object[] parameters)
- => Execute(connection, command => (T)command.ExecuteScalar(), sql, false, parameters);
+ => Execute(connection, command => (T)command.ExecuteScalar()!, sql, false, parameters);
// ReSharper disable once UnusedMember.Global
public Task ExecuteScalarAsync(string sql, params object[] parameters)
=> ExecuteScalarAsync(Connection, sql, parameters);
- private static Task ExecuteScalarAsync(DbConnection connection, string sql, object[] parameters = null)
- => ExecuteAsync(connection, async command => (T)await command.ExecuteScalarAsync(), sql, false, parameters);
+ private static Task ExecuteScalarAsync(DbConnection connection, string sql, object[]? parameters = null)
+ => ExecuteAsync(connection, async command => (T)(await command.ExecuteScalarAsync())!, sql, false, parameters);
// ReSharper disable once UnusedMethodReturnValue.Global
public int ExecuteNonQuery(string sql, params object[] parameters)
=> ExecuteNonQuery(Connection, sql, parameters);
- private static int ExecuteNonQuery(DbConnection connection, string sql, object[] parameters = null)
+ private static int ExecuteNonQuery(DbConnection connection, string sql, object[]? parameters = null)
=> Execute(connection, command => command.ExecuteNonQuery(), sql, false, parameters);
// ReSharper disable once UnusedMember.Global
public Task ExecuteNonQueryAsync(string sql, params object[] parameters)
=> ExecuteNonQueryAsync(Connection, sql, parameters);
- private static Task ExecuteNonQueryAsync(DbConnection connection, string sql, object[] parameters = null)
+ private static Task ExecuteNonQueryAsync(DbConnection connection, string sql, object[]? parameters = null)
=> ExecuteAsync(connection, command => command.ExecuteNonQueryAsync(), sql, false, parameters);
// ReSharper disable once UnusedMember.Global
public IEnumerable Query(string sql, params object[] parameters)
=> Query(Connection, sql, parameters);
- private static IEnumerable Query(DbConnection connection, string sql, object[] parameters = null)
+ private static IEnumerable Query(DbConnection connection, string sql, object[]? parameters = null)
=> Execute(
connection, command =>
{
@@ -320,7 +320,7 @@ private static IEnumerable Query(DbConnection connection, string sql, obje
public Task> QueryAsync(string sql, params object[] parameters)
=> QueryAsync(Connection, sql, parameters);
- private static Task> QueryAsync(DbConnection connection, string sql, object[] parameters = null)
+ private static Task> QueryAsync(DbConnection connection, string sql, object[]? parameters = null)
=> ExecuteAsync(
connection, async command =>
{
@@ -341,7 +341,7 @@ private static T Execute(
Func execute,
string sql,
bool useTransaction = false,
- object[] parameters = null)
+ object[]? parameters = null)
=> ExecuteCommand(connection, execute, sql, useTransaction, parameters);
private static T ExecuteCommand(
@@ -349,7 +349,7 @@ private static T ExecuteCommand(
Func execute,
string sql,
bool useTransaction,
- object[] parameters)
+ object[]? parameters)
{
if (connection.State != ConnectionState.Closed)
{
@@ -388,7 +388,7 @@ private static Task ExecuteAsync(
Func> executeAsync,
string sql,
bool useTransaction = false,
- IReadOnlyList