Fix nested relations (#7158)
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
+124
-87
@@ -1,4 +1,9 @@
|
||||
import { FindOptionsWhere, ObjectLiteral } from 'typeorm';
|
||||
import {
|
||||
Brackets,
|
||||
NotBrackets,
|
||||
SelectQueryBuilder,
|
||||
WhereExpressionBuilder,
|
||||
} from 'typeorm';
|
||||
|
||||
import { RecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
|
||||
@@ -8,106 +13,138 @@ import { GraphqlQueryFilterFieldParser } from './graphql-query-filter-field.pars
|
||||
|
||||
export class GraphqlQueryFilterConditionParser {
|
||||
private fieldMetadataMap: FieldMetadataMap;
|
||||
private fieldConditionParser: GraphqlQueryFilterFieldParser;
|
||||
private queryFilterFieldParser: GraphqlQueryFilterFieldParser;
|
||||
|
||||
constructor(fieldMetadataMap: FieldMetadataMap) {
|
||||
this.fieldMetadataMap = fieldMetadataMap;
|
||||
this.fieldConditionParser = new GraphqlQueryFilterFieldParser(
|
||||
this.queryFilterFieldParser = new GraphqlQueryFilterFieldParser(
|
||||
this.fieldMetadataMap,
|
||||
);
|
||||
}
|
||||
|
||||
public parse(
|
||||
conditions: RecordFilter,
|
||||
isNegated = false,
|
||||
): FindOptionsWhere<ObjectLiteral> | FindOptionsWhere<ObjectLiteral>[] {
|
||||
if (Array.isArray(conditions)) {
|
||||
return this.parseAndCondition(conditions, isNegated);
|
||||
queryBuilder: SelectQueryBuilder<any>,
|
||||
objectNameSingular: string,
|
||||
filter: RecordFilter,
|
||||
): SelectQueryBuilder<any> {
|
||||
if (!filter || Object.keys(filter).length === 0) {
|
||||
return queryBuilder;
|
||||
}
|
||||
|
||||
const result: FindOptionsWhere<ObjectLiteral> = {};
|
||||
return queryBuilder.where(
|
||||
new Brackets((qb) => {
|
||||
Object.entries(filter).forEach(([key, value], index) => {
|
||||
this.parseKeyFilter(qb, objectNameSingular, key, value, index === 0);
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(conditions)) {
|
||||
switch (key) {
|
||||
case 'and': {
|
||||
const andConditions = this.parseAndCondition(value, isNegated);
|
||||
private parseKeyFilter(
|
||||
queryBuilder: WhereExpressionBuilder,
|
||||
objectNameSingular: string,
|
||||
key: string,
|
||||
value: any,
|
||||
isFirst = false,
|
||||
): void {
|
||||
switch (key) {
|
||||
case 'and': {
|
||||
const andWhereCondition = new Brackets((qb) => {
|
||||
value.forEach((filter: RecordFilter, index: number) => {
|
||||
const whereCondition = new Brackets((qb2) => {
|
||||
Object.entries(filter).forEach(
|
||||
([subFilterkey, subFilterValue], index) => {
|
||||
this.parseKeyFilter(
|
||||
qb2,
|
||||
objectNameSingular,
|
||||
subFilterkey,
|
||||
subFilterValue,
|
||||
index === 0,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return andConditions.map((condition) => ({
|
||||
...result,
|
||||
...condition,
|
||||
}));
|
||||
if (index === 0) {
|
||||
qb.where(whereCondition);
|
||||
} else {
|
||||
qb.andWhere(whereCondition);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (isFirst) {
|
||||
queryBuilder.where(andWhereCondition);
|
||||
} else {
|
||||
queryBuilder.andWhere(andWhereCondition);
|
||||
}
|
||||
case 'or': {
|
||||
const orConditions = this.parseOrCondition(value, isNegated);
|
||||
|
||||
return orConditions.map((condition) => ({ ...result, ...condition }));
|
||||
}
|
||||
case 'not':
|
||||
Object.assign(result, this.parse(value, !isNegated));
|
||||
break;
|
||||
default:
|
||||
Object.assign(
|
||||
result,
|
||||
this.fieldConditionParser.parse(key, value, isNegated),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'or': {
|
||||
const orWhereCondition = new Brackets((qb) => {
|
||||
value.forEach((filter: RecordFilter, index: number) => {
|
||||
const whereCondition = new Brackets((qb2) => {
|
||||
Object.entries(filter).forEach(
|
||||
([subFilterkey, subFilterValue], index) => {
|
||||
this.parseKeyFilter(
|
||||
qb2,
|
||||
objectNameSingular,
|
||||
subFilterkey,
|
||||
subFilterValue,
|
||||
index === 0,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (index === 0) {
|
||||
qb.where(whereCondition);
|
||||
} else {
|
||||
qb.orWhere(whereCondition);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (isFirst) {
|
||||
queryBuilder.where(orWhereCondition);
|
||||
} else {
|
||||
queryBuilder.andWhere(orWhereCondition);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'not': {
|
||||
const notWhereCondition = new NotBrackets((qb) => {
|
||||
Object.entries(value).forEach(
|
||||
([subFilterkey, subFilterValue], index) => {
|
||||
this.parseKeyFilter(
|
||||
qb,
|
||||
objectNameSingular,
|
||||
subFilterkey,
|
||||
subFilterValue,
|
||||
index === 0,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (isFirst) {
|
||||
queryBuilder.where(notWhereCondition);
|
||||
} else {
|
||||
queryBuilder.andWhere(notWhereCondition);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
this.queryFilterFieldParser.parse(
|
||||
queryBuilder,
|
||||
objectNameSingular,
|
||||
key,
|
||||
value,
|
||||
isFirst,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private parseAndCondition(
|
||||
conditions: RecordFilter[],
|
||||
isNegated: boolean,
|
||||
): FindOptionsWhere<ObjectLiteral>[] {
|
||||
const parsedConditions = conditions.map((condition) =>
|
||||
this.parse(condition, isNegated),
|
||||
);
|
||||
|
||||
return this.combineConditions(parsedConditions, isNegated ? 'or' : 'and');
|
||||
}
|
||||
|
||||
private parseOrCondition(
|
||||
conditions: RecordFilter[],
|
||||
isNegated: boolean,
|
||||
): FindOptionsWhere<ObjectLiteral>[] {
|
||||
const parsedConditions = conditions.map((condition) =>
|
||||
this.parse(condition, isNegated),
|
||||
);
|
||||
|
||||
return this.combineConditions(parsedConditions, isNegated ? 'and' : 'or');
|
||||
}
|
||||
|
||||
private combineConditions(
|
||||
conditions: (
|
||||
| FindOptionsWhere<ObjectLiteral>
|
||||
| FindOptionsWhere<ObjectLiteral>[]
|
||||
)[],
|
||||
combineType: 'and' | 'or',
|
||||
): FindOptionsWhere<ObjectLiteral>[] {
|
||||
if (combineType === 'and') {
|
||||
return conditions.reduce<FindOptionsWhere<ObjectLiteral>[]>(
|
||||
(acc, condition) => {
|
||||
if (Array.isArray(condition)) {
|
||||
return acc.flatMap((accCondition) =>
|
||||
condition.map((subCondition) => ({
|
||||
...accCondition,
|
||||
...subCondition,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
return acc.map((accCondition) => ({
|
||||
...accCondition,
|
||||
...condition,
|
||||
}));
|
||||
},
|
||||
[{}],
|
||||
);
|
||||
}
|
||||
|
||||
return conditions.flatMap((condition) =>
|
||||
Array.isArray(condition) ? condition : [condition],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+142
-51
@@ -1,64 +1,153 @@
|
||||
import { FindOptionsWhere, Not, ObjectLiteral } from 'typeorm';
|
||||
import { ObjectLiteral, WhereExpressionBuilder } from 'typeorm';
|
||||
|
||||
import { RecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
import { isPlainObject } from 'src/utils/is-plain-object';
|
||||
|
||||
import { GraphqlQueryFilterConditionParser } from './graphql-query-filter-condition.parser';
|
||||
import { GraphqlQueryFilterOperatorParser } from './graphql-query-filter-operator.parser';
|
||||
type WhereConditionParts = {
|
||||
sql: string;
|
||||
params: ObjectLiteral;
|
||||
};
|
||||
|
||||
export class GraphqlQueryFilterFieldParser {
|
||||
private fieldMetadataMap: FieldMetadataMap;
|
||||
private operatorParser: GraphqlQueryFilterOperatorParser;
|
||||
|
||||
constructor(fieldMetadataMap: FieldMetadataMap) {
|
||||
this.fieldMetadataMap = fieldMetadataMap;
|
||||
this.operatorParser = new GraphqlQueryFilterOperatorParser();
|
||||
}
|
||||
|
||||
public parse(
|
||||
queryBuilder: WhereExpressionBuilder,
|
||||
objectNameSingular: string,
|
||||
key: string,
|
||||
value: any,
|
||||
isNegated: boolean,
|
||||
): FindOptionsWhere<ObjectLiteral> {
|
||||
const fieldMetadata = this.fieldMetadataMap[key];
|
||||
filterValue: any,
|
||||
isFirst = false,
|
||||
): void {
|
||||
const fieldMetadata = this.fieldMetadataMap[`${key}`];
|
||||
|
||||
if (!fieldMetadata) {
|
||||
return {
|
||||
[key]: (value: RecordFilter, isNegated: boolean) => {
|
||||
const conditionParser = new GraphqlQueryFilterConditionParser(
|
||||
this.fieldMetadataMap,
|
||||
);
|
||||
|
||||
return conditionParser.parse(value, isNegated);
|
||||
},
|
||||
};
|
||||
throw new Error(`Field metadata not found for field: ${key}`);
|
||||
}
|
||||
|
||||
if (isCompositeFieldMetadataType(fieldMetadata.type)) {
|
||||
return this.parseCompositeFieldForFilter(fieldMetadata, value, isNegated);
|
||||
return this.parseCompositeFieldForFilter(
|
||||
queryBuilder,
|
||||
fieldMetadata,
|
||||
objectNameSingular,
|
||||
filterValue,
|
||||
isFirst,
|
||||
);
|
||||
}
|
||||
const [[operator, value]] = Object.entries(filterValue);
|
||||
|
||||
if (isPlainObject(value)) {
|
||||
const parsedValue = this.operatorParser.parseOperator(value, isNegated);
|
||||
const { sql, params } = this.computeWhereConditionParts(
|
||||
fieldMetadata,
|
||||
operator,
|
||||
objectNameSingular,
|
||||
key,
|
||||
value,
|
||||
);
|
||||
|
||||
return { [key]: parsedValue };
|
||||
if (isFirst) {
|
||||
queryBuilder.where(sql, params);
|
||||
} else {
|
||||
queryBuilder.andWhere(sql, params);
|
||||
}
|
||||
}
|
||||
|
||||
return { [key]: isNegated ? Not(value) : value };
|
||||
private computeWhereConditionParts(
|
||||
fieldMetadata: FieldMetadataInterface,
|
||||
operator: string,
|
||||
objectNameSingular: string,
|
||||
key: string,
|
||||
value: any,
|
||||
): WhereConditionParts {
|
||||
const uuid = Math.random().toString(36).slice(2, 7);
|
||||
|
||||
switch (operator) {
|
||||
case 'eq':
|
||||
return {
|
||||
sql: `${objectNameSingular}.${key} = :${key}${uuid}`,
|
||||
params: { [`${key}${uuid}`]: value },
|
||||
};
|
||||
case 'neq':
|
||||
return {
|
||||
sql: `${objectNameSingular}.${key} != :${key}${uuid}`,
|
||||
params: { [`${key}${uuid}`]: value },
|
||||
};
|
||||
case 'gt':
|
||||
return {
|
||||
sql: `${objectNameSingular}.${key} > :${key}${uuid}`,
|
||||
params: { [`${key}${uuid}`]: value },
|
||||
};
|
||||
case 'gte':
|
||||
return {
|
||||
sql: `${objectNameSingular}.${key} >= :${key}${uuid}`,
|
||||
params: { [`${key}${uuid}`]: value },
|
||||
};
|
||||
case 'lt':
|
||||
return {
|
||||
sql: `${objectNameSingular}.${key} < :${key}${uuid}`,
|
||||
params: { [`${key}${uuid}`]: value },
|
||||
};
|
||||
case 'lte':
|
||||
return {
|
||||
sql: `${objectNameSingular}.${key} <= :${key}${uuid}`,
|
||||
params: { [`${key}${uuid}`]: value },
|
||||
};
|
||||
case 'in':
|
||||
return {
|
||||
sql: `${objectNameSingular}.${key} IN (:...${key}${uuid})`,
|
||||
params: { [`${key}${uuid}`]: value },
|
||||
};
|
||||
case 'is':
|
||||
return {
|
||||
sql: `${objectNameSingular}.${key} IS ${value === 'NULL' ? 'NULL' : 'NOT NULL'}`,
|
||||
params: {},
|
||||
};
|
||||
case 'like':
|
||||
return {
|
||||
sql: `${objectNameSingular}.${key} LIKE :${key}${uuid}`,
|
||||
params: { [`${key}${uuid}`]: `${value}` },
|
||||
};
|
||||
case 'ilike':
|
||||
return {
|
||||
sql: `${objectNameSingular}.${key} ILIKE :${key}${uuid}`,
|
||||
params: { [`${key}${uuid}`]: `${value}` },
|
||||
};
|
||||
case 'startsWith':
|
||||
return {
|
||||
sql: `${objectNameSingular}.${key} LIKE :${key}${uuid}`,
|
||||
params: { [`${key}${uuid}`]: `${value}` },
|
||||
};
|
||||
case 'endsWith':
|
||||
return {
|
||||
sql: `${objectNameSingular}.${key} LIKE :${key}${uuid}`,
|
||||
params: { [`${key}${uuid}`]: `${value}` },
|
||||
};
|
||||
default:
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Operator "${operator}" is not supported`,
|
||||
GraphqlQueryRunnerExceptionCode.UNSUPPORTED_OPERATOR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private parseCompositeFieldForFilter(
|
||||
queryBuilder: WhereExpressionBuilder,
|
||||
fieldMetadata: FieldMetadataInterface,
|
||||
objectNameSingular: string,
|
||||
fieldValue: any,
|
||||
isNegated: boolean,
|
||||
): FindOptionsWhere<ObjectLiteral> {
|
||||
isFirst = false,
|
||||
): void {
|
||||
const compositeType = compositeTypeDefinitions.get(
|
||||
fieldMetadata.type as CompositeFieldMetadataType,
|
||||
);
|
||||
@@ -69,34 +158,36 @@ export class GraphqlQueryFilterFieldParser {
|
||||
);
|
||||
}
|
||||
|
||||
return Object.entries(fieldValue).reduce(
|
||||
(result, [subFieldKey, subFieldValue]) => {
|
||||
const subFieldMetadata = compositeType.properties.find(
|
||||
(property) => property.name === subFieldKey,
|
||||
Object.entries(fieldValue).map(([subFieldKey, subFieldFilter], index) => {
|
||||
const subFieldMetadata = compositeType.properties.find(
|
||||
(property) => property.name === subFieldKey,
|
||||
);
|
||||
|
||||
if (!subFieldMetadata) {
|
||||
throw new Error(
|
||||
`Sub field metadata not found for composite type: ${fieldMetadata.type}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!subFieldMetadata) {
|
||||
throw new Error(
|
||||
`Sub field metadata not found for composite type: ${fieldMetadata.type}`,
|
||||
);
|
||||
}
|
||||
const fullFieldName = `${fieldMetadata.name}${capitalize(subFieldKey)}`;
|
||||
|
||||
const fullFieldName = `${fieldMetadata.name}${capitalize(subFieldKey)}`;
|
||||
const [[operator, value]] = Object.entries(
|
||||
subFieldFilter as Record<string, any>,
|
||||
);
|
||||
|
||||
if (isPlainObject(subFieldValue)) {
|
||||
result[fullFieldName] = this.operatorParser.parseOperator(
|
||||
subFieldValue,
|
||||
isNegated,
|
||||
);
|
||||
} else {
|
||||
result[fullFieldName] = isNegated
|
||||
? Not(subFieldValue)
|
||||
: subFieldValue;
|
||||
}
|
||||
const { sql, params } = this.computeWhereConditionParts(
|
||||
fieldMetadata,
|
||||
operator,
|
||||
objectNameSingular,
|
||||
fullFieldName,
|
||||
value,
|
||||
);
|
||||
|
||||
return result;
|
||||
},
|
||||
{} as FindOptionsWhere<ObjectLiteral>,
|
||||
);
|
||||
if (isFirst && index === 0) {
|
||||
queryBuilder.where(sql, params);
|
||||
}
|
||||
|
||||
queryBuilder.andWhere(sql, params);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
import {
|
||||
FindOperator,
|
||||
ILike,
|
||||
In,
|
||||
IsNull,
|
||||
LessThan,
|
||||
LessThanOrEqual,
|
||||
Like,
|
||||
MoreThan,
|
||||
MoreThanOrEqual,
|
||||
Not,
|
||||
} from 'typeorm';
|
||||
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
|
||||
export class GraphqlQueryFilterOperatorParser {
|
||||
private operatorMap: { [key: string]: (value: any) => FindOperator<any> };
|
||||
|
||||
constructor() {
|
||||
this.operatorMap = {
|
||||
eq: (value: any) => value,
|
||||
neq: (value: any) => Not(value),
|
||||
gt: (value: any) => MoreThan(value),
|
||||
gte: (value: any) => MoreThanOrEqual(value),
|
||||
lt: (value: any) => LessThan(value),
|
||||
lte: (value: any) => LessThanOrEqual(value),
|
||||
in: (value: any) => In(value),
|
||||
is: (value: any) => {
|
||||
if (value === 'NULL') {
|
||||
return IsNull();
|
||||
} else if (value === 'NOT_NULL') {
|
||||
return Not(IsNull());
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
like: (value: string) => Like(`%${value}%`),
|
||||
ilike: (value: string) => ILike(`%${value}%`),
|
||||
startsWith: (value: string) => ILike(`${value}%`),
|
||||
endsWith: (value: string) => ILike(`%${value}`),
|
||||
};
|
||||
}
|
||||
|
||||
public parseOperator(
|
||||
operatorObj: Record<string, any>,
|
||||
isNegated: boolean,
|
||||
): FindOperator<any> {
|
||||
const [[operator, value]] = Object.entries(operatorObj);
|
||||
|
||||
if (operator in this.operatorMap) {
|
||||
const operatorFunction = this.operatorMap[operator];
|
||||
|
||||
return isNegated ? Not(operatorFunction(value)) : operatorFunction(value);
|
||||
}
|
||||
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Operator "${operator}" is not supported`,
|
||||
GraphqlQueryRunnerExceptionCode.UNSUPPORTED_OPERATOR,
|
||||
);
|
||||
}
|
||||
}
|
||||
+15
-28
@@ -1,5 +1,3 @@
|
||||
import { FindOptionsOrderValue } from 'typeorm';
|
||||
|
||||
import {
|
||||
OrderByDirection,
|
||||
RecordOrderBy,
|
||||
@@ -24,8 +22,9 @@ export class GraphqlQueryOrderFieldParser {
|
||||
|
||||
parse(
|
||||
orderBy: RecordOrderBy,
|
||||
objectNameSingular: string,
|
||||
isForwardPagination = true,
|
||||
): Record<string, FindOptionsOrderValue> {
|
||||
): Record<string, string> {
|
||||
return orderBy.reduce(
|
||||
(acc, item) => {
|
||||
Object.entries(item).forEach(([key, value]) => {
|
||||
@@ -42,29 +41,29 @@ export class GraphqlQueryOrderFieldParser {
|
||||
const compositeOrder = this.parseCompositeFieldForOrder(
|
||||
fieldMetadata,
|
||||
value,
|
||||
objectNameSingular,
|
||||
isForwardPagination,
|
||||
);
|
||||
|
||||
Object.assign(acc, compositeOrder);
|
||||
} else {
|
||||
acc[key] = this.convertOrderByToFindOptionsOrder(
|
||||
value,
|
||||
isForwardPagination,
|
||||
);
|
||||
acc[`"${objectNameSingular}"."${key}"`] =
|
||||
this.convertOrderByToFindOptionsOrder(value, isForwardPagination);
|
||||
}
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, FindOptionsOrderValue>,
|
||||
{} as Record<string, string>,
|
||||
);
|
||||
}
|
||||
|
||||
private parseCompositeFieldForOrder(
|
||||
fieldMetadata: FieldMetadataInterface,
|
||||
value: any,
|
||||
objectNameSingular: string,
|
||||
isForwardPagination = true,
|
||||
): Record<string, FindOptionsOrderValue> {
|
||||
): Record<string, string> {
|
||||
const compositeType = compositeTypeDefinitions.get(
|
||||
fieldMetadata.type as CompositeFieldMetadataType,
|
||||
);
|
||||
@@ -87,7 +86,7 @@ export class GraphqlQueryOrderFieldParser {
|
||||
);
|
||||
}
|
||||
|
||||
const fullFieldName = `${fieldMetadata.name}${capitalize(subFieldKey)}`;
|
||||
const fullFieldName = `"${objectNameSingular}"."${fieldMetadata.name}${capitalize(subFieldKey)}"`;
|
||||
|
||||
if (!this.isOrderByDirection(subFieldValue)) {
|
||||
throw new Error(
|
||||
@@ -101,35 +100,23 @@ export class GraphqlQueryOrderFieldParser {
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, FindOptionsOrderValue>,
|
||||
{} as Record<string, string>,
|
||||
);
|
||||
}
|
||||
|
||||
private convertOrderByToFindOptionsOrder(
|
||||
direction: OrderByDirection,
|
||||
isForwardPagination = true,
|
||||
): FindOptionsOrderValue {
|
||||
): string {
|
||||
switch (direction) {
|
||||
case OrderByDirection.AscNullsFirst:
|
||||
return {
|
||||
direction: isForwardPagination ? 'ASC' : 'DESC',
|
||||
nulls: 'FIRST',
|
||||
};
|
||||
return `${isForwardPagination ? 'ASC' : 'DESC'} NULLS FIRST`;
|
||||
case OrderByDirection.AscNullsLast:
|
||||
return {
|
||||
direction: isForwardPagination ? 'ASC' : 'DESC',
|
||||
nulls: 'LAST',
|
||||
};
|
||||
return `${isForwardPagination ? 'ASC' : 'DESC'} NULLS LAST`;
|
||||
case OrderByDirection.DescNullsFirst:
|
||||
return {
|
||||
direction: isForwardPagination ? 'DESC' : 'ASC',
|
||||
nulls: 'FIRST',
|
||||
};
|
||||
return `${isForwardPagination ? 'DESC' : 'ASC'} NULLS FIRST`;
|
||||
case OrderByDirection.DescNullsLast:
|
||||
return {
|
||||
direction: isForwardPagination ? 'DESC' : 'ASC',
|
||||
nulls: 'LAST',
|
||||
};
|
||||
return `${isForwardPagination ? 'DESC' : 'ASC'} NULLS LAST`;
|
||||
default:
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Invalid direction: ${direction}`,
|
||||
|
||||
+42
-24
@@ -1,7 +1,8 @@
|
||||
import {
|
||||
FindOptionsOrderValue,
|
||||
FindOptionsWhere,
|
||||
ObjectLiteral,
|
||||
OrderByCondition,
|
||||
SelectQueryBuilder,
|
||||
} from 'typeorm';
|
||||
|
||||
import {
|
||||
@@ -10,8 +11,8 @@ import {
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
import { GraphqlQueryFilterConditionParser as GraphqlQueryFilterParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser';
|
||||
import { GraphqlQueryOrderFieldParser as GraphqlQueryOrderParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser';
|
||||
import { GraphqlQueryFilterConditionParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser';
|
||||
import { GraphqlQueryOrderFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser';
|
||||
import { GraphqlQuerySelectedFieldsParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||
import {
|
||||
FieldMetadataMap,
|
||||
@@ -21,6 +22,8 @@ import {
|
||||
export class GraphqlQueryParser {
|
||||
private fieldMetadataMap: FieldMetadataMap;
|
||||
private objectMetadataMap: ObjectMetadataMap;
|
||||
private filterConditionParser: GraphqlQueryFilterConditionParser;
|
||||
private orderFieldParser: GraphqlQueryOrderFieldParser;
|
||||
|
||||
constructor(
|
||||
fieldMetadataMap: FieldMetadataMap,
|
||||
@@ -28,33 +31,44 @@ export class GraphqlQueryParser {
|
||||
) {
|
||||
this.objectMetadataMap = objectMetadataMap;
|
||||
this.fieldMetadataMap = fieldMetadataMap;
|
||||
}
|
||||
|
||||
parseFilter(recordFilter: RecordFilter): {
|
||||
parsedFilters:
|
||||
| FindOptionsWhere<ObjectLiteral>
|
||||
| FindOptionsWhere<ObjectLiteral>[];
|
||||
withDeleted: boolean;
|
||||
} {
|
||||
const graphqlQueryFilterParser = new GraphqlQueryFilterParser(
|
||||
this.filterConditionParser = new GraphqlQueryFilterConditionParser(
|
||||
this.fieldMetadataMap,
|
||||
);
|
||||
this.orderFieldParser = new GraphqlQueryOrderFieldParser(
|
||||
this.fieldMetadataMap,
|
||||
);
|
||||
}
|
||||
|
||||
const parsedFilter = graphqlQueryFilterParser.parse(recordFilter);
|
||||
applyFilterToBuilder(
|
||||
queryBuilder: SelectQueryBuilder<any>,
|
||||
objectNameSingular: string,
|
||||
recordFilter: RecordFilter,
|
||||
): SelectQueryBuilder<any> {
|
||||
return this.filterConditionParser.parse(
|
||||
queryBuilder,
|
||||
objectNameSingular,
|
||||
recordFilter,
|
||||
);
|
||||
}
|
||||
|
||||
const hasDeletedAtFilter = this.checkForDeletedAtFilter(parsedFilter);
|
||||
applyDeletedAtToBuilder(
|
||||
queryBuilder: SelectQueryBuilder<any>,
|
||||
recordFilter: RecordFilter,
|
||||
): SelectQueryBuilder<any> {
|
||||
if (this.checkForDeletedAtFilter(recordFilter)) {
|
||||
queryBuilder.withDeleted();
|
||||
}
|
||||
|
||||
return {
|
||||
parsedFilters: parsedFilter,
|
||||
withDeleted: hasDeletedAtFilter,
|
||||
};
|
||||
return queryBuilder;
|
||||
}
|
||||
|
||||
private checkForDeletedAtFilter(
|
||||
filter: FindOptionsWhere<ObjectLiteral> | FindOptionsWhere<ObjectLiteral>[],
|
||||
): boolean {
|
||||
if (Array.isArray(filter)) {
|
||||
return filter.some(this.checkForDeletedAtFilter);
|
||||
return filter.some((subFilter) =>
|
||||
this.checkForDeletedAtFilter(subFilter),
|
||||
);
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(filter)) {
|
||||
@@ -74,15 +88,19 @@ export class GraphqlQueryParser {
|
||||
return false;
|
||||
}
|
||||
|
||||
parseOrder(
|
||||
applyOrderToBuilder(
|
||||
queryBuilder: SelectQueryBuilder<any>,
|
||||
orderBy: RecordOrderBy,
|
||||
objectNameSingular: string,
|
||||
isForwardPagination = true,
|
||||
): Record<string, FindOptionsOrderValue> {
|
||||
const graphqlQueryOrderParser = new GraphqlQueryOrderParser(
|
||||
this.fieldMetadataMap,
|
||||
): SelectQueryBuilder<any> {
|
||||
const parsedOrderBys = this.orderFieldParser.parse(
|
||||
orderBy,
|
||||
objectNameSingular,
|
||||
isForwardPagination,
|
||||
);
|
||||
|
||||
return graphqlQueryOrderParser.parse(orderBy, isForwardPagination);
|
||||
return queryBuilder.orderBy(parsedOrderBys as OrderByCondition);
|
||||
}
|
||||
|
||||
parseSelectedFields(
|
||||
|
||||
Reference in New Issue
Block a user