@entity-access/entity-access 1.0.251 → 1.0.253
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/.vscode/launch.json +2 -1
- package/dist/common/symbols/symbols.d.ts +1 -0
- package/dist/common/symbols/symbols.d.ts.map +1 -1
- package/dist/common/symbols/symbols.js +1 -0
- package/dist/common/symbols/symbols.js.map +1 -1
- package/dist/compiler/QueryCompiler.d.ts +3 -3
- package/dist/decorators/ForeignKey.d.ts +8 -7
- package/dist/decorators/ForeignKey.d.ts.map +1 -1
- package/dist/decorators/ForeignKey.js +43 -8
- package/dist/decorators/ForeignKey.js.map +1 -1
- package/dist/decorators/IColumn.d.ts +6 -3
- package/dist/decorators/IColumn.d.ts.map +1 -1
- package/dist/decorators/Relate.d.ts.map +1 -1
- package/dist/decorators/Relate.js +8 -6
- package/dist/decorators/Relate.js.map +1 -1
- package/dist/entity-query/EntityType.d.ts +5 -1
- package/dist/entity-query/EntityType.d.ts.map +1 -1
- package/dist/entity-query/EntityType.js +57 -25
- package/dist/entity-query/EntityType.js.map +1 -1
- package/dist/migrations/postgres/PostgresAutomaticMigrations.js +1 -1
- package/dist/migrations/postgres/PostgresAutomaticMigrations.js.map +1 -1
- package/dist/migrations/sql-server/SqlServerAutomaticMigrations.js +1 -1
- package/dist/migrations/sql-server/SqlServerAutomaticMigrations.js.map +1 -1
- package/dist/model/EntityModel.js +2 -2
- package/dist/model/EntityModel.js.map +1 -1
- package/dist/model/EntitySource.d.ts +6 -1
- package/dist/model/EntitySource.d.ts.map +1 -1
- package/dist/model/EntitySource.js.map +1 -1
- package/dist/model/SourceExpression.d.ts +1 -22
- package/dist/model/SourceExpression.d.ts.map +1 -1
- package/dist/model/SourceExpression.js +116 -98
- package/dist/model/SourceExpression.js.map +1 -1
- package/dist/model/changes/ChangeEntry.d.ts.map +1 -1
- package/dist/model/changes/ChangeEntry.js +62 -25
- package/dist/model/changes/ChangeEntry.js.map +1 -1
- package/dist/model/changes/ChangeSet.d.ts +2 -1
- package/dist/model/changes/ChangeSet.d.ts.map +1 -1
- package/dist/model/changes/ChangeSet.js +4 -3
- package/dist/model/changes/ChangeSet.js.map +1 -1
- package/dist/model/identity/IdentityMap.d.ts +23 -0
- package/dist/model/identity/IdentityMap.d.ts.map +1 -0
- package/dist/model/identity/IdentityMap.js +113 -0
- package/dist/model/identity/IdentityMap.js.map +1 -0
- package/dist/model/identity/RelationMapper.d.ts +2 -3
- package/dist/model/identity/RelationMapper.d.ts.map +1 -1
- package/dist/model/identity/RelationMapper.js +60 -27
- package/dist/model/identity/RelationMapper.js.map +1 -1
- package/dist/model/identity/SearchIndex.d.ts +17 -0
- package/dist/model/identity/SearchIndex.d.ts.map +1 -0
- package/dist/model/identity/SearchIndex.js +109 -0
- package/dist/model/identity/SearchIndex.js.map +1 -0
- package/dist/model/verification/VerificationSession.d.ts +1 -1
- package/dist/model/verification/VerificationSession.d.ts.map +1 -1
- package/dist/model/verification/VerificationSession.js +18 -16
- package/dist/model/verification/VerificationSession.js.map +1 -1
- package/dist/query/ast/ExpressionToSql.d.ts.map +1 -1
- package/dist/query/ast/ExpressionToSql.js +74 -52
- package/dist/query/ast/ExpressionToSql.js.map +1 -1
- package/dist/query/expander/QueryExpander.d.ts.map +1 -1
- package/dist/query/expander/QueryExpander.js +41 -10
- package/dist/query/expander/QueryExpander.js.map +1 -1
- package/dist/query/parser/ArrowToExpression.d.ts +16 -5
- package/dist/query/parser/ArrowToExpression.d.ts.map +1 -1
- package/dist/query/parser/ArrowToExpression.js +45 -25
- package/dist/query/parser/ArrowToExpression.js.map +1 -1
- package/dist/tests/db-tests/tests/multi-fk-tests.d.ts +3 -0
- package/dist/tests/db-tests/tests/multi-fk-tests.d.ts.map +1 -0
- package/dist/tests/db-tests/tests/multi-fk-tests.js +38 -0
- package/dist/tests/db-tests/tests/multi-fk-tests.js.map +1 -0
- package/dist/tests/expressions/left-joins/child-joins.js +7 -7
- package/dist/tests/model/ShoppingContext.d.ts +9 -0
- package/dist/tests/model/ShoppingContext.d.ts.map +1 -1
- package/dist/tests/model/ShoppingContext.js +34 -0
- package/dist/tests/model/ShoppingContext.js.map +1 -1
- package/dist/tests/security/tests/include-items.d.ts.map +1 -1
- package/dist/tests/security/tests/include-items.js +1 -0
- package/dist/tests/security/tests/include-items.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/common/symbols/symbols.ts +2 -1
- package/src/decorators/ForeignKey.ts +66 -28
- package/src/decorators/IColumn.ts +4 -3
- package/src/decorators/Relate.ts +8 -6
- package/src/entity-query/EntityType.ts +60 -26
- package/src/migrations/postgres/PostgresAutomaticMigrations.ts +1 -1
- package/src/migrations/sql-server/SqlServerAutomaticMigrations.ts +1 -1
- package/src/model/EntityModel.ts +2 -2
- package/src/model/EntitySource.ts +6 -1
- package/src/model/SourceExpression.ts +132 -132
- package/src/model/changes/ChangeEntry.ts +68 -25
- package/src/model/changes/ChangeSet.ts +4 -3
- package/src/model/identity/IdentityMap.ts +126 -0
- package/src/model/identity/RelationMapper.ts +71 -27
- package/src/model/identity/SearchIndex.ts +120 -0
- package/src/model/verification/VerificationSession.ts +19 -16
- package/src/query/ast/ExpressionToSql.ts +77 -61
- package/src/query/expander/QueryExpander.ts +52 -28
- package/src/query/parser/ArrowToExpression.ts +50 -26
- package/src/tests/db-tests/tests/multi-fk-tests.ts +46 -0
- package/src/tests/expressions/left-joins/child-joins.ts +7 -7
- package/src/tests/model/ShoppingContext.ts +32 -0
- package/src/tests/security/tests/include-items.ts +1 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { entityTypeSymbol } from "../../common/symbols/symbols.js";
|
|
2
|
+
import { IColumn } from "../../decorators/IColumn.js";
|
|
3
|
+
import type EntityType from "../../entity-query/EntityType.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Locally cache uniquely identifiable entities
|
|
7
|
+
*/
|
|
8
|
+
export default class IdentityMap {
|
|
9
|
+
|
|
10
|
+
public get indexedColumns() {
|
|
11
|
+
return this.keys.keys();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private map = new Map<string, any>();
|
|
15
|
+
|
|
16
|
+
private keys = new Map<IColumn, Map<any, any[]>>();
|
|
17
|
+
|
|
18
|
+
public delete(jsonKey) {
|
|
19
|
+
const item = this.map.get(jsonKey);
|
|
20
|
+
this.map.delete(jsonKey);
|
|
21
|
+
if (item) {
|
|
22
|
+
const type = item[entityTypeSymbol] as EntityType;
|
|
23
|
+
for (const column of type.columns) {
|
|
24
|
+
const values = this.keys.get(column);
|
|
25
|
+
if (!values) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const value = item[column.name];
|
|
29
|
+
if (value === void 0 || value === null) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const entries = values.get(value);
|
|
33
|
+
if (!entries) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const index = entries.findIndex(item);
|
|
37
|
+
entries.splice(index, 1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public get(jsonKeys) {
|
|
43
|
+
return this.map.get(jsonKeys);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public set(jsonKey, entity, type: EntityType) {
|
|
47
|
+
entity[entityTypeSymbol] = type;
|
|
48
|
+
this.map.set(jsonKey, entity);
|
|
49
|
+
this.updateSearchIndex(type, entity);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public clear() {
|
|
53
|
+
this.map.clear();
|
|
54
|
+
this.keys.clear();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public build(key: IColumn) {
|
|
58
|
+
return this.getKeyEntry(key, true);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
searchByKeys(pairs: { key: IColumn, value}[], create = true) {
|
|
62
|
+
let results: any[];
|
|
63
|
+
for (const { key, value } of pairs) {
|
|
64
|
+
const items = this.getAll(key, value, create);
|
|
65
|
+
if (!items?.length) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!results) {
|
|
69
|
+
results = [].concat(items);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const old = results;
|
|
73
|
+
results = [];
|
|
74
|
+
for (const item of items) {
|
|
75
|
+
if (old.includes(item)) {
|
|
76
|
+
results.push(item);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return results[0];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private getAll(key: IColumn, value: any, create = true) {
|
|
84
|
+
const keyEntry = this.getKeyEntry(key, create);
|
|
85
|
+
return keyEntry.get(value);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private getKeyEntry(key: IColumn, create = false) {
|
|
89
|
+
let keyEntry = this.keys.get(key);
|
|
90
|
+
if (keyEntry) {
|
|
91
|
+
return keyEntry;
|
|
92
|
+
}
|
|
93
|
+
if (!create) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
keyEntry = new Map<any, any[]>();
|
|
97
|
+
this.keys.set(key, keyEntry);
|
|
98
|
+
for (const entry of this.map.values()) {
|
|
99
|
+
this.updateSearchIndex(entry[entityTypeSymbol], entry);
|
|
100
|
+
}
|
|
101
|
+
return keyEntry;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private updateSearchIndex(type: EntityType, entity: any) {
|
|
105
|
+
for (const key of this.keys.keys()) {
|
|
106
|
+
if (type.getField(key.name) !== key) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const keyEntry = this.getKeyEntry(key, true);
|
|
110
|
+
const value = entity[key.name];
|
|
111
|
+
if (value === void 0 || value === null) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
let values = keyEntry.get(value);
|
|
115
|
+
if (!values) {
|
|
116
|
+
values = [];
|
|
117
|
+
keyEntry.set(value, values);
|
|
118
|
+
}
|
|
119
|
+
if (values.includes(entity)) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
values.push(entity);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
}
|
|
@@ -1,26 +1,33 @@
|
|
|
1
|
+
import EventEmitter from "events";
|
|
1
2
|
import type ChangeEntry from "../changes/ChangeEntry.js";
|
|
2
3
|
import type ChangeSet from "../changes/ChangeSet.js";
|
|
3
4
|
import IdentityService, { identityMapSymbol } from "./IdentityService.js";
|
|
5
|
+
import { IColumn } from "../../decorators/IColumn.js";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
4
9
|
|
|
5
10
|
export default class RelationMapper {
|
|
6
11
|
|
|
7
|
-
private map: Map<string, ChangeEntry[]> = new Map();
|
|
12
|
+
// private map: Map<string, ChangeEntry[]> = new Map();
|
|
13
|
+
|
|
14
|
+
private events: EventEmitter = new EventEmitter();
|
|
8
15
|
|
|
9
16
|
constructor(
|
|
10
17
|
private changeSet: ChangeSet,
|
|
11
|
-
private identityMap
|
|
18
|
+
private identityMap = changeSet[identityMapSymbol]
|
|
12
19
|
) {
|
|
13
20
|
|
|
14
21
|
}
|
|
15
22
|
|
|
16
|
-
push(id: string, waiter: ChangeEntry) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
23
|
+
// push(id: string, waiter: ChangeEntry) {
|
|
24
|
+
// let queue = this.map.get(id);
|
|
25
|
+
// if (!queue) {
|
|
26
|
+
// queue = [];
|
|
27
|
+
// this.map.set(id, queue);
|
|
28
|
+
// }
|
|
29
|
+
// queue.push(waiter);
|
|
30
|
+
// }
|
|
24
31
|
|
|
25
32
|
fix(entry: ChangeEntry, nest = true) {
|
|
26
33
|
|
|
@@ -30,23 +37,48 @@ export default class RelationMapper {
|
|
|
30
37
|
if (iterator.isInverseRelation) {
|
|
31
38
|
continue;
|
|
32
39
|
}
|
|
33
|
-
const fkColumn = iterator.fkColumn.name;
|
|
34
|
-
const fkValue = entity[fkColumn];
|
|
35
|
-
if (fkValue === void 0) {
|
|
36
|
-
|
|
37
|
-
}
|
|
40
|
+
// const fkColumn = iterator.fkColumn.name;
|
|
41
|
+
// const fkValue = entity[fkColumn];
|
|
42
|
+
// if (fkValue === void 0) {
|
|
43
|
+
// continue;
|
|
44
|
+
// }
|
|
45
|
+
|
|
38
46
|
// get from identity...
|
|
39
|
-
const id = IdentityService.buildIdentity(iterator.relatedEntity, fkValue);
|
|
40
|
-
const parent = this.identityMap.get(id);
|
|
47
|
+
// const id = IdentityService.buildIdentity(iterator.relatedEntity, fkValue);
|
|
48
|
+
// const parent = this.identityMap.get(id);
|
|
49
|
+
// if (!parent) {
|
|
50
|
+
// let waiters = this.map.get(id);
|
|
51
|
+
// if (!waiters) {
|
|
52
|
+
// waiters = [];
|
|
53
|
+
// this.map.set(id, waiters);
|
|
54
|
+
// }
|
|
55
|
+
// waiters.push(entry);
|
|
56
|
+
// continue;
|
|
57
|
+
// }
|
|
58
|
+
|
|
59
|
+
const pairs = [] as { key: IColumn, value: any}[];
|
|
60
|
+
|
|
61
|
+
for (const { fkColumn, relatedKeyColumn } of iterator.fkMap) {
|
|
62
|
+
this.identityMap.build(relatedKeyColumn);
|
|
63
|
+
const fkValue = entity[fkColumn.name];
|
|
64
|
+
if (fkValue === void 0) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
pairs.push({ key: relatedKeyColumn, value: fkValue});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const parent = this.identityMap.searchByKeys(pairs, true);
|
|
41
71
|
if (!parent) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
72
|
+
if (nest) {
|
|
73
|
+
for (const { key, value } of pairs) {
|
|
74
|
+
this.events.once(`${key.entityType.name}-${key.name}-${value}`, (k) => {
|
|
75
|
+
this.fix(entry, false);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
46
78
|
}
|
|
47
|
-
waiters.push(entry);
|
|
48
79
|
continue;
|
|
49
80
|
}
|
|
81
|
+
|
|
50
82
|
entity[iterator.name] = parent;
|
|
51
83
|
|
|
52
84
|
if (iterator.relatedRelation.isCollection) {
|
|
@@ -64,12 +96,24 @@ export default class RelationMapper {
|
|
|
64
96
|
}
|
|
65
97
|
|
|
66
98
|
// see if anyone is waiting for us or not...
|
|
67
|
-
const identity = IdentityService.getIdentity(entry.type, entry.entity);
|
|
68
|
-
const pending = this.map.get(identity);
|
|
69
|
-
if (pending && pending.length) {
|
|
70
|
-
|
|
71
|
-
|
|
99
|
+
// const identity = IdentityService.getIdentity(entry.type, entry.entity);
|
|
100
|
+
// const pending = this.map.get(identity);
|
|
101
|
+
// if (pending && pending.length) {
|
|
102
|
+
// for (const iterator of pending) {
|
|
103
|
+
// this.fix(iterator, false);
|
|
104
|
+
// }
|
|
105
|
+
// }
|
|
106
|
+
|
|
107
|
+
for (const iterator of this.identityMap.indexedColumns) {
|
|
108
|
+
if (iterator.entityType !== entry.type) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const value = entry.entity[iterator.name];
|
|
112
|
+
if (value === void 0 || value === null) {
|
|
113
|
+
continue;
|
|
72
114
|
}
|
|
115
|
+
const key = `${iterator.entityType.name}-${iterator.name}-${value}`;
|
|
116
|
+
this.events.emit(key, key);
|
|
73
117
|
}
|
|
74
118
|
}
|
|
75
119
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { IColumn } from "../../decorators/IColumn.js";
|
|
2
|
+
import type ChangeEntry from "../changes/ChangeEntry.js";
|
|
3
|
+
|
|
4
|
+
export default class SearchIndex {
|
|
5
|
+
|
|
6
|
+
private keys = new Map<string, Map<any, any[]>>();
|
|
7
|
+
|
|
8
|
+
constructor(private entries: ChangeEntry[]) {
|
|
9
|
+
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getByKeys(pairs: { key, value}[], create = true) {
|
|
13
|
+
let results: any[];
|
|
14
|
+
for (const { key, value } of pairs) {
|
|
15
|
+
const items = this.getAll(key, value, create);
|
|
16
|
+
if (!items?.length) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!results) {
|
|
20
|
+
results = [].concat(items);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const old = results;
|
|
24
|
+
results = [];
|
|
25
|
+
for (const item of items) {
|
|
26
|
+
if (old.includes(item)) {
|
|
27
|
+
results.push(item);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return results;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getAll(key: string, value: any, create = true) {
|
|
35
|
+
const keyEntry = this.getKeyEntry(key, create);
|
|
36
|
+
return keyEntry.get(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
delete(entry) {
|
|
40
|
+
for (const [key,values] of this.keys) {
|
|
41
|
+
const value = entry[key];
|
|
42
|
+
if (value === void 0 || value === null) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const entries = values.get(value);
|
|
46
|
+
if (!entries) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const index = entries.findIndex(entry);
|
|
50
|
+
entries.splice(index, 1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get(keys: IColumn[], entry) {
|
|
55
|
+
let results: any[];
|
|
56
|
+
for (const { name } of keys) {
|
|
57
|
+
const items = this.getAll(name, entry[name]);
|
|
58
|
+
if (!items?.length) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (!results) {
|
|
62
|
+
results = [].concat(items);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const old = results;
|
|
66
|
+
results = [];
|
|
67
|
+
for (const item of items) {
|
|
68
|
+
if (old.includes(item)) {
|
|
69
|
+
results.push(item);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return results[0];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
update(keys: IColumn[], entry) {
|
|
77
|
+
for (const key of keys) {
|
|
78
|
+
const keyEntry = this.getKeyEntry(key.name, true);
|
|
79
|
+
const value = entry[key.name];
|
|
80
|
+
if (value === void 0 || value === null) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
let values = keyEntry.get(value);
|
|
84
|
+
if (!values) {
|
|
85
|
+
values = [];
|
|
86
|
+
keyEntry.set(value, values);
|
|
87
|
+
}
|
|
88
|
+
if (values.includes(entry)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
values.push(entry);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private getKeyEntry(key: string, create = false) {
|
|
96
|
+
let keyEntry = this.keys.get(key);
|
|
97
|
+
if (keyEntry) {
|
|
98
|
+
return keyEntry;
|
|
99
|
+
}
|
|
100
|
+
if (!create) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
keyEntry = new Map<any, any[]>();
|
|
104
|
+
this.keys.set(key, keyEntry);
|
|
105
|
+
for (const entry of this.entries) {
|
|
106
|
+
const value = entry[key];
|
|
107
|
+
if (value === void 0 || value === null) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
let values = keyEntry.get(value);
|
|
111
|
+
if (!values) {
|
|
112
|
+
values = [];
|
|
113
|
+
keyEntry.set(value, values);
|
|
114
|
+
}
|
|
115
|
+
values.push(value);
|
|
116
|
+
}
|
|
117
|
+
return keyEntry;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
}
|
|
@@ -63,30 +63,33 @@ export default class VerificationSession {
|
|
|
63
63
|
continue;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
const fk = relation.
|
|
66
|
+
const fk = relation.fkMap;
|
|
67
67
|
if (!fk) {
|
|
68
68
|
continue;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const
|
|
72
|
-
if (fkValue === void 0) {
|
|
73
|
-
// not set... ignore..
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (isKeyEmpty(fkValue, relation.fkColumn)) {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
71
|
+
for (const { fkColumn , relatedKeyColumn } of fk) {
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
73
|
+
const fkValue = entity[fkColumn.name];
|
|
74
|
+
if (fkValue === void 0) {
|
|
75
|
+
// not set... ignore..
|
|
83
76
|
continue;
|
|
84
77
|
}
|
|
78
|
+
if (isKeyEmpty(fkValue, fkColumn)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// only if it is modified...
|
|
83
|
+
if (change.status !== "inserted") {
|
|
84
|
+
if (!change.isModified(fkColumn.name)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
this.queueEntityForeignKey(change, relation, fkColumn, relatedKeyColumn, fkValue);
|
|
85
89
|
}
|
|
86
|
-
this.queueEntityForeignKey(change, relation, fkValue);
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
|
-
queueEntityForeignKey(change: ChangeEntry, relation: IEntityRelation, value) {
|
|
92
|
+
queueEntityForeignKey(change: ChangeEntry, relation: IEntityRelation, fkColumn, relatedKeyColumn, value) {
|
|
90
93
|
const relatedModel = relation.relatedEntity;
|
|
91
94
|
const type = relation.relatedEntity.typeClass;
|
|
92
95
|
const events = this.context.eventsFor(change.type.typeClass);
|
|
@@ -97,7 +100,7 @@ export default class VerificationSession {
|
|
|
97
100
|
events: relatedEvents,
|
|
98
101
|
type: relatedModel,
|
|
99
102
|
name: relation.name,
|
|
100
|
-
fkName:
|
|
103
|
+
fkName: fkColumn.name,
|
|
101
104
|
entity: change.entity
|
|
102
105
|
});
|
|
103
106
|
let query = events.onForeignKeyFilter(fk);
|
|
@@ -110,7 +113,7 @@ export default class VerificationSession {
|
|
|
110
113
|
|
|
111
114
|
const eq = query as EntityQuery;
|
|
112
115
|
const compare = Expression.equal(
|
|
113
|
-
Expression.member(eq.selectStatement.sourceParameter,
|
|
116
|
+
Expression.member(eq.selectStatement.sourceParameter, relatedKeyColumn.columnName),
|
|
114
117
|
Expression.constant(value)
|
|
115
118
|
);
|
|
116
119
|
const typeName = TypeInfo.nameOfType(type);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import EntityAccessError from "../../common/EntityAccessError.js";
|
|
1
2
|
import QueryCompiler from "../../compiler/QueryCompiler.js";
|
|
2
3
|
import EntityType, { IEntityProperty } from "../../entity-query/EntityType.js";
|
|
3
4
|
import EntityQuery from "../../model/EntityQuery.js";
|
|
@@ -225,33 +226,27 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
225
226
|
|
|
226
227
|
this.scope.create({ parameter: select.sourceParameter, model: relatedModel, selectStatement: select });
|
|
227
228
|
select[filteredSymbol] = true;
|
|
228
|
-
const targetKey = MemberExpression.create({
|
|
229
|
-
target: parameter,
|
|
230
|
-
property: Identifier.create({
|
|
231
|
-
value: targetType.keys[0].columnName
|
|
232
|
-
})
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
const relatedKey = MemberExpression.create({
|
|
236
|
-
target: select.sourceParameter,
|
|
237
|
-
property: Identifier.create({
|
|
238
|
-
value: relation.relation.fkColumn.columnName
|
|
239
|
-
})
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const join = Expression.equal(targetKey, relatedKey);
|
|
244
229
|
|
|
245
230
|
let where = select.where;
|
|
246
231
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
232
|
+
for (const { fkColumn, relatedKeyColumn } of relation.relation.fkMap) {
|
|
233
|
+
const targetKey = MemberExpression.create({
|
|
234
|
+
target: parameter,
|
|
235
|
+
property: Identifier.create({
|
|
236
|
+
value: relatedKeyColumn.columnName
|
|
237
|
+
})
|
|
252
238
|
});
|
|
253
|
-
|
|
254
|
-
|
|
239
|
+
|
|
240
|
+
const relatedKey = MemberExpression.create({
|
|
241
|
+
target: select.sourceParameter,
|
|
242
|
+
property: Identifier.create({
|
|
243
|
+
value: fkColumn.columnName
|
|
244
|
+
})
|
|
245
|
+
});
|
|
246
|
+
const join = Expression.equal(targetKey, relatedKey);
|
|
247
|
+
where = where
|
|
248
|
+
? Expression.logicalAnd(where, join)
|
|
249
|
+
: join;
|
|
255
250
|
}
|
|
256
251
|
|
|
257
252
|
select.where = where;
|
|
@@ -282,36 +277,33 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
282
277
|
this.scope.alias(param1, select.sourceParameter, select);
|
|
283
278
|
select.sourceParameter = param1;
|
|
284
279
|
select[filteredSymbol] = true;
|
|
285
|
-
const targetKey = MemberExpression.create({
|
|
286
|
-
target: parameter,
|
|
287
|
-
property: Identifier.create({
|
|
288
|
-
value: targetType.keys[0].columnName
|
|
289
|
-
})
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
const relatedKey = MemberExpression.create({
|
|
293
|
-
target: param1,
|
|
294
|
-
property: Identifier.create({
|
|
295
|
-
value: relation.relation.fkColumn.columnName
|
|
296
|
-
})
|
|
297
|
-
});
|
|
298
280
|
|
|
281
|
+
let where = select.where;
|
|
282
|
+
where = where
|
|
283
|
+
? Expression.logicalAnd(where, body.body)
|
|
284
|
+
: body.body;
|
|
299
285
|
|
|
300
|
-
const
|
|
301
|
-
Expression.equal(targetKey, relatedKey),
|
|
302
|
-
body.body
|
|
303
|
-
);
|
|
286
|
+
for (const { fkColumn, relatedKeyColumn } of relation.relation.relatedRelation.fkMap) {
|
|
304
287
|
|
|
305
|
-
|
|
288
|
+
const targetKey = MemberExpression.create({
|
|
289
|
+
target: parameter,
|
|
290
|
+
property: Identifier.create({
|
|
291
|
+
value: relatedKeyColumn.columnName
|
|
292
|
+
})
|
|
293
|
+
});
|
|
306
294
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
295
|
+
const relatedKey = MemberExpression.create({
|
|
296
|
+
target: param1,
|
|
297
|
+
property: Identifier.create({
|
|
298
|
+
value: fkColumn.columnName
|
|
299
|
+
})
|
|
312
300
|
});
|
|
313
|
-
|
|
314
|
-
|
|
301
|
+
|
|
302
|
+
const join = Expression.equal(targetKey, relatedKey);
|
|
303
|
+
|
|
304
|
+
where = where
|
|
305
|
+
? Expression.logicalAnd(where, join)
|
|
306
|
+
: join;
|
|
315
307
|
}
|
|
316
308
|
|
|
317
309
|
select.where = where;
|
|
@@ -723,16 +715,18 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
723
715
|
}
|
|
724
716
|
if (relation) {
|
|
725
717
|
|
|
726
|
-
const { fkColumn } = relation;
|
|
727
|
-
|
|
728
718
|
if (!relation.isCollection) {
|
|
729
719
|
|
|
730
|
-
let columnName = fkColumn.columnName;
|
|
731
|
-
// for inverse relation, we need to
|
|
732
|
-
// use primary key of current model
|
|
733
|
-
if (relation.isInverseRelation) {
|
|
734
|
-
|
|
735
|
-
}
|
|
720
|
+
// let columnName = fkColumn.columnName;
|
|
721
|
+
// // for inverse relation, we need to
|
|
722
|
+
// // use primary key of current model
|
|
723
|
+
// if (relation.isInverseRelation) {
|
|
724
|
+
// columnName = peModel.keys[0].columnName;
|
|
725
|
+
// }
|
|
726
|
+
|
|
727
|
+
const fkMap = relation.fkMap ?? relation.relatedRelation.fkMap;
|
|
728
|
+
|
|
729
|
+
const isNullable = fkMap.some(({ fkColumn }) => fkColumn.nullable);
|
|
736
730
|
|
|
737
731
|
const select = scope?.selectStatement ?? this.source?.selectStatement;
|
|
738
732
|
if (select) {
|
|
@@ -743,20 +737,42 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
743
737
|
this.scope.create({ parameter: join.as as ParameterExpression, model: join.model, selectStatement: select });
|
|
744
738
|
return join.as;
|
|
745
739
|
}
|
|
746
|
-
const joinType = select.preferLeftJoins ? "LEFT" : (
|
|
740
|
+
const joinType = select.preferLeftJoins ? "LEFT" : (isNullable ? "LEFT" : "INNER");
|
|
747
741
|
const joinParameter = ParameterExpression.create({
|
|
748
742
|
name: relation.relatedEntity.name[0],
|
|
749
743
|
model: relation.relatedEntity
|
|
750
744
|
});
|
|
745
|
+
let where: Expression;
|
|
746
|
+
for (const {fkColumn, relatedKeyColumn} of fkMap) {
|
|
747
|
+
let peColumn;
|
|
748
|
+
let joinColumn;
|
|
749
|
+
if (fkColumn.entityType === pe.model) {
|
|
750
|
+
peColumn = fkColumn;
|
|
751
|
+
} else if (relatedKeyColumn.entityType === pe.model) {
|
|
752
|
+
peColumn = relatedKeyColumn;
|
|
753
|
+
} else {
|
|
754
|
+
throw new EntityAccessError(`Invalid configuration`);
|
|
755
|
+
}
|
|
756
|
+
if (fkColumn.entityType === joinParameter.model) {
|
|
757
|
+
joinColumn = fkColumn;
|
|
758
|
+
} else if (relatedKeyColumn.entityType === joinParameter.model) {
|
|
759
|
+
joinColumn = relatedKeyColumn;
|
|
760
|
+
} else {
|
|
761
|
+
throw new EntityAccessError(`Invalid configuration`);
|
|
762
|
+
}
|
|
763
|
+
const joinOn = Expression.equal(
|
|
764
|
+
Expression.member(pe, peColumn.columnName),
|
|
765
|
+
Expression.member(joinParameter, joinColumn.columnName));
|
|
766
|
+
where = where
|
|
767
|
+
? Expression.logicalAnd(where, joinOn)
|
|
768
|
+
: joinOn;
|
|
769
|
+
}
|
|
751
770
|
join = JoinExpression.create({
|
|
752
771
|
as: joinParameter,
|
|
753
772
|
joinType,
|
|
754
773
|
model: joinParameter.model,
|
|
755
774
|
source: Expression.identifier(relation.relatedEntity.name),
|
|
756
|
-
where
|
|
757
|
-
Expression.member(pe, columnName),
|
|
758
|
-
Expression.member(joinParameter, relation.relatedEntity.keys[0].columnName)
|
|
759
|
-
)
|
|
775
|
+
where
|
|
760
776
|
});
|
|
761
777
|
select.joins.push(join);
|
|
762
778
|
this.scope.create({ parameter: joinParameter, model: relation.relatedEntity, selectStatement: select});
|