@entity-access/entity-access 1.0.5 → 1.0.7

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.
@@ -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
- replace?: Expression;
19
+ name: string;
19
20
  }
20
21
 
21
22
  export default class ExpressionToSql extends Visitor<ITextQuery> {
22
23
 
23
- protected targets: Map<ParameterExpression, IMappingModel> = new Map();
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, replace: root });
33
- this.targets.set(target, { parameter: target, model: this.source?.type, replace: target ?? source?.selectStatement.as});
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
- // inject parameter and types if we don't have it..
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.name)}` : "";
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.targets.get(parameter);
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
- const replaceParam = this.createParameter(this.source.selectStatement, relatedModel.name[0]);
155
- select.as = replaceParam;
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
- this.targets.set(param1, { parameter: param1, model: relatedModel, replace: replaceParam });
158
- this.targets.set(select.as, { parameter: param1, model: relatedModel, replace: replaceParam });
159
- this.targets.set(replaceParam, { parameter: param1, model: relatedModel, replace: replaceParam });
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: this.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: replaceParam,
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.targets.delete(param1);
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 replace = this.targets.get(pe);
237
- if (replace && replace.replace !== pe) {
238
- return this.visit(replace.replace);
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
- return [ QueryParameter.create(() => parameter.name, this.compiler.quotedLiteral) , "." , chain.map((x) => this.compiler.quotedLiteral(x)).join(".")];
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 as any).name)}` : "";
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
- x = this.replaceParameters(x as MemberExpression);
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.targets.get(parameter);
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.isCollection) {
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 = this.createParameter(select, relation.relatedEntity.name[0]);
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: relation.relatedEntity,
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.targets.set(parameter, { parameter, model: type, replace: parameter});
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
  ;
@@ -77,7 +77,9 @@ export const prepareJoin = (a: (ITextQueryFragment | ITextQuery)[], sep: string
77
77
  let first = true;
78
78
  for (const iterator of a) {
79
79
  if (!first) {
80
- r.push(",");
80
+ if (sep) {
81
+ r.push(sep);
82
+ }
81
83
  }
82
84
  first = false;
83
85
  addNonEmptyFlat(r, iterator);
@@ -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
@@ -1,4 +1,5 @@
1
- import { 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";
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
+ }