Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<EFCoreVersion>11.0.0-preview.7.26324.112</EFCoreVersion>
<MicrosoftExtensionsVersion>11.0.0-preview.7.26324.112</MicrosoftExtensionsVersion>
<MicrosoftExtensionsConfigurationVersion>11.0.0-preview.7.26324.112</MicrosoftExtensionsConfigurationVersion>
<EFCoreVersion>11.0.0-preview.7.26381.103</EFCoreVersion>
<MicrosoftExtensionsVersion>11.0.0-preview.7.26381.103</MicrosoftExtensionsVersion>
<MicrosoftExtensionsConfigurationVersion>11.0.0-preview.7.26381.103</MicrosoftExtensionsConfigurationVersion>
<NpgsqlVersion>10.0.3</NpgsqlVersion>
<XunitVersion>4.0.0-pre.154</XunitVersion>
</PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "11.0.100-preview.6.26359.118",
"version": "11.0.100-preview.7.26381.103",
"allowPrerelease": true
},
"test": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public NpgsqlMethodCallTranslatorProvider(
new NpgsqlNetworkTranslator(typeMappingSource, sqlExpressionFactory, model),
new NpgsqlGuidTranslator(sqlExpressionFactory, npgsqlOptions.PostgresVersion),
new NpgsqlObjectToStringTranslator(typeMappingSource, sqlExpressionFactory),
new NpgsqlParseTranslator(sqlExpressionFactory),
new NpgsqlRandomTranslator(sqlExpressionFactory),
new NpgsqlRangeTranslator(typeMappingSource, sqlExpressionFactory, model, supportsMultiranges),
new NpgsqlRegexTranslator(typeMappingSource, sqlExpressionFactory, supportRegexCount),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal;

/// <summary>
/// Translates single-argument numeric <c>Parse</c> methods (e.g. <see cref="int.Parse(string)" />) into PostgreSQL CAST
/// expressions.
/// </summary>
public class NpgsqlParseTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMethodCallTranslator
{
private static readonly Type[] SupportedClrTypes =
[
typeof(bool), // boolean
typeof(byte), // smallint
typeof(decimal), // numeric
typeof(double), // double precision
typeof(float), // real
typeof(short), // smallint
typeof(int), // integer
typeof(long) // bigint
];

private static readonly MethodInfo[] SupportedMethods
= SupportedClrTypes
.SelectMany(
t => t.GetTypeInfo().GetDeclaredMethods(nameof(int.Parse))
.Where(
m => m.GetParameters().Length == 1
&& m.GetParameters().First().ParameterType == typeof(string)))
.ToArray();

/// <summary>
/// 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.
/// </summary>
public virtual SqlExpression? Translate(
SqlExpression? instance,
MethodInfo method,
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
=> SupportedMethods.Contains(method)
? sqlExpressionFactory.Convert(arguments[0], method.ReturnType)
: null;
}
18 changes: 18 additions & 0 deletions src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@ public override RelationalTypeMapping ElementTypeMapping
return (RelationalTypeMapping)elementTypeMapping;
}
}

