@entity-access/entity-access 1.0.45 → 1.0.47
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 +20 -1
- package/dist/common/Logger.d.ts +8 -1
- package/dist/common/Logger.d.ts.map +1 -1
- package/dist/common/Logger.js +18 -1
- package/dist/common/Logger.js.map +1 -1
- package/dist/common/ObjectPool.d.ts +42 -0
- package/dist/common/ObjectPool.d.ts.map +1 -0
- package/dist/common/ObjectPool.js +86 -0
- package/dist/common/ObjectPool.js.map +1 -0
- package/dist/common/sleep.d.ts +3 -0
- package/dist/common/sleep.d.ts.map +1 -0
- package/dist/common/sleep.js +17 -0
- package/dist/common/sleep.js.map +1 -0
- package/dist/decorators/Column.js +1 -1
- package/dist/decorators/Column.js.map +1 -1
- package/dist/di/di.d.ts +2 -2
- package/dist/di/di.d.ts.map +1 -1
- package/dist/di/di.js +33 -19
- package/dist/di/di.js.map +1 -1
- package/dist/drivers/base/BaseDriver.d.ts.map +1 -1
- package/dist/drivers/base/BaseDriver.js.map +1 -1
- package/dist/drivers/postgres/PostgreSqlDriver.d.ts +2 -2
- package/dist/drivers/postgres/PostgreSqlDriver.d.ts.map +1 -1
- package/dist/drivers/postgres/PostgreSqlDriver.js +54 -25
- package/dist/drivers/postgres/PostgreSqlDriver.js.map +1 -1
- package/dist/drivers/sql-server/ExpressionToSqlServer.d.ts +2 -1
- package/dist/drivers/sql-server/ExpressionToSqlServer.d.ts.map +1 -1
- package/dist/drivers/sql-server/ExpressionToSqlServer.js +3 -0
- package/dist/drivers/sql-server/ExpressionToSqlServer.js.map +1 -1
- package/dist/eternity/ActivitySuspendedError.d.ts +3 -0
- package/dist/eternity/ActivitySuspendedError.d.ts.map +1 -0
- package/dist/eternity/ActivitySuspendedError.js +3 -0
- package/dist/eternity/ActivitySuspendedError.js.map +1 -0
- package/dist/eternity/EternityContext.d.ts +27 -0
- package/dist/eternity/EternityContext.d.ts.map +1 -0
- package/dist/eternity/EternityContext.js +230 -0
- package/dist/eternity/EternityContext.js.map +1 -0
- package/dist/eternity/EternityStorage.d.ts +40 -0
- package/dist/eternity/EternityStorage.d.ts.map +1 -0
- package/dist/eternity/EternityStorage.js +217 -0
- package/dist/eternity/EternityStorage.js.map +1 -0
- package/dist/eternity/Workflow.d.ts +24 -0
- package/dist/eternity/Workflow.d.ts.map +1 -0
- package/dist/eternity/Workflow.js +49 -0
- package/dist/eternity/Workflow.js.map +1 -0
- package/dist/eternity/WorkflowClock.d.ts +5 -0
- package/dist/eternity/WorkflowClock.d.ts.map +1 -0
- package/dist/eternity/WorkflowClock.js +18 -0
- package/dist/eternity/WorkflowClock.js.map +1 -0
- package/dist/eternity/WorkflowRegistry.d.ts +13 -0
- package/dist/eternity/WorkflowRegistry.d.ts.map +1 -0
- package/dist/eternity/WorkflowRegistry.js +24 -0
- package/dist/eternity/WorkflowRegistry.js.map +1 -0
- package/dist/migrations/Migrations.d.ts.map +1 -1
- package/dist/migrations/Migrations.js +1 -0
- package/dist/migrations/Migrations.js.map +1 -1
- package/dist/model/EntityContext.d.ts +1 -1
- package/dist/model/EntityContext.d.ts.map +1 -1
- package/dist/model/EntityContext.js +2 -1
- package/dist/model/EntityContext.js.map +1 -1
- package/dist/model/EntityQuery.d.ts.map +1 -1
- package/dist/model/EntityQuery.js +1 -3
- package/dist/model/EntityQuery.js.map +1 -1
- package/dist/query/ast/DebugStringVisitor.d.ts +3 -1
- package/dist/query/ast/DebugStringVisitor.d.ts.map +1 -1
- package/dist/query/ast/DebugStringVisitor.js +6 -0
- package/dist/query/ast/DebugStringVisitor.js.map +1 -1
- package/dist/query/ast/ExpressionToSql.d.ts +3 -1
- package/dist/query/ast/ExpressionToSql.d.ts.map +1 -1
- package/dist/query/ast/ExpressionToSql.js +19 -3
- package/dist/query/ast/ExpressionToSql.js.map +1 -1
- package/dist/query/ast/Expressions.d.ts +10 -7
- package/dist/query/ast/Expressions.d.ts.map +1 -1
- package/dist/query/ast/Expressions.js +12 -4
- package/dist/query/ast/Expressions.js.map +1 -1
- package/dist/query/ast/Visitor.d.ts +3 -1
- package/dist/query/ast/Visitor.d.ts.map +1 -1
- package/dist/query/ast/Visitor.js +10 -0
- package/dist/query/ast/Visitor.js.map +1 -1
- package/dist/query/parser/ArrowToExpression.d.ts +2 -2
- package/dist/query/parser/ArrowToExpression.d.ts.map +1 -1
- package/dist/query/parser/ArrowToExpression.js +2 -2
- package/dist/query/parser/ArrowToExpression.js.map +1 -1
- package/dist/tests/eternity/eternity-tests.d.ts +3 -0
- package/dist/tests/eternity/eternity-tests.d.ts.map +1 -0
- package/dist/tests/eternity/eternity-tests.js +91 -0
- package/dist/tests/eternity/eternity-tests.js.map +1 -0
- package/dist/tests/security/tests/place-order.js +6 -2
- package/dist/tests/security/tests/place-order.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/common/Logger.ts +25 -2
- package/src/common/ObjectPool.ts +124 -0
- package/src/common/sleep.ts +16 -0
- package/src/decorators/Column.ts +1 -1
- package/src/di/di.ts +37 -20
- package/src/drivers/base/BaseDriver.ts +2 -1
- package/src/drivers/postgres/PostgreSqlDriver.ts +59 -32
- package/src/drivers/sql-server/ExpressionToSqlServer.ts +5 -1
- package/src/eternity/ActivitySuspendedError.ts +3 -0
- package/src/eternity/EternityContext.ts +254 -0
- package/src/eternity/EternityStorage.ts +180 -0
- package/src/eternity/Workflow.ts +55 -0
- package/src/eternity/WorkflowClock.ts +10 -0
- package/src/eternity/WorkflowRegistry.ts +34 -0
- package/src/migrations/Migrations.ts +2 -0
- package/src/model/EntityContext.ts +3 -2
- package/src/model/EntityQuery.ts +1 -2
- package/src/query/ast/DebugStringVisitor.ts +10 -1
- package/src/query/ast/ExpressionToSql.ts +22 -4
- package/src/query/ast/Expressions.ts +17 -5
- package/src/query/ast/Visitor.ts +11 -1
- package/src/query/parser/ArrowToExpression.ts +2 -2
- package/src/tests/eternity/eternity-tests.ts +108 -0
- package/src/tests/security/tests/place-order.ts +6 -2
- package/test.js +38 -3
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import Column from "../decorators/Column.js";
|
|
2
|
+
import Index from "../decorators/Index.js";
|
|
3
|
+
import Table from "../decorators/Table.js";
|
|
4
|
+
import Inject, { RegisterScoped, RegisterSingleton, ServiceProvider } from "../di/di.js";
|
|
5
|
+
import { BaseDriver } from "../drivers/base/BaseDriver.js";
|
|
6
|
+
import EntityContext from "../model/EntityContext.js";
|
|
7
|
+
import DateTime from "../types/DateTime.js";
|
|
8
|
+
import WorkflowClock from "./WorkflowClock.js";
|
|
9
|
+
|
|
10
|
+
@Table("Workflows")
|
|
11
|
+
@Index({
|
|
12
|
+
name: "IX_Workflows_Group",
|
|
13
|
+
columns: [{ name: (x) => x.group, descending: false }],
|
|
14
|
+
filter: (x) => x.group !== null
|
|
15
|
+
})
|
|
16
|
+
@Index({
|
|
17
|
+
name: "IX_Workflows_ETA",
|
|
18
|
+
columns: [{ name: (x) => x.eta, descending: false }],
|
|
19
|
+
filter: (x) => x.isWorkflow === true
|
|
20
|
+
})
|
|
21
|
+
export class WorkflowStorage {
|
|
22
|
+
|
|
23
|
+
@Column({ dataType: "Char", length: 400, key: true })
|
|
24
|
+
public id: string;
|
|
25
|
+
|
|
26
|
+
@Column({ dataType: "Boolean" })
|
|
27
|
+
public isWorkflow: boolean;
|
|
28
|
+
|
|
29
|
+
@Column({ dataType: "Char", nullable: true })
|
|
30
|
+
public name: string;
|
|
31
|
+
|
|
32
|
+
@Column({ dataType: "Char", length: 200, nullable: true })
|
|
33
|
+
public group: string;
|
|
34
|
+
|
|
35
|
+
@Column({ dataType: "Char"})
|
|
36
|
+
public input: string;
|
|
37
|
+
|
|
38
|
+
@Column({ dataType: "Char", nullable: true})
|
|
39
|
+
public output: string;
|
|
40
|
+
|
|
41
|
+
@Column({ })
|
|
42
|
+
public eta: DateTime;
|
|
43
|
+
|
|
44
|
+
@Column({ })
|
|
45
|
+
public queued: DateTime;
|
|
46
|
+
|
|
47
|
+
@Column({ })
|
|
48
|
+
public updated: DateTime;
|
|
49
|
+
|
|
50
|
+
@Column({ dataType: "Int", default: "0"})
|
|
51
|
+
public priority: number;
|
|
52
|
+
|
|
53
|
+
@Column({ nullable: true })
|
|
54
|
+
public lockedTTL: DateTime;
|
|
55
|
+
|
|
56
|
+
@Column({ dataType: "AsciiChar", length: 10})
|
|
57
|
+
public state: "queued" | "failed" | "done";
|
|
58
|
+
|
|
59
|
+
@Column({ dataType: "Char", nullable: true})
|
|
60
|
+
public error: string;
|
|
61
|
+
|
|
62
|
+
@Column({ dataType: "Char", nullable: true})
|
|
63
|
+
public extra: string;
|
|
64
|
+
|
|
65
|
+
@Column({ dataType: "Char", length: 200 , nullable: true})
|
|
66
|
+
public parentID: string;
|
|
67
|
+
|
|
68
|
+
@Column({ dataType: "Char", length: 200 , nullable: true})
|
|
69
|
+
public lastID: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@RegisterScoped
|
|
73
|
+
class WorkflowContext extends EntityContext {
|
|
74
|
+
|
|
75
|
+
public workflows = this.model.register(WorkflowStorage);
|
|
76
|
+
|
|
77
|
+
verifyFilters: boolean = false;
|
|
78
|
+
|
|
79
|
+
raiseEvents: boolean = false;
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@RegisterSingleton
|
|
84
|
+
export default class EternityStorage {
|
|
85
|
+
|
|
86
|
+
constructor(
|
|
87
|
+
@Inject
|
|
88
|
+
private driver: BaseDriver,
|
|
89
|
+
@Inject
|
|
90
|
+
public readonly clock: WorkflowClock
|
|
91
|
+
) {
|
|
92
|
+
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async get(id: string, input?) {
|
|
96
|
+
const db = new WorkflowContext(this.driver);
|
|
97
|
+
const r = await db.workflows.where({ id }, (p) => (x) => x.id === p.id && x.isWorkflow === true).first();
|
|
98
|
+
if (r !== null) {
|
|
99
|
+
return {
|
|
100
|
+
updated: r.updated,
|
|
101
|
+
eta: r.eta,
|
|
102
|
+
queued: r.queued,
|
|
103
|
+
state: r.state,
|
|
104
|
+
output: r.output,
|
|
105
|
+
error: r.error
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async delete(id) {
|
|
112
|
+
const db = new WorkflowContext(this.driver);
|
|
113
|
+
const children = await db.workflows.where({ id}, (p) => (x) => x.parentID === p.id)
|
|
114
|
+
.limit(100)
|
|
115
|
+
.toArray();
|
|
116
|
+
for (const iterator of children) {
|
|
117
|
+
db.workflows.delete(iterator);
|
|
118
|
+
}
|
|
119
|
+
await db.saveChanges();
|
|
120
|
+
if (children.length === 100) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const w = await db.workflows.where({ id}, (p) => (x) => x.id === p.id).first();
|
|
125
|
+
if (!w) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
db.workflows.delete(w);
|
|
129
|
+
await db.saveChanges();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async save(state: Partial<WorkflowStorage>) {
|
|
133
|
+
const db = new WorkflowContext(this.driver);
|
|
134
|
+
await this.driver.runInTransaction(async () => {
|
|
135
|
+
let w = await db.workflows.where(state, (p) => (x) => x.id === p.id).first();
|
|
136
|
+
if (!w) {
|
|
137
|
+
w = db.workflows.add(state);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
for (const key in state) {
|
|
141
|
+
if (Object.prototype.hasOwnProperty.call(state, key)) {
|
|
142
|
+
const element = state[key];
|
|
143
|
+
w[key] = element;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
w.state ||= "queued";
|
|
148
|
+
await db.saveChanges();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async dequeue(signal?: AbortSignal) {
|
|
153
|
+
const db = new WorkflowContext(this.driver);
|
|
154
|
+
const now = this.clock.utcNow;
|
|
155
|
+
const lockedTTL = now.addMinutes(1);
|
|
156
|
+
return this.driver.runInTransaction(async () => {
|
|
157
|
+
const list = await db.workflows
|
|
158
|
+
.where({now}, (p) => (x) => x.eta <= p.now
|
|
159
|
+
&& (x.lockedTTL === null || x.lockedTTL <= p.now)
|
|
160
|
+
&& x.isWorkflow === true)
|
|
161
|
+
.orderBy({}, (p) => (x) => x.eta)
|
|
162
|
+
.thenBy({}, (p) => (x) => x.priority)
|
|
163
|
+
.limit(20)
|
|
164
|
+
.withSignal(signal)
|
|
165
|
+
.toArray();
|
|
166
|
+
for (const iterator of list) {
|
|
167
|
+
iterator.lockedTTL = lockedTTL;
|
|
168
|
+
}
|
|
169
|
+
await db.saveChanges(signal);
|
|
170
|
+
return list;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async seed() {
|
|
175
|
+
const db = new WorkflowContext(this.driver);
|
|
176
|
+
await db.driver.ensureDatabase();
|
|
177
|
+
await db.driver.automaticMigrations().migrate(db);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import Inject from "../di/di.js";
|
|
2
|
+
import DateTime from "../types/DateTime.js";
|
|
3
|
+
import TimeSpan from "../types/TimeSpan.js";
|
|
4
|
+
import EternityContext from "./EternityContext.js";
|
|
5
|
+
import { WorkflowRegistry } from "./WorkflowRegistry.js";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export function Activity(target, key) {
|
|
9
|
+
WorkflowRegistry.register(target.constructor, key);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function UniqueActivity(target, key) {
|
|
13
|
+
WorkflowRegistry.register(target.constructor, key, true);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export default abstract class Workflow<TIn = any, TOut = any> {
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* If specified, all workflows in same sequence
|
|
21
|
+
* will executed sequentially in a single worker node
|
|
22
|
+
*/
|
|
23
|
+
public readonly sequence: string;
|
|
24
|
+
|
|
25
|
+
public readonly input: TIn;
|
|
26
|
+
|
|
27
|
+
public readonly id: string;
|
|
28
|
+
|
|
29
|
+
public readonly eta: DateTime;
|
|
30
|
+
|
|
31
|
+
public readonly currentTime: DateTime;
|
|
32
|
+
|
|
33
|
+
public preserveTime: TimeSpan = TimeSpan.fromMinutes(5);
|
|
34
|
+
|
|
35
|
+
public failedPreserveTime: TimeSpan = TimeSpan.fromDays(1);
|
|
36
|
+
|
|
37
|
+
@Inject
|
|
38
|
+
protected context: EternityContext;
|
|
39
|
+
|
|
40
|
+
constructor(p: Partial<Workflow>) {
|
|
41
|
+
Object.setPrototypeOf(p, new.target.prototype);
|
|
42
|
+
return p as Workflow;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public abstract run(): Promise<TOut>;
|
|
46
|
+
|
|
47
|
+
public delay(ts: TimeSpan) {
|
|
48
|
+
return Promise.resolve("");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public waitForExternalEvent(ts: TimeSpan, ... names: string[]) {
|
|
52
|
+
// do nothing...
|
|
53
|
+
return Promise.resolve("");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { IClassOf } from "../decorators/IClassOf.js";
|
|
2
|
+
import type Workflow from "./Workflow.js";
|
|
3
|
+
|
|
4
|
+
export interface IWorkflowSchema {
|
|
5
|
+
name: string;
|
|
6
|
+
type: IClassOf<Workflow>;
|
|
7
|
+
activities: string[];
|
|
8
|
+
uniqueActivities: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// const schema: Map<any,IWorkflowSchema> = new Map();
|
|
12
|
+
const schemaByName: Map<string,IWorkflowSchema> = new Map();
|
|
13
|
+
|
|
14
|
+
export const WorkflowRegistry = {
|
|
15
|
+
register(target: IClassOf<any>, key, unique = false) {
|
|
16
|
+
let methods = schemaByName.get(target.name);
|
|
17
|
+
if (!methods) {
|
|
18
|
+
methods = { name: target.name, type: target, activities: ["delay", "waitForExternalEvent"], uniqueActivities: [] };
|
|
19
|
+
schemaByName.set(target.name, methods);
|
|
20
|
+
}
|
|
21
|
+
if (key) {
|
|
22
|
+
if (unique) {
|
|
23
|
+
methods.uniqueActivities.push(key);
|
|
24
|
+
} else {
|
|
25
|
+
methods.activities.push(key);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return methods;
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
getByName(name: string) {
|
|
32
|
+
return schemaByName.get(name);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -24,6 +24,8 @@ export default abstract class Migrations {
|
|
|
24
24
|
async migrateIndexInternal(context: EntityContext, index: IIndex, type: EntityType) {
|
|
25
25
|
// parse filter... pending...
|
|
26
26
|
|
|
27
|
+
index = { ... index };
|
|
28
|
+
|
|
27
29
|
if (index.filter && typeof index.filter !== "string") {
|
|
28
30
|
// parse..
|
|
29
31
|
const source = context.query(type.typeClass) as EntityQuery<any>;
|
|
@@ -35,7 +35,7 @@ export default class EntityContext {
|
|
|
35
35
|
@Inject
|
|
36
36
|
private events?: ContextEvents,
|
|
37
37
|
@Inject
|
|
38
|
-
|
|
38
|
+
public readonly logger?: Logger
|
|
39
39
|
) {
|
|
40
40
|
this.raiseEvents = !!events;
|
|
41
41
|
}
|
|
@@ -159,7 +159,8 @@ export default class EntityContext {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
protected async saveChangesWithoutEvents(signal: AbortSignal) {
|
|
162
|
-
|
|
162
|
+
const copy = [].concat(this.changeSet.entries);
|
|
163
|
+
for (const iterator of copy) {
|
|
163
164
|
switch (iterator.status) {
|
|
164
165
|
case "inserted":
|
|
165
166
|
const insert = this.driver.createInsertExpression(iterator.type, iterator.entity);
|
package/src/model/EntityQuery.ts
CHANGED
|
@@ -69,9 +69,8 @@ export default class EntityQuery<T = any>
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
async *enumerate(): AsyncGenerator<T, any, unknown> {
|
|
72
|
-
const logger = ServiceProvider.resolve(this.context, Logger);
|
|
73
72
|
const scope = new DisposableScope();
|
|
74
|
-
const session = logger
|
|
73
|
+
const session = this.context.logger?.newSession() ?? Logger.nullLogger;
|
|
75
74
|
let query: { text: string, values: any[]};
|
|
76
75
|
try {
|
|
77
76
|
scope.register(session);
|
|
@@ -1,4 +1,4 @@
|
|
|
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";
|
|
1
|
+
import { ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NotExits, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateElement, TemplateLiteral, UnionAllStatement, UpdateStatement, ValuesStatement } from "./Expressions.js";
|
|
2
2
|
import Visitor from "./Visitor.js";
|
|
3
3
|
|
|
4
4
|
const isBinary = (type) => /^(BinaryExpression|CoalesceExpression)$/.test(type);
|
|
@@ -169,7 +169,16 @@ export default class DebugStringVisitor extends Visitor<string> {
|
|
|
169
169
|
return "UPDATE";
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
visitNotExists(e: NotExits): string {
|
|
173
|
+
return ` NOT EXISTS ${this.visit(e.target)}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
visitUnionAllStatement(e: UnionAllStatement): string {
|
|
177
|
+
return e.queries.map((x) => this.visit(x)).join("\nUNION ALL\n");
|
|
178
|
+
}
|
|
179
|
+
|
|
172
180
|
private visitArray(e: Expression[], separator = ", ") {
|
|
173
181
|
return e.map((x) => this.visit(x)).join(separator);
|
|
174
182
|
}
|
|
183
|
+
|
|
175
184
|
}
|
|
@@ -2,7 +2,7 @@ import QueryCompiler from "../../compiler/QueryCompiler.js";
|
|
|
2
2
|
import EntityType from "../../entity-query/EntityType.js";
|
|
3
3
|
import EntityQuery from "../../model/EntityQuery.js";
|
|
4
4
|
import { filteredSymbol } from "../../model/events/EntityEvents.js";
|
|
5
|
-
import { BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateLiteral, UpdateStatement, ValuesStatement } from "./Expressions.js";
|
|
5
|
+
import { BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NotExits, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateLiteral, UnionAllStatement, UpdateStatement, ValuesStatement } from "./Expressions.js";
|
|
6
6
|
import { ITextQuery, QueryParameter, prepare, prepareJoin } from "./IStringTransformer.js";
|
|
7
7
|
import ParameterScope from "./ParameterScope.js";
|
|
8
8
|
import Visitor from "./Visitor.js";
|
|
@@ -126,7 +126,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
visitBooleanLiteral( { value }: BooleanLiteral): ITextQuery {
|
|
129
|
-
return [
|
|
129
|
+
return [ value ? " true ": " false "];
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
visitTemplateLiteral(e: TemplateLiteral): ITextQuery {
|
|
@@ -284,6 +284,10 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
284
284
|
return prepare `${this.visit(target)}.${this.visit(property)}`;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
visitNotExists(e: NotExits): ITextQuery {
|
|
288
|
+
return [" NOT EXISTS ", this.visit(e.target) ];
|
|
289
|
+
}
|
|
290
|
+
|
|
287
291
|
visitNullExpression(e: NullExpression): ITextQuery {
|
|
288
292
|
return ["NULL"];
|
|
289
293
|
}
|
|
@@ -298,7 +302,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
298
302
|
: this.visit(e.right);
|
|
299
303
|
|
|
300
304
|
if ((e.right as ExpressionType).type === "NullExpression") {
|
|
301
|
-
if (e.operator === "===" || e.operator === "==") {
|
|
305
|
+
if (e.operator === "===" || e.operator === "==" || e.operator === "=") {
|
|
302
306
|
return prepare `${left} IS NULL`;
|
|
303
307
|
}
|
|
304
308
|
if (e.operator === "!==" || e.operator === "!=" || e.operator === "<>") {
|
|
@@ -306,7 +310,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
306
310
|
}
|
|
307
311
|
}
|
|
308
312
|
if ((e.left as ExpressionType).type === "NullExpression") {
|
|
309
|
-
if (e.operator === "===" || e.operator === "==") {
|
|
313
|
+
if (e.operator === "===" || e.operator === "==" || e.operator === "=") {
|
|
310
314
|
return prepare `${right} IS NULL`;
|
|
311
315
|
}
|
|
312
316
|
if (e.operator === "!==" || e.operator === "!=" || e.operator === "<>") {
|
|
@@ -411,6 +415,20 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
|
411
415
|
return prepare `EXISTS (${this.visit(e.target)})`;
|
|
412
416
|
}
|
|
413
417
|
|
|
418
|
+
visitUnionAllStatement(e: UnionAllStatement): ITextQuery {
|
|
419
|
+
const all: ITextQuery = [];
|
|
420
|
+
let first = true;
|
|
421
|
+
for (const iterator of e.queries) {
|
|
422
|
+
all.push(this.visit(iterator));
|
|
423
|
+
if (first) {
|
|
424
|
+
first = false;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
all.push(" UNION ALL ");
|
|
428
|
+
}
|
|
429
|
+
return all;
|
|
430
|
+
}
|
|
431
|
+
|
|
414
432
|
/**
|
|
415
433
|
* This will also create and replace joins if query is provided.
|
|
416
434
|
* @param x MemberExpression
|
|
@@ -124,7 +124,7 @@ export abstract class Expression {
|
|
|
124
124
|
return copy;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
readonly type:
|
|
127
|
+
readonly type: ExpressionType["type"];
|
|
128
128
|
|
|
129
129
|
}
|
|
130
130
|
|
|
@@ -133,10 +133,10 @@ export class ArrayExpression extends Expression {
|
|
|
133
133
|
elements: Expression[];
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
export class PartialExpression extends Expression {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
136
|
+
// export class PartialExpression extends Expression {
|
|
137
|
+
// readonly type = "PartialExpression";
|
|
138
|
+
// query: ITextQuery;
|
|
139
|
+
// }
|
|
140
140
|
|
|
141
141
|
export class BinaryExpression extends Expression {
|
|
142
142
|
|
|
@@ -166,6 +166,11 @@ export class OrderByExpression extends Expression {
|
|
|
166
166
|
descending: boolean;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
export class NotExits extends Expression {
|
|
170
|
+
readonly type = "NotExists";
|
|
171
|
+
target: Expression;
|
|
172
|
+
}
|
|
173
|
+
|
|
169
174
|
export class ExistsExpression extends Expression {
|
|
170
175
|
readonly type = "ExistsExpression";
|
|
171
176
|
target: Expression;
|
|
@@ -371,6 +376,11 @@ export class UpdateStatement extends Expression {
|
|
|
371
376
|
|
|
372
377
|
}
|
|
373
378
|
|
|
379
|
+
export class UnionAllStatement extends Expression {
|
|
380
|
+
readonly type = "UnionAllStatement";
|
|
381
|
+
queries: Expression[];
|
|
382
|
+
}
|
|
383
|
+
|
|
374
384
|
export class DeleteStatement extends Expression {
|
|
375
385
|
readonly type = "DeleteStatement";
|
|
376
386
|
table: TableLiteral | QuotedLiteral;
|
|
@@ -407,5 +417,7 @@ export type ExpressionType =
|
|
|
407
417
|
NewObjectExpression |
|
|
408
418
|
ParameterExpression |
|
|
409
419
|
ArrayExpression |
|
|
420
|
+
NotExits |
|
|
421
|
+
UnionAllStatement |
|
|
410
422
|
TemplateElement
|
|
411
423
|
;
|
package/src/query/ast/Visitor.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NotSupportedError } from "../parser/NotSupportedError.js";
|
|
2
|
-
import { ArrayExpression, ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateElement, TemplateLiteral, UpdateStatement, ValuesStatement } from "./Expressions.js";
|
|
2
|
+
import { ArrayExpression, ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NotExits, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateElement, TemplateLiteral, UnionAllStatement, UpdateStatement, ValuesStatement } from "./Expressions.js";
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
export default abstract class Visitor<T = any> {
|
|
@@ -69,10 +69,20 @@ export default abstract class Visitor<T = any> {
|
|
|
69
69
|
return this.visitParameterExpression(e);
|
|
70
70
|
case "ArrayExpression":
|
|
71
71
|
return this.visitArrayExpression(e);
|
|
72
|
+
case "NotExists":
|
|
73
|
+
return this.visitNotExists(e);
|
|
74
|
+
case "UnionAllStatement":
|
|
75
|
+
return this.visitUnionAllStatement(e);
|
|
72
76
|
}
|
|
73
77
|
const c: never = e;
|
|
74
78
|
throw new Error(`${e1.type} Not implemented`);
|
|
75
79
|
}
|
|
80
|
+
visitUnionAllStatement(e: UnionAllStatement): T {
|
|
81
|
+
throw new NotSupportedError("Union All");
|
|
82
|
+
}
|
|
83
|
+
visitNotExists(e: NotExits): T {
|
|
84
|
+
throw new NotSupportedError("Not Exists");
|
|
85
|
+
}
|
|
76
86
|
visitArrayExpression(e: ArrayExpression): T {
|
|
77
87
|
throw new NotSupportedError("Array Expression");
|
|
78
88
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseExpression } from "@babel/parser";
|
|
2
|
-
import { ArrowFunctionExpression, BinaryExpression, CallExpression, CoalesceExpression, ConditionalExpression, Constant, Expression, ExpressionAs, Identifier, MemberExpression, NewObjectExpression, NullExpression, NumberLiteral, ParameterExpression, QuotedLiteral, StringLiteral, TemplateLiteral } from "../ast/Expressions.js";
|
|
2
|
+
import { ArrowFunctionExpression, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, Expression, ExpressionAs, Identifier, MemberExpression, NewObjectExpression, NullExpression, NumberLiteral, ParameterExpression, QuotedLiteral, StringLiteral, TemplateLiteral } from "../ast/Expressions.js";
|
|
3
3
|
import { BabelVisitor } from "./BabelVisitor.js";
|
|
4
4
|
import * as bpe from "@babel/types";
|
|
5
5
|
import Restructure from "./Restructure.js";
|
|
@@ -85,7 +85,7 @@ export default class ArrowToExpression extends BabelVisitor<Expression> {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
visitBooleanLiteral({ value }: bpe.BooleanLiteral) {
|
|
88
|
-
return
|
|
88
|
+
return BooleanLiteral.create({ value });
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
visitDecimalLiteral( { value }: bpe.DecimalLiteral) {
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import assert from "assert";
|
|
2
|
+
import Inject, { Register, RegisterSingleton, ServiceProvider } from "../../di/di.js";
|
|
3
|
+
import EternityContext from "../../eternity/EternityContext.js";
|
|
4
|
+
import Workflow, { Activity } from "../../eternity/Workflow.js";
|
|
5
|
+
import WorkflowClock from "../../eternity/WorkflowClock.js";
|
|
6
|
+
import DateTime from "../../types/DateTime.js";
|
|
7
|
+
import { TestConfig } from "../TestConfig.js";
|
|
8
|
+
import { BaseDriver } from "../../drivers/base/BaseDriver.js";
|
|
9
|
+
import EternityStorage from "../../eternity/EternityStorage.js";
|
|
10
|
+
import TimeSpan from "../../types/TimeSpan.js";
|
|
11
|
+
import sleep from "../../common/sleep.js";
|
|
12
|
+
|
|
13
|
+
class MockClock extends WorkflowClock {
|
|
14
|
+
|
|
15
|
+
public get utcNow(): DateTime {
|
|
16
|
+
return this.time;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public set utcNow(v: DateTime) {
|
|
20
|
+
this.time = v;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private time: DateTime = DateTime.utcNow;
|
|
24
|
+
|
|
25
|
+
public add(ts: TimeSpan) {
|
|
26
|
+
this.time = this.time.add(ts);
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@RegisterSingleton
|
|
32
|
+
class Mailer {
|
|
33
|
+
|
|
34
|
+
public items: any[] = [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class SendWorkflow extends Workflow<string, string> {
|
|
38
|
+
|
|
39
|
+
public async run(): Promise<any> {
|
|
40
|
+
|
|
41
|
+
await this.delay(TimeSpan.fromHours(1));
|
|
42
|
+
|
|
43
|
+
await this.sendMail("a", "b", "c");
|
|
44
|
+
return "1";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Activity
|
|
48
|
+
public async sendMail(
|
|
49
|
+
from: string,
|
|
50
|
+
to: string,
|
|
51
|
+
message: string,
|
|
52
|
+
@Inject logger?: Mailer) {
|
|
53
|
+
await sleep(10);
|
|
54
|
+
logger.items.push({ from, to, message });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default async function (this: TestConfig) {
|
|
60
|
+
|
|
61
|
+
const mockClock = new MockClock();
|
|
62
|
+
const mailer = new Mailer();
|
|
63
|
+
|
|
64
|
+
const scope = new ServiceProvider();
|
|
65
|
+
scope.add(WorkflowClock, mockClock);
|
|
66
|
+
scope.add(BaseDriver, this.driver);
|
|
67
|
+
const storage = new EternityStorage(this.driver, mockClock);
|
|
68
|
+
await storage.seed();
|
|
69
|
+
scope.add(Mailer, mailer);
|
|
70
|
+
scope.add(EternityStorage, storage);
|
|
71
|
+
|
|
72
|
+
const c = new EternityContext(storage);
|
|
73
|
+
scope.add(EternityContext, c);
|
|
74
|
+
|
|
75
|
+
// this is an important step
|
|
76
|
+
c.register(SendWorkflow);
|
|
77
|
+
|
|
78
|
+
const id = await c.queue(SendWorkflow, "a");
|
|
79
|
+
|
|
80
|
+
mockClock.add(TimeSpan.fromSeconds(15));
|
|
81
|
+
|
|
82
|
+
await c.processQueueOnce();
|
|
83
|
+
|
|
84
|
+
mockClock.add(TimeSpan.fromSeconds(15));
|
|
85
|
+
|
|
86
|
+
await c.processQueueOnce();
|
|
87
|
+
|
|
88
|
+
assert.equal(0, mailer.items.length);
|
|
89
|
+
|
|
90
|
+
mockClock.add(TimeSpan.fromHours(1));
|
|
91
|
+
|
|
92
|
+
await c.processQueueOnce();
|
|
93
|
+
|
|
94
|
+
assert.equal(1, mailer.items.length);
|
|
95
|
+
|
|
96
|
+
let r = await c.get(SendWorkflow, id);
|
|
97
|
+
assert.equal("1", r.output);
|
|
98
|
+
|
|
99
|
+
mockClock.add(TimeSpan.fromDays(2));
|
|
100
|
+
|
|
101
|
+
await c.processQueueOnce();
|
|
102
|
+
|
|
103
|
+
r = await c.get(SendWorkflow, id);
|
|
104
|
+
assert.strictEqual(null, r);
|
|
105
|
+
|
|
106
|
+
// throw new Error("Preserve");
|
|
107
|
+
|
|
108
|
+
}
|
|
@@ -27,7 +27,9 @@ export default async function(this: TestConfig) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
async function getNewOrders(this: TestConfig) {
|
|
30
|
-
const
|
|
30
|
+
const global = new ServiceProvider();
|
|
31
|
+
global.add(BaseDriver, this.driver);
|
|
32
|
+
const scope = global.createScope();
|
|
31
33
|
try {
|
|
32
34
|
const user = new UserInfo();
|
|
33
35
|
user.userID = 2;
|
|
@@ -49,7 +51,9 @@ async function getNewOrders(this: TestConfig) {
|
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
async function addNewOrder(this: TestConfig, customer: User, userID?) {
|
|
52
|
-
const
|
|
54
|
+
const global = new ServiceProvider();
|
|
55
|
+
global.add(BaseDriver, this.driver);
|
|
56
|
+
const scope = global.createScope();
|
|
53
57
|
try {
|
|
54
58
|
const user = new UserInfo();
|
|
55
59
|
user.userID = userID ?? customer.userID;
|