jinaga-server 1.1.3 → 2.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/README.md +12 -0
- package/dist/authentication/authentication-device.d.ts +3 -2
- package/dist/authentication/authentication-device.js +6 -0
- package/dist/authentication/authentication-device.js.map +1 -1
- package/dist/authentication/authentication-impl.d.ts +3 -2
- package/dist/authentication/authentication-impl.js +6 -0
- package/dist/authentication/authentication-impl.js.map +1 -1
- package/dist/authentication/authentication-noop.d.ts +20 -0
- package/dist/authentication/authentication-noop.js +52 -0
- package/dist/authentication/authentication-noop.js.map +1 -0
- package/dist/authentication/authentication-session.d.ts +3 -2
- package/dist/authentication/authentication-session.js +6 -0
- package/dist/authentication/authentication-session.js.map +1 -1
- package/dist/authentication.d.ts +3 -2
- package/dist/authentication.js +6 -0
- package/dist/authentication.js.map +1 -1
- package/dist/authorization/authorization-keystore.d.ts +3 -4
- package/dist/authorization/authorization-keystore.js +9 -4
- package/dist/authorization/authorization-keystore.js.map +1 -1
- package/dist/debug.d.ts +1 -0
- package/dist/debug.js +30 -0
- package/dist/debug.js.map +1 -0
- package/dist/http/router.js.map +1 -1
- package/dist/jinaga-server.js +15 -7
- package/dist/jinaga-server.js.map +1 -1
- package/dist/memory/memory-keystore.js +0 -6
- package/dist/memory/memory-keystore.js.map +1 -1
- package/dist/postgres/connection.js +1 -0
- package/dist/postgres/connection.js.map +1 -1
- package/dist/postgres/maps.d.ts +7 -6
- package/dist/postgres/maps.js +9 -1
- package/dist/postgres/maps.js.map +1 -1
- package/dist/postgres/postgres-keystore.js +0 -6
- package/dist/postgres/postgres-keystore.js.map +1 -1
- package/dist/postgres/postgres-store.d.ts +1 -2
- package/dist/postgres/postgres-store.js +29 -24
- package/dist/postgres/postgres-store.js.map +1 -1
- package/dist/postgres/specification-result-sql.d.ts +4 -5
- package/dist/postgres/specification-result-sql.js +139 -112
- package/dist/postgres/specification-result-sql.js.map +1 -1
- package/dist/postgres/specification-sql.js.map +1 -1
- package/dist/postgres/sql.d.ts +1 -1
- package/dist/postgres/sql.js +15 -7
- package/dist/postgres/sql.js.map +1 -1
- package/dist/util/maps.d.ts +0 -0
- package/dist/util/maps.js +2 -0
- package/dist/util/maps.js.map +1 -0
- package/docs/sql/SQL Generator (projection).png +0 -0
- package/docs/sql/SQLFromSpecification.md +396 -0
- package/docs/sql/example.sql +49 -0
- package/docs/sql/first-path.gv +9 -0
- package/docs/sql/first-path.png +0 -0
- package/docs/sql/model.gv +20 -0
- package/docs/sql/model.png +0 -0
- package/integration-test/jinaga-test/Dockerfile +2 -2
- package/integration-test/jinaga-test/jinaga.test.js +3 -3
- package/integration-test/jinaga-test/package-lock.json +745 -3484
- package/integration-test/jinaga-test/package.json +2 -0
- package/integration-test/run.sh +3 -3
- package/package.json +5 -6
- package/replicator/index.ts +31 -0
- package/replicator/package-lock.json +855 -0
- package/replicator/package.json +27 -0
- package/replicator/tsconfig.json +15 -0
- package/src/authentication/authentication-device.ts +11 -1
- package/src/authentication/authentication-impl.ts +9 -2
- package/src/authentication/authentication-noop.ts +65 -0
- package/src/authentication/authentication-session.ts +11 -1
- package/src/authentication.ts +12 -2
- package/src/authorization/authorization-keystore.ts +12 -7
- package/src/debug.ts +30 -0
- package/src/http/router.ts +2 -2
- package/src/jinaga-server.ts +18 -8
- package/src/memory/memory-keystore.ts +0 -6
- package/src/postgres/connection.ts +2 -1
- package/src/postgres/maps.ts +8 -0
- package/src/postgres/postgres-keystore.ts +2 -8
- package/src/postgres/postgres-store.ts +37 -30
- package/src/postgres/specification-result-sql.ts +200 -145
- package/src/postgres/specification-sql.ts +1 -1
- package/src/postgres/sql.ts +23 -12
- package/src/util/maps.ts +0 -0
- package/test/postgres/postgresReadSpec.ts +292 -0
- package/test/postgres/postgresSpec.ts +31 -31
- package/test/postgres/postgresSpecificationSpec.ts +1 -1
- package/tsconfig.json +1 -0
- package/tsconfig.test.json +26 -0
- package/docs/specification.txt +0 -41
- package/interactive/index.js +0 -56
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
ElementProjection,
|
|
2
|
+
ComponentProjection,
|
|
4
3
|
FactReference,
|
|
4
|
+
FieldProjection,
|
|
5
5
|
Label,
|
|
6
6
|
Match,
|
|
7
7
|
PathCondition,
|
|
8
|
-
|
|
8
|
+
Projection,
|
|
9
9
|
SingularProjection,
|
|
10
10
|
Specification,
|
|
11
|
-
SpecificationProjection
|
|
11
|
+
SpecificationProjection
|
|
12
12
|
} from "jinaga";
|
|
13
13
|
|
|
14
|
-
import { FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
|
|
14
|
+
import { ensureGetFactTypeId, FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
|
|
15
15
|
|
|
16
16
|
interface SpecificationLabel {
|
|
17
17
|
name: string;
|
|
18
|
-
type: string;
|
|
19
18
|
index: number;
|
|
20
19
|
}
|
|
21
20
|
interface FactDescription {
|
|
@@ -28,9 +27,11 @@ interface EdgeDescription {
|
|
|
28
27
|
successorFactIndex: number;
|
|
29
28
|
roleParameter: number;
|
|
30
29
|
}
|
|
31
|
-
interface
|
|
30
|
+
interface ExistentialConditionDescription {
|
|
31
|
+
exists: boolean;
|
|
32
|
+
inputs: InputDescription[];
|
|
32
33
|
edges: EdgeDescription[];
|
|
33
|
-
|
|
34
|
+
existentialConditions: ExistentialConditionDescription[];
|
|
34
35
|
}
|
|
35
36
|
interface SpecificationSqlQuery {
|
|
36
37
|
sql: string;
|
|
@@ -41,8 +42,6 @@ interface SpecificationSqlQuery {
|
|
|
41
42
|
interface InputDescription {
|
|
42
43
|
label: string;
|
|
43
44
|
factIndex: number;
|
|
44
|
-
factTypeId: number;
|
|
45
|
-
factHash: string;
|
|
46
45
|
factTypeParameter: number;
|
|
47
46
|
factHashParameter: number;
|
|
48
47
|
}
|
|
@@ -52,8 +51,8 @@ interface OutputDescription {
|
|
|
52
51
|
factIndex: number;
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
function countEdges(
|
|
56
|
-
return
|
|
54
|
+
function countEdges(existentialConditions: ExistentialConditionDescription[]): number {
|
|
55
|
+
return existentialConditions.reduce((count, c) => count + c.edges.length + countEdges(c.existentialConditions),
|
|
57
56
|
0);
|
|
58
57
|
}
|
|
59
58
|
|
|
@@ -69,7 +68,7 @@ class QueryDescription {
|
|
|
69
68
|
private readonly outputs: OutputDescription[],
|
|
70
69
|
private readonly facts: FactDescription[],
|
|
71
70
|
private readonly edges: EdgeDescription[],
|
|
72
|
-
private readonly
|
|
71
|
+
private readonly existentialConditions: ExistentialConditionDescription[] = []
|
|
73
72
|
) {}
|
|
74
73
|
|
|
75
74
|
public withParameter(parameter: string | number): { query: QueryDescription; parameterIndex: number; } {
|
|
@@ -80,27 +79,57 @@ class QueryDescription {
|
|
|
80
79
|
this.outputs,
|
|
81
80
|
this.facts,
|
|
82
81
|
this.edges,
|
|
83
|
-
this.
|
|
82
|
+
this.existentialConditions
|
|
84
83
|
);
|
|
85
84
|
return { query, parameterIndex };
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
public withInputParameter(label: string): QueryDescription {
|
|
87
|
+
public withInputParameter(label: Label, factTypeId: number, factHash: string, path: number[]): { queryDescription: QueryDescription, factDescription: FactDescription } {
|
|
89
88
|
const factTypeParameter = this.parameters.length + 1;
|
|
90
89
|
const factHashParameter = factTypeParameter + 1;
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
90
|
+
const factIndex = this.facts.length + 1;
|
|
91
|
+
const factDescription: FactDescription = {
|
|
92
|
+
factIndex: factIndex,
|
|
93
|
+
type: label.type
|
|
94
|
+
};
|
|
95
|
+
const facts = [
|
|
96
|
+
...this.facts,
|
|
97
|
+
factDescription
|
|
98
|
+
]
|
|
99
|
+
const input: InputDescription = {
|
|
100
|
+
label: label.name,
|
|
101
|
+
factIndex,
|
|
102
|
+
factTypeParameter,
|
|
103
|
+
factHashParameter
|
|
104
|
+
};
|
|
105
|
+
const parameters = this.parameters.concat(factTypeId, factHash);
|
|
106
|
+
if (path.length === 0) {
|
|
107
|
+
const inputs = [
|
|
108
|
+
...this.inputs,
|
|
109
|
+
input
|
|
110
|
+
];
|
|
111
|
+
const queryDescription = new QueryDescription(
|
|
112
|
+
inputs,
|
|
113
|
+
parameters,
|
|
114
|
+
this.outputs,
|
|
115
|
+
facts,
|
|
116
|
+
this.edges,
|
|
117
|
+
this.existentialConditions
|
|
118
|
+
);
|
|
119
|
+
return { queryDescription, factDescription };
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
const existentialConditions = existentialsWithInput(this.existentialConditions, input, path);
|
|
123
|
+
const queryDescription = new QueryDescription(
|
|
124
|
+
this.inputs,
|
|
125
|
+
parameters,
|
|
126
|
+
this.outputs,
|
|
127
|
+
facts,
|
|
128
|
+
this.edges,
|
|
129
|
+
existentialConditions
|
|
130
|
+
);
|
|
131
|
+
return { queryDescription, factDescription };
|
|
132
|
+
}
|
|
104
133
|
}
|
|
105
134
|
|
|
106
135
|
public withFact(type: string): { query: QueryDescription; factIndex: number; } {
|
|
@@ -112,7 +141,7 @@ class QueryDescription {
|
|
|
112
141
|
this.outputs,
|
|
113
142
|
this.facts.concat(fact),
|
|
114
143
|
this.edges,
|
|
115
|
-
this.
|
|
144
|
+
this.existentialConditions
|
|
116
145
|
);
|
|
117
146
|
return { query, factIndex };
|
|
118
147
|
}
|
|
@@ -125,14 +154,14 @@ class QueryDescription {
|
|
|
125
154
|
this.outputs.concat(output),
|
|
126
155
|
this.facts,
|
|
127
156
|
this.edges,
|
|
128
|
-
this.
|
|
157
|
+
this.existentialConditions
|
|
129
158
|
);
|
|
130
159
|
return query;
|
|
131
160
|
}
|
|
132
161
|
|
|
133
162
|
public withEdge(predecessorFactIndex: number, successorFactIndex: number, roleParameter: number, path: number[]) {
|
|
134
163
|
const edge = {
|
|
135
|
-
edgeIndex: this.edges.length + countEdges(this.
|
|
164
|
+
edgeIndex: this.edges.length + countEdges(this.existentialConditions) + 1,
|
|
136
165
|
predecessorFactIndex,
|
|
137
166
|
successorFactIndex,
|
|
138
167
|
roleParameter
|
|
@@ -144,7 +173,7 @@ class QueryDescription {
|
|
|
144
173
|
this.outputs,
|
|
145
174
|
this.facts,
|
|
146
175
|
this.edges.concat(edge),
|
|
147
|
-
this.
|
|
176
|
+
this.existentialConditions
|
|
148
177
|
)
|
|
149
178
|
: new QueryDescription(
|
|
150
179
|
this.inputs,
|
|
@@ -152,20 +181,20 @@ class QueryDescription {
|
|
|
152
181
|
this.outputs,
|
|
153
182
|
this.facts,
|
|
154
183
|
this.edges,
|
|
155
|
-
|
|
184
|
+
existentialsWithEdge(this.existentialConditions, edge, path)
|
|
156
185
|
);
|
|
157
186
|
return query;
|
|
158
187
|
}
|
|
159
188
|
|
|
160
|
-
public
|
|
161
|
-
const {
|
|
189
|
+
public withExistentialCondition(exists: boolean, path: number[]): { query: QueryDescription; path: number[]; } {
|
|
190
|
+
const { existentialConditions: newExistentialConditions, path: newPath } = existentialsWithNewCondition(this.existentialConditions, exists, path);
|
|
162
191
|
const query = new QueryDescription(
|
|
163
192
|
this.inputs,
|
|
164
193
|
this.parameters,
|
|
165
194
|
this.outputs,
|
|
166
195
|
this.facts,
|
|
167
196
|
this.edges,
|
|
168
|
-
|
|
197
|
+
newExistentialConditions
|
|
169
198
|
);
|
|
170
199
|
return { query, path: newPath };
|
|
171
200
|
}
|
|
@@ -193,20 +222,19 @@ class QueryDescription {
|
|
|
193
222
|
const firstEdge = this.edges[0];
|
|
194
223
|
const predecessorFact = this.inputs.find(i => i.factIndex === firstEdge.predecessorFactIndex);
|
|
195
224
|
const successorFact = this.inputs.find(i => i.factIndex === firstEdge.successorFactIndex);
|
|
196
|
-
const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact
|
|
225
|
+
const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact!.factIndex;
|
|
197
226
|
const writtenFactIndexes = new Set<number>().add(firstFactIndex);
|
|
198
227
|
const joins: string[] = generateJoins(this.edges, writtenFactIndexes);
|
|
199
228
|
const inputWhereClauses = this.inputs
|
|
200
|
-
.filter(input => input.factTypeParameter !== 0)
|
|
201
229
|
.map(input => `f${input.factIndex}.fact_type_id = $${input.factTypeParameter} AND f${input.factIndex}.hash = $${input.factHashParameter}`)
|
|
202
230
|
.join(" AND ");
|
|
203
|
-
const
|
|
204
|
-
.map(
|
|
231
|
+
const existentialWhereClauses = this.existentialConditions
|
|
232
|
+
.map(existentialCondition => ` AND ${existentialCondition.exists ? "EXISTS" : "NOT EXISTS"} (${generateExistentialWhereClause(existentialCondition, writtenFactIndexes)})`)
|
|
205
233
|
.join("");
|
|
206
234
|
const orderByClause = this.outputs
|
|
207
235
|
.map(output => `f${output.factIndex}.fact_id ASC`)
|
|
208
236
|
.join(", ");
|
|
209
|
-
const sql = `SELECT ${columns} FROM public.fact f${firstFactIndex}${joins.join("")} WHERE ${inputWhereClauses}${
|
|
237
|
+
const sql = `SELECT ${columns} FROM public.fact f${firstFactIndex}${joins.join("")} WHERE ${inputWhereClauses}${existentialWhereClauses} ORDER BY ${orderByClause}`;
|
|
210
238
|
return {
|
|
211
239
|
sql,
|
|
212
240
|
parameters: this.parameters,
|
|
@@ -220,50 +248,75 @@ class QueryDescription {
|
|
|
220
248
|
}
|
|
221
249
|
}
|
|
222
250
|
|
|
223
|
-
function
|
|
251
|
+
function existentialsWithInput(existentialConditions: ExistentialConditionDescription[], input: InputDescription, path: number[]): ExistentialConditionDescription[] {
|
|
224
252
|
if (path.length === 1) {
|
|
225
|
-
return
|
|
253
|
+
return existentialConditions.map((c, i) => i === path[0] ?
|
|
226
254
|
{
|
|
227
|
-
|
|
228
|
-
|
|
255
|
+
...c,
|
|
256
|
+
inputs: [...c.inputs, input]
|
|
229
257
|
} :
|
|
230
258
|
c
|
|
231
259
|
);
|
|
232
260
|
}
|
|
233
261
|
else {
|
|
234
|
-
return
|
|
262
|
+
return existentialConditions.map((c, i) => i === path[0] ?
|
|
235
263
|
{
|
|
236
|
-
|
|
237
|
-
|
|
264
|
+
...c,
|
|
265
|
+
existentialConditions: existentialsWithInput(c.existentialConditions, input, path.slice(1))
|
|
238
266
|
} :
|
|
239
267
|
c
|
|
240
268
|
);
|
|
241
269
|
}
|
|
242
270
|
}
|
|
243
271
|
|
|
244
|
-
function
|
|
272
|
+
function existentialsWithEdge(existentialConditions: ExistentialConditionDescription[], edge: EdgeDescription, path: number[]): ExistentialConditionDescription[] {
|
|
273
|
+
if (path.length === 1) {
|
|
274
|
+
return existentialConditions.map((c, i) => i === path[0] ?
|
|
275
|
+
{
|
|
276
|
+
...c,
|
|
277
|
+
edges: [...c.edges, edge]
|
|
278
|
+
} :
|
|
279
|
+
c
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
return existentialConditions.map((c, i) => i === path[0] ?
|
|
284
|
+
{
|
|
285
|
+
...c,
|
|
286
|
+
existentialConditions: existentialsWithEdge(c.existentialConditions, edge, path.slice(1))
|
|
287
|
+
} :
|
|
288
|
+
c
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function existentialsWithNewCondition(existentialConditions: ExistentialConditionDescription[], exists: boolean, path: number[]): { existentialConditions: ExistentialConditionDescription[]; path: number[]; } {
|
|
245
294
|
if (path.length === 0) {
|
|
246
|
-
path = [
|
|
247
|
-
|
|
248
|
-
...
|
|
295
|
+
path = [existentialConditions.length];
|
|
296
|
+
existentialConditions = [
|
|
297
|
+
...existentialConditions,
|
|
249
298
|
{
|
|
299
|
+
exists: exists,
|
|
300
|
+
inputs: [],
|
|
250
301
|
edges: [],
|
|
251
|
-
|
|
302
|
+
existentialConditions: []
|
|
252
303
|
}
|
|
253
304
|
];
|
|
254
|
-
return {
|
|
305
|
+
return { existentialConditions: existentialConditions, path };
|
|
255
306
|
}
|
|
256
307
|
else {
|
|
257
|
-
const {
|
|
258
|
-
|
|
308
|
+
const { existentialConditions: newExistentialConditions, path: newPath } = existentialsWithNewCondition(existentialConditions[path[0]].existentialConditions, exists, path.slice(1));
|
|
309
|
+
existentialConditions = existentialConditions.map((c, i) => i === path[0] ?
|
|
259
310
|
{
|
|
311
|
+
exists: c.exists,
|
|
312
|
+
inputs: c.inputs,
|
|
260
313
|
edges: c.edges,
|
|
261
|
-
|
|
314
|
+
existentialConditions: newExistentialConditions
|
|
262
315
|
} :
|
|
263
316
|
c
|
|
264
317
|
);
|
|
265
318
|
path = [path[0], ...newPath];
|
|
266
|
-
return {
|
|
319
|
+
return { existentialConditions: existentialConditions, path };
|
|
267
320
|
}
|
|
268
321
|
}
|
|
269
322
|
|
|
@@ -311,8 +364,8 @@ function generateJoins(edges: EdgeDescription[], writtenFactIndexes: Set<number>
|
|
|
311
364
|
return joins;
|
|
312
365
|
}
|
|
313
366
|
|
|
314
|
-
function
|
|
315
|
-
const firstEdge =
|
|
367
|
+
function generateExistentialWhereClause(existentialCondition: ExistentialConditionDescription, outerFactIndexes: Set<number>): string {
|
|
368
|
+
const firstEdge = existentialCondition.edges[0];
|
|
316
369
|
const writtenFactIndexes = new Set<number>(outerFactIndexes);
|
|
317
370
|
const firstJoin: string[] = [];
|
|
318
371
|
const whereClause: string[] = [];
|
|
@@ -346,18 +399,24 @@ function generateNotExistsWhereClause(notExistsWhereClause: NotExistsConditionDe
|
|
|
346
399
|
else {
|
|
347
400
|
throw new Error("Neither predecessor nor successor fact has been written");
|
|
348
401
|
}
|
|
349
|
-
const tailJoins: string[] = generateJoins(
|
|
402
|
+
const tailJoins: string[] = generateJoins(existentialCondition.edges.slice(1), writtenFactIndexes);
|
|
350
403
|
const joins = firstJoin.concat(tailJoins);
|
|
351
|
-
|
|
404
|
+
const inputWhereClauses = existentialCondition.inputs
|
|
405
|
+
.map(input => ` AND f${input.factIndex}.fact_type_id = $${input.factTypeParameter} AND f${input.factIndex}.hash = $${input.factHashParameter}`)
|
|
406
|
+
.join("");
|
|
407
|
+
const existentialWhereClauses = existentialCondition.existentialConditions
|
|
408
|
+
.map(e => ` AND ${e.exists ? "EXISTS" : "NOT EXISTS"} (${generateExistentialWhereClause(e, writtenFactIndexes)})`)
|
|
409
|
+
.join("");
|
|
410
|
+
return `SELECT 1 FROM public.edge e${firstEdge.edgeIndex}${joins.join("")} WHERE ${whereClause.join(" AND ")}${inputWhereClauses}${existentialWhereClauses}`;
|
|
352
411
|
}
|
|
353
412
|
|
|
354
|
-
type
|
|
355
|
-
[
|
|
413
|
+
type FactByLabel = {
|
|
414
|
+
[label: string]: FactDescription;
|
|
356
415
|
};
|
|
357
416
|
|
|
358
417
|
interface ResultDescription {
|
|
359
418
|
queryDescription: QueryDescription;
|
|
360
|
-
resultProjection:
|
|
419
|
+
resultProjection: Projection;
|
|
361
420
|
childResultDescriptions: NamedResultDescription[];
|
|
362
421
|
}
|
|
363
422
|
|
|
@@ -396,7 +455,7 @@ interface NamedResultSetTree extends ResultSetTree {
|
|
|
396
455
|
export class ResultComposer {
|
|
397
456
|
constructor(
|
|
398
457
|
private readonly sqlQuery: SpecificationSqlQuery,
|
|
399
|
-
private readonly resultProjection:
|
|
458
|
+
private readonly resultProjection: Projection,
|
|
400
459
|
private readonly parentFactIdLength: number,
|
|
401
460
|
private readonly childResultComposers: NamedResultComposer[]
|
|
402
461
|
) { }
|
|
@@ -496,36 +555,41 @@ export class ResultComposer {
|
|
|
496
555
|
}
|
|
497
556
|
|
|
498
557
|
private projectionOf(row: any): any {
|
|
499
|
-
if (
|
|
500
|
-
return this.
|
|
558
|
+
if (this.resultProjection.type === "field") {
|
|
559
|
+
return this.fieldValue(this.resultProjection, row);
|
|
501
560
|
}
|
|
502
|
-
else if (this.resultProjection.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
.
|
|
561
|
+
else if (this.resultProjection.type === "composite") {
|
|
562
|
+
if (
|
|
563
|
+
this.resultProjection.components.length === 0 &&
|
|
564
|
+
this.childResultComposers.length === 0) {
|
|
565
|
+
return this.sqlQuery.labels
|
|
566
|
+
.slice(this.parentFactIdLength)
|
|
567
|
+
.reduce((acc, label) => ({
|
|
568
|
+
...acc,
|
|
569
|
+
[label.name]: row[`data${label.index}`].fields
|
|
570
|
+
}), {})
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
return this.resultProjection.components.reduce((acc, component) => ({
|
|
506
574
|
...acc,
|
|
507
|
-
[
|
|
508
|
-
}), {})
|
|
509
|
-
|
|
510
|
-
else {
|
|
511
|
-
return this.resultProjection.reduce((acc, elementProjection) => ({
|
|
512
|
-
...acc,
|
|
513
|
-
[elementProjection.name]: this.elementValue(elementProjection, row)
|
|
514
|
-
}), {});
|
|
575
|
+
[component.name]: this.elementValue(component, row)
|
|
576
|
+
}), {});
|
|
577
|
+
}
|
|
515
578
|
}
|
|
516
579
|
}
|
|
517
580
|
|
|
518
|
-
private elementValue(projection:
|
|
519
|
-
const label = this.getLabel(projection.label);
|
|
581
|
+
private elementValue(projection: ComponentProjection, row: any): any {
|
|
520
582
|
if (projection.type === "field") {
|
|
583
|
+
const label = this.getLabel(projection.label);
|
|
521
584
|
return row[`data${label.index}`].fields[projection.field];
|
|
522
585
|
}
|
|
523
586
|
else if (projection.type === "hash") {
|
|
587
|
+
const label = this.getLabel(projection.label);
|
|
524
588
|
return row[`hash${label.index}`];
|
|
525
589
|
}
|
|
526
590
|
}
|
|
527
591
|
|
|
528
|
-
private
|
|
592
|
+
private fieldValue(projection: FieldProjection, row: any): any {
|
|
529
593
|
const label = this.getLabel(projection.label);
|
|
530
594
|
return row[`data${label.index}`].fields[projection.field];
|
|
531
595
|
}
|
|
@@ -562,53 +626,35 @@ class ResultDescriptionBuilder {
|
|
|
562
626
|
}
|
|
563
627
|
}
|
|
564
628
|
|
|
565
|
-
// Allocate a fact table for each given.
|
|
566
|
-
// While the fact type and hash parameters are zero, the join will not be written.
|
|
567
|
-
const inputs: InputDescription[] = specification.given
|
|
568
|
-
.map((label, i) => ({
|
|
569
|
-
label: label.name,
|
|
570
|
-
factIndex: i+1,
|
|
571
|
-
factTypeId: getFactTypeId(this.factTypes, label.type),
|
|
572
|
-
factHash: start[i].hash,
|
|
573
|
-
factTypeParameter: 0,
|
|
574
|
-
factHashParameter: 0
|
|
575
|
-
}));
|
|
576
|
-
const facts: FactDescription[] = specification.given
|
|
577
|
-
.map((label, i) => ({
|
|
578
|
-
factIndex: i+1,
|
|
579
|
-
type: label.type
|
|
580
|
-
}));
|
|
581
|
-
const givenFacts = specification.given.reduce((knownFacts, label, i) => ({
|
|
582
|
-
...knownFacts,
|
|
583
|
-
[label.name]: facts[i]
|
|
584
|
-
}), {} as FactByIdentifier);
|
|
585
|
-
|
|
586
629
|
// The QueryDescription is an immutable data type.
|
|
587
630
|
// Initialize it with the inputs and facts.
|
|
588
631
|
// The DescriptionBuilder will branch at various points, and
|
|
589
632
|
// build on the current query description along each branch.
|
|
590
|
-
const initialQueryDescription = new QueryDescription(
|
|
591
|
-
return this.createResultDescription(initialQueryDescription, specification.matches, specification.
|
|
633
|
+
const initialQueryDescription = new QueryDescription([], [], [], [], [], []);
|
|
634
|
+
return this.createResultDescription(initialQueryDescription, specification.given, start, specification.matches, specification.projection, {}, []);
|
|
592
635
|
}
|
|
593
636
|
|
|
594
|
-
private createResultDescription(queryDescription: QueryDescription, matches: Match[],
|
|
595
|
-
({ queryDescription, knownFacts } = this.addEdges(queryDescription, knownFacts, path, matches));
|
|
637
|
+
private createResultDescription(queryDescription: QueryDescription, given: Label[], start: FactReference[], matches: Match[], projection: Projection, knownFacts: FactByLabel, path: number[]): ResultDescription {
|
|
638
|
+
({ queryDescription, knownFacts } = this.addEdges(queryDescription, given, start, knownFacts, path, matches));
|
|
596
639
|
if (!queryDescription.isSatisfiable()) {
|
|
597
640
|
// Abort the branch if the query is not satisfiable
|
|
598
641
|
return {
|
|
599
642
|
queryDescription,
|
|
600
|
-
resultProjection:
|
|
643
|
+
resultProjection: {
|
|
644
|
+
type: "composite",
|
|
645
|
+
components: []
|
|
646
|
+
},
|
|
601
647
|
childResultDescriptions: []
|
|
602
648
|
}
|
|
603
649
|
}
|
|
604
650
|
const childResultDescriptions: NamedResultDescription[] = [];
|
|
605
|
-
if (
|
|
606
|
-
const specificationProjections =
|
|
607
|
-
.filter(projection => projection.type === "specification") as SpecificationProjection[];
|
|
608
|
-
const
|
|
609
|
-
.filter(projection => projection.type === "field" || projection.type === "hash") as
|
|
651
|
+
if (projection.type === "composite") {
|
|
652
|
+
const specificationProjections = projection.components
|
|
653
|
+
.filter(projection => projection.type === "specification") as ({ name: string } & SpecificationProjection)[];
|
|
654
|
+
const singularProjections = projection.components
|
|
655
|
+
.filter(projection => projection.type === "field" || projection.type === "hash") as ({ name: string } & SingularProjection)[];
|
|
610
656
|
for (const child of specificationProjections) {
|
|
611
|
-
const childResultDescription = this.createResultDescription(queryDescription, child.matches, child.
|
|
657
|
+
const childResultDescription = this.createResultDescription(queryDescription, given, start, child.matches, child.projection, knownFacts, []);
|
|
612
658
|
childResultDescriptions.push({
|
|
613
659
|
name: child.name,
|
|
614
660
|
...childResultDescription
|
|
@@ -616,44 +662,40 @@ class ResultDescriptionBuilder {
|
|
|
616
662
|
}
|
|
617
663
|
return {
|
|
618
664
|
queryDescription,
|
|
619
|
-
resultProjection:
|
|
665
|
+
resultProjection: {
|
|
666
|
+
type: "composite",
|
|
667
|
+
components: singularProjections
|
|
668
|
+
},
|
|
620
669
|
childResultDescriptions
|
|
621
670
|
};
|
|
622
671
|
}
|
|
623
672
|
else {
|
|
624
673
|
return {
|
|
625
674
|
queryDescription,
|
|
626
|
-
resultProjection:
|
|
675
|
+
resultProjection: projection,
|
|
627
676
|
childResultDescriptions: []
|
|
628
677
|
}
|
|
629
678
|
}
|
|
630
679
|
}
|
|
631
680
|
|
|
632
|
-
private addEdges(queryDescription: QueryDescription, knownFacts:
|
|
681
|
+
private addEdges(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByLabel, path: number[], matches: Match[]): { queryDescription: QueryDescription, knownFacts: FactByLabel } {
|
|
633
682
|
for (const match of matches) {
|
|
634
683
|
for (const condition of match.conditions) {
|
|
635
684
|
if (condition.type === "path") {
|
|
636
|
-
({queryDescription, knownFacts} = this.addPathCondition(queryDescription, knownFacts, path, match.unknown, "", condition));
|
|
685
|
+
({queryDescription, knownFacts} = this.addPathCondition(queryDescription, given, start, knownFacts, path, match.unknown, "", condition));
|
|
637
686
|
}
|
|
638
687
|
else if (condition.type === "existential") {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
// If the negative existential condition is not satisfiable, then
|
|
652
|
-
// that means that the condition will always be true.
|
|
653
|
-
// We can therefore skip the branch for the negative existential condition.
|
|
654
|
-
if (queryDescriptionConditional.isSatisfiable()) {
|
|
655
|
-
queryDescription = queryDescriptionConditional;
|
|
656
|
-
}
|
|
688
|
+
// Apply the where clause and continue with the tuple where it is true.
|
|
689
|
+
// The path describes which not-exists condition we are currently building on.
|
|
690
|
+
// Because the path is not empty, labeled facts will be included in the output.
|
|
691
|
+
const { query: queryDescriptionWithExistential, path: conditionalPath } = queryDescription.withExistentialCondition(condition.exists, path);
|
|
692
|
+
const { queryDescription: queryDescriptionConditional } = this.addEdges(queryDescriptionWithExistential, given, start, knownFacts, conditionalPath, condition.matches);
|
|
693
|
+
|
|
694
|
+
// If the negative existential condition is not satisfiable, then
|
|
695
|
+
// that means that the condition will always be true.
|
|
696
|
+
// We can therefore skip the branch for the negative existential condition.
|
|
697
|
+
if (queryDescriptionConditional.isSatisfiable()) {
|
|
698
|
+
queryDescription = queryDescriptionConditional;
|
|
657
699
|
}
|
|
658
700
|
}
|
|
659
701
|
if (!queryDescription.isSatisfiable()) {
|
|
@@ -670,11 +712,24 @@ class ResultDescriptionBuilder {
|
|
|
670
712
|
};
|
|
671
713
|
}
|
|
672
714
|
|
|
673
|
-
private addPathCondition(queryDescription: QueryDescription, knownFacts:
|
|
715
|
+
private addPathCondition(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByLabel, path: number[], unknown: Label, prefix: string, condition: PathCondition): { queryDescription: QueryDescription, knownFacts: FactByLabel } {
|
|
674
716
|
// If no input parameter has been allocated, allocate one now.
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
717
|
+
if (!knownFacts.hasOwnProperty(condition.labelRight)) {
|
|
718
|
+
const givenIndex = given.findIndex(given => given.name === condition.labelRight);
|
|
719
|
+
if (givenIndex < 0) {
|
|
720
|
+
throw new Error(`No input parameter found for label ${condition.labelRight}`);
|
|
721
|
+
}
|
|
722
|
+
const { queryDescription: newQueryDescription, factDescription } = queryDescription.withInputParameter(
|
|
723
|
+
given[givenIndex],
|
|
724
|
+
ensureGetFactTypeId(this.factTypes, start[givenIndex].type),
|
|
725
|
+
start[givenIndex].hash,
|
|
726
|
+
path
|
|
727
|
+
);
|
|
728
|
+
queryDescription = newQueryDescription;
|
|
729
|
+
knownFacts = {
|
|
730
|
+
...knownFacts,
|
|
731
|
+
[condition.labelRight]: factDescription
|
|
732
|
+
};
|
|
678
733
|
}
|
|
679
734
|
|
|
680
735
|
// Determine whether we have already written the output.
|
|
@@ -709,11 +764,11 @@ class ResultDescriptionBuilder {
|
|
|
709
764
|
}
|
|
710
765
|
else {
|
|
711
766
|
// If we have not written the fact, we need to write it now.
|
|
712
|
-
const { query, factIndex: predecessorFactIndex } = queryWithParameter.withFact(role.
|
|
767
|
+
const { query, factIndex: predecessorFactIndex } = queryWithParameter.withFact(role.predecessorType);
|
|
713
768
|
queryDescription = query.withEdge(predecessorFactIndex, factIndex, roleParameter, path);
|
|
714
769
|
factIndex = predecessorFactIndex;
|
|
715
770
|
}
|
|
716
|
-
type = role.
|
|
771
|
+
type = role.predecessorType;
|
|
717
772
|
}
|
|
718
773
|
|
|
719
774
|
const rightType = type;
|
|
@@ -741,7 +796,7 @@ class ResultDescriptionBuilder {
|
|
|
741
796
|
roleId,
|
|
742
797
|
declaringType: type
|
|
743
798
|
});
|
|
744
|
-
type = role.
|
|
799
|
+
type = role.predecessorType;
|
|
745
800
|
}
|
|
746
801
|
|
|
747
802
|
if (type !== rightType) {
|
|
@@ -779,7 +834,7 @@ function idsEqual(a: number[], b: number[]) {
|
|
|
779
834
|
return a.every((value, index) => value === b[index]);
|
|
780
835
|
}
|
|
781
836
|
|
|
782
|
-
export function resultSqlFromSpecification(start: FactReference[], specification: Specification, factTypes: FactTypeMap, roleMap: RoleMap): ResultComposer {
|
|
837
|
+
export function resultSqlFromSpecification(start: FactReference[], specification: Specification, factTypes: FactTypeMap, roleMap: RoleMap): ResultComposer | null {
|
|
783
838
|
const descriptionBuilder = new ResultDescriptionBuilder(factTypes, roleMap);
|
|
784
839
|
const description = descriptionBuilder.buildDescription(start, specification);
|
|
785
840
|
|
|
@@ -63,7 +63,7 @@ class QueryDescription {
|
|
|
63
63
|
const firstEdge = this.edges[0];
|
|
64
64
|
const predecessorFact = this.inputs.find(i => i.factIndex === firstEdge.predecessorFactIndex);
|
|
65
65
|
const successorFact = this.inputs.find(i => i.factIndex === firstEdge.successorFactIndex);
|
|
66
|
-
const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact
|
|
66
|
+
const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact!.factIndex;
|
|
67
67
|
const writtenFactIndexes = new Set<number>().add(firstFactIndex);
|
|
68
68
|
const joins: string[] = generateJoins(this.edges, writtenFactIndexes);
|
|
69
69
|
const inputWhereClauses = this.inputs
|