diff --git a/GRDB.xcworkspace/contents.xcworkspacedata b/GRDB.xcworkspace/contents.xcworkspacedata
index 180e0c43..61082ffc 100644
--- a/GRDB.xcworkspace/contents.xcworkspacedata
+++ b/GRDB.xcworkspace/contents.xcworkspacedata
@@ -10,9 +10,6 @@
-
-
diff --git a/GRDB/Core/Database+Statements.swift b/GRDB/Core/Database+Statements.swift
index c9958165..11362e4f 100644
--- a/GRDB/Core/Database+Statements.swift
+++ b/GRDB/Core/Database+Statements.swift
@@ -290,7 +290,7 @@ struct StatementCache {
let statement: SelectStatement
if #available(iOS 12.0, OSX 10.14, watchOS 5.0, *) {
// SQLite 3.24.0 or more
- statement = try db.makeSelectStatement(sql, prepFlags: SQLITE_PREPARE_PERSISTENT)
+ statement = try db.makeSelectStatement(sql: sql, prepFlags: SQLITE_PREPARE_PERSISTENT)
} else {
// SQLite 3.19.3 or less
statement = try db.makeSelectStatement(sql: sql)
diff --git a/GRDB/QueryInterface/VirtualTableModule.swift b/GRDB/QueryInterface/VirtualTableModule.swift
index ae6d817d..06eaa3ea 100644
--- a/GRDB/QueryInterface/VirtualTableModule.swift
+++ b/GRDB/QueryInterface/VirtualTableModule.swift
@@ -55,7 +55,6 @@ extension Database {
/// - module: The name of an SQLite virtual table module.
/// - throws: A DatabaseError whenever an SQLite error occurs.
public func create(virtualTable name: String, ifNotExists: Bool = false, using module: String) throws {
- // TODO? refactor with SQLLiteral
var chunks: [String] = []
chunks.append("CREATE VIRTUAL TABLE")
if ifNotExists {
diff --git a/Playgrounds/MyPlayground.playground/Contents.swift b/Playgrounds/MyPlayground.playground/Contents.swift
index 400de1c3..067ff1a1 100644
--- a/Playgrounds/MyPlayground.playground/Contents.swift
+++ b/Playgrounds/MyPlayground.playground/Contents.swift
@@ -13,9 +13,9 @@ try! dbQueue.inDatabase { db in
t.column("name", .text)
}
- try db.execute(rawSQL: "INSERT INTO persons (name) VALUES (?)", arguments: ["Arthur"])
- try db.execute(rawSQL: "INSERT INTO persons (name) VALUES (?)", arguments: ["Barbara"])
+ try db.execute(sql: "INSERT INTO persons (name) VALUES (?)", arguments: ["Arthur"])
+ try db.execute(sql: "INSERT INTO persons (name) VALUES (?)", arguments: ["Barbara"])
- let names = try String.fetchAll(db, rawSQL: "SELECT name FROM persons")
+ let names = try String.fetchAll(db, sql: "SELECT name FROM persons")
print(names)
}
diff --git a/Playgrounds/Record.playground/Contents.swift b/Playgrounds/Record.playground/Contents.swift
deleted file mode 100644
index bd5b3aa4..00000000
--- a/Playgrounds/Record.playground/Contents.swift
+++ /dev/null
@@ -1,136 +0,0 @@
-//: To run this playground:
-//:
-//: - Open GRDB.xcworkspace
-//: - Select the GRDBOSX scheme: menu Product > Scheme > GRDBOSX
-//: - Build: menu Product > Build
-//: - Select the playground in the Playgrounds Group
-//: - Run the playground
-//:
-//: Record
-//: ======
-//:
-//: This playground is a demo of the Record class, the type that provides fetching methods, persistence methods, and change tracking.
-
-import GRDB
-
-
-//: ## Setup
-//:
-//: First open an in-memory database, with a trace function that prints all SQL statements.
-
-var configuration = Configuration()
-configuration.trace = { print($0) }
-let dbQueue = DatabaseQueue(configuration: configuration)
-
-//: Create a database table which stores persons.
-
-try dbQueue.inDatabase { db in
- try db.execute(
- "CREATE TABLE persons (" +
- "id INTEGER PRIMARY KEY, " +
- "firstName TEXT, " +
- "lastName TEXT" +
- ")")
-}
-
-
-//: ## Subclassing Record
-//:
-//: The Person class is a subclass of Record, with regular properties, and a regular initializer:
-
-class Person : Record {
- var id: Int64?
- var firstName: String?
- var lastName: String?
-
- var fullName: String {
- return [firstName, lastName].flatMap { $0 }.joined(separator: " ")
- }
-
- init(firstName: String?, lastName: String?) {
- self.id = nil
- self.firstName = firstName
- self.lastName = lastName
- super.init()
- }
-
-
-//: Subclasses of Record have to override the methods that define how they interact with the database.
-//:
-//: 1. The table name:
-
- override class var databaseTableName: String {
- return "persons"
- }
-
-//: 2. How to build a Person from a database row:
-
- required init(row: Row) {
- id = row["id"]
- firstName = row["firstName"]
- lastName = row["lastName"]
- super.init(row: row)
- }
-
-//: 3. The dictionary of values that are stored in the database:
-
- override func encode(to container: inout PersistenceContainer) {
- container["id"] = id
- container["firstName"] = firstName
- container["lastName"] = lastName
- }
-
-//: 4. When relevant, update the person's id after a database row has been inserted:
-
- override func didInsert(with rowID: Int64, for column: String?) {
- id = rowID
- }
-}
-
-
-//: ## Insert Records
-//:
-//: Persons are regular objects, that you can freely create:
-
-let arthur = Person(firstName: "Arthur", lastName: "Miller")
-let barbra = Person(firstName: "Barbra", lastName: "Streisand")
-let cinderella = Person(firstName: "Cinderella", lastName: nil)
-
-//: They are not stored in the database yet. Insert them:
-
-try dbQueue.inDatabase { db in
- try arthur.insert(db)
- try barbra.insert(db)
- try cinderella.insert(db)
-}
-
-
-//: ## Fetching Records
-
-try dbQueue.inDatabase { db in
-
- //: Fetch records from the database:
- let allPersons = try Person.fetchAll(db)
-
- //: Fetch record by primary key:
- let person = try Person.fetchOne(db, key: arthur.id)!
- person.fullName
-
- //: Fetch persons with an SQL query:
- let millers = try Person.fetchAll(db, rawSQL: "SELECT * FROM persons WHERE lastName = ?", arguments: ["Miller"])
- millers.first!.fullName
-
-
- //: To fetch persons using the query interface, you need some colums that can filter or sort:
-
- struct Col {
- static let firstName = Column("firstName")
- static let lastName = Column("lastName")
- }
-
- //: Sort
- let personsSortedByName = try Person.order(Col.firstName, Col.lastName).fetchAll(db)
-
- //: Filter
- let streisands = try Person.filter(Col.lastName == "Streisand").fetchAll(db)
-}
diff --git a/Playgrounds/Record.playground/contents.xcplayground b/Playgrounds/Record.playground/contents.xcplayground
deleted file mode 100644
index fd676d5b..00000000
--- a/Playgrounds/Record.playground/contents.xcplayground
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/Playgrounds/Tour.playground/Contents.swift b/Playgrounds/Tour.playground/Contents.swift
index 2f313729..32ec34a0 100644
--- a/Playgrounds/Tour.playground/Contents.swift
+++ b/Playgrounds/Tour.playground/Contents.swift
@@ -9,7 +9,7 @@
//: Tour
//: ======
//:
-//: This playground is a tour of GRDB.
+//: This playground is a quick tour of GRDB.
import GRDB
import CoreLocation
@@ -26,44 +26,45 @@ let dbQueue = DatabaseQueue(configuration: configuration)
//: Execute SQL queries
try dbQueue.inDatabase { db in
- try db.execute(
- "CREATE TABLE pointOfInterests (" +
- "id INTEGER PRIMARY KEY, " +
- "title TEXT, " +
- "favorite BOOLEAN NOT NULL, " +
- "latitude DOUBLE NOT NULL, " +
- "longitude DOUBLE NOT NULL" +
- ")")
+ try db.execute(sql: """
+ CREATE TABLE place (
+ id INTEGER PRIMARY KEY,
+ title TEXT,
+ favorite BOOLEAN NOT NULL,
+ latitude DOUBLE NOT NULL,
+ longitude DOUBLE NOT NULL
+ )
+ """)
- try db.execute(
- "INSERT INTO pointOfInterests (title, favorite, latitude, longitude) " +
- "VALUES (?, ?, ?, ?)",
- arguments: ["Paris", true, 48.85341, 2.3488])
+ try db.execute(sql: """
+ INSERT INTO place (title, favorite, latitude, longitude)
+ VALUES (?, ?, ?, ?)
+ """, arguments: ["Paris", true, 48.85341, 2.3488])
let parisId = db.lastInsertedRowID
}
//: Fetch database rows and values
-try dbQueue.inDatabase { db in
- let rows = try Row.fetchCursor(db, rawSQL: "SELECT * FROM pointOfInterests")
+try! dbQueue.inDatabase { db in
+ let rows = try Row.fetchCursor(db, sql: "SELECT * FROM place")
while let row = try rows.next() {
let title: String = row["title"]
let favorite: Bool = row["favorite"]
- let coordinate = CLLocationCoordinate2DMake(
- row["latitude"],
- row["longitude"])
+ let coordinate = CLLocationCoordinate2D(
+ latitude: row["latitude"],
+ longitude: row["longitude"])
print("Fetched", title, favorite, coordinate)
}
- let poiCount = try Int.fetchOne(db, rawSQL: "SELECT COUNT(*) FROM pointOfInterests")! // Int
- let poiTitles = try String.fetchAll(db, rawSQL: "SELECT title FROM pointOfInterests") // [String]
+ let placeCount = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM place")! // Int
+ let placeTitles = try String.fetchAll(db, sql: "SELECT title FROM place") // [String]
}
//: Insert and fetch records
-struct PointOfInterest {
+struct Place {
var id: Int64?
var title: String?
var favorite: Bool
@@ -71,7 +72,7 @@ struct PointOfInterest {
}
// Adopt FetchableRecord
-extension PointOfInterest : FetchableRecord {
+extension Place : FetchableRecord {
init(row: Row) {
id = row["id"]
title = row["title"]
@@ -83,12 +84,12 @@ extension PointOfInterest : FetchableRecord {
}
// Adopt TableRecord
-extension PointOfInterest : TableRecord {
- static let databaseTableName = "pointOfInterests"
+extension Place : TableRecord {
+ static let databaseTableName = "place"
}
// Adopt MutablePersistableRecord
-extension PointOfInterest : MutablePersistableRecord {
+extension Place : MutablePersistableRecord {
func encode(to container: inout PersistenceContainer) {
container["id"] = id
container["title"] = title
@@ -103,7 +104,7 @@ extension PointOfInterest : MutablePersistableRecord {
}
try dbQueue.inDatabase { db in
- var berlin = PointOfInterest(
+ var berlin = Place(
id: nil,
title: "Berlin",
favorite: false,
@@ -116,7 +117,7 @@ try dbQueue.inDatabase { db in
try berlin.update(db)
// Fetch from SQL
- let pois = try PointOfInterest.fetchAll(db, rawSQL: "SELECT * FROM pointOfInterests") // [PointOfInterest]
+ let places = try Place.fetchAll(db, sql: "SELECT * FROM place") // [Place]
//: Avoid SQL with the query interface:
@@ -124,10 +125,10 @@ try dbQueue.inDatabase { db in
let title = Column("title")
let favorite = Column("favorite")
- berlin = try PointOfInterest.filter(title == "Berlin").fetchOne(db)! // PointOfInterest
- let paris = try PointOfInterest.fetchOne(db, key: 1) // PointOfInterest?
- let favoritePois = try PointOfInterest // [PointOfInterest]
- .filter(favorite)
+ berlin = try Place.filter(title == "Berlin").fetchOne(db)! // Place
+ let paris = try Place.fetchOne(db, key: 1) // Place?
+ let favoritePlaces = try Place // [Place]
+ .filter(favorite == true)
.order(title)
.fetchAll(db)
}
diff --git a/Playgrounds/TransactionObserver.playground/Contents.swift b/Playgrounds/TransactionObserver.playground/Contents.swift
index cbffb9c6..e5aa0429 100644
--- a/Playgrounds/TransactionObserver.playground/Contents.swift
+++ b/Playgrounds/TransactionObserver.playground/Contents.swift
@@ -13,26 +13,24 @@ import GRDB
let dbQueue = DatabaseQueue() // Memory database
var migrator = DatabaseMigrator()
-migrator.registerMigration("createPersons") { db in
- try db.execute(rawSQL: """
- CREATE TABLE persons (
- id INTEGER PRIMARY KEY,
- name TEXT NOT NULL)
- """)
+migrator.registerMigration("createPerson") { db in
+ try db.create(table: "person") { t in
+ t.autoIncrementedPrimaryKey("id")
+ t.column("name", .text).notNull()
+ }
}
-migrator.registerMigration("createPets") { db in
- try db.execute(rawSQL: """
- CREATE TABLE pets (
- id INTEGER PRIMARY KEY,
- name TEXT,
- ownerId INTEGER NOT NULL REFERENCES persons(id) ON DELETE CASCADE)
- """)
+migrator.registerMigration("createPet") { db in
+ try db.create(table: "pet") { t in
+ t.autoIncrementedPrimaryKey("id")
+ t.column("name", .text).notNull()
+ t.column("ownerId", .integer).references("person", onDelete: .cascade)
+ }
}
try! migrator.migrate(dbQueue)
+//
-/// TableChangeObserver prints on the main thread the changes database tables.
class TableChangeObserver : NSObject, TransactionObserver {
private var changedTableNames: Set = []
@@ -44,9 +42,6 @@ class TableChangeObserver : NSObject, TransactionObserver {
changedTableNames.insert(event.tableName)
}
- func databaseWillCommit() throws {
- }
-
func databaseDidCommit(_ db: Database) {
print("Changed table(s): \(changedTableNames.joined(separator: ", "))")
changedTableNames = []
@@ -57,34 +52,35 @@ class TableChangeObserver : NSObject, TransactionObserver {
}
}
-
-
-// Register observer
-
let observer = TableChangeObserver()
dbQueue.add(transactionObserver: observer)
//
-print("-- Changes 1")
-try! dbQueue.inDatabase { db in
- try db.execute(rawSQL: "INSERT INTO persons (name) VALUES (?)", arguments: ["Arthur"])
+print("-- Changes without transaction")
+try dbQueue.inDatabase { db in
+ try db.execute(sql: "INSERT INTO person (name) VALUES (?)", arguments: ["Arthur"])
let arthurId = db.lastInsertedRowID
- try db.execute(rawSQL: "INSERT INTO persons (name) VALUES (?)", arguments: ["Barbara"])
- try db.execute(rawSQL: "INSERT INTO pets (ownerId, name) VALUES (?, ?)", arguments: [arthurId, "Barbara"])
+ try db.execute(sql: "INSERT INTO person (name) VALUES (?)", arguments: ["Barbara"])
+ try db.execute(sql: "INSERT INTO pet (ownerId, name) VALUES (?, ?)", arguments: [arthurId, "Barbara"])
+ try db.execute(sql: "DELETE FROM person WHERE id = ?", arguments: [arthurId])
}
-print("-- Changes 2")
+print("-- Rollbacked changes")
try dbQueue.inTransaction { db in
- try db.execute(rawSQL: "INSERT INTO persons (name) VALUES ('Arthur')")
- try db.execute(rawSQL: "INSERT INTO persons (name) VALUES ('Barbara')")
+ try db.execute(sql: "INSERT INTO person (name) VALUES ('Arthur')")
+ try db.execute(sql: "INSERT INTO person (name) VALUES ('Barbara')")
return .rollback
}
-print("-- Changes 3")
+print("-- Changes wrapped in a transaction")
try dbQueue.write { db in
- try db.execute(rawSQL: "DELETE FROM persons")
- try db.execute(rawSQL: "DELETE FROM pets")
+ try db.execute(sql: "DELETE FROM person")
+ try db.execute(sql: "INSERT INTO person (name) VALUES (?)", arguments: ["Arthur"])
+ let arthurId = db.lastInsertedRowID
+ try db.execute(sql: "INSERT INTO person (name) VALUES (?)", arguments: ["Barbara"])
+ try db.execute(sql: "INSERT INTO pet (ownerId, name) VALUES (?, ?)", arguments: [arthurId, "Barbara"])
+ try db.execute(sql: "DELETE FROM person WHERE id = ?", arguments: [arthurId])
}