/// <summary>
/// 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.
/// </summary>
public override object? GetDefaultProviderValue()
{
// The base implementation assumes any mapping with an element type mapping and a JsonValueReaderWriter stores its
// collection as a JSON string in the column, and returns "[]"; PostgreSQL arrays are natively-typed columns, so
// return an actual collection default instead (see https://github.com/dotnet/efcore/issues/38796).
var providerType = (Converter?.ProviderClrType ?? ClrType).UnwrapNullableType();

return providerType.IsArray
? Array.CreateInstance(providerType.GetElementType()!, 0)
: providerType.GetDefaultValue();
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,71 @@ protected override Task Seed2951(Context2951 context)
INSERT INTO "ZeroKey" VALUES (NULL)
""");

protected override async Task Seed30915(Context30915 context)
{
context.Statuses.AddRange(
new Context30915.PickupStatus30915 { PickupStatusId = 1, Name = "Active" },
new Context30915.PickupStatus30915 { PickupStatusId = 2, Name = "NoRequests" },
new Context30915.PickupStatus30915 { PickupStatusId = 3, Name = "Busy" });

context.Requests.AddRange(
new Context30915.PickupRequest30915 { PickupStatusId = 1, Priority = 5 },
new Context30915.PickupRequest30915 { PickupStatusId = 1, Priority = null },
new Context30915.PickupRequest30915 { PickupStatusId = 3, Priority = 7 });

await context.SaveChangesAsync();
}

// PROVIDER DIVERGENCE: PostgreSQL supports LATERAL, so unlike SQLite (which throws because it
// has no APPLY) the correlated whole-object DefaultIfEmpty actually translates and materializes
// correctly here. Override the base (SQLite-shaped) assert-throws with the real-results assertion.
public override async Task Correlated_SelectMany_DefaultIfEmpty_whole_object()
{
var contextFactory = await InitializeNonSharedTest<Context30915>(seed: Seed30915);
using var context = contextFactory.CreateDbContext();

var query = from s in context.Statuses
from countInfo in context.Requests
.Where(r => r.PickupStatusId == s.PickupStatusId)
.GroupBy(r => r.PickupStatusId, (k, els) => new { pickupStatusId = k, Count = els.Count() })
.DefaultIfEmpty()
orderby s.PickupStatusId
select new { s.PickupStatusId, countInfo };

var result = await query.ToListAsync();

Assert.Equal(3, result.Count);

// status 1 -> matched, Count 2
Assert.Equal(1, result[0].PickupStatusId);
Assert.NotNull(result[0].countInfo);
Assert.Equal(1, result[0].countInfo.pickupStatusId);
Assert.Equal(2, result[0].countInfo.Count);

// status 2 -> no match: whole non-entity object is null
Assert.Equal(2, result[1].PickupStatusId);
Assert.Null(result[1].countInfo);

// status 3 -> matched, Count 1
Assert.Equal(3, result[2].PickupStatusId);
Assert.NotNull(result[2].countInfo);
Assert.Equal(3, result[2].countInfo.pickupStatusId);
Assert.Equal(1, result[2].countInfo.Count);

AssertSql(
"""
SELECT s."PickupStatusId", r0."pickupStatusId", r0."Count", r0.marker
FROM "Statuses" AS s
LEFT JOIN LATERAL (
SELECT r."PickupStatusId" AS "pickupStatusId", count(*)::int AS "Count", 1 AS marker
FROM "Requests" AS r
WHERE r."PickupStatusId" = s."PickupStatusId"
GROUP BY r."PickupStatusId"
) AS r0 ON TRUE
ORDER BY s."PickupStatusId" NULLS FIRST
""");
}

// Writes DateTime with Kind=Unspecified to timestamptz
public override Task SelectMany_where_Select(bool async)
=> Task.CompletedTask;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,28 @@ public class AdHocPrecompiledQueryNpgsqlTest(NonSharedFixture fixture, ITestOutp
protected override bool AlwaysPrintGeneratedSources
=> false;

public override async Task Invalid_identifier_json_property_name()
{
await base.Invalid_identifier_json_property_name();

AssertSql(
"""
SELECT e."Id", e."Nested"
FROM "Entities" AS e
""");
}

public override async Task Invalid_identifier_shadow_property_name()
{
await base.Invalid_identifier_shadow_property_name();

AssertSql(
"""
SELECT e."Id", e."NOT VALID !!!1"
FROM "Entities" AS e
""");
}

public override async Task Index_no_evaluatability()
{
await base.Index_no_evaluatability();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,39 @@ namespace Microsoft.EntityFrameworkCore.Query.Associations.ComplexJson;
public class ComplexJsonProjectionNpgsqlTest(ComplexJsonNpgsqlFixture fixture, ITestOutputHelper testOutputHelper)
: ComplexJsonProjectionRelationalTestBase<ComplexJsonNpgsqlFixture>(fixture, testOutputHelper)
{
public override async Task Select_required_associate_duplicated(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_required_associate_duplicated(queryTrackingBehavior);

AssertSql(
"""
SELECT r."RequiredAssociate", r."RequiredAssociate"
FROM "RootEntity" AS r
""");
}

public override async Task Select_required_associate_and_optional_associate(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_required_associate_and_optional_associate(queryTrackingBehavior);

AssertSql(
"""
SELECT r."RequiredAssociate", r."OptionalAssociate"
FROM "RootEntity" AS r
""");
}

public override async Task Select_optional_associate_and_ints(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_optional_associate_and_ints(queryTrackingBehavior);

AssertSql(
"""
SELECT r."OptionalAssociate", r."RequiredAssociate" -> 'Ints' AS "Ints"
FROM "RootEntity" AS r
""");
}

public override async Task Select_root(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_root(queryTrackingBehavior);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,39 @@ public class ComplexTableSplittingProjectionNpgsqlTest(
ITestOutputHelper testOutputHelper)
: ComplexTableSplittingProjectionRelationalTestBase<ComplexTableSplittingNpgsqlFixture>(fixture, testOutputHelper)
{
public override async Task Select_required_associate_duplicated(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_required_associate_duplicated(queryTrackingBehavior);

AssertSql(
"""
SELECT r."RequiredAssociate_Id", r."RequiredAssociate_Int", r."RequiredAssociate_Ints", r."RequiredAssociate_Name", r."RequiredAssociate_String", r."RequiredAssociate_OptionalNestedAssociate_Id", r."RequiredAssociate_OptionalNestedAssociate_Int", r."RequiredAssociate_OptionalNestedAssociate_Ints", r."RequiredAssociate_OptionalNestedAssociate_Name", r."RequiredAssociate_OptionalNestedAssociate_String", r."RequiredAssociate_RequiredNestedAssociate_Id", r."RequiredAssociate_RequiredNestedAssociate_Int", r."RequiredAssociate_RequiredNestedAssociate_Ints", r."RequiredAssociate_RequiredNestedAssociate_Name", r."RequiredAssociate_RequiredNestedAssociate_String"
FROM "RootEntity" AS r
""");
}

public override async Task Select_required_associate_and_optional_associate(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_required_associate_and_optional_associate(queryTrackingBehavior);

AssertSql(
"""
SELECT r."RequiredAssociate_Id", r."RequiredAssociate_Int", r."RequiredAssociate_Ints", r."RequiredAssociate_Name", r."RequiredAssociate_String", r."RequiredAssociate_OptionalNestedAssociate_Id", r."RequiredAssociate_OptionalNestedAssociate_Int", r."RequiredAssociate_OptionalNestedAssociate_Ints", r."RequiredAssociate_OptionalNestedAssociate_Name", r."RequiredAssociate_OptionalNestedAssociate_String", r."RequiredAssociate_RequiredNestedAssociate_Id", r."RequiredAssociate_RequiredNestedAssociate_Int", r."RequiredAssociate_RequiredNestedAssociate_Ints", r."RequiredAssociate_RequiredNestedAssociate_Name", r."RequiredAssociate_RequiredNestedAssociate_String", r."OptionalAssociate_Id", r."OptionalAssociate_Int", r."OptionalAssociate_Ints", r."OptionalAssociate_Name", r."OptionalAssociate_String", r."OptionalAssociate_OptionalNestedAssociate_Id", r."OptionalAssociate_OptionalNestedAssociate_Int", r."OptionalAssociate_OptionalNestedAssociate_Ints", r."OptionalAssociate_OptionalNestedAssociate_Name", r."OptionalAssociate_OptionalNestedAssociate_String", r."OptionalAssociate_RequiredNestedAssociate_Id", r."OptionalAssociate_RequiredNestedAssociate_Int", r."OptionalAssociate_RequiredNestedAssociate_Ints", r."OptionalAssociate_RequiredNestedAssociate_Name", r."OptionalAssociate_RequiredNestedAssociate_String"
FROM "RootEntity" AS r
""");
}

public override async Task Select_optional_associate_and_ints(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_optional_associate_and_ints(queryTrackingBehavior);

AssertSql(
"""
SELECT r."OptionalAssociate_Id", r."OptionalAssociate_Int", r."OptionalAssociate_Ints", r."OptionalAssociate_Name", r."OptionalAssociate_String", r."OptionalAssociate_OptionalNestedAssociate_Id", r."OptionalAssociate_OptionalNestedAssociate_Int", r."OptionalAssociate_OptionalNestedAssociate_Ints", r."OptionalAssociate_OptionalNestedAssociate_Name", r."OptionalAssociate_OptionalNestedAssociate_String", r."OptionalAssociate_RequiredNestedAssociate_Id", r."OptionalAssociate_RequiredNestedAssociate_Int", r."OptionalAssociate_RequiredNestedAssociate_Ints", r."OptionalAssociate_RequiredNestedAssociate_Name", r."OptionalAssociate_RequiredNestedAssociate_String", r."RequiredAssociate_Ints" AS "Ints"
FROM "RootEntity" AS r
""");
}

public override async Task Select_root(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_root(queryTrackingBehavior);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,60 @@ namespace Microsoft.EntityFrameworkCore.Query.Associations.Navigations;
public class NavigationsProjectionNpgsqlTest(NavigationsNpgsqlFixture fixture, ITestOutputHelper testOutputHelper)
: NavigationsProjectionRelationalTestBase<NavigationsNpgsqlFixture>(fixture, testOutputHelper)
{
public override async Task Select_required_associate_duplicated(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_required_associate_duplicated(queryTrackingBehavior);

AssertSql(
"""
SELECT a."Id", a."CollectionRootId", a."Int", a."Ints", a."Name", a."OptionalNestedAssociateId", a."RequiredNestedAssociateId", a."String", r."Id", n1."Id", n1."CollectionAssociateId", n1."Int", n1."Ints", n1."Name", n1."String", n."Id", n."CollectionAssociateId", n."Int", n."Ints", n."Name", n."String", n0."Id", n0."CollectionAssociateId", n0."Int", n0."Ints", n0."Name", n0."String", n2."Id", n2."CollectionAssociateId", n2."Int", n2."Ints", n2."Name", n2."String"
FROM "RootEntity" AS r
INNER JOIN "AssociateType" AS a ON r."RequiredAssociateId" = a."Id"
LEFT JOIN "NestedAssociateType" AS n ON a."OptionalNestedAssociateId" = n."Id"
INNER JOIN "NestedAssociateType" AS n0 ON a."RequiredNestedAssociateId" = n0."Id"
LEFT JOIN "NestedAssociateType" AS n1 ON a."Id" = n1."CollectionAssociateId"
LEFT JOIN "NestedAssociateType" AS n2 ON a."Id" = n2."CollectionAssociateId"
ORDER BY r."Id" NULLS FIRST, n1."Id" NULLS FIRST
""");
}

public override async Task Select_required_associate_and_optional_associate(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_required_associate_and_optional_associate(queryTrackingBehavior);

AssertSql(
"""
SELECT a."Id", a."CollectionRootId", a."Int", a."Ints", a."Name", a."OptionalNestedAssociateId", a."RequiredNestedAssociateId", a."String", r."Id", n3."Id", n3."CollectionAssociateId", n3."Int", n3."Ints", n3."Name", n3."String", n."Id", n."CollectionAssociateId", n."Int", n."Ints", n."Name", n."String", n0."Id", n0."CollectionAssociateId", n0."Int", n0."Ints", n0."Name", n0."String", a0."Id", a0."CollectionRootId", a0."Int", a0."Ints", a0."Name", a0."OptionalNestedAssociateId", a0."RequiredNestedAssociateId", a0."String", n4."Id", n4."CollectionAssociateId", n4."Int", n4."Ints", n4."Name", n4."String", n1."Id", n1."CollectionAssociateId", n1."Int", n1."Ints", n1."Name", n1."String", n2."Id", n2."CollectionAssociateId", n2."Int", n2."Ints", n2."Name", n2."String"
FROM "RootEntity" AS r
INNER JOIN "AssociateType" AS a ON r."RequiredAssociateId" = a."Id"
LEFT JOIN "AssociateType" AS a0 ON r."OptionalAssociateId" = a0."Id"
LEFT JOIN "NestedAssociateType" AS n ON a."OptionalNestedAssociateId" = n."Id"
INNER JOIN "NestedAssociateType" AS n0 ON a."RequiredNestedAssociateId" = n0."Id"
LEFT JOIN "NestedAssociateType" AS n1 ON a0."OptionalNestedAssociateId" = n1."Id"
LEFT JOIN "NestedAssociateType" AS n2 ON a0."RequiredNestedAssociateId" = n2."Id"
LEFT JOIN "NestedAssociateType" AS n3 ON a."Id" = n3."CollectionAssociateId"
LEFT JOIN "NestedAssociateType" AS n4 ON a0."Id" = n4."CollectionAssociateId"
ORDER BY r."Id" NULLS FIRST, n3."Id" NULLS FIRST
""");
}

public override async Task Select_optional_associate_and_ints(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_optional_associate_and_ints(queryTrackingBehavior);

AssertSql(
"""
SELECT a."Id", a."CollectionRootId", a."Int", a."Ints", a."Name", a."OptionalNestedAssociateId", a."RequiredNestedAssociateId", a."String", r."Id", n1."Id", n1."CollectionAssociateId", n1."Int", n1."Ints", n1."Name", n1."String", n."Id", n."CollectionAssociateId", n."Int", n."Ints", n."Name", n."String", n0."Id", n0."CollectionAssociateId", n0."Int", n0."Ints", n0."Name", n0."String", a0."Ints"
FROM "RootEntity" AS r
LEFT JOIN "AssociateType" AS a ON r."OptionalAssociateId" = a."Id"
INNER JOIN "AssociateType" AS a0 ON r."RequiredAssociateId" = a0."Id"
LEFT JOIN "NestedAssociateType" AS n ON a."OptionalNestedAssociateId" = n."Id"
LEFT JOIN "NestedAssociateType" AS n0 ON a."RequiredNestedAssociateId" = n0."Id"
LEFT JOIN "NestedAssociateType" AS n1 ON a."Id" = n1."CollectionAssociateId"
ORDER BY r."Id" NULLS FIRST
""");
}

public override async Task Select_root(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_root(queryTrackingBehavior);
Expand Down
Loading