jinaga-server 1.1.4 → 2.0.1
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 +6 -4
- package/dist/postgres/specification-result-sql.js +63 -23
- 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 +2 -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 -5
- 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 +88 -43
- 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 +4 -1
- 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
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
Direction,
|
|
4
4
|
ExistentialCondition,
|
|
5
5
|
FactEnvelope,
|
|
6
|
+
FactFeed,
|
|
6
7
|
FactPath,
|
|
7
8
|
FactRecord,
|
|
8
9
|
FactReference,
|
|
@@ -20,9 +21,8 @@ import {
|
|
|
20
21
|
Specification,
|
|
21
22
|
Step,
|
|
22
23
|
Storage,
|
|
23
|
-
TopologicalSorter
|
|
24
|
+
TopologicalSorter
|
|
24
25
|
} from "jinaga";
|
|
25
|
-
import { FactFeed } from "jinaga/dist/storage";
|
|
26
26
|
import { PoolClient } from "pg";
|
|
27
27
|
|
|
28
28
|
import { distinct, flatten } from "../util/fn";
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
emptyFactTypeMap,
|
|
38
38
|
emptyPublicKeyMap,
|
|
39
39
|
emptyRoleMap,
|
|
40
|
+
ensureGetFactTypeId,
|
|
40
41
|
FactMap,
|
|
41
42
|
FactTypeMap,
|
|
42
43
|
getFactId,
|
|
@@ -48,7 +49,7 @@ import {
|
|
|
48
49
|
mergeFactTypes,
|
|
49
50
|
mergeRoleMaps,
|
|
50
51
|
PublicKeyMap,
|
|
51
|
-
RoleMap
|
|
52
|
+
RoleMap
|
|
52
53
|
} from "./maps";
|
|
53
54
|
import { ResultSetTree, resultSqlFromSpecification, SqlQueryTree } from "./specification-result-sql";
|
|
54
55
|
import { sqlFromFeed } from "./specification-sql";
|
|
@@ -167,7 +168,7 @@ export class PostgresStore implements Storage {
|
|
|
167
168
|
} = await this.connectionFactory.withTransaction(async (connection) => {
|
|
168
169
|
const factTypes = await storeFactTypes(facts, this.factTypeMap, connection);
|
|
169
170
|
const existingFacts = await findExistingFacts(facts, factTypes, connection);
|
|
170
|
-
const newFacts = facts.filter(f => !hasFact(existingFacts, f.hash, factTypes
|
|
171
|
+
const newFacts = facts.filter(f => !hasFact(existingFacts, f.hash, ensureGetFactTypeId(factTypes, f.type)));
|
|
171
172
|
if (newFacts.length === 0) {
|
|
172
173
|
return {
|
|
173
174
|
newEnvelopes: [],
|
|
@@ -235,6 +236,11 @@ export class PostgresStore implements Storage {
|
|
|
235
236
|
const factTypes = await this.loadFactTypesFromSpecification(specification);
|
|
236
237
|
const roleMap = await this.loadRolesFromSpecification(specification, factTypes);
|
|
237
238
|
|
|
239
|
+
// If any of the start facts are not known types, the specification cannot be satisfied.
|
|
240
|
+
if (start.filter(f => getFactTypeId(factTypes, f.type) === undefined).length > 0) {
|
|
241
|
+
return [];
|
|
242
|
+
}
|
|
243
|
+
|
|
238
244
|
const composer = resultSqlFromSpecification(start, specification, factTypes, roleMap);
|
|
239
245
|
if (composer === null) {
|
|
240
246
|
return [];
|
|
@@ -317,7 +323,7 @@ export class PostgresStore implements Storage {
|
|
|
317
323
|
private async loadRolesFromSteps(steps: Step[], factTypes: FactTypeMap, initialType: string) {
|
|
318
324
|
const roleMap = this.roleMap;
|
|
319
325
|
const unknownRoles = allRoles(steps, factTypes, getFactTypeId(factTypes, initialType))
|
|
320
|
-
.filter(r => !hasRole(roleMap, r.
|
|
326
|
+
.filter(r => !hasRole(roleMap, r.successor_type_id, r.role));
|
|
321
327
|
if (unknownRoles.length > 0) {
|
|
322
328
|
const loadedRoles = await this.connectionFactory.with(async (connection) => {
|
|
323
329
|
return await loadRoles(unknownRoles, roleMap, connection);
|
|
@@ -332,11 +338,12 @@ export class PostgresStore implements Storage {
|
|
|
332
338
|
async loadRolesFromSpecification(specification: Specification, factTypes: FactTypeMap): Promise<RoleMap> {
|
|
333
339
|
const roleMap = this.roleMap;
|
|
334
340
|
const unknownRoles = getAllRoles(specification)
|
|
341
|
+
.filter(r => getFactTypeId(factTypes, r.successorType))
|
|
335
342
|
.map(r => ({
|
|
336
|
-
|
|
343
|
+
successor_type_id: ensureGetFactTypeId(factTypes, r.successorType),
|
|
337
344
|
role: r.name
|
|
338
345
|
}))
|
|
339
|
-
.filter(r => !hasRole(roleMap, r.
|
|
346
|
+
.filter(r => !hasRole(roleMap, r.successor_type_id, r.role));
|
|
340
347
|
if (unknownRoles.length > 0) {
|
|
341
348
|
const loadedRoles = await this.connectionFactory.with(async (connection) => {
|
|
342
349
|
return await loadRoles(unknownRoles, roleMap, connection);
|
|
@@ -352,10 +359,10 @@ export class PostgresStore implements Storage {
|
|
|
352
359
|
const roleMap = this.roleMap;
|
|
353
360
|
const unknownRoles = getAllRolesFromFeed(feed)
|
|
354
361
|
.map(r => ({
|
|
355
|
-
|
|
362
|
+
successor_type_id: ensureGetFactTypeId(factTypes, r.successorType),
|
|
356
363
|
role: r.name
|
|
357
364
|
}))
|
|
358
|
-
.filter(r => !hasRole(roleMap, r.
|
|
365
|
+
.filter(r => !hasRole(roleMap, r.successor_type_id, r.role));
|
|
359
366
|
if (unknownRoles.length > 0) {
|
|
360
367
|
const loadedRoles = await this.connectionFactory.with(async (connection) => {
|
|
361
368
|
return await loadRoles(unknownRoles, roleMap, connection);
|
|
@@ -535,21 +542,21 @@ async function loadFactTypes(factTypeNames: string[], connection: PoolClient) {
|
|
|
535
542
|
async function storeRoles(facts: FactRecord[], factTypes: FactTypeMap, roleMap: RoleMap, connection: PoolClient) {
|
|
536
543
|
// Find distinct roles
|
|
537
544
|
const roles = flatten(facts, fact => {
|
|
538
|
-
const
|
|
545
|
+
const successor_type_id = ensureGetFactTypeId(factTypes, fact.type);
|
|
539
546
|
return Object.keys(fact.predecessors).map(role => ({
|
|
540
547
|
role,
|
|
541
|
-
|
|
548
|
+
successor_type_id
|
|
542
549
|
}));
|
|
543
550
|
}).filter((role, index, array) => array.findIndex(r =>
|
|
544
551
|
r.role === role.role &&
|
|
545
|
-
r.
|
|
552
|
+
r.successor_type_id === role.successor_type_id
|
|
546
553
|
) === index);
|
|
547
554
|
|
|
548
555
|
if (roles.length > 0) {
|
|
549
556
|
// Look up existing roles
|
|
550
557
|
const roleIds = await loadRoles(roles, roleMap, connection);
|
|
551
558
|
const remainingRoles = roles.filter(role => !hasRole(
|
|
552
|
-
roleIds, role.
|
|
559
|
+
roleIds, role.successor_type_id, role.role));
|
|
553
560
|
if (remainingRoles.length === 0) {
|
|
554
561
|
return roleIds;
|
|
555
562
|
}
|
|
@@ -562,7 +569,7 @@ async function storeRoles(facts: FactRecord[], factTypes: FactTypeMap, roleMap:
|
|
|
562
569
|
' RETURNING role_id, name, defining_fact_type_id;';
|
|
563
570
|
const remainingRoleParameters = flatten(remainingRoles, (role) => [
|
|
564
571
|
role.role,
|
|
565
|
-
role.
|
|
572
|
+
role.successor_type_id
|
|
566
573
|
]);
|
|
567
574
|
const { rows: newRows }: RoleResult = await connection.query(insertSql, remainingRoleParameters);
|
|
568
575
|
if (newRows.length !== remainingRoles.length) {
|
|
@@ -579,12 +586,12 @@ async function storeRoles(facts: FactRecord[], factTypes: FactTypeMap, roleMap:
|
|
|
579
586
|
}
|
|
580
587
|
}
|
|
581
588
|
|
|
582
|
-
async function loadRoles(roles: { role: string;
|
|
589
|
+
async function loadRoles(roles: { role: string; successor_type_id: number; }[], roleMap: RoleMap, connection: PoolClient) {
|
|
583
590
|
const roleValues = roles.map((role, index) =>
|
|
584
591
|
`($${index * 2 + 1}, $${index * 2 + 2}::integer)`);
|
|
585
592
|
const roleParameters = flatten(roles, (role) => [
|
|
586
593
|
role.role,
|
|
587
|
-
role.
|
|
594
|
+
role.successor_type_id
|
|
588
595
|
]);
|
|
589
596
|
|
|
590
597
|
const lookUpSql = 'SELECT role.name, role.defining_fact_type_id, role.role_id' +
|
|
@@ -646,7 +653,7 @@ async function insertFactsEdgesAndAncestors(facts: FactRecord[], factTypes: Fact
|
|
|
646
653
|
|
|
647
654
|
function sqlInsertFacts(facts: FactRecord[], roles: RoleMap, factTypes: FactTypeMap) {
|
|
648
655
|
const factValues = facts.map((f, i) => `(\$${i * 3 + 1}, \$${i * 3 + 2}::integer, \$${i * 3 + 3}::jsonb)`);
|
|
649
|
-
const factParameters = flatten(facts, (f) => [f.hash, factTypes
|
|
656
|
+
const factParameters = flatten(facts, (f) => [f.hash, ensureGetFactTypeId(factTypes, f.type), {
|
|
650
657
|
fields: f.fields,
|
|
651
658
|
predecessors: canonicalPredecessors(f.predecessors)
|
|
652
659
|
}]);
|
|
@@ -669,8 +676,8 @@ function sqlInsertFactsEdgesAndAncestors(facts: FactRecord[], factParameters: (s
|
|
|
669
676
|
let parameterOffset = factParameters.length;
|
|
670
677
|
const edgeValues = edgeRecords.map((e, i) => `(\$${i * 5 + 1 + parameterOffset}, \$${i * 5 + 2 + parameterOffset}::integer, \$${i * 5 + 3 + parameterOffset}, \$${i * 5 + 4 + parameterOffset}::integer, \$${i * 5 + 5 + parameterOffset}::integer)`);
|
|
671
678
|
const edgeParameters = flatten(edgeRecords, (e) => {
|
|
672
|
-
const successor_fact_type_id =
|
|
673
|
-
const predecessor_fact_type_id =
|
|
679
|
+
const successor_fact_type_id = ensureGetFactTypeId(factTypes, e.successor_type);
|
|
680
|
+
const predecessor_fact_type_id = ensureGetFactTypeId(factTypes, e.predecessor_type);
|
|
674
681
|
return [
|
|
675
682
|
e.successor_hash,
|
|
676
683
|
successor_fact_type_id,
|
|
@@ -833,7 +840,7 @@ async function storePublicKeys(envelopes: FactEnvelope[], connection: PoolClient
|
|
|
833
840
|
|
|
834
841
|
async function insertSignatures(envelopes: FactEnvelope[], allFacts: FactMap, factTypes: FactTypeMap, publicKey: PublicKeyMap, connection: PoolClient) {
|
|
835
842
|
const signatureRecords = flatten(envelopes, envelope => envelope.signatures.map(signature => ({
|
|
836
|
-
factId: getFactId(allFacts, envelope.fact.hash,
|
|
843
|
+
factId: getFactId(allFacts, envelope.fact.hash, ensureGetFactTypeId(factTypes, envelope.fact.type)),
|
|
837
844
|
publicKeyId: getPublicKeyId(publicKey, signature.publicKey),
|
|
838
845
|
signature: signature.signature
|
|
839
846
|
})));
|
|
@@ -862,35 +869,35 @@ function allFactTypes(steps: Step[]): string[] {
|
|
|
862
869
|
return [...factTypes, ...childFactTypes].filter(distinct);
|
|
863
870
|
}
|
|
864
871
|
|
|
865
|
-
function allRoles(steps: Step[], factTypes: FactTypeMap, initialTypeId: number) {
|
|
866
|
-
let roles: {
|
|
867
|
-
let
|
|
872
|
+
function allRoles(steps: Step[], factTypes: FactTypeMap, initialTypeId: number | undefined) {
|
|
873
|
+
let roles: { successor_type_id: number; role: string }[] = [];
|
|
874
|
+
let successor_type_id = initialTypeId;
|
|
868
875
|
let role: string | undefined = undefined;
|
|
869
876
|
|
|
870
877
|
for (const step of steps) {
|
|
871
878
|
if (step instanceof PropertyCondition) {
|
|
872
879
|
if (step.name === 'type') {
|
|
873
|
-
|
|
874
|
-
if (
|
|
875
|
-
roles.push({
|
|
880
|
+
successor_type_id = getFactTypeId(factTypes, step.value);
|
|
881
|
+
if (successor_type_id && role) {
|
|
882
|
+
roles.push({ successor_type_id: successor_type_id, role });
|
|
876
883
|
}
|
|
877
884
|
role = undefined;
|
|
878
885
|
}
|
|
879
886
|
}
|
|
880
887
|
else if (step instanceof Join) {
|
|
881
888
|
if (step.direction === Direction.Predecessor) {
|
|
882
|
-
if (
|
|
883
|
-
roles.push({
|
|
889
|
+
if (successor_type_id) {
|
|
890
|
+
roles.push({ successor_type_id: successor_type_id, role: step.role });
|
|
884
891
|
}
|
|
885
892
|
role = undefined;
|
|
886
893
|
}
|
|
887
894
|
else {
|
|
888
895
|
role = step.role;
|
|
889
896
|
}
|
|
890
|
-
|
|
897
|
+
successor_type_id = undefined;
|
|
891
898
|
}
|
|
892
899
|
else if (step instanceof ExistentialCondition) {
|
|
893
|
-
roles = roles.concat(allRoles(step.steps, factTypes,
|
|
900
|
+
roles = roles.concat(allRoles(step.steps, factTypes, successor_type_id));
|
|
894
901
|
}
|
|
895
902
|
}
|
|
896
903
|
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
ComponentProjection,
|
|
3
|
+
FactProjection,
|
|
4
4
|
FactReference,
|
|
5
|
+
FieldProjection,
|
|
6
|
+
HashProjection,
|
|
5
7
|
Label,
|
|
6
8
|
Match,
|
|
7
9
|
PathCondition,
|
|
8
|
-
|
|
10
|
+
Projection,
|
|
9
11
|
SingularProjection,
|
|
10
12
|
Specification,
|
|
11
|
-
SpecificationProjection
|
|
13
|
+
SpecificationProjection
|
|
12
14
|
} from "jinaga";
|
|
13
15
|
|
|
14
|
-
import { FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
|
|
16
|
+
import { ensureGetFactTypeId, FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
|
|
15
17
|
|
|
16
18
|
interface SpecificationLabel {
|
|
17
19
|
name: string;
|
|
@@ -222,7 +224,7 @@ class QueryDescription {
|
|
|
222
224
|
const firstEdge = this.edges[0];
|
|
223
225
|
const predecessorFact = this.inputs.find(i => i.factIndex === firstEdge.predecessorFactIndex);
|
|
224
226
|
const successorFact = this.inputs.find(i => i.factIndex === firstEdge.successorFactIndex);
|
|
225
|
-
const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact
|
|
227
|
+
const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact!.factIndex;
|
|
226
228
|
const writtenFactIndexes = new Set<number>().add(firstFactIndex);
|
|
227
229
|
const joins: string[] = generateJoins(this.edges, writtenFactIndexes);
|
|
228
230
|
const inputWhereClauses = this.inputs
|
|
@@ -410,13 +412,13 @@ function generateExistentialWhereClause(existentialCondition: ExistentialConditi
|
|
|
410
412
|
return `SELECT 1 FROM public.edge e${firstEdge.edgeIndex}${joins.join("")} WHERE ${whereClause.join(" AND ")}${inputWhereClauses}${existentialWhereClauses}`;
|
|
411
413
|
}
|
|
412
414
|
|
|
413
|
-
type
|
|
414
|
-
[
|
|
415
|
+
type FactByLabel = {
|
|
416
|
+
[label: string]: FactDescription;
|
|
415
417
|
};
|
|
416
418
|
|
|
417
419
|
interface ResultDescription {
|
|
418
420
|
queryDescription: QueryDescription;
|
|
419
|
-
resultProjection:
|
|
421
|
+
resultProjection: Projection;
|
|
420
422
|
childResultDescriptions: NamedResultDescription[];
|
|
421
423
|
}
|
|
422
424
|
|
|
@@ -455,7 +457,7 @@ interface NamedResultSetTree extends ResultSetTree {
|
|
|
455
457
|
export class ResultComposer {
|
|
456
458
|
constructor(
|
|
457
459
|
private readonly sqlQuery: SpecificationSqlQuery,
|
|
458
|
-
private readonly resultProjection:
|
|
460
|
+
private readonly resultProjection: Projection,
|
|
459
461
|
private readonly parentFactIdLength: number,
|
|
460
462
|
private readonly childResultComposers: NamedResultComposer[]
|
|
461
463
|
) { }
|
|
@@ -555,40 +557,77 @@ export class ResultComposer {
|
|
|
555
557
|
}
|
|
556
558
|
|
|
557
559
|
private projectionOf(row: any): any {
|
|
558
|
-
if (
|
|
559
|
-
return this.
|
|
560
|
+
if (this.resultProjection.type === "field") {
|
|
561
|
+
return this.fieldValue(this.resultProjection, row);
|
|
560
562
|
}
|
|
561
|
-
else if (this.resultProjection.
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
.
|
|
563
|
+
else if (this.resultProjection.type === "composite") {
|
|
564
|
+
if (
|
|
565
|
+
this.resultProjection.components.length === 0 &&
|
|
566
|
+
this.childResultComposers.length === 0) {
|
|
567
|
+
return this.sqlQuery.labels
|
|
568
|
+
.slice(this.parentFactIdLength)
|
|
569
|
+
.reduce((acc, label) => ({
|
|
570
|
+
...acc,
|
|
571
|
+
[label.name]: row[`data${label.index}`].fields
|
|
572
|
+
}), {})
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
return this.resultProjection.components.reduce((acc, component) => ({
|
|
565
576
|
...acc,
|
|
566
|
-
[
|
|
567
|
-
}), {})
|
|
577
|
+
[component.name]: this.elementValue(component, row)
|
|
578
|
+
}), {});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
else if (this.resultProjection.type === "hash") {
|
|
582
|
+
return this.hashValue(this.resultProjection, row);
|
|
583
|
+
}
|
|
584
|
+
else if (this.resultProjection.type === "fact") {
|
|
585
|
+
return this.factValue(this.resultProjection, row);
|
|
568
586
|
}
|
|
569
587
|
else {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
[elementProjection.name]: this.elementValue(elementProjection, row)
|
|
573
|
-
}), {});
|
|
588
|
+
const _exhaustiveCheck: never = this.resultProjection;
|
|
589
|
+
throw new Error(`Unknown projection type ${(this.resultProjection as any).type}`);
|
|
574
590
|
}
|
|
575
591
|
}
|
|
576
592
|
|
|
577
|
-
private elementValue(projection:
|
|
578
|
-
const label = this.getLabel(projection.label);
|
|
593
|
+
private elementValue(projection: ComponentProjection, row: any): any {
|
|
579
594
|
if (projection.type === "field") {
|
|
595
|
+
const label = this.getLabel(projection.label);
|
|
580
596
|
return row[`data${label.index}`].fields[projection.field];
|
|
581
597
|
}
|
|
582
598
|
else if (projection.type === "hash") {
|
|
599
|
+
const label = this.getLabel(projection.label);
|
|
583
600
|
return row[`hash${label.index}`];
|
|
584
601
|
}
|
|
602
|
+
else if (projection.type === "fact") {
|
|
603
|
+
const label = this.getLabel(projection.label);
|
|
604
|
+
return row[`data${label.index}`].fields;
|
|
605
|
+
}
|
|
606
|
+
else if (projection.type === "specification") {
|
|
607
|
+
// This should have already been taken care of
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
const _exhaustiveCheck: never = projection;
|
|
612
|
+
throw new Error(`Unknown projection type ${(projection as any).type}`);
|
|
613
|
+
}
|
|
585
614
|
}
|
|
586
615
|
|
|
587
|
-
private
|
|
616
|
+
private fieldValue(projection: FieldProjection, row: any): any {
|
|
588
617
|
const label = this.getLabel(projection.label);
|
|
589
618
|
return row[`data${label.index}`].fields[projection.field];
|
|
590
619
|
}
|
|
591
620
|
|
|
621
|
+
private hashValue(projection: HashProjection, row: any): any {
|
|
622
|
+
const label = this.getLabel(projection.label);
|
|
623
|
+
return row[`hash${label.index}`];
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
private factValue(projection: FactProjection, row: any): any {
|
|
627
|
+
const label = this.getLabel(projection.label);
|
|
628
|
+
return row[`data${label.index}`].fields;
|
|
629
|
+
}
|
|
630
|
+
|
|
592
631
|
private getLabel(name: string) {
|
|
593
632
|
const label = this.sqlQuery.labels.find(label => label.name === name);
|
|
594
633
|
if (!label) {
|
|
@@ -626,27 +665,30 @@ class ResultDescriptionBuilder {
|
|
|
626
665
|
// The DescriptionBuilder will branch at various points, and
|
|
627
666
|
// build on the current query description along each branch.
|
|
628
667
|
const initialQueryDescription = new QueryDescription([], [], [], [], [], []);
|
|
629
|
-
return this.createResultDescription(initialQueryDescription, specification.given, start, specification.matches, specification.
|
|
668
|
+
return this.createResultDescription(initialQueryDescription, specification.given, start, specification.matches, specification.projection, {}, []);
|
|
630
669
|
}
|
|
631
670
|
|
|
632
|
-
private createResultDescription(queryDescription: QueryDescription, given: Label[], start: FactReference[], matches: Match[],
|
|
671
|
+
private createResultDescription(queryDescription: QueryDescription, given: Label[], start: FactReference[], matches: Match[], projection: Projection, knownFacts: FactByLabel, path: number[]): ResultDescription {
|
|
633
672
|
({ queryDescription, knownFacts } = this.addEdges(queryDescription, given, start, knownFacts, path, matches));
|
|
634
673
|
if (!queryDescription.isSatisfiable()) {
|
|
635
674
|
// Abort the branch if the query is not satisfiable
|
|
636
675
|
return {
|
|
637
676
|
queryDescription,
|
|
638
|
-
resultProjection:
|
|
677
|
+
resultProjection: {
|
|
678
|
+
type: "composite",
|
|
679
|
+
components: []
|
|
680
|
+
},
|
|
639
681
|
childResultDescriptions: []
|
|
640
682
|
}
|
|
641
683
|
}
|
|
642
684
|
const childResultDescriptions: NamedResultDescription[] = [];
|
|
643
|
-
if (
|
|
644
|
-
const specificationProjections =
|
|
645
|
-
.filter(projection => projection.type === "specification") as SpecificationProjection[];
|
|
646
|
-
const
|
|
647
|
-
.filter(projection => projection.type === "field" || projection.type === "hash") as
|
|
685
|
+
if (projection.type === "composite") {
|
|
686
|
+
const specificationProjections = projection.components
|
|
687
|
+
.filter(projection => projection.type === "specification") as ({ name: string } & SpecificationProjection)[];
|
|
688
|
+
const singularProjections = projection.components
|
|
689
|
+
.filter(projection => projection.type === "field" || projection.type === "hash" || projection.type === "fact") as ({ name: string } & SingularProjection)[];
|
|
648
690
|
for (const child of specificationProjections) {
|
|
649
|
-
const childResultDescription = this.createResultDescription(queryDescription, given, start, child.matches, child.
|
|
691
|
+
const childResultDescription = this.createResultDescription(queryDescription, given, start, child.matches, child.projection, knownFacts, []);
|
|
650
692
|
childResultDescriptions.push({
|
|
651
693
|
name: child.name,
|
|
652
694
|
...childResultDescription
|
|
@@ -654,20 +696,23 @@ class ResultDescriptionBuilder {
|
|
|
654
696
|
}
|
|
655
697
|
return {
|
|
656
698
|
queryDescription,
|
|
657
|
-
resultProjection:
|
|
699
|
+
resultProjection: {
|
|
700
|
+
type: "composite",
|
|
701
|
+
components: singularProjections
|
|
702
|
+
},
|
|
658
703
|
childResultDescriptions
|
|
659
704
|
};
|
|
660
705
|
}
|
|
661
706
|
else {
|
|
662
707
|
return {
|
|
663
708
|
queryDescription,
|
|
664
|
-
resultProjection:
|
|
709
|
+
resultProjection: projection,
|
|
665
710
|
childResultDescriptions: []
|
|
666
711
|
}
|
|
667
712
|
}
|
|
668
713
|
}
|
|
669
714
|
|
|
670
|
-
private addEdges(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts:
|
|
715
|
+
private addEdges(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByLabel, path: number[], matches: Match[]): { queryDescription: QueryDescription, knownFacts: FactByLabel } {
|
|
671
716
|
for (const match of matches) {
|
|
672
717
|
for (const condition of match.conditions) {
|
|
673
718
|
if (condition.type === "path") {
|
|
@@ -701,7 +746,7 @@ class ResultDescriptionBuilder {
|
|
|
701
746
|
};
|
|
702
747
|
}
|
|
703
748
|
|
|
704
|
-
private addPathCondition(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts:
|
|
749
|
+
private addPathCondition(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByLabel, path: number[], unknown: Label, prefix: string, condition: PathCondition): { queryDescription: QueryDescription, knownFacts: FactByLabel } {
|
|
705
750
|
// If no input parameter has been allocated, allocate one now.
|
|
706
751
|
if (!knownFacts.hasOwnProperty(condition.labelRight)) {
|
|
707
752
|
const givenIndex = given.findIndex(given => given.name === condition.labelRight);
|
|
@@ -710,7 +755,7 @@ class ResultDescriptionBuilder {
|
|
|
710
755
|
}
|
|
711
756
|
const { queryDescription: newQueryDescription, factDescription } = queryDescription.withInputParameter(
|
|
712
757
|
given[givenIndex],
|
|
713
|
-
|
|
758
|
+
ensureGetFactTypeId(this.factTypes, start[givenIndex].type),
|
|
714
759
|
start[givenIndex].hash,
|
|
715
760
|
path
|
|
716
761
|
);
|
|
@@ -753,11 +798,11 @@ class ResultDescriptionBuilder {
|
|
|
753
798
|
}
|
|
754
799
|
else {
|
|
755
800
|
// If we have not written the fact, we need to write it now.
|
|
756
|
-
const { query, factIndex: predecessorFactIndex } = queryWithParameter.withFact(role.
|
|
801
|
+
const { query, factIndex: predecessorFactIndex } = queryWithParameter.withFact(role.predecessorType);
|
|
757
802
|
queryDescription = query.withEdge(predecessorFactIndex, factIndex, roleParameter, path);
|
|
758
803
|
factIndex = predecessorFactIndex;
|
|
759
804
|
}
|
|
760
|
-
type = role.
|
|
805
|
+
type = role.predecessorType;
|
|
761
806
|
}
|
|
762
807
|
|
|
763
808
|
const rightType = type;
|
|
@@ -785,7 +830,7 @@ class ResultDescriptionBuilder {
|
|
|
785
830
|
roleId,
|
|
786
831
|
declaringType: type
|
|
787
832
|
});
|
|
788
|
-
type = role.
|
|
833
|
+
type = role.predecessorType;
|
|
789
834
|
}
|
|
790
835
|
|
|
791
836
|
if (type !== rightType) {
|
|
@@ -823,7 +868,7 @@ function idsEqual(a: number[], b: number[]) {
|
|
|
823
868
|
return a.every((value, index) => value === b[index]);
|
|
824
869
|
}
|
|
825
870
|
|
|
826
|
-
export function resultSqlFromSpecification(start: FactReference[], specification: Specification, factTypes: FactTypeMap, roleMap: RoleMap): ResultComposer {
|
|
871
|
+
export function resultSqlFromSpecification(start: FactReference[], specification: Specification, factTypes: FactTypeMap, roleMap: RoleMap): ResultComposer | null {
|
|
827
872
|
const descriptionBuilder = new ResultDescriptionBuilder(factTypes, roleMap);
|
|
828
873
|
const description = descriptionBuilder.buildDescription(start, specification);
|
|
829
874
|
|
|
@@ -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
|
package/src/postgres/sql.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Direction, ExistentialCondition, FactReference, Join, PropertyCondition, Quantifier, Step } from "jinaga";
|
|
2
2
|
|
|
3
|
-
import { FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
|
|
3
|
+
import { ensureGetFactTypeId, FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
|
|
4
4
|
|
|
5
5
|
export type SqlQuery = {
|
|
6
6
|
empty: boolean,
|
|
@@ -10,7 +10,7 @@ export type SqlQuery = {
|
|
|
10
10
|
factTypeNames: string[]
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
export function sqlFromSteps(start: FactReference, steps: Step[], factTypes: FactTypeMap, roleMap: RoleMap) : SqlQuery {
|
|
13
|
+
export function sqlFromSteps(start: FactReference, steps: Step[], factTypes: FactTypeMap, roleMap: RoleMap) : SqlQuery | null {
|
|
14
14
|
const builder = new QueryBuilder(factTypes, roleMap);
|
|
15
15
|
|
|
16
16
|
return builder.buildQuery(start, steps);
|
|
@@ -90,18 +90,18 @@ class QueryBuilder {
|
|
|
90
90
|
constructor(private factTypes: FactTypeMap, private roleMap: RoleMap) {
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
buildQuery(start: FactReference, steps: Step[]): SqlQuery {
|
|
93
|
+
buildQuery(start: FactReference, steps: Step[]): SqlQuery | null {
|
|
94
94
|
if (steps.length === 0) {
|
|
95
95
|
return null;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
const startTypeId =
|
|
98
|
+
const startTypeId = ensureGetFactTypeId(this.factTypes, start.type);
|
|
99
99
|
const startState: QueryBuilderState = {
|
|
100
100
|
state: 'start',
|
|
101
101
|
typeId: startTypeId,
|
|
102
102
|
typeName: start.type
|
|
103
103
|
};
|
|
104
|
-
const finalState = steps.reduce((state, step) => {
|
|
104
|
+
const finalState = steps.reduce((state: QueryBuilderState, step) => {
|
|
105
105
|
return this.matchStep(state, step);
|
|
106
106
|
}, startState);
|
|
107
107
|
this.end(finalState);
|
|
@@ -134,6 +134,14 @@ class QueryBuilder {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
private buildJoins(joins: QueryJoin[], priorFactId: string) {
|
|
137
|
+
interface Joins {
|
|
138
|
+
priorFactId: string,
|
|
139
|
+
clauses: string[]
|
|
140
|
+
};
|
|
141
|
+
const initialJoins: Joins = {
|
|
142
|
+
priorFactId,
|
|
143
|
+
clauses: []
|
|
144
|
+
};
|
|
137
145
|
const clauses = joins.reduce((joins, join) => {
|
|
138
146
|
if (join.table === 'edge') {
|
|
139
147
|
if (join.direction === 'successor') {
|
|
@@ -161,10 +169,7 @@ class QueryBuilder {
|
|
|
161
169
|
clauses: [...joins.clauses, clause]
|
|
162
170
|
};
|
|
163
171
|
}
|
|
164
|
-
},
|
|
165
|
-
priorFactId,
|
|
166
|
-
clauses: []
|
|
167
|
-
}).clauses;
|
|
172
|
+
}, initialJoins).clauses;
|
|
168
173
|
return clauses.join('');
|
|
169
174
|
}
|
|
170
175
|
|
|
@@ -209,7 +214,7 @@ class QueryBuilder {
|
|
|
209
214
|
typeId: typeId,
|
|
210
215
|
typeName: typeName
|
|
211
216
|
};
|
|
212
|
-
const finalState = steps.reduce((state, step) => {
|
|
217
|
+
const finalState = steps.reduce((state: QueryBuilderState, step) => {
|
|
213
218
|
return this.matchStep(state, step);
|
|
214
219
|
}, startState);
|
|
215
220
|
this.end(finalState);
|
|
@@ -270,12 +275,15 @@ class QueryBuilder {
|
|
|
270
275
|
position: `${state.typeName}.${step.role}`
|
|
271
276
|
}
|
|
272
277
|
}
|
|
273
|
-
if (step.direction === Direction.Successor) {
|
|
278
|
+
else if (step.direction === Direction.Successor) {
|
|
274
279
|
return {
|
|
275
280
|
state: 'successor-join',
|
|
276
281
|
role: step.role
|
|
277
282
|
}
|
|
278
283
|
}
|
|
284
|
+
else {
|
|
285
|
+
throw new Error(`Unknown direction ${step.direction}`);
|
|
286
|
+
}
|
|
279
287
|
}
|
|
280
288
|
else if (step instanceof ExistentialCondition) {
|
|
281
289
|
return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
|
|
@@ -311,13 +319,16 @@ class QueryBuilder {
|
|
|
311
319
|
position: `${state.typeName}.${step.role}`
|
|
312
320
|
}
|
|
313
321
|
}
|
|
314
|
-
if (step.direction === Direction.Successor) {
|
|
322
|
+
else if (step.direction === Direction.Successor) {
|
|
315
323
|
this.emitFact(state.typeName);
|
|
316
324
|
return {
|
|
317
325
|
state: 'successor-join',
|
|
318
326
|
role: step.role
|
|
319
327
|
}
|
|
320
328
|
}
|
|
329
|
+
else {
|
|
330
|
+
throw new Error(`Unknown direction ${step.direction}`);
|
|
331
|
+
}
|
|
321
332
|
}
|
|
322
333
|
else if (step instanceof ExistentialCondition) {
|
|
323
334
|
return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
|
package/src/util/maps.ts
ADDED
|
File without changes
|
|
@@ -27,7 +27,7 @@ function sqlFor(descriptiveString: string, bookmarks: string[] = []) {
|
|
|
27
27
|
// Filter out the roles named "unknown", and those of unknown fact types.
|
|
28
28
|
let roleMap = getAllRoles(specification).filter(r => r.name !== 'unknown').reduce(
|
|
29
29
|
(r, role, i) => {
|
|
30
|
-
const factTypeId = getFactTypeId(factTypes, role.
|
|
30
|
+
const factTypeId = getFactTypeId(factTypes, role.successorType);
|
|
31
31
|
if (!factTypeId) {
|
|
32
32
|
return r;
|
|
33
33
|
}
|
|
@@ -44,6 +44,9 @@ function sqlFor(descriptiveString: string, bookmarks: string[] = []) {
|
|
|
44
44
|
throw new Error(`Unknown input type ${input.type}`);
|
|
45
45
|
});
|
|
46
46
|
const composer = resultSqlFromSpecification(start, specification, factTypes, roleMap);
|
|
47
|
+
if (!composer) {
|
|
48
|
+
throw new Error("The specification is not satisfiable.");
|
|
49
|
+
}
|
|
47
50
|
return { composer, factTypes, roleMap };
|
|
48
51
|
}
|
|
49
52
|
|