Skip to content

Commit 6899254

Browse files
authored
Add important note about safe SQL strings (#41)
* Add important note about safe SQL strings We should stress against using `SELECT *` and referring to table/column names using strings. * wip * wip
1 parent e927231 commit 6899254

File tree

1 file changed

+33
-11
lines changed

1 file changed

+33
-11
lines changed

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

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,27 @@ could be written as a single invocation of the macro:
108108
All of the columns provided to trailing closures in the query builder are available statically on
109109
each table type, so you can freely interpolate this schema information into the SQL string.
110110

111+
> Important: _Always_ interpolate as much static schema information as possible into the SQL string
112+
> to better ensure that queries are correct and will successfully decode.
113+
>
114+
> For example:
115+
>
116+
> ```diff
117+
> -SELECT * FROM reminders
118+
> +SELECT \(Reminder.columns) FROM \(Reminder.self)
119+
> ```
120+
>
121+
> * Selecting "`*`" requires that the column order in the database matches the field order in the
122+
> Swift data type. Because StructuredQueries decodes columns in positional order, a query using
123+
> "`*`" will fail to decode unless the field order matches exactly. Instead of leaving this to
124+
> chance, prefer interpolating `Table.columns`, which will generate an explicit SQL column
125+
> selection that matches the order of fields in the Swift data type.
126+
> * Spelling out table and column names directly inside the query (_e.g._ "`reminders`") can lead
127+
> to runtime errors due to typos or stale queries that refer to schema columns that have been
128+
> renamed or removed. Instead, prefer interpolating `Table.columnName` to refer to a particular
129+
> column (_e.g._, `Reminder.isCompleted`), and `Table.self` to refer to a table (_e.g._,
130+
> `Reminder.self`).
131+
111132
Note that the query's represented type cannot be inferred here, and so the `as` parameter is used
112133
to let Swift know that we expect to decode the `Reminder` type when we execute the query.
113134
@@ -131,11 +152,12 @@ Values can be interpolated into `#sql` strings to produce dynamic queries:
131152
@Column {
132153
```swift
133154
let isCompleted = true
155+
134156
#sql(
135157
"""
136158
SELECT count(*)
137-
FROM reminders
138-
WHERE isCompleted = \(isCompleted)
159+
FROM \(Reminder.self)
160+
WHERE \(Reminder.isCompleted) = \(isCompleted)
139161
""",
140162
as: Reminder.self
141163
)
@@ -144,22 +166,22 @@ Values can be interpolated into `#sql` strings to produce dynamic queries:
144166
@Column {
145167
```sql
146168
SELECT count(*)
147-
FROM reminders
148-
WHERE isCompleted = ?
169+
FROM "reminders"
170+
WHERE "reminders"."isCompleted" = ?
149171
-- [1]
150172
```
151173
}
152174
}
153175

154-
Note that although it seems the literal value is being interpolated directly into the string, that
176+
Note that although it seems that `isCompleted` is being interpolated directly into the string, that
155177
is not what is happening. The interpolated value is captured as a separate statement binding in
156178
order to protect against SQL injection.
157179

158-
String bindings are handled in a special fashion to make it clear what the intended usage is. If
159-
you interpolate a string into a `#sql` string, you will get a deprecation warning:
180+
String bindings are handled in a special fashion to make it clear what the intended usage is. If you
181+
interpolate a string into a `#sql` string, you will get a deprecation warning:
160182

161183
```swift
162-
let searchText = "get"
184+
let searchText = "%get%"
163185
#sql(
164186
"""
165187
SELECT \(Reminder.columns)
@@ -178,7 +200,7 @@ If you mean to bind the string as a value, you can update the interpolation to u
178200
@Row {
179201
@Column {
180202
```swift
181-
let searchText = "get"
203+
let searchText = "%get%"
182204
#sql(
183205
"""
184206
SELECT \(Reminder.columns)
@@ -205,12 +227,12 @@ If you mean to interpolate the string directly into the SQL you can use
205227
@Row {
206228
@Column {
207229
```swift
208-
let searchText = "get"
230+
let searchText = "%get%"
209231
#sql(
210232
"""
211233
SELECT \(Reminder.columns)
212234
FROM \(Reminder.self)
213-
WHERE \(Reminder.title) COLLATE NOCASE LIKE '%\(raw: searchText)%'
235+
WHERE \(Reminder.title) COLLATE NOCASE LIKE '\(raw: searchText)'
214236
""",
215237
as: Reminder.self
216238
)

0 commit comments

Comments
 (0)