Add Feature: SQL query generator

This commit is contained in:
Benjamin Toby 2024-11-08 19:54:14 +01:00
parent df967a4ffb
commit f53a9bf00e
5 changed files with 265 additions and 6 deletions

8
functions/sql/sql-generator.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
export = sqlGenerator;
declare function sqlGenerator(Param0: {
genObject?: import("../../package-shared/types").ServerQueryParam;
tableName: string;
}): {
string: string;
values: string[];
} | undefined;

View File

@ -0,0 +1,164 @@
// @ts-check
/**
* # SQL Query Generator
* @description Generates an SQL Query for node module `mysql` or `serverless-mysql`
* @type {import("../../package-shared/types").SqlGeneratorFn}
*/
function sqlGenerator({ tableName, genObject }) {
if (!genObject) return undefined;
const finalQuery = genObject.query ? genObject.query : undefined;
const queryKeys = finalQuery ? Object.keys(finalQuery) : undefined;
/** @type {string[]} */
const sqlSearhValues = [];
const sqlSearhString = queryKeys?.map((field) => {
const queryObj = finalQuery?.[field];
if (!queryObj) return;
let str = `${field}=?`;
if (
typeof queryObj.value == "string" ||
typeof queryObj.value == "number"
) {
const valueParsed = String(queryObj.value);
if (queryObj.equality == "LIKE") {
str = `LOWER(${field}) LIKE LOWER('%${valueParsed}%')`;
} else {
sqlSearhValues.push(valueParsed);
}
} else if (Array.isArray(queryObj.value)) {
/** @type {string[]} */
const strArray = [];
queryObj.value.forEach((val) => {
const valueParsed = val;
if (queryObj.equality == "LIKE") {
strArray.push(
`LOWER(${field}) LIKE LOWER('%${valueParsed}%')`
);
} else {
strArray.push(`${field} = ?`);
sqlSearhValues.push(valueParsed);
}
});
str = "(" + strArray.join(` ${queryObj.operator || "AND"} `) + ")";
}
return str;
});
function generateJoinStr(
/** @type {import("../../package-shared/types").ServerQueryParamsJoinMatchObject} */ mtch,
/** @type {import("../../package-shared/types").ServerQueryParamsJoin} */ join
) {
return `${
typeof mtch.source == "object" ? mtch.source.tableName : tableName
}.${
typeof mtch.source == "object" ? mtch.source.fieldName : mtch.source
}=${
typeof mtch.target == "object"
? mtch.target.tableName
: join.tableName
}.${
typeof mtch.target == "object" ? mtch.target.fieldName : mtch.target
}`;
}
let queryString = (() => {
let str = "SELECT";
if (genObject.selectFields?.[0]) {
if (genObject.join) {
str += ` ${genObject.selectFields
?.map((fld) => `${tableName}.${fld}`)
.join(",")}`;
} else {
str += ` ${genObject.selectFields?.join(",")}`;
}
} else {
if (genObject.join) {
str += ` ${tableName}.*`;
} else {
str += " *";
}
}
if (genObject.join) {
str +=
"," +
genObject.join
.map((joinObj) => {
if (joinObj.selectFields) {
return joinObj.selectFields
.map((slFld) => {
if (typeof slFld == "string") {
return `${joinObj.tableName}.${slFld}`;
} else if (typeof slFld == "object") {
let aliasSlctFld = `${joinObj.tableName}.${slFld.field}`;
if (slFld.alias)
aliasSlctFld += ` as ${slFld.alias}`;
return aliasSlctFld;
}
})
.join(",");
} else {
return `${joinObj.tableName}.*`;
}
})
.join(",");
}
str += ` FROM ${tableName}`;
if (genObject.join) {
str +=
" " +
genObject.join
.map((join) => {
return (
join.joinType +
" " +
join.tableName +
" ON " +
(() => {
if (Array.isArray(join.match)) {
return (
"(" +
join.match
.map((mtch) =>
generateJoinStr(mtch, join)
)
.join(" AND ") +
")"
);
} else if (typeof join.match == "object") {
return generateJoinStr(join.match, join);
}
})()
);
})
.join(" ");
}
return str;
})();
if (sqlSearhString) {
const stringOperator = genObject?.searchOperator || "AND";
queryString += ` WHERE ${sqlSearhString.join(` ${stringOperator} `)} `;
}
if (genObject.order)
queryString += ` ORDER BY ${genObject.order.field} ${genObject.order.strategy}`;
if (genObject.limit) queryString += ` LIMIT ${genObject.limit}`;
return {
string: queryString,
values: sqlSearhValues,
};
}
module.exports = sqlGenerator;

