@entity-access/entity-access 1.0.233 → 1.0.234

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 (34) hide show
  1. package/dist/drivers/sql-server/ExpressionToSqlServer.d.ts +3 -1
  2. package/dist/drivers/sql-server/ExpressionToSqlServer.d.ts.map +1 -1
  3. package/dist/drivers/sql-server/ExpressionToSqlServer.js +30 -13
  4. package/dist/drivers/sql-server/ExpressionToSqlServer.js.map +1 -1
  5. package/dist/entity-query/EntityType.d.ts +0 -2
  6. package/dist/entity-query/EntityType.d.ts.map +1 -1
  7. package/dist/model/EntityQuery.d.ts +2 -1
  8. package/dist/model/EntityQuery.d.ts.map +1 -1
  9. package/dist/model/EntityQuery.js +85 -39
  10. package/dist/model/EntityQuery.js.map +1 -1
  11. package/dist/model/IFilterWithParameter.d.ts +6 -0
  12. package/dist/model/IFilterWithParameter.d.ts.map +1 -1
  13. package/dist/query/ast/ExpressionToSql.d.ts.map +1 -1
  14. package/dist/query/ast/ExpressionToSql.js +27 -13
  15. package/dist/query/ast/ExpressionToSql.js.map +1 -1
  16. package/dist/query/ast/Expressions.d.ts +8 -4
  17. package/dist/query/ast/Expressions.d.ts.map +1 -1
  18. package/dist/query/ast/Expressions.js.map +1 -1
  19. package/dist/tests/db-tests/tests/delete-items.d.ts +3 -0
  20. package/dist/tests/db-tests/tests/delete-items.d.ts.map +1 -0
  21. package/dist/tests/db-tests/tests/delete-items.js +25 -0
  22. package/dist/tests/db-tests/tests/delete-items.js.map +1 -0
  23. package/dist/tests/db-tests/tests/update-items.d.ts.map +1 -1
  24. package/dist/tests/db-tests/tests/update-items.js +12 -1
  25. package/dist/tests/db-tests/tests/update-items.js.map +1 -1
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +2 -2
  28. package/src/drivers/sql-server/ExpressionToSqlServer.ts +34 -14
  29. package/src/model/EntityQuery.ts +97 -44
  30. package/src/model/IFilterWithParameter.ts +7 -0
  31. package/src/query/ast/ExpressionToSql.ts +31 -14
  32. package/src/query/ast/Expressions.ts +9 -5
  33. package/src/tests/db-tests/tests/delete-items.ts +42 -0
  34. package/src/tests/db-tests/tests/update-items.ts +15 -1
@@ -3,7 +3,7 @@ import Logger from "../common/Logger.js";
3
3
  import { AsyncDisposableScope } from "../common/usingAsync.js";
4
4
  import { IDbReader } from "../drivers/base/BaseDriver.js";
5
5
  import EntityType from "../entity-query/EntityType.js";
6
- import { CallExpression, ExistsExpression, Expression, ExpressionAs, Identifier, InsertStatement, NewObjectExpression, NumberLiteral, OrderByExpression, SelectStatement, TableLiteral } from "../query/ast/Expressions.js";
6
+ import { BinaryExpression, CallExpression, DeleteStatement, ExistsExpression, Expression, ExpressionAs, Identifier, InsertStatement, JoinExpression, NewObjectExpression, NumberLiteral, OrderByExpression, SelectStatement, TableLiteral, UpdateStatement } from "../query/ast/Expressions.js";
7
7
  import { QueryExpander } from "../query/expander/QueryExpander.js";
8
8
  import EntityContext from "./EntityContext.js";
9
9
  import type { EntitySource } from "./EntitySource.js";
@@ -107,67 +107,120 @@ export default class EntityQuery<T = any>
107
107
  });
108
108
  }
109
109
 
110
- async update(p, f): Promise<number> {
110
+ async delete(p, f): Promise<number> {
111
+ if (p || f) {
112
+ return this.where(p, f).delete(void 0, void 0);
113
+ }
111
114
 
112
- let fields: Expression[];
115
+ const source = this.selectStatement;
116
+
117
+ const sp = Expression.parameter("d1", this.type);
118
+ const as = Expression.parameter("s1", this.type);
119
+ let where: Expression;
120
+ for (const iterator of this.type.keys) {
121
+ const compare = Expression.equal(
122
+ Expression.member(sp, Expression.quotedIdentifier(iterator.columnName)),
123
+ Expression.member(as, Expression.quotedIdentifier(iterator.columnName))
124
+ );
125
+ where = where ? Expression.logicalAnd(where, compare) : compare;
126
+ }
113
127
 
114
- let updateQuery = new EntityQuery({ ... this, selectStatement: {
115
- ... this.selectStatement,
116
- sourceParameter: {
117
- ... this.selectStatement.sourceParameter
118
- },
119
- where: null,
120
- joins: null
121
- }});
128
+ const join = JoinExpression.create({
129
+ source,
130
+ as,
131
+ where
132
+ });
122
133
 
123
- updateQuery = updateQuery.extend(p, f, (select, body) => {
124
- fields = [] as Expression[];
125
- switch(body.type) {
126
- case "NewObjectExpression":
127
- const noe = body as NewObjectExpression;
128
- for (const iterator of noe.properties) {
129
- const column = this.type.getProperty(iterator.alias.value);
130
- fields.push(Expression.assign(
131
- Expression.quotedIdentifier(column.field.columnName),
132
- iterator.expression
133
- ));
134
- }
135
- break;
136
- default:
137
- fields.push(body);
138
- break;
134
+ let deleteQuery = DeleteStatement.create({
135
+ table: this.type.fullyQualifiedName,
136
+ sourceParameter: sp,
137
+ join
138
+ })
139
139
 
140
- }
141
- return { ... select, fields };
140
+ const session = this.context.logger?.newSession() ?? Logger.nullLogger;
141
+ let query;
142
+ try {
143
+ query = this.context.driver.compiler.compileExpression(this, deleteQuery);
144
+ this.traceQuery?.(query.text);
145
+ const r = await this.context.connection.executeQuery(query);
146
+ return r.updated;
147
+ } catch (error) {
148
+ session.error(`Failed executing ${query?.text}\r\n${error.stack ?? error}`);
149
+ throw error;
150
+ }
151
+ }
152
+
153
+ async update(p?, f?): Promise<number> {
154
+
155
+ if (p || f) {
156
+ return this.extend(p, f, (select, body) => {
157
+ const fields = [] as Expression[];
158
+ switch(body.type) {
159
+ case "NewObjectExpression":
160
+ const noe = body as NewObjectExpression;
161
+ for (const iterator of noe.properties) {
162
+ fields.push(ExpressionAs.create({
163
+ expression: iterator.expression,
164
+ alias: Expression.quotedIdentifier(iterator.alias.value)
165
+ }));
166
+ }
167
+ break;
168
+ default:
169
+ fields.push(body);
170
+ break;
171
+
172
+ }
173
+ return { ... select, fields };
174
+ }).update();
175
+ }
176
+
177
+ const as = Expression.parameter("s1", this.type);
178
+ const sp = Expression.parameter("u1", this.type);
179
+ const join = JoinExpression.create({
180
+ source: this.selectStatement,
181
+ as
142
182
  });
143
- // q.selectStatement.updateStatement = true;
144
183
 
184
+ const set = [] as BinaryExpression[];
185
+
186
+ const fieldMap = new Set();
187
+
188
+ for (const iterator of this.selectStatement.fields) {
189
+ if(iterator.type !== "ExpressionAs") {
190
+ throw new Error(`Invalid expression ${iterator.type}`);
191
+ }
192
+ const eAs = iterator as ExpressionAs;
193
+ const { field } = this.type.getProperty(eAs.alias.value);
194
+ fieldMap.add(field.columnName);
195
+ set.push(Expression.assign( Expression.quotedIdentifier(field.columnName), Expression.member(as, Expression.quotedIdentifier(eAs.alias.value))));
196
+ }
145
197
 
146
- updateQuery.selectStatement.updateStatement = true;
147
- const lm = updateQuery.selectStatement.sourceParameter;
148
- const rm = this.selectStatement.sourceParameter;
149
- let where = this.selectStatement.where ? { ... this.selectStatement.where }: null;
198
+ let where = null as Expression;
150
199
  for (const iterator of this.type.keys) {
151
200
  const compare = Expression.equal(
152
- Expression.member(lm, Expression.quotedIdentifier(iterator.columnName)),
153
- Expression.member(rm, Expression.quotedIdentifier(iterator.columnName))
201
+ Expression.member(as, Expression.quotedIdentifier(iterator.columnName)),
202
+ Expression.member(sp, Expression.quotedIdentifier(iterator.columnName))
154
203
  );
155
204
  where = where ? Expression.logicalAnd(where, compare) : compare;
205
+ if (fieldMap.has(iterator.columnName)) {
206
+ continue;
207
+ }
208
+ this.selectStatement.fields.push(Expression.quotedIdentifier(iterator.columnName));
156
209
  }
157
- fields = [NumberLiteral.one];
158
- const target = {
159
- ... this.selectStatement,
160
- fields,
210
+
211
+ const updateStatement = UpdateStatement.create({
212
+ set,
213
+ sourceParameter: sp,
214
+ table: this.type.fullyQualifiedName,
215
+ model: this.type,
161
216
  where,
162
- };
163
- updateQuery.selectStatement.where = ExistsExpression.create({
164
- target
217
+ join
165
218
  });
166
219
 
167
220
  const session = this.context.logger?.newSession() ?? Logger.nullLogger;
168
221
  let query;
169
222
  try {
170
- query = this.context.driver.compiler.compileExpression(updateQuery, updateQuery.selectStatement);
223
+ query = this.context.driver.compiler.compileExpression(this, updateStatement);
171
224
  this.traceQuery?.(query.text);
172
225
  const r = await this.context.connection.executeQuery(query);
173
226
  return r.updated;
@@ -38,6 +38,13 @@ export interface IBaseQuery<T> {
38
38
 
39
39
  update<P>(parameters: P, fx: (p: P) => (x:T) => Partial<T>): Promise<number>;
40
40
 
41
+ /**
42
+ * Warning !! Be careful, this will delete rows from the database and neither soft delete nor any other events will be invoked.
43
+ * @param parameters parameters to supply
44
+ * @param fx filter expression
45
+ */
46
+ delete<P>(parameters: P, fx: (p: P) => (x: T) => boolean): Promise<number>;
47
+
41
48
  trace<DT>(this: DT, tracer: (text: string) => void): DT;
42
49
 
43
50
  /**
@@ -90,20 +90,6 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
90
90
  const fields = this.visitArray(e.fields, ",\n\t\t");
91
91
  const joins = e.joins?.length > 0 ? prepare `\n\t\t${this.visitArray(e.joins, "\n")}` : [];
92
92
 
93
- if (e.updateStatement) {
94
- const s = e.source;
95
- switch(s.type) {
96
- case "Identifier":
97
- case "TableLiteral":
98
- break;
99
- default:
100
- throw new Error(`${s.type} Not supported`);
101
- }
102
- return prepare `UPDATE ${source}${as} SET
103
- ${fields}
104
- ${where}${orderBy}${limit}${offset}`;
105
- }
106
-
107
93
  return prepare `SELECT
108
94
  ${fields}
109
95
  FROM ${source}${as}${joins}${where}${orderBy}${limit}${offset}`;
@@ -520,6 +506,19 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
520
506
 
521
507
  const table = this.visit(e.table);
522
508
 
509
+ if (e.join) {
510
+ this.scope.create({ parameter: e.sourceParameter, model: e.sourceParameter.model })
511
+ const as = e.join.as as ParameterExpression;
512
+ this.scope.create({ parameter: as, model: as.model })
513
+ const join = this.visit(e.join.source);
514
+ const where = this.visit(e.where);
515
+ const joinName = this.scope.nameOf(as);
516
+ const asName = this.scope.nameOf(e.sourceParameter);
517
+ const set = this.visitArray(e.set, ",");
518
+ return prepare `WITH ${joinName} as (${join}) UPDATE ${table} ${asName} SET ${set} FROM ${joinName} WHERE ${where}`;
519
+
520
+ }
521
+
523
522
  const where = this.visit(e.where);
524
523
 
525
524
  const set = this.visitArray(e.set);
@@ -642,6 +641,17 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
642
641
 
643
642
  visitDeleteStatement(e: DeleteStatement): ITextQuery {
644
643
  const table = this.visit(e.table);
644
+ if (e.join) {
645
+ this.scope.create({ parameter: e.sourceParameter, model: e.sourceParameter.model })
646
+ const as = e.join.as as ParameterExpression;
647
+ this.scope.create({ parameter: as, model: as.model })
648
+ const join = this.visit(e.join.source);
649
+ const where = this.visit(e.join.where);
650
+ const joinName = this.scope.nameOf(as);
651
+ const asName = this.scope.nameOf(e.sourceParameter);
652
+ return prepare `WITH ${joinName} as (${join}) DELETE FROM ${table} as ${asName} USING ${joinName} WHERE ${where}`;
653
+ }
654
+
645
655
  const where = this.visit(e.where);
646
656
  return prepare `DELETE FROM ${table} WHERE ${where}`;
647
657
  }
@@ -699,6 +709,9 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
699
709
  const target = this.resolveExpression(me.target);
700
710
  if (target.type === "ParameterExpression" && me.property.type === "Identifier") {
701
711
  const id = me.property as Identifier;
712
+ if (id.quoted) {
713
+ return;
714
+ }
702
715
  const pe = target as ParameterExpression;
703
716
  const scope = this.scope.get(pe);
704
717
  const peModel = scope?.model;
@@ -770,6 +783,10 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
770
783
 
771
784
  const resolved = this.resolveExpression(x);
772
785
 
786
+ if (!resolved) {
787
+ return;
788
+ }
789
+
773
790
  if (resolved.type === "MemberExpression") {
774
791
  x = resolved;
775
792
  }
@@ -257,8 +257,6 @@ export class SelectStatement extends Expression {
257
257
 
258
258
  preferLeftJoins: boolean;
259
259
 
260
- updateStatement: boolean;
261
-
262
260
  source: TableSource | ValuesStatement;
263
261
 
264
262
  sourceParameter: ParameterExpression;
@@ -409,13 +407,15 @@ export class UpdateStatement extends Expression {
409
407
 
410
408
  readonly type = "UpdateStatement";
411
409
 
412
- table: TableLiteral | Identifier;
410
+ table: TableSource;
411
+ sourceParameter: ParameterExpression;
412
+ model: EntityType;
413
413
 
414
414
  set: BinaryExpression[];
415
415
 
416
416
  where: Expression;
417
417
 
418
- joins: JoinExpression[];
418
+ join: JoinExpression;
419
419
 
420
420
  }
421
421
 
@@ -435,8 +435,12 @@ export class UnionAllStatement extends Expression {
435
435
 
436
436
  export class DeleteStatement extends Expression {
437
437
  readonly type = "DeleteStatement";
438
- table: TableLiteral | Identifier;
438
+ table: TableSource;
439
+ model: EntityType;
439
440
  where: Expression;
441
+
442
+ sourceParameter: ParameterExpression;
443
+ join: JoinExpression;
440
444
  }
441
445
 
442
446
  export type ExpressionType =
@@ -0,0 +1,42 @@
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
+ let total = await context.products.all().count();
21
+
22
+ let n = await context.products.all()
23
+ .delete(void 0, (p) => (x) => x.productID === 1);
24
+
25
+ assert.equal(n, 1);
26
+
27
+ let after = await context.products.all().count();
28
+
29
+ assert.equal(total - 1, after);
30
+
31
+ total = await context.products.all().count();
32
+ n = await context.products.all()
33
+ .limit(1)
34
+ .delete(void 0, (p) => (x) => true === true);
35
+
36
+ assert.equal(n, 1);
37
+
38
+ after = await context.products.all().count();
39
+
40
+ assert.equal(total - 1, after);
41
+
42
+ }
@@ -20,11 +20,25 @@ export default async function(this: TestConfig) {
20
20
 
21
21
  await context.products
22
22
  .where(void 0, (p) => (x) => x.productID > 1)
23
+ // .trace(console.log)
23
24
  .update(void 0, (p) => (x) => ({
24
25
  productDescription: "updated"
25
26
  }));
26
27
 
27
28
  // we must verify that productID's description should not have changed...
28
- const p1 = await context.products.where(void 0, (p) => (x) => x.productID === 1).first();
29
+ let p1 = await context.products.where(void 0, (p) => (x) => x.productID === 1).first();
29
30
  assert.notStrictEqual("updated", p1.productDescription);
31
+
32
+ const n = await context.products
33
+ .where(void 0, (p) => (x) => true === true)
34
+ .limit(1)
35
+ // .trace(console.log)
36
+ .update(void 0, (p) => (x) => ({
37
+ productDescription: "x"
38
+ }));
39
+
40
+ assert.equal(n, 1);
41
+
42
+ p1 = await context.products.where(void 0, (p) => (x) => x.productID === 1).first();
43
+ assert.strictEqual("x", p1.productDescription);
30
44
  }