Skip to content

Commit 49ce85d

Browse files
Better @Table/@Selection composition: nested column groupings, enums, and more (#184)
* Incorporate `@Selection` logic into `@Table` The `@Selection` and `@Table` type overlap in many ways, but there is no reason why `@Selection`'s `Columns` type should ever be omitted from `@Table`, and so this PR introduces the machinery for just that. Draft for now because there's much to discuss: - Should we deprecate `@Selection` entirely, and push people to use `@Table` for everything? It might be a little weird, but we've already blurred the line with CTEs and database views that maybe we should consider `@Table` to mean groups of columns that can be assembled in Swift. It's also less library code to maintain/document, though it is _more_ code generation (arguably not a ton, but who knows the impact on the compiler/binary). - Or should we keep `@Selection` around as a lightweight subset of `@Table`? - If so, should we somehow compose our macro code better, so that `@Table` calls down to `@Selection`? - Or should it still live in a parallel world, but be updated to use some formal machinery, like the `TableExpression` protocol introduced in this PR. * wip * Support tuple operations with `Table` records * Support nested tables * fixes * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Basic docs pass * wip * Fixes * wip * fixes * wip * wip * db function support * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Infer multi-column when possible * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Docs and tests * wip * wip * wip * wip * wip * wip * wip --------- Co-authored-by: Brandon Williams <[email protected]>
1 parent 9a6aff7 commit 49ce85d

File tree

99 files changed

+7219
-2935
lines changed

Some content is hidden

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

99 files changed

+7219
-2935
lines changed

Package.resolved

Lines changed: 20 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
import CompilerPluginSupport
44
import PackageDescription
55

6+
#if canImport(FoundationEssentials)
7+
import FoundationEssentials
8+
#else
9+
import Foundation
10+
#endif
11+
612
let package = Package(
713
name: "swift-structured-queries",
814
platforms: [
@@ -34,13 +40,17 @@ let package = Package(
3440
),
3541
],
3642
traits: [
43+
.trait(
44+
name: "StructuredQueriesCasePaths",
45+
description: "Introduce enum table support to StructuredQueries."
46+
),
3747
.trait(
3848
name: "StructuredQueriesTagged",
39-
description: "Introduce StructuredQueries conformances to the swift-tagged package.",
40-
enabledTraits: []
41-
)
49+
description: "Introduce StructuredQueries conformances to the swift-tagged package."
50+
),
4251
],
4352
dependencies: [
53+
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.0.0"),
4454
.package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.3"),
4555
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.8.1"),
4656
.package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.6.3"),
@@ -61,6 +71,11 @@ let package = Package(
6171
name: "StructuredQueriesCore",
6272
dependencies: [
6373
.product(name: "IssueReporting", package: "xctest-dynamic-overlay"),
74+
.product(
75+
name: "CasePaths",
76+
package: "swift-case-paths",
77+
condition: .when(traits: ["StructuredQueriesCasePaths"])
78+
),
6479
.product(
6580
name: "Tagged",
6681
package: "swift-tagged",
@@ -141,6 +156,19 @@ let package = Package(
141156
swiftLanguageModes: [.v6]
142157
)
143158

159+
// NB: For local testing in Xcode:
160+
// if true {
161+
if ProcessInfo.processInfo.environment["SPI_GENERATE_DOCS"] != nil {
162+
package.traits.insert(
163+
.default(
164+
enabledTraits: [
165+
"StructuredQueriesCasePaths",
166+
"StructuredQueriesTagged",
167+
]
168+
)
169+
)
170+
}
171+
144172
let swiftSettings: [SwiftSetting] = [
145173
.enableUpcomingFeature("MemberImportVisibility")
146174
// .unsafeFlags([

README.md

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ comfortable with the library:
155155

156156
* [Getting started](https://swiftpackageindex.com/pointfreeco/swift-structured-queries/~/documentation/structuredqueriescore/gettingstarted)
157157
* [Defining your schema](https://swiftpackageindex.com/pointfreeco/swift-structured-queries/~/documentation/structuredqueriescore/definingyourschema)
158-
* [Primary keyed tables](https://swiftpackageindex.com/pointfreeco/swift-structured-queries/~/documentation/structuredqueriescore/primarykeyedtables)
158+
* [Primary-keyed tables](https://swiftpackageindex.com/pointfreeco/swift-structured-queries/~/documentation/structuredqueriescore/primarykeyedtables)
159159
* [Safe SQL strings](https://swiftpackageindex.com/pointfreeco/swift-structured-queries/~/documentation/structuredqueriescore/safesqlstrings)
160160
* [Query cookbook](https://swiftpackageindex.com/pointfreeco/swift-structured-queries/~/documentation/structuredqueriescore/querycookbook)
161161

@@ -174,19 +174,19 @@ As well as more comprehensive example usage:
174174
## Demos
175175

176176
There are a number of sample applications that demonstrate how to use StructuredQueries in the
177-
[SharingGRDB](https://github.com/pointfreeco/sharing-grdb) repo. Check out
178-
[this](https://github.com/pointfreeco/sharing-grdb/tree/main/Examples) directory to see them all,
177+
[SQLiteData](https://github.com/pointfreeco/sqlite-data) repo. Check out
178+
[this](https://github.com/pointfreeco/sqlite-data/tree/main/Examples) directory to see them all,
179179
including:
180180

181-
* [Case Studies](https://github.com/pointfreeco/sharing-grdb/tree/main/Examples/CaseStudies):
181+
* [Case Studies](https://github.com/pointfreeco/sqlite-data/tree/main/Examples/CaseStudies):
182182
A number of case studies demonstrating the built-in features of the library.
183183

184-
* [Reminders](https://github.com/pointfreeco/sharing-grdb/tree/main/Examples/Reminders): A rebuild
184+
* [Reminders](https://github.com/pointfreeco/sqlite-data/tree/main/Examples/Reminders): A rebuild
185185
of Apple's [Reminders][reminders-app-store] app that uses a SQLite database to model the
186186
reminders, lists and tags. It features many advanced queries, such as searching, and stats
187187
aggregation.
188188

189-
* [SyncUps](https://github.com/pointfreeco/sharing-grdb/tree/main/Examples/SyncUps): We also
189+
* [SyncUps](https://github.com/pointfreeco/sqlite-data/tree/main/Examples/SyncUps): We also
190190
rebuilt Apple's [Scrumdinger][scrumdinger] demo application using modern, best practices for
191191
SwiftUI development, including using this library to query and persist state using SQLite.
192192

@@ -198,8 +198,8 @@ including:
198198
StructuredQueries is built with the goal of supporting any SQL database (SQLite, MySQL, Postgres,
199199
_etc._), but is currently tuned to work with SQLite. It currently has one official driver:
200200

201-
* [SharingGRDB](https://github.com/pointfreeco/sharing-grdb): A lightweight replacement for
202-
SwiftData and the `@Query` macro. SharingGRDB includes `StructuredQueriesGRDB`, a library that
201+
* [SQLiteData](https://github.com/pointfreeco/sqlite-data): A lightweight replacement for
202+
SwiftData and the `@Query` macro. SQLiteData includes `StructuredQueriesGRDB`, a library that
203203
integrates this one with the popular [GRDB](https://github.com/groue/GRDB.swift) SQLite library.
204204

205205
If you are interested in building a StructuredQueries integration for another database library,
@@ -220,7 +220,7 @@ it's as simple as adding it to your `Package.swift`:
220220

221221
``` swift
222222
dependencies: [
223-
.package(url: "https://github.com/pointfreeco/swift-structured-queries", from: "0.17.0"),
223+
.package(url: "https://github.com/pointfreeco/swift-structured-queries", from: "0.22.0"),
224224
]
225225
```
226226

@@ -231,16 +231,21 @@ And then adding the product to any target that needs access to the library:
231231
```
232232

233233
If you are on Swift 6.1 or greater, you can enable package traits that extend the library with
234-
support for other libraries. For example, you can introduce type-safe identifiers to your tables
235-
_via_ [Tagged](https://github.com/pointfreeco/swift-tagged) by enabling the
236-
`StructuredQueriesTagged` trait:
234+
support for other libraries:
235+
236+
* `StructuredQueriesCasePaths`: Adds support for single-table inheritance _via_ "enum" tables by
237+
leveraging the [CasePaths](https://github.com/pointfreeco/swift-case-paths) library.
238+
239+
* `StructuredQueriesTagged`: Adds support for type-safe identifiers _via_
240+
the [Tagged](https://github.com/pointfreeco/swift-tagged) library.
237241

238242
```diff
239243
dependencies: [
240244
.package(
241245
url: "https://github.com/pointfreeco/swift-structured-queries",
242-
from: "0.17.0",
246+
from: "0.22.0",
243247
+ traits: [
248+
+ "StructuredQueriesCasePaths",
244249
+ "StructuredQueriesTagged",
245250
+ ]
246251
),

Sources/StructuredQueries/Documentation.docc/StructuredQueries.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ StructuredQueries also ships SQLite-specific helpers:
2626

2727
- ``Table(_:)``
2828
- ``Column(_:as:primaryKey:)``
29+
- ``Columns(primaryKey:)``
2930
- ``Ephemeral()``
3031
- ``Selection()``
3132
- ``sql(_:as:)``

Sources/StructuredQueries/Macros.swift

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,25 @@ import StructuredQueriesCore
22

33
/// Defines and implements a conformance to the ``/StructuredQueriesCore/Table`` protocol.
44
///
5-
/// - Parameter name: The table's name. Defaults to a lower-camel-case pluralization of the type,
6-
/// _e.g._ `RemindersList` becomes `"remindersLists"`.
5+
/// - Parameters
6+
/// - name: The table's name. Defaults to a lower-camel-case pluralization of the type,
7+
/// _e.g._ `RemindersList` becomes `"remindersLists"`.
8+
/// - schemaName: The table's schema name.
79
@attached(
810
extension,
911
conformances: Table,
1012
PartialSelectStatement,
1113
PrimaryKeyedTable,
1214
names: named(From),
1315
named(columns),
16+
named(_columnWidth),
1417
named(init(_:)),
1518
named(init(decoder:)),
1619
named(QueryValue),
1720
named(schemaName),
1821
named(tableName)
1922
)
20-
@attached(member, names: named(Draft), named(TableColumns))
23+
@attached(member, names: named(Draft), named(Selection), named(TableColumns))
2124
@attached(memberAttribute)
2225
public macro Table(
2326
_ name: String = "",
@@ -28,38 +31,7 @@ public macro Table(
2831
type: "TableMacro"
2932
)
3033

31-
/// Customizes a column generated by the ``/StructuredQueriesCore/Table`` protocol.
32-
///
33-
/// - Parameters:
34-
/// - name: The column's name. Defaults to the property's name, _e.g._ 'id' becomes `"id"`.
35-
/// - representableType: A type that represents the property type in a query expression. For types
36-
/// that don't have a single representation in SQL, like `Date` and `UUID`.
37-
/// - generated: Allows to declare the column as a read-only database computed column, making it
38-
/// available for queries but not for updates.
39-
/// - primaryKey: The column is its table's auto-incrementing primary key.
40-
@attached(peer)
41-
public macro Column(
42-
_ name: String = "",
43-
as representableType: (any QueryRepresentable.Type)? = nil,
44-
generated: GeneratedColumnStorage? = nil,
45-
primaryKey: Bool = false
46-
) =
47-
#externalMacro(
48-
module: "StructuredQueriesMacros",
49-
type: "ColumnMacro"
50-
)
51-
52-
/// Tells StructuredQueries not to consider the annotated property a column of the table.
53-
///
54-
/// Like SwiftData's `@Transient` macro, but for SQL.
55-
@attached(peer)
56-
public macro Ephemeral() =
57-
#externalMacro(
58-
module: "StructuredQueriesMacros",
59-
type: "EphemeralMacro"
60-
)
61-
62-
/// Defines the ability for a type to be selected from a query.
34+
/// Defines a "selection" of columns that can be decoded from a query.
6335
///
6436
/// When selecting tables and fields from a query, this data is bundled up into a tuple:
6537
///
@@ -87,19 +59,77 @@ public macro Ephemeral() =
8759
/// // [RemindersListWithReminderCount]
8860
/// ```
8961
///
90-
/// The ``Table(_:)`` and `@Selection` macros can be composed together to describe a virtual table
91-
/// or common table expression.
62+
/// > Tip: `@Selection`s can also be used to build up common table expressions.
63+
///
64+
/// - Parameter name: The selection's name, _i.e._ for a common table expression. Defaults to a
65+
/// lower-camel-case pluralization of the type, _e.g._ `RemindersList` becomes `"remindersLists"`.
9266
@attached(
9367
extension,
9468
conformances: _Selection,
95-
names: named(Columns),
96-
named(init(decoder:))
69+
Table,
70+
PartialSelectStatement,
71+
PrimaryKeyedTable,
72+
names: named(From),
73+
named(columns),
74+
named(_columnWidth),
75+
named(init(_:)),
76+
named(init(decoder:)),
77+
named(QueryValue),
78+
named(schemaName),
79+
named(tableName)
9780
)
98-
@attached(member, names: named(Columns))
99-
public macro Selection() =
81+
@attached(member, names: named(Draft), named(Selection), named(TableColumns))
82+
@attached(memberAttribute)
83+
public macro Selection(
84+
_ name: String = ""
85+
) =
86+
#externalMacro(
87+
module: "StructuredQueriesMacros",
88+
type: "TableMacro"
89+
)
90+
91+
/// Customizes a column generated by the ``/StructuredQueriesCore/Table`` protocol.
92+
///
93+
/// - Parameters:
94+
/// - name: The column's name. Defaults to the property's name, _e.g._ 'id' becomes `"id"`.
95+
/// - representableType: A type that represents the property type in a query expression. For types
96+
/// that don't have a single representation in SQL, like `Date` and `UUID`.
97+
/// - generated: Allows to declare the column as a read-only database computed column, making it
98+
/// available for queries but not for updates.
99+
/// - primaryKey: The column is its table's primary key.
100+
@attached(peer)
101+
public macro Column(
102+
_ name: String = "",
103+
as representableType: (any QueryRepresentable.Type)? = nil,
104+
generated: GeneratedColumnStorage? = nil,
105+
primaryKey: Bool = false
106+
) =
107+
#externalMacro(
108+
module: "StructuredQueriesMacros",
109+
type: "ColumnMacro"
110+
)
111+
112+
/// Customizes a group of columns generated by the ``/StructuredQueriesCore/Table`` protocol.
113+
///
114+
/// - Parameters primaryKey: These columns are the table's composite primary key.
115+
@attached(peer)
116+
public macro Columns(
117+
// as representableType: (any QueryRepresentable.Type)? = nil,
118+
primaryKey: Bool = false
119+
) =
100120
#externalMacro(
101121
module: "StructuredQueriesMacros",
102-
type: "SelectionMacro"
122+
type: "ColumnsMacro"
123+
)
124+
125+
/// Tells StructuredQueries not to consider the annotated property a column of the table.
126+
///
127+
/// Like SwiftData's `@Transient` macro, but for SQL.
128+
@attached(peer)
129+
public macro Ephemeral() =
130+
#externalMacro(
131+
module: "StructuredQueriesMacros",
132+
type: "EphemeralMacro"
103133
)
104134

105135
/// Explicitly bind a value to a query.

Sources/StructuredQueriesCore/Bind.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
///
33
/// It is not common to interact with this type directly. A value of this type is returned from the
44
/// `#bind` macro.
5-
public struct BindQueryExpression<QueryValue: QueryBindable>: QueryExpression {
5+
public struct BindQueryExpression<QueryValue: QueryRepresentable & QueryExpression>: QueryExpression
6+
{
67
public let base: QueryValue
78

89
public init(

0 commit comments

Comments
 (0)