Skip to content

Commit d4d82a0

Browse files
Brarroji
authored andcommitted
Respect NOT NULL constraints on domain types in arrays.
Return non-nullable arrays from NpgsqlDataReader.GetValue() for arrays of a domain with a NOT NULL constraint.
1 parent c95b6ec commit d4d82a0

4 files changed

Lines changed: 40 additions & 6 deletions

File tree

src/Npgsql/PostgresDatabaseInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ internal async Task LoadPostgresInfo(NpgsqlConnection conn, NpgsqlTimeout timeou
113113
static string GenerateTypesQuery(bool withRange, bool withEnum, bool withEnumSortOrder, bool loadTableComposites, bool withTypeCategory)
114114
=> $@"
115115
/*** Load all supported types ***/
116-
SELECT ns.nspname, a.typname, a.oid, a.typbasetype,
116+
SELECT ns.nspname, a.typname, a.oid, a.typbasetype, a.typnotnull,
117117
CASE WHEN pg_proc.proname='array_recv' THEN 'a' ELSE a.typtype END AS typtype,
118118
CASE
119119
WHEN pg_proc.proname='array_recv' THEN a.typelem
@@ -264,7 +264,7 @@ internal async Task<List<PostgresType>> LoadBackendTypes(NpgsqlConnection conn,
264264
Log.Trace($"Domain type '{internalName}' refers to unknown base type with OID {baseTypeOID}, skipping", conn.ProcessID);
265265
continue;
266266
}
267-
var domainType = new PostgresDomainType(ns, internalName, oid, basePostgresType);
267+
var domainType = new PostgresDomainType(ns, internalName, oid, basePostgresType, reader.GetString("typnotnull") == "t");
268268
byOID[domainType.OID] = domainType;
269269
continue;
270270

src/Npgsql/PostgresTypes/PostgresDomainType.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,20 @@ public class PostgresDomainType : PostgresType
2121
[PublicAPI]
2222
public PostgresType BaseType { get; }
2323

24+
/// <summary>
25+
/// <b>True</b> if the domain has a NOT NULL constraint, otherwise <b>false</b>.
26+
/// </summary>
27+
[PublicAPI]
28+
public bool NotNull { get; }
29+
2430
/// <summary>
2531
/// Constructs a representation of a PostgreSQL domain data type.
2632
/// </summary>
27-
protected internal PostgresDomainType(string ns, string name, uint oid, PostgresType baseType)
33+
protected internal PostgresDomainType(string ns, string name, uint oid, PostgresType baseType, bool notNull)
2834
: base(ns, name, oid)
2935
{
3036
BaseType = baseType;
37+
NotNull = notNull;
3138
}
3239

3340
internal override PostgresFacets GetFacets(int typeModifier)

src/Npgsql/TypeHandlers/ArrayHandler.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Diagnostics.CodeAnalysis;
66
using System.Linq.Expressions;
77
using System.Reflection;
8+
using System.Runtime.CompilerServices;
89
using System.Threading.Tasks;
910
using Npgsql.BackendMessages;
1011
using Npgsql.PostgresTypes;
@@ -279,15 +280,22 @@ public ArrayHandler(PostgresType arrayPostgresType, NpgsqlTypeHandler elementHan
279280
#region Read
280281

281282
internal override async ValueTask<object> ReadAsObject(NpgsqlReadBuffer buf, int len, bool async, FieldDescription? fieldDescription = null)
282-
=> typeof(TElement).IsValueType
283+
=> ReadAsNullable(fieldDescription)
283284
? await ElementTypeInfo<TElement>.ReadNullableArrayFunc(this, buf, async)
284285
: await ReadArray<TElement>(buf, async);
285286

286287
internal override object ReadAsObject(NpgsqlReadBuffer buf, int len, FieldDescription? fieldDescription = null)
287-
=> typeof(TElement).IsValueType
288+
=> ReadAsNullable(fieldDescription)
288289
? ElementTypeInfo<TElement>.ReadNullableArrayFunc(this, buf, false).GetAwaiter().GetResult()
289290
: ReadArray<TElement>(buf, false).GetAwaiter().GetResult();
290291

292+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
293+
static bool ReadAsNullable(FieldDescription? fieldDescription)
294+
=> typeof(TElement).IsValueType &&
295+
!(fieldDescription?.PostgresType is PostgresArrayType arrayType &&
296+
arrayType.Element is PostgresDomainType domainType &&
297+
domainType.NotNull);
298+
291299
#endregion
292300

293301
#region Write

test/Npgsql.Tests/Types/ArrayTests.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public void EmptyArray()
132132
}
133133

134134
[Test, Description("Verifies that the array type returned from NpgsqlDataReader.GetValue() is always compatible with null values.")]
135-
public void GetValueArrayTypeForValueTypesDependsOnActualValue()
135+
public void GetValueArrayTypeForValueTypesIsNullable()
136136
{
137137
using (var conn = OpenConnection())
138138
using (var cmd = new NpgsqlCommand(@"
@@ -157,6 +157,25 @@ INSERT into GetValueArrayTypeForValueTypesDependsOnActualValue VALUES
157157
}
158158
}
159159

160+
[Test, Description("Verifies that the array type returned from NpgsqlDataReader.GetValue() handles NOT NULL constraints on domains correctly.")]
161+
public void GetValueRespectsNotNullConstraintsOnDomainsInArrays()
162+
{
163+
using var conn = OpenConnection();
164+
conn.ExecuteNonQuery("CREATE DOMAIN pg_temp.int_not_null AS integer NOT NULL;");
165+
conn.ExecuteNonQuery("CREATE DOMAIN pg_temp.int_null AS integer;");
166+
conn.ReloadTypes();
167+
168+
using var cmd = new NpgsqlCommand(@"SELECT '{1,2,3,4}'::pg_temp.int_not_null[], '{1,2,3,4}'::pg_temp.int_null[];", conn);
169+
var reader = cmd.ExecuteReader();
170+
reader.Read();
171+
Assert.That(reader.GetFieldType(0), Is.EqualTo(typeof(Array)));
172+
Assert.That(reader.GetValue(0), Is.TypeOf<int[]>());
173+
Assert.That(reader.GetFieldValue<int?[]>(0), Is.TypeOf<int?[]>());
174+
175+
Assert.That(reader.GetFieldType(1), Is.EqualTo(typeof(Array)));
176+
Assert.That(reader.GetValue(1), Is.TypeOf<int?[]>());
177+
Assert.That(reader.GetFieldValue<int[]>(1), Is.TypeOf<int[]>());
178+
}
160179

161180
[Test, Description("Verifies that an attempt to read an Array of value types that contains null values as array of a non-nullable type fails.")]
162181
public void GetFieldValueNonNullableValueTypeArrayFailsOnArrayContainingNull()

0 commit comments

Comments
 (0)