-
Notifications
You must be signed in to change notification settings - Fork 327
Add read-only support for PostgreSQL array columns #3402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b2e7ad3
a18fe77
684ced5
b0090a4
3d84621
8928278
c9fd8ff
b165f60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Data; | ||
| using System.Net; | ||
| using Azure.DataApiBuilder.Config.DatabasePrimitives; | ||
| using Azure.DataApiBuilder.Core.Configurations; | ||
| using Azure.DataApiBuilder.Core.Resolvers.Factories; | ||
| using Azure.DataApiBuilder.Service.Exceptions; | ||
|
|
@@ -75,5 +77,74 @@ public override Type SqlToCLRType(string sqlType) | |
| { | ||
| throw new NotImplementedException(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Maps PostgreSQL array udt_name prefixes to their CLR element types. | ||
| /// PostgreSQL array types in information_schema use udt_name with a leading underscore | ||
| /// (e.g., _int4 for int[], _text for text[]). | ||
| /// </summary> | ||
| private static readonly Dictionary<string, Type> _pgArrayUdtToElementType = new(StringComparer.OrdinalIgnoreCase) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be able to add
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make sense. Will verify they work with some new tests. |
||
| { | ||
| ["_int2"] = typeof(short), | ||
| ["_int4"] = typeof(int), | ||
| ["_int8"] = typeof(long), | ||
| ["_float4"] = typeof(float), | ||
| ["_float8"] = typeof(double), | ||
| ["_numeric"] = typeof(decimal), | ||
| ["_bool"] = typeof(bool), | ||
| ["_text"] = typeof(string), | ||
| ["_varchar"] = typeof(string), | ||
| ["_bpchar"] = typeof(string), | ||
| ["_uuid"] = typeof(Guid), | ||
| ["_timestamp"] = typeof(DateTime), | ||
| ["_timestamptz"] = typeof(DateTimeOffset), | ||
| ["_json"] = typeof(string), | ||
| ["_jsonb"] = typeof(string), | ||
| ["_money"] = typeof(decimal), | ||
| }; | ||
|
|
||
| /// <summary> | ||
| /// Override to detect PostgreSQL array columns using information_schema metadata. | ||
| /// Npgsql's DataAdapter reports array columns as System.Array (the abstract base class), | ||
| /// so we use the data_type and udt_name from information_schema.columns to identify arrays | ||
| /// and resolve their element types. | ||
| /// </summary> | ||
| protected override void PopulateColumnDefinitionWithHasDefaultAndDbType( | ||
| SourceDefinition sourceDefinition, | ||
| DataTable allColumnsInTable) | ||
| { | ||
| foreach (DataRow columnInfo in allColumnsInTable.Rows) | ||
| { | ||
| string columnName = (string)columnInfo["COLUMN_NAME"]; | ||
| bool hasDefault = | ||
| Type.GetTypeCode(columnInfo["COLUMN_DEFAULT"].GetType()) != TypeCode.DBNull; | ||
|
|
||
| if (sourceDefinition.Columns.TryGetValue(columnName, out ColumnDefinition? columnDefinition)) | ||
| { | ||
| columnDefinition.HasDefault = hasDefault; | ||
|
|
||
| if (hasDefault) | ||
| { | ||
| columnDefinition.DefaultValue = columnInfo["COLUMN_DEFAULT"]; | ||
| } | ||
|
|
||
| // Detect array columns: data_type is "ARRAY" in information_schema for PostgreSQL array types. | ||
| string dataType = columnInfo["DATA_TYPE"] is string dt ? dt : string.Empty; | ||
| if (string.Equals(dataType, "ARRAY", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| string udtName = columnInfo["UDT_NAME"] is string udt ? udt : string.Empty; | ||
| if (_pgArrayUdtToElementType.TryGetValue(udtName, out Type? elementType)) | ||
| { | ||
| columnDefinition.IsArrayType = true; | ||
| columnDefinition.ElementSystemType = elementType; | ||
| columnDefinition.SystemType = elementType.MakeArrayType(); | ||
| columnDefinition.IsReadOnly = true; | ||
| } | ||
| } | ||
|
|
||
| columnDefinition.DbType = TypeHelper.GetDbTypeFromSystemType(columnDefinition.SystemType); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason why we want
isNullableto be true?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isNullable: true is used because PostgreSQL arrays can contain NULL elements ('{1,NULL,3}'::int[] is valid). The element type reference should reflect this.
It's also consistent with how non-PK columns are handled in the same method — line 140 uses isNullable: true for regular structural properties. Only primary keys use isNullable: false.
I verified that changing to isNullable: false doesn't cause a runtime failure — DAB's OData pipeline doesn't enforce element-level nullability in collections at serialization time. So this is about correctness of the EDM model representation rather than a breaking behavior difference.
I added a test row (id=4) with NULL elements inside arrays ('{1,NULL,3}', '{hello,NULL,world}', etc.) and two new tests that query it:
Both pass with isNullable: true and also with false, confirming the pipeline handles both.