@carno.js/orm 0.2.3
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 +674 -0
- package/dist/SqlBuilder.d.ts +103 -0
- package/dist/SqlBuilder.js +618 -0
- package/dist/cache/cache-key-generator.d.ts +13 -0
- package/dist/cache/cache-key-generator.js +66 -0
- package/dist/cache/query-cache-manager.d.ts +14 -0
- package/dist/cache/query-cache-manager.js +44 -0
- package/dist/common/email.vo.d.ts +4 -0
- package/dist/common/email.vo.js +11 -0
- package/dist/common/uuid.d.ts +4 -0
- package/dist/common/uuid.js +10 -0
- package/dist/common/value-object.d.ts +95 -0
- package/dist/common/value-object.js +99 -0
- package/dist/constants.d.ts +6 -0
- package/dist/constants.js +9 -0
- package/dist/decorators/computed.decorator.d.ts +1 -0
- package/dist/decorators/computed.decorator.js +12 -0
- package/dist/decorators/entity.decorator.d.ts +3 -0
- package/dist/decorators/entity.decorator.js +12 -0
- package/dist/decorators/enum.decorator.d.ts +2 -0
- package/dist/decorators/enum.decorator.js +16 -0
- package/dist/decorators/event-hook.decorator.d.ts +4 -0
- package/dist/decorators/event-hook.decorator.js +31 -0
- package/dist/decorators/index.decorator.d.ts +17 -0
- package/dist/decorators/index.decorator.js +36 -0
- package/dist/decorators/one-many.decorator.d.ts +6 -0
- package/dist/decorators/one-many.decorator.js +42 -0
- package/dist/decorators/primary-key.decorator.d.ts +2 -0
- package/dist/decorators/primary-key.decorator.js +8 -0
- package/dist/decorators/property.decorator.d.ts +24 -0
- package/dist/decorators/property.decorator.js +44 -0
- package/dist/decorators/unique.decorator.d.ts +9 -0
- package/dist/decorators/unique.decorator.js +44 -0
- package/dist/domain/base-entity.d.ts +57 -0
- package/dist/domain/base-entity.js +198 -0
- package/dist/domain/collection.d.ts +6 -0
- package/dist/domain/collection.js +15 -0
- package/dist/domain/entities.d.ts +49 -0
- package/dist/domain/entities.js +259 -0
- package/dist/domain/reference.d.ts +86 -0
- package/dist/domain/reference.js +86 -0
- package/dist/driver/bun-driver.base.d.ts +72 -0
- package/dist/driver/bun-driver.base.js +270 -0
- package/dist/driver/bun-mysql.driver.d.ts +53 -0
- package/dist/driver/bun-mysql.driver.js +256 -0
- package/dist/driver/bun-pg.driver.d.ts +52 -0
- package/dist/driver/bun-pg.driver.js +263 -0
- package/dist/driver/driver.interface.d.ts +333 -0
- package/dist/driver/driver.interface.js +2 -0
- package/dist/entry.d.ts +2 -0
- package/dist/entry.js +13 -0
- package/dist/identity-map/entity-key-generator.d.ts +10 -0
- package/dist/identity-map/entity-key-generator.js +45 -0
- package/dist/identity-map/entity-registry.d.ts +11 -0
- package/dist/identity-map/entity-registry.js +41 -0
- package/dist/identity-map/identity-map-context.d.ts +9 -0
- package/dist/identity-map/identity-map-context.js +22 -0
- package/dist/identity-map/identity-map-integration.d.ts +5 -0
- package/dist/identity-map/identity-map-integration.js +37 -0
- package/dist/identity-map/identity-map.d.ts +11 -0
- package/dist/identity-map/identity-map.js +35 -0
- package/dist/identity-map/index.d.ts +5 -0
- package/dist/identity-map/index.js +14 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +48 -0
- package/dist/middleware/identity-map.middleware.d.ts +4 -0
- package/dist/middleware/identity-map.middleware.js +22 -0
- package/dist/orm-session-context.d.ts +16 -0
- package/dist/orm-session-context.js +22 -0
- package/dist/orm.d.ts +20 -0
- package/dist/orm.js +69 -0
- package/dist/orm.service.d.ts +13 -0
- package/dist/orm.service.js +361 -0
- package/dist/query/index-condition-builder.d.ts +41 -0
- package/dist/query/index-condition-builder.js +235 -0
- package/dist/query/model-transformer.d.ts +27 -0
- package/dist/query/model-transformer.js +201 -0
- package/dist/query/sql-column-manager.d.ts +28 -0
- package/dist/query/sql-column-manager.js +157 -0
- package/dist/query/sql-condition-builder.d.ts +51 -0
- package/dist/query/sql-condition-builder.js +264 -0
- package/dist/query/sql-join-manager.d.ts +39 -0
- package/dist/query/sql-join-manager.js +242 -0
- package/dist/query/sql-subquery-builder.d.ts +20 -0
- package/dist/query/sql-subquery-builder.js +119 -0
- package/dist/repository/Repository.d.ts +121 -0
- package/dist/repository/Repository.js +174 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/with-database.d.ts +20 -0
- package/dist/testing/with-database.js +311 -0
- package/dist/transaction/transaction-context.d.ts +10 -0
- package/dist/transaction/transaction-context.js +19 -0
- package/dist/utils/value-processor.d.ts +14 -0
- package/dist/utils/value-processor.js +94 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +24 -0
- package/package.json +59 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { FilterQuery, Relationship, Statement } from '../driver/driver.interface';
|
|
2
|
+
import { EntityStorage } from '../domain/entities';
|
|
3
|
+
import { SqlSubqueryBuilder } from './sql-subquery-builder';
|
|
4
|
+
type ApplyJoinCallback = (relationship: Relationship<any>, value: FilterQuery<any>, alias: string) => string;
|
|
5
|
+
export declare class SqlConditionBuilder<T> {
|
|
6
|
+
private entityStorage;
|
|
7
|
+
private applyJoinCallback;
|
|
8
|
+
private statements;
|
|
9
|
+
private readonly OPERATORS;
|
|
10
|
+
private lastKeyNotOperator;
|
|
11
|
+
private subqueryBuilder?;
|
|
12
|
+
constructor(entityStorage: EntityStorage, applyJoinCallback: ApplyJoinCallback, statements: Statement<T>);
|
|
13
|
+
setSubqueryBuilder(subqueryBuilder: SqlSubqueryBuilder): void;
|
|
14
|
+
build(condition: FilterQuery<T>, alias: string, model: Function): string;
|
|
15
|
+
private processConditions;
|
|
16
|
+
private processEntry;
|
|
17
|
+
private hasExistsOperator;
|
|
18
|
+
private handleRelationship;
|
|
19
|
+
private handleScalarValue;
|
|
20
|
+
private handleObjectValue;
|
|
21
|
+
private buildLogicalOperatorCondition;
|
|
22
|
+
private buildOperatorConditions;
|
|
23
|
+
private buildOperatorCondition;
|
|
24
|
+
private buildSimpleCondition;
|
|
25
|
+
private buildInCondition;
|
|
26
|
+
private buildNotInCondition;
|
|
27
|
+
private buildLikeCondition;
|
|
28
|
+
private buildComparisonCondition;
|
|
29
|
+
private buildNestedLogicalCondition;
|
|
30
|
+
private wrapWithLogicalOperator;
|
|
31
|
+
private extractValueFromValueObject;
|
|
32
|
+
private formatValue;
|
|
33
|
+
private formatDate;
|
|
34
|
+
private isNullish;
|
|
35
|
+
private buildNullCondition;
|
|
36
|
+
private isPrimitive;
|
|
37
|
+
private formatPrimitive;
|
|
38
|
+
private formatJson;
|
|
39
|
+
private escapeString;
|
|
40
|
+
private findRelationship;
|
|
41
|
+
private isScalarValue;
|
|
42
|
+
private isArrayValue;
|
|
43
|
+
private isLogicalOperator;
|
|
44
|
+
private extractLogicalOperator;
|
|
45
|
+
private trackLastNonOperatorKey;
|
|
46
|
+
private buildExistsCondition;
|
|
47
|
+
private buildTopLevelExistsCondition;
|
|
48
|
+
private resolveColumnName;
|
|
49
|
+
private resolveRelationColumn;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SqlConditionBuilder = void 0;
|
|
4
|
+
const value_object_1 = require("../common/value-object");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
class SqlConditionBuilder {
|
|
7
|
+
constructor(entityStorage, applyJoinCallback, statements) {
|
|
8
|
+
this.entityStorage = entityStorage;
|
|
9
|
+
this.applyJoinCallback = applyJoinCallback;
|
|
10
|
+
this.statements = statements;
|
|
11
|
+
this.OPERATORS = ['$eq', '$ne', '$in', '$nin', '$like', '$gt', '$gte', '$lt', '$lte', '$and', '$or', '$exists', '$nexists'];
|
|
12
|
+
this.lastKeyNotOperator = '';
|
|
13
|
+
}
|
|
14
|
+
setSubqueryBuilder(subqueryBuilder) {
|
|
15
|
+
this.subqueryBuilder = subqueryBuilder;
|
|
16
|
+
}
|
|
17
|
+
build(condition, alias, model) {
|
|
18
|
+
const sqlParts = this.processConditions(condition, alias, model);
|
|
19
|
+
if (sqlParts.length === 0) {
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
const result = this.wrapWithLogicalOperator(sqlParts, 'AND');
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
processConditions(condition, alias, model) {
|
|
26
|
+
const sqlParts = [];
|
|
27
|
+
for (let [key, value] of Object.entries(condition)) {
|
|
28
|
+
const extractedValue = this.extractValueFromValueObject(value);
|
|
29
|
+
const conditionSql = this.processEntry(key, extractedValue, alias, model);
|
|
30
|
+
if (conditionSql) {
|
|
31
|
+
sqlParts.push(conditionSql);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return sqlParts;
|
|
35
|
+
}
|
|
36
|
+
processEntry(key, value, alias, model) {
|
|
37
|
+
this.trackLastNonOperatorKey(key, model);
|
|
38
|
+
const relationship = this.findRelationship(key, model);
|
|
39
|
+
if (relationship && !this.hasExistsOperator(value)) {
|
|
40
|
+
return this.handleRelationship(relationship, value, alias);
|
|
41
|
+
}
|
|
42
|
+
if (this.isScalarValue(value)) {
|
|
43
|
+
return this.handleScalarValue(key, value, alias, model);
|
|
44
|
+
}
|
|
45
|
+
if (this.isArrayValue(key, value)) {
|
|
46
|
+
return this.buildInCondition(key, value, alias, model);
|
|
47
|
+
}
|
|
48
|
+
return this.handleObjectValue(key, value, alias, model);
|
|
49
|
+
}
|
|
50
|
+
hasExistsOperator(value) {
|
|
51
|
+
return typeof value === 'object' && value !== null && ('$exists' in value || '$nexists' in value);
|
|
52
|
+
}
|
|
53
|
+
handleRelationship(relationship, value, alias) {
|
|
54
|
+
const sql = this.applyJoinCallback(relationship, value, alias);
|
|
55
|
+
if (this.statements.strategy === 'joined') {
|
|
56
|
+
return sql;
|
|
57
|
+
}
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
handleScalarValue(key, value, alias, model) {
|
|
61
|
+
if (key === '$eq') {
|
|
62
|
+
return this.buildSimpleCondition(this.lastKeyNotOperator, value, alias, '=', model);
|
|
63
|
+
}
|
|
64
|
+
return this.buildSimpleCondition(key, value, alias, '=', model);
|
|
65
|
+
}
|
|
66
|
+
handleObjectValue(key, value, alias, model) {
|
|
67
|
+
if (this.isLogicalOperator(key)) {
|
|
68
|
+
return this.buildLogicalOperatorCondition(key, value, alias, model);
|
|
69
|
+
}
|
|
70
|
+
if (key === '$exists' || key === '$nexists') {
|
|
71
|
+
return this.buildTopLevelExistsCondition(key, value, alias, model);
|
|
72
|
+
}
|
|
73
|
+
return this.buildOperatorConditions(key, value, alias, model);
|
|
74
|
+
}
|
|
75
|
+
buildLogicalOperatorCondition(key, value, alias, model) {
|
|
76
|
+
const conditions = value.map((cond) => this.build(cond, alias, model));
|
|
77
|
+
const operator = this.extractLogicalOperator(key);
|
|
78
|
+
return this.wrapWithLogicalOperator(conditions, operator);
|
|
79
|
+
}
|
|
80
|
+
buildOperatorConditions(key, value, alias, model) {
|
|
81
|
+
const parts = [];
|
|
82
|
+
for (const operator of this.OPERATORS) {
|
|
83
|
+
if (operator in value) {
|
|
84
|
+
const condition = this.buildOperatorCondition(key, operator, value[operator], alias, model);
|
|
85
|
+
parts.push(condition);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return parts.join(' AND ');
|
|
89
|
+
}
|
|
90
|
+
buildOperatorCondition(key, operator, value, alias, model) {
|
|
91
|
+
switch (operator) {
|
|
92
|
+
case '$eq':
|
|
93
|
+
return this.buildSimpleCondition(key, value, alias, '=', model);
|
|
94
|
+
case '$ne':
|
|
95
|
+
return this.buildSimpleCondition(key, value, alias, '!=', model);
|
|
96
|
+
case '$in':
|
|
97
|
+
return this.buildInCondition(key, value, alias, model);
|
|
98
|
+
case '$nin':
|
|
99
|
+
return this.buildNotInCondition(key, value, alias, model);
|
|
100
|
+
case '$like':
|
|
101
|
+
return this.buildLikeCondition(key, value, alias, model);
|
|
102
|
+
case '$gt':
|
|
103
|
+
return this.buildComparisonCondition(key, value, alias, '>', model);
|
|
104
|
+
case '$gte':
|
|
105
|
+
return this.buildComparisonCondition(key, value, alias, '>=', model);
|
|
106
|
+
case '$lt':
|
|
107
|
+
return this.buildComparisonCondition(key, value, alias, '<', model);
|
|
108
|
+
case '$lte':
|
|
109
|
+
return this.buildComparisonCondition(key, value, alias, '<=', model);
|
|
110
|
+
case '$and':
|
|
111
|
+
case '$or':
|
|
112
|
+
return this.buildNestedLogicalCondition(operator, value, alias, model);
|
|
113
|
+
case '$exists':
|
|
114
|
+
return this.buildExistsCondition(key, value, alias, model, false);
|
|
115
|
+
case '$nexists':
|
|
116
|
+
return this.buildExistsCondition(key, value, alias, model, true);
|
|
117
|
+
default:
|
|
118
|
+
return '';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
buildSimpleCondition(key, value, alias, operator, model) {
|
|
122
|
+
const column = this.resolveColumnName(key, model);
|
|
123
|
+
if (this.isNullish(value))
|
|
124
|
+
return this.buildNullCondition(alias, column, operator);
|
|
125
|
+
const formattedValue = this.formatValue(value);
|
|
126
|
+
return `${alias}.${column} ${operator} ${formattedValue}`;
|
|
127
|
+
}
|
|
128
|
+
buildInCondition(key, values, alias, model) {
|
|
129
|
+
const column = this.resolveColumnName(key, model);
|
|
130
|
+
const formattedValues = values.map(val => this.formatValue(val)).join(', ');
|
|
131
|
+
return `${alias}.${column} IN (${formattedValues})`;
|
|
132
|
+
}
|
|
133
|
+
buildNotInCondition(key, values, alias, model) {
|
|
134
|
+
const column = this.resolveColumnName(key, model);
|
|
135
|
+
const formattedValues = values.map(val => this.formatValue(val)).join(', ');
|
|
136
|
+
return `${alias}.${column} NOT IN (${formattedValues})`;
|
|
137
|
+
}
|
|
138
|
+
buildLikeCondition(key, value, alias, model) {
|
|
139
|
+
const column = this.resolveColumnName(key, model);
|
|
140
|
+
return `${alias}.${column} LIKE '${value}'`;
|
|
141
|
+
}
|
|
142
|
+
buildComparisonCondition(key, value, alias, operator, model) {
|
|
143
|
+
const column = this.resolveColumnName(key, model);
|
|
144
|
+
if (this.isNullish(value))
|
|
145
|
+
return this.buildNullCondition(alias, column, operator);
|
|
146
|
+
const formattedValue = this.formatValue(value);
|
|
147
|
+
return `${alias}.${column} ${operator} ${formattedValue}`;
|
|
148
|
+
}
|
|
149
|
+
buildNestedLogicalCondition(operator, value, alias, model) {
|
|
150
|
+
const conditions = value.map((cond) => this.build(cond, alias, model));
|
|
151
|
+
const logicalOp = this.extractLogicalOperator(operator);
|
|
152
|
+
return this.wrapWithLogicalOperator(conditions, logicalOp);
|
|
153
|
+
}
|
|
154
|
+
wrapWithLogicalOperator(conditions, operator) {
|
|
155
|
+
return `(${conditions.join(` ${operator} `)})`;
|
|
156
|
+
}
|
|
157
|
+
extractValueFromValueObject(value) {
|
|
158
|
+
if ((0, utils_1.extendsFrom)(value_object_1.ValueObject, value?.constructor?.prototype)) {
|
|
159
|
+
return value.getValue();
|
|
160
|
+
}
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
formatValue(value) {
|
|
164
|
+
if (value instanceof Date)
|
|
165
|
+
return this.formatDate(value);
|
|
166
|
+
if (this.isNullish(value))
|
|
167
|
+
return 'NULL';
|
|
168
|
+
if (this.isPrimitive(value))
|
|
169
|
+
return this.formatPrimitive(value);
|
|
170
|
+
return this.formatJson(value);
|
|
171
|
+
}
|
|
172
|
+
formatDate(value) {
|
|
173
|
+
return `'${value.toISOString()}'`;
|
|
174
|
+
}
|
|
175
|
+
isNullish(value) {
|
|
176
|
+
return value === null || value === undefined;
|
|
177
|
+
}
|
|
178
|
+
buildNullCondition(alias, column, operator) {
|
|
179
|
+
if (operator === '!=' || operator === '<>')
|
|
180
|
+
return `${alias}.${column} IS NOT NULL`;
|
|
181
|
+
return `${alias}.${column} IS NULL`;
|
|
182
|
+
}
|
|
183
|
+
isPrimitive(value) {
|
|
184
|
+
return ['string', 'number', 'boolean', 'bigint'].includes(typeof value);
|
|
185
|
+
}
|
|
186
|
+
formatPrimitive(value) {
|
|
187
|
+
if (typeof value === 'string')
|
|
188
|
+
return `'${this.escapeString(value)}'`;
|
|
189
|
+
return `${value}`;
|
|
190
|
+
}
|
|
191
|
+
formatJson(value) {
|
|
192
|
+
return `'${this.escapeString(JSON.stringify(value))}'`;
|
|
193
|
+
}
|
|
194
|
+
escapeString(value) {
|
|
195
|
+
return value.replace(/'/g, "''");
|
|
196
|
+
}
|
|
197
|
+
findRelationship(key, model) {
|
|
198
|
+
const entity = this.entityStorage.get(model);
|
|
199
|
+
return entity?.relations?.find(rel => rel.propertyKey === key);
|
|
200
|
+
}
|
|
201
|
+
isScalarValue(value) {
|
|
202
|
+
const isDate = value instanceof Date;
|
|
203
|
+
return typeof value !== 'object' || value === null || isDate;
|
|
204
|
+
}
|
|
205
|
+
isArrayValue(key, value) {
|
|
206
|
+
return !this.OPERATORS.includes(key) && Array.isArray(value);
|
|
207
|
+
}
|
|
208
|
+
isLogicalOperator(key) {
|
|
209
|
+
return ['$or', '$and'].includes(key);
|
|
210
|
+
}
|
|
211
|
+
extractLogicalOperator(key) {
|
|
212
|
+
return key.toUpperCase().replace('$', '');
|
|
213
|
+
}
|
|
214
|
+
trackLastNonOperatorKey(key, model) {
|
|
215
|
+
if (!this.OPERATORS.includes(key)) {
|
|
216
|
+
this.lastKeyNotOperator = key;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
buildExistsCondition(key, filters, alias, model, negate) {
|
|
220
|
+
const relationship = this.findRelationship(key, model);
|
|
221
|
+
if (!relationship) {
|
|
222
|
+
const entity = this.entityStorage.get(model);
|
|
223
|
+
const availableRelations = entity?.relations
|
|
224
|
+
?.map((r) => r.propertyKey)
|
|
225
|
+
.join(', ') || 'none';
|
|
226
|
+
throw new Error(`Cannot use $${negate ? 'nexists' : 'exists'} on non-relationship field '${key}'. ` +
|
|
227
|
+
`Available relationships: ${availableRelations}`);
|
|
228
|
+
}
|
|
229
|
+
if (!this.subqueryBuilder) {
|
|
230
|
+
throw new Error('SqlSubqueryBuilder not initialized. This is an internal error.');
|
|
231
|
+
}
|
|
232
|
+
return this.subqueryBuilder.buildExistsSubquery(relationship, filters, alias, negate, model);
|
|
233
|
+
}
|
|
234
|
+
buildTopLevelExistsCondition(operator, value, alias, model) {
|
|
235
|
+
const negate = operator === '$nexists';
|
|
236
|
+
const conditions = [];
|
|
237
|
+
for (const [relationshipKey, filters] of Object.entries(value)) {
|
|
238
|
+
const condition = this.buildExistsCondition(relationshipKey, filters, alias, model, negate);
|
|
239
|
+
conditions.push(condition);
|
|
240
|
+
}
|
|
241
|
+
const result = this.wrapWithLogicalOperator(conditions, 'AND');
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
resolveColumnName(property, model) {
|
|
245
|
+
if (property.startsWith('$')) {
|
|
246
|
+
return property;
|
|
247
|
+
}
|
|
248
|
+
const entity = this.entityStorage.get(model);
|
|
249
|
+
if (!entity) {
|
|
250
|
+
return property;
|
|
251
|
+
}
|
|
252
|
+
const column = entity.properties?.[property]?.options.columnName;
|
|
253
|
+
if (column) {
|
|
254
|
+
return column;
|
|
255
|
+
}
|
|
256
|
+
return this.resolveRelationColumn(property, entity) ?? property;
|
|
257
|
+
}
|
|
258
|
+
resolveRelationColumn(property, entity) {
|
|
259
|
+
const relation = entity.relations?.find(rel => rel.propertyKey === property);
|
|
260
|
+
const column = relation?.columnName;
|
|
261
|
+
return typeof column === 'string' ? column : undefined;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
exports.SqlConditionBuilder = SqlConditionBuilder;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Statement, Relationship, FilterQuery, DriverInterface } from '../driver/driver.interface';
|
|
2
|
+
import { EntityStorage, Options } from '../domain/entities';
|
|
3
|
+
import { SqlConditionBuilder } from './sql-condition-builder';
|
|
4
|
+
import { SqlColumnManager } from './sql-column-manager';
|
|
5
|
+
import { ModelTransformer } from './model-transformer';
|
|
6
|
+
import { LoggerService } from '@carno.js/core';
|
|
7
|
+
export declare class SqlJoinManager<T> {
|
|
8
|
+
private entityStorage;
|
|
9
|
+
private statements;
|
|
10
|
+
private entity;
|
|
11
|
+
private model;
|
|
12
|
+
private driver;
|
|
13
|
+
private logger;
|
|
14
|
+
private conditionBuilder;
|
|
15
|
+
private columnManager;
|
|
16
|
+
private modelTransformer;
|
|
17
|
+
private getOriginalColumnsCallback;
|
|
18
|
+
private getAliasCallback;
|
|
19
|
+
constructor(entityStorage: EntityStorage, statements: Statement<T>, entity: Options, model: new () => T, driver: DriverInterface, logger: LoggerService, conditionBuilder: SqlConditionBuilder<T>, columnManager: SqlColumnManager, modelTransformer: ModelTransformer, getOriginalColumnsCallback: () => string[], getAliasCallback: (tableName: string) => string);
|
|
20
|
+
addJoinForRelationshipPath(relationshipPath: string): void;
|
|
21
|
+
applyJoin(relationShip: Relationship<any>, value: FilterQuery<any>, alias: string): string;
|
|
22
|
+
handleSelectJoin(entities: any, models: any): Promise<void>;
|
|
23
|
+
getPathForSelectJoin(selectJoin: Statement<any>): string[] | null;
|
|
24
|
+
private buildJoinOn;
|
|
25
|
+
private addJoinedJoin;
|
|
26
|
+
private addSelectJoin;
|
|
27
|
+
private processSelectJoin;
|
|
28
|
+
private getIds;
|
|
29
|
+
private updateJoinWhere;
|
|
30
|
+
private updateJoinColumns;
|
|
31
|
+
private attachJoinResults;
|
|
32
|
+
private setValueByPath;
|
|
33
|
+
private getPathForSelectJoinRecursive;
|
|
34
|
+
private findIdRecursively;
|
|
35
|
+
private getFkKey;
|
|
36
|
+
private getTableName;
|
|
37
|
+
private getPrimaryKey;
|
|
38
|
+
private formatValue;
|
|
39
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SqlJoinManager = void 0;
|
|
4
|
+
class SqlJoinManager {
|
|
5
|
+
constructor(entityStorage, statements, entity, model, driver, logger, conditionBuilder, columnManager, modelTransformer, getOriginalColumnsCallback, getAliasCallback) {
|
|
6
|
+
this.entityStorage = entityStorage;
|
|
7
|
+
this.statements = statements;
|
|
8
|
+
this.entity = entity;
|
|
9
|
+
this.model = model;
|
|
10
|
+
this.driver = driver;
|
|
11
|
+
this.logger = logger;
|
|
12
|
+
this.conditionBuilder = conditionBuilder;
|
|
13
|
+
this.columnManager = columnManager;
|
|
14
|
+
this.modelTransformer = modelTransformer;
|
|
15
|
+
this.getOriginalColumnsCallback = getOriginalColumnsCallback;
|
|
16
|
+
this.getAliasCallback = getAliasCallback;
|
|
17
|
+
}
|
|
18
|
+
addJoinForRelationshipPath(relationshipPath) {
|
|
19
|
+
const relationshipNames = relationshipPath.split('.');
|
|
20
|
+
let currentEntity = this.entity;
|
|
21
|
+
let currentAlias = this.statements.alias;
|
|
22
|
+
for (const relationshipName of relationshipNames) {
|
|
23
|
+
const relationship = currentEntity.relations.find((rel) => rel.propertyKey === relationshipName);
|
|
24
|
+
if (!relationship) {
|
|
25
|
+
throw new Error(`Relationship "${relationshipName}" not found in entity "${currentEntity.tableName}"`);
|
|
26
|
+
}
|
|
27
|
+
const statement = this.statements.strategy === 'joined'
|
|
28
|
+
? this.statements.join
|
|
29
|
+
: this.statements.selectJoin;
|
|
30
|
+
const nameAliasProperty = this.statements.strategy === 'joined' ? 'joinAlias' : 'alias';
|
|
31
|
+
const existingJoin = statement?.find((j) => j.joinProperty === relationshipName && j.originAlias === currentAlias);
|
|
32
|
+
if (existingJoin) {
|
|
33
|
+
currentAlias = existingJoin[nameAliasProperty];
|
|
34
|
+
currentEntity = this.entityStorage.get(relationship.entity());
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
this.applyJoin(relationship, {}, currentAlias);
|
|
38
|
+
const newStatement = this.statements.strategy === 'joined'
|
|
39
|
+
? this.statements.join
|
|
40
|
+
: this.statements.selectJoin;
|
|
41
|
+
currentAlias = newStatement[newStatement.length - 1][nameAliasProperty];
|
|
42
|
+
currentEntity = this.entityStorage.get(relationship.entity());
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
applyJoin(relationShip, value, alias) {
|
|
46
|
+
const { tableName, schema } = this.getTableName();
|
|
47
|
+
const { tableName: joinTableName, schema: joinSchema, hooks: joinHooks, } = this.entityStorage.get(relationShip.entity()) || {
|
|
48
|
+
tableName: relationShip.entity().name.toLowerCase(),
|
|
49
|
+
schema: 'public',
|
|
50
|
+
};
|
|
51
|
+
const originPrimaryKey = this.getPrimaryKey();
|
|
52
|
+
const joinAlias = this.getAliasCallback(joinTableName);
|
|
53
|
+
const joinWhere = this.conditionBuilder.build(value, joinAlias, relationShip.entity());
|
|
54
|
+
const on = this.buildJoinOn(relationShip, alias, joinAlias, originPrimaryKey);
|
|
55
|
+
if (this.statements.strategy === 'joined') {
|
|
56
|
+
this.addJoinedJoin(relationShip, joinTableName, joinSchema, joinAlias, joinWhere, on, alias, schema, tableName, joinHooks);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
this.addSelectJoin(relationShip, joinTableName, joinSchema, joinAlias, joinWhere, originPrimaryKey, alias, joinHooks);
|
|
60
|
+
}
|
|
61
|
+
return joinWhere;
|
|
62
|
+
}
|
|
63
|
+
async handleSelectJoin(entities, models) {
|
|
64
|
+
if (!this.statements.selectJoin || this.statements.selectJoin.length === 0) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (const join of this.statements.selectJoin.reverse()) {
|
|
68
|
+
await this.processSelectJoin(join, entities, models);
|
|
69
|
+
}
|
|
70
|
+
return models;
|
|
71
|
+
}
|
|
72
|
+
getPathForSelectJoin(selectJoin) {
|
|
73
|
+
const path = this.getPathForSelectJoinRecursive(selectJoin);
|
|
74
|
+
return path.reverse();
|
|
75
|
+
}
|
|
76
|
+
buildJoinOn(relationShip, alias, joinAlias, originPrimaryKey) {
|
|
77
|
+
let on = '';
|
|
78
|
+
switch (relationShip.relation) {
|
|
79
|
+
case "one-to-many":
|
|
80
|
+
on = `${joinAlias}."${this.getFkKey(relationShip)}" = ${alias}."${originPrimaryKey}"`;
|
|
81
|
+
break;
|
|
82
|
+
case "many-to-one":
|
|
83
|
+
on = `${alias}."${relationShip.columnName}" = ${joinAlias}."${this.getFkKey(relationShip)}"`;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
return on;
|
|
87
|
+
}
|
|
88
|
+
addJoinedJoin(relationShip, joinTableName, joinSchema, joinAlias, joinWhere, on, alias, schema, tableName, joinHooks) {
|
|
89
|
+
this.statements.join = this.statements.join || [];
|
|
90
|
+
this.statements.join.push({
|
|
91
|
+
joinAlias: joinAlias,
|
|
92
|
+
joinTable: joinTableName,
|
|
93
|
+
joinSchema: joinSchema || 'public',
|
|
94
|
+
joinWhere: joinWhere,
|
|
95
|
+
joinProperty: relationShip.propertyKey,
|
|
96
|
+
originAlias: alias,
|
|
97
|
+
originSchema: schema,
|
|
98
|
+
originTable: tableName,
|
|
99
|
+
propertyKey: relationShip.propertyKey,
|
|
100
|
+
joinEntity: relationShip.entity(),
|
|
101
|
+
type: 'LEFT',
|
|
102
|
+
on,
|
|
103
|
+
originalEntity: relationShip.originalEntity,
|
|
104
|
+
hooks: joinHooks,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
addSelectJoin(relationShip, joinTableName, joinSchema, joinAlias, joinWhere, originPrimaryKey, alias, joinHooks) {
|
|
108
|
+
this.statements.selectJoin = this.statements.selectJoin || [];
|
|
109
|
+
this.statements.selectJoin.push({
|
|
110
|
+
statement: 'select',
|
|
111
|
+
columns: this.getOriginalColumnsCallback().filter(column => column.startsWith(`${relationShip.propertyKey}`)).map(column => column.split('.')[1]) || [],
|
|
112
|
+
table: `"${joinSchema || 'public'}"."${joinTableName}"`,
|
|
113
|
+
alias: joinAlias,
|
|
114
|
+
where: joinWhere,
|
|
115
|
+
joinProperty: relationShip.propertyKey,
|
|
116
|
+
fkKey: this.getFkKey(relationShip),
|
|
117
|
+
primaryKey: originPrimaryKey,
|
|
118
|
+
originAlias: alias,
|
|
119
|
+
originProperty: relationShip.propertyKey,
|
|
120
|
+
joinEntity: relationShip.entity(),
|
|
121
|
+
originEntity: relationShip.originalEntity,
|
|
122
|
+
hooks: joinHooks,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async processSelectJoin(join, entities, models) {
|
|
126
|
+
let ids = this.getIds(join, entities, models);
|
|
127
|
+
if (Array.isArray(ids)) {
|
|
128
|
+
ids = ids.map((id) => this.formatValue(id)).join(', ');
|
|
129
|
+
}
|
|
130
|
+
this.updateJoinWhere(join, ids);
|
|
131
|
+
this.updateJoinColumns(join);
|
|
132
|
+
const child = await this.driver.executeStatement(join);
|
|
133
|
+
this.logger.debug(`SQL: ${child.sql} [${Date.now() - child.startTime}ms]`);
|
|
134
|
+
this.attachJoinResults(join, child, models);
|
|
135
|
+
}
|
|
136
|
+
getIds(join, entities, models) {
|
|
137
|
+
let ids = entities[`${join.originAlias}_${join.primaryKey}`];
|
|
138
|
+
if (typeof ids === 'undefined') {
|
|
139
|
+
const selectJoined = this.statements.selectJoin.find(j => j.joinEntity === join.originEntity);
|
|
140
|
+
if (!selectJoined) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
ids = this.findIdRecursively(models, selectJoined, join);
|
|
144
|
+
}
|
|
145
|
+
return ids;
|
|
146
|
+
}
|
|
147
|
+
updateJoinWhere(join, ids) {
|
|
148
|
+
if (join.where) {
|
|
149
|
+
join.where = `${join.where} AND ${join.alias}."${join.fkKey}" IN (${ids})`;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
join.where = `${join.alias}."${join.fkKey}" IN (${ids})`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
updateJoinColumns(join) {
|
|
156
|
+
if (join.columns && join.columns.length > 0) {
|
|
157
|
+
join.columns = join.columns.map((column) => `${join.alias}."${column}" as "${join.alias}_${column}"`);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
join.columns = this.columnManager.getColumnsForEntity(join.joinEntity, join.alias);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
attachJoinResults(join, child, models) {
|
|
164
|
+
const property = this.entityStorage.get(this.model).relations.find((rel) => rel.propertyKey === join.joinProperty);
|
|
165
|
+
const values = child.query.rows.map((row) => this.modelTransformer.transform(join.joinEntity, join, row));
|
|
166
|
+
const path = this.getPathForSelectJoin(join);
|
|
167
|
+
this.setValueByPath(models, path, property?.type === Array ? [...values] : values[0]);
|
|
168
|
+
}
|
|
169
|
+
setValueByPath(obj, path, value) {
|
|
170
|
+
let currentObj = obj;
|
|
171
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
172
|
+
const key = path[i];
|
|
173
|
+
currentObj[key] = currentObj[key] || {};
|
|
174
|
+
currentObj = currentObj[key];
|
|
175
|
+
}
|
|
176
|
+
currentObj[path[path.length - 1]] = value;
|
|
177
|
+
}
|
|
178
|
+
getPathForSelectJoinRecursive(selectJoin) {
|
|
179
|
+
const originJoin = this.statements.selectJoin.find(j => j.joinEntity === selectJoin.originEntity);
|
|
180
|
+
let pathInJoin = [];
|
|
181
|
+
if (!originJoin) {
|
|
182
|
+
return [selectJoin.joinProperty];
|
|
183
|
+
}
|
|
184
|
+
if (originJoin.originEntity !== this.statements.originEntity) {
|
|
185
|
+
pathInJoin = this.getPathForSelectJoinRecursive(originJoin);
|
|
186
|
+
}
|
|
187
|
+
return [selectJoin.joinProperty, ...pathInJoin];
|
|
188
|
+
}
|
|
189
|
+
findIdRecursively(models, selectJoined, join) {
|
|
190
|
+
let ids = models[selectJoined.originProperty][join.primaryKey];
|
|
191
|
+
if (typeof ids === 'undefined') {
|
|
192
|
+
const nextSelectJoined = this.statements.selectJoin.find(j => j.joinEntity === selectJoined.originEntity);
|
|
193
|
+
if (nextSelectJoined) {
|
|
194
|
+
ids = this.findIdRecursively(models, nextSelectJoined, join);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return ids;
|
|
198
|
+
}
|
|
199
|
+
getFkKey(relationShip) {
|
|
200
|
+
if (typeof relationShip.fkKey === 'undefined') {
|
|
201
|
+
return 'id';
|
|
202
|
+
}
|
|
203
|
+
if (typeof relationShip.fkKey === 'string') {
|
|
204
|
+
return relationShip.fkKey;
|
|
205
|
+
}
|
|
206
|
+
const match = /\.(?<propriedade>[\w]+)/.exec(relationShip.fkKey.toString());
|
|
207
|
+
const propertyKey = match ? match.groups.propriedade : '';
|
|
208
|
+
const entity = this.entityStorage.get(relationShip.entity());
|
|
209
|
+
if (!entity) {
|
|
210
|
+
throw new Error(`Entity not found in storage for relationship. ` +
|
|
211
|
+
`Make sure the entity ${relationShip.entity().name} is decorated with @Entity()`);
|
|
212
|
+
}
|
|
213
|
+
const property = Object.entries(entity.properties).find(([key, _value]) => key === propertyKey)?.[1];
|
|
214
|
+
if (property) {
|
|
215
|
+
return property.options.columnName;
|
|
216
|
+
}
|
|
217
|
+
const relation = entity.relations.find(rel => rel.propertyKey === propertyKey);
|
|
218
|
+
if (relation && relation.columnName) {
|
|
219
|
+
return relation.columnName;
|
|
220
|
+
}
|
|
221
|
+
throw new Error(`Property or relation "${propertyKey}" not found in entity "${entity.tableName}". ` +
|
|
222
|
+
`Available properties: ${Object.keys(entity.properties).join(', ')}. ` +
|
|
223
|
+
`Available relations: ${entity.relations.map(r => r.propertyKey).join(', ')}`);
|
|
224
|
+
}
|
|
225
|
+
getTableName() {
|
|
226
|
+
const tableName = this.entity.tableName || this.model.name.toLowerCase();
|
|
227
|
+
const schema = this.entity.schema || 'public';
|
|
228
|
+
return { tableName, schema };
|
|
229
|
+
}
|
|
230
|
+
getPrimaryKey() {
|
|
231
|
+
for (const prop in this.entity.properties) {
|
|
232
|
+
if (this.entity.properties[prop].options.isPrimary) {
|
|
233
|
+
return prop;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return 'id';
|
|
237
|
+
}
|
|
238
|
+
formatValue(value) {
|
|
239
|
+
return (typeof value === 'string') ? `'${value}'` : value;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
exports.SqlJoinManager = SqlJoinManager;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FilterQuery, Relationship } from '../driver/driver.interface';
|
|
2
|
+
import { EntityStorage } from '../domain/entities';
|
|
3
|
+
import { SqlConditionBuilder } from './sql-condition-builder';
|
|
4
|
+
export declare class SqlSubqueryBuilder {
|
|
5
|
+
private entityStorage;
|
|
6
|
+
private getConditionBuilder;
|
|
7
|
+
private aliasCounter;
|
|
8
|
+
constructor(entityStorage: EntityStorage, getConditionBuilder: () => SqlConditionBuilder<any>);
|
|
9
|
+
buildExistsSubquery(relationship: Relationship<any>, filters: FilterQuery<any>, outerAlias: string, negate: boolean, outerModel?: Function): string;
|
|
10
|
+
private buildSubquery;
|
|
11
|
+
private buildWhereClause;
|
|
12
|
+
private buildCorrelation;
|
|
13
|
+
private getOuterPrimaryKey;
|
|
14
|
+
private buildFilterConditions;
|
|
15
|
+
private combineConditions;
|
|
16
|
+
private resolveTableName;
|
|
17
|
+
private generateAlias;
|
|
18
|
+
private getFkKey;
|
|
19
|
+
private getRelatedPrimaryKey;
|
|
20
|
+
}
|