jinaga-server 2.5.0 → 2.6.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/dist/authorization/authorization-keystore.d.ts +5 -4
- package/dist/authorization/authorization-keystore.js +16 -13
- package/dist/authorization/authorization-keystore.js.map +1 -1
- package/dist/http/router.d.ts +5 -3
- package/dist/http/router.js +125 -52
- package/dist/http/router.js.map +1 -1
- package/dist/http/stream.d.ts +10 -0
- package/dist/http/stream.js +35 -0
- package/dist/http/stream.js.map +1 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/jinaga-server.js +7 -8
- package/dist/jinaga-server.js.map +1 -1
- package/dist/postgres/postgres-store.d.ts +4 -4
- package/dist/postgres/postgres-store.js +2 -2
- package/dist/postgres/postgres-store.js.map +1 -1
- package/dist/postgres/query-description.d.ts +75 -0
- package/dist/postgres/query-description.js +283 -0
- package/dist/postgres/query-description.js.map +1 -0
- package/dist/postgres/specification-result-sql.d.ts +2 -2
- package/dist/postgres/specification-result-sql.js +39 -324
- package/dist/postgres/specification-result-sql.js.map +1 -1
- package/dist/postgres/specification-sql.d.ts +2 -2
- package/dist/postgres/specification-sql.js +60 -163
- package/dist/postgres/specification-sql.js.map +1 -1
- package/http/sans-auth/.env.local +1 -0
- package/http/sans-auth/Create post sans auth.http +27 -0
- package/http/sans-auth/Create site sans auth.http +11 -0
- package/http/sans-auth/Feed sites.http +1 -0
- package/http/sans-auth/Feeds sites.http +12 -0
- package/http/sans-auth/Get posts sans auth.http +35 -0
- package/http/sans-auth/Get sites sans auth.http +12 -0
- package/http/sans-auth/stream.sh +2 -0
- package/integration-test/jinaga-test/Dockerfile +1 -1
- package/integration-test/jinaga-test/package-lock.json +917 -748
- package/integration-test/jinaga-test/package.json +3 -3
- package/package.json +2 -2
- package/replicator/package-lock.json +16 -16
- package/replicator/package.json +1 -1
- package/src/authorization/authorization-keystore.ts +23 -14
- package/src/http/router.ts +154 -64
- package/src/http/stream.ts +32 -0
- package/src/index.ts +0 -2
- package/src/jinaga-server.ts +8 -8
- package/src/postgres/postgres-store.ts +5 -8
- package/src/postgres/query-description.ts +435 -0
- package/src/postgres/specification-result-sql.ts +56 -489
- package/src/postgres/specification-sql.ts +71 -232
- package/test/authorization/authorizationSpec.ts +12 -2
- package/test/postgres/postgresSpecificationSpec.ts +15 -15
- package/dist/http/feed-cache.d.ts +0 -12
- package/dist/http/feed-cache.js +0 -3
- package/dist/http/feed-cache.js.map +0 -1
- package/dist/memory/memory-feed-cache.d.ts +0 -6
- package/dist/memory/memory-feed-cache.js +0 -17
- package/dist/memory/memory-feed-cache.js.map +0 -1
- package/src/http/feed-cache.ts +0 -14
- package/src/memory/memory-feed-cache.ts +0 -14
|
@@ -8,327 +8,50 @@ import {
|
|
|
8
8
|
hydrateFromTree,
|
|
9
9
|
Label,
|
|
10
10
|
Match,
|
|
11
|
-
PathCondition,
|
|
12
11
|
PredecessorCollection,
|
|
13
12
|
ProjectedResult,
|
|
14
13
|
Projection,
|
|
15
14
|
ReferencesByName,
|
|
16
15
|
SingularProjection,
|
|
17
16
|
Specification,
|
|
18
|
-
SpecificationProjection
|
|
17
|
+
SpecificationProjection,
|
|
18
|
+
validateGiven
|
|
19
19
|
} from "jinaga";
|
|
20
20
|
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
factIndex
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
interface InputDescription {
|
|
51
|
-
label: string;
|
|
52
|
-
type: string;
|
|
53
|
-
factIndex: number;
|
|
54
|
-
factTypeParameter: number;
|
|
55
|
-
factHashParameter: number;
|
|
56
|
-
}
|
|
57
|
-
interface OutputDescription {
|
|
58
|
-
label: string;
|
|
59
|
-
type: string;
|
|
60
|
-
factIndex: number;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function countEdges(existentialConditions: ExistentialConditionDescription[]): number {
|
|
64
|
-
return existentialConditions.reduce((count, c) => count + c.edges.length + countEdges(c.existentialConditions),
|
|
65
|
-
0);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
class QueryDescription {
|
|
69
|
-
// An unsatisfiable query description will produce no results.
|
|
70
|
-
static unsatisfiable: QueryDescription = new QueryDescription(
|
|
71
|
-
[], [], [], [], [], []
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
constructor(
|
|
75
|
-
private readonly inputs: InputDescription[],
|
|
76
|
-
private readonly parameters: (string | number)[],
|
|
77
|
-
private readonly outputs: OutputDescription[],
|
|
78
|
-
private readonly facts: FactDescription[],
|
|
79
|
-
private readonly edges: EdgeDescription[],
|
|
80
|
-
private readonly existentialConditions: ExistentialConditionDescription[] = []
|
|
81
|
-
) {}
|
|
82
|
-
|
|
83
|
-
public withParameter(parameter: string | number): { query: QueryDescription; parameterIndex: number; } {
|
|
84
|
-
const parameterIndex = this.parameters.length + 1;
|
|
85
|
-
const query = new QueryDescription(
|
|
86
|
-
this.inputs,
|
|
87
|
-
this.parameters.concat(parameter),
|
|
88
|
-
this.outputs,
|
|
89
|
-
this.facts,
|
|
90
|
-
this.edges,
|
|
91
|
-
this.existentialConditions
|
|
92
|
-
);
|
|
93
|
-
return { query, parameterIndex };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
public withInputParameter(label: Label, factTypeId: number, factHash: string, path: number[]): { queryDescription: QueryDescription, factDescription: FactDescription } {
|
|
97
|
-
const factTypeParameter = this.parameters.length + 1;
|
|
98
|
-
const factHashParameter = factTypeParameter + 1;
|
|
99
|
-
const factIndex = this.facts.length + 1;
|
|
100
|
-
const factDescription: FactDescription = {
|
|
101
|
-
factIndex: factIndex,
|
|
102
|
-
type: label.type
|
|
103
|
-
};
|
|
104
|
-
const facts = [
|
|
105
|
-
...this.facts,
|
|
106
|
-
factDescription
|
|
107
|
-
]
|
|
108
|
-
const input: InputDescription = {
|
|
109
|
-
label: label.name,
|
|
21
|
+
import { FactTypeMap, RoleMap } from "./maps";
|
|
22
|
+
import { EdgeDescription, ExistentialConditionDescription, FactByLabel, QueryDescription, QueryDescriptionBuider } from "./query-description";
|
|
23
|
+
|
|
24
|
+
function generateResultSqlQuery(queryDescription: QueryDescription, schema: string): SpecificationSqlQuery {
|
|
25
|
+
const allLabels = [ ...queryDescription.inputs, ...queryDescription.outputs ];
|
|
26
|
+
const columns = allLabels
|
|
27
|
+
.map(label => `f${label.factIndex}.hash as hash${label.factIndex}, f${label.factIndex}.fact_id as id${label.factIndex}, f${label.factIndex}.data as data${label.factIndex}`)
|
|
28
|
+
.join(", ");
|
|
29
|
+
const firstEdge = queryDescription.edges[0];
|
|
30
|
+
const predecessorFact = queryDescription.inputs.find(i => i.factIndex === firstEdge.predecessorFactIndex);
|
|
31
|
+
const successorFact = queryDescription.inputs.find(i => i.factIndex === firstEdge.successorFactIndex);
|
|
32
|
+
const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact!.factIndex;
|
|
33
|
+
const writtenFactIndexes = new Set<number>().add(firstFactIndex);
|
|
34
|
+
const joins: string[] = generateJoins(queryDescription.edges, writtenFactIndexes, schema);
|
|
35
|
+
const inputWhereClauses = queryDescription.inputs
|
|
36
|
+
.map(input => `f${input.factIndex}.fact_type_id = $${input.factTypeParameter} AND f${input.factIndex}.hash = $${input.factHashParameter}`)
|
|
37
|
+
.join(" AND ");
|
|
38
|
+
const existentialWhereClauses = queryDescription.existentialConditions
|
|
39
|
+
.map(existentialCondition => ` AND ${existentialCondition.exists ? "EXISTS" : "NOT EXISTS"} (${generateExistentialWhereClause(existentialCondition, writtenFactIndexes, schema)})`)
|
|
40
|
+
.join("");
|
|
41
|
+
const orderByClause = queryDescription.outputs
|
|
42
|
+
.map(output => `f${output.factIndex}.fact_id ASC`)
|
|
43
|
+
.join(", ");
|
|
44
|
+
const sql = `SELECT ${columns} FROM ${schema}.fact f${firstFactIndex}${joins.join("")} WHERE ${inputWhereClauses}${existentialWhereClauses} ORDER BY ${orderByClause}`;
|
|
45
|
+
return {
|
|
46
|
+
sql,
|
|
47
|
+
parameters: queryDescription.parameters,
|
|
48
|
+
labels: allLabels.map(label => ({
|
|
49
|
+
name: label.label,
|
|
110
50
|
type: label.type,
|
|
111
|
-
factIndex
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const parameters = this.parameters.concat(factTypeId, factHash);
|
|
116
|
-
if (path.length === 0) {
|
|
117
|
-
const inputs = [
|
|
118
|
-
...this.inputs,
|
|
119
|
-
input
|
|
120
|
-
];
|
|
121
|
-
const queryDescription = new QueryDescription(
|
|
122
|
-
inputs,
|
|
123
|
-
parameters,
|
|
124
|
-
this.outputs,
|
|
125
|
-
facts,
|
|
126
|
-
this.edges,
|
|
127
|
-
this.existentialConditions
|
|
128
|
-
);
|
|
129
|
-
return { queryDescription, factDescription };
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
const existentialConditions = existentialsWithInput(this.existentialConditions, input, path);
|
|
133
|
-
const queryDescription = new QueryDescription(
|
|
134
|
-
this.inputs,
|
|
135
|
-
parameters,
|
|
136
|
-
this.outputs,
|
|
137
|
-
facts,
|
|
138
|
-
this.edges,
|
|
139
|
-
existentialConditions
|
|
140
|
-
);
|
|
141
|
-
return { queryDescription, factDescription };
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
public withFact(type: string): { query: QueryDescription; factIndex: number; } {
|
|
146
|
-
const factIndex = this.facts.length + 1;
|
|
147
|
-
const fact = { factIndex, type };
|
|
148
|
-
const query = new QueryDescription(
|
|
149
|
-
this.inputs,
|
|
150
|
-
this.parameters,
|
|
151
|
-
this.outputs,
|
|
152
|
-
this.facts.concat(fact),
|
|
153
|
-
this.edges,
|
|
154
|
-
this.existentialConditions
|
|
155
|
-
);
|
|
156
|
-
return { query, factIndex };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
public withOutput(label: string, type: string, factIndex: number): QueryDescription {
|
|
160
|
-
const output = { label, type, factIndex };
|
|
161
|
-
const query = new QueryDescription(
|
|
162
|
-
this.inputs,
|
|
163
|
-
this.parameters,
|
|
164
|
-
this.outputs.concat(output),
|
|
165
|
-
this.facts,
|
|
166
|
-
this.edges,
|
|
167
|
-
this.existentialConditions
|
|
168
|
-
);
|
|
169
|
-
return query;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
public withEdge(predecessorFactIndex: number, successorFactIndex: number, roleParameter: number, path: number[]) {
|
|
173
|
-
const edge = {
|
|
174
|
-
edgeIndex: this.edges.length + countEdges(this.existentialConditions) + 1,
|
|
175
|
-
predecessorFactIndex,
|
|
176
|
-
successorFactIndex,
|
|
177
|
-
roleParameter
|
|
178
|
-
};
|
|
179
|
-
const query = (path.length === 0)
|
|
180
|
-
? new QueryDescription(
|
|
181
|
-
this.inputs,
|
|
182
|
-
this.parameters,
|
|
183
|
-
this.outputs,
|
|
184
|
-
this.facts,
|
|
185
|
-
this.edges.concat(edge),
|
|
186
|
-
this.existentialConditions
|
|
187
|
-
)
|
|
188
|
-
: new QueryDescription(
|
|
189
|
-
this.inputs,
|
|
190
|
-
this.parameters,
|
|
191
|
-
this.outputs,
|
|
192
|
-
this.facts,
|
|
193
|
-
this.edges,
|
|
194
|
-
existentialsWithEdge(this.existentialConditions, edge, path)
|
|
195
|
-
);
|
|
196
|
-
return query;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
public withExistentialCondition(exists: boolean, path: number[]): { query: QueryDescription; path: number[]; } {
|
|
200
|
-
const { existentialConditions: newExistentialConditions, path: newPath } = existentialsWithNewCondition(this.existentialConditions, exists, path);
|
|
201
|
-
const query = new QueryDescription(
|
|
202
|
-
this.inputs,
|
|
203
|
-
this.parameters,
|
|
204
|
-
this.outputs,
|
|
205
|
-
this.facts,
|
|
206
|
-
this.edges,
|
|
207
|
-
newExistentialConditions
|
|
208
|
-
);
|
|
209
|
-
return { query, path: newPath };
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
isSatisfiable() {
|
|
213
|
-
return this.inputs.length > 0;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
hasOutput(label: string) {
|
|
217
|
-
return this.outputs.some(o => o.label === label);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
inputByLabel(label: string): InputDescription | undefined {
|
|
221
|
-
return this.inputs.find(i => i.label === label);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
outputLength(): number {
|
|
225
|
-
return this.inputs.length + this.outputs.length;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
generateResultSqlQuery(schema: string): SpecificationSqlQuery {
|
|
229
|
-
const allLabels = [ ...this.inputs, ...this.outputs ];
|
|
230
|
-
const columns = allLabels
|
|
231
|
-
.map(label => `f${label.factIndex}.hash as hash${label.factIndex}, f${label.factIndex}.fact_id as id${label.factIndex}, f${label.factIndex}.data as data${label.factIndex}`)
|
|
232
|
-
.join(", ");
|
|
233
|
-
const firstEdge = this.edges[0];
|
|
234
|
-
const predecessorFact = this.inputs.find(i => i.factIndex === firstEdge.predecessorFactIndex);
|
|
235
|
-
const successorFact = this.inputs.find(i => i.factIndex === firstEdge.successorFactIndex);
|
|
236
|
-
const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact!.factIndex;
|
|
237
|
-
const writtenFactIndexes = new Set<number>().add(firstFactIndex);
|
|
238
|
-
const joins: string[] = generateJoins(this.edges, writtenFactIndexes, schema);
|
|
239
|
-
const inputWhereClauses = this.inputs
|
|
240
|
-
.map(input => `f${input.factIndex}.fact_type_id = $${input.factTypeParameter} AND f${input.factIndex}.hash = $${input.factHashParameter}`)
|
|
241
|
-
.join(" AND ");
|
|
242
|
-
const existentialWhereClauses = this.existentialConditions
|
|
243
|
-
.map(existentialCondition => ` AND ${existentialCondition.exists ? "EXISTS" : "NOT EXISTS"} (${generateExistentialWhereClause(existentialCondition, writtenFactIndexes, schema)})`)
|
|
244
|
-
.join("");
|
|
245
|
-
const orderByClause = this.outputs
|
|
246
|
-
.map(output => `f${output.factIndex}.fact_id ASC`)
|
|
247
|
-
.join(", ");
|
|
248
|
-
const sql = `SELECT ${columns} FROM ${schema}.fact f${firstFactIndex}${joins.join("")} WHERE ${inputWhereClauses}${existentialWhereClauses} ORDER BY ${orderByClause}`;
|
|
249
|
-
return {
|
|
250
|
-
sql,
|
|
251
|
-
parameters: this.parameters,
|
|
252
|
-
labels: allLabels.map(label => ({
|
|
253
|
-
name: label.label,
|
|
254
|
-
type: label.type,
|
|
255
|
-
index: label.factIndex
|
|
256
|
-
})),
|
|
257
|
-
bookmark: "[]"
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function existentialsWithInput(existentialConditions: ExistentialConditionDescription[], input: InputDescription, path: number[]): ExistentialConditionDescription[] {
|
|
263
|
-
if (path.length === 1) {
|
|
264
|
-
return existentialConditions.map((c, i) => i === path[0] ?
|
|
265
|
-
{
|
|
266
|
-
...c,
|
|
267
|
-
inputs: [...c.inputs, input]
|
|
268
|
-
} :
|
|
269
|
-
c
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
return existentialConditions.map((c, i) => i === path[0] ?
|
|
274
|
-
{
|
|
275
|
-
...c,
|
|
276
|
-
existentialConditions: existentialsWithInput(c.existentialConditions, input, path.slice(1))
|
|
277
|
-
} :
|
|
278
|
-
c
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function existentialsWithEdge(existentialConditions: ExistentialConditionDescription[], edge: EdgeDescription, path: number[]): ExistentialConditionDescription[] {
|
|
284
|
-
if (path.length === 1) {
|
|
285
|
-
return existentialConditions.map((c, i) => i === path[0] ?
|
|
286
|
-
{
|
|
287
|
-
...c,
|
|
288
|
-
edges: [...c.edges, edge]
|
|
289
|
-
} :
|
|
290
|
-
c
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
return existentialConditions.map((c, i) => i === path[0] ?
|
|
295
|
-
{
|
|
296
|
-
...c,
|
|
297
|
-
existentialConditions: existentialsWithEdge(c.existentialConditions, edge, path.slice(1))
|
|
298
|
-
} :
|
|
299
|
-
c
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function existentialsWithNewCondition(existentialConditions: ExistentialConditionDescription[], exists: boolean, path: number[]): { existentialConditions: ExistentialConditionDescription[]; path: number[]; } {
|
|
305
|
-
if (path.length === 0) {
|
|
306
|
-
path = [existentialConditions.length];
|
|
307
|
-
existentialConditions = [
|
|
308
|
-
...existentialConditions,
|
|
309
|
-
{
|
|
310
|
-
exists: exists,
|
|
311
|
-
inputs: [],
|
|
312
|
-
edges: [],
|
|
313
|
-
existentialConditions: []
|
|
314
|
-
}
|
|
315
|
-
];
|
|
316
|
-
return { existentialConditions: existentialConditions, path };
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
319
|
-
const { existentialConditions: newExistentialConditions, path: newPath } = existentialsWithNewCondition(existentialConditions[path[0]].existentialConditions, exists, path.slice(1));
|
|
320
|
-
existentialConditions = existentialConditions.map((c, i) => i === path[0] ?
|
|
321
|
-
{
|
|
322
|
-
exists: c.exists,
|
|
323
|
-
inputs: c.inputs,
|
|
324
|
-
edges: c.edges,
|
|
325
|
-
existentialConditions: newExistentialConditions
|
|
326
|
-
} :
|
|
327
|
-
c
|
|
328
|
-
);
|
|
329
|
-
path = [path[0], ...newPath];
|
|
330
|
-
return { existentialConditions: existentialConditions, path };
|
|
331
|
-
}
|
|
51
|
+
index: label.factIndex
|
|
52
|
+
})),
|
|
53
|
+
bookmark: "[]"
|
|
54
|
+
};
|
|
332
55
|
}
|
|
333
56
|
|
|
334
57
|
function generateJoins(edges: EdgeDescription[], writtenFactIndexes: Set<number>, schema: string) {
|
|
@@ -421,10 +144,6 @@ function generateExistentialWhereClause(existentialCondition: ExistentialConditi
|
|
|
421
144
|
return `SELECT 1 FROM ${schema}.edge e${firstEdge.edgeIndex}${joins.join("")} WHERE ${whereClause.join(" AND ")}${inputWhereClauses}${existentialWhereClauses}`;
|
|
422
145
|
}
|
|
423
146
|
|
|
424
|
-
type FactByLabel = {
|
|
425
|
-
[label: string]: FactDescription;
|
|
426
|
-
};
|
|
427
|
-
|
|
428
147
|
interface ResultDescription {
|
|
429
148
|
queryDescription: QueryDescription;
|
|
430
149
|
resultProjection: Projection;
|
|
@@ -447,6 +166,19 @@ interface ChildResults {
|
|
|
447
166
|
results: ProjectedResult[];
|
|
448
167
|
}
|
|
449
168
|
|
|
169
|
+
export interface SpecificationLabel {
|
|
170
|
+
name: string;
|
|
171
|
+
index: number;
|
|
172
|
+
type: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface SpecificationSqlQuery {
|
|
176
|
+
sql: string;
|
|
177
|
+
parameters: (string | number | number[])[];
|
|
178
|
+
labels: SpecificationLabel[];
|
|
179
|
+
bookmark: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
450
182
|
export interface SqlQueryTree {
|
|
451
183
|
sqlQuery: SpecificationSqlQuery;
|
|
452
184
|
childQueries: NamedSqlQueryTree[];
|
|
@@ -749,28 +481,13 @@ interface NamedResultComposer {
|
|
|
749
481
|
|
|
750
482
|
class ResultDescriptionBuilder {
|
|
751
483
|
constructor(
|
|
752
|
-
private
|
|
753
|
-
private roleMap: RoleMap
|
|
484
|
+
private queryDescriptionBuilder: QueryDescriptionBuider
|
|
754
485
|
) { }
|
|
755
486
|
|
|
756
487
|
buildDescription(start: FactReference[], specification: Specification): ResultDescription {
|
|
757
|
-
|
|
758
|
-
if (start.length !== specification.given.length) {
|
|
759
|
-
throw new Error(`The number of start facts (${start.length}) does not equal the number of inputs (${specification.given.length})`);
|
|
760
|
-
}
|
|
761
|
-
// Verify that the input type matches the start fact type
|
|
762
|
-
for (let i = 0; i < start.length; i++) {
|
|
763
|
-
if (start[i].type !== specification.given[i].type) {
|
|
764
|
-
throw new Error(`The type of start fact ${i} (${start[i].type}) does not match the type of input ${i} (${specification.given[i].type})`);
|
|
765
|
-
}
|
|
766
|
-
}
|
|
488
|
+
validateGiven(start, specification);
|
|
767
489
|
|
|
768
|
-
|
|
769
|
-
// Initialize it with the inputs and facts.
|
|
770
|
-
// The DescriptionBuilder will branch at various points, and
|
|
771
|
-
// build on the current query description along each branch.
|
|
772
|
-
const initialQueryDescription = new QueryDescription([], [], [], [], [], []);
|
|
773
|
-
return this.createResultDescription(initialQueryDescription, specification.given, start, specification.matches, specification.projection, {}, []);
|
|
490
|
+
return this.createResultDescription(QueryDescription.unsatisfiable, specification.given, start, specification.matches, specification.projection, {}, []);
|
|
774
491
|
}
|
|
775
492
|
|
|
776
493
|
private createResultDescription(queryDescription: QueryDescription, given: Label[], start: FactReference[], matches: Match[], projection: Projection, knownFacts: FactByLabel, path: number[]): ResultDescription {
|
|
@@ -778,7 +495,7 @@ class ResultDescriptionBuilder {
|
|
|
778
495
|
...acc,
|
|
779
496
|
[label.name]: start[index]
|
|
780
497
|
}), {} as ReferencesByName);
|
|
781
|
-
({ queryDescription, knownFacts } = this.addEdges(queryDescription, given, start, knownFacts, path, matches));
|
|
498
|
+
({ queryDescription, knownFacts } = this.queryDescriptionBuilder.addEdges(queryDescription, given, start, knownFacts, path, matches));
|
|
782
499
|
if (!queryDescription.isSatisfiable()) {
|
|
783
500
|
// Abort the branch if the query is not satisfiable
|
|
784
501
|
return {
|
|
@@ -823,157 +540,6 @@ class ResultDescriptionBuilder {
|
|
|
823
540
|
}
|
|
824
541
|
}
|
|
825
542
|
}
|
|
826
|
-
|
|
827
|
-
private addEdges(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByLabel, path: number[], matches: Match[]): { queryDescription: QueryDescription, knownFacts: FactByLabel } {
|
|
828
|
-
for (const match of matches) {
|
|
829
|
-
for (const condition of match.conditions) {
|
|
830
|
-
if (condition.type === "path") {
|
|
831
|
-
({queryDescription, knownFacts} = this.addPathCondition(queryDescription, given, start, knownFacts, path, match.unknown, "", condition));
|
|
832
|
-
}
|
|
833
|
-
else if (condition.type === "existential") {
|
|
834
|
-
// Apply the where clause and continue with the tuple where it is true.
|
|
835
|
-
// The path describes which not-exists condition we are currently building on.
|
|
836
|
-
// Because the path is not empty, labeled facts will be included in the output.
|
|
837
|
-
const { query: queryDescriptionWithExistential, path: conditionalPath } = queryDescription.withExistentialCondition(condition.exists, path);
|
|
838
|
-
const { queryDescription: queryDescriptionConditional } = this.addEdges(queryDescriptionWithExistential, given, start, knownFacts, conditionalPath, condition.matches);
|
|
839
|
-
|
|
840
|
-
// If the negative existential condition is not satisfiable, then
|
|
841
|
-
// that means that the condition will always be true.
|
|
842
|
-
// We can therefore skip the branch for the negative existential condition.
|
|
843
|
-
if (queryDescriptionConditional.isSatisfiable()) {
|
|
844
|
-
queryDescription = queryDescriptionConditional;
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
if (!queryDescription.isSatisfiable()) {
|
|
848
|
-
break;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
if (!queryDescription.isSatisfiable()) {
|
|
852
|
-
break;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
return {
|
|
856
|
-
queryDescription,
|
|
857
|
-
knownFacts
|
|
858
|
-
};
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
private addPathCondition(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByLabel, path: number[], unknown: Label, prefix: string, condition: PathCondition): { queryDescription: QueryDescription, knownFacts: FactByLabel } {
|
|
862
|
-
// If no input parameter has been allocated, allocate one now.
|
|
863
|
-
if (!knownFacts.hasOwnProperty(condition.labelRight)) {
|
|
864
|
-
const givenIndex = given.findIndex(given => given.name === condition.labelRight);
|
|
865
|
-
if (givenIndex < 0) {
|
|
866
|
-
throw new Error(`No input parameter found for label ${condition.labelRight}`);
|
|
867
|
-
}
|
|
868
|
-
const { queryDescription: newQueryDescription, factDescription } = queryDescription.withInputParameter(
|
|
869
|
-
given[givenIndex],
|
|
870
|
-
ensureGetFactTypeId(this.factTypes, start[givenIndex].type),
|
|
871
|
-
start[givenIndex].hash,
|
|
872
|
-
path
|
|
873
|
-
);
|
|
874
|
-
queryDescription = newQueryDescription;
|
|
875
|
-
knownFacts = {
|
|
876
|
-
...knownFacts,
|
|
877
|
-
[condition.labelRight]: factDescription
|
|
878
|
-
};
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
// Determine whether we have already written the output.
|
|
882
|
-
const knownFact = knownFacts[unknown.name];
|
|
883
|
-
const roleCount = condition.rolesLeft.length + condition.rolesRight.length;
|
|
884
|
-
|
|
885
|
-
// Walk up the right-hand side.
|
|
886
|
-
// This generates predecessor joins from a given or prior label.
|
|
887
|
-
let fact = knownFacts[condition.labelRight];
|
|
888
|
-
if (!fact) {
|
|
889
|
-
throw new Error(`Label ${condition.labelRight} not found. Known labels: ${Object.keys(knownFacts).join(", ")}`);
|
|
890
|
-
}
|
|
891
|
-
let type = fact.type;
|
|
892
|
-
let factIndex = fact.factIndex;
|
|
893
|
-
for (const [i, role] of condition.rolesRight.entries()) {
|
|
894
|
-
// If the type or role is not known, then no facts matching the condition can
|
|
895
|
-
// exist. The query is unsatisfiable.
|
|
896
|
-
const typeId = getFactTypeId(this.factTypes, type);
|
|
897
|
-
if (!typeId) {
|
|
898
|
-
return { queryDescription: QueryDescription.unsatisfiable, knownFacts };
|
|
899
|
-
}
|
|
900
|
-
const roleId = getRoleId(this.roleMap, typeId, role.name);
|
|
901
|
-
if (!roleId) {
|
|
902
|
-
return { queryDescription: QueryDescription.unsatisfiable, knownFacts };
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
const { query: queryWithParameter, parameterIndex: roleParameter } = queryDescription.withParameter(roleId);
|
|
906
|
-
if (i === roleCount - 1 && knownFact) {
|
|
907
|
-
// If we have already written the output, we can use the fact index.
|
|
908
|
-
queryDescription = queryWithParameter.withEdge(knownFact.factIndex, factIndex, roleParameter, path);
|
|
909
|
-
factIndex = knownFact.factIndex;
|
|
910
|
-
}
|
|
911
|
-
else {
|
|
912
|
-
// If we have not written the fact, we need to write it now.
|
|
913
|
-
const { query, factIndex: predecessorFactIndex } = queryWithParameter.withFact(role.predecessorType);
|
|
914
|
-
queryDescription = query.withEdge(predecessorFactIndex, factIndex, roleParameter, path);
|
|
915
|
-
factIndex = predecessorFactIndex;
|
|
916
|
-
}
|
|
917
|
-
type = role.predecessorType;
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
const rightType = type;
|
|
921
|
-
|
|
922
|
-
// Walk up the left-hand side.
|
|
923
|
-
// We will need to reverse this walk to generate successor joins.
|
|
924
|
-
type = unknown.type;
|
|
925
|
-
const newEdges: {
|
|
926
|
-
roleId: number;
|
|
927
|
-
declaringType: string;
|
|
928
|
-
}[] = [];
|
|
929
|
-
for (const role of condition.rolesLeft) {
|
|
930
|
-
// If the type or role is not known, then no facts matching the condition can
|
|
931
|
-
// exist. The query is unsatisfiable.
|
|
932
|
-
const typeId = getFactTypeId(this.factTypes, type);
|
|
933
|
-
if (!typeId) {
|
|
934
|
-
return { queryDescription: QueryDescription.unsatisfiable, knownFacts };
|
|
935
|
-
}
|
|
936
|
-
const roleId = getRoleId(this.roleMap, typeId, role.name);
|
|
937
|
-
if (!roleId) {
|
|
938
|
-
return { queryDescription: QueryDescription.unsatisfiable, knownFacts };
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
newEdges.push({
|
|
942
|
-
roleId,
|
|
943
|
-
declaringType: type
|
|
944
|
-
});
|
|
945
|
-
type = role.predecessorType;
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
if (type !== rightType) {
|
|
949
|
-
throw new Error(`Type mismatch: ${type} is compared to ${rightType}`);
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
newEdges.reverse().forEach(({ roleId, declaringType }, i) => {
|
|
953
|
-
const { query: queryWithParameter, parameterIndex: roleParameter } = queryDescription.withParameter(roleId);
|
|
954
|
-
if (condition.rolesRight.length + i === roleCount - 1 && knownFact) {
|
|
955
|
-
queryDescription = queryWithParameter.withEdge(factIndex, knownFact.factIndex, roleParameter, path);
|
|
956
|
-
factIndex = knownFact.factIndex;
|
|
957
|
-
}
|
|
958
|
-
else {
|
|
959
|
-
const { query: queryWithFact, factIndex: successorFactIndex } = queryWithParameter.withFact(declaringType);
|
|
960
|
-
queryDescription = queryWithFact.withEdge(factIndex, successorFactIndex, roleParameter, path);
|
|
961
|
-
factIndex = successorFactIndex;
|
|
962
|
-
}
|
|
963
|
-
});
|
|
964
|
-
|
|
965
|
-
// If we have not captured the known fact, add it now.
|
|
966
|
-
if (!knownFact) {
|
|
967
|
-
knownFacts = { ...knownFacts, [unknown.name]: { factIndex, type: unknown.type } };
|
|
968
|
-
// If we have not written the output, write it now.
|
|
969
|
-
// Only write the output if we are not inside of an existential condition.
|
|
970
|
-
// Use the prefix, which will be set for projections.
|
|
971
|
-
if (path.length === 0) {
|
|
972
|
-
queryDescription = queryDescription.withOutput(prefix + unknown.name, unknown.type, factIndex);
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
return { queryDescription, knownFacts };
|
|
976
|
-
}
|
|
977
543
|
}
|
|
978
544
|
|
|
979
545
|
function idsEqual(a: number[], b: number[]) {
|
|
@@ -981,7 +547,8 @@ function idsEqual(a: number[], b: number[]) {
|
|
|
981
547
|
}
|
|
982
548
|
|
|
983
549
|
export function resultSqlFromSpecification(start: FactReference[], specification: Specification, factTypes: FactTypeMap, roleMap: RoleMap, schema: string): ResultComposer | null {
|
|
984
|
-
const
|
|
550
|
+
const queryDescriptionBuilder = new QueryDescriptionBuider(factTypes, roleMap);
|
|
551
|
+
const descriptionBuilder = new ResultDescriptionBuilder(queryDescriptionBuilder);
|
|
985
552
|
const description = descriptionBuilder.buildDescription(start, specification);
|
|
986
553
|
|
|
987
554
|
if (!description.queryDescription.isSatisfiable()) {
|
|
@@ -991,7 +558,7 @@ export function resultSqlFromSpecification(start: FactReference[], specification
|
|
|
991
558
|
}
|
|
992
559
|
|
|
993
560
|
function createResultComposer(description: ResultDescription, parentFactIdLength: number, schema: string): ResultComposer {
|
|
994
|
-
const sqlQuery = description.queryDescription
|
|
561
|
+
const sqlQuery = generateResultSqlQuery(description.queryDescription, schema);
|
|
995
562
|
const resultProjection = description.resultProjection;
|
|
996
563
|
const childResultComposers = description.childResultDescriptions
|
|
997
564
|
.filter(child => child.queryDescription.isSatisfiable())
|
|
@@ -1000,4 +567,4 @@ function createResultComposer(description: ResultDescription, parentFactIdLength
|
|
|
1000
567
|
resultComposer: createResultComposer(child, description.queryDescription.outputLength(), schema)
|
|
1001
568
|
}));
|
|
1002
569
|
return new ResultComposer(sqlQuery, resultProjection, parentFactIdLength, description.givenTuple, childResultComposers);
|
|
1003
|
-
}
|
|
570
|
+
}
|