Skip to content

Commit 5ae1c32

Browse files
authored
Merge branch 'main' into optional-helpers
2 parents c0b5e92 + 073c8ae commit 5ae1c32

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1983
-734
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ jobs:
3434
strategy:
3535
matrix:
3636
swift:
37+
- '6.0'
3738
- '6.1'
3839
runs-on: ubuntu-latest
3940
container: swift:${{ matrix.swift }}

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ DerivedData/
77
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
88
.netrc
99
Package.resolved
10-
db.sqlite
10+
*.sqlite

Sources/StructuredQueries/Macros.swift

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,19 @@ import StructuredQueriesCore
99
conformances: Table,
1010
PartialSelectStatement,
1111
PrimaryKeyedTable,
12-
names: named(TableColumns),
13-
named(From),
14-
named(Draft),
12+
names: named(From),
1513
named(columns),
1614
named(init(_:)),
1715
named(init(decoder:)),
1816
named(QueryValue),
1917
named(schemaName),
2018
named(tableName)
2119
)
22-
@attached(
23-
memberAttribute
24-
)
20+
@attached(member, names: named(Draft), named(TableColumns))
21+
@attached(memberAttribute)
2522
public macro Table(
26-
_ name: String? = nil,
27-
schema schemaName: String? = nil
23+
_ name: String = "",
24+
schema schemaName: String = ""
2825
) =
2926
#externalMacro(
3027
module: "StructuredQueriesMacros",
@@ -38,9 +35,9 @@ public macro Table(
3835
/// - representableType: A type that represents the property type in a query expression. For types
3936
/// that don't have a single representation in SQL, like `Date` and `UUID`.
4037
/// - primaryKey: The column is its table's auto-incrementing primary key.
41-
@attached(accessor, names: named(willSet))
38+
@attached(peer)
4239
public macro Column(
43-
_ name: String? = nil,
40+
_ name: String = "",
4441
as representableType: (any QueryRepresentable.Type)? = nil,
4542
primaryKey: Bool = false
4643
) =
@@ -52,7 +49,7 @@ public macro Column(
5249
/// Tells Structured Queries not to consider the annotated property a column of the table
5350
///
5451
/// Like SwiftData's `@Transient` macro, but for SQL.
55-
@attached(accessor, names: named(willSet))
52+
@attached(peer)
5653
public macro Ephemeral() =
5754
#externalMacro(
5855
module: "StructuredQueriesMacros",
@@ -95,6 +92,7 @@ public macro Ephemeral() =
9592
names: named(Columns),
9693
named(init(decoder:))
9794
)
95+
@attached(member, names: named(Columns))
9896
public macro Selection() =
9997
#externalMacro(
10098
module: "StructuredQueriesMacros",
@@ -135,21 +133,9 @@ public macro sql<QueryValue>(
135133
) -> SQLQueryExpression<QueryValue> =
136134
#externalMacro(module: "StructuredQueriesMacros", type: "SQLMacro")
137135

138-
// NB: Due to a bug in Swift, this macro is expanded internally by the '@Table' macro.
139-
// @attached(
140-
// memberAttribute
141-
// )
142-
// @attached(
143-
// extension,
144-
// conformances: Table,
145-
// names: named(TableColumns),
146-
// named(columns),
147-
// named(init(_:)),
148-
// named(init(decoder:)),
149-
// named(tableName)
150-
// )
151-
// public macro _Draft<T: Table>(_: T.Type) =
152-
// #externalMacro(
153-
// module: "StructuredQueriesMacros",
154-
// type: "TableMacro"
155-
// )
136+
@freestanding(expression)
137+
public macro sql(
138+
_ queryFragment: QueryFragment,
139+
as queryValueType: Any.Type = Any.self
140+
) -> SQLQueryExpression<Any> =
141+
#externalMacro(module: "StructuredQueriesMacros", type: "SQLMacro")

Sources/StructuredQueriesCore/AggregateFunctions.swift

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ extension QueryExpression where QueryValue: QueryBindable {
2020
distinct isDistinct: Bool = false,
2121
filter: (some QueryExpression<Bool>)? = Bool?.none
2222
) -> some QueryExpression<Int> {
23-
AggregateFunction("count", isDistinct: isDistinct, self, filter: filter)
23+
AggregateFunction(
24+
"count",
25+
isDistinct: isDistinct,
26+
[queryFragment],
27+
filter: filter?.queryFragment
28+
)
2429
}
2530
}
2631

