Spaces:
Runtime error
Runtime error
File size: 8,836 Bytes
4acc19f f3abb0d 4acc19f f3abb0d 4acc19f f3abb0d 4acc19f f3abb0d 4acc19f f3abb0d 4acc19f f3abb0d 4acc19f f3abb0d 4acc19f f3abb0d 4acc19f f3abb0d 4acc19f f3abb0d 4acc19f f3abb0d 4acc19f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | import { Injectable } from '@nestjs/common';
import { BaseQueryCoreDto } from './dto/base-query-core.dto';
import * as _ from 'lodash';
import { Prisma } from '@prisma/client';
@Injectable()
export class BaseQueryCoreService {
// Cache for storing relationship metadata to avoid repeated lookups
private relationshipCache: Map<string, 'one-to-many' | 'one-to-one'> =
new Map();
generatePrismaQuery(query: BaseQueryCoreDto = {}) {
const tempQuery: any = { ...query };
if (typeof tempQuery?.orderBy === 'string') {
tempQuery.orderBy = [tempQuery?.orderBy];
}
if (typeof tempQuery?.include === 'string') {
tempQuery.include = [tempQuery?.include];
}
if (typeof tempQuery?.search_column === 'string') {
tempQuery.search_column = [tempQuery?.search_column];
}
// Add this: Convert multi_search to array if it's a string
if (typeof tempQuery?.multi_search === 'string') {
tempQuery.multi_search = [tempQuery?.multi_search];
}
if (tempQuery?.orderBy?.length > 0) {
const orderBy = {};
tempQuery.orderBy.forEach((ob) => {
let sortField = ob.split('|')[0];
sortField = sortField.split('.');
if (sortField.length > 1) {
orderBy[sortField[0]] = this.getOrderByArray(
sortField,
ob.split('|')[1],
);
} else {
orderBy[sortField[0]] = ob.split('|')[1];
}
});
tempQuery.orderBy = orderBy;
}
if (tempQuery?.include?.length > 0) {
const formattedQry = this.formatIncludeQueryArray(
tempQuery.include.sort(),
);
const { include } = this.getIncludeArray(formattedQry);
tempQuery.include = include;
}
if (tempQuery?.search_column?.length > 0 && tempQuery?.search?.length > 0) {
const searchType = tempQuery?.search_type || 'contains';
const searchCondition = this.createSearchCondition(
tempQuery.search,
searchType,
);
const formattedSearchQry = this.formatSearchQueryArray(
tempQuery.search_column.sort(),
searchCondition,
);
tempQuery['where'] = formattedSearchQry;
}
// Handle multi-criteria search
if (tempQuery?.multi_search?.length > 0) {
const multiSearchConditions = this.formatMultiSearchQueryArray(
tempQuery.multi_search,
);
tempQuery['where'] = { AND: multiSearchConditions };
}
delete tempQuery?.search_column;
delete tempQuery?.search;
delete tempQuery?.search_type;
delete tempQuery?.multi_search;
return tempQuery;
}
formatIncludeQueryArray(queryArr: any, baseVal: any = true) {
let returnObj = {};
queryArr.map((qry, i) => {
const rslt = this.formatArray(qry, baseVal);
if (i > 0) {
returnObj = _.merge(returnObj, rslt);
} else {
returnObj = rslt;
}
});
return returnObj;
}
formatSearchQueryArray(queryArr: any, baseVal: any) {
const searchConditions: any[] = [];
queryArr.forEach((qry: any) => {
searchConditions.push(this.formatArray(qry, baseVal));
});
return { OR: searchConditions };
}
formatMultiSearchQueryArray(multiSearchArr: string[]) {
// Group conditions by column
const columnGroups: { [key: string]: any[] } = {};
multiSearchArr.forEach((searchString: string) => {
const [searchTerm, column, searchType] = searchString.split('|');
if (searchTerm && column) {
const condition = this.createSearchCondition(
searchTerm.trim(),
(searchType || 'contains').trim() as 'contains' | 'equals',
);
const formattedCondition = this.formatMultiSearchArray(
column,
condition,
);
// Group by column name
if (!columnGroups[column]) {
columnGroups[column] = [];
}
columnGroups[column].push(formattedCondition);
}
});
// Convert grouped conditions to final query structure
const finalConditions: any[] = [];
Object.keys(columnGroups).forEach((column) => {
const conditions = columnGroups[column];
if (conditions.length === 1) {
// Single condition for this column
finalConditions.push(conditions[0]);
} else {
// Multiple conditions for same column - use OR
finalConditions.push({ OR: conditions });
}
});
return finalConditions;
}
createSearchCondition(
searchValue: string,
searchType: 'contains' | 'equals',
) {
const condition: any = {};
if (searchType === 'contains') {
condition.contains = searchValue;
condition.mode = 'insensitive';
} else if (searchType === 'equals') {
condition.equals = searchValue;
}
return condition;
}
formatArray(qry: any, baseVal: any) {
const returnObj = {};
const qArr = qry.split('.');
if (qArr.length > 1) {
const ky = qArr[0];
qArr.shift();
if (typeof returnObj[ky] === 'undefined') {
returnObj[ky] = this.formatArray(qArr.join('.'), baseVal);
} else {
returnObj[ky] = {
...returnObj[ky],
...this.formatArray(qArr.join('.'), baseVal),
};
}
} else {
returnObj[qArr[0]] = baseVal;
}
return returnObj;
}
/**
* Format array specifically for multi-search functionality
* This method handles nested relations and applies dynamic relationship detection
*/
formatMultiSearchArray(qry: any, baseVal: any) {
const returnObj = {};
const qArr = qry.split('.');
if (qArr.length > 1) {
const ky = qArr[0];
qArr.shift();
// Recursively build nested condition
const nestedCondition = this.formatMultiSearchArray(
qArr.join('.'),
baseVal,
);
if (typeof returnObj[ky] === 'undefined') {
// Apply dynamic relation wrapping for where conditions
returnObj[ky] = this.wrapRelationCondition(ky, nestedCondition);
} else {
returnObj[ky] = {
...returnObj[ky],
...this.wrapRelationCondition(ky, nestedCondition),
};
}
} else {
returnObj[qArr[0]] = baseVal;
}
return returnObj;
}
/**
* Wrap relation conditions with proper Prisma syntax
* For one-to-many relations, use 'some'
* For one-to-one relations, use 'is'
* Dynamically detects relationship type from Prisma DMMF
*/
private wrapRelationCondition(relationName: string, condition: any) {
const relationType = this.detectRelationType(relationName);
if (relationType === 'one-to-many') {
return { some: condition };
} else {
return { is: condition };
}
}
/**
* Dynamically detect if a relation is one-to-many or one-to-one
* Uses Prisma's DMMF (Data Model Meta Format) to introspect the schema
*/
private detectRelationType(
relationName: string,
): 'one-to-many' | 'one-to-one' {
// Check cache first
if (this.relationshipCache.has(relationName)) {
return this.relationshipCache.get(relationName)!;
}
try {
// Access Prisma's DMMF (Data Model Meta Format) for schema introspection
const dmmf = Prisma.dmmf;
// Search through all models to find the relation
for (const model of dmmf.datamodel.models) {
const field = model.fields.find((f) => f.name === relationName);
if (field && field.kind === 'object') {
// Check if it's a list (array) which indicates one-to-many
const relationType = field.isList ? 'one-to-many' : 'one-to-one';
// Cache the result
this.relationshipCache.set(relationName, relationType);
return relationType;
}
}
// Default to one-to-one if not found (safer default for filtering)
// This prevents incorrect 'some' usage on non-existent relations
this.relationshipCache.set(relationName, 'one-to-one');
return 'one-to-one';
} catch {
// Fallback: If DMMF is not accessible, use one-to-one as safe default
console.warn(
`Could not detect relation type for "${relationName}". Defaulting to one-to-one.`,
);
this.relationshipCache.set(relationName, 'one-to-one');
return 'one-to-one';
}
}
getOrderByArray(fieldArr: any, sort) {
fieldArr.shift();
const incO: any = {};
incO[fieldArr[0]] =
fieldArr.length > 1 ? this.getOrderByArray(fieldArr, sort) : sort;
return incO;
}
getIncludeArray(incArr: any) {
const include = {};
for (const key in incArr) {
if (incArr.hasOwnProperty(key)) {
if (incArr[key] === true) {
include[key] = true;
} else {
include[key] = this.getIncludeArray(incArr[key]);
}
}
}
return { include };
}
}
|