diff --git a/Sources/StructuredQueries/Macros.swift b/Sources/StructuredQueries/Macros.swift index 95accb33..778d0fa5 100644 --- a/Sources/StructuredQueries/Macros.swift +++ b/Sources/StructuredQueries/Macros.swift @@ -46,7 +46,7 @@ public macro Column( type: "ColumnMacro" ) -/// Tells Structured Queries not to consider the annotated property a column of the table +/// Tells StructuredQueries not to consider the annotated property a column of the table. /// /// Like SwiftData's `@Transient` macro, but for SQL. @attached(peer) diff --git a/Sources/StructuredQueriesCore/Documentation.docc/Articles/Triggers.md b/Sources/StructuredQueriesCore/Documentation.docc/Articles/Triggers.md new file mode 100644 index 00000000..4348d429 --- /dev/null +++ b/Sources/StructuredQueriesCore/Documentation.docc/Articles/Triggers.md @@ -0,0 +1,194 @@ +# Triggers + +Learn how to build trigger statements that can monitor the database for events and react. + +## Overview + +[Triggers](https://sqlite.org/lang_createtrigger.html) are operations that execute in your database +when some specific database event occurs. StructuredQueries comes with tools to create _temporary_ +triggers in a type-safe and schema-safe fashion. + +### Trigger basics + +One of the most common use cases for a trigger is refreshing an "updatedAt" timestamp on a row when +it is updated in the database. One can create such a trigger SQL statement using the +``Table/createTemporaryTrigger(_:ifNotExists:after:fileID:line:column:)`` static method: + +@Row { + @Column { + ```swift + Reminder.createTemporaryTrigger( + after: .update { _, _ in + Reminder.update { + $0.updatedAt = #sql("datetime('subsec')") + } + } + ) + ``` + } + @Column { + ```sql + CREATE TEMPORARY TRIGGER "after_update_on_reminders@…" + AFTER UPDATE ON "reminders" + FOR EACH ROW + BEGIN + UPDATE "reminders" + SET "updatedAt" = datetime('subsec'); + END + ``` + } +} + +This will make it so that anytime a reminder is updated in the database its `updatedAt` will be +refreshed with the current time immediately. + +This pattern of updating a timestamp when a row changes is so common that the library comes with +a specialized tool just for that kind of trigger, +``Table/createTemporaryTrigger(_:ifNotExists:afterUpdateTouch:fileID:line:column:)``: + +@Row { + @Column { + ```swift + Reminder.createTemporaryTrigger( + afterUpdateTouch: { + $0.updatedAt = datetime('subsec') + } + ) + ``` + } + @Column { + ```sql + CREATE TEMPORARY TRIGGER "after_update_on_reminders@…" + AFTER UPDATE ON "reminders" + FOR EACH ROW + BEGIN + UPDATE "reminders" + SET "updatedAt" = datetime('subsec'); + END + ``` + } +} + +And further, the pattern of specifically updating a _timestamp_ column is so common that the library +comes with another specialized too just for that kind of trigger, +``Table/createTemporaryTrigger(_:ifNotExists:afterUpdateTouch:fileID:line:column:)``: + + +@Row { + @Column { + ```swift + Reminder.createTemporaryTrigger( + afterUpdateTouch: \.updatedAt + ) + ``` + } + @Column { + ```sql + CREATE TEMPORARY TRIGGER "after_update_on_reminders@…" + AFTER UPDATE ON "reminders" + FOR EACH ROW + BEGIN + UPDATE "reminders" + SET "updatedAt" = datetime('subsec'); + END + ``` + } +} + +### More types of triggers + +There are 3 kinds of triggers depending on the event being listened for in the database: inserts, +updates, and deletes. For each of these kinds of triggers one can perform 4 kinds of actions: a +select, insert, update, or delete. Each action can be performed either before or after the event +being listened for executes. All 24 combinations of these kinds of triggers are supported by the +library. + +> Tip: SQLite generally +> [recommends against](https://sqlite.org/lang_createtrigger.html#cautions_on_the_use_of_before_triggers) +> using `BEFORE` triggers, as it can lead to undefined behavior. + +Here are a few examples to show you the possibilities with triggers: + +#### Non-empty tables + +One can use triggers to enforce that a table is never fully emptied out. For example, suppose you +want to make sure that the `remindersLists` table always has at least one row. Then one can use an +`AFTER DELETE` trigger with an `INSERT` action to insert a stub reminders list when it detects the +last list was deleted: + +@Row { + @Column { + ```swift + RemindersList.createTemporaryTrigger( + after: .delete { _ in + RemindersList.insert { + RemindersList.Draft(title: "Personal") + } + } when: { _ in + !RemindersList.exists() + } + ) + ``` + } + @Column { + ```sql + CREATE TEMPORARY TRIGGER "after_delete_on_remindersLists@…" + AFTER DELETE ON "remindersLists" + FOR EACH ROW WHEN NOT (EXISTS (SELECT * FROM "remindersLists")) + BEGIN + INSERT INTO "remindersLists" + ("id", "color", "title") + VALUES + (NULL, 0xffaaff00, 'Personal'); + END + ``` + } +} + +#### Invoke Swift code from triggers + +One can use triggers with a `SELECT` action to invoke Swift code when an event occurs in your +database. For example, suppose you want to execute a Swift function a new reminder is inserted +into the database. First you must register the function with SQLite and that depends on what +SQLite driver you are using ([here][grdb-add-function] is how to do it in GRDB). + +Suppose we registered a function called `didInsertReminder`, and further suppose it takes one +argument of the ID of the newly inserted reminder. Then one can invoke this function whenever a +reminder is inserted into the database with the following trigger: + +[grdb-add-function]: https://swiftpackageindex.com/groue/grdb.swift/v7.5.0/documentation/grdb/database/add(function:) + +@Row { + @Column { + ```swift + Reminders.createTemporaryTrigger( + after: .insert { new in + #sql("SELECT didInsertReminder(\(new.id))") + } + ) + ``` + } + @Column { + ```sql + CREATE TEMPORARY TRIGGER "after_insert_on_reminders@…" + AFTER INSERT ON "reminders" + FOR EACH ROW + BEGIN + SELECT didInsertReminder("new"."id") + END + ``` + } +} + + +## Topics + +### Creating temporary triggers + +- ``Table/createTemporaryTrigger(_:ifNotExists:after:fileID:line:column:)`` +- ``Table/createTemporaryTrigger(_:ifNotExists:before:fileID:line:column:)`` + +### Touching records + +- ``Table/createTemporaryTrigger(_:ifNotExists:afterInsertTouch:fileID:line:column:)`` +- ``Table/createTemporaryTrigger(_:ifNotExists:afterUpdateTouch:fileID:line:column:)`` diff --git a/Sources/StructuredQueriesCore/Documentation.docc/StructuredQueriesCore.md b/Sources/StructuredQueriesCore/Documentation.docc/StructuredQueriesCore.md index 414c6d67..657ce76c 100644 --- a/Sources/StructuredQueriesCore/Documentation.docc/StructuredQueriesCore.md +++ b/Sources/StructuredQueriesCore/Documentation.docc/StructuredQueriesCore.md @@ -124,6 +124,7 @@ reading to learn more about building SQL with StructuredQueries. - - - +- - - diff --git a/Sources/StructuredQueriesCore/QueryFragmentBuilder.swift b/Sources/StructuredQueriesCore/QueryFragmentBuilder.swift index 19e7e1ed..95a1939b 100644 --- a/Sources/StructuredQueriesCore/QueryFragmentBuilder.swift +++ b/Sources/StructuredQueriesCore/QueryFragmentBuilder.swift @@ -42,3 +42,18 @@ extension QueryFragmentBuilder<()> { Array(repeat each expression) } } + +extension QueryFragmentBuilder { + public static func buildExpression( + _ expression: some Statement + ) -> [QueryFragment] { + [expression.query] + } + + public static func buildBlock( + _ first: [QueryFragment], + _ rest: [QueryFragment]... + ) -> [QueryFragment] { + first + rest.flatMap(\.self) + } +} diff --git a/Sources/StructuredQueriesCore/Triggers.swift b/Sources/StructuredQueriesCore/Triggers.swift new file mode 100644 index 00000000..106720e3 --- /dev/null +++ b/Sources/StructuredQueriesCore/Triggers.swift @@ -0,0 +1,488 @@ +import Foundation +import IssueReporting + +extension Table { + /// A `CREATE TEMPORARY TRIGGER` statement that executes after a database event. + /// + /// See for more information. + /// + /// > Important: A name for the trigger is automatically derived from the arguments if one is not + /// > provided. If you build your own trigger helper that call this function, then your helper + /// > should also take `fileID`, `line` and `column` arguments and pass them to this function. + /// + /// - Parameters: + /// - name: The trigger's name. By default a unique name is generated depending using the table, + /// operation, and source location. + /// - ifNotExists: Adds an `IF NOT EXISTS` clause to the `CREATE TRIGGER` statement. + /// - operation: The trigger's operation. + /// - fileID: The source `#fileID` associated with the trigger. + /// - line: The source `#line` associated with the trigger. + /// - column: The source `#column` associated with the trigger. + /// - Returns: A temporary trigger. + public static func createTemporaryTrigger( + _ name: String? = nil, + ifNotExists: Bool = false, + after operation: TemporaryTrigger.Operation, + fileID: StaticString = #fileID, + line: UInt = #line, + column: UInt = #column + ) -> TemporaryTrigger { + TemporaryTrigger( + name: name, + ifNotExists: ifNotExists, + operation: operation, + when: .after, + fileID: fileID, + line: line, + column: column + ) + } + + /// A `CREATE TEMPORARY TRIGGER` statement that executes before a database event. + /// + /// See for more information. + /// + /// > Important: A name for the trigger is automatically derived from the arguments if one is not + /// > provided. If you build your own trigger helper that call this function, then your helper + /// > should also take `fileID`, `line` and `column` arguments and pass them to this function. + /// + /// - Parameters: + /// - name: The trigger's name. By default a unique name is generated depending using the table, + /// operation, and source location. + /// - ifNotExists: Adds an `IF NOT EXISTS` clause to the `CREATE TRIGGER` statement. + /// - operation: The trigger's operation. + /// - fileID: The source `#fileID` associated with the trigger. + /// - line: The source `#line` associated with the trigger. + /// - column: The source `#column` associated with the trigger. + /// - Returns: A temporary trigger. + public static func createTemporaryTrigger( + _ name: String? = nil, + ifNotExists: Bool = false, + before operation: TemporaryTrigger.Operation, + fileID: StaticString = #fileID, + line: UInt = #line, + column: UInt = #column + ) -> TemporaryTrigger { + TemporaryTrigger( + name: name, + ifNotExists: ifNotExists, + operation: operation, + when: .before, + fileID: fileID, + line: line, + column: column + ) + } + + /// A `CREATE TEMPORARY TRIGGER` statement that applies additional updates to a row that has just + /// been updated. + /// + /// See for more information. + /// + /// > Important: A name for the trigger is automatically derived from the arguments if one is not + /// > provided. If you build your own trigger helper that call this function, then your helper + /// > should also take `fileID`, `line` and `column` arguments and pass them to this function. + /// + /// - Parameters: + /// - name: The trigger's name. By default a unique name is generated depending using the table, + /// operation, and source location. + /// - ifNotExists: Adds an `IF NOT EXISTS` clause to the `CREATE TRIGGER` statement. + /// - updates: The updates to apply after the row has been updated. + /// - fileID: The source `#fileID` associated with the trigger. + /// - line: The source `#line` associated with the trigger. + /// - column: The source `#column` associated with the trigger. + /// - Returns: A temporary trigger. + public static func createTemporaryTrigger( + _ name: String? = nil, + ifNotExists: Bool = false, + afterUpdateTouch updates: (inout Updates) -> Void, + fileID: StaticString = #fileID, + line: UInt = #line, + column: UInt = #column + ) -> TemporaryTrigger { + Self.createTemporaryTrigger( + name, + ifNotExists: ifNotExists, + after: .update { _, new in + Self + .where { $0.rowid.eq(new.rowid) } + .update { updates(&$0) } + }, + fileID: fileID, + line: line, + column: column + ) + } + + /// A `CREATE TEMPORARY TRIGGER` statement that updates a datetime column when a row has been + /// updated. + /// + /// See for more information. + /// + /// > Important: A name for the trigger is automatically derived from the arguments if one is not + /// > provided. If you build your own trigger helper that call this function, then your helper + /// > should also take `fileID`, `line` and `column` arguments and pass them to this function. + /// + /// - Parameters: + /// - name: The trigger's name. By default a unique name is generated depending using the table, + /// operation, and source location. + /// - ifNotExists: Adds an `IF NOT EXISTS` clause to the `CREATE TRIGGER` statement. + /// - date: A key path to a datetime column. + /// - fileID: The source `#fileID` associated with the trigger. + /// - line: The source `#line` associated with the trigger. + /// - column: The source `#column` associated with the trigger. + /// - Returns: A temporary trigger. + public static func createTemporaryTrigger>( + _ name: String? = nil, + ifNotExists: Bool = false, + afterUpdateTouch date: KeyPath>, + fileID: StaticString = #fileID, + line: UInt = #line, + column: UInt = #column + ) -> TemporaryTrigger { + Self.createTemporaryTrigger( + name, + ifNotExists: ifNotExists, + afterUpdateTouch: { + $0[dynamicMember: date] = SQLQueryExpression("datetime('subsec')") + }, + fileID: fileID, + line: line, + column: column + ) + } + + /// A `CREATE TEMPORARY TRIGGER` statement that applies additional updates to a row that has just + /// been inserted. + /// + /// See for more information. + /// + /// > Important: A name for the trigger is automatically derived from the arguments if one is not + /// > provided. If you build your own trigger helper that call this function, then your helper + /// > should also take `fileID`, `line` and `column` arguments and pass them to this function. + /// + /// - Parameters: + /// - name: The trigger's name. By default a unique name is generated depending using the table, + /// operation, and source location. + /// - ifNotExists: Adds an `IF NOT EXISTS` clause to the `CREATE TRIGGER` statement. + /// - updates: The updates to apply after the row has been inserted. + /// - fileID: The source `#fileID` associated with the trigger. + /// - line: The source `#line` associated with the trigger. + /// - column: The source `#column` associated with the trigger. + /// - Returns: A temporary trigger. + public static func createTemporaryTrigger( + _ name: String? = nil, + ifNotExists: Bool = false, + afterInsertTouch updates: (inout Updates) -> Void, + fileID: StaticString = #fileID, + line: UInt = #line, + column: UInt = #column + ) -> TemporaryTrigger { + Self.createTemporaryTrigger( + name, + ifNotExists: ifNotExists, + after: .insert { new in + Self + .where { $0.rowid.eq(new.rowid) } + .update { updates(&$0) } + }, + fileID: fileID, + line: line, + column: column + ) + } + + /// A `CREATE TEMPORARY TRIGGER` statement that updates a datetime column when a row has been + /// inserted. + /// + /// See for more information. + /// + /// > Important: A name for the trigger is automatically derived from the arguments if one is not + /// > provided. If you build your own trigger helper that call this function, then your helper + /// > should also take `fileID`, `line` and `column` arguments and pass them to this function. + /// + /// - Parameters: + /// - name: The trigger's name. By default a unique name is generated depending using the table, + /// operation, and source location. + /// - ifNotExists: Adds an `IF NOT EXISTS` clause to the `CREATE TRIGGER` statement. + /// - date: A key path to a datetime column. + /// - fileID: The source `#fileID` associated with the trigger. + /// - line: The source `#line` associated with the trigger. + /// - column: The source `#column` associated with the trigger. + /// - Returns: A temporary trigger. + public static func createTemporaryTrigger>( + _ name: String? = nil, + ifNotExists: Bool = false, + afterInsertTouch date: KeyPath>, + fileID: StaticString = #fileID, + line: UInt = #line, + column: UInt = #column + ) -> TemporaryTrigger { + Self.createTemporaryTrigger( + name, + ifNotExists: ifNotExists, + afterInsertTouch: { + $0[dynamicMember: date] = SQLQueryExpression("datetime('subsec')") + }, + fileID: fileID, + line: line, + column: column + ) + } +} + +/// A `CREATE TEMPORARY TRIGGER` statement. +/// +/// This type of statement is returned from the +/// `[Table.createTemporaryTrigger]` family of +/// functions. +/// +/// To learn more, see . +public struct TemporaryTrigger: Statement { + public typealias From = Never + public typealias Joins = () + public typealias QueryValue = () + + fileprivate enum When: QueryFragment { + case before = "BEFORE" + case after = "AFTER" + } + + /// The database event used in a trigger. + /// + /// To learn more, see . + public struct Operation: QueryExpression { + public typealias QueryValue = () + + public enum _Old: AliasName { public static var aliasName: String { "old" } } + public enum _New: AliasName { public static var aliasName: String { "new" } } + + public typealias Old = TableAlias.TableColumns + public typealias New = TableAlias.TableColumns + + /// An `AFTER INSERT` trigger operation. + /// + /// - Parameters: + /// - perform: A statement to perform for each triggered row. + /// - condition: A predicate that must be satisfied to perform the given statement. + /// - Returns: An `AFTER INSERT` trigger operation. + public static func insert( + @QueryFragmentBuilder + forEachRow perform: (_ new: New) -> [QueryFragment], + when condition: ((_ new: New) -> any QueryExpression)? = nil + ) -> Self { + Self( + kind: .insert(operations: perform(On.as(_New.self).columns)), + when: condition?(On.as(_New.self).columns).queryFragment + ) + } + + /// An `AFTER UPDATE` trigger operation. + /// + /// - Parameters: + /// - perform: A statement to perform for each triggered row. + /// - condition: A predicate that must be satisfied to perform the given statement. + /// - Returns: An `AFTER UPDATE` trigger operation. + public static func update( + @QueryFragmentBuilder + forEachRow perform: (_ old: Old, _ new: New) -> [QueryFragment], + when condition: ((_ old: Old, _ new: New) -> any QueryExpression)? = nil + ) -> Self { + update( + of: { _ in }, + forEachRow: perform, + when: condition + ) + } + + /// An `AFTER UPDATE` trigger operation. + /// + /// - Parameters: + /// - columns: Updated columns to scope the operation to. + /// - perform: A statement to perform for each triggered row. + /// - condition: A predicate that must be satisfied to perform the given statement. + /// - Returns: An `AFTER UPDATE` trigger operation. + public static func update( + of columns: (On.TableColumns) -> (repeat TableColumn), + @QueryFragmentBuilder + forEachRow perform: (_ old: Old, _ new: New) -> [QueryFragment], + when condition: ((_ old: Old, _ new: New) -> any QueryExpression)? = nil + ) -> Self { + var columnNames: [String] = [] + for column in repeat each columns(On.columns) { + columnNames.append(column.name) + } + return Self( + kind: .update( + operations: perform(On.as(_Old.self).columns, On.as(_New.self).columns), + columnNames: columnNames + ), + when: condition?(On.as(_Old.self).columns, On.as(_New.self).columns).queryFragment + ) + } + + /// An `AFTER DELETE` trigger operation. + /// + /// - Parameters: + /// - perform: A statement to perform for each triggered row. + /// - condition: A predicate that must be satisfied to perform the given statement. + /// - Returns: An `AFTER DELETE` trigger operation. + public static func delete( + @QueryFragmentBuilder + forEachRow perform: (_ old: Old) -> [QueryFragment], + when condition: ((_ old: Old) -> any QueryExpression)? = nil + ) -> Self { + Self( + kind: .delete(operations: perform(On.as(_Old.self).columns)), + when: condition?(On.as(_Old.self).columns).queryFragment + ) + } + + private enum Kind { + case insert(operations: [QueryFragment]) + case update(operations: [QueryFragment], columnNames: [String]) + case delete(operations: [QueryFragment]) + } + + private let kind: Kind + private let when: QueryFragment? + + public var queryFragment: QueryFragment { + var query: QueryFragment = "" + let statements: [QueryFragment] + switch kind { + case .insert(let begin): + query.append("INSERT") + statements = begin + case .update(let begin, let columnNames): + query.append("UPDATE") + if !columnNames.isEmpty { + query.append( + " OF \(columnNames.map { QueryFragment(quote: $0) }.joined(separator: ", "))" + ) + } + statements = begin + case .delete(let begin): + query.append("DELETE") + statements = begin + } + query.append(" ON \(On.self)\(.newlineOrSpace)FOR EACH ROW") + if let when { + query.append(" WHEN \(when)") + } + query.append(" BEGIN") + for statement in statements { + query.append("\(.newlineOrSpace)\(statement.indented());") + } + query.append("\(.newlineOrSpace)END") + return query + } + + fileprivate var description: String { + switch kind { + case .insert: "after_insert" + case .update: "after_update" + case .delete: "after_delete" + } + } + } + + fileprivate let name: String? + fileprivate let ifNotExists: Bool + fileprivate let operation: Operation + fileprivate let when: When + fileprivate let fileID: StaticString + fileprivate let line: UInt + fileprivate let column: UInt + + /// Returns a `DROP TRIGGER` statement for this trigger. + /// + /// - Parameter ifExists: Adds an `IF EXISTS` condition to the `DROP TRIGGER`. + /// - Returns: A `DROP TRIGGER` statement for this trigger. + public func drop(ifExists: Bool = false) -> some Statement<()> { + var query: QueryFragment = "DROP TRIGGER" + if ifExists { + query.append(" IF EXISTS") + } + query.append(" \(triggerName)") + return SQLQueryExpression(query) + } + + public var query: QueryFragment { + var query: QueryFragment = "CREATE TEMPORARY TRIGGER" + if ifNotExists { + query.append(" IF NOT EXISTS") + } + query.append("\(.newlineOrSpace)\(triggerName.indented())") + query.append("\(.newlineOrSpace)\(when.rawValue) \(operation)") + return query.segments.reduce(into: QueryFragment()) { + switch $1 { + case .sql(let sql): + $0.append("\(raw: sql)") + case .binding(let binding): + switch binding { + case .blob(let blob): + reportIssue( + """ + Cannot bind bytes to a trigger statement. To hardcode a constant BLOB, use the '#sql' \ + macro. + """ + ) + let hex = blob.reduce(into: "") { + let hex = String($1, radix: 16) + if hex.count == 1 { + $0.append("0") + } + $0.append(hex) + } + $0.append("unhex(\(quote: hex, delimiter: .text))") + case .double(let double): + $0.append("\(raw: double)") + case .date(let date): + reportIssue( + """ + Cannot bind a date to a trigger statement. Specify dates using the '#sql' macro, \ + instead. For example, the current date: + + #sql("datetime()") + + Or a constant date: + + #sql("'2018-01-29 00:08:00'") + """ + ) + $0.append("\(quote: date.iso8601String, delimiter: .text)") + case .int(let int): + $0.append("\(raw: int)") + case .null: + $0.append("NULL") + case .text(let string): + $0.append("\(quote: string, delimiter: .text)") + case .uuid(let uuid): + reportIssue( + """ + Cannot bind a UUID to a trigger statement. Specify UUIDs using the '#sql' macro, \ + instead. For example, a random UUID: + + #sql("uuid()") + + Or a constant UUID: + + #sql("'00000000-0000-0000-0000-000000000000'") + """ + ) + $0.append("\(quote: uuid.uuidString.lowercased(), delimiter: .text)") + case .invalid(let error): + $0.append("\(.invalid(error.underlyingError))") + } + } + } + } + + private var triggerName: QueryFragment { + guard let name else { + return "\(quote: "\(operation.description)_on_\(On.tableName)@\(fileID):\(line):\(column)")" + } + return "\(quote: name)" + } +} diff --git a/Tests/StructuredQueriesTests/CommonTableExpressionTests.swift b/Tests/StructuredQueriesTests/CommonTableExpressionTests.swift index 0c0b9ec3..e5f911bb 100644 --- a/Tests/StructuredQueriesTests/CommonTableExpressionTests.swift +++ b/Tests/StructuredQueriesTests/CommonTableExpressionTests.swift @@ -108,7 +108,7 @@ extension SnapshotTests { .select { ($1.remindersListID, $0.title, !$0.isFlagged, true) } .limit(1) } - .returning(\.self) + .returning { ($0.id, $0.title) } } ) { """ @@ -123,23 +123,13 @@ extension SnapshotTests { FROM "incompleteReminders" JOIN "reminders" ON ("incompleteReminders"."title" = "reminders"."title") LIMIT 1 - RETURNING "id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title" + RETURNING "id", "title" """ } results: { """ - ┌────────────────────────┐ - │ Reminder( │ - │ id: 11, │ - │ assignedUserID: nil, │ - │ dueDate: nil, │ - │ isCompleted: true, │ - │ isFlagged: true, │ - │ notes: "", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Groceries" │ - │ ) │ - └────────────────────────┘ + ┌────┬─────────────┐ + │ 11 │ "Groceries" │ + └────┴─────────────┘ """ } } diff --git a/Tests/StructuredQueriesTests/DeleteTests.swift b/Tests/StructuredQueriesTests/DeleteTests.swift index 157b3fcd..05eddb93 100644 --- a/Tests/StructuredQueriesTests/DeleteTests.swift +++ b/Tests/StructuredQueriesTests/DeleteTests.swift @@ -47,23 +47,24 @@ extension SnapshotTests { """ DELETE FROM "reminders" WHERE ("reminders"."id" = 1) - RETURNING "id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title" + RETURNING "id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt" """ } results: { """ - ┌────────────────────────────────────────────┐ - │ Reminder( │ - │ id: 1, │ - │ assignedUserID: 1, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Milk, Eggs, Apples", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Groceries" │ - │ ) │ - └────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────┐ + │ Reminder( │ + │ id: 1, │ + │ assignedUserID: 1, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Milk, Eggs, Apples", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Groceries", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + └─────────────────────────────────────────────┘ """ } assertQuery(Reminder.count()) { @@ -135,17 +136,18 @@ extension SnapshotTests { """ DELETE FROM "remindersLists" AS "rs" WHERE ("rs"."id" = 1) - RETURNING "id", "color", "title" + RETURNING "id", "color", "title", "position" """ } results: { """ - ┌─────────────────────┐ - │ RemindersList( │ - │ id: 1, │ - │ color: 4889071, │ - │ title: "Personal" │ - │ ) │ - └─────────────────────┘ + ┌──────────────────────┐ + │ RemindersList( │ + │ id: 1, │ + │ color: 4889071, │ + │ title: "Personal", │ + │ position: 0 │ + │ ) │ + └──────────────────────┘ """ } } diff --git a/Tests/StructuredQueriesTests/InsertTests.swift b/Tests/StructuredQueriesTests/InsertTests.swift index c7268823..a0843eef 100644 --- a/Tests/StructuredQueriesTests/InsertTests.swift +++ b/Tests/StructuredQueriesTests/InsertTests.swift @@ -16,7 +16,7 @@ extension SnapshotTests { } onConflictDoUpdate: { $0.title += " Copy" } - .returning(\.self) + .returning(\.id) ) { """ INSERT INTO "reminders" @@ -24,35 +24,14 @@ extension SnapshotTests { VALUES (1, 'Groceries', 1, '2001-01-01 00:00:00.000', 3), (2, 'Haircut', 0, '1970-01-01 00:00:00.000', 1) ON CONFLICT DO UPDATE SET "title" = ("reminders"."title" || ' Copy') - RETURNING "id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title" + RETURNING "id" """ } results: { """ - ┌────────────────────────────────────────────┐ - │ Reminder( │ - │ id: 11, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: true, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: .high, │ - │ remindersListID: 1, │ - │ title: "Groceries" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder( │ - │ id: 12, │ - │ assignedUserID: nil, │ - │ dueDate: Date(1970-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: .low, │ - │ remindersListID: 2, │ - │ title: "Haircut" │ - │ ) │ - └────────────────────────────────────────────┘ + ┌────┐ + │ 11 │ + │ 12 │ + └────┘ """ } } @@ -61,30 +40,20 @@ extension SnapshotTests { assertQuery( Reminder .insert(\.remindersListID) { 1 } - .returning(\.self) + .returning(\.id) ) { """ INSERT INTO "reminders" ("remindersListID") VALUES (1) - RETURNING "id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title" + RETURNING "id" """ } results: { """ - ┌────────────────────────┐ - │ Reminder( │ - │ id: 11, │ - │ assignedUserID: nil, │ - │ dueDate: nil, │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "" │ - │ ) │ - └────────────────────────┘ + ┌────┐ + │ 11 │ + └────┘ """ } } @@ -115,9 +84,9 @@ extension SnapshotTests { ) { """ INSERT INTO "reminders" - ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title") + ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt") VALUES - (100, NULL, NULL, 0, 0, '', NULL, 1, 'Check email') + (100, NULL, NULL, 0, 0, '', NULL, 1, 'Check email', '2040-02-14 23:31:30.000') RETURNING "id" """ } results: { @@ -135,9 +104,9 @@ extension SnapshotTests { ) { """ INSERT INTO "reminders" - ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title") + ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt") VALUES - (101, NULL, NULL, 0, 0, '', NULL, 1, 'Check voicemail') + (101, NULL, NULL, 0, 0, '', NULL, 1, 'Check voicemail', '2040-02-14 23:31:30.000') RETURNING "id" """ } results: { @@ -156,9 +125,9 @@ extension SnapshotTests { ) { """ INSERT INTO "reminders" - ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title") + ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt") VALUES - (102, NULL, NULL, 0, 0, '', NULL, 1, 'Check mailbox'), (103, NULL, NULL, 0, 0, '', NULL, 1, 'Check Slack') + (102, NULL, NULL, 0, 0, '', NULL, 1, 'Check mailbox', '2040-02-14 23:31:30.000'), (103, NULL, NULL, 0, 0, '', NULL, 1, 'Check Slack', '2040-02-14 23:31:30.000') RETURNING "id" """ } results: { @@ -177,9 +146,9 @@ extension SnapshotTests { ) { """ INSERT INTO "reminders" - ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title") + ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt") VALUES - (104, NULL, NULL, 0, 0, '', NULL, 1, 'Check pager') + (104, NULL, NULL, 0, 0, '', NULL, 1, 'Check pager', '2040-02-14 23:31:30.000') RETURNING "id" """ } results: { @@ -263,9 +232,9 @@ extension SnapshotTests { ) { """ INSERT INTO "reminders" - ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title") + ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt") VALUES - (NULL, NULL, NULL, 0, 0, '', NULL, 1, 'Check email') + (NULL, NULL, NULL, 0, 0, '', NULL, 1, 'Check email', '2040-02-14 23:31:30.000') RETURNING "id" """ } results: { @@ -284,9 +253,9 @@ extension SnapshotTests { ) { """ INSERT INTO "reminders" - ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title") + ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt") VALUES - (NULL, NULL, NULL, 0, 0, '', NULL, 1, 'Check voicemail') + (NULL, NULL, NULL, 0, 0, '', NULL, 1, 'Check voicemail', '2040-02-14 23:31:30.000') RETURNING "id" """ } results: { @@ -308,9 +277,9 @@ extension SnapshotTests { ) { """ INSERT INTO "reminders" - ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title") + ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt") VALUES - (NULL, NULL, NULL, 0, 0, '', NULL, 1, 'Check mailbox'), (NULL, NULL, NULL, 0, 0, '', NULL, 1, 'Check Slack') + (NULL, NULL, NULL, 0, 0, '', NULL, 1, 'Check mailbox', '2040-02-14 23:31:30.000'), (NULL, NULL, NULL, 0, 0, '', NULL, 1, 'Check Slack', '2040-02-14 23:31:30.000') RETURNING "id" """ } results: { @@ -326,25 +295,26 @@ extension SnapshotTests { @Test func upsertWithID() { assertQuery(Reminder.where { $0.id == 1 }) { """ - SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt" FROM "reminders" WHERE ("reminders"."id" = 1) """ } results: { """ - ┌────────────────────────────────────────────┐ - │ Reminder( │ - │ id: 1, │ - │ assignedUserID: 1, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Milk, Eggs, Apples", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Groceries" │ - │ ) │ - └────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────┐ + │ Reminder( │ + │ id: 1, │ + │ assignedUserID: 1, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Milk, Eggs, Apples", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Groceries", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + └─────────────────────────────────────────────┘ """ } assertQuery( @@ -354,28 +324,29 @@ extension SnapshotTests { ) { """ INSERT INTO "reminders" - ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title") + ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt") VALUES - (1, NULL, NULL, 0, 0, '', NULL, 1, 'Cash check') + (1, NULL, NULL, 0, 0, '', NULL, 1, 'Cash check', '2040-02-14 23:31:30.000') ON CONFLICT ("id") - DO UPDATE SET "assignedUserID" = "excluded"."assignedUserID", "dueDate" = "excluded"."dueDate", "isCompleted" = "excluded"."isCompleted", "isFlagged" = "excluded"."isFlagged", "notes" = "excluded"."notes", "priority" = "excluded"."priority", "remindersListID" = "excluded"."remindersListID", "title" = "excluded"."title" - RETURNING "id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title" + DO UPDATE SET "assignedUserID" = "excluded"."assignedUserID", "dueDate" = "excluded"."dueDate", "isCompleted" = "excluded"."isCompleted", "isFlagged" = "excluded"."isFlagged", "notes" = "excluded"."notes", "priority" = "excluded"."priority", "remindersListID" = "excluded"."remindersListID", "title" = "excluded"."title", "updatedAt" = "excluded"."updatedAt" + RETURNING "id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt" """ } results: { """ - ┌────────────────────────┐ - │ Reminder( │ - │ id: 1, │ - │ assignedUserID: nil, │ - │ dueDate: nil, │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Cash check" │ - │ ) │ - └────────────────────────┘ + ┌─────────────────────────────────────────────┐ + │ Reminder( │ + │ id: 1, │ + │ assignedUserID: nil, │ + │ dueDate: nil, │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Cash check", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + └─────────────────────────────────────────────┘ """ } } @@ -401,28 +372,29 @@ extension SnapshotTests { ) { """ INSERT INTO "reminders" - ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title") + ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt") VALUES - (NULL, NULL, NULL, 0, 0, '', NULL, 1, '') + (NULL, NULL, NULL, 0, 0, '', NULL, 1, '', '2040-02-14 23:31:30.000') ON CONFLICT ("id") - DO UPDATE SET "assignedUserID" = "excluded"."assignedUserID", "dueDate" = "excluded"."dueDate", "isCompleted" = "excluded"."isCompleted", "isFlagged" = "excluded"."isFlagged", "notes" = "excluded"."notes", "priority" = "excluded"."priority", "remindersListID" = "excluded"."remindersListID", "title" = "excluded"."title" - RETURNING "id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title" + DO UPDATE SET "assignedUserID" = "excluded"."assignedUserID", "dueDate" = "excluded"."dueDate", "isCompleted" = "excluded"."isCompleted", "isFlagged" = "excluded"."isFlagged", "notes" = "excluded"."notes", "priority" = "excluded"."priority", "remindersListID" = "excluded"."remindersListID", "title" = "excluded"."title", "updatedAt" = "excluded"."updatedAt" + RETURNING "id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt" """ } results: { """ - ┌────────────────────────┐ - │ Reminder( │ - │ id: 11, │ - │ assignedUserID: nil, │ - │ dueDate: nil, │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "" │ - │ ) │ - └────────────────────────┘ + ┌─────────────────────────────────────────────┐ + │ Reminder( │ + │ id: 11, │ + │ assignedUserID: nil, │ + │ dueDate: nil, │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + └─────────────────────────────────────────────┘ """ } } @@ -436,12 +408,12 @@ extension SnapshotTests { ) { """ INSERT INTO "remindersLists" - ("id", "color", "title") + ("id", "color", "title", "position") VALUES - (NULL, 4889071, 'Personal') + (NULL, 4889071, 'Personal', 0) ON CONFLICT ("id") - DO UPDATE SET "color" = "excluded"."color", "title" = "excluded"."title" - RETURNING "id", "color", "title" + DO UPDATE SET "color" = "excluded"."color", "title" = "excluded"."title", "position" = "excluded"."position" + RETURNING "id", "color", "title", "position" """ } results: { """ @@ -462,22 +434,23 @@ extension SnapshotTests { ) { """ INSERT INTO "remindersLists" - ("id", "color", "title") + ("id", "color", "title", "position") VALUES - (NULL, 4889071, 'Personal') + (NULL, 4889071, 'Personal', 0) ON CONFLICT ("title") DO UPDATE SET "color" = 65280 - RETURNING "id", "color", "title" + RETURNING "id", "color", "title", "position" """ } results: { """ - ┌─────────────────────┐ - │ RemindersList( │ - │ id: 1, │ - │ color: 65280, │ - │ title: "Personal" │ - │ ) │ - └─────────────────────┘ + ┌──────────────────────┐ + │ RemindersList( │ + │ id: 1, │ + │ color: 65280, │ + │ title: "Personal", │ + │ position: 0 │ + │ ) │ + └──────────────────────┘ """ } } @@ -541,17 +514,18 @@ extension SnapshotTests { ("title") VALUES ('cruise') - RETURNING "id", "color", "title" + RETURNING "id", "color", "title", "position" """ } results: { """ - ┌───────────────────┐ - │ RemindersList( │ - │ id: 4, │ - │ color: 4889071, │ - │ title: "cruise" │ - │ ) │ - └───────────────────┘ + ┌────────────────────┐ + │ RemindersList( │ + │ id: 4, │ + │ color: 4889071, │ + │ title: "cruise", │ + │ position: 0 │ + │ ) │ + └────────────────────┘ """ } } @@ -586,9 +560,9 @@ extension SnapshotTests { ) { """ INSERT INTO "reminders" - ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title") + ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt") VALUES - (NULL, NULL, NULL, 0, 0, '', NULL, 1, '') + (NULL, NULL, NULL, 0, 0, '', NULL, 1, '', '2040-02-14 23:31:30.000') ON CONFLICT ("id") WHERE NOT ("reminders"."isCompleted") DO UPDATE SET "isCompleted" = 1 @@ -610,9 +584,9 @@ extension SnapshotTests { ) { """ INSERT INTO "reminders" - ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title") + ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt") VALUES - (NULL, NULL, NULL, 0, 0, '', NULL, 1, '') + (NULL, NULL, NULL, 0, 0, '', NULL, 1, '', '2040-02-14 23:31:30.000') """ } } @@ -632,9 +606,9 @@ extension SnapshotTests { ) { """ INSERT INTO "reminders" - ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title") + ("id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt") VALUES - (NULL, NULL, NULL, 0, 0, '', NULL, 1, '') + (NULL, NULL, NULL, 0, 0, '', NULL, 1, '', '2040-02-14 23:31:30.000') """ } } diff --git a/Tests/StructuredQueriesTests/JSONFunctionsTests.swift b/Tests/StructuredQueriesTests/JSONFunctionsTests.swift index 1d4fd6a5..18e063e6 100644 --- a/Tests/StructuredQueriesTests/JSONFunctionsTests.swift +++ b/Tests/StructuredQueriesTests/JSONFunctionsTests.swift @@ -142,7 +142,7 @@ extension SnapshotTests { .limit(2) ) { """ - SELECT "users"."id", "users"."name" AS "assignedUser", "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" AS "reminder", json_group_array(CASE WHEN ("tags"."id" IS NOT NULL) THEN json_object('id', json_quote("tags"."id"), 'title', json_quote("tags"."title")) END) FILTER (WHERE ("tags"."id" IS NOT NULL)) AS "tags" + SELECT "users"."id", "users"."name" AS "assignedUser", "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt" AS "reminder", json_group_array(CASE WHEN ("tags"."id" IS NOT NULL) THEN json_object('id', json_quote("tags"."id"), 'title', json_quote("tags"."title")) END) FILTER (WHERE ("tags"."id" IS NOT NULL)) AS "tags" FROM "reminders" LEFT JOIN "remindersTags" ON ("reminders"."id" = "remindersTags"."reminderID") LEFT JOIN "tags" ON ("remindersTags"."tagID" = "tags"."id") @@ -152,60 +152,62 @@ extension SnapshotTests { """ } results: { """ - ┌──────────────────────────────────────────────┐ - │ ReminderRow( │ - │ assignedUser: User( │ - │ id: 1, │ - │ name: "Blob" │ - │ ), │ - │ reminder: Reminder( │ - │ id: 1, │ - │ assignedUserID: 1, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Milk, Eggs, Apples", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Groceries" │ - │ ), │ - │ tags: [ │ - │ [0]: Tag( │ - │ id: 3, │ - │ title: "someday" │ - │ ), │ - │ [1]: Tag( │ - │ id: 4, │ - │ title: "optional" │ - │ ) │ - │ ] │ - │ ) │ - ├──────────────────────────────────────────────┤ - │ ReminderRow( │ - │ assignedUser: nil, │ - │ reminder: Reminder( │ - │ id: 2, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2000-12-30T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: true, │ - │ notes: "", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Haircut" │ - │ ), │ - │ tags: [ │ - │ [0]: Tag( │ - │ id: 3, │ - │ title: "someday" │ - │ ), │ - │ [1]: Tag( │ - │ id: 4, │ - │ title: "optional" │ - │ ) │ - │ ] │ - │ ) │ - └──────────────────────────────────────────────┘ + ┌───────────────────────────────────────────────┐ + │ ReminderRow( │ + │ assignedUser: User( │ + │ id: 1, │ + │ name: "Blob" │ + │ ), │ + │ reminder: Reminder( │ + │ id: 1, │ + │ assignedUserID: 1, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Milk, Eggs, Apples", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Groceries", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ), │ + │ tags: [ │ + │ [0]: Tag( │ + │ id: 3, │ + │ title: "someday" │ + │ ), │ + │ [1]: Tag( │ + │ id: 4, │ + │ title: "optional" │ + │ ) │ + │ ] │ + │ ) │ + ├───────────────────────────────────────────────┤ + │ ReminderRow( │ + │ assignedUser: nil, │ + │ reminder: Reminder( │ + │ id: 2, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2000-12-30T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: true, │ + │ notes: "", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Haircut", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ), │ + │ tags: [ │ + │ [0]: Tag( │ + │ id: 3, │ + │ title: "someday" │ + │ ), │ + │ [1]: Tag( │ + │ id: 4, │ + │ title: "optional" │ + │ ) │ + │ ] │ + │ ) │ + └───────────────────────────────────────────────┘ """ } } @@ -226,7 +228,7 @@ extension SnapshotTests { .limit(1) ) { """ - SELECT "remindersLists"."id", "remindersLists"."color", "remindersLists"."title" AS "remindersList", json_group_array(DISTINCT CASE WHEN ("milestones"."id" IS NOT NULL) THEN json_object('id', json_quote("milestones"."id"), 'remindersListID', json_quote("milestones"."remindersListID"), 'title', json_quote("milestones"."title")) END) FILTER (WHERE ("milestones"."id" IS NOT NULL)) AS "milestones", json_group_array(DISTINCT CASE WHEN ("reminders"."id" IS NOT NULL) THEN json_object('id', json_quote("reminders"."id"), 'assignedUserID', json_quote("reminders"."assignedUserID"), 'dueDate', json_quote("reminders"."dueDate"), 'isCompleted', json(CASE "reminders"."isCompleted" WHEN 0 THEN 'false' WHEN 1 THEN 'true' END), 'isFlagged', json(CASE "reminders"."isFlagged" WHEN 0 THEN 'false' WHEN 1 THEN 'true' END), 'notes', json_quote("reminders"."notes"), 'priority', json_quote("reminders"."priority"), 'remindersListID', json_quote("reminders"."remindersListID"), 'title', json_quote("reminders"."title")) END) FILTER (WHERE ("reminders"."id" IS NOT NULL)) AS "reminders" + SELECT "remindersLists"."id", "remindersLists"."color", "remindersLists"."title", "remindersLists"."position" AS "remindersList", json_group_array(DISTINCT CASE WHEN ("milestones"."id" IS NOT NULL) THEN json_object('id', json_quote("milestones"."id"), 'remindersListID', json_quote("milestones"."remindersListID"), 'title', json_quote("milestones"."title")) END) FILTER (WHERE ("milestones"."id" IS NOT NULL)) AS "milestones", json_group_array(DISTINCT CASE WHEN ("reminders"."id" IS NOT NULL) THEN json_object('id', json_quote("reminders"."id"), 'assignedUserID', json_quote("reminders"."assignedUserID"), 'dueDate', json_quote("reminders"."dueDate"), 'isCompleted', json(CASE "reminders"."isCompleted" WHEN 0 THEN 'false' WHEN 1 THEN 'true' END), 'isFlagged', json(CASE "reminders"."isFlagged" WHEN 0 THEN 'false' WHEN 1 THEN 'true' END), 'notes', json_quote("reminders"."notes"), 'priority', json_quote("reminders"."priority"), 'remindersListID', json_quote("reminders"."remindersListID"), 'title', json_quote("reminders"."title"), 'updatedAt', json_quote("reminders"."updatedAt")) END) FILTER (WHERE ("reminders"."id" IS NOT NULL)) AS "reminders" FROM "remindersLists" LEFT JOIN "milestones" ON ("remindersLists"."id" = "milestones"."remindersListID") LEFT JOIN "reminders" ON ("remindersLists"."id" = "reminders"."remindersListID") @@ -236,78 +238,83 @@ extension SnapshotTests { """ } results: { """ - ┌────────────────────────────────────────────────┐ - │ RemindersListRow( │ - │ remindersList: RemindersList( │ - │ id: 1, │ - │ color: 4889071, │ - │ title: "Personal" │ - │ ), │ - │ milestones: [ │ - │ [0]: Milestone( │ - │ id: 1, │ - │ remindersListID: 1, │ - │ title: "Phase 1" │ - │ ), │ - │ [1]: Milestone( │ - │ id: 2, │ - │ remindersListID: 1, │ - │ title: "Phase 2" │ - │ ), │ - │ [2]: Milestone( │ - │ id: 3, │ - │ remindersListID: 1, │ - │ title: "Phase 3" │ - │ ) │ - │ ], │ - │ reminders: [ │ - │ [0]: Reminder( │ - │ id: 1, │ - │ assignedUserID: 1, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Milk, Eggs, Apples", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Groceries" │ - │ ), │ - │ [1]: Reminder( │ - │ id: 2, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2000-12-30T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: true, │ - │ notes: "", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Haircut" │ - │ ), │ - │ [2]: Reminder( │ - │ id: 3, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Ask about diet", │ - │ priority: .high, │ - │ remindersListID: 1, │ - │ title: "Doctor appointment" │ - │ ), │ - │ [3]: Reminder( │ - │ id: 5, │ - │ assignedUserID: nil, │ - │ dueDate: nil, │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Buy concert tickets" │ - │ ) │ - │ ] │ - │ ) │ - └────────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────────┐ + │ RemindersListRow( │ + │ remindersList: RemindersList( │ + │ id: 1, │ + │ color: 4889071, │ + │ title: "Personal", │ + │ position: 0 │ + │ ), │ + │ milestones: [ │ + │ [0]: Milestone( │ + │ id: 1, │ + │ remindersListID: 1, │ + │ title: "Phase 1" │ + │ ), │ + │ [1]: Milestone( │ + │ id: 2, │ + │ remindersListID: 1, │ + │ title: "Phase 2" │ + │ ), │ + │ [2]: Milestone( │ + │ id: 3, │ + │ remindersListID: 1, │ + │ title: "Phase 3" │ + │ ) │ + │ ], │ + │ reminders: [ │ + │ [0]: Reminder( │ + │ id: 1, │ + │ assignedUserID: 1, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Milk, Eggs, Apples", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Groceries", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ), │ + │ [1]: Reminder( │ + │ id: 2, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2000-12-30T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: true, │ + │ notes: "", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Haircut", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ), │ + │ [2]: Reminder( │ + │ id: 3, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Ask about diet", │ + │ priority: .high, │ + │ remindersListID: 1, │ + │ title: "Doctor appointment", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ), │ + │ [3]: Reminder( │ + │ id: 5, │ + │ assignedUserID: nil, │ + │ dueDate: nil, │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Buy concert tickets", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + │ ] │ + │ ) │ + └─────────────────────────────────────────────────┘ """ } } diff --git a/Tests/StructuredQueriesTests/LiveTests.swift b/Tests/StructuredQueriesTests/LiveTests.swift index 05c98f9d..62d2d79e 100644 --- a/Tests/StructuredQueriesTests/LiveTests.swift +++ b/Tests/StructuredQueriesTests/LiveTests.swift @@ -9,136 +9,146 @@ extension SnapshotTests { @Test func selectAll() { assertQuery(Reminder.all) { """ - SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt" FROM "reminders" """ } results: { #""" - ┌────────────────────────────────────────────┐ - │ Reminder( │ - │ id: 1, │ - │ assignedUserID: 1, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Milk, Eggs, Apples", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Groceries" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder( │ - │ id: 2, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2000-12-30T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: true, │ - │ notes: "", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Haircut" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder( │ - │ id: 3, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Ask about diet", │ - │ priority: .high, │ - │ remindersListID: 1, │ - │ title: "Doctor appointment" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder( │ - │ id: 4, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2000-06-25T00:00:00.000Z), │ - │ isCompleted: true, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Take a walk" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder( │ - │ id: 5, │ - │ assignedUserID: nil, │ - │ dueDate: nil, │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Buy concert tickets" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder( │ - │ id: 6, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2001-01-03T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: true, │ - │ notes: "", │ - │ priority: .high, │ - │ remindersListID: 2, │ - │ title: "Pick up kids from school" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder( │ - │ id: 7, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2000-12-30T00:00:00.000Z), │ - │ isCompleted: true, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: .low, │ - │ remindersListID: 2, │ - │ title: "Get laundry" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder( │ - │ id: 8, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2001-01-05T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: .high, │ - │ remindersListID: 2, │ - │ title: "Take out trash" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder( │ - │ id: 9, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2001-01-03T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: """ │ - │ Status of tax return │ - │ Expenses for next year │ - │ Changing payroll company │ - │ """, │ - │ priority: nil, │ - │ remindersListID: 3, │ - │ title: "Call accountant" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder( │ - │ id: 10, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2000-12-30T00:00:00.000Z), │ - │ isCompleted: true, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: .medium, │ - │ remindersListID: 3, │ - │ title: "Send weekly emails" │ - │ ) │ - └────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────┐ + │ Reminder( │ + │ id: 1, │ + │ assignedUserID: 1, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Milk, Eggs, Apples", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Groceries", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + ├─────────────────────────────────────────────┤ + │ Reminder( │ + │ id: 2, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2000-12-30T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: true, │ + │ notes: "", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Haircut", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + ├─────────────────────────────────────────────┤ + │ Reminder( │ + │ id: 3, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Ask about diet", │ + │ priority: .high, │ + │ remindersListID: 1, │ + │ title: "Doctor appointment", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + ├─────────────────────────────────────────────┤ + │ Reminder( │ + │ id: 4, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2000-06-25T00:00:00.000Z), │ + │ isCompleted: true, │ + │ isFlagged: false, │ + │ notes: "", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Take a walk", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + ├─────────────────────────────────────────────┤ + │ Reminder( │ + │ id: 5, │ + │ assignedUserID: nil, │ + │ dueDate: nil, │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Buy concert tickets", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + ├─────────────────────────────────────────────┤ + │ Reminder( │ + │ id: 6, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2001-01-03T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: true, │ + │ notes: "", │ + │ priority: .high, │ + │ remindersListID: 2, │ + │ title: "Pick up kids from school", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + ├─────────────────────────────────────────────┤ + │ Reminder( │ + │ id: 7, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2000-12-30T00:00:00.000Z), │ + │ isCompleted: true, │ + │ isFlagged: false, │ + │ notes: "", │ + │ priority: .low, │ + │ remindersListID: 2, │ + │ title: "Get laundry", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + ├─────────────────────────────────────────────┤ + │ Reminder( │ + │ id: 8, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2001-01-05T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "", │ + │ priority: .high, │ + │ remindersListID: 2, │ + │ title: "Take out trash", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + ├─────────────────────────────────────────────┤ + │ Reminder( │ + │ id: 9, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2001-01-03T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: """ │ + │ Status of tax return │ + │ Expenses for next year │ + │ Changing payroll company │ + │ """, │ + │ priority: nil, │ + │ remindersListID: 3, │ + │ title: "Call accountant", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + ├─────────────────────────────────────────────┤ + │ Reminder( │ + │ id: 10, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2000-12-30T00:00:00.000Z), │ + │ isCompleted: true, │ + │ isFlagged: false, │ + │ notes: "", │ + │ priority: .medium, │ + │ remindersListID: 3, │ + │ title: "Send weekly emails", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + └─────────────────────────────────────────────┘ """# } } @@ -184,32 +194,35 @@ extension SnapshotTests { .select { ($0, $1.id.count()) } ) { """ - SELECT "remindersLists"."id", "remindersLists"."color", "remindersLists"."title", count("reminders"."id") + SELECT "remindersLists"."id", "remindersLists"."color", "remindersLists"."title", "remindersLists"."position", count("reminders"."id") FROM "remindersLists" JOIN "reminders" ON ("remindersLists"."id" = "reminders"."remindersListID") GROUP BY "remindersLists"."id" """ } results: { """ - ┌─────────────────────┬───┐ - │ RemindersList( │ 5 │ - │ id: 1, │ │ - │ color: 4889071, │ │ - │ title: "Personal" │ │ - │ ) │ │ - ├─────────────────────┼───┤ - │ RemindersList( │ 3 │ - │ id: 2, │ │ - │ color: 15567157, │ │ - │ title: "Family" │ │ - │ ) │ │ - ├─────────────────────┼───┤ - │ RemindersList( │ 2 │ - │ id: 3, │ │ - │ color: 11689427, │ │ - │ title: "Business" │ │ - │ ) │ │ - └─────────────────────┴───┘ + ┌──────────────────────┬───┐ + │ RemindersList( │ 5 │ + │ id: 1, │ │ + │ color: 4889071, │ │ + │ title: "Personal", │ │ + │ position: 0 │ │ + │ ) │ │ + ├──────────────────────┼───┤ + │ RemindersList( │ 3 │ + │ id: 2, │ │ + │ color: 15567157, │ │ + │ title: "Family", │ │ + │ position: 0 │ │ + │ ) │ │ + ├──────────────────────┼───┤ + │ RemindersList( │ 2 │ + │ id: 3, │ │ + │ color: 11689427, │ │ + │ title: "Business", │ │ + │ position: 0 │ │ + │ ) │ │ + └──────────────────────┴───┘ """ } } @@ -223,7 +236,7 @@ extension SnapshotTests { .select { ($0, $2.title.groupConcat()) } ) { """ - SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", group_concat("tags"."title") + SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt", group_concat("tags"."title") FROM "reminders" JOIN "remindersTags" ON ("reminders"."id" = "remindersTags"."reminderID") JOIN "tags" ON ("remindersTags"."tagID" = "tags"."id") @@ -231,43 +244,46 @@ extension SnapshotTests { """ } results: { """ - ┌────────────────────────────────────────────┬────────────────────┐ - │ Reminder( │ "someday,optional" │ - │ id: 1, │ │ - │ assignedUserID: 1, │ │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ │ - │ isCompleted: false, │ │ - │ isFlagged: false, │ │ - │ notes: "Milk, Eggs, Apples", │ │ - │ priority: nil, │ │ - │ remindersListID: 1, │ │ - │ title: "Groceries" │ │ - │ ) │ │ - ├────────────────────────────────────────────┼────────────────────┤ - │ Reminder( │ "someday,optional" │ - │ id: 2, │ │ - │ assignedUserID: nil, │ │ - │ dueDate: Date(2000-12-30T00:00:00.000Z), │ │ - │ isCompleted: false, │ │ - │ isFlagged: true, │ │ - │ notes: "", │ │ - │ priority: nil, │ │ - │ remindersListID: 1, │ │ - │ title: "Haircut" │ │ - │ ) │ │ - ├────────────────────────────────────────────┼────────────────────┤ - │ Reminder( │ "car,kids" │ - │ id: 4, │ │ - │ assignedUserID: nil, │ │ - │ dueDate: Date(2000-06-25T00:00:00.000Z), │ │ - │ isCompleted: true, │ │ - │ isFlagged: false, │ │ - │ notes: "", │ │ - │ priority: nil, │ │ - │ remindersListID: 1, │ │ - │ title: "Take a walk" │ │ - │ ) │ │ - └────────────────────────────────────────────┴────────────────────┘ + ┌─────────────────────────────────────────────┬────────────────────┐ + │ Reminder( │ "someday,optional" │ + │ id: 1, │ │ + │ assignedUserID: 1, │ │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ │ + │ isCompleted: false, │ │ + │ isFlagged: false, │ │ + │ notes: "Milk, Eggs, Apples", │ │ + │ priority: nil, │ │ + │ remindersListID: 1, │ │ + │ title: "Groceries", │ │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ │ + │ ) │ │ + ├─────────────────────────────────────────────┼────────────────────┤ + │ Reminder( │ "someday,optional" │ + │ id: 2, │ │ + │ assignedUserID: nil, │ │ + │ dueDate: Date(2000-12-30T00:00:00.000Z), │ │ + │ isCompleted: false, │ │ + │ isFlagged: true, │ │ + │ notes: "", │ │ + │ priority: nil, │ │ + │ remindersListID: 1, │ │ + │ title: "Haircut", │ │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ │ + │ ) │ │ + ├─────────────────────────────────────────────┼────────────────────┤ + │ Reminder( │ "car,kids" │ + │ id: 4, │ │ + │ assignedUserID: nil, │ │ + │ dueDate: Date(2000-06-25T00:00:00.000Z), │ │ + │ isCompleted: true, │ │ + │ isFlagged: false, │ │ + │ notes: "", │ │ + │ priority: nil, │ │ + │ remindersListID: 1, │ │ + │ title: "Take a walk", │ │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ │ + │ ) │ │ + └─────────────────────────────────────────────┴────────────────────┘ """ } } diff --git a/Tests/StructuredQueriesTests/OperatorsTests.swift b/Tests/StructuredQueriesTests/OperatorsTests.swift index 856c0842..16ed1f8a 100644 --- a/Tests/StructuredQueriesTests/OperatorsTests.swift +++ b/Tests/StructuredQueriesTests/OperatorsTests.swift @@ -425,7 +425,7 @@ extension SnapshotTests { } ) { """ - SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt" FROM "reminders" WHERE ("reminders"."id" BETWEEN coalesce(( SELECT min("reminders"."id") @@ -437,43 +437,46 @@ extension SnapshotTests { """ } results: { """ - ┌────────────────────────────────────────────┐ - │ Reminder( │ - │ id: 1, │ - │ assignedUserID: 1, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Milk, Eggs, Apples", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Groceries" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder( │ - │ id: 2, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2000-12-30T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: true, │ - │ notes: "", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Haircut" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder( │ - │ id: 3, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Ask about diet", │ - │ priority: .high, │ - │ remindersListID: 1, │ - │ title: "Doctor appointment" │ - │ ) │ - └────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────┐ + │ Reminder( │ + │ id: 1, │ + │ assignedUserID: 1, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Milk, Eggs, Apples", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Groceries", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + ├─────────────────────────────────────────────┤ + │ Reminder( │ + │ id: 2, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2000-12-30T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: true, │ + │ notes: "", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Haircut", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + ├─────────────────────────────────────────────┤ + │ Reminder( │ + │ id: 3, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Ask about diet", │ + │ priority: .high, │ + │ remindersListID: 1, │ + │ title: "Doctor appointment", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + └─────────────────────────────────────────────┘ """ } } @@ -571,7 +574,7 @@ extension SnapshotTests { assertQuery(Values(Reminder.exists())) { """ SELECT EXISTS ( - SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt" FROM "reminders" ) """ @@ -585,7 +588,7 @@ extension SnapshotTests { assertQuery(Values(Reminder.where { $0.id == 1 }.exists())) { """ SELECT EXISTS ( - SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt" FROM "reminders" WHERE ("reminders"."id" = 1) ) @@ -600,7 +603,7 @@ extension SnapshotTests { assertQuery(Values(Reminder.where { $0.id == 100 }.exists())) { """ SELECT EXISTS ( - SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt" FROM "reminders" WHERE ("reminders"."id" = 100) ) diff --git a/Tests/StructuredQueriesTests/SQLMacroTests.swift b/Tests/StructuredQueriesTests/SQLMacroTests.swift index c6706585..40fa5a0b 100644 --- a/Tests/StructuredQueriesTests/SQLMacroTests.swift +++ b/Tests/StructuredQueriesTests/SQLMacroTests.swift @@ -18,26 +18,27 @@ extension SnapshotTests { ) ) { """ - SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt" FROM "reminders" ORDER BY "reminders"."id" LIMIT 1 """ } results: { """ - ┌────────────────────────────────────────────┐ - │ Reminder( │ - │ id: 1, │ - │ assignedUserID: 1, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Milk, Eggs, Apples", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Groceries" │ - │ ) │ - └────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────┐ + │ Reminder( │ + │ id: 1, │ + │ assignedUserID: 1, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Milk, Eggs, Apples", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Groceries", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + └─────────────────────────────────────────────┘ """ } } @@ -59,8 +60,8 @@ extension SnapshotTests { ) { """ SELECT - "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", - "remindersLists"."id", "remindersLists"."color", "remindersLists"."title" + "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt", + "remindersLists"."id", "remindersLists"."color", "remindersLists"."title", "remindersLists"."position" FROM "reminders" JOIN "remindersLists" ON "reminders"."remindersListID" = "remindersLists"."id" @@ -68,19 +69,20 @@ extension SnapshotTests { """ } results: { """ - ┌────────────────────────────────────────────┬─────────────────────┐ - │ Reminder( │ RemindersList( │ - │ id: 1, │ id: 1, │ - │ assignedUserID: 1, │ color: 4889071, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ title: "Personal" │ - │ isCompleted: false, │ ) │ - │ isFlagged: false, │ │ - │ notes: "Milk, Eggs, Apples", │ │ - │ priority: nil, │ │ - │ remindersListID: 1, │ │ - │ title: "Groceries" │ │ - │ ) │ │ - └────────────────────────────────────────────┴─────────────────────┘ + ┌─────────────────────────────────────────────┬──────────────────────┐ + │ Reminder( │ RemindersList( │ + │ id: 1, │ id: 1, │ + │ assignedUserID: 1, │ color: 4889071, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ title: "Personal", │ + │ isCompleted: false, │ position: 0 │ + │ isFlagged: false, │ ) │ + │ notes: "Milk, Eggs, Apples", │ │ + │ priority: nil, │ │ + │ remindersListID: 1, │ │ + │ title: "Groceries", │ │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ │ + │ ) │ │ + └─────────────────────────────────────────────┴──────────────────────┘ """ } } @@ -99,31 +101,33 @@ extension SnapshotTests { ) ) { """ - SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "remindersLists"."id", "remindersLists"."color", "remindersLists"."title" + SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt", "remindersLists"."id", "remindersLists"."color", "remindersLists"."title", "remindersLists"."position" FROM "reminders" JOIN "remindersLists" ON "reminders"."remindersListID" = "remindersLists"."id" LIMIT 1 """ } results: { """ - ┌──────────────────────────────────────────────┐ - │ ReminderWithList( │ - │ reminder: Reminder( │ - │ id: 1, │ - │ assignedUserID: 1, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Milk, Eggs, Apples", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Groceries" │ - │ ), │ - │ list: RemindersList( │ - │ id: 1, │ - │ color: 4889071, │ - │ title: "Personal" │ - │ ) │ - │ ) │ - └──────────────────────────────────────────────┘ + ┌───────────────────────────────────────────────┐ + │ ReminderWithList( │ + │ reminder: Reminder( │ + │ id: 1, │ + │ assignedUserID: 1, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Milk, Eggs, Apples", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Groceries", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ), │ + │ list: RemindersList( │ + │ id: 1, │ + │ color: 4889071, │ + │ title: "Personal", │ + │ position: 0 │ + │ ) │ + │ ) │ + └───────────────────────────────────────────────┘ """ } } diff --git a/Tests/StructuredQueriesTests/SelectTests.swift b/Tests/StructuredQueriesTests/SelectTests.swift index 8ff5f861..1a7c054c 100644 --- a/Tests/StructuredQueriesTests/SelectTests.swift +++ b/Tests/StructuredQueriesTests/SelectTests.swift @@ -169,137 +169,147 @@ extension SnapshotTests { .join(RemindersList.all) { $0.remindersListID.eq($1.id) } ) { """ - SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "remindersLists"."id", "remindersLists"."color", "remindersLists"."title" + SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt", "remindersLists"."id", "remindersLists"."color", "remindersLists"."title", "remindersLists"."position" FROM "reminders" JOIN "remindersLists" ON ("reminders"."remindersListID" = "remindersLists"."id") """ } results: { #""" - ┌────────────────────────────────────────────┬─────────────────────┐ - │ Reminder( │ RemindersList( │ - │ id: 1, │ id: 1, │ - │ assignedUserID: 1, │ color: 4889071, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ title: "Personal" │ - │ isCompleted: false, │ ) │ - │ isFlagged: false, │ │ - │ notes: "Milk, Eggs, Apples", │ │ - │ priority: nil, │ │ - │ remindersListID: 1, │ │ - │ title: "Groceries" │ │ - │ ) │ │ - ├────────────────────────────────────────────┼─────────────────────┤ - │ Reminder( │ RemindersList( │ - │ id: 2, │ id: 1, │ - │ assignedUserID: nil, │ color: 4889071, │ - │ dueDate: Date(2000-12-30T00:00:00.000Z), │ title: "Personal" │ - │ isCompleted: false, │ ) │ - │ isFlagged: true, │ │ - │ notes: "", │ │ - │ priority: nil, │ │ - │ remindersListID: 1, │ │ - │ title: "Haircut" │ │ - │ ) │ │ - ├────────────────────────────────────────────┼─────────────────────┤ - │ Reminder( │ RemindersList( │ - │ id: 3, │ id: 1, │ - │ assignedUserID: nil, │ color: 4889071, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ title: "Personal" │ - │ isCompleted: false, │ ) │ - │ isFlagged: false, │ │ - │ notes: "Ask about diet", │ │ - │ priority: .high, │ │ - │ remindersListID: 1, │ │ - │ title: "Doctor appointment" │ │ - │ ) │ │ - ├────────────────────────────────────────────┼─────────────────────┤ - │ Reminder( │ RemindersList( │ - │ id: 4, │ id: 1, │ - │ assignedUserID: nil, │ color: 4889071, │ - │ dueDate: Date(2000-06-25T00:00:00.000Z), │ title: "Personal" │ - │ isCompleted: true, │ ) │ - │ isFlagged: false, │ │ - │ notes: "", │ │ - │ priority: nil, │ │ - │ remindersListID: 1, │ │ - │ title: "Take a walk" │ │ - │ ) │ │ - ├────────────────────────────────────────────┼─────────────────────┤ - │ Reminder( │ RemindersList( │ - │ id: 5, │ id: 1, │ - │ assignedUserID: nil, │ color: 4889071, │ - │ dueDate: nil, │ title: "Personal" │ - │ isCompleted: false, │ ) │ - │ isFlagged: false, │ │ - │ notes: "", │ │ - │ priority: nil, │ │ - │ remindersListID: 1, │ │ - │ title: "Buy concert tickets" │ │ - │ ) │ │ - ├────────────────────────────────────────────┼─────────────────────┤ - │ Reminder( │ RemindersList( │ - │ id: 6, │ id: 2, │ - │ assignedUserID: nil, │ color: 15567157, │ - │ dueDate: Date(2001-01-03T00:00:00.000Z), │ title: "Family" │ - │ isCompleted: false, │ ) │ - │ isFlagged: true, │ │ - │ notes: "", │ │ - │ priority: .high, │ │ - │ remindersListID: 2, │ │ - │ title: "Pick up kids from school" │ │ - │ ) │ │ - ├────────────────────────────────────────────┼─────────────────────┤ - │ Reminder( │ RemindersList( │ - │ id: 7, │ id: 2, │ - │ assignedUserID: nil, │ color: 15567157, │ - │ dueDate: Date(2000-12-30T00:00:00.000Z), │ title: "Family" │ - │ isCompleted: true, │ ) │ - │ isFlagged: false, │ │ - │ notes: "", │ │ - │ priority: .low, │ │ - │ remindersListID: 2, │ │ - │ title: "Get laundry" │ │ - │ ) │ │ - ├────────────────────────────────────────────┼─────────────────────┤ - │ Reminder( │ RemindersList( │ - │ id: 8, │ id: 2, │ - │ assignedUserID: nil, │ color: 15567157, │ - │ dueDate: Date(2001-01-05T00:00:00.000Z), │ title: "Family" │ - │ isCompleted: false, │ ) │ - │ isFlagged: false, │ │ - │ notes: "", │ │ - │ priority: .high, │ │ - │ remindersListID: 2, │ │ - │ title: "Take out trash" │ │ - │ ) │ │ - ├────────────────────────────────────────────┼─────────────────────┤ - │ Reminder( │ RemindersList( │ - │ id: 9, │ id: 3, │ - │ assignedUserID: nil, │ color: 11689427, │ - │ dueDate: Date(2001-01-03T00:00:00.000Z), │ title: "Business" │ - │ isCompleted: false, │ ) │ - │ isFlagged: false, │ │ - │ notes: """ │ │ - │ Status of tax return │ │ - │ Expenses for next year │ │ - │ Changing payroll company │ │ - │ """, │ │ - │ priority: nil, │ │ - │ remindersListID: 3, │ │ - │ title: "Call accountant" │ │ - │ ) │ │ - ├────────────────────────────────────────────┼─────────────────────┤ - │ Reminder( │ RemindersList( │ - │ id: 10, │ id: 3, │ - │ assignedUserID: nil, │ color: 11689427, │ - │ dueDate: Date(2000-12-30T00:00:00.000Z), │ title: "Business" │ - │ isCompleted: true, │ ) │ - │ isFlagged: false, │ │ - │ notes: "", │ │ - │ priority: .medium, │ │ - │ remindersListID: 3, │ │ - │ title: "Send weekly emails" │ │ - │ ) │ │ - └────────────────────────────────────────────┴─────────────────────┘ + ┌─────────────────────────────────────────────┬──────────────────────┐ + │ Reminder( │ RemindersList( │ + │ id: 1, │ id: 1, │ + │ assignedUserID: 1, │ color: 4889071, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ title: "Personal", │ + │ isCompleted: false, │ position: 0 │ + │ isFlagged: false, │ ) │ + │ notes: "Milk, Eggs, Apples", │ │ + │ priority: nil, │ │ + │ remindersListID: 1, │ │ + │ title: "Groceries", │ │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ │ + │ ) │ │ + ├─────────────────────────────────────────────┼──────────────────────┤ + │ Reminder( │ RemindersList( │ + │ id: 2, │ id: 1, │ + │ assignedUserID: nil, │ color: 4889071, │ + │ dueDate: Date(2000-12-30T00:00:00.000Z), │ title: "Personal", │ + │ isCompleted: false, │ position: 0 │ + │ isFlagged: true, │ ) │ + │ notes: "", │ │ + │ priority: nil, │ │ + │ remindersListID: 1, │ │ + │ title: "Haircut", │ │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ │ + │ ) │ │ + ├─────────────────────────────────────────────┼──────────────────────┤ + │ Reminder( │ RemindersList( │ + │ id: 3, │ id: 1, │ + │ assignedUserID: nil, │ color: 4889071, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ title: "Personal", │ + │ isCompleted: false, │ position: 0 │ + │ isFlagged: false, │ ) │ + │ notes: "Ask about diet", │ │ + │ priority: .high, │ │ + │ remindersListID: 1, │ │ + │ title: "Doctor appointment", │ │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ │ + │ ) │ │ + ├─────────────────────────────────────────────┼──────────────────────┤ + │ Reminder( │ RemindersList( │ + │ id: 4, │ id: 1, │ + │ assignedUserID: nil, │ color: 4889071, │ + │ dueDate: Date(2000-06-25T00:00:00.000Z), │ title: "Personal", │ + │ isCompleted: true, │ position: 0 │ + │ isFlagged: false, │ ) │ + │ notes: "", │ │ + │ priority: nil, │ │ + │ remindersListID: 1, │ │ + │ title: "Take a walk", │ │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ │ + │ ) │ │ + ├─────────────────────────────────────────────┼──────────────────────┤ + │ Reminder( │ RemindersList( │ + │ id: 5, │ id: 1, │ + │ assignedUserID: nil, │ color: 4889071, │ + │ dueDate: nil, │ title: "Personal", │ + │ isCompleted: false, │ position: 0 │ + │ isFlagged: false, │ ) │ + │ notes: "", │ │ + │ priority: nil, │ │ + │ remindersListID: 1, │ │ + │ title: "Buy concert tickets", │ │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ │ + │ ) │ │ + ├─────────────────────────────────────────────┼──────────────────────┤ + │ Reminder( │ RemindersList( │ + │ id: 6, │ id: 2, │ + │ assignedUserID: nil, │ color: 15567157, │ + │ dueDate: Date(2001-01-03T00:00:00.000Z), │ title: "Family", │ + │ isCompleted: false, │ position: 0 │ + │ isFlagged: true, │ ) │ + │ notes: "", │ │ + │ priority: .high, │ │ + │ remindersListID: 2, │ │ + │ title: "Pick up kids from school", │ │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ │ + │ ) │ │ + ├─────────────────────────────────────────────┼──────────────────────┤ + │ Reminder( │ RemindersList( │ + │ id: 7, │ id: 2, │ + │ assignedUserID: nil, │ color: 15567157, │ + │ dueDate: Date(2000-12-30T00:00:00.000Z), │ title: "Family", │ + │ isCompleted: true, │ position: 0 │ + │ isFlagged: false, │ ) │ + │ notes: "", │ │ + │ priority: .low, │ │ + │ remindersListID: 2, │ │ + │ title: "Get laundry", │ │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ │ + │ ) │ │ + ├─────────────────────────────────────────────┼──────────────────────┤ + │ Reminder( │ RemindersList( │ + │ id: 8, │ id: 2, │ + │ assignedUserID: nil, │ color: 15567157, │ + │ dueDate: Date(2001-01-05T00:00:00.000Z), │ title: "Family", │ + │ isCompleted: false, │ position: 0 │ + │ isFlagged: false, │ ) │ + │ notes: "", │ │ + │ priority: .high, │ │ + │ remindersListID: 2, │ │ + │ title: "Take out trash", │ │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ │ + │ ) │ │ + ├─────────────────────────────────────────────┼──────────────────────┤ + │ Reminder( │ RemindersList( │ + │ id: 9, │ id: 3, │ + │ assignedUserID: nil, │ color: 11689427, │ + │ dueDate: Date(2001-01-03T00:00:00.000Z), │ title: "Business", │ + │ isCompleted: false, │ position: 0 │ + │ isFlagged: false, │ ) │ + │ notes: """ │ │ + │ Status of tax return │ │ + │ Expenses for next year │ │ + │ Changing payroll company │ │ + │ """, │ │ + │ priority: nil, │ │ + │ remindersListID: 3, │ │ + │ title: "Call accountant", │ │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ │ + │ ) │ │ + ├─────────────────────────────────────────────┼──────────────────────┤ + │ Reminder( │ RemindersList( │ + │ id: 10, │ id: 3, │ + │ assignedUserID: nil, │ color: 11689427, │ + │ dueDate: Date(2000-12-30T00:00:00.000Z), │ title: "Business", │ + │ isCompleted: true, │ position: 0 │ + │ isFlagged: false, │ ) │ + │ notes: "", │ │ + │ priority: .medium, │ │ + │ remindersListID: 3, │ │ + │ title: "Send weekly emails", │ │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ │ + │ ) │ │ + └─────────────────────────────────────────────┴──────────────────────┘ """# } @@ -357,38 +367,40 @@ extension SnapshotTests { .limit(2) ) { """ - SELECT "users"."id", "users"."name", "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + SELECT "users"."id", "users"."name", "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt" FROM "users" RIGHT JOIN "reminders" ON ("users"."id" IS "reminders"."assignedUserID") LIMIT 2 """ } results: { """ - ┌────────────────┬────────────────────────────────────────────┐ - │ User( │ Reminder( │ - │ id: 1, │ id: 1, │ - │ name: "Blob" │ assignedUserID: 1, │ - │ ) │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ │ isCompleted: false, │ - │ │ isFlagged: false, │ - │ │ notes: "Milk, Eggs, Apples", │ - │ │ priority: nil, │ - │ │ remindersListID: 1, │ - │ │ title: "Groceries" │ - │ │ ) │ - ├────────────────┼────────────────────────────────────────────┤ - │ nil │ Reminder( │ - │ │ id: 2, │ - │ │ assignedUserID: nil, │ - │ │ dueDate: Date(2000-12-30T00:00:00.000Z), │ - │ │ isCompleted: false, │ - │ │ isFlagged: true, │ - │ │ notes: "", │ - │ │ priority: nil, │ - │ │ remindersListID: 1, │ - │ │ title: "Haircut" │ - │ │ ) │ - └────────────────┴────────────────────────────────────────────┘ + ┌────────────────┬─────────────────────────────────────────────┐ + │ User( │ Reminder( │ + │ id: 1, │ id: 1, │ + │ name: "Blob" │ assignedUserID: 1, │ + │ ) │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ │ isCompleted: false, │ + │ │ isFlagged: false, │ + │ │ notes: "Milk, Eggs, Apples", │ + │ │ priority: nil, │ + │ │ remindersListID: 1, │ + │ │ title: "Groceries", │ + │ │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ │ ) │ + ├────────────────┼─────────────────────────────────────────────┤ + │ nil │ Reminder( │ + │ │ id: 2, │ + │ │ assignedUserID: nil, │ + │ │ dueDate: Date(2000-12-30T00:00:00.000Z), │ + │ │ isCompleted: false, │ + │ │ isFlagged: true, │ + │ │ notes: "", │ + │ │ priority: nil, │ + │ │ remindersListID: 1, │ + │ │ title: "Haircut", │ + │ │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ │ ) │ + └────────────────┴─────────────────────────────────────────────┘ """ } @@ -399,38 +411,40 @@ extension SnapshotTests { .select { ($0, $1) } ) { """ - SELECT "users"."id", "users"."name", "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + SELECT "users"."id", "users"."name", "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt" FROM "users" RIGHT JOIN "reminders" ON ("users"."id" IS "reminders"."assignedUserID") LIMIT 2 """ } results: { """ - ┌────────────────┬────────────────────────────────────────────┐ - │ User( │ Reminder( │ - │ id: 1, │ id: 1, │ - │ name: "Blob" │ assignedUserID: 1, │ - │ ) │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ │ isCompleted: false, │ - │ │ isFlagged: false, │ - │ │ notes: "Milk, Eggs, Apples", │ - │ │ priority: nil, │ - │ │ remindersListID: 1, │ - │ │ title: "Groceries" │ - │ │ ) │ - ├────────────────┼────────────────────────────────────────────┤ - │ nil │ Reminder( │ - │ │ id: 2, │ - │ │ assignedUserID: nil, │ - │ │ dueDate: Date(2000-12-30T00:00:00.000Z), │ - │ │ isCompleted: false, │ - │ │ isFlagged: true, │ - │ │ notes: "", │ - │ │ priority: nil, │ - │ │ remindersListID: 1, │ - │ │ title: "Haircut" │ - │ │ ) │ - └────────────────┴────────────────────────────────────────────┘ + ┌────────────────┬─────────────────────────────────────────────┐ + │ User( │ Reminder( │ + │ id: 1, │ id: 1, │ + │ name: "Blob" │ assignedUserID: 1, │ + │ ) │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ │ isCompleted: false, │ + │ │ isFlagged: false, │ + │ │ notes: "Milk, Eggs, Apples", │ + │ │ priority: nil, │ + │ │ remindersListID: 1, │ + │ │ title: "Groceries", │ + │ │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ │ ) │ + ├────────────────┼─────────────────────────────────────────────┤ + │ nil │ Reminder( │ + │ │ id: 2, │ + │ │ assignedUserID: nil, │ + │ │ dueDate: Date(2000-12-30T00:00:00.000Z), │ + │ │ isCompleted: false, │ + │ │ isFlagged: true, │ + │ │ notes: "", │ + │ │ priority: nil, │ + │ │ remindersListID: 1, │ + │ │ title: "Haircut", │ + │ │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ │ ) │ + └────────────────┴─────────────────────────────────────────────┘ """ } @@ -482,49 +496,52 @@ extension SnapshotTests { Reminder.where(\.isCompleted) ) { """ - SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt" FROM "reminders" WHERE "reminders"."isCompleted" """ } results: { """ - ┌────────────────────────────────────────────┐ - │ Reminder( │ - │ id: 4, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2000-06-25T00:00:00.000Z), │ - │ isCompleted: true, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Take a walk" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder( │ - │ id: 7, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2000-12-30T00:00:00.000Z), │ - │ isCompleted: true, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: .low, │ - │ remindersListID: 2, │ - │ title: "Get laundry" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder( │ - │ id: 10, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2000-12-30T00:00:00.000Z), │ - │ isCompleted: true, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: .medium, │ - │ remindersListID: 3, │ - │ title: "Send weekly emails" │ - │ ) │ - └────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────┐ + │ Reminder( │ + │ id: 4, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2000-06-25T00:00:00.000Z), │ + │ isCompleted: true, │ + │ isFlagged: false, │ + │ notes: "", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Take a walk", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + ├─────────────────────────────────────────────┤ + │ Reminder( │ + │ id: 7, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2000-12-30T00:00:00.000Z), │ + │ isCompleted: true, │ + │ isFlagged: false, │ + │ notes: "", │ + │ priority: .low, │ + │ remindersListID: 2, │ + │ title: "Get laundry", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + ├─────────────────────────────────────────────┤ + │ Reminder( │ + │ id: 10, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2000-12-30T00:00:00.000Z), │ + │ isCompleted: true, │ + │ isFlagged: false, │ + │ notes: "", │ + │ priority: .medium, │ + │ remindersListID: 3, │ + │ title: "Send weekly emails", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + └─────────────────────────────────────────────┘ """ } } @@ -905,25 +922,26 @@ extension SnapshotTests { } assertQuery(Reminder.limit(1).select { ($0.id, $0.title) }.map { _, _ in }) { """ - SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt" FROM "reminders" LIMIT 1 """ } results: { """ - ┌────────────────────────────────────────────┐ - │ Reminder( │ - │ id: 1, │ - │ assignedUserID: 1, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Milk, Eggs, Apples", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Groceries" │ - │ ) │ - └────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────┐ + │ Reminder( │ + │ id: 1, │ + │ assignedUserID: 1, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Milk, Eggs, Apples", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Groceries", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + └─────────────────────────────────────────────┘ """ } assertQuery(Reminder.limit(1).select { ($0.id, $0.title) }.map { ($1, $0) }) { @@ -975,26 +993,27 @@ extension SnapshotTests { .limit(1) ) { """ - SELECT "r1s"."id", "r1s"."assignedUserID", "r1s"."dueDate", "r1s"."isCompleted", "r1s"."isFlagged", "r1s"."notes", "r1s"."priority", "r1s"."remindersListID", "r1s"."title", "r2s"."id", "r2s"."assignedUserID", "r2s"."dueDate", "r2s"."isCompleted", "r2s"."isFlagged", "r2s"."notes", "r2s"."priority", "r2s"."remindersListID", "r2s"."title" + SELECT "r1s"."id", "r1s"."assignedUserID", "r1s"."dueDate", "r1s"."isCompleted", "r1s"."isFlagged", "r1s"."notes", "r1s"."priority", "r1s"."remindersListID", "r1s"."title", "r1s"."updatedAt", "r2s"."id", "r2s"."assignedUserID", "r2s"."dueDate", "r2s"."isCompleted", "r2s"."isFlagged", "r2s"."notes", "r2s"."priority", "r2s"."remindersListID", "r2s"."title", "r2s"."updatedAt" FROM "reminders" AS "r1s" JOIN "reminders" AS "r2s" ON ("r1s"."id" = "r2s"."id") LIMIT 1 """ } results: { """ - ┌────────────────────────────────────────────┬────────────────────────────────────────────┐ - │ Reminder( │ Reminder( │ - │ id: 1, │ id: 1, │ - │ assignedUserID: 1, │ assignedUserID: 1, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ isCompleted: false, │ - │ isFlagged: false, │ isFlagged: false, │ - │ notes: "Milk, Eggs, Apples", │ notes: "Milk, Eggs, Apples", │ - │ priority: nil, │ priority: nil, │ - │ remindersListID: 1, │ remindersListID: 1, │ - │ title: "Groceries" │ title: "Groceries" │ - │ ) │ ) │ - └────────────────────────────────────────────┴────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────┬─────────────────────────────────────────────┐ + │ Reminder( │ Reminder( │ + │ id: 1, │ id: 1, │ + │ assignedUserID: 1, │ assignedUserID: 1, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ isCompleted: false, │ + │ isFlagged: false, │ isFlagged: false, │ + │ notes: "Milk, Eggs, Apples", │ notes: "Milk, Eggs, Apples", │ + │ priority: nil, │ priority: nil, │ + │ remindersListID: 1, │ remindersListID: 1, │ + │ title: "Groceries", │ title: "Groceries", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ ) │ + └─────────────────────────────────────────────┴─────────────────────────────────────────────┘ """ } } @@ -1030,7 +1049,7 @@ extension SnapshotTests { .select { ($0, $1.jsonGroupArray()) } ) { """ - SELECT "r1s"."id", "r1s"."assignedUserID", "r1s"."dueDate", "r1s"."isCompleted", "r1s"."isFlagged", "r1s"."notes", "r1s"."priority", "r1s"."remindersListID", "r1s"."title", json_group_array(CASE WHEN ("r2s"."id" IS NOT NULL) THEN json_object('id', json_quote("r2s"."id"), 'assignedUserID', json_quote("r2s"."assignedUserID"), 'dueDate', json_quote("r2s"."dueDate"), 'isCompleted', json(CASE "r2s"."isCompleted" WHEN 0 THEN 'false' WHEN 1 THEN 'true' END), 'isFlagged', json(CASE "r2s"."isFlagged" WHEN 0 THEN 'false' WHEN 1 THEN 'true' END), 'notes', json_quote("r2s"."notes"), 'priority', json_quote("r2s"."priority"), 'remindersListID', json_quote("r2s"."remindersListID"), 'title', json_quote("r2s"."title")) END) + SELECT "r1s"."id", "r1s"."assignedUserID", "r1s"."dueDate", "r1s"."isCompleted", "r1s"."isFlagged", "r1s"."notes", "r1s"."priority", "r1s"."remindersListID", "r1s"."title", "r1s"."updatedAt", json_group_array(CASE WHEN ("r2s"."id" IS NOT NULL) THEN json_object('id', json_quote("r2s"."id"), 'assignedUserID', json_quote("r2s"."assignedUserID"), 'dueDate', json_quote("r2s"."dueDate"), 'isCompleted', json(CASE "r2s"."isCompleted" WHEN 0 THEN 'false' WHEN 1 THEN 'true' END), 'isFlagged', json(CASE "r2s"."isFlagged" WHEN 0 THEN 'false' WHEN 1 THEN 'true' END), 'notes', json_quote("r2s"."notes"), 'priority', json_quote("r2s"."priority"), 'remindersListID', json_quote("r2s"."remindersListID"), 'title', json_quote("r2s"."title"), 'updatedAt', json_quote("r2s"."updatedAt")) END) FROM "reminders" AS "r1s" LEFT JOIN "reminders" AS "r2s" ON ("r1s"."id" = "r2s"."id") GROUP BY "r1s"."id" @@ -1038,23 +1057,24 @@ extension SnapshotTests { """ } results: { """ - ┌────────────────────────────────────────────┬────────────────────────────────────────────────┐ - │ Reminder( │ [ │ - │ id: 1, │ [0]: TableAlias( │ - │ assignedUserID: 1, │ base: Reminder( │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ id: 1, │ - │ isCompleted: false, │ assignedUserID: 1, │ - │ isFlagged: false, │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ notes: "Milk, Eggs, Apples", │ isCompleted: false, │ - │ priority: nil, │ isFlagged: false, │ - │ remindersListID: 1, │ notes: "Milk, Eggs, Apples", │ - │ title: "Groceries" │ priority: nil, │ - │ ) │ remindersListID: 1, │ - │ │ title: "Groceries" │ - │ │ ) │ - │ │ ) │ - │ │ ] │ - └────────────────────────────────────────────┴────────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────┬─────────────────────────────────────────────────┐ + │ Reminder( │ [ │ + │ id: 1, │ [0]: TableAlias( │ + │ assignedUserID: 1, │ base: Reminder( │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ id: 1, │ + │ isCompleted: false, │ assignedUserID: 1, │ + │ isFlagged: false, │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ notes: "Milk, Eggs, Apples", │ isCompleted: false, │ + │ priority: nil, │ isFlagged: false, │ + │ remindersListID: 1, │ notes: "Milk, Eggs, Apples", │ + │ title: "Groceries", │ priority: nil, │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ remindersListID: 1, │ + │ ) │ title: "Groceries", │ + │ │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ │ ) │ + │ │ ) │ + │ │ ] │ + └─────────────────────────────────────────────┴─────────────────────────────────────────────────┘ """ } } @@ -1123,50 +1143,53 @@ extension SnapshotTests { .where { $1.isHighPriority.ifnull(false) } ) { """ - SELECT "remindersLists"."id", "remindersLists"."color", "remindersLists"."title", "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + SELECT "remindersLists"."id", "remindersLists"."color", "remindersLists"."title", "remindersLists"."position", "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt" FROM "remindersLists" LEFT JOIN "reminders" ON ("remindersLists"."id" = "reminders"."remindersListID") WHERE ifnull(("reminders"."priority" IS 3), 0) """ } results: { """ - ┌─────────────────────┬────────────────────────────────────────────┐ - │ RemindersList( │ Reminder( │ - │ id: 1, │ id: 3, │ - │ color: 4889071, │ assignedUserID: nil, │ - │ title: "Personal" │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ ) │ isCompleted: false, │ - │ │ isFlagged: false, │ - │ │ notes: "Ask about diet", │ - │ │ priority: .high, │ - │ │ remindersListID: 1, │ - │ │ title: "Doctor appointment" │ - │ │ ) │ - ├─────────────────────┼────────────────────────────────────────────┤ - │ RemindersList( │ Reminder( │ - │ id: 2, │ id: 6, │ - │ color: 15567157, │ assignedUserID: nil, │ - │ title: "Family" │ dueDate: Date(2001-01-03T00:00:00.000Z), │ - │ ) │ isCompleted: false, │ - │ │ isFlagged: true, │ - │ │ notes: "", │ - │ │ priority: .high, │ - │ │ remindersListID: 2, │ - │ │ title: "Pick up kids from school" │ - │ │ ) │ - ├─────────────────────┼────────────────────────────────────────────┤ - │ RemindersList( │ Reminder( │ - │ id: 2, │ id: 8, │ - │ color: 15567157, │ assignedUserID: nil, │ - │ title: "Family" │ dueDate: Date(2001-01-05T00:00:00.000Z), │ - │ ) │ isCompleted: false, │ - │ │ isFlagged: false, │ - │ │ notes: "", │ - │ │ priority: .high, │ - │ │ remindersListID: 2, │ - │ │ title: "Take out trash" │ - │ │ ) │ - └─────────────────────┴────────────────────────────────────────────┘ + ┌──────────────────────┬─────────────────────────────────────────────┐ + │ RemindersList( │ Reminder( │ + │ id: 1, │ id: 3, │ + │ color: 4889071, │ assignedUserID: nil, │ + │ title: "Personal", │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ position: 0 │ isCompleted: false, │ + │ ) │ isFlagged: false, │ + │ │ notes: "Ask about diet", │ + │ │ priority: .high, │ + │ │ remindersListID: 1, │ + │ │ title: "Doctor appointment", │ + │ │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ │ ) │ + ├──────────────────────┼─────────────────────────────────────────────┤ + │ RemindersList( │ Reminder( │ + │ id: 2, │ id: 6, │ + │ color: 15567157, │ assignedUserID: nil, │ + │ title: "Family", │ dueDate: Date(2001-01-03T00:00:00.000Z), │ + │ position: 0 │ isCompleted: false, │ + │ ) │ isFlagged: true, │ + │ │ notes: "", │ + │ │ priority: .high, │ + │ │ remindersListID: 2, │ + │ │ title: "Pick up kids from school", │ + │ │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ │ ) │ + ├──────────────────────┼─────────────────────────────────────────────┤ + │ RemindersList( │ Reminder( │ + │ id: 2, │ id: 8, │ + │ color: 15567157, │ assignedUserID: nil, │ + │ title: "Family", │ dueDate: Date(2001-01-05T00:00:00.000Z), │ + │ position: 0 │ isCompleted: false, │ + │ ) │ isFlagged: false, │ + │ │ notes: "", │ + │ │ priority: .high, │ + │ │ remindersListID: 2, │ + │ │ title: "Take out trash", │ + │ │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ │ ) │ + └──────────────────────┴─────────────────────────────────────────────┘ """ } } diff --git a/Tests/StructuredQueriesTests/SelectionTests.swift b/Tests/StructuredQueriesTests/SelectionTests.swift index f02e5818..daef8585 100644 --- a/Tests/StructuredQueriesTests/SelectionTests.swift +++ b/Tests/StructuredQueriesTests/SelectionTests.swift @@ -19,7 +19,7 @@ extension SnapshotTests { } ) { """ - SELECT "remindersLists"."id", "remindersLists"."color", "remindersLists"."title" AS "remindersList", count("reminders"."id") AS "remindersCount" + SELECT "remindersLists"."id", "remindersLists"."color", "remindersLists"."title", "remindersLists"."position" AS "remindersList", count("reminders"."id") AS "remindersCount" FROM "remindersLists" JOIN "reminders" ON ("remindersLists"."id" = "reminders"."remindersListID") GROUP BY "remindersLists"."id" @@ -32,7 +32,8 @@ extension SnapshotTests { │ remindersList: RemindersList( │ │ id: 1, │ │ color: 4889071, │ - │ title: "Personal" │ + │ title: "Personal", │ + │ position: 0 │ │ ), │ │ remindersCount: 5 │ │ ) │ @@ -41,7 +42,8 @@ extension SnapshotTests { │ remindersList: RemindersList( │ │ id: 2, │ │ color: 15567157, │ - │ title: "Family" │ + │ title: "Family", │ + │ position: 0 │ │ ), │ │ remindersCount: 3 │ │ ) │ @@ -54,7 +56,7 @@ extension SnapshotTests { .map { RemindersListAndReminderCount.Columns(remindersList: $1, remindersCount: $0) } ) { """ - SELECT "remindersLists"."id", "remindersLists"."color", "remindersLists"."title" AS "remindersList", count("reminders"."id") AS "remindersCount" + SELECT "remindersLists"."id", "remindersLists"."color", "remindersLists"."title", "remindersLists"."position" AS "remindersList", count("reminders"."id") AS "remindersCount" FROM "remindersLists" JOIN "reminders" ON ("remindersLists"."id" = "reminders"."remindersListID") GROUP BY "remindersLists"."id" @@ -67,7 +69,8 @@ extension SnapshotTests { │ remindersList: RemindersList( │ │ id: 1, │ │ color: 4889071, │ - │ title: "Personal" │ + │ title: "Personal", │ + │ position: 0 │ │ ), │ │ remindersCount: 5 │ │ ) │ @@ -76,7 +79,8 @@ extension SnapshotTests { │ remindersList: RemindersList( │ │ id: 2, │ │ color: 15567157, │ - │ title: "Family" │ + │ title: "Family", │ + │ position: 0 │ │ ), │ │ remindersCount: 3 │ │ ) │ diff --git a/Tests/StructuredQueriesTests/Support/Schema.swift b/Tests/StructuredQueriesTests/Support/Schema.swift index 0fc6ef23..596db2f8 100644 --- a/Tests/StructuredQueriesTests/Support/Schema.swift +++ b/Tests/StructuredQueriesTests/Support/Schema.swift @@ -12,6 +12,7 @@ struct RemindersList: Codable, Equatable, Identifiable { let id: Int var color = 0x4a99ef var title = "" + var position = 0 } @Table @@ -27,6 +28,7 @@ struct Reminder: Codable, Equatable, Identifiable { var priority: Priority? var remindersListID: Int var title = "" + var updatedAt: Date = Date(timeIntervalSinceReferenceDate: 1_234_567_890) static func searching(_ text: String) -> Where { Self.where { $0.title.collate(.nocase).contains(text) @@ -85,7 +87,8 @@ extension Database { CREATE TABLE "remindersLists" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "color" INTEGER NOT NULL DEFAULT 4889071, - "title" TEXT NOT NULL DEFAULT '' + "title" TEXT NOT NULL DEFAULT '', + "position" INTEGER NOT NULL DEFAULT 0 ) """ ) @@ -105,7 +108,8 @@ extension Database { "remindersListID" INTEGER NOT NULL REFERENCES "remindersLists"("id") ON DELETE CASCADE, "notes" TEXT NOT NULL DEFAULT '', "priority" INTEGER, - "title" TEXT NOT NULL DEFAULT '' + "title" TEXT NOT NULL DEFAULT '', + "updatedAt" TEXT NOT NULL DEFAULT (datetime('subsec')) ) """ ) diff --git a/Tests/StructuredQueriesTests/TriggersTests.swift b/Tests/StructuredQueriesTests/TriggersTests.swift new file mode 100644 index 00000000..49071dce --- /dev/null +++ b/Tests/StructuredQueriesTests/TriggersTests.swift @@ -0,0 +1,154 @@ +import Dependencies +import Foundation +import InlineSnapshotTesting +import StructuredQueries +import StructuredQueriesSQLite +import Testing + +extension SnapshotTests { + @Suite struct TriggersTests { + @Test func basics() { + let trigger = RemindersList.createTemporaryTrigger( + after: .insert { new in + RemindersList + .update { + $0.position = RemindersList.select { ($0.position.max() ?? -1) + 1 } + } + .where { $0.id.eq(new.id) } + } + ) + assertQuery(trigger) { + """ + CREATE TEMPORARY TRIGGER + "after_insert_on_remindersLists@StructuredQueriesTests/TriggersTests.swift:11:57" + AFTER INSERT ON "remindersLists" + FOR EACH ROW BEGIN + UPDATE "remindersLists" + SET "position" = ( + SELECT (coalesce(max("remindersLists"."position"), -1) + 1) + FROM "remindersLists" + ) + WHERE ("remindersLists"."id" = "new"."id"); + END + """ + } + assertQuery(trigger.drop()) { + """ + DROP TRIGGER "after_insert_on_remindersLists@StructuredQueriesTests/TriggersTests.swift:11:57" + """ + } + } + + @Test func dateDiagnostic() { + withKnownIssue { + assertQuery( + Reminder.createTemporaryTrigger( + after: .update { _, new in + Reminder + .update { $0.dueDate = Date(timeIntervalSinceReferenceDate: 0) } + .where { $0.id.eq(new.id) } + } + ) + ) { + """ + CREATE TEMPORARY TRIGGER + "after_update_on_reminders@StructuredQueriesTests/TriggersTests.swift:45:42" + AFTER UPDATE ON "reminders" + FOR EACH ROW BEGIN + UPDATE "reminders" + SET "dueDate" = '2001-01-01 00:00:00.000' + WHERE ("reminders"."id" = "new"."id"); + END + """ + } + } matching: { + $0.description.contains( + """ + Cannot bind a date to a trigger statement. Specify dates using the '#sql' macro, \ + instead. For example, the current date: + + #sql("datetime()") + + Or a constant date: + + #sql("'2018-01-29 00:08:00'") + """ + ) + } + } + + @Test func afterUpdateTouch() { + assertQuery( + RemindersList.createTemporaryTrigger( + afterUpdateTouch: { + $0.position += 1 + } + ) + ) { + """ + CREATE TEMPORARY TRIGGER + "after_update_on_remindersLists@StructuredQueriesTests/TriggersTests.swift:82:45" + AFTER UPDATE ON "remindersLists" + FOR EACH ROW BEGIN + UPDATE "remindersLists" + SET "position" = ("remindersLists"."position" + 1) + WHERE ("remindersLists"."rowid" = "new"."rowid"); + END + """ + } + } + + @Test func afterUpdateTouchDate() { + assertQuery( + Reminder.createTemporaryTrigger(afterUpdateTouch: \.updatedAt) + ) { + """ + CREATE TEMPORARY TRIGGER + "after_update_on_reminders@StructuredQueriesTests/TriggersTests.swift:103:40" + AFTER UPDATE ON "reminders" + FOR EACH ROW BEGIN + UPDATE "reminders" + SET "updatedAt" = datetime('subsec') + WHERE ("reminders"."rowid" = "new"."rowid"); + END + """ + } + } + + @Test func multiStatement() { + let trigger = RemindersList.createTemporaryTrigger( + after: .insert { new in + RemindersList + .update { + $0.position = RemindersList.select { ($0.position.max() ?? -1) + 1 } + } + .where { $0.id.eq(new.id) } + RemindersList + .where { $0.position.eq(0) } + .delete() + RemindersList + .select(\.position) + } + ) + assertQuery(trigger) { + """ + CREATE TEMPORARY TRIGGER + "after_insert_on_remindersLists@StructuredQueriesTests/TriggersTests.swift:119:57" + AFTER INSERT ON "remindersLists" + FOR EACH ROW BEGIN + UPDATE "remindersLists" + SET "position" = ( + SELECT (coalesce(max("remindersLists"."position"), -1) + 1) + FROM "remindersLists" + ) + WHERE ("remindersLists"."id" = "new"."id"); + DELETE FROM "remindersLists" + WHERE ("remindersLists"."position" = 0); + SELECT "remindersLists"."position" + FROM "remindersLists"; + END + """ + } + } + } +} diff --git a/Tests/StructuredQueriesTests/UpdateTests.swift b/Tests/StructuredQueriesTests/UpdateTests.swift index 1fcbc3f8..66b35c5d 100644 --- a/Tests/StructuredQueriesTests/UpdateTests.swift +++ b/Tests/StructuredQueriesTests/UpdateTests.swift @@ -101,25 +101,26 @@ extension SnapshotTests { ) { """ UPDATE "reminders" - SET "assignedUserID" = 1, "dueDate" = '2001-01-01 00:00:00.000', "isCompleted" = 1, "isFlagged" = 0, "notes" = 'Milk, Eggs, Apples', "priority" = NULL, "remindersListID" = 1, "title" = 'Groceries' + SET "assignedUserID" = 1, "dueDate" = '2001-01-01 00:00:00.000', "isCompleted" = 1, "isFlagged" = 0, "notes" = 'Milk, Eggs, Apples', "priority" = NULL, "remindersListID" = 1, "title" = 'Groceries', "updatedAt" = '2040-02-14 23:31:30.000' WHERE ("reminders"."id" = 1) - RETURNING "id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title" + RETURNING "id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt" """ } results: { """ - ┌────────────────────────────────────────────┐ - │ Reminder( │ - │ id: 1, │ - │ assignedUserID: 1, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: true, │ - │ isFlagged: false, │ - │ notes: "Milk, Eggs, Apples", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Groceries" │ - │ ) │ - └────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────┐ + │ Reminder( │ + │ id: 1, │ + │ assignedUserID: 1, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: true, │ + │ isFlagged: false, │ + │ notes: "Milk, Eggs, Apples", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Groceries", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + └─────────────────────────────────────────────┘ """ } } @@ -270,23 +271,24 @@ extension SnapshotTests { UPDATE "reminders" AS "rs" SET "title" = ("rs"."title" || ' 2') WHERE ("rs"."id" = 1) - RETURNING "id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title" + RETURNING "id", "assignedUserID", "dueDate", "isCompleted", "isFlagged", "notes", "priority", "remindersListID", "title", "updatedAt" """ } results: { """ - ┌────────────────────────────────────────────┐ - │ Reminder( │ - │ id: 1, │ - │ assignedUserID: 1, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Milk, Eggs, Apples", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Groceries 2" │ - │ ) │ - └────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────┐ + │ Reminder( │ + │ id: 1, │ + │ assignedUserID: 1, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Milk, Eggs, Apples", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Groceries 2", │ + │ updatedAt: Date(2040-02-14T23:31:30.000Z) │ + │ ) │ + └─────────────────────────────────────────────┘ """ } } @@ -340,7 +342,7 @@ extension SnapshotTests { ) { """ UPDATE "reminders" - SET "assignedUserID" = NULL, "dueDate" = NULL, "isCompleted" = 1, "isFlagged" = 0, "notes" = '', "priority" = NULL, "remindersListID" = 1, "title" = 'Buy iPhone' + SET "assignedUserID" = NULL, "dueDate" = NULL, "isCompleted" = 1, "isFlagged" = 0, "notes" = '', "priority" = NULL, "remindersListID" = 1, "title" = 'Buy iPhone', "updatedAt" = '2040-02-14 23:31:30.000' WHERE ("reminders"."id" = 100) """ } diff --git a/Tests/StructuredQueriesTests/WhereTests.swift b/Tests/StructuredQueriesTests/WhereTests.swift index 62a01989..c413d694 100644 --- a/Tests/StructuredQueriesTests/WhereTests.swift +++ b/Tests/StructuredQueriesTests/WhereTests.swift @@ -147,7 +147,7 @@ extension SnapshotTests { .where { $1.isCompleted } ) { """ - SELECT "remindersLists"."id", "remindersLists"."color", "remindersLists"."title", "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + SELECT "remindersLists"."id", "remindersLists"."color", "remindersLists"."title", "remindersLists"."position", "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt" FROM "remindersLists" LEFT JOIN "reminders" ON ("remindersLists"."id" = "reminders"."remindersListID") WHERE ("remindersLists"."id" = 4) AND "reminders"."isCompleted"