@@ -46,11 +51,12 @@ where QueryValue: _OptionalPromotable, QueryValue._Optionalized.Wrapped == Strin
4651
order: (some QueryExpression)? = Bool?.none,
4752
filter: (some QueryExpression<Bool>)? = Bool?.none
4853
) -> some QueryExpression<String?> {
49-
if let separator {
50-
return AggregateFunction("group_concat", self, separator, order: order, filter: filter)
51-
} else {
52-
return AggregateFunction("group_concat", self, order: order, filter: filter)
53-
}
54+
AggregateFunction(
55+
"group_concat",
56+
separator.map { [queryFragment, $0.queryFragment] } ?? [queryFragment],
57+
order: order?.queryFragment,
58+
filter: filter?.queryFragment
59+
)
5460
}
5561

5662
/// A string concatenation aggregate of this expression.
@@ -68,7 +74,13 @@ where QueryValue: _OptionalPromotable, QueryValue._Optionalized.Wrapped == Strin
6874
order: (some QueryExpression)? = Bool?.none,
6975
filter: (some QueryExpression<Bool>)? = Bool?.none
7076
) -> some QueryExpression<String?> {
71-
AggregateFunction("group_concat", isDistinct: isDistinct, self, order: order, filter: filter)
77+
AggregateFunction(
78+
"group_concat",
79+
isDistinct: isDistinct,
80+
[queryFragment],
81+
order: order?.queryFragment,
82+
filter: filter?.queryFragment
83+
)
7284
}
7385
}
7486

@@ -85,7 +97,7 @@ extension QueryExpression where QueryValue: QueryBindable & _OptionalPromotable
8597
public func max(
8698
filter: (some QueryExpression<Bool>)? = Bool?.none
8799
) -> some QueryExpression<QueryValue._Optionalized.Wrapped?> {
88-
AggregateFunction("max", self, filter: filter)
100+
AggregateFunction("max", [queryFragment], filter: filter?.queryFragment)
89101
}
90102

91103
/// A minimum aggregate of this expression.
@@ -100,7 +112,7 @@ extension QueryExpression where QueryValue: QueryBindable & _OptionalPromotable
100112
public func min(
101113
filter: (some QueryExpression<Bool>)? = Bool?.none
102114
) -> some QueryExpression<QueryValue._Optionalized.Wrapped?> {
103-
AggregateFunction("min", self, filter: filter)
115+
AggregateFunction("min", [queryFragment], filter: filter?.queryFragment)
104116
}
105117
}
106118

@@ -122,7 +134,7 @@ where QueryValue: _OptionalPromotable, QueryValue._Optionalized.Wrapped: Numeric
122134
distinct isDistinct: Bool = false,
123135
filter: (some QueryExpression<Bool>)? = Bool?.none
124136
) -> some QueryExpression<Double?> {
125-
AggregateFunction("avg", isDistinct: isDistinct, self, filter: filter)
137+
AggregateFunction("avg", isDistinct: isDistinct, [queryFragment], filter: filter?.queryFragment)
126138
}
127139

128140
/// An sum aggregate of this expression.
@@ -145,7 +157,10 @@ where QueryValue: _OptionalPromotable, QueryValue._Optionalized.Wrapped: Numeric
145157
// TODO: Report issue to Swift team.
146158
SQLQueryExpression(
147159
AggregateFunction<QueryValue._Optionalized>(
148-
"sum", isDistinct: isDistinct, self, filter: filter
160+
"sum",
161+
isDistinct: isDistinct,
162+
[queryFragment],
163+
filter: filter?.queryFragment
149164
)
150165
.queryFragment
151166
)
@@ -167,7 +182,12 @@ where QueryValue: _OptionalPromotable, QueryValue._Optionalized.Wrapped: Numeric
167182
distinct isDistinct: Bool = false,
168183
filter: (some QueryExpression<Bool>)? = Bool?.none
169184
) -> some QueryExpression<QueryValue> {
170-
AggregateFunction("total", isDistinct: isDistinct, self, filter: filter)
185+
AggregateFunction(
186+
"total",
187+
isDistinct: isDistinct,
188+
[queryFragment],
189+
filter: filter?.queryFragment
190+
)
171191
}
172192
}
173193

@@ -182,9 +202,9 @@ extension QueryExpression where Self == AggregateFunction<Int> {
182202
/// - Parameter filter: A `FILTER` clause to apply to the aggregation.
183203
/// - Returns: A `count(*)` aggregate.
184204
public static func count(
185-
filter: (some QueryExpression<Bool>)? = Bool?.none
205+
filter: (any QueryExpression<Bool>)? = nil
186206
) -> Self {
187-
AggregateFunction("count", SQLQueryExpression("*"), filter: filter)
207+
AggregateFunction("count", ["*"], filter: filter?.queryFragment)
188208
}
189209
}
190210

