jinaga-server 1.0.0 → 1.1.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 +103 -0
- package/dist/authentication/authentication-device.d.ts +3 -1
- package/dist/authentication/authentication-device.js +3 -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 +3 -0
- package/dist/authentication/authentication-impl.js.map +1 -1
- package/dist/authentication/authentication-session.d.ts +4 -2
- package/dist/authentication/authentication-session.js +7 -2
- package/dist/authentication/authentication-session.js.map +1 -1
- package/dist/authentication.d.ts +5 -4
- package/dist/authentication.js +3 -0
- package/dist/authentication.js.map +1 -1
- package/dist/authorization/authorization-keystore.d.ts +5 -3
- package/dist/authorization/authorization-keystore.js +12 -9
- package/dist/authorization/authorization-keystore.js.map +1 -1
- package/dist/http/feed-cache.d.ts +5 -0
- package/dist/http/feed-cache.js +3 -0
- package/dist/http/feed-cache.js.map +1 -0
- package/dist/http/node-http.js +4 -1
- package/dist/http/node-http.js.map +1 -1
- package/dist/http/router.d.ts +5 -1
- package/dist/http/router.js +157 -24
- package/dist/http/router.js.map +1 -1
- package/dist/jinaga-server.js +6 -4
- package/dist/jinaga-server.js.map +1 -1
- package/dist/memory/memory-feed-cache.d.ts +7 -0
- package/dist/memory/memory-feed-cache.js +17 -0
- package/dist/memory/memory-feed-cache.js.map +1 -0
- package/dist/postgres/postgres-store.d.ts +5 -2
- package/dist/postgres/postgres-store.js +53 -28
- package/dist/postgres/postgres-store.js.map +1 -1
- package/dist/postgres/specification-result-sql.d.ts +11 -4
- package/dist/postgres/specification-result-sql.js +302 -5
- package/dist/postgres/specification-result-sql.js.map +1 -1
- package/dist/postgres/specification-sql.d.ts +14 -3
- package/dist/postgres/specification-sql.js +233 -100
- package/dist/postgres/specification-sql.js.map +1 -1
- package/package.json +2 -2
- package/src/authentication/authentication-device.ts +6 -0
- package/src/authentication/authentication-impl.ts +6 -3
- package/src/authentication/authentication-session.ts +11 -3
- package/src/authentication.ts +18 -5
- package/src/authorization/authorization-keystore.ts +14 -8
- package/src/http/feed-cache.ts +6 -0
- package/src/http/node-http.ts +5 -2
- package/src/http/router.ts +179 -31
- package/src/jinaga-server.ts +11 -9
- package/src/memory/memory-feed-cache.ts +15 -0
- package/src/postgres/postgres-store.ts +59 -31
- package/src/postgres/specification-result-sql.ts +450 -10
- package/src/postgres/specification-sql.ts +335 -115
- package/test/authorization/authorizationSpec.ts +11 -2
- package/test/postgres/postgresSpec.ts +31 -25
- package/test/postgres/postgresSpecificationSpec.ts +40 -68
- package/dist/postgres/query-description-builder.d.ts +0 -13
- package/dist/postgres/query-description-builder.js +0 -104
- package/dist/postgres/query-description-builder.js.map +0 -1
- package/dist/postgres/query-description.d.ts +0 -71
- package/dist/postgres/query-description.js +0 -256
- package/dist/postgres/query-description.js.map +0 -1
- package/src/postgres/query-description-builder.ts +0 -116
- package/src/postgres/query-description.ts +0 -385
|
@@ -51,7 +51,7 @@ describe('Postgres', () => {
|
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
it('should parse successor query with type', () => {
|
|
54
|
-
const { sql, parameters, pathLength, factTypes, roleMap } = sqlFor('S.predecessor F.type="IntegrationTest.Successor"')
|
|
54
|
+
const { sql, parameters, pathLength, factTypes, roleMap } = sqlFor('S.predecessor F.type="IntegrationTest.Successor"')!;
|
|
55
55
|
expect(sql).toEqual(
|
|
56
56
|
'SELECT f2.hash as hash2 ' +
|
|
57
57
|
'FROM public.fact f1 ' +
|
|
@@ -66,7 +66,7 @@ describe('Postgres', () => {
|
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
it('should parse predecessor query with type', () => {
|
|
69
|
-
const { sql, parameters, pathLength, factTypes, roleMap } = sqlFor('P.parent F.type="Parent"')
|
|
69
|
+
const { sql, parameters, pathLength, factTypes, roleMap } = sqlFor('P.parent F.type="Parent"')!;
|
|
70
70
|
expect(sql).toEqual(
|
|
71
71
|
'SELECT f2.hash as hash2 ' +
|
|
72
72
|
'FROM public.fact f1 ' +
|
|
@@ -81,7 +81,7 @@ describe('Postgres', () => {
|
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
it('should parse successor query with existential', () => {
|
|
84
|
-
const { sql, parameters, pathLength, factTypes, roleMap } = sqlFor('S.predecessor F.type="IntegrationTest.Successor" E(S.successor F.type="IntegrationTest.Grandchild")')
|
|
84
|
+
const { sql, parameters, pathLength, factTypes, roleMap } = sqlFor('S.predecessor F.type="IntegrationTest.Successor" E(S.successor F.type="IntegrationTest.Grandchild")')!;
|
|
85
85
|
expect(sql).toEqual(
|
|
86
86
|
'SELECT f2.hash as hash2 ' +
|
|
87
87
|
'FROM public.fact f1 ' +
|
|
@@ -100,7 +100,7 @@ describe('Postgres', () => {
|
|
|
100
100
|
});
|
|
101
101
|
|
|
102
102
|
it('should parse successor query with negative existential', () => {
|
|
103
|
-
const { sql, parameters, pathLength, factTypes, roleMap } = sqlFor('S.predecessor F.type="IntegrationTest.Successor" N(S.successor F.type="IntegrationTest.Grandchild")')
|
|
103
|
+
const { sql, parameters, pathLength, factTypes, roleMap } = sqlFor('S.predecessor F.type="IntegrationTest.Successor" N(S.successor F.type="IntegrationTest.Grandchild")')!;
|
|
104
104
|
expect(sql).toEqual(
|
|
105
105
|
'SELECT f2.hash as hash2 ' +
|
|
106
106
|
'FROM public.fact f1 ' +
|
|
@@ -119,7 +119,7 @@ describe('Postgres', () => {
|
|
|
119
119
|
});
|
|
120
120
|
|
|
121
121
|
it('should parse successor query with existential predecessor', () => {
|
|
122
|
-
const { sql, parameters, pathLength, factTypes, roleMap } = sqlFor('S.parent F.type="Child" E(P.uncle F.type="Uncle")')
|
|
122
|
+
const { sql, parameters, pathLength, factTypes, roleMap } = sqlFor('S.parent F.type="Child" E(P.uncle F.type="Uncle")')!;
|
|
123
123
|
expect(sql).toEqual(
|
|
124
124
|
'SELECT f2.hash as hash2 ' +
|
|
125
125
|
'FROM public.fact f1 ' +
|
|
@@ -138,7 +138,7 @@ describe('Postgres', () => {
|
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
it('should parse successor query with negative existential predecessor', () => {
|
|
141
|
-
const { sql, parameters, pathLength, factTypes, roleMap } = sqlFor('S.parent F.type="Child" N(P.uncle F.type="Uncle")')
|
|
141
|
+
const { sql, parameters, pathLength, factTypes, roleMap } = sqlFor('S.parent F.type="Child" N(P.uncle F.type="Uncle")')!;
|
|
142
142
|
expect(sql).toEqual(
|
|
143
143
|
'SELECT f2.hash as hash2 ' +
|
|
144
144
|
'FROM public.fact f1 ' +
|
|
@@ -157,7 +157,7 @@ describe('Postgres', () => {
|
|
|
157
157
|
});
|
|
158
158
|
|
|
159
159
|
it('should parse consecutive existential queries', () => {
|
|
160
|
-
const { sql, parameters, factTypes, roleMap } = sqlFor('S.parent F.type="Child" N(S.condition F.type="Condition") N(S.other F.type="Other")')
|
|
160
|
+
const { sql, parameters, factTypes, roleMap } = sqlFor('S.parent F.type="Child" N(S.condition F.type="Condition") N(S.other F.type="Other")')!;
|
|
161
161
|
expect(sql).toEqual(
|
|
162
162
|
'SELECT f2.hash as hash2 ' +
|
|
163
163
|
'FROM public.fact f1 ' +
|
|
@@ -181,7 +181,7 @@ describe('Postgres', () => {
|
|
|
181
181
|
});
|
|
182
182
|
|
|
183
183
|
it('should parse existential query form predecessor', () => {
|
|
184
|
-
const { sql, parameters, factTypes, roleMap } = sqlFor('S.root F.type="Identifier" N(S.prior F.type="Identifier") P.identified F.type="Identified" N(S.identified F.type="Delete")')
|
|
184
|
+
const { sql, parameters, factTypes, roleMap } = sqlFor('S.root F.type="Identifier" N(S.prior F.type="Identifier") P.identified F.type="Identified" N(S.identified F.type="Delete")')!;
|
|
185
185
|
expect(sql).toEqual(
|
|
186
186
|
'SELECT f2.hash as hash2, f3.hash as hash3 ' +
|
|
187
187
|
'FROM public.fact f1 ' +
|
|
@@ -213,7 +213,7 @@ describe('Postgres', () => {
|
|
|
213
213
|
});
|
|
214
214
|
|
|
215
215
|
it('should parse zig-zag pipeline', () => {
|
|
216
|
-
const { sql, parameters, pathLength, factTypes, roleMap } = sqlFor('S.user F.type="Assignment" P.project F.type="Project" S.project F.type="Task" S.task F.type="Task.Title"')
|
|
216
|
+
const { sql, parameters, pathLength, factTypes, roleMap } = sqlFor('S.user F.type="Assignment" P.project F.type="Project" S.project F.type="Task" S.task F.type="Task.Title"')!;
|
|
217
217
|
expect(sql).toEqual(
|
|
218
218
|
'SELECT f2.hash as hash2, f3.hash as hash3, f4.hash as hash4, f5.hash as hash5 ' +
|
|
219
219
|
'FROM public.fact f1 ' +
|
|
@@ -239,37 +239,37 @@ describe('Postgres', () => {
|
|
|
239
239
|
});
|
|
240
240
|
|
|
241
241
|
it('should parse query with unknown successor type', () => {
|
|
242
|
-
const { empty } = sqlFor('S.root F.type="Unknown"')
|
|
242
|
+
const { empty } = sqlFor('S.root F.type="Unknown"')!;
|
|
243
243
|
expect(empty).toBeTruthy();
|
|
244
244
|
});
|
|
245
245
|
|
|
246
246
|
it('should parse query with unknown predecessor type', () => {
|
|
247
|
-
const { empty } = sqlFor('S.root F.type="Assignment" P.pred F.type="Unknown" P.user F.type="Jinaga.User"')
|
|
247
|
+
const { empty } = sqlFor('S.root F.type="Assignment" P.pred F.type="Unknown" P.user F.type="Jinaga.User"')!;
|
|
248
248
|
expect(empty).toBeTruthy();
|
|
249
249
|
});
|
|
250
250
|
|
|
251
251
|
it('should parse query with some unknown types', () => {
|
|
252
|
-
const { empty } = sqlFor('S.root F.type="Unknown" P.user F.type="Jinaga.User"')
|
|
252
|
+
const { empty } = sqlFor('S.root F.type="Unknown" P.user F.type="Jinaga.User"')!;
|
|
253
253
|
expect(empty).toBeTruthy();
|
|
254
254
|
});
|
|
255
255
|
|
|
256
256
|
it('should parse query with unknown successor role', () => {
|
|
257
|
-
const { empty } = sqlFor('S.unknown F.type="Successor"')
|
|
257
|
+
const { empty } = sqlFor('S.unknown F.type="Successor"')!;
|
|
258
258
|
expect(empty).toBeTruthy();
|
|
259
259
|
});
|
|
260
260
|
|
|
261
261
|
it('should parse query with unknown predecessor role', () => {
|
|
262
|
-
const { empty } = sqlFor('S.root F.type="Assignment" P.unknown F.type="Jinaga.User"')
|
|
262
|
+
const { empty } = sqlFor('S.root F.type="Assignment" P.unknown F.type="Jinaga.User"')!;
|
|
263
263
|
expect(empty).toBeTruthy();
|
|
264
264
|
});
|
|
265
265
|
|
|
266
266
|
it('should parse query with some unknown predecessor roles', () => {
|
|
267
|
-
const { empty } = sqlFor('S.root F.type="Assignment" P.predecessor F.type="Predecessor" P.unknown F.type="Jinaga.User"')
|
|
267
|
+
const { empty } = sqlFor('S.root F.type="Assignment" P.predecessor F.type="Predecessor" P.unknown F.type="Jinaga.User"')!;
|
|
268
268
|
expect(empty).toBeTruthy();
|
|
269
269
|
});
|
|
270
270
|
|
|
271
271
|
it('should ignore negative existential with unknown type', () => {
|
|
272
|
-
const { empty, sql, parameters, factTypes, roleMap } = sqlFor('S.root F.type="Assignment" N(S.assignment F.type="Unknown")')
|
|
272
|
+
const { empty, sql, parameters, factTypes, roleMap } = sqlFor('S.root F.type="Assignment" N(S.assignment F.type="Unknown")')!;
|
|
273
273
|
expect(empty).toBeFalsy();
|
|
274
274
|
expect(sql).toEqual(
|
|
275
275
|
'SELECT f2.hash as hash2 ' +
|
|
@@ -286,7 +286,7 @@ describe('Postgres', () => {
|
|
|
286
286
|
});
|
|
287
287
|
|
|
288
288
|
it('should ignore negative existential with unknown role', () => {
|
|
289
|
-
const { empty, sql, parameters, factTypes, roleMap } = sqlFor('S.root F.type="Assignment" N(S.unknown F.type="Assignment.Delete")')
|
|
289
|
+
const { empty, sql, parameters, factTypes, roleMap } = sqlFor('S.root F.type="Assignment" N(S.unknown F.type="Assignment.Delete")')!;
|
|
290
290
|
expect(empty).toBeFalsy();
|
|
291
291
|
expect(sql).toEqual(
|
|
292
292
|
'SELECT f2.hash as hash2 ' +
|
|
@@ -303,17 +303,17 @@ describe('Postgres', () => {
|
|
|
303
303
|
});
|
|
304
304
|
|
|
305
305
|
it('should elevate positive existential with unknown type', () => {
|
|
306
|
-
const { empty } = sqlFor('S.root F.type="Assignment" E(S.assignment F.type="Unknown")')
|
|
306
|
+
const { empty } = sqlFor('S.root F.type="Assignment" E(S.assignment F.type="Unknown")')!;
|
|
307
307
|
expect(empty).toBeTruthy();
|
|
308
308
|
});
|
|
309
309
|
|
|
310
310
|
it('should elevate positive existential with unknown role', () => {
|
|
311
|
-
const { empty } = sqlFor('S.root F.type="Assignment" E(S.unknown F.type="Assignment.Delete")')
|
|
311
|
+
const { empty } = sqlFor('S.root F.type="Assignment" E(S.unknown F.type="Assignment.Delete")')!;
|
|
312
312
|
expect(empty).toBeTruthy();
|
|
313
313
|
});
|
|
314
314
|
|
|
315
315
|
it('should parse nested existential condition', () => {
|
|
316
|
-
const { sql, parameters, factTypes, roleMap } = sqlFor('S.catalog F.type="ImprovingU.Idea" N(S.idea F.type="ImprovingU.Idea.Deletion" N(S.ideaDeletion F.type="ImprovingU.Idea.Restore"))')
|
|
316
|
+
const { sql, parameters, factTypes, roleMap } = sqlFor('S.catalog F.type="ImprovingU.Idea" N(S.idea F.type="ImprovingU.Idea.Deletion" N(S.ideaDeletion F.type="ImprovingU.Idea.Restore"))')!;
|
|
317
317
|
|
|
318
318
|
expect(sql).toEqual(
|
|
319
319
|
'SELECT f2.hash as hash2 ' +
|
|
@@ -353,22 +353,26 @@ function allFactTypes(steps: Step[]): string[] {
|
|
|
353
353
|
|
|
354
354
|
function allRoles(steps: Step[], initialType: string) {
|
|
355
355
|
let roles: { type: string; role: string }[] = [];
|
|
356
|
-
let type: string = initialType;
|
|
357
|
-
let role: string = undefined;
|
|
356
|
+
let type: string | undefined = initialType;
|
|
357
|
+
let role: string | undefined = undefined;
|
|
358
358
|
|
|
359
359
|
for (const step of steps) {
|
|
360
360
|
if (step instanceof PropertyCondition) {
|
|
361
361
|
if (step.name === 'type') {
|
|
362
362
|
type = step.value;
|
|
363
363
|
if (role) {
|
|
364
|
-
|
|
364
|
+
if (type) {
|
|
365
|
+
roles.push({ type, role });
|
|
366
|
+
}
|
|
365
367
|
role = undefined;
|
|
366
368
|
}
|
|
367
369
|
}
|
|
368
370
|
}
|
|
369
371
|
else if (step instanceof Join) {
|
|
370
372
|
if (step.direction === Direction.Predecessor) {
|
|
371
|
-
|
|
373
|
+
if (type) {
|
|
374
|
+
roles.push({ type, role: step.role });
|
|
375
|
+
}
|
|
372
376
|
role = undefined;
|
|
373
377
|
}
|
|
374
378
|
else {
|
|
@@ -377,7 +381,9 @@ function allRoles(steps: Step[], initialType: string) {
|
|
|
377
381
|
type = undefined;
|
|
378
382
|
}
|
|
379
383
|
else if (step instanceof ExistentialCondition) {
|
|
380
|
-
|
|
384
|
+
if (type) {
|
|
385
|
+
roles = roles.concat(allRoles(step.steps, type));
|
|
386
|
+
}
|
|
381
387
|
}
|
|
382
388
|
}
|
|
383
389
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { dehydrateReference,
|
|
1
|
+
import { dehydrateReference, getAllFactTypes, getAllRoles, SpecificationParser } from "jinaga";
|
|
2
2
|
|
|
3
3
|
import { addFactType, addRole, emptyFactTypeMap, emptyRoleMap, getFactTypeId, getRoleId } from "../../src/postgres/maps";
|
|
4
|
-
import { SpecificationSqlQuery } from "../../src/postgres/query-description";
|
|
5
4
|
import { sqlFromSpecification } from "../../src/postgres/specification-sql";
|
|
6
5
|
|
|
7
6
|
const root = dehydrateReference({ type: 'Root' });
|
|
@@ -15,7 +14,7 @@ function parseSpecification(input: string) {
|
|
|
15
14
|
return parser.parseSpecification();
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
function sqlFor(descriptiveString: string, bookmarks:
|
|
17
|
+
function sqlFor(descriptiveString: string, bookmarks: string[] = []) {
|
|
19
18
|
const specification = parseSpecification(descriptiveString);
|
|
20
19
|
const factTypeNames = getAllFactTypes(specification);
|
|
21
20
|
|
|
@@ -45,7 +44,7 @@ function sqlFor(descriptiveString: string, bookmarks: FactBookmark[] = []) {
|
|
|
45
44
|
}
|
|
46
45
|
throw new Error(`Unknown input type ${input.type}`);
|
|
47
46
|
});
|
|
48
|
-
const sqlQueries
|
|
47
|
+
const sqlQueries = sqlFromSpecification(start, bookmarks, 100, specification, factTypes, roleMap);
|
|
49
48
|
return { sqlQueries, factTypes, roleMap };
|
|
50
49
|
}
|
|
51
50
|
|
|
@@ -91,7 +90,6 @@ describe("Postgres query generator", () => {
|
|
|
91
90
|
]);
|
|
92
91
|
expect(query.labels).toEqual([
|
|
93
92
|
{
|
|
94
|
-
name: 'successor',
|
|
95
93
|
type: 'IntegrationTest.Successor',
|
|
96
94
|
index: 2
|
|
97
95
|
}
|
|
@@ -134,12 +132,10 @@ describe("Postgres query generator", () => {
|
|
|
134
132
|
]);
|
|
135
133
|
expect(sqlQueries[0].labels).toEqual([
|
|
136
134
|
{
|
|
137
|
-
name: "successor",
|
|
138
135
|
type: "IntegrationTest.Successor",
|
|
139
136
|
index: 2
|
|
140
137
|
},
|
|
141
138
|
{
|
|
142
|
-
name: "other",
|
|
143
139
|
type: "IntegrationTest.OtherPredecessor",
|
|
144
140
|
index: 3
|
|
145
141
|
}
|
|
@@ -185,12 +181,10 @@ describe("Postgres query generator", () => {
|
|
|
185
181
|
]);
|
|
186
182
|
expect(sqlQueries[0].labels).toEqual([
|
|
187
183
|
{
|
|
188
|
-
name: "project",
|
|
189
184
|
type: "MyApplication.Project",
|
|
190
185
|
index: 2
|
|
191
186
|
},
|
|
192
187
|
{
|
|
193
|
-
name: "assignment",
|
|
194
188
|
type: "MyApplication.Assignment",
|
|
195
189
|
index: 3
|
|
196
190
|
}
|
|
@@ -236,12 +230,10 @@ describe("Postgres query generator", () => {
|
|
|
236
230
|
]);
|
|
237
231
|
expect(sqlQueries[0].labels).toEqual([
|
|
238
232
|
{
|
|
239
|
-
name: "project",
|
|
240
233
|
type: "MyApplication.Project",
|
|
241
234
|
index: 2
|
|
242
235
|
},
|
|
243
236
|
{
|
|
244
|
-
name: "deleted",
|
|
245
237
|
type: "MyApplication.Project.Deleted",
|
|
246
238
|
index: 3
|
|
247
239
|
}
|
|
@@ -274,7 +266,6 @@ describe("Postgres query generator", () => {
|
|
|
274
266
|
]);
|
|
275
267
|
expect(sqlQueries[1].labels).toEqual([
|
|
276
268
|
{
|
|
277
|
-
name: "project",
|
|
278
269
|
type: "MyApplication.Project",
|
|
279
270
|
index: 2
|
|
280
271
|
}
|
|
@@ -296,43 +287,41 @@ describe("Postgres query generator", () => {
|
|
|
296
287
|
|
|
297
288
|
expect(sqlQueries.length).toEqual(1);
|
|
298
289
|
expect(sqlQueries[0].sql).toEqual(
|
|
299
|
-
'SELECT
|
|
300
|
-
'
|
|
301
|
-
'sort(array[
|
|
290
|
+
'SELECT f2.hash as hash2, ' +
|
|
291
|
+
'f3.hash as hash3, ' +
|
|
292
|
+
'sort(array[f2.fact_id, f3.fact_id], \'desc\') as bookmark ' +
|
|
302
293
|
'FROM public.fact f1 ' +
|
|
303
|
-
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $
|
|
304
|
-
'JOIN public.fact
|
|
305
|
-
'JOIN public.edge e2 ON e2.predecessor_fact_id =
|
|
306
|
-
'JOIN public.fact
|
|
307
|
-
'JOIN public.edge e3 ON e3.successor_fact_id =
|
|
308
|
-
'JOIN public.fact
|
|
294
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $5 ' +
|
|
295
|
+
'JOIN public.fact f2 ON f2.fact_id = e1.successor_fact_id ' +
|
|
296
|
+
'JOIN public.edge e2 ON e2.predecessor_fact_id = f2.fact_id AND e2.role_id = $6 ' +
|
|
297
|
+
'JOIN public.fact f3 ON f3.fact_id = e2.successor_fact_id ' +
|
|
298
|
+
'JOIN public.edge e3 ON e3.successor_fact_id = f3.fact_id AND e3.role_id = $7 ' +
|
|
299
|
+
'JOIN public.fact f4 ON f4.fact_id = e3.predecessor_fact_id ' +
|
|
309
300
|
'WHERE f1.fact_type_id = $1 AND f1.hash = $2 ' +
|
|
310
|
-
'AND
|
|
311
|
-
'AND sort(array[
|
|
301
|
+
'AND f4.fact_type_id = $3 AND f4.hash = $4 ' +
|
|
302
|
+
'AND sort(array[f2.fact_id, f3.fact_id], \'desc\') > $8 ' +
|
|
312
303
|
'ORDER BY bookmark ASC ' +
|
|
313
304
|
'LIMIT $9'
|
|
314
305
|
);
|
|
315
306
|
expect(sqlQueries[0].parameters).toEqual([
|
|
316
307
|
getFactTypeId(factTypes, "Root"),
|
|
317
308
|
rootHash,
|
|
318
|
-
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
319
|
-
roleParameter(roleMap, factTypes, "MyApplication.Assignment", "project"),
|
|
320
309
|
getFactTypeId(factTypes, "Jinaga.User"),
|
|
321
310
|
userHash,
|
|
311
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
312
|
+
roleParameter(roleMap, factTypes, "MyApplication.Assignment", "project"),
|
|
322
313
|
roleParameter(roleMap, factTypes, "MyApplication.Assignment", "user"),
|
|
323
314
|
[],
|
|
324
315
|
100
|
|
325
316
|
]);
|
|
326
317
|
expect(sqlQueries[0].labels).toEqual([
|
|
327
318
|
{
|
|
328
|
-
name: "project",
|
|
329
319
|
type: "MyApplication.Project",
|
|
330
|
-
index:
|
|
320
|
+
index: 2
|
|
331
321
|
},
|
|
332
322
|
{
|
|
333
|
-
name: "assignment",
|
|
334
323
|
type: "MyApplication.Assignment",
|
|
335
|
-
index:
|
|
324
|
+
index: 3
|
|
336
325
|
}
|
|
337
326
|
]);
|
|
338
327
|
});
|
|
@@ -352,43 +341,41 @@ describe("Postgres query generator", () => {
|
|
|
352
341
|
|
|
353
342
|
expect(sqlQueries.length).toEqual(1);
|
|
354
343
|
expect(sqlQueries[0].sql).toEqual(
|
|
355
|
-
'SELECT
|
|
356
|
-
'
|
|
357
|
-
'sort(array[
|
|
358
|
-
'FROM public.fact
|
|
359
|
-
'JOIN public.edge e1 ON e1.predecessor_fact_id =
|
|
360
|
-
'JOIN public.fact
|
|
361
|
-
'JOIN public.edge e2 ON e2.predecessor_fact_id =
|
|
362
|
-
'JOIN public.fact
|
|
363
|
-
'JOIN public.edge e3 ON e3.successor_fact_id =
|
|
364
|
-
'JOIN public.fact
|
|
365
|
-
'WHERE f1.fact_type_id = $
|
|
366
|
-
'AND
|
|
367
|
-
'AND sort(array[
|
|
344
|
+
'SELECT f2.hash as hash2, ' +
|
|
345
|
+
'f3.hash as hash3, ' +
|
|
346
|
+
'sort(array[f2.fact_id, f3.fact_id], \'desc\') as bookmark ' +
|
|
347
|
+
'FROM public.fact f1 ' +
|
|
348
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $5 ' +
|
|
349
|
+
'JOIN public.fact f2 ON f2.fact_id = e1.successor_fact_id ' +
|
|
350
|
+
'JOIN public.edge e2 ON e2.predecessor_fact_id = f2.fact_id AND e2.role_id = $6 ' +
|
|
351
|
+
'JOIN public.fact f3 ON f3.fact_id = e2.successor_fact_id ' +
|
|
352
|
+
'JOIN public.edge e3 ON e3.successor_fact_id = f3.fact_id AND e3.role_id = $7 ' +
|
|
353
|
+
'JOIN public.fact f4 ON f4.fact_id = e3.predecessor_fact_id ' +
|
|
354
|
+
'WHERE f1.fact_type_id = $1 AND f1.hash = $2 ' +
|
|
355
|
+
'AND f4.fact_type_id = $3 AND f4.hash = $4 ' +
|
|
356
|
+
'AND sort(array[f2.fact_id, f3.fact_id], \'desc\') > $8 ' +
|
|
368
357
|
'ORDER BY bookmark ASC ' +
|
|
369
358
|
'LIMIT $9'
|
|
370
359
|
);
|
|
371
360
|
expect(sqlQueries[0].parameters).toEqual([
|
|
372
361
|
getFactTypeId(factTypes, "Root"),
|
|
373
362
|
rootHash,
|
|
374
|
-
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
375
|
-
roleParameter(roleMap, factTypes, "MyApplication.Assignment", "project"),
|
|
376
363
|
getFactTypeId(factTypes, "Jinaga.User"),
|
|
377
364
|
userHash,
|
|
365
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
366
|
+
roleParameter(roleMap, factTypes, "MyApplication.Assignment", "project"),
|
|
378
367
|
roleParameter(roleMap, factTypes, "MyApplication.Assignment", "user"),
|
|
379
368
|
[],
|
|
380
369
|
100
|
|
381
370
|
]);
|
|
382
371
|
expect(sqlQueries[0].labels).toEqual([
|
|
383
372
|
{
|
|
384
|
-
name: "project",
|
|
385
373
|
type: "MyApplication.Project",
|
|
386
|
-
index:
|
|
374
|
+
index: 2
|
|
387
375
|
},
|
|
388
376
|
{
|
|
389
|
-
name: "assignment",
|
|
390
377
|
type: "MyApplication.Assignment",
|
|
391
|
-
index:
|
|
378
|
+
index: 3
|
|
392
379
|
}
|
|
393
380
|
]);
|
|
394
381
|
});
|
|
@@ -428,7 +415,6 @@ describe("Postgres query generator", () => {
|
|
|
428
415
|
]);
|
|
429
416
|
expect(sqlQueries[0].labels).toEqual([
|
|
430
417
|
{
|
|
431
|
-
name: "project",
|
|
432
418
|
type: "MyApplication.Project",
|
|
433
419
|
index: 2
|
|
434
420
|
}
|
|
@@ -456,12 +442,10 @@ describe("Postgres query generator", () => {
|
|
|
456
442
|
]);
|
|
457
443
|
expect(sqlQueries[1].labels).toEqual([
|
|
458
444
|
{
|
|
459
|
-
name: "project",
|
|
460
445
|
type: "MyApplication.Project",
|
|
461
446
|
index: 2
|
|
462
447
|
},
|
|
463
448
|
{
|
|
464
|
-
name: "names.name",
|
|
465
449
|
type: "MyApplication.Project.Name",
|
|
466
450
|
index: 3
|
|
467
451
|
}
|
|
@@ -486,18 +470,9 @@ describe("Postgres query generator", () => {
|
|
|
486
470
|
]
|
|
487
471
|
}
|
|
488
472
|
}`, [
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
labels: [ "project", "names.name", "names.next" ],
|
|
495
|
-
bookmark: "456.345.234"
|
|
496
|
-
},
|
|
497
|
-
{
|
|
498
|
-
labels: [ "project", "names.name" ],
|
|
499
|
-
bookmark: "789.678"
|
|
500
|
-
}
|
|
473
|
+
"123",
|
|
474
|
+
"456.345.234",
|
|
475
|
+
"789.678"
|
|
501
476
|
]);
|
|
502
477
|
|
|
503
478
|
expect(sqlQueries.length).toEqual(3);
|
|
@@ -521,7 +496,6 @@ describe("Postgres query generator", () => {
|
|
|
521
496
|
]);
|
|
522
497
|
expect(sqlQueries[0].labels).toEqual([
|
|
523
498
|
{
|
|
524
|
-
name: "project",
|
|
525
499
|
type: "MyApplication.Project",
|
|
526
500
|
index: 2
|
|
527
501
|
}
|
|
@@ -560,7 +534,6 @@ describe("Postgres query generator", () => {
|
|
|
560
534
|
]);
|
|
561
535
|
expect(sqlQueries[0].labels).toEqual([
|
|
562
536
|
{
|
|
563
|
-
name: "project",
|
|
564
537
|
type: "MyApplication.Project",
|
|
565
538
|
index: 2
|
|
566
539
|
}
|
|
@@ -645,7 +618,7 @@ describe("Postgres query generator", () => {
|
|
|
645
618
|
const { sqlQueries } = sqlFor(`
|
|
646
619
|
(root: Root) {
|
|
647
620
|
successor: MyApplication.Successor [
|
|
648
|
-
successor = root->intermediate: Unknown->
|
|
621
|
+
successor = root->intermediate: Unknown->successor: MyApplication.Successor
|
|
649
622
|
]
|
|
650
623
|
}`);
|
|
651
624
|
|
|
@@ -667,7 +640,7 @@ describe("Postgres query generator", () => {
|
|
|
667
640
|
const { sqlQueries } = sqlFor(`
|
|
668
641
|
(root: Root) {
|
|
669
642
|
successor: MyApplication.Successor [
|
|
670
|
-
successor = root->unknown: Intermediate->
|
|
643
|
+
successor = root->unknown: Intermediate->successor: MyApplication.Successor
|
|
671
644
|
]
|
|
672
645
|
}`);
|
|
673
646
|
|
|
@@ -733,7 +706,6 @@ describe("Postgres query generator", () => {
|
|
|
733
706
|
]);
|
|
734
707
|
expect(sqlQueries[0].labels).toEqual([
|
|
735
708
|
{
|
|
736
|
-
name: "project",
|
|
737
709
|
type: "MyApplication.Project",
|
|
738
710
|
index: 2
|
|
739
711
|
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Label, PathCondition } from "jinaga";
|
|
2
|
-
import { FactTypeMap, RoleMap } from "./maps";
|
|
3
|
-
import { QueryDescription } from "./query-description";
|
|
4
|
-
import { FactByIdentifier } from "./specification-result-sql";
|
|
5
|
-
export declare class QueryDescriptionBuilder {
|
|
6
|
-
protected readonly factTypes: FactTypeMap;
|
|
7
|
-
protected readonly roleMap: RoleMap;
|
|
8
|
-
constructor(factTypes: FactTypeMap, roleMap: RoleMap);
|
|
9
|
-
protected addPathCondition(queryDescription: QueryDescription, knownFacts: FactByIdentifier, path: number[], unknown: Label, prefix: string, condition: PathCondition): {
|
|
10
|
-
queryDescription: QueryDescription;
|
|
11
|
-
knownFacts: FactByIdentifier;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.QueryDescriptionBuilder = void 0;
|
|
4
|
-
const maps_1 = require("./maps");
|
|
5
|
-
const query_description_1 = require("./query-description");
|
|
6
|
-
class QueryDescriptionBuilder {
|
|
7
|
-
constructor(factTypes, roleMap) {
|
|
8
|
-
this.factTypes = factTypes;
|
|
9
|
-
this.roleMap = roleMap;
|
|
10
|
-
}
|
|
11
|
-
addPathCondition(queryDescription, knownFacts, path, unknown, prefix, condition) {
|
|
12
|
-
// If no input parameter has been allocated, allocate one now.
|
|
13
|
-
const input = queryDescription.inputByLabel(condition.labelRight);
|
|
14
|
-
if (input && input.factTypeParameter === 0) {
|
|
15
|
-
queryDescription = queryDescription.withInputParameter(input.label);
|
|
16
|
-
}
|
|
17
|
-
// Determine whether we have already written the output.
|
|
18
|
-
const knownFact = knownFacts[unknown.name];
|
|
19
|
-
const roleCount = condition.rolesLeft.length + condition.rolesRight.length;
|
|
20
|
-
// Walk up the right-hand side.
|
|
21
|
-
// This generates predecessor joins from a given or prior label.
|
|
22
|
-
let fact = knownFacts[condition.labelRight];
|
|
23
|
-
if (!fact) {
|
|
24
|
-
throw new Error(`Label ${condition.labelRight} not found. Known labels: ${Object.keys(knownFacts).join(", ")}`);
|
|
25
|
-
}
|
|
26
|
-
let type = fact.type;
|
|
27
|
-
let factIndex = fact.factIndex;
|
|
28
|
-
for (const [i, role] of condition.rolesRight.entries()) {
|
|
29
|
-
// If the type or role is not known, then no facts matching the condition can
|
|
30
|
-
// exist. The query is unsatisfiable.
|
|
31
|
-
const typeId = (0, maps_1.getFactTypeId)(this.factTypes, type);
|
|
32
|
-
if (!typeId) {
|
|
33
|
-
return { queryDescription: query_description_1.QueryDescription.unsatisfiable, knownFacts };
|
|
34
|
-
}
|
|
35
|
-
const roleId = (0, maps_1.getRoleId)(this.roleMap, typeId, role.name);
|
|
36
|
-
if (!roleId) {
|
|
37
|
-
return { queryDescription: query_description_1.QueryDescription.unsatisfiable, knownFacts };
|
|
38
|
-
}
|
|
39
|
-
const { query: queryWithParameter, parameterIndex: roleParameter } = queryDescription.withParameter(roleId);
|
|
40
|
-
if (i === roleCount - 1 && knownFact) {
|
|
41
|
-
// If we have already written the output, we can use the fact index.
|
|
42
|
-
queryDescription = queryWithParameter.withEdge(knownFact.factIndex, factIndex, roleParameter, path);
|
|
43
|
-
factIndex = knownFact.factIndex;
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
// If we have not written the fact, we need to write it now.
|
|
47
|
-
const { query, factIndex: predecessorFactIndex } = queryWithParameter.withFact(role.targetType);
|
|
48
|
-
queryDescription = query.withEdge(predecessorFactIndex, factIndex, roleParameter, path);
|
|
49
|
-
factIndex = predecessorFactIndex;
|
|
50
|
-
}
|
|
51
|
-
type = role.targetType;
|
|
52
|
-
}
|
|
53
|
-
const rightType = type;
|
|
54
|
-
// Walk up the left-hand side.
|
|
55
|
-
// We will need to reverse this walk to generate successor joins.
|
|
56
|
-
type = unknown.type;
|
|
57
|
-
const newEdges = [];
|
|
58
|
-
for (const role of condition.rolesLeft) {
|
|
59
|
-
// If the type or role is not known, then no facts matching the condition can
|
|
60
|
-
// exist. The query is unsatisfiable.
|
|
61
|
-
const typeId = (0, maps_1.getFactTypeId)(this.factTypes, type);
|
|
62
|
-
if (!typeId) {
|
|
63
|
-
return { queryDescription: query_description_1.QueryDescription.unsatisfiable, knownFacts };
|
|
64
|
-
}
|
|
65
|
-
const roleId = (0, maps_1.getRoleId)(this.roleMap, typeId, role.name);
|
|
66
|
-
if (!roleId) {
|
|
67
|
-
return { queryDescription: query_description_1.QueryDescription.unsatisfiable, knownFacts };
|
|
68
|
-
}
|
|
69
|
-
newEdges.push({
|
|
70
|
-
roleId,
|
|
71
|
-
declaringType: type
|
|
72
|
-
});
|
|
73
|
-
type = role.targetType;
|
|
74
|
-
}
|
|
75
|
-
if (type !== rightType) {
|
|
76
|
-
throw new Error(`Type mismatch: ${type} is compared to ${rightType}`);
|
|
77
|
-
}
|
|
78
|
-
newEdges.reverse().forEach(({ roleId, declaringType }, i) => {
|
|
79
|
-
const { query: queryWithParameter, parameterIndex: roleParameter } = queryDescription.withParameter(roleId);
|
|
80
|
-
if (condition.rolesRight.length + i === roleCount - 1 && knownFact) {
|
|
81
|
-
queryDescription = queryWithParameter.withEdge(factIndex, knownFact.factIndex, roleParameter, path);
|
|
82
|
-
factIndex = knownFact.factIndex;
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
const { query: queryWithFact, factIndex: successorFactIndex } = queryWithParameter.withFact(declaringType);
|
|
86
|
-
queryDescription = queryWithFact.withEdge(factIndex, successorFactIndex, roleParameter, path);
|
|
87
|
-
factIndex = successorFactIndex;
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
// If we have not captured the known fact, add it now.
|
|
91
|
-
if (!knownFact) {
|
|
92
|
-
knownFacts = Object.assign(Object.assign({}, knownFacts), { [unknown.name]: { factIndex, type: unknown.type } });
|
|
93
|
-
// If we have not written the output, write it now.
|
|
94
|
-
// Only write the output if we are not inside of an existential condition.
|
|
95
|
-
// Use the prefix, which will be set for projections.
|
|
96
|
-
if (path.length === 0) {
|
|
97
|
-
queryDescription = queryDescription.withOutput(prefix + unknown.name, unknown.type, factIndex);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return { queryDescription, knownFacts };
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
exports.QueryDescriptionBuilder = QueryDescriptionBuilder;
|
|
104
|
-
//# sourceMappingURL=query-description-builder.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"query-description-builder.js","sourceRoot":"","sources":["../../src/postgres/query-description-builder.ts"],"names":[],"mappings":"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"}
|