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/GRDB/Core/Database.swift b/GRDB/Core/Database.swift index 762ff7506b..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 } @@ -2101,6 +2102,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.hasPrefix("--") { return sql } + } guard let cString = sqlite3_expanded_sql(sqliteStatement) else { return "" } diff --git a/GRDB/Record/FetchableRecord+Decodable.swift b/GRDB/Record/FetchableRecord+Decodable.swift index 5d894df30c..f89a12a5c2 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,50 @@ 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) } +#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 { + // 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/README.md b/README.md index 79838cbd24..b45d3bd5d4 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ CI Status

-**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 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") + } }