sqliterally
Version:
Lightweight SQL query builder
244 lines (199 loc) • 5.6 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
const mergeAdjecent = (arr, i, before = 0, after = 0) =>
arr.splice(
i - before,
before + 1 + after,
[...arr.slice(i - before, i), arr[i], ...arr.slice(i + 1, i + 1 + after)].join('')
);
const copy = o =>
Object.keys(o).reduce((newObject, key) => ((newObject[key] = o[key].slice()), newObject), {});
const ADDTOCLAUSE = Symbol('addToClause');
const STRINGIFY = Symbol('stringify');
const clauseOrder = [
`select`,
`insert`,
`delete`,
`values`,
`update`,
`set`,
`from`,
`join`,
`where`,
`onDuplicate`,
`groupBy`,
`having`,
`orderBy`,
`limit`,
`returning`,
`lock`
];
const startingClauses = {
select: [],
insert: [],
onDuplicate: [],
values: [],
update: [],
set: [],
from: [],
join: [],
where: [],
groupBy: [],
having: [],
orderBy: [],
limit: [],
delete: [],
returning: [],
lock: []
};
const clauseStrings = {
select: 'SELECT ',
insert: 'INSERT INTO ',
onDuplicate: 'ON DUPLICATE KEY UPDATE',
values: 'VALUES ',
update: 'UPDATE ',
set: 'SET ',
from: 'FROM ',
join: '',
where: 'WHERE ',
groupBy: 'GROUP BY ',
having: 'HAVING ',
orderBy: 'ORDER BY ',
limit: 'LIMIT ',
delete: 'DELETE ',
returning: 'RETURNING ',
lock: ''
};
class Literal {
constructor(pieces = [''], values = [], delimiter = '') {
this.pieces = [...pieces];
this.values = [...values];
this.delimiter = delimiter;
for (let i = 0, j = 1, k = 0; i < values.length; i++, j++, k++) {
let val = values[i];
if (val && val[ADDTOCLAUSE]) val = val.build(' ');
if (val instanceof Literal) {
this.values.splice(k, 1);
if (val.pieces.length === 0) {
mergeAdjecent(this.pieces, j, 1);
continue;
}
this.pieces.splice(j, 0, ...val.pieces);
mergeAdjecent(this.pieces, j, 1, 0);
mergeAdjecent(this.pieces, j + val.pieces.length - 2, 0, 1);
this.values.splice(k, 0, ...val.values);
j += val.pieces.length;
k += val.values.length;
i += val.values.length;
}
}
}
append(literal, delimiter = '') {
const clone = this.clone();
if (typeof literal === 'string') {
clone.pieces[clone.pieces.length - 1] += `${delimiter}${literal}`;
return clone;
}
clone.pieces[clone.pieces.length - 1] += `${delimiter || literal.delimiter}${literal.pieces[0]}`;
const [_, ...pieces] = literal.pieces;
clone.pieces.push(...pieces);
clone.values.push(...literal.values);
return clone;
}
prefix(string = '') {
const clone = this.clone();
clone.pieces[0] = `${string}${this.pieces[0]}`;
return clone;
}
suffix(string = '') {
const clone = this.clone();
clone.pieces[clone.pieces.length] += string;
return clone;
}
clone() {
return new Literal(this.pieces, this.values, this.delimiter);
}
[STRINGIFY](type = 'pg') {
return this.pieces.reduce((acc, part, i) => acc + (type == 'pg' ? '$' + i : '?') + part);
}
get text() {
return this[STRINGIFY]('pg');
}
get sql() {
return this[STRINGIFY]('mysql');
}
}
class Query {
constructor(clauses) {
this.clauses = copy(clauses);
}
[ADDTOCLAUSE](key, literal, override) {
const state = copy(this.clauses);
override ? (state[key] = [literal]) : state[key].push(literal);
return new Query(state);
}
build(delimiter = '\n') {
return clauseOrder
.map(key => ({key, expressions: this.clauses[key]}))
.filter(clause => clause.expressions && clause.expressions.length > 0)
.map(({expressions, key}) =>
expressions.reduce((acc, literal) => acc.append(literal)).prefix(clauseStrings[key])
)
.reduce((acc, query) => acc.append(query, delimiter));
}
select(pieces, ...values) {
return this[ADDTOCLAUSE]('select', new Literal(pieces, values, ', '));
}
update(pieces, ...values) {
return this[ADDTOCLAUSE]('update', new Literal(pieces, values), true);
}
set(pieces, ...values) {
return this[ADDTOCLAUSE]('set', new Literal(pieces, values, ', '));
}
from(pieces, ...values) {
return this[ADDTOCLAUSE]('from', new Literal(pieces, values), true);
}
join(pieces, ...values) {
return this[ADDTOCLAUSE]('join', new Literal(pieces, values, '\n').prefix('JOIN '));
}
leftJoin(pieces, ...values) {
return this[ADDTOCLAUSE]('join', new Literal(pieces, values, '\n').prefix('LEFT JOIN '));
}
where(pieces, ...values) {
return this[ADDTOCLAUSE]('where', new Literal(pieces, values, ' AND '));
}
orWhere(pieces, ...values) {
return this[ADDTOCLAUSE]('where', new Literal(pieces, values, ' OR '));
}
having(pieces, ...values) {
return this[ADDTOCLAUSE]('having', new Literal(pieces, values, ' AND '));
}
orHaving(pieces, ...values) {
return this[ADDTOCLAUSE]('having', new Literal(pieces, values, ' OR '));
}
groupBy(pieces, ...values) {
return this[ADDTOCLAUSE]('groupBy', new Literal(pieces, values, ', '));
}
orderBy(pieces, ...values) {
return this[ADDTOCLAUSE]('orderBy', new Literal(pieces, values, ', '));
}
update(pieces, ...values) {
return this[ADDTOCLAUSE]('update', new Literal(pieces, values), true);
}
limit(pieces, ...values) {
return this[ADDTOCLAUSE]('limit', new Literal(pieces, values), true);
}
returning(pieces, ...values) {
return this[ADDTOCLAUSE]('returning', new Literal(pieces, values, ', '));
}
get lockInShareMode() {
return this[ADDTOCLAUSE](`lock`, new Literal([`LOCK IN SHARE MODE`]), true);
}
get forUpdate() {
return this[ADDTOCLAUSE](`lock`, new Literal([`FOR UPDATE`]), true);
}
}
const query = new Query(startingClauses);
const sql = (pieces, ...values) => new Literal(pieces, values);
exports.query = query;
exports.sql = sql;