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.
- 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 -5
- package/dist/postgres/specification-result-sql.js +139 -112
- 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 +396 -0
- package/docs/sql/example.sql +49 -0
- package/docs/sql/first-path.gv +9 -0
- package/docs/sql/first-path.png +0 -0
- package/docs/sql/model.gv +20 -0
- package/docs/sql/model.png +0 -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 +200 -145
- 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 +292 -0
- 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
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
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { dehydrateReference, getAllFactTypes, getAllRoles, SpecificationParser } from "jinaga";
|
|
2
|
+
import { addFactType, addRole, emptyFactTypeMap, emptyRoleMap, getFactTypeId, getRoleId } from "../../src/postgres/maps";
|
|
3
|
+
import { resultSqlFromSpecification } from "../../src/postgres/specification-result-sql";
|
|
4
|
+
|
|
5
|
+
const company = dehydrateReference({ type: "Company" });
|
|
6
|
+
const companyHash = company.hash;
|
|
7
|
+
const user = dehydrateReference({ type: "Jinaga.User", publicKey: "PUBLIC KEY"});
|
|
8
|
+
const userHash = user.hash;
|
|
9
|
+
|
|
10
|
+
function parseSpecification(input: string) {
|
|
11
|
+
const parser = new SpecificationParser(input);
|
|
12
|
+
parser.skipWhitespace();
|
|
13
|
+
return parser.parseSpecification();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function sqlFor(descriptiveString: string, bookmarks: string[] = []) {
|
|
17
|
+
const specification = parseSpecification(descriptiveString);
|
|
18
|
+
const factTypeNames = getAllFactTypes(specification);
|
|
19
|
+
|
|
20
|
+
// Build a fact type map containing all fact types in the specification.
|
|
21
|
+
// Filter out fact types named "Unknown".
|
|
22
|
+
const factTypes = factTypeNames.filter(t => t !== 'Unknown').reduce(
|
|
23
|
+
(f, factType, i) => addFactType(f, factType, i + 1),
|
|
24
|
+
emptyFactTypeMap());
|
|
25
|
+
|
|
26
|
+
// Build a role map containing all roles in the specification.
|
|
27
|
+
// Filter out the roles named "unknown", and those of unknown fact types.
|
|
28
|
+
let roleMap = getAllRoles(specification).filter(r => r.name !== 'unknown').reduce(
|
|
29
|
+
(r, role, i) => {
|
|
30
|
+
const factTypeId = getFactTypeId(factTypes, role.successorType);
|
|
31
|
+
if (!factTypeId) {
|
|
32
|
+
return r;
|
|
33
|
+
}
|
|
34
|
+
return addRole(r, factTypeId, role.name, i + 1);
|
|
35
|
+
},
|
|
36
|
+
emptyRoleMap());
|
|
37
|
+
const start = specification.given.map(input => {
|
|
38
|
+
if (input.type === "Company") {
|
|
39
|
+
return company;
|
|
40
|
+
}
|
|
41
|
+
if (input.type === "Jinaga.User") {
|
|
42
|
+
return user;
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`Unknown input type ${input.type}`);
|
|
45
|
+
});
|
|
46
|
+
const composer = resultSqlFromSpecification(start, specification, factTypes, roleMap);
|
|
47
|
+
if (!composer) {
|
|
48
|
+
throw new Error("The specification is not satisfiable.");
|
|
49
|
+
}
|
|
50
|
+
return { composer, factTypes, roleMap };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function roleParameter(roleMap: Map<number, Map<string, number>>, factTypes: Map<string, number>, factTypeName: string, roleName: string): number {
|
|
54
|
+
const factTypeId = getFactTypeId(factTypes, factTypeName);
|
|
55
|
+
if (!factTypeId) {
|
|
56
|
+
throw new Error(`Unknown fact type ${factTypeName}`);
|
|
57
|
+
}
|
|
58
|
+
const roleId = getRoleId(roleMap, factTypeId, roleName);
|
|
59
|
+
if (!roleId) {
|
|
60
|
+
throw new Error(`Unknown role ${roleName} in fact type ${factTypeName}`);
|
|
61
|
+
}
|
|
62
|
+
return roleId;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe("Postgres read", () => {
|
|
66
|
+
it("should join successors", () => {
|
|
67
|
+
const { composer } = sqlFor(`
|
|
68
|
+
(company: Company) {
|
|
69
|
+
department: Department [
|
|
70
|
+
department->company: Company = company
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
`);
|
|
74
|
+
|
|
75
|
+
const sql = composer.getSqlQueries().sqlQuery.sql;
|
|
76
|
+
expect(sql).toEqual(
|
|
77
|
+
`SELECT f2.hash as hash2, f2.fact_id as id2, f2.data as data2 ` +
|
|
78
|
+
`FROM public.fact f1 ` +
|
|
79
|
+
`JOIN public.edge e1 ` +
|
|
80
|
+
`ON e1.predecessor_fact_id = f1.fact_id ` +
|
|
81
|
+
`AND e1.role_id = $3 ` +
|
|
82
|
+
`JOIN public.fact f2 ` +
|
|
83
|
+
`ON f2.fact_id = e1.successor_fact_id ` +
|
|
84
|
+
`WHERE f1.fact_type_id = $1 AND f1.hash = $2 ` +
|
|
85
|
+
`ORDER BY f2.fact_id ASC`
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should apply negative existential conditions", () => {
|
|
90
|
+
const { composer, factTypes, roleMap } = sqlFor(`
|
|
91
|
+
(company: Company) {
|
|
92
|
+
project: Project [
|
|
93
|
+
project->department: Department->company: Company = company
|
|
94
|
+
!E {
|
|
95
|
+
deleted: Project.Deleted [
|
|
96
|
+
deleted->project: Project = project
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
`);
|
|
102
|
+
|
|
103
|
+
const tree = composer.getSqlQueries();
|
|
104
|
+
const sql = tree.sqlQuery.sql;
|
|
105
|
+
expect(sql).toEqual(
|
|
106
|
+
`SELECT f3.hash as hash3, f3.fact_id as id3, f3.data as data3 ` +
|
|
107
|
+
`FROM public.fact f1 ` +
|
|
108
|
+
`JOIN public.edge e1 ` +
|
|
109
|
+
`ON e1.predecessor_fact_id = f1.fact_id ` +
|
|
110
|
+
`AND e1.role_id = $3 ` +
|
|
111
|
+
`JOIN public.fact f2 ` +
|
|
112
|
+
`ON f2.fact_id = e1.successor_fact_id ` +
|
|
113
|
+
`JOIN public.edge e2 ` +
|
|
114
|
+
`ON e2.predecessor_fact_id = f2.fact_id ` +
|
|
115
|
+
`AND e2.role_id = $4 ` +
|
|
116
|
+
`JOIN public.fact f3 ` +
|
|
117
|
+
`ON f3.fact_id = e2.successor_fact_id ` +
|
|
118
|
+
`WHERE f1.fact_type_id = $1 AND f1.hash = $2 ` +
|
|
119
|
+
`AND NOT EXISTS (` +
|
|
120
|
+
`SELECT 1 ` +
|
|
121
|
+
`FROM public.edge e3 ` +
|
|
122
|
+
`JOIN public.fact f4 ` +
|
|
123
|
+
`ON f4.fact_id = e3.successor_fact_id ` +
|
|
124
|
+
`WHERE e3.predecessor_fact_id = f3.fact_id ` +
|
|
125
|
+
`AND e3.role_id = $5` +
|
|
126
|
+
`) ` +
|
|
127
|
+
`ORDER BY f3.fact_id ASC`
|
|
128
|
+
);
|
|
129
|
+
expect(tree.sqlQuery.parameters).toEqual([
|
|
130
|
+
getFactTypeId(factTypes, 'Company'),
|
|
131
|
+
companyHash,
|
|
132
|
+
roleParameter(roleMap, factTypes, 'Department', 'company'),
|
|
133
|
+
roleParameter(roleMap, factTypes, 'Project', 'department'),
|
|
134
|
+
roleParameter(roleMap, factTypes, 'Project.Deleted', 'project')
|
|
135
|
+
]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should apply positive existential conditions", () => {
|
|
139
|
+
const { composer, factTypes, roleMap } = sqlFor(`
|
|
140
|
+
(company: Company) {
|
|
141
|
+
project: Project [
|
|
142
|
+
project->department: Department->company: Company = company
|
|
143
|
+
E {
|
|
144
|
+
deleted: Project.Deleted [
|
|
145
|
+
deleted->project: Project = project
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
`);
|
|
151
|
+
|
|
152
|
+
const tree = composer.getSqlQueries();
|
|
153
|
+
const sql = tree.sqlQuery.sql;
|
|
154
|
+
expect(sql).toEqual(
|
|
155
|
+
`SELECT f3.hash as hash3, f3.fact_id as id3, f3.data as data3 ` +
|
|
156
|
+
`FROM public.fact f1 ` +
|
|
157
|
+
`JOIN public.edge e1 ` +
|
|
158
|
+
`ON e1.predecessor_fact_id = f1.fact_id ` +
|
|
159
|
+
`AND e1.role_id = $3 ` +
|
|
160
|
+
`JOIN public.fact f2 ` +
|
|
161
|
+
`ON f2.fact_id = e1.successor_fact_id ` +
|
|
162
|
+
`JOIN public.edge e2 ` +
|
|
163
|
+
`ON e2.predecessor_fact_id = f2.fact_id ` +
|
|
164
|
+
`AND e2.role_id = $4 ` +
|
|
165
|
+
`JOIN public.fact f3 ` +
|
|
166
|
+
`ON f3.fact_id = e2.successor_fact_id ` +
|
|
167
|
+
`WHERE f1.fact_type_id = $1 AND f1.hash = $2 ` +
|
|
168
|
+
`AND EXISTS (` +
|
|
169
|
+
`SELECT 1 ` +
|
|
170
|
+
`FROM public.edge e3 ` +
|
|
171
|
+
`JOIN public.fact f4 ` +
|
|
172
|
+
`ON f4.fact_id = e3.successor_fact_id ` +
|
|
173
|
+
`WHERE e3.predecessor_fact_id = f3.fact_id ` +
|
|
174
|
+
`AND e3.role_id = $5` +
|
|
175
|
+
`) ` +
|
|
176
|
+
`ORDER BY f3.fact_id ASC`
|
|
177
|
+
);
|
|
178
|
+
expect(tree.sqlQuery.parameters).toEqual([
|
|
179
|
+
getFactTypeId(factTypes, 'Company'),
|
|
180
|
+
companyHash,
|
|
181
|
+
roleParameter(roleMap, factTypes, 'Department', 'company'),
|
|
182
|
+
roleParameter(roleMap, factTypes, 'Project', 'department'),
|
|
183
|
+
roleParameter(roleMap, factTypes, 'Project.Deleted', 'project')
|
|
184
|
+
]);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should read complex specifications", () => {
|
|
188
|
+
const { composer, factTypes, roleMap } = sqlFor(`
|
|
189
|
+
(company: Company, user: Jinaga.User) {
|
|
190
|
+
project: Project [
|
|
191
|
+
project->department: Department->company: Company = company
|
|
192
|
+
!E {
|
|
193
|
+
deleted: Project.Deleted [
|
|
194
|
+
deleted->project: Project = project
|
|
195
|
+
!E {
|
|
196
|
+
restored: Project.Restored [
|
|
197
|
+
restored->deleted: Project.Deleted = deleted
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
E {
|
|
203
|
+
assignment: Project.Assignment [
|
|
204
|
+
assignment->project: Project = project
|
|
205
|
+
assignment->user: Jinaga.User = user
|
|
206
|
+
!E {
|
|
207
|
+
revoked: Project.Assignment.Revoked [
|
|
208
|
+
revoked->assignment: Project.Assignment = assignment
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
deliverable: Deliverable [
|
|
215
|
+
deliverable->project: Project = project
|
|
216
|
+
]
|
|
217
|
+
} => {
|
|
218
|
+
assignedTasks = {
|
|
219
|
+
task: Task [
|
|
220
|
+
task->deliverable: Deliverable = deliverable
|
|
221
|
+
E {
|
|
222
|
+
assignment: Task.Assignment [
|
|
223
|
+
assignment->task: Task = task
|
|
224
|
+
assignment->user: Jinaga.User = user
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
} => {
|
|
229
|
+
number = task.number
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
`);
|
|
233
|
+
|
|
234
|
+
const queries = composer.getSqlQueries();
|
|
235
|
+
expect(queries.sqlQuery.sql).toEqual(
|
|
236
|
+
`SELECT ` +
|
|
237
|
+
`f3.hash as hash3, f3.fact_id as id3, f3.data as data3, ` +
|
|
238
|
+
`f9.hash as hash9, f9.fact_id as id9, f9.data as data9 ` +
|
|
239
|
+
`FROM public.fact f1 ` +
|
|
240
|
+
`JOIN public.edge e1 ` +
|
|
241
|
+
`ON e1.predecessor_fact_id = f1.fact_id ` +
|
|
242
|
+
`AND e1.role_id = $3 ` +
|
|
243
|
+
`JOIN public.fact f2 ` +
|
|
244
|
+
`ON f2.fact_id = e1.successor_fact_id ` +
|
|
245
|
+
`JOIN public.edge e2 ` +
|
|
246
|
+
`ON e2.predecessor_fact_id = f2.fact_id ` +
|
|
247
|
+
`AND e2.role_id = $4 ` +
|
|
248
|
+
`JOIN public.fact f3 ` +
|
|
249
|
+
`ON f3.fact_id = e2.successor_fact_id ` +
|
|
250
|
+
`JOIN public.edge e8 ` +
|
|
251
|
+
`ON e8.predecessor_fact_id = f3.fact_id ` +
|
|
252
|
+
`AND e8.role_id = $12 ` +
|
|
253
|
+
`JOIN public.fact f9 ` +
|
|
254
|
+
`ON f9.fact_id = e8.successor_fact_id ` +
|
|
255
|
+
`WHERE f1.fact_type_id = $1 AND f1.hash = $2 ` +
|
|
256
|
+
`AND NOT EXISTS (` +
|
|
257
|
+
`SELECT 1 ` +
|
|
258
|
+
`FROM public.edge e3 ` +
|
|
259
|
+
`JOIN public.fact f4 ` +
|
|
260
|
+
`ON f4.fact_id = e3.successor_fact_id ` +
|
|
261
|
+
`WHERE e3.predecessor_fact_id = f3.fact_id ` +
|
|
262
|
+
`AND e3.role_id = $5 ` +
|
|
263
|
+
`AND NOT EXISTS (` +
|
|
264
|
+
`SELECT 1 ` +
|
|
265
|
+
`FROM public.edge e4 ` +
|
|
266
|
+
`JOIN public.fact f5 ` +
|
|
267
|
+
`ON f5.fact_id = e4.successor_fact_id ` +
|
|
268
|
+
`WHERE e4.predecessor_fact_id = f4.fact_id AND e4.role_id = $6` +
|
|
269
|
+
`)` +
|
|
270
|
+
`) AND EXISTS (` +
|
|
271
|
+
`SELECT 1 ` +
|
|
272
|
+
`FROM public.edge e5 ` +
|
|
273
|
+
`JOIN public.fact f6 ` +
|
|
274
|
+
`ON f6.fact_id = e5.successor_fact_id ` +
|
|
275
|
+
`JOIN public.edge e6 ` +
|
|
276
|
+
`ON e6.successor_fact_id = f6.fact_id ` +
|
|
277
|
+
`AND e6.role_id = $10 ` +
|
|
278
|
+
`JOIN public.fact f7 ` +
|
|
279
|
+
`ON f7.fact_id = e6.predecessor_fact_id ` +
|
|
280
|
+
`WHERE e5.predecessor_fact_id = f3.fact_id AND e5.role_id = $7 ` +
|
|
281
|
+
`AND f7.fact_type_id = $8 AND f7.hash = $9 ` +
|
|
282
|
+
`AND NOT EXISTS (` +
|
|
283
|
+
`SELECT 1 ` +
|
|
284
|
+
`FROM public.edge e7 ` +
|
|
285
|
+
`JOIN public.fact f8 ` +
|
|
286
|
+
`ON f8.fact_id = e7.successor_fact_id ` +
|
|
287
|
+
`WHERE e7.predecessor_fact_id = f6.fact_id AND e7.role_id = $11` +
|
|
288
|
+
`)` +
|
|
289
|
+
`) ` +
|
|
290
|
+
`ORDER BY f3.fact_id ASC, f9.fact_id ASC`);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
Step,
|
|
11
11
|
} from "jinaga";
|
|
12
12
|
|
|
13
|
-
import { addFactType, addRole, emptyFactTypeMap, emptyRoleMap, getFactTypeId, getRoleId } from "../../src/postgres/maps";
|
|
13
|
+
import { addFactType, addRole, emptyFactTypeMap, emptyRoleMap, ensureGetFactTypeId, getFactTypeId, getRoleId } from "../../src/postgres/maps";
|
|
14
14
|
import { sqlFromSteps } from "../../src/postgres/sql";
|
|
15
15
|
import { distinct } from "../../src/util/fn";
|
|
16
16
|
|
|
@@ -24,8 +24,8 @@ describe('Postgres', () => {
|
|
|
24
24
|
const factTypes = allFactTypes(query.steps).filter(t => t !== 'Unknown').reduce(
|
|
25
25
|
(f, factType, i) => addFactType(f, factType, i + 1),
|
|
26
26
|
emptyFactTypeMap());
|
|
27
|
-
let roleMap = allRoles(query.steps, 'Root').filter(r => r.role !== 'unknown').reduce(
|
|
28
|
-
(r, role, i) => addRole(r,
|
|
27
|
+
let roleMap = allRoles(query.steps, 'Root').filter(r => r.type !== 'Unknown' && r.role !== 'unknown').reduce(
|
|
28
|
+
(r, role, i) => addRole(r, ensureGetFactTypeId(factTypes, role.type), role.role, i + 1),
|
|
29
29
|
emptyRoleMap());
|
|
30
30
|
const sqlQuery = sqlFromSteps(start, query.steps, factTypes, roleMap);
|
|
31
31
|
return sqlQuery ? { sql: sqlQuery.sql, parameters: sqlQuery.parameters, pathLength: sqlQuery.pathLength, empty: sqlQuery.empty, factTypes, roleMap } : null;
|
|
@@ -61,7 +61,7 @@ describe('Postgres', () => {
|
|
|
61
61
|
);
|
|
62
62
|
expect(parameters[0]).toEqual(getFactTypeId(factTypes, 'Root'));
|
|
63
63
|
expect(parameters[1]).toEqual(startHash);
|
|
64
|
-
expect(parameters[2]).toEqual(getRoleId(roleMap,
|
|
64
|
+
expect(parameters[2]).toEqual(getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'IntegrationTest.Successor'), 'predecessor'));
|
|
65
65
|
expect(pathLength).toEqual(1);
|
|
66
66
|
});
|
|
67
67
|
|
|
@@ -76,7 +76,7 @@ describe('Postgres', () => {
|
|
|
76
76
|
);
|
|
77
77
|
expect(parameters[0]).toEqual(getFactTypeId(factTypes, 'Root'));
|
|
78
78
|
expect(parameters[1]).toEqual(startHash);
|
|
79
|
-
expect(parameters[2]).toEqual(getRoleId(roleMap,
|
|
79
|
+
expect(parameters[2]).toEqual(getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Root'), 'parent'));
|
|
80
80
|
expect(pathLength).toEqual(1);
|
|
81
81
|
});
|
|
82
82
|
|
|
@@ -94,8 +94,8 @@ describe('Postgres', () => {
|
|
|
94
94
|
);
|
|
95
95
|
expect(parameters[0]).toEqual(getFactTypeId(factTypes, 'Root'));
|
|
96
96
|
expect(parameters[1]).toEqual(startHash);
|
|
97
|
-
expect(parameters[2]).toEqual(getRoleId(roleMap,
|
|
98
|
-
expect(parameters[3]).toEqual(getRoleId(roleMap,
|
|
97
|
+
expect(parameters[2]).toEqual(getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'IntegrationTest.Successor'), 'predecessor'));
|
|
98
|
+
expect(parameters[3]).toEqual(getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'IntegrationTest.Grandchild'), 'successor'));
|
|
99
99
|
expect(pathLength).toEqual(1);
|
|
100
100
|
});
|
|
101
101
|
|
|
@@ -113,8 +113,8 @@ describe('Postgres', () => {
|
|
|
113
113
|
);
|
|
114
114
|
expect(parameters[0]).toEqual(getFactTypeId(factTypes, 'Root'));
|
|
115
115
|
expect(parameters[1]).toEqual(startHash);
|
|
116
|
-
expect(parameters[2]).toEqual(getRoleId(roleMap,
|
|
117
|
-
expect(parameters[3]).toEqual(getRoleId(roleMap,
|
|
116
|
+
expect(parameters[2]).toEqual(getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'IntegrationTest.Successor'), 'predecessor'));
|
|
117
|
+
expect(parameters[3]).toEqual(getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'IntegrationTest.Grandchild'), 'successor'));
|
|
118
118
|
expect(pathLength).toEqual(1);
|
|
119
119
|
});
|
|
120
120
|
|
|
@@ -132,8 +132,8 @@ describe('Postgres', () => {
|
|
|
132
132
|
);
|
|
133
133
|
expect(parameters[0]).toEqual(getFactTypeId(factTypes, 'Root'));
|
|
134
134
|
expect(parameters[1]).toEqual(startHash);
|
|
135
|
-
expect(parameters[2]).toEqual(getRoleId(roleMap,
|
|
136
|
-
expect(parameters[3]).toEqual(getRoleId(roleMap,
|
|
135
|
+
expect(parameters[2]).toEqual(getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Child'), 'parent'));
|
|
136
|
+
expect(parameters[3]).toEqual(getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Child'), 'uncle'));
|
|
137
137
|
expect(pathLength).toEqual(1);
|
|
138
138
|
});
|
|
139
139
|
|
|
@@ -151,8 +151,8 @@ describe('Postgres', () => {
|
|
|
151
151
|
);
|
|
152
152
|
expect(parameters[0]).toEqual(getFactTypeId(factTypes, 'Root'));
|
|
153
153
|
expect(parameters[1]).toEqual(startHash);
|
|
154
|
-
expect(parameters[2]).toEqual(getRoleId(roleMap,
|
|
155
|
-
expect(parameters[3]).toEqual(getRoleId(roleMap,
|
|
154
|
+
expect(parameters[2]).toEqual(getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Child'), 'parent'));
|
|
155
|
+
expect(parameters[3]).toEqual(getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Child'), 'uncle'));
|
|
156
156
|
expect(pathLength).toEqual(1);
|
|
157
157
|
});
|
|
158
158
|
|
|
@@ -174,9 +174,9 @@ describe('Postgres', () => {
|
|
|
174
174
|
expect(parameters).toEqual([
|
|
175
175
|
getFactTypeId(factTypes, 'Root'),
|
|
176
176
|
startHash,
|
|
177
|
-
getRoleId(roleMap,
|
|
178
|
-
getRoleId(roleMap,
|
|
179
|
-
getRoleId(roleMap,
|
|
177
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Child'), 'parent'),
|
|
178
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Condition'), 'condition'),
|
|
179
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Other'), 'other')
|
|
180
180
|
]);
|
|
181
181
|
});
|
|
182
182
|
|
|
@@ -200,10 +200,10 @@ describe('Postgres', () => {
|
|
|
200
200
|
expect(parameters).toEqual([
|
|
201
201
|
getFactTypeId(factTypes, 'Root'),
|
|
202
202
|
startHash,
|
|
203
|
-
getRoleId(roleMap,
|
|
204
|
-
getRoleId(roleMap,
|
|
205
|
-
getRoleId(roleMap,
|
|
206
|
-
getRoleId(roleMap,
|
|
203
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Identifier'), 'root'),
|
|
204
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Identifier'), 'prior'),
|
|
205
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Identifier'), 'identified'),
|
|
206
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Delete'), 'identified')
|
|
207
207
|
]);
|
|
208
208
|
});
|
|
209
209
|
|
|
@@ -230,10 +230,10 @@ describe('Postgres', () => {
|
|
|
230
230
|
expect(parameters).toEqual([
|
|
231
231
|
getFactTypeId(factTypes, 'Root'),
|
|
232
232
|
startHash,
|
|
233
|
-
getRoleId(roleMap,
|
|
234
|
-
getRoleId(roleMap,
|
|
235
|
-
getRoleId(roleMap,
|
|
236
|
-
getRoleId(roleMap,
|
|
233
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Assignment'), 'user'),
|
|
234
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Assignment'), 'project'),
|
|
235
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Task'), 'project'),
|
|
236
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Task.Title'), 'task')
|
|
237
237
|
]);
|
|
238
238
|
expect(pathLength).toEqual(4);
|
|
239
239
|
});
|
|
@@ -281,7 +281,7 @@ describe('Postgres', () => {
|
|
|
281
281
|
expect(parameters).toEqual([
|
|
282
282
|
getFactTypeId(factTypes, 'Root'),
|
|
283
283
|
startHash,
|
|
284
|
-
getRoleId(roleMap,
|
|
284
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Assignment'), 'root')
|
|
285
285
|
]);
|
|
286
286
|
});
|
|
287
287
|
|
|
@@ -298,7 +298,7 @@ describe('Postgres', () => {
|
|
|
298
298
|
expect(parameters).toEqual([
|
|
299
299
|
getFactTypeId(factTypes, 'Root'),
|
|
300
300
|
startHash,
|
|
301
|
-
getRoleId(roleMap,
|
|
301
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'Assignment'), 'root')
|
|
302
302
|
]);
|
|
303
303
|
});
|
|
304
304
|
|
|
@@ -332,11 +332,11 @@ describe('Postgres', () => {
|
|
|
332
332
|
')' +
|
|
333
333
|
')');
|
|
334
334
|
expect(parameters).toEqual([
|
|
335
|
-
getFactTypeId(factTypes, '
|
|
335
|
+
getFactTypeId(factTypes, 'Root'),
|
|
336
336
|
startHash,
|
|
337
|
-
getRoleId(roleMap,
|
|
338
|
-
getRoleId(roleMap,
|
|
339
|
-
getRoleId(roleMap,
|
|
337
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'ImprovingU.Idea'), 'catalog'),
|
|
338
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'ImprovingU.Idea.Deletion'), 'idea'),
|
|
339
|
+
getRoleId(roleMap, ensureGetFactTypeId(factTypes, 'ImprovingU.Idea.Restore'), 'ideaDeletion')
|
|
340
340
|
]);
|
|
341
341
|
});
|
|
342
342
|
});
|
|
@@ -348,7 +348,7 @@ function allFactTypes(steps: Step[]): string[] {
|
|
|
348
348
|
const childFactTypes = steps
|
|
349
349
|
.filter(step => step instanceof ExistentialCondition)
|
|
350
350
|
.flatMap(step => allFactTypes((step as ExistentialCondition).steps));
|
|
351
|
-
return [...factTypes, ...childFactTypes].filter(distinct);
|
|
351
|
+
return ["Root", ...factTypes, ...childFactTypes].filter(distinct);
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
function allRoles(steps: Step[], initialType: string) {
|
|
@@ -28,7 +28,7 @@ function sqlFor(descriptiveString: string, bookmarks: string[] = []) {
|
|
|
28
28
|
// Filter out the roles named "unknown", and those of unknown fact types.
|
|
29
29
|
let roleMap = getAllRoles(specification).filter(r => r.name !== 'unknown').reduce(
|
|
30
30
|
(r, role, i) => {
|
|
31
|
-
const factTypeId = getFactTypeId(factTypes, role.
|
|
31
|
+
const factTypeId = getFactTypeId(factTypes, role.successorType);
|
|
32
32
|
if (!factTypeId) {
|
|
33
33
|
return r;
|
|
34
34
|
}
|
package/tsconfig.json
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "commonjs",
|
|
4
|
+
"target": "es6",
|
|
5
|
+
"lib": [ "ES2019" ],
|
|
6
|
+
"noImplicitAny": true,
|
|
7
|
+
"moduleResolution": "node",
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"strict": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"esModuleInterop": true,
|
|
15
|
+
"baseUrl": ".",
|
|
16
|
+
"paths": {
|
|
17
|
+
"*": [
|
|
18
|
+
"./node_modules/*",
|
|
19
|
+
"./types/*"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"include": [
|
|
24
|
+
"test/**/*.ts"
|
|
25
|
+
]
|
|
26
|
+
}
|
package/docs/specification.txt
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
let user: Jinaga.User = me
|
|
2
|
-
let semester: ImprovingU.Semester = #Ws6RWV8bWmP7ibBlzwL2UoKKwg6E1iItQPFpX/mvU5u6FLwHJAbRd0SbH+P1hoMn4QndhArLqJqVReRqJdoyBg==
|
|
3
|
-
|
|
4
|
-
(semester: ImprovingU.Semester, user: Jinaga.User) {
|
|
5
|
-
catalog: ImprovingU.Catalog [
|
|
6
|
-
catalog->_in:ImprovingU.Semester = semester
|
|
7
|
-
]
|
|
8
|
-
} => {
|
|
9
|
-
office = catalog.office
|
|
10
|
-
courses = {
|
|
11
|
-
course: ImprovingU.Course [
|
|
12
|
-
course->_in:ImprovingU.Catalog = catalog
|
|
13
|
-
!E {
|
|
14
|
-
delete: ImprovingU.Course.Delete [
|
|
15
|
-
delete->course:ImprovingU.Course = course
|
|
16
|
-
]
|
|
17
|
-
}
|
|
18
|
-
]
|
|
19
|
-
} => {
|
|
20
|
-
titles = {
|
|
21
|
-
title: ImprovingU.Course.Title [
|
|
22
|
-
title->course: ImprovingU.Course = course
|
|
23
|
-
!E {
|
|
24
|
-
next: ImprovingU.Course.Title [
|
|
25
|
-
next->prior: ImprovingU.Course.Title = title
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
]
|
|
29
|
-
} => title.value
|
|
30
|
-
instructors = {
|
|
31
|
-
instructor: ImprovingU.Course.Instructor [
|
|
32
|
-
instructor->course: ImprovingU.Course = course
|
|
33
|
-
!E {
|
|
34
|
-
next: ImprovingU.Course.Instructor [
|
|
35
|
-
next->prior: ImprovingU.Course.Instructor = instructor
|
|
36
|
-
]
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
} => instructor.value
|
|
40
|
-
}
|
|
41
|
-
}
|