/// Table creation options. public struct TableOptions: OptionSet, Sendable { public let rawValue: Int public init(rawValue: Int) { self.rawValue = rawValue } /// Only creates the table if it does not already exist. public static let ifNotExists = TableOptions(rawValue: 1 << 0) /// Creates a temporary table. public static let temporary = TableOptions(rawValue: 1 << 1) /// Creates a [`WITHOUT ROWID`](https://www.sqlite.org/withoutrowid.html) table. /// /// Such tables can not be tracked with tools. public static let withoutRowID = TableOptions(rawValue: 1 << 2) #if GRDBCUSTOMSQLITE || GRDBCIPHER /// Creates a [STRICT](https://www.sqlite.org/stricttables.html) table. public static let strict = TableOptions(rawValue: 1 << 3) #else /// Creates a [STRICT](https://www.sqlite.org/stricttables.html) table. @available(iOS 15.4, macOS 12.4, tvOS 15.4, watchOS 8.5, *) // SQLite 3.37+ public static let strict = TableOptions(rawValue: 1 << 3) #endif } /// A `TableDefinition` lets you define the components of a database table. /// /// See the documentation of the `Database` /// ``Database/create(table:options:body:)`` method for usage information: /// /// ```swift /// try db.create(table: "player") { t in // t is TableDefinition /// t.autoIncrementedPrimaryKey("id") /// t.column("name", .text).notNull() /// } /// ``` /// /// ## Topics /// /// ### Define Columns /// /// - ``column(_:_:)`` /// - ``column(literal:)`` /// - ``column(sql:)`` /// - ``ColumnDefinition`` /// /// ### Define the Primary Key /// /// - ``autoIncrementedPrimaryKey(_:onConflict:)`` /// - ``primaryKey(_:_:onConflict:)`` /// - ``primaryKey(onConflict:body:)`` /// - ``primaryKey(_:onConflict:)`` /// /// ### Define a Foreign Key /// /// - ``belongsTo(_:inTable:onDelete:onUpdate:deferred:indexed:)`` /// - ``foreignKey(_:references:columns:onDelete:onUpdate:deferred:)`` /// - ``ForeignKeyDefinition`` /// /// ### Define a Unique Key /// /// - ``uniqueKey(_:onConflict:)`` /// /// ### Define Others Constraints /// /// - ``check(_:)-6u1za`` /// - ``check(_:)-jpcg`` /// - ``check(sql:)`` /// - ``constraint(literal:)`` /// - ``constraint(sql:)`` public final class TableDefinition { struct KeyConstraint { enum Component { case columnName(String) case columnDefinition(ColumnDefinition) case foreignKeyDefinition(ForeignKeyDefinition) } var components: [Component] var conflictResolution: Database.ConflictResolution? init(components: [Component], conflictResolution: Database.ConflictResolution?) { self.components = components self.conflictResolution = conflictResolution } init(columns: [String], conflictResolution: Database.ConflictResolution?) { let components = columns.map { name in Component.columnName(name) } self.init(components: components, conflictResolution: conflictResolution) } } enum ColumnComponent { case columnDefinition(ColumnDefinition) case columnLiteral(SQL) case foreignKeyDefinition(ForeignKeyDefinition) case foreignKeyConstraint(SQLForeignKeyConstraint) } let name: String let options: TableOptions var columnComponents: [ColumnComponent] = [] var inPrimaryKeyBody = false var primaryKeyConstraint: KeyConstraint? var uniqueKeyConstraints: [KeyConstraint] = [] var checkConstraints: [SQLExpression] = [] var literalConstraints: [SQL] = [] init(name: String, options: TableOptions) { self.name = name self.options = options } /// Appends an auto-incremented primary key column. /// /// For example: /// /// ```swift /// // CREATE TABLE player ( /// // id INTEGER PRIMARY KEY AUTOINCREMENT /// // ) /// try db.create(table: "player") { t in /// t.autoIncrementedPrimaryKey("id") /// } /// ``` /// /// The auto-incremented primary key is an integer primary key that /// automatically generates unused values when you do not explicitly /// provide one, and prevents the reuse of ids over the lifetime of /// the database. /// /// Related SQLite documentation: /// - /// - /// /// - parameter conflictResolution: An optional conflict resolution /// (see ). /// - returns: `self` so that you can further refine the column definition. @discardableResult public func autoIncrementedPrimaryKey( _ name: String, onConflict conflictResolution: Database.ConflictResolution? = nil) -> ColumnDefinition { column(name, .integer).primaryKey(onConflict: conflictResolution, autoincrement: true) } /// Appends a primary key column. /// /// For example: /// /// ```swift /// // CREATE TABLE country ( /// // isoCode TEXT NOT NULL PRIMARY KEY /// // ) /// try db.create(table: "country") { t in /// t.primaryKey("isoCode", .text) /// } /// ``` /// /// - parameter name: the column name. /// - parameter type: the column type. /// - returns: A ``ColumnDefinition`` that allows you to refine the /// column definition. @discardableResult public func primaryKey( _ name: String, _ type: Database.ColumnType, onConflict conflictResolution: Database.ConflictResolution? = nil) -> ColumnDefinition { let pk = column(name, type).primaryKey(onConflict: conflictResolution) if type == .integer { // INTEGER PRIMARY KEY is always NOT NULL return pk } else { // Add a not null constraint in order to fix an SQLite bug: // return pk.notNull() } } /// Defines the primary key on wrapped columns. /// /// For example: /// /// ```swift /// // CREATE TABLE passport ( /// // citizenId INTEGER NOT NULL, /// // countryCode TEXT NOT NULL, /// // issueDate DATE NOT NULL, /// // PRIMARY KEY (citizenId, countryCode) /// // ) /// try db.create(table: "passport") { t in /// t.primaryKey { /// t.column("citizenId", .integer) /// t.column("countryCode", .text) /// } /// t.column("issueDate", .date).notNull() /// } /// ``` /// /// A NOT NULL constraint is always added to the wrapped primary key columns. public func primaryKey( onConflict conflictResolution: Database.ConflictResolution? = nil, body: () throws -> Void) rethrows { guard primaryKeyConstraint == nil else { // Programmer error fatalError("can't define several primary keys") } primaryKeyConstraint = KeyConstraint(components: [], conflictResolution: conflictResolution) let oldValue = inPrimaryKeyBody inPrimaryKeyBody = true defer { inPrimaryKeyBody = oldValue } try body() } /// Appends a table column. /// /// For example: /// /// ```swift /// // CREATE TABLE player ( /// // name TEXT /// // ) /// try db.create(table: "player") { t in /// t.column("name", .text) /// } /// ``` /// /// Related SQLite documentation: /// /// - parameter name: the column name. /// - parameter type: the eventual column type. /// - returns: A ``ColumnDefinition`` that allows you to refine the /// column definition. @discardableResult public func column(_ name: String, _ type: Database.ColumnType? = nil) -> ColumnDefinition { let column = ColumnDefinition(name: name, type: type) columnComponents.append(.columnDefinition(column)) if inPrimaryKeyBody { // Add a not null constraint in order to fix an SQLite bug: // column.notNull() primaryKeyConstraint!.components.append(.columnDefinition(column)) } return column } /// Appends a table column. /// /// For example: /// /// ```swift /// // CREATE TABLE player ( /// // name TEXT /// // ) /// try db.create(table: "player") { t in /// t.column(sql: "name TEXT") /// } /// ``` public func column(sql: String) { column(literal: SQL(sql: sql)) } /// Appends a table column. /// /// ``SQL`` literals allow you to safely embed raw values in your SQL, /// without any risk of syntax errors or SQL injection: /// /// ```swift /// // CREATE TABLE player ( /// // name TEXT DEFAULT 'Anonymous' /// // ) /// let defaultName = "Anonymous" /// try db.create(table: "player") { t in /// t.column(literal: "name TEXT DEFAULT \(defaultName)") /// } /// ``` public func column(literal: SQL) { GRDBPrecondition(!inPrimaryKeyBody, "Primary key columns can not be defined with raw SQL") columnComponents.append(.columnLiteral(literal)) } /// Adds a primary key constraint. /// /// For example: /// /// ```swift /// // CREATE TABLE citizenship ( /// // citizenId INTEGER NOT NULL, /// // countryCode TEXT NOT NULL, /// // PRIMARY KEY (citizenId, countryCode) /// // ) /// try db.create(table: "citizenship") { t in /// t.column("citizenId", .integer).notNull() /// t.column("countryCode", .text).notNull() /// t.primaryKey(["citizenId", "countryCode"]) /// } /// ``` /// /// - important: Make sure you add not null constraints on your primary key /// columns, as in the above example, or SQLite will allow null values. /// See /// for more information. /// /// - parameter columns: The primary key columns. /// - parameter conflictResolution: An optional conflict resolution /// (see ). public func primaryKey(_ columns: [String], onConflict conflictResolution: Database.ConflictResolution? = nil) { guard primaryKeyConstraint == nil else { // Programmer error fatalError("can't define several primary keys") } primaryKeyConstraint = KeyConstraint(columns: columns, conflictResolution: conflictResolution) } /// Adds a unique constraint. /// /// For example: /// /// ```swift /// // CREATE TABLE place ( /// // latitude DOUBLE, /// // longitude DOUBLE, /// // UNIQUE (latitude, longitude) /// // ) /// try db.create(table: "place") { t in /// t.column("latitude", .double) /// t.column("longitude", .double) /// t.uniqueKey(["latitude", "longitude"]) /// } /// ``` /// /// When defining a unique constraint on a single column, you can use the /// ``ColumnDefinition/unique(onConflict:)`` shortcut: /// /// ```swift /// // CREATE TABLE player( /// // email TEXT UNIQUE /// // ) /// try db.create(table: "player") { t in /// t.column("email", .text).unique() /// } /// ``` /// /// Related SQLite documentation: /// /// - parameter columns: The unique key columns. /// - parameter conflictResolution: An optional conflict resolution /// (see ). public func uniqueKey(_ columns: [String], onConflict conflictResolution: Database.ConflictResolution? = nil) { uniqueKeyConstraints.append(KeyConstraint(columns: columns, conflictResolution: conflictResolution)) } /// Adds a foreign key. /// /// For example: /// /// ```swift /// // CREATE TABLE passport ( /// // issueDate DATE NOT NULL, /// // citizenId INTEGER NOT NULL, /// // countryCode INTEGER NOT NULL, /// // FOREIGN KEY (citizenId, countryCode) /// // REFERENCES citizenship(citizenId, countryCode) /// // ON DELETE CASCADE /// // ) /// try db.create(table: "passport") { t in /// t.column("issueDate", .date).notNull() /// t.column("citizenId", .integer).notNull() /// t.column("countryCode", .text).notNull() /// t.foreignKey(["citizenId", "countryCode"], references: "citizenship", onDelete: .cascade) /// } /// ``` /// /// When defining a foreign key on a single column, you can use the /// ``ColumnDefinition/references(_:column:onDelete:onUpdate:deferred:)`` /// shortcut: /// /// ```swift /// try db.create(table: "player") { t in /// t.column("teamId", .integer).references("team", onDelete: .cascade) /// } /// ``` /// /// Related SQLite documentation: /// /// - parameters: /// - columns: The foreign key columns. /// - table: The referenced table. /// - destinationColumns: The columns in the referenced table. If not /// specified, the columns of the primary key of the referenced table /// are used. /// - deleteAction: Optional action when the referenced row is deleted. /// - updateAction: Optional action when the referenced row is updated. /// - isDeferred: A boolean value indicating whether the foreign key /// constraint is deferred. /// See . public func foreignKey( _ columns: [String], references table: String, columns destinationColumns: [String]? = nil, onDelete deleteAction: Database.ForeignKeyAction? = nil, onUpdate updateAction: Database.ForeignKeyAction? = nil, deferred isDeferred: Bool = false) { let foreignKeyConstraint = SQLForeignKeyConstraint( columns: columns, destinationTable: table, destinationColumns: destinationColumns, deleteAction: deleteAction, updateAction: updateAction, isDeferred: isDeferred) columnComponents.append(.foreignKeyConstraint(foreignKeyConstraint)) } /// Declares an association to another table. /// /// `belongsTo` appends as many columns as there are columns in the /// primary key of the referenced table, and declares a foreign key that /// guarantees schema integrity. All primary keys are supported, /// including composite primary keys that span several columns, and the /// hidden `rowid` column. /// /// Added columns are prefixed with `name`, and end with the name of the /// matching column in the primary key of the referenced table. In the /// following example, `belongsTo("team")` adds a `teamId` column, and /// `belongsTo("country")` adds a `countryCode` column: /// /// ```swift /// try db.create(table: "team") { t in /// t.autoIncrementedPrimaryKey("id") /// } /// try db.create(table: "country") { t in /// t.primaryKey("code", .text) /// } /// /// // CREATE TABLE player ( /// // id INTEGER PRIMARY KEY AUTOINCREMENT, /// // teamId INTEGER REFERENCES team(id), /// // countryCode TEXT NOT NULL REFERENCES country(code), /// // ) /// try db.create(table: "player") { t in /// t.autoIncrementedPrimaryKey("id") /// t.belongsTo("team") /// t.belongsTo("country").notNull() /// } /// ``` /// /// When in doubt, you can check the names of the created columns: /// /// ```swift /// // Prints ["id", "teamId", "countryCode"] /// try print(db.columns(in: "player").map(\.name)) /// ``` /// /// Singular names can refer to database tables whose name is plural: /// /// ```swift /// try db.create(table: "teams") { t in /// t.autoIncrementedPrimaryKey("id") /// } /// try db.create(table: "countries") { t in /// t.primaryKey("code", .text) /// } /// /// // CREATE TABLE players ( /// // teamId INTEGER REFERENCES teams(id), /// // countryCode TEXT REFERENCES countries(code), /// // ) /// try db.create(table: "players") { t in /// t.belongsTo("team") /// t.belongsTo("country") /// } /// ``` /// /// When the added columns should have a custom prefix, specify an /// explicit table name: /// /// ```swift /// // CREATE TABLE player ( /// // id INTEGER PRIMARY KEY AUTOINCREMENT, /// // captainId INTEGER REFERENCES player(id), /// // ) /// try db.create(table: "player") { t in /// t.autoIncrementedPrimaryKey("id") /// t.belongsTo("captain", inTable: "player") /// } /// /// // CREATE TABLE book ( /// // id INTEGER PRIMARY KEY AUTOINCREMENT, /// // authorId INTEGER REFERENCES person(id), /// // translatorId INTEGER REFERENCES person(id), /// // title TEXT /// // ) /// try db.create(table: "book") { t in /// t.autoIncrementedPrimaryKey("id") /// t.belongsTo("author", inTable: "person") /// t.belongsTo("translator", inTable: "person") /// t.column("title", .text) /// } /// ``` /// /// Specify foreign key actions: /// /// ```swift /// try db.create(table: "player") { t in /// t.belongsTo("team", onDelete: .cascade) /// t.belongsTo("captain", inTable: "player", onDelete: .setNull) /// } /// ``` /// /// The added columns are indexed by default. You can disable this /// automatic index with the `indexed: false` option. You can also make /// this index unique with ``ForeignKeyDefinition/unique()``: /// /// ```swift /// try db.create(table: "player") { t in /// // teamId is not indexed /// t.belongsTo("team", indexed: false) /// /// // One single player per country /// t.belongsTo("country").unique() /// } /// ``` /// /// For more precision in the definition of foreign keys, use instead /// ``ColumnDefinition/references(_:column:onDelete:onUpdate:deferred:)`` /// or ``TableDefinition/foreignKey(_:references:columns:onDelete:onUpdate:deferred:)``. /// For example: /// /// ```swift /// try db.create(table: "player") { t in /// // This convenience method... /// t.belongsTo("team") /// /// // ... is equivalent to: /// t.column("teamId", .integer) /// .references("team") /// .indexed() /// /// // ... and is equivalent to: /// t.column("teamId", .integer).indexed() /// t.foreignKey(["teamId"], references: "team") /// } /// ``` /// /// See [Associations](https://github.com/groue/GRDB.swift/blob/master/Documentation/AssociationsBasics.md) /// for more information about foreign keys and associations. /// /// - parameters: /// - name: The name of the foreign key, used as a prefix for the /// added columns. /// - table: The referenced table. If nil, the referenced table is /// designated by the `name` parameter. /// - deleteAction: Optional action when the referenced row /// is deleted. /// - updateAction: Optional action when the referenced row /// is updated. /// - isDeferred: A boolean value indicating whether the foreign key /// constraint is deferred. /// See . /// - indexed: A boolean value indicating whether the foreign key is /// indexed. It is true by default. /// - returns: A ``ForeignKeyDefinition`` that allows you to refine the /// foreign key. @discardableResult public func belongsTo( _ name: String, inTable table: String? = nil, onDelete deleteAction: Database.ForeignKeyAction? = nil, onUpdate updateAction: Database.ForeignKeyAction? = nil, deferred isDeferred: Bool = false, indexed: Bool = true) -> ForeignKeyDefinition { let foreignKey = ForeignKeyDefinition( name: name, table: table, deleteAction: deleteAction, updateAction: updateAction, isIndexed: indexed && !inPrimaryKeyBody, isDeferred: isDeferred) columnComponents.append(.foreignKeyDefinition(foreignKey)) if inPrimaryKeyBody { // Add a not null constraint in order to fix an SQLite bug: // foreignKey.notNull() primaryKeyConstraint!.components.append(.foreignKeyDefinition(foreignKey)) } return foreignKey } /// Adds a check constraint. /// /// For example: /// /// ```swift /// // CREATE TABLE player ( /// // personalPhone TEXT, /// // workPhone TEXT, /// // CHECK personalPhone IS NOT NULL OR workPhone IS NOT NULL /// // ) /// try db.create(table: "player") { t in /// t.column("personalPhone", .text) /// t.column("workPhone", .text) /// let personalPhone = Column("personalPhone") /// let workPhone = Column("workPhone") /// t.check(personalPhone != nil || workPhone != nil) /// } /// ``` /// /// When defining a check constraint on a single column, you can use the /// ``ColumnDefinition/check(_:)`` shortcut: /// /// ```swift /// // CREATE TABLE player( /// // name TEXT CHECK (LENGTH(name) > 0) /// // ) /// try db.create(table: "player") { t in /// t.column("name", .text).check { length($0) > 0 } /// } /// ``` /// /// Related SQLite documentation: /// /// - parameter condition: The checked condition. @available(*, deprecated) public func check(_ condition: some SQLExpressible) { checkConstraints.append(condition.sqlExpression) } /// Adds a check constraint. /// /// For example: /// /// ```swift /// // CREATE TABLE player ( /// // personalPhone TEXT, /// // workPhone TEXT, /// // CHECK personalPhone IS NOT NULL OR workPhone IS NOT NULL /// // ) /// try db.create(table: "player") { t in /// t.column("personalPhone", .text) /// t.column("workPhone", .text) /// let personalPhone = Column("personalPhone") /// let workPhone = Column("workPhone") /// t.check(personalPhone != nil || workPhone != nil) /// } /// ``` /// /// When defining a check constraint on a single column, you can use the /// ``ColumnDefinition/check(_:)`` shortcut: /// /// ```swift /// // CREATE TABLE player( /// // name TEXT CHECK (LENGTH(name) > 0) /// // ) /// try db.create(table: "player") { t in /// t.column("name", .text).check { length($0) > 0 } /// } /// ``` /// /// Related SQLite documentation: /// /// - parameter condition: The checked condition. public func check(_ condition: some SQLSpecificExpressible) { checkConstraints.append(condition.sqlExpression) } /// Adds a check constraint. /// /// For example: /// /// ```swift /// // CREATE TABLE player ( /// // personalPhone TEXT, /// // workPhone TEXT, /// // CHECK personalPhone IS NOT NULL OR workPhone IS NOT NULL /// // ) /// try db.create(table: "player") { t in /// t.column("personalPhone", .text) /// t.column("workPhone", .text) /// t.check(sql: "personalPhone IS NOT NULL OR workPhone IS NOT NULL") /// } /// ``` /// /// When defining a check constraint on a single column, you can use the /// ``ColumnDefinition/check(sql:)`` shortcut: /// /// ```swift /// // CREATE TABLE player( /// // name TEXT CHECK (LENGTH(name) > 0) /// // ) /// try db.create(table: "player") { t in /// t.column("name", .text).check(sql: "LENGTH(name) > 0") /// } /// ``` /// /// Related SQLite documentation: /// /// - parameter sql: An SQL snippet public func check(sql: String) { checkConstraints.append(SQL(sql: sql).sqlExpression) } /// Appends a table constraint. /// /// For example: /// /// ```swift /// // CREATE TABLE player ( /// // score INTEGER, /// // CHECK (score >= 0) /// // ) /// try db.create(table: "player") { t in /// t.column("score", .integer) /// t.constraint(sql: "CHECK (score >= 0)") /// } /// ``` public func constraint(sql: String) { literalConstraints.append(SQL(sql: sql)) } /// Appends a table constraint. /// /// ``SQL`` literals allow you to safely embed raw values in your SQL, /// without any risk of syntax errors or SQL injection: /// /// ```swift /// // CREATE TABLE player ( /// // score INTEGER, /// // CHECK (score >= 0) /// // ) /// let minScore = 0 /// try db.create(table: "player") { t in /// t.column("score", .integer) /// t.constraint(literal: "CHECK (score >= \(minScore))") /// } /// ``` public func constraint(literal: SQL) { literalConstraints.append(literal) } } // Explicit non-conformance to Sendable: `TableDefinition` is a mutable // class and there is no known reason for making it thread-safe. @available(*, unavailable) extension TableDefinition: Sendable { }