diff --git a/README.md b/README.md index cc27ff4..1524911 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,6 @@ struct Reminder { var title = "" var isCompleted = false var priority: Int? - @Column(as: Date.ISO8601Representation?.self) var dueDate: Date? } ``` diff --git a/Sources/StructuredQueriesCore/Documentation.docc/Articles/DefiningYourSchema.md b/Sources/StructuredQueriesCore/Documentation.docc/Articles/DefiningYourSchema.md index c582cd1..5ee3f35 100644 --- a/Sources/StructuredQueriesCore/Documentation.docc/Articles/DefiningYourSchema.md +++ b/Sources/StructuredQueriesCore/Documentation.docc/Articles/DefiningYourSchema.md @@ -14,6 +14,17 @@ you can use the static description of its properties to build type-safe queries. schema of your app is defined first and foremost in your database, and then you define Swift types that represent those database definitions. +* [Defining a table](#Defining-a-table) +* [Customizing a table](#Customizing-a-table) + * [Table names](#Table-names) + * [Column names](#Column-names) + * [Custom data types](#Custom-data-types) + * [RawRepresentable](#RawRepresentable) + * [JSON](#JSON) + * [Default representations for dates and UUIDs](#Default-representations-for-dates-and-UUIDs) +* [Primary keyed tables](#Primary-keyed-tables) +* [Ephemeral columns](#Ephemeral-columns) + ### Defining a table Suppose your database has a table defined with the following create statement: @@ -152,113 +163,6 @@ with your table's columns, instead. For these data types you must either define The library comes with several `QueryRepresentable` conformances to aid in representing dates, UUIDs, and JSON, and you can define your own conformances for your own custom data types. -#### Dates - -While some relational databases, like MySQL and Postgres, have native support for dates, SQLite -does _not_. Instead, it has 3 different ways to represent dates: - - * Text column interpreted as ISO-8601-formatted string. - * Int column interpreted as number of seconds since Unix epoch. - * Double column interpreted as a Julian day (number of days since November 24, 4713 BC). - -Because of this ambiguity, the `@Table` macro does not know what you intend when you define a data -type like this: - -```swift -@Table struct Reminder { - let id: Int - var date: Date // 🛑 'Date' column requires a query representation -} -``` - -In order to make it explicit how you expect to turn a `Date` into a type that SQLite understands -(_i.e._ text, integer, or double) you can use the `@Column` macro with the `as:` argument. The -library comes with 3 strategies out the box to help, ``Foundation/Date/ISO8601Representation`` for -storing the date as a string, ``Foundation/Date/UnixTimeRepresentation`` for storing the date as an -integer, and ``Foundation/Date/JulianDayRepresentation`` for storing the date as a floating point -number. - -Any of these representations can be used like so: - -```swift -@Table struct Reminder { - let id: Int - @Column(as: Date.ISO8601Representation.self) - var date: Date -} -``` - -And StructuredQueries will take care of formatting the value for the database: - -@Row { - @Column { - ```swift - Reminder.insert( - Reminder.Draft(date: Date()) - ) - ``` - } - @Column { - ```sql - INSERT INTO "reminders" - ("date") - VALUES - ('2018-01-29 00:08:00.000') - ``` - } -} - -When querying against a date column with a Swift date, you will need to explicitly bundle up the -Swift date into the appropriate representation to use various query helpers. This can be done using -the `#bind` macro: - -```swift -Reminder.where { $0.created > #bind(startDate) } -``` - -#### UUID - -SQLite also does not have native support for UUIDs. If you try to use a UUID in your tables you -will get an error: - -```swift -@Table struct Reminder { - let id: UUID // 🛑 'UUID' column requires a query representation - var title = "" -} -``` - -To use such identifiers in your table you can store the column as a data blob, and then you can -use the ``Foundation/UUID/BytesRepresentation`` column representation: - -```swift -@Table struct Reminder { - @Column(as: UUID.BytesRepresentation.self) - let id: UUID - var title = "" -} -``` - -Alternatively you can store the column as text and use either -``Foundation/UUID/LowercasedRepresentation`` or ``Foundation/UUID/UppercasedRepresentation`` to -translate the UUID to text: - -```swift -@Table struct Reminder { - @Column(as: UUID.LowercasedRepresentation.self) - let id: UUID - var title = "" -} -``` - -When querying against a UUID column with a Swift UUID, you will need to explicitly bundle up the -Swift UUID into the appropriate representation to use various query helpers. This can be done using -the `#bind` macro: - -```swift -Reminder.where { $0.id != #bind(reminder.id) } -``` - #### RawRepresentable Simple data types, in particular ones conforming to `RawRepresentable` whose `RawValue` is a string @@ -353,6 +257,106 @@ With that you can insert reminders with notes like so: } } +#### Default representations for dates and UUIDs + +While some relational databases, like MySQL and Postgres, have native types for dates and UUIDs, +SQLite does _not_, and instead can represent them in a variety of ways. In order to lessen the +friction of building queries with dates and UUIDs, the library has decided to provide a default +representation for dates and UUIDs, and if that choice does not fit your schema you can explicitly +specify the representation you want. + +##### Dates + +Dates in SQLite have 3 different representations: + + * Text column interpreted as ISO-8601-formatted string. + * Int column interpreted as number of seconds since Unix epoch. + * Double column interpreted as a Julian day (number of days since November 24, 4713 BC). + +By default, StructuredQueries will bind and decode dates as ISO-8601 text. If you want the library +to use a different representation (_i.e._ integer or double), you can provide an explicit query +representation to the `@Column` macro's `as:` argument. ``Foundation/Date/UnixTimeRepresentation`` +will store the date as an integer, and ``Foundation/Date/JulianDayRepresentation`` will store the +date as a floating point number. + +For example: + +```swift +@Table struct Reminder { + let id: Int + @Column(as: Date.UnixTimeRepresentation.self) + var date: Date +} +``` + +And StructuredQueries will take care of formatting the value for the database: + +@Row { + @Column { + ```swift + Reminder.insert( + Reminder.Draft(date: Date()) + ) + ``` + } + @Column { + ```sql + INSERT INTO "reminders" + ("date") + VALUES + (1517184480) + ``` + } +} + +If you use the non-default date representation in your schema, then while querying against a +date column with a Swift Date, you will need to explicitly bundle up the Swift date into the +appropriate representation to use various query helpers. This can be done using the `#bind` macro: + +```swift +Reminder.where { $0.created > #bind(startDate) } +``` + +> Note: When using the default representation for dates (ISO-8601 text) you do not need to use +> the `#bind` macro: +> +> ```swift +> Reminder.where { $0.created > startDate } +> ``` + +##### UUIDs + +SQLite also does not have type-level support for UUIDs. By default, the library will bind and decode +UUIDs as lowercased, hexadecimal text, but it also provides custom representations. This includes +``Foundation/UUID/UppercasedRepresentation`` for uppercased text, as well as +``Foundation/UUID/BytesRepresentation`` for raw bytes. + +To use such custom representations, you can provide it to the `@Column` macro's `as:` parameter: + +```swift +@Table struct Reminder { + @Column(as: UUID.BytesRepresentation.self) + let id: UUID + var title = "" +} +``` + +If you use the non-default UUID representation in your schema, then while querying against a UUID +column with a Swift UUID, you will need to explicitly bundle up the Swift UUID into the appropriate +representation to use various query helpers. This can be done using +the `#bind` macro: + +```swift +Reminder.where { $0.id != #bind(reminder.id) } +``` + +> Note: When using the default representation for UUID (lower-cased text) you do not need to use +> the `#bind` macro: +> +> ```swift +> Reminder.where { $0.id != reminder.id } +> ``` + ### Primary keyed tables It is possible to tell let the `@Table` macro know which property of your data type is the primary diff --git a/Sources/StructuredQueriesCore/Documentation.docc/Articles/QueryCookbook.md b/Sources/StructuredQueriesCore/Documentation.docc/Articles/QueryCookbook.md index ae0e708..396c236 100644 --- a/Sources/StructuredQueriesCore/Documentation.docc/Articles/QueryCookbook.md +++ b/Sources/StructuredQueriesCore/Documentation.docc/Articles/QueryCookbook.md @@ -26,7 +26,6 @@ could be restored for a certain amount of time. These tables can be represented struct RemindersList: Identifiable { let id: Int var title = "" - @Column(as: Date.ISO8601Representation?.self) var deletedAt: Date? } @Table @@ -34,9 +33,7 @@ struct Reminder: Identifiable { let id: Int var title = "" var isCompleted = false - @Column(as: Date.ISO8601Representation?.self) var dueAt: Date? - @Column(as: Date.ISO8601Representation?.self) var deletedAt: Date? var remindersListID: RemindersList.ID } @@ -207,7 +204,6 @@ struct Reminder { let id: Int var title = "" var isCompleted = false - @Column(as: Date.ISO8601Representation?.self) var deletedAt: Date? static let all = Self.where { $0.isDeleted.isNot(nil) } diff --git a/Sources/StructuredQueriesCore/Documentation.docc/Extensions/QueryRepresentable.md b/Sources/StructuredQueriesCore/Documentation.docc/Extensions/QueryRepresentable.md index 3efbbd5..40e3f42 100644 --- a/Sources/StructuredQueriesCore/Documentation.docc/Extensions/QueryRepresentable.md +++ b/Sources/StructuredQueriesCore/Documentation.docc/Extensions/QueryRepresentable.md @@ -10,10 +10,13 @@ ### Conformances -- ``Foundation/Date/ISO8601Representation`` +- ``Swift/Decodable/JSONRepresentation`` - ``Foundation/Date/JulianDayRepresentation`` - ``Foundation/Date/UnixTimeRepresentation`` - ``Foundation/UUID/BytesRepresentation`` -- ``Foundation/UUID/LowercasedRepresentation`` - ``Foundation/UUID/UppercasedRepresentation`` -- ``Swift/Decodable/JSONRepresentation`` + +### Deprecations + +- ``Foundation/Date/ISO8601Representation`` +- ``Foundation/UUID/LowercasedRepresentation`` diff --git a/Sources/StructuredQueriesCore/Documentation.docc/StructuredQueriesCore.md b/Sources/StructuredQueriesCore/Documentation.docc/StructuredQueriesCore.md index 877e160..414c6d6 100644 --- a/Sources/StructuredQueriesCore/Documentation.docc/StructuredQueriesCore.md +++ b/Sources/StructuredQueriesCore/Documentation.docc/StructuredQueriesCore.md @@ -15,7 +15,6 @@ struct Reminder { var title = "" var isCompleted = false var priority: Int? - @Column(as: Date.ISO8601Representation?.self) var dueDate: Date? } ``` diff --git a/Sources/StructuredQueriesCore/Exports.swift b/Sources/StructuredQueriesCore/Exports.swift new file mode 100644 index 0000000..8b5c4d2 --- /dev/null +++ b/Sources/StructuredQueriesCore/Exports.swift @@ -0,0 +1 @@ +@_exported import StructuredQueriesSupport diff --git a/Sources/StructuredQueriesCore/Internal/Deprecations.swift b/Sources/StructuredQueriesCore/Internal/Deprecations.swift index 5ffa3fc..3ce36cb 100644 --- a/Sources/StructuredQueriesCore/Internal/Deprecations.swift +++ b/Sources/StructuredQueriesCore/Internal/Deprecations.swift @@ -1,3 +1,134 @@ +import Foundation + +// NB: Deprecated after 0.3.0: + +extension Date { + @available( + *, + deprecated, + message: "ISO-8601 text is the default representation and is no longer explicitly needed." + ) + public struct ISO8601Representation: QueryRepresentable { + public var queryOutput: Date + + public var iso8601String: String { + queryOutput.iso8601String + } + + public init(queryOutput: Date) { + self.queryOutput = queryOutput + } + + public init(iso8601String: String) throws { + try self.init(queryOutput: Date(iso8601String: iso8601String)) + } + } +} + +@available( + *, + deprecated, + message: "ISO-8601 text is the default representation and is no longer explicitly needed." +) +extension Date? { + public typealias ISO8601Representation = Date.ISO8601Representation? +} + +@available( + *, + deprecated, + message: "ISO-8601 text is the default representation and is no longer explicitly needed." +) +extension Date.ISO8601Representation: QueryBindable { + public var queryBinding: QueryBinding { + .text(queryOutput.iso8601String) + } +} + +@available( + *, + deprecated, + message: "ISO-8601 text is the default representation and is no longer explicitly needed." +) +extension Date.ISO8601Representation: QueryDecodable { + public init(decoder: inout some QueryDecoder) throws { + try self.init(queryOutput: Date(iso8601String: String(decoder: &decoder))) + } +} + +@available( + *, + deprecated, + message: "ISO-8601 text is the default representation and is no longer explicitly needed." +) +extension Date.ISO8601Representation: SQLiteType { + public static var typeAffinity: SQLiteTypeAffinity { + String.typeAffinity + } +} + +@available( + *, + deprecated, + message: "Lowercased text is the default representation and is no longer explicitly needed." +) +extension UUID { + public struct LowercasedRepresentation: QueryRepresentable { + public var queryOutput: UUID + + public init(queryOutput: UUID) { + self.queryOutput = queryOutput + } + } +} + +@available( + *, + deprecated, + message: "Lowercased text is the default representation and is no longer explicitly needed." +) +extension UUID? { + public typealias LowercasedRepresentation = UUID.LowercasedRepresentation? +} + +@available( + *, + deprecated, + message: "Lowercased text is the default representation and is no longer explicitly needed." +) +extension UUID.LowercasedRepresentation: QueryBindable { + public var queryBinding: QueryBinding { + .text(queryOutput.uuidString.lowercased()) + } +} + +@available( + *, + deprecated, + message: "Lowercased text is the default representation and is no longer explicitly needed." +) +extension UUID.LowercasedRepresentation: QueryDecodable { + public init(decoder: inout some QueryDecoder) throws { + guard let uuid = try UUID(uuidString: String(decoder: &decoder)) else { + throw InvalidString() + } + self.init(queryOutput: uuid) + } + + private struct InvalidString: Error {} +} + +@available( + *, + deprecated, + message: "Lowercased text is the default representation and is no longer explicitly needed." +) +extension UUID.LowercasedRepresentation: SQLiteType { + public static var typeAffinity: SQLiteTypeAffinity { + String.typeAffinity + } +} + // NB: Deprecated after 0.1.1: @available(*, deprecated, message: "Use 'MyCodableType.JSONRepresentation', instead.") diff --git a/Sources/StructuredQueriesCore/QueryBindable.swift b/Sources/StructuredQueriesCore/QueryBindable.swift index fcb76c1..863e759 100644 --- a/Sources/StructuredQueriesCore/QueryBindable.swift +++ b/Sources/StructuredQueriesCore/QueryBindable.swift @@ -1,3 +1,5 @@ +import Foundation + /// A type representing a value that can be bound to a parameter of a SQL statement. public protocol QueryBindable: QueryRepresentable, QueryExpression where QueryValue: QueryBindable { /// The Swift data type representation of the expression's SQL bindable data type. @@ -13,6 +15,10 @@ extension QueryBindable { public var queryFragment: QueryFragment { "\(queryBinding)" } } +extension [UInt8]: QueryBindable, QueryExpression { + public var queryBinding: QueryBinding { .blob(self) } +} + extension Bool: QueryBindable { public var queryBinding: QueryBinding { .int(self ? 1 : 0) } } @@ -21,6 +27,10 @@ extension Double: QueryBindable { public var queryBinding: QueryBinding { .double(self) } } +extension Date: QueryBindable { + public var queryBinding: QueryBinding { .date(self) } +} + extension Float: QueryBindable { public var queryBinding: QueryBinding { .double(Double(self)) } } @@ -71,8 +81,8 @@ extension UInt64: QueryBindable { } } -extension [UInt8]: QueryBindable, QueryExpression { - public var queryBinding: QueryBinding { .blob(self) } +extension UUID: QueryBindable { + public var queryBinding: QueryBinding { .uuid(self) } } extension DefaultStringInterpolation { diff --git a/Sources/StructuredQueriesCore/QueryBinding.swift b/Sources/StructuredQueriesCore/QueryBinding.swift index 9815f10..83dd6a7 100644 --- a/Sources/StructuredQueriesCore/QueryBinding.swift +++ b/Sources/StructuredQueriesCore/QueryBinding.swift @@ -9,6 +9,9 @@ public enum QueryBinding: Hashable, Sendable { /// A value that should be bound to a statement as a double. case double(Double) + /// A value that should be bound to a statement as a date. + case date(Date) + /// A value that should be bound to a statement as an integer. case int(Int64) @@ -18,6 +21,9 @@ public enum QueryBinding: Hashable, Sendable { /// A value that should be bound to a statement as a string. case text(String) + /// A value that should be bound to a statement as a unique identifier. + case uuid(UUID) + /// An error describing why a value cannot be bound to a statement. case invalid(QueryBindingError) @@ -46,6 +52,8 @@ extension QueryBinding: CustomDebugStringConvertible { .dropLast() .dropFirst() .quoted(.text) + case let .date(date): + return date.iso8601String.quoted(.text) case let .double(value): return "\(value)" case let .int(value): @@ -54,6 +62,8 @@ extension QueryBinding: CustomDebugStringConvertible { return "NULL" case let .text(string): return string.quoted(.text) + case let .uuid(uuid): + return uuid.uuidString.lowercased().quoted(.text) case let .invalid(error): return "" } diff --git a/Sources/StructuredQueriesCore/QueryDecodable.swift b/Sources/StructuredQueriesCore/QueryDecodable.swift index f93528f..b8c1da7 100644 --- a/Sources/StructuredQueriesCore/QueryDecodable.swift +++ b/Sources/StructuredQueriesCore/QueryDecodable.swift @@ -1,3 +1,5 @@ +import Foundation + /// A type that can decode itself from a query. public protocol QueryDecodable: _OptionalPromotable { /// Creates a new instance by decoding from the given decoder. @@ -52,6 +54,15 @@ extension Bool: QueryDecodable { } } +extension Date: QueryDecodable { + @inlinable + public init(decoder: inout some QueryDecoder) throws { + guard let result = try decoder.decode(Date.self) + else { throw QueryDecodingError.missingRequiredColumn } + self = result + } +} + extension Float: QueryDecodable { @inlinable public init(decoder: inout some QueryDecoder) throws { @@ -138,6 +149,15 @@ extension UInt64: QueryDecodable { } } +extension UUID: QueryDecodable { + @inlinable + public init(decoder: inout some QueryDecoder) throws { + guard let result = try decoder.decode(UUID.self) + else { throw QueryDecodingError.missingRequiredColumn } + self = result + } +} + extension QueryDecodable where Self: LosslessStringConvertible { @inlinable public init(decoder: inout some QueryDecoder) throws { diff --git a/Sources/StructuredQueriesCore/QueryDecoder.swift b/Sources/StructuredQueriesCore/QueryDecoder.swift index e877da2..4d5ba3f 100644 --- a/Sources/StructuredQueriesCore/QueryDecoder.swift +++ b/Sources/StructuredQueriesCore/QueryDecoder.swift @@ -1,3 +1,5 @@ +import Foundation + /// A type that can decode values from a database connection into in-memory representations. public protocol QueryDecoder { /// Decodes a single value of the given type from the current column. @@ -36,6 +38,18 @@ public protocol QueryDecoder { /// - Returns: A value of the requested type, or `nil` if the column is `NULL`. mutating func decode(_ columnType: Int.Type) throws -> Int? + /// Decodes a single value of the given type from the current column. + /// + /// - Parameter columnType: The type to decode as. + /// - Returns: A value of the requested type, or `nil` if the column is `NULL`. + mutating func decode(_ columnType: Date.Type) throws -> Date? + + /// Decodes a single value of the given type from the current column. + /// + /// - Parameter columnType: The type to decode as. + /// - Returns: A value of the requested type, or `nil` if the column is `NULL`. + mutating func decode(_ columnType: UUID.Type) throws -> UUID? + /// Decodes a single value of the given type starting from the current column. /// /// - Parameter columnType: The type to decode as. diff --git a/Sources/StructuredQueriesCore/QueryRepresentable.swift b/Sources/StructuredQueriesCore/QueryRepresentable.swift index d54bf0d..5a27a70 100644 --- a/Sources/StructuredQueriesCore/QueryRepresentable.swift +++ b/Sources/StructuredQueriesCore/QueryRepresentable.swift @@ -9,10 +9,9 @@ import Foundation /// This protocol can also be used to create a compile-time distinction between multiple query /// representations of a single Swift type. For example, in SQLite there are three distinct /// representations of date and time values, including ISO-8601-formatted strings, integer second -/// offsets from the Unix epoch, or double Julian day numbers. This library provides -/// ``Foundation/Date/ISO8601Representation``, ``Foundation/Date/UnixTimeRepresentation``, and -/// ``Foundation/Date/JulianDayRepresentation`` types for each of these representations, all of -/// which decode to `Date` when querying a database. +/// offsets from the Unix epoch, or double Julian day numbers. This library defaults to ISO-8601 +/// text, but provides ``Foundation/Date/UnixTimeRepresentation`` to represent a date as an integer, +/// and ``Foundation/Date/JulianDayRepresentation`` to represent a date as a floating point number. public protocol QueryRepresentable: QueryDecodable { /// The Swift type this value is ultimately decoded to. associatedtype QueryOutput diff --git a/Sources/StructuredQueriesCore/QueryRepresentable/Codable+JSON.swift b/Sources/StructuredQueriesCore/QueryRepresentable/Codable+JSON.swift index 7102e83..85a6942 100644 --- a/Sources/StructuredQueriesCore/QueryRepresentable/Codable+JSON.swift +++ b/Sources/StructuredQueriesCore/QueryRepresentable/Codable+JSON.swift @@ -57,7 +57,7 @@ extension _CodableJSONRepresentation: SQLiteType { private let jsonDecoder: JSONDecoder = { var decoder = JSONDecoder() decoder.dateDecodingStrategy = .custom { - try $0.singleValueContainer().decode(String.self).iso8601 + try Date(iso8601String: $0.singleValueContainer().decode(String.self)) } return decoder }() diff --git a/Sources/StructuredQueriesCore/QueryRepresentable/Date+ISO8601.swift b/Sources/StructuredQueriesCore/QueryRepresentable/Date+ISO8601.swift index d2f46fb..3ccf869 100644 --- a/Sources/StructuredQueriesCore/QueryRepresentable/Date+ISO8601.swift +++ b/Sources/StructuredQueriesCore/QueryRepresentable/Date+ISO8601.swift @@ -1,45 +1,7 @@ import Foundation extension Date { - /// A query expression representing a date as an ISO-8601-formatted string (in RFC 3339 format). - /// - /// ```swift - /// @Table - /// struct Item { - /// @Column(as: Date.ISO8601Representation.self) - /// var date: Date - /// } - /// - /// Item.insert { $0.date } values: { Date() } - /// // INSERT INTO "items" ("date") VALUES ('2018-01-29 00:08:00.000') - /// ``` - public struct ISO8601Representation: QueryRepresentable { - public var queryOutput: Date - - public init(queryOutput: Date) { - self.queryOutput = queryOutput - } - } -} - -extension Date? { - public typealias ISO8601Representation = Date.ISO8601Representation? -} - -extension Date.ISO8601Representation: QueryBindable { - public var queryBinding: QueryBinding { - .text(queryOutput.iso8601String) - } -} - -extension Date.ISO8601Representation: QueryDecodable { - public init(decoder: inout some QueryDecoder) throws { - try self.init(queryOutput: String(decoder: &decoder).iso8601) - } -} - -extension Date { - fileprivate var iso8601String: String { + package var iso8601String: String { if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) { return formatted(.iso8601.currentTimestamp(includingFractionalSeconds: true)) } else { @@ -72,41 +34,34 @@ extension DateFormatter { }() } -extension String { - var iso8601: Date { - get throws { - if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) { - do { - return try Date( - queryOutput, - strategy: .iso8601.currentTimestamp(includingFractionalSeconds: true) - ) - } catch { - return try Date( - queryOutput, - strategy: .iso8601.currentTimestamp(includingFractionalSeconds: false) - ) - } - } else { - guard - let date = DateFormatter.iso8601(includingFractionalSeconds: true).date(from: self) - ?? DateFormatter.iso8601(includingFractionalSeconds: false).date(from: self) - else { - struct InvalidDate: Error { let string: String } - throw InvalidDate(string: self) - } - return date +extension Date { + @usableFromInline + package init(iso8601String: String) throws { + if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) { + do { + try self.init( + iso8601String.queryOutput, + strategy: .iso8601.currentTimestamp(includingFractionalSeconds: true) + ) + } catch { + try self.init( + iso8601String.queryOutput, + strategy: .iso8601.currentTimestamp(includingFractionalSeconds: false) + ) } + } else { + guard + let date = DateFormatter.iso8601(includingFractionalSeconds: true).date(from: iso8601String) + ?? DateFormatter.iso8601(includingFractionalSeconds: false).date(from: iso8601String) + else { + struct InvalidDate: Error { let string: String } + throw InvalidDate(string: iso8601String) + } + self = date } } } -extension Date.ISO8601Representation: SQLiteType { - public static var typeAffinity: SQLiteTypeAffinity { - String.typeAffinity - } -} - @available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) extension Date.ISO8601FormatStyle { fileprivate func currentTimestamp(includingFractionalSeconds: Bool) -> Self { diff --git a/Sources/StructuredQueriesCore/QueryRepresentable/UUID+Lowercased.swift b/Sources/StructuredQueriesCore/QueryRepresentable/UUID+Lowercased.swift deleted file mode 100644 index 2460469..0000000 --- a/Sources/StructuredQueriesCore/QueryRepresentable/UUID+Lowercased.swift +++ /dev/null @@ -1,50 +0,0 @@ -import Foundation - -extension UUID { - /// A query expression representing a UUID as a lowercased string. - /// - /// ```swift - /// @Table - /// struct Item { - /// @Column(as: UUID.LowercasedRepresentation.self) - /// let id: UUID - /// } - /// - /// Item.insert { $0.id } values: { UUID() } - /// // INSERT INTO "items" ("id") VALUES ('deadbeef-dead-beef-dead-deadbeefdead') - /// ``` - public struct LowercasedRepresentation: QueryRepresentable { - public var queryOutput: UUID - - public init(queryOutput: UUID) { - self.queryOutput = queryOutput - } - } -} - -extension UUID? { - public typealias LowercasedRepresentation = UUID.LowercasedRepresentation? -} - -extension UUID.LowercasedRepresentation: QueryBindable { - public var queryBinding: QueryBinding { - .text(queryOutput.uuidString.lowercased()) - } -} - -extension UUID.LowercasedRepresentation: QueryDecodable { - public init(decoder: inout some QueryDecoder) throws { - guard let uuid = try UUID(uuidString: String(decoder: &decoder)) else { - throw InvalidString() - } - self.init(queryOutput: uuid) - } - - private struct InvalidString: Error {} -} - -extension UUID.LowercasedRepresentation: SQLiteType { - public static var typeAffinity: SQLiteTypeAffinity { - String.typeAffinity - } -} diff --git a/Sources/StructuredQueriesCore/QueryRepresentable/UUID+Uppercased.swift b/Sources/StructuredQueriesCore/QueryRepresentable/UUID+Uppercased.swift index 1fe2e68..b826353 100644 --- a/Sources/StructuredQueriesCore/QueryRepresentable/UUID+Uppercased.swift +++ b/Sources/StructuredQueriesCore/QueryRepresentable/UUID+Uppercased.swift @@ -6,7 +6,7 @@ extension UUID { /// ```swift /// @Table /// struct Item { - /// @Column(as: UUID.LowercasedRepresentation.self) + /// @Column(as: UUID.UppercasedRepresentation.self) /// let id: UUID /// } /// diff --git a/Sources/StructuredQueriesMacros/SelectionMacro.swift b/Sources/StructuredQueriesMacros/SelectionMacro.swift index 9438cf7..fb26025 100644 --- a/Sources/StructuredQueriesMacros/SelectionMacro.swift +++ b/Sources/StructuredQueriesMacros/SelectionMacro.swift @@ -134,67 +134,6 @@ extension SelectionMacro: ExtensionMacro { .baseName .text } - if columnQueryValueType == columnQueryOutputType, - let typeIdentifier = columnQueryValueType?.identifier ?? assignedType, - ["Date", "UUID"].contains(typeIdentifier) - { - var fixIts: [FixIt] = [] - let optional = columnQueryValueType?.isOptionalType == true ? "?" : "" - if typeIdentifier.hasPrefix("Date") { - for representation in ["ISO8601", "UnixTime", "JulianDay"] { - var newProperty = property.with(\.leadingTrivia, "") - let attribute = "@Column(as: Date.\(representation)Representation\(optional).self)" - newProperty.attributes.insert( - AttributeListSyntax.Element("\(raw: attribute)") - .with( - \.trailingTrivia, - .newline.merging(property.leadingTrivia.indentation(isOnNewline: true)) - ), - at: newProperty.attributes.startIndex - ) - fixIts.append( - FixIt( - message: MacroExpansionFixItMessage("Insert '\(attribute)'"), - changes: [ - .replace( - oldNode: Syntax(property), - newNode: Syntax(newProperty.with(\.leadingTrivia, property.leadingTrivia)) - ) - ] - ) - ) - } - } else if typeIdentifier.hasPrefix("UUID") { - for representation in ["Lowercased", "Uppercased", "Bytes"] { - var newProperty = property.with(\.leadingTrivia, "") - let attribute = "@Column(as: UUID.\(representation)Representation\(optional).self)" - newProperty.attributes.insert( - AttributeListSyntax.Element("\(raw: attribute)"), - at: newProperty.attributes.startIndex - ) - fixIts.append( - FixIt( - message: MacroExpansionFixItMessage("Insert '\(attribute)'"), - changes: [ - .replace( - oldNode: Syntax(property), - newNode: Syntax(newProperty.with(\.leadingTrivia, property.leadingTrivia)) - ) - ] - ) - ) - } - } - diagnostics.append( - Diagnostic( - node: property, - message: MacroExpansionErrorMessage( - "'\(typeIdentifier)' column requires a query representation" - ), - fixIts: fixIts - ) - ) - } allColumns.append((identifier, columnQueryValueType)) let decodedType = columnQueryValueType?.asNonOptionalType() diff --git a/Sources/StructuredQueriesMacros/TableMacro.swift b/Sources/StructuredQueriesMacros/TableMacro.swift index 1dfdb70..8ca7344 100644 --- a/Sources/StructuredQueriesMacros/TableMacro.swift +++ b/Sources/StructuredQueriesMacros/TableMacro.swift @@ -238,67 +238,6 @@ extension TableMacro: ExtensionMacro { .baseName .text } - if columnQueryValueType == columnQueryOutputType, - let typeIdentifier = columnQueryValueType?.identifier ?? assignedType, - ["Date", "UUID"].contains(typeIdentifier) - { - var fixIts: [FixIt] = [] - let optional = columnQueryValueType?.isOptionalType == true ? "?" : "" - if typeIdentifier.hasPrefix("Date") { - for representation in ["ISO8601", "UnixTime", "JulianDay"] { - var newProperty = property.with(\.leadingTrivia, "") - let attribute = "@Column(as: Date.\(representation)Representation\(optional).self)" - newProperty.attributes.insert( - AttributeListSyntax.Element("\(raw: attribute)") - .with( - \.trailingTrivia, - .newline.merging(property.leadingTrivia.indentation(isOnNewline: true)) - ), - at: newProperty.attributes.startIndex - ) - fixIts.append( - FixIt( - message: MacroExpansionFixItMessage("Insert '\(attribute)'"), - changes: [ - .replace( - oldNode: Syntax(property), - newNode: Syntax(newProperty.with(\.leadingTrivia, property.leadingTrivia)) - ) - ] - ) - ) - } - } else if typeIdentifier.hasPrefix("UUID") { - for representation in ["Lowercased", "Uppercased", "Bytes"] { - var newProperty = property.with(\.leadingTrivia, "") - let attribute = "@Column(as: UUID.\(representation)Representation\(optional).self)\n" - newProperty.attributes.insert( - AttributeListSyntax.Element("\(raw: attribute)"), - at: newProperty.attributes.startIndex - ) - fixIts.append( - FixIt( - message: MacroExpansionFixItMessage("Insert '\(attribute)'"), - changes: [ - .replace( - oldNode: Syntax(property), - newNode: Syntax(newProperty.with(\.leadingTrivia, property.leadingTrivia)) - ) - ] - ) - ) - } - } - diagnostics.append( - Diagnostic( - node: property, - message: MacroExpansionErrorMessage( - "'\(typeIdentifier)' column requires a query representation" - ), - fixIts: fixIts - ) - ) - } let defaultValue = binding.initializer?.value.rewritten(selfRewriter) columnsProperties.append( diff --git a/Sources/StructuredQueriesSQLite/Database.swift b/Sources/StructuredQueriesSQLite/Database.swift index 94bc106..2270389 100644 --- a/Sources/StructuredQueriesSQLite/Database.swift +++ b/Sources/StructuredQueriesSQLite/Database.swift @@ -137,6 +137,8 @@ public struct Database { switch binding { case let .blob(blob): sqlite3_bind_blob(statement, index, Array(blob), Int32(blob.count), SQLITE_TRANSIENT) + case let .date(date): + sqlite3_bind_text(statement, index, date.iso8601String, -1, SQLITE_TRANSIENT) case let .double(double): sqlite3_bind_double(statement, index, double) case let .int(int): @@ -145,6 +147,8 @@ public struct Database { sqlite3_bind_null(statement, index) case let .text(text): sqlite3_bind_text(statement, index, text, -1, SQLITE_TRANSIENT) + case let .uuid(uuid): + sqlite3_bind_text(statement, index, uuid.uuidString.lowercased(), -1, SQLITE_TRANSIENT) case let .invalid(error): throw error.underlyingError } diff --git a/Sources/StructuredQueriesSQLite/SQLiteQueryDecoder.swift b/Sources/StructuredQueriesSQLite/SQLiteQueryDecoder.swift index f891250..9e09175 100644 --- a/Sources/StructuredQueriesSQLite/SQLiteQueryDecoder.swift +++ b/Sources/StructuredQueriesSQLite/SQLiteQueryDecoder.swift @@ -1,3 +1,4 @@ +import Foundation import StructuredQueries #if canImport(Darwin) @@ -36,6 +37,17 @@ struct SQLiteQueryDecoder: QueryDecoder { ) } + @inlinable + mutating func decode(_ columnType: Bool.Type) throws -> Bool? { + try decode(Int64.self).map { $0 != 0 } + } + + @usableFromInline + mutating func decode(_ columnType: Date.Type) throws -> Date? { + guard let iso8601String = try decode(String.self) else { return nil } + return try Date(iso8601String: iso8601String) + } + @inlinable mutating func decode(_ columnType: Double.Type) throws -> Double? { defer { currentIndex += 1 } @@ -43,6 +55,11 @@ struct SQLiteQueryDecoder: QueryDecoder { return sqlite3_column_double(statement, currentIndex) } + @inlinable + mutating func decode(_ columnType: Int.Type) throws -> Int? { + try decode(Int64.self).map(Int.init) + } + @inlinable mutating func decode(_ columnType: Int64.Type) throws -> Int64? { defer { currentIndex += 1 } @@ -57,13 +74,9 @@ struct SQLiteQueryDecoder: QueryDecoder { return String(cString: sqlite3_column_text(statement, currentIndex)) } - @inlinable - mutating func decode(_ columnType: Bool.Type) throws -> Bool? { - try decode(Int64.self).map { $0 != 0 } - } - - @inlinable - mutating func decode(_ columnType: Int.Type) throws -> Int? { - try decode(Int64.self).map(Int.init) + @usableFromInline + mutating func decode(_ columnType: UUID.Type) throws -> UUID? { + guard let uuidString = try decode(String.self) else { return nil } + return UUID(uuidString: uuidString) } } diff --git a/Tests/StructuredQueriesMacrosTests/BindMacroTests.swift b/Tests/StructuredQueriesMacrosTests/BindMacroTests.swift index 9c0304d..ba1d1fe 100644 --- a/Tests/StructuredQueriesMacrosTests/BindMacroTests.swift +++ b/Tests/StructuredQueriesMacrosTests/BindMacroTests.swift @@ -20,11 +20,11 @@ extension SnapshotTests { @Test func queryValueType() { assertMacro { #""" - \(date) < #bind(Date(), as: Date.ISO8601Representation.self) + \(date) < #bind(Date(), as: Date.UnixTimeRepresentation.self) """# } expansion: { #""" - \(date) < StructuredQueriesCore.BindQueryExpression(Date(), as: Date.ISO8601Representation.self) + \(date) < StructuredQueriesCore.BindQueryExpression(Date(), as: Date.UnixTimeRepresentation.self) """# } } diff --git a/Tests/StructuredQueriesMacrosTests/SelectionMacroTests.swift b/Tests/StructuredQueriesMacrosTests/SelectionMacroTests.swift index 442c4b4..ea32fb5 100644 --- a/Tests/StructuredQueriesMacrosTests/SelectionMacroTests.swift +++ b/Tests/StructuredQueriesMacrosTests/SelectionMacroTests.swift @@ -113,7 +113,7 @@ extension SnapshotTests { assertMacro { """ @Selection struct ReminderDate { - @Column(as: Date.ISO8601Representation.self) + @Column(as: Date.UnixTimeRepresentation.self) var date: Date } """ @@ -128,7 +128,7 @@ extension SnapshotTests { public typealias QueryValue = ReminderDate public let queryFragment: StructuredQueriesCore.QueryFragment public init( - date: some StructuredQueriesCore.QueryExpression + date: some StructuredQueriesCore.QueryExpression ) { self.queryFragment = """ \(date.queryFragment) AS "date" @@ -136,62 +136,7 @@ extension SnapshotTests { } } public init(decoder: inout some StructuredQueriesCore.QueryDecoder) throws { - let date = try decoder.decode(Date.ISO8601Representation.self) - guard let date else { - throw QueryDecodingError.missingRequiredColumn - } - self.date = date - } - } - """# - } - } - - @Test func dateDiagnostic() { - assertMacro { - """ - @Selection struct ReminderDate { - var date: Date - } - """ - } diagnostics: { - """ - @Selection struct ReminderDate { - var date: Date - ┬───────────── - ╰─ 🛑 'Date' column requires a query representation - ✏️ Insert '@Column(as: Date.ISO8601Representation.self)' - ✏️ Insert '@Column(as: Date.UnixTimeRepresentation.self)' - ✏️ Insert '@Column(as: Date.JulianDayRepresentation.self)' - } - """ - } fixes: { - """ - @Selection struct ReminderDate { - @Column(as: Date.ISO8601Representation.self) - var date: Date - } - """ - } expansion: { - #""" - struct ReminderDate { - var date: Date - } - - extension ReminderDate: StructuredQueriesCore.QueryRepresentable { - public struct Columns: StructuredQueriesCore.QueryExpression { - public typealias QueryValue = ReminderDate - public let queryFragment: StructuredQueriesCore.QueryFragment - public init( - date: some StructuredQueriesCore.QueryExpression - ) { - self.queryFragment = """ - \(date.queryFragment) AS "date" - """ - } - } - public init(decoder: inout some StructuredQueriesCore.QueryDecoder) throws { - let date = try decoder.decode(Date.ISO8601Representation.self) + let date = try decoder.decode(Date.UnixTimeRepresentation.self) guard let date else { throw QueryDecodingError.missingRequiredColumn } diff --git a/Tests/StructuredQueriesMacrosTests/TableMacroTests.swift b/Tests/StructuredQueriesMacrosTests/TableMacroTests.swift index 3efbfe2..8af46b5 100644 --- a/Tests/StructuredQueriesMacrosTests/TableMacroTests.swift +++ b/Tests/StructuredQueriesMacrosTests/TableMacroTests.swift @@ -422,7 +422,7 @@ extension SnapshotTests { """ @Table struct Foo { - @Column(as: Date.ISO8601Representation.self) + @Column(as: Date.UnixTimeRepresentation.self) var bar: Date } """ @@ -435,7 +435,7 @@ extension SnapshotTests { extension Foo: StructuredQueriesCore.Table { public struct TableColumns: StructuredQueriesCore.TableDefinition { public typealias QueryValue = Foo - public let bar = StructuredQueriesCore.TableColumn("bar", keyPath: \QueryValue.bar) + public let bar = StructuredQueriesCore.TableColumn("bar", keyPath: \QueryValue.bar) public static var allColumns: [any StructuredQueriesCore.TableColumnExpression] { [QueryValue.columns.bar] } @@ -443,7 +443,7 @@ extension SnapshotTests { public static let columns = TableColumns() public static let tableName = "foos" public init(decoder: inout some StructuredQueriesCore.QueryDecoder) throws { - let bar = try decoder.decode(Date.ISO8601Representation.self) + let bar = try decoder.decode(Date.UnixTimeRepresentation.self) guard let bar else { throw QueryDecodingError.missingRequiredColumn } @@ -530,218 +530,6 @@ extension SnapshotTests { } } - @Test func dateDiagnostic() { - assertMacro { - """ - @Table - struct Foo { - var bar: Date - } - """ - } diagnostics: { - """ - @Table - struct Foo { - var bar: Date - ┬──────────── - ╰─ 🛑 'Date' column requires a query representation - ✏️ Insert '@Column(as: Date.ISO8601Representation.self)' - ✏️ Insert '@Column(as: Date.UnixTimeRepresentation.self)' - ✏️ Insert '@Column(as: Date.JulianDayRepresentation.self)' - } - """ - } fixes: { - """ - @Table - struct Foo { - @Column(as: Date.ISO8601Representation.self) - var bar: Date - } - """ - } expansion: { - #""" - struct Foo { - var bar: Date - } - - extension Foo: StructuredQueriesCore.Table { - public struct TableColumns: StructuredQueriesCore.TableDefinition { - public typealias QueryValue = Foo - public let bar = StructuredQueriesCore.TableColumn("bar", keyPath: \QueryValue.bar) - public static var allColumns: [any StructuredQueriesCore.TableColumnExpression] { - [QueryValue.columns.bar] - } - } - public static let columns = TableColumns() - public static let tableName = "foos" - public init(decoder: inout some StructuredQueriesCore.QueryDecoder) throws { - let bar = try decoder.decode(Date.ISO8601Representation.self) - guard let bar else { - throw QueryDecodingError.missingRequiredColumn - } - self.bar = bar - } - } - """# - } - } - - @Test func optionalDateDiagnostic() { - assertMacro { - """ - @Table - struct Foo { - var bar: Date? - } - """ - } diagnostics: { - """ - @Table - struct Foo { - var bar: Date? - ┬───────────── - ╰─ 🛑 'Date' column requires a query representation - ✏️ Insert '@Column(as: Date.ISO8601Representation?.self)' - ✏️ Insert '@Column(as: Date.UnixTimeRepresentation?.self)' - ✏️ Insert '@Column(as: Date.JulianDayRepresentation?.self)' - } - """ - } fixes: { - """ - @Table - struct Foo { - @Column(as: Date.ISO8601Representation?.self) - var bar: Date? - } - """ - } expansion: { - #""" - struct Foo { - var bar: Date? - } - - extension Foo: StructuredQueriesCore.Table { - public struct TableColumns: StructuredQueriesCore.TableDefinition { - public typealias QueryValue = Foo - public let bar = StructuredQueriesCore.TableColumn("bar", keyPath: \QueryValue.bar) - public static var allColumns: [any StructuredQueriesCore.TableColumnExpression] { - [QueryValue.columns.bar] - } - } - public static let columns = TableColumns() - public static let tableName = "foos" - public init(decoder: inout some StructuredQueriesCore.QueryDecoder) throws { - self.bar = try decoder.decode(Date.ISO8601Representation.self) - } - } - """# - } - } - - @Test func optionalTypeDateDiagnostic() { - assertMacro { - """ - @Table - struct Foo { - var bar: Optional - } - """ - } diagnostics: { - """ - @Table - struct Foo { - var bar: Optional - ┬────────────────────── - ╰─ 🛑 'Date' column requires a query representation - ✏️ Insert '@Column(as: Date.ISO8601Representation?.self)' - ✏️ Insert '@Column(as: Date.UnixTimeRepresentation?.self)' - ✏️ Insert '@Column(as: Date.JulianDayRepresentation?.self)' - } - """ - } fixes: { - """ - @Table - struct Foo { - @Column(as: Date.ISO8601Representation?.self) - var bar: Optional - } - """ - } expansion: { - #""" - struct Foo { - var bar: Optional - } - - extension Foo: StructuredQueriesCore.Table { - public struct TableColumns: StructuredQueriesCore.TableDefinition { - public typealias QueryValue = Foo - public let bar = StructuredQueriesCore.TableColumn("bar", keyPath: \QueryValue.bar) - public static var allColumns: [any StructuredQueriesCore.TableColumnExpression] { - [QueryValue.columns.bar] - } - } - public static let columns = TableColumns() - public static let tableName = "foos" - public init(decoder: inout some StructuredQueriesCore.QueryDecoder) throws { - self.bar = try decoder.decode(Date.ISO8601Representation.self) - } - } - """# - } - } - - @Test func defaultDateDiagnostic() { - assertMacro { - """ - @Table - struct Foo { - var bar = Date() - } - """ - } diagnostics: { - """ - @Table - struct Foo { - var bar = Date() - ┬─────────────── - ╰─ 🛑 'Date' column requires a query representation - ✏️ Insert '@Column(as: Date.ISO8601Representation.self)' - ✏️ Insert '@Column(as: Date.UnixTimeRepresentation.self)' - ✏️ Insert '@Column(as: Date.JulianDayRepresentation.self)' - } - """ - } fixes: { - """ - @Table - struct Foo { - @Column(as: Date.ISO8601Representation.self) - var bar = Date() - } - """ - } expansion: { - #""" - struct Foo { - var bar = Date() - } - - extension Foo: StructuredQueriesCore.Table { - public struct TableColumns: StructuredQueriesCore.TableDefinition { - public typealias QueryValue = Foo - public let bar = StructuredQueriesCore.TableColumn("bar", keyPath: \QueryValue.bar, default: Date()) - public static var allColumns: [any StructuredQueriesCore.TableColumnExpression] { - [QueryValue.columns.bar] - } - } - public static let columns = TableColumns() - public static let tableName = "foos" - public init(decoder: inout some StructuredQueriesCore.QueryDecoder) throws { - self.bar = try decoder.decode(Date.ISO8601Representation.self) ?? Date() - } - } - """# - } - } - @Test func backticks() { assertMacro { """ diff --git a/Tests/StructuredQueriesTests/BindingTests.swift b/Tests/StructuredQueriesTests/BindingTests.swift index 5818398..d2fddec 100644 --- a/Tests/StructuredQueriesTests/BindingTests.swift +++ b/Tests/StructuredQueriesTests/BindingTests.swift @@ -76,8 +76,7 @@ extension SnapshotTests { @Test func uuids() throws { assertQuery( SimpleSelect { - #bind(UUID(0), as: UUID.LowercasedRepresentation.self) - .in([UUID(1), UUID(2)].map { #bind($0) }) + UUID(0).in([UUID(1), UUID(2)]) } ) { """ diff --git a/Tests/StructuredQueriesTests/DecodingTests.swift b/Tests/StructuredQueriesTests/DecodingTests.swift index cdd2136..1699dc4 100644 --- a/Tests/StructuredQueriesTests/DecodingTests.swift +++ b/Tests/StructuredQueriesTests/DecodingTests.swift @@ -78,7 +78,7 @@ extension SnapshotTests { @Test func queryRepresentable() throws { #expect( try db.execute( - SimpleSelect { #sql("'2001-01-01 00:00:00'", as: Date.ISO8601Representation.self) } + SimpleSelect { #sql("'2001-01-01 00:00:00'", as: Date.self) } ) .first == Date(timeIntervalSinceReferenceDate: 0) ) @@ -97,7 +97,7 @@ extension SnapshotTests { #expect( try db.execute( SimpleSelect { - #sql("'deadbeef-dead-beef-dead-beefdeadbeef'", as: UUID.LowercasedRepresentation.self) + #sql("'deadbeef-dead-beef-dead-beefdeadbeef'", as: UUID.self) } ) .first == UUID(uuidString: "deadbeef-dead-beef-dead-beefdeadbeef") @@ -134,7 +134,13 @@ extension SnapshotTests { @Test func optionalDate() throws { #expect( try db.execute( - SimpleSelect { #sql("NULL", as: Date.ISO8601Representation?.self) } + SimpleSelect { #sql("NULL", as: Date?.self) } + ) + .first == .some(.none) + ) + #expect( + try db.execute( + SimpleSelect { #sql("NULL", as: Date.UnixTimeRepresentation?.self) } ) .first == .some(.none) ) @@ -228,7 +234,7 @@ extension SnapshotTests { _ = try #require( try db.execute( SimpleSelect { - #bind(Date(timeIntervalSince1970: 0), as: Date.ISO8601Representation.self) + #bind(Date(timeIntervalSince1970: 0), as: Date.UnixTimeRepresentation.self) } ) .first diff --git a/Tests/StructuredQueriesTests/KitchenSinkTests.swift b/Tests/StructuredQueriesTests/KitchenSinkTests.swift index 9822bfb..aa3755b 100644 --- a/Tests/StructuredQueriesTests/KitchenSinkTests.swift +++ b/Tests/StructuredQueriesTests/KitchenSinkTests.swift @@ -315,9 +315,7 @@ private struct KitchenSink: Codable { var optionalDouble: Double? var rawRepresentable: Color var optionalRawRepresentable: Color? - @Column(as: Date.ISO8601Representation.self) var iso8601Date: Date - @Column(as: Date.ISO8601Representation?.self) var optionalISO8601Date: Date? @Column(as: Date.UnixTimeRepresentation.self) var unixTimeDate: Date diff --git a/Tests/StructuredQueriesTests/LiveTests.swift b/Tests/StructuredQueriesTests/LiveTests.swift index 1c81f8a..05c98f9 100644 --- a/Tests/StructuredQueriesTests/LiveTests.swift +++ b/Tests/StructuredQueriesTests/LiveTests.swift @@ -318,7 +318,6 @@ extension SnapshotTests { let id: Int var isActive: Bool var title: String - @Column(as: Date.ISO8601Representation.self) var createdAt: Date } @@ -327,7 +326,6 @@ extension SnapshotTests { let id: Int var name: String var syncUpID: Int - @Column(as: Date.ISO8601Representation.self) var createdAt: Date } } diff --git a/Tests/StructuredQueriesTests/PrimaryKeyedTableTests.swift b/Tests/StructuredQueriesTests/PrimaryKeyedTableTests.swift index 3e99352..f415b4a 100644 --- a/Tests/StructuredQueriesTests/PrimaryKeyedTableTests.swift +++ b/Tests/StructuredQueriesTests/PrimaryKeyedTableTests.swift @@ -189,6 +189,5 @@ extension SnapshotTests { @Table private struct Row { - @Column(as: UUID.LowercasedRepresentation.self) let id: UUID } diff --git a/Tests/StructuredQueriesTests/ScalarFunctionsTests.swift b/Tests/StructuredQueriesTests/ScalarFunctionsTests.swift index eef1446..ff8d4e7 100644 --- a/Tests/StructuredQueriesTests/ScalarFunctionsTests.swift +++ b/Tests/StructuredQueriesTests/ScalarFunctionsTests.swift @@ -13,7 +13,6 @@ extension SnapshotTests { var isAdmin: Bool var salary: Double var referrerID: Int? - @Column(as: Date.ISO8601Representation.self) var updatedAt: Date var image: [UInt8] } diff --git a/Tests/StructuredQueriesTests/SelectionTests.swift b/Tests/StructuredQueriesTests/SelectionTests.swift index 57648b3..f02e581 100644 --- a/Tests/StructuredQueriesTests/SelectionTests.swift +++ b/Tests/StructuredQueriesTests/SelectionTests.swift @@ -179,7 +179,6 @@ extension SnapshotTests { @Selection struct ReminderDate { - @Column(as: Date.ISO8601Representation?.self) var date: Date? } diff --git a/Tests/StructuredQueriesTests/Support/Schema.swift b/Tests/StructuredQueriesTests/Support/Schema.swift index 8b4f0a2..d676656 100644 --- a/Tests/StructuredQueriesTests/Support/Schema.swift +++ b/Tests/StructuredQueriesTests/Support/Schema.swift @@ -20,7 +20,6 @@ struct Reminder: Codable, Equatable, Identifiable { let id: Int var assignedUserID: User.ID? - @Column(as: Date.ISO8601Representation?.self) var dueDate: Date? var isCompleted = false var isFlagged = false