import sqlGenGenSearchStr from "./sql-generator-gen-search-str"; import sqlGenGenQueryStr from "./sql-generator-gen-query-str"; import { columnRef, quoteIdentifier, quoteQualified, sqlLogicalOperator, sqlNonNegativeInt, sqlOrderStrategy, } from "./sql-identifier"; /** MariaDB requires LIMIT before OFFSET; this is the documented "no limit" value */ const MAX_LIMIT = "18446744073709551615"; /** * # SQL Query Generator * @description Builds parameterized SELECT SQL for MariaDB */ export default function sqlGenerator({ tableName: rawTableName, genObject, dbFullName, count, targetId, }) { const tableName = quoteQualified(rawTableName); const finalQuery = genObject?.query ? genObject.query : undefined; const queryKeys = finalQuery ? Object.keys(finalQuery) : undefined; const whereValues = []; let fullTextMatchStr = genObject?.fullTextSearch ? ` MATCH(${genObject.fullTextSearch.fields .map((f) => columnRef(String(f), genObject.join ? tableName : undefined)) .join(",")}) AGAINST (? IN BOOLEAN MODE)` : undefined; const fullTextSearchStr = genObject?.fullTextSearch ? genObject.fullTextSearch.searchTerm .split(` `) .map((t) => `${t}`) .join(" ") : undefined; let { str: queryString, values: selectValues } = sqlGenGenQueryStr({ table_name: tableName, append_table_names: true, full_text_match_str: fullTextMatchStr, full_text_search_str: fullTextSearchStr, genObject, }); 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"); return ("(" + subSearchString.join(` ${sqlLogicalOperator(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 isSearchStr = cleanedUpSearchStr?.[0] && cleanedUpSearchStr.find((str) => str); const whereConditions = []; if (isSearchStr) { const stringOperator = sqlLogicalOperator(genObject?.searchOperator); whereConditions.push(`(${cleanedUpSearchStr.join(` ${stringOperator} `)})`); } if (genObject?.fullTextSearch && fullTextSearchStr && fullTextMatchStr) { whereConditions.push(fullTextMatchStr.trim()); whereValues.push(fullTextSearchStr); } if (targetId !== undefined && targetId !== null && targetId !== "") { const id = Number(targetId); if (!Number.isFinite(id)) { throw new Error(`Invalid targetId: ${String(targetId)}`); } whereConditions.push(`${columnRef("id", genObject?.join ? tableName : undefined)} = ?`); whereValues.push(id); } const where = whereConditions.length ? `WHERE ${whereConditions.join(" AND ")}` : ``; if (where) { queryString += ` ${where}`; } if (genObject?.group) { const groupRef = (group) => typeof group == "object" ? columnRef(String(group.field), group.table) : columnRef(String(group)); let group_by_txt = ``; if (Array.isArray(genObject.group)) { group_by_txt = genObject.group .map((group) => groupRef(group)) .join(","); } else { group_by_txt = groupRef(genObject.group); } queryString += ` GROUP BY ${group_by_txt}`; } function grabOrderString(order) { const scoreAlias = genObject?.fullTextSearch?.scoreAlias; let orderField = ``; if (scoreAlias && String(order.field) === scoreAlias) { orderField = quoteIdentifier(scoreAlias); } else { orderField = columnRef(String(order.field), genObject?.join ? tableName : undefined); } return ` ${orderField} ${sqlOrderStrategy(order.strategy)}`; } if (genObject?.order) { const orders = (Array.isArray(genObject.order) ? genObject.order : [genObject.order]).filter(Boolean); if (orders.length) { queryString += ` ORDER BY${orders.map(grabOrderString).join(",")}`; } } if (!count) { const limit = genObject?.limit !== undefined && genObject.limit !== null ? sqlNonNegativeInt(genObject.limit, "limit") : undefined; let offset; if (genObject?.offset !== undefined && genObject.offset !== null) { offset = sqlNonNegativeInt(genObject.offset, "offset"); } else if (genObject?.page && limit) { const page = sqlNonNegativeInt(genObject.page, "page"); offset = Math.max(page - 1, 0) * limit; } if (limit) { queryString += ` LIMIT ${limit}`; } else if (offset) { queryString += ` LIMIT ${MAX_LIMIT}`; } if (offset) { queryString += ` OFFSET ${offset}`; } } return { string: queryString, values: [...selectValues, ...whereValues], where, whereValues, }; }