import { ServerQueryParam, ServerQueryParamsJoin, ServerQueryParamsJoinMatchObject, ServerQueryQueryObject, } from "../../../types"; import sqlGenOperatorGen from "./sql-gen-operator-gen"; type Param = { genObject?: ServerQueryParam; tableName: string; dbFullName?: string; count?: boolean; }; type Return = { string: string; values: string[]; }; /** * # SQL Query Generator * @description Generates an SQL Query for node module `mysql` or `serverless-mysql` */ export default function sqlGenerator< T extends { [key: string]: any } = { [key: string]: any } >({ tableName, genObject, dbFullName, count }: Param): Return { const finalQuery = genObject?.query ? genObject.query : undefined; const queryKeys = finalQuery ? Object.keys(finalQuery) : undefined; const sqlSearhValues: string[] = []; const finalDbName = dbFullName ? `${dbFullName}.` : ""; /** * # Generate Query */ function genSqlSrchStr({ queryObj, join, field, }: { queryObj: ServerQueryQueryObject[string]; join?: ServerQueryParamsJoin[]; field?: string; }) { const finalFieldName = (() => { if (queryObj?.tableName) { return `${finalDbName}${queryObj.tableName}.${field}`; } if (join) { return `${finalDbName}${tableName}.${field}`; } return field; })(); let str = `${finalFieldName}=?`; if (Array.isArray(queryObj.value)) { const strArray: string[] = []; queryObj.value.forEach((val) => { const valueParsed = val; if (!valueParsed) return; const valueString = typeof valueParsed == "string" ? valueParsed : valueParsed ? valueParsed.value?.toString() : undefined; const valueEquality = typeof valueParsed == "object" ? valueParsed.equality || queryObj.equality : queryObj.equality; const operatorStrParam = sqlGenOperatorGen({ queryObj, equality: valueEquality, fieldName: finalFieldName || "", value: valueString?.toString() || "", }); if (operatorStrParam.str && operatorStrParam.param) { strArray.push(operatorStrParam.str); sqlSearhValues.push(operatorStrParam.param); } else if (operatorStrParam.str) { strArray.push(operatorStrParam.str); } }); str = "(" + strArray.join(` ${queryObj.operator || "AND"} `) + ")"; } else { const valueParsed = queryObj.value ? String(queryObj.value) : undefined; const operatorStrParam = sqlGenOperatorGen({ equality: queryObj.equality, fieldName: finalFieldName || "", value: valueParsed, queryObj, }); if (operatorStrParam.str && operatorStrParam.param) { str = operatorStrParam.str; sqlSearhValues.push(operatorStrParam.param); } else if (operatorStrParam.str) { str = operatorStrParam.str; } } return str; } function generateJoinStr( mtch: ServerQueryParamsJoinMatchObject, join: ServerQueryParamsJoin ) { return `${finalDbName}${ typeof mtch.source == "object" ? mtch.source.tableName : tableName }.${ typeof mtch.source == "object" ? mtch.source.fieldName : mtch.source }=${(() => { if (mtch.targetLiteral) { return `'${mtch.targetLiteral}'`; } if (join.alias) { return `${finalDbName}${ typeof mtch.target == "object" ? mtch.target.tableName : join.alias }.${ typeof mtch.target == "object" ? mtch.target.fieldName : mtch.target }`; } return `${finalDbName}${ typeof mtch.target == "object" ? mtch.target.tableName : join.tableName }.${ typeof mtch.target == "object" ? mtch.target.fieldName : mtch.target }`; })()}`; } let fullTextMatchStr = genObject?.fullTextSearch ? ` MATCH(${genObject.fullTextSearch.fields .map((f) => genObject.join ? `${tableName}.${String(f)}` : `${String(f)}` ) .join(",")}) AGAINST (? IN BOOLEAN MODE)` : undefined; const fullTextSearchStr = genObject?.fullTextSearch ? genObject.fullTextSearch.searchTerm .split(` `) .map((t) => `${t}`) .join(" ") : undefined; let queryString = (() => { let str = "SELECT"; if (count) { str += ` COUNT(*)`; } else if (genObject?.selectFields?.[0]) { if (genObject.join) { str += ` ${genObject.selectFields ?.map((fld) => typeof fld == "object" ? `${finalDbName}${tableName}.${fld.fieldName.toString()}` + (fld.alias ? ` as ${fld.alias}` : ``) : `${finalDbName}${tableName}.${String(fld)}` ) .join(",")}`; } else { str += ` ${genObject.selectFields ?.map((fld) => typeof fld == "object" ? `${fld.fieldName.toString()}` + (fld.alias ? ` as ${fld.alias}` : ``) : fld ) .join(",")}`; } } else { if (genObject?.join) { str += ` ${finalDbName}${tableName}.*`; } else { str += " *"; } } if (genObject?.countSubQueries) { let countSqls: string[] = []; for (let i = 0; i < genObject.countSubQueries.length; i++) { const countSubQuery = genObject.countSubQueries[i]; let subQStr = `(SELECT COUNT(*)`; subQStr += ` FROM ${countSubQuery.table}`; subQStr += ` WHERE (`; for (let j = 0; j < countSubQuery.srcTrgMap.length; j++) { const csqSrc = countSubQuery.srcTrgMap[j]; subQStr += ` ${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 && !count) { const existingJoinTableNames: string[] = [tableName]; str += "," + genObject.join .map((joinObj) => { const joinTableName = joinObj.alias ? joinObj.alias : joinObj.tableName; if (existingJoinTableNames.includes(joinTableName)) return null; existingJoinTableNames.push(joinTableName); if (joinObj.selectFields) { return joinObj.selectFields .map((selectField) => { if (typeof selectField == "string") { return `${finalDbName}${joinTableName}.${selectField}`; } else if (typeof selectField == "object") { let aliasSelectField = selectField.count ? `COUNT(${finalDbName}${joinTableName}.${selectField.field})` : `${finalDbName}${joinTableName}.${selectField.field}`; if (selectField.alias) aliasSelectField += ` AS ${selectField.alias}`; return aliasSelectField; } }) .join(","); } else { return `${finalDbName}${joinTableName}.*`; } }) .filter((_) => Boolean(_)) .join(","); } if ( genObject?.fullTextSearch && fullTextMatchStr && fullTextSearchStr ) { str += `, ${fullTextMatchStr} AS ${genObject.fullTextSearch.scoreAlias}`; sqlSearhValues.push(fullTextSearchStr); } str += ` FROM ${finalDbName}${tableName}`; if (genObject?.join) { str += " " + genObject.join .map((join) => { return ( join.joinType + " " + (join.alias ? `${finalDbName}${join.tableName}` + " " + join.alias : `${finalDbName}${join.tableName}`) + " ON " + (() => { if (Array.isArray(join.match)) { return ( "(" + join.match .map((mtch) => generateJoinStr(mtch, join) ) .join( join.operator ? ` ${join.operator} ` : " AND " ) + ")" ); } else if (typeof join.match == "object") { return generateJoinStr(join.match, join); } })() ); }) .join(" "); } return str; })(); 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]; return genSqlSrchStr({ queryObj: newSubQueryObj, field: _field, join: genObject?.join, }); }); return ( "(" + subSearchString.join(` ${queryObj.operator || "AND"} `) + ")" ); } return genSqlSrchStr({ queryObj, field, join: genObject?.join }); }); const isSearchStr = sqlSearhString?.[0] && sqlSearhString.find((str) => str); if (isSearchStr) { const stringOperator = genObject?.searchOperator || "AND"; queryString += ` WHERE ${sqlSearhString.join(` ${stringOperator} `)}`; } if (genObject?.fullTextSearch && fullTextSearchStr && fullTextMatchStr) { queryString += `${isSearchStr ? " AND" : " WHERE"} ${fullTextMatchStr}`; sqlSearhValues.push(fullTextSearchStr); } if (genObject?.group?.[0]) { queryString += ` GROUP BY ${genObject.group .map((g) => `\`${g.toString()}\``) .join(",")}`; } if (genObject?.order && !count) { let orderFields = []; let orderSrt = ` ORDER BY`; if (genObject?.fullTextSearch && genObject.fullTextSearch.scoreAlias) { orderFields.push(genObject.fullTextSearch.scoreAlias); } else if (genObject.join) { orderFields.push( `${finalDbName}${tableName}.${String(genObject.order.field)}` ); } else { orderFields.push(genObject.order.field); } orderSrt += ` ${orderFields.join(", ")} ${genObject.order.strategy}`; queryString += ` ${orderSrt}`; } if (genObject?.limit && !count) queryString += ` LIMIT ${genObject.limit}`; if (genObject?.offset && !count) queryString += ` OFFSET ${genObject.offset}`; return { string: queryString, values: sqlSearhValues, }; }