jinaga-server 1.1.2 → 1.1.4

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.
@@ -15,7 +15,6 @@ import { 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
  }
@@ -197,16 +226,15 @@ class QueryDescription {
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,9 +399,15 @@ 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
413
  type FactByIdentifier = {
@@ -562,37 +621,16 @@ class ResultDescriptionBuilder {
562
621
  }
563
622
  }
564
623
 
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
624
  // The QueryDescription is an immutable data type.
587
625
  // Initialize it with the inputs and facts.
588
626
  // The DescriptionBuilder will branch at various points, and
589
627
  // 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, []);
628
+ const initialQueryDescription = new QueryDescription([], [], [], [], [], []);
629
+ return this.createResultDescription(initialQueryDescription, specification.given, start, specification.matches, specification.childProjections, {}, []);
592
630
  }
593
631
 
594
- private createResultDescription(queryDescription: QueryDescription, matches: Match[], childProjections: ChildProjections, knownFacts: FactByIdentifier, path: number[]): ResultDescription {
595
- ({ queryDescription, knownFacts } = this.addEdges(queryDescription, knownFacts, path, matches));
632
+ private createResultDescription(queryDescription: QueryDescription, given: Label[], start: FactReference[], matches: Match[], childProjections: ChildProjections, knownFacts: FactByIdentifier, path: number[]): ResultDescription {
633
+ ({ queryDescription, knownFacts } = this.addEdges(queryDescription, given, start, knownFacts, path, matches));
596
634
  if (!queryDescription.isSatisfiable()) {
597
635
  // Abort the branch if the query is not satisfiable
598
636
  return {
@@ -608,7 +646,7 @@ class ResultDescriptionBuilder {
608
646
  const elementProjections = childProjections
609
647
  .filter(projection => projection.type === "field" || projection.type === "hash") as ElementProjection[];
610
648
  for (const child of specificationProjections) {
611
- const childResultDescription = this.createResultDescription(queryDescription, child.matches, child.childProjections, knownFacts, []);
649
+ const childResultDescription = this.createResultDescription(queryDescription, given, start, child.matches, child.childProjections, knownFacts, []);
612
650
  childResultDescriptions.push({
613
651
  name: child.name,
614
652
  ...childResultDescription
@@ -629,31 +667,24 @@ class ResultDescriptionBuilder {
629
667
  }
630
668
  }
631
669
 
632
- private addEdges(queryDescription: QueryDescription, knownFacts: FactByIdentifier, path: number[], matches: Match[]): { queryDescription: QueryDescription, knownFacts: FactByIdentifier } {
670
+ private addEdges(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByIdentifier, path: number[], matches: Match[]): { queryDescription: QueryDescription, knownFacts: FactByIdentifier } {
633
671
  for (const match of matches) {
634
672
  for (const condition of match.conditions) {
635
673
  if (condition.type === "path") {
636
- ({queryDescription, knownFacts} = this.addPathCondition(queryDescription, knownFacts, path, match.unknown, "", condition));
674
+ ({queryDescription, knownFacts} = this.addPathCondition(queryDescription, given, start, knownFacts, path, match.unknown, "", condition));
637
675
  }
638
676
  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
- }
677
+ // Apply the where clause and continue with the tuple where it is true.
678
+ // The path describes which not-exists condition we are currently building on.
679
+ // Because the path is not empty, labeled facts will be included in the output.
680
+ const { query: queryDescriptionWithExistential, path: conditionalPath } = queryDescription.withExistentialCondition(condition.exists, path);
681
+ const { queryDescription: queryDescriptionConditional } = this.addEdges(queryDescriptionWithExistential, given, start, knownFacts, conditionalPath, condition.matches);
682
+
683
+ // If the negative existential condition is not satisfiable, then
684
+ // that means that the condition will always be true.
685
+ // We can therefore skip the branch for the negative existential condition.
686
+ if (queryDescriptionConditional.isSatisfiable()) {
687
+ queryDescription = queryDescriptionConditional;
657
688
  }
658
689
  }
659
690
  if (!queryDescription.isSatisfiable()) {
@@ -670,11 +701,24 @@ class ResultDescriptionBuilder {
670
701
  };
671
702
  }
672
703
 
673
- private addPathCondition(queryDescription: QueryDescription, knownFacts: FactByIdentifier, path: number[], unknown: Label, prefix: string, condition: PathCondition): { queryDescription: QueryDescription, knownFacts: FactByIdentifier } {
704
+ private addPathCondition(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByIdentifier, path: number[], unknown: Label, prefix: string, condition: PathCondition): { queryDescription: QueryDescription, knownFacts: FactByIdentifier } {
674
705
  // 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);
706
+ if (!knownFacts.hasOwnProperty(condition.labelRight)) {
707
+ const givenIndex = given.findIndex(given => given.name === condition.labelRight);
708
+ if (givenIndex < 0) {
709
+ throw new Error(`No input parameter found for label ${condition.labelRight}`);
710
+ }
711
+ const { queryDescription: newQueryDescription, factDescription } = queryDescription.withInputParameter(
712
+ given[givenIndex],
713
+ getFactTypeId(this.factTypes, start[givenIndex].type),
714
+ start[givenIndex].hash,
715
+ path
716
+ );
717
+ queryDescription = newQueryDescription;
718
+ knownFacts = {
719
+ ...knownFacts,
720
+ [condition.labelRight]: factDescription
721
+ };
678
722
  }
679
723
 
680
724
  // Determine whether we have already written the output.