Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Examples/CaseStudies/SwiftDataTemplateDemo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ struct SwiftDataTemplateView: SwiftUICaseStudy {
@Table
private struct Item: Identifiable {
let id: Int
@Column(as: Date.ISO8601Representation.self)
var timestamp: Date
}

Expand Down
1 change: 0 additions & 1 deletion Examples/Reminders/Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ struct RemindersList: Hashable, Identifiable {
@Table
struct Reminder: Equatable, Identifiable {
var id: Int
@Column(as: Date.ISO8601Representation?.self)
var dueDate: Date?
var isCompleted = false
var isFlagged = false
Expand Down
1 change: 0 additions & 1 deletion Examples/SyncUps/Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ struct Attendee: Hashable, Identifiable {
@Table
struct Meeting: Hashable, Identifiable {
let id: Int
@Column(as: Date.ISO8601Representation.self)
var date: Date
var syncUpID: SyncUp.ID
var transcript: String
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ let package = Package(
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.9.0"),
.package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.5.0"),
.package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.3.0"),
.package(url: "https://github.com/pointfreeco/swift-structured-queries", from: "0.2.0"),
.package(url: "https://github.com/pointfreeco/swift-structured-queries", from: "0.4.0"),
],
targets: [
.target(
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ simple as adding it to your `Package.swift`:

``` swift
dependencies: [
.package(url: "https://github.com/pointfreeco/sharing-grdb", from: "0.2.0")
.package(url: "https://github.com/pointfreeco/sharing-grdb", from: "0.4.0")
]
```

Expand Down
6 changes: 3 additions & 3 deletions SharingGRDB.xcworkspace/xcshareddata/swiftpm/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,11 @@ your table:
struct Reminder {
let id: Int
var title = ""
@Column(as: Date.ISO8601Representation?.self)
var dueAt: Date?
var isCompleted = false
}
```

> Note: The `@Column` macro determines how to store the date in SQLite, which does not have a native
> date data type. The `Date.ISO8601Representation` strategy stores dates as text formatted with the
> ISO-8601 standard. See [Defining your schema] for more info.

[Defining your schema]: https://swiftpackageindex.com/pointfreeco/swift-structured-queries/main/documentation/structuredqueriescore/definingyourschema

With that done you can already fetch all records from the `Reminder` table in their default order by
simply doing:

Expand Down Expand Up @@ -106,7 +99,6 @@ exactly one list:
struct Reminder {
let id: Int
var title = ""
@Column(as: Date.ISO8601Representation?.self)
var dueAt: Date?
var isCompleted = false
var remindersListID: RemindersList.ID
Expand Down
71 changes: 71 additions & 0 deletions Sources/StructuredQueriesGRDBCore/Internal/ISO8601.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import Foundation

extension Date {
@usableFromInline
var iso8601String: String {
if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) {
return formatted(.iso8601.currentTimestamp(includingFractionalSeconds: true))
} else {
return DateFormatter.iso8601(includingFractionalSeconds: true).string(from: self)
}
}

@usableFromInline
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 DateFormatter {
fileprivate static func iso8601(includingFractionalSeconds: Bool) -> DateFormatter {
includingFractionalSeconds ? iso8601Fractional : iso8601Whole
}

fileprivate static let iso8601Fractional: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}()

fileprivate static let iso8601Whole: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}()
}

@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
extension Date.ISO8601FormatStyle {
fileprivate func currentTimestamp(includingFractionalSeconds: Bool) -> Self {
year().month().day()
.dateTimeSeparator(.space)
.time(includingFractionalSeconds: includingFractionalSeconds)
}
}
4 changes: 4 additions & 0 deletions Sources/StructuredQueriesGRDBCore/QueryCursor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ extension QueryBinding {
switch self {
case let .blob(blob):
return Data(blob).databaseValue
case let .date(date):
return date.iso8601String.databaseValue
case let .double(double):
return double.databaseValue
case let .int(int):
Expand All @@ -117,6 +119,8 @@ extension QueryBinding {
return .null
case let .text(text):
return text.databaseValue
case let .uuid(uuid):
return uuid.uuidString.lowercased().databaseValue
case let .invalid(error):
throw error
}
Expand Down
31 changes: 25 additions & 6 deletions Sources/StructuredQueriesGRDBCore/SQLiteQueryDecoder.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Foundation
import GRDBSQLite
import StructuredQueriesCore

Expand Down Expand Up @@ -31,13 +32,28 @@ struct SQLiteQueryDecoder: QueryDecoder {
)
}

@inlinable
mutating func decode(_ columnType: Bool.Type) throws -> Bool? {
try decode(Int64.self).map { $0 != 0 }
}

@inlinable
mutating func decode(_ columnType: Date.Type) throws -> Date? {
try decode(String.self).map { try Date(iso8601String: $0) }
}

@inlinable
mutating func decode(_ columnType: Double.Type) throws -> Double? {
defer { currentIndex += 1 }
guard sqlite3_column_type(statement, currentIndex) != SQLITE_NULL else { return nil }
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 }
Expand All @@ -53,12 +69,15 @@ struct SQLiteQueryDecoder: QueryDecoder {
}

@inlinable
mutating func decode(_ columnType: Bool.Type) throws -> Bool? {
try decode(Int64.self).map { $0 != 0 }
mutating func decode(_ columnType: UUID.Type) throws -> UUID? {
guard let uuidString = try decode(String.self) else { return nil }
guard let uuid = UUID(uuidString: uuidString) else { throw InvalidUUID() }
return uuid
}
}

@inlinable
mutating func decode(_ columnType: Int.Type) throws -> Int? {
try decode(Int64.self).map(Int.init)
}
@usableFromInline
struct InvalidUUID: Error {
@usableFromInline
init() {}
}
1 change: 0 additions & 1 deletion Tests/StructuredQueriesGRDBTests/MigrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,5 @@ import Testing

@available(iOS 15, *)
@Table private struct Model {
@Column(as: Date.ISO8601Representation.self)
var date: Date
}