Skip to content

Commit 5144ec0

Browse files
Add default query representations for dates and UUIDs (#37)
* Add default query representations for dates and UUIDs SQLite does not have date or UUID types, and instead can represent dates and UUIDs in several different ways. While this works for SQLite, other database systems _do_ have dedicated date and UUID types, and so we should probably encode these types in StructuredQueries' decoding and binding layers, and then SQLite drivers (like SharingGRDB) will pick a sensible default, like ISO8601 strings for dates, and lowercased strings for UUIDs. Draft PR for now as we figure out if this is the right direction, and there is plenty to do before merging (documentation, etc.). * wip * wip * Use StructuredQueriesCore module name in macro expansions. (#42) * wip * wip; * Update docs * wip * wip --------- Co-authored-by: Brandon Williams <[email protected]>
1 parent c773eb9 commit 5144ec0

31 files changed

+378
-662
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ struct Reminder {
3131
var title = ""
3232
var isCompleted = false
3333
var priority: Int?
34-
@Column(as: Date.ISO8601Representation?.self)
3534
var dueDate: Date?
3635
}
3736
```

Sources/StructuredQueriesCore/Documentation.docc/Articles/DefiningYourSchema.md

Lines changed: 111 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ you can use the static description of its properties to build type-safe queries.
1414
schema of your app is defined first and foremost in your database, and then you define Swift types
1515
that represent those database definitions.
1616

17+
* [Defining a table](#Defining-a-table)
18+
* [Customizing a table](#Customizing-a-table)
19+
* [Table names](#Table-names)
20+
* [Column names](#Column-names)
21+
* [Custom data types](#Custom-data-types)
22+
* [RawRepresentable](#RawRepresentable)
23+
* [JSON](#JSON)
24+
* [Default representations for dates and UUIDs](#Default-representations-for-dates-and-UUIDs)
25+
* [Primary keyed tables](#Primary-keyed-tables)
26+
* [Ephemeral columns](#Ephemeral-columns)
27+
1728
### Defining a table
1829

1930
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
152163
The library comes with several `QueryRepresentable` conformances to aid in representing dates,
153164
UUIDs, and JSON, and you can define your own conformances for your own custom data types.
154165
155-
#### Dates
156-
157-
While some relational databases, like MySQL and Postgres, have native support for dates, SQLite
158-
does _not_. Instead, it has 3 different ways to represent dates:
159-
160-
* Text column interpreted as ISO-8601-formatted string.
161-
* Int column interpreted as number of seconds since Unix epoch.
162-
* Double column interpreted as a Julian day (number of days since November 24, 4713 BC).
163-
164-
Because of this ambiguity, the `@Table` macro does not know what you intend when you define a data
165-
type like this:
166-
167-
```swift
168-
@Table struct Reminder {
169-
let id: Int
170-
var date: Date // 🛑 'Date' column requires a query representation
171-
}
172-
```
173-
174-
In order to make it explicit how you expect to turn a `Date` into a type that SQLite understands
175-
(_i.e._ text, integer, or double) you can use the `@Column` macro with the `as:` argument. The
176-
library comes with 3 strategies out the box to help, ``Foundation/Date/ISO8601Representation`` for
177-
storing the date as a string, ``Foundation/Date/UnixTimeRepresentation`` for storing the date as an
178-
integer, and ``Foundation/Date/JulianDayRepresentation`` for storing the date as a floating point
179-
number.
180-
181-
Any of these representations can be used like so:
182-
183-
```swift
184-
@Table struct Reminder {
185-
let id: Int
186-
@Column(as: Date.ISO8601Representation.self)
187-
var date: Date
188-
}
189-
```
190-
191-
And StructuredQueries will take care of formatting the value for the database:
192-
193-
@Row {
194-
@Column {
195-
```swift
196-
Reminder.insert(
197-
Reminder.Draft(date: Date())
198-
)
199-
```
200-
}
201-
@Column {
202-
```sql
203-
INSERT INTO "reminders"
204-
("date")
205-
VALUES
206-
('2018-01-29 00:08:00.000')
207-
```
208-
}
209-
}
210-
211-
When querying against a date column with a Swift date, you will need to explicitly bundle up the
212-
Swift date into the appropriate representation to use various query helpers. This can be done using
213-
the `#bind` macro:
214-
215-
```swift
216-
Reminder.where { $0.created > #bind(startDate) }
217-
```
218-
219-
#### UUID
220-
221-
SQLite also does not have native support for UUIDs. If you try to use a UUID in your tables you
222-
will get an error:
223-
224-
```swift
225-
@Table struct Reminder {
226-
let id: UUID // 🛑 'UUID' column requires a query representation
227-
var title = ""
228-
}
229-
```
230-
231-
To use such identifiers in your table you can store the column as a data blob, and then you can
232-
use the ``Foundation/UUID/BytesRepresentation`` column representation:
233-
234-
```swift
235-
@Table struct Reminder {
236-
@Column(as: UUID.BytesRepresentation.self)
237-
let id: UUID
238-
var title = ""
239-
}
240-
```
241-
242-
Alternatively you can store the column as text and use either
243-
``Foundation/UUID/LowercasedRepresentation`` or ``Foundation/UUID/UppercasedRepresentation`` to
244-
translate the UUID to text:
245-
246-
```swift
247-
@Table struct Reminder {
248-
@Column(as: UUID.LowercasedRepresentation.self)
249-
let id: UUID
250-
var title = ""
251-
}
252-
```
253-
254-
When querying against a UUID column with a Swift UUID, you will need to explicitly bundle up the
255-
Swift UUID into the appropriate representation to use various query helpers. This can be done using
256-
the `#bind` macro:
257-
258-
```swift
259-
Reminder.where { $0.id != #bind(reminder.id) }
260-
```
261-
262166
#### RawRepresentable
263167
264168
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:
353257
}
354258
}
355259
260+
#### Default representations for dates and UUIDs
261+
262+
While some relational databases, like MySQL and Postgres, have native types for dates and UUIDs,
263+
SQLite does _not_, and instead can represent them in a variety of ways. In order to lessen the
264+
friction of building queries with dates and UUIDs, the library has decided to provide a default
265+
representation for dates and UUIDs, and if that choice does not fit your schema you can explicitly
266+
specify the representation you want.
267+
268+
##### Dates
269+
270+
Dates in SQLite have 3 different representations:
271+
272+
* Text column interpreted as ISO-8601-formatted string.
273+
* Int column interpreted as number of seconds since Unix epoch.
274+
* Double column interpreted as a Julian day (number of days since November 24, 4713 BC).
275+
276+
By default, StructuredQueries will bind and decode dates as ISO-8601 text. If you want the library
277+
to use a different representation (_i.e._ integer or double), you can provide an explicit query
278+
representation to the `@Column` macro's `as:` argument. ``Foundation/Date/UnixTimeRepresentation``
279+
will store the date as an integer, and ``Foundation/Date/JulianDayRepresentation`` will store the
280+
date as a floating point number.
281+
282+
For example:
283+
284+
```swift
285+
@Table struct Reminder {
286+
let id: Int
287+
@Column(as: Date.UnixTimeRepresentation.self)
288+
var date: Date
289+
}
290+
```
291+
292+
And StructuredQueries will take care of formatting the value for the database:
293+
294+
@Row {
295+
@Column {
296+
```swift
297+
Reminder.insert(
298+
Reminder.Draft(date: Date())
299+
)
300+
```
301+
}
302+
@Column {
303+
```sql
304+
INSERT INTO "reminders"
305+
("date")
306+
VALUES
307+
(1517184480)
308+
```
309+
}
310+
}
311+
312+
If you use the non-default date representation in your schema, then while querying against a
313+
date column with a Swift Date, you will need to explicitly bundle up the Swift date into the
314+
appropriate representation to use various query helpers. This can be done using the `#bind` macro:
315+
316+
```swift
317+
Reminder.where { $0.created > #bind(startDate) }
318+
```
319+
320+
> Note: When using the default representation for dates (ISO-8601 text) you do not need to use
321+
> the `#bind` macro:
322+
>
323+
> ```swift
324+
> Reminder.where { $0.created > startDate }
325+
> ```
326+
327+
##### UUIDs
328+
329+
SQLite also does not have type-level support for UUIDs. By default, the library will bind and decode
330+
UUIDs as lowercased, hexadecimal text, but it also provides custom representations. This includes
331+
``Foundation/UUID/UppercasedRepresentation`` for uppercased text, as well as
332+
``Foundation/UUID/BytesRepresentation`` for raw bytes.
333+
334+
To use such custom representations, you can provide it to the `@Column` macro's `as:` parameter:
335+
336+
```swift
337+
@Table struct Reminder {
338+
@Column(as: UUID.BytesRepresentation.self)
339+
let id: UUID
340+
var title = ""
341+
}
342+
```
343+
344+
If you use the non-default UUID representation in your schema, then while querying against a UUID
345+
column with a Swift UUID, you will need to explicitly bundle up the Swift UUID into the appropriate
346+
representation to use various query helpers. This can be done using
347+
the `#bind` macro:
348+
349+
```swift
350+
Reminder.where { $0.id != #bind(reminder.id) }
351+
```
352+
353+
> Note: When using the default representation for UUID (lower-cased text) you do not need to use
354+
> the `#bind` macro:
355+
>
356+
> ```swift
357+
> Reminder.where { $0.id != reminder.id }
358+
> ```
359+
356360
### Primary keyed tables
357361
358362
It is possible to tell let the `@Table` macro know which property of your data type is the primary

