Skip to content

Commit 945ce71

Browse files
committed
Add Seeds type for preparings seeds for a database
This type is mainly to simplify downstream integrations like `SharingGRDB`, which already provides such a tool defined directly on `GRDB.Database`. That tool will be able to simply use this tool, instead, and future integrations can also leverage it.
1 parent 31b9cce commit 945ce71

File tree

2 files changed

+109
-0
lines changed

2 files changed

+109
-0
lines changed

Sources/StructuredQueriesCore/Documentation.docc/Articles/InsertStatements.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,3 +447,7 @@ clause:
447447
### Statement types
448448

449449
- ``Insert``
450+
451+
### Seeding a database
452+
453+
- ``Seeds``
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/// A type that can prepare statements to seed a database's initial state.
2+
public struct Seeds: Sequence {
3+
let seeds: [any Table]
4+
5+
/// Prepares a number of batched insert statements to be executed.
6+
///
7+
/// ```swift
8+
/// Seeds {
9+
/// SyncUp(id: 1, seconds: 60, theme: .appOrange, title: "Design")
10+
/// SyncUp(id: 2, seconds: 60 * 10, theme: .periwinkle, title: "Engineering")
11+
/// SyncUp(id: 3, seconds: 60 * 30, theme: .poppy, title: "Product")
12+
///
13+
/// for name in ["Blob", "Blob Jr", "Blob Sr", "Blob Esq", "Blob III", "Blob I"] {
14+
/// Attendee.Draft(name: name, syncUpID: 1)
15+
/// }
16+
/// for name in ["Blob", "Blob Jr"] {
17+
/// Attendee.Draft(name: name, syncUpID: 2)
18+
/// }
19+
/// for name in ["Blob Sr", "Blob Jr"] {
20+
/// Attendee.Draft(name: name, syncUpID: 3)
21+
/// }
22+
/// }
23+
/// // INSERT INTO "syncUps"
24+
/// // ("id", "seconds", "theme", "title")
25+
/// // VALUES
26+
/// // (1, 60, 'appOrange', 'Design'),
27+
/// // (2, 600, 'periwinkle', 'Engineering'),
28+
/// // (3, 1800, 'poppy', 'Product');
29+
/// // INSERT INTO "attendees"
30+
/// // ("id", "name", "syncUpID")
31+
/// // VALUES
32+
/// // (NULL, 'Blob', 1),
33+
/// // (NULL, 'Blob Jr', 1),
34+
/// // (NULL, 'Blob Sr', 1),
35+
/// // (NULL, 'Blob Esq', 1),
36+
/// // (NULL, 'Blob III', 1),
37+
/// // (NULL, 'Blob I', 1),
38+
/// // (NULL, 'Blob', 2),
39+
/// // (NULL, 'Blob Jr', 2),
40+
/// // (NULL, 'Blob Sr', 3),
41+
/// // (NULL, 'Blob Jr', 3);
42+
/// ```
43+
///
44+
/// And then you can iterate over each insert statement and execute it given a database
45+
/// connection. For example, using the [SharingGRDB][] driver:
46+
///
47+
/// ```swift
48+
/// try database.write { db in
49+
/// let seeds = Seeds {
50+
/// // ...
51+
/// }
52+
/// for insert in seeds {
53+
/// try db.execute(insert)
54+
/// }
55+
/// }
56+
/// ```
57+
///
58+
/// > Tip: [SharingGRDB][] extends GRDB's `Database` connection with a `seed` method that can
59+
/// > build and insert records in a single step:
60+
/// >
61+
/// > ```swift
62+
/// > try db.seed {
63+
/// > // ...
64+
/// > }
65+
/// > ```
66+
///
67+
/// [SharingGRDB]: https://github.com/pointfreeco/sharing-grdb
68+
///
69+
/// - Parameter build: A result builder closure that prepares statements to insert every built row.
70+
public init(@InsertValuesBuilder<any Table> _ build: () -> [any Table]) {
71+
self.seeds = build()
72+
}
73+
74+
public func makeIterator() -> Iterator {
75+
Iterator(seeds: seeds)
76+
}
77+
78+
public struct Iterator: IteratorProtocol {
79+
var seeds: [any Table]
80+
81+
public mutating func next() -> SQLQueryExpression<Void>? {
82+
guard let first = seeds.first else { return nil }
83+
84+
let firstType = type(of: first)
85+
86+
if let firstType = firstType as? any TableDraft.Type {
87+
func insertBatch<T: TableDraft>(_: T.Type) -> SQLQueryExpression<Void> {
88+
let batch = Array(seeds.lazy.prefix { $0 is T }.compactMap { $0 as? T })
89+
defer { seeds.removeFirst(batch.count) }
90+
return SQLQueryExpression(T.PrimaryTable.insert(batch))
91+
}
92+
93+
return insertBatch(firstType)
94+
} else {
95+
func insertBatch<T: StructuredQueriesCore.Table>(_: T.Type) -> SQLQueryExpression<Void> {
96+
let batch = Array(seeds.lazy.prefix { $0 is T }.compactMap { $0 as? T })
97+
defer { seeds.removeFirst(batch.count) }
98+
return SQLQueryExpression(T.insert(batch))
99+
}
100+
101+
return insertBatch(firstType)
102+
}
103+
}
104+
}
105+
}

0 commit comments

Comments
 (0)