jinaga-server 2.5.0 → 2.6.1
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/dist/authorization/authorization-keystore.d.ts +5 -4
- package/dist/authorization/authorization-keystore.js +16 -13
- package/dist/authorization/authorization-keystore.js.map +1 -1
- package/dist/http/router.d.ts +5 -3
- package/dist/http/router.js +125 -52
- package/dist/http/router.js.map +1 -1
- package/dist/http/stream.d.ts +10 -0
- package/dist/http/stream.js +35 -0
- package/dist/http/stream.js.map +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/jinaga-server.js +7 -8
- package/dist/jinaga-server.js.map +1 -1
- package/dist/postgres/postgres-store.d.ts +4 -4
- package/dist/postgres/postgres-store.js +2 -2
- package/dist/postgres/postgres-store.js.map +1 -1
- package/dist/postgres/query-description.d.ts +75 -0
- package/dist/postgres/query-description.js +283 -0
- package/dist/postgres/query-description.js.map +1 -0
- package/dist/postgres/specification-result-sql.d.ts +2 -2
- package/dist/postgres/specification-result-sql.js +39 -324
- package/dist/postgres/specification-result-sql.js.map +1 -1
- package/dist/postgres/specification-sql.d.ts +2 -2
- package/dist/postgres/specification-sql.js +60 -163
- package/dist/postgres/specification-sql.js.map +1 -1
- package/http/sans-auth/.env.local +1 -0
- package/http/sans-auth/Create post sans auth.http +27 -0
- package/http/sans-auth/Create site sans auth.http +11 -0
- package/http/sans-auth/Feed sites.http +1 -0
- package/http/sans-auth/Feeds sites.http +12 -0
- package/http/sans-auth/Get posts sans auth.http +35 -0
- package/http/sans-auth/Get sites sans auth.http +12 -0
- package/http/sans-auth/stream.sh +2 -0
- package/integration-test/jinaga-test/Dockerfile +1 -1
- package/integration-test/jinaga-test/package-lock.json +917 -748
- package/integration-test/jinaga-test/package.json +3 -3
- package/package.json +2 -2
- package/replicator/package-lock.json +16 -16
- package/replicator/package.json +1 -1
- package/src/authorization/authorization-keystore.ts +23 -14
- package/src/http/router.ts +154 -64
- package/src/http/stream.ts +32 -0
- package/src/index.ts +1 -2
- package/src/jinaga-server.ts +8 -8
- package/src/postgres/postgres-store.ts +5 -8
- package/src/postgres/query-description.ts +435 -0
- package/src/postgres/specification-result-sql.ts +56 -489
- package/src/postgres/specification-sql.ts +71 -232
- package/test/authorization/authorizationSpec.ts +12 -2
- package/test/postgres/postgresSpecificationSpec.ts +15 -15
- package/dist/http/feed-cache.d.ts +0 -12
- package/dist/http/feed-cache.js +0 -3
- package/dist/http/feed-cache.js.map +0 -1
- package/dist/memory/memory-feed-cache.d.ts +0 -6
- package/dist/memory/memory-feed-cache.js +0 -17
- package/dist/memory/memory-feed-cache.js.map +0 -1
- package/src/http/feed-cache.ts +0 -14
- package/src/memory/memory-feed-cache.ts +0 -14
package/src/jinaga-server.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
AuthorizationRules,
|
|
7
7
|
DistributionRules,
|
|
8
8
|
FactManager,
|
|
9
|
+
FeedCache,
|
|
9
10
|
Jinaga,
|
|
10
11
|
MemoryStore,
|
|
11
12
|
Model,
|
|
@@ -25,7 +26,6 @@ import { AuthenticationSession } from "./authentication/authentication-session";
|
|
|
25
26
|
import { AuthorizationKeystore } from "./authorization/authorization-keystore";
|
|
26
27
|
import { HttpRouter, RequestUser } from "./http/router";
|
|
27
28
|
import { Keystore } from "./keystore";
|
|
28
|
-
import { MemoryFeedCache } from "./memory/memory-feed-cache";
|
|
29
29
|
import { PostgresKeystore } from "./postgres/postgres-keystore";
|
|
30
30
|
import { PostgresStore } from "./postgres/postgres-store";
|
|
31
31
|
|
|
@@ -62,13 +62,13 @@ export class JinagaServer {
|
|
|
62
62
|
const keystore = createKeystore(config, pools);
|
|
63
63
|
const authorizationRules = config.authorization ? config.authorization(new AuthorizationRules(config.model)) : null;
|
|
64
64
|
const distributionRules = config.distribution ? config.distribution(new DistributionRules([])) : null;
|
|
65
|
-
const
|
|
66
|
-
const feedCache = new MemoryFeedCache();
|
|
67
|
-
// Retain backward compatibility until the user provides distribution rules.
|
|
68
|
-
const router = new HttpRouter(authorization, feedCache, distributionRules === null);
|
|
65
|
+
const feedCache = new FeedCache();
|
|
69
66
|
const authentication = createAuthentication(store, keystore, authorizationRules);
|
|
70
67
|
const network = new NetworkNoOp();
|
|
71
68
|
const factManager = new FactManager(authentication, fork, source, store, network);
|
|
69
|
+
const authorization = createAuthorization(authorizationRules, distributionRules, factManager, store, keystore);
|
|
70
|
+
// Retain backward compatibility until the user provides distribution rules.
|
|
71
|
+
const router = new HttpRouter(factManager, authorization, feedCache, distributionRules === null);
|
|
72
72
|
const j: Jinaga = new Jinaga(factManager, syncStatusNotifier);
|
|
73
73
|
|
|
74
74
|
async function close() {
|
|
@@ -110,13 +110,13 @@ function createKeystore(config: JinagaServerConfig, pools: { [uri: string]: Pool
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
function createAuthorization(authorizationRules: AuthorizationRules | null, distributionRules: DistributionRules | null, store: Storage, keystore: Keystore | null): Authorization {
|
|
113
|
+
function createAuthorization(authorizationRules: AuthorizationRules | null, distributionRules: DistributionRules | null, factManager: FactManager, store: Storage, keystore: Keystore | null): Authorization {
|
|
114
114
|
if (keystore) {
|
|
115
|
-
const authorization = new AuthorizationKeystore(store, keystore, authorizationRules, distributionRules);
|
|
115
|
+
const authorization = new AuthorizationKeystore(factManager, store, keystore, authorizationRules, distributionRules);
|
|
116
116
|
return authorization;
|
|
117
117
|
}
|
|
118
118
|
else {
|
|
119
|
-
return new AuthorizationNoOp(store);
|
|
119
|
+
return new AuthorizationNoOp(factManager, store);
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -9,11 +9,8 @@ import {
|
|
|
9
9
|
FactReference,
|
|
10
10
|
factReferenceEquals,
|
|
11
11
|
FactTuple,
|
|
12
|
-
Feed,
|
|
13
12
|
getAllFactTypes,
|
|
14
|
-
getAllFactTypesFromFeed,
|
|
15
13
|
getAllRoles,
|
|
16
|
-
getAllRolesFromFeed,
|
|
17
14
|
Join,
|
|
18
15
|
PredecessorCollection,
|
|
19
16
|
ProjectedResult,
|
|
@@ -261,7 +258,7 @@ export class PostgresStore implements Storage {
|
|
|
261
258
|
return composer.compose(resultSets, factRecords);
|
|
262
259
|
}
|
|
263
260
|
|
|
264
|
-
async feed(feed:
|
|
261
|
+
async feed(feed: Specification, start: FactReference[], bookmark: string): Promise<FactFeed> {
|
|
265
262
|
const factTypes: FactTypeMap = await this.loadFactTypesFromFeed(feed);
|
|
266
263
|
const roleMap: RoleMap = await this.loadRolesFromFeed(feed, factTypes);
|
|
267
264
|
const sql = sqlFromFeed(feed, start, this.schema, bookmark, 100, factTypes, roleMap);
|
|
@@ -298,9 +295,9 @@ export class PostgresStore implements Storage {
|
|
|
298
295
|
return factTypes;
|
|
299
296
|
}
|
|
300
297
|
|
|
301
|
-
async loadFactTypesFromFeed(feed:
|
|
298
|
+
async loadFactTypesFromFeed(feed: Specification): Promise<FactTypeMap> {
|
|
302
299
|
const factTypes = this.factTypeMap;
|
|
303
|
-
const unknownFactTypes =
|
|
300
|
+
const unknownFactTypes = getAllFactTypes(feed)
|
|
304
301
|
.filter(factType => !factTypes.has(factType));
|
|
305
302
|
if (unknownFactTypes.length > 0) {
|
|
306
303
|
const loadedFactTypes = await this.connectionFactory.with(async (connection) => {
|
|
@@ -363,9 +360,9 @@ export class PostgresStore implements Storage {
|
|
|
363
360
|
return roleMap;
|
|
364
361
|
}
|
|
365
362
|
|
|
366
|
-
async loadRolesFromFeed(feed:
|
|
363
|
+
async loadRolesFromFeed(feed: Specification, factTypes: FactTypeMap): Promise<RoleMap> {
|
|
367
364
|
const roleMap = this.roleMap;
|
|
368
|
-
const unknownRoles =
|
|
365
|
+
const unknownRoles = getAllRoles(feed)
|
|
369
366
|
.map(r => ({
|
|
370
367
|
successor_type_id: getFactTypeId(factTypes, r.successorType)!,
|
|
371
368
|
role: r.name
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { FactReference, Label, Match, PathCondition } from "jinaga";
|
|
2
|
+
import { FactTypeMap, RoleMap, ensureGetFactTypeId, getFactTypeId, getRoleId } from "./maps";
|
|
3
|
+
|
|
4
|
+
export interface FactDescription {
|
|
5
|
+
type: string;
|
|
6
|
+
factIndex: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface EdgeDescription {
|
|
10
|
+
edgeIndex: number;
|
|
11
|
+
predecessorFactIndex: number;
|
|
12
|
+
successorFactIndex: number;
|
|
13
|
+
roleParameter: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ExistentialConditionDescription {
|
|
17
|
+
exists: boolean;
|
|
18
|
+
inputs: InputDescription[];
|
|
19
|
+
edges: EdgeDescription[];
|
|
20
|
+
existentialConditions: ExistentialConditionDescription[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface InputDescription {
|
|
24
|
+
label: string;
|
|
25
|
+
type: string;
|
|
26
|
+
factIndex: number;
|
|
27
|
+
factTypeParameter: number;
|
|
28
|
+
factHashParameter: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface OutputDescription {
|
|
32
|
+
label: string;
|
|
33
|
+
type: string;
|
|
34
|
+
factIndex: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function countEdges(existentialConditions: ExistentialConditionDescription[]): number {
|
|
38
|
+
return existentialConditions.reduce((count, c) => count + c.edges.length + countEdges(c.existentialConditions),
|
|
39
|
+
0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class QueryDescription {
|
|
43
|
+
// An unsatisfiable query description will produce no results.
|
|
44
|
+
static unsatisfiable: QueryDescription = new QueryDescription(
|
|
45
|
+
[], [], [], [], [], []
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
public readonly inputs: InputDescription[],
|
|
50
|
+
public readonly parameters: (string | number)[],
|
|
51
|
+
public readonly outputs: OutputDescription[],
|
|
52
|
+
public readonly facts: FactDescription[],
|
|
53
|
+
public readonly edges: EdgeDescription[],
|
|
54
|
+
public readonly existentialConditions: ExistentialConditionDescription[] = []
|
|
55
|
+
) {}
|
|
56
|
+
|
|
57
|
+
public withParameter(parameter: string | number): { query: QueryDescription; parameterIndex: number; } {
|
|
58
|
+
const parameterIndex = this.parameters.length + 1;
|
|
59
|
+
const query = new QueryDescription(
|
|
60
|
+
this.inputs,
|
|
61
|
+
this.parameters.concat(parameter),
|
|
62
|
+
this.outputs,
|
|
63
|
+
this.facts,
|
|
64
|
+
this.edges,
|
|
65
|
+
this.existentialConditions
|
|
66
|
+
);
|
|
67
|
+
return { query, parameterIndex };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public withInputParameter(label: Label, factTypeId: number, factHash: string, path: number[]): { queryDescription: QueryDescription; factDescription: FactDescription; } {
|
|
71
|
+
const factTypeParameter = this.parameters.length + 1;
|
|
72
|
+
const factHashParameter = factTypeParameter + 1;
|
|
73
|
+
const factIndex = this.facts.length + 1;
|
|
74
|
+
const factDescription: FactDescription = {
|
|
75
|
+
factIndex: factIndex,
|
|
76
|
+
type: label.type
|
|
77
|
+
};
|
|
78
|
+
const facts = [
|
|
79
|
+
...this.facts,
|
|
80
|
+
factDescription
|
|
81
|
+
];
|
|
82
|
+
const input: InputDescription = {
|
|
83
|
+
label: label.name,
|
|
84
|
+
type: label.type,
|
|
85
|
+
factIndex,
|
|
86
|
+
factTypeParameter,
|
|
87
|
+
factHashParameter
|
|
88
|
+
};
|
|
89
|
+
const parameters = this.parameters.concat(factTypeId, factHash);
|
|
90
|
+
if (path.length === 0) {
|
|
91
|
+
const inputs = [
|
|
92
|
+
...this.inputs,
|
|
93
|
+
input
|
|
94
|
+
];
|
|
95
|
+
const queryDescription = new QueryDescription(
|
|
96
|
+
inputs,
|
|
97
|
+
parameters,
|
|
98
|
+
this.outputs,
|
|
99
|
+
facts,
|
|
100
|
+
this.edges,
|
|
101
|
+
this.existentialConditions
|
|
102
|
+
);
|
|
103
|
+
return { queryDescription, factDescription };
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const existentialConditions = existentialsWithInput(this.existentialConditions, input, path);
|
|
107
|
+
const queryDescription = new QueryDescription(
|
|
108
|
+
this.inputs,
|
|
109
|
+
parameters,
|
|
110
|
+
this.outputs,
|
|
111
|
+
facts,
|
|
112
|
+
this.edges,
|
|
113
|
+
existentialConditions
|
|
114
|
+
);
|
|
115
|
+
return { queryDescription, factDescription };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public withFact(type: string): { query: QueryDescription; factIndex: number; } {
|
|
120
|
+
const factIndex = this.facts.length + 1;
|
|
121
|
+
const fact = { factIndex, type };
|
|
122
|
+
const query = new QueryDescription(
|
|
123
|
+
this.inputs,
|
|
124
|
+
this.parameters,
|
|
125
|
+
this.outputs,
|
|
126
|
+
this.facts.concat(fact),
|
|
127
|
+
this.edges,
|
|
128
|
+
this.existentialConditions
|
|
129
|
+
);
|
|
130
|
+
return { query, factIndex };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public withOutput(label: string, type: string, factIndex: number): QueryDescription {
|
|
134
|
+
const output = { label, type, factIndex };
|
|
135
|
+
const query = new QueryDescription(
|
|
136
|
+
this.inputs,
|
|
137
|
+
this.parameters,
|
|
138
|
+
this.outputs.concat(output),
|
|
139
|
+
this.facts,
|
|
140
|
+
this.edges,
|
|
141
|
+
this.existentialConditions
|
|
142
|
+
);
|
|
143
|
+
return query;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public withEdge(predecessorFactIndex: number, successorFactIndex: number, roleParameter: number, path: number[]) {
|
|
147
|
+
const edge = {
|
|
148
|
+
edgeIndex: this.edges.length + countEdges(this.existentialConditions) + 1,
|
|
149
|
+
predecessorFactIndex,
|
|
150
|
+
successorFactIndex,
|
|
151
|
+
roleParameter
|
|
152
|
+
};
|
|
153
|
+
const query = (path.length === 0)
|
|
154
|
+
? new QueryDescription(
|
|
155
|
+
this.inputs,
|
|
156
|
+
this.parameters,
|
|
157
|
+
this.outputs,
|
|
158
|
+
this.facts,
|
|
159
|
+
this.edges.concat(edge),
|
|
160
|
+
this.existentialConditions
|
|
161
|
+
)
|
|
162
|
+
: new QueryDescription(
|
|
163
|
+
this.inputs,
|
|
164
|
+
this.parameters,
|
|
165
|
+
this.outputs,
|
|
166
|
+
this.facts,
|
|
167
|
+
this.edges,
|
|
168
|
+
existentialsWithEdge(this.existentialConditions, edge, path)
|
|
169
|
+
);
|
|
170
|
+
return query;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public withExistentialCondition(exists: boolean, path: number[]): { query: QueryDescription; path: number[]; } {
|
|
174
|
+
const { existentialConditions: newExistentialConditions, path: newPath } = existentialsWithNewCondition(this.existentialConditions, exists, path);
|
|
175
|
+
const query = new QueryDescription(
|
|
176
|
+
this.inputs,
|
|
177
|
+
this.parameters,
|
|
178
|
+
this.outputs,
|
|
179
|
+
this.facts,
|
|
180
|
+
this.edges,
|
|
181
|
+
newExistentialConditions
|
|
182
|
+
);
|
|
183
|
+
return { query, path: newPath };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
isSatisfiable() {
|
|
187
|
+
return this.inputs.length > 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
hasOutput(label: string) {
|
|
191
|
+
return this.outputs.some(o => o.label === label);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
inputByLabel(label: string): InputDescription | undefined {
|
|
195
|
+
return this.inputs.find(i => i.label === label);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
outputLength(): number {
|
|
199
|
+
return this.inputs.length + this.outputs.length;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function existentialsWithInput(existentialConditions: ExistentialConditionDescription[], input: InputDescription, path: number[]): ExistentialConditionDescription[] {
|
|
204
|
+
if (path.length === 1) {
|
|
205
|
+
return existentialConditions.map((c, i) => i === path[0] ?
|
|
206
|
+
{
|
|
207
|
+
...c,
|
|
208
|
+
inputs: [...c.inputs, input]
|
|
209
|
+
} :
|
|
210
|
+
c
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
return existentialConditions.map((c, i) => i === path[0] ?
|
|
215
|
+
{
|
|
216
|
+
...c,
|
|
217
|
+
existentialConditions: existentialsWithInput(c.existentialConditions, input, path.slice(1))
|
|
218
|
+
} :
|
|
219
|
+
c
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function existentialsWithEdge(existentialConditions: ExistentialConditionDescription[], edge: EdgeDescription, path: number[]): ExistentialConditionDescription[] {
|
|
225
|
+
if (path.length === 1) {
|
|
226
|
+
return existentialConditions.map((c, i) => i === path[0] ?
|
|
227
|
+
{
|
|
228
|
+
...c,
|
|
229
|
+
edges: [...c.edges, edge]
|
|
230
|
+
} :
|
|
231
|
+
c
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
return existentialConditions.map((c, i) => i === path[0] ?
|
|
236
|
+
{
|
|
237
|
+
...c,
|
|
238
|
+
existentialConditions: existentialsWithEdge(c.existentialConditions, edge, path.slice(1))
|
|
239
|
+
} :
|
|
240
|
+
c
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function existentialsWithNewCondition(existentialConditions: ExistentialConditionDescription[], exists: boolean, path: number[]): { existentialConditions: ExistentialConditionDescription[]; path: number[]; } {
|
|
246
|
+
if (path.length === 0) {
|
|
247
|
+
path = [existentialConditions.length];
|
|
248
|
+
existentialConditions = [
|
|
249
|
+
...existentialConditions,
|
|
250
|
+
{
|
|
251
|
+
exists: exists,
|
|
252
|
+
inputs: [],
|
|
253
|
+
edges: [],
|
|
254
|
+
existentialConditions: []
|
|
255
|
+
}
|
|
256
|
+
];
|
|
257
|
+
return { existentialConditions: existentialConditions, path };
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
const { existentialConditions: newExistentialConditions, path: newPath } = existentialsWithNewCondition(existentialConditions[path[0]].existentialConditions, exists, path.slice(1));
|
|
261
|
+
existentialConditions = existentialConditions.map((c, i) => i === path[0] ?
|
|
262
|
+
{
|
|
263
|
+
exists: c.exists,
|
|
264
|
+
inputs: c.inputs,
|
|
265
|
+
edges: c.edges,
|
|
266
|
+
existentialConditions: newExistentialConditions
|
|
267
|
+
} :
|
|
268
|
+
c
|
|
269
|
+
);
|
|
270
|
+
path = [path[0], ...newPath];
|
|
271
|
+
return { existentialConditions: existentialConditions, path };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export type FactByLabel = {
|
|
276
|
+
[label: string]: FactDescription;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export class QueryDescriptionBuider {
|
|
280
|
+
constructor(
|
|
281
|
+
private factTypes: FactTypeMap,
|
|
282
|
+
private roleMap: RoleMap
|
|
283
|
+
) { }
|
|
284
|
+
|
|
285
|
+
public addEdges(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByLabel, path: number[], matches: Match[]): { queryDescription: QueryDescription, knownFacts: FactByLabel } {
|
|
286
|
+
for (const match of matches) {
|
|
287
|
+
for (const condition of match.conditions) {
|
|
288
|
+
if (condition.type === "path") {
|
|
289
|
+
({queryDescription, knownFacts} = this.addPathCondition(queryDescription, given, start, knownFacts, path, match.unknown, "", condition));
|
|
290
|
+
}
|
|
291
|
+
else if (condition.type === "existential") {
|
|
292
|
+
// Apply the where clause and continue with the tuple where it is true.
|
|
293
|
+
// The path describes which not-exists condition we are currently building on.
|
|
294
|
+
// Because the path is not empty, labeled facts will be included in the output.
|
|
295
|
+
const { query: queryDescriptionWithExistential, path: conditionalPath } = queryDescription.withExistentialCondition(condition.exists, path);
|
|
296
|
+
const { queryDescription: queryDescriptionConditional } = this.addEdges(queryDescriptionWithExistential, given, start, knownFacts, conditionalPath, condition.matches);
|
|
297
|
+
|
|
298
|
+
// If the negative existential condition is not satisfiable, then
|
|
299
|
+
// that means that the condition will always be true.
|
|
300
|
+
// We can therefore skip the branch for the negative existential condition.
|
|
301
|
+
if (queryDescriptionConditional.isSatisfiable()) {
|
|
302
|
+
queryDescription = queryDescriptionConditional;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (!queryDescription.isSatisfiable()) {
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (!queryDescription.isSatisfiable()) {
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
queryDescription,
|
|
315
|
+
knownFacts
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private addPathCondition(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByLabel, path: number[], unknown: Label, prefix: string, condition: PathCondition): { queryDescription: QueryDescription, knownFacts: FactByLabel } {
|
|
320
|
+
// If no input parameter has been allocated, allocate one now.
|
|
321
|
+
if (!knownFacts.hasOwnProperty(condition.labelRight)) {
|
|
322
|
+
const givenIndex = given.findIndex(given => given.name === condition.labelRight);
|
|
323
|
+
if (givenIndex < 0) {
|
|
324
|
+
throw new Error(`No input parameter found for label ${condition.labelRight}`);
|
|
325
|
+
}
|
|
326
|
+
const { queryDescription: newQueryDescription, factDescription } = queryDescription.withInputParameter(
|
|
327
|
+
given[givenIndex],
|
|
328
|
+
ensureGetFactTypeId(this.factTypes, start[givenIndex].type),
|
|
329
|
+
start[givenIndex].hash,
|
|
330
|
+
path
|
|
331
|
+
);
|
|
332
|
+
queryDescription = newQueryDescription;
|
|
333
|
+
knownFacts = {
|
|
334
|
+
...knownFacts,
|
|
335
|
+
[condition.labelRight]: factDescription
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Determine whether we have already written the output.
|
|
340
|
+
const knownFact = knownFacts[unknown.name];
|
|
341
|
+
const roleCount = condition.rolesLeft.length + condition.rolesRight.length;
|
|
342
|
+
|
|
343
|
+
// Walk up the right-hand side.
|
|
344
|
+
// This generates predecessor joins from a given or prior label.
|
|
345
|
+
let fact = knownFacts[condition.labelRight];
|
|
346
|
+
if (!fact) {
|
|
347
|
+
throw new Error(`Label ${condition.labelRight} not found. Known labels: ${Object.keys(knownFacts).join(", ")}`);
|
|
348
|
+
}
|
|
349
|
+
let type = fact.type;
|
|
350
|
+
let factIndex = fact.factIndex;
|
|
351
|
+
for (const [i, role] of condition.rolesRight.entries()) {
|
|
352
|
+
// If the type or role is not known, then no facts matching the condition can
|
|
353
|
+
// exist. The query is unsatisfiable.
|
|
354
|
+
const typeId = getFactTypeId(this.factTypes, type);
|
|
355
|
+
if (!typeId) {
|
|
356
|
+
return { queryDescription: QueryDescription.unsatisfiable, knownFacts };
|
|
357
|
+
}
|
|
358
|
+
const roleId = getRoleId(this.roleMap, typeId, role.name);
|
|
359
|
+
if (!roleId) {
|
|
360
|
+
return { queryDescription: QueryDescription.unsatisfiable, knownFacts };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const { query: queryWithParameter, parameterIndex: roleParameter } = queryDescription.withParameter(roleId);
|
|
364
|
+
if (i === roleCount - 1 && knownFact) {
|
|
365
|
+
// If we have already written the output, we can use the fact index.
|
|
366
|
+
queryDescription = queryWithParameter.withEdge(knownFact.factIndex, factIndex, roleParameter, path);
|
|
367
|
+
factIndex = knownFact.factIndex;
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
// If we have not written the fact, we need to write it now.
|
|
371
|
+
const { query, factIndex: predecessorFactIndex } = queryWithParameter.withFact(role.predecessorType);
|
|
372
|
+
queryDescription = query.withEdge(predecessorFactIndex, factIndex, roleParameter, path);
|
|
373
|
+
factIndex = predecessorFactIndex;
|
|
374
|
+
}
|
|
375
|
+
type = role.predecessorType;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const rightType = type;
|
|
379
|
+
|
|
380
|
+
// Walk up the left-hand side.
|
|
381
|
+
// We will need to reverse this walk to generate successor joins.
|
|
382
|
+
type = unknown.type;
|
|
383
|
+
const newEdges: {
|
|
384
|
+
roleId: number;
|
|
385
|
+
declaringType: string;
|
|
386
|
+
}[] = [];
|
|
387
|
+
for (const role of condition.rolesLeft) {
|
|
388
|
+
// If the type or role is not known, then no facts matching the condition can
|
|
389
|
+
// exist. The query is unsatisfiable.
|
|
390
|
+
const typeId = getFactTypeId(this.factTypes, type);
|
|
391
|
+
if (!typeId) {
|
|
392
|
+
return { queryDescription: QueryDescription.unsatisfiable, knownFacts };
|
|
393
|
+
}
|
|
394
|
+
const roleId = getRoleId(this.roleMap, typeId, role.name);
|
|
395
|
+
if (!roleId) {
|
|
396
|
+
return { queryDescription: QueryDescription.unsatisfiable, knownFacts };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
newEdges.push({
|
|
400
|
+
roleId,
|
|
401
|
+
declaringType: type
|
|
402
|
+
});
|
|
403
|
+
type = role.predecessorType;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (type !== rightType) {
|
|
407
|
+
throw new Error(`Type mismatch: ${type} is compared to ${rightType}`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
newEdges.reverse().forEach(({ roleId, declaringType }, i) => {
|
|
411
|
+
const { query: queryWithParameter, parameterIndex: roleParameter } = queryDescription.withParameter(roleId);
|
|
412
|
+
if (condition.rolesRight.length + i === roleCount - 1 && knownFact) {
|
|
413
|
+
queryDescription = queryWithParameter.withEdge(factIndex, knownFact.factIndex, roleParameter, path);
|
|
414
|
+
factIndex = knownFact.factIndex;
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
const { query: queryWithFact, factIndex: successorFactIndex } = queryWithParameter.withFact(declaringType);
|
|
418
|
+
queryDescription = queryWithFact.withEdge(factIndex, successorFactIndex, roleParameter, path);
|
|
419
|
+
factIndex = successorFactIndex;
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// If we have not captured the known fact, add it now.
|
|
424
|
+
if (!knownFact) {
|
|
425
|
+
knownFacts = { ...knownFacts, [unknown.name]: { factIndex, type: unknown.type } };
|
|
426
|
+
// If we have not written the output, write it now.
|
|
427
|
+
// Only write the output if we are not inside of an existential condition.
|
|
428
|
+
// Use the prefix, which will be set for projections.
|
|
429
|
+
if (path.length === 0) {
|
|
430
|
+
queryDescription = queryDescription.withOutput(prefix + unknown.name, unknown.type, factIndex);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return { queryDescription, knownFacts };
|
|
434
|
+
}
|
|
435
|
+
}
|