GRDB.swift v2.0 Release Notes
-
๐ Released September 16, 2017 • diff
๐ GRDB 2.0 brings support for Swift 4. Notable changes are:
๐ Use subscript notation when extracting row values (documentation):
let name: String = row[0] // 0 is the leftmost column let name: String = row["name"] // Leftmost matching column - lookup is case-insensitive let name: String = row[Column("name")] // Using query interface's Column
๐ Support for the
Codable
protocol (documentation)Record types that adopt the standard
Codable
protocol are granted with automatic adoption of GRDB record protocols. This means that you no longer have to write boilerplate code :tada::struct Player : RowConvertible, Persistable, Codable { static let databaseTableName = "players" let name: String let score: Int } // Automatically derived: // // extension Player { // init(row: Row) { // name = row["name"] // score = row["score"] // } // // func encode(to container: inout PersistenceContainer) { // container["name"] = name // container["score"] = score // } // } try dbQueue.inDatabase { db in let arthur = Player(name: "Arthur", score: 100) try arthur.insert(db) let players = try Player.fetchAll(db) // [Players] }
Records can now specify the columns they feed from (documentation).
In previous versions of GRDB,
SELECT *
was the norm. GRDB 2.0 introducesdatabaseSelection
, which allows any type to define its preferred set of columns:struct Player : RowConvertible, TableMapping { let id: Int64 let name: String enum Columns { static let id = Column("id") static let name = Column("name") } init(row: Row) { id = row[Columns.id] name = row[Columns.name] } static let databaseTableName = "players" static let databaseSelection: [SQLSelectable] = [Columns.id, Columns.name] } // SELECT id, name FROM players let players = Player.fetchAll(db)
Record protocols have more precise semantics: RowConvertible reads database rows, TableMapping builds SQL requests, and Persistable writes (documentation).
This means that with GRDB 2.0, being able to write
Player.fetchAll(db)
does not imply thatPlayer.deleteAll(db)
is available: you have a better control on the abilities of your record types.
๐ Fixed
- GRDB is now able to store and load zero-length blobs.
๐ New
๐ New features have been added in order to plug a few holes and support the RxGRDB and GRDBObjc companion projects:
Persistable records can export themselves as dictionaries:
let player = try Player.fetchOne(db, key: 1) let dict = player.databaseDictionary // [String: DatabaseValue] print(dict) // Prints {"id": 1, "name": "Arthur", "score": 1000}
Query interface requests learned how to limit the number of deleted rows:
// Delete the last ten players: // DELETE FROM players ORDER BY score LIMIT 10 let request = Player.order(scoreColumn).limit(10) try request.deleteAll(db)
Prepared statements know the index of their columns:
let statement = try db.makeSelectStatement("SELECT a, b FROM t") statement.index(ofColumn: "b") // 1
Row cursors (of type RowCursor) expose their underlying statement:
let rows = try Row.fetchCursor(db, "SELECT ...") let statement = rows.statement
๐ One can build a Set from a cursor:
let strings = try Set(String.fetchCursor(...))
The new
AnyDatabaseReader
andAnyDatabaseWriter
type erasers help dealing with theDatabaseReader
andDatabaseWriter
protocols.
๐ฅ Breaking Changes
Requirements have changed: Xcode 9+ / Swift 4
๐ WatchOS extension targets no longer need
libsqlite3.tbd
to be added to the Linked Frameworks and Libraries section of their General tab.The
Row.value
method has been replaced with subscript notation:-row.value(atIndex: 0) -row.value(named: "id") -row.value(Column("id")) +row[0] +row["id"] +row[Column("id")]
๐ Date and NSDate now interpret numerical database values as timestamps that fuel
Date(timeIntervalSince1970:)
. Previous version of GRDB would interpret numbers as julian days (a date representation supported by SQLite). Support for julian days remains, with theDate(julianDay:)
initializer.๐ All
TableMapping
methods that would modify the database have moved toMutablePersistable
, now the only record protocol that is able to write.The
TableMapping.selectsRowID
property has been replaced withTableMapping.databaseSelection
.To upgrade, replace:
struct Player: TableMapping { - static let selectsRowID = true + static let databaseSelection: [SQLSelectable] = [AllColumns(), Column.rowID] }
๐ The
DatabaseCursor
type has been removed, and replaced with several concrete types that all adopt theCursor
protocol. Those new types allow dedicated optimizations depending on the type of the fetched elements.If your application has code that depends on
DatabaseCursor
, make it target the new concrete types, or make it generic on theCursor
protocol, just like you'd write generic methods on the SwiftSequence
protocol.RowConvertible.fetchCursor(_:keys:)
returns a non-optional cursor.Database.primaryKey(_:)
returns a non-optional PrimaryKeyInfo. When a table has no explicit primary key, the result is the hidden rowid column.๐ The deprecated
TableMapping.primaryKeyRowComparator
method has been removed.
๐ Documentation Diff
- โก๏ธ WatchOS installation procedure has been updated with new instructions.
- The introduction to multithreading with database pools has been enhanced.
- The Rows as Dictionaries chapter has been enhanced with instructions for converting a row into a dictionary.
- ๐ The description of cursors better explains how to choose between a cursor and an array.
- โก๏ธ The Date and DateComponents chapter has been updated for the new support for unix timestamps.
- The Fatal Errors chapter has an enhanced description of value conversion errors and how to handle them.
- โก๏ธ The Usage of raw SQLite pointers chapter has been updated for the new
SQLite3
standard module that comes with Swift4. - A new Record Protocols Overview chapter has been added.
- A new Codable Records chapter has been added.
- A new Columns Selected by a Request chapter describes the new
TableMapping.databaseSelection
property. - โก๏ธ The Exposing the RowID Column chapter has been updated for the new
TableMapping.databaseSelection
property.
API diff
class Row { - func value(atIndex index: Int) -> DatabaseValueConvertible? - func value<Value: DatabaseValueConvertible>(atIndex index: Int) -> Value? - func value<Value: DatabaseValueConvertible>(atIndex index: Int) -> Value - func value(named columnName: String) -> DatabaseValueConvertible? - func value<Value: DatabaseValueConvertible>(named columnName: String) -> Value? - func value<Value: DatabaseValueConvertible>(named columnName: String) -> Value - func value(_ column: Column) -> DatabaseValueConvertible? - func value<Value: DatabaseValueConvertible>(_ column: Column) -> Value? - func value<Value: DatabaseValueConvertible>(_ column: Column) -> Value + subscript(_ index: Int) -> DatabaseValueConvertible? + subscript<Value: DatabaseValueConvertible>(_ index: Int) -> Value? + subscript<Value: DatabaseValueConvertible>(_ index: Int) -> Value + subscript(_ columnName: String) -> DatabaseValueConvertible? + subscript<Value: DatabaseValueConvertible>(_ columnName: String) -> Value? + subscript<Value: DatabaseValueConvertible>(_ columnName: String) -> Value + subscript(_ column: Column) -> DatabaseValueConvertible? + subscript<Value: DatabaseValueConvertible>(_ column: Column) -> Value? + subscript<Value: DatabaseValueConvertible>(_ column: Column) -> Value } class SelectStatement { + func index(ofColumn columnName: String) -> Int? } +extension MutablePersistable { + var databaseDictionary: [String: DatabaseValue] +} -extension TableMapping { +extension MutablePersistable { @discardableResult static func deleteAll(_ db: Database) throws -> Int @discardableResult static func deleteAll<Sequence: Swift.Sequence>(_ db: Database, keys: Sequence) throws -> Int where Sequence.Element: DatabaseValueConvertible @discardableResult static func deleteOne<PrimaryKeyType: DatabaseValueConvertible>(_ db: Database, key: PrimaryKeyType?) throws -> Bool @discardableResult static func deleteAll(_ db: Database, keys: [[String: DatabaseValueConvertible?]]) throws -> Int @discardableResult static func deleteOne(_ db: Database, key: [String: DatabaseValueConvertible?]) throws -> Bool } -extension QueryInterfaceRequest { +extension QueryInterfaceRequest where RowDecoder: MutablePersistable { @discardableResult func deleteAll(_ db: Database) throws -> Int } protocol TableMapping { - static var selectsRowID: Bool { get } + static var databaseSelection: [SQLSelectable] { get } } class Record { - open class var selectsRowID: Bool + open class var databaseSelection: [SQLSelectable] } +struct AllColumns { + init() +} class Database { - func primaryKey(_ tableName: String) throws -> PrimaryKeyInfo? + func primaryKey(_ tableName: String) throws -> PrimaryKeyInfo } struct PrimaryKeyInfo { + var isRowID: Bool { get } } +extension Set { + init<C: Cursor>(_ cursor: C) throws where C.Element == Element +} -final class DatabaseCursor { } +final class ColumnCursor<Value: DatabaseValueConvertible & StatementColumnConvertible> : Cursor { + typealias Element = Value +} +final class DatabaseValueCursor<Value: DatabaseValueConvertible> : Cursor { + typealias Element = Value +} +final class NullableColumnCursor<Value: DatabaseValueConvertible & StatementColumnConvertible> : Cursor { + typealias Element = Value? +} +final class NullableDatabaseValueCursor<Value: DatabaseValueConvertible> : Cursor { + typealias Element = Value? +} +final class RecordCursor<Record: RowConvertible> : Cursor { + typealias Element = Record +} +final class RowCursor : Cursor { + typealias Element = Row + let statement: SelectStatement +} extension DatabaseValueConvertible { - static func fetchCursor(...) throws -> DatabaseCursor<Self> + static func fetchCursor(...) throws -> DatabaseValueCursor<Self> } extension DatabaseValueConvertible where Self: StatementColumnConvertible { + static func fetchCursor(...) throws -> ColumnCursor<Self> } extension Optional where Wrapped: DatabaseValueConvertible { - static func fetchCursor(...) throws -> DatabaseCursor<Wrapped?> + static func fetchCursor(...) throws -> NullableDatabaseValueCursor<Wrapped> } extension Optional where Wrapped: DatabaseValueConvertible & StatementColumnConvertible { + static func fetchCursor(...) throws -> NullableColumnCursor<Wrapped> } extension Row { - static func fetchCursor(...) throws -> DatabaseCursor<Row> + static func fetchCursor(...) throws -> RowCursor } extension RowConvertible where Self: TableMapping { - static func fetchCursor(...) throws -> DatabaseCursor<Self>? + static func fetchCursor(...) throws -> RecordCursor<Self> } extension TypedRequest where RowDecoder: RowConvertible { - func fetchCursor(_ db: Database) throws -> DatabaseCursor<RowDecoder> + func fetchCursor(_ db: Database) throws -> RecordCursor<RowDecoder> } extension TypedRequest where RowDecoder: DatabaseValueConvertible { - func fetchCursor(_ db: Database) throws -> DatabaseCursor<RowDecoder> + func fetchCursor(_ db: Database) throws -> DatabaseValueCursor<RowDecoder> } extension TypedRequest where RowDecoder: DatabaseValueConvertible & StatementColumnConvertible { + func fetchCursor(_ db: Database) throws -> ColumnCursor<RowDecoder> } extension TypedRequest where RowDecoder: _OptionalProtocol, RowDecoder._Wrapped: DatabaseValueConvertible { - func fetchCursor(_ db: Database) throws -> DatabaseCursor<RowDecoder._Wrapped?> + func fetchCursor(_ db: Database) throws -> NullableDatabaseValueCursor<RowDecoder._Wrapped> } extension TypedRequest where RowDecoder: _OptionalProtocol, RowDecoder._Wrapped: DatabaseValueConvertible & StatementColumnConvertible { + func fetchCursor(_ db: Database) throws -> NullableColumnCursor<RowDecoder._Wrapped> } extension TypedRequest where RowDecoder: Row { - func fetchCursor(_ db: Database) throws -> DatabaseCursor<Row> + func fetchCursor(_ db: Database) throws -> RowCursor } extension TableMapping { - @available(*, deprecated) static func primaryKeyRowComparator(_ db: Database) throws -> (Row, Row) -> Bool } +final class AnyDatabaseReader : DatabaseReader { + init(_ base: DatabaseReader) +} +final class AnyDatabaseWriter : DatabaseWriter { + init(_ base: DatabaseWriter) +}
Experimental API diff
enum SQLCount { - case star + case all }