jinaga-server 1.0.0
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/LICENSE +22 -0
- package/dist/authentication/authentication-device.d.ts +20 -0
- package/dist/authentication/authentication-device.js +61 -0
- package/dist/authentication/authentication-device.js.map +1 -0
- package/dist/authentication/authentication-impl.d.ts +21 -0
- package/dist/authentication/authentication-impl.js +61 -0
- package/dist/authentication/authentication-impl.js.map +1 -0
- package/dist/authentication/authentication-session.d.ts +23 -0
- package/dist/authentication/authentication-session.js +85 -0
- package/dist/authentication/authentication-session.js.map +1 -0
- package/dist/authentication/authentication.d.ts +5 -0
- package/dist/authentication/authentication.js +3 -0
- package/dist/authentication/authentication.js.map +1 -0
- package/dist/authentication.d.ts +17 -0
- package/dist/authentication.js +52 -0
- package/dist/authentication.js.map +1 -0
- package/dist/authorization/authorization-keystore.d.ts +13 -0
- package/dist/authorization/authorization-keystore.js +61 -0
- package/dist/authorization/authorization-keystore.js.map +1 -0
- package/dist/http/node-http.d.ts +7 -0
- package/dist/http/node-http.js +126 -0
- package/dist/http/node-http.js.map +1 -0
- package/dist/http/router.d.ts +20 -0
- package/dist/http/router.js +223 -0
- package/dist/http/router.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/jinaga-server.d.ts +18 -0
- package/dist/jinaga-server.js +101 -0
- package/dist/jinaga-server.js.map +1 -0
- package/dist/keystore.d.ts +9 -0
- package/dist/keystore.js +3 -0
- package/dist/keystore.js.map +1 -0
- package/dist/memory/memory-keystore.d.ts +16 -0
- package/dist/memory/memory-keystore.js +86 -0
- package/dist/memory/memory-keystore.js.map +1 -0
- package/dist/postgres/connection.d.ts +12 -0
- package/dist/postgres/connection.js +83 -0
- package/dist/postgres/connection.js.map +1 -0
- package/dist/postgres/edge-record.d.ts +9 -0
- package/dist/postgres/edge-record.js +30 -0
- package/dist/postgres/edge-record.js.map +1 -0
- package/dist/postgres/maps.d.ts +21 -0
- package/dist/postgres/maps.js +86 -0
- package/dist/postgres/maps.js.map +1 -0
- package/dist/postgres/postgres-keystore.d.ts +20 -0
- package/dist/postgres/postgres-keystore.js +168 -0
- package/dist/postgres/postgres-keystore.js.map +1 -0
- package/dist/postgres/postgres-store.d.ts +20 -0
- package/dist/postgres/postgres-store.js +717 -0
- package/dist/postgres/postgres-store.js.map +1 -0
- package/dist/postgres/query-description-builder.d.ts +13 -0
- package/dist/postgres/query-description-builder.js +104 -0
- package/dist/postgres/query-description-builder.js.map +1 -0
- package/dist/postgres/query-description.d.ts +71 -0
- package/dist/postgres/query-description.js +256 -0
- package/dist/postgres/query-description.js.map +1 -0
- package/dist/postgres/specification-result-sql.d.ts +41 -0
- package/dist/postgres/specification-result-sql.js +260 -0
- package/dist/postgres/specification-result-sql.js.map +1 -0
- package/dist/postgres/specification-sql.d.ts +3 -0
- package/dist/postgres/specification-sql.js +119 -0
- package/dist/postgres/specification-sql.js.map +1 -0
- package/dist/postgres/sql.d.ts +10 -0
- package/dist/postgres/sql.js +396 -0
- package/dist/postgres/sql.js.map +1 -0
- package/dist/util/fn.d.ts +6 -0
- package/dist/util/fn.js +81 -0
- package/dist/util/fn.js.map +1 -0
- package/dist/util/promise.d.ts +1 -0
- package/dist/util/promise.js +10 -0
- package/dist/util/promise.js.map +1 -0
- package/docs/registrations.sql +22 -0
- package/docs/specification.txt +41 -0
- package/docs/sql-generator.gv +25 -0
- package/integration-test/docker-compose.yaml +13 -0
- package/integration-test/jinaga-test/Dockerfile +12 -0
- package/integration-test/jinaga-test/jinaga.test.js +425 -0
- package/integration-test/jinaga-test/package-lock.json +6767 -0
- package/integration-test/jinaga-test/package.json +25 -0
- package/integration-test/postgres/0-init-app-db.sh +16 -0
- package/integration-test/postgres/1-setup.sql +219 -0
- package/integration-test/postgres/2-grant.sh +17 -0
- package/integration-test/postgres/Dockerfile +5 -0
- package/integration-test/run.sh +17 -0
- package/integration-test/start_db.sh +2 -0
- package/integration-test/stop_db.sh +3 -0
- package/integration-test/test.sh +8 -0
- package/interactive/index.js +56 -0
- package/jest.config.js +12 -0
- package/package.json +51 -0
- package/scripts/combined.sql +46 -0
- package/scripts/cte.sql +55 -0
- package/scripts/separate.sql +27 -0
- package/setup.sh +34 -0
- package/setup.sql +329 -0
- package/src/authentication/authentication-device.ts +68 -0
- package/src/authentication/authentication-impl.ts +59 -0
- package/src/authentication/authentication-session.ts +95 -0
- package/src/authentication/authentication.ts +6 -0
- package/src/authentication.ts +45 -0
- package/src/authorization/authorization-keystore.ts +67 -0
- package/src/http/node-http.ts +106 -0
- package/src/http/router.ts +237 -0
- package/src/index.ts +1 -0
- package/src/jinaga-server.ts +125 -0
- package/src/keystore.ts +10 -0
- package/src/memory/memory-keystore.ts +93 -0
- package/src/postgres/connection.ts +73 -0
- package/src/postgres/edge-record.ts +36 -0
- package/src/postgres/maps.ts +89 -0
- package/src/postgres/postgres-keystore.ts +167 -0
- package/src/postgres/postgres-store.ts +874 -0
- package/src/postgres/query-description-builder.ts +116 -0
- package/src/postgres/query-description.ts +385 -0
- package/src/postgres/specification-result-sql.ts +362 -0
- package/src/postgres/specification-sql.ts +138 -0
- package/src/postgres/sql.ts +482 -0
- package/src/util/fn.ts +63 -0
- package/src/util/promise.ts +5 -0
- package/test/authorization/authorizationSpec.ts +209 -0
- package/test/postgres/postgresSpec.ts +385 -0
- package/test/postgres/postgresSpecificationSpec.ts +742 -0
- package/test/reporter.js +25 -0
- package/tsconfig.json +25 -0
- package/upgrade.sh +21 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { Direction, ExistentialCondition, FactReference, Join, PropertyCondition, Quantifier, Step } from "jinaga";
|
|
2
|
+
|
|
3
|
+
import { FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
|
|
4
|
+
|
|
5
|
+
export type SqlQuery = {
|
|
6
|
+
empty: boolean,
|
|
7
|
+
sql: string,
|
|
8
|
+
parameters: any[],
|
|
9
|
+
pathLength: number,
|
|
10
|
+
factTypeNames: string[]
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function sqlFromSteps(start: FactReference, steps: Step[], factTypes: FactTypeMap, roleMap: RoleMap) : SqlQuery {
|
|
14
|
+
const builder = new QueryBuilder(factTypes, roleMap);
|
|
15
|
+
|
|
16
|
+
return builder.buildQuery(start, steps);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface QueryJoinEdge {
|
|
20
|
+
table: 'edge';
|
|
21
|
+
direction: 'predecessor' | 'successor';
|
|
22
|
+
edgeAlias: number;
|
|
23
|
+
roleParameter: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface QueryJoinFact {
|
|
27
|
+
table: 'fact';
|
|
28
|
+
factAlias: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type QueryJoin = QueryJoinEdge | QueryJoinFact;
|
|
32
|
+
|
|
33
|
+
interface QueryParts {
|
|
34
|
+
joins: QueryJoin[];
|
|
35
|
+
existentialClauses: ExistentialClause[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
interface ExistentialClause {
|
|
39
|
+
quantifier: Quantifier;
|
|
40
|
+
priorEdgeJoin: QueryJoinEdge;
|
|
41
|
+
query: QueryParts;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface QueryBuilderStateStart {
|
|
45
|
+
state: 'start';
|
|
46
|
+
typeId: number;
|
|
47
|
+
typeName: string;
|
|
48
|
+
}
|
|
49
|
+
interface QueryBuilderStatePredecessorType {
|
|
50
|
+
state: 'predecessor-type';
|
|
51
|
+
typeId: number;
|
|
52
|
+
typeName: string;
|
|
53
|
+
}
|
|
54
|
+
interface QueryBuilderStateSuccessorType {
|
|
55
|
+
state: 'successor-type';
|
|
56
|
+
typeId: number;
|
|
57
|
+
typeName: string;
|
|
58
|
+
}
|
|
59
|
+
interface QueryBuilderStatePredecessorJoin {
|
|
60
|
+
state: 'predecessor-join';
|
|
61
|
+
position: string;
|
|
62
|
+
}
|
|
63
|
+
interface QueryBuilderStateSuccessorJoin {
|
|
64
|
+
state: 'successor-join';
|
|
65
|
+
role: string;
|
|
66
|
+
}
|
|
67
|
+
interface QueryBuilderStateEmpty {
|
|
68
|
+
state: 'empty';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type QueryBuilderState =
|
|
72
|
+
QueryBuilderStateStart |
|
|
73
|
+
QueryBuilderStatePredecessorType |
|
|
74
|
+
QueryBuilderStateSuccessorType |
|
|
75
|
+
QueryBuilderStatePredecessorJoin |
|
|
76
|
+
QueryBuilderStateSuccessorJoin |
|
|
77
|
+
QueryBuilderStateEmpty;
|
|
78
|
+
|
|
79
|
+
class QueryBuilder {
|
|
80
|
+
private nextEdge: number = 1;
|
|
81
|
+
private nextFact: number = 2;
|
|
82
|
+
private shouldEmitFacts: boolean = true;
|
|
83
|
+
private queryParts: QueryParts = {
|
|
84
|
+
joins: [],
|
|
85
|
+
existentialClauses: []
|
|
86
|
+
};
|
|
87
|
+
private roleIds: number[] = [];
|
|
88
|
+
private factTypeNames: string[] = [];
|
|
89
|
+
|
|
90
|
+
constructor(private factTypes: FactTypeMap, private roleMap: RoleMap) {
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
buildQuery(start: FactReference, steps: Step[]): SqlQuery {
|
|
94
|
+
if (steps.length === 0) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const startTypeId = getFactTypeId(this.factTypes, start.type);
|
|
99
|
+
const startState: QueryBuilderState = {
|
|
100
|
+
state: 'start',
|
|
101
|
+
typeId: startTypeId,
|
|
102
|
+
typeName: start.type
|
|
103
|
+
};
|
|
104
|
+
const finalState = steps.reduce((state, step) => {
|
|
105
|
+
return this.matchStep(state, step);
|
|
106
|
+
}, startState);
|
|
107
|
+
this.end(finalState);
|
|
108
|
+
|
|
109
|
+
if (finalState.state === 'empty') {
|
|
110
|
+
return {
|
|
111
|
+
empty: true,
|
|
112
|
+
sql: '',
|
|
113
|
+
parameters: [],
|
|
114
|
+
pathLength: 0,
|
|
115
|
+
factTypeNames: []
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const factAliases = this.queryParts.joins
|
|
120
|
+
.filter(j => j.table === 'fact')
|
|
121
|
+
.map(j => (j as QueryJoinFact).factAlias);
|
|
122
|
+
const hashes = factAliases.map(a => `f${a}.hash as hash${a}`).join(', ');
|
|
123
|
+
const joins = this.buildJoins(this.queryParts.joins, 'f1.fact_id');
|
|
124
|
+
const whereClause = this.buildWhereClause(this.queryParts.existentialClauses);
|
|
125
|
+
const sql = `SELECT ${hashes} FROM public.fact f1${joins} WHERE f1.fact_type_id = $1 AND f1.hash = $2${whereClause}`;
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
empty: false,
|
|
129
|
+
sql,
|
|
130
|
+
parameters: [startTypeId, start.hash, ...this.roleIds],
|
|
131
|
+
pathLength: factAliases.length,
|
|
132
|
+
factTypeNames: this.factTypeNames
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private buildJoins(joins: QueryJoin[], priorFactId: string) {
|
|
137
|
+
const clauses = joins.reduce((joins, join) => {
|
|
138
|
+
if (join.table === 'edge') {
|
|
139
|
+
if (join.direction === 'successor') {
|
|
140
|
+
const clause = ` JOIN public.edge e${join.edgeAlias} ` +
|
|
141
|
+
`ON e${join.edgeAlias}.predecessor_fact_id = ${joins.priorFactId} ` +
|
|
142
|
+
`AND e${join.edgeAlias}.role_id = $${join.roleParameter}`;
|
|
143
|
+
return {
|
|
144
|
+
priorFactId: `e${join.edgeAlias}.successor_fact_id`,
|
|
145
|
+
clauses: [...joins.clauses, clause]
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const clause = ` JOIN public.edge e${join.edgeAlias} ` +
|
|
150
|
+
`ON e${join.edgeAlias}.successor_fact_id = ${joins.priorFactId} ` +
|
|
151
|
+
`AND e${join.edgeAlias}.role_id = $${join.roleParameter}`;
|
|
152
|
+
return {
|
|
153
|
+
priorFactId: `e${join.edgeAlias}.predecessor_fact_id`,
|
|
154
|
+
clauses: [...joins.clauses, clause]
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
const clause = ` JOIN public.fact f${join.factAlias} ON f${join.factAlias}.fact_id = ${joins.priorFactId}`;
|
|
159
|
+
return {
|
|
160
|
+
priorFactId: joins.priorFactId,
|
|
161
|
+
clauses: [...joins.clauses, clause]
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}, {
|
|
165
|
+
priorFactId,
|
|
166
|
+
clauses: []
|
|
167
|
+
}).clauses;
|
|
168
|
+
return clauses.join('');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
buildWhereClause(existentialClauses: ExistentialClause[]): string {
|
|
172
|
+
if (existentialClauses.length === 0) {
|
|
173
|
+
return '';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const clauses = existentialClauses.map(clause => {
|
|
177
|
+
const quantifierSql = clause.quantifier === Quantifier.Exists ? 'EXISTS' : 'NOT EXISTS';
|
|
178
|
+
const [first, ...rest] : QueryJoinEdge[] = clause.query.joins as QueryJoinEdge[];
|
|
179
|
+
const firstHeadFactId = first.direction === 'predecessor'
|
|
180
|
+
? `e${first.edgeAlias}.predecessor_fact_id`
|
|
181
|
+
: `e${first.edgeAlias}.successor_fact_id`;
|
|
182
|
+
const firstTailFactId = first.direction === 'predecessor'
|
|
183
|
+
? `e${first.edgeAlias}.successor_fact_id`
|
|
184
|
+
: `e${first.edgeAlias}.predecessor_fact_id`;
|
|
185
|
+
const joins = this.buildJoins(rest, firstHeadFactId);
|
|
186
|
+
const priorFactId = clause.priorEdgeJoin.direction === 'predecessor'
|
|
187
|
+
? `e${clause.priorEdgeJoin.edgeAlias}.predecessor_fact_id`
|
|
188
|
+
: `e${clause.priorEdgeJoin.edgeAlias}.successor_fact_id`;
|
|
189
|
+
const whereClause = this.buildWhereClause(clause.query.existentialClauses);
|
|
190
|
+
return ` AND ${quantifierSql} (SELECT 1 FROM public.edge e${first.edgeAlias}${joins} ` +
|
|
191
|
+
`WHERE ${firstTailFactId} = ${priorFactId} ` +
|
|
192
|
+
`AND e${first.edgeAlias}.role_id = $${first.roleParameter}${whereClause})`;
|
|
193
|
+
});
|
|
194
|
+
return clauses.join('');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
buildNestedSql(typeId: number, typeName: string, steps: Step[]): QueryParts | null {
|
|
198
|
+
// Push a new query to the stack.
|
|
199
|
+
const parentQuery = this.queryParts;
|
|
200
|
+
const parentShouldEmitFact = this.shouldEmitFacts;
|
|
201
|
+
this.shouldEmitFacts = false;
|
|
202
|
+
this.queryParts = {
|
|
203
|
+
joins: [],
|
|
204
|
+
existentialClauses: []
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const startState: QueryBuilderState = {
|
|
208
|
+
state: 'predecessor-type',
|
|
209
|
+
typeId: typeId,
|
|
210
|
+
typeName: typeName
|
|
211
|
+
};
|
|
212
|
+
const finalState = steps.reduce((state, step) => {
|
|
213
|
+
return this.matchStep(state, step);
|
|
214
|
+
}, startState);
|
|
215
|
+
this.end(finalState);
|
|
216
|
+
|
|
217
|
+
// Pop the stack and return the nested query.
|
|
218
|
+
const nestedQuery = this.queryParts;
|
|
219
|
+
this.shouldEmitFacts = parentShouldEmitFact;
|
|
220
|
+
this.queryParts = parentQuery;
|
|
221
|
+
|
|
222
|
+
// If the nested query cannot match any results, return null.
|
|
223
|
+
if (finalState.state === 'empty') {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
return nestedQuery;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private matchStep(state: QueryBuilderState, step: Step): QueryBuilderState {
|
|
230
|
+
switch (state.state) {
|
|
231
|
+
case 'start':
|
|
232
|
+
return this.matchStepStart(state, step);
|
|
233
|
+
case 'predecessor-type':
|
|
234
|
+
return this.matchStepPredecessorType(state, step);
|
|
235
|
+
case 'predecessor-join':
|
|
236
|
+
return this.matchStepPredecessorJoin(state, step);
|
|
237
|
+
case 'successor-join':
|
|
238
|
+
return this.matchStepSuccessorJoin(state, step);
|
|
239
|
+
case 'successor-type':
|
|
240
|
+
return this.matchStepSuccessorType(state, step);
|
|
241
|
+
case 'empty':
|
|
242
|
+
return state;
|
|
243
|
+
default:
|
|
244
|
+
throw new Error(`Unknown state: ${(state as any).state}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private matchStepStart(state: QueryBuilderStateStart, step: Step): QueryBuilderState {
|
|
249
|
+
if (step instanceof PropertyCondition) {
|
|
250
|
+
if (step.name !== 'type') {
|
|
251
|
+
throw new Error(`Property condition on non-type property ${step.name}`);
|
|
252
|
+
}
|
|
253
|
+
const typeId = getFactTypeId(this.factTypes, step.value);
|
|
254
|
+
if (typeId !== state.typeId) {
|
|
255
|
+
throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
|
|
256
|
+
}
|
|
257
|
+
return state;
|
|
258
|
+
}
|
|
259
|
+
else if (step instanceof Join) {
|
|
260
|
+
if (step.direction === Direction.Predecessor) {
|
|
261
|
+
const roleId = getRoleId(this.roleMap, state.typeId, step.role);
|
|
262
|
+
if (!roleId) {
|
|
263
|
+
return {
|
|
264
|
+
state: 'empty'
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
this.emitEdge('predecessor', roleId);
|
|
268
|
+
return {
|
|
269
|
+
state: 'predecessor-join',
|
|
270
|
+
position: `${state.typeName}.${step.role}`
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (step.direction === Direction.Successor) {
|
|
274
|
+
return {
|
|
275
|
+
state: 'successor-join',
|
|
276
|
+
role: step.role
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else if (step instanceof ExistentialCondition) {
|
|
281
|
+
return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
throw new Error(`Unknown step ${step.constructor.name}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private matchStepPredecessorType(state: QueryBuilderStatePredecessorType, step: Step): QueryBuilderState {
|
|
289
|
+
if (step instanceof PropertyCondition) {
|
|
290
|
+
if (step.name !== 'type') {
|
|
291
|
+
throw new Error(`Property condition on non-type property ${step.name}`);
|
|
292
|
+
}
|
|
293
|
+
const typeId = getFactTypeId(this.factTypes, step.value);
|
|
294
|
+
if (typeId !== state.typeId) {
|
|
295
|
+
throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
|
|
296
|
+
}
|
|
297
|
+
return state;
|
|
298
|
+
}
|
|
299
|
+
else if (step instanceof Join) {
|
|
300
|
+
if (step.direction === Direction.Predecessor) {
|
|
301
|
+
const roleId = getRoleId(this.roleMap, state.typeId, step.role);
|
|
302
|
+
if (!roleId) {
|
|
303
|
+
return {
|
|
304
|
+
state: 'empty'
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
this.emitFact(state.typeName);
|
|
308
|
+
this.emitEdge('predecessor', roleId);
|
|
309
|
+
return {
|
|
310
|
+
state: 'predecessor-join',
|
|
311
|
+
position: `${state.typeName}.${step.role}`
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (step.direction === Direction.Successor) {
|
|
315
|
+
this.emitFact(state.typeName);
|
|
316
|
+
return {
|
|
317
|
+
state: 'successor-join',
|
|
318
|
+
role: step.role
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else if (step instanceof ExistentialCondition) {
|
|
323
|
+
return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
throw new Error(`Unknown step ${step.constructor.name}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private matchStepPredecessorJoin(state: QueryBuilderStatePredecessorJoin, step: Step): QueryBuilderState {
|
|
331
|
+
if (step instanceof PropertyCondition) {
|
|
332
|
+
if (step.name !== 'type') {
|
|
333
|
+
throw new Error(`Property condition on non-type property ${step.name}`);
|
|
334
|
+
}
|
|
335
|
+
const typeId = getFactTypeId(this.factTypes, step.value);
|
|
336
|
+
if (!typeId) {
|
|
337
|
+
return {
|
|
338
|
+
state: 'empty'
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
state: 'predecessor-type',
|
|
343
|
+
typeId,
|
|
344
|
+
typeName: step.value
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
throw new Error(`Missing type of "${state.position}"`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private matchStepSuccessorJoin(state: QueryBuilderStateSuccessorJoin, step: Step): QueryBuilderState {
|
|
353
|
+
if (step instanceof PropertyCondition) {
|
|
354
|
+
if (step.name !== 'type') {
|
|
355
|
+
throw new Error(`Property condition on non-type property ${step.name}`);
|
|
356
|
+
}
|
|
357
|
+
const typeId = getFactTypeId(this.factTypes, step.value);
|
|
358
|
+
if (!typeId) {
|
|
359
|
+
return {
|
|
360
|
+
state: 'empty'
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const roleId = getRoleId(this.roleMap, typeId, state.role);
|
|
364
|
+
if (!roleId) {
|
|
365
|
+
return {
|
|
366
|
+
state: 'empty'
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
this.emitEdge('successor', roleId);
|
|
370
|
+
return {
|
|
371
|
+
state: 'successor-type',
|
|
372
|
+
typeId: typeId,
|
|
373
|
+
typeName: step.value
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
throw new Error(`Missing type for role ${state.role}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private matchStepSuccessorType(state: QueryBuilderStateSuccessorType, step: Step): QueryBuilderState {
|
|
382
|
+
if (step instanceof PropertyCondition) {
|
|
383
|
+
if (step.name !== 'type') {
|
|
384
|
+
throw new Error(`Property condition on non-type property ${step.name}`);
|
|
385
|
+
}
|
|
386
|
+
const typeId = getFactTypeId(this.factTypes, step.value);
|
|
387
|
+
if (typeId !== state.typeId) {
|
|
388
|
+
throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
|
|
389
|
+
}
|
|
390
|
+
return state;
|
|
391
|
+
}
|
|
392
|
+
else if (step instanceof Join) {
|
|
393
|
+
if (step.direction === Direction.Predecessor) {
|
|
394
|
+
const roleId = getRoleId(this.roleMap, state.typeId, step.role);
|
|
395
|
+
if (!roleId) {
|
|
396
|
+
return {
|
|
397
|
+
state: 'empty'
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
this.emitFact(state.typeName);
|
|
401
|
+
this.emitEdge('predecessor', roleId);
|
|
402
|
+
return {
|
|
403
|
+
state: 'predecessor-join',
|
|
404
|
+
position: `${state.typeName}.${step.role}`
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
this.emitFact(state.typeName);
|
|
409
|
+
return {
|
|
410
|
+
state: 'successor-join',
|
|
411
|
+
role: step.role
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
else if (step instanceof ExistentialCondition) {
|
|
416
|
+
return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
throw new Error(`Unknown step ${step.constructor.name}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private end(finalState: QueryBuilderState) {
|
|
424
|
+
if (finalState.state === 'successor-join') {
|
|
425
|
+
throw new Error(`Missing type for role ${finalState.role}`);
|
|
426
|
+
}
|
|
427
|
+
else if (finalState.state === 'predecessor-join') {
|
|
428
|
+
throw new Error(`Missing type of "${finalState.position}"`);
|
|
429
|
+
}
|
|
430
|
+
else if (finalState.state === 'predecessor-type' || finalState.state === 'successor-type') {
|
|
431
|
+
this.emitFact(finalState.typeName);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private emitEdge(direction: 'predecessor' | 'successor', roleId: number) {
|
|
436
|
+
const edgeAlias = this.nextEdge++;
|
|
437
|
+
this.roleIds.push(roleId);
|
|
438
|
+
this.queryParts.joins.push({
|
|
439
|
+
table: 'edge',
|
|
440
|
+
direction: direction,
|
|
441
|
+
edgeAlias: edgeAlias,
|
|
442
|
+
roleParameter: edgeAlias + 2
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private emitFact(factTypeName: string) {
|
|
447
|
+
if (this.shouldEmitFacts) {
|
|
448
|
+
const factAlias = this.nextFact++;
|
|
449
|
+
this.queryParts.joins.push({
|
|
450
|
+
table: 'fact',
|
|
451
|
+
factAlias: factAlias
|
|
452
|
+
});
|
|
453
|
+
this.factTypeNames.push(factTypeName);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private pushExistentialCondition(typeId: number, typeName: string, state: QueryBuilderState, step: ExistentialCondition): QueryBuilderState {
|
|
458
|
+
const nested = this.buildNestedSql(typeId, typeName, step.steps);
|
|
459
|
+
if (!nested) {
|
|
460
|
+
if (step.quantifier === Quantifier.Exists) {
|
|
461
|
+
// An empty positive existential condition cannot match any facts.
|
|
462
|
+
return {
|
|
463
|
+
state: 'empty'
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
// An empty negative existential condition cannot exclude any facts.
|
|
468
|
+
return state;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const lastJoin = this.queryParts.joins[this.queryParts.joins.length - 1];
|
|
472
|
+
if (lastJoin.table !== 'edge') {
|
|
473
|
+
throw new Error(`Existential condition on non-edge table ${lastJoin.table}`);
|
|
474
|
+
}
|
|
475
|
+
this.queryParts.existentialClauses.push({
|
|
476
|
+
quantifier: step.quantifier,
|
|
477
|
+
priorEdgeJoin: lastJoin,
|
|
478
|
+
query: nested
|
|
479
|
+
});
|
|
480
|
+
return state;
|
|
481
|
+
}
|
|
482
|
+
}
|
package/src/util/fn.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
function safeFlatten<U>(results: U[][]) {
|
|
2
|
+
const flat = results.reduce((a, b) =>
|
|
3
|
+
(!a || a.length === 0) ? b :
|
|
4
|
+
(!b || b.length === 0) ? a :
|
|
5
|
+
a.concat(b));
|
|
6
|
+
return flat ? flat : [];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function flattenAsync<T, U>(collection: T[], selector: (element: T) => Promise<U[]>) {
|
|
10
|
+
if (collection.length === 0) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
const results = await Promise.all(collection.map(selector));
|
|
15
|
+
return safeFlatten(results);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function flatten<T, U>(collection: T[], selector: (element: T) => U[]) {
|
|
20
|
+
if (collection.length === 0) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
return safeFlatten(collection.map(selector));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function mapAsync<T, U>(collection: T[], action: (element: T) => Promise<U>) {
|
|
29
|
+
if (collection.length === 0) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
return await Promise.all(collection.map(action));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function filterAsync<T>(collection: T[], predicate: (element: T) => Promise<boolean>) {
|
|
38
|
+
if (collection.length === 0) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const filters = await Promise.all(collection.map(async element => ({
|
|
43
|
+
include: await predicate(element),
|
|
44
|
+
element
|
|
45
|
+
})));
|
|
46
|
+
|
|
47
|
+
return filters.filter(f => f.include).map(f => f.element);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function findIndex<T>(array: T[], predicate: ((element: T) => boolean)): number {
|
|
52
|
+
for (let index = 0; index < array.length; index++) {
|
|
53
|
+
if (predicate(array[index])) {
|
|
54
|
+
return index;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return -1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function distinct<T>(value: T, index: number, self: T[]) {
|
|
62
|
+
return self.indexOf(value) === index;
|
|
63
|
+
}
|