jinaga-server 1.1.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/authentication/authentication-device.d.ts +3 -2
- package/dist/authentication/authentication-device.js +6 -0
- package/dist/authentication/authentication-device.js.map +1 -1
- package/dist/authentication/authentication-impl.d.ts +3 -2
- package/dist/authentication/authentication-impl.js +6 -0
- package/dist/authentication/authentication-impl.js.map +1 -1
- package/dist/authentication/authentication-noop.d.ts +20 -0
- package/dist/authentication/authentication-noop.js +52 -0
- package/dist/authentication/authentication-noop.js.map +1 -0
- package/dist/authentication/authentication-session.d.ts +3 -2
- package/dist/authentication/authentication-session.js +6 -0
- package/dist/authentication/authentication-session.js.map +1 -1
- package/dist/authentication.d.ts +3 -2
- package/dist/authentication.js +6 -0
- package/dist/authentication.js.map +1 -1
- package/dist/authorization/authorization-keystore.d.ts +3 -4
- package/dist/authorization/authorization-keystore.js +9 -4
- package/dist/authorization/authorization-keystore.js.map +1 -1
- package/dist/debug.d.ts +1 -0
- package/dist/debug.js +30 -0
- package/dist/debug.js.map +1 -0
- package/dist/http/router.js.map +1 -1
- package/dist/jinaga-server.js +15 -7
- package/dist/jinaga-server.js.map +1 -1
- package/dist/memory/memory-keystore.js +0 -6
- package/dist/memory/memory-keystore.js.map +1 -1
- package/dist/postgres/connection.js +1 -0
- package/dist/postgres/connection.js.map +1 -1
- package/dist/postgres/maps.d.ts +7 -6
- package/dist/postgres/maps.js +9 -1
- package/dist/postgres/maps.js.map +1 -1
- package/dist/postgres/postgres-keystore.js +0 -6
- package/dist/postgres/postgres-keystore.js.map +1 -1
- package/dist/postgres/postgres-store.d.ts +1 -2
- package/dist/postgres/postgres-store.js +29 -24
- package/dist/postgres/postgres-store.js.map +1 -1
- package/dist/postgres/specification-result-sql.d.ts +4 -4
- package/dist/postgres/specification-result-sql.js +34 -24
- 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 -6
- package/replicator/index.ts +31 -0
- package/replicator/package-lock.json +855 -0
- package/replicator/package.json +27 -0
- package/replicator/tsconfig.json +15 -0
- package/src/authentication/authentication-device.ts +11 -1
- package/src/authentication/authentication-impl.ts +9 -2
- package/src/authentication/authentication-noop.ts +65 -0
- package/src/authentication/authentication-session.ts +11 -1
- package/src/authentication.ts +12 -2
- package/src/authorization/authorization-keystore.ts +12 -7
- package/src/debug.ts +30 -0
- package/src/http/router.ts +2 -2
- package/src/jinaga-server.ts +18 -8
- package/src/memory/memory-keystore.ts +0 -6
- package/src/postgres/connection.ts +2 -1
- package/src/postgres/maps.ts +8 -0
- package/src/postgres/postgres-keystore.ts +2 -8
- package/src/postgres/postgres-store.ts +37 -30
- package/src/postgres/specification-result-sql.ts +56 -45
- 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,17 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
ElementProjection,
|
|
2
|
+
ComponentProjection,
|
|
4
3
|
FactReference,
|
|
4
|
+
FieldProjection,
|
|
5
5
|
Label,
|
|
6
6
|
Match,
|
|
7
7
|
PathCondition,
|
|
8
|
-
|
|
8
|
+
Projection,
|
|
9
9
|
SingularProjection,
|
|
10
10
|
Specification,
|
|
11
|
-
SpecificationProjection
|
|
11
|
+
SpecificationProjection
|
|
12
12
|
} from "jinaga";
|
|
13
13
|
|
|
14
|
-
import { FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
|
|
14
|
+
import { ensureGetFactTypeId, FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
|
|
15
15
|
|
|
16
16
|
interface SpecificationLabel {
|
|
17
17
|
name: string;
|
|
@@ -222,7 +222,7 @@ class QueryDescription {
|
|
|
222
222
|
const firstEdge = this.edges[0];
|
|
223
223
|
const predecessorFact = this.inputs.find(i => i.factIndex === firstEdge.predecessorFactIndex);
|
|
224
224
|
const successorFact = this.inputs.find(i => i.factIndex === firstEdge.successorFactIndex);
|
|
225
|
-
const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact
|
|
225
|
+
const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact!.factIndex;
|
|
226
226
|
const writtenFactIndexes = new Set<number>().add(firstFactIndex);
|
|
227
227
|
const joins: string[] = generateJoins(this.edges, writtenFactIndexes);
|
|
228
228
|
const inputWhereClauses = this.inputs
|
|
@@ -410,13 +410,13 @@ function generateExistentialWhereClause(existentialCondition: ExistentialConditi
|
|
|
410
410
|
return `SELECT 1 FROM public.edge e${firstEdge.edgeIndex}${joins.join("")} WHERE ${whereClause.join(" AND ")}${inputWhereClauses}${existentialWhereClauses}`;
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
-
type
|
|
414
|
-
[
|
|
413
|
+
type FactByLabel = {
|
|
414
|
+
[label: string]: FactDescription;
|
|
415
415
|
};
|
|
416
416
|
|
|
417
417
|
interface ResultDescription {
|
|
418
418
|
queryDescription: QueryDescription;
|
|
419
|
-
resultProjection:
|
|
419
|
+
resultProjection: Projection;
|
|
420
420
|
childResultDescriptions: NamedResultDescription[];
|
|
421
421
|
}
|
|
422
422
|
|
|
@@ -455,7 +455,7 @@ interface NamedResultSetTree extends ResultSetTree {
|
|
|
455
455
|
export class ResultComposer {
|
|
456
456
|
constructor(
|
|
457
457
|
private readonly sqlQuery: SpecificationSqlQuery,
|
|
458
|
-
private readonly resultProjection:
|
|
458
|
+
private readonly resultProjection: Projection,
|
|
459
459
|
private readonly parentFactIdLength: number,
|
|
460
460
|
private readonly childResultComposers: NamedResultComposer[]
|
|
461
461
|
) { }
|
|
@@ -555,36 +555,41 @@ export class ResultComposer {
|
|
|
555
555
|
}
|
|
556
556
|
|
|
557
557
|
private projectionOf(row: any): any {
|
|
558
|
-
if (
|
|
559
|
-
return this.
|
|
558
|
+
if (this.resultProjection.type === "field") {
|
|
559
|
+
return this.fieldValue(this.resultProjection, row);
|
|
560
560
|
}
|
|
561
|
-
else if (this.resultProjection.
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
.
|
|
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) => ({
|
|
565
574
|
...acc,
|
|
566
|
-
[
|
|
567
|
-
}), {})
|
|
568
|
-
|
|
569
|
-
else {
|
|
570
|
-
return this.resultProjection.reduce((acc, elementProjection) => ({
|
|
571
|
-
...acc,
|
|
572
|
-
[elementProjection.name]: this.elementValue(elementProjection, row)
|
|
573
|
-
}), {});
|
|
575
|
+
[component.name]: this.elementValue(component, row)
|
|
576
|
+
}), {});
|
|
577
|
+
}
|
|
574
578
|
}
|
|
575
579
|
}
|
|
576
580
|
|
|
577
|
-
private elementValue(projection:
|
|
578
|
-
const label = this.getLabel(projection.label);
|
|
581
|
+
private elementValue(projection: ComponentProjection, row: any): any {
|
|
579
582
|
if (projection.type === "field") {
|
|
583
|
+
const label = this.getLabel(projection.label);
|
|
580
584
|
return row[`data${label.index}`].fields[projection.field];
|
|
581
585
|
}
|
|
582
586
|
else if (projection.type === "hash") {
|
|
587
|
+
const label = this.getLabel(projection.label);
|
|
583
588
|
return row[`hash${label.index}`];
|
|
584
589
|
}
|
|
585
590
|
}
|
|
586
591
|
|
|
587
|
-
private
|
|
592
|
+
private fieldValue(projection: FieldProjection, row: any): any {
|
|
588
593
|
const label = this.getLabel(projection.label);
|
|
589
594
|
return row[`data${label.index}`].fields[projection.field];
|
|
590
595
|
}
|
|
@@ -626,27 +631,30 @@ class ResultDescriptionBuilder {
|
|
|
626
631
|
// The DescriptionBuilder will branch at various points, and
|
|
627
632
|
// build on the current query description along each branch.
|
|
628
633
|
const initialQueryDescription = new QueryDescription([], [], [], [], [], []);
|
|
629
|
-
return this.createResultDescription(initialQueryDescription, specification.given, start, specification.matches, specification.
|
|
634
|
+
return this.createResultDescription(initialQueryDescription, specification.given, start, specification.matches, specification.projection, {}, []);
|
|
630
635
|
}
|
|
631
636
|
|
|
632
|
-
private createResultDescription(queryDescription: QueryDescription, given: Label[], start: FactReference[], matches: Match[],
|
|
637
|
+
private createResultDescription(queryDescription: QueryDescription, given: Label[], start: FactReference[], matches: Match[], projection: Projection, knownFacts: FactByLabel, path: number[]): ResultDescription {
|
|
633
638
|
({ queryDescription, knownFacts } = this.addEdges(queryDescription, given, start, knownFacts, path, matches));
|
|
634
639
|
if (!queryDescription.isSatisfiable()) {
|
|
635
640
|
// Abort the branch if the query is not satisfiable
|
|
636
641
|
return {
|
|
637
642
|
queryDescription,
|
|
638
|
-
resultProjection:
|
|
643
|
+
resultProjection: {
|
|
644
|
+
type: "composite",
|
|
645
|
+
components: []
|
|
646
|
+
},
|
|
639
647
|
childResultDescriptions: []
|
|
640
648
|
}
|
|
641
649
|
}
|
|
642
650
|
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
|
|
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)[];
|
|
648
656
|
for (const child of specificationProjections) {
|
|
649
|
-
const childResultDescription = this.createResultDescription(queryDescription, given, start, child.matches, child.
|
|
657
|
+
const childResultDescription = this.createResultDescription(queryDescription, given, start, child.matches, child.projection, knownFacts, []);
|
|
650
658
|
childResultDescriptions.push({
|
|
651
659
|
name: child.name,
|
|
652
660
|
...childResultDescription
|
|
@@ -654,20 +662,23 @@ class ResultDescriptionBuilder {
|
|
|
654
662
|
}
|
|
655
663
|
return {
|
|
656
664
|
queryDescription,
|
|
657
|
-
resultProjection:
|
|
665
|
+
resultProjection: {
|
|
666
|
+
type: "composite",
|
|
667
|
+
components: singularProjections
|
|
668
|
+
},
|
|
658
669
|
childResultDescriptions
|
|
659
670
|
};
|
|
660
671
|
}
|
|
661
672
|
else {
|
|
662
673
|
return {
|
|
663
674
|
queryDescription,
|
|
664
|
-
resultProjection:
|
|
675
|
+
resultProjection: projection,
|
|
665
676
|
childResultDescriptions: []
|
|
666
677
|
}
|
|
667
678
|
}
|
|
668
679
|
}
|
|
669
680
|
|
|
670
|
-
private addEdges(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts:
|
|
681
|
+
private addEdges(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByLabel, path: number[], matches: Match[]): { queryDescription: QueryDescription, knownFacts: FactByLabel } {
|
|
671
682
|
for (const match of matches) {
|
|
672
683
|
for (const condition of match.conditions) {
|
|
673
684
|
if (condition.type === "path") {
|
|
@@ -701,7 +712,7 @@ class ResultDescriptionBuilder {
|
|
|
701
712
|
};
|
|
702
713
|
}
|
|
703
714
|
|
|
704
|
-
private addPathCondition(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts:
|
|
715
|
+
private addPathCondition(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByLabel, path: number[], unknown: Label, prefix: string, condition: PathCondition): { queryDescription: QueryDescription, knownFacts: FactByLabel } {
|
|
705
716
|
// If no input parameter has been allocated, allocate one now.
|
|
706
717
|
if (!knownFacts.hasOwnProperty(condition.labelRight)) {
|
|
707
718
|
const givenIndex = given.findIndex(given => given.name === condition.labelRight);
|
|
@@ -710,7 +721,7 @@ class ResultDescriptionBuilder {
|
|
|
710
721
|
}
|
|
711
722
|
const { queryDescription: newQueryDescription, factDescription } = queryDescription.withInputParameter(
|
|
712
723
|
given[givenIndex],
|
|
713
|
-
|
|
724
|
+
ensureGetFactTypeId(this.factTypes, start[givenIndex].type),
|
|
714
725
|
start[givenIndex].hash,
|
|
715
726
|
path
|
|
716
727
|
);
|
|
@@ -753,11 +764,11 @@ class ResultDescriptionBuilder {
|
|
|
753
764
|
}
|
|
754
765
|
else {
|
|
755
766
|
// If we have not written the fact, we need to write it now.
|
|
756
|
-
const { query, factIndex: predecessorFactIndex } = queryWithParameter.withFact(role.
|
|
767
|
+
const { query, factIndex: predecessorFactIndex } = queryWithParameter.withFact(role.predecessorType);
|
|
757
768
|
queryDescription = query.withEdge(predecessorFactIndex, factIndex, roleParameter, path);
|
|
758
769
|
factIndex = predecessorFactIndex;
|
|
759
770
|
}
|
|
760
|
-
type = role.
|
|
771
|
+
type = role.predecessorType;
|
|
761
772
|
}
|
|
762
773
|
|
|
763
774
|
const rightType = type;
|
|
@@ -785,7 +796,7 @@ class ResultDescriptionBuilder {
|
|
|
785
796
|
roleId,
|
|
786
797
|
declaringType: type
|
|
787
798
|
});
|
|
788
|
-
type = role.
|
|
799
|
+
type = role.predecessorType;
|
|
789
800
|
}
|
|
790
801
|
|
|
791
802
|
if (type !== rightType) {
|
|
@@ -823,7 +834,7 @@ function idsEqual(a: number[], b: number[]) {
|
|
|
823
834
|
return a.every((value, index) => value === b[index]);
|
|
824
835
|
}
|
|
825
836
|
|
|
826
|
-
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 {
|
|
827
838
|
const descriptionBuilder = new ResultDescriptionBuilder(factTypes, roleMap);
|
|
828
839
|
const description = descriptionBuilder.buildDescription(start, specification);
|
|
829
840
|
|
|
@@ -63,7 +63,7 @@ class QueryDescription {
|
|
|
63
63
|
const firstEdge = this.edges[0];
|
|
64
64
|
const predecessorFact = this.inputs.find(i => i.factIndex === firstEdge.predecessorFactIndex);
|
|
65
65
|
const successorFact = this.inputs.find(i => i.factIndex === firstEdge.successorFactIndex);
|
|
66
|
-
const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact
|
|
66
|
+
const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact!.factIndex;
|
|
67
67
|
const writtenFactIndexes = new Set<number>().add(firstFactIndex);
|
|
68
68
|
const joins: string[] = generateJoins(this.edges, writtenFactIndexes);
|
|
69
69
|
const inputWhereClauses = this.inputs
|
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
|
|