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.
Files changed (89) hide show
  1. package/README.md +12 -0
  2. package/dist/authentication/authentication-device.d.ts +3 -2
  3. package/dist/authentication/authentication-device.js +6 -0
  4. package/dist/authentication/authentication-device.js.map +1 -1
  5. package/dist/authentication/authentication-impl.d.ts +3 -2
  6. package/dist/authentication/authentication-impl.js +6 -0
  7. package/dist/authentication/authentication-impl.js.map +1 -1
  8. package/dist/authentication/authentication-noop.d.ts +20 -0
  9. package/dist/authentication/authentication-noop.js +52 -0
  10. package/dist/authentication/authentication-noop.js.map +1 -0
  11. package/dist/authentication/authentication-session.d.ts +3 -2
  12. package/dist/authentication/authentication-session.js +6 -0
  13. package/dist/authentication/authentication-session.js.map +1 -1
  14. package/dist/authentication.d.ts +3 -2
  15. package/dist/authentication.js +6 -0
  16. package/dist/authentication.js.map +1 -1
  17. package/dist/authorization/authorization-keystore.d.ts +3 -4
  18. package/dist/authorization/authorization-keystore.js +9 -4
  19. package/dist/authorization/authorization-keystore.js.map +1 -1
  20. package/dist/debug.d.ts +1 -0
  21. package/dist/debug.js +30 -0
  22. package/dist/debug.js.map +1 -0
  23. package/dist/http/router.js.map +1 -1
  24. package/dist/jinaga-server.js +15 -7
  25. package/dist/jinaga-server.js.map +1 -1
  26. package/dist/memory/memory-keystore.js +0 -6
  27. package/dist/memory/memory-keystore.js.map +1 -1
  28. package/dist/postgres/connection.js +1 -0
  29. package/dist/postgres/connection.js.map +1 -1
  30. package/dist/postgres/maps.d.ts +7 -6
  31. package/dist/postgres/maps.js +9 -1
  32. package/dist/postgres/maps.js.map +1 -1
  33. package/dist/postgres/postgres-keystore.js +0 -6
  34. package/dist/postgres/postgres-keystore.js.map +1 -1
  35. package/dist/postgres/postgres-store.d.ts +1 -2
  36. package/dist/postgres/postgres-store.js +29 -24
  37. package/dist/postgres/postgres-store.js.map +1 -1
  38. package/dist/postgres/specification-result-sql.d.ts +4 -5
  39. package/dist/postgres/specification-result-sql.js +139 -112
  40. package/dist/postgres/specification-result-sql.js.map +1 -1
  41. package/dist/postgres/specification-sql.js.map +1 -1
  42. package/dist/postgres/sql.d.ts +1 -1
  43. package/dist/postgres/sql.js +15 -7
  44. package/dist/postgres/sql.js.map +1 -1
  45. package/dist/util/maps.d.ts +0 -0
  46. package/dist/util/maps.js +2 -0
  47. package/dist/util/maps.js.map +1 -0
  48. package/docs/sql/SQL Generator (projection).png +0 -0
  49. package/docs/sql/SQLFromSpecification.md +396 -0
  50. package/docs/sql/example.sql +49 -0
  51. package/docs/sql/first-path.gv +9 -0
  52. package/docs/sql/first-path.png +0 -0
  53. package/docs/sql/model.gv +20 -0
  54. package/docs/sql/model.png +0 -0
  55. package/integration-test/jinaga-test/Dockerfile +2 -2
  56. package/integration-test/jinaga-test/jinaga.test.js +3 -3
  57. package/integration-test/jinaga-test/package-lock.json +745 -3484
  58. package/integration-test/jinaga-test/package.json +2 -0
  59. package/integration-test/run.sh +3 -3
  60. package/package.json +5 -6
  61. package/replicator/index.ts +31 -0
  62. package/replicator/package-lock.json +855 -0
  63. package/replicator/package.json +27 -0
  64. package/replicator/tsconfig.json +15 -0
  65. package/src/authentication/authentication-device.ts +11 -1
  66. package/src/authentication/authentication-impl.ts +9 -2
  67. package/src/authentication/authentication-noop.ts +65 -0
  68. package/src/authentication/authentication-session.ts +11 -1
  69. package/src/authentication.ts +12 -2
  70. package/src/authorization/authorization-keystore.ts +12 -7
  71. package/src/debug.ts +30 -0
  72. package/src/http/router.ts +2 -2
  73. package/src/jinaga-server.ts +18 -8
  74. package/src/memory/memory-keystore.ts +0 -6
  75. package/src/postgres/connection.ts +2 -1
  76. package/src/postgres/maps.ts +8 -0
  77. package/src/postgres/postgres-keystore.ts +2 -8
  78. package/src/postgres/postgres-store.ts +37 -30
  79. package/src/postgres/specification-result-sql.ts +200 -145
  80. package/src/postgres/specification-sql.ts +1 -1
  81. package/src/postgres/sql.ts +23 -12
  82. package/src/util/maps.ts +0 -0
  83. package/test/postgres/postgresReadSpec.ts +292 -0
  84. package/test/postgres/postgresSpec.ts +31 -31
  85. package/test/postgres/postgresSpecificationSpec.ts +1 -1
  86. package/tsconfig.json +1 -0
  87. package/tsconfig.test.json +26 -0
  88. package/docs/specification.txt +0 -41
  89. package/interactive/index.js +0 -56
