Skip to content

Commit 2ba74f9

Browse files
committed
Sqlate quickly.
0 parents  commit 2ba74f9

File tree

11 files changed

+495
-0
lines changed

11 files changed

+495
-0
lines changed

.editorconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
root = true
2+
3+
[Makefile]
4+
indent_style = tab
5+
6+
[*.js]
7+
indent_style = tab
8+
9+
[*.json]
10+
indent_style = tab

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/node_modules/
2+
/*.tgz

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/*.tgz

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
## 1.0.0 (Apr 27, 2019)
2+
- Things could sqlate quickly.

LICENSE

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Sqlate.js
2+
Copyright (C) 2019– Andri Möll <[email protected]>
3+
4+
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.
5+
6+
Additional permission under the GNU Affero GPL version 3 section 7:
7+
If you modify this Program, or any covered work, by linking or combining it with other code, such other code is not for that reason alone subject to any of the requirements of the GNU Affero GPL version 3.
8+
9+
In summary:
10+
- You can use this program for no cost.
11+
- You can use this program for both personal and commercial reasons.
12+
- You do not have to share your own program's code which uses this program.
13+
- You have to share modifications (e.g bug-fixes) you've made to this program.
14+
15+
For the full copy of the GNU Affero General Public License see:
16+
http://www.gnu.org/licenses.

Makefile

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
NODE = node
2+
NODE_OPTS = --use-strict
3+
MOCHA = ./node_modules/.bin/_mocha
4+
TEST = test/**/*_test.js
5+
6+
love:
7+
@echo "Feel like makin' love."
8+
9+
test:
10+
@$(NODE) $(NODE_OPTS) $(MOCHA) -R dot $(TEST)
11+
12+
spec:
13+
@$(NODE) $(NODE_OPTS) $(MOCHA) -R spec $(TEST)
14+
15+
autotest:
16+
@$(NODE) $(NODE_OPTS) $(MOCHA) -R dot --watch $(TEST)
17+
18+
autospec:
19+
@$(NODE) $(NODE_OPTS) $(MOCHA) -R spec --watch $(TEST)
20+
21+
pack:
22+
@file=$$(npm pack); echo "$$file"; tar tf "$$file"
23+
24+
publish:
25+
npm publish
26+
27+
tag:
28+
git tag "v$$($(NODE) -e 'console.log(require("./package").version)')"
29+
30+
clean:
31+
-$(RM) *.tgz
32+
npm prune --production
33+
34+
.PHONY: love
35+
.PHONY: test spec autotest autospec
36+
.PHONY: pack publish tag
37+
.PHONY: clean

README.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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+
[email]: mailto:[email protected]
179+
[issues]: https://github.com/moll/js-sqlate/issues
180+
[moll]: https://m811.com
181+
[monday]: https://mondayapp.com

package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "sqlate",
3+
"version": "1.0.0",
4+
"description": "Tiny tagged template string function for safe SQL. Supports SQlite3, PostgreSQL and others.",
5+
"keywords": ["sql", "sqlite", "sqlite3", "postgresql"],
6+
"homepage": "https://github.com/moll/js-sqlate",
7+
"bugs": "https://github.com/moll/js-sqlate/issues",
8+
9+
"author": {
10+
"name": "Andri Möll",
11+
"email": "[email protected]",
12+
"url": "https://m811.com"
13+
},
14+
15+
"repository": {
16+
"type": "git",
17+
"url": "git://github.com/moll/js-sqlate.git"
18+
},
19+
20+
"licenses": [{
21+
"type": "LAGPL",
22+
"url": "https://github.com/moll/js-sqlate/blob/master/LICENSE"
23+
}],
24+
25+
"main": "sqlate.js",
26+
"scripts": {"test": "make test"},
27+
28+
"devDependencies": {
29+
"mocha": ">= 2 < 4",
30+
"must": ">= 0.13.0 < 0.14"
31+
}
32+
}

sqlate.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
var isArray = Array.isArray
2+
var slice = Function.call.bind(Array.prototype.slice)
3+
var flatten = Function.apply.bind(Array.prototype.concat, Array.prototype)
4+
var EMPTY_ARR = Array.prototype
5+
var TYPE_ERR = "SQL should be a string: "
6+
exports = module.exports = template
7+
exports.Sql = Sql
8+
exports.new = raw
9+
exports.table = quoteSql
10+
exports.column = quoteSql
11+
exports.tuple = tupleSql
12+
13+
function Sql(sql, params) {
14+
if (typeof sql != "string") throw new TypeError(TYPE_ERR + sql)
15+
16+
this.sql = sql
17+
this.parameters = params || EMPTY_ARR
18+
}
19+
20+
Sql.prototype.toString = function() {
21+
return this.sql
22+
}
23+
24+
// The "text" and "values" aliases are for Brian Carlson's PostgreSQL library
25+
// and its query-object style: https://node-postgres.com/features/queries
26+
Object.defineProperty(Sql.prototype, "text", {
27+
get: function() { return this.sql }, configurable: true
28+
})
29+
30+
Object.defineProperty(Sql.prototype, "values", {
31+
get: function() { return this.parameters }, configurable: true
32+
})
33+
34+
function template(sqls) {
35+
var params = slice(arguments, 1)
36+
37+
var sql = sqls.reduce(function(left, right, i) {
38+
return left + toPlaceholder(params[i - 1]) + right
39+
})
40+
41+
return new Sql(sql, flatten(params.map(toParameter)))
42+
}
43+
44+
function toPlaceholder(value) {
45+
// Binding a single level for now. Could be done recursively in the future.
46+
if (isArray(value)) return value.map(bind).join(", ")
47+
return bind(value)
48+
49+
function bind(value) { return value instanceof Sql ? value.sql : "?" }
50+
}
51+
52+
function toParameter(value) {
53+
return isArray(value) ? flatten(value.map(toValues)) : toValues(value)
54+
}
55+
56+
function tupleSql(tuple) {
57+
if (!isArray(tuple)) throw new TypeError("Tuple must be an array: " + tuple)
58+
return new Sql("(" + toPlaceholder(tuple) + ")", flatten(tuple.map(toValues)))
59+
}
60+
61+
function raw(sql, params) { return new Sql(sql, params) }
62+
function quoteSql(name) { return new Sql(quote(name)) }
63+
function quote(name) { return '"' + name.replace(/"/g, '""') + '"'}
64+
function toValues(val) { return val instanceof Sql ? val.parameters : [val] }

test/mocha.opts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--recursive
2+
--check-leaks
3+
--require must/register

0 commit comments

Comments
 (0)