@entity-access/entity-access 1.0.99 → 1.0.101

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 (35) hide show
  1. package/dist/compiler/postgres/PostgreSqlMethodTransformer.d.ts.map +1 -1
  2. package/dist/compiler/postgres/PostgreSqlMethodTransformer.js +11 -0
  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 +11 -0
  6. package/dist/compiler/sql-server/SqlServerSqlMethodTransformer.js.map +1 -1
  7. package/dist/entity-query/EntityType.d.ts +5 -4
  8. package/dist/entity-query/EntityType.d.ts.map +1 -1
  9. package/dist/entity-query/EntityType.js.map +1 -1
  10. package/dist/model/EntityQuery.d.ts +2 -1
  11. package/dist/model/EntityQuery.d.ts.map +1 -1
  12. package/dist/model/EntityQuery.js +58 -4
  13. package/dist/model/EntityQuery.js.map +1 -1
  14. package/dist/model/IFilterWithParameter.d.ts +2 -0
  15. package/dist/model/IFilterWithParameter.d.ts.map +1 -1
  16. package/dist/query/ast/ExpressionToSql.d.ts +4 -2
  17. package/dist/query/ast/ExpressionToSql.d.ts.map +1 -1
  18. package/dist/query/ast/ExpressionToSql.js +114 -53
  19. package/dist/query/ast/ExpressionToSql.js.map +1 -1
  20. package/dist/sql/ISql.d.ts +5 -0
  21. package/dist/sql/ISql.d.ts.map +1 -1
  22. package/dist/tests/db-tests/tests/select-items-map.d.ts +3 -0
  23. package/dist/tests/db-tests/tests/select-items-map.d.ts.map +1 -0
  24. package/dist/tests/db-tests/tests/select-items-map.js +14 -0
  25. package/dist/tests/db-tests/tests/select-items-map.js.map +1 -0
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +1 -1
  28. package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +12 -1
  29. package/src/compiler/sql-server/SqlServerSqlMethodTransformer.ts +12 -1
  30. package/src/entity-query/EntityType.ts +6 -1
  31. package/src/model/EntityQuery.ts +63 -5
  32. package/src/model/IFilterWithParameter.ts +3 -0
  33. package/src/query/ast/ExpressionToSql.ts +140 -68
  34. package/src/sql/ISql.ts +5 -0
  35. package/src/tests/db-tests/tests/select-items-map.ts +20 -0
@@ -1,9 +1,10 @@
1
+ import EntityAccessError from "../common/EntityAccessError.js";
1
2
  import Logger from "../common/Logger.js";
2
3
  import { DisposableScope } from "../common/usingAsync.js";
3
4
  import { ServiceProvider } from "../di/di.js";
4
5
  import { IDbReader } from "../drivers/base/BaseDriver.js";
5
6
  import EntityType from "../entity-query/EntityType.js";
6
- import { CallExpression, Expression, ExpressionAs, Identifier, OrderByExpression, SelectStatement } from "../query/ast/Expressions.js";
7
+ import { CallExpression, Expression, ExpressionAs, Identifier, NewObjectExpression, OrderByExpression, SelectStatement } from "../query/ast/Expressions.js";
7
8
  import { ITextQuery } from "../query/ast/IStringTransformer.js";
8
9
  import { QueryExpander } from "../query/expander/QueryExpander.js";
9
10
  import EntityContext from "./EntityContext.js";
@@ -29,10 +30,26 @@ export default class EntityQuery<T = any>
29
30
  return this.map(p, fx);
30
31
  }
31
32
 
