@entity-access/entity-access 1.0.233 → 1.0.235
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/drivers/sql-server/ExpressionToSqlServer.d.ts +3 -1
- package/dist/drivers/sql-server/ExpressionToSqlServer.d.ts.map +1 -1
- package/dist/drivers/sql-server/ExpressionToSqlServer.js +30 -13
- package/dist/drivers/sql-server/ExpressionToSqlServer.js.map +1 -1
- package/dist/entity-query/EntityType.d.ts +0 -2
- package/dist/entity-query/EntityType.d.ts.map +1 -1
- package/dist/model/EntityQuery.d.ts +2 -1
- package/dist/model/EntityQuery.d.ts.map +1 -1
- package/dist/model/EntityQuery.js +85 -39
- package/dist/model/EntityQuery.js.map +1 -1
- package/dist/model/IFilterWithParameter.d.ts +6 -0
- package/dist/model/IFilterWithParameter.d.ts.map +1 -1
- package/dist/query/ast/ExpressionToSql.d.ts.map +1 -1
- package/dist/query/ast/ExpressionToSql.js +27 -13
- package/dist/query/ast/ExpressionToSql.js.map +1 -1
- package/dist/query/ast/Expressions.d.ts +8 -4
- package/dist/query/ast/Expressions.d.ts.map +1 -1
- package/dist/query/ast/Expressions.js.map +1 -1
- package/dist/tests/db-tests/tests/delete-items.d.ts +3 -0
- package/dist/tests/db-tests/tests/delete-items.d.ts.map +1 -0
- package/dist/tests/db-tests/tests/delete-items.js +25 -0
- package/dist/tests/db-tests/tests/delete-items.js.map +1 -0
- package/dist/tests/db-tests/tests/update-items.d.ts.map +1 -1
- package/dist/tests/db-tests/tests/update-items.js +12 -1
- package/dist/tests/db-tests/tests/update-items.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/drivers/sql-server/ExpressionToSqlServer.ts +34 -14
- package/src/model/EntityQuery.ts +96 -44
- package/src/model/IFilterWithParameter.ts +7 -0
- package/src/query/ast/ExpressionToSql.ts +31 -14
- package/src/query/ast/Expressions.ts +9 -5
- package/src/tests/db-tests/tests/delete-items.ts +42 -0
- package/src/tests/db-tests/tests/update-items.ts +15 -1
package/src/model/EntityQuery.ts
CHANGED
|
@@ -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,119 @@ export default class EntityQuery<T = any>
|
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
async
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
where: null,
|
|
120
|
-
joins: null
|
|
121
|
-
}});
|
|
128
|
+
const join = JoinExpression.create({
|
|
129
|
+
source,
|
|
130
|
+
as,
|
|
131
|
+
where
|
|
132
|
+
});
|
|
122
133
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
+
return { ... select, fields };
|
|
173
|
+
}).update();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const as = Expression.parameter("s1", this.type);
|
|
177
|
+
const sp = Expression.parameter("u1", this.type);
|
|
178
|
+
const join = JoinExpression.create({
|
|
179
|
+
source: this.selectStatement,
|
|
180
|
+
as
|
|
142
181
|
});
|
|
143
|
-
// q.selectStatement.updateStatement = true;
|
|
144
182
|
|
|
183
|
+
const set = [] as BinaryExpression[];
|
|
184
|
+
|
|
185
|
+
const fieldMap = new Set();
|
|
186
|
+
|
|
187
|
+
for (const iterator of this.selectStatement.fields) {
|
|
188
|
+
if(iterator.type !== "ExpressionAs") {
|
|
189
|
+
throw new Error(`Invalid expression ${iterator.type}`);
|
|
190
|
+
}
|
|
191
|
+
const eAs = iterator as ExpressionAs;
|
|
192
|
+
const { field } = this.type.getProperty(eAs.alias.value);
|
|
193
|
+
fieldMap.add(field.columnName);
|
|
194
|
+
set.push(Expression.assign( Expression.quotedIdentifier(field.columnName), Expression.member(as, Expression.quotedIdentifier(eAs.alias.value))));
|
|
195
|
+
}
|
|
145
196
|
|
|
146
|
-
|
|
147
|
-
const lm = updateQuery.selectStatement.sourceParameter;
|
|
148
|
-
const rm = this.selectStatement.sourceParameter;
|
|
149
|
-
let where = this.selectStatement.where ? { ... this.selectStatement.where }: null;
|
|
197
|
+
let where = null as Expression;
|
|
150
198
|
for (const iterator of this.type.keys) {
|
|
151
199
|
const compare = Expression.equal(
|
|
152
|
-
Expression.member(
|
|
153
|
-
Expression.member(
|
|
200
|
+
Expression.member(as, Expression.quotedIdentifier(iterator.columnName)),
|
|
201
|
+
Expression.member(sp, Expression.quotedIdentifier(iterator.columnName))
|
|
154
202
|
);
|
|
155
203
|
where = where ? Expression.logicalAnd(where, compare) : compare;
|
|
204
|
+
if (fieldMap.has(iterator.columnName)) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
this.selectStatement.fields.push( Expression.member( this.selectStatement.sourceParameter, Expression.quotedIdentifier(iterator.columnName)));
|
|
156
208
|
}
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
209
|
+
|
|
210
|
+
const updateStatement = UpdateStatement.create({
|
|
211
|
+
set,
|
|
212
|
+
sourceParameter: sp,
|
|
213
|
+
table: this.type.fullyQualifiedName,
|
|
214
|
+
model: this.type,
|
|
161
215
|
where,
|
|
162
|
-
|
|
163
|
-
updateQuery.selectStatement.where = ExistsExpression.create({
|
|
164
|
-
target
|
|
216
|
+
join
|
|
165
217
|
});
|
|
166
218
|
|
|
167
219
|
const session = this.context.logger?.newSession() ?? Logger.nullLogger;
|
|
168
220
|
let query;
|
|
169
221
|
try {
|
|
170
|
-
query = this.context.driver.compiler.compileExpression(
|
|
222
|
+
query = this.context.driver.compiler.compileExpression(this, updateStatement);
|
|
171
223
|
this.traceQuery?.(query.text);
|
|
172
224
|
const r = await this.context.connection.executeQuery(query);
|
|
173
225
|
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:
|
|
410
|
+
table: TableSource;
|
|
411
|
+
sourceParameter: ParameterExpression;
|
|
412
|
+
model: EntityType;
|
|
413
413
|
|
|
414
414
|
set: BinaryExpression[];
|
|
415
415
|
|
|
416
416
|
where: Expression;
|
|
417
417
|
|
|
418
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
}
|