From 5334a4654ef452f99fb4b68c718034132e9e1e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Sat, 20 Apr 2024 11:39:24 +0200 Subject: [PATCH] Test decodeNil Two failing tests related to https://github.com/groue/GRDB.swift/issues/1531 --- .../FetchableRecordDecodableTests.swift | 177 ++++++++++++++++-- 1 file changed, 160 insertions(+), 17 deletions(-) diff --git a/Tests/GRDBTests/FetchableRecordDecodableTests.swift b/Tests/GRDBTests/FetchableRecordDecodableTests.swift index 06b3ea2352..1f7789a5e2 100644 --- a/Tests/GRDBTests/FetchableRecordDecodableTests.swift +++ b/Tests/GRDBTests/FetchableRecordDecodableTests.swift @@ -1736,8 +1736,8 @@ extension FetchableRecordDecodableTests { } } - let row = try makeDatabaseQueue().read { db in - try Row.fetchOne( + try makeDatabaseQueue().read { db in + let row = try Row.fetchOne( db, sql: """ SELECT 1 AS a, -- main row 2 AS b, -- not exposed @@ -1753,21 +1753,164 @@ extension FetchableRecordDecodableTests { "nestedScope2": RangeRowAdapter(4..<5), ]), ]))! + + row.prefetchedRows.setRows([], forKeyPath: ["prefetchedRows1"]) + row.prefetchedRows.setRows([Row()], forKeyPath: ["topLevelScope2", "prefetchedRows2"]) + // Check test setup + XCTAssertEqual(row.debugDescription, """ + ▿ [a:1] + unadapted: [a:1 b:2 c:3 d:4 e:5] + - topLevelScope1: [c:3] + - topLevelScope2: [] + - nestedScope1: [d:4] + - nestedScope2: [e:5] + + prefetchedRows2: 1 row + + prefetchedRows1: 0 row + + prefetchedRows2: 1 row + """) + + // Test keyed container + _ = try FetchableRecordDecoder().decode(Witness.self, from: row) + } + } + + // Regression test for + func test_decodeNil_and_containsKey() throws { + struct Witness: Decodable, FetchableRecord { + struct NestedRecord: Decodable, FetchableRecord { } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: AnyCodingKey.self) + + // column + do { + let key = AnyCodingKey("a") + let nilDecoded = try container.decodeNil(forKey: key) + let value = try container.decodeIfPresent(Int.self, forKey: key) + XCTAssertTrue(nilDecoded == (value == nil)) + XCTAssertTrue(container.contains(key)) + } + + // scope + do { + let key = AnyCodingKey("nested") + let nilDecoded = try container.decodeNil(forKey: key) + let value = try container.decodeIfPresent(NestedRecord.self, forKey: key) + XCTAssertTrue(nilDecoded == (value == nil)) + XCTAssertTrue(container.contains(key)) + } + + // missing key + do { + let key = AnyCodingKey("missing") + try XCTAssertTrue(container.decodeNil(forKey: key)) + try XCTAssertNil(container.decodeIfPresent(Int.self, forKey: key)) + try XCTAssertNil(container.decodeIfPresent(NestedRecord.self, forKey: key)) + XCTAssertFalse(container.contains(key)) + } + } + } + + try makeDatabaseQueue().read { db in + do { + let row = try Row.fetchOne( + db, sql: """ + SELECT 1 AS a, 2 AS b + """, + adapter: ScopeAdapter([ + "nested": RangeRowAdapter(1..<2), + ]))! + + // Check test setup + XCTAssertEqual(row.debugDescription, """ + ▿ [a:1 b:2] + unadapted: [a:1 b:2] + - nested: [b:2] + """) + + // Test keyed container + _ = try FetchableRecordDecoder().decode(Witness.self, from: row) + } + + do { + let row = try Row.fetchOne( + db, sql: """ + SELECT NULL AS a, NULL AS b + """, + adapter: ScopeAdapter([ + "nested": RangeRowAdapter(1..<2), + ]))! + + // Check test setup + XCTAssertEqual(row.debugDescription, """ + ▿ [a:NULL b:NULL] + unadapted: [a:NULL b:NULL] + - nested: [b:NULL] + """) + + // Test keyed container + _ = try FetchableRecordDecoder().decode(Witness.self, from: row) + } + } + } + + // Regression test for + func test_decodeNil_when_scope_and_column_have_the_same_name() throws { + struct Witness: Decodable, FetchableRecord { + struct NestedRecord: Decodable, FetchableRecord { } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: AnyCodingKey.self) + + let key = AnyCodingKey("a") + let nilDecoded = try container.decodeNil(forKey: key) + let intValue = try container.decodeIfPresent(Int.self, forKey: key) + let recordValue = try container.decodeIfPresent(NestedRecord.self, forKey: key) + XCTAssertTrue(nilDecoded == (intValue == nil)) + XCTAssertTrue(nilDecoded == (recordValue == nil)) + } + } + + try makeDatabaseQueue().read { db in + do { + let row = try Row.fetchOne( + db, sql: """ + SELECT 1 AS a + """, + adapter: ScopeAdapter([ + "a": SuffixRowAdapter(fromIndex: 0), + ]))! + + // Check test setup + XCTAssertEqual(row.debugDescription, """ + ▿ [a:1] + unadapted: [a:1] + - a: [a:1] + """) + + // Test keyed container + _ = try FetchableRecordDecoder().decode(Witness.self, from: row) + } + + do { + let row = try Row.fetchOne( + db, sql: """ + SELECT NULL AS a + """, + adapter: ScopeAdapter([ + "a": SuffixRowAdapter(fromIndex: 0), + ]))! + + // Check test setup + XCTAssertEqual(row.debugDescription, """ + ▿ [a:NULL] + unadapted: [a:NULL] + - a: [a:NULL] + """) + + // Test keyed container + _ = try FetchableRecordDecoder().decode(Witness.self, from: row) + } } - row.prefetchedRows.setRows([], forKeyPath: ["prefetchedRows1"]) - row.prefetchedRows.setRows([Row()], forKeyPath: ["topLevelScope2", "prefetchedRows2"]) - XCTAssertEqual(row.debugDescription, """ - ▿ [a:1] - unadapted: [a:1 b:2 c:3 d:4 e:5] - - topLevelScope1: [c:3] - - topLevelScope2: [] - - nestedScope1: [d:4] - - nestedScope2: [e:5] - + prefetchedRows2: 1 row - + prefetchedRows1: 0 row - + prefetchedRows2: 1 row - """) - - _ = try FetchableRecordDecoder().decode(Witness.self, from: row) } }