@entity-access/entity-access 1.0.300 → 1.0.302

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.
Files changed (31) hide show
  1. package/dist/compiler/postgres/PostgreSqlMethodTransformer.d.ts.map +1 -1
  2. package/dist/compiler/postgres/PostgreSqlMethodTransformer.js +4 -1
  3. package/dist/compiler/postgres/PostgreSqlMethodTransformer.js.map +1 -1
  4. package/dist/compiler/sql-server/SqlServerSqlMethodTransformer.d.ts.map +1 -1
  5. package/dist/compiler/sql-server/SqlServerSqlMethodTransformer.js +3 -0
  6. package/dist/compiler/sql-server/SqlServerSqlMethodTransformer.js.map +1 -1
  7. package/dist/query/ast/ExpressionToSql.d.ts.map +1 -1
  8. package/dist/query/ast/ExpressionToSql.js +62 -14
  9. package/dist/query/ast/ExpressionToSql.js.map +1 -1
  10. package/dist/query/parser/ArrowToExpression.d.ts.map +1 -1
  11. package/dist/query/parser/ArrowToExpression.js +6 -0
  12. package/dist/query/parser/ArrowToExpression.js.map +1 -1
  13. package/dist/sql/ISql.d.ts +1 -0
  14. package/dist/sql/ISql.d.ts.map +1 -1
  15. package/dist/tests/db-tests/tests/select-items-sum.d.ts +3 -0
  16. package/dist/tests/db-tests/tests/select-items-sum.d.ts.map +1 -0
  17. package/dist/tests/db-tests/tests/select-items-sum.js +30 -0
  18. package/dist/tests/db-tests/tests/select-items-sum.js.map +1 -0
  19. package/dist/tests/model/ShoppingContext.d.ts +1 -0
  20. package/dist/tests/model/ShoppingContext.d.ts.map +1 -1
  21. package/dist/tests/model/ShoppingContext.js +4 -0
  22. package/dist/tests/model/ShoppingContext.js.map +1 -1
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +2 -2
  25. package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +4 -1
  26. package/src/compiler/sql-server/SqlServerSqlMethodTransformer.ts +3 -0
  27. package/src/query/ast/ExpressionToSql.ts +73 -16
  28. package/src/query/parser/ArrowToExpression.ts +7 -0
  29. package/src/sql/ISql.ts +1 -0
  30. package/src/tests/db-tests/tests/select-items-sum.ts +38 -0
  31. package/src/tests/model/ShoppingContext.ts +3 -0
@@ -12,7 +12,13 @@ import Visitor from "./Visitor.js";
12
12
  interface IPropertyChain {
13
13
  identifier?: Identifier,
14
14
  parameter?: ParameterExpression,
15
- chain: string[]
15
+ chain: { member: string, args?: Expression[] }[]
16
+ }
17
+
18
+ interface IPropertyMethods {
19
+ identifier?: Identifier;
20
+ parameter?: ParameterExpression;
21
+
16
22
  }
17
23
 
