Skip to content

Commit 684ced5

Browse files
bilby91claude
andcommitted
Add json/jsonb/money array support, REST tests, and snapshot fix
- Add _json, _jsonb, _money to PostgreSQL array type mapping dictionary - Add json[], jsonb[], money[] columns to array_type_table test schema - Add REST integration tests for array type endpoints (list, by PK, nulls) - Update GraphQL array tests to cover new array column types - Update PostgreSQL snapshot to include ArrayType entity Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a18fe77 commit 684ced5

5 files changed

Lines changed: 212 additions & 6 deletions

File tree

src/Core/Services/MetadataProviders/PostgreSqlMetadataProvider.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ public override Type SqlToCLRType(string sqlType)
9898
["_uuid"] = typeof(Guid),
9999
["_timestamp"] = typeof(DateTime),
100100
["_timestamptz"] = typeof(DateTimeOffset),
101+
["_json"] = typeof(string),
102+
["_jsonb"] = typeof(string),
103+
["_money"] = typeof(decimal),
101104
};
102105

103106
/// <summary>

src/Service.Tests/DatabaseSchema-PostgreSql.sql

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,10 @@ CREATE TABLE array_type_table(
172172
int_array_col int[],
173173
text_array_col text[],
174174
bool_array_col boolean[],
175-
long_array_col bigint[]
175+
long_array_col bigint[],
176+
json_array_col json[],
177+
jsonb_array_col jsonb[],
178+
money_array_col money[]
176179
);
177180

