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,874 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canonicalPredecessors,
|
|
3
|
+
Direction,
|
|
4
|
+
ExistentialCondition,
|
|
5
|
+
FactBookmark,
|
|
6
|
+
FactEnvelope,
|
|
7
|
+
FactPath,
|
|
8
|
+
FactRecord,
|
|
9
|
+
FactReference,
|
|
10
|
+
factReferenceEquals,
|
|
11
|
+
FactStream,
|
|
12
|
+
FactTuple,
|
|
13
|
+
getAllFactTypes,
|
|
14
|
+
getAllRoles,
|
|
15
|
+
Join,
|
|
16
|
+
PredecessorCollection,
|
|
17
|
+
PropertyCondition,
|
|
18
|
+
Query,
|
|
19
|
+
Specification,
|
|
20
|
+
Step,
|
|
21
|
+
Storage,
|
|
22
|
+
} from "jinaga";
|
|
23
|
+
import { PoolClient } from "pg";
|
|
24
|
+
|
|
25
|
+
import { distinct, flatten } from "../util/fn";
|
|
26
|
+
import { ConnectionFactory, Row } from "./connection";
|
|
27
|
+
import { EdgeRecord, makeEdgeRecords } from "./edge-record";
|
|
28
|
+
import {
|
|
29
|
+
addFact,
|
|
30
|
+
addFactType,
|
|
31
|
+
addRole,
|
|
32
|
+
copyRoleMap,
|
|
33
|
+
emptyFactMap,
|
|
34
|
+
emptyFactTypeMap,
|
|
35
|
+
emptyPublicKeyMap,
|
|
36
|
+
emptyRoleMap,
|
|
37
|
+
FactMap,
|
|
38
|
+
FactTypeMap,
|
|
39
|
+
getFactId,
|
|
40
|
+
getFactTypeId,
|
|
41
|
+
getPublicKeyId,
|
|
42
|
+
getRoleId,
|
|
43
|
+
hasFact,
|
|
44
|
+
hasRole,
|
|
45
|
+
mergeFactTypes,
|
|
46
|
+
mergeRoleMaps,
|
|
47
|
+
PublicKeyMap,
|
|
48
|
+
RoleMap,
|
|
49
|
+
} from "./maps";
|
|
50
|
+
import { SpecificationLabel } from "./query-description";
|
|
51
|
+
import { ResultSetTree, resultSqlFromSpecification, SqlQueryTree } from "./specification-result-sql";
|
|
52
|
+
import { sqlFromSpecification } from "./specification-sql";
|
|
53
|
+
import { sqlFromSteps } from "./sql";
|
|
54
|
+
|
|
55
|
+
interface FactTypeResult {
|
|
56
|
+
rows: {
|
|
57
|
+
fact_type_id: number;
|
|
58
|
+
name: string;
|
|
59
|
+
}[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface RoleResult {
|
|
63
|
+
rows: {
|
|
64
|
+
role_id: number;
|
|
65
|
+
name: string;
|
|
66
|
+
defining_fact_type_id: number;
|
|
67
|
+
}[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface FactResult {
|
|
71
|
+
rows: {
|
|
72
|
+
fact_id: number;
|
|
73
|
+
fact_type_id: number;
|
|
74
|
+
hash: string;
|
|
75
|
+
}[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface ExistsResult {
|
|
79
|
+
rows: {
|
|
80
|
+
fact_type_id: number;
|
|
81
|
+
hash: string;
|
|
82
|
+
}[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface PublicKeyResult {
|
|
86
|
+
rows: {
|
|
87
|
+
public_key_id: number;
|
|
88
|
+
public_key: string;
|
|
89
|
+
}[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface AncestorResult {
|
|
93
|
+
rows: {
|
|
94
|
+
fact_type_id: number;
|
|
95
|
+
name: string;
|
|
96
|
+
hash: string;
|
|
97
|
+
data: string;
|
|
98
|
+
}[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function loadFactReference(r: Row): FactReference {
|
|
102
|
+
return {
|
|
103
|
+
type: r.type,
|
|
104
|
+
hash: r.hash
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function loadFactTuple(labels: SpecificationLabel[], row: Row): FactTuple {
|
|
109
|
+
const facts = labels.map(label => {
|
|
110
|
+
const hashColumn = `hash${label.index}`;
|
|
111
|
+
const hash = row[hashColumn];
|
|
112
|
+
if (hash === null) {
|
|
113
|
+
const columns = Object.keys(row);
|
|
114
|
+
throw new Error(`Cannot find column '${hashColumn}'. Available columns: ${columns.join(', ')}`);
|
|
115
|
+
}
|
|
116
|
+
const fact: FactReference = {
|
|
117
|
+
type: label.type,
|
|
118
|
+
hash
|
|
119
|
+
}
|
|
120
|
+
return fact;
|
|
121
|
+
});
|
|
122
|
+
const bookmark: number[] = row.bookmark;
|
|
123
|
+
return {
|
|
124
|
+
facts,
|
|
125
|
+
bookmark: bookmark.join(".")
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function loadFactPath(pathLength: number, factTypeNames: string[], r: Row): FactPath {
|
|
130
|
+
let path: FactPath = [];
|
|
131
|
+
for (let i = 0; i < pathLength; i++) {
|
|
132
|
+
const hash = r['hash' + (i + 2)];
|
|
133
|
+
if (!hash) {
|
|
134
|
+
throw new Error(`Cannot find column 'hash${i + 2}'`);
|
|
135
|
+
}
|
|
136
|
+
path.push({
|
|
137
|
+
type: factTypeNames[i],
|
|
138
|
+
hash: hash
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return path;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export class PostgresStore implements Storage {
|
|
145
|
+
private connectionFactory: ConnectionFactory;
|
|
146
|
+
private factTypeMap: FactTypeMap = emptyFactTypeMap();
|
|
147
|
+
private roleMap: RoleMap = emptyRoleMap();
|
|
148
|
+
|
|
149
|
+
constructor (postgresUri: string) {
|
|
150
|
+
this.connectionFactory = new ConnectionFactory(postgresUri);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async close() {
|
|
154
|
+
await this.connectionFactory.close();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async save(envelopes: FactEnvelope[]): Promise<FactEnvelope[]> {
|
|
158
|
+
if (envelopes.length > 0) {
|
|
159
|
+
const facts = envelopes.map(e => e.fact);
|
|
160
|
+
if (facts.some(f => !f.hash || !f.type)) {
|
|
161
|
+
throw new Error('Attempted to save a fact with no hash or type.');
|
|
162
|
+
}
|
|
163
|
+
const { newEnvelopes, factTypes, roles } : {
|
|
164
|
+
newEnvelopes: FactEnvelope[];
|
|
165
|
+
factTypes: FactTypeMap;
|
|
166
|
+
roles: RoleMap;
|
|
167
|
+
} = await this.connectionFactory.withTransaction(async (connection) => {
|
|
168
|
+
const factTypes = await storeFactTypes(facts, this.factTypeMap, connection);
|
|
169
|
+
const existingFacts = await findExistingFacts(facts, factTypes, connection);
|
|
170
|
+
const newFacts = facts.filter(f => !hasFact(existingFacts, f.hash, factTypes.get(f.type)));
|
|
171
|
+
if (newFacts.length === 0) {
|
|
172
|
+
return {
|
|
173
|
+
newEnvelopes: [],
|
|
174
|
+
factTypes,
|
|
175
|
+
roles: emptyRoleMap()
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const roles = await storeRoles(newFacts, factTypes, copyRoleMap(this.roleMap), connection);
|
|
180
|
+
const allFacts = await insertFactsEdgesAndAncestors(newFacts, factTypes, existingFacts, connection, roles);
|
|
181
|
+
const newEnvelopes = envelopes.filter(envelope => newFacts.some(
|
|
182
|
+
factReferenceEquals(envelope.fact)));
|
|
183
|
+
if (newEnvelopes.length === 0) {
|
|
184
|
+
return {
|
|
185
|
+
newEnvelopes,
|
|
186
|
+
factTypes,
|
|
187
|
+
roles
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const publicKeys = await storePublicKeys(newEnvelopes, connection);
|
|
192
|
+
await insertSignatures(newEnvelopes, allFacts, factTypes, publicKeys, connection);
|
|
193
|
+
return {
|
|
194
|
+
newEnvelopes,
|
|
195
|
+
factTypes,
|
|
196
|
+
roles
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
this.factTypeMap = mergeFactTypes(this.factTypeMap, factTypes);
|
|
200
|
+
this.roleMap = mergeRoleMaps(this.roleMap, roles);
|
|
201
|
+
return newEnvelopes;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async query(start: FactReference, query: Query): Promise<FactPath[]> {
|
|
209
|
+
if (query.steps.length === 0) {
|
|
210
|
+
return [[start]];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const factTypes = await this.loadFactTypesFromSteps(query.steps, start.type);
|
|
215
|
+
const roleMap = await this.loadRolesFromSteps(query.steps, factTypes, start.type);
|
|
216
|
+
|
|
217
|
+
const sqlQuery = sqlFromSteps(start, query.steps, factTypes, roleMap);
|
|
218
|
+
if (!sqlQuery) {
|
|
219
|
+
throw new Error(`Could not generate SQL for query "${query.toDescriptiveString()}" starting at "${start.type}"`);
|
|
220
|
+
}
|
|
221
|
+
if (sqlQuery.empty) {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
const { rows } = await this.connectionFactory.with(async (connection) => {
|
|
225
|
+
return await connection.query(sqlQuery.sql, sqlQuery.parameters);
|
|
226
|
+
});
|
|
227
|
+
return rows.map(row => loadFactPath(sqlQuery.pathLength, sqlQuery.factTypeNames, row));
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
throw new Error(`Could not generate SQL for query "${query.toDescriptiveString()}" starting at "${start.type}": ${e}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async read(start: FactReference[], specification: Specification): Promise<any[]> {
|
|
235
|
+
const factTypes = await this.loadFactTypesFromSpecification(specification);
|
|
236
|
+
const roleMap = await this.loadRolesFromSpecification(specification, factTypes);
|
|
237
|
+
|
|
238
|
+
const composer = resultSqlFromSpecification(start, specification, factTypes, roleMap);
|
|
239
|
+
if (composer === null) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const sqlQueryTree = composer.getSqlQueries();
|
|
244
|
+
const resultSets = await this.connectionFactory.with(async (connection) => {
|
|
245
|
+
return await executeQueryTree(sqlQueryTree, connection);
|
|
246
|
+
});
|
|
247
|
+
return composer.compose(resultSets);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async streamsFromSpecification(start: FactReference[], bookmarks: FactBookmark[], limit: number, specification: Specification): Promise<FactStream[]> {
|
|
251
|
+
const factTypes = await this.loadFactTypesFromSpecification(specification);
|
|
252
|
+
const roleMap = await this.loadRolesFromSpecification(specification, factTypes);
|
|
253
|
+
|
|
254
|
+
const sqlQueries = sqlFromSpecification(start, bookmarks, limit, specification, factTypes, roleMap);
|
|
255
|
+
const streams = await this.connectionFactory.with(async (connection) => {
|
|
256
|
+
const streams: FactStream[] = [];
|
|
257
|
+
for (const sqlQuery of sqlQueries) {
|
|
258
|
+
try {
|
|
259
|
+
const { rows } = await connection.query(sqlQuery.sql, sqlQuery.parameters);
|
|
260
|
+
const tuples = rows.map(row => loadFactTuple(sqlQuery.labels, row));
|
|
261
|
+
streams.push({
|
|
262
|
+
labels: sqlQuery.labels.map(l => l.name),
|
|
263
|
+
tuples,
|
|
264
|
+
bookmark: tuples.length > 0 ? tuples[tuples.length - 1].bookmark : sqlQuery.bookmark
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
throw new Error(`Could not execute query "${sqlQuery.sql}", parameters ${sqlQuery.parameters}:\n${error}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return streams;
|
|
272
|
+
});
|
|
273
|
+
return streams;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private async loadFactTypesFromSteps(steps: Step[], startType: string): Promise<FactTypeMap> {
|
|
277
|
+
const factTypes = this.factTypeMap;
|
|
278
|
+
const unknownFactTypes = [...allFactTypes(steps), startType]
|
|
279
|
+
.filter(factType => !factTypes.has(factType))
|
|
280
|
+
.filter(distinct);
|
|
281
|
+
if (unknownFactTypes.length > 0) {
|
|
282
|
+
const loadedFactTypes = await this.connectionFactory.with(async (connection) => {
|
|
283
|
+
return await loadFactTypes(unknownFactTypes, connection);
|
|
284
|
+
});
|
|
285
|
+
const merged = mergeFactTypes(this.factTypeMap, loadedFactTypes);
|
|
286
|
+
this.factTypeMap = merged;
|
|
287
|
+
return merged;
|
|
288
|
+
}
|
|
289
|
+
return factTypes;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async loadFactTypesFromSpecification(specification: Specification): Promise<FactTypeMap> {
|
|
293
|
+
const factTypes = this.factTypeMap;
|
|
294
|
+
const unknownFactTypes = getAllFactTypes(specification)
|
|
295
|
+
.filter(factType => !factTypes.has(factType));
|
|
296
|
+
if (unknownFactTypes.length > 0) {
|
|
297
|
+
const loadedFactTypes = await this.connectionFactory.with(async (connection) => {
|
|
298
|
+
return await loadFactTypes(unknownFactTypes, connection);
|
|
299
|
+
});
|
|
300
|
+
const merged = mergeFactTypes(this.factTypeMap, loadedFactTypes);
|
|
301
|
+
this.factTypeMap = merged;
|
|
302
|
+
return merged;
|
|
303
|
+
}
|
|
304
|
+
return factTypes;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private async loadRolesFromSteps(steps: Step[], factTypes: FactTypeMap, initialType: string) {
|
|
308
|
+
const roleMap = this.roleMap;
|
|
309
|
+
const unknownRoles = allRoles(steps, factTypes, getFactTypeId(factTypes, initialType))
|
|
310
|
+
.filter(r => !hasRole(roleMap, r.defining_fact_type_id, r.role));
|
|
311
|
+
if (unknownRoles.length > 0) {
|
|
312
|
+
const loadedRoles = await this.connectionFactory.with(async (connection) => {
|
|
313
|
+
return await loadRoles(unknownRoles, roleMap, connection);
|
|
314
|
+
});
|
|
315
|
+
const merged = mergeRoleMaps(this.roleMap, loadedRoles);
|
|
316
|
+
this.roleMap = merged;
|
|
317
|
+
return merged;
|
|
318
|
+
}
|
|
319
|
+
return roleMap;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async loadRolesFromSpecification(specification: Specification, factTypes: FactTypeMap): Promise<RoleMap> {
|
|
323
|
+
const roleMap = this.roleMap;
|
|
324
|
+
const unknownRoles = getAllRoles(specification)
|
|
325
|
+
.map(r => ({
|
|
326
|
+
defining_fact_type_id: getFactTypeId(factTypes, r.definingFactType),
|
|
327
|
+
role: r.name
|
|
328
|
+
}))
|
|
329
|
+
.filter(r => !hasRole(roleMap, r.defining_fact_type_id, r.role));
|
|
330
|
+
if (unknownRoles.length > 0) {
|
|
331
|
+
const loadedRoles = await this.connectionFactory.with(async (connection) => {
|
|
332
|
+
return await loadRoles(unknownRoles, roleMap, connection);
|
|
333
|
+
});
|
|
334
|
+
const merged = mergeRoleMaps(this.roleMap, loadedRoles);
|
|
335
|
+
this.roleMap = merged;
|
|
336
|
+
return merged;
|
|
337
|
+
}
|
|
338
|
+
return roleMap;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async whichExist(references: FactReference[]): Promise<FactReference[]> {
|
|
342
|
+
if (references.length === 0) {
|
|
343
|
+
return [];
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const factTypes = await this.loadFactTypesFromReferences(references);
|
|
347
|
+
|
|
348
|
+
const factValues = references.map((f, i) =>
|
|
349
|
+
`(\$${i * 2 + 1}, \$${i * 2 + 2}::integer)`);
|
|
350
|
+
const factParameters = flatten(references, (f) =>
|
|
351
|
+
[f.hash, factTypes.get(f.type)]);
|
|
352
|
+
const sql =
|
|
353
|
+
'SELECT f.fact_type_id, f.hash ' +
|
|
354
|
+
'FROM public.fact f ' +
|
|
355
|
+
'JOIN (VALUES ' + factValues.join(', ') + ') AS v (hash, fact_type_id) ' +
|
|
356
|
+
' ON v.fact_type_id = f.fact_type_id AND v.hash = f.hash ';
|
|
357
|
+
const result: ExistsResult = await this.connectionFactory.with(async (connection) => {
|
|
358
|
+
return await connection.query(sql, factParameters);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const existing = references.filter(r =>
|
|
362
|
+
result.rows.some(row =>
|
|
363
|
+
row.fact_type_id === factTypes.get(r.type) && row.hash === r.hash
|
|
364
|
+
)
|
|
365
|
+
);
|
|
366
|
+
return existing;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async load(references: FactReference[]): Promise<FactRecord[]> {
|
|
370
|
+
if (references.length === 0) {
|
|
371
|
+
return [];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const factTypes = await this.loadFactTypesFromReferences(references);
|
|
375
|
+
|
|
376
|
+
const factValues = references.map((f, i) =>
|
|
377
|
+
`(\$${i * 2 + 1}, \$${i * 2 + 2}::integer)`);
|
|
378
|
+
const factParameters = flatten(references, (f) =>
|
|
379
|
+
[f.hash, factTypes.get(f.type)]);
|
|
380
|
+
const sql =
|
|
381
|
+
'SELECT f.fact_type_id, t.name, f.hash, f.data ' +
|
|
382
|
+
'FROM public.fact f ' +
|
|
383
|
+
'JOIN public.fact_type t ' +
|
|
384
|
+
' ON f.fact_type_id = t.fact_type_id ' +
|
|
385
|
+
'JOIN (VALUES ' + factValues.join(', ') + ') AS v (hash, fact_type_id) ' +
|
|
386
|
+
' ON v.fact_type_id = f.fact_type_id AND v.hash = f.hash ' +
|
|
387
|
+
'UNION ' +
|
|
388
|
+
'SELECT f2.fact_type_id, t.name, f2.hash, f2.data ' +
|
|
389
|
+
'FROM public.fact f1 ' +
|
|
390
|
+
'JOIN (VALUES ' + factValues.join(', ') + ') AS v (hash, fact_type_id) ' +
|
|
391
|
+
' ON v.fact_type_id = f1.fact_type_id AND v.hash = f1.hash ' +
|
|
392
|
+
'JOIN public.ancestor a ' +
|
|
393
|
+
' ON a.fact_id = f1.fact_id ' +
|
|
394
|
+
'JOIN public.fact f2 ' +
|
|
395
|
+
' ON f2.fact_id = a.ancestor_fact_id ' +
|
|
396
|
+
'JOIN public.fact_type t ' +
|
|
397
|
+
' ON t.fact_type_id = f2.fact_type_id;';
|
|
398
|
+
const result: AncestorResult = await this.connectionFactory.with(async (connection) => {
|
|
399
|
+
return await connection.query(sql, factParameters);
|
|
400
|
+
})
|
|
401
|
+
const resultFactTypes = result.rows.reduce(
|
|
402
|
+
(factTypes, r) => addFactType(factTypes, r.name, r.fact_type_id),
|
|
403
|
+
emptyFactTypeMap()
|
|
404
|
+
);
|
|
405
|
+
this.factTypeMap = mergeFactTypes(this.factTypeMap, resultFactTypes);
|
|
406
|
+
return result.rows.map((r) => {
|
|
407
|
+
const { fields, predecessors }: { fields: {}, predecessors: PredecessorCollection } = r.data as any;
|
|
408
|
+
return <FactRecord>{
|
|
409
|
+
type: r.name,
|
|
410
|
+
hash: r.hash,
|
|
411
|
+
fields,
|
|
412
|
+
predecessors
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private async loadFactTypesFromReferences(references: FactReference[]): Promise<FactTypeMap> {
|
|
418
|
+
const factTypes = this.factTypeMap;
|
|
419
|
+
const newFactTypes = references
|
|
420
|
+
.map(reference => reference.type)
|
|
421
|
+
.filter(type => !factTypes.has(type))
|
|
422
|
+
.filter(distinct);
|
|
423
|
+
if (newFactTypes.length > 0) {
|
|
424
|
+
const loadedFactTypes = await this.connectionFactory.with(async (connection) => {
|
|
425
|
+
return await loadFactTypes(newFactTypes, connection);
|
|
426
|
+
});
|
|
427
|
+
const mergedFactTypes = mergeFactTypes(factTypes, loadedFactTypes);
|
|
428
|
+
this.factTypeMap = mergedFactTypes;
|
|
429
|
+
return mergedFactTypes;
|
|
430
|
+
}
|
|
431
|
+
return factTypes;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async function executeQueryTree(sqlQueryTree: SqlQueryTree, connection: PoolClient): Promise<ResultSetTree> {
|
|
436
|
+
const sqlQuery = sqlQueryTree.sqlQuery;
|
|
437
|
+
const { rows } = await connection.query(sqlQuery.sql, sqlQuery.parameters);
|
|
438
|
+
const resultSets: ResultSetTree = {
|
|
439
|
+
resultSet: rows,
|
|
440
|
+
childResultSets: []
|
|
441
|
+
};
|
|
442
|
+
for (const child of sqlQueryTree.childQueries) {
|
|
443
|
+
const childResultSet = await executeQueryTree(child, connection);
|
|
444
|
+
resultSets.childResultSets.push({
|
|
445
|
+
name: child.name,
|
|
446
|
+
...childResultSet
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
return resultSets;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function predecessorTypes(predecessor: FactReference[] | FactReference): string[] {
|
|
453
|
+
if (Array.isArray(predecessor)) {
|
|
454
|
+
return predecessor.map(p => p.type);
|
|
455
|
+
}
|
|
456
|
+
return [predecessor.type];
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function predecessorCollectionTypes(predecessors: PredecessorCollection): string[] {
|
|
460
|
+
return Object.keys(predecessors).flatMap(key => predecessorTypes(predecessors[key]));
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async function storeFactTypes(facts: FactRecord[], factTypes: FactTypeMap, connection: PoolClient) {
|
|
464
|
+
const newFactTypes = facts
|
|
465
|
+
.flatMap(fact => [fact.type, ...predecessorCollectionTypes(fact.predecessors)])
|
|
466
|
+
.filter(type => !factTypes.has(type))
|
|
467
|
+
.filter(distinct);
|
|
468
|
+
if (newFactTypes.length === 0) {
|
|
469
|
+
return factTypes;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Look up existing fact types
|
|
473
|
+
const loadedFactTypes = await loadFactTypes(newFactTypes, connection);
|
|
474
|
+
const remainingNames = newFactTypes.filter(type => !loadedFactTypes.has(type));
|
|
475
|
+
if (remainingNames.length === 0) {
|
|
476
|
+
return mergeFactTypes(loadedFactTypes, factTypes);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Insert new fact types
|
|
480
|
+
const values = remainingNames.map((name, index) => `($${index + 1})`);
|
|
481
|
+
const insertSql = 'INSERT INTO public.fact_type (name) VALUES ' + values.join(', ') +
|
|
482
|
+
' RETURNING fact_type_id, name;';
|
|
483
|
+
const { rows: newRows }: FactTypeResult = await connection.query(insertSql, remainingNames);
|
|
484
|
+
if (newRows.length !== remainingNames.length) {
|
|
485
|
+
throw new Error('Failed to insert all new fact types.');
|
|
486
|
+
}
|
|
487
|
+
const allFactTypes = newRows.reduce(
|
|
488
|
+
(map, row) => addFactType(map, row.name, row.fact_type_id),
|
|
489
|
+
mergeFactTypes(loadedFactTypes, factTypes)
|
|
490
|
+
);
|
|
491
|
+
return allFactTypes;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async function loadFactTypes(factTypeNames: string[], connection: PoolClient) {
|
|
495
|
+
const lookUpSql = 'SELECT name, fact_type_id FROM public.fact_type WHERE name=ANY($1);';
|
|
496
|
+
const { rows: existingRows }: FactTypeResult = await connection.query(lookUpSql, [factTypeNames]);
|
|
497
|
+
const factTypeIds = existingRows.reduce(
|
|
498
|
+
(map, row) => addFactType(map, row.name, row.fact_type_id),
|
|
499
|
+
emptyFactTypeMap()
|
|
500
|
+
);
|
|
501
|
+
return factTypeIds;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async function storeRoles(facts: FactRecord[], factTypes: FactTypeMap, roleMap: RoleMap, connection: PoolClient) {
|
|
505
|
+
// Find distinct roles
|
|
506
|
+
const roles = flatten(facts, fact => {
|
|
507
|
+
const defining_fact_type_id = factTypes.get(fact.type);
|
|
508
|
+
return Object.keys(fact.predecessors).map(role => ({
|
|
509
|
+
role,
|
|
510
|
+
defining_fact_type_id
|
|
511
|
+
}));
|
|
512
|
+
}).filter((role, index, array) => array.findIndex(r =>
|
|
513
|
+
r.role === role.role &&
|
|
514
|
+
r.defining_fact_type_id === role.defining_fact_type_id
|
|
515
|
+
) === index);
|
|
516
|
+
|
|
517
|
+
if (roles.length > 0) {
|
|
518
|
+
// Look up existing roles
|
|
519
|
+
const roleIds = await loadRoles(roles, roleMap, connection);
|
|
520
|
+
const remainingRoles = roles.filter(role => !hasRole(
|
|
521
|
+
roleIds, role.defining_fact_type_id, role.role));
|
|
522
|
+
if (remainingRoles.length === 0) {
|
|
523
|
+
return roleIds;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Insert new roles
|
|
527
|
+
const remainingRoleValues = remainingRoles.map((role, index) =>
|
|
528
|
+
`($${index * 2 + 1}, $${index * 2 + 2}::integer)`);
|
|
529
|
+
const insertSql = 'INSERT INTO public.role (name, defining_fact_type_id) VALUES ' +
|
|
530
|
+
remainingRoleValues.join(', ') +
|
|
531
|
+
' RETURNING role_id, name, defining_fact_type_id;';
|
|
532
|
+
const remainingRoleParameters = flatten(remainingRoles, (role) => [
|
|
533
|
+
role.role,
|
|
534
|
+
role.defining_fact_type_id
|
|
535
|
+
]);
|
|
536
|
+
const { rows: newRows }: RoleResult = await connection.query(insertSql, remainingRoleParameters);
|
|
537
|
+
if (newRows.length !== remainingRoles.length) {
|
|
538
|
+
throw new Error('Failed to insert all new roles.');
|
|
539
|
+
}
|
|
540
|
+
const allRoleIds = newRows.reduce(
|
|
541
|
+
(map, row) => addRole(map, row.defining_fact_type_id, row.name, row.role_id),
|
|
542
|
+
roleIds
|
|
543
|
+
);
|
|
544
|
+
return allRoleIds;
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
return roleMap;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async function loadRoles(roles: { role: string; defining_fact_type_id: number; }[], roleMap: RoleMap, connection: PoolClient) {
|
|
552
|
+
const roleValues = roles.map((role, index) =>
|
|
553
|
+
`($${index * 2 + 1}, $${index * 2 + 2}::integer)`);
|
|
554
|
+
const roleParameters = flatten(roles, (role) => [
|
|
555
|
+
role.role,
|
|
556
|
+
role.defining_fact_type_id
|
|
557
|
+
]);
|
|
558
|
+
|
|
559
|
+
const lookUpSql = 'SELECT role.name, role.defining_fact_type_id, role.role_id' +
|
|
560
|
+
' FROM public.role' +
|
|
561
|
+
' JOIN (VALUES ' + roleValues.join(', ') + ') AS v (name, defining_fact_type_id)' +
|
|
562
|
+
' ON v.name = role.name AND v.defining_fact_type_id = role.defining_fact_type_id;';
|
|
563
|
+
const { rows }: RoleResult = await connection.query(lookUpSql, roleParameters);
|
|
564
|
+
const roleIds = rows.reduce(
|
|
565
|
+
(map, row) => addRole(map, row.defining_fact_type_id, row.name, row.role_id),
|
|
566
|
+
roleMap
|
|
567
|
+
);
|
|
568
|
+
return roleIds;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async function findExistingFacts(facts: FactRecord[], factTypes: FactTypeMap, connection: PoolClient) {
|
|
572
|
+
if (facts.length > 0) {
|
|
573
|
+
const factReferences: FactReference[] = facts.flatMap(fact => [
|
|
574
|
+
...predecessorsOf(fact),
|
|
575
|
+
fact
|
|
576
|
+
]);
|
|
577
|
+
const factValues = factReferences.map((f, i) =>
|
|
578
|
+
`(\$${i * 2 + 1}, \$${i * 2 + 2}::integer)`);
|
|
579
|
+
const factParameters = factReferences.flatMap((f) =>
|
|
580
|
+
[f.hash, factTypes.get(f.type)]);
|
|
581
|
+
|
|
582
|
+
const sql = 'SELECT fact_id, fact.fact_type_id, fact.hash' +
|
|
583
|
+
' FROM public.fact' +
|
|
584
|
+
' JOIN (VALUES ' + factValues.join(', ') + ') AS v (hash, fact_type_id)' +
|
|
585
|
+
' ON v.fact_type_id = fact.fact_type_id AND v.hash = fact.hash;';
|
|
586
|
+
const { rows }: FactResult = await connection.query(sql, factParameters);
|
|
587
|
+
const existingFacts = rows.reduce(
|
|
588
|
+
(map, row) => addFact(map, row.hash, row.fact_type_id, row.fact_id),
|
|
589
|
+
emptyFactMap()
|
|
590
|
+
);
|
|
591
|
+
return existingFacts;
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
return emptyFactMap();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async function insertFactsEdgesAndAncestors(facts: FactRecord[], factTypes: FactTypeMap, existingFacts: Map<string, Map<number, number>>, connection: PoolClient, roles: RoleMap) {
|
|
599
|
+
if (facts.length === 0) {
|
|
600
|
+
return emptyFactMap();
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const { sql, parameters } = sqlInsertFacts(facts, roles, factTypes);
|
|
604
|
+
|
|
605
|
+
const { rows }: FactResult = await connection.query(sql, parameters);
|
|
606
|
+
if (rows.length !== facts.length) {
|
|
607
|
+
throw new Error('Failed to insert all new facts.');
|
|
608
|
+
}
|
|
609
|
+
const allFacts = rows.reduce(
|
|
610
|
+
(map, row) => addFact(map, row.hash, row.fact_type_id, row.fact_id),
|
|
611
|
+
existingFacts
|
|
612
|
+
);
|
|
613
|
+
return allFacts;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function sqlInsertFacts(facts: FactRecord[], roles: RoleMap, factTypes: FactTypeMap) {
|
|
617
|
+
const factValues = facts.map((f, i) => `(\$${i * 3 + 1}, \$${i * 3 + 2}::integer, \$${i * 3 + 3}::jsonb)`);
|
|
618
|
+
const factParameters = flatten(facts, (f) => [f.hash, factTypes.get(f.type), {
|
|
619
|
+
fields: f.fields,
|
|
620
|
+
predecessors: canonicalPredecessors(f.predecessors)
|
|
621
|
+
}]);
|
|
622
|
+
|
|
623
|
+
const edgeRecords = flatten(facts, fact => makeEdgeRecords(fact));
|
|
624
|
+
if (edgeRecords.length > 0) {
|
|
625
|
+
return sqlInsertFactsEdgesAndAncestors(facts, factParameters, edgeRecords, factTypes, roles, factValues);
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
return {
|
|
629
|
+
sql: 'INSERT INTO public.fact (hash, fact_type_id, data) VALUES ' +
|
|
630
|
+
factValues.join(', ') +
|
|
631
|
+
' RETURNING fact_id, hash, fact_type_id;',
|
|
632
|
+
parameters: factParameters
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function sqlInsertFactsEdgesAndAncestors(facts: FactRecord[], factParameters: (string | number | { fields: {}; predecessors: PredecessorCollection; })[], edgeRecords: EdgeRecord[], factTypes: FactTypeMap, roles: RoleMap, factValues: string[]) {
|
|
638
|
+
let parameterOffset = factParameters.length;
|
|
639
|
+
const edgeValues = edgeRecords.map((e, i) => `(\$${i * 5 + 1 + parameterOffset}, \$${i * 5 + 2 + parameterOffset}::integer, \$${i * 5 + 3 + parameterOffset}, \$${i * 5 + 4 + parameterOffset}::integer, \$${i * 5 + 5 + parameterOffset}::integer)`);
|
|
640
|
+
const edgeParameters = flatten(edgeRecords, (e) => {
|
|
641
|
+
const successor_fact_type_id = getFactTypeId(factTypes, e.successor_type);
|
|
642
|
+
const predecessor_fact_type_id = getFactTypeId(factTypes, e.predecessor_type);
|
|
643
|
+
return [
|
|
644
|
+
e.successor_hash,
|
|
645
|
+
successor_fact_type_id,
|
|
646
|
+
e.predecessor_hash,
|
|
647
|
+
predecessor_fact_type_id,
|
|
648
|
+
getRoleId(roles, successor_fact_type_id, e.role)
|
|
649
|
+
];
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
parameterOffset += edgeParameters.length;
|
|
653
|
+
const ancestors = ancestorRecords(facts);
|
|
654
|
+
const ancestorValues = ancestors.map((a, i) => `(\$${i * 4 + 1 + parameterOffset}, \$${i * 4 + 2 + parameterOffset}::integer, \$${i * 4 + 3 + parameterOffset}, \$${i * 4 + 4 + parameterOffset}::integer)`);
|
|
655
|
+
const ancestorParameters = flatten(ancestors, (a) => [
|
|
656
|
+
a.fact.hash,
|
|
657
|
+
factTypes.get(a.fact.type),
|
|
658
|
+
a.ancestor.hash,
|
|
659
|
+
factTypes.get(a.ancestor.type)
|
|
660
|
+
]);
|
|
661
|
+
|
|
662
|
+
const sql =
|
|
663
|
+
`WITH new_fact AS (
|
|
664
|
+
SELECT hash, fact_type_id, data
|
|
665
|
+
FROM (VALUES ${factValues.join(', ')})
|
|
666
|
+
AS fv (hash, fact_type_id, data)
|
|
667
|
+
),
|
|
668
|
+
new_edge AS (
|
|
669
|
+
SELECT successor_hash, successor_fact_type_id, predecessor_hash, predecessor_fact_type_id, role_id
|
|
670
|
+
FROM (VALUES ${edgeValues.join(', ')})
|
|
671
|
+
AS ev (successor_hash, successor_fact_type_id, predecessor_hash, predecessor_fact_type_id, role_id)
|
|
672
|
+
),
|
|
673
|
+
new_ancestor AS (
|
|
674
|
+
SELECT hash, fact_type_id, ancestor_hash, ancestor_fact_type_id
|
|
675
|
+
FROM (VALUES ${ancestorValues.join(', ')})
|
|
676
|
+
AS ev (hash, fact_type_id, ancestor_hash, ancestor_fact_type_id)
|
|
677
|
+
),
|
|
678
|
+
inserted_fact AS (
|
|
679
|
+
INSERT INTO public.fact (hash, fact_type_id, data)
|
|
680
|
+
SELECT hash, fact_type_id, data
|
|
681
|
+
FROM new_fact
|
|
682
|
+
RETURNING fact_id, fact_type_id, hash
|
|
683
|
+
),
|
|
684
|
+
inserted_edge AS (
|
|
685
|
+
INSERT INTO public.edge
|
|
686
|
+
(role_id, successor_fact_id, predecessor_fact_id)
|
|
687
|
+
SELECT
|
|
688
|
+
new_edge.role_id,
|
|
689
|
+
successor.fact_id,
|
|
690
|
+
predecessor.fact_id
|
|
691
|
+
FROM new_edge
|
|
692
|
+
JOIN inserted_fact AS successor
|
|
693
|
+
ON successor.hash = new_edge.successor_hash
|
|
694
|
+
AND successor.fact_type_id = new_edge.successor_fact_type_id
|
|
695
|
+
JOIN (
|
|
696
|
+
SELECT fact_id, fact_type_id, hash
|
|
697
|
+
FROM inserted_fact
|
|
698
|
+
UNION ALL
|
|
699
|
+
SELECT fact_id, fact_type_id, hash
|
|
700
|
+
FROM public.fact
|
|
701
|
+
) AS predecessor
|
|
702
|
+
ON predecessor.hash = new_edge.predecessor_hash
|
|
703
|
+
AND predecessor.fact_type_id = new_edge.predecessor_fact_type_id
|
|
704
|
+
ON CONFLICT DO NOTHING
|
|
705
|
+
),
|
|
706
|
+
ancestor_id AS (
|
|
707
|
+
SELECT
|
|
708
|
+
fact.fact_id,
|
|
709
|
+
ancestor.fact_id AS ancestor_fact_id
|
|
710
|
+
FROM new_ancestor
|
|
711
|
+
JOIN inserted_fact AS fact
|
|
712
|
+
ON fact.hash = new_ancestor.hash
|
|
713
|
+
AND fact.fact_type_id = new_ancestor.fact_type_id
|
|
714
|
+
JOIN (
|
|
715
|
+
SELECT fact_id, fact_type_id, hash
|
|
716
|
+
FROM inserted_fact
|
|
717
|
+
UNION ALL
|
|
718
|
+
SELECT fact_id, fact_type_id, hash
|
|
719
|
+
FROM public.fact
|
|
720
|
+
) AS ancestor
|
|
721
|
+
ON ancestor.hash = new_ancestor.ancestor_hash
|
|
722
|
+
AND ancestor.fact_type_id = new_ancestor.ancestor_fact_type_id
|
|
723
|
+
),
|
|
724
|
+
inserted_ancestor AS (
|
|
725
|
+
INSERT INTO public.ancestor
|
|
726
|
+
(fact_id, ancestor_fact_id)
|
|
727
|
+
SELECT ancestor_id.fact_id, ancestor_id.ancestor_fact_id
|
|
728
|
+
FROM ancestor_id
|
|
729
|
+
UNION ALL
|
|
730
|
+
SELECT ancestor_id.fact_id, ancestor.ancestor_fact_id
|
|
731
|
+
FROM ancestor_id
|
|
732
|
+
JOIN public.ancestor
|
|
733
|
+
ON ancestor.fact_id = ancestor_id.ancestor_fact_id
|
|
734
|
+
ON CONFLICT DO NOTHING
|
|
735
|
+
)
|
|
736
|
+
SELECT fact_id, fact_type_id, hash
|
|
737
|
+
FROM inserted_fact;`;
|
|
738
|
+
const parameters = [...factParameters, ...edgeParameters, ...ancestorParameters];
|
|
739
|
+
return { sql, parameters };
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function ancestorRecords(facts: FactRecord[]): { fact: FactReference, ancestor: FactReference }[] {
|
|
743
|
+
return facts.flatMap(fact => {
|
|
744
|
+
const factReference: FactReference = { hash: fact.hash, type: fact.type };
|
|
745
|
+
const ancestorReferences: FactReference[] = recursivePredecessors(factReference, facts);
|
|
746
|
+
return ancestorReferences.map(ancestor => (
|
|
747
|
+
{
|
|
748
|
+
fact: factReference,
|
|
749
|
+
ancestor
|
|
750
|
+
}
|
|
751
|
+
));
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function recursivePredecessors(factReference: FactReference, facts: FactRecord[]): FactReference[] {
|
|
756
|
+
return facts
|
|
757
|
+
.filter(f => f.hash === factReference.hash && f.type === factReference.type)
|
|
758
|
+
.flatMap(fact => {
|
|
759
|
+
const predecessorReferences = Object.keys(fact.predecessors).flatMap(role => {
|
|
760
|
+
const predecessors = fact.predecessors[role];
|
|
761
|
+
if (Array.isArray(predecessors)) {
|
|
762
|
+
return predecessors;
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
return [predecessors];
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
const ancestorReferences = predecessorReferences.flatMap(predecessor => recursivePredecessors(predecessor, facts));
|
|
769
|
+
return [...predecessorReferences, ...ancestorReferences];
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
async function storePublicKeys(envelopes: FactEnvelope[], connection: PoolClient) {
|
|
774
|
+
// Look up existing fact types
|
|
775
|
+
const publicKeys = flatten(envelopes, e => e.signatures.map(s => s.publicKey))
|
|
776
|
+
.filter(distinct);
|
|
777
|
+
const lookUpSql = 'SELECT public_key, public_key_id FROM public.public_key WHERE public_key=ANY($1);';
|
|
778
|
+
const { rows: existingRows }: PublicKeyResult = await connection.query(lookUpSql, [publicKeys]);
|
|
779
|
+
const publicKeyIds = existingRows.reduce(
|
|
780
|
+
(map, row) => addFactType(map, row.public_key, row.public_key_id),
|
|
781
|
+
emptyPublicKeyMap()
|
|
782
|
+
);
|
|
783
|
+
const remainingPublicKeys = publicKeys.filter(pk => !publicKeyIds.has(pk));
|
|
784
|
+
if (remainingPublicKeys.length === 0) {
|
|
785
|
+
return publicKeyIds;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Insert new fact types
|
|
789
|
+
const values = remainingPublicKeys.map((name, index) => `($${index + 1})`);
|
|
790
|
+
const insertSql = 'INSERT INTO public.public_key (public_key) VALUES ' + values.join(', ') +
|
|
791
|
+
' RETURNING public_key, public_key_id;';
|
|
792
|
+
const { rows: newRows }: PublicKeyResult = await connection.query(insertSql, remainingPublicKeys);
|
|
793
|
+
if (newRows.length !== remainingPublicKeys.length) {
|
|
794
|
+
throw new Error('Failed to insert all new public keys.');
|
|
795
|
+
}
|
|
796
|
+
const allPublicKeyIds = newRows.reduce(
|
|
797
|
+
(map, row) => addFactType(map, row.public_key, row.public_key_id),
|
|
798
|
+
publicKeyIds
|
|
799
|
+
);
|
|
800
|
+
return allPublicKeyIds;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
async function insertSignatures(envelopes: FactEnvelope[], allFacts: FactMap, factTypes: FactTypeMap, publicKey: PublicKeyMap, connection: PoolClient) {
|
|
804
|
+
const signatureRecords = flatten(envelopes, envelope => envelope.signatures.map(signature => ({
|
|
805
|
+
factId: getFactId(allFacts, envelope.fact.hash, getFactTypeId(factTypes, envelope.fact.type)),
|
|
806
|
+
publicKeyId: getPublicKeyId(publicKey, signature.publicKey),
|
|
807
|
+
signature: signature.signature
|
|
808
|
+
})));
|
|
809
|
+
if (signatureRecords.length > 0) {
|
|
810
|
+
const signatureValues = signatureRecords.map((s, i) =>
|
|
811
|
+
`($${i * 3 + 1}::integer, $${i * 3 + 2}::integer, $${i * 3 + 3})`);
|
|
812
|
+
const signatureParameters = flatten(signatureRecords, s =>
|
|
813
|
+
[s.factId, s.publicKeyId, s.signature]);
|
|
814
|
+
|
|
815
|
+
const sql = `INSERT INTO public.signature
|
|
816
|
+
(fact_id, public_key_id, signature)
|
|
817
|
+
(SELECT fact_id, public_key_id, signature
|
|
818
|
+
FROM (VALUES ${signatureValues.join(', ')}) AS v(fact_id, public_key_id, signature))
|
|
819
|
+
ON CONFLICT DO NOTHING`;
|
|
820
|
+
await connection.query(sql, signatureParameters);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function allFactTypes(steps: Step[]): string[] {
|
|
825
|
+
const factTypes = steps
|
|
826
|
+
.filter(step => step instanceof PropertyCondition && step.name === 'type')
|
|
827
|
+
.map(step => (step as PropertyCondition).value);
|
|
828
|
+
const childFactTypes = steps
|
|
829
|
+
.filter(step => step instanceof ExistentialCondition)
|
|
830
|
+
.flatMap(step => allFactTypes((step as ExistentialCondition).steps));
|
|
831
|
+
return [...factTypes, ...childFactTypes].filter(distinct);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function allRoles(steps: Step[], factTypes: FactTypeMap, initialTypeId: number) {
|
|
835
|
+
let roles: { defining_fact_type_id: number; role: string }[] = [];
|
|
836
|
+
let defining_fact_type_id = initialTypeId;
|
|
837
|
+
let role: string | undefined = undefined;
|
|
838
|
+
|
|
839
|
+
for (const step of steps) {
|
|
840
|
+
if (step instanceof PropertyCondition) {
|
|
841
|
+
if (step.name === 'type') {
|
|
842
|
+
defining_fact_type_id = getFactTypeId(factTypes, step.value);
|
|
843
|
+
if (defining_fact_type_id && role) {
|
|
844
|
+
roles.push({ defining_fact_type_id, role });
|
|
845
|
+
}
|
|
846
|
+
role = undefined;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
else if (step instanceof Join) {
|
|
850
|
+
if (step.direction === Direction.Predecessor) {
|
|
851
|
+
if (defining_fact_type_id) {
|
|
852
|
+
roles.push({ defining_fact_type_id, role: step.role });
|
|
853
|
+
}
|
|
854
|
+
role = undefined;
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
role = step.role;
|
|
858
|
+
}
|
|
859
|
+
defining_fact_type_id = undefined;
|
|
860
|
+
}
|
|
861
|
+
else if (step instanceof ExistentialCondition) {
|
|
862
|
+
roles = roles.concat(allRoles(step.steps, factTypes, defining_fact_type_id));
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
return roles;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function predecessorsOf(fact: FactRecord): FactReference[] {
|
|
870
|
+
const references = Object.values(fact.predecessors).flatMap(predecessor =>
|
|
871
|
+
Array.isArray(predecessor) ? predecessor : [predecessor]
|
|
872
|
+
);
|
|
873
|
+
return references;
|
|
874
|
+
}
|