Record

Record is a class that wraps a table row, or the result of any query. It is designed to be subclassed.

Initializers

Core methods

  • The name of a database table.

    This table name is required by the insert, update, save, delete, and exists methods.

    class Player : Record {
        override class var databaseTableName: String {
            return "player"
        }
    }
    

    The implementation of the base class Record raises a fatal error.

  • The policy that handles SQLite conflicts when records are inserted or updated.

    The default implementation uses the ABORT policy for both insertions and updates, and has GRDB generate regular INSERT and UPDATE queries.

    See https://www.sqlite.org/lang_conflict.html

  • The default request selection.

    Unless this method is overridden, requests select all columns:

    // SELECT * FROM player
    try Player.fetchAll(db)
    

    You can override this property and provide an explicit list of columns:

    class RestrictedPlayer : Record {
        override static var databaseSelection: [any SQLSelectable] {
            return [Column("id"), Column("name")]
        }
    }
    
    // SELECT id, name FROM player
    try RestrictedPlayer.fetchAll(db)
    

    You can also add extra columns such as the rowid column:

    class ExtendedPlayer : Player {
        override static var databaseSelection: [any SQLSelectable] {
            return [AllColumns(), Column.rowID]
        }
    }
    
    // SELECT *, rowid FROM player
    try ExtendedPlayer.fetchAll(db)
    
  • Defines the values persisted in the database.

    Store in the container parameter all values that should be stored in the columns of the database table (see Record.databaseTableName()).

    Primary key columns, if any, must be included.

    class Player : Record {
        var id: Int64?
        var name: String?
    
        override func encode(to container: inout PersistenceContainer) throws {
            container["id"] = id
            container["name"] = name
        }
    }
    

    The implementation of the base class Record does not store any value in the container.

Compare with Previous Versions

  • A boolean that indicates whether the record has changes that have not been saved.

    This flag is purely informative, and does not prevent insert(), update(), and save() from performing their database queries.

    A record is edited if has been changed since last database synchronization (fetch, update, insert). Comparison is performed between values (values stored in the encode(to:) method, and values loaded from the database). Property setters do not trigger this flag.

    You can rely on the Record base class to compute this flag for you, or you may set it to true or false when you know better. Setting it to false does not prevent it from turning true on subsequent modifications of the record.

  • A dictionary of changes that have not been saved.

    Its keys are column names, and values the old values that have been changed since last fetching or saving of the record.

    Unless the record has actually been fetched or saved, the old values are nil.

    See hasDatabaseChanges for more information.

    Throws

    An error is thrown if the record can’t be encoded to its database representation.

Persistence Callbacks

  • Called before the record is inserted.

    If you override this method, you must call super at some point in your implementation.

  • Called around the record insertion.

    If you override this method, you must call super at some point in your implementation (this calls the insert parameter).

    For example:

    class Player: Record {
        func aroundInsert(_ db: Database, insert: () throws -> InsertionSuccess) throws {
            print("Player will insert")
            try super.aroundInsert(db, insert: insert)
            print("Player did insert")
        }
    }
    
  • Called upon successful insertion.

    You can override this method in order to grab the auto-incremented id:

    class Player: Record {
        var id: Int64?
        var name: String
    
        override func didInsert(_ inserted: InsertionSuccess) {
            super.didInsert(inserted)
            id = inserted.rowID
        }
    }
    

    If you override this method, you must call super at some point in your implementation.

  • Called before the record is updated.

    If you override this method, you must call super at some point in your implementation.

  • Called around the record update.

    If you override this method, you must call super at some point in your implementation (this calls the update parameter).

    For example:

    class Player: Record {
        override func aroundUpdate(_ db: Database, columns: Set<String>, update: () throws -> PersistenceSuccess) throws {
            print("Player will update")
            try super.aroundUpdate(db, columns: columns, update: update)
            print("Player did update")
        }
    }
    
  • Called upon successful update.

    If you override this method, you must call super at some point in your implementation.

  • Called before the record is updated or inserted.

    If you override this method, you must call super at some point in your implementation.

  • Called around the record update or insertion.

    If you override this method, you must call super at some point in your implementation (this calls the update parameter).

    For example:

    class Player: Record {
        override func aroundSave(_ db: Database, save: () throws -> PersistenceSuccess) throws {
            print("Player will save")
            try super.aroundSave(db, save: save)
            print("Player did save")
        }
    }
    
  • Called upon successful update or insertion.

    If you override this method, you must call super at some point in your implementation.

  • Called before the record is deleted.

    If you override this method, you must call super at some point in your implementation.

  • Called around the destruction of the record.

    If you override this method, you must call super at some point in your implementation (this calls the delete parameter).

    For example:

    class Player: Record {
        override func aroundDelete(_ db: Database, delete: () throws -> Bool) throws {
            print("Player will delete")
            try super.aroundDelete(db, delete: delete)
            print("Player did delete")
        }
    }
    
  • Called upon successful deletion.

    If you override this method, you must call super at some point in your implementation.

CRUD

  • If the record has been changed, executes an UPDATE statement so that those changes and only those changes are saved in the database.

    On success, this method sets the hasDatabaseChanges flag to false.

    This method is guaranteed to have saved the eventual changes in the database if it returns without error.

    Throws

    A DatabaseError is thrown whenever an SQLite error occurs. PersistenceError.recordNotFound is thrown if the primary key does not match any row in the database and record could not be updated.