|
| 1 | +Sqlate.js |
| 2 | +========= |
| 3 | +[![NPM version][npm-badge]](https://www.npmjs.com/package/sqlate) |
| 4 | + |
| 5 | +Sqlate.js is a tiny [tagged template string][template-string] function library for JavaScript that **permits you to write SQL in a template string and get a `Sql` instance out with parameter placeholders**. You can then pass the SQL and parameters safely to [Mapbox's SQLite3][node-sqlite3], [Brian Carlson's PostgreSQL][node-postgresql] or other Node.js database libraries. |
| 6 | + |
| 7 | +[npm-badge]: https://img.shields.io/npm/v/sqlate.svg |
| 8 | +[template-string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals |
| 9 | +[node-sqlite3]: https://github.com/mapbox/node-sqlite3 |
| 10 | +[node-postgresql]: https://node-postgres.com |
| 11 | + |
| 12 | + |
| 13 | +Installing |
| 14 | +---------- |
| 15 | +```sh |
| 16 | +npm install sqlate |
| 17 | +``` |
| 18 | + |
| 19 | +Sqlate.js follows [semantic versioning](http://semver.org), so feel free to depend on its major version with something like `>= 1.0.0 < 2` (a.k.a `^1.0.0`). |
| 20 | + |
| 21 | + |
| 22 | +Using |
| 23 | +----- |
| 24 | +```javascript |
| 25 | +var Sql = require("sqlate").Sql |
| 26 | +var sql = require("sqlate") |
| 27 | + |
| 28 | +var ids = [1, 2, 3] |
| 29 | +var age = 42 |
| 30 | +var query = sql`SELECT * FROM models WHERE id IN (${ids}) AND age > ${age}` |
| 31 | +query instanceof Sql // => true |
| 32 | +``` |
| 33 | + |
| 34 | +The `query` variable will be set to an instance of `Sql`. This way you can differentiate safe SQL from plain strings. |
| 35 | + |
| 36 | +When you stringify the above query via `String(query)`, you'll get the SQL with values transformed to placeholders: |
| 37 | + |
| 38 | +```sql |
| 39 | +SELECT * FROM models WHERE id IN (?, ?, ?) AND age > ? |
| 40 | +``` |
| 41 | + |
| 42 | +To get the values, get the `parameters` property from the `query`. |
| 43 | + |
| 44 | +Regular values get interpolated as placeholders (question marks) and arrays get interpolated as a comma-separated list of question marks. This allows using JavaScript arrays both for SQL tuples and [(PostgreSQL) arrays](https://www.postgresql.org/docs/9.6/functions-array.html): |
| 45 | + |
| 46 | +```javascript |
| 47 | +var nameAndAge = ["John", 42] |
| 48 | +var query = sql`SELECT * FROM models WHERE (name, age) = (${ids})` |
| 49 | + |
| 50 | +var tags = ["convertible", "v8"] |
| 51 | +var query = sql`SELECT * FROM cars WHERE tags @> ARRAY[${ids}]` |
| 52 | +``` |
| 53 | + |
| 54 | +When you need to get nested tuples, like when creating an insert statement, use `sql.tuple` on each array element. See [below](#creating-insert-statements) for an example. |
| 55 | + |
| 56 | +### Composing SQL |
| 57 | +You can freely compose different pieces of SQL safely by passing one `Sql` instance to another: |
| 58 | + |
| 59 | +```javascript |
| 60 | +var id = 42 |
| 61 | +var name = "John" |
| 62 | +var idClause = sql`id = ${id}` |
| 63 | +var nameClause = sql`name = ${name}` |
| 64 | +db.query(sql`SELECT * FROM models WHERE ${idClause} AND ${nameClause}`) |
| 65 | +``` |
| 66 | + |
| 67 | +This will generate the following query: |
| 68 | + |
| 69 | +```sql |
| 70 | +SELECT * FROM models WHERE id = ? AND name ? |
| 71 | +``` |
| 72 | + |
| 73 | +### Creating Insert Statements |
| 74 | +Sqlate.js also has helpers to quote table and column names. These come in handy for insert statements: |
| 75 | + |
| 76 | +```javascript |
| 77 | +var table = sql.table("models") |
| 78 | +var columns = ["name", "age"].map(sql.column) |
| 79 | +var values = [["John", 42], ["Mike", 13]].map(sql.tuple) |
| 80 | +db.query(sql`INSERT INTO ${table} ${columns} VALUES ${values}`) |
| 81 | +``` |
| 82 | + |
| 83 | +This will generate the following query: |
| 84 | + |
| 85 | +```sql |
| 86 | +INSERT INTO "models" ("name", "age") VALUES (?, ?), (?, ?) |
| 87 | +``` |
| 88 | + |
| 89 | +The two helpers, `sql.table` and `sql.column`, have no differences other than their names. While it's safe to pass untrusted data as values, **watch out for using untrusted data as table and column names**. Sqlate.js quotes them as per the SQL 1999 standard (using two double-quotes `""` for embedded quotes) if you use `sql.column`, but just to be safe, use a whitelist. |
| 90 | + |
| 91 | +### Using with Mapbox's SQLite3 |
| 92 | +If you'd like to use Sqlate.js with [Mapbox's SQLite3][node-sqlite3] library, here's an example of how you'd do so: |
| 93 | + |
| 94 | +```javascript |
| 95 | +var Sqlite3 = require("sqlite3") |
| 96 | +var db = new Sqlite3.Database(":memory:") |
| 97 | +db.serialize() |
| 98 | + |
| 99 | +var ids = [1, 2, 3] |
| 100 | +var age = 42 |
| 101 | +var query = sql`SELECT * FROM models WHERE id IN (${ids}) AND age > ${age}` |
| 102 | +db.all(String(query), query.parameters) |
| 103 | +``` |
| 104 | + |
| 105 | +For a complete [Table Data Gateway][table-data-gateway] library for SQLite that works with Sqlate.js, see [Heaven.js on SQLite](https://github.com/moll/node-heaven-sqlite). |
| 106 | + |
| 107 | +[table-data-gateway]: https://en.wikipedia.org/wiki/Table_data_gateway |
| 108 | + |
| 109 | +### Using with Brian Carlson's PostgreSQL |
| 110 | +If you'd like to use Sqlate.js with [Brian Carlson's PostgreSQL][node-postgresql] library, here's an example of how you'd do so: |
| 111 | + |
| 112 | +```javascript |
| 113 | +var PgClient = require("pg") |
| 114 | +var db = new PgClient({host: "localhost", database: "models"}) |
| 115 | +db.connect() |
| 116 | + |
| 117 | +var ids = [1, 2, 3] |
| 118 | +var age = 42 |
| 119 | +var query = sql`SELECT * FROM models WHERE id IN (${ids}) AND age > ${age}` |
| 120 | +db.query(String(query), query.parameters) |
| 121 | +``` |
| 122 | + |
| 123 | +Because Sqlate.js's `Sql` object also has property aliases for the PostgreSQL's library's [query config object](https://node-postgres.com/features/queries), you can also pass the query directly: |
| 124 | + |
| 125 | +```javascript |
| 126 | +var ids = [1, 2, 3] |
| 127 | +var age = 42 |
| 128 | +db.query(sql`SELECT * FROM models WHERE id IN (${ids}) AND age > ${age}`) |
| 129 | +``` |
| 130 | + |
| 131 | +### Query Helpers |
| 132 | +Rather than create a query and unpack it to SQL and parameters at call sites manually, I recommend you create a two helper functions — `search` and `read` — for accessing your database: |
| 133 | + |
| 134 | +```javascript |
| 135 | +var Sql = require("sqlate").Sql |
| 136 | +var db = connect() // Using your favorite database library here. |
| 137 | + |
| 138 | +// Returns an promise of an array of rows. |
| 139 | +function search(query) { |
| 140 | + if (!(query instanceof Sql)) throw new TypeError("Invalid Query: " + query) |
| 141 | + return db.query(String(query), query.parameters) |
| 142 | +} |
| 143 | + |
| 144 | +// Returns a promise of a single row. |
| 145 | +function read(query) { |
| 146 | + return search(query).then(function(rows) { return rows[0] }) |
| 147 | +} |
| 148 | +``` |
| 149 | + |
| 150 | +This way you have a [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) interface that you can safely pass SQL to without worrying you'll accidentally cause an SQL injection: |
| 151 | + |
| 152 | +```javascript |
| 153 | +var sql = require("sqlate") |
| 154 | +var id = 42 |
| 155 | +read(sql`SELECT * FROM models WHERE id = ${id} LIMIT 1`) |
| 156 | +``` |
| 157 | + |
| 158 | + |
| 159 | +License |
| 160 | +------- |
| 161 | +Sqlate.js is released under a *Lesser GNU Affero General Public License*, which in summary means: |
| 162 | + |
| 163 | +- You **can** use this program for **no cost**. |
| 164 | +- You **can** use this program for **both personal and commercial reasons**. |
| 165 | +- You **do not have to share your own program's code** which uses this program. |
| 166 | +- You **have to share modifications** (e.g. bug-fixes) you've made to this program. |
| 167 | + |
| 168 | +For more convoluted language, see the `LICENSE` file. |
| 169 | + |
| 170 | + |
| 171 | +About |
| 172 | +----- |
| 173 | +**[Andri Möll][moll]** typed this and the code. |
| 174 | +[Monday Calendar][monday] supported the engineering work. |
| 175 | + |
| 176 | +If you find Sqlate.js needs improving, please don't hesitate to type to me now at [[email protected]][email] or [create an issue online ][issues]. |
| 177 | + |
| 178 | + |
| 179 | +[issues]: https://github.com/moll/js-sqlate/issues |
| 180 | +[moll]: https://m811.com |
| 181 | +[monday]: https://mondayapp.com |
0 commit comments