Skip to content

Commit 5ab6fff

Browse files
authored
Support INSTEAD OF triggers (#179)
* 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! * wip
1 parent d10d77e commit 5ab6fff

File tree

3 files changed

+91
-0
lines changed

3 files changed

+91
-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: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,59 @@ extension SnapshotTests {
5454
└────────────────────────┘
5555
"""
5656
}
57+
assertQuery(
58+
CompletedReminder.createTemporaryTrigger(
59+
insteadOf: .insert { new in
60+
Reminder.insert {
61+
($0.title, $0.isCompleted, $0.remindersListID)
62+
} values: {
63+
(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+
("title", "isCompleted", "remindersListID")
75+
VALUES
76+
("new"."title", 1, 1);
77+
END
78+
"""
79+
}
80+
assertQuery(
81+
CompletedReminder.insert(\.title) { "Already done" }
82+
) {
83+
"""
84+
INSERT INTO "completedReminders"
85+
("title")
86+
VALUES
87+
('Already done')
88+
"""
89+
}
90+
// NB: Can't use 'RETURNING' above due to a SQLite bug where 'reminderID' is 'NULL'.
91+
assertQuery(
92+
CompletedReminder.order { $0.reminderID.desc() }.limit(1)
93+
) {
94+
"""
95+
SELECT "completedReminders"."reminderID", "completedReminders"."title"
96+
FROM "completedReminders"
97+
ORDER BY "completedReminders"."reminderID" DESC
98+
LIMIT 1
99+
"""
100+
} results: {
101+
"""
102+
┌─────────────────────────┐
103+
│ CompletedReminder( │
104+
│ reminderID: 11, │
105+
│ title: "Already done"
106+
│ ) │
107+
└─────────────────────────┘
108+
"""
109+
}
57110
assertQuery(
58111
query.drop()
59112
) {

0 commit comments

Comments
 (0)