Skip to content

Commit

Permalink
Improved append(...) and nested statements
Browse files Browse the repository at this point in the history
This MR addresses all concerns and optimizations reised in felixfbecker#30 and felixfbecker#44

  * SQLStatement can be used as value
  * raw names can be passed via `'${'table_' + name}'` with `'` or `"` transform
  * `.append(...)` doesn't need a space at the beginning, it's handled automatically
  • Loading branch information
Andrea Giammarchi committed Mar 1, 2019
1 parent 7be1d2a commit a594bfe
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 8 deletions.
42 changes: 34 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
'use strict'

const lsp = str => (/^[\s\n\r]/.test(str) ? str : ' ' + str)
const push = (str, val, statement, spaced) => {
const { strings } = statement
str[str.length - 1] += spaced ? lsp(strings[0]) : strings[0]
str.push(...strings.slice(1))
val.push(...(statement.values || statement.bind))
}

class SQLStatement {
/**
* @param {string[]} strings
Expand All @@ -25,20 +33,19 @@ class SQLStatement {
* @returns {this}
*/
append(statement) {
const { strings } = this
if (statement instanceof SQLStatement) {
this.strings[this.strings.length - 1] += statement.strings[0]
this.strings.push.apply(this.strings, statement.strings.slice(1))
const list = this.values || this.bind
list.push.apply(list, statement.values)
push(strings, this.values || this.bind, statement, true)
} else {
this.strings[this.strings.length - 1] += statement
strings[strings.length - 1] += lsp(statement)
}
return this
}

/**
* Use a prepared statement with Sequelize.
* Makes `query` return a query with `$n` syntax instead of `?` and switches the `values` key name to `bind`
* Makes `query` return a query with `$n` syntax instead of `?`
* and switches the `values` key name to `bind`
* @param {boolean} [value=true] value If omitted, defaults to `true`
* @returns this
*/
Expand Down Expand Up @@ -79,8 +86,27 @@ Object.defineProperty(SQLStatement.prototype, 'sql', {
* @param {...any} values
* @returns {SQLStatement}
*/
function SQL(strings) {
return new SQLStatement(strings.slice(0), Array.from(arguments).slice(1))
function SQL(tpl, ...val) {
const strings = [tpl[0]]
const values = []
for (let { length } = tpl, prev = tpl[0], j = 0, i = 1; i < length; i++) {
const current = tpl[i]
const value = val[i - 1]
if (/^('|")/.test(current) && RegExp.$1 === prev.slice(-1)) {
strings[j] = [strings[j].slice(0, -1), value, current.slice(1)].join('`')
} else {
if (value instanceof SQLStatement) {
push(strings, values, value, false)
j = strings.length - 1
strings[j] += current
} else {
values.push(value)
j = strings.push(current) - 1
}
prev = strings[j]
}
}
return new SQLStatement(strings, values)
}

module.exports = SQL
Expand Down
19 changes: 19 additions & 0 deletions test/unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,23 @@ describe('SQL', () => {
assert.deepStrictEqual(statement.values, [123])
})
})

describe('Auto Escape', () => {
it('should escape the table name', () => {
assert.strictEqual(SQL`SELECT * FROM '${`table_${123}`}'`.sql, 'SELECT * FROM `table_123`')
})
it('should have no extra values', () => {
assert.strictEqual(SQL`SELECT * FROM '${`table_${123}`}'`.values.length, 0)
})
})

describe('Nested SQLStatement', () => {
it('should pollute the initial statement', () => {
const name = 'no-shenanigans'
const age = Math.random()
const sql = SQL`SELECT * FROM table WHERE ${SQL`name = ${name}`} AND age = ${age}`
assert.strictEqual(sql.sql, 'SELECT * FROM table WHERE name = ? AND age = ?')
assert.strictEqual(JSON.stringify(sql.values), JSON.stringify([name, age]))
})
})
})

0 comments on commit a594bfe

Please sign in to comment.