@entity-access/entity-access 1.0.2 → 1.0.5
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/.github/workflows/node.yml +7 -2
- package/.vscode/settings.json +1 -0
- package/README.md +57 -27
- package/package.json +2 -2
- package/src/common/EntityAccessError.ts +10 -0
- package/src/common/IDisposable.ts +25 -0
- package/src/common/ImmutableObject.ts +53 -0
- package/src/common/Logger.ts +59 -0
- package/src/common/TypeInfo.ts +3 -0
- package/src/common/usingAsync.ts +42 -12
- package/src/compiler/QueryCompiler.ts +28 -30
- package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +23 -0
- package/src/compiler/sql-server/SqlServerSqlMethodTransformer.ts +23 -0
- package/src/decorators/ForeignKey.ts +1 -1
- package/src/decorators/IClassOf.ts +2 -1
- package/src/decorators/parser/NameParser.ts +15 -0
- package/src/di/di.ts +224 -0
- package/src/drivers/base/BaseDriver.ts +28 -3
- package/src/drivers/sql-server/ExpressionToSqlServer.ts +48 -9
- package/src/drivers/sql-server/SqlServerDriver.ts +7 -16
- package/src/entity-query/EntityType.ts +1 -1
- package/src/model/EntityContext.ts +167 -22
- package/src/model/EntityQuery.ts +76 -60
- package/src/model/EntitySource.ts +39 -33
- package/src/model/IFilterWithParameter.ts +3 -0
- package/src/model/SourceExpression.ts +21 -25
- package/src/model/{ChangeEntry.ts → changes/ChangeEntry.ts} +33 -3
- package/src/model/{ChangeSet.ts → changes/ChangeSet.ts} +12 -11
- package/src/model/events/ContextEvents.ts +26 -0
- package/src/model/events/EntityEvents.ts +92 -0
- package/src/model/{IdentityService.ts → identity/IdentityService.ts} +1 -1
- package/src/model/symbols.ts +1 -0
- package/src/model/verification/VerificationSession.ts +173 -0
- package/src/query/ast/DebugStringVisitor.ts +128 -0
- package/src/query/ast/ExpressionToSql.ts +289 -119
- package/src/query/ast/Expressions.ts +111 -12
- package/src/query/ast/IStringTransformer.ts +16 -4
- package/src/query/ast/ReplaceParameter.ts +40 -0
- package/src/query/ast/Visitor.ts +20 -5
- package/src/query/parser/ArrowToExpression.ts +116 -16
- package/src/query/parser/BabelVisitor.ts +27 -44
- package/src/query/parser/NotSupportedError.ts +5 -0
- package/src/query/parser/Restructure.ts +66 -0
- package/src/query/parser/TransformVisitor.ts +83 -0
- package/src/sql/ISql.ts +10 -0
- package/src/tests/db-tests/tests/select-items.ts +12 -0
- package/src/tests/expressions/left-joins/child-joins.ts +19 -26
- package/src/tests/expressions/sanitize/sanitize-test.ts +17 -0
- package/src/tests/expressions/select/select.ts +24 -0
- package/src/tests/expressions/simple/parse-arrow.ts +10 -0
- package/src/tests/model/ShoppingContext.ts +7 -3
- package/src/tests/model/createContext.ts +45 -13
- package/src/tests/security/ShoppingContextEvents.ts +20 -0
- package/src/tests/security/events/OrderEvents.ts +66 -0
- package/src/tests/security/events/ProductEvents.ts +92 -0
- package/src/tests/security/events/UserEvents.ts +18 -0
- package/src/tests/security/events/UserInfo.ts +7 -0
- package/src/tests/security/tests/place-order.ts +71 -0
- package/test.js +11 -4
- package/tsconfig.json +2 -0
- package/src/decorators/parser/MemberParser.ts +0 -8
- package/src/model/EntitySchema.ts +0 -21
package/src/model/EntityQuery.ts
CHANGED
|
@@ -1,17 +1,37 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
1
|
+
import Logger from "../common/Logger.js";
|
|
2
|
+
import { DisposableScope } from "../common/usingAsync.js";
|
|
3
|
+
import { ServiceProvider } from "../di/di.js";
|
|
4
|
+
import EntityType from "../entity-query/EntityType.js";
|
|
5
|
+
import { CallExpression, Expression, ExpressionAs, Identifier, OrderByExpression, QuotedLiteral, SelectStatement } from "../query/ast/Expressions.js";
|
|
6
|
+
import EntityContext from "./EntityContext.js";
|
|
7
|
+
import { IOrderedEntityQuery, IEntityQuery } from "./IFilterWithParameter.js";
|
|
5
8
|
|
|
6
9
|
export default class EntityQuery<T = any>
|
|
7
10
|
implements IOrderedEntityQuery<T>, IEntityQuery<T> {
|
|
8
|
-
|
|
11
|
+
|
|
12
|
+
public context: EntityContext;
|
|
13
|
+
public type: EntityType;
|
|
14
|
+
public selectStatement: SelectStatement;
|
|
15
|
+
public signal?: AbortSignal;
|
|
16
|
+
constructor (p: Partial<EntityQuery<any>>
|
|
17
|
+
) {
|
|
18
|
+
// lets clone select...
|
|
19
|
+
Object.setPrototypeOf(p, EntityQuery.prototype);
|
|
20
|
+
return p as EntityQuery;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
select(p: any, fx: any): any {
|
|
24
|
+
return this.map(p, fx);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
map(p: any, fx: any): any {
|
|
28
|
+
// const source = this.source.copy();
|
|
29
|
+
// const { select } = source;
|
|
30
|
+
// const exp = this.source.context.driver.compiler.compileToExpression(source, p, fx);
|
|
9
31
|
}
|
|
10
32
|
|
|
11
33
|
withSignal(signal: AbortSignal): any {
|
|
12
|
-
|
|
13
|
-
source.signal = signal;
|
|
14
|
-
return new EntityQuery(source);
|
|
34
|
+
return new EntityQuery({ ... this, signal });
|
|
15
35
|
}
|
|
16
36
|
|
|
17
37
|
thenBy(parameters: any, fx: any): any {
|
|
@@ -20,21 +40,13 @@ export default class EntityQuery<T = any>
|
|
|
20
40
|
thenByDescending(parameters: any, fx: any) {
|
|
21
41
|
return this.orderByDescending(parameters, fx);
|
|
22
42
|
}
|
|
43
|
+
|
|
23
44
|
where<P>(parameters: P, fx: (p: P) => (x: T) => boolean): any {
|
|
24
45
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
select.where = exp;
|
|
30
|
-
} else {
|
|
31
|
-
select.where = BinaryExpression.create({
|
|
32
|
-
left: select.where,
|
|
33
|
-
operator: "AND",
|
|
34
|
-
right: exp
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
return new EntityQuery(source);
|
|
46
|
+
return this.extend(parameters, fx, (select, body) => ({
|
|
47
|
+
... select,
|
|
48
|
+
where: select.where ? Expression.logicalAnd(select.where, body): body
|
|
49
|
+
}));
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
async toArray(): Promise<T[]> {
|
|
@@ -46,23 +58,32 @@ export default class EntityQuery<T = any>
|
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
async *enumerate(): AsyncGenerator<T, any, unknown> {
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
|
|
61
|
+
const logger = ServiceProvider.resolve(this.context, Logger);
|
|
62
|
+
const scope = new DisposableScope();
|
|
63
|
+
const session = logger.newSession();
|
|
64
|
+
let query: { text: string, values: any[]};
|
|
53
65
|
try {
|
|
66
|
+
scope.register(session);
|
|
67
|
+
const type = this.type;
|
|
68
|
+
const signal = this.signal;
|
|
69
|
+
query = this.context.driver.compiler.compileExpression(this, this.selectStatement);
|
|
70
|
+
const reader = await this.context.driver.executeReader(query, signal);
|
|
71
|
+
scope.register(reader);
|
|
54
72
|
for await (const iterator of reader.next(10, signal)) {
|
|
55
73
|
if (type) {
|
|
56
|
-
Object.setPrototypeOf(iterator, type.prototype);
|
|
74
|
+
Object.setPrototypeOf(iterator, type.typeClass.prototype);
|
|
57
75
|
// set identity...
|
|
58
|
-
const entry = this.
|
|
76
|
+
const entry = this.context.changeSet.getEntry(iterator, iterator);
|
|
59
77
|
yield entry.entity;
|
|
60
78
|
continue;
|
|
61
79
|
}
|
|
62
80
|
yield iterator as T;
|
|
63
81
|
}
|
|
82
|
+
} catch(error) {
|
|
83
|
+
session.error(`Failed executing ${query?.text}\n${error.stack ?? error}`);
|
|
84
|
+
throw error;
|
|
64
85
|
} finally {
|
|
65
|
-
await
|
|
86
|
+
await scope.dispose();
|
|
66
87
|
}
|
|
67
88
|
}
|
|
68
89
|
|
|
@@ -70,7 +91,7 @@ export default class EntityQuery<T = any>
|
|
|
70
91
|
for await(const iterator of this.limit(1).enumerate()) {
|
|
71
92
|
return iterator;
|
|
72
93
|
}
|
|
73
|
-
throw new Error(`No records found for ${this.
|
|
94
|
+
throw new Error(`No records found for ${this.type?.name || "Table"}`);
|
|
74
95
|
}
|
|
75
96
|
|
|
76
97
|
async first(): Promise<T> {
|
|
@@ -80,42 +101,32 @@ export default class EntityQuery<T = any>
|
|
|
80
101
|
return null;
|
|
81
102
|
}
|
|
82
103
|
toQuery(): { text: string; values: any[]; } {
|
|
83
|
-
return this.
|
|
104
|
+
return this.context.driver.compiler.compileExpression(this, this.selectStatement);
|
|
84
105
|
}
|
|
85
106
|
orderBy(parameters: any, fx: any): any {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
target: exp
|
|
107
|
+
return this.extend(parameters, fx, (select, target) => ({
|
|
108
|
+
... select,
|
|
109
|
+
orderBy: select.orderBy
|
|
110
|
+
? [ ... select.orderBy, OrderByExpression.create({ target})]
|
|
111
|
+
: [OrderByExpression.create({ target})]
|
|
92
112
|
}));
|
|
93
|
-
return new EntityQuery(source);
|
|
94
113
|
}
|
|
95
114
|
orderByDescending(parameters: any, fx: any): any {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
descending: true
|
|
115
|
+
const descending = true;
|
|
116
|
+
return this.extend(parameters, fx, (select, target) => ({
|
|
117
|
+
... select,
|
|
118
|
+
orderBy: select.orderBy
|
|
119
|
+
? [ ... select.orderBy, OrderByExpression.create({ target, descending })]
|
|
120
|
+
: [OrderByExpression.create({ target, descending })]
|
|
103
121
|
}));
|
|
104
|
-
return new EntityQuery(source);
|
|
105
122
|
}
|
|
106
123
|
|
|
107
124
|
limit(n: number): any {
|
|
108
|
-
|
|
109
|
-
const { select } = source;
|
|
110
|
-
select.limit = n;
|
|
111
|
-
return new EntityQuery(source);
|
|
125
|
+
return new EntityQuery({ ... this, selectStatement: { ... this.selectStatement, limit: n} });
|
|
112
126
|
}
|
|
113
127
|
|
|
114
128
|
offset(n: number): any {
|
|
115
|
-
|
|
116
|
-
const { select } = source;
|
|
117
|
-
select.offset = n;
|
|
118
|
-
return new EntityQuery(source);
|
|
129
|
+
return new EntityQuery({ ... this, selectStatement: { ... this.selectStatement, offset: n} });
|
|
119
130
|
}
|
|
120
131
|
|
|
121
132
|
async count(parameters?:any, fx?: any): Promise<number> {
|
|
@@ -123,10 +134,7 @@ export default class EntityQuery<T = any>
|
|
|
123
134
|
return this.where(parameters, fx).count();
|
|
124
135
|
}
|
|
125
136
|
|
|
126
|
-
const
|
|
127
|
-
const { select } = source;
|
|
128
|
-
|
|
129
|
-
select.fields = [
|
|
137
|
+
const select = { ... this.selectStatement, fields: [
|
|
130
138
|
ExpressionAs.create({
|
|
131
139
|
expression: CallExpression.create({
|
|
132
140
|
callee: Identifier.create({ value: "COUNT"}),
|
|
@@ -134,10 +142,12 @@ export default class EntityQuery<T = any>
|
|
|
134
142
|
}),
|
|
135
143
|
alias: QuotedLiteral.create({ literal: "count" })
|
|
136
144
|
})
|
|
137
|
-
];
|
|
145
|
+
] };
|
|
138
146
|
|
|
139
|
-
const
|
|
140
|
-
|
|
147
|
+
const nq = new EntityQuery({ ... this, selectStatement: select });
|
|
148
|
+
|
|
149
|
+
const query = this.context.driver.compiler.compileExpression(nq, select);
|
|
150
|
+
const reader = await this.context.driver.executeReader(query);
|
|
141
151
|
|
|
142
152
|
try {
|
|
143
153
|
for await (const iterator of reader.next()) {
|
|
@@ -149,4 +159,10 @@ export default class EntityQuery<T = any>
|
|
|
149
159
|
|
|
150
160
|
}
|
|
151
161
|
|
|
162
|
+
private extend(parameters: any, fx: any, map: (select: SelectStatement, exp: Expression) => SelectStatement) {
|
|
163
|
+
const exp = this.context.driver.compiler.compile(this, fx);
|
|
164
|
+
exp.params[0].value = parameters;
|
|
165
|
+
return new EntityQuery({ ... this, selectStatement: map(this.selectStatement, exp.body)});
|
|
166
|
+
}
|
|
167
|
+
|
|
152
168
|
}
|
|
@@ -1,25 +1,15 @@
|
|
|
1
1
|
import type EntityContext from "./EntityContext.js";
|
|
2
2
|
import type EntityType from "../entity-query/EntityType.js";
|
|
3
3
|
import type { IEntityQuery, IFilterExpression } from "./IFilterWithParameter.js";
|
|
4
|
-
import {
|
|
4
|
+
import { Expression, ExpressionAs, QuotedLiteral, SelectStatement } from "../query/ast/Expressions.js";
|
|
5
5
|
import EntityQuery from "./EntityQuery.js";
|
|
6
6
|
import TimedCache from "../common/cache/TimedCache.js";
|
|
7
7
|
import { contextSymbol, modelSymbol } from "../common/symbols/symbols.js";
|
|
8
|
-
import { SourceExpression } from "./SourceExpression.js";
|
|
9
8
|
|
|
10
9
|
const modelCache = new TimedCache<any, SelectStatement>();
|
|
11
10
|
|
|
12
11
|
export class EntitySource<T = any> {
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
public readonly filters: {
|
|
16
|
-
read?: () => IFilterExpression;
|
|
17
|
-
modify?: () => IFilterExpression;
|
|
18
|
-
delete?: () => IFilterExpression;
|
|
19
|
-
include?: () => IFilterExpression;
|
|
20
|
-
} = {};
|
|
21
|
-
|
|
22
|
-
|
|
23
13
|
get [modelSymbol]() {
|
|
24
14
|
return this.model;
|
|
25
15
|
}
|
|
@@ -37,7 +27,7 @@ export class EntitySource<T = any> {
|
|
|
37
27
|
|
|
38
28
|
}
|
|
39
29
|
|
|
40
|
-
public add(item: Partial<T>) {
|
|
30
|
+
public add(item: Partial<T>): T {
|
|
41
31
|
const p = Object.getPrototypeOf(item).constructor;
|
|
42
32
|
if (!p || p === Object) {
|
|
43
33
|
Object.setPrototypeOf(item, this.model.typeClass.prototype);
|
|
@@ -47,52 +37,68 @@ export class EntitySource<T = any> {
|
|
|
47
37
|
throw new Error("Entity is already attached to the context");
|
|
48
38
|
}
|
|
49
39
|
entry.status = "inserted";
|
|
50
|
-
return
|
|
40
|
+
return entry.entity;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Entity can only be deleted if all primary keys are present
|
|
45
|
+
* @param item entity to delete
|
|
46
|
+
*/
|
|
47
|
+
public delete(item: Partial<T>) {
|
|
48
|
+
const p = Object.getPrototypeOf(item).constructor;
|
|
49
|
+
if (!p || p === Object) {
|
|
50
|
+
Object.setPrototypeOf(item, this.model.typeClass.prototype);
|
|
51
|
+
}
|
|
52
|
+
const entry = this.context.changeSet.getEntry(item);
|
|
53
|
+
if (entry.status !== "detached" && entry.status !== "unchanged") {
|
|
54
|
+
throw new Error("Entity is already attached to the context");
|
|
55
|
+
}
|
|
56
|
+
entry.status = "deleted";
|
|
57
|
+
return entry.entity;
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
public all(): IEntityQuery<T> {
|
|
54
|
-
|
|
55
|
-
const select = modelCache.getOrCreate(`select-model-${this.model.name}`, () => this.generateModel());
|
|
56
|
-
return new EntityQuery<T>(SourceExpression.create({
|
|
57
|
-
alias: select.as.literal,
|
|
58
|
-
context,
|
|
59
|
-
model,
|
|
60
|
-
select
|
|
61
|
-
})) as IEntityQuery<T>;
|
|
61
|
+
return this.asQuery();
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
public where<P>(...[parameter, fx]: IFilterExpression<P, T>) {
|
|
65
|
-
|
|
66
|
-
const select = modelCache.getOrCreate(`select-model-${this.model.name}`, () => this.generateModel());
|
|
67
|
-
return new EntityQuery<T>(SourceExpression.create({
|
|
68
|
-
alias: select.as.literal,
|
|
69
|
-
context,
|
|
70
|
-
model,
|
|
71
|
-
select
|
|
72
|
-
})).where(parameter, fx) as IEntityQuery<T>;
|
|
65
|
+
return this.asQuery().where(parameter, fx);
|
|
73
66
|
}
|
|
74
67
|
|
|
75
68
|
generateModel(): SelectStatement {
|
|
76
69
|
const source = this.model.fullyQualifiedName;
|
|
77
|
-
const as =
|
|
70
|
+
const as = Expression.parameter(this.model.name[0] + "1");
|
|
78
71
|
const fields = this.model.columns.map((c) => c.name !== c.columnName
|
|
79
72
|
? ExpressionAs.create({
|
|
80
|
-
expression:
|
|
73
|
+
expression: Expression.member(as, c.columnName),
|
|
81
74
|
alias: QuotedLiteral.create({ literal: c.name })
|
|
82
75
|
})
|
|
83
|
-
:
|
|
76
|
+
: Expression.member(as, c.columnName));
|
|
84
77
|
return SelectStatement.create({
|
|
85
78
|
source,
|
|
86
79
|
as,
|
|
87
80
|
fields,
|
|
81
|
+
names: JSON.stringify([as.name])
|
|
88
82
|
});
|
|
89
83
|
}
|
|
90
84
|
|
|
85
|
+
public asQuery() {
|
|
86
|
+
const { model, context } = this;
|
|
87
|
+
const selectStatement = modelCache.getOrCreate(`select-model-${this.model.name}`, () => this.generateModel());
|
|
88
|
+
selectStatement.model = model;
|
|
89
|
+
return new EntityQuery<T>({
|
|
90
|
+
context,
|
|
91
|
+
type: model,
|
|
92
|
+
selectStatement
|
|
93
|
+
}) as any as IEntityQuery<T>;
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
91
97
|
public toQuery() {
|
|
92
98
|
const filter = this.filter;
|
|
93
99
|
if(!filter) {
|
|
94
100
|
return "";
|
|
95
101
|
}
|
|
96
|
-
return this.context.driver.compiler.compileExpression(filter);
|
|
102
|
+
return this.context.driver.compiler.compileExpression( this.asQuery() as any, filter);
|
|
97
103
|
}
|
|
98
104
|
}
|
|
@@ -11,6 +11,9 @@ export interface IBaseQuery<T> {
|
|
|
11
11
|
firstOrFail(): Promise<T>;
|
|
12
12
|
first(): Promise<T>;
|
|
13
13
|
|
|
14
|
+
select<P, TR>(parameters: P, fx: (p: P) => (x: T) => TR): IBaseQuery<TR>;
|
|
15
|
+
map<P, TR>(parameters: P, fx: (p: P) => (x: T) => TR): IBaseQuery<TR>;
|
|
16
|
+
|
|
14
17
|
toArray(): Promise<T[]>;
|
|
15
18
|
|
|
16
19
|
toQuery(): { text: string, values: any[]};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { modelSymbol } from "../common/symbols/symbols.js";
|
|
2
2
|
import EntityType from "../entity-query/EntityType.js";
|
|
3
|
-
import { BinaryExpression, Expression, ExpressionAs, Identifier, JoinExpression, MemberExpression, QuotedLiteral, SelectStatement } from "../query/ast/Expressions.js";
|
|
4
|
-
import {
|
|
3
|
+
import { BinaryExpression, Expression, ExpressionAs, Identifier, JoinExpression, MemberExpression, ParameterExpression, QuotedLiteral, SelectStatement } from "../query/ast/Expressions.js";
|
|
4
|
+
import { ITextQueryFragment, QueryParameter } from "../query/ast/IStringTransformer.js";
|
|
5
5
|
import EntityContext from "./EntityContext.js";
|
|
6
6
|
|
|
7
7
|
const sourceSymbol = Symbol("source");
|
|
@@ -12,9 +12,8 @@ export class SourceExpression {
|
|
|
12
12
|
return new SourceExpression(p);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
parameter?: string;
|
|
15
|
+
alias: ParameterExpression;
|
|
16
|
+
parameter?: ParameterExpression;
|
|
18
17
|
context: EntityContext;
|
|
19
18
|
model?: EntityType;
|
|
20
19
|
include?: EntityType[];
|
|
@@ -59,31 +58,28 @@ export class SourceExpression {
|
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
const column = relation.relation.fkColumn;
|
|
62
|
-
const parameter = this.parameter + "." + property;
|
|
61
|
+
const parameter = ParameterExpression.create({ name: this.parameter + "." + property});
|
|
63
62
|
const source = this.addSource(model, parameter);
|
|
64
63
|
const join = JoinExpression.create({
|
|
65
|
-
as:
|
|
64
|
+
as: source.alias,
|
|
66
65
|
joinType: column.nullable ? "LEFT" : "INNER",
|
|
67
66
|
model,
|
|
68
67
|
source: QuotedLiteral.create({ literal: model.name }),
|
|
69
|
-
where:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
property: Identifier.create({ value: model.keys[0].columnName })
|
|
78
|
-
})
|
|
79
|
-
})
|
|
68
|
+
where: Expression.logicalAnd(
|
|
69
|
+
Expression.member(
|
|
70
|
+
this.alias,
|
|
71
|
+
column.columnName),
|
|
72
|
+
Expression.member(
|
|
73
|
+
source.alias,
|
|
74
|
+
model.keys[0].columnName)
|
|
75
|
+
)
|
|
80
76
|
});
|
|
81
77
|
select.joins.push(join);
|
|
82
78
|
join[sourceSymbol] = source;
|
|
83
79
|
return source;
|
|
84
80
|
}
|
|
85
81
|
|
|
86
|
-
addSource(model: EntityType, parameter:
|
|
82
|
+
addSource(model: EntityType, parameter: ParameterExpression) {
|
|
87
83
|
|
|
88
84
|
const { context } = this;
|
|
89
85
|
const source = SourceExpression.create({
|
|
@@ -105,15 +101,15 @@ export class SourceExpression {
|
|
|
105
101
|
break;
|
|
106
102
|
}
|
|
107
103
|
}while (true);
|
|
108
|
-
source.alias = alias;
|
|
104
|
+
source.alias = ParameterExpression.create({ name: alias });
|
|
109
105
|
this.map.set(alias, source);
|
|
110
|
-
this.paramMap.set(parameter, source);
|
|
106
|
+
this.paramMap.set(parameter.name, source);
|
|
111
107
|
return source;
|
|
112
108
|
}
|
|
113
109
|
|
|
114
|
-
flatten(chain: string[]):
|
|
110
|
+
flatten(chain: string[]): ITextQueryFragment {
|
|
115
111
|
const [start, ... others ] = chain;
|
|
116
|
-
if (start === this.parameter) {
|
|
112
|
+
if (start === this.parameter.name) {
|
|
117
113
|
return this.prepareNames(others);
|
|
118
114
|
}
|
|
119
115
|
const mapped = this.paramMap.get(start);
|
|
@@ -123,11 +119,11 @@ export class SourceExpression {
|
|
|
123
119
|
throw new Error("Not found");
|
|
124
120
|
}
|
|
125
121
|
|
|
126
|
-
prepareNames([property , ... others]: string[]):
|
|
122
|
+
prepareNames([property , ... others]: string[]): ITextQueryFragment {
|
|
127
123
|
const p = this.model.getProperty(property);
|
|
128
124
|
const quotedLiteral = this.context.driver.compiler.quotedLiteral;
|
|
129
125
|
if (others.length === 0) {
|
|
130
|
-
return `${
|
|
126
|
+
return `${ QueryParameter.create(() => this.alias.name, quotedLiteral)}.${quotedLiteral(p.field.columnName)}`;
|
|
131
127
|
}
|
|
132
128
|
|
|
133
129
|
// this must be a navigation...
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { IColumn } from "
|
|
2
|
-
import EntityType from "
|
|
1
|
+
import { IColumn } from "../../decorators/IColumn.js";
|
|
2
|
+
import EntityType from "../../entity-query/EntityType.js";
|
|
3
3
|
import type ChangeSet from "./ChangeSet.js";
|
|
4
4
|
import { privateUpdateEntry } from "./ChangeSet.js";
|
|
5
5
|
|
|
@@ -31,17 +31,24 @@ export default class ChangeEntry implements IChanges {
|
|
|
31
31
|
|
|
32
32
|
private pending: (() => void)[];
|
|
33
33
|
|
|
34
|
+
private dependents: ChangeEntry[];
|
|
35
|
+
|
|
34
36
|
constructor(p: IChanges, changeSet: ChangeSet) {
|
|
35
37
|
Object.setPrototypeOf(p, ChangeEntry.prototype);
|
|
36
38
|
const ce = p as ChangeEntry;
|
|
37
39
|
ce.changeSet = changeSet;
|
|
38
40
|
ce.pending = [];
|
|
41
|
+
ce.dependents = [];
|
|
39
42
|
ce.modified = new Map();
|
|
40
43
|
return ce;
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
public detect() {
|
|
44
47
|
|
|
48
|
+
if (this.status === "deleted") {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
const { type: { columns }, entity, original } = this;
|
|
46
53
|
|
|
47
54
|
if (original === void 0) {
|
|
@@ -76,6 +83,15 @@ export default class ChangeEntry implements IChanges {
|
|
|
76
83
|
// apply values to main entity
|
|
77
84
|
// set status to unchanged
|
|
78
85
|
|
|
86
|
+
if(this.status === "deleted") {
|
|
87
|
+
// remove...
|
|
88
|
+
for (const iterator of this.pending) {
|
|
89
|
+
iterator();
|
|
90
|
+
}
|
|
91
|
+
this.changeSet[privateUpdateEntry](this);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
79
95
|
// we will only apply the columns defined
|
|
80
96
|
if (dbValues !== void 0) {
|
|
81
97
|
for (const iterator of this.type.columns) {
|
|
@@ -95,6 +111,7 @@ export default class ChangeEntry implements IChanges {
|
|
|
95
111
|
|
|
96
112
|
this.pending.length = 0;
|
|
97
113
|
this.original = { ... this.entity };
|
|
114
|
+
|
|
98
115
|
this.status = "unchanged";
|
|
99
116
|
this.modified.clear();
|
|
100
117
|
}
|
|
@@ -120,7 +137,15 @@ export default class ChangeEntry implements IChanges {
|
|
|
120
137
|
|
|
121
138
|
const keyValue = related[rKey.name];
|
|
122
139
|
if (keyValue === void 0) {
|
|
140
|
+
|
|
141
|
+
relatedChanges.dependents.push(this);
|
|
142
|
+
|
|
123
143
|
this.order++;
|
|
144
|
+
|
|
145
|
+
for (const d of this.dependents) {
|
|
146
|
+
d.order++;
|
|
147
|
+
}
|
|
148
|
+
|
|
124
149
|
const fk = iterator;
|
|
125
150
|
relatedChanges.pending.push(() => {
|
|
126
151
|
this.entity[fk.fkColumn.name] = related[rKey.name];
|
|
@@ -134,10 +159,12 @@ export default class ChangeEntry implements IChanges {
|
|
|
134
159
|
}
|
|
135
160
|
|
|
136
161
|
setupInverseProperties() {
|
|
162
|
+
const deleted = this.status === "deleted";
|
|
137
163
|
for (const iterator of this.type.relations) {
|
|
138
164
|
if (!iterator.isCollection) {
|
|
139
165
|
continue;
|
|
140
166
|
}
|
|
167
|
+
const { relatedName } = iterator;
|
|
141
168
|
const related = this.entity[iterator.name];
|
|
142
169
|
if (related === void 0) {
|
|
143
170
|
continue;
|
|
@@ -148,7 +175,10 @@ export default class ChangeEntry implements IChanges {
|
|
|
148
175
|
}
|
|
149
176
|
continue;
|
|
150
177
|
}
|
|
151
|
-
related[
|
|
178
|
+
related[relatedName] = this.entity;
|
|
179
|
+
if (deleted) {
|
|
180
|
+
this.pending.push(() => delete related[relatedName]);
|
|
181
|
+
}
|
|
152
182
|
}
|
|
153
183
|
}
|
|
154
184
|
}
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import SchemaRegistry from "../../decorators/SchemaRegistry.js";
|
|
2
|
+
import EntityContext from "../EntityContext.js";
|
|
3
|
+
import IdentityService from "../identity/IdentityService.js";
|
|
3
4
|
import ChangeEntry from "./ChangeEntry.js";
|
|
4
|
-
import { IRecord } from "../drivers/base/BaseDriver.js";
|
|
5
|
-
import IdentityService from "./IdentityService.js";
|
|
6
|
-
|
|
7
|
-
const entrySymbol = Symbol("entry");
|
|
8
|
-
|
|
9
|
-
const identitySymbol = Symbol("identity");
|
|
10
|
-
|
|
11
|
-
const getEntityByIdentity = Symbol("getEntityByIdentity");
|
|
12
5
|
|
|
13
6
|
export const privateUpdateEntry = Symbol("updateEntry");
|
|
14
7
|
|
|
@@ -32,6 +25,15 @@ export default class ChangeSet {
|
|
|
32
25
|
[privateUpdateEntry](entry: ChangeEntry) {
|
|
33
26
|
const jsonKey = IdentityService.getIdentity(entry.entity);
|
|
34
27
|
if (jsonKey) {
|
|
28
|
+
if (entry.status === "deleted") {
|
|
29
|
+
this.identityMap.delete(jsonKey);
|
|
30
|
+
const index = this.entries.indexOf(entry);
|
|
31
|
+
if (index !== -1) {
|
|
32
|
+
this.entries.splice(index, 1);
|
|
33
|
+
}
|
|
34
|
+
this.entryMap.delete(entry.entity);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
35
37
|
this.identityMap.set(jsonKey, entry.entity);
|
|
36
38
|
}
|
|
37
39
|
}
|
|
@@ -62,7 +64,6 @@ export default class ChangeSet {
|
|
|
62
64
|
original: original ? { ... original } : void 0,
|
|
63
65
|
status: "unchanged"
|
|
64
66
|
}, this);
|
|
65
|
-
entity[entrySymbol] = entry;
|
|
66
67
|
this.entries.push(entry);
|
|
67
68
|
this.entryMap.set(entity, entry);
|
|
68
69
|
return entry;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { IClassOf } from "../../decorators/IClassOf.js";
|
|
2
|
+
import { ServiceProvider } from "../../di/di.js";
|
|
3
|
+
import { NotSupportedError } from "../../query/parser/NotSupportedError.js";
|
|
4
|
+
import type EntityContext from "../EntityContext.js";
|
|
5
|
+
import type EntityEvents from "./EntityEvents.js";
|
|
6
|
+
|
|
7
|
+
export default class ContextEvents {
|
|
8
|
+
|
|
9
|
+
private map: Map<any, IClassOf<EntityEvents<any>>> = new Map();
|
|
10
|
+
|
|
11
|
+
public for<T>(type: IClassOf<T>, fail = true): IClassOf<EntityEvents<T>> {
|
|
12
|
+
const typeClass = this.map.get(type);
|
|
13
|
+
if (!typeClass) {
|
|
14
|
+
if (fail) {
|
|
15
|
+
throw new NotSupportedError();
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return typeClass;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public register<T>(type: IClassOf<T>, events: IClassOf<EntityEvents<T>>) {
|
|
23
|
+
this.map.set(type, events);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
}
|