Sources/StructuredQueriesCore/Documentation.docc/Articles/QueryCookbook.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,14 @@ could be restored for a certain amount of time. These tables can be represented
2626
struct RemindersList: Identifiable {
2727
let id: Int
2828
var title = ""
29-
@Column(as: Date.ISO8601Representation?.self)
3029
var deletedAt: Date?
3130
}
3231
@Table
3332
struct Reminder: Identifiable {
3433
let id: Int
3534
var title = ""
3635
var isCompleted = false
37-
@Column(as: Date.ISO8601Representation?.self)
3836
var dueAt: Date?
39-
@Column(as: Date.ISO8601Representation?.self)
4037
var deletedAt: Date?
4138
var remindersListID: RemindersList.ID
4239
}
@@ -207,7 +204,6 @@ struct Reminder {
207204
let id: Int
208205
var title = ""
209206
var isCompleted = false
210-
@Column(as: Date.ISO8601Representation?.self)
211207
var deletedAt: Date?
212208

213209
static let all = Self.where { $0.isDeleted.isNot(nil) }

Sources/StructuredQueriesCore/Documentation.docc/Extensions/QueryRepresentable.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010

1111
### Conformances
1212

13-
- ``Foundation/Date/ISO8601Representation``
13+
- ``Swift/Decodable/JSONRepresentation``
1414
- ``Foundation/Date/JulianDayRepresentation``
1515
- ``Foundation/Date/UnixTimeRepresentation``
1616
- ``Foundation/UUID/BytesRepresentation``
17-
- ``Foundation/UUID/LowercasedRepresentation``
1817
- ``Foundation/UUID/UppercasedRepresentation``
19-
- ``Swift/Decodable/JSONRepresentation``
18+
19+
### Deprecations
20+
21+
- ``Foundation/Date/ISO8601Representation``
22+
- ``Foundation/UUID/LowercasedRepresentation``

Sources/StructuredQueriesCore/Documentation.docc/StructuredQueriesCore.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ struct Reminder {
1515
var title = ""
1616
var isCompleted = false
1717
var priority: Int?
18-
@Column(as: Date.ISO8601Representation?.self)
1918
var dueDate: Date?
2019
}
2120
```
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@_exported import StructuredQueriesSupport

0 commit comments

Comments
 (0)