jinaga-server 1.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/LICENSE +22 -0
- package/dist/authentication/authentication-device.d.ts +20 -0
- package/dist/authentication/authentication-device.js +61 -0
- package/dist/authentication/authentication-device.js.map +1 -0
- package/dist/authentication/authentication-impl.d.ts +21 -0
- package/dist/authentication/authentication-impl.js +61 -0
- package/dist/authentication/authentication-impl.js.map +1 -0
- package/dist/authentication/authentication-session.d.ts +23 -0
- package/dist/authentication/authentication-session.js +85 -0
- package/dist/authentication/authentication-session.js.map +1 -0
- package/dist/authentication/authentication.d.ts +5 -0
- package/dist/authentication/authentication.js +3 -0
- package/dist/authentication/authentication.js.map +1 -0
- package/dist/authentication.d.ts +17 -0
- package/dist/authentication.js +52 -0
- package/dist/authentication.js.map +1 -0
- package/dist/authorization/authorization-keystore.d.ts +13 -0
- package/dist/authorization/authorization-keystore.js +61 -0
- package/dist/authorization/authorization-keystore.js.map +1 -0
- package/dist/http/node-http.d.ts +7 -0
- package/dist/http/node-http.js +126 -0
- package/dist/http/node-http.js.map +1 -0
- package/dist/http/router.d.ts +20 -0
- package/dist/http/router.js +223 -0
- package/dist/http/router.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/jinaga-server.d.ts +18 -0
- package/dist/jinaga-server.js +101 -0
- package/dist/jinaga-server.js.map +1 -0
- package/dist/keystore.d.ts +9 -0
- package/dist/keystore.js +3 -0
- package/dist/keystore.js.map +1 -0
- package/dist/memory/memory-keystore.d.ts +16 -0
- package/dist/memory/memory-keystore.js +86 -0
- package/dist/memory/memory-keystore.js.map +1 -0
- package/dist/postgres/connection.d.ts +12 -0
- package/dist/postgres/connection.js +83 -0
- package/dist/postgres/connection.js.map +1 -0
- package/dist/postgres/edge-record.d.ts +9 -0
- package/dist/postgres/edge-record.js +30 -0
- package/dist/postgres/edge-record.js.map +1 -0
- package/dist/postgres/maps.d.ts +21 -0
- package/dist/postgres/maps.js +86 -0
- package/dist/postgres/maps.js.map +1 -0
- package/dist/postgres/postgres-keystore.d.ts +20 -0
- package/dist/postgres/postgres-keystore.js +168 -0
- package/dist/postgres/postgres-keystore.js.map +1 -0
- package/dist/postgres/postgres-store.d.ts +20 -0
- package/dist/postgres/postgres-store.js +717 -0
- package/dist/postgres/postgres-store.js.map +1 -0
- package/dist/postgres/query-description-builder.d.ts +13 -0
- package/dist/postgres/query-description-builder.js +104 -0
- package/dist/postgres/query-description-builder.js.map +1 -0
- package/dist/postgres/query-description.d.ts +71 -0
- package/dist/postgres/query-description.js +256 -0
- package/dist/postgres/query-description.js.map +1 -0
- package/dist/postgres/specification-result-sql.d.ts +41 -0
- package/dist/postgres/specification-result-sql.js +260 -0
- package/dist/postgres/specification-result-sql.js.map +1 -0
- package/dist/postgres/specification-sql.d.ts +3 -0
- package/dist/postgres/specification-sql.js +119 -0
- package/dist/postgres/specification-sql.js.map +1 -0
- package/dist/postgres/sql.d.ts +10 -0
- package/dist/postgres/sql.js +396 -0
- package/dist/postgres/sql.js.map +1 -0
- package/dist/util/fn.d.ts +6 -0
- package/dist/util/fn.js +81 -0
- package/dist/util/fn.js.map +1 -0
- package/dist/util/promise.d.ts +1 -0
- package/dist/util/promise.js +10 -0
- package/dist/util/promise.js.map +1 -0
- package/docs/registrations.sql +22 -0
- package/docs/specification.txt +41 -0
- package/docs/sql-generator.gv +25 -0
- package/integration-test/docker-compose.yaml +13 -0
- package/integration-test/jinaga-test/Dockerfile +12 -0
- package/integration-test/jinaga-test/jinaga.test.js +425 -0
- package/integration-test/jinaga-test/package-lock.json +6767 -0
- package/integration-test/jinaga-test/package.json +25 -0
- package/integration-test/postgres/0-init-app-db.sh +16 -0
- package/integration-test/postgres/1-setup.sql +219 -0
- package/integration-test/postgres/2-grant.sh +17 -0
- package/integration-test/postgres/Dockerfile +5 -0
- package/integration-test/run.sh +17 -0
- package/integration-test/start_db.sh +2 -0
- package/integration-test/stop_db.sh +3 -0
- package/integration-test/test.sh +8 -0
- package/interactive/index.js +56 -0
- package/jest.config.js +12 -0
- package/package.json +51 -0
- package/scripts/combined.sql +46 -0
- package/scripts/cte.sql +55 -0
- package/scripts/separate.sql +27 -0
- package/setup.sh +34 -0
- package/setup.sql +329 -0
- package/src/authentication/authentication-device.ts +68 -0
- package/src/authentication/authentication-impl.ts +59 -0
- package/src/authentication/authentication-session.ts +95 -0
- package/src/authentication/authentication.ts +6 -0
- package/src/authentication.ts +45 -0
- package/src/authorization/authorization-keystore.ts +67 -0
- package/src/http/node-http.ts +106 -0
- package/src/http/router.ts +237 -0
- package/src/index.ts +1 -0
- package/src/jinaga-server.ts +125 -0
- package/src/keystore.ts +10 -0
- package/src/memory/memory-keystore.ts +93 -0
- package/src/postgres/connection.ts +73 -0
- package/src/postgres/edge-record.ts +36 -0
- package/src/postgres/maps.ts +89 -0
- package/src/postgres/postgres-keystore.ts +167 -0
- package/src/postgres/postgres-store.ts +874 -0
- package/src/postgres/query-description-builder.ts +116 -0
- package/src/postgres/query-description.ts +385 -0
- package/src/postgres/specification-result-sql.ts +362 -0
- package/src/postgres/specification-sql.ts +138 -0
- package/src/postgres/sql.ts +482 -0
- package/src/util/fn.ts +63 -0
- package/src/util/promise.ts +5 -0
- package/test/authorization/authorizationSpec.ts +209 -0
- package/test/postgres/postgresSpec.ts +385 -0
- package/test/postgres/postgresSpecificationSpec.ts +742 -0
- package/test/reporter.js +25 -0
- package/tsconfig.json +25 -0
- package/upgrade.sh +21 -0
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
import { dehydrateReference, FactBookmark, getAllFactTypes, getAllRoles, SpecificationParser } from "jinaga";
|
|
2
|
+
|
|
3
|
+
import { addFactType, addRole, emptyFactTypeMap, emptyRoleMap, getFactTypeId, getRoleId } from "../../src/postgres/maps";
|
|
4
|
+
import { SpecificationSqlQuery } from "../../src/postgres/query-description";
|
|
5
|
+
import { sqlFromSpecification } from "../../src/postgres/specification-sql";
|
|
6
|
+
|
|
7
|
+
const root = dehydrateReference({ type: 'Root' });
|
|
8
|
+
const rootHash = root.hash;
|
|
9
|
+
const user = dehydrateReference({ type: "Jinaga.User", publicKey: "PUBLIC KEY"});
|
|
10
|
+
const userHash = user.hash;
|
|
11
|
+
|
|
12
|
+
function parseSpecification(input: string) {
|
|
13
|
+
const parser = new SpecificationParser(input);
|
|
14
|
+
parser.skipWhitespace();
|
|
15
|
+
return parser.parseSpecification();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function sqlFor(descriptiveString: string, bookmarks: FactBookmark[] = []) {
|
|
19
|
+
const specification = parseSpecification(descriptiveString);
|
|
20
|
+
const factTypeNames = getAllFactTypes(specification);
|
|
21
|
+
|
|
22
|
+
// Build a fact type map containing all fact types in the specification.
|
|
23
|
+
// Filter out fact types named "Unknown".
|
|
24
|
+
const factTypes = factTypeNames.filter(t => t !== 'Unknown').reduce(
|
|
25
|
+
(f, factType, i) => addFactType(f, factType, i + 1),
|
|
26
|
+
emptyFactTypeMap());
|
|
27
|
+
|
|
28
|
+
// Build a role map containing all roles in the specification.
|
|
29
|
+
// Filter out the roles named "unknown", and those of unknown fact types.
|
|
30
|
+
let roleMap = getAllRoles(specification).filter(r => r.name !== 'unknown').reduce(
|
|
31
|
+
(r, role, i) => {
|
|
32
|
+
const factTypeId = getFactTypeId(factTypes, role.definingFactType);
|
|
33
|
+
if (!factTypeId) {
|
|
34
|
+
return r;
|
|
35
|
+
}
|
|
36
|
+
return addRole(r, factTypeId, role.name, i + 1);
|
|
37
|
+
},
|
|
38
|
+
emptyRoleMap());
|
|
39
|
+
const start = specification.given.map(input => {
|
|
40
|
+
if (input.type === 'Root') {
|
|
41
|
+
return root;
|
|
42
|
+
}
|
|
43
|
+
if (input.type === 'Jinaga.User') {
|
|
44
|
+
return user;
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Unknown input type ${input.type}`);
|
|
47
|
+
});
|
|
48
|
+
const sqlQueries: SpecificationSqlQuery[] = sqlFromSpecification(start, bookmarks, 100, specification, factTypes, roleMap);
|
|
49
|
+
return { sqlQueries, factTypes, roleMap };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function roleParameter(roleMap: Map<number, Map<string, number>>, factTypes: Map<string, number>, factTypeName: string, roleName: string): number {
|
|
53
|
+
const factTypeId = getFactTypeId(factTypes, factTypeName);
|
|
54
|
+
if (!factTypeId) {
|
|
55
|
+
throw new Error(`Unknown fact type ${factTypeName}`);
|
|
56
|
+
}
|
|
57
|
+
const roleId = getRoleId(roleMap, factTypeId, roleName);
|
|
58
|
+
if (!roleId) {
|
|
59
|
+
throw new Error(`Unknown role ${roleName} in fact type ${factTypeName}`);
|
|
60
|
+
}
|
|
61
|
+
return roleId;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe("Postgres query generator", () => {
|
|
65
|
+
it("should generate a join to successors", () => {
|
|
66
|
+
const { sqlQueries, factTypes, roleMap } = sqlFor(`
|
|
67
|
+
(predecessor: Root) {
|
|
68
|
+
successor: IntegrationTest.Successor [
|
|
69
|
+
successor->predecessor:Root = predecessor
|
|
70
|
+
]
|
|
71
|
+
}`);
|
|
72
|
+
expect(sqlQueries.length).toBe(1);
|
|
73
|
+
const query = sqlQueries[0];
|
|
74
|
+
expect(query.sql).toEqual(
|
|
75
|
+
'SELECT f2.hash as hash2, ' +
|
|
76
|
+
'sort(array[f2.fact_id], \'desc\') as bookmark ' +
|
|
77
|
+
'FROM public.fact f1 ' +
|
|
78
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $3 ' +
|
|
79
|
+
'JOIN public.fact f2 ON f2.fact_id = e1.successor_fact_id ' +
|
|
80
|
+
'WHERE f1.fact_type_id = $1 AND f1.hash = $2 ' +
|
|
81
|
+
'AND sort(array[f2.fact_id], \'desc\') > $4 ' +
|
|
82
|
+
'ORDER BY bookmark ASC ' +
|
|
83
|
+
'LIMIT $5'
|
|
84
|
+
);
|
|
85
|
+
expect(query.parameters).toEqual([
|
|
86
|
+
getFactTypeId(factTypes, 'Root'),
|
|
87
|
+
rootHash,
|
|
88
|
+
roleParameter(roleMap, factTypes, 'IntegrationTest.Successor', 'predecessor'),
|
|
89
|
+
[],
|
|
90
|
+
100
|
|
91
|
+
]);
|
|
92
|
+
expect(query.labels).toEqual([
|
|
93
|
+
{
|
|
94
|
+
name: 'successor',
|
|
95
|
+
type: 'IntegrationTest.Successor',
|
|
96
|
+
index: 2
|
|
97
|
+
}
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should generate a join to successor and then predecessor", () => {
|
|
102
|
+
const { sqlQueries, factTypes, roleMap } = sqlFor(`
|
|
103
|
+
(predecessor: Root) {
|
|
104
|
+
successor: IntegrationTest.Successor [
|
|
105
|
+
successor->predecessor:Root = predecessor
|
|
106
|
+
]
|
|
107
|
+
other: IntegrationTest.OtherPredecessor [
|
|
108
|
+
other = successor->other:IntegrationTest.OtherPredecessor
|
|
109
|
+
]
|
|
110
|
+
}`);
|
|
111
|
+
|
|
112
|
+
expect(sqlQueries.length).toEqual(1)
|
|
113
|
+
expect(sqlQueries[0].sql).toEqual(
|
|
114
|
+
'SELECT f2.hash as hash2, ' +
|
|
115
|
+
'f3.hash as hash3, ' +
|
|
116
|
+
'sort(array[f2.fact_id, f3.fact_id], \'desc\') as bookmark ' +
|
|
117
|
+
'FROM public.fact f1 ' +
|
|
118
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $3 ' +
|
|
119
|
+
'JOIN public.fact f2 ON f2.fact_id = e1.successor_fact_id ' +
|
|
120
|
+
'JOIN public.edge e2 ON e2.successor_fact_id = f2.fact_id AND e2.role_id = $4 ' +
|
|
121
|
+
'JOIN public.fact f3 ON f3.fact_id = e2.predecessor_fact_id ' +
|
|
122
|
+
'WHERE f1.fact_type_id = $1 AND f1.hash = $2 ' +
|
|
123
|
+
'AND sort(array[f2.fact_id, f3.fact_id], \'desc\') > $5 ' +
|
|
124
|
+
'ORDER BY bookmark ASC ' +
|
|
125
|
+
'LIMIT $6'
|
|
126
|
+
);
|
|
127
|
+
expect(sqlQueries[0].parameters).toEqual([
|
|
128
|
+
getFactTypeId(factTypes, "Root"),
|
|
129
|
+
rootHash,
|
|
130
|
+
roleParameter(roleMap, factTypes, "IntegrationTest.Successor", "predecessor"),
|
|
131
|
+
roleParameter(roleMap, factTypes, "IntegrationTest.Successor", "other"),
|
|
132
|
+
[],
|
|
133
|
+
100
|
|
134
|
+
]);
|
|
135
|
+
expect(sqlQueries[0].labels).toEqual([
|
|
136
|
+
{
|
|
137
|
+
name: "successor",
|
|
138
|
+
type: "IntegrationTest.Successor",
|
|
139
|
+
index: 2
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "other",
|
|
143
|
+
type: "IntegrationTest.OtherPredecessor",
|
|
144
|
+
index: 3
|
|
145
|
+
}
|
|
146
|
+
]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should generate positive existential conditions", () => {
|
|
150
|
+
const { sqlQueries, factTypes, roleMap } = sqlFor(`
|
|
151
|
+
(root: Root) {
|
|
152
|
+
project: MyApplication.Project [
|
|
153
|
+
project->root: Root = root
|
|
154
|
+
E {
|
|
155
|
+
assignment: MyApplication.Assignment [
|
|
156
|
+
assignment->project: MyApplication.Project = project
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
`);
|
|
162
|
+
|
|
163
|
+
expect(sqlQueries.length).toEqual(1);
|
|
164
|
+
expect(sqlQueries[0].sql).toEqual(
|
|
165
|
+
'SELECT f2.hash as hash2, ' +
|
|
166
|
+
'f3.hash as hash3, ' +
|
|
167
|
+
'sort(array[f2.fact_id, f3.fact_id], \'desc\') as bookmark ' +
|
|
168
|
+
'FROM public.fact f1 ' +
|
|
169
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $3 ' +
|
|
170
|
+
'JOIN public.fact f2 ON f2.fact_id = e1.successor_fact_id ' +
|
|
171
|
+
'JOIN public.edge e2 ON e2.predecessor_fact_id = f2.fact_id AND e2.role_id = $4 ' +
|
|
172
|
+
'JOIN public.fact f3 ON f3.fact_id = e2.successor_fact_id ' +
|
|
173
|
+
'WHERE f1.fact_type_id = $1 AND f1.hash = $2 ' +
|
|
174
|
+
'AND sort(array[f2.fact_id, f3.fact_id], \'desc\') > $5 ' +
|
|
175
|
+
'ORDER BY bookmark ASC ' +
|
|
176
|
+
'LIMIT $6'
|
|
177
|
+
);
|
|
178
|
+
expect(sqlQueries[0].parameters).toEqual([
|
|
179
|
+
getFactTypeId(factTypes, "Root"),
|
|
180
|
+
rootHash,
|
|
181
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
182
|
+
roleParameter(roleMap, factTypes, "MyApplication.Assignment", "project"),
|
|
183
|
+
[],
|
|
184
|
+
100
|
|
185
|
+
]);
|
|
186
|
+
expect(sqlQueries[0].labels).toEqual([
|
|
187
|
+
{
|
|
188
|
+
name: "project",
|
|
189
|
+
type: "MyApplication.Project",
|
|
190
|
+
index: 2
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "assignment",
|
|
194
|
+
type: "MyApplication.Assignment",
|
|
195
|
+
index: 3
|
|
196
|
+
}
|
|
197
|
+
]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("should generate negative existential conditions", () => {
|
|
201
|
+
const { sqlQueries, factTypes, roleMap } = sqlFor(`
|
|
202
|
+
(root: Root) {
|
|
203
|
+
project: MyApplication.Project [
|
|
204
|
+
project->root: Root = root
|
|
205
|
+
!E {
|
|
206
|
+
deleted: MyApplication.Project.Deleted [
|
|
207
|
+
deleted->project: MyApplication.Project = project
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
`);
|
|
213
|
+
|
|
214
|
+
expect(sqlQueries.length).toEqual(2);
|
|
215
|
+
expect(sqlQueries[0].sql).toEqual(
|
|
216
|
+
'SELECT f2.hash as hash2, ' +
|
|
217
|
+
'f3.hash as hash3, ' +
|
|
218
|
+
'sort(array[f2.fact_id, f3.fact_id], \'desc\') as bookmark ' +
|
|
219
|
+
'FROM public.fact f1 ' +
|
|
220
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $3 ' +
|
|
221
|
+
'JOIN public.fact f2 ON f2.fact_id = e1.successor_fact_id ' +
|
|
222
|
+
'JOIN public.edge e2 ON e2.predecessor_fact_id = f2.fact_id AND e2.role_id = $4 ' +
|
|
223
|
+
'JOIN public.fact f3 ON f3.fact_id = e2.successor_fact_id ' +
|
|
224
|
+
'WHERE f1.fact_type_id = $1 AND f1.hash = $2 ' +
|
|
225
|
+
'AND sort(array[f2.fact_id, f3.fact_id], \'desc\') > $5 ' +
|
|
226
|
+
'ORDER BY bookmark ASC ' +
|
|
227
|
+
'LIMIT $6'
|
|
228
|
+
);
|
|
229
|
+
expect(sqlQueries[0].parameters).toEqual([
|
|
230
|
+
getFactTypeId(factTypes, "Root"),
|
|
231
|
+
rootHash,
|
|
232
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
233
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project.Deleted", "project"),
|
|
234
|
+
[],
|
|
235
|
+
100
|
|
236
|
+
]);
|
|
237
|
+
expect(sqlQueries[0].labels).toEqual([
|
|
238
|
+
{
|
|
239
|
+
name: "project",
|
|
240
|
+
type: "MyApplication.Project",
|
|
241
|
+
index: 2
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "deleted",
|
|
245
|
+
type: "MyApplication.Project.Deleted",
|
|
246
|
+
index: 3
|
|
247
|
+
}
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
expect(sqlQueries[1].sql).toEqual(
|
|
251
|
+
'SELECT f2.hash as hash2, ' +
|
|
252
|
+
'sort(array[f2.fact_id], \'desc\') as bookmark ' +
|
|
253
|
+
'FROM public.fact f1 ' +
|
|
254
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $3 ' +
|
|
255
|
+
'JOIN public.fact f2 ON f2.fact_id = e1.successor_fact_id ' +
|
|
256
|
+
'WHERE f1.fact_type_id = $1 AND f1.hash = $2 ' +
|
|
257
|
+
'AND NOT EXISTS (' +
|
|
258
|
+
'SELECT 1 ' +
|
|
259
|
+
'FROM public.edge e2 ' +
|
|
260
|
+
'JOIN public.fact f3 ON f3.fact_id = e2.successor_fact_id ' +
|
|
261
|
+
'WHERE e2.predecessor_fact_id = f2.fact_id AND e2.role_id = $4' +
|
|
262
|
+
') ' +
|
|
263
|
+
'AND sort(array[f2.fact_id], \'desc\') > $5 ' +
|
|
264
|
+
'ORDER BY bookmark ASC ' +
|
|
265
|
+
'LIMIT $6'
|
|
266
|
+
);
|
|
267
|
+
expect(sqlQueries[1].parameters).toEqual([
|
|
268
|
+
getFactTypeId(factTypes, "Root"),
|
|
269
|
+
rootHash,
|
|
270
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
271
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project.Deleted", "project"),
|
|
272
|
+
[],
|
|
273
|
+
100
|
|
274
|
+
]);
|
|
275
|
+
expect(sqlQueries[1].labels).toEqual([
|
|
276
|
+
{
|
|
277
|
+
name: "project",
|
|
278
|
+
type: "MyApplication.Project",
|
|
279
|
+
index: 2
|
|
280
|
+
}
|
|
281
|
+
]);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("should accept multiple givens", () => {
|
|
285
|
+
const { sqlQueries, factTypes, roleMap } = sqlFor(`
|
|
286
|
+
(root: Root, user: Jinaga.User) {
|
|
287
|
+
project: MyApplication.Project [
|
|
288
|
+
project->root: Root = root
|
|
289
|
+
]
|
|
290
|
+
assignment: MyApplication.Assignment [
|
|
291
|
+
assignment->project: MyApplication.Project = project
|
|
292
|
+
assignment->user: Jinaga.User = user
|
|
293
|
+
]
|
|
294
|
+
}
|
|
295
|
+
`);
|
|
296
|
+
|
|
297
|
+
expect(sqlQueries.length).toEqual(1);
|
|
298
|
+
expect(sqlQueries[0].sql).toEqual(
|
|
299
|
+
'SELECT f3.hash as hash3, ' +
|
|
300
|
+
'f4.hash as hash4, ' +
|
|
301
|
+
'sort(array[f3.fact_id, f4.fact_id], \'desc\') as bookmark ' +
|
|
302
|
+
'FROM public.fact f1 ' +
|
|
303
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $3 ' +
|
|
304
|
+
'JOIN public.fact f3 ON f3.fact_id = e1.successor_fact_id ' +
|
|
305
|
+
'JOIN public.edge e2 ON e2.predecessor_fact_id = f3.fact_id AND e2.role_id = $4 ' +
|
|
306
|
+
'JOIN public.fact f4 ON f4.fact_id = e2.successor_fact_id ' +
|
|
307
|
+
'JOIN public.edge e3 ON e3.successor_fact_id = f4.fact_id AND e3.role_id = $7 ' +
|
|
308
|
+
'JOIN public.fact f2 ON f2.fact_id = e3.predecessor_fact_id ' +
|
|
309
|
+
'WHERE f1.fact_type_id = $1 AND f1.hash = $2 ' +
|
|
310
|
+
'AND f2.fact_type_id = $5 AND f2.hash = $6 ' +
|
|
311
|
+
'AND sort(array[f3.fact_id, f4.fact_id], \'desc\') > $8 ' +
|
|
312
|
+
'ORDER BY bookmark ASC ' +
|
|
313
|
+
'LIMIT $9'
|
|
314
|
+
);
|
|
315
|
+
expect(sqlQueries[0].parameters).toEqual([
|
|
316
|
+
getFactTypeId(factTypes, "Root"),
|
|
317
|
+
rootHash,
|
|
318
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
319
|
+
roleParameter(roleMap, factTypes, "MyApplication.Assignment", "project"),
|
|
320
|
+
getFactTypeId(factTypes, "Jinaga.User"),
|
|
321
|
+
userHash,
|
|
322
|
+
roleParameter(roleMap, factTypes, "MyApplication.Assignment", "user"),
|
|
323
|
+
[],
|
|
324
|
+
100
|
|
325
|
+
]);
|
|
326
|
+
expect(sqlQueries[0].labels).toEqual([
|
|
327
|
+
{
|
|
328
|
+
name: "project",
|
|
329
|
+
type: "MyApplication.Project",
|
|
330
|
+
index: 3
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name: "assignment",
|
|
334
|
+
type: "MyApplication.Assignment",
|
|
335
|
+
index: 4
|
|
336
|
+
}
|
|
337
|
+
]);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("should accept givens in any order", () => {
|
|
341
|
+
const { sqlQueries, factTypes, roleMap } = sqlFor(`
|
|
342
|
+
(user: Jinaga.User, root: Root) {
|
|
343
|
+
project: MyApplication.Project [
|
|
344
|
+
project->root: Root = root
|
|
345
|
+
]
|
|
346
|
+
assignment: MyApplication.Assignment [
|
|
347
|
+
assignment->project: MyApplication.Project = project
|
|
348
|
+
assignment->user: Jinaga.User = user
|
|
349
|
+
]
|
|
350
|
+
}
|
|
351
|
+
`);
|
|
352
|
+
|
|
353
|
+
expect(sqlQueries.length).toEqual(1);
|
|
354
|
+
expect(sqlQueries[0].sql).toEqual(
|
|
355
|
+
'SELECT f3.hash as hash3, ' +
|
|
356
|
+
'f4.hash as hash4, ' +
|
|
357
|
+
'sort(array[f3.fact_id, f4.fact_id], \'desc\') as bookmark ' +
|
|
358
|
+
'FROM public.fact f2 ' +
|
|
359
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f2.fact_id AND e1.role_id = $3 ' +
|
|
360
|
+
'JOIN public.fact f3 ON f3.fact_id = e1.successor_fact_id ' +
|
|
361
|
+
'JOIN public.edge e2 ON e2.predecessor_fact_id = f3.fact_id AND e2.role_id = $4 ' +
|
|
362
|
+
'JOIN public.fact f4 ON f4.fact_id = e2.successor_fact_id ' +
|
|
363
|
+
'JOIN public.edge e3 ON e3.successor_fact_id = f4.fact_id AND e3.role_id = $7 ' +
|
|
364
|
+
'JOIN public.fact f1 ON f1.fact_id = e3.predecessor_fact_id ' +
|
|
365
|
+
'WHERE f1.fact_type_id = $5 AND f1.hash = $6 ' +
|
|
366
|
+
'AND f2.fact_type_id = $1 AND f2.hash = $2 ' +
|
|
367
|
+
'AND sort(array[f3.fact_id, f4.fact_id], \'desc\') > $8 ' +
|
|
368
|
+
'ORDER BY bookmark ASC ' +
|
|
369
|
+
'LIMIT $9'
|
|
370
|
+
);
|
|
371
|
+
expect(sqlQueries[0].parameters).toEqual([
|
|
372
|
+
getFactTypeId(factTypes, "Root"),
|
|
373
|
+
rootHash,
|
|
374
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
375
|
+
roleParameter(roleMap, factTypes, "MyApplication.Assignment", "project"),
|
|
376
|
+
getFactTypeId(factTypes, "Jinaga.User"),
|
|
377
|
+
userHash,
|
|
378
|
+
roleParameter(roleMap, factTypes, "MyApplication.Assignment", "user"),
|
|
379
|
+
[],
|
|
380
|
+
100
|
|
381
|
+
]);
|
|
382
|
+
expect(sqlQueries[0].labels).toEqual([
|
|
383
|
+
{
|
|
384
|
+
name: "project",
|
|
385
|
+
type: "MyApplication.Project",
|
|
386
|
+
index: 3
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: "assignment",
|
|
390
|
+
type: "MyApplication.Assignment",
|
|
391
|
+
index: 4
|
|
392
|
+
}
|
|
393
|
+
]);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it("should accept a projection", () => {
|
|
397
|
+
const { sqlQueries, factTypes, roleMap } = sqlFor(`
|
|
398
|
+
(root: Root) {
|
|
399
|
+
project: MyApplication.Project [
|
|
400
|
+
project->root: Root = root
|
|
401
|
+
]
|
|
402
|
+
} => {
|
|
403
|
+
names = {
|
|
404
|
+
name: MyApplication.Project.Name [
|
|
405
|
+
name->project: MyApplication.Project = project
|
|
406
|
+
]
|
|
407
|
+
}
|
|
408
|
+
}`);
|
|
409
|
+
|
|
410
|
+
expect(sqlQueries.length).toEqual(2);
|
|
411
|
+
expect(sqlQueries[0].sql).toEqual(
|
|
412
|
+
'SELECT f2.hash as hash2, ' +
|
|
413
|
+
'sort(array[f2.fact_id], \'desc\') as bookmark ' +
|
|
414
|
+
'FROM public.fact f1 ' +
|
|
415
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $3 ' +
|
|
416
|
+
'JOIN public.fact f2 ON f2.fact_id = e1.successor_fact_id ' +
|
|
417
|
+
'WHERE f1.fact_type_id = $1 AND f1.hash = $2 ' +
|
|
418
|
+
'AND sort(array[f2.fact_id], \'desc\') > $4 ' +
|
|
419
|
+
'ORDER BY bookmark ASC ' +
|
|
420
|
+
'LIMIT $5'
|
|
421
|
+
);
|
|
422
|
+
expect(sqlQueries[0].parameters).toEqual([
|
|
423
|
+
getFactTypeId(factTypes, "Root"),
|
|
424
|
+
rootHash,
|
|
425
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
426
|
+
[],
|
|
427
|
+
100
|
|
428
|
+
]);
|
|
429
|
+
expect(sqlQueries[0].labels).toEqual([
|
|
430
|
+
{
|
|
431
|
+
name: "project",
|
|
432
|
+
type: "MyApplication.Project",
|
|
433
|
+
index: 2
|
|
434
|
+
}
|
|
435
|
+
]);
|
|
436
|
+
expect(sqlQueries[1].sql).toEqual(
|
|
437
|
+
'SELECT f2.hash as hash2, f3.hash as hash3, ' +
|
|
438
|
+
'sort(array[f2.fact_id, f3.fact_id], \'desc\') as bookmark ' +
|
|
439
|
+
'FROM public.fact f1 ' +
|
|
440
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $3 ' +
|
|
441
|
+
'JOIN public.fact f2 ON f2.fact_id = e1.successor_fact_id ' +
|
|
442
|
+
'JOIN public.edge e2 ON e2.predecessor_fact_id = f2.fact_id AND e2.role_id = $4 ' +
|
|
443
|
+
'JOIN public.fact f3 ON f3.fact_id = e2.successor_fact_id ' +
|
|
444
|
+
'WHERE f1.fact_type_id = $1 AND f1.hash = $2 ' +
|
|
445
|
+
'AND sort(array[f2.fact_id, f3.fact_id], \'desc\') > $5 ' +
|
|
446
|
+
'ORDER BY bookmark ASC ' +
|
|
447
|
+
'LIMIT $6'
|
|
448
|
+
);
|
|
449
|
+
expect(sqlQueries[1].parameters).toEqual([
|
|
450
|
+
getFactTypeId(factTypes, "Root"),
|
|
451
|
+
rootHash,
|
|
452
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
453
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project.Name", "project"),
|
|
454
|
+
[],
|
|
455
|
+
100
|
|
456
|
+
]);
|
|
457
|
+
expect(sqlQueries[1].labels).toEqual([
|
|
458
|
+
{
|
|
459
|
+
name: "project",
|
|
460
|
+
type: "MyApplication.Project",
|
|
461
|
+
index: 2
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
name: "names.name",
|
|
465
|
+
type: "MyApplication.Project.Name",
|
|
466
|
+
index: 3
|
|
467
|
+
}
|
|
468
|
+
]);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("should apply the bookmark for each feed", () => {
|
|
472
|
+
const { sqlQueries, factTypes, roleMap } = sqlFor(`
|
|
473
|
+
(root: Root) {
|
|
474
|
+
project: MyApplication.Project [
|
|
475
|
+
project->root: Root = root
|
|
476
|
+
]
|
|
477
|
+
} => {
|
|
478
|
+
names = {
|
|
479
|
+
name: MyApplication.Project.Name [
|
|
480
|
+
name->project: MyApplication.Project = project
|
|
481
|
+
!E {
|
|
482
|
+
next: MyApplication.Project.Name [
|
|
483
|
+
next->prior: MyApplication.Project.Name = name
|
|
484
|
+
]
|
|
485
|
+
}
|
|
486
|
+
]
|
|
487
|
+
}
|
|
488
|
+
}`, [
|
|
489
|
+
{
|
|
490
|
+
labels: [ "project" ],
|
|
491
|
+
bookmark: "123"
|
|
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
|
+
}
|
|
501
|
+
]);
|
|
502
|
+
|
|
503
|
+
expect(sqlQueries.length).toEqual(3);
|
|
504
|
+
expect(sqlQueries[0].sql).toEqual(
|
|
505
|
+
'SELECT f2.hash as hash2, ' +
|
|
506
|
+
'sort(array[f2.fact_id], \'desc\') as bookmark ' +
|
|
507
|
+
'FROM public.fact f1 ' +
|
|
508
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $3 ' +
|
|
509
|
+
'JOIN public.fact f2 ON f2.fact_id = e1.successor_fact_id ' +
|
|
510
|
+
'WHERE f1.fact_type_id = $1 AND f1.hash = $2 ' +
|
|
511
|
+
'AND sort(array[f2.fact_id], \'desc\') > $4 ' +
|
|
512
|
+
'ORDER BY bookmark ASC ' +
|
|
513
|
+
'LIMIT $5'
|
|
514
|
+
);
|
|
515
|
+
expect(sqlQueries[0].parameters).toEqual([
|
|
516
|
+
getFactTypeId(factTypes, "Root"),
|
|
517
|
+
rootHash,
|
|
518
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
519
|
+
[ 123 ],
|
|
520
|
+
100
|
|
521
|
+
]);
|
|
522
|
+
expect(sqlQueries[0].labels).toEqual([
|
|
523
|
+
{
|
|
524
|
+
name: "project",
|
|
525
|
+
type: "MyApplication.Project",
|
|
526
|
+
index: 2
|
|
527
|
+
}
|
|
528
|
+
]);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it("should accept overconstrained specification", () => {
|
|
532
|
+
const { sqlQueries, factTypes, roleMap } = sqlFor(`
|
|
533
|
+
(root: Root) {
|
|
534
|
+
project: MyApplication.Project [
|
|
535
|
+
project->root: Root = root
|
|
536
|
+
project->root2: Root = root
|
|
537
|
+
]
|
|
538
|
+
}`);
|
|
539
|
+
|
|
540
|
+
expect(sqlQueries.length).toEqual(1);
|
|
541
|
+
expect(sqlQueries[0].sql).toEqual(
|
|
542
|
+
'SELECT f2.hash as hash2, ' +
|
|
543
|
+
'sort(array[f2.fact_id], \'desc\') as bookmark ' +
|
|
544
|
+
'FROM public.fact f1 ' +
|
|
545
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $3 ' +
|
|
546
|
+
'JOIN public.fact f2 ON f2.fact_id = e1.successor_fact_id ' +
|
|
547
|
+
'JOIN public.edge e2 ON e2.predecessor_fact_id = f1.fact_id AND e2.successor_fact_id = f2.fact_id AND e2.role_id = $4 ' +
|
|
548
|
+
'WHERE f1.fact_type_id = $1 AND f1.hash = $2 ' +
|
|
549
|
+
'AND sort(array[f2.fact_id], \'desc\') > $5 ' +
|
|
550
|
+
'ORDER BY bookmark ASC ' +
|
|
551
|
+
'LIMIT $6'
|
|
552
|
+
);
|
|
553
|
+
expect(sqlQueries[0].parameters).toEqual([
|
|
554
|
+
getFactTypeId(factTypes, "Root"),
|
|
555
|
+
rootHash,
|
|
556
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
557
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project", "root2"),
|
|
558
|
+
[],
|
|
559
|
+
100
|
|
560
|
+
]);
|
|
561
|
+
expect(sqlQueries[0].labels).toEqual([
|
|
562
|
+
{
|
|
563
|
+
name: "project",
|
|
564
|
+
type: "MyApplication.Project",
|
|
565
|
+
index: 2
|
|
566
|
+
}
|
|
567
|
+
]);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it("should accept overconstrained existential query", () => {
|
|
571
|
+
const { sqlQueries, factTypes, roleMap } = sqlFor(`
|
|
572
|
+
(root: Root) {
|
|
573
|
+
project: MyApplication.Project [
|
|
574
|
+
project->root: Root = root
|
|
575
|
+
]
|
|
576
|
+
} => {
|
|
577
|
+
names = {
|
|
578
|
+
name: MyApplication.Project.Name [
|
|
579
|
+
name->project: MyApplication.Project = project
|
|
580
|
+
!E {
|
|
581
|
+
next: MyApplication.Project.Name [
|
|
582
|
+
next->prior: MyApplication.Project.Name = name
|
|
583
|
+
next->project: MyApplication.Project = project
|
|
584
|
+
]
|
|
585
|
+
}
|
|
586
|
+
]
|
|
587
|
+
}
|
|
588
|
+
}`);
|
|
589
|
+
expect(sqlQueries.length).toEqual(3);
|
|
590
|
+
expect(sqlQueries[2].sql).toEqual(
|
|
591
|
+
'SELECT f2.hash as hash2, f3.hash as hash3, ' +
|
|
592
|
+
'sort(array[f2.fact_id, f3.fact_id], \'desc\') as bookmark ' +
|
|
593
|
+
'FROM public.fact f1 ' +
|
|
594
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $3 ' +
|
|
595
|
+
'JOIN public.fact f2 ON f2.fact_id = e1.successor_fact_id ' +
|
|
596
|
+
'JOIN public.edge e2 ON e2.predecessor_fact_id = f2.fact_id AND e2.role_id = $4 ' +
|
|
597
|
+
'JOIN public.fact f3 ON f3.fact_id = e2.successor_fact_id ' +
|
|
598
|
+
'WHERE f1.fact_type_id = $1 AND f1.hash = $2 ' +
|
|
599
|
+
'AND NOT EXISTS (' +
|
|
600
|
+
'SELECT 1 ' +
|
|
601
|
+
'FROM public.edge e3 ' +
|
|
602
|
+
'JOIN public.fact f4 ON f4.fact_id = e3.successor_fact_id ' +
|
|
603
|
+
'JOIN public.edge e4 ON e4.predecessor_fact_id = f2.fact_id AND e4.successor_fact_id = f4.fact_id AND e4.role_id = $6 ' +
|
|
604
|
+
'WHERE e3.predecessor_fact_id = f3.fact_id AND e3.role_id = $5' +
|
|
605
|
+
') ' +
|
|
606
|
+
'AND sort(array[f2.fact_id, f3.fact_id], \'desc\') > $7 ' +
|
|
607
|
+
'ORDER BY bookmark ASC ' +
|
|
608
|
+
'LIMIT $8'
|
|
609
|
+
);
|
|
610
|
+
expect(sqlQueries[1].parameters).toEqual([
|
|
611
|
+
getFactTypeId(factTypes, "Root"),
|
|
612
|
+
rootHash,
|
|
613
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
614
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project.Name", "project"),
|
|
615
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project.Name", "prior"),
|
|
616
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project.Name", "project"),
|
|
617
|
+
[],
|
|
618
|
+
100
|
|
619
|
+
]);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
it("skips unknown types in matches", () => {
|
|
623
|
+
const { sqlQueries } = sqlFor(`
|
|
624
|
+
(root: Root) {
|
|
625
|
+
successor: Unknown [
|
|
626
|
+
successor->root: Root = root
|
|
627
|
+
]
|
|
628
|
+
}`);
|
|
629
|
+
|
|
630
|
+
expect(sqlQueries.length).toEqual(0);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it("skips unknown types on left in paths", () => {
|
|
634
|
+
const { sqlQueries } = sqlFor(`
|
|
635
|
+
(root: Root) {
|
|
636
|
+
successor: MyApplication.Project [
|
|
637
|
+
successor->intermediate: Unknown->root: Root = root
|
|
638
|
+
]
|
|
639
|
+
}`);
|
|
640
|
+
|
|
641
|
+
expect(sqlQueries.length).toEqual(0);
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it("skips unknown types on right in paths", () => {
|
|
645
|
+
const { sqlQueries } = sqlFor(`
|
|
646
|
+
(root: Root) {
|
|
647
|
+
successor: MyApplication.Successor [
|
|
648
|
+
successor = root->intermediate: Unknown->root: Root
|
|
649
|
+
]
|
|
650
|
+
}`);
|
|
651
|
+
|
|
652
|
+
expect(sqlQueries.length).toEqual(0);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it("skips unknown roles on left in paths", () => {
|
|
656
|
+
const { sqlQueries } = sqlFor(`
|
|
657
|
+
(root: Root) {
|
|
658
|
+
successor: MyApplication.Project [
|
|
659
|
+
successor->unknown: Root = root
|
|
660
|
+
]
|
|
661
|
+
}`);
|
|
662
|
+
|
|
663
|
+
expect(sqlQueries.length).toEqual(0);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
it("skips unknown roles on right in paths", () => {
|
|
667
|
+
const { sqlQueries } = sqlFor(`
|
|
668
|
+
(root: Root) {
|
|
669
|
+
successor: MyApplication.Successor [
|
|
670
|
+
successor = root->unknown: Intermediate->root: Root
|
|
671
|
+
]
|
|
672
|
+
}`);
|
|
673
|
+
|
|
674
|
+
expect(sqlQueries.length).toEqual(0);
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it("skips entire query when second path contains unknown role", () => {
|
|
678
|
+
const { sqlQueries } = sqlFor(`
|
|
679
|
+
(root: Root) {
|
|
680
|
+
successor: MyApplication.Project [
|
|
681
|
+
successor->root: Root = root
|
|
682
|
+
successor->unknown: Root = root
|
|
683
|
+
]
|
|
684
|
+
}`);
|
|
685
|
+
|
|
686
|
+
expect(sqlQueries.length).toEqual(0);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
it("skips entire query when first path contains unknown role", () => {
|
|
690
|
+
const { sqlQueries } = sqlFor(`
|
|
691
|
+
(root: Root) {
|
|
692
|
+
successor: MyApplication.Project [
|
|
693
|
+
successor->unknown: Root = root
|
|
694
|
+
successor->root: Root = root
|
|
695
|
+
]
|
|
696
|
+
}`);
|
|
697
|
+
|
|
698
|
+
expect(sqlQueries.length).toEqual(0);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
it("skips existential conditions that are unsatisfiable", () => {
|
|
702
|
+
const { sqlQueries, factTypes, roleMap } = sqlFor(`
|
|
703
|
+
(root: Root) {
|
|
704
|
+
project: MyApplication.Project [
|
|
705
|
+
project->root: Root = root
|
|
706
|
+
!E {
|
|
707
|
+
delete: Unknown [
|
|
708
|
+
delete->project: MyApplication.Project = project
|
|
709
|
+
]
|
|
710
|
+
}
|
|
711
|
+
]
|
|
712
|
+
}`);
|
|
713
|
+
|
|
714
|
+
expect(sqlQueries.length).toEqual(1);
|
|
715
|
+
|
|
716
|
+
expect(sqlQueries[0].sql).toEqual(
|
|
717
|
+
'SELECT f2.hash as hash2, ' +
|
|
718
|
+
'sort(array[f2.fact_id], \'desc\') as bookmark ' +
|
|
719
|
+
'FROM public.fact f1 ' +
|
|
720
|
+
'JOIN public.edge e1 ON e1.predecessor_fact_id = f1.fact_id AND e1.role_id = $3 ' +
|
|
721
|
+
'JOIN public.fact f2 ON f2.fact_id = e1.successor_fact_id ' +
|
|
722
|
+
'WHERE f1.fact_type_id = $1 AND f1.hash = $2 ' +
|
|
723
|
+
'AND sort(array[f2.fact_id], \'desc\') > $4 ' +
|
|
724
|
+
'ORDER BY bookmark ASC ' +
|
|
725
|
+
'LIMIT $5'
|
|
726
|
+
);
|
|
727
|
+
expect(sqlQueries[0].parameters).toEqual([
|
|
728
|
+
getFactTypeId(factTypes, "Root"),
|
|
729
|
+
rootHash,
|
|
730
|
+
roleParameter(roleMap, factTypes, "MyApplication.Project", "root"),
|
|
731
|
+
[],
|
|
732
|
+
100
|
|
733
|
+
]);
|
|
734
|
+
expect(sqlQueries[0].labels).toEqual([
|
|
735
|
+
{
|
|
736
|
+
name: "project",
|
|
737
|
+
type: "MyApplication.Project",
|
|
738
|
+
index: 2
|
|
739
|
+
}
|
|
740
|
+
])
|
|
741
|
+
});
|
|
742
|
+
});
|