@entity-access/entity-access 1.0.5 → 1.0.6
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/package.json +1 -1
- package/src/common/cache/TimedCache.ts +2 -2
- package/src/decorators/IColumn.ts +2 -0
- package/src/drivers/sql-server/ExpressionToSqlServer.ts +2 -16
- package/src/entity-query/EntityType.ts +44 -2
- package/src/model/EntityQuery.ts +43 -0
- package/src/model/EntitySource.ts +8 -22
- package/src/model/IFilterWithParameter.ts +2 -0
- package/src/model/changes/ChangeEntry.ts +2 -2
- package/src/model/changes/ChangeSet.ts +5 -1
- package/src/model/events/EntityEvents.ts +9 -5
- package/src/model/identity/IdentityService.ts +8 -0
- package/src/model/identity/RelationMapper.ts +71 -0
- package/src/model/verification/VerificationSession.ts +1 -1
- package/src/query/ast/DebugStringVisitor.ts +51 -4
- package/src/query/ast/ExpressionToSql.ts +75 -87
- package/src/query/ast/Expressions.ts +22 -4
- package/src/query/ast/IStringTransformer.ts +3 -1
- package/src/query/ast/ParameterScope.ts +97 -0
- package/src/query/ast/Types.ts +0 -0
- package/src/query/ast/Visitor.ts +7 -1
- package/src/query/expander/QueryExpander.ts +147 -0
- package/src/query/parser/ArrowToExpression.ts +22 -7
- package/src/query/parser/BabelVisitor.ts +5 -0
- package/src/tests/expressions/left-joins/child-joins.ts +48 -21
- package/src/tests/model/createContext.ts +26 -7
- package/src/tests/security/events/OrderEvents.ts +8 -2
- package/src/tests/security/events/UserEvents.ts +11 -1
- package/src/tests/security/tests/include-items.ts +19 -0
- package/src/tests/security/tests/place-order.ts +33 -0
|
@@ -3,6 +3,7 @@ import EntityType from "../../entity-query/EntityType.js";
|
|
|
3
3
|
import EntityQuery from "../../model/EntityQuery.js";
|
|
4
4
|
import { BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateLiteral, UpdateStatement, ValuesStatement } from "./Expressions.js";
|
|
5
5
|
import { ITextQuery, QueryParameter, prepare, prepareJoin } from "./IStringTransformer.js";
|
|
6
|
+
import ParameterScope from "./ParameterScope.js";
|
|
6
7
|
import Visitor from "./Visitor.js";
|
|
7
8
|
|
|
8
9
|
interface IPropertyChain {
|
|
@@ -15,12 +16,12 @@ export interface IMappingModel {
|
|
|
15
16
|
parameter: ParameterExpression;
|
|
16
17
|
model?: EntityType;
|
|
17
18
|
selectStatement?: SelectStatement;
|
|
18
|
-
|
|
19
|
+
name: string;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
22
23
|
|
|
23
|
-
protected
|
|
24
|
+
protected readonly scope: ParameterScope = new ParameterScope();
|
|
24
25
|
|
|
25
26
|
constructor(
|
|
26
27
|
private source: EntityQuery,
|
|
@@ -29,8 +30,14 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
29
30
|
private compiler: QueryCompiler
|
|
30
31
|
) {
|
|
31
32
|
super();
|
|
32
|
-
this.targets.set(root, { parameter: root
|
|
33
|
-
this.targets.set(target, { parameter: target, model: this.source?.type
|
|
33
|
+
// this.targets.set(root, { parameter: root });
|
|
34
|
+
// this.targets.set(target, { parameter: target, model: this.source?.type });
|
|
35
|
+
if (this.root) {
|
|
36
|
+
this.scope.create({ parameter: root, name: root.name ?? "x", isRuntimeParam: true });
|
|
37
|
+
}
|
|
38
|
+
if (this.target) {
|
|
39
|
+
this.scope.create({ parameter: target });
|
|
40
|
+
}
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
visitArray(e: Expression[], sep = ","): ITextQuery {
|
|
@@ -57,37 +64,40 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
57
64
|
|
|
58
65
|
visitSelectStatement(e: SelectStatement): ITextQuery {
|
|
59
66
|
|
|
60
|
-
|
|
61
|
-
if (e.as && e.model) {
|
|
62
|
-
const scope = this.targets.get(e.as);
|
|
63
|
-
if (!scope) {
|
|
64
|
-
this.targets.set(e.as, {
|
|
65
|
-
parameter: e.as,
|
|
66
|
-
model:
|
|
67
|
-
e.model,
|
|
68
|
-
replace: e.as,
|
|
69
|
-
selectStatement: e
|
|
70
|
-
});
|
|
71
|
-
} else {
|
|
72
|
-
scope.selectStatement = e;
|
|
73
|
-
scope.model = e.model;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
67
|
+
this.prepareStatement(e);
|
|
76
68
|
|
|
77
69
|
const where = e.where ? prepare `\n\tWHERE ${this.visit(e.where)}` : "";
|
|
78
|
-
const joins = e.joins?.length > 0 ? prepare `\n\t\t${this.visitArray(e.joins)}` : [];
|
|
79
70
|
const orderBy = e.orderBy?.length > 0 ? prepare `\n\t\tORDER BY ${this.visitArray(e.orderBy)}` : "";
|
|
71
|
+
const joins = e.joins?.length > 0 ? prepare `\n\t\t${this.visitArray(e.joins, "\n")}` : [];
|
|
80
72
|
const limit = e.limit > 0 ? prepare ` LIMIT ${Number(e.limit).toString()}` : "";
|
|
81
73
|
const offset = e.offset > 0 ? prepare ` OFFSET ${Number(e.offset).toString()}` : "";
|
|
82
74
|
const source = e.source.type === "ValuesStatement"
|
|
83
75
|
? prepare `(${this.visit(e.source)})`
|
|
84
76
|
: this.visit(e.source);
|
|
85
|
-
const as = e.as ? prepare ` AS ${this.compiler.quotedLiteral(e.as
|
|
77
|
+
const as = e.as ? prepare ` AS ${this.compiler.quotedLiteral( this.scope.nameOf(e.as))}` : "";
|
|
86
78
|
const fields = this.visitArray(e.fields, ",\n\t\t");
|
|
87
79
|
return prepare `SELECT
|
|
88
80
|
${fields}
|
|
89
81
|
FROM ${source}${as}${joins}${where}${orderBy}${limit}${offset}`;
|
|
90
82
|
}
|
|
83
|
+
prepareStatement(e: SelectStatement) {
|
|
84
|
+
// inject parameter and types if we don't have it..
|
|
85
|
+
if (e.as && e.model) {
|
|
86
|
+
this.scope.create({ parameter: e.as, selectStatement: e });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const joins = e.joins;
|
|
90
|
+
if (joins?.length) {
|
|
91
|
+
for (const iterator of joins) {
|
|
92
|
+
if (iterator.as) {
|
|
93
|
+
this.scope.create({ parameter: iterator.as as ParameterExpression, model: iterator.model});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const iterator of joins) {
|
|
97
|
+
this.visit(iterator.where);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
91
101
|
|
|
92
102
|
visitQuotedLiteral(e: QuotedLiteral): ITextQuery {
|
|
93
103
|
return [this.compiler.quotedLiteral(e.literal)];
|
|
@@ -131,7 +141,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
131
141
|
const targetProperty = this.getPropertyChain(e.callee as ExpressionType);
|
|
132
142
|
if (targetProperty) {
|
|
133
143
|
const { parameter , identifier, chain } = targetProperty;
|
|
134
|
-
const existingTarget = this.
|
|
144
|
+
const existingTarget = this.scope.get(parameter);
|
|
135
145
|
if (existingTarget) {
|
|
136
146
|
|
|
137
147
|
|
|
@@ -148,35 +158,42 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
148
158
|
const param1 = body.params[0];
|
|
149
159
|
const relatedModel = relation.relation.relatedEntity;
|
|
150
160
|
const relatedType = relatedModel.typeClass;
|
|
151
|
-
let query = this.source.context.query(relatedType);
|
|
152
|
-
const select = { ... (query as EntityQuery).selectStatement };
|
|
153
161
|
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
let select: SelectStatement;
|
|
163
|
+
|
|
164
|
+
if (this.source?.context) {
|
|
165
|
+
let query = this.source.context.query(relatedType);
|
|
166
|
+
// check if we have filter...
|
|
167
|
+
const entityEvents = this.source.context.eventsFor(relatedType, false);
|
|
168
|
+
if (entityEvents) {
|
|
169
|
+
query = entityEvents.includeFilter(query);
|
|
170
|
+
}
|
|
171
|
+
select = { ... (query as EntityQuery).selectStatement };
|
|
172
|
+
select.fields = [
|
|
173
|
+
Identifier.create({ value: "1"})
|
|
174
|
+
];
|
|
175
|
+
} else {
|
|
176
|
+
select = relatedModel.selectOneNumber();
|
|
177
|
+
}
|
|
156
178
|
|
|
157
|
-
|
|
158
|
-
this.
|
|
159
|
-
this.
|
|
179
|
+
param1.model = relatedModel;
|
|
180
|
+
this.scope.create({ parameter: param1, model: relatedModel, selectStatement: select });
|
|
181
|
+
this.scope.alias(param1, select.as, select);
|
|
182
|
+
select.as = param1;
|
|
160
183
|
const targetKey = MemberExpression.create({
|
|
161
|
-
target:
|
|
184
|
+
target: parameter,
|
|
162
185
|
property: Identifier.create({
|
|
163
186
|
value: targetType.keys[0].columnName
|
|
164
187
|
})
|
|
165
188
|
});
|
|
166
189
|
|
|
167
190
|
const relatedKey = MemberExpression.create({
|
|
168
|
-
target:
|
|
191
|
+
target: param1,
|
|
169
192
|
property: Identifier.create({
|
|
170
193
|
value: relation.relation.fkColumn.columnName
|
|
171
194
|
})
|
|
172
195
|
});
|
|
173
196
|
|
|
174
|
-
// check if we have filter...
|
|
175
|
-
const entityEvents = this.source.context.eventsFor(relatedType, false);
|
|
176
|
-
if (entityEvents) {
|
|
177
|
-
query = entityEvents.includeFilter(query);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
197
|
|
|
181
198
|
const join = Expression.logicalAnd(
|
|
182
199
|
Expression.equal(targetKey, relatedKey),
|
|
@@ -197,16 +214,12 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
197
214
|
|
|
198
215
|
select.where = where;
|
|
199
216
|
|
|
200
|
-
select.fields = [
|
|
201
|
-
Identifier.create({ value: "1"})
|
|
202
|
-
];
|
|
203
|
-
|
|
204
217
|
const exists = ExistsExpression.create({
|
|
205
218
|
target: select
|
|
206
219
|
});
|
|
207
220
|
|
|
208
221
|
const r = this.visit(exists);
|
|
209
|
-
this.
|
|
222
|
+
this.scope.delete(param1);
|
|
210
223
|
return r;
|
|
211
224
|
}
|
|
212
225
|
|
|
@@ -233,11 +246,9 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
233
246
|
}
|
|
234
247
|
|
|
235
248
|
visitParameterExpression(pe: ParameterExpression): ITextQuery {
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
const { name, value } = pe;
|
|
249
|
+
const scope = this.scope.get(pe);
|
|
250
|
+
const { value } = scope.parameter;
|
|
251
|
+
const name = this.scope.nameOf(pe);
|
|
241
252
|
if (value !== void 0) {
|
|
242
253
|
return [() => value];
|
|
243
254
|
}
|
|
@@ -256,7 +267,12 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
256
267
|
const value = parameter.value;
|
|
257
268
|
return [() => value[chain[0]]];
|
|
258
269
|
}
|
|
259
|
-
|
|
270
|
+
const scope = this.scope.get(parameter);
|
|
271
|
+
if (scope.isRuntimeParam) {
|
|
272
|
+
return [(p) => p[chain[0]]];
|
|
273
|
+
}
|
|
274
|
+
const name = this.scope.nameOf(parameter);
|
|
275
|
+
return [ QueryParameter.create(() => name, this.compiler.quotedLiteral) , "." , chain.map((x) => this.compiler.quotedLiteral(x)).join(".")];
|
|
260
276
|
}
|
|
261
277
|
|
|
262
278
|
const { target, computed, property } = me;
|
|
@@ -358,7 +374,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
358
374
|
const where = this.visit(e.where);
|
|
359
375
|
const as = e.as ? prepare ` AS ${ e.as.type === "QuotedLiteral"
|
|
360
376
|
? this.compiler.quotedLiteral(e.as.literal)
|
|
361
|
-
: this.compiler.quotedLiteral((e.as
|
|
377
|
+
: this.compiler.quotedLiteral( this.scope.nameOf(e.as) )}` : "";
|
|
362
378
|
return prepare ` ${e.joinType || "LEFT"} JOIN ${table}${as} ON ${where}`;
|
|
363
379
|
}
|
|
364
380
|
|
|
@@ -384,9 +400,9 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
384
400
|
private getPropertyChain(x: Expression): IPropertyChain {
|
|
385
401
|
|
|
386
402
|
// first replace root parameters
|
|
387
|
-
if (x.type === "MemberExpression") {
|
|
388
|
-
|
|
389
|
-
}
|
|
403
|
+
// if (x.type === "MemberExpression") {
|
|
404
|
+
// x = this.replaceParameters(x as MemberExpression);
|
|
405
|
+
// }
|
|
390
406
|
|
|
391
407
|
const chain = [];
|
|
392
408
|
let me = (x as ExpressionType).type === "MemberExpression"
|
|
@@ -416,20 +432,6 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
416
432
|
};
|
|
417
433
|
}
|
|
418
434
|
|
|
419
|
-
private createParameter (s: SelectStatement, prefix: string) {
|
|
420
|
-
const names = JSON.parse(s.names ?? "[]") as string[];
|
|
421
|
-
let i = 0;
|
|
422
|
-
while(true) {
|
|
423
|
-
const name = `${prefix}${i++}`;
|
|
424
|
-
if (names.includes(name)) {
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
names.push(name);
|
|
428
|
-
s.names = JSON.stringify(names);
|
|
429
|
-
return ParameterExpression.create({ name });
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
|
|
433
435
|
private flatten(pc: IPropertyChain) : IPropertyChain {
|
|
434
436
|
|
|
435
437
|
if (!pc) {
|
|
@@ -447,7 +449,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
447
449
|
|
|
448
450
|
const chain = [ ... pc.chain];
|
|
449
451
|
|
|
450
|
-
const scope = this.
|
|
452
|
+
const scope = this.scope.get(parameter);
|
|
451
453
|
const select = scope?.selectStatement ?? this.source?.selectStatement;
|
|
452
454
|
if (!select) {
|
|
453
455
|
return pc;
|
|
@@ -459,7 +461,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
459
461
|
while(chain.length > 1) {
|
|
460
462
|
const property = chain.shift();
|
|
461
463
|
const propertyInfo = type.getProperty(property);
|
|
462
|
-
if (!propertyInfo.relation || propertyInfo.relation.
|
|
464
|
+
if (!propertyInfo.relation || propertyInfo.relation.isInverseRelation) {
|
|
463
465
|
return pc;
|
|
464
466
|
}
|
|
465
467
|
|
|
@@ -474,11 +476,12 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
474
476
|
const join = select.joins.find((x) => x.model === relation.relatedEntity);
|
|
475
477
|
if (!join) {
|
|
476
478
|
const joinType = fkColumn.nullable ? "LEFT" : "INNER";
|
|
477
|
-
const joinParameter =
|
|
479
|
+
const joinParameter = ParameterExpression.create({ name: relation.relatedEntity.name[0]});
|
|
480
|
+
joinParameter.model = relation.relatedEntity;
|
|
478
481
|
select.joins.push(JoinExpression.create({
|
|
479
482
|
as: joinParameter,
|
|
480
483
|
joinType,
|
|
481
|
-
model:
|
|
484
|
+
model: joinParameter.model,
|
|
482
485
|
source: Expression.quotedLiteral(relation.relatedEntity.name),
|
|
483
486
|
where: Expression.equal(
|
|
484
487
|
Expression.member(parameter, fkColumn.columnName),
|
|
@@ -487,7 +490,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
487
490
|
}));
|
|
488
491
|
parameter = joinParameter;
|
|
489
492
|
type = relation.relatedEntity;
|
|
490
|
-
this.
|
|
493
|
+
this.scope.create({ parameter, model: type});
|
|
491
494
|
pc.parameter = parameter;
|
|
492
495
|
pc.chain = [ ... chain ];
|
|
493
496
|
} else {
|
|
@@ -500,19 +503,4 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
500
503
|
return { parameter, chain };
|
|
501
504
|
}
|
|
502
505
|
|
|
503
|
-
private replaceParameters(x: MemberExpression): Expression {
|
|
504
|
-
const { target, property } = x;
|
|
505
|
-
if (target.type === "ParameterExpression") {
|
|
506
|
-
const replace = this.targets.get(target as ParameterExpression);
|
|
507
|
-
if (replace && replace.replace !== target) {
|
|
508
|
-
return Expression.member(replace.replace, property);
|
|
509
|
-
}
|
|
510
|
-
return x;
|
|
511
|
-
}
|
|
512
|
-
if (target.type === "MemberExpression") {
|
|
513
|
-
return Expression.member(this.replaceParameters(target as MemberExpression), property);
|
|
514
|
-
}
|
|
515
|
-
return x;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
506
|
}
|
|
@@ -2,6 +2,7 @@ import { IClassOf } from "../../decorators/IClassOf.js";
|
|
|
2
2
|
import { ITextQuery } from "./IStringTransformer.js";
|
|
3
3
|
import type EntityType from "../../entity-query/EntityType.js";
|
|
4
4
|
import DebugStringVisitor from "./DebugStringVisitor.js";
|
|
5
|
+
import { IEntityRelation } from "../../decorators/IColumn.js";
|
|
5
6
|
|
|
6
7
|
const flattenedSelf = Symbol("flattenedSelf");
|
|
7
8
|
|
|
@@ -24,6 +25,10 @@ const flattenedSelf = Symbol("flattenedSelf");
|
|
|
24
25
|
|
|
25
26
|
export abstract class Expression {
|
|
26
27
|
|
|
28
|
+
static array(elements: Expression[]) {
|
|
29
|
+
return ArrayExpression.create({ elements });
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
static as(expression: Expression, alias: QuotedLiteral | string) {
|
|
28
33
|
if (typeof alias === "string") {
|
|
29
34
|
alias = Expression.quotedLiteral(alias);
|
|
@@ -123,6 +128,11 @@ export abstract class Expression {
|
|
|
123
128
|
|
|
124
129
|
}
|
|
125
130
|
|
|
131
|
+
export class ArrayExpression extends Expression {
|
|
132
|
+
readonly type = "ArrayExpression";
|
|
133
|
+
elements: Expression[];
|
|
134
|
+
}
|
|
135
|
+
|
|
126
136
|
export class PartialExpression extends Expression {
|
|
127
137
|
readonly type = "PartialExpression";
|
|
128
138
|
query: ITextQuery;
|
|
@@ -175,6 +185,11 @@ export class ParameterExpression extends Expression {
|
|
|
175
185
|
*/
|
|
176
186
|
value: any;
|
|
177
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Entity Model representing the variable
|
|
190
|
+
*/
|
|
191
|
+
model: EntityType;
|
|
192
|
+
|
|
178
193
|
quotedLiteral: (x: string) => string;
|
|
179
194
|
|
|
180
195
|
toString() {
|
|
@@ -197,6 +212,8 @@ export class ArrowFunctionExpression extends Expression {
|
|
|
197
212
|
|
|
198
213
|
export type TableSource = SelectStatement | QuotedLiteral | ExpressionAs | TableLiteral;
|
|
199
214
|
|
|
215
|
+
export type Expand = { [key: string]: string | Expand };
|
|
216
|
+
|
|
200
217
|
export class SelectStatement extends Expression {
|
|
201
218
|
|
|
202
219
|
readonly type = "SelectStatement";
|
|
@@ -217,11 +234,11 @@ export class SelectStatement extends Expression {
|
|
|
217
234
|
|
|
218
235
|
offset: number;
|
|
219
236
|
|
|
220
|
-
// name holds string
|
|
221
|
-
names: string;
|
|
222
|
-
|
|
223
237
|
model: EntityType;
|
|
224
238
|
|
|
239
|
+
// include relations...
|
|
240
|
+
include: SelectStatement[];
|
|
241
|
+
|
|
225
242
|
}
|
|
226
243
|
|
|
227
244
|
export class NewObjectExpression extends Expression {
|
|
@@ -239,7 +256,7 @@ export class ConditionalExpression extends Expression {
|
|
|
239
256
|
export class JoinExpression extends Expression {
|
|
240
257
|
readonly type = "JoinExpression";
|
|
241
258
|
joinType: "LEFT" | "INNER";
|
|
242
|
-
source: SelectStatement | QuotedLiteral | ExpressionAs;
|
|
259
|
+
source: SelectStatement | QuotedLiteral | ExpressionAs | TableLiteral;
|
|
243
260
|
as: QuotedLiteral | ParameterExpression;
|
|
244
261
|
where: Expression;
|
|
245
262
|
model: EntityType;
|
|
@@ -389,5 +406,6 @@ export type ExpressionType =
|
|
|
389
406
|
ConditionalExpression |
|
|
390
407
|
NewObjectExpression |
|
|
391
408
|
ParameterExpression |
|
|
409
|
+
ArrayExpression |
|
|
392
410
|
TemplateElement
|
|
393
411
|
;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type EntityType from "../../entity-query/EntityType.js";
|
|
2
|
+
import type { ParameterExpression, SelectStatement } from "./Expressions.js";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export interface IMappingModel {
|
|
6
|
+
parameter: ParameterExpression;
|
|
7
|
+
selectStatement?: SelectStatement;
|
|
8
|
+
name?: string;
|
|
9
|
+
model?: EntityType;
|
|
10
|
+
replace?: ParameterExpression;
|
|
11
|
+
isRuntimeParam?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* This class will provide scope for ever parameter along with type mapping, unique name and
|
|
16
|
+
* and replace parameter
|
|
17
|
+
*/
|
|
18
|
+
export default class ParameterScope {
|
|
19
|
+
|
|
20
|
+
private map:Map<ParameterExpression, IMappingModel> = new Map();
|
|
21
|
+
|
|
22
|
+
private names: Set<string> = new Set();
|
|
23
|
+
|
|
24
|
+
public get(p: ParameterExpression) {
|
|
25
|
+
const model = this.map.get(p);
|
|
26
|
+
return model;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public nameOf(p: ParameterExpression) {
|
|
30
|
+
const model = this.map.get(p);
|
|
31
|
+
return model.name;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public alias(
|
|
35
|
+
originalParameter: ParameterExpression,
|
|
36
|
+
alias: ParameterExpression,
|
|
37
|
+
selectStatement: SelectStatement
|
|
38
|
+
) {
|
|
39
|
+
const model = this.map.get(originalParameter);
|
|
40
|
+
if (!model.selectStatement) {
|
|
41
|
+
model.selectStatement = selectStatement;
|
|
42
|
+
} else {
|
|
43
|
+
selectStatement = model.selectStatement;
|
|
44
|
+
}
|
|
45
|
+
return this.create({ parameter: alias, selectStatement, name: model.name, replace: originalParameter });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
create(model: IMappingModel) {
|
|
49
|
+
|
|
50
|
+
const existing = this.map.get(model.parameter);
|
|
51
|
+
if (existing) {
|
|
52
|
+
existing.replace = model.replace;
|
|
53
|
+
existing.selectStatement = model.selectStatement;
|
|
54
|
+
existing.model ??= existing.selectStatement.model;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!model.replace) {
|
|
59
|
+
// create name...
|
|
60
|
+
model.name = this.createName(model.parameter.model?.name?.[0] ?? model.parameter.name, model.parameter.name );
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!model.model) {
|
|
64
|
+
if (model.parameter.model) {
|
|
65
|
+
model.model = model.parameter.model;
|
|
66
|
+
} else if (model.selectStatement){
|
|
67
|
+
model.model = model.selectStatement.model;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.map.set(model.parameter, model);
|
|
72
|
+
}
|
|
73
|
+
createName(prefix: string, name?: string): string {
|
|
74
|
+
if (name) {
|
|
75
|
+
name = name.toLocaleLowerCase();
|
|
76
|
+
if (!this.names.has(name)) {
|
|
77
|
+
this.names.add(name);
|
|
78
|
+
return name;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
let index = 1;
|
|
82
|
+
prefix = prefix.toLocaleLowerCase();
|
|
83
|
+
while(true) {
|
|
84
|
+
name = `${prefix}${index++}`;
|
|
85
|
+
if (this.names.has(name)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
this.names.add(name);
|
|
89
|
+
return name;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
delete(param1: ParameterExpression) {
|
|
94
|
+
// this.map.delete(param1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
}
|
|
File without changes
|
package/src/query/ast/Visitor.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NotSupportedError } from "../parser/NotSupportedError.js";
|
|
2
|
+
import { ArrayExpression, ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateElement, TemplateLiteral, UpdateStatement, ValuesStatement } from "./Expressions.js";
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
export default abstract class Visitor<T = any> {
|
|
@@ -66,10 +67,15 @@ export default abstract class Visitor<T = any> {
|
|
|
66
67
|
return this.visitTemplateElement(e);
|
|
67
68
|
case "ParameterExpression":
|
|
68
69
|
return this.visitParameterExpression(e);
|
|
70
|
+
case "ArrayExpression":
|
|
71
|
+
return this.visitArrayExpression(e);
|
|
69
72
|
}
|
|
70
73
|
const c: never = e;
|
|
71
74
|
throw new Error(`${e1.type} Not implemented`);
|
|
72
75
|
}
|
|
76
|
+
visitArrayExpression(e: ArrayExpression): T {
|
|
77
|
+
throw new NotSupportedError("Array Expression");
|
|
78
|
+
}
|
|
73
79
|
visitParameterExpression(e: ParameterExpression): T {
|
|
74
80
|
return;
|
|
75
81
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import EntityType from "../../entity-query/EntityType.js";
|
|
2
|
+
import EntityContext from "../../model/EntityContext.js";
|
|
3
|
+
import EntityQuery from "../../model/EntityQuery.js";
|
|
4
|
+
import { ArrowFunctionExpression, ExistsExpression, Expression, ExpressionType, JoinExpression, ParameterExpression, SelectStatement, TableSource } from "../ast/Expressions.js";
|
|
5
|
+
import ArrowToExpression from "../parser/ArrowToExpression.js";
|
|
6
|
+
import { NotSupportedError } from "../parser/NotSupportedError.js";
|
|
7
|
+
|
|
8
|
+
export class QueryExpander {
|
|
9
|
+
static expand(context: EntityContext, select: SelectStatement, p) {
|
|
10
|
+
const qe = new QueryExpander(context, select);
|
|
11
|
+
const expression = ArrowToExpression.transform(`(_____________________x) => ${p}` as any);
|
|
12
|
+
qe.expandNode(select, select.model, expression.body as ExpressionType);
|
|
13
|
+
return select;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
private context: EntityContext,
|
|
18
|
+
private select: SelectStatement
|
|
19
|
+
) {
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
expandNode(parent: SelectStatement, model: EntityType, node: ExpressionType): [SelectStatement, EntityType] {
|
|
24
|
+
|
|
25
|
+
if (node.type === "ArrayExpression") {
|
|
26
|
+
for (const iterator of node.elements) {
|
|
27
|
+
this.expandNode(parent, model, iterator as ExpressionType);
|
|
28
|
+
}
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if(node.type === "CallExpression") {
|
|
33
|
+
const callee = node.callee as ExpressionType;
|
|
34
|
+
if (callee.type !== "MemberExpression") {
|
|
35
|
+
throw new NotSupportedError(callee.type);
|
|
36
|
+
}
|
|
37
|
+
const property = callee.property as ExpressionType;
|
|
38
|
+
if (property.type !== "Identifier") {
|
|
39
|
+
throw new NotSupportedError(property.type);
|
|
40
|
+
}
|
|
41
|
+
if (property.value !== "forEach") {
|
|
42
|
+
throw new NotSupportedError(property.value);
|
|
43
|
+
}
|
|
44
|
+
const [expandedSelect, expandedType] = this.expandNode(parent, model, callee.target as ExpressionType);
|
|
45
|
+
|
|
46
|
+
const arrow = node.arguments[0];
|
|
47
|
+
if (!arrow || arrow.type !== "ArrowFunctionExpression") {
|
|
48
|
+
throw new NotSupportedError(arrow?.type ?? "Empty Expression");
|
|
49
|
+
}
|
|
50
|
+
this.expandNode(expandedSelect, expandedType, (arrow as ArrowFunctionExpression).body as ExpressionType);
|
|
51
|
+
return [expandedSelect, expandedType];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (node.type !== "MemberExpression") {
|
|
55
|
+
throw new NotSupportedError(node.type);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const p = node.property as ExpressionType;
|
|
59
|
+
if (p.type !== "Identifier") {
|
|
60
|
+
throw new NotSupportedError(p.type);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const target = node.target as ExpressionType;
|
|
64
|
+
if (target.type === "MemberExpression") {
|
|
65
|
+
const [mepSelect, mepType] = this.expandNode(parent, model, target);
|
|
66
|
+
parent = mepSelect;
|
|
67
|
+
model = mepType;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const mp = model.getProperty(p.value);
|
|
71
|
+
if (!mp.relation) {
|
|
72
|
+
throw new NotSupportedError(`No relation found ${p.value} in ${model.name}`);
|
|
73
|
+
}
|
|
74
|
+
const { relation } = mp;
|
|
75
|
+
const { relatedTypeClass: propertyType } = relation;
|
|
76
|
+
|
|
77
|
+
let query = this.context.query(propertyType);
|
|
78
|
+
const events = this.context.eventsFor(propertyType, false);
|
|
79
|
+
if (events) {
|
|
80
|
+
query = events.includeFilter(query, model, p.value) ?? query;
|
|
81
|
+
}
|
|
82
|
+
const select = { ... (query as EntityQuery).selectStatement };
|
|
83
|
+
|
|
84
|
+
let where: Expression;
|
|
85
|
+
let joinWhere: Expression;
|
|
86
|
+
|
|
87
|
+
const fk = relation.fkColumn ?? relation.relatedRelation.fkColumn;
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if(relation.isInverseRelation) {
|
|
91
|
+
|
|
92
|
+
joinWhere = Expression.equal(
|
|
93
|
+
Expression.member(
|
|
94
|
+
parent.as,
|
|
95
|
+
Expression.quotedLiteral(fk.columnName)
|
|
96
|
+
),
|
|
97
|
+
Expression.member(
|
|
98
|
+
select.as,
|
|
99
|
+
Expression.quotedLiteral(model.keys[0].columnName)
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
// load parent..
|
|
103
|
+
where = parent.where
|
|
104
|
+
? Expression.logicalAnd(joinWhere, parent.where)
|
|
105
|
+
: joinWhere;
|
|
106
|
+
|
|
107
|
+
const joins = (select.joins ??= []);
|
|
108
|
+
joins.push(JoinExpression.create({
|
|
109
|
+
source: parent.source as TableSource,
|
|
110
|
+
as: parent.as,
|
|
111
|
+
model,
|
|
112
|
+
where
|
|
113
|
+
}));
|
|
114
|
+
(this.select.include ??= []).push(select);
|
|
115
|
+
return [select, relation.relatedEntity];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
joinWhere = Expression.equal(
|
|
119
|
+
Expression.member(
|
|
120
|
+
parent.as,
|
|
121
|
+
Expression.quotedLiteral(fk.columnName)
|
|
122
|
+
),
|
|
123
|
+
Expression.member(
|
|
124
|
+
select.as,
|
|
125
|
+
Expression.quotedLiteral(relation.relatedEntity.keys[0].columnName)
|
|
126
|
+
)
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
parent = { ... parent };
|
|
130
|
+
|
|
131
|
+
parent.where = parent.where
|
|
132
|
+
? Expression.logicalAnd(parent.where, joinWhere)
|
|
133
|
+
: joinWhere;
|
|
134
|
+
|
|
135
|
+
const existsWhere = ExistsExpression.create({
|
|
136
|
+
target: parent
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
select.where = select.where
|
|
140
|
+
? Expression.logicalAnd(select.where, existsWhere)
|
|
141
|
+
: existsWhere;
|
|
142
|
+
|
|
143
|
+
(this.select.include ??= []).push(select);
|
|
144
|
+
|
|
145
|
+
return [select, relation.relatedEntity];
|
|
146
|
+
}
|
|
147
|
+
}
|