builder concept
This commit is contained in:
parent
ee0d6ddcce
commit
63a0a60b12
2 changed files with 217 additions and 11 deletions
|
@ -1,5 +1,6 @@
|
||||||
|
import { Brackets, DataSource, ObjectLiteral, SelectQueryBuilder } from "typeorm";
|
||||||
import { dataSource } from "../data-source";
|
import { dataSource } from "../data-source";
|
||||||
import { DynamicQueryStructure } from "../type/dynamicQueries";
|
import { ConditionStructure, DynamicQueryStructure } from "../type/dynamicQueries";
|
||||||
import { TableMeta } from "../type/tableMeta";
|
import { TableMeta } from "../type/tableMeta";
|
||||||
|
|
||||||
export default abstract class DynamicQueryBuilder {
|
export default abstract class DynamicQueryBuilder {
|
||||||
|
@ -45,11 +46,201 @@ export default abstract class DynamicQueryBuilder {
|
||||||
return this.allowedTables.map((table) => this.getTableMeta(table));
|
return this.allowedTables.map((table) => this.getTableMeta(table));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static buildQuery(query: DynamicQueryStructure, offset: number = 0, count: number = 25) {
|
public static buildQuery(
|
||||||
// execute:
|
queryObj: DynamicQueryStructure,
|
||||||
// .offset(offset)
|
offset: number = 0,
|
||||||
// .limit(count)
|
count: number = 25
|
||||||
// .getManyAndCount()
|
): SelectQueryBuilder<ObjectLiteral> {
|
||||||
|
let query = dataSource.getRepository(queryObj.table).createQueryBuilder(queryObj.table + "_0");
|
||||||
|
query = this.buildDynamicQuery(query, queryObj);
|
||||||
|
query = query.offset(offset);
|
||||||
|
query = query.limit(count);
|
||||||
|
|
||||||
|
return query;
|
||||||
|
|
||||||
|
dataSource
|
||||||
|
.getRepository("member")
|
||||||
|
.createQueryBuilder("member_0")
|
||||||
|
.select("member_0.firstname")
|
||||||
|
.addSelect("member_0.lastname")
|
||||||
|
.where("member_0.mail LIKE '%@gmail.com'")
|
||||||
|
.andWhere(
|
||||||
|
new Brackets((qb) => {
|
||||||
|
qb.where("user.firstName LIKE '%J'").orWhere("user.lastName LIKE '%K'");
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.leftJoinAndSelect("member_0.sendNewsletter", "communication_0")
|
||||||
|
.addSelect("communication_0.*")
|
||||||
|
.orderBy("member_0.firstname", "ASC")
|
||||||
|
.addOrderBy("member_0.lastname", "ASC");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static buildDynamicQuery(
|
||||||
|
queryBuilder: SelectQueryBuilder<ObjectLiteral>,
|
||||||
|
queryObject: DynamicQueryStructure,
|
||||||
|
depth: number = 0
|
||||||
|
): SelectQueryBuilder<ObjectLiteral> {
|
||||||
|
const alias = queryObject.table + "_" + depth;
|
||||||
|
|
||||||
|
// Handle SELECT
|
||||||
|
if (queryObject.select == "*") {
|
||||||
|
queryBuilder.select(`${alias}.*`);
|
||||||
|
} else {
|
||||||
|
queryBuilder.select(queryObject.select.map((col) => `${alias}.${col}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle WHERE
|
||||||
|
if (queryObject.where) {
|
||||||
|
this.applyWhere(queryBuilder, queryObject.where, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle JOINS
|
||||||
|
if (queryObject.join) {
|
||||||
|
queryObject.join.forEach((join) => {
|
||||||
|
const joinAlias = join.table;
|
||||||
|
const joinType = "leftJoin"; // Default join type
|
||||||
|
const joinCondition = `${alias}.${join.foreignColumn} = ${joinAlias}.${join.foreignColumn}`;
|
||||||
|
|
||||||
|
queryBuilder[joinType](`${join.table}`, joinAlias, joinCondition);
|
||||||
|
|
||||||
|
// Recursively handle sub-joins and their conditions
|
||||||
|
this.buildDynamicQuery(queryBuilder, join, depth + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle ORDER BY
|
||||||
|
if (queryObject.orderBy) {
|
||||||
|
queryObject.orderBy.forEach((order) => {
|
||||||
|
queryBuilder.addOrderBy(`${alias}.${order.column}`, order.order);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Apply WHERE conditions
|
||||||
|
public static applyWhere<T>(
|
||||||
|
queryBuilder: SelectQueryBuilder<T>,
|
||||||
|
conditions: Array<ConditionStructure>,
|
||||||
|
alias: string
|
||||||
|
): void {
|
||||||
|
conditions.forEach((condition, index) => {
|
||||||
|
if (condition.structureType === "condition") {
|
||||||
|
const whereClause = this.buildConditionClause(condition, alias);
|
||||||
|
const whereMethod = condition.concat === "_" ? "where" : "andWhere";
|
||||||
|
|
||||||
|
if (condition.concat === "OR") {
|
||||||
|
queryBuilder.orWhere(whereClause.query, whereClause.parameters);
|
||||||
|
} else {
|
||||||
|
queryBuilder[whereMethod](whereClause.query, whereClause.parameters);
|
||||||
|
}
|
||||||
|
} else if (condition.structureType === "nested") {
|
||||||
|
const nestedQuery = this.conditionsToQuery(condition.condition, alias);
|
||||||
|
const whereMethod = condition.concat === "OR" ? "orWhere" : "andWhere";
|
||||||
|
|
||||||
|
queryBuilder[whereMethod](`(${nestedQuery.query})`, nestedQuery.parameters);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Build a single condition clause
|
||||||
|
public static buildConditionClause(
|
||||||
|
condition: ConditionStructure,
|
||||||
|
alias: string
|
||||||
|
): { query: string; parameters: Record<string, unknown> } {
|
||||||
|
if (condition.structureType == "nested") return;
|
||||||
|
const parameterKey = `${alias}_${condition.column}_${Math.random().toString(36).substring(2)}`;
|
||||||
|
let query = `${alias}.${condition.column}`;
|
||||||
|
let parameters: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
switch (condition.operation) {
|
||||||
|
case "eq":
|
||||||
|
query += ` = :${parameterKey}`;
|
||||||
|
parameters[parameterKey] = condition.value;
|
||||||
|
break;
|
||||||
|
case "neq":
|
||||||
|
query += ` != :${parameterKey}`;
|
||||||
|
parameters[parameterKey] = condition.value;
|
||||||
|
break;
|
||||||
|
case "lt":
|
||||||
|
query += ` < :${parameterKey}`;
|
||||||
|
parameters[parameterKey] = condition.value;
|
||||||
|
break;
|
||||||
|
case "lte":
|
||||||
|
query += ` <= :${parameterKey}`;
|
||||||
|
parameters[parameterKey] = condition.value;
|
||||||
|
break;
|
||||||
|
case "gt":
|
||||||
|
query += ` > :${parameterKey}`;
|
||||||
|
parameters[parameterKey] = condition.value;
|
||||||
|
break;
|
||||||
|
case "gte":
|
||||||
|
query += ` >= :${parameterKey}`;
|
||||||
|
parameters[parameterKey] = condition.value;
|
||||||
|
break;
|
||||||
|
case "in":
|
||||||
|
query += ` IN (:...${parameterKey})`;
|
||||||
|
parameters[parameterKey] = condition.value;
|
||||||
|
break;
|
||||||
|
case "notIn":
|
||||||
|
query += ` NOT IN (:...${parameterKey})`;
|
||||||
|
parameters[parameterKey] = condition.value;
|
||||||
|
break;
|
||||||
|
case "between":
|
||||||
|
query += ` BETWEEN :${parameterKey}_start AND :${parameterKey}_end`;
|
||||||
|
parameters[`${parameterKey}_start`] = (condition.value as { start: any }).start;
|
||||||
|
parameters[`${parameterKey}_end`] = (condition.value as { end: any }).end;
|
||||||
|
break;
|
||||||
|
case "null":
|
||||||
|
query += ` IS NULL`;
|
||||||
|
break;
|
||||||
|
case "notNull":
|
||||||
|
query += ` IS NOT NULL`;
|
||||||
|
break;
|
||||||
|
case "contains":
|
||||||
|
query += ` LIKE :${parameterKey}`;
|
||||||
|
parameters[parameterKey] = `%${condition.value}%`;
|
||||||
|
break;
|
||||||
|
case "notContains":
|
||||||
|
query += ` NOT LIKE :${parameterKey}`;
|
||||||
|
parameters[parameterKey] = `%${condition.value}%`;
|
||||||
|
break;
|
||||||
|
case "startsWith":
|
||||||
|
query += ` LIKE :${parameterKey}`;
|
||||||
|
parameters[parameterKey] = `${condition.value}%`;
|
||||||
|
break;
|
||||||
|
case "endsWith":
|
||||||
|
query += ` LIKE :${parameterKey}`;
|
||||||
|
parameters[parameterKey] = `%${condition.value}`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported operation: ${condition.operation}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { query, parameters };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Convert nested conditions to a query
|
||||||
|
public static conditionsToQuery(
|
||||||
|
conditions: Array<ConditionStructure>,
|
||||||
|
alias: string
|
||||||
|
): { query: string; parameters: Record<string, unknown> } {
|
||||||
|
let queryParts: string[] = [];
|
||||||
|
let parameters: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
conditions.forEach((condition, index) => {
|
||||||
|
if (condition.structureType === "condition") {
|
||||||
|
const clause = this.buildConditionClause(condition, alias);
|
||||||
|
queryParts.push(clause.query);
|
||||||
|
parameters = { ...parameters, ...clause.parameters };
|
||||||
|
} else if (condition.structureType === "nested") {
|
||||||
|
const nested = this.conditionsToQuery(condition.condition, alias);
|
||||||
|
queryParts.push(`(${nested.query})`);
|
||||||
|
parameters = { ...parameters, ...nested.parameters };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { query: queryParts.join(" AND "), parameters };
|
||||||
}
|
}
|
||||||
|
|
||||||
// use switch... for compare functions
|
// use switch... for compare functions
|
||||||
|
|
|
@ -3,16 +3,18 @@ export interface DynamicQueryStructure {
|
||||||
table: string;
|
table: string;
|
||||||
where?: Array<ConditionStructure>;
|
where?: Array<ConditionStructure>;
|
||||||
join?: Array<DynamicQueryStructure & { foreignColumn: string }>;
|
join?: Array<DynamicQueryStructure & { foreignColumn: string }>;
|
||||||
orderBy?: { [key: string]: "ASC" | "DESC" };
|
orderBy?: Array<OrderByStructure>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ConditionStructure = (
|
export type ConditionStructure = (
|
||||||
| {
|
| {
|
||||||
|
structureType: "condition";
|
||||||
column: string;
|
column: string;
|
||||||
operation: WhereOperation;
|
operation: WhereOperation;
|
||||||
value: ConditionValue;
|
value: ConditionValue;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
structureType: "nested";
|
||||||
invert?: boolean;
|
invert?: boolean;
|
||||||
condition: Array<ConditionStructure>;
|
condition: Array<ConditionStructure>;
|
||||||
}
|
}
|
||||||
|
@ -43,6 +45,13 @@ export type WhereOperation =
|
||||||
| "startsWith" // Starts with
|
| "startsWith" // Starts with
|
||||||
| "endsWith"; // Ends with
|
| "endsWith"; // Ends with
|
||||||
|
|
||||||
|
export type OrderByStructure = {
|
||||||
|
column: string;
|
||||||
|
order: OrderByType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderByType = "ASC" | "DESC";
|
||||||
|
|
||||||
const exampleQuery: DynamicQueryStructure = {
|
const exampleQuery: DynamicQueryStructure = {
|
||||||
select: ["firstname", "lastname"],
|
select: ["firstname", "lastname"],
|
||||||
table: "member",
|
table: "member",
|
||||||
|
@ -103,8 +112,14 @@ const exampleQuery: DynamicQueryStructure = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
orderBy: {
|
orderBy: [
|
||||||
firstname: "ASC",
|
{
|
||||||
lastname: "ASC",
|
column: "firstname",
|
||||||
},
|
order: "ASC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
column: "lastname",
|
||||||
|
order: "ASC",
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue