@entity-access/entity-access 1.0.316 → 1.0.318

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 (38) hide show
  1. package/dist/compiler/postgres/PostgreSqlMethodTransformer.js +1 -1
  2. package/dist/compiler/postgres/PostgreSqlMethodTransformer.js.map +1 -1
  3. package/dist/drivers/sql-server/ExpressionToSqlServer.d.ts.map +1 -1
  4. package/dist/drivers/sql-server/ExpressionToSqlServer.js +5 -1
  5. package/dist/drivers/sql-server/ExpressionToSqlServer.js.map +1 -1
  6. package/dist/model/EntityContext.d.ts.map +1 -1
  7. package/dist/model/EntityContext.js +4 -0
  8. package/dist/model/EntityContext.js.map +1 -1
  9. package/dist/model/EntityQuery.d.ts +3 -1
  10. package/dist/model/EntityQuery.d.ts.map +1 -1
  11. package/dist/model/EntityQuery.js +125 -56
  12. package/dist/model/EntityQuery.js.map +1 -1
  13. package/dist/model/IFilterWithParameter.d.ts +5 -0
  14. package/dist/model/IFilterWithParameter.d.ts.map +1 -1
  15. package/dist/query/ast/ExpressionToSql.d.ts.map +1 -1
  16. package/dist/query/ast/ExpressionToSql.js +4 -1
  17. package/dist/query/ast/ExpressionToSql.js.map +1 -1
  18. package/dist/query/ast/Expressions.d.ts +1 -0
  19. package/dist/query/ast/Expressions.d.ts.map +1 -1
  20. package/dist/query/ast/Expressions.js.map +1 -1
  21. package/dist/tests/db-tests/tests/select-items-sum.d.ts.map +1 -1
  22. package/dist/tests/db-tests/tests/select-items-sum.js +17 -8
  23. package/dist/tests/db-tests/tests/select-items-sum.js.map +1 -1
  24. package/dist/tests/db-tests/tests/update-select.d.ts +3 -0
  25. package/dist/tests/db-tests/tests/update-select.d.ts.map +1 -0
  26. package/dist/tests/db-tests/tests/update-select.js +19 -0
  27. package/dist/tests/db-tests/tests/update-select.js.map +1 -0
  28. package/dist/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +1 -1
  30. package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +1 -1
  31. package/src/drivers/sql-server/ExpressionToSqlServer.ts +6 -1
  32. package/src/model/EntityContext.ts +4 -0
  33. package/src/model/EntityQuery.ts +99 -28
  34. package/src/model/IFilterWithParameter.ts +4 -0
  35. package/src/query/ast/ExpressionToSql.ts +5 -1
  36. package/src/query/ast/Expressions.ts +2 -0
  37. package/src/tests/db-tests/tests/select-items-sum.ts +19 -9
  38. package/src/tests/db-tests/tests/update-select.ts +28 -0
@@ -176,7 +176,7 @@ export default class EntityQuery<T = any>
176
176
  }
177
177
 
178
178
  async delete(p, f): Promise<number> {
179
- if (p || f) {
179
+ if (f) {
180
180
  return this.where(p, f).delete(void 0, void 0);
181
181
  }
182
182
 
@@ -218,9 +218,60 @@ export default class EntityQuery<T = any>
218
218
  }
219
219
  }
220
220
 
