#5-intelligent-groups #23

Merged
jkeffects merged 16 commits from #5-intelligent-groups into main 2024-12-19 09:50:45 +00:00
2 changed files with 217 additions and 11 deletions
Showing only changes of commit 63a0a60b12 - Show all commits

View file

@ -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

View file

@ -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",
},
],
}; };