Skip to content

Commit 2b76ff7

Browse files
committed
Support INSTEAD OF triggers
After adding support for database views (#172) it is easy enough to support triggers for them, so let's do so!
1 parent d10d77e commit 2b76ff7

File tree

3 files changed

+94
-0
lines changed

3 files changed

+94
-0
lines changed

Sources/StructuredQueriesSQLiteCore/Documentation.docc/Articles/Triggers.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ reminder is inserted into the database with the following trigger:
188188

189189
- ``StructuredQueriesCore/Table/createTemporaryTrigger(_:ifNotExists:after:fileID:line:column:)``
190190
- ``StructuredQueriesCore/Table/createTemporaryTrigger(_:ifNotExists:before:fileID:line:column:)``
191+
- ``StructuredQueriesCore/Table/createTemporaryTrigger(_:ifNotExists:insteadOf:fileID:line:column:)``
191192

192193
### Touching records
193194

Sources/StructuredQueriesSQLiteCore/Triggers.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,42 @@ extension Table {
7474
)
7575
}
7676

77+
/// A `CREATE TEMPORARY TRIGGER` statement that executes instead of a database view event.
78+
///
79+
/// See <doc:Triggers> for more information.
80+
///
81+
/// > Important: A name for the trigger is automatically derived from the arguments if one is not
82+
/// > provided. If you build your own trigger helper that call this function, then your helper
83+
/// > should also take `fileID`, `line` and `column` arguments and pass them to this function.
84+
///
85+
/// - Parameters:
86+
/// - name: The trigger's name. By default a unique name is generated depending using the table,
87+
/// operation, and source location.
88+
/// - ifNotExists: Adds an `IF NOT EXISTS` clause to the `CREATE TRIGGER` statement.
89+
/// - operation: The trigger's operation.
90+
/// - fileID: The source `#fileID` associated with the trigger.
91+
/// - line: The source `#line` associated with the trigger.
92+
/// - column: The source `#column` associated with the trigger.
93+
/// - Returns: A temporary trigger.
94+
public static func createTemporaryTrigger(
95+
_ name: String? = nil,
96+
ifNotExists: Bool = false,
97+
insteadOf operation: TemporaryTrigger<Self>.Operation,
98+
fileID: StaticString = #fileID,
99+
line: UInt = #line,
100+
column: UInt = #column
101+
) -> TemporaryTrigger<Self> {
102+
TemporaryTrigger(
103+
name: name,
104+
ifNotExists: ifNotExists,
105+
operation: operation,
106+
when: .insteadOf,
107+
fileID: fileID,
108+
line: line,
109+
column: column
110+
)
111+
}
112+
77113
/// A `CREATE TEMPORARY TRIGGER` statement that applies additional updates to a row that has just
78114
/// been updated.
79115
///
@@ -252,6 +288,7 @@ public struct TemporaryTrigger<On: Table>: Sendable, Statement {
252288
fileprivate enum When: QueryFragment {
253289
case before = "BEFORE"
254290
case after = "AFTER"
291+
case insteadOf = "INSTEAD OF"
255292
}
256293

257294
/// The database event used in a trigger.

Tests/StructuredQueriesTests/ViewsTests.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,62 @@ extension SnapshotTests {
5454
└────────────────────────┘
5555
"""
5656
}
57+
assertQuery(
58+
CompletedReminder.createTemporaryTrigger(
59+
insteadOf: .insert { new in
60+
Reminder.insert {
61+
($0.id, $0.title, $0.isCompleted, $0.remindersListID)
62+
} values: {
63+
(Reminder.select { ($0.id.max() ?? 0) + 1 }, new.title, true, 1)
64+
}
65+
}
66+
)
67+
) {
68+
"""
69+
CREATE TEMPORARY TRIGGER
70+
"after_insert_on_completedReminders@StructuredQueriesTests/ViewsTests.swift:58:49"
71+
INSTEAD OF INSERT ON "completedReminders"
72+
FOR EACH ROW BEGIN
73+
INSERT INTO "reminders"
74+
("id", "title", "isCompleted", "remindersListID")
75+
VALUES
76+
((
77+
SELECT (coalesce(max("reminders"."id"), 0) + 1)
78+
FROM "reminders"
79+
), "new"."title", 1, 1);
80+
END
81+
"""
82+
}
83+
assertQuery(
84+
CompletedReminder.insert(\.title) { "Already done" }
85+
) {
86+
"""
87+
INSERT INTO "completedReminders"
88+
("title")
89+
VALUES
90+
('Already done')
91+
"""
92+
}
93+
// NB: Can't use 'RETURNING' above due to a SQLite bug where 'reminderID' is 'NULL'.
94+
assertQuery(
95+
CompletedReminder.order { $0.reminderID.desc() }.limit(1)
96+
) {
97+
"""
98+
SELECT "completedReminders"."reminderID", "completedReminders"."title"
99+
FROM "completedReminders"
100+
ORDER BY "completedReminders"."reminderID" DESC
101+
LIMIT 1
102+
"""
103+
} results: {
104+
"""
105+
┌─────────────────────────┐
106+
│ CompletedReminder( │
107+
│ reminderID: 11, │
108+
│ title: "Already done"
109+
│ ) │
110+
└─────────────────────────┘
111+
"""
112+
}
57113
assertQuery(
58114
query.drop()
59115
) {

0 commit comments

Comments
 (0)