squid
Version:
Provides SQL tagged template strings and a schema definition function.
113 lines (112 loc) • 4.55 kB
JavaScript
;
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;