221
+ async updateSelect(p?, f?): Promise<T[]> {
222
+ const updateStatement = this.getUpdateStatement(p, f, true);
223
+
224
+ await using scope = new AsyncDisposableScope();
225
+ const session = this.context.logger?.newSession() ?? Logger.nullLogger;
226
+ let query: { text: string, values: any[]};
227
+ try {
228
+ scope.register(session);
229
+ const type = this.type;
230
+ const signal = this.signal;
231
+
232
+ const relationMapper = new RelationMapper(this.context.changeSet);
233
+
234
+ signal?.throwIfAborted();
235
+
236
+ query = this.context.driver.compiler.compileExpression(this, updateStatement);
237
+ this.traceQuery?.(query.text);
238
+ const reader = await this.context.connection.executeReader(query, signal);
239
+ scope.register(reader);
240
+ const results = [] as T[];
241
+ for await (const iterator of reader.next(10, signal)) {
242
+ const item = type.map(iterator) as any;
243
+ // set identity...
244
+ const entry = this.context.changeSet.getEntry(item, item);
245
+ relationMapper.fix(entry);
246
+ results.push(entry.entity);
247
+ }
248
+ return results;
249
+ } catch(error) {
250
+ session.error(`Failed executing ${query?.text}\n${error.stack ?? error}`);
251
+ throw error;
252
+ }
253
+ }
254
+
221
255
  async update(p?, f?): Promise<number> {
222
256
 
223
- if (p || f) {
257
+ const updateStatement = this.getUpdateStatement(p, f);
258
+
259
+ const session = this.context.logger?.newSession() ?? Logger.nullLogger;
260
+ let query;
261
+ try {
262
+ query = this.context.driver.compiler.compileExpression(this, updateStatement);
263
+ this.traceQuery?.(query.text);
264
+ const r = await this.context.connection.executeQuery(query);
265
+ return r.updated;
266
+ } catch (error) {
267
+ session.error(`Failed executing ${query?.text}\r\n${error.stack ?? error}`);
268
+ throw error;
269
+ }
270
+ }
271
+
272
+ getUpdateStatement(p?, f?, returnEntity = false) {
273
+
274
+ if (f) {
224
275
  return this.extend(p, f, (select, body) => {
225
276
  const fields = [] as Expression[];
226
277
  switch(body.type) {
@@ -238,7 +289,7 @@ export default class EntityQuery<T = any>
238
289
  break;
239
290
  }
240
291
  return { ... select, fields };
241
- }).update();
292
+ }).getUpdateStatement(void 0, void 0, returnEntity);
242
293
  }
243
294
 
244
295
  const as = Expression.parameter("s1", this.type);
@@ -253,13 +304,13 @@ export default class EntityQuery<T = any>
253
304
  const fieldMap = new Set();
254
305
 
255
306
  for (const iterator of this.selectStatement.fields) {
256
- if(iterator.type !== "ExpressionAs") {
307
+ if (iterator.type !== "ExpressionAs") {
257
308
  throw new Error(`Invalid expression ${iterator.type}`);
258
309
  }
259
310
  const eAs = iterator as ExpressionAs;
260
311
  const { field } = this.type.getProperty(eAs.alias.value);
261
312
  fieldMap.add(field.columnName);
262
- set.push(Expression.assign( Expression.quotedIdentifier(field.columnName), Expression.member(as, Expression.quotedIdentifier(eAs.alias.value))));
313
+ set.push(Expression.assign(Expression.quotedIdentifier(field.columnName), Expression.member(as, Expression.quotedIdentifier(eAs.alias.value))));
263
314
  }
264
315
 
265
316
  let where = null as Expression;
@@ -272,7 +323,18 @@ export default class EntityQuery<T = any>
272
323
  if (fieldMap.has(iterator.columnName)) {
273
324
  continue;
274
325
  }
275
- this.selectStatement.fields.push( Expression.member( this.selectStatement.sourceParameter, Expression.quotedIdentifier(iterator.columnName)));
326
+ this.selectStatement.fields.push(Expression.member(this.selectStatement.sourceParameter, Expression.quotedIdentifier(iterator.columnName)));
327
+ }
328
+
329
+ let returnUpdated = null as ExpressionAs[];
330
+ if(returnEntity) {
331
+ returnUpdated = [];
332
+ for (const iterator of this.type.columns) {
333
+ returnUpdated.push(Expression.as(
334
+ Expression.identifier(iterator.columnName),
335
+ Expression.quotedIdentifier(iterator.name)
336
+ ));
337
+ }
276
338
  }
277
339
 
278
340
  const updateStatement = UpdateStatement.create({
@@ -281,20 +343,10 @@ export default class EntityQuery<T = any>
281
343
  table: this.type.fullyQualifiedName,
282
344
  model: this.type,
283
345
  where,
284
- join
346
+ join,
347
+ returnUpdated
285
348
  });
