From c977cb8226f1ee6ac590816ab2df808b46437871 Mon Sep 17 00:00:00 2001
From: Steven Roebert
Date: Sat, 13 Jul 2024 11:41:17 +0200
Subject: [PATCH 1/6] Implemented support for single value decoding in
_RowDecoder
---
GRDB/Record/FetchableRecord+Decodable.swift | 50 +++++++++++++-
.../FetchableRecordDecodableTests.swift | 66 +++++++++++++++++++
2 files changed, 113 insertions(+), 3 deletions(-)
diff --git a/GRDB/Record/FetchableRecord+Decodable.swift b/GRDB/Record/FetchableRecord+Decodable.swift
index 5d894df30c..16b4e57892 100644
--- a/GRDB/Record/FetchableRecord+Decodable.swift
+++ b/GRDB/Record/FetchableRecord+Decodable.swift
@@ -108,8 +108,12 @@ private struct _RowDecoder: Decoder {
func singleValueContainer() throws -> SingleValueDecodingContainer {
guard let key = codingPath.last else {
- // Decoding an array of scalars from rows: pick the first column
- return ColumnDecoder(row: row, columnIndex: 0, codingPath: codingPath)
+ // Not yet sure what we are decoding, this will be decided in the SingleValueDecodingContainer functions.
+ // For decoding an array of scalars (in case of prefetched rows) we pick the first column.
+ return SingleValueRowDecoder(
+ columnDecoder: ColumnDecoder(row: row, columnIndex: 0, codingPath: codingPath),
+ columnDecodingStrategy: columnDecodingStrategy
+ )
}
guard let index = row.index(forColumn: key.stringValue) else {
// Don't use DecodingError.keyNotFound:
@@ -346,7 +350,7 @@ private struct _RowDecoder: Decoder {
// Unknown key
//
- // Should be throw an error? Well... The use case is the following:
+ // Should we throw an error? Well... The use case is the following:
//
// // SELECT book.*, author.* FROM book
// // JOIN author ON author.id = book.authorId
@@ -487,6 +491,46 @@ private struct _RowDecoder: Decoder {
}
}
+private struct SingleValueRowDecoder: SingleValueDecodingContainer {
+ var columnDecoder: ColumnDecoder
+ var columnDecodingStrategy: DatabaseColumnDecodingStrategy
+ let codingPath: [any CodingKey] = []
+
+ func decodeNil() -> Bool { columnDecoder.decodeNil() }
+ func decode(_ type: Bool.Type) throws -> Bool { try columnDecoder.decode(type) }
+ func decode(_ type: String.Type) throws -> String { try columnDecoder.decode(type) }
+ func decode(_ type: Double.Type) throws -> Double { try columnDecoder.decode(type) }
+ func decode(_ type: Float.Type) throws -> Float { try columnDecoder.decode(type) }
+ func decode(_ type: Int.Type) throws -> Int { try columnDecoder.decode(type) }
+ func decode(_ type: Int8.Type) throws -> Int8 { try columnDecoder.decode(type) }
+ func decode(_ type: Int16.Type) throws -> Int16 { try columnDecoder.decode(type) }
+ func decode(_ type: Int32.Type) throws -> Int32 { try columnDecoder.decode(type) }
+ func decode(_ type: Int64.Type) throws -> Int64 { try columnDecoder.decode(type) }
+ @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+ func decode(_ type: Int128.Type) throws -> Int128 { try columnDecoder.decode(type) }
+ func decode(_ type: UInt.Type) throws -> UInt { try columnDecoder.decode(type) }
+ func decode(_ type: UInt8.Type) throws -> UInt8 { try columnDecoder.decode(type) }
+ func decode(_ type: UInt16.Type) throws -> UInt16 { try columnDecoder.decode(type) }
+ func decode(_ type: UInt32.Type) throws -> UInt32 { try columnDecoder.decode(type) }
+ func decode(_ type: UInt64.Type) throws -> UInt64 { try columnDecoder.decode(type) }
+ @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+ func decode(_ type: UInt128.Type) throws -> UInt128 { try columnDecoder.decode(type) }
+
+ func decode(_ type: T.Type) throws -> T where T: Decodable {
+ if let type = T.self as? any FetchableRecord.Type {
+ // Prefer FetchableRecord decoding over Decodable.
+ return try type.init(row: columnDecoder.row) as! T
+ } else {
+ let decoder = _RowDecoder(
+ row: columnDecoder.row,
+ codingPath: [],
+ columnDecodingStrategy: columnDecodingStrategy
+ )
+ return try T(from: decoder)
+ }
+ }
+}
+
// MARK: - PrefetchedRowsDecoder
private struct PrefetchedRowsDecoder: Decoder {
diff --git a/Tests/GRDBTests/FetchableRecordDecodableTests.swift b/Tests/GRDBTests/FetchableRecordDecodableTests.swift
index 1f7789a5e2..8c7d0c195c 100644
--- a/Tests/GRDBTests/FetchableRecordDecodableTests.swift
+++ b/Tests/GRDBTests/FetchableRecordDecodableTests.swift
@@ -1913,4 +1913,70 @@ extension FetchableRecordDecodableTests {
}
}
}
+
+ // Regression test for
+ func testSingleValueContainer() throws {
+ struct Struct: Decodable {
+ let value: String
+ }
+
+ struct Wrapper: FetchableRecord, Decodable {
+ var model: Model
+ var otherValue: String
+
+ enum CodingKeys: String, CodingKey {
+ case otherValue
+ }
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ otherValue = try container.decode(String.self, forKey: . otherValue)
+
+ let singleValueContainer = try decoder.singleValueContainer()
+ model = try singleValueContainer.decode(Model.self)
+ }
+ }
+
+ let row = Row(["value": "foo", "otherValue": "bar"])
+
+ let wrapper = try Wrapper(row: row)
+ XCTAssertEqual(wrapper.model.value, "foo")
+ XCTAssertEqual(wrapper.otherValue, "bar")
+ }
+
+ // Regression test for
+ // Here we test that `FetchableRecord` takes precedence over `Decodable`
+ // when a record is encoded with a `SingleValueEncodingContainer`.
+ func testSingleValueContainerWithFetchableRecord() throws {
+ struct Struct: Decodable, FetchableRecord {
+ let value: String
+
+ init(row: Row) throws {
+ value = row["actualValue"]
+ }
+ }
+
+ struct Wrapper: FetchableRecord, Decodable {
+ var model: Model
+ var otherValue: String
+
+ enum CodingKeys: String, CodingKey {
+ case otherValue
+ }
+
+ init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ otherValue = try container.decode(String.self, forKey: . otherValue)
+
+ let singleValueContainer = try decoder.singleValueContainer()
+ model = try singleValueContainer.decode(Model.self)
+ }
+ }
+
+ let row = Row(["value": "foo", "otherValue": "bar", "actualValue": "test"])
+
+ let wrapper = try Wrapper(row: row)
+ XCTAssertEqual(wrapper.model.value, "test")
+ XCTAssertEqual(wrapper.otherValue, "bar")
+ }
}
From 0ae3f32f7fd877f105cbe3b2f3d90cd7b13653ae Mon Sep 17 00:00:00 2001
From: Steven Roebert
Date: Sat, 13 Jul 2024 12:53:00 +0200
Subject: [PATCH 2/6] Added compiler checks for Swift 6 specific code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Gwendal Roué
---
GRDB/Record/FetchableRecord+Decodable.swift | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/GRDB/Record/FetchableRecord+Decodable.swift b/GRDB/Record/FetchableRecord+Decodable.swift
index 16b4e57892..f89a12a5c2 100644
--- a/GRDB/Record/FetchableRecord+Decodable.swift
+++ b/GRDB/Record/FetchableRecord+Decodable.swift
@@ -506,15 +506,19 @@ private struct SingleValueRowDecoder: SingleValueDecodingCon
func decode(_ type: Int16.Type) throws -> Int16 { try columnDecoder.decode(type) }
func decode(_ type: Int32.Type) throws -> Int32 { try columnDecoder.decode(type) }
func decode(_ type: Int64.Type) throws -> Int64 { try columnDecoder.decode(type) }
+#if compiler(>=6)
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func decode(_ type: Int128.Type) throws -> Int128 { try columnDecoder.decode(type) }
+#endif
func decode(_ type: UInt.Type) throws -> UInt { try columnDecoder.decode(type) }
func decode(_ type: UInt8.Type) throws -> UInt8 { try columnDecoder.decode(type) }
func decode(_ type: UInt16.Type) throws -> UInt16 { try columnDecoder.decode(type) }
func decode(_ type: UInt32.Type) throws -> UInt32 { try columnDecoder.decode(type) }
func decode(_ type: UInt64.Type) throws -> UInt64 { try columnDecoder.decode(type) }
+#if compiler(>=6)
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func decode(_ type: UInt128.Type) throws -> UInt128 { try columnDecoder.decode(type) }
+#endif
func decode(_ type: T.Type) throws -> T where T: Decodable {
if let type = T.self as? any FetchableRecord.Type {
From 1c60e5ef424d4a6638f1dc282db633b96daa12db Mon Sep 17 00:00:00 2001
From: Jason Abbott
Date: Tue, 16 Jul 2024 16:51:50 -0600
Subject: [PATCH 3/6] Show trigger comments for expanded statement tracing
---
GRDB/Core/Database.swift | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/GRDB/Core/Database.swift b/GRDB/Core/Database.swift
index 762ff7506b..e52c58052a 100644
--- a/GRDB/Core/Database.swift
+++ b/GRDB/Core/Database.swift
@@ -2101,6 +2101,10 @@ extension Database {
/// information from leaking in unexpected locations, so use this
/// property with care.
public var expandedSQL: String {
+ if let unexpandedSQL {
+ let sql = String(cString: unexpandedSQL)
+ if sql.hasSuffix("--") { return sql }
+ }
guard let cString = sqlite3_expanded_sql(sqliteStatement) else {
return ""
}
From 802b4ca78a9018dc983d459b1a16f1a40b0d9f60 Mon Sep 17 00:00:00 2001
From: Jason Abbott
Date: Tue, 16 Jul 2024 16:57:57 -0600
Subject: [PATCH 4/6] Correct suffix/prefix test
---
GRDB/Core/Database.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/GRDB/Core/Database.swift b/GRDB/Core/Database.swift
index e52c58052a..c7708f67b7 100644
--- a/GRDB/Core/Database.swift
+++ b/GRDB/Core/Database.swift
@@ -2103,7 +2103,7 @@ extension Database {
public var expandedSQL: String {
if let unexpandedSQL {
let sql = String(cString: unexpandedSQL)
- if sql.hasSuffix("--") { return sql }
+ if sql.hasPrefix("--") { return sql }
}
guard let cString = sqlite3_expanded_sql(sqliteStatement) else {
return ""
From 14d2b931a4bbc9321bfd6a11ba0be691053f0215 Mon Sep 17 00:00:00 2001
From: Jason Abbott
Date: Wed, 17 Jul 2024 09:17:03 -0600
Subject: [PATCH 5/6] Also return untrimmed comments when tracing without
visible values
---
GRDB/Core/Database.swift | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/GRDB/Core/Database.swift b/GRDB/Core/Database.swift
index c7708f67b7..6281109b31 100644
--- a/GRDB/Core/Database.swift
+++ b/GRDB/Core/Database.swift
@@ -2083,7 +2083,8 @@ extension Database {
/// ```
public var sql: String {
if let unexpandedSQL {
- return String(cString: unexpandedSQL).trimmedSQLStatement
+ let sql = String(cString: unexpandedSQL)
+ return sql.hasPrefix("--") ? sql : sql.trimmedSQLStatement
} else {
return String(cString: sqlite3_sql(sqliteStatement)).trimmedSQLStatement
}
From bbbe0d899dd1031e960ebda95922aece3a85c1cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gwendal=20Roue=CC=81?=
Date: Sat, 20 Jul 2024 11:17:13 +0200
Subject: [PATCH 6/6] v6.29.0
---
CHANGELOG.md | 8 ++++++++
Documentation/ReleaseProcess.md | 6 ++++--
GRDB.swift.podspec | 2 +-
README.md | 2 +-
Support/Info.plist | 2 +-
5 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d31c3a7ec..ec95cb1929 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:
#### 6.x Releases
+- `6.29.x` Releases - [6.29.0](#6290)
- `6.28.x` Releases - [6.28.0](#6280)
- `6.27.x` Releases - [6.27.0](#6270)
- `6.26.x` Releases - [6.26.0](#6260)
@@ -126,6 +127,13 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:
---
+## 6.29.0
+
+Released July 20, 2024
+
+- **New**: [#1574](https://github.com/groue/GRDB.swift/pull/1574) by [@sroebert](https://github.com/sroebert): Support for single value decoding (the complement of [#1570](https://github.com/groue/GRDB.swift/pull/1570) shipped in 6.28.0)
+- **New**: [#1575](https://github.com/groue/GRDB.swift/pull/1575) by [@Jason-Abbott](https://github.com/Jason-Abbott): Show comments when tracing expanded statements
+
## 6.28.0
Released July 11, 2024
diff --git a/Documentation/ReleaseProcess.md b/Documentation/ReleaseProcess.md
index 008b24d66c..08210d7404 100644
--- a/Documentation/ReleaseProcess.md
+++ b/Documentation/ReleaseProcess.md
@@ -18,8 +18,10 @@ To release a new GRDB version:
- README.md
- Support/Info.plist
- Commit and tag
-- Check tag authors: `git for-each-ref --format '%(refname) %(authorname)' refs/tags`
-- Push to the master & development branch
+- Look for undesired tags: `git for-each-ref --format '%(refname) %(authorname)' refs/tags`
+- Push to the `master` branch
+- Push to the `development` branch
+- Push to the `GRDB6` branch
- `pod trunk push --allow-warnings GRDB.swift.podspec`
- Update [performance comparison](https://github.com/groue/GRDB.swift/wiki/Performance):
diff --git a/GRDB.swift.podspec b/GRDB.swift.podspec
index 9c58025bca..8436768f31 100644
--- a/GRDB.swift.podspec
+++ b/GRDB.swift.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'GRDB.swift'
- s.version = '6.28.0'
+ s.version = '6.29.0'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.summary = 'A toolkit for SQLite databases, with a focus on application development.'
diff --git a/README.md b/README.md
index 79838cbd24..b45d3bd5d4 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
-**Latest release**: July 11, 2024 • [version 6.28.0](https://github.com/groue/GRDB.swift/tree/v6.28.0) • [CHANGELOG](CHANGELOG.md) • [Migrating From GRDB 5 to GRDB 6](Documentation/GRDB6MigrationGuide.md)
+**Latest release**: July 20, 2024 • [version 6.29.0](https://github.com/groue/GRDB.swift/tree/v6.29.0) • [CHANGELOG](CHANGELOG.md) • [Migrating From GRDB 5 to GRDB 6](Documentation/GRDB6MigrationGuide.md)
**Requirements**: iOS 11.0+ / macOS 10.13+ / tvOS 11.0+ / watchOS 4.0+ • SQLite 3.19.3+ • Swift 5.7+ / Xcode 14+
diff --git a/Support/Info.plist b/Support/Info.plist
index a9a6ba2d38..53b9879694 100644
--- a/Support/Info.plist
+++ b/Support/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
FMWK
CFBundleShortVersionString
- 6.28.0
+ 6.29.0
CFBundleSignature
????
CFBundleVersion