import { Logger } from '@nestjs/common'; import { UserProfile, db, RowModelRequest } from '@nice/common'; import { LogicalCondition, OperatorType, SQLBuilder } from './sql-builder'; export interface GetRowOptions { id?: string; ids?: string[]; extraCondition?: LogicalCondition; staff?: UserProfile; } export abstract class RowModelService { private keywords: Set = new Set([ 'SELECT', 'FROM', 'WHERE', 'ORDER', 'BY', 'GROUP', 'JOIN', 'AND', 'OR', // 添加更多需要引号的关键词 ]); protected logger = new Logger(this.tableName); protected constructor(protected tableName: string) { } protected async getRowDto(row: any, staff?: UserProfile): Promise { return row; } protected async getRowsSqlWrapper( sql: string, request?: RowModelRequest, staff?: UserProfile, ) { if (request) return SQLBuilder.join([sql, this.getLimitSql(request)]); return sql; } protected getLimitSql(request: RowModelRequest) { return SQLBuilder.limit( request.endRow - request.startRow, request.startRow, ); } abstract createJoinSql(request?: RowModelRequest): string[]; async getRows(request: RowModelRequest, staff?: UserProfile) { try { let SQL = SQLBuilder.join([ SQLBuilder.select(this.getRowSelectCols(request)), SQLBuilder.from(this.tableName), SQLBuilder.join(this.createJoinSql(request)), SQLBuilder.where(this.createGetRowsFilters(request, staff)), SQLBuilder.groupBy(this.getGroupByColumns(request)), SQLBuilder.orderBy(this.getOrderByColumns(request)), ]); SQL = await this.getRowsSqlWrapper(SQL, request, staff); // this.logger.debug('getrows', SQL); const results: any[] = (await db?.$queryRawUnsafe(SQL)) || []; const rowDataDto = await Promise.all( results.map((row) => this.getRowDto(row, staff)), ); return { rowCount: this.getRowCount(request, rowDataDto) || 0, rowData: rowDataDto, }; } catch (error: any) { this.logger.error('Error executing getRows:', error); } } getRowCount(request: RowModelRequest, results: any[]) { if (results === null || results === undefined || results.length === 0) { return null; } const currentLastRow = request.startRow + results.length; return currentLastRow <= request.endRow ? currentLastRow : -1; } async getRowById(options: GetRowOptions): Promise { const { id, extraCondition = { field: `${this.tableName}.deleted_at`, op: 'blank', type: 'date', }, staff, } = options; return this.getSingleRow( { AND: [this.createGetByIdFilter(id!), extraCondition] }, staff, ); } async getRowByIds(options: GetRowOptions): Promise { const { ids, extraCondition = { field: `${this.tableName}.deleted_at`, op: 'blank', type: 'date', }, staff, } = options; return this.getMultipleRows( { AND: [this.createGetByIdsFilter(ids!), extraCondition] }, staff, ); } protected createGetRowsFilters( request: RowModelRequest, staff?: UserProfile, ): LogicalCondition { let groupConditions: LogicalCondition[] = []; if (this.isDoingTreeGroup(request)) { groupConditions = [ { field: 'parent_id', op: 'equals' as OperatorType, value: request.groupKeys[request.groupKeys.length - 1], }, ]; } else { groupConditions = request?.groupKeys?.map((key, index) => ({ field: request.rowGroupCols[index].field, op: 'equals' as OperatorType, value: key, })); } const condition: LogicalCondition = { AND: [ ...groupConditions, ...this.buildFilterConditions(request.filterModel), ], }; return condition; } private buildFilterConditions(filterModel: any): LogicalCondition[] { return filterModel ? Object.entries(filterModel)?.map(([key, item]) => SQLBuilder.createFilterSql( key === 'ag-Grid-AutoColumn' ? 'name' : key, item, ), ) : []; } getRowSelectCols(request: RowModelRequest): string[] { return this.isDoingGroup(request) ? this.createGroupingRowSelect(request) : this.createUnGroupingRowSelect(request); } protected createUnGroupingRowSelect(request?: RowModelRequest): string[] { return ['*']; } protected createAggSqlForWrapper(request: RowModelRequest) { const { rowGroupCols, valueCols, groupKeys } = request; return valueCols.map( (valueCol) => `${valueCol.aggFunc}(${valueCol.field.replace('.', '_')}) AS ${valueCol.field.split('.').join('_')}`, ); } protected createGroupingRowSelect( request: RowModelRequest, wrapperSql: boolean = false, ): string[] { const { rowGroupCols, valueCols, groupKeys } = request; const colsToSelect: string[] = []; const rowGroupCol = rowGroupCols[groupKeys!.length]; if (rowGroupCol) { colsToSelect.push( `${rowGroupCol.field} AS ${rowGroupCol.field.replace('.', '_')}`, ); } colsToSelect.push( ...valueCols.map( (valueCol) => `${wrapperSql ? '' : valueCol.aggFunc}(${valueCol.field}) AS ${valueCol.field.replace('.', '_')}`, ), ); return colsToSelect; } getGroupByColumns(request: RowModelRequest): string[] { return this.isDoingGroup(request) ? [request.rowGroupCols[request.groupKeys!.length]?.field] : []; } getOrderByColumns(request: RowModelRequest): string[] { const { sortModel, rowGroupCols, groupKeys } = request; const grouping = this.isDoingGroup(request); const sortParts: string[] = []; if (sortModel) { const groupColIds = rowGroupCols .map((groupCol) => groupCol.id) .slice(0, groupKeys.length + 1); sortModel.forEach((item) => { if ( !grouping || (groupColIds.indexOf(item.colId) >= 0 && rowGroupCols[groupKeys.length].field === item.colId) ) { const colId = this.keywords.has(item.colId.toUpperCase()) ? `"${item.colId}"` : item.colId; sortParts.push(`${colId} ${item.sort}`); } }); } return sortParts; } isDoingGroup(requset: RowModelRequest): boolean { return requset.rowGroupCols.length > requset.groupKeys.length; } isDoingTreeGroup(requset: RowModelRequest): boolean { return requset.rowGroupCols.length === 0 && requset.groupKeys.length > 0; } private async getSingleRow( condition: LogicalCondition, staff?: UserProfile, ): Promise { const results = await this.getRowsWithFilters(condition, staff); return results[0]; } private async getMultipleRows( condition: LogicalCondition, staff?: UserProfile, ): Promise { return this.getRowsWithFilters(condition, staff); } private async getRowsWithFilters( condition: LogicalCondition, staff?: UserProfile, ): Promise { try { const SQL = SQLBuilder.join([ SQLBuilder.select(this.createUnGroupingRowSelect()), SQLBuilder.from(this.tableName), SQLBuilder.join(this.createJoinSql()), SQLBuilder.where(condition), ]); // this.logger.debug(SQL) const results: any[] = await db.$queryRawUnsafe(SQL); const rowDataDto = await Promise.all( results.map((item) => this.getRowDto(item, staff)), ); // rowDataDto = getUniqueItems(rowDataDto, "id") return rowDataDto; } catch (error) { this.logger.error('Error executing query:', error); throw error; } } async getAggValues(request: RowModelRequest) { try { const SQL = SQLBuilder.join([ SQLBuilder.select(this.buildAggSelect(request.valueCols)), SQLBuilder.from(this.tableName), SQLBuilder.join(this.createJoinSql(request)), SQLBuilder.where(this.createGetRowsFilters(request)), SQLBuilder.groupBy(this.buildAggGroupBy()), ]); const result: any[] = await db.$queryRawUnsafe(SQL); return result[0]; } catch (error) { this.logger.error('Error executing query:', error); throw error; } } protected buildAggGroupBy(): string[] { return []; } protected buildAggSelect(valueCols: any[]): string[] { return valueCols.map( (valueCol) => `${valueCol.aggFunc}(${valueCol.field}) AS ${valueCol.field.replace('.', '_')}`, ); } private createGetByIdFilter(id: string): LogicalCondition { return { field: `${this.tableName}.id`, value: id, op: 'equals', }; } private createGetByIdsFilter(ids: string[]): LogicalCondition { return { field: `${this.tableName}.id`, value: ids, op: 'in', }; } }