@entity-access/entity-access 1.0.5 → 1.0.7
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/package.json +1 -1
- package/src/common/cache/TimedCache.ts +2 -2
- package/src/decorators/IColumn.ts +2 -0
- package/src/drivers/sql-server/ExpressionToSqlServer.ts +2 -16
- package/src/entity-query/EntityType.ts +44 -2
- package/src/model/EntityQuery.ts +43 -0
- package/src/model/EntitySource.ts +8 -22
- package/src/model/IFilterWithParameter.ts +2 -0
- package/src/model/changes/ChangeEntry.ts +2 -2
- package/src/model/changes/ChangeSet.ts +5 -1
- package/src/model/events/EntityEvents.ts +9 -5
- package/src/model/identity/IdentityService.ts +8 -0
- package/src/model/identity/RelationMapper.ts +71 -0
- package/src/model/verification/VerificationSession.ts +1 -1
- package/src/query/ast/DebugStringVisitor.ts +51 -4
- package/src/query/ast/ExpressionToSql.ts +75 -87
- package/src/query/ast/Expressions.ts +22 -4
- package/src/query/ast/IStringTransformer.ts +3 -1
- package/src/query/ast/ParameterScope.ts +97 -0
- package/src/query/ast/Types.ts +0 -0
- package/src/query/ast/Visitor.ts +7 -1
- package/src/query/expander/QueryExpander.ts +147 -0
- package/src/query/parser/ArrowToExpression.ts +22 -7
- package/src/query/parser/BabelVisitor.ts +5 -0
- package/src/tests/expressions/left-joins/child-joins.ts +48 -21
- package/src/tests/model/createContext.ts +26 -7
- package/src/tests/security/events/OrderEvents.ts +8 -2
- package/src/tests/security/events/UserEvents.ts +11 -1
- package/src/tests/security/tests/include-items.ts +19 -0
- package/src/tests/security/tests/place-order.ts +33 -0
package/package.json
CHANGED
|
@@ -11,10 +11,10 @@ export default class TimedCache<TKey = any, T = any> {
|
|
|
11
11
|
this.map.delete(key);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
getOrCreate(key: TKey, factory: (k: TKey) => T, ttl: number = 15000) {
|
|
14
|
+
getOrCreate<TP>(key: TKey, p1: TP, factory: (k: TKey,p: TP) => T, ttl: number = 15000) {
|
|
15
15
|
let item = this.map.get(key);
|
|
16
16
|
if (!item) {
|
|
17
|
-
item = { value: factory(key), ttl, expire: Date.now() + ttl };
|
|
17
|
+
item = { value: factory(key, p1), ttl, expire: Date.now() + ttl };
|
|
18
18
|
this.map.set(key, item);
|
|
19
19
|
} else {
|
|
20
20
|
item.expire = Date.now() + ttl;
|
|
@@ -47,25 +47,11 @@ export default class ExpressionToSqlServer extends ExpressionToSql {
|
|
|
47
47
|
|
|
48
48
|
visitSelectStatement(e: SelectStatement): ITextQuery {
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
const scope = this.targets.get(e.as);
|
|
52
|
-
if (!scope) {
|
|
53
|
-
this.targets.set(e.as, {
|
|
54
|
-
parameter: e.as,
|
|
55
|
-
model:
|
|
56
|
-
e.model,
|
|
57
|
-
replace: e.as,
|
|
58
|
-
selectStatement: e
|
|
59
|
-
});
|
|
60
|
-
} else {
|
|
61
|
-
scope.selectStatement = e;
|
|
62
|
-
scope.model = e.model;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
50
|
+
this.prepareStatement(e);
|
|
65
51
|
|
|
66
52
|
const orderBy = e.orderBy?.length > 0 ? prepare `\n\t\tORDER BY ${this.visitArray(e.orderBy)}` : "";
|
|
67
53
|
const where = e.where ? prepare `\n\tWHERE ${this.visit(e.where)}` : "";
|
|
68
|
-
const joins = e.joins?.length > 0 ? prepare `\n\t\t${this.visitArray(e.joins)}` : [];
|
|
54
|
+
const joins = e.joins?.length > 0 ? prepare `\n\t\t${this.visitArray(e.joins, "\n")}` : [];
|
|
69
55
|
|
|
70
56
|
const fields = this.visitArray(e.fields, ",\n\t\t");
|
|
71
57
|
|
|
@@ -3,7 +3,7 @@ import { IClassOf } from "../decorators/IClassOf.js";
|
|
|
3
3
|
import { Query } from "../query/Query.js";
|
|
4
4
|
import NameParser from "../decorators/parser/NameParser.js";
|
|
5
5
|
import SchemaRegistry from "../decorators/SchemaRegistry.js";
|
|
6
|
-
import { QuotedLiteral, TableLiteral } from "../query/ast/Expressions.js";
|
|
6
|
+
import { Expression, ExpressionAs, QuotedLiteral, SelectStatement, TableLiteral } from "../query/ast/Expressions.js";
|
|
7
7
|
import InstanceCache from "../common/cache/InstanceCache.js";
|
|
8
8
|
|
|
9
9
|
|
|
@@ -40,6 +40,9 @@ export default class EntityType {
|
|
|
40
40
|
private columnMap: Map<string, IColumn> = new Map();
|
|
41
41
|
private relationMap: Map<string, IEntityRelation> = new Map();
|
|
42
42
|
|
|
43
|
+
private selectAll: SelectStatement;
|
|
44
|
+
private selectOne: SelectStatement;
|
|
45
|
+
|
|
43
46
|
public getProperty(name: string) {
|
|
44
47
|
const field = this.fieldMap.get(name);
|
|
45
48
|
const relation = this.relationMap.get(name);
|
|
@@ -103,14 +106,53 @@ export default class EntityType {
|
|
|
103
106
|
relatedTypeClass: this.typeClass,
|
|
104
107
|
dotNotCreateIndex: true,
|
|
105
108
|
fkColumn,
|
|
106
|
-
|
|
109
|
+
isInverseRelation: true,
|
|
107
110
|
relatedRelation: relation,
|
|
108
111
|
relatedEntity: this
|
|
109
112
|
};
|
|
110
113
|
relatedType.relationMap.set(inverseRelation.name, inverseRelation);
|
|
111
114
|
relatedType.relations.push(inverseRelation);
|
|
112
115
|
inverseRelation.relatedRelation = relation;
|
|
116
|
+
relation.relatedRelation = inverseRelation;
|
|
113
117
|
|
|
114
118
|
}
|
|
115
119
|
|
|
120
|
+
public selectAllFields() {
|
|
121
|
+
if (this.selectAll) {
|
|
122
|
+
return { ... this.selectAll };
|
|
123
|
+
}
|
|
124
|
+
const source = this.fullyQualifiedName;
|
|
125
|
+
const as = Expression.parameter(this.name[0] + "1");
|
|
126
|
+
const fields = this.columns.map((c) => c.name !== c.columnName
|
|
127
|
+
? ExpressionAs.create({
|
|
128
|
+
expression: Expression.member(as, c.columnName),
|
|
129
|
+
alias: QuotedLiteral.create({ literal: c.name })
|
|
130
|
+
})
|
|
131
|
+
: Expression.member(as, c.columnName));
|
|
132
|
+
this.selectAll = SelectStatement.create({
|
|
133
|
+
source,
|
|
134
|
+
model: this,
|
|
135
|
+
as,
|
|
136
|
+
fields
|
|
137
|
+
});
|
|
138
|
+
return { ... this.selectAll };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public selectOneNumber() {
|
|
142
|
+
if (this.selectOne) {
|
|
143
|
+
return { ... this.selectOne };
|
|
144
|
+
}
|
|
145
|
+
const source = this.fullyQualifiedName;
|
|
146
|
+
const as = Expression.parameter(this.name[0] + "1");
|
|
147
|
+
const fields = [
|
|
148
|
+
Expression.identifier("1")
|
|
149
|
+
];
|
|
150
|
+
this.selectOne = SelectStatement.create({
|
|
151
|
+
source,
|
|
152
|
+
model: this,
|
|
153
|
+
as,
|
|
154
|
+
fields
|
|
155
|
+
});
|
|
156
|
+
return { ... this.selectOne };
|
|
157
|
+
}
|
|
116
158
|
}
|
package/src/model/EntityQuery.ts
CHANGED
|
@@ -3,8 +3,10 @@ import { DisposableScope } from "../common/usingAsync.js";
|
|
|
3
3
|
import { ServiceProvider } from "../di/di.js";
|
|
4
4
|
import EntityType from "../entity-query/EntityType.js";
|
|
5
5
|
import { CallExpression, Expression, ExpressionAs, Identifier, OrderByExpression, QuotedLiteral, SelectStatement } from "../query/ast/Expressions.js";
|
|
6
|
+
import { QueryExpander } from "../query/expander/QueryExpander.js";
|
|
6
7
|
import EntityContext from "./EntityContext.js";
|
|
7
8
|
import { IOrderedEntityQuery, IEntityQuery } from "./IFilterWithParameter.js";
|
|
9
|
+
import RelationMapper from "./identity/RelationMapper.js";
|
|
8
10
|
|
|
9
11
|
export default class EntityQuery<T = any>
|
|
10
12
|
implements IOrderedEntityQuery<T>, IEntityQuery<T> {
|
|
@@ -49,6 +51,14 @@ export default class EntityQuery<T = any>
|
|
|
49
51
|
}));
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
include(p: any): any {
|
|
55
|
+
const selectStatement = QueryExpander.expand(this.context, { ... this.selectStatement }, p);
|
|
56
|
+
return new EntityQuery({
|
|
57
|
+
... this,
|
|
58
|
+
selectStatement
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
52
62
|
async toArray(): Promise<T[]> {
|
|
53
63
|
const results: T[] = [];
|
|
54
64
|
for await (const iterator of this.enumerate()) {
|
|
@@ -66,6 +76,20 @@ export default class EntityQuery<T = any>
|
|
|
66
76
|
scope.register(session);
|
|
67
77
|
const type = this.type;
|
|
68
78
|
const signal = this.signal;
|
|
79
|
+
|
|
80
|
+
const relationMapper = new RelationMapper(this.context.changeSet);
|
|
81
|
+
|
|
82
|
+
const include = this.selectStatement.include;
|
|
83
|
+
if (include?.length > 0) {
|
|
84
|
+
// since we will be streaming results...
|
|
85
|
+
// it is important that we load all the
|
|
86
|
+
// included entities first...
|
|
87
|
+
const loaders = include.map((x) => this.load(relationMapper, session, x, signal));
|
|
88
|
+
await Promise.all(loaders);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
signal?.throwIfAborted();
|
|
92
|
+
|
|
69
93
|
query = this.context.driver.compiler.compileExpression(this, this.selectStatement);
|
|
70
94
|
const reader = await this.context.driver.executeReader(query, signal);
|
|
71
95
|
scope.register(reader);
|
|
@@ -74,11 +98,13 @@ export default class EntityQuery<T = any>
|
|
|
74
98
|
Object.setPrototypeOf(iterator, type.typeClass.prototype);
|
|
75
99
|
// set identity...
|
|
76
100
|
const entry = this.context.changeSet.getEntry(iterator, iterator);
|
|
101
|
+
relationMapper.fix(entry);
|
|
77
102
|
yield entry.entity;
|
|
78
103
|
continue;
|
|
79
104
|
}
|
|
80
105
|
yield iterator as T;
|
|
81
106
|
}
|
|
107
|
+
|
|
82
108
|
} catch(error) {
|
|
83
109
|
session.error(`Failed executing ${query?.text}\n${error.stack ?? error}`);
|
|
84
110
|
throw error;
|
|
@@ -87,6 +113,23 @@ export default class EntityQuery<T = any>
|
|
|
87
113
|
}
|
|
88
114
|
}
|
|
89
115
|
|
|
116
|
+
async load(relationMapper: RelationMapper, session: Logger, select: SelectStatement, signal: AbortSignal) {
|
|
117
|
+
const query = this.context.driver.compiler.compileExpression(this, select);
|
|
118
|
+
const reader = await this.context.driver.executeReader(query, signal);
|
|
119
|
+
try {
|
|
120
|
+
for await (const iterator of reader.next(10, signal)) {
|
|
121
|
+
Object.setPrototypeOf(iterator, select.model.typeClass.prototype);
|
|
122
|
+
const entry = this.context.changeSet.getEntry(iterator, iterator);
|
|
123
|
+
relationMapper.fix(entry);
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
session.error(`Failed loading ${query.text}\n${error.stack ?? error}`);
|
|
127
|
+
throw error;
|
|
128
|
+
} finally {
|
|
129
|
+
await reader.dispose();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
90
133
|
async firstOrFail(): Promise<T> {
|
|
91
134
|
for await(const iterator of this.limit(1).enumerate()) {
|
|
92
135
|
return iterator;
|
|
@@ -1,13 +1,10 @@
|
|
|
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 { Expression
|
|
4
|
+
import { Expression } from "../query/ast/Expressions.js";
|
|
5
5
|
import EntityQuery from "./EntityQuery.js";
|
|
6
|
-
import TimedCache from "../common/cache/TimedCache.js";
|
|
7
6
|
import { contextSymbol, modelSymbol } from "../common/symbols/symbols.js";
|
|
8
7
|
|
|
9
|
-
const modelCache = new TimedCache<any, SelectStatement>();
|
|
10
|
-
|
|
11
8
|
export class EntitySource<T = any> {
|
|
12
9
|
|
|
13
10
|
get [modelSymbol]() {
|
|
@@ -61,30 +58,19 @@ export class EntitySource<T = any> {
|
|
|
61
58
|
return this.asQuery();
|
|
62
59
|
}
|
|
63
60
|
|
|
64
|
-
public
|
|
65
|
-
|
|
61
|
+
public filtered(mode: "read" | "modify" = "read"): IEntityQuery<T> {
|
|
62
|
+
const query = this.asQuery();
|
|
63
|
+
const events = this.context.eventsFor(this.model.typeClass, true);
|
|
64
|
+
return mode === "modify" ? events.modify(query) : events.filter(query);
|
|
66
65
|
}
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const as = Expression.parameter(this.model.name[0] + "1");
|
|
71
|
-
const fields = this.model.columns.map((c) => c.name !== c.columnName
|
|
72
|
-
? ExpressionAs.create({
|
|
73
|
-
expression: Expression.member(as, c.columnName),
|
|
74
|
-
alias: QuotedLiteral.create({ literal: c.name })
|
|
75
|
-
})
|
|
76
|
-
: Expression.member(as, c.columnName));
|
|
77
|
-
return SelectStatement.create({
|
|
78
|
-
source,
|
|
79
|
-
as,
|
|
80
|
-
fields,
|
|
81
|
-
names: JSON.stringify([as.name])
|
|
82
|
-
});
|
|
67
|
+
public where<P>(...[parameter, fx]: IFilterExpression<P, T>) {
|
|
68
|
+
return this.asQuery().where(parameter, fx);
|
|
83
69
|
}
|
|
84
70
|
|
|
85
71
|
public asQuery() {
|
|
86
72
|
const { model, context } = this;
|
|
87
|
-
const selectStatement =
|
|
73
|
+
const selectStatement = this.model.selectAllFields();
|
|
88
74
|
selectStatement.model = model;
|
|
89
75
|
return new EntityQuery<T>({
|
|
90
76
|
context,
|
|
@@ -26,6 +26,8 @@ export interface IBaseQuery<T> {
|
|
|
26
26
|
count<P>(parameters: P, fx: (p: P) => (x: T) => boolean): Promise<number>;
|
|
27
27
|
|
|
28
28
|
withSignal<DT>(this:DT, signal: AbortSignal): DT;
|
|
29
|
+
|
|
30
|
+
include<TR>(fx: (x: T) => TR | TR[]): IBaseQuery<T>;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export interface IOrderedEntityQuery<T> extends IBaseQuery<T> {
|
|
@@ -120,7 +120,7 @@ export default class ChangeEntry implements IChanges {
|
|
|
120
120
|
const { type: { relations }, entity } = this;
|
|
121
121
|
// for parent relations.. check if related key is set or not...
|
|
122
122
|
for (const iterator of relations) {
|
|
123
|
-
if (iterator.
|
|
123
|
+
if (iterator.isInverseRelation) {
|
|
124
124
|
continue;
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -161,7 +161,7 @@ export default class ChangeEntry implements IChanges {
|
|
|
161
161
|
setupInverseProperties() {
|
|
162
162
|
const deleted = this.status === "deleted";
|
|
163
163
|
for (const iterator of this.type.relations) {
|
|
164
|
-
if (!iterator.
|
|
164
|
+
if (!iterator.isInverseRelation) {
|
|
165
165
|
continue;
|
|
166
166
|
}
|
|
167
167
|
const { relatedName } = iterator;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import SchemaRegistry from "../../decorators/SchemaRegistry.js";
|
|
2
2
|
import EntityContext from "../EntityContext.js";
|
|
3
|
-
import IdentityService from "../identity/IdentityService.js";
|
|
3
|
+
import IdentityService, { identityMapSymbol } from "../identity/IdentityService.js";
|
|
4
4
|
import ChangeEntry from "./ChangeEntry.js";
|
|
5
5
|
|
|
6
6
|
export const privateUpdateEntry = Symbol("updateEntry");
|
|
@@ -9,6 +9,10 @@ export default class ChangeSet {
|
|
|
9
9
|
|
|
10
10
|
public readonly entries: ChangeEntry[] = [];
|
|
11
11
|
|
|
12
|
+
get [identityMapSymbol]() {
|
|
13
|
+
return this.identityMap;
|
|
14
|
+
}
|
|
15
|
+
|
|
12
16
|
private entryMap: Map<any, ChangeEntry> = new Map();
|
|
13
17
|
|
|
14
18
|
/**
|
|
@@ -8,13 +8,13 @@ import ChangeEntry from "../changes/ChangeEntry.js";
|
|
|
8
8
|
|
|
9
9
|
const done = Promise.resolve() as Promise<void>;
|
|
10
10
|
|
|
11
|
-
export class ForeignKeyFilter<T = any> {
|
|
11
|
+
export class ForeignKeyFilter<T = any, TE = any> {
|
|
12
12
|
|
|
13
13
|
public type: EntityType;
|
|
14
14
|
public name: string;
|
|
15
15
|
public fkName: string;
|
|
16
16
|
|
|
17
|
-
private events: EntityEvents<
|
|
17
|
+
private events: EntityEvents<TE>;
|
|
18
18
|
private context: EntityContext;
|
|
19
19
|
|
|
20
20
|
constructor(p: Partial<ForeignKeyFilter> & { context: EntityContext, events: EntityEvents<any> }) {
|
|
@@ -22,17 +22,21 @@ export class ForeignKeyFilter<T = any> {
|
|
|
22
22
|
return p as any as ForeignKeyFilter;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
public is<TR>(fx: (x: T) => TR): boolean {
|
|
25
|
+
public is<TR>(fx: (x: T) => TR): this is ForeignKeyFilter<T, TR> & boolean {
|
|
26
26
|
const name = NameParser.parseMember(fx);
|
|
27
27
|
return name === this.fkName || name === this.name;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
public read() {
|
|
30
|
+
public read(): IEntityQuery<TE> {
|
|
31
31
|
const read = this.context.query(this.type.typeClass);
|
|
32
32
|
return this.events.filter(read);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
public
|
|
35
|
+
public unfiltered(): IEntityQuery<TE> {
|
|
36
|
+
return this.context.query(this.type.typeClass);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public modify(): IEntityQuery<TE> {
|
|
36
40
|
const read = this.context.query(this.type.typeClass);
|
|
37
41
|
return this.events.modify(read);
|
|
38
42
|
}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import SchemaRegistry from "../../decorators/SchemaRegistry.js";
|
|
2
|
+
import type EntityType from "../../entity-query/EntityType.js";
|
|
3
|
+
|
|
4
|
+
export const identityMapSymbol = Symbol("identityMapSymbol");
|
|
2
5
|
|
|
3
6
|
export default class IdentityService {
|
|
4
7
|
|
|
8
|
+
public static buildIdentity(model: EntityType, ... keys: any[]) {
|
|
9
|
+
const type = model.name;
|
|
10
|
+
return JSON.stringify({ type, keys });
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
public static getIdentity(entity) {
|
|
6
14
|
const entityType = SchemaRegistry.model(Object.getPrototypeOf(entity).constructor);
|
|
7
15
|
const keys = [];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type ChangeEntry from "../changes/ChangeEntry.js";
|
|
2
|
+
import type ChangeSet from "../changes/ChangeSet.js";
|
|
3
|
+
import IdentityService, { identityMapSymbol } from "./IdentityService.js";
|
|
4
|
+
|
|
5
|
+
export default class RelationMapper {
|
|
6
|
+
|
|
7
|
+
private map: Map<string, ChangeEntry[]> = new Map();
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
private changeSet: ChangeSet,
|
|
11
|
+
private identityMap: Map<string, ChangeEntry> = changeSet[identityMapSymbol]
|
|
12
|
+
) {
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
push(id: string, waiter: ChangeEntry) {
|
|
17
|
+
let queue = this.map.get(id);
|
|
18
|
+
if (!queue) {
|
|
19
|
+
queue = [];
|
|
20
|
+
this.map.set(id, queue);
|
|
21
|
+
}
|
|
22
|
+
queue.push(waiter);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fix(entry: ChangeEntry, nest = true) {
|
|
26
|
+
|
|
27
|
+
// find all parents...
|
|
28
|
+
const { type, entity } = entry;
|
|
29
|
+
for (const iterator of type.relations) {
|
|
30
|
+
if (iterator.isInverseRelation) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const fkColumn = iterator.fkColumn.name;
|
|
34
|
+
const fkValue = entity[fkColumn];
|
|
35
|
+
if (fkValue === void 0) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
// get from identity...
|
|
39
|
+
const id = IdentityService.buildIdentity(iterator.relatedEntity, fkValue);
|
|
40
|
+
const parent = this.identityMap.get(id);
|
|
41
|
+
if (!parent) {
|
|
42
|
+
let waiters = this.map.get(id);
|
|
43
|
+
if (!waiters) {
|
|
44
|
+
waiters = [];
|
|
45
|
+
this.map.set(id, waiters);
|
|
46
|
+
}
|
|
47
|
+
waiters.push(entry);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
entity[iterator.name] = parent;
|
|
51
|
+
|
|
52
|
+
const coll = (parent[iterator.relatedRelation.name] ??= []) as any[];
|
|
53
|
+
if(!coll.includes(entity)){
|
|
54
|
+
coll.push(entity);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!nest) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// see if anyone is waiting for us or not...
|
|
63
|
+
const identity = IdentityService.getIdentity(entry.entity);
|
|
64
|
+
const pending = this.map.get(identity);
|
|
65
|
+
if (pending && pending.length) {
|
|
66
|
+
for (const iterator of pending) {
|
|
67
|
+
this.fix(iterator, false);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, Expression, ExpressionAs, Identifier, MemberExpression, NewObjectExpression, NullExpression, NumberLiteral, ParameterExpression, QuotedLiteral, StringLiteral, TableLiteral, TemplateElement, TemplateLiteral } from "./Expressions.js";
|
|
1
|
+
import { ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateElement, TemplateLiteral, UpdateStatement, ValuesStatement } from "./Expressions.js";
|
|
2
2
|
import Visitor from "./Visitor.js";
|
|
3
3
|
|
|
4
4
|
const isBinary = (type) => /^(BinaryExpression|CoalesceExpression)$/.test(type);
|
|
@@ -47,7 +47,7 @@ export default class DebugStringVisitor extends Visitor<string> {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
visitConditionalExpression(e: ConditionalExpression): string {
|
|
50
|
-
return `${e.test} ? ${e.consequent} : ${e.alternate}`;
|
|
50
|
+
return `${this.visit(e.test)} ? ${this.visit(e.consequent)} : ${this.visit(e.alternate)}`;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
visitConstant(e: Constant): string {
|
|
@@ -55,7 +55,7 @@ export default class DebugStringVisitor extends Visitor<string> {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
visitExpressionAs(e: ExpressionAs): string {
|
|
58
|
-
return `${e.expression} as ${e.alias}`;
|
|
58
|
+
return `${this.visit(e.expression)} as ${this.visit(e.alias)}`;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
visitIdentifier(e: Identifier): string {
|
|
@@ -94,7 +94,7 @@ export default class DebugStringVisitor extends Visitor<string> {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
visitStringLiteral(e: StringLiteral): string {
|
|
97
|
-
return `
|
|
97
|
+
return `'${e.value}'`;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
visitTemplateElement(e: TemplateElement): string {
|
|
@@ -122,6 +122,53 @@ export default class DebugStringVisitor extends Visitor<string> {
|
|
|
122
122
|
return "`" + items.join("") + "`";
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
visitDeleteStatement(e: DeleteStatement): string {
|
|
126
|
+
if (e.where) {
|
|
127
|
+
return `DELETE FROM ${this.visit(e.table)} WHERE ${this.visit(e.where)}`;
|
|
128
|
+
}
|
|
129
|
+
return `DELETE FROM ${this.visit(e.table)}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
visitExistsExpression(e: ExistsExpression): string {
|
|
133
|
+
return `EXISTS (${this.visit(e.target)})`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
visitInsertStatement(e: InsertStatement): string {
|
|
137
|
+
return `INSERT INTO ${this.visit(e.table)} ${e.values} ${this.visit(e.returnValues)}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
visitJoinExpression(e: JoinExpression): string {
|
|
141
|
+
return `\n${e.joinType} JOIN ${this.visit(e.source)}\n\t\tON ${this.visit(e.where)}\n`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
visitOrderByExpression(e: OrderByExpression): string {
|
|
145
|
+
return `${e.target} ${e.descending ? "DESC" : "ASC"}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
visitReturnUpdated(e: ReturnUpdated): string {
|
|
149
|
+
return `\nRETURNING ${this.visitArray(e.fields)}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
visitValuesStatement(e: ValuesStatement): string {
|
|
153
|
+
const rows = e.values.map((x) => `(${this.visit(x[0])})`).join(",\n\t");
|
|
154
|
+
return `(VALUES ${rows}) as ${this.visit(e.as)})`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
visitSelectStatement(e: SelectStatement): string {
|
|
158
|
+
const select = `SELECT\n\t${this.visitArray(e.fields, ",\n\t")}\n\tFROM ${this.visit(e.source)}`;
|
|
159
|
+
const as = e.as ? this.visit(e.as): "";
|
|
160
|
+
const joins = e.joins?.length > 0 ? this.visitArray(e.joins, "\n\t") : "";
|
|
161
|
+
const where = e.where ? `\n\tWHERE ${this.visit(e.where)}` : "";
|
|
162
|
+
const orderBy = e.orderBy ? `\n\tORDER BY ${this.visitArray(e.orderBy, "\n\t\tTHEN BY")}`: "";
|
|
163
|
+
const limit = e.limit > 0 ? `\n\tLIMIT ${e.limit}` : "";
|
|
164
|
+
const offset = e.offset > 0 ? `\n\OFFSET ${e.offset}` : "";
|
|
165
|
+
return `${select}${as}${joins}${where}${orderBy}${limit}${offset}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
visitUpdateStatement(e: UpdateStatement): string {
|
|
169
|
+
return "UPDATE";
|
|
170
|
+
}
|
|
171
|
+
|
|
125
172
|
private visitArray(e: Expression[], separator = ", ") {
|
|
126
173
|
return e.map((x) => this.visit(x)).join(separator);
|
|
127
174
|
}
|