286
-
287
- const session = this.context.logger?.newSession() ?? Logger.nullLogger;
288
- let query;
289
- try {
290
- query = this.context.driver.compiler.compileExpression(this, updateStatement);
291
- this.traceQuery?.(query.text);
292
- const r = await this.context.connection.executeQuery(query);
293
- return r.updated;
294
- } catch (error) {
295
- session.error(`Failed executing ${query?.text}\r\n${error.stack ?? error}`);
296
- throw error;
297
- }
349
+ return updateStatement;
298
350
  }
299
351
 
300
352
  async toArray(): Promise<T[]> {
@@ -453,20 +505,35 @@ export default class EntityQuery<T = any>
453
505
  return new EntityQuery({ ... this, selectStatement: { ... this.selectStatement, offset: n} });
454
506
  }
455
507
 
456
- async sum(parameters?:any, fx?: any): Promise<number> {
457
- if (parameters !== void 0) {
508
+ async sum(parameters?:any, fx?: any): Promise<any> {
509
+ if (fx !== void 0) {
458
510
  return this.map(parameters, fx).sum();
459
511
  }
460
- const field = this.selectStatement.fields[0];
461
- const select = { ... this.selectStatement, fields: [
462
- ExpressionAs.create({
512
+
513
+ const fields = [];
514
+
515
+ let fieldName;
516
+
517
+ for (const field of this.selectStatement.fields) {
518
+ let expression = field;
519
+ if (field.type === "ExpressionAs") {
520
+ const fe = field as ExpressionAs;
521
+ expression = fe.expression;
522
+ fieldName = fe.alias.value;
523
+ } else {
524
+ fieldName = "c1";
525
+ }
526
+
527
+ fields.push(ExpressionAs.create({
463
528
  expression: Expression.callExpression(
464
529
  "COALESCE",
465
- Expression.callExpression("SUM", field),
530
+ Expression.callExpression("SUM", expression),
466
531
  NumberLiteral.zero),
467
- alias: Expression.identifier("c1")
468
- })
469
- ],
532
+ alias: Expression.quotedIdentifier(fieldName)
533
+ }));
534
+ }
535
+
536
+ const select = { ... this.selectStatement, fields,
470
537
  orderBy: void 0
471
538
  };
472
539
 
@@ -477,9 +544,13 @@ export default class EntityQuery<T = any>
477
544
  let query;
478
545
  try {
479
546
  query = this.context.driver.compiler.compileExpression(nq, select);
547
+ this.traceQuery?.(query.text);
480
548
  const reader = await this.context.connection.executeReader(query);
481
549
  scope.register(reader);
482
550
  for await (const iterator of reader.next()) {
551
+ if (fields.length > 1) {
552
+ return iterator as any;
553
+ }
483
554
  return iterator.c1 as number;
484
555
  }
485
556
  // this is special case when database does not return any count
@@ -7,6 +7,8 @@ export type ILambdaExpression<P = any, T = any, TR = any> = [input: P, x: (p: P)
7
7
 
8
8
  export type IFilterExpression<P = any, T = any> = [input: P, x: (p: P) => (s: T) => boolean];
9
9
 
10
+ export type IFieldsAsNumbers<T> = { [P in keyof T]: number };
11
+
10
12
  export interface IBaseQuery<T> {
11
13
  enumerate(): AsyncGenerator<T>;
12
14
 
@@ -32,6 +34,7 @@ export interface IBaseQuery<T> {
32
34
 
33
35
  sum(): Promise<number>;
34
36
  sum<P>(parameters: P, fx: (p: P) => (x: T) => number): Promise<number>;
37
+ sum<P, TR>(parameters: P, fx: (p: P) => (x: T) => TR): Promise<IFieldsAsNumbers<TR>>;
35
38
 
36
39
 
37
40
  withSignal<DT>(this:DT, signal: AbortSignal): DT;
@@ -39,6 +42,7 @@ export interface IBaseQuery<T> {
39
42
  include<TR>(fx: (x: T) => TR | TR[]): IBaseQuery<T>;
40
43
 
41
44
  update<P>(parameters: P, fx: (p: P) => (x:T) => Partial<T>): Promise<number>;
45
+ updateSelect<P>(parameters: P, fx: (p: P) => (x:T) => Partial<T>): Promise<T[]>;
42
46
 
43
47
  /**
44
48
  * Warning !! Be careful, this will delete rows from the database and neither soft delete nor any other events will be invoked.
@@ -587,7 +587,11 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
587
587
  const joinName = this.scope.nameOf(as);
588
588
  const asName = this.scope.nameOf(e.sourceParameter);
589
589
  const set = this.visitArray(e.set, ",");
590
- return prepare `WITH ${joinName} as (${join}) UPDATE ${table} ${asName} SET ${set} FROM ${joinName} WHERE ${where}`;
590
+ const returning = e.returnUpdated ? [ ` RETURNING `, ... e.returnUpdated.map((r, i) => i
591
+ ? [ `, ${asName}.`, this.visit(r.expression), ` as ${this.visit(r.alias)}`]
592
+ : [ `${asName}.`, this.visit(r.expression), ` as ${this.visit(r.alias)}`]
593
+ ) ] : [];
594
+ return prepare `WITH ${joinName} as (${join}) UPDATE ${table} ${asName} SET ${set} FROM ${joinName} WHERE ${where} ${returning}`;
591
595
 
592
596
  }
593
597
 
@@ -423,6 +423,8 @@ export class UpdateStatement extends Expression {
423
423
 
424
424
  join: JoinExpression;
425
425
 
426
+ returnUpdated: ExpressionAs[];
427
+
426
428
  }
427
429
 
428
430
  export class UpsertStatement extends Expression {
@@ -20,16 +20,16 @@ export default async function(this: TestConfig) {
20
20
 
21
21
  // assert.notEqual(null, user);
22
22
 
23
- report = await context.users.all()
24
- // .where({}, (p) => (x) => x.orders.some((oi) => oi.customerID > 0))
25
- .map({}, (p) => (x) => ({
26
- total: 5 * Sql.coll.sum(x.orders.map((o) => Sql.coll.sum(o.orderItems.map((oi) => oi.amount))))
27
- })
28
- )
29
- .trace(console.log)
30
- .first();
23
+ // report = await context.users.all()
24
+ // // .where({}, (p) => (x) => x.orders.some((oi) => oi.customerID > 0))
25
+ // .map({}, (p) => (x) => ({
26
+ // total: 5 * Sql.coll.sum(x.orders.map((o) => Sql.coll.sum(o.orderItems.map((oi) => oi.amount))))
27
+ // })
28
+ // )
29
+ // .trace(console.log)
30
+ // .first();
31
31
 
32
- assert.notEqual(null, report);
32
+ // assert.notEqual(null, report);
33
33
 
34
34
  // await context.orders.asQuery()
35
35
  // .update(void 0, (p) => (x) => ({
@@ -47,4 +47,14 @@ export default async function(this: TestConfig) {
47
47
  .first();
48
48
 
49
49
  assert.notEqual(null, report);
50
+
51
+ // const r = await context.users.all()
52
+ // .trace(console.log)
53
+ // .sum(void 0, (p) => (x) => ({
54
+ // paid: x.orders.some((o) => o.status === "pending") ? 1 : 0,
55
+ // total: 1
56
+ // }));
57
+
58
+ // assert.notEqual(r.paid, undefined);
59
+ // assert.notEqual(r.total, undefined);
50
60
  }
@@ -0,0 +1,28 @@
1
+ import assert from "assert";
2
+ import { Sql } from "../../../index.js";
3
+ import { TestConfig } from "../../TestConfig.js";
4
+ import { createContext } from "../../model/createContext.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
+ const first = await context.products.all().first();
15
+
16
+ first.name = "First product";
17
+
18
+ await context.saveChanges();
19
+
20
+
21
+ const results = await context.products
22
+ .where(void 0, (p) => (x) => x.productID > 1)
23
+ .trace(console.log)
24
+ .updateSelect(void 0, (p) => (x) => ({
25
+ productDescription: "updated"
26
+ }));
27
+ assert.equal(results[0].productDescription, "updated");
28
+ }