Files
bun-sqlite/dist/utils/sql-generator.js
T

302 lines
12 KiB
JavaScript

import sqlGenGenSearchStr from "./sql-generator-gen-search-str";
import sqlGenGenQueryStr from "./sql-generator-gen-query-str";
import { sqlField, sqlIdent, sqlInt, sqlOperator, sqlOrderStrategy, } from "./sql-safe";
/**
* # SQL Query Generator
* @description Generates a parameterized SQLite SELECT query. All values are
* returned in `values` to be bound; identifiers are validated.
*/
export default function sqlGenerator({ tableName: passedTableName, genObject, count, targetId, }) {
const tableName = sqlIdent(passedTableName, "table name");
const finalQuery = genObject?.query ? genObject.query : undefined;
const queryKeys = finalQuery ? Object.keys(finalQuery) : undefined;
const sqlSearhValues = [];
const whereValues = [];
if (genObject?.fullTextSearch) {
throw new Error("`fullTextSearch` is not supported by SQLite. Use an FTS5 table and the `MATCH` equality instead.");
}
let { str: queryString, values } = sqlGenGenQueryStr({
table_name: tableName,
append_table_names: true,
genObject,
});
sqlSearhValues.push(...values);
const sqlSearhString = queryKeys?.map((field) => {
const queryObj = finalQuery?.[field];
if (!queryObj)
return;
if (queryObj.__query) {
const subQueryGroup = queryObj.__query;
const subSearchKeys = Object.keys(subQueryGroup);
const subSearchString = subSearchKeys
.map((_field) => {
const newSubQueryObj = subQueryGroup?.[_field];
if (newSubQueryObj) {
const { str, values } = sqlGenGenSearchStr({
queryObj: newSubQueryObj,
field: newSubQueryObj.fieldName || _field,
join: genObject?.join,
table_name: tableName,
});
whereValues.push(...values);
return str;
}
})
.filter((str) => typeof str == "string");
if (!subSearchString.length)
return;
return ("(" +
subSearchString.join(` ${sqlOperator(queryObj.operator)} `) +
")");
}
const { str, values } = sqlGenGenSearchStr({
queryObj,
field: queryObj.fieldName || field,
join: genObject?.join,
table_name: tableName,
});
whereValues.push(...values);
return str;
});
const cleanedUpSearchStr = sqlSearhString?.filter((str) => typeof str == "string");
const whereConditions = [];
if (cleanedUpSearchStr?.[0]) {
const stringOperator = sqlOperator(genObject?.searchOperator);
whereConditions.push(`(${cleanedUpSearchStr.join(` ${stringOperator} `)})`);
}
if (targetId !== undefined && targetId !== null && targetId !== "") {
const id = Number(targetId);
if (!Number.isSafeInteger(id)) {
throw new Error(`Invalid targetId: ${JSON.stringify(targetId)}`);
}
whereConditions.push(`${genObject?.join ? `${tableName}.` : ""}id = ?`);
whereValues.push(id);
}
const whereString = whereConditions.length
? `WHERE ${whereConditions.join(" AND ")}`
: "";
if (whereString) {
queryString += ` ${whereString}`;
sqlSearhValues.push(...whereValues);
}
if (genObject?.group) {
const grabGroupStr = (group) => {
if (typeof group == "object") {
return group.table
? `${sqlIdent(group.table, "table name")}.${sqlIdent(group.field, "field name")}`
: sqlField(group.field);
}
return sqlField(group);
};
const group_by_txt = Array.isArray(genObject.group)
? genObject.group.map(grabGroupStr).join(",")
: grabGroupStr(genObject.group);
queryString += ` GROUP BY ${group_by_txt}`;
}
function grabOrderString(order) {
const field = genObject?.join && !String(order.field).includes(".")
? `${tableName}.${sqlIdent(order.field, "order field")}`
: sqlField(order.field, "order field");
return ` ${field} ${sqlOrderStrategy(order.strategy)}`;
}
if (genObject?.order) {
let orderSrt = ` ORDER BY`;
const orders = (Array.isArray(genObject.order) ? genObject.order : [genObject.order]).filter(Boolean);
orderSrt += orders.map(grabOrderString).join(",");
queryString += ` ${orderSrt}`;
}
/**
* Pagination is skipped for count queries so the count covers every
* matching row
*/
if (!count) {
const { limit: rawLimit, offset: rawOffset, page } = genObject || {};
/**
* `limit: 0` has always meant "no limit"
*/
const limit = rawLimit ? sqlInt(rawLimit, "limit") : undefined;
const offset = isSet(rawOffset)
? sqlInt(rawOffset, "offset")
: isSet(page) && limit !== undefined
? Math.max(sqlInt(page, "page") - 1, 0) * limit
: undefined;
if (limit !== undefined) {
queryString += ` LIMIT ${limit}`;
}
else if (offset) {
/**
* SQLite doesn't allow OFFSET without LIMIT
*/
queryString += ` LIMIT -1`;
}
if (offset) {
queryString += ` OFFSET ${offset}`;
}
}
return {
string: queryString,
values: sqlSearhValues,
where: {
string: whereString,
values: whereString ? whereValues : [],
},
};
}
function isSet(val) {
return val !== undefined && val !== null;
}
// let queryString = (() => {
// let str = "SELECT";
// if (genObject?.select_sql) {
// str += ` ${genObject.select_sql}`;
// } else if (genObject?.selectFields?.[0]) {
// if (genObject.join) {
// str += sqlGenGrabSelectFieldSQL<T>({
// selectFields: genObject.selectFields,
// append_table_names: true,
// table_name: tableName,
// });
// } else {
// str += sqlGenGrabSelectFieldSQL({
// selectFields: genObject.selectFields,
// table_name: tableName,
// });
// }
// } else {
// if (genObject?.join) {
// str += ` ${tableName}.*`;
// } else {
// str += " *";
// }
// }
// if (genObject?.countSubQueries) {
// let countSqls: string[] = [];
// for (let i = 0; i < genObject.countSubQueries.length; i++) {
// const countSubQuery = genObject.countSubQueries[i];
// if (!countSubQuery) continue;
// const tableAlias = countSubQuery.table_alias;
// let subQStr = `(SELECT COUNT(*)`;
// subQStr += ` FROM ${countSubQuery.table}${
// tableAlias ? ` ${tableAlias}` : ""
// }`;
// subQStr += ` WHERE (`;
// for (let j = 0; j < countSubQuery.srcTrgMap.length; j++) {
// const csqSrc = countSubQuery.srcTrgMap[j];
// if (!csqSrc) continue;
// subQStr += ` ${tableAlias || countSubQuery.table}.${
// csqSrc.src
// }`;
// if (typeof csqSrc.trg == "string") {
// subQStr += ` = ?`;
// sqlSearhValues.push(csqSrc.trg);
// } else if (typeof csqSrc.trg == "object") {
// subQStr += ` = ${csqSrc.trg.table}.${csqSrc.trg.field}`;
// }
// if (j < countSubQuery.srcTrgMap.length - 1) {
// subQStr += ` AND `;
// }
// }
// subQStr += ` )) AS ${countSubQuery.alias}`;
// countSqls.push(subQStr);
// }
// str += `, ${countSqls.join(",")}`;
// }
// if (genObject?.join) {
// const existingJoinTableNames: string[] = [tableName];
// str +=
// "," +
// genObject.join
// .flat()
// .filter((j) => !isUndefined(j))
// .map((joinObj) => {
// const joinTableName = joinObj.alias
// ? joinObj.alias
// : joinObj.tableName;
// if (existingJoinTableNames.includes(joinTableName))
// return null;
// existingJoinTableNames.push(joinTableName);
// if (joinObj.group_concat) {
// return sqlGenGrabConcatStr({
// field: `${joinTableName}.${joinObj.group_concat.field}`,
// alias: joinObj.group_concat.alias,
// separator: joinObj.group_concat.separator,
// });
// } else if (joinObj.selectFields) {
// return joinObj.selectFields
// .map((selectField) => {
// if (typeof selectField == "string") {
// return `${joinTableName}.${selectField}`;
// } else if (typeof selectField == "object") {
// let aliasSelectField = selectField.count
// ? `COUNT(${joinTableName}.${selectField.field})`
// : `${joinTableName}.${selectField.field}`;
// if (selectField.alias)
// aliasSelectField += ` AS ${selectField.alias}`;
// return aliasSelectField;
// }
// })
// .join(",");
// } else {
// return `${joinTableName}.*`;
// }
// })
// .filter((_) => Boolean(_))
// .join(",");
// }
// if (
// genObject?.fullTextSearch &&
// fullTextMatchStr &&
// fullTextSearchStr
// ) {
// str += `, ${fullTextMatchStr} AS ${genObject.fullTextSearch.scoreAlias}`;
// sqlSearhValues.push(fullTextSearchStr);
// }
// str += ` FROM ${tableName}`;
// if (genObject?.join) {
// str +=
// " " +
// genObject.join
// .flat()
// .filter((j) => !isUndefined(j))
// .map((join) => {
// return (
// join.joinType +
// " " +
// (join.alias
// ? `${join.tableName}` + " " + join.alias
// : `${join.tableName}`) +
// " ON " +
// (() => {
// if (Array.isArray(join.match)) {
// return (
// "(" +
// join.match
// .map((mtch) =>
// sqlGenGenJoinStr({
// mtch,
// join,
// table_name: tableName,
// }),
// )
// .join(
// join.operator
// ? ` ${join.operator} `
// : " AND ",
// ) +
// ")"
// );
// } else if (typeof join.match == "object") {
// return sqlGenGenJoinStr({
// mtch: join.match,
// join,
// table_name: tableName,
// });
// }
// })()
// );
// })
// .join(" ");
// }
// return str;
// })();