@@ -1,21 +1,20 @@
1
1
  import {
2
- ChildProjections,
3
- ElementProjection,
2
+ ComponentProjection,
4
3
  FactReference,
4
+ FieldProjection,
5
5
  Label,
6
6
  Match,
7
7
  PathCondition,
8
- ResultProjection,
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 NotExistsConditionDescription {
30
+ interface ExistentialConditionDescription {
31
+ exists: boolean;
32
+ inputs: InputDescription[];
32
33
  edges: EdgeDescription[];
33
- notExistsConditions: NotExistsConditionDescription[];
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(notExistsConditions: NotExistsConditionDescription[]): number {
56
- return notExistsConditions.reduce((count, c) => count + c.edges.length + countEdges(c.notExistsConditions),
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 notExistsConditions: NotExistsConditionDescription[] = []
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.notExistsConditions
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 input = this.inputs.find(i => i.label === label);
92
- const inputs = this.inputs.map(input => input.label === label
93
- ? { ...input, factTypeParameter, factHashParameter }
94
- : input
95
- );
96
- return new QueryDescription(
97
- inputs,
98
- this.parameters.concat(input.factTypeId, input.factHash),
99
- this.outputs,
100
- this.facts,
101
- this.edges,
102
- this.notExistsConditions
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.notExistsConditions
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.notExistsConditions
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.notExistsConditions) + 1,
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.notExistsConditions
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
- notExistsWithEdge(this.notExistsConditions, edge, path)
184
+ existentialsWithEdge(this.existentialConditions, edge, path)
156
185
  );
157
186
  return query;
158
187
  }
159
188
 
160
- public withNotExistsCondition(path: number[]): { query: QueryDescription; path: number[]; } {
161
- const { notExistsConditions: newNotExistsConditions, path: newPath } = notExistsWithCondition(this.notExistsConditions, path);
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
- newNotExistsConditions
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.factIndex;
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 notExistsWhereClauses = this.notExistsConditions
204
- .map(notExistsWhereClause => ` AND NOT EXISTS (${generateNotExistsWhereClause(notExistsWhereClause, writtenFactIndexes)})`)
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}${notExistsWhereClauses} ORDER BY ${orderByClause}`;
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 notExistsWithEdge(notExistsConditions: NotExistsConditionDescription[], edge: { edgeIndex: number; predecessorFactIndex: number; successorFactIndex: number; roleParameter: number; }, path: number[]): NotExistsConditionDescription[] {
251
+ function existentialsWithInput(existentialConditions: ExistentialConditionDescription[], input: InputDescription, path: number[]): ExistentialConditionDescription[] {
224
252
  if (path.length === 1) {
225
- return notExistsConditions.map((c, i) => i === path[0] ?
253
+ return existentialConditions.map((c, i) => i === path[0] ?
226
254
  {
227
- edges: [...c.edges, edge],
228
- notExistsConditions: c.notExistsConditions
255
+ ...c,
256
+ inputs: [...c.inputs, input]
229
257
  } :
230
258
  c
231
259
  );
232
260
  }
233
261
  else {
234
- return notExistsConditions.map((c, i) => i === path[0] ?
262
+ return existentialConditions.map((c, i) => i === path[0] ?
235
263
  {
236
- edges: c.edges,
237
- notExistsConditions: notExistsWithEdge(c.notExistsConditions, edge, path.slice(1))
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 notExistsWithCondition(notExistsConditions: NotExistsConditionDescription[], path: number[]): { notExistsConditions: NotExistsConditionDescription[]; path: number[]; } {
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 = [notExistsConditions.length];
247
- notExistsConditions = [
248
- ...notExistsConditions,
295
+ path = [existentialConditions.length];
296
+ existentialConditions = [
297
+ ...existentialConditions,
249
298
  {
299
+ exists: exists,
300
+ inputs: [],
250
301
  edges: [],
251
- notExistsConditions: []
302
+ existentialConditions: []
252
303
  }
253
304
  ];
254
- return { notExistsConditions, path };
305
+ return { existentialConditions: existentialConditions, path };
255
306
  }
256
307
  else {
257
- const { notExistsConditions: newNotExistsConditions, path: newPath } = notExistsWithCondition(notExistsConditions[path[0]].notExistsConditions, path.slice(1));
258
- notExistsConditions = notExistsConditions.map((c, i) => i === path[0] ?
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
- notExistsConditions: newNotExistsConditions
314
+ existentialConditions: newExistentialConditions
262
315
  } :
263
316
  c
264
317
  );
265
318
  path = [path[0], ...newPath];
266
- return { notExistsConditions, path };
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 generateNotExistsWhereClause(notExistsWhereClause: NotExistsConditionDescription, outerFactIndexes: Set<number>): string {
315
- const firstEdge = notExistsWhereClause.edges[0];
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(notExistsWhereClause.edges.slice(1), writtenFactIndexes);
402
+ const tailJoins: string[] = generateJoins(existentialCondition.edges.slice(1), writtenFactIndexes);
350
403
  const joins = firstJoin.concat(tailJoins);
351
- return `SELECT 1 FROM public.edge e${firstEdge.edgeIndex}${joins.join("")} WHERE ${whereClause.join(" AND ")}`;
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 FactByIdentifier = {
355
- [identifier: string]: FactDescription;
413
+ type FactByLabel = {
414
+ [label: string]: FactDescription;
356
415
  };
357
416
 
358
417
  interface ResultDescription {
359
418
  queryDescription: QueryDescription;
360
- resultProjection: 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: 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 (!Array.isArray(this.resultProjection)) {
500
- return this.singularValue(this.resultProjection, row);
558
+ if (this.resultProjection.type === "field") {
559
+ return this.fieldValue(this.resultProjection, row);
501
560
  }
502
- else if (this.resultProjection.length === 0 && this.childResultComposers.length === 0) {
503
- return this.sqlQuery.labels
504
- .slice(this.parentFactIdLength)
505
- .reduce((acc, label) => ({
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
- [label.name]: row[`data${label.index}`].fields
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: ElementProjection, row: any): any {
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 singularValue(projection: SingularProjection, row: any): any {
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(inputs, [], [], facts, [], []);
591
- return this.createResultDescription(initialQueryDescription, specification.matches, specification.childProjections, givenFacts, []);
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[], childProjections: ChildProjections, knownFacts: FactByIdentifier, path: number[]): ResultDescription {
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 (Array.isArray(childProjections)) {
606
- const specificationProjections = childProjections
607
- .filter(projection => projection.type === "specification") as SpecificationProjection[];
608
- const elementProjections = childProjections
609
- .filter(projection => projection.type === "field" || projection.type === "hash") as ElementProjection[];
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.childProjections, knownFacts, []);
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: elementProjections,
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: childProjections,
675
+ resultProjection: projection,
627
676
  childResultDescriptions: []
628
677
  }
629
678
  }
630
679
  }
631
680
 
632
- private addEdges(queryDescription: QueryDescription, knownFacts: FactByIdentifier, path: number[], matches: Match[]): { queryDescription: QueryDescription, knownFacts: FactByIdentifier } {
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
- if (condition.exists) {
640
- // Include the edges of the existential condition into the current
641
- // query description.
642
- ({ queryDescription } = this.addEdges(queryDescription, knownFacts, path, condition.matches));
643
- }
644
- else {
645
- // Apply the where clause and continue with the tuple where it is true.
646
- // The path describes which not-exists condition we are currently building on.
647
- // Because the path is not empty, labeled facts will be included in the output.
648
- const { query: queryDescriptionWithNotExist, path: conditionalPath } = queryDescription.withNotExistsCondition(path);
649
- const { queryDescription: queryDescriptionConditional } = this.addEdges(queryDescriptionWithNotExist, knownFacts, conditionalPath, condition.matches);
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: FactByIdentifier, path: number[], unknown: Label, prefix: string, condition: PathCondition): { queryDescription: QueryDescription, knownFacts: FactByIdentifier } {
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
- const input = queryDescription.inputByLabel(condition.labelRight);
676
- if (input && input.factTypeParameter === 0) {
677
- queryDescription = queryDescription.withInputParameter(input.label);
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.targetType);
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.targetType;
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.targetType;
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.factIndex;
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