GRDB.swift v4.1.0 Release Notes

Release Date: 2019-06-20 // almost 5 years ago
  • ๐Ÿš€ Released June 20, 2019 • diff

    • ๐Ÿšš #537: Remove useless parenthesis from generated SQL
    • ๐Ÿ™‹ #538 by @Timac: Add FAQ to clarify "Wrong number of statement arguments" error with "like '%?%'"
    • #539: Expose joining methods on both requests and associations
    • โšก๏ธ #540: Update SQLite to 3.28.0 (thanks to @swiftlyfalling)
    • ๐Ÿšš #542: Move eager loading of hasMany associations to FetchRequest
    • #546 by @robcas3: Fix SPM errors with Xcode 11 beta
    • ๐Ÿ‘ #549 Support for Combine
    • #550 Asynchronous Database Access Methods
    • #555 Avoid a crash when read-only access can't be established

    ๐Ÿ“š Documentation Diff

    ๐Ÿ“š Good practices evolve: the [Define Record Requests](Documentation/GoodPracticesForDesigningRecordTypes.md#define-record-requests) chapter of the The [Good Practices for Designing Record Types](Documentation/GoodPracticesForDesigningRecordTypes.md) has been rewritten.

    ๐ŸŽ The [Examples of Record Definitions](README.md#examples-of-record-definitions) has been extended with a sample record optimized for fetching performance.

    โšก๏ธ The [ValueObservation](README.md#valueobservation) chapter has been updated with new APIs for building observation, and combining observations together in order to avoid data races.

    The [ValueObservation Error Handling](README.md#valueobservation-error-handling) chapter explains with more details how to deal with observation errors.

    API Diff

    Asynchronous database access methods

     protocol DatabaseReader {
    +    var configuration: Configuration { get }
    +
    +    #if compiler(>=5.0)
    +    func asyncRead(_ block: @escaping (Result<Database, Error>) -> Void)
    +    #endif
     }
    
     protocol DatabaseWriter {
    +    func asyncWriteWithoutTransaction(_ updates: @escaping (Database) -> Void)
    +
    +    #if compiler(>=5.0)
    +    func asyncWrite<T>(_ updates: @escaping (Database) throws -> T, completion: @escaping (Database, Result<T, Error>) -> Void)
    +    #endif
     }
    

    ValueObservation changes

    +extension FetchRequest {
    +    func observationForCount() -> ValueObservation<...>
    +}
    +extension FetchRequest where RowDecoder: ... {
    +    func observationForAll() -> ValueObservation<...>
    +    func observationForFirst() -> ValueObservation<...>
    +)
    +extension TableRecord {
    +    static func observationForCount() -> ValueObservation<...>
    +    static func observationForAll() -> ValueObservation<...>
    +    static func observationForFirst() -> ValueObservation<...>
    +}
     extension ValueObservation where ... {
    +    @available(*, deprecated)
         static func trackingCount<Request: FetchRequest>(_ request: Request) -> ValueObservation<...>
    +    @available(*, deprecated)
         static func trackingAll<Request: FetchRequest>(_ request: Request) -> ValueObservation<...>
    +    @available(*, deprecated)
         static func trackingOne<Request: FetchRequest>(_ request: Request) -> ValueObservation<...>
     }
     extension ValueObservation where Reducer: ValueReducer {
    -    func start(in reader: DatabaseReader, onError: ((Error) -> Void)? = nil, onChange: @escaping (Reducer.Value) -> Void) throws -> TransactionObserver
    +    func start(in reader: DatabaseReader, onChange: @escaping (Reducer.Value) -> Void) throws -> TransactionObserver
    +    func start(in reader: DatabaseReader, onError: @escaping (Error) -> Void, onChange: @escaping (Reducer.Value) -> Void) -> TransactionObserver
    +    func combine<..., Combined>(..., transform: @escaping (...) -> Combined) -> ValueObservation<...>
     }
     extension ValueObservation where Reducer: ValueReducer, Reducer.Value: Equatable {
    +    @available(*, deprecated)
         func distinctUntilChanged() -> ValueObservation<...>
    +    func removeDuplicates() -> ValueObservation<...>
     }
    

    Joining methods on both requests and associations

    +protocol JoinableRequest {
    +    associatedtype RowDecoder
    +}
    +
    +extension JoinableRequest {
    +    func including<A: AssociationToMany>(all association: A) -> Self where A.OriginRowDecoder == RowDecoder
    +    func including<A: Association>(optional association: A) -> Self where A.OriginRowDecoder == RowDecoder
    +    func including<A: Association>(required association: A) -> Self where A.OriginRowDecoder == RowDecoder
    +    func joining<A: Association>(optional association: A) -> Self where A.OriginRowDecoder == RowDecoder
    +    func joining<A: Association>(required association: A) -> Self where A.OriginRowDecoder == RowDecoder
    +}
    
    -protocol DerivableRequest: SelectionRequest, FilteredRequest, OrderedRequest { }
    +protocol DerivableRequest: SelectionRequest, FilteredRequest, OrderedRequest, JoinableRequest { }
    

    FetchRequest changes

    +struct PreparedRequest {
    +    var statement: SelectStatement
    +    var adapter: RowAdapter?
    +    init(statement: SelectStatement, adapter: RowAdapter? = nil)
    +}
    
     protocol FetchRequest {
    +    // deprecated
         func prepare(_ db: Database, forSingleResult singleResult: Bool) throws -> (SelectStatement, RowAdapter?)
    +    func makePreparedRequest(_ db: Database, forSingleResult singleResult: Bool) throws -> PreparedRequest
     }
    

    The core FetchRequest preparation method is now makePreparedRequest(_:forSingleResult:). The former core method prepare(_:forSingleResult:) will remain a requirement of the FetchRequest protocol until GRDB 5 due to semantic versioning constraints. Both methods are provided with a default implementation which makes each one depend on the other: this creates an infinite loop unless you provide at least one of them. If you have a choice, implement only makePreparedRequest(_:forSingleResult:).