All Versions
267
Latest Version
Avg Release Cycle
15 days
Latest Release
-

Changelog History
Page 12

  • v2.0 Changes

    🚀 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 introduces databaseSelection, 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 that Player.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 and AnyDatabaseWriter type erasers help dealing with the DatabaseReader and DatabaseWriter 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 the Date(julianDay:) initializer.

    • 🚚 All TableMapping methods that would modify the database have moved to MutablePersistable, now the only record protocol that is able to write.

    • The TableMapping.selectsRowID property has been replaced with TableMapping.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 the Cursor 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 the Cursor protocol, just like you'd write generic methods on the Swift Sequence 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

    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
     }
    
  • v1.3.0 Changes

    🚀 Released August 18, 2017 • diff

    🆕 New

    • ⬆️ Upgrade custom SQLite builds to v3.20.0 (thanks to @swiftlyfalling).
    • 👍 Complete support for all signed and unsigned integer types: from Int8 toUInt64, all integers can be stored and loaded from the database.

    API diff

    +extension Int8: DatabaseValueConvertible, StatementColumnConvertible { }
    +extension Int16: DatabaseValueConvertible, StatementColumnConvertible { }
    +extension UInt8: DatabaseValueConvertible, StatementColumnConvertible { }
    +extension UInt16: DatabaseValueConvertible, StatementColumnConvertible { }
    +extension UInt32: DatabaseValueConvertible, StatementColumnConvertible { }
    +extension UInt64: DatabaseValueConvertible, StatementColumnConvertible { }
    +extension UInt: DatabaseValueConvertible, StatementColumnConvertible { }
    
  • v1.2.2 Changes

    🚀 Released July 20, 2017 • diff

    🛠 Fixed

    • 🔧 The Configuration.trace function no longer leaks memory.
  • v1.2.1 Changes

    🚀 Released July 19, 2017 • diff

    🛠 Fixed

    • ⬆️ Upgrade custom SQLite builds to v3.19.3 (thanks to @swiftlyfalling).
    • The Query Interface now generates IS NULL SQL snippets for comparisons with DatabaseValue.null:

      // SELECT * FROM players WHERE email IS NULL
      Player.filter(Column("email") == DatabaseValue.null)
      

      It used to generate = NULL which would not behave as expected.

  • v1.2.0 Changes

    🚀 Released July 13, 2017 • diff

    🆕 New

    • 👍 Record types that do not provide values for all table columns in their encode(to:) method are now supported.

    • The table creation API has been enhanced:

      • SQLite supports untyped columns:

        try dbQueue.inDatabase { db in
            // CREATE TABLE t(a, b)
            try db.create(table: "t") { t in
                t.column("a")
                t.column("b")
            }
        }
        

        Untyped columns behave like decimal, boolean, date columns, and generally all columns with NUMERIC type affinity.

        This feature addresses #169.

      • The indexed() methods lets you create a non-unique index on a table column:

        try dbQueue.inDatabase { db in
            // CREATE TABLE rounds(score INTEGER)
            // CREATE INDEX rounds_on_score ON rounds(score)
            try db.create(table: "rounds") { t in
                t.column("score", .integer).indexed()
            }
        }
        
      • It is now possible to define references to tables without any explicit primary key. The generated SQL then uses the rowid hidden primary key column:

        try dbQueue.inDatabase { db in
            // CREATE TABLE nodes(
            //   name TEXT,
            //   parentId INTEGER REFERENCES nodes(rowid)
            // )
            try db.create(table: "nodes") { t in
                t.column("name", .text)
                t.column("parentId", .integer).references("nodes")
            }
        }
        
    • 📚 DatabaseQueue, DatabasePool and their common protocol DatabaseReader can now perform "unsafe reentrant reads" (documentation):

      try dbPool.read { db in
          // This is allowed
          try dbPool.unsafeReentrantRead { db in
              ...
          }
      }
      

    🛠 Fixed

    • DatabasePool.read, and DatabasePool.unsafeRead now raise a fatal error when used in a reentrant way:

      try dbPool.read { db in
          // fatal error: "Database methods are not reentrant."
          try dbPool.read { db in
              ...
          }
      }
      

      While this change may appear as a breaking change, it is really a fix: reentrant reads deadlock as soon as the maximum number of readers has been reached.

    API diff

     final class ColumnDefinition {
    +    @discardableResult func indexed() -> Self
     }
    
     final class TableDefinition {
    -    func column(_ name: String, _ type: Database.ColumnType) -> ColumnDefinition
    +    func column(_ name: String, _ type: Database.ColumnType? = nil) -> ColumnDefinition
     }
    
     protocol DatabaseReader {
    +    func unsafeReentrantRead<T>(_ block: (Database) throws -> T) throws -> T
     }
    
  • v1.1.0 Changes

    🚀 Released July 1, 2017 • diff

    🆕 New

    • 📚 DatabaseAggregate is the protocol for custom aggregate functions (fixes #236, documentation):

      struct MySum : DatabaseAggregate {
          var sum: Int = 0
      
          mutating func step(_ dbValues: [DatabaseValue]) {
              if let int = Int.fromDatabaseValue(dbValues[0]) {
                  sum += int
              }
          }
      
          func finalize() -> DatabaseValueConvertible? {
              return sum
          }
      }
      
      let dbQueue = DatabaseQueue()
      let fn = DatabaseFunction("mysum", argumentCount: 1, aggregate: MySum.self)
      dbQueue.add(function: fn)
      try dbQueue.inDatabase { db in
          try db.execute("CREATE TABLE test(i)")
          try db.execute("INSERT INTO test(i) VALUES (1)")
          try db.execute("INSERT INTO test(i) VALUES (2)")
          try Int.fetchOne(db, "SELECT mysum(i) FROM test")! // 3
      }
      

    🛠 Fixed

    • 📚 QueryInterfaceRequest.order(_:) clears the eventual reversed flag, and better reflects the documentation of this method: "Any previous ordering is replaced."

    🗄 Deprecated

    • 🗄 TableMapping.primaryKeyRowComparator is deprecated, without any replacement.

    API diff

     final class DatabaseFunction {
    +    init<Aggregate: DatabaseAggregate>(_ name: String, argumentCount: Int32? = nil, pure: Bool = false, aggregate: Aggregate.Type)
     }
    
    +protocol DatabaseAggregate {
    +    init()
    +    mutating func step(_ dbValues: [DatabaseValue]) throws
    +    func finalize() throws -> DatabaseValueConvertible?
    +}
    
     extension TableMapping {
    +    @available(*, deprecated)
         static func primaryKeyRowComparator(_ db: Database) throws -> (Row, Row) -> Bool
     }
    
  • v1.0.0 Changes

    🚀 Released June 20, 2017 :tada:

    GRDB 1.0 comes with enhancements, and API stability.

    It comes with breaking changes, but the good news is that they are the last (until GRDB 2.0) :sweat_smile:!

    • Requirements have changed: Xcode 8.3+ / Swift 3.1

      As a matter of fact, GRDB 1.0 still supports Xcode 8.1 and Swift 3.0. But future versions are free to use Swift 3.1 features, and will require Xcode 8.3+.

      The targetted operating systems are unchanged: iOS 8.0+ / OSX 10.9+ / watchOS 2.0+

    • Record types have their persistentDictionary property replaced with the encode(to:) method:

      struct Player : Persistable {
          let name: String
          let score: Int
      
          // Old
      //    var persistentDictionary: [String: DatabaseValueConvertible?] {
      //        return [
      //            "name": name,
      //            "score": score,
      //        ]
      //    }
      
          // New
          func encode(to container: inout PersistenceContainer) {
              container["name"] = name
              container["score"] = score
          }
      }
      

      This is good for applications that declare lists of columns:

      struct Player : RowConvertible, Persistable {
          let name: String
          let score: Int
      
          static let databaseTableName = "players"
      
          // Declare Player columns
          enum Columns {
              static let name = Column("name")
              static let score = Column("score")
          }
      
          // Use columns in `init(row:)`
          init(row: Row) {
              name = row.value(Columns.name)
              score = row.value(Columns.score)
          }
      
          // Use columns in the new `encode(to:)` method:
          func encode(to container: inout PersistenceContainer) {
              container[Columns.name] = name
              container[Columns.score] = score
          }
      }
      
    • Database Observation has been enhanced:

      Database.afterNextTransactionCommit(_:) is the simplest way to handle successful transactions, and synchronize the database with other resources such as files, or system sensors (documentation).

      // Make sure the database is inside a transaction
      db.inSavepoint {
          // Perform some database job
          try ...
      
          // Register extra job that is only executed after database changes
          // have been committed and written to disk.
          db.afterNextTransactionCommit { ... }
      }
      

      On the low-level side, applications can now specify the extent of database observation (documentation).

    • DatabaseMigrator is easier to test, with its DatabaseMigrator.migrate(_:upTo:) method which partially migrates your databases (documentation).

    • On the side of database schema introspection, the new Database.foreignKeys(on:) method lists the foreign keys defined on a table.

    Full list of changes

     class Database {
    -    func add(transactionObserver: TransactionObserver)
    +    func add(transactionObserver: TransactionObserver, extent: TransactionObservationExtent = .observerLifetime)
    +    func afterNextTransactionCommit(_ closure: @escaping (Database) -> ())
    +    func foreignKeys(on tableName: String) throws -> [ForeignKeyInfo]
     }
    
    -struct DatabaseCoder: DatabaseValueConvertible
    
     struct DatabaseMigrator {
    +    func migrate(_ writer: DatabaseWriter, upTo targetIdentifier: String) throws
     }
    
     protocol DatabaseWriter : DatabaseReader {
    -    func add(transactionObserver: TransactionObserver)
    +    func add(transactionObserver: TransactionObserver, extent: TransactionObservationExtent = .observerLifetime)
     }
    
     protocol MutablePersistable : TableMapping {
    -    var persistentDictionary: [String: DatabaseValueConvertible?] { get }
    +    func encode(to container: inout PersistenceContainer)
     }
    
     extension QueryInterfaceRequest {
    -    func contains(_ value: SQLExpressible) -> SQLExpression
    -    func exists() -> SQLExpression
     }
    
     extension Request {
    -    func adapted(_ adapter: @escaping (Database) throws -> RowAdapter) -> AnyRequest
    +    func adapted(_ adapter: @escaping (Database) throws -> RowAdapter) -> AdaptedRequest<Self>
     }
    
    protocol TypedRequest : Request {
    -    associatedtype Fetched
    +    associatedtype RowDecoder
    }
    
     extension TypedRequest {
    -    func adapted(_ adapter: @escaping (Database) throws -> RowAdapter) -> AnyTypedRequest<Fetched>
    +    func adapted(_ adapter: @escaping (Database) throws -> RowAdapter) -> AdaptedTypedRequest<Self>
     }
    
  • v0.110.0 Changes

    🚀 Released May 28, 2017

    🆕 New

    🛠 Fixed

  • v0.109.0 Changes

    🚀 Released May 22, 2017

    🛠 Fixed

    • Failed value conversions now consistently crash with a fatal error.

    🆕 New

    • DatabaseValue.losslessConvert() performs a lossless conversion to a value type.

    💥 Breaking Changes

    • 👀 DatabaseEventKind.impacts(_ selectionInfo:SelectStatement.SelectionInfo) now returns an non-optional boolean.
    • DatabaseWriter.availableDatabaseConnection has been replaced by DatabaseWriter.unsafeReentrantWrite().
    • Request.bound(to:) has been renamed Request.asRequest(of:).
  • v0.108.0 Changes

    🚀 Released May 17, 2017

    🆕 New

    • 👉 Use CocoaPods to install GRDB with SQLCipher:

      pod 'GRDBCipher'
      

    💥 Breaking Changes

    • 🚚 RowConvertible.awakeFromFetch() has been removed.