@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,192 @@
|
|
|
1
|
+
import { entityUtils } from '@loomcore/common/utils';
|
|
2
|
+
import { ObjectId } from 'mongodb';
|
|
3
|
+
import { LeftJoinMany } from '../../operations/left-join-many.operation.js';
|
|
4
|
+
import { PROPERTIES_THAT_ARE_NOT_OBJECT_IDS } from '../../models/constants.js';
|
|
5
|
+
import { findJoinOperation } from '../../utils/resolve-join-filter.js';
|
|
6
|
+
function shouldConvertEqToObjectId(column) {
|
|
7
|
+
if (PROPERTIES_THAT_ARE_NOT_OBJECT_IDS.includes(column)) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
return column === '_id' || column.endsWith('Id');
|
|
11
|
+
}
|
|
12
|
+
function shouldConvertInToObjectIds(column) {
|
|
13
|
+
if (PROPERTIES_THAT_ARE_NOT_OBJECT_IDS.includes(column)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return column === '_id' || column.endsWith('Id') || column.endsWith('Ids');
|
|
17
|
+
}
|
|
18
|
+
function convertEqValue(column, value) {
|
|
19
|
+
if (shouldConvertEqToObjectId(column) && typeof value === 'string' && entityUtils.isValidObjectId(value)) {
|
|
20
|
+
return new ObjectId(value);
|
|
21
|
+
}
|
|
22
|
+
if (typeof value === 'string' && !isNaN(Number(value))) {
|
|
23
|
+
return Number(value);
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
function convertInValues(column, values) {
|
|
28
|
+
if (shouldConvertInToObjectIds(column)) {
|
|
29
|
+
const objectIds = values
|
|
30
|
+
.filter(val => typeof val === 'string' && entityUtils.isValidObjectId(val))
|
|
31
|
+
.map(val => new ObjectId(val));
|
|
32
|
+
if (objectIds.length === 0) {
|
|
33
|
+
return { values: [], dropped: true };
|
|
34
|
+
}
|
|
35
|
+
return { values: objectIds, dropped: false };
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
values: values.map(val => typeof val === 'string' && !isNaN(Number(val)) ? Number(val) : val),
|
|
39
|
+
dropped: false,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function compareExpression(fieldExpr, column, filter) {
|
|
43
|
+
const parts = [];
|
|
44
|
+
if (filter.eq !== undefined) {
|
|
45
|
+
parts.push({ $eq: [fieldExpr, convertEqValue(column, filter.eq)] });
|
|
46
|
+
}
|
|
47
|
+
if (filter.ne !== undefined) {
|
|
48
|
+
parts.push({ $ne: [fieldExpr, convertEqValue(column, filter.ne)] });
|
|
49
|
+
}
|
|
50
|
+
if (filter.in !== undefined && Array.isArray(filter.in)) {
|
|
51
|
+
const converted = convertInValues(column, filter.in);
|
|
52
|
+
if (!converted.dropped) {
|
|
53
|
+
parts.push({ $in: [fieldExpr, converted.values] });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (filter.gte !== undefined) {
|
|
57
|
+
parts.push({ $gte: [fieldExpr, filter.gte] });
|
|
58
|
+
}
|
|
59
|
+
if (filter.lte !== undefined) {
|
|
60
|
+
parts.push({ $lte: [fieldExpr, filter.lte] });
|
|
61
|
+
}
|
|
62
|
+
if (filter.gt !== undefined) {
|
|
63
|
+
parts.push({ $gt: [fieldExpr, filter.gt] });
|
|
64
|
+
}
|
|
65
|
+
if (filter.lt !== undefined) {
|
|
66
|
+
parts.push({ $lt: [fieldExpr, filter.lt] });
|
|
67
|
+
}
|
|
68
|
+
if (filter.contains !== undefined) {
|
|
69
|
+
parts.push({
|
|
70
|
+
$cond: [
|
|
71
|
+
{ $eq: [{ $type: fieldExpr }, 'string'] },
|
|
72
|
+
{ $regexMatch: { input: fieldExpr, regex: filter.contains, options: 'i' } },
|
|
73
|
+
false,
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (parts.length === 0) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
if (parts.length === 1) {
|
|
81
|
+
return parts[0];
|
|
82
|
+
}
|
|
83
|
+
return { $and: parts };
|
|
84
|
+
}
|
|
85
|
+
function scalarAssignment(column, filter) {
|
|
86
|
+
const ops = {};
|
|
87
|
+
if (filter.eq !== undefined) {
|
|
88
|
+
ops.$eq = convertEqValue(column, filter.eq);
|
|
89
|
+
}
|
|
90
|
+
if (filter.ne !== undefined) {
|
|
91
|
+
ops.$ne = convertEqValue(column, filter.ne);
|
|
92
|
+
}
|
|
93
|
+
if (filter.in !== undefined && Array.isArray(filter.in)) {
|
|
94
|
+
const converted = convertInValues(column, filter.in);
|
|
95
|
+
if (!converted.dropped) {
|
|
96
|
+
ops.$in = converted.values;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (filter.gte !== undefined) {
|
|
100
|
+
ops.$gte = filter.gte;
|
|
101
|
+
}
|
|
102
|
+
if (filter.lte !== undefined) {
|
|
103
|
+
ops.$lte = filter.lte;
|
|
104
|
+
}
|
|
105
|
+
if (filter.gt !== undefined) {
|
|
106
|
+
ops.$gt = filter.gt;
|
|
107
|
+
}
|
|
108
|
+
if (filter.lt !== undefined) {
|
|
109
|
+
ops.$lt = filter.lt;
|
|
110
|
+
}
|
|
111
|
+
if (filter.contains !== undefined) {
|
|
112
|
+
ops.$regex = filter.contains;
|
|
113
|
+
ops.$options = 'i';
|
|
114
|
+
}
|
|
115
|
+
const keys = Object.keys(ops);
|
|
116
|
+
if (keys.length === 0) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
if (keys.length === 1 && keys[0] === '$eq') {
|
|
120
|
+
return ops.$eq;
|
|
121
|
+
}
|
|
122
|
+
return ops;
|
|
123
|
+
}
|
|
124
|
+
function anyMatchExpression(joinFilter, operations) {
|
|
125
|
+
const walk = (scope, index) => {
|
|
126
|
+
const alias = joinFilter.aliases[index];
|
|
127
|
+
const operation = findJoinOperation(operations, alias);
|
|
128
|
+
if (!operation) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const here = scope ? `${scope}.${alias}` : `$_joinData.${alias}`;
|
|
132
|
+
const isLast = index === joinFilter.aliases.length - 1;
|
|
133
|
+
if (operation instanceof LeftJoinMany) {
|
|
134
|
+
const varName = `j${index}`;
|
|
135
|
+
const cond = isLast
|
|
136
|
+
? compareExpression(`$$${varName}.${joinFilter.column}`, joinFilter.column, joinFilter.filter)
|
|
137
|
+
: walk(`$$${varName}`, index + 1);
|
|
138
|
+
if (!cond) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
$gt: [
|
|
143
|
+
{
|
|
144
|
+
$size: {
|
|
145
|
+
$filter: {
|
|
146
|
+
input: { $ifNull: [here, []] },
|
|
147
|
+
as: varName,
|
|
148
|
+
cond,
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
0,
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (isLast) {
|
|
157
|
+
return compareExpression(`${here}.${joinFilter.column}`, joinFilter.column, joinFilter.filter);
|
|
158
|
+
}
|
|
159
|
+
return walk(here, index + 1);
|
|
160
|
+
};
|
|
161
|
+
return walk('', 0);
|
|
162
|
+
}
|
|
163
|
+
export function buildJoinMatchStage(joinFilters, operations) {
|
|
164
|
+
const match = {};
|
|
165
|
+
const expressions = [];
|
|
166
|
+
for (const joinFilter of joinFilters) {
|
|
167
|
+
if (joinFilter.crossesMany) {
|
|
168
|
+
const expression = anyMatchExpression(joinFilter, operations);
|
|
169
|
+
if (expression) {
|
|
170
|
+
expressions.push(expression);
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const assignment = scalarAssignment(joinFilter.column, joinFilter.filter);
|
|
175
|
+
if (assignment !== undefined) {
|
|
176
|
+
match[`_joinData.${joinFilter.aliases.join('.')}.${joinFilter.column}`] = assignment;
|
|
177
|
+
}
|
|
178
|
+
if (joinFilter.filter.ne !== undefined) {
|
|
179
|
+
match[`_joinData.${joinFilter.aliases.join('.')}`] = { $ne: null };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (expressions.length === 1) {
|
|
183
|
+
match.$expr = expressions[0];
|
|
184
|
+
}
|
|
185
|
+
else if (expressions.length > 1) {
|
|
186
|
+
match.$expr = { $and: expressions };
|
|
187
|
+
}
|
|
188
|
+
if (Object.keys(match).length === 0) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
return { $match: match };
|
|
192
|
+
}
|
|
@@ -2,6 +2,7 @@ import { entityUtils } from '@loomcore/common/utils';
|
|
|
2
2
|
import { ObjectId } from 'mongodb';
|
|
3
3
|
import { PROPERTIES_THAT_ARE_NOT_OBJECT_IDS } from '../../models/constants.js';
|
|
4
4
|
import { getPropertySchema } from '../../utils/get-property-schema.util.js';
|
|
5
|
+
import { readFilterValue } from '../../utils/nullish-filter-value.js';
|
|
5
6
|
function isExcludedFromObjectIdConversion(key) {
|
|
6
7
|
return PROPERTIES_THAT_ARE_NOT_OBJECT_IDS.includes(key);
|
|
7
8
|
}
|
|
@@ -36,16 +37,41 @@ export function buildNoSqlMatch(queryOptions, modelSpec) {
|
|
|
36
37
|
if (value) {
|
|
37
38
|
const propSchema = schema ? getPropertySchema(key, schema) : undefined;
|
|
38
39
|
if (value.eq !== undefined) {
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
match[key] = new ObjectId(valueToCompare);
|
|
40
|
+
const comparison = readFilterValue(value.eq);
|
|
41
|
+
if (comparison.kind === 'null') {
|
|
42
|
+
match[key] = { $type: 'null' };
|
|
43
43
|
}
|
|
44
|
-
else if (
|
|
45
|
-
match[key] =
|
|
44
|
+
else if (comparison.kind === 'undefined') {
|
|
45
|
+
match[key] = { $exists: false };
|
|
46
|
+
}
|
|
47
|
+
else if (shouldConvertEqToObjectId(key, propSchema, hasSchema)
|
|
48
|
+
&& typeof comparison.value === 'string' && entityUtils.isValidObjectId(comparison.value)) {
|
|
49
|
+
match[key] = new ObjectId(comparison.value);
|
|
50
|
+
}
|
|
51
|
+
else if (typeof comparison.value === 'string' && !isNaN(Number(comparison.value))) {
|
|
52
|
+
match[key] = Number(comparison.value);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
match[key] = comparison.value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (value.ne !== undefined) {
|
|
59
|
+
const comparison = readFilterValue(value.ne);
|
|
60
|
+
if (comparison.kind === 'null') {
|
|
61
|
+
match[key] = { $not: { $type: 'null' } };
|
|
62
|
+
}
|
|
63
|
+
else if (comparison.kind === 'undefined') {
|
|
64
|
+
match[key] = { $exists: true };
|
|
65
|
+
}
|
|
66
|
+
else if (shouldConvertEqToObjectId(key, propSchema, hasSchema)
|
|
67
|
+
&& typeof comparison.value === 'string' && entityUtils.isValidObjectId(comparison.value)) {
|
|
68
|
+
match[key] = { $ne: new ObjectId(comparison.value) };
|
|
69
|
+
}
|
|
70
|
+
else if (typeof comparison.value === 'string' && !isNaN(Number(comparison.value))) {
|
|
71
|
+
match[key] = { $ne: Number(comparison.value) };
|
|
46
72
|
}
|
|
47
73
|
else {
|
|
48
|
-
match[key] =
|
|
74
|
+
match[key] = { $ne: comparison.value };
|
|
49
75
|
}
|
|
50
76
|
}
|
|
51
77
|
if (value.in !== undefined && Array.isArray(value.in)) {
|
|
@@ -13,17 +13,27 @@ function resolveLocalFieldPath(localField, processedOperations) {
|
|
|
13
13
|
}
|
|
14
14
|
return localField;
|
|
15
15
|
}
|
|
16
|
+
function innerJoinDropsDocument(operation, processedOperations) {
|
|
17
|
+
if (!(operation instanceof InnerJoin)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const dot = operation.localField.indexOf('.');
|
|
21
|
+
if (dot === -1) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
const parentAlias = operation.localField.slice(0, dot);
|
|
25
|
+
const parent = processedOperations.find(op => (op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany) && op.as === parentAlias);
|
|
26
|
+
return !(parent instanceof LeftJoinMany);
|
|
27
|
+
}
|
|
16
28
|
export function convertOperationsToPipeline(operations) {
|
|
17
29
|
let pipeline = [];
|
|
18
30
|
const processedOperations = [];
|
|
19
|
-
const joinAliases = operations
|
|
20
|
-
.filter(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany)
|
|
21
|
-
.map(op => op.as);
|
|
22
31
|
operations.forEach(operation => {
|
|
23
32
|
if (operation instanceof LeftJoin || operation instanceof InnerJoin) {
|
|
24
33
|
const needsObjectIdConversion = operation.foreignField === '_id';
|
|
25
34
|
const isNestedField = operation.localField.includes('.');
|
|
26
35
|
const resolvedLocalField = resolveLocalFieldPath(operation.localField, processedOperations);
|
|
36
|
+
const preserveMissingMatch = !innerJoinDropsDocument(operation, processedOperations);
|
|
27
37
|
if (needsObjectIdConversion) {
|
|
28
38
|
pipeline.push({
|
|
29
39
|
$lookup: {
|
|
@@ -51,7 +61,7 @@ export function convertOperationsToPipeline(operations) {
|
|
|
51
61
|
}, {
|
|
52
62
|
$unwind: {
|
|
53
63
|
path: `$${operation.as}Arr`,
|
|
54
|
-
preserveNullAndEmptyArrays:
|
|
64
|
+
preserveNullAndEmptyArrays: preserveMissingMatch
|
|
55
65
|
}
|
|
56
66
|
}, {
|
|
57
67
|
$addFields: {
|
|
@@ -79,7 +89,7 @@ export function convertOperationsToPipeline(operations) {
|
|
|
79
89
|
}, {
|
|
80
90
|
$unwind: {
|
|
81
91
|
path: `$${operation.as}Arr`,
|
|
82
|
-
preserveNullAndEmptyArrays:
|
|
92
|
+
preserveNullAndEmptyArrays: preserveMissingMatch
|
|
83
93
|
}
|
|
84
94
|
}, {
|
|
85
95
|
$addFields: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -39,7 +39,7 @@ export async function batchUpdate(connection, entities, operations, queryObject,
|
|
|
39
39
|
const { columns, values } = columnsAndValuesFromEntity(updateData);
|
|
40
40
|
const setClause = columns.map((col, index) => `${col} = $${index + 1}`).join(', ');
|
|
41
41
|
queryObject.filters._id = { eq: _id };
|
|
42
|
-
const { whereClause } = buildWhereClause(queryObject, values);
|
|
42
|
+
const { whereClause } = buildWhereClause(queryObject, pluralResourceName, values);
|
|
43
43
|
const query = `
|
|
44
44
|
UPDATE "${pluralResourceName}"
|
|
45
45
|
SET ${setClause}
|
|
@@ -50,9 +50,8 @@ export async function batchUpdate(connection, entities, operations, queryObject,
|
|
|
50
50
|
await session.query('COMMIT');
|
|
51
51
|
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
52
52
|
const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
|
|
53
|
-
const tablePrefix = hasJoins ? pluralResourceName : undefined;
|
|
54
53
|
queryObject.filters._id = { in: entityIds };
|
|
55
|
-
const { whereClause, values } = buildWhereClause(queryObject,
|
|
54
|
+
const { whereClause, values } = buildWhereClause(queryObject, pluralResourceName);
|
|
56
55
|
const selectClause = hasJoins
|
|
57
56
|
? await buildSelectClause(session, pluralResourceName, operations)
|
|
58
57
|
: '*';
|
|
@@ -2,7 +2,7 @@ import { buildWhereClause } from '../utils/build-where-clause.js';
|
|
|
2
2
|
import { DeleteResult } from '../../models/delete-result.js';
|
|
3
3
|
export async function deleteMany(client, queryObject, pluralResourceName) {
|
|
4
4
|
try {
|
|
5
|
-
const { whereClause, values } = buildWhereClause(queryObject);
|
|
5
|
+
const { whereClause, values } = buildWhereClause(queryObject, pluralResourceName);
|
|
6
6
|
const query = `DELETE FROM "${pluralResourceName}" ${whereClause}`;
|
|
7
7
|
const result = await client.query(query, values);
|
|
8
8
|
return new DeleteResult(true, result.rowCount || 0);
|
|
@@ -54,9 +54,7 @@ export async function fullUpdateById(client, operations, id, entity, pluralResou
|
|
|
54
54
|
throw new IdNotFoundError();
|
|
55
55
|
}
|
|
56
56
|
const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
|
|
57
|
-
const joinClauses =
|
|
58
|
-
? buildJoinClauses(operations, pluralResourceName, { oneToOneOnly: true })
|
|
59
|
-
: buildJoinClauses(operations, pluralResourceName);
|
|
57
|
+
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
60
58
|
const selectClause = hasJoins
|
|
61
59
|
? await buildSelectClause(client, pluralResourceName, operations)
|
|
62
60
|
: '*';
|
|
@@ -24,9 +24,7 @@ export async function partialUpdateById(client, operations, id, entity, pluralRe
|
|
|
24
24
|
throw new IdNotFoundError();
|
|
25
25
|
}
|
|
26
26
|
const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
|
|
27
|
-
const joinClauses =
|
|
28
|
-
? buildJoinClauses(operations, pluralResourceName, { oneToOneOnly: true })
|
|
29
|
-
: buildJoinClauses(operations, pluralResourceName);
|
|
27
|
+
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
30
28
|
const selectClause = hasJoins
|
|
31
29
|
? await buildSelectClause(client, pluralResourceName, operations)
|
|
32
30
|
: '*';
|
|
@@ -9,7 +9,7 @@ import { buildSelectClause } from '../utils/build-select-clause.js';
|
|
|
9
9
|
import { columnsAndValuesFromEntity } from '../utils/columns-and-values-from-entity.js';
|
|
10
10
|
export async function update(client, queryObject, entity, operations, pluralResourceName) {
|
|
11
11
|
try {
|
|
12
|
-
const { whereClause, values: whereValues } = buildWhereClause(queryObject);
|
|
12
|
+
const { whereClause, values: whereValues } = buildWhereClause(queryObject, pluralResourceName);
|
|
13
13
|
const { columns, values: entityValues } = columnsAndValuesFromEntity(entity);
|
|
14
14
|
const updateColumns = columns.filter(col => col !== '"_id"');
|
|
15
15
|
const updateValues = entityValues.filter((_, index) => columns[index] !== '"_id"');
|
|
@@ -32,12 +32,9 @@ export async function update(client, queryObject, entity, operations, pluralReso
|
|
|
32
32
|
throw new NotFoundError('No records found matching update query');
|
|
33
33
|
}
|
|
34
34
|
const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
|
|
35
|
-
const joinClauses =
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const orderByClause = buildOrderByClause(queryObject, hasJoins ? { tablePrefix: pluralResourceName } : undefined);
|
|
39
|
-
const tablePrefix = hasJoins ? pluralResourceName : undefined;
|
|
40
|
-
const { whereClause: selectWhereClause, values: selectWhereValues } = buildWhereClause(queryObject, [], tablePrefix);
|
|
35
|
+
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
36
|
+
const orderByClause = buildOrderByClause(queryObject, pluralResourceName);
|
|
37
|
+
const { whereClause: selectWhereClause, values: selectWhereValues } = buildWhereClause(queryObject, pluralResourceName);
|
|
41
38
|
const selectClause = hasJoins
|
|
42
39
|
? await buildSelectClause(client, pluralResourceName, operations)
|
|
43
40
|
: '*';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { buildWhereClause } from '../utils/build-where-clause.js';
|
|
2
2
|
import { buildOrderByClause } from '../utils/build-order-by-clause.js';
|
|
3
3
|
export async function findOne(client, queryObject, pluralResourceName) {
|
|
4
|
-
const { whereClause, values } = buildWhereClause(queryObject);
|
|
5
|
-
const orderByClause = buildOrderByClause(queryObject);
|
|
4
|
+
const { whereClause, values } = buildWhereClause(queryObject, pluralResourceName);
|
|
5
|
+
const orderByClause = buildOrderByClause(queryObject, pluralResourceName);
|
|
6
6
|
const query = `SELECT * FROM "${pluralResourceName}" ${whereClause} ${orderByClause} LIMIT 1`;
|
|
7
7
|
const result = await client.query(query, values);
|
|
8
8
|
if (result.rows.length === 0) {
|
|
@@ -2,8 +2,8 @@ import { buildWhereClause } from '../utils/build-where-clause.js';
|
|
|
2
2
|
import { buildOrderByClause } from '../utils/build-order-by-clause.js';
|
|
3
3
|
import { buildPaginationClause } from '../utils/build-pagination-clause.js';
|
|
4
4
|
export async function find(client, queryObject, pluralResourceName) {
|
|
5
|
-
const { whereClause, values } = buildWhereClause(queryObject);
|
|
6
|
-
const orderByClause = buildOrderByClause(queryObject);
|
|
5
|
+
const { whereClause, values } = buildWhereClause(queryObject, pluralResourceName);
|
|
6
|
+
const orderByClause = buildOrderByClause(queryObject, pluralResourceName);
|
|
7
7
|
const paginationClause = buildPaginationClause(queryObject);
|
|
8
8
|
const query = `SELECT * FROM "${pluralResourceName}" ${whereClause} ${orderByClause} ${paginationClause}`.trim();
|
|
9
9
|
const result = await client.query(query, values);
|
|
@@ -5,9 +5,7 @@ import { buildJoinClauses } from '../utils/build-join-clauses.js';
|
|
|
5
5
|
import { buildSelectClause } from '../utils/build-select-clause.js';
|
|
6
6
|
export async function getAll(client, operations, pluralResourceName) {
|
|
7
7
|
const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
|
|
8
|
-
const joinClauses =
|
|
9
|
-
? buildJoinClauses(operations, pluralResourceName, { oneToOneOnly: true })
|
|
10
|
-
: buildJoinClauses(operations, pluralResourceName);
|
|
8
|
+
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
11
9
|
const selectClause = hasJoins
|
|
12
10
|
? await buildSelectClause(client, pluralResourceName, operations)
|
|
13
11
|
: '*';
|
|
@@ -9,16 +9,13 @@ export async function getById(client, operations, queryObject, id, pluralResourc
|
|
|
9
9
|
if (!id)
|
|
10
10
|
throw new BadRequestError('id is required');
|
|
11
11
|
const hasJoins = operations.some(op => op instanceof LeftJoin || op instanceof InnerJoin || op instanceof LeftJoinMany);
|
|
12
|
-
const joinClauses =
|
|
13
|
-
? buildJoinClauses(operations, pluralResourceName, { oneToOneOnly: true })
|
|
14
|
-
: buildJoinClauses(operations, pluralResourceName);
|
|
12
|
+
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
15
13
|
const selectClause = hasJoins
|
|
16
14
|
? await buildSelectClause(client, pluralResourceName, operations)
|
|
17
15
|
: '*';
|
|
18
16
|
queryObject.filters || (queryObject.filters = {});
|
|
19
17
|
queryObject.filters._id = { eq: id };
|
|
20
|
-
const
|
|
21
|
-
const { whereClause, values } = buildWhereClause(queryObject, [], tablePrefix);
|
|
18
|
+
const { whereClause, values } = buildWhereClause(queryObject, pluralResourceName, [], operations);
|
|
22
19
|
const query = `SELECT ${selectClause} FROM "${pluralResourceName}" ${joinClauses} ${whereClause} LIMIT 1`;
|
|
23
20
|
const result = await client.query(query, values);
|
|
24
21
|
if (result.rows.length === 0) {
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { LeftJoin } from '../../operations/left-join.operation.js';
|
|
2
|
-
import { InnerJoin } from '../../operations/inner-join.operation.js';
|
|
3
|
-
import { LeftJoinMany } from '../../operations/left-join-many.operation.js';
|
|
4
1
|
import { buildWhereClause } from '../utils/build-where-clause.js';
|
|
5
2
|
import { buildOrderByClause } from '../utils/build-order-by-clause.js';
|
|
6
3
|
import { buildJoinClauses } from '../utils/build-join-clauses.js';
|
|
@@ -8,20 +5,18 @@ import { buildSelectClause } from '../utils/build-select-clause.js';
|
|
|
8
5
|
import { executeCountQuery } from '../utils/build-count-query.js';
|
|
9
6
|
import { apiUtils } from '../../../utils/api.utils.js';
|
|
10
7
|
import { buildPaginationClause } from '../utils/build-pagination-clause.js';
|
|
8
|
+
import { isJoinOperation } from '../../utils/is-join-operation.util.js';
|
|
11
9
|
export async function get(client, operations, queryOptions, pluralResourceName) {
|
|
12
|
-
const hasJoins = operations.some(
|
|
13
|
-
const joinClauses =
|
|
14
|
-
|
|
15
|
-
: buildJoinClauses(operations, pluralResourceName);
|
|
16
|
-
const orderByClause = buildOrderByClause(queryOptions, hasJoins ? { tablePrefix: pluralResourceName } : undefined);
|
|
10
|
+
const hasJoins = operations.some(isJoinOperation);
|
|
11
|
+
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
12
|
+
const orderByClause = buildOrderByClause(queryOptions, pluralResourceName);
|
|
17
13
|
const paginationClause = buildPaginationClause(queryOptions);
|
|
18
14
|
const selectClause = hasJoins
|
|
19
15
|
? await buildSelectClause(client, pluralResourceName, operations)
|
|
20
16
|
: '*';
|
|
21
|
-
const
|
|
22
|
-
const { whereClause, values } = buildWhereClause(queryOptions, [], tablePrefix);
|
|
17
|
+
const { whereClause, values } = buildWhereClause(queryOptions, pluralResourceName, [], operations);
|
|
23
18
|
const baseQuery = `SELECT ${selectClause} FROM "${pluralResourceName}" ${joinClauses} ${whereClause}`;
|
|
24
|
-
const total = await executeCountQuery(client, pluralResourceName, queryOptions);
|
|
19
|
+
const total = await executeCountQuery(client, pluralResourceName, queryOptions, operations);
|
|
25
20
|
const dataQuery = `${baseQuery} ${orderByClause} ${paginationClause}`.trim();
|
|
26
21
|
const dataResult = await client.query(dataQuery, values);
|
|
27
22
|
const entities = hasJoins
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import type { PostgresConnection } from '../postgres-connection.js';
|
|
2
2
|
import { IQueryOptions } from '@loomcore/common/models';
|
|
3
|
-
|
|
3
|
+
import type { Operation } from '../../operations/operation.js';
|
|
4
|
+
export declare function buildCountQuerySql(pluralResourceName: string, queryOptions?: IQueryOptions, operations?: Operation[]): {
|
|
5
|
+
countQuery: string;
|
|
6
|
+
values: any[];
|
|
7
|
+
};
|
|
8
|
+
export declare function executeCountQuery(client: PostgresConnection, pluralResourceName: string, queryOptions?: IQueryOptions, operations?: Operation[]): Promise<number>;
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import { buildJoinClauses } from './build-join-clauses.js';
|
|
1
2
|
import { buildWhereClause } from './build-where-clause.js';
|
|
2
|
-
export
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
export function buildCountQuerySql(pluralResourceName, queryOptions = {}, operations = []) {
|
|
4
|
+
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
5
|
+
const { whereClause, values } = buildWhereClause(queryOptions, pluralResourceName, [], operations);
|
|
6
|
+
const countQuery = ['SELECT COUNT(*) as total', `FROM "${pluralResourceName}"`, joinClauses, whereClause]
|
|
7
|
+
.filter(part => part.trim().length > 0)
|
|
8
|
+
.join(' ');
|
|
9
|
+
return { countQuery, values };
|
|
10
|
+
}
|
|
11
|
+
export async function executeCountQuery(client, pluralResourceName, queryOptions = {}, operations = []) {
|
|
12
|
+
const { countQuery, values } = buildCountQuerySql(pluralResourceName, queryOptions, operations);
|
|
5
13
|
const countResult = await client.query(countQuery, values);
|
|
6
14
|
return parseInt(countResult.rows[0].total, 10);
|
|
7
15
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
import { Operation } from '../../operations/operation.js';
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
export declare function buildJoinClauses(operations: Operation[], mainTableName?: string, options?: BuildJoinClausesOptions): string;
|
|
2
|
+
export declare function collectOuterOneToOneAliases(operations: Operation[], mainTableName: string): Set<string>;
|
|
3
|
+
export declare function buildJoinClauses(operations: Operation[], mainTableName: string): string;
|
|
@@ -1,54 +1,47 @@
|
|
|
1
1
|
import { LeftJoin } from '../../operations/left-join.operation.js';
|
|
2
2
|
import { InnerJoin } from '../../operations/inner-join.operation.js';
|
|
3
|
-
import { LeftJoinMany } from '../../operations/left-join-many.operation.js';
|
|
4
3
|
import { toPostgresStoreName } from './convert-keys.util.js';
|
|
5
4
|
function getParentAlias(localField) {
|
|
6
5
|
if (!localField.includes('.'))
|
|
7
6
|
return null;
|
|
8
7
|
return localField.split('.')[0];
|
|
9
8
|
}
|
|
10
|
-
function
|
|
11
|
-
const
|
|
12
|
-
for (const
|
|
13
|
-
if (
|
|
14
|
-
|
|
9
|
+
export function collectOuterOneToOneAliases(operations, mainTableName) {
|
|
10
|
+
const aliases = new Set();
|
|
11
|
+
for (const operation of operations) {
|
|
12
|
+
if (!(operation instanceof LeftJoin) && !(operation instanceof InnerJoin)) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const parent = getParentAlias(operation.localField);
|
|
16
|
+
const parentInScope = parent === null || parent === mainTableName || aliases.has(parent);
|
|
17
|
+
if (!parentInScope) {
|
|
18
|
+
continue;
|
|
15
19
|
}
|
|
20
|
+
aliases.add(operation.as);
|
|
16
21
|
}
|
|
17
|
-
return
|
|
22
|
+
return aliases;
|
|
18
23
|
}
|
|
19
|
-
export function buildJoinClauses(operations, mainTableName
|
|
20
|
-
const oneToOneOnly = options?.oneToOneOnly ?? false;
|
|
21
|
-
const throughTableAliases = oneToOneOnly ? getThroughTableAliases(operations) : new Set();
|
|
24
|
+
export function buildJoinClauses(operations, mainTableName) {
|
|
22
25
|
const joinClauses = [];
|
|
23
|
-
const oneToOneAliasesInScope =
|
|
26
|
+
const oneToOneAliasesInScope = collectOuterOneToOneAliases(operations, mainTableName);
|
|
24
27
|
for (const operation of operations) {
|
|
25
|
-
if (operation instanceof
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
if (!(operation instanceof LeftJoin) && !(operation instanceof InnerJoin)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (!oneToOneAliasesInScope.has(operation.as)) {
|
|
32
|
+
continue;
|
|
28
33
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
continue;
|
|
35
|
-
oneToOneAliasesInScope.add(operation.as);
|
|
36
|
-
}
|
|
34
|
+
const joinType = operation instanceof LeftJoin ? 'LEFT JOIN' : 'INNER JOIN';
|
|
35
|
+
let leftSide;
|
|
36
|
+
if (operation.localField.includes('.')) {
|
|
37
|
+
const [alias, column] = operation.localField.split('.');
|
|
38
|
+
leftSide = `"${alias}"."${column}"`;
|
|
37
39
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const joinType = useLeftJoin || operation instanceof LeftJoin || operation instanceof LeftJoinMany ? 'LEFT JOIN' : 'INNER JOIN';
|
|
41
|
-
let leftSide;
|
|
42
|
-
if (operation.localField.includes('.')) {
|
|
43
|
-
const [alias, column] = operation.localField.split('.');
|
|
44
|
-
leftSide = `"${alias}"."${column}"`;
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
leftSide = mainTableName ? `"${mainTableName}"."${operation.localField}"` : `"${operation.localField}"`;
|
|
48
|
-
}
|
|
49
|
-
const fromTable = toPostgresStoreName(operation.from);
|
|
50
|
-
joinClauses.push(`${joinType} "${fromTable}" AS "${operation.as}" ON ${leftSide} = "${operation.as}"."${operation.foreignField}"`);
|
|
40
|
+
else {
|
|
41
|
+
leftSide = `"${mainTableName}"."${operation.localField}"`;
|
|
51
42
|
}
|
|
43
|
+
const fromTable = toPostgresStoreName(operation.from);
|
|
44
|
+
joinClauses.push(`${joinType} "${fromTable}" AS "${operation.as}" ON ${leftSide} = "${operation.as}"."${operation.foreignField}"`);
|
|
52
45
|
}
|
|
53
46
|
return joinClauses.join(' ');
|
|
54
47
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Filter } from '@loomcore/common/models';
|
|
2
|
+
import type { Operation } from '../../operations/operation.js';
|
|
3
|
+
import { JoinFilter } from '../../models/join-filter.interface.js';
|
|
4
|
+
export interface SqlParamIndex {
|
|
5
|
+
current: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function toSqlColumnName(column: string): string;
|
|
8
|
+
export declare function appendFilterConditions(qualifiedKey: string, value: Filter, conditions: string[], values: any[], paramIndex: SqlParamIndex): void;
|
|
9
|
+
export declare function joinFilterSql(joinFilter: JoinFilter, operations: Operation[], mainTableName: string, values: any[], paramIndex: SqlParamIndex): string | null;
|