4
index.d.ts vendored
View File

@ -24,6 +24,9 @@ export namespace user {
import getSchema = require("./utils/get-schema"); import getSchema = require("./utils/get-schema");
import sanitizeSql = require("./utils/functions/sanitizeSql"); import sanitizeSql = require("./utils/functions/sanitizeSql");
import datasquirelClient = require("./client"); import datasquirelClient = require("./client");
export namespace sql {
export { sqlGenerator };
}
import uploadImage = require("./utils/upload-image"); import uploadImage = require("./utils/upload-image");
import uploadFile = require("./utils/upload-file"); import uploadFile = require("./utils/upload-file");
import deleteFile = require("./utils/delete-file"); import deleteFile = require("./utils/delete-file");
@ -39,4 +42,5 @@ import getToken = require("./users/get-token");
import validateToken = require("./users/validate-token"); import validateToken = require("./users/validate-token");
import loginWithGoogle = require("./users/social/google-auth"); import loginWithGoogle = require("./users/social/google-auth");
import loginWithGithub = require("./users/social/github-auth"); import loginWithGithub = require("./users/social/github-auth");
import sqlGenerator = require("./functions/sql/sql-generator");
export { get, post, getSchema, sanitizeSql, datasquirelClient as client }; export { get, post, getSchema, sanitizeSql, datasquirelClient as client };

View File

@ -28,6 +28,7 @@ const validateToken = require("./users/validate-token");
const sanitizeSql = require("./utils/functions/sanitizeSql"); const sanitizeSql = require("./utils/functions/sanitizeSql");
const datasquirelClient = require("./client"); const datasquirelClient = require("./client");
const sqlGenerator = require("./functions/sql/sql-generator");
//////////////////////////////////////// ////////////////////////////////////////
//////////////////////////////////////// ////////////////////////////////////////
@ -62,17 +63,25 @@ const media = {
deleteFile: deleteFile, deleteFile: deleteFile,
}; };
/**
* SQL Utils
*/
const sql = {
sqlGenerator,
};
/** /**
* Main Export * Main Export
*/ */
const datasquirel = { const datasquirel = {
get: get, get,
post: post, post,
media: media, media,
user: user, user,
getSchema: getSchema, getSchema,
sanitizeSql: sanitizeSql, sanitizeSql,
client: datasquirelClient, client: datasquirelClient,
sql,
}; };
module.exports = datasquirel; module.exports = datasquirel;

View File

@ -1134,3 +1134,77 @@ export type FetchApiReturn = {
msg?: string; msg?: string;
[key: string]: any; [key: string]: any;
}; };
export type ServerQueryParam = {
selectFields?: string[];
query?: ServerQueryQueryObject;
limit?: number;
order?: {
field: string;
strategy: "ASC" | "DESC";
};
searchOperator?: "AND" | "OR";
searchEquality?: "EQUAL" | "LIKE";
addUserId?: {
fieldName: string;
};
join?: ServerQueryParamsJoin[];
} & {
[key: string]: any;
};
export type ServerQueryQueryObject = {
[key: string]: {
value: string | string[];
operator?: "AND" | "OR";
equality?: "EQUAL" | "LIKE";
};
};
export type FetchDataParams = {
path: string;
method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
body?: object | string;
query?: AuthFetchQuery;
};
export type AuthFetchQuery = ServerQueryParam & {
[key: string]: string | number | { [key: string]: any };
};
export type ServerQueryParamsJoin = {
joinType: "INNER JOIN" | "JOIN" | "LEFT JOIN" | "RIGHT JOIN";
tableName: string;
match?:
| ServerQueryParamsJoinMatchObject
| ServerQueryParamsJoinMatchObject[];
selectFields?: (
| string
| {
field: string;
alias?: string;
}
)[];
};
export type ServerQueryParamsJoinMatchObject = {
/** Field name from the **Root Table** */
source: string | ServerQueryParamsJoinMatchSourceTargetObject;
/** Field name from the **Join Table** */
target: string | ServerQueryParamsJoinMatchSourceTargetObject;
};
export type ServerQueryParamsJoinMatchSourceTargetObject = {
tableName: string;
fieldName: string;
};
export type SqlGeneratorFn = (Param0: {
genObject?: ServerQueryParam;
tableName: string;
}) =>
| {
string: string;
values: string[];
}
| undefined;