32
- map(p: any, fx: any): any {
33
- // const source = this.source.copy();
34
- // const { select } = source;
35
- // const exp = this.source.context.driver.compiler.compileToExpression(source, p, fx);
33
+ map(parameters: any, fx: any): any {
34
+ return this.extend(parameters, fx, (select, body) => {
35
+ const fields = [] as Expression[];
36
+ switch(body.type) {
37
+ case "NewObjectExpression":
38
+ const noe = body as NewObjectExpression;
39
+ for (const iterator of noe.properties) {
40
+ fields.push(ExpressionAs.create({
41
+ expression: iterator.expression,
42
+ alias: iterator.alias
43
+ }));
44
+ }
45
+ break;
46
+ default:
47
+ fields.push(body);
48
+ break;
49
+
50
+ }
51
+ return { ... select, fields };
52
+ });
36
53
  }
37
54
 
38
55
  withSignal(signal: AbortSignal): any {
@@ -176,6 +193,47 @@ export default class EntityQuery<T = any>
176
193
  return new EntityQuery({ ... this, selectStatement: { ... this.selectStatement, offset: n} });
177
194
  }
178
195
 
196
+ async sum(parameters?:any, fx?: any): Promise<number> {
197
+ if (parameters !== void 0) {
198
+ return this.map(parameters, fx).sum();
199
+ }
200
+ const field = this.selectStatement.fields[0];
201
+ const select = { ... this.selectStatement, fields: [
202
+ ExpressionAs.create({
203
+ expression: CallExpression.create({
204
+ callee: Identifier.create({ value: "SUM"}),
205
+ arguments: [ field]
206
+ }),
207
+ alias: Expression.identifier("c1")
208
+ })
209
+ ],
210
+ orderBy: void 0
211
+ };
212
+
213
+ const nq = new EntityQuery({ ... this, selectStatement: select });
214
+
215
+ const scope = new DisposableScope();
216
+ const session = this.context.logger?.newSession() ?? Logger.nullLogger;
217
+ let query;
218
+ try {
219
+ query = this.context.driver.compiler.compileExpression(nq, select);
220
+ const reader = await this.context.connection.executeReader(query);
221
+ scope.register(reader);
222
+ for await (const iterator of reader.next()) {
223
+ return iterator.c1 as number;
224
+ }
225
+ // this is special case when database does not return any count
226
+ // like sql server
227
+ return 0;
228
+ } catch (error) {
229
+ session.error(`Failed executing ${query?.text}\r\n${error.stack ?? error}`);
230
+ throw error;
231
+ } finally {
232
+ await scope.dispose();
233
+ }
234
+
235
+ }
236
+
179
237
  async count(parameters?:any, fx?: any): Promise<number> {
180
238
  if (parameters !== void 0) {
181
239
  return this.where(parameters, fx).count();
@@ -25,6 +25,9 @@ export interface IBaseQuery<T> {
25
25
  count(): Promise<number>;
26
26
  count<P>(parameters: P, fx: (p: P) => (x: T) => boolean): Promise<number>;
27
27
 
28
+ sum(): Promise<number>;
29
+ sum<P>(parameters: P, fx: (p: P) => (x: T) => number): Promise<number>;
30
+
28
31
  withSignal<DT>(this:DT, signal: AbortSignal): DT;
29
32
 
30
33
  include<TR>(fx: (x: T) => TR | TR[]): IBaseQuery<T>;
@@ -1,10 +1,11 @@
1
+ import EntityAccessError from "../../common/EntityAccessError.js";
1
2
  import { DisposableScope } from "../../common/usingAsync.js";
2
3
  import QueryCompiler from "../../compiler/QueryCompiler.js";
3
- import EntityType from "../../entity-query/EntityType.js";
4
+ import EntityType, { IEntityProperty } from "../../entity-query/EntityType.js";
4
5
  import EntityQuery from "../../model/EntityQuery.js";
5
6
  import { FilteredExpression, filteredSymbol } from "../../model/events/FilteredExpression.js";
6
7
  import { NotSupportedError } from "../parser/NotSupportedError.js";
7
- import { BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NotExits, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateLiteral, UnionAllStatement, UpdateStatement, ValuesStatement } from "./Expressions.js";
8
+ import { ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NotExits, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateLiteral, UnionAllStatement, UpdateStatement, ValuesStatement } from "./Expressions.js";
8
9
  import { ITextQuery, QueryParameter, prepare, prepareJoin } from "./IStringTransformer.js";
9
10
  import ParameterScope from "./ParameterScope.js";
10
11
  import Visitor from "./Visitor.js";
@@ -166,74 +167,15 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
166
167
 
167
168
  const body = e.arguments[0] as ExpressionType;
168
169
  if (body.type === "ArrowFunctionExpression") {
169
-
170
- const param1 = body.params[0];
171
- const relatedModel = relation.relation.relatedEntity;
172
- const relatedType = relatedModel.typeClass;
173
-
174
- let select: SelectStatement;
175
-
176
- if (this.source?.context) {
177
- const query = FilteredExpression.isFiltered(e)
178
- ? this.source.context.query(relatedType)
179
- : this.source.context.filteredQuery(relatedType, "include", false);
180
- select = { ... (query as EntityQuery).selectStatement };
181
- select.fields = [
182
- NumberLiteral.create({ value: 1})
183
- ];
184
- } else {
185
- select = relatedModel.selectOneNumber();
186
- }
187
-
188
- param1.model = relatedModel;
189
- this.scope.create({ parameter: param1, model: relatedModel, selectStatement: select });
190
- this.scope.alias(param1, select.sourceParameter, select);
191
- select.sourceParameter = param1;
192
- select[filteredSymbol] = true;
193
- const targetKey = MemberExpression.create({
194
- target: parameter,
195
- property: Identifier.create({
196
- value: targetType.keys[0].columnName
197
- })
198
- });
199
-
200
- const relatedKey = MemberExpression.create({
201
- target: param1,
202
- property: Identifier.create({
203
- value: relation.relation.fkColumn.columnName
204
- })
205
- });
206
-
207
-
208
- const join = Expression.logicalAnd(
209
- Expression.equal(targetKey, relatedKey),
210
- body.body
211
- );
212
-
213
- let where = select.where;
214
-
215
- if(where) {
216
- where = BinaryExpression.create({
217
- left: select.where,
218
- operator: "AND",
219
- right: join
220
- });
221
- } else {
222
- where = join;
223
- }
224
-
225
- select.where = where;
226
-
227
- const exists = ExistsExpression.create({
228
- target: select
229
- });
230
-
231
- const r = this.visit(exists);
232
- this.scope.delete(param1);
233
- this.scope.delete(select.sourceParameter);
234
- return r;
170
+ return this.expandSome(body, relation, e, parameter, targetType);
235
171
  }
172
+ }
173
+ if (/^(map|select)$/i.test(chain[1])) {
236
174
 
175
+ const body = e.arguments[0] as ExpressionType;
176
+ if (body.type === "ArrowFunctionExpression") {
177
+ return this.expandMap(body, relation, e, parameter, targetType);
178
+ }
237
179
  }
238
180
  }
239
181
  }
@@ -250,6 +192,136 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
250
192
  return prepare `${this.visit(e.callee)}(${args})`;
251
193
  }
