Skip to content
Merged
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
2 changes: 0 additions & 2 deletions src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -871,8 +871,6 @@ NpgsqlMultirangeTypeMapping multirangeTypeMapping
// (e.g. IP address containment)
containerMapping = _typeMappingSource.FindContainerMapping(container.Type, containeeMapping, Dependencies.Model);

// containerMapping = _typeMappingSource.FindContainerMapping(container.Type, containeeMapping);

// Apply the inferred mapping to the container, or fall back to the default type mapping
if (containerMapping is not null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ private static RelationalTypeMappingParameters CreateParameters(string storeType
if (elementJsonReaderWriter is not null && elementJsonReaderWriter.ValueType != typeof(TElement).UnwrapNullableType())
{
throw new InvalidOperationException(
$"When '{elementJsonReaderWriter.ValueType}', '{typeof(TElement).UnwrapNullableType()}' building an array mapping, the JsonValueReaderWriter for element mapping '{elementMapping.GetType().Name}' is incorrect ('{elementMapping.JsonValueReaderWriter?.GetType().Name ?? "<null>"}').");
$"When building an array mapping over '{typeof(TElement).Name}', the JsonValueReaderWriter for element mapping '{elementMapping.GetType().Name}' is incorrect ('{elementMapping.JsonValueReaderWriter?.GetType().Name ?? "<null>"}' instead of '{typeof(TElement).UnwrapNullableType()}').");
}

// If there's no JsonValueReaderWriter on the element, we also don't set one on its array (this is for rare edge cases such as
Expand Down
20 changes: 20 additions & 0 deletions src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,26 @@ static Type FindTypeToInstantiate(Type collectionType, Type elementType)
return rangeStoreType is null ? null : FindMapping(containerClrType, rangeStoreType);
}

// If this containment of a range within a multirange, just flow down to the general collection mapping logic; multiranges are
// handled just like normal collections over ranges (since they can be unnested).
// However, we also support containment of the base type (e.g. int) directly in its multirange (e.g. int4range). A multirange
// is *not* a collection over the base type, so handle that specific case here.
if (containerClrType.IsMultirange() && !containeeTypeMapping.ClrType.IsRange())
{
var multirangeStoreType = containeeTypeMapping.StoreType switch
{
"int" or "integer" => "int4multirange",
"bigint" => "int8multirange",
"decimal" or "numeric" => "nummultirange",
"date" => "datemultirange",
"timestamp" or "timestamp without time zone" => "tsmultirange",
"timestamptz" or "timestamp with time zone" => "tstzmultirange",
_ => null
};

return !_supportsMultiranges || multirangeStoreType is null ? null : FindMapping(containerClrType, multirangeStoreType);
}

// Then, try to find the mapping with the containee mapping as the element type mapping.
// This is the standard EF lookup mechanism, and takes care of regular arrays and multiranges, which are supported as full primitive
// collections.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,32 @@ public override async Task Parameter_collection_null_Contains(bool async)
""");
}

[ConditionalTheory] // #3012
[MinimumPostgresVersion(14, 0)] // Multiranges were introduced in PostgreSQL 14
[MemberData(nameof(IsAsyncData))]
public virtual async Task Parameter_collection_of_ranges_Contains(bool async)
{
var ranges = new NpgsqlRange<int>[]
{
new(5, 15),
new(40, 50)
};

await AssertQuery(
async,
ss => ss.Set<PrimitiveCollectionsEntity>().Where(e => ranges.Contains(e.Int)),
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => ranges.Any(p => p.LowerBound <= c.Int && p.UpperBound >= c.Int)));

AssertSql(
"""
@__ranges_0={ '[5,15]', '[40,50]' } (DbType = Object)

SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings"
FROM "PrimitiveCollectionsEntity" AS p
WHERE @__ranges_0 @> p."Int"
""");
}

public override async Task Column_collection_of_ints_Contains(bool async)
{
await base.Column_collection_of_ints_Contains(async);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,13 @@ private void DropCollations(NpgsqlConnection conn)
return;
}

const string getUserCollations = @"SELECT nspname, collname
const string getUserCollations =
"""
SELECT nspname, collname
FROM pg_collation coll
JOIN pg_namespace ns ON ns.oid=coll.collnamespace
JOIN pg_authid auth ON auth.oid = coll.collowner WHERE rolname <> 'postgres';
";
JOIN pg_authid auth ON auth.oid = coll.collowner WHERE nspname <> 'pg_catalog';
""";

(string Schema, string Name)[] userDefinedTypes;
using (var cmd = new NpgsqlCommand(getUserCollations, conn))
Expand Down