@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.
- package/dist/compiler/postgres/PostgreSqlMethodTransformer.d.ts.map +1 -1
- package/dist/compiler/postgres/PostgreSqlMethodTransformer.js +4 -1
- package/dist/compiler/postgres/PostgreSqlMethodTransformer.js.map +1 -1
- package/dist/compiler/sql-server/SqlServerSqlMethodTransformer.d.ts.map +1 -1
- package/dist/compiler/sql-server/SqlServerSqlMethodTransformer.js +3 -0
- package/dist/compiler/sql-server/SqlServerSqlMethodTransformer.js.map +1 -1
- package/dist/query/ast/ExpressionToSql.d.ts.map +1 -1
- package/dist/query/ast/ExpressionToSql.js +62 -14
- package/dist/query/ast/ExpressionToSql.js.map +1 -1
- package/dist/query/parser/ArrowToExpression.d.ts.map +1 -1
- package/dist/query/parser/ArrowToExpression.js +6 -0
- package/dist/query/parser/ArrowToExpression.js.map +1 -1
- package/dist/sql/ISql.d.ts +1 -0
- package/dist/sql/ISql.d.ts.map +1 -1
- package/dist/tests/db-tests/tests/select-items-sum.d.ts +3 -0
- package/dist/tests/db-tests/tests/select-items-sum.d.ts.map +1 -0
- package/dist/tests/db-tests/tests/select-items-sum.js +30 -0
- package/dist/tests/db-tests/tests/select-items-sum.js.map +1 -0
- package/dist/tests/model/ShoppingContext.d.ts +1 -0
- package/dist/tests/model/ShoppingContext.d.ts.map +1 -1
- package/dist/tests/model/ShoppingContext.js +4 -0
- package/dist/tests/model/ShoppingContext.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +4 -1
- package/src/compiler/sql-server/SqlServerSqlMethodTransformer.ts +3 -0
- package/src/query/ast/ExpressionToSql.ts +73 -16
- package/src/query/parser/ArrowToExpression.ts +7 -0
- package/src/sql/ISql.ts +1 -0
- package/src/tests/db-tests/tests/select-items-sum.ts +38 -0
- 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(
|
|
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(
|
|
209
|
+
if (/^(some|any)$/.test(lastMethod.member)) {
|
|
182
210
|
return this.visit(exists);
|
|
183
211
|
}
|
|
184
|
-
if (/^(map|select)$/.test(
|
|
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
|
@@ -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;
|