18
24
  export interface IMappingModel {
@@ -161,6 +167,24 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
161
167
  // let us check if we are using any of array extension methods...
162
168
  // .some alias .any
163
169
  // .find alias .firstOrDefault
170
+ // if (e.callee.type === "MemberExpression") {
171
+ // const me = e.callee as MemberExpression;
172
+ // if (me.target.type === "CallExpression") {
173
+ // // nested...
174
+ // const ce = me.target as CallExpression;
175
+ // const cme = ce.callee as MemberExpression;
176
+ // if(cme.property.type !== "Identifier") {
177
+ // throw new EntityAccessError("Invalid expression");
178
+ // }
179
+ // const property = cme.property as Identifier;
180
+ // if(property.value !== "filter") {
181
+ // throw new EntityAccessError("Invalid expression");
182
+ // }
183
+
184
+ // filter = e;
185
+ // e = cme.target as CallExpression;
186
+ // }
187
+ // }
164
188
 
165
189
  const targetProperty = this.getPropertyChain(e.callee as ExpressionType);
166
190
  if (targetProperty) {
@@ -168,29 +192,50 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
168
192
  const existingTarget = parameter; // this.scope.get(parameter);
169
193
  if (existingTarget) {
170
194
 
195
+ // check if first chain is a filter
196
+ const [firstMember, ... methods] = chain;
197
+ const lastMethod = methods.pop();
198
+
171
199
 
172
200
  // calling method on property...
173
201
  // should be navigation...
174
202
  const targetType = existingTarget.model;
175
- const relation = targetType?.getProperty(chain[0]);
203
+ const relation = targetType?.getProperty(firstMember.member);
176
204
  if (relation) {
177
205
 
178
206
  const body = e.arguments?.[0] as ExpressionType;
179
207
  if (body?.type === "ArrowFunctionExpression") {
180
208
  const exists = this.expandSome(body, relation, e, parameter, targetType) as ExistsExpression;
181
- if (/^(some|any)$/.test(chain[1])) {
209
+ if (/^(some|any)$/.test(lastMethod.member)) {
182
210
  return this.visit(exists);
183
211
  }
184
- if (/^(map|select)$/.test(chain[1])) {
212
+ if (/^(map|select)$/.test(lastMethod.member)) {
185
213
  const select = this.expandCollection(relation, e, parameter, targetType);
214
+ const p1 = body.params[0];
215
+ this.scope.alias(select.sourceParameter, p1, select);
216
+
217
+ let where = select.where;
218
+
219
+ while(methods.length) {
220
+ const last = methods.pop();
221
+ if (last.member !== "filter") {
222
+ throw new EntityAccessError(`Invalid method ${last.member}`);
223
+ }
224
+ const filterArrow = last.args[0] as ArrowFunctionExpression;
225
+ this.scope.alias(select.sourceParameter, filterArrow.params[0], select);
226
+ if (where) {
227
+ where = Expression.logicalAnd(where, filterArrow.body);
228
+ } else {
229
+ where = filterArrow.body;
230
+ }
231
+ }
232
+
186
233
  if (body.body.type === "NewObjectExpression") {
187
234
  const noe = body.body as NewObjectExpression;
188
- const p1 = body.params[0];
189
- this.scope.alias(select.sourceParameter, p1, select);
190
235
  const fields = noe.properties as ExpressionAs[];
191
- return this.visit({ ... select, fields } as SelectStatement);
236
+ return this.visit({ ... select, where, fields } as SelectStatement);
192
237
  }
193
- return this.visit({ ... select, fields: [body.body] } as SelectStatement);
238
+ return this.visit({ ... select, where, fields: [body.body] } as SelectStatement);
194
239
  }
195
240
  }
196
241
 
@@ -200,7 +245,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
200
245
 
201
246
  if (identifier?.value === "Sql") {
202
247
  const argList = e.arguments?.map((x) => this.visit(x)) ?? [];
203
- const transformedCallee = this.compiler.sqlMethodTransformer(this.compiler, chain, argList.map((al) => al.flat(2)) as any[]);
248
+ const transformedCallee = this.compiler.sqlMethodTransformer(this.compiler, chain.map((x) => x.member), argList.map((al) => al.flat(2)) as any[]);
204
249
  if (transformedCallee) {
205
250
  return prepare `${transformedCallee}`;
206
251
  }
@@ -233,7 +278,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
233
278
 
234
279
  let where = select.where;
235
280
 
236
- for (const { fkColumn, relatedKeyColumn } of relation.relation.fkMap) {
281
+ for (const { fkColumn, relatedKeyColumn } of relation.relation.relatedRelation.fkMap) {
237
282
  const targetKey = MemberExpression.create({
238
283
  target: parameter,
239
284
  property: Identifier.create({
@@ -347,15 +392,15 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
347
392
  if (parameter) {
348
393
  if (parameter === this.root) {
349
394
  // we have a parameter...
350
- return [(p) => p[chain[0]]];
395
+ return [(p) => p[chain[0].member]];
351
396
  }
352
397
  if (parameter.value) {
353
398
  const value = parameter.value;
354
- return [() => value[chain[0]]];
399
+ return [() => value[chain[0].member]];
355
400
  }
356
401
  const scope = this.scope.get(parameter);
357
402
  if (scope.isRuntimeParam) {
358
- return [(p) => p[chain[0]]];
403
+ return [(p) => p[chain[0].member]];
359
404
  }
360
405
 
361
406
  const name = this.scope.nameOf(parameter);
@@ -366,7 +411,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
366
411
  // chain[0] = namingConvention(chain[0]);
367
412
  // }
368
413
 
369
- return [ QueryParameter.create(() => name) , "." , chain.join(".")];
414
+ return [ QueryParameter.create(() => name) , "." , chain.map((x) => x.member).join(".")];
370
415
  }
371
416
  }
372
417
 
@@ -809,7 +854,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
809
854
  x = resolved;
810
855
  }
811
856
 
812
- const chain = [];
857
+ const chain = [] as { member: string, args?: Expression[]}[];
813
858
  while (x) {
814
859
  if (x.type === "ParameterExpression") {
815
860
  return { parameter: x as ParameterExpression, chain };
@@ -820,8 +865,20 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
820
865
  if (x.type === "MemberExpression") {
821
866
  const me = x as MemberExpression;
822
867
  x = me.target;
823
- chain.unshift((me.property as Identifier).value);
868
+ chain.unshift({ member: (me.property as Identifier).value });
869
+ continue;
870
+ }
871
+ if (x.type === "CallExpression") {
872
+ const ce = x as CallExpression;
873
+ const me = ce.callee.type === "MemberExpression" && ce.callee as MemberExpression;
874
+ if (!me) {
875
+ throw new EntityAccessError("Invalid call expression");
876
+ }
877
+ x = me.target;
878
+ chain.splice(0, 0, { member: (me.property as Identifier).value, args: ce.arguments });
879
+ continue;
824
880
  }
881
+ throw new EntityAccessError(`Invalid expression expression ${x.type}`);
825
882
  }
826
883
 
827
884
  throw new NotSupportedError();
@@ -215,6 +215,8 @@ export default class ArrowToExpression extends BabelVisitor<Expression> {
215
215
  // we need to sanitize callee
216
216
  this.sanitize(callee);
217
217
 
218
+ // change Sql.coll. with arrow functions to move it inside
219
+
218
220
  return CallExpression.create({
219
221
  callee: callee ? this.visit(callee) : void 0,
220
222
  arguments: args ? args.map((x) => this.visit(x)) : []
@@ -302,6 +304,11 @@ export default class ArrowToExpression extends BabelVisitor<Expression> {
302
304
  throw new Error(`Unknown identifier ${name}`);
303
305
  }
304
306
  return;
307
+ case "CallExpression":
308
+ for (const iterator of node.arguments) {
309
+ this.visit(iterator as bpe.Expression);
310
+ }
311
+ return;
305
312
  case "MemberExpression":
306
313
  case "OptionalMemberExpression":
307
314
  return this.sanitize(node.object);
package/src/sql/ISql.ts CHANGED
@@ -90,6 +90,7 @@ export interface ISql {
90
90
  addHours(d: Date|DateTime, n: number): Date;
91
91
  addMinutes(d: Date|DateTime, n: number): Date;
92
92
  addSeconds(d: Date|DateTime, n: number): Date;
93
+ epoch(d: DateTime);
93
94
  }
94
95
 
95
96
  }
@@ -0,0 +1,38 @@
1
+ import assert from "assert";
2
+ import { TestConfig } from "../../TestConfig.js";
3
+ import { createContext, headPhoneCategory } from "../../model/createContext.js";
4
+ import Sql from "../../../sql/Sql.js";
5
+
6
+ export default async function(this: TestConfig) {
7
+
8
+ if (!this.db) {
9
+ return;
10
+ }
11
+
12
+ const context = await createContext(this.driver);
13
+
14
+ let report;
15
+ // report = await context.users.all()
16
+ // .where({}, (p) => (x) => x.orders.some((oi) => oi.customerID > 0))
17
+ // .map({}, (p) => (x) => ({
18
+ // total: Sql.coll.sum(x.orders.map((o) => Sql.coll.sum(o.orderItems.map((oi) => oi.amount))))
19
+ // })
20
+ // )
21
+ // .trace(console.log)
22
+ // .first();
23
+
24
+ // assert.notEqual(null, report);
25
+
26
+ report = await context.users.all()
27
+ .where({}, (p) => (x) => x.orders.some((oi) => oi.customerID > 0))
28
+ .map({}, (p) => (x) => ({
29
+ total: Sql.coll.sum(x.orders
30
+ .filter((o) => o.status === "pending")
31
+ .map((o) => Sql.coll.sum(o.orderItems.map((oi) => oi.amount))))
32
+ })
33
+ )
34
+ .trace(console.log)
35
+ .first();
36
+
37
+ assert.notEqual(null, report);
38
+ }
@@ -342,6 +342,9 @@ export class Order {
342
342
  @Column({ dataType: "Char", length: 200, nullable: true})
343
343
  public purchaseOrder: string;
344
344
 
345
+ @Column({ dataType: "Char", length: 20, default: () => "pending"})
346
+ public status: string;
347
+
345
348
  public orderItems: OrderItem[];
346
349
 
347
350
  public customer: User;