178181
CREATE TABLE trees (
@@ -421,10 +424,10 @@ INSERT INTO type_table(id, short_types, int_types, long_types, string_types, sin
421424
(4, 32767, 2147483647, 9223372036854775807, 'null', 3.4E38, 1.7E308, 2.929292E-14, true, '9999-12-31 23:59:59.997', '\xFFFFFFFF'),
422425
(5, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
423426
INSERT INTO type_table(id, uuid_types) values(10, 'D1D021A8-47B4-4AE4-B718-98E89C41A161');
424-
INSERT INTO array_type_table(id, int_array_col, text_array_col, bool_array_col, long_array_col) VALUES
425-
(1, '{1,2,3}', '{hello,world}', '{true,false}', '{100,200,300}'),
426-
(2, '{10,20}', '{foo,bar,baz}', '{true,true}', '{999}'),
427-
(3, NULL, NULL, NULL, NULL);
427+
INSERT INTO array_type_table(id, int_array_col, text_array_col, bool_array_col, long_array_col, json_array_col, jsonb_array_col, money_array_col) VALUES
428+
(1, '{1,2,3}', '{hello,world}', '{true,false}', '{100,200,300}', ARRAY['{"key":"value"}'::json, '{"num":42}'::json], ARRAY['{"key":"value"}'::jsonb, '{"num":42}'::jsonb], '{10.50,20.75,30.25}'),
429+
(2, '{10,20}', '{foo,bar,baz}', '{true,true}', '{999}', ARRAY['{"id":1}'::json], ARRAY['{"id":1}'::jsonb], '{5.00,15.00}'),
430+
(3, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
428431
INSERT INTO trees("treeId", species, region, height) VALUES (1, 'Tsuga terophylla', 'Pacific Northwest', '30m'), (2, 'Pseudotsuga menziesii', 'Pacific Northwest', '40m');
429432
INSERT INTO trees("treeId", species, region, height) VALUES (4, 'test', 'Pacific Northwest', '0m');
430433
INSERT INTO fungi(speciesid, region, habitat) VALUES (1, 'northeast', 'forest'), (2, 'southwest', 'sand');

src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForPostgreSql.verified.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2665,6 +2665,40 @@
26652665
}
26662666
}
26672667
},
2668+
{
2669+
ArrayType: {
2670+
Source: {
2671+
Object: array_type_table,
2672+
Type: Table
2673+
},
2674+
GraphQL: {
2675+
Singular: arrayType,
2676+
Plural: arrayTypes,
2677+
Enabled: true
2678+
},
2679+
Rest: {
2680+
Enabled: true
2681+
},
2682+
Permissions: [
2683+
{
2684+
Role: anonymous,
2685+
Actions: [
2686+
{
2687+
Action: Read
2688+
}
2689+
]
2690+
},
2691+
{
2692+
Role: authenticated,
2693+
Actions: [
2694+
{
2695+
Action: Read
2696+
}
2697+
]
2698+
}
2699+
]
2700+
}
2701+
},
26682702
{
26692703
dbo_DimAccount: {
26702704
Source: {

src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLArrayTypesTests.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Azure.DataApiBuilder.Service.Tests.SqlTests.GraphQLSupportedTypesTests
1010

1111
/// <summary>
1212
/// Tests for PostgreSQL array column support (read-only).
13-
/// Verifies that array columns (int[], text[], boolean[], bigint[]) are correctly
13+
/// Verifies that array columns (int[], text[], boolean[], bigint[], json[], jsonb[], money[]) are correctly
1414
/// returned as JSON arrays via GraphQL queries.
1515
/// </summary>
1616
[TestClass, TestCategory(TestCategory.POSTGRESQL)]
@@ -39,6 +39,9 @@ public async Task QueryArrayColumnsByPrimaryKey()
3939
text_array_col
4040
bool_array_col
4141
long_array_col
42+
json_array_col
43+
jsonb_array_col
44+
money_array_col
4245
}
4346
}";
4447

@@ -79,6 +82,25 @@ public async Task QueryArrayColumnsByPrimaryKey()
7982
Assert.AreEqual("100", longArray[0].ToString());
8083
Assert.AreEqual("200", longArray[1].ToString());
8184
Assert.AreEqual("300", longArray[2].ToString());
85+
86+
// Verify json array
87+
JsonElement jsonArray = actual.GetProperty("json_array_col");
88+
Assert.AreEqual(JsonValueKind.Array, jsonArray.ValueKind, $"json_array_col actual: {jsonArray}");
89+
Assert.AreEqual(2, jsonArray.GetArrayLength());
90+
Assert.IsTrue(jsonArray[0].ToString().Contains("key"));
91+
Assert.IsTrue(jsonArray[1].ToString().Contains("42"));
92+
93+
// Verify jsonb array
94+
JsonElement jsonbArray = actual.GetProperty("jsonb_array_col");
95+
Assert.AreEqual(JsonValueKind.Array, jsonbArray.ValueKind, $"jsonb_array_col actual: {jsonbArray}");
96+
Assert.AreEqual(2, jsonbArray.GetArrayLength());
97+
Assert.IsTrue(jsonbArray[0].ToString().Contains("key"));
98+
Assert.IsTrue(jsonbArray[1].ToString().Contains("42"));
99+
100+
// Verify money array
101+
JsonElement moneyArray = actual.GetProperty("money_array_col");
102+
Assert.AreEqual(JsonValueKind.Array, moneyArray.ValueKind, $"money_array_col actual: {moneyArray}");
103+
Assert.AreEqual(3, moneyArray.GetArrayLength());
82104
}
83105

84106
/// <summary>
@@ -94,6 +116,9 @@ public async Task QueryNullArrayColumns()
94116
text_array_col
95117
bool_array_col
96118
long_array_col
119+
json_array_col
120+
jsonb_array_col
121+
money_array_col
97122
}
98123
}";
99124

@@ -104,6 +129,9 @@ public async Task QueryNullArrayColumns()
104129
Assert.AreEqual(JsonValueKind.Null, actual.GetProperty("text_array_col").ValueKind);
105130
Assert.AreEqual(JsonValueKind.Null, actual.GetProperty("bool_array_col").ValueKind);
106131
Assert.AreEqual(JsonValueKind.Null, actual.GetProperty("long_array_col").ValueKind);
132+
Assert.AreEqual(JsonValueKind.Null, actual.GetProperty("json_array_col").ValueKind);
133+
Assert.AreEqual(JsonValueKind.Null, actual.GetProperty("jsonb_array_col").ValueKind);
134+
Assert.AreEqual(JsonValueKind.Null, actual.GetProperty("money_array_col").ValueKind);
107135
}
108136

