jinaga-server 2.6.2 → 3.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/dist/authentication/authentication-device.js +27 -6
- package/dist/authentication/authentication-device.js.map +1 -1
- package/dist/authentication/authentication-session.js +27 -6
- package/dist/authentication/authentication-session.js.map +1 -1
- package/dist/authorization/authorization-keystore.d.ts +4 -5
- package/dist/authorization/authorization-keystore.js +42 -18
- package/dist/authorization/authorization-keystore.js.map +1 -1
- package/dist/http/router.d.ts +1 -2
- package/dist/http/router.js +13 -17
- package/dist/http/router.js.map +1 -1
- package/dist/jinaga-server.js +5 -6
- package/dist/jinaga-server.js.map +1 -1
- package/dist/postgres/postgres-store.d.ts +2 -5
- package/dist/postgres/postgres-store.js +44 -121
- package/dist/postgres/postgres-store.js.map +1 -1
- package/integration-test/jinaga-test/jinaga.test.js +247 -231
- package/integration-test/jinaga-test/package-lock.json +9 -52
- package/integration-test/jinaga-test/package.json +2 -5
- package/package.json +2 -3
- package/src/authentication/authentication-device.ts +29 -7
- package/src/authentication/authentication-session.ts +29 -7
- package/src/authorization/authorization-keystore.ts +42 -22
- package/src/http/router.ts +13 -23
- package/src/jinaga-server.ts +5 -6
- package/src/postgres/postgres-store.ts +63 -149
- package/test/authorization/authorizationSpec.ts +75 -89
- package/dist/postgres/sql.d.ts +0 -10
- package/dist/postgres/sql.js +0 -408
- package/dist/postgres/sql.js.map +0 -1
- package/src/postgres/sql.ts +0 -498
- package/test/postgres/postgresSpec.ts +0 -391
|
@@ -1,21 +1,59 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AuthenticationNoOp,
|
|
3
2
|
AuthorizationRules,
|
|
3
|
+
buildModel,
|
|
4
4
|
dehydrateFact,
|
|
5
|
-
|
|
5
|
+
FactEnvelope,
|
|
6
6
|
FactManager,
|
|
7
7
|
FactRecord,
|
|
8
8
|
hydrate,
|
|
9
|
-
Jinaga as j,
|
|
10
9
|
MemoryStore,
|
|
11
10
|
NetworkNoOp,
|
|
12
11
|
ObservableSource,
|
|
13
|
-
PassThroughFork
|
|
12
|
+
PassThroughFork,
|
|
13
|
+
User
|
|
14
14
|
} from "jinaga";
|
|
15
15
|
|
|
16
16
|
import { AuthorizationKeystore } from "../../src/authorization/authorization-keystore";
|
|
17
17
|
import { MemoryKeystore } from "../../src/memory/memory-keystore";
|
|
18
18
|
|
|
19
|
+
class Hashtag {
|
|
20
|
+
public static Type = "Hashtag" as const;
|
|
21
|
+
public type = Hashtag.Type;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
public word: string
|
|
25
|
+
) { }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class Tweet {
|
|
29
|
+
public static Type = "Tweet" as const;
|
|
30
|
+
public type = Tweet.Type;
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
public message: string,
|
|
34
|
+
public sender: User
|
|
35
|
+
) { }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class Like {
|
|
39
|
+
public static Type = "Like" as const;
|
|
40
|
+
public type = Like.Type;
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
public user: User,
|
|
44
|
+
public tweet: Tweet
|
|
45
|
+
) { }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class Delete {
|
|
49
|
+
public static Type = "Delete" as const;
|
|
50
|
+
public type = Delete.Type;
|
|
51
|
+
|
|
52
|
+
constructor(
|
|
53
|
+
public tweet: Tweet
|
|
54
|
+
) { }
|
|
55
|
+
}
|
|
56
|
+
|
|
19
57
|
describe('Authorization', () => {
|
|
20
58
|
it('should authorize empty save', async () => {
|
|
21
59
|
const authorization = givenAuthorization();
|
|
@@ -25,19 +63,13 @@ describe('Authorization', () => {
|
|
|
25
63
|
|
|
26
64
|
it('should save a new fact', async () => {
|
|
27
65
|
const authorization = givenAuthorization();
|
|
28
|
-
const result = await whenSave(authorization, dehydrateFact(
|
|
29
|
-
type: 'Hashtag',
|
|
30
|
-
word: 'vorpal'
|
|
31
|
-
}));
|
|
66
|
+
const result = await whenSave(authorization, dehydrateFact(new Hashtag('vorpal')));
|
|
32
67
|
expect(result.length).toEqual(1);
|
|
33
68
|
});
|
|
34
69
|
|
|
35
70
|
it('should save a fact once', async () => {
|
|
36
71
|
const authorization = givenAuthorization();
|
|
37
|
-
const facts = dehydrateFact(
|
|
38
|
-
type: 'Hashtag',
|
|
39
|
-
word: 'vorpal'
|
|
40
|
-
});
|
|
72
|
+
const facts = dehydrateFact(new Hashtag('vorpal'));
|
|
41
73
|
await whenSave(authorization, facts);
|
|
42
74
|
const result = await whenSave(authorization, facts);
|
|
43
75
|
expect(result.length).toEqual(0);
|
|
@@ -46,64 +78,36 @@ describe('Authorization', () => {
|
|
|
46
78
|
it('should reject a fact from an unauthorized user', async () => {
|
|
47
79
|
const authorization = givenAuthorization();
|
|
48
80
|
const mickeyMouse = givenOtherUser();
|
|
49
|
-
const promise = whenSave(authorization, dehydrateFact(
|
|
50
|
-
type: 'Tweet',
|
|
51
|
-
message: 'Twas brillig',
|
|
52
|
-
sender: mickeyMouse
|
|
53
|
-
}));
|
|
81
|
+
const promise = whenSave(authorization, dehydrateFact(new Tweet("Twas brillig", mickeyMouse)));
|
|
54
82
|
await expect(promise).rejects.not.toBeNull();
|
|
55
83
|
});
|
|
56
84
|
|
|
57
85
|
it('should accept a fact from an authorized user', async () => {
|
|
58
86
|
const authorization = givenAuthorization();
|
|
59
87
|
const lewiscarrol = await givenLoggedInUser(authorization);
|
|
60
|
-
const result = await whenSave(authorization, dehydrateFact(
|
|
61
|
-
type: 'Tweet',
|
|
62
|
-
message: 'Twas brillig',
|
|
63
|
-
sender: lewiscarrol
|
|
64
|
-
}));
|
|
88
|
+
const result = await whenSave(authorization, dehydrateFact(new Tweet("Twas brillig", lewiscarrol)));
|
|
65
89
|
expect(result.length).toBeGreaterThan(0);
|
|
66
90
|
});
|
|
67
91
|
|
|
68
92
|
it('should accept a predecessor from an authorized user', async () => {
|
|
69
93
|
const authorization = givenAuthorization();
|
|
70
94
|
const lewiscarrol = await givenLoggedInUser(authorization);
|
|
71
|
-
const result = await whenSave(authorization, dehydrateFact(
|
|
72
|
-
|
|
73
|
-
user: lewiscarrol,
|
|
74
|
-
tweet: {
|
|
75
|
-
type: 'Tweet',
|
|
76
|
-
message: 'Twas Brillig',
|
|
77
|
-
sender: lewiscarrol
|
|
78
|
-
}
|
|
79
|
-
}));
|
|
80
|
-
expect(result.filter(r => r.type === 'Tweet').length).toEqual(1);
|
|
95
|
+
const result = await whenSave(authorization, dehydrateFact(new Like(lewiscarrol, new Tweet('Twas Brillig', lewiscarrol))));
|
|
96
|
+
expect(result.filter(r => r.fact.type === 'Tweet').length).toEqual(1);
|
|
81
97
|
});
|
|
82
98
|
|
|
83
99
|
it('should reject a predecessor from an unauthorized user', async () => {
|
|
84
100
|
const authorization = givenAuthorization();
|
|
85
101
|
const lewiscarrol = await givenLoggedInUser(authorization);
|
|
86
102
|
const mickeyMouse = givenOtherUser();
|
|
87
|
-
const promise = whenSave(authorization, dehydrateFact(
|
|
88
|
-
type: 'Like',
|
|
89
|
-
user: lewiscarrol,
|
|
90
|
-
tweet: {
|
|
91
|
-
type: 'Tweet',
|
|
92
|
-
message: 'Hiya, Pal!',
|
|
93
|
-
sender: mickeyMouse
|
|
94
|
-
}
|
|
95
|
-
}));
|
|
103
|
+
const promise = whenSave(authorization, dehydrateFact(new Like(lewiscarrol, new Tweet("Hiya, Pal!", mickeyMouse))));
|
|
96
104
|
await expect(promise).rejects.not.toBeNull();
|
|
97
105
|
});
|
|
98
106
|
|
|
99
107
|
it('should accept a pre-existing predecessor from an unauthorized user', async () => {
|
|
100
108
|
const storage = givenStorage();
|
|
101
109
|
const mickeyMouse = givenOtherUser();
|
|
102
|
-
const tweet =
|
|
103
|
-
type: 'Tweet',
|
|
104
|
-
message: 'Hiya, Pal!',
|
|
105
|
-
sender: mickeyMouse
|
|
106
|
-
};
|
|
110
|
+
const tweet = new Tweet("Hiya, Pal!", mickeyMouse);
|
|
107
111
|
await storage.save(dehydrateFact(tweet).map(f => ({ fact: f, signatures: [] })));
|
|
108
112
|
|
|
109
113
|
const authorization = givenAuthorizationWithStorage(storage);
|
|
@@ -114,17 +118,13 @@ describe('Authorization', () => {
|
|
|
114
118
|
tweet: tweet
|
|
115
119
|
}));
|
|
116
120
|
expect(result.length).toBeGreaterThan(0);
|
|
117
|
-
expect(result.filter(r => r.type === 'Tweet').length).toEqual(0);
|
|
121
|
+
expect(result.filter(r => r.fact.type === 'Tweet').length).toEqual(0);
|
|
118
122
|
});
|
|
119
123
|
|
|
120
124
|
it('should accept a fact authorized by predecessor', async () => {
|
|
121
125
|
const authorization = givenAuthorization();
|
|
122
126
|
const lewiscarrol = await givenLoggedInUser(authorization);
|
|
123
|
-
const tweet =
|
|
124
|
-
type: 'Tweet',
|
|
125
|
-
message: 'Twas brillig',
|
|
126
|
-
sender: lewiscarrol
|
|
127
|
-
};
|
|
127
|
+
const tweet = new Tweet("Twas brillig", lewiscarrol);
|
|
128
128
|
await whenSave(authorization, dehydrateFact(tweet));
|
|
129
129
|
|
|
130
130
|
const result = await whenSave(authorization, dehydrateFact({
|
|
@@ -137,11 +137,7 @@ describe('Authorization', () => {
|
|
|
137
137
|
it('should accept a fact based on in-flight predecessor', async () => {
|
|
138
138
|
const authorization = givenAuthorization();
|
|
139
139
|
const lewiscarrol = await givenLoggedInUser(authorization);
|
|
140
|
-
const tweet =
|
|
141
|
-
type: 'Tweet',
|
|
142
|
-
message: 'Twas brillig',
|
|
143
|
-
sender: lewiscarrol
|
|
144
|
-
};
|
|
140
|
+
const tweet = new Tweet("Twas brillig", lewiscarrol);
|
|
145
141
|
// Note that this is missing.
|
|
146
142
|
// await whenSave(authorization, dehydrateFact(tweet));
|
|
147
143
|
|
|
@@ -165,32 +161,39 @@ function givenStorage() {
|
|
|
165
161
|
function givenAuthorizationWithStorage(storage: MemoryStore) {
|
|
166
162
|
const keystore = new MemoryKeystore();
|
|
167
163
|
keystore.getOrCreateUserFact(givenMockUserIdentity());
|
|
168
|
-
const authentication = new AuthenticationNoOp();
|
|
169
164
|
const fork = new PassThroughFork(storage);
|
|
170
165
|
const observableSource = new ObservableSource(storage);
|
|
171
166
|
const network = new NetworkNoOp();
|
|
172
|
-
const factManager = new FactManager(
|
|
173
|
-
const
|
|
174
|
-
.
|
|
175
|
-
.
|
|
176
|
-
.type(
|
|
177
|
-
|
|
178
|
-
.type(
|
|
167
|
+
const factManager = new FactManager(fork, observableSource, storage, network);
|
|
168
|
+
const model = buildModel(b => b
|
|
169
|
+
.type(Hashtag)
|
|
170
|
+
.type(User)
|
|
171
|
+
.type(Tweet, m => m
|
|
172
|
+
.predecessor("sender", User))
|
|
173
|
+
.type(Like, m => m
|
|
174
|
+
.predecessor("user", User)
|
|
175
|
+
.predecessor("tweet", Tweet))
|
|
176
|
+
.type(Delete, m => m
|
|
177
|
+
.predecessor("tweet", Tweet))
|
|
178
|
+
);
|
|
179
|
+
const authorizationRules = new AuthorizationRules(model)
|
|
180
|
+
.any(Hashtag)
|
|
181
|
+
.no(User)
|
|
182
|
+
.type(Tweet, t => t.sender)
|
|
183
|
+
.type(Like, l => l.user)
|
|
184
|
+
.type(Delete, d => d.tweet.sender)
|
|
179
185
|
;
|
|
180
186
|
return new AuthorizationKeystore(factManager, storage, keystore, authorizationRules, null);
|
|
181
187
|
}
|
|
182
188
|
|
|
183
189
|
function givenOtherUser() {
|
|
184
|
-
return
|
|
185
|
-
type: 'Jinaga.User',
|
|
186
|
-
publicKey: 'other'
|
|
187
|
-
};
|
|
190
|
+
return new User('other');
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
async function givenLoggedInUser(authorization: AuthorizationKeystore) {
|
|
191
194
|
const userIdentity = givenMockUserIdentity();
|
|
192
195
|
const userFact = await authorization.getOrCreateUserFact(userIdentity);
|
|
193
|
-
const user = hydrate<
|
|
196
|
+
const user = hydrate<User>(userFact);
|
|
194
197
|
return user;
|
|
195
198
|
}
|
|
196
199
|
|
|
@@ -203,24 +206,7 @@ function givenMockUserIdentity() {
|
|
|
203
206
|
|
|
204
207
|
async function whenSave(authorization: AuthorizationKeystore, facts: FactRecord[]) {
|
|
205
208
|
const userIdentity = givenMockUserIdentity();
|
|
206
|
-
const
|
|
209
|
+
const envelopes: FactEnvelope[] = facts.map(f => ({ fact: f, signatures: [] }));
|
|
210
|
+
const result = await authorization.save(userIdentity, envelopes);
|
|
207
211
|
return result;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function tweetSender(t: { sender: {} }) {
|
|
211
|
-
ensure(t).has("sender", "Jinaga.User");
|
|
212
|
-
|
|
213
|
-
return j.match(t.sender);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function likeUser(l: { user: {} }) {
|
|
217
|
-
ensure(l).has("user", "Jinaga.User");
|
|
218
|
-
|
|
219
|
-
return j.match(l.user);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function deleteSender(d: { tweet: { sender: {} } }) {
|
|
223
|
-
ensure(d).has("tweet", "Tweet").has("sender", "Jinaga.User");
|
|
224
|
-
|
|
225
|
-
return j.match(d.tweet.sender);
|
|
226
212
|
}
|
package/dist/postgres/sql.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { FactReference, Step } from "jinaga";
|
|
2
|
-
import { FactTypeMap, RoleMap } from "./maps";
|
|
3
|
-
export declare type SqlQuery = {
|
|
4
|
-
empty: boolean;
|
|
5
|
-
sql: string;
|
|
6
|
-
parameters: any[];
|
|
7
|
-
pathLength: number;
|
|
8
|
-
factTypeNames: string[];
|
|
9
|
-
};
|
|
10
|
-
export declare function sqlFromSteps(start: FactReference, steps: Step[], factTypes: FactTypeMap, roleMap: RoleMap, schema: string): SqlQuery | null;
|
package/dist/postgres/sql.js
DELETED
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sqlFromSteps = void 0;
|
|
4
|
-
const jinaga_1 = require("jinaga");
|
|
5
|
-
const maps_1 = require("./maps");
|
|
6
|
-
function sqlFromSteps(start, steps, factTypes, roleMap, schema) {
|
|
7
|
-
const builder = new QueryBuilder(factTypes, roleMap);
|
|
8
|
-
return builder.buildQuery(start, steps, schema);
|
|
9
|
-
}
|
|
10
|
-
exports.sqlFromSteps = sqlFromSteps;
|
|
11
|
-
;
|
|
12
|
-
class QueryBuilder {
|
|
13
|
-
constructor(factTypes, roleMap) {
|
|
14
|
-
this.factTypes = factTypes;
|
|
15
|
-
this.roleMap = roleMap;
|
|
16
|
-
this.nextEdge = 1;
|
|
17
|
-
this.nextFact = 2;
|
|
18
|
-
this.shouldEmitFacts = true;
|
|
19
|
-
this.queryParts = {
|
|
20
|
-
joins: [],
|
|
21
|
-
existentialClauses: []
|
|
22
|
-
};
|
|
23
|
-
this.roleIds = [];
|
|
24
|
-
this.factTypeNames = [];
|
|
25
|
-
}
|
|
26
|
-
buildQuery(start, steps, schema) {
|
|
27
|
-
if (steps.length === 0) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
const startTypeId = (0, maps_1.ensureGetFactTypeId)(this.factTypes, start.type);
|
|
31
|
-
const startState = {
|
|
32
|
-
state: 'start',
|
|
33
|
-
typeId: startTypeId,
|
|
34
|
-
typeName: start.type
|
|
35
|
-
};
|
|
36
|
-
const finalState = steps.reduce((state, step) => {
|
|
37
|
-
return this.matchStep(state, step);
|
|
38
|
-
}, startState);
|
|
39
|
-
this.end(finalState);
|
|
40
|
-
if (finalState.state === 'empty') {
|
|
41
|
-
return {
|
|
42
|
-
empty: true,
|
|
43
|
-
sql: '',
|
|
44
|
-
parameters: [],
|
|
45
|
-
pathLength: 0,
|
|
46
|
-
factTypeNames: []
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
const factAliases = this.queryParts.joins
|
|
50
|
-
.filter(j => j.table === 'fact')
|
|
51
|
-
.map(j => j.factAlias);
|
|
52
|
-
const hashes = factAliases.map(a => `f${a}.hash as hash${a}`).join(', ');
|
|
53
|
-
const joins = this.buildJoins(schema, this.queryParts.joins, 'f1.fact_id');
|
|
54
|
-
const whereClause = this.buildWhereClause(schema, this.queryParts.existentialClauses);
|
|
55
|
-
const sql = `SELECT ${hashes} FROM ${schema}.fact f1${joins} WHERE f1.fact_type_id = $1 AND f1.hash = $2${whereClause}`;
|
|
56
|
-
return {
|
|
57
|
-
empty: false,
|
|
58
|
-
sql,
|
|
59
|
-
parameters: [startTypeId, start.hash, ...this.roleIds],
|
|
60
|
-
pathLength: factAliases.length,
|
|
61
|
-
factTypeNames: this.factTypeNames
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
buildJoins(schema, joins, priorFactId) {
|
|
65
|
-
;
|
|
66
|
-
const initialJoins = {
|
|
67
|
-
priorFactId,
|
|
68
|
-
clauses: []
|
|
69
|
-
};
|
|
70
|
-
const clauses = joins.reduce((joins, join) => {
|
|
71
|
-
if (join.table === 'edge') {
|
|
72
|
-
if (join.direction === 'successor') {
|
|
73
|
-
const clause = ` JOIN ${schema}.edge e${join.edgeAlias} ` +
|
|
74
|
-
`ON e${join.edgeAlias}.predecessor_fact_id = ${joins.priorFactId} ` +
|
|
75
|
-
`AND e${join.edgeAlias}.role_id = $${join.roleParameter}`;
|
|
76
|
-
return {
|
|
77
|
-
priorFactId: `e${join.edgeAlias}.successor_fact_id`,
|
|
78
|
-
clauses: [...joins.clauses, clause]
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
const clause = ` JOIN ${schema}.edge e${join.edgeAlias} ` +
|
|
83
|
-
`ON e${join.edgeAlias}.successor_fact_id = ${joins.priorFactId} ` +
|
|
84
|
-
`AND e${join.edgeAlias}.role_id = $${join.roleParameter}`;
|
|
85
|
-
return {
|
|
86
|
-
priorFactId: `e${join.edgeAlias}.predecessor_fact_id`,
|
|
87
|
-
clauses: [...joins.clauses, clause]
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
const clause = ` JOIN ${schema}.fact f${join.factAlias} ON f${join.factAlias}.fact_id = ${joins.priorFactId}`;
|
|
93
|
-
return {
|
|
94
|
-
priorFactId: joins.priorFactId,
|
|
95
|
-
clauses: [...joins.clauses, clause]
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}, initialJoins).clauses;
|
|
99
|
-
return clauses.join('');
|
|
100
|
-
}
|
|
101
|
-
buildWhereClause(schema, existentialClauses) {
|
|
102
|
-
if (existentialClauses.length === 0) {
|
|
103
|
-
return '';
|
|
104
|
-
}
|
|
105
|
-
const clauses = existentialClauses.map(clause => {
|
|
106
|
-
const quantifierSql = clause.quantifier === jinaga_1.Quantifier.Exists ? 'EXISTS' : 'NOT EXISTS';
|
|
107
|
-
const [first, ...rest] = clause.query.joins;
|
|
108
|
-
const firstHeadFactId = first.direction === 'predecessor'
|
|
109
|
-
? `e${first.edgeAlias}.predecessor_fact_id`
|
|
110
|
-
: `e${first.edgeAlias}.successor_fact_id`;
|
|
111
|
-
const firstTailFactId = first.direction === 'predecessor'
|
|
112
|
-
? `e${first.edgeAlias}.successor_fact_id`
|
|
113
|
-
: `e${first.edgeAlias}.predecessor_fact_id`;
|
|
114
|
-
const joins = this.buildJoins(schema, rest, firstHeadFactId);
|
|
115
|
-
const priorFactId = clause.priorEdgeJoin.direction === 'predecessor'
|
|
116
|
-
? `e${clause.priorEdgeJoin.edgeAlias}.predecessor_fact_id`
|
|
117
|
-
: `e${clause.priorEdgeJoin.edgeAlias}.successor_fact_id`;
|
|
118
|
-
const whereClause = this.buildWhereClause(schema, clause.query.existentialClauses);
|
|
119
|
-
return ` AND ${quantifierSql} (SELECT 1 FROM ${schema}.edge e${first.edgeAlias}${joins} ` +
|
|
120
|
-
`WHERE ${firstTailFactId} = ${priorFactId} ` +
|
|
121
|
-
`AND e${first.edgeAlias}.role_id = $${first.roleParameter}${whereClause})`;
|
|
122
|
-
});
|
|
123
|
-
return clauses.join('');
|
|
124
|
-
}
|
|
125
|
-
buildNestedSql(typeId, typeName, steps) {
|
|
126
|
-
// Push a new query to the stack.
|
|
127
|
-
const parentQuery = this.queryParts;
|
|
128
|
-
const parentShouldEmitFact = this.shouldEmitFacts;
|
|
129
|
-
this.shouldEmitFacts = false;
|
|
130
|
-
this.queryParts = {
|
|
131
|
-
joins: [],
|
|
132
|
-
existentialClauses: []
|
|
133
|
-
};
|
|
134
|
-
const startState = {
|
|
135
|
-
state: 'predecessor-type',
|
|
136
|
-
typeId: typeId,
|
|
137
|
-
typeName: typeName
|
|
138
|
-
};
|
|
139
|
-
const finalState = steps.reduce((state, step) => {
|
|
140
|
-
return this.matchStep(state, step);
|
|
141
|
-
}, startState);
|
|
142
|
-
this.end(finalState);
|
|
143
|
-
// Pop the stack and return the nested query.
|
|
144
|
-
const nestedQuery = this.queryParts;
|
|
145
|
-
this.shouldEmitFacts = parentShouldEmitFact;
|
|
146
|
-
this.queryParts = parentQuery;
|
|
147
|
-
// If the nested query cannot match any results, return null.
|
|
148
|
-
if (finalState.state === 'empty') {
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
return nestedQuery;
|
|
152
|
-
}
|
|
153
|
-
matchStep(state, step) {
|
|
154
|
-
switch (state.state) {
|
|
155
|
-
case 'start':
|
|
156
|
-
return this.matchStepStart(state, step);
|
|
157
|
-
case 'predecessor-type':
|
|
158
|
-
return this.matchStepPredecessorType(state, step);
|
|
159
|
-
case 'predecessor-join':
|
|
160
|
-
return this.matchStepPredecessorJoin(state, step);
|
|
161
|
-
case 'successor-join':
|
|
162
|
-
return this.matchStepSuccessorJoin(state, step);
|
|
163
|
-
case 'successor-type':
|
|
164
|
-
return this.matchStepSuccessorType(state, step);
|
|
165
|
-
case 'empty':
|
|
166
|
-
return state;
|
|
167
|
-
default:
|
|
168
|
-
throw new Error(`Unknown state: ${state.state}`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
matchStepStart(state, step) {
|
|
172
|
-
if (step instanceof jinaga_1.PropertyCondition) {
|
|
173
|
-
if (step.name !== 'type') {
|
|
174
|
-
throw new Error(`Property condition on non-type property ${step.name}`);
|
|
175
|
-
}
|
|
176
|
-
const typeId = (0, maps_1.getFactTypeId)(this.factTypes, step.value);
|
|
177
|
-
if (typeId !== state.typeId) {
|
|
178
|
-
throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
|
|
179
|
-
}
|
|
180
|
-
return state;
|
|
181
|
-
}
|
|
182
|
-
else if (step instanceof jinaga_1.Join) {
|
|
183
|
-
if (step.direction === jinaga_1.Direction.Predecessor) {
|
|
184
|
-
const roleId = (0, maps_1.getRoleId)(this.roleMap, state.typeId, step.role);
|
|
185
|
-
if (!roleId) {
|
|
186
|
-
return {
|
|
187
|
-
state: 'empty'
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
this.emitEdge('predecessor', roleId);
|
|
191
|
-
return {
|
|
192
|
-
state: 'predecessor-join',
|
|
193
|
-
position: `${state.typeName}.${step.role}`
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
else if (step.direction === jinaga_1.Direction.Successor) {
|
|
197
|
-
return {
|
|
198
|
-
state: 'successor-join',
|
|
199
|
-
role: step.role
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
throw new Error(`Unknown direction ${step.direction}`);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
else if (step instanceof jinaga_1.ExistentialCondition) {
|
|
207
|
-
return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
throw new Error(`Unknown step ${step.constructor.name}`);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
matchStepPredecessorType(state, step) {
|
|
214
|
-
if (step instanceof jinaga_1.PropertyCondition) {
|
|
215
|
-
if (step.name !== 'type') {
|
|
216
|
-
throw new Error(`Property condition on non-type property ${step.name}`);
|
|
217
|
-
}
|
|
218
|
-
const typeId = (0, maps_1.getFactTypeId)(this.factTypes, step.value);
|
|
219
|
-
if (typeId !== state.typeId) {
|
|
220
|
-
throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
|
|
221
|
-
}
|
|
222
|
-
return state;
|
|
223
|
-
}
|
|
224
|
-
else if (step instanceof jinaga_1.Join) {
|
|
225
|
-
if (step.direction === jinaga_1.Direction.Predecessor) {
|
|
226
|
-
const roleId = (0, maps_1.getRoleId)(this.roleMap, state.typeId, step.role);
|
|
227
|
-
if (!roleId) {
|
|
228
|
-
return {
|
|
229
|
-
state: 'empty'
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
this.emitFact(state.typeName);
|
|
233
|
-
this.emitEdge('predecessor', roleId);
|
|
234
|
-
return {
|
|
235
|
-
state: 'predecessor-join',
|
|
236
|
-
position: `${state.typeName}.${step.role}`
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
else if (step.direction === jinaga_1.Direction.Successor) {
|
|
240
|
-
this.emitFact(state.typeName);
|
|
241
|
-
return {
|
|
242
|
-
state: 'successor-join',
|
|
243
|
-
role: step.role
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
throw new Error(`Unknown direction ${step.direction}`);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
else if (step instanceof jinaga_1.ExistentialCondition) {
|
|
251
|
-
return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
throw new Error(`Unknown step ${step.constructor.name}`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
matchStepPredecessorJoin(state, step) {
|
|
258
|
-
if (step instanceof jinaga_1.PropertyCondition) {
|
|
259
|
-
if (step.name !== 'type') {
|
|
260
|
-
throw new Error(`Property condition on non-type property ${step.name}`);
|
|
261
|
-
}
|
|
262
|
-
const typeId = (0, maps_1.getFactTypeId)(this.factTypes, step.value);
|
|
263
|
-
if (!typeId) {
|
|
264
|
-
return {
|
|
265
|
-
state: 'empty'
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
return {
|
|
269
|
-
state: 'predecessor-type',
|
|
270
|
-
typeId,
|
|
271
|
-
typeName: step.value
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
else {
|
|
275
|
-
throw new Error(`Missing type of "${state.position}"`);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
matchStepSuccessorJoin(state, step) {
|
|
279
|
-
if (step instanceof jinaga_1.PropertyCondition) {
|
|
280
|
-
if (step.name !== 'type') {
|
|
281
|
-
throw new Error(`Property condition on non-type property ${step.name}`);
|
|
282
|
-
}
|
|
283
|
-
const typeId = (0, maps_1.getFactTypeId)(this.factTypes, step.value);
|
|
284
|
-
if (!typeId) {
|
|
285
|
-
return {
|
|
286
|
-
state: 'empty'
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
const roleId = (0, maps_1.getRoleId)(this.roleMap, typeId, state.role);
|
|
290
|
-
if (!roleId) {
|
|
291
|
-
return {
|
|
292
|
-
state: 'empty'
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
this.emitEdge('successor', roleId);
|
|
296
|
-
return {
|
|
297
|
-
state: 'successor-type',
|
|
298
|
-
typeId: typeId,
|
|
299
|
-
typeName: step.value
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
else {
|
|
303
|
-
throw new Error(`Missing type for role ${state.role}`);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
matchStepSuccessorType(state, step) {
|
|
307
|
-
if (step instanceof jinaga_1.PropertyCondition) {
|
|
308
|
-
if (step.name !== 'type') {
|
|
309
|
-
throw new Error(`Property condition on non-type property ${step.name}`);
|
|
310
|
-
}
|
|
311
|
-
const typeId = (0, maps_1.getFactTypeId)(this.factTypes, step.value);
|
|
312
|
-
if (typeId !== state.typeId) {
|
|
313
|
-
throw new Error(`Two property conditions in a row on different types, ending in ${step.value}`);
|
|
314
|
-
}
|
|
315
|
-
return state;
|
|
316
|
-
}
|
|
317
|
-
else if (step instanceof jinaga_1.Join) {
|
|
318
|
-
if (step.direction === jinaga_1.Direction.Predecessor) {
|
|
319
|
-
const roleId = (0, maps_1.getRoleId)(this.roleMap, state.typeId, step.role);
|
|
320
|
-
if (!roleId) {
|
|
321
|
-
return {
|
|
322
|
-
state: 'empty'
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
this.emitFact(state.typeName);
|
|
326
|
-
this.emitEdge('predecessor', roleId);
|
|
327
|
-
return {
|
|
328
|
-
state: 'predecessor-join',
|
|
329
|
-
position: `${state.typeName}.${step.role}`
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
this.emitFact(state.typeName);
|
|
334
|
-
return {
|
|
335
|
-
state: 'successor-join',
|
|
336
|
-
role: step.role
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
else if (step instanceof jinaga_1.ExistentialCondition) {
|
|
341
|
-
return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
throw new Error(`Unknown step ${step.constructor.name}`);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
end(finalState) {
|
|
348
|
-
if (finalState.state === 'successor-join') {
|
|
349
|
-
throw new Error(`Missing type for role ${finalState.role}`);
|
|
350
|
-
}
|
|
351
|
-
else if (finalState.state === 'predecessor-join') {
|
|
352
|
-
throw new Error(`Missing type of "${finalState.position}"`);
|
|
353
|
-
}
|
|
354
|
-
else if (finalState.state === 'predecessor-type' || finalState.state === 'successor-type') {
|
|
355
|
-
this.emitFact(finalState.typeName);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
emitEdge(direction, roleId) {
|
|
359
|
-
const edgeAlias = this.nextEdge++;
|
|
360
|
-
this.roleIds.push(roleId);
|
|
361
|
-
this.queryParts.joins.push({
|
|
362
|
-
table: 'edge',
|
|
363
|
-
direction: direction,
|
|
364
|
-
edgeAlias: edgeAlias,
|
|
365
|
-
roleParameter: edgeAlias + 2
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
emitFact(factTypeName) {
|
|
369
|
-
if (this.shouldEmitFacts) {
|
|
370
|
-
const factAlias = this.nextFact++;
|
|
371
|
-
this.queryParts.joins.push({
|
|
372
|
-
table: 'fact',
|
|
373
|
-
factAlias: factAlias
|
|
374
|
-
});
|
|
375
|
-
this.factTypeNames.push(factTypeName);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
pushExistentialCondition(typeId, typeName, state, step) {
|
|
379
|
-
if (this.queryParts.joins.length === 0) {
|
|
380
|
-
// We cannot apply a condition until we have some joins.
|
|
381
|
-
return state;
|
|
382
|
-
}
|
|
383
|
-
const lastJoin = this.queryParts.joins[this.queryParts.joins.length - 1];
|
|
384
|
-
if (lastJoin.table !== 'edge') {
|
|
385
|
-
throw new Error(`Existential condition on non-edge table ${lastJoin.table}`);
|
|
386
|
-
}
|
|
387
|
-
const nested = this.buildNestedSql(typeId, typeName, step.steps);
|
|
388
|
-
if (!nested) {
|
|
389
|
-
if (step.quantifier === jinaga_1.Quantifier.Exists) {
|
|
390
|
-
// An empty positive existential condition cannot match any facts.
|
|
391
|
-
return {
|
|
392
|
-
state: 'empty'
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
// An empty negative existential condition cannot exclude any facts.
|
|
397
|
-
return state;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
this.queryParts.existentialClauses.push({
|
|
401
|
-
quantifier: step.quantifier,
|
|
402
|
-
priorEdgeJoin: lastJoin,
|
|
403
|
-
query: nested
|
|
404
|
-
});
|
|
405
|
-
return state;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
//# sourceMappingURL=sql.js.map
|