Skip to content

Commit f968488

Browse files
committed
Add a little bit of documentation for dynamic SQL.
1 parent 1c661b3 commit f968488

2 files changed

Lines changed: 125 additions & 0 deletions

File tree

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
* [SQLite](doc/Language/Quirks/SQLiteQuirks.md)
3636
* [TSQL](doc/Language/Quirks/TSQLQuirks.md)
3737
* [Postgres](doc/Language/Quirks/PostgresQuirks.md)
38+
* [Dynamic SQL](doc/Language/DynamicSQL.md)
3839
* [What's missing?](doc/Language/MissingFeatures.md)
3940
* [API](doc/API/README.md)
4041
* [Rezoom.SQL](doc/API/RezoomSQL.md)

doc/Language/DynamicSQL.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Dynamic SQL
2+
3+
Occasionally it is necessary to generate SQL dynamically.
4+
5+
Rezoom.SQL permits running SQL statements specified fully at runtime, or adding
6+
SQL expressions at runtime into an otherwise-static query. These dynamic SQL
7+
expression do _not_ go through the RZSQL typechecker and translator, so they
8+
should be written directly for the dialect of SQL you are targeting with your
9+
[backend](../Configuration/Json.md#backend).
10+
11+
You should avoid using dynamic SQL whenever possible, since it is easy to make
12+
mistakes. In particular, you must be careful to avoid SQL injection.
13+
14+
Future additions to Rezoom.SQL are planned to reduce the number of situations in
15+
which dynamic SQL will be necessary and add safer APIs for common use cases like
16+
dynamic filtering and sorting on the output columns of a query.
17+
18+
## How to use dynamic SQL
19+
20+
The simplest way is to create a fully dynamic `Command`. You do this with the
21+
`dynamicCommand` function, which takes an array of "command fragments". You can
22+
get a command fragment representing a raw SQL string using the `sql` function,
23+
and a command fragment representing a parameter using `arg` or `argOfType`.
24+
25+
An example will illustrate it better than any explanation I could write:
26+
27+
```fsharp
28+
open Rezoom.SQL
29+
open Rezoom.SQL.Raw -- module with helpers for dynamic SQL
30+
31+
type ExampleQueryResult =
32+
{ Id : int
33+
Name : string
34+
}
35+
36+
let exampleCommand (id : int) (nameSearch : string) =
37+
dynamicCommand<ExampleQueryResult>
38+
[| sql "SELECT Id, Name FROM USERS"
39+
sql " WHERE Id = "
40+
arg id
41+
sql " OR Name LIKE "
42+
arg ("%" + nameSearch + "%")
43+
|]
44+
```
45+
46+
## Adding dynamic SQL expressions to static SQL queries
47+
48+
You can use the [erased function](Functions/README.md#erased-functions)
49+
`unsafe_inject_raw` on a parameter to pass dynamic SQL via that parameter at
50+
runtime. Again, an example will help:
51+
52+
```fsharp
53+
open Rezoom.SQL
54+
open Rezoom.SQL.Raw
55+
56+
type MyMostlyStaticQuery = SQL<"""
57+
SELECT Id, Name FROM USERS
58+
WHERE unsafe_inject_raw(@dynSql)
59+
""">
60+
61+
let exampleCommand (id : int) (nameSearch : string) =
62+
let exampleSql =
63+
[| sql "Id = "
64+
arg id
65+
sql " OR Name LIKE "
66+
arg ("%" + nameSearch + "%")
67+
|]
68+
MyMostlyStaticQuery.Command(dynSql = exampleSql)
69+
70+
```
71+
72+
Remember that dynamic SQL does not go through RZSQL translation so you'll need
73+
to use your backend's actual syntax.
74+
75+
## Avoiding SQL injection in dynamic SQL
76+
77+
**NEVER** pass inputs from an untrusted source (e.g. an end user) to the `sql`
78+
function. This allows that user to craft inputs that run whatever SQL they want.
79+
This is called [SQL injection](https://www.google.com/search?q=sql+injection)
80+
and is one of the worst vulnerabilities an application can have. It combines
81+
potentially devastating consequences with easy exploitation.
82+
83+
To avoid SQL injection, **ALWAYS** pass user inputs as SQL parameters using the
84+
`arg` or `argOfType` functions. `arg` will attempt to guess the `DbType` of the
85+
given value based on its .NET type, while `argOfType` has you specify it
86+
yourself.
87+
88+
### WRONG
89+
90+
```fsharp
91+
92+
// DO NOT do it this way!
93+
94+
let exampleCommand (id : int) (nameSearch : string) =
95+
dynamicCommand<ExampleQueryResult>
96+
[| sql "SELECT Id, Name FROM USERS"
97+
sql " WHERE Name LIKE "
98+
99+
// BAD. DO NOT DO THIS!
100+
sql ("'%" + nameSearch + "%'") // <-- NO!
101+
|]
102+
103+
```
104+
105+
### RIGHT
106+
107+
```fsharp
108+
109+
// Use a parameter to pass the untrusted user input.
110+
111+
// Notice that the percent signs aren't surrounded with single quotes anymore,
112+
// because we no longer need to (badly) attempt to convert the input string to
113+
// SQL source code.
114+
115+
let exampleCommand (id : int) (nameSearch : string) =
116+
dynamicCommand<ExampleQueryResult>
117+
[| sql "SELECT Id, Name FROM USERS"
118+
sql " WHERE Name LIKE "
119+
120+
// GOOD: use `arg` function to make a bind-parameter at runtime
121+
arg ("%" + nameSearch + "%")
122+
|]
123+
124+
```

0 commit comments

Comments
 (0)