1 | <img align="right" src="https://cdn.rawgit.com/mikesamuel/template-tag-common/7f0159bda72d616af30645d49c3c9203c963c0a6/images/logo.png" alt="Sisyphus Logo">
|
2 |
|
3 | # Safe SQL Template Tag
|
4 |
|
5 | [![Build Status](https://travis-ci.org/mikesamuel/safesql.svg?branch=master)](https://travis-ci.org/mikesamuel/safesql)
|
6 | [![Dependencies Status](https://david-dm.org/mikesamuel/safesql/status.svg)](https://david-dm.org/mikesamuel/safesql)
|
7 | [![npm](https://img.shields.io/npm/v/safesql.svg)](https://www.npmjs.com/package/safesql)
|
8 | [![Coverage Status](https://coveralls.io/repos/github/mikesamuel/safesql/badge.svg?branch=master)](https://coveralls.io/github/mikesamuel/safesql?branch=master)
|
9 | [![Install Size](https://packagephobia.now.sh/badge?p=safesql)](https://packagephobia.now.sh/result?p=safesql)
|
10 | [![Known Vulnerabilities](https://snyk.io/test/github/mikesamuel/safesql/badge.svg?targetFile=package.json)](https://snyk.io/test/github/mikesamuel/safesql?targetFile=package.json)
|
11 |
|
12 | Provides a string template tag that makes it easy to compose
|
13 | [MySQL][mysql] and [PostgreSQL][pg] query strings from untrusted
|
14 | inputs by escaping dynamic values based on the context in which they
|
15 | appear.
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | * [Installation](#installation)
|
22 | * [Supported Databases](#supported)
|
23 | * [Usage By Example](#usage)
|
24 | * [`mysql` returns a *SqlFragment*](#sql-returns-sqlfragment)
|
25 | * [No excess quotes](#minimal-quotes)
|
26 | * [Escaped backticks delimit SQL identifiers](#escaped-backticks)
|
27 | * [Escape Sequences are Raw](#raw-escapes)
|
28 | * [API](#API)
|
29 | * [mysql(options)](#mysql-options)
|
30 | * [pgsql(options)](#pg-options)
|
31 | * [mysql\`...\`](#mysql-as-tag)
|
32 | * [pg\`...\`](#pg-as-tag)
|
33 | * [SqlFragment](#class-SqlFragment)
|
34 | * [SqlId](#class-SqlId)
|
35 |
|
36 |
|
37 |
|
38 | ## Installation <a name="installation"></a>
|
39 |
|
40 | ```bash
|
41 | $ npm install safesql
|
42 | ```
|
43 |
|
44 | ## Supported Databases <a name="supported"></a>
|
45 |
|
46 | **MySQL** via
|
47 |
|
48 | ```js
|
49 | const { mysql } = require('safesql');
|
50 | ```
|
51 |
|
52 | **PostgreSQL** via
|
53 |
|
54 | ```js
|
55 | const { pg } = require('safesql');
|
56 | ```
|
57 |
|
58 |
|
59 | ## Usage By Example <a name="usage"></a>
|
60 |
|
61 |
|
62 |
|
63 | This mirrors a testcase in ./test/example-test.js so if you modify this,
|
64 | be sure to reflect changes there.
|
65 |
|
66 | -->
|
67 |
|
68 | ```js
|
69 | const { mysql, SqlId } = require('safesql');
|
70 |
|
71 | const table = 'table';
|
72 | const ids = [ 'x', 'y', 'z' ];
|
73 | const str = 'foo\'"bar';
|
74 |
|
75 | const query = mysql`SELECT * FROM \`${ table }\` WHERE id IN (${ ids }) AND s=${ str }`;
|
76 |
|
77 | console.log(query);
|
78 | // SELECT * FROM `table` WHERE id IN ('x', 'y', 'z') AND s='foo''"bar'
|
79 | ```
|
80 |
|
81 | `mysql` functions as a template tag.
|
82 |
|
83 | Commas separate elements of arrays in the output.
|
84 |
|
85 | `mysql` treats a `${...}` between backticks (<tt>\\\`</tt>) as a SQL identifier.
|
86 |
|
87 | A `${...}` outside any quotes will be escaped and wrapped in appropriate quotes if necessary.
|
88 |
|
89 | ----
|
90 |
|
91 | PostgreSQL differs from MySQL in important ways. Use `pg` for Postgres.
|
92 |
|
93 | ```js
|
94 | const { pg, SqlId } = require('safesql');
|
95 |
|
96 | const table = 'table';
|
97 | const ids = [ 'x', 'y', 'z' ];
|
98 | const str = 'foo\'"bar';
|
99 |
|
100 | const query = pg`SELECT * FROM "${ table }" WHERE id IN (${ ids }) AND s=${ str }`;
|
101 |
|
102 | console.log(query);
|
103 | // SELECT * FROM "table" WHERE id IN ('x', 'y', 'z') AND s=e'foo''\"bar'
|
104 | ```
|
105 |
|
106 | ----
|
107 |
|
108 | You can pass in an object to relate columns to values as in a `SET` clause above.
|
109 |
|
110 | The output of <tt>mysql\`...\`</tt> has type *SqlFragment* so the
|
111 | `NOW()` function call is not re-escaped when used in `${data}`.
|
112 |
|
113 | ```js
|
114 | const { mysql } = require('safesql');
|
115 |
|
116 | const column = 'users';
|
117 | const userId = 1;
|
118 | const data = {
|
119 | email: 'foobar@example.com',
|
120 | modified: mysql`NOW()`
|
121 | };
|
122 | const query = mysql`UPDATE \`${column}\` SET ${data} WHERE \`id\` = ${userId}`;
|
123 |
|
124 | console.log(query);
|
125 | // UPDATE `users` SET `email` = 'foobar@example.com', `modified` = NOW() WHERE `id` = 1
|
126 | ```
|
127 |
|
128 | ### `mysql` returns a *SqlFragment* <a name="sql-returns-sqlfragment"></a>
|
129 |
|
130 | Since `mysql` returns a *SqlFragment* you can chain uses:
|
131 |
|
132 | ```js
|
133 | const { mysql } = require('safesql');
|
134 |
|
135 | const data = { a: 1 };
|
136 | const whereClause = mysql`WHERE ${data}`;
|
137 | console.log(mysql`SELECT * FROM TABLE ${whereClause}`);
|
138 | // SELECT * FROM TABLE WHERE `a` = 1
|
139 | ```
|
140 |
|
141 | ### No excess quotes <a name="minimal-quotes"></a>
|
142 |
|
143 | An interpolation in a quoted string will not insert excess quotes:
|
144 |
|
145 | ```js
|
146 | const { mysql } = require('safesql')
|
147 |
|
148 | console.log(mysql`SELECT '${ 'foo' }' `)
|
149 | // SELECT 'foo'
|
150 | console.log(mysql`SELECT ${ 'foo' } `)
|
151 | // SELECT 'foo'
|
152 | ```
|
153 |
|
154 | ### Escaped backticks delimit SQL identifiers <a name="escaped-backticks"></a>
|
155 |
|
156 | Backticks end a template tag, so you need to escape backticks.
|
157 |
|
158 | ```js
|
159 | const { mysql } = require('safesql')
|
160 |
|
161 | console.log(mysql`SELECT \`${ 'id' }\` FROM \`TABLE\``)
|
162 | // SELECT `id` FROM `TABLE`
|
163 | ```
|
164 |
|
165 | ### Escape Sequences are Raw <a name="raw-escapes"></a>
|
166 |
|
167 | Other escape sequences are raw.
|
168 |
|
169 | ```js
|
170 | const { mysql } = require('safesql')
|
171 |
|
172 | console.log(mysql`SELECT "\n"`)
|
173 | // SELECT "\n"
|
174 | ```
|
175 |
|
176 | ## API <a name="API"></a>
|
177 |
|
178 | Assuming
|
179 |
|
180 | ```js
|
181 | const { mysql, pg, SqlFragment, SqlId } = require('safesql')
|
182 | ```
|
183 |
|
184 | ### mysql(options) <a name="mysql-options"></a>
|
185 | ### pgsql(options) <a name="pg-options"></a>
|
186 |
|
187 | When called with an options bundle instead of as a template tag,
|
188 | `mysql` and `pg` return a template tag that uses those options.
|
189 |
|
190 | The options object can contain any of
|
191 | `{ stringifyObjects, timeZone, forbidQualified }` which have the
|
192 | same meaning as when used with *[sqlstring][]*.
|
193 |
|
194 | ```js
|
195 | const timeZone = 'GMT'
|
196 | const date = new Date(Date.UTC(2000, 0, 1))
|
197 |
|
198 | console.log(mysql({ timeZone })`SELECT ${date}`)
|
199 | // SELECT '2000-01-01 00:00:00.000'
|
200 | ```
|
201 |
|
202 | ### mysql\`...\` <a name="mysql-as-tag"></a>
|
203 |
|
204 | When used as a template tag, chooses an appropriate escaping
|
205 | convention for each `${...}` based on the context in which it appears.
|
206 |
|
207 | `mysql` handles `${...}` inside quoted strings as if the template
|
208 | matched the following grammar:
|
209 |
|
210 | [![Railroad Diagram][mysql-railroad-raw]][mysql-railroad]
|
211 |
|
212 | ### pg\`...\` <a name="pg-as-tag"></a>
|
213 |
|
214 | When used as a template tag, chooses an appropriate escaping
|
215 | convention for each `${...}` based on the context in which it appears.
|
216 |
|
217 | `pg` handles `${...}` inside quoted strings as if the template
|
218 | matched the following grammar:
|
219 |
|
220 | [![Railroad Diagram][pg-railroad-raw]][pg-railroad]
|
221 |
|
222 | ### SqlFragment <a name="class-SqlFragment"></a>
|
223 |
|
224 | *SqlFragment* is a [Mintable][] class that represents fragments of SQL
|
225 | that are safe to send to a database.
|
226 |
|
227 | See [minting][] for example on how to create instances, and why this is a
|
228 | tad more involved than just using `new`.
|
229 |
|
230 | ### SqlId <a name="class-SqlId"></a>
|
231 |
|
232 | *SqlId* is a [Mintable][] class that represents a SQL identifier.
|
233 |
|
234 | See [minting][] for example on how to create instances, and why this is a
|
235 | tad more involved than just using `new`.
|
236 |
|
237 | A `SqlId`'s content must be the raw text of a SQL identifier and
|
238 | creators should not rely on case folding by the database client.
|
239 |
|
240 |
|
241 | [mysql]: https://www.npmjs.com/package/mysql
|
242 | [pg]: https://www.npmjs.com/package/pg
|
243 | [sqlstring]: https://www.npmjs.com/package/sqlstring
|
244 | [Mintable]: https://www.npmjs.com/package/node-sec-patterns
|
245 | [minting]: https://www.npmjs.com/package/node-sec-patterns#creating-mintable-values
|
246 |
|
247 | [mysql-railroad]: docs/mysql-railroad.svg
|
248 | [mysql-railroad-raw]: https://rawgit.com/mikesamuel/safesql/master/docs/mysql-railroad.svg
|
249 | [pg-railroad]: docs/pg-railroad.svg
|
250 | [pg-railroad-raw]: https://rawgit.com/mikesamuel/safesql/master/docs/pg-railroad.svg
|