109137
/// <summary>
@@ -118,6 +146,9 @@ public async Task QueryMultipleRowsWithArrayColumns()
118146
id
119147
int_array_col
120148
text_array_col
149+
json_array_col
150+
jsonb_array_col
151+
money_array_col
121152
}
122153
}
123154
}";
@@ -130,12 +161,18 @@ public async Task QueryMultipleRowsWithArrayColumns()
130161
// First row
131162
Assert.AreEqual(1, items[0].GetProperty("id").GetInt32());
132163
Assert.AreEqual(3, items[0].GetProperty("int_array_col").GetArrayLength());
164+
Assert.AreEqual(2, items[0].GetProperty("json_array_col").GetArrayLength());
165+
Assert.AreEqual(2, items[0].GetProperty("jsonb_array_col").GetArrayLength());
166+
Assert.AreEqual(3, items[0].GetProperty("money_array_col").GetArrayLength());
133167

134168
// Second row
135169
Assert.AreEqual(2, items[1].GetProperty("id").GetInt32());
136170
Assert.AreEqual(2, items[1].GetProperty("int_array_col").GetArrayLength());
137171
Assert.AreEqual(3, items[1].GetProperty("text_array_col").GetArrayLength());
138172
Assert.AreEqual("foo", items[1].GetProperty("text_array_col")[0].ToString());
173+
Assert.AreEqual(1, items[1].GetProperty("json_array_col").GetArrayLength());
174+
Assert.AreEqual(1, items[1].GetProperty("jsonb_array_col").GetArrayLength());
175+
Assert.AreEqual(2, items[1].GetProperty("money_array_col").GetArrayLength());
139176
}
140177
}
141178
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Net;
5+
using System.Net.Http;
6+
using System.Text.Json;
7+
using System.Threading.Tasks;
8+
using Microsoft.VisualStudio.TestTools.UnitTesting;
9+
10+
namespace Azure.DataApiBuilder.Service.Tests.SqlTests.RestApiTests.Find
11+
{
12+
/// <summary>
13+
/// Tests for PostgreSQL array column support via REST endpoints (read-only).
14+
/// Verifies that array columns are correctly returned as JSON arrays via REST GET requests.
15+
/// </summary>
16+
[TestClass, TestCategory(TestCategory.POSTGRESQL)]
17+
public class PostgreSqlRestArrayTypesTests : SqlTestBase
18+
{
19+
private const string ARRAY_TYPE_REST_PATH = "api/ArrayType";
20+
21+
[ClassInitialize]
22+
public static async Task SetupAsync(TestContext context)
23+
{
24+
DatabaseEngine = TestCategory.POSTGRESQL;
25+
await InitializeTestFixture();
26+
}
27+
28+
/// <summary>
29+
/// GET /api/ArrayType - Verify that listing array type entities returns array columns as JSON arrays.
30+
/// </summary>
31+
[TestMethod]
32+
public async Task GetArrayTypeList()
33+
{
34+
HttpResponseMessage response = await HttpClient.GetAsync(ARRAY_TYPE_REST_PATH);
35+
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
36+
37+
string body = await response.Content.ReadAsStringAsync();
38+
JsonElement root = JsonDocument.Parse(body).RootElement;
39+
JsonElement items = root.GetProperty("value");
40+
41+
Assert.IsTrue(items.GetArrayLength() >= 2, $"Expected at least 2 items, got {items.GetArrayLength()}");
42+
43+
// First row should have array values
44+
JsonElement first = items[0];
45+
Assert.AreEqual(1, first.GetProperty("id").GetInt32());
46+
Assert.AreEqual(JsonValueKind.Array, first.GetProperty("int_array_col").ValueKind);
47+
Assert.AreEqual(JsonValueKind.Array, first.GetProperty("text_array_col").ValueKind);
48+
Assert.AreEqual(JsonValueKind.Array, first.GetProperty("bool_array_col").ValueKind);
49+
Assert.AreEqual(JsonValueKind.Array, first.GetProperty("long_array_col").ValueKind);
50+
Assert.AreEqual(JsonValueKind.Array, first.GetProperty("json_array_col").ValueKind);
51+
Assert.AreEqual(JsonValueKind.Array, first.GetProperty("jsonb_array_col").ValueKind);
52+
Assert.AreEqual(JsonValueKind.Array, first.GetProperty("money_array_col").ValueKind);
53+
}
54+
55+
/// <summary>
56+
/// GET /api/ArrayType/id/1 - Verify that fetching by primary key returns array columns correctly.
57+
/// </summary>
58+
[TestMethod]
59+
public async Task GetArrayTypeByPrimaryKey()
60+
{
61+
HttpResponseMessage response = await HttpClient.GetAsync($"{ARRAY_TYPE_REST_PATH}/id/1");
62+
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
63+
64+
string body = await response.Content.ReadAsStringAsync();
65+
JsonElement root = JsonDocument.Parse(body).RootElement;
66+
JsonElement value = root.GetProperty("value")[0];
67+
68+
Assert.AreEqual(1, value.GetProperty("id").GetInt32());
69+
70+
// Verify int array
71+
JsonElement intArray = value.GetProperty("int_array_col");
72+
Assert.AreEqual(JsonValueKind.Array, intArray.ValueKind);
73+
Assert.AreEqual(3, intArray.GetArrayLength());
74+
75+
// Verify text array
76+
JsonElement textArray = value.GetProperty("text_array_col");
77+
Assert.AreEqual(JsonValueKind.Array, textArray.ValueKind);
78+
Assert.AreEqual(2, textArray.GetArrayLength());
79+
80+
// Verify boolean array
81+
JsonElement boolArray = value.GetProperty("bool_array_col");
82+
Assert.AreEqual(JsonValueKind.Array, boolArray.ValueKind);
83+
Assert.AreEqual(2, boolArray.GetArrayLength());
84+
85+
// Verify long array
86+
JsonElement longArray = value.GetProperty("long_array_col");
87+
Assert.AreEqual(JsonValueKind.Array, longArray.ValueKind);
88+
Assert.AreEqual(3, longArray.GetArrayLength());
89+
90+
// Verify json array
91+
JsonElement jsonArray = value.GetProperty("json_array_col");
92+
Assert.AreEqual(JsonValueKind.Array, jsonArray.ValueKind);
93+
Assert.AreEqual(2, jsonArray.GetArrayLength());
94+
95+
// Verify jsonb array
96+
JsonElement jsonbArray = value.GetProperty("jsonb_array_col");
97+
Assert.AreEqual(JsonValueKind.Array, jsonbArray.ValueKind);
98+
Assert.AreEqual(2, jsonbArray.GetArrayLength());
99+
100+
// Verify money array
101+
JsonElement moneyArray = value.GetProperty("money_array_col");
102+
Assert.AreEqual(JsonValueKind.Array, moneyArray.ValueKind);
103+
Assert.AreEqual(3, moneyArray.GetArrayLength());
104+
}
105+
106+
/// <summary>
107+
/// GET /api/ArrayType/id/3 - Verify that null array columns are returned as JSON null.
108+
/// </summary>
109+
[TestMethod]
110+
public async Task GetArrayTypeWithNullArrays()
111+
{
112+
HttpResponseMessage response = await HttpClient.GetAsync($"{ARRAY_TYPE_REST_PATH}/id/3");
113+
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
114+
115+
string body = await response.Content.ReadAsStringAsync();
116+
JsonElement root = JsonDocument.Parse(body).RootElement;
117+
JsonElement value = root.GetProperty("value")[0];
118+
119+
Assert.AreEqual(3, value.GetProperty("id").GetInt32());
120+
Assert.AreEqual(JsonValueKind.Null, value.GetProperty("int_array_col").ValueKind);
121+
Assert.AreEqual(JsonValueKind.Null, value.GetProperty("text_array_col").ValueKind);
122+
Assert.AreEqual(JsonValueKind.Null, value.GetProperty("bool_array_col").ValueKind);
123+
Assert.AreEqual(JsonValueKind.Null, value.GetProperty("long_array_col").ValueKind);
124+
Assert.AreEqual(JsonValueKind.Null, value.GetProperty("json_array_col").ValueKind);
125+
Assert.AreEqual(JsonValueKind.Null, value.GetProperty("jsonb_array_col").ValueKind);
126+
Assert.AreEqual(JsonValueKind.Null, value.GetProperty("money_array_col").ValueKind);
127+
}
128+
}
129+
}

0 commit comments

Comments
 (0)