@@ -196,18 +216,18 @@ public struct AggregateFunction<QueryValue>: QueryExpression {
196216
var order: QueryFragment?
197217
var filter: QueryFragment?
198218

199-
init<each Argument: QueryExpression>(
219+
init(
200220
_ name: QueryFragment,
201221
isDistinct: Bool = false,
202-
_ arguments: repeat each Argument,
203-
order: (some QueryExpression)? = Bool?.none,
204-
filter: (some QueryExpression)? = Bool?.none
222+
_ arguments: [QueryFragment] = [],
223+
order: QueryFragment? = nil,
224+
filter: QueryFragment? = nil
205225
) {
206226
self.name = name
207227
self.isDistinct = isDistinct
208-
self.arguments = Array(repeat each arguments)
209-
self.order = order?.queryFragment
210-
self.filter = filter?.queryFragment
228+
self.arguments = arguments
229+
self.order = order
230+
self.filter = filter
211231
}
212232

213233
public var queryFragment: QueryFragment {

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ that represent those database definitions.
2121
* [Custom data types](#Custom-data-types)
2222
* [RawRepresentable](#RawRepresentable)
2323
* [JSON](#JSON)
24+
* [Tagged identifiers](#Tagged-identifiers)
2425
* [Default representations for dates and UUIDs](#Default-representations-for-dates-and-UUIDs)
2526
* [Primary keyed tables](#Primary-keyed-tables)
2627
* [Ephemeral columns](#Ephemeral-columns)
@@ -188,12 +189,12 @@ and will decode data from the database using the `RawRepresentable` conformance
188189
@Row {
189190
@Column {
190191
```swift
191-
Reminder.insert(
192+
Reminder.insert {
192193
Reminder.Draft(
193194
title: "Get haircut",
194195
priority: .medium
195196
)
196-
)
197+
}
197198
```
198199
}
199200
@Column {
@@ -239,12 +240,12 @@ With that you can insert reminders with notes like so:
239240
@Row {
240241
@Column {
241242
```swift
242-
Reminder.insert(
243+
Reminder.insert {
243244
Reminder.Draft(
244245
title: "Get groceries",
245246
notes: ["Milk", "Eggs", "Bananas"]
246247
)
247-
)
248+
}
248249
```
249250
}
250251
@Column {
@@ -298,10 +299,11 @@ This adds a new layer of type-safety when constructing queries. Previously compa
298299
`RemindersList.ID` to a `Reminder.ID` would compile just fine, even though it is a nonsensical thing
299300
to do. But now, such a comparison is a compile time error:
300301

301-
```
302+
```swift
302303
RemindersList.leftJoin(Reminder.all) {
303304
$0.id == $1.id // 🛑 Requires the types 'Reminder.ID' and 'RemindersList.ID' be equivalent
304305
}
306+
```
305307

306308
#### Default representations for dates and UUIDs
307309

@@ -340,9 +342,9 @@ And StructuredQueries will take care of formatting the value for the database:
340342
@Row {
341343
@Column {
342344
```swift
343-
Reminder.insert(
345+
Reminder.insert {
344346
Reminder.Draft(date: Date())
345-
)
347+
}
346348
```
347349
}
348350
@Column {

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ capable of. See <doc:SelectStatements> for more examples of select statements, a
305305
The library provides the tools necessary to construct type-safe insert statements in SQL, including
306306
inserting an entire value into a table, inserting only a subset of rows, as well as what to do on
307307
conflicts. Using the `Reminder` data type from above, we can insert data for all of its rows using
308-
the ``Table/insert(or:_:values:onConflict:)`` method:
308+
the ``Table/insert(or:_:values:onConflict:where:doUpdate:where:)`` method:
309309

310310
@Row {
311311
@Column {
@@ -338,10 +338,11 @@ mechanism). The second trailing closure is a list of values that you want to ins
338338
and the number of columns and data type of each column must match what is specified in the first
339339
trailing closure.
340340

341-
You can provide a 3rd trailing closure to ``Table/insert(or:_:values:onConflict:)`` to describe what
342-
to do in case there is a conflict while inserting data. For example, suppose we had a unique index
343-
on the "title" column of the reminders table. Then when inserting a value with a repeated title we
344-
could resolve the conflict by appending the string `" (Copy)"` to the title:
341+
You can provide a 3rd trailing closure to
342+
``Table/insert(or:_:values:onConflict:where:doUpdate:where:)`` to describe what to do in case there
343+
is a conflict while inserting data. For example, suppose we had a unique index on the "title" column
344+
of the reminders table. Then when inserting a value with a repeated title we could resolve the
345+
conflict by appending the string `" (Copy)"` to the title:
345346

346347
@Row {
347348
@Column {

0 commit comments

Comments
 (0)