@dossierhq/database-adapter
Version:
A library for adapting Dossier to a database, such as SQLite or PostgreSQL.
135 lines • 4.95 kB
JavaScript
/// <reference types="./SqlQueryBuilder.d.ts" />
export const DEFAULT = Symbol('DEFAULT');
const ValueReferenceSymbol = Symbol('ValueReference');
const RawSqlSymbol = Symbol('RawSql');
function createSqlQuery(config) {
const query = { text: '', values: [] };
const sql = (strings, ...args) => {
for (let i = 0; i < strings.length; i++) {
if (i > 0) {
addValueToQuery(config, query, args[i - 1]);
}
addTextToQuery(query, strings[i], i === 0);
}
};
const addValue = (value) => addValueReference(query, value);
const removeTrailingWhere = () => {
if (query.text.endsWith('WHERE')) {
query.text = query.text.slice(0, query.text.length - 'WHERE'.length).trimEnd();
}
};
return { sql, query, addValue, removeTrailingWhere };
}
function createRawSql(sql) {
return { marker: RawSqlSymbol, sql };
}
function addValueReference(query, value) {
query.values.push(value);
return { marker: ValueReferenceSymbol, index: query.values.length };
}
function addValueToQuery(config, query, value) {
if (value === DEFAULT) {
query.text += 'DEFAULT';
}
else if (typeof value === 'object' &&
value &&
'marker' in value &&
value.marker === ValueReferenceSymbol) {
query.text += `${config.indexPrefix}${value.index}`;
}
else if (typeof value === 'object' &&
value &&
'marker' in value &&
value.marker === RawSqlSymbol) {
query.text += value.sql;
}
else {
query.values.push(value);
query.text += `${config.indexPrefix}${query.values.length}`; // 1-based index
}
}
function addTextToQuery(query, text, addSeparator) {
let existingText = query.text;
let textToAdd = text;
if (existingText.endsWith('WHERE') && textToAdd.startsWith('AND')) {
textToAdd = textToAdd.slice('AND'.length).trimStart();
}
else if (existingText.endsWith('WHERE') && textToAdd.startsWith('ORDER')) {
existingText = existingText.slice(0, existingText.length - 'WHERE'.length).trimEnd();
}
else if (existingText.endsWith('VALUES') && textToAdd.startsWith(',')) {
textToAdd = textToAdd.slice(','.length);
}
let separator = '';
if (existingText && addSeparator) {
//TODO simplify this
const currentEndsWithPunctuation = endsWithPunctuation(existingText);
const newStartsWithBracket = startsWithPunctuation(textToAdd);
const currentEndsWithKeyword = endsWithKeyword(existingText);
const newStartsWithKeyword = startsWithKeyword(textToAdd);
const currentEndsWithOperator = endsWithOperator(existingText);
const newStartsWithOperator = startsWithOperator(textToAdd);
if (currentEndsWithPunctuation || newStartsWithBracket) {
separator = '';
}
else if (currentEndsWithOperator || newStartsWithOperator) {
separator = ' ';
}
else if (currentEndsWithKeyword && !newStartsWithKeyword) {
separator = ' ';
}
else if (!currentEndsWithKeyword && !newStartsWithKeyword) {
separator = ' ';
}
else if (!currentEndsWithKeyword && newStartsWithKeyword) {
separator = ' ';
}
else if (currentEndsWithKeyword && newStartsWithKeyword) {
separator = ' ';
}
}
query.text = existingText + separator + textToAdd;
}
function startsWithPunctuation(query) {
const firstChar = query[0];
return firstChar === ')' || firstChar === ',' || firstChar === '.' || firstChar === ' ';
}
function endsWithPunctuation(query) {
const lastChar = query[query.length - 1];
return lastChar === '(' || lastChar === ',' || lastChar === '.' || lastChar === ' ';
}
function startsWithKeyword(query) {
return !!/^[A-Z]+\w/.exec(query);
}
function endsWithKeyword(query) {
return !!/\w[A-Z]+$/.exec(query);
}
function startsWithOperator(query) {
return !!/^[=!<>]+/.exec(query);
}
function endsWithOperator(query) {
return !!/[=!<>]+$/.exec(query);
}
export function createPostgresSqlQuery() {
return createSqlQuery({ indexPrefix: '$' });
}
export function buildPostgresSqlQuery(callback) {
const { query, ...builder } = createPostgresSqlQuery();
callback(builder);
return query;
}
export function createSqliteSqlQuery() {
const sqlBuilder = createSqlQuery({ indexPrefix: '?' });
const addValueList = (list) => {
const values = list.map((it) => sqlBuilder.addValue(it));
return createRawSql('(' + values.map((it) => `?${it.index}`).join(', ') + ')');
};
const sqliteBuilder = { ...sqlBuilder, addValueList };
return sqliteBuilder;
}
export function buildSqliteSqlQuery(callback) {
const { query, ...builder } = createSqliteSqlQuery();
callback(builder);
return query;
}
//# sourceMappingURL=SqlQueryBuilder.js.map