@entity-access/entity-access 1.0.99 → 1.0.100
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 +3 -0
- 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/entity-query/EntityType.d.ts +5 -4
- package/dist/entity-query/EntityType.d.ts.map +1 -1
- package/dist/entity-query/EntityType.js.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 +58 -4
- package/dist/model/EntityQuery.js.map +1 -1
- package/dist/model/IFilterWithParameter.d.ts +2 -0
- package/dist/model/IFilterWithParameter.d.ts.map +1 -1
- package/dist/query/ast/ExpressionToSql.d.ts +4 -2
- package/dist/query/ast/ExpressionToSql.d.ts.map +1 -1
- package/dist/query/ast/ExpressionToSql.js +114 -53
- package/dist/query/ast/ExpressionToSql.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-map.d.ts +3 -0
- package/dist/tests/db-tests/tests/select-items-map.d.ts.map +1 -0
- package/dist/tests/db-tests/tests/select-items-map.js +14 -0
- package/dist/tests/db-tests/tests/select-items-map.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +3 -0
- package/src/compiler/sql-server/SqlServerSqlMethodTransformer.ts +3 -0
- package/src/entity-query/EntityType.ts +6 -1
- package/src/model/EntityQuery.ts +63 -5
- package/src/model/IFilterWithParameter.ts +3 -0
- package/src/query/ast/ExpressionToSql.ts +141 -68
- package/src/sql/ISql.ts +1 -0
- package/src/tests/db-tests/tests/select-items-map.ts +20 -0
package/src/model/EntityQuery.ts
CHANGED
|
@@ -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(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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,137 @@ 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
|
+
throw new EntityAccessError(`Nested map/select not yet supported`);
|
|
197
|
+
// const param1 = body.params[0];
|
|
198
|
+
// const relatedModel = relation.relation.relatedEntity;
|
|
199
|
+
// const relatedType = relatedModel.typeClass;
|
|
200
|
+
|
|
201
|
+
// let select: SelectStatement;
|
|
202
|
+
|
|
203
|
+
// if (this.source?.context) {
|
|
204
|
+
// const query = FilteredExpression.isFiltered(e)
|
|
205
|
+
// ? this.source.context.query(relatedType)
|
|
206
|
+
// : this.source.context.filteredQuery(relatedType, "include", false);
|
|
207
|
+
// select = { ...(query as EntityQuery).selectStatement };
|
|
208
|
+
// } else {
|
|
209
|
+
// select = relatedModel.selectOneNumber();
|
|
210
|
+
// }
|
|
211
|
+
|
|
212
|
+
// param1.model = relatedModel;
|
|
213
|
+
// this.scope.create({ parameter: param1, model: relatedModel, selectStatement: select });
|
|
214
|
+
// this.scope.alias(param1, select.sourceParameter, select);
|
|
215
|
+
// select.sourceParameter = param1;
|
|
216
|
+
// select[filteredSymbol] = true;
|
|
217
|
+
// const targetKey = MemberExpression.create({
|
|
218
|
+
// target: parameter,
|
|
219
|
+
// property: Identifier.create({
|
|
220
|
+
// value: targetType.keys[0].columnName
|
|
221
|
+
// })
|
|
222
|
+
// });
|
|
223
|
+
|
|
224
|
+
// const relatedKey = MemberExpression.create({
|
|
225
|
+
// target: param1,
|
|
226
|
+
// property: Identifier.create({
|
|
227
|
+
// value: relation.relation.fkColumn.columnName
|
|
228
|
+
// })
|
|
229
|
+
// });
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
// const join = Expression.equal(targetKey, relatedKey);
|
|
233
|
+
|
|
234
|
+
// let where = select.where;
|
|
235
|
+
|
|
236
|
+
// if (where) {
|
|
237
|
+
// where = BinaryExpression.create({
|
|
238
|
+
// left: select.where,
|
|
239
|
+
// operator: "AND",
|
|
240
|
+
// right: join
|
|
241
|
+
// });
|
|
242
|
+
// } else {
|
|
243
|
+
// where = join;
|
|
244
|
+
// }
|
|
245
|
+
|
|
246
|
+
// select.where = where;
|
|
247
|
+
|
|
248
|
+
// const exists = ExistsExpression.create({
|
|
249
|
+
// target: select
|
|
250
|
+
// });
|
|
251
|
+
|
|
252
|
+
// const r = this.visit(exists);
|
|
253
|
+
// this.scope.delete(param1);
|
|
254
|
+
// this.scope.delete(select.sourceParameter);
|
|
255
|
+
// return r;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
expandSome(body: ArrowFunctionExpression, relation: IEntityProperty, e: CallExpression, parameter: ParameterExpression, targetType: EntityType) {
|
|
259
|
+
const param1 = body.params[0];
|
|
260
|
+
const relatedModel = relation.relation.relatedEntity;
|
|
261
|
+
const relatedType = relatedModel.typeClass;
|
|
262
|
+
|
|
263
|
+
let select: SelectStatement;
|
|
264
|
+
|
|
265
|
+
if (this.source?.context) {
|
|
266
|
+
const query = FilteredExpression.isFiltered(e)
|
|
267
|
+
? this.source.context.query(relatedType)
|
|
268
|
+
: this.source.context.filteredQuery(relatedType, "include", false);
|
|
269
|
+
select = { ...(query as EntityQuery).selectStatement };
|
|
270
|
+
select.fields = [
|
|
271
|
+
NumberLiteral.create({ value: 1 })
|
|
272
|
+
];
|
|
273
|
+
} else {
|
|
274
|
+
select = relatedModel.selectOneNumber();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
param1.model = relatedModel;
|
|
278
|
+
this.scope.create({ parameter: param1, model: relatedModel, selectStatement: select });
|
|
279
|
+
this.scope.alias(param1, select.sourceParameter, select);
|
|
280
|
+
select.sourceParameter = param1;
|
|
281
|
+
select[filteredSymbol] = true;
|
|
282
|
+
const targetKey = MemberExpression.create({
|
|
283
|
+
target: parameter,
|
|
284
|
+
property: Identifier.create({
|
|
285
|
+
value: targetType.keys[0].columnName
|
|
286
|
+
})
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const relatedKey = MemberExpression.create({
|
|
290
|
+
target: param1,
|
|
291
|
+
property: Identifier.create({
|
|
292
|
+
value: relation.relation.fkColumn.columnName
|
|
293
|
+
})
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
const join = Expression.logicalAnd(
|
|
298
|
+
Expression.equal(targetKey, relatedKey),
|
|
299
|
+
body.body
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
let where = select.where;
|
|
303
|
+
|
|
304
|
+
if (where) {
|
|
305
|
+
where = BinaryExpression.create({
|
|
306
|
+
left: select.where,
|
|
307
|
+
operator: "AND",
|
|
308
|
+
right: join
|
|
309
|
+
});
|
|
310
|
+
} else {
|
|
311
|
+
where = join;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
select.where = where;
|
|
315
|
+
|
|
316
|
+
const exists = ExistsExpression.create({
|
|
317
|
+
target: select
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const r = this.visit(exists);
|
|
321
|
+
this.scope.delete(param1);
|
|
322
|
+
this.scope.delete(select.sourceParameter);
|
|
323
|
+
return r;
|
|
324
|
+
}
|
|
325
|
+
|
|
253
326
|
visitIdentifier(e: Identifier): ITextQuery {
|
|
254
327
|
// need to visit parameters
|
|
255
328
|
return [e.value];
|
package/src/sql/ISql.ts
CHANGED
|
@@ -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
|
+
}
|