Skip to content

Commit

Permalink
Merge pull request #337 from groue/GRDB3-autoIncrementedPrimaryKey
Browse files Browse the repository at this point in the history
Foster auto-incremented primary keys
  • Loading branch information
groue authored Apr 22, 2018
2 parents c21a330 + 7aacaef commit ddc5516
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 12 deletions.
2 changes: 1 addition & 1 deletion GRDB/Core/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ extension Database {
/// An SQL column type.
///
/// try db.create(table: "players") { t in
/// t.column("id", .integer).primaryKey()
/// t.autoIncrementedPrimaryKey("id")
/// t.column("title", .text)
/// }
///
Expand Down
38 changes: 35 additions & 3 deletions GRDB/QueryInterface/TableDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ extension Database {
/// Creates a database table.
///
/// try db.create(table: "pointOfInterests") { t in
/// t.column("id", .integer).primaryKey()
/// t.autoIncrementedPrimaryKey("id")
/// t.column("title", .text)
/// t.column("favorite", .boolean).notNull().default(false)
/// t.column("longitude", .double).notNull()
Expand Down Expand Up @@ -35,7 +35,7 @@ extension Database {
/// Creates a database table.
///
/// try db.create(table: "pointOfInterests") { t in
/// t.column("id", .integer).primaryKey()
/// t.autoIncrementedPrimaryKey("id")
/// t.column("title", .text)
/// t.column("favorite", .boolean).notNull().default(false)
/// t.column("longitude", .double).notNull()
Expand Down Expand Up @@ -67,7 +67,7 @@ extension Database {
/// Creates a database table.
///
/// try db.create(table: "pointOfInterests") { t in
/// t.column("id", .integer).primaryKey()
/// t.autoIncrementedPrimaryKey("id")
/// t.column("title", .text)
/// t.column("favorite", .boolean).notNull().default(false)
/// t.column("longitude", .double).notNull()
Expand Down Expand Up @@ -248,6 +248,38 @@ public final class TableDefinition {
self.withoutRowID = withoutRowID
}

/// Defines the auto-incremented primary key.
///
/// try db.create(table: "players") { t in
/// t.autoIncrementedPrimaryKey("id")
/// }
///
/// The auto-incremented primary key is an integer primary key that
/// automatically generates unused values when you do not explicitely
/// provide one, and prevents the reuse of ids over the lifetime of
/// the database.
///
/// **It is the preferred way to define a numeric primary key**.
///
/// The fact that an auto-incremented primary key prevents the reuse of
/// ids is an excellent guard against data races that could happen when your
/// application processes ids in an asynchronous way. The auto-incremented
/// primary key provides the guarantee that a given id can't reference a row
/// that is different from the one it used to be at the beginning of the
/// asynchronous process, even if this row gets deleted and a new one is
/// inserted in between.
///
/// See https://www.sqlite.org/lang_createtable.html#primkeyconst and
/// https://www.sqlite.org/lang_createtable.html#rowid
///
/// - parameter conflitResolution: An optional conflict resolution
/// (see https://www.sqlite.org/lang_conflict.html).
/// - returns: Self so that you can further refine the column definition.
@discardableResult
public func autoIncrementedPrimaryKey(_ name: String, onConflict conflictResolution: Database.ConflictResolution? = nil) -> ColumnDefinition {
return column(name, .integer).primaryKey(onConflict: conflictResolution, autoincrement: true)
}

/// Appends a table column.
///
/// try db.create(table: "players") { t in
Expand Down
2 changes: 1 addition & 1 deletion GRDB/Record/TableRecord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ extension TableRecord {
///
/// try dbQueue.write { db in
/// try db.create(table: "players") { t in
/// t.column("id", .integer).primaryKey()
/// t.autoIncrementedPrimaryKey("id")
/// t.column("name", .text)
/// t.column("score", .integer)
/// }
Expand Down
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ Avoid SQL with the [query interface](#the-query-interface):
```swift
try dbQueue.write { db in
try db.create(table: "places") { t in
t.column("id", .integer).primaryKey()
t.autoIncrementedPrimaryKey("id")
t.column("title", .text).notNull()
t.column("favorite", .boolean).notNull().defaults(to: false)
t.column("longitude", .double).notNull()
Expand Down Expand Up @@ -1707,7 +1707,7 @@ Int.fetchOne(db, Player.select(maxLength.apply(nameColumn))) // Int?

```swift
try db.create(table: "players") { t in
t.column("id", .integer).primaryKey()
t.autoIncrementedPrimaryKey("id")
t.column("name", .text)
}

Expand Down Expand Up @@ -2660,7 +2660,7 @@ The [five different policies](https://www.sqlite.org/lang_conflict.html) are: ab
// email TEXT UNIQUE ON CONFLICT REPLACE
// )
try db.create(table: "players") { t in
t.column("id", .integer).primaryKey()
t.autoIncrementedPrimaryKey("id")
t.column("email", .text).unique(onConflict: .replace) // <--
}

Expand All @@ -2678,7 +2678,7 @@ The [five different policies](https://www.sqlite.org/lang_conflict.html) are: ab
// email TEXT UNIQUE
// )
try db.create(table: "players") { t in
t.column("id", .integer).primaryKey()
t.autoIncrementedPrimaryKey("id")
t.column("email", .text)
}

Expand Down Expand Up @@ -3013,7 +3013,7 @@ Once granted with a [database connection](#database-connections), you can setup
// longitude DOUBLE NOT NULL
// )
try db.create(table: "places") { t in
t.column("id", .integer).primaryKey()
t.autoIncrementedPrimaryKey("id")
t.column("title", .text)
t.column("favorite", .boolean).notNull().defaults(to: false)
t.column("longitude", .double).notNull()
Expand Down Expand Up @@ -3067,8 +3067,11 @@ Define **not null** columns, and set **default** values:
Use an individual column as **primary**, **unique**, or **foreign key**. When defining a foreign key, the referenced column is the primary key of the referenced table (unless you specify otherwise):

```swift
// id INTEGER PRIMARY KEY,
t.column("id", .integer).primaryKey()
// id INTEGER PRIMARY KEY AUTOINCREMENT,
t.autoIncrementedPrimaryKey("id")

// uuid TEXT PRIMARY KEY,
t.column("uuid", .text).primaryKey()

// email TEXT UNIQUE,
t.column("email", .text).unique()
Expand All @@ -3077,6 +3080,10 @@ Use an individual column as **primary**, **unique**, or **foreign key**. When de
t.column("countryCode", .text).references("countries", onDelete: .cascade)
```

> :bulb: **Tip**: when you need an integer primary key that automatically generates unique values, it is highly recommended that you use the `autoIncrementedPrimaryKey` method.
>
> Such primary key prevents the reuse of ids, and is an excellent guard against data races that could happen when your application processes ids in an asynchronous way. The auto-incremented primary key provides the guarantee that a given id can't reference a row that is different from the one it used to be at the beginning of the asynchronous process, even if this row gets deleted and a new one is inserted in between.

**Create an index** on the column:

```swift
Expand Down
26 changes: 26 additions & 0 deletions Tests/GRDBTests/TableDefinitionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,32 @@ class TableDefinitionTests: GRDBTestCase {
}
}

func testAutoIncrementedPrimaryKey() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inTransaction { db in
try db.create(table: "test") { t in
t.autoIncrementedPrimaryKey("id")
}
assertEqualSQL(lastSQLQuery, """
CREATE TABLE "test" (\
"id" INTEGER PRIMARY KEY AUTOINCREMENT\
)
""")
return .rollback
}
try dbQueue.inTransaction { db in
try db.create(table: "test") { t in
t.autoIncrementedPrimaryKey("id", onConflict: .fail)
}
assertEqualSQL(lastSQLQuery, """
CREATE TABLE "test" (\
"id" INTEGER PRIMARY KEY ON CONFLICT FAIL AUTOINCREMENT\
)
""")
return .rollback
}
}

func testColumnPrimaryKeyOptions() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inTransaction { db in
Expand Down

0 comments on commit ddc5516

Please sign in to comment.