UNPKG

sqliterally

Version:
244 lines (199 loc) 5.6 kB
'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;