UNPKG

squid

Version:

Provides SQL tagged template strings and a schema definition function.

113 lines (112 loc) 4.55 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const debug_1 = __importDefault(require("debug")); const utils_1 = require("../utils"); const builder_1 = require("./builder"); const debugQuery = debug_1.default("squid:query"); /** * SQL template string tag. Returns a query object: `{ text: string, values: any[] }`. * Template expressions are automatically SQL-injection-proofed unless wrapped in `sql.raw()`. * * @example * const { rows } = await database.query(sql`SELECT name, email FROM users WHERE id=${userID}`) * @example * const { rows } = await database.query<{ name: string, email: string }>(sql` * SELECT name, email FROM users WHERE id=${userID} * `) */ function sql(texts, ...values) { const sqlBuilder = sqlBuild(texts, ...values); const query = builder_1.buildSql(sqlBuilder); debugQuery(query); return query; } exports.sql = sql; /** * Internal template string tag that builds a SqlBuilder. */ function sqlBuild(texts, ...values) { // General idea: // sql`foo ${1} bar ${sql.raw(2)}` // => // sql(['foo ', ' bar ', ''], [1, rawSqlBuilder(2)]) // => // [rawSqlBuilder('foo '), rawSqlBuilder(' bar '), rawSqlBuilder('')] // [paramSqlBuilder(1), rawSqlBuilder(2)] // => // [ // rawSqlBuilder('foo '), // paramSqlBuilder(1), // rawSqlBuilder(' bar '), // rawSqlBuilder(2), // rawSqlBuilder(''), // ] const textSqlBuilders = texts.map(builder_1.rawSqlBuilder); const valueSqlBuilders = values.map(builder_1.toSqlBuilder); const sqlBuilders = utils_1.mergeLists(textSqlBuilders, valueSqlBuilders); return builder_1.joinSql(sqlBuilders); } /** * Wrap an SQL template expression value in `sql.raw()` to inject the raw value into the query. * Attention: Can easily lead to SQL injections! Use with caution and only if necessary. */ function rawExpression(rawValue) { return builder_1.rawSqlBuilder(rawValue); } exports.raw = rawExpression; function safeExpression(value) { return builder_1.paramSqlBuilder(value); } exports.safe = safeExpression; sql.raw = rawExpression; sql.safe = safeExpression; /** * Convenience function to keep WHERE clauses concise. Takes an object: * Keys are supposed to be a column name, values the values that the record must have set. * * @example * const { rows } = await database.query(sql`SELECT * FROM users WHERE ${spreadAnd({ name: "John", email: "john@example.com" })}`) */ function spreadAnd(record) { const columnValues = Object.entries(utils_1.filterUndefined(record)); const andChainBuilders = columnValues.map(([columnName, columnValue]) => { const identifier = utils_1.escapeIdentifier(columnName); return sqlBuild `${sql.raw(identifier)} = ${columnValue}`; }); const andChain = builder_1.joinSql(andChainBuilders, " AND "); return sqlBuild `(${andChain})`; } exports.spreadAnd = spreadAnd; /** * Convenience function to keep INSERT statements concise. * @example * await database.query(sql`INSERT INTO users ${spreadInsert({ name: "John", email: "john@example.com" })}`) */ function spreadInsert(...records) { const columnNames = utils_1.extractKeys(records.map(utils_1.filterUndefined)); const identifiers = columnNames.map(utils_1.escapeIdentifier).join(", "); const insertChainBuilders = records.map(record => { const recordColumns = columnNames.map(columnName => builder_1.toSqlBuilder(record[columnName])); return sqlBuild `(${builder_1.joinSql(recordColumns, ", ")})`; }); const insertChain = builder_1.joinSql(insertChainBuilders, ", "); return sqlBuild `(${sql.raw(identifiers)}) VALUES ${insertChain}`; } exports.spreadInsert = spreadInsert; /** * Convenience function to keep UPDATE statements concise. Takes an object: * @example * await database.query(sql`UPDATE users SET ${spreadUpdate({ name: "John", email: "john@example.com" })} WHERE id = 1`) */ function spreadUpdate(record) { const updateValues = Object.entries(utils_1.filterUndefined(record)); const updateChainBuilders = updateValues.map(([columnName, columnValue]) => { const identifier = utils_1.escapeIdentifier(columnName); return sqlBuild `${sql.raw(identifier)} = ${columnValue}`; }); return builder_1.joinSql(updateChainBuilders, ", "); } exports.spreadUpdate = spreadUpdate;