GRDB.swift/Playgrounds/JSONSynchronization.playground/Contents.swift
2018-10-14 12:49:37 -07:00

145 lines
4.7 KiB
Swift

//: 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
//:
//: This sample code shows how to use GRDB to synchronize a database table
//: with a JSON payload. We use as few SQL queries as possible:
//:
//: - Only one SELECT query.
//: - One query per insert, delete, and update.
//: - Useless UPDATE statements are avoided.
import Foundation
import GRDB
// Open an in-memory database that logs all its SQL statements
var configuration = Configuration()
configuration.trace = { print($0) }
let dbQueue = DatabaseQueue(configuration: configuration)
// Create the database table to store the players
try dbQueue.inDatabase { db in
try db.create(table: "player") { t in
t.column("id", .integer).primaryKey()
t.column("name", .text)
t.column("score", .integer)
}
}
// Define the Player `Codable` that is a `FetchableRecord` and `MutablePersistableRecord`.
struct Player: Codable, FetchableRecord, MutablePersistableRecord {
var id: Int64
var name: String
var score: Int
// Add ColumnExpression to Codable's CodingKeys so that we can use them
// as database columns.
//
// See https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types
// for more information about CodingKeys.
private enum CodingKeys: String, CodingKey, ColumnExpression {
case id, name, score
}
// Update a player id after it has been inserted in the database.
mutating func didInsert(with rowID: Int64, for column: String?) {
id = rowID
}
}
extension Player {
static func orderedById() -> QueryInterfaceRequest<Player> {
return order(CodingKeys.id)
}
}
// This is just the container of our incoming JSON
struct PlayersToSync: Codable {
let players: [Player]
}
// Synchronizes the players table with a JSON payload
func synchronizePlayers(with jsonString: String, in db: Database) throws {
let jsonData = jsonString.data(using: .utf8)!
let dataToSync = try JSONDecoder().decode(PlayersToSync.self, from: jsonData)
// Sort new players to sync by id:
let playersToSync = dataToSync.players.sorted { $0.id < $1.id }
// Sort database players by id:
let players = try Player.orderedById().fetchAll(db)
// Now that both lists are sorted by id, we can compare them with
// the sortedMerge() function (see https://gist.github.com/groue/7e8510849ded36f7d770).
//
// We'll delete, insert or update players, depending on their presence
// in either lists.
for mergeStep in sortedMerge(
left: players, // Database players
right: playersToSync, // PlayersToSync players (Decoded)
leftKey: { $0.id }, // The id of a database player
rightKey: { $0.id }) // The id of a PlayersToSync player
{
switch mergeStep {
case .left(let player):
// Delete database player without matching JSON player:
try player.delete(db)
case .right(var playerToSync):
// Insert the Codable player without matching database player:
try playerToSync.insert(db)
case .common(let player, let playerToSync):
// Update database player with its JSON counterpart:
try playerToSync.updateChanges(db, from: player)
}
}
}
do {
let jsonString1 = """
{
"players": [
{ "id": 1, "name": "Arthur", "score": 1000},
{ "id": 2, "name": "Barbara", "score": 2000},
{ "id": 3, "name": "Craig", "score": 500},
]
}
"""
print("---\nImport \(jsonString1)")
try dbQueue.inDatabase { db in
// SELECT * FROM player ORDER BY id
// INSERT INTO "player" ("id", "name", "score") VALUES (1,'Arthur',1000)
// INSERT INTO "player" ("id", "name", "score") VALUES (2,'Barbara',2000)
// INSERT INTO "player" ("id", "name", "score") VALUES (3,'Craig',500)
try synchronizePlayers(with: jsonString1, in: db)
}
}
do {
let jsonString2 = """
{
"players": [
{ "id": 2, "name": "Barbara", "score": 3000},
{ "id": 3, "name": "Craig", "score": 500},
{ "id": 4, "name": "Daniel", "score": 1500},
]
}
"""
print("---\nImport \(jsonString2)")
try dbQueue.inDatabase { db in
// SELECT * FROM player ORDER BY id
// DELETE FROM "player" WHERE "id"=1
// UPDATE "player" SET "score"=3000 WHERE "id"=2
// INSERT INTO "player" ("id", "name", "score") VALUES (4,'Daniel',1500)
try synchronizePlayers(with: jsonString2, in: db)
}
}