252
194
 
195
+ expandMap(body: ArrowFunctionExpression, relation: IEntityProperty, e: CallExpression, parameter: ParameterExpression, targetType: EntityType): ITextQuery {
196
+ const param1 = body.params[0];
197
+ const relatedModel = relation.relation.relatedEntity;
198
+ const relatedType = relatedModel.typeClass;
199
+
200
+ let select: SelectStatement;
201
+
202
+ if (this.source?.context) {
203
+ const query = FilteredExpression.isFiltered(e)
204
+ ? this.source.context.query(relatedType)
205
+ : this.source.context.filteredQuery(relatedType, "include", false);
206
+ select = { ...(query as EntityQuery).selectStatement };
207
+ } else {
208
+ select = relatedModel.selectOneNumber();
209
+ }
210
+
211
+ param1.model = relatedModel;
212
+ this.scope.create({ parameter: param1, model: relatedModel, selectStatement: select });
213
+ this.scope.alias(param1, select.sourceParameter, select);
214
+ select.sourceParameter = param1;
215
+ select[filteredSymbol] = true;
216
+ const targetKey = MemberExpression.create({
217
+ target: parameter,
218
+ property: Identifier.create({
219
+ value: targetType.keys[0].columnName
220
+ })
221
+ });
222
+
223
+ const relatedKey = MemberExpression.create({
224
+ target: param1,
225
+ property: Identifier.create({
226
+ value: relation.relation.fkColumn.columnName
227
+ })
228
+ });
229
+
230
+
231
+ const join = Expression.equal(targetKey, relatedKey);
232
+
233
+ let where = select.where;
234
+
235
+ if (where) {
236
+ where = BinaryExpression.create({
237
+ left: select.where,
238
+ operator: "AND",
239
+ right: join
240
+ });
241
+ } else {
242
+ where = join;
243
+ }
244
+
245
+ select.where = where;
246
+
247
+ const exists = ExistsExpression.create({
248
+ target: select
249
+ });
250
+
251
+ const r = this.visit(exists);
252
+ this.scope.delete(param1);
253
+ this.scope.delete(select.sourceParameter);
254
+ return r;
255
+ }
256
+
257
+ expandSome(body: ArrowFunctionExpression, relation: IEntityProperty, e: CallExpression, parameter: ParameterExpression, targetType: EntityType) {
258
+ const param1 = body.params[0];
259
+ const relatedModel = relation.relation.relatedEntity;
260
+ const relatedType = relatedModel.typeClass;
261
+
262
+ let select: SelectStatement;
263
+
264
+ if (this.source?.context) {
265
+ const query = FilteredExpression.isFiltered(e)
266
+ ? this.source.context.query(relatedType)
267
+ : this.source.context.filteredQuery(relatedType, "include", false);
268
+ select = { ...(query as EntityQuery).selectStatement };
269
+ select.fields = [
270
+ NumberLiteral.create({ value: 1 })
271
+ ];
272
+ } else {
273
+ select = relatedModel.selectOneNumber();
274
+ }
275
+
276
+ param1.model = relatedModel;
277
+ this.scope.create({ parameter: param1, model: relatedModel, selectStatement: select });
278
+ this.scope.alias(param1, select.sourceParameter, select);
279
+ select.sourceParameter = param1;
280
+ select[filteredSymbol] = true;
281
+ const targetKey = MemberExpression.create({
282
+ target: parameter,
283
+ property: Identifier.create({
284
+ value: targetType.keys[0].columnName
285
+ })
286
+ });
287
+
288
+ const relatedKey = MemberExpression.create({
289
+ target: param1,
290
+ property: Identifier.create({
291
+ value: relation.relation.fkColumn.columnName
292
+ })
293
+ });
294
+
295
+
296
+ const join = Expression.logicalAnd(
297
+ Expression.equal(targetKey, relatedKey),
298
+ body.body
299
+ );
300
+
301
+ let where = select.where;
302
+
303
+ if (where) {
304
+ where = BinaryExpression.create({
305
+ left: select.where,
306
+ operator: "AND",
307
+ right: join
308
+ });
309
+ } else {
310
+ where = join;
311
+ }
312
+
313
+ select.where = where;
314
+
315
+ const exists = ExistsExpression.create({
316
+ target: select
317
+ });
318
+
319
+ const r = this.visit(exists);
320
+ this.scope.delete(param1);
321
+ this.scope.delete(select.sourceParameter);
322
+ return r;
323
+ }
324
+
253
325
  visitIdentifier(e: Identifier): ITextQuery {
254
326
  // need to visit parameters
255
327
  return [e.value];
package/src/sql/ISql.ts CHANGED
@@ -3,6 +3,11 @@ import DateTime from "../types/DateTime.js";
3
3
  export interface ISql {
4
4
 
5
5
  in<T>(a: T, array: T[]): boolean;
6
+ coll: {
7
+ sum<T>(a: number[]): number;
8
+ count<T>(a: number[]): number;
9
+ avg<T>(a: number[]): number
10
+ }
6
11
 
7
12
  cast: {
8
13
  asNumber(a: any): number;
@@ -0,0 +1,20 @@
1
+ import assert from "assert";
2
+ import { TestConfig } from "../../TestConfig.js";
3
+ import { createContext, headPhoneCategory } from "../../model/createContext.js";
4
+
5
+ export default async function(this: TestConfig) {
6
+
7
+ if (!this.db) {
8
+ return;
9
+ }
10
+
11
+ const context = await createContext(this.driver);
12
+
13
+ const sum = await context.orderItems.all()
14
+ .where({}, (p) => (x) => x.order.orderID > 0)
15
+ .map({}, (p) => ({ amount }) => amount)
16
+ .sum();
17
+
18
+ assert.notEqual(0, sum);
19
+
20
+ }