@loomcore/api 0.2.68 → 0.2.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/dist/databases/models/classified-filter-key.d.ts +8 -0
- package/dist/databases/models/classified-filter-key.js +1 -0
- package/dist/databases/models/index.d.ts +2 -0
- package/dist/databases/models/index.js +2 -0
- package/dist/databases/models/join-filter.interface.d.ts +8 -0
- package/dist/databases/models/join-filter.interface.js +1 -0
- package/dist/databases/mongo-db/queries/mongo-get-by-id.query.js +11 -4
- package/dist/databases/mongo-db/queries/mongo-get.query.js +11 -3
- package/dist/databases/mongo-db/utils/build-join-match.util.d.ts +4 -0
- package/dist/databases/mongo-db/utils/build-join-match.util.js +192 -0
- package/dist/databases/mongo-db/utils/build-no-sql-match.util.js +33 -7
- package/dist/databases/mongo-db/utils/convert-operations-to-pipeline.util.js +15 -5
- package/dist/databases/operations/index.d.ts +1 -0
- package/dist/databases/operations/index.js +1 -0
- package/dist/databases/operations/join-operation.d.ts +4 -0
- package/dist/databases/operations/join-operation.js +1 -0
- package/dist/databases/postgres/commands/postgres-batch-update.command.js +2 -3
- package/dist/databases/postgres/commands/postgres-delete-many.command.js +1 -1
- package/dist/databases/postgres/commands/postgres-full-update-by-id.command.js +1 -3
- package/dist/databases/postgres/commands/postgres-partial-update-by-id.command.js +1 -3
- package/dist/databases/postgres/commands/postgres-update.command.js +4 -7
- package/dist/databases/postgres/queries/postgres-find-one.query.js +2 -2
- package/dist/databases/postgres/queries/postgres-find.query.js +2 -2
- package/dist/databases/postgres/queries/postgres-get-all.query.js +1 -3
- package/dist/databases/postgres/queries/postgres-get-by-id.query.js +2 -5
- package/dist/databases/postgres/queries/postgres-get.query.js +6 -11
- package/dist/databases/postgres/utils/build-count-query.d.ts +6 -1
- package/dist/databases/postgres/utils/build-count-query.js +11 -3
- package/dist/databases/postgres/utils/build-join-clauses.d.ts +2 -4
- package/dist/databases/postgres/utils/build-join-clauses.js +28 -35
- package/dist/databases/postgres/utils/build-join-where.d.ts +9 -0
- package/dist/databases/postgres/utils/build-join-where.js +136 -0
- package/dist/databases/postgres/utils/build-order-by-clause.d.ts +1 -4
- package/dist/databases/postgres/utils/build-order-by-clause.js +2 -3
- package/dist/databases/postgres/utils/build-select-clause.js +2 -4
- package/dist/databases/postgres/utils/build-where-clause.d.ts +2 -1
- package/dist/databases/postgres/utils/build-where-clause.js +18 -57
- package/dist/databases/utils/is-join-operation.util.d.ts +3 -0
- package/dist/databases/utils/is-join-operation.util.js +6 -0
- package/dist/databases/utils/nullish-filter-value.d.ts +9 -0
- package/dist/databases/utils/nullish-filter-value.js +15 -0
- package/dist/databases/utils/resolve-join-filter.d.ts +13 -0
- package/dist/databases/utils/resolve-join-filter.js +75 -0
- package/package.json +1 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { readFilterValue } from '../../utils/nullish-filter-value.js';
|
|
2
|
+
import { findJoinOperation, parentAliasOf, } from '../../utils/resolve-join-filter.js';
|
|
3
|
+
import { collectOuterOneToOneAliases } from './build-join-clauses.js';
|
|
4
|
+
import { toPostgresStoreName, toSnakeCase } from './convert-keys.util.js';
|
|
5
|
+
export function toSqlColumnName(column) {
|
|
6
|
+
if (column.startsWith('_')) {
|
|
7
|
+
return column;
|
|
8
|
+
}
|
|
9
|
+
if (column === 'id') {
|
|
10
|
+
return '_id';
|
|
11
|
+
}
|
|
12
|
+
return toSnakeCase(column);
|
|
13
|
+
}
|
|
14
|
+
function quoteIdent(value) {
|
|
15
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
16
|
+
}
|
|
17
|
+
export function appendFilterConditions(qualifiedKey, value, conditions, values, paramIndex) {
|
|
18
|
+
if (value.eq !== undefined) {
|
|
19
|
+
const comparison = readFilterValue(value.eq);
|
|
20
|
+
if (comparison.kind === 'value') {
|
|
21
|
+
conditions.push(`(${qualifiedKey} = $${paramIndex.current} OR (${qualifiedKey} IS NULL AND $${paramIndex.current} IS NULL))`);
|
|
22
|
+
values.push(comparison.value);
|
|
23
|
+
paramIndex.current++;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
conditions.push(`${qualifiedKey} IS NULL`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (value.ne !== undefined) {
|
|
30
|
+
const comparison = readFilterValue(value.ne);
|
|
31
|
+
if (comparison.kind === 'value') {
|
|
32
|
+
conditions.push(`(${qualifiedKey} != $${paramIndex.current} OR (${qualifiedKey} IS NOT NULL AND $${paramIndex.current} IS NULL) OR (${qualifiedKey} IS NULL AND $${paramIndex.current} IS NOT NULL))`);
|
|
33
|
+
values.push(comparison.value);
|
|
34
|
+
paramIndex.current++;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
conditions.push(`${qualifiedKey} IS NOT NULL`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (value.in !== undefined && Array.isArray(value.in)) {
|
|
41
|
+
const placeholders = value.in.map(() => `$${paramIndex.current++}`).join(', ');
|
|
42
|
+
conditions.push(`${qualifiedKey} IN (${placeholders})`);
|
|
43
|
+
values.push(...value.in);
|
|
44
|
+
}
|
|
45
|
+
if (value.gte !== undefined) {
|
|
46
|
+
conditions.push(`${qualifiedKey} >= $${paramIndex.current}`);
|
|
47
|
+
values.push(value.gte);
|
|
48
|
+
paramIndex.current++;
|
|
49
|
+
}
|
|
50
|
+
if (value.lte !== undefined) {
|
|
51
|
+
conditions.push(`${qualifiedKey} <= $${paramIndex.current}`);
|
|
52
|
+
values.push(value.lte);
|
|
53
|
+
paramIndex.current++;
|
|
54
|
+
}
|
|
55
|
+
if (value.gt !== undefined) {
|
|
56
|
+
conditions.push(`${qualifiedKey} > $${paramIndex.current}`);
|
|
57
|
+
values.push(value.gt);
|
|
58
|
+
paramIndex.current++;
|
|
59
|
+
}
|
|
60
|
+
if (value.lt !== undefined) {
|
|
61
|
+
conditions.push(`${qualifiedKey} < $${paramIndex.current}`);
|
|
62
|
+
values.push(value.lt);
|
|
63
|
+
paramIndex.current++;
|
|
64
|
+
}
|
|
65
|
+
if (value.contains !== undefined) {
|
|
66
|
+
conditions.push(`LOWER(${qualifiedKey}) LIKE LOWER($${paramIndex.current})`);
|
|
67
|
+
values.push(`%${value.contains}%`);
|
|
68
|
+
paramIndex.current++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function qualifyLocalField(localField, mainTableName) {
|
|
72
|
+
const dot = localField.indexOf('.');
|
|
73
|
+
if (dot === -1) {
|
|
74
|
+
return mainTableName ? `${quoteIdent(mainTableName)}.${quoteIdent(localField)}` : quoteIdent(localField);
|
|
75
|
+
}
|
|
76
|
+
const alias = localField.slice(0, dot);
|
|
77
|
+
const column = localField.slice(dot + 1);
|
|
78
|
+
return `${quoteIdent(alias)}.${quoteIdent(column)}`;
|
|
79
|
+
}
|
|
80
|
+
function ancestorChain(target, operations, mainTableName, outerAliases) {
|
|
81
|
+
const chain = [target];
|
|
82
|
+
let current = target;
|
|
83
|
+
const seen = new Set([target.as]);
|
|
84
|
+
while (true) {
|
|
85
|
+
const parentAlias = parentAliasOf(current.localField);
|
|
86
|
+
if (parentAlias === null || parentAlias === mainTableName || outerAliases.has(parentAlias)) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (seen.has(parentAlias)) {
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
const parent = findJoinOperation(operations, parentAlias);
|
|
93
|
+
if (!parent) {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
seen.add(parent.as);
|
|
97
|
+
chain.unshift(parent);
|
|
98
|
+
current = parent;
|
|
99
|
+
}
|
|
100
|
+
return chain;
|
|
101
|
+
}
|
|
102
|
+
function existsSql(target, qualifiedColumn, filter, operations, mainTableName, outerAliases, values, paramIndex) {
|
|
103
|
+
const chain = ancestorChain(target, operations, mainTableName, outerAliases);
|
|
104
|
+
const first = chain[0];
|
|
105
|
+
let sql = `EXISTS (SELECT 1 FROM ${quoteIdent(toPostgresStoreName(first.from))} AS ${quoteIdent(first.as)}`;
|
|
106
|
+
for (let index = 1; index < chain.length; index++) {
|
|
107
|
+
const child = chain[index];
|
|
108
|
+
const fromTable = quoteIdent(toPostgresStoreName(child.from));
|
|
109
|
+
const childAlias = quoteIdent(child.as);
|
|
110
|
+
const onLeft = qualifyLocalField(child.localField, mainTableName);
|
|
111
|
+
sql += ` INNER JOIN ${fromTable} AS ${childAlias} ON ${onLeft} = ${childAlias}.${quoteIdent(child.foreignField)}`;
|
|
112
|
+
}
|
|
113
|
+
const conditions = [
|
|
114
|
+
`${qualifyLocalField(first.localField, mainTableName)} = ${quoteIdent(first.as)}.${quoteIdent(first.foreignField)}`,
|
|
115
|
+
];
|
|
116
|
+
appendFilterConditions(qualifiedColumn, filter, conditions, values, paramIndex);
|
|
117
|
+
sql += ` WHERE ${conditions.join(' AND ')})`;
|
|
118
|
+
return sql;
|
|
119
|
+
}
|
|
120
|
+
export function joinFilterSql(joinFilter, operations, mainTableName, values, paramIndex) {
|
|
121
|
+
const target = findJoinOperation(operations, joinFilter.aliases[joinFilter.aliases.length - 1]);
|
|
122
|
+
if (!target) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const outerAliases = collectOuterOneToOneAliases(operations, mainTableName);
|
|
126
|
+
const qualified = `${quoteIdent(target.as)}.${quoteIdent(toSqlColumnName(joinFilter.column))}`;
|
|
127
|
+
if (!joinFilter.crossesMany && outerAliases.has(target.as)) {
|
|
128
|
+
const conditions = [];
|
|
129
|
+
appendFilterConditions(qualified, joinFilter.filter, conditions, values, paramIndex);
|
|
130
|
+
if (joinFilter.filter.ne !== undefined) {
|
|
131
|
+
conditions.push(`${quoteIdent(target.as)}."_id" IS NOT NULL`);
|
|
132
|
+
}
|
|
133
|
+
return conditions.join(' AND ');
|
|
134
|
+
}
|
|
135
|
+
return existsSql(target, qualified, joinFilter.filter, operations, mainTableName, outerAliases, values, paramIndex);
|
|
136
|
+
}
|
|
@@ -1,5 +1,2 @@
|
|
|
1
1
|
import { IQueryOptions } from '@loomcore/common/models';
|
|
2
|
-
export
|
|
3
|
-
tablePrefix?: string;
|
|
4
|
-
}
|
|
5
|
-
export declare function buildOrderByClause(queryObject: IQueryOptions, options?: BuildOrderByClauseOptions): string;
|
|
2
|
+
export declare function buildOrderByClause(queryObject: IQueryOptions, tablePrefix: string): string;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
export function buildOrderByClause(queryObject,
|
|
1
|
+
export function buildOrderByClause(queryObject, tablePrefix) {
|
|
2
2
|
if (!queryObject.orderBy) {
|
|
3
3
|
return '';
|
|
4
4
|
}
|
|
5
5
|
const orderBy = queryObject.orderBy;
|
|
6
6
|
const sortDirection = queryObject.sortDirection || 'asc';
|
|
7
|
-
|
|
8
|
-
return `ORDER BY ${qualified} ${sortDirection}`;
|
|
7
|
+
return `ORDER BY "${tablePrefix}"."${orderBy}" ${sortDirection}`;
|
|
9
8
|
}
|
|
@@ -2,6 +2,7 @@ import { LeftJoin } from '../../operations/left-join.operation.js';
|
|
|
2
2
|
import { InnerJoin } from '../../operations/inner-join.operation.js';
|
|
3
3
|
import { LeftJoinMany } from '../../operations/left-join-many.operation.js';
|
|
4
4
|
import { toPostgresStoreName } from './convert-keys.util.js';
|
|
5
|
+
import { isJoinOperation } from '../../utils/is-join-operation.util.js';
|
|
5
6
|
const PK = '_id';
|
|
6
7
|
export async function getTableColumns(client, tableName) {
|
|
7
8
|
const storeName = toPostgresStoreName(tableName);
|
|
@@ -172,10 +173,7 @@ async function buildJoinDataObject(client, operations, mainTableName) {
|
|
|
172
173
|
return jsonbBuildObject(entries);
|
|
173
174
|
}
|
|
174
175
|
export async function buildSelectClause(client, mainTableName, operations) {
|
|
175
|
-
const
|
|
176
|
-
const innerJoinOperations = operations.filter(op => op instanceof InnerJoin);
|
|
177
|
-
const leftJoinManyOperations = operations.filter(op => op instanceof LeftJoinMany);
|
|
178
|
-
const hasJoins = leftJoinOperations.length > 0 || innerJoinOperations.length > 0 || leftJoinManyOperations.length > 0;
|
|
176
|
+
const hasJoins = operations.some(isJoinOperation);
|
|
179
177
|
if (!hasJoins) {
|
|
180
178
|
return '*';
|
|
181
179
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IQueryOptions } from '@loomcore/common/models';
|
|
2
|
-
|
|
2
|
+
import type { Operation } from '../../operations/operation.js';
|
|
3
|
+
export declare function buildWhereClause(queryObject: IQueryOptions, tablePrefix: string, values?: any[], operations?: Operation[]): {
|
|
3
4
|
whereClause: string;
|
|
4
5
|
values: any[];
|
|
5
6
|
};
|
|
@@ -1,63 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { splitFiltersByType } from '../../utils/resolve-join-filter.js';
|
|
2
|
+
import { appendFilterConditions, joinFilterSql, toSqlColumnName } from './build-join-where.js';
|
|
3
|
+
export function buildWhereClause(queryObject, tablePrefix, values = [], operations) {
|
|
4
|
+
const useJoinFilters = operations !== undefined && operations.length > 0;
|
|
5
|
+
const { rootOptions, joinFilters } = useJoinFilters
|
|
6
|
+
? splitFiltersByType(queryObject, operations, tablePrefix)
|
|
7
|
+
: { rootOptions: queryObject, joinFilters: [] };
|
|
8
|
+
const filters = rootOptions.filters || {};
|
|
4
9
|
const conditions = [];
|
|
5
|
-
|
|
6
|
-
const qualifyColumn = (columnName) => {
|
|
7
|
-
return tablePrefix ? `"${tablePrefix}"."${columnName}"` : `"${columnName}"`;
|
|
8
|
-
};
|
|
10
|
+
const paramIndex = { current: values.length + 1 };
|
|
9
11
|
for (const [key, value] of Object.entries(filters)) {
|
|
10
12
|
if (value) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
const qualifiedKey = qualifyColumn(snakeKey);
|
|
22
|
-
if (value.eq !== undefined) {
|
|
23
|
-
conditions.push(`(${qualifiedKey} = $${paramIndex} OR (${qualifiedKey} IS NULL AND $${paramIndex} IS NULL))`);
|
|
24
|
-
values.push(value.eq);
|
|
25
|
-
paramIndex++;
|
|
26
|
-
}
|
|
27
|
-
if (value.ne !== undefined) {
|
|
28
|
-
conditions.push(`(${qualifiedKey} != $${paramIndex} OR (${qualifiedKey} IS NOT NULL AND $${paramIndex} IS NULL) OR (${qualifiedKey} IS NULL AND $${paramIndex} IS NOT NULL))`);
|
|
29
|
-
values.push(value.ne);
|
|
30
|
-
paramIndex++;
|
|
31
|
-
}
|
|
32
|
-
if (value.in !== undefined && Array.isArray(value.in)) {
|
|
33
|
-
const placeholders = value.in.map(() => `$${paramIndex++}`).join(', ');
|
|
34
|
-
conditions.push(`${qualifiedKey} IN (${placeholders})`);
|
|
35
|
-
values.push(...value.in);
|
|
36
|
-
}
|
|
37
|
-
if (value.gte !== undefined) {
|
|
38
|
-
conditions.push(`${qualifiedKey} >= $${paramIndex}`);
|
|
39
|
-
values.push(value.gte);
|
|
40
|
-
paramIndex++;
|
|
41
|
-
}
|
|
42
|
-
if (value.lte !== undefined) {
|
|
43
|
-
conditions.push(`${qualifiedKey} <= $${paramIndex}`);
|
|
44
|
-
values.push(value.lte);
|
|
45
|
-
paramIndex++;
|
|
46
|
-
}
|
|
47
|
-
if (value.gt !== undefined) {
|
|
48
|
-
conditions.push(`${qualifiedKey} > $${paramIndex}`);
|
|
49
|
-
values.push(value.gt);
|
|
50
|
-
paramIndex++;
|
|
51
|
-
}
|
|
52
|
-
if (value.lt !== undefined) {
|
|
53
|
-
conditions.push(`${qualifiedKey} < $${paramIndex}`);
|
|
54
|
-
values.push(value.lt);
|
|
55
|
-
paramIndex++;
|
|
56
|
-
}
|
|
57
|
-
if (value.contains !== undefined) {
|
|
58
|
-
conditions.push(`LOWER(${qualifiedKey}) LIKE LOWER($${paramIndex})`);
|
|
59
|
-
values.push(`%${value.contains}%`);
|
|
60
|
-
paramIndex++;
|
|
13
|
+
const qualifiedKey = `"${tablePrefix}"."${toSqlColumnName(key)}"`;
|
|
14
|
+
appendFilterConditions(qualifiedKey, value, conditions, values, paramIndex);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (useJoinFilters) {
|
|
18
|
+
for (const joinFilter of joinFilters) {
|
|
19
|
+
const predicate = joinFilterSql(joinFilter, operations, tablePrefix, values, paramIndex);
|
|
20
|
+
if (predicate) {
|
|
21
|
+
conditions.push(predicate);
|
|
61
22
|
}
|
|
62
23
|
}
|
|
63
24
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { InnerJoin } from "../operations/inner-join.operation.js";
|
|
2
|
+
import { LeftJoin } from "../operations/left-join.operation.js";
|
|
3
|
+
import { LeftJoinMany } from "../operations/left-join-many.operation.js";
|
|
4
|
+
export function isJoinOperation(operation) {
|
|
5
|
+
return operation instanceof LeftJoin || operation instanceof InnerJoin || operation instanceof LeftJoinMany;
|
|
6
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function readFilterValue(raw) {
|
|
2
|
+
if (typeof raw === 'string' && raw.startsWith('"') && raw.endsWith('"')) {
|
|
3
|
+
const inner = raw.slice(1, -1);
|
|
4
|
+
if (inner === 'null' || inner === 'undefined') {
|
|
5
|
+
return { kind: 'value', value: inner };
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
if (raw === null || raw === 'null') {
|
|
9
|
+
return { kind: 'null' };
|
|
10
|
+
}
|
|
11
|
+
if (raw === 'undefined') {
|
|
12
|
+
return { kind: 'undefined' };
|
|
13
|
+
}
|
|
14
|
+
return { kind: 'value', value: raw };
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IQueryOptions } from '@loomcore/common/models';
|
|
2
|
+
import type { ClassifiedFilterKey } from '../models/classified-filter-key.js';
|
|
3
|
+
import type { Operation } from '../operations/operation.js';
|
|
4
|
+
import type { JoinOperation } from '../operations/join-operation.js';
|
|
5
|
+
import { JoinFilter } from '../models/join-filter.interface.js';
|
|
6
|
+
export declare function parentAliasOf(localField: string): string | null;
|
|
7
|
+
export declare function findJoinOperation(operations: Operation[], alias: string): JoinOperation | undefined;
|
|
8
|
+
export declare function logicalParentAlias(operation: JoinOperation, operations: Operation[], mainTableName: string): string | null;
|
|
9
|
+
export declare function classifyFilterKey(key: string, operations: Operation[], mainTableName: string): ClassifiedFilterKey;
|
|
10
|
+
export declare function splitFiltersByType(queryOptions: IQueryOptions, operations: Operation[], mainTableName: string): {
|
|
11
|
+
rootOptions: IQueryOptions;
|
|
12
|
+
joinFilters: JoinFilter[];
|
|
13
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { BadRequestError } from '../../errors/index.js';
|
|
2
|
+
import { LeftJoinMany } from '../operations/left-join-many.operation.js';
|
|
3
|
+
import { isJoinOperation } from './is-join-operation.util.js';
|
|
4
|
+
export function parentAliasOf(localField) {
|
|
5
|
+
const dot = localField.indexOf('.');
|
|
6
|
+
if (dot === -1) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
return localField.slice(0, dot);
|
|
10
|
+
}
|
|
11
|
+
export function findJoinOperation(operations, alias) {
|
|
12
|
+
return operations.find((operation) => isJoinOperation(operation) && operation.as === alias);
|
|
13
|
+
}
|
|
14
|
+
export function logicalParentAlias(operation, operations, mainTableName) {
|
|
15
|
+
const rawParent = parentAliasOf(operation.localField);
|
|
16
|
+
if (rawParent === null || rawParent === mainTableName) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const parent = findJoinOperation(operations, rawParent);
|
|
20
|
+
return parent ? parent.as : null;
|
|
21
|
+
}
|
|
22
|
+
export function classifyFilterKey(key, operations, mainTableName) {
|
|
23
|
+
if (!key.includes('.') || operations.length === 0) {
|
|
24
|
+
return { type: 'root' };
|
|
25
|
+
}
|
|
26
|
+
const parts = key.split('.');
|
|
27
|
+
if (parts.some(part => part.length === 0)) {
|
|
28
|
+
return { type: 'root' };
|
|
29
|
+
}
|
|
30
|
+
if (!findJoinOperation(operations, parts[0])) {
|
|
31
|
+
return { type: 'root' };
|
|
32
|
+
}
|
|
33
|
+
const column = parts[parts.length - 1];
|
|
34
|
+
const aliases = parts.slice(0, -1);
|
|
35
|
+
for (let index = 0; index < aliases.length; index++) {
|
|
36
|
+
const alias = aliases[index];
|
|
37
|
+
const operation = findJoinOperation(operations, alias);
|
|
38
|
+
if (!operation) {
|
|
39
|
+
throw new BadRequestError(`Invalid join filter "${key}": "${alias}" is not a joined table`);
|
|
40
|
+
}
|
|
41
|
+
const expectedParent = index === 0 ? null : aliases[index - 1];
|
|
42
|
+
const actualParent = logicalParentAlias(operation, operations, mainTableName);
|
|
43
|
+
if (actualParent !== expectedParent) {
|
|
44
|
+
const where = actualParent ? `"${actualParent}"` : 'the root collection';
|
|
45
|
+
throw new BadRequestError(`Invalid join filter "${key}": "${alias}" is nested under ${where}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const crossesMany = aliases.some(alias => findJoinOperation(operations, alias) instanceof LeftJoinMany);
|
|
49
|
+
return { type: 'join', aliases, column, crossesMany };
|
|
50
|
+
}
|
|
51
|
+
export function splitFiltersByType(queryOptions, operations, mainTableName) {
|
|
52
|
+
const rootFilters = {};
|
|
53
|
+
const joinFilters = [];
|
|
54
|
+
for (const [key, filter] of Object.entries(queryOptions.filters ?? {})) {
|
|
55
|
+
if (!filter) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const classified = classifyFilterKey(key, operations, mainTableName);
|
|
59
|
+
if (classified.type === 'root') {
|
|
60
|
+
rootFilters[key] = filter;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
joinFilters.push({
|
|
64
|
+
key,
|
|
65
|
+
filter,
|
|
66
|
+
aliases: classified.aliases,
|
|
67
|
+
column: classified.column,
|
|
68
|
+
crossesMany: classified.crossesMany,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
rootOptions: { ...queryOptions, filters: rootFilters },
|
|
73
|
+
joinFilters,
|
|
74
|
+
};
|
|
75
|
+
}
|