Up to date playgrounds
This commit is contained in:
parent
df05e940e8
commit
7e9e50e52d
3
GRDB.xcworkspace/contents.xcworkspacedata
generated
3
GRDB.xcworkspace/contents.xcworkspacedata
generated
@ -10,9 +10,6 @@
|
||||
<FileRef
|
||||
location = "group:JSONSynchronization.playground">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Record.playground">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:TransactionObserver.playground">
|
||||
</FileRef>
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<playground version='5.0' target-platform='osx' display-mode='raw'>
|
||||
<timeline fileName='timeline.xctimeline'/>
|
||||
</playground>
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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<String> = []
|
||||
|
||||
@ -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])
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user