jinaga-server 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -0
- package/dist/authentication/authentication-device.d.ts +3 -1
- package/dist/authentication/authentication-device.js +3 -0
- package/dist/authentication/authentication-device.js.map +1 -1
- package/dist/authentication/authentication-impl.d.ts +3 -2
- package/dist/authentication/authentication-impl.js +3 -0
- package/dist/authentication/authentication-impl.js.map +1 -1
- package/dist/authentication/authentication-session.d.ts +4 -2
- package/dist/authentication/authentication-session.js +7 -2
- package/dist/authentication/authentication-session.js.map +1 -1
- package/dist/authentication.d.ts +5 -4
- package/dist/authentication.js +3 -0
- package/dist/authentication.js.map +1 -1
- package/dist/authorization/authorization-keystore.d.ts +5 -3
- package/dist/authorization/authorization-keystore.js +12 -9
- package/dist/authorization/authorization-keystore.js.map +1 -1
- package/dist/http/feed-cache.d.ts +5 -0
- package/dist/http/feed-cache.js +3 -0
- package/dist/http/feed-cache.js.map +1 -0
- package/dist/http/node-http.js +4 -1
- package/dist/http/node-http.js.map +1 -1
- package/dist/http/router.d.ts +5 -1
- package/dist/http/router.js +157 -24
- package/dist/http/router.js.map +1 -1
- package/dist/jinaga-server.js +6 -4
- package/dist/jinaga-server.js.map +1 -1
- package/dist/memory/memory-feed-cache.d.ts +7 -0
- package/dist/memory/memory-feed-cache.js +17 -0
- package/dist/memory/memory-feed-cache.js.map +1 -0
- package/dist/postgres/postgres-store.d.ts +5 -2
- package/dist/postgres/postgres-store.js +53 -28
- package/dist/postgres/postgres-store.js.map +1 -1
- package/dist/postgres/specification-result-sql.d.ts +11 -4
- package/dist/postgres/specification-result-sql.js +302 -5
- package/dist/postgres/specification-result-sql.js.map +1 -1
- package/dist/postgres/specification-sql.d.ts +14 -3
- package/dist/postgres/specification-sql.js +233 -100
- package/dist/postgres/specification-sql.js.map +1 -1
- package/package.json +2 -2
- package/src/authentication/authentication-device.ts +6 -0
- package/src/authentication/authentication-impl.ts +6 -3
- package/src/authentication/authentication-session.ts +11 -3
- package/src/authentication.ts +18 -5
- package/src/authorization/authorization-keystore.ts +14 -8
- package/src/http/feed-cache.ts +6 -0
- package/src/http/node-http.ts +5 -2
- package/src/http/router.ts +179 -31
- package/src/jinaga-server.ts +11 -9
- package/src/memory/memory-feed-cache.ts +15 -0
- package/src/postgres/postgres-store.ts +59 -31
- package/src/postgres/specification-result-sql.ts +450 -10
- package/src/postgres/specification-sql.ts +335 -115
- package/test/authorization/authorizationSpec.ts +11 -2
- package/test/postgres/postgresSpec.ts +31 -25
- package/test/postgres/postgresSpecificationSpec.ts +40 -68
- package/dist/postgres/query-description-builder.d.ts +0 -13
- package/dist/postgres/query-description-builder.js +0 -104
- package/dist/postgres/query-description-builder.js.map +0 -1
- package/dist/postgres/query-description.d.ts +0 -71
- package/dist/postgres/query-description.js +0 -256
- package/dist/postgres/query-description.js.map +0 -1
- package/src/postgres/query-description-builder.ts +0 -116
- package/src/postgres/query-description.ts +0 -385
|
@@ -1,138 +1,358 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
buildFeeds,
|
|
3
|
+
EdgeDescription as FeedEdgeDescription,
|
|
4
|
+
FactReference,
|
|
5
|
+
Feed,
|
|
6
|
+
InputDescription as FeedInputDescription,
|
|
7
|
+
NotExistsConditionDescription as FeedNotExistsConditionDescription,
|
|
8
|
+
OutputDescription as FeedOutputDescription,
|
|
9
|
+
Specification,
|
|
10
|
+
} from "jinaga";
|
|
2
11
|
|
|
3
|
-
import { FactTypeMap, getFactTypeId, RoleMap } from "./maps";
|
|
4
|
-
import { FactDescription, InputDescription, QueryDescription, SpecificationSqlQuery } from "./query-description";
|
|
5
|
-
import { QueryDescriptionBuilder } from "./query-description-builder";
|
|
12
|
+
import { FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
|
|
6
13
|
|
|
7
|
-
|
|
8
|
-
|
|
14
|
+
interface SpecificationLabel {
|
|
15
|
+
type: string;
|
|
16
|
+
index: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface SpecificationSqlQuery {
|
|
20
|
+
sql: string;
|
|
21
|
+
parameters: (string | number | number[])[];
|
|
22
|
+
labels: SpecificationLabel[];
|
|
23
|
+
bookmark: string;
|
|
9
24
|
};
|
|
10
25
|
|
|
11
|
-
|
|
26
|
+
interface InputDescription {
|
|
27
|
+
factIndex: number;
|
|
28
|
+
factTypeId: number;
|
|
29
|
+
factHash: string;
|
|
30
|
+
factTypeParameter: number;
|
|
31
|
+
factHashParameter: number;
|
|
32
|
+
}
|
|
33
|
+
interface OutputDescription {
|
|
34
|
+
type: string;
|
|
35
|
+
factIndex: number;
|
|
36
|
+
}
|
|
37
|
+
interface EdgeDescription {
|
|
38
|
+
edgeIndex: number;
|
|
39
|
+
predecessorFactIndex: number;
|
|
40
|
+
successorFactIndex: number;
|
|
41
|
+
roleParameter: number;
|
|
42
|
+
}
|
|
43
|
+
interface NotExistsConditionDescription {
|
|
44
|
+
edges: EdgeDescription[];
|
|
45
|
+
notExistsConditions: NotExistsConditionDescription[];
|
|
46
|
+
}
|
|
47
|
+
class QueryDescription {
|
|
12
48
|
constructor(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
49
|
+
private readonly inputs: InputDescription[],
|
|
50
|
+
private readonly parameters: (string | number)[],
|
|
51
|
+
private readonly outputs: OutputDescription[],
|
|
52
|
+
private readonly edges: EdgeDescription[],
|
|
53
|
+
private readonly notExistsConditions: NotExistsConditionDescription[] = []
|
|
54
|
+
) {}
|
|
55
|
+
|
|
56
|
+
generateSqlQuery(bookmark: string, limit: number): SpecificationSqlQuery {
|
|
57
|
+
const hashes = this.outputs
|
|
58
|
+
.map(output => `f${output.factIndex}.hash as hash${output.factIndex}`)
|
|
59
|
+
.join(", ");
|
|
60
|
+
const factIds = this.outputs
|
|
61
|
+
.map(output => `f${output.factIndex}.fact_id`)
|
|
62
|
+
.join(", ");
|
|
63
|
+
const firstEdge = this.edges[0];
|
|
64
|
+
const predecessorFact = this.inputs.find(i => i.factIndex === firstEdge.predecessorFactIndex);
|
|
65
|
+
const successorFact = this.inputs.find(i => i.factIndex === firstEdge.successorFactIndex);
|
|
66
|
+
const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact.factIndex;
|
|
67
|
+
const writtenFactIndexes = new Set<number>().add(firstFactIndex);
|
|
68
|
+
const joins: string[] = generateJoins(this.edges, writtenFactIndexes);
|
|
69
|
+
const inputWhereClauses = this.inputs
|
|
70
|
+
.filter(input => input.factTypeParameter !== 0)
|
|
71
|
+
.map(input => `f${input.factIndex}.fact_type_id = $${input.factTypeParameter} AND f${input.factIndex}.hash = $${input.factHashParameter}`)
|
|
72
|
+
.join(" AND ");
|
|
73
|
+
const notExistsWhereClauses = this.notExistsConditions
|
|
74
|
+
.map(notExistsWhereClause => ` AND NOT EXISTS (${generateNotExistsWhereClause(notExistsWhereClause, writtenFactIndexes)})`)
|
|
75
|
+
.join("");
|
|
76
|
+
const bookmarkParameter = this.parameters.length + 1;
|
|
77
|
+
const limitParameter = bookmarkParameter + 1;
|
|
78
|
+
const sql = `SELECT ${hashes}, sort(array[${factIds}], 'desc') as bookmark FROM public.fact f${firstFactIndex}${joins.join("")} WHERE ${inputWhereClauses}${notExistsWhereClauses} AND sort(array[${factIds}], 'desc') > $${bookmarkParameter} ORDER BY bookmark ASC LIMIT $${limitParameter}`;
|
|
79
|
+
const bookmarkValue: number[] = parseBookmark(bookmark);
|
|
80
|
+
return {
|
|
81
|
+
sql,
|
|
82
|
+
parameters: [...this.parameters, bookmarkValue, limit],
|
|
83
|
+
labels: this.outputs.map(output => ({
|
|
84
|
+
type: output.type,
|
|
85
|
+
index: output.factIndex
|
|
86
|
+
})),
|
|
87
|
+
bookmark: "[]"
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function generateJoins(edges: EdgeDescription[], writtenFactIndexes: Set<number>) {
|
|
93
|
+
const joins: string[] = [];
|
|
94
|
+
edges.forEach(edge => {
|
|
95
|
+
if (writtenFactIndexes.has(edge.predecessorFactIndex)) {
|
|
96
|
+
if (writtenFactIndexes.has(edge.successorFactIndex)) {
|
|
97
|
+
joins.push(
|
|
98
|
+
` JOIN public.edge e${edge.edgeIndex}` +
|
|
99
|
+
` ON e${edge.edgeIndex}.predecessor_fact_id = f${edge.predecessorFactIndex}.fact_id` +
|
|
100
|
+
` AND e${edge.edgeIndex}.successor_fact_id = f${edge.successorFactIndex}.fact_id` +
|
|
101
|
+
` AND e${edge.edgeIndex}.role_id = $${edge.roleParameter}`
|
|
102
|
+
);
|
|
26
103
|
}
|
|
104
|
+
else {
|
|
105
|
+
joins.push(
|
|
106
|
+
` JOIN public.edge e${edge.edgeIndex}` +
|
|
107
|
+
` ON e${edge.edgeIndex}.predecessor_fact_id = f${edge.predecessorFactIndex}.fact_id` +
|
|
108
|
+
` AND e${edge.edgeIndex}.role_id = $${edge.roleParameter}`
|
|
109
|
+
);
|
|
110
|
+
joins.push(
|
|
111
|
+
` JOIN public.fact f${edge.successorFactIndex}` +
|
|
112
|
+
` ON f${edge.successorFactIndex}.fact_id = e${edge.edgeIndex}.successor_fact_id`
|
|
113
|
+
);
|
|
114
|
+
writtenFactIndexes.add(edge.successorFactIndex);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if (writtenFactIndexes.has(edge.successorFactIndex)) {
|
|
118
|
+
joins.push(
|
|
119
|
+
` JOIN public.edge e${edge.edgeIndex}` +
|
|
120
|
+
` ON e${edge.edgeIndex}.successor_fact_id = f${edge.successorFactIndex}.fact_id` +
|
|
121
|
+
` AND e${edge.edgeIndex}.role_id = $${edge.roleParameter}`
|
|
122
|
+
);
|
|
123
|
+
joins.push(
|
|
124
|
+
` JOIN public.fact f${edge.predecessorFactIndex}` +
|
|
125
|
+
` ON f${edge.predecessorFactIndex}.fact_id = e${edge.edgeIndex}.predecessor_fact_id`
|
|
126
|
+
);
|
|
127
|
+
writtenFactIndexes.add(edge.predecessorFactIndex);
|
|
27
128
|
}
|
|
129
|
+
else {
|
|
130
|
+
throw new Error("Neither predecessor nor successor fact has been written");
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
return joins;
|
|
134
|
+
}
|
|
28
135
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
factTypeParameter: 0,
|
|
38
|
-
factHashParameter: 0
|
|
39
|
-
}));
|
|
40
|
-
const facts: FactDescription[] = specification.given
|
|
41
|
-
.map((label, i) => ({
|
|
42
|
-
factIndex: i+1,
|
|
43
|
-
type: label.type
|
|
44
|
-
}));
|
|
45
|
-
const givenFacts = specification.given.reduce((knownFacts, label, i) => ({
|
|
46
|
-
...knownFacts,
|
|
47
|
-
[label.name]: facts[i]
|
|
48
|
-
}), {} as FactByIdentifier);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
// The QueryDescription is an immutable data type.
|
|
52
|
-
// Initialize it with the inputs and facts.
|
|
53
|
-
// The DescriptionBuilder will branch at various points, and
|
|
54
|
-
// build on the current query description along each branch.
|
|
55
|
-
const initialQueryDescription = new QueryDescription(inputs, [], [], facts, [], []);
|
|
56
|
-
const { queryDescriptions, knownFacts } = this.addEdges(initialQueryDescription, givenFacts, [], "", specification.matches);
|
|
57
|
-
|
|
58
|
-
// The final query description represents the complete tuple.
|
|
59
|
-
// Build projections onto that one.
|
|
60
|
-
const finalQueryDescription = queryDescriptions[queryDescriptions.length - 1];
|
|
61
|
-
if (Array.isArray(specification.childProjections)) {
|
|
62
|
-
const queryDescriptionsWithProjections = this.addProjections(finalQueryDescription, knownFacts, specification.childProjections);
|
|
63
|
-
return [ ...queryDescriptions, ...queryDescriptionsWithProjections ];
|
|
136
|
+
function generateNotExistsWhereClause(notExistsWhereClause: NotExistsConditionDescription, outerFactIndexes: Set<number>): string {
|
|
137
|
+
const firstEdge = notExistsWhereClause.edges[0];
|
|
138
|
+
const writtenFactIndexes = new Set<number>(outerFactIndexes);
|
|
139
|
+
const firstJoin: string[] = [];
|
|
140
|
+
const whereClause: string[] = [];
|
|
141
|
+
if (writtenFactIndexes.has(firstEdge.predecessorFactIndex)) {
|
|
142
|
+
if (writtenFactIndexes.has(firstEdge.successorFactIndex)) {
|
|
143
|
+
throw new Error("Not yet implemented");
|
|
64
144
|
}
|
|
65
145
|
else {
|
|
66
|
-
|
|
146
|
+
whereClause.push(
|
|
147
|
+
`e${firstEdge.edgeIndex}.predecessor_fact_id = f${firstEdge.predecessorFactIndex}.fact_id` +
|
|
148
|
+
` AND e${firstEdge.edgeIndex}.role_id = $${firstEdge.roleParameter}`
|
|
149
|
+
);
|
|
150
|
+
firstJoin.push(
|
|
151
|
+
` JOIN public.fact f${firstEdge.successorFactIndex}` +
|
|
152
|
+
` ON f${firstEdge.successorFactIndex}.fact_id = e${firstEdge.edgeIndex}.successor_fact_id`
|
|
153
|
+
);
|
|
154
|
+
writtenFactIndexes.add(firstEdge.successorFactIndex);
|
|
67
155
|
}
|
|
68
156
|
}
|
|
157
|
+
else if (writtenFactIndexes.has(firstEdge.successorFactIndex)) {
|
|
158
|
+
whereClause.push(
|
|
159
|
+
`e${firstEdge.edgeIndex}.successor_fact_id = f${firstEdge.successorFactIndex}.fact_id` +
|
|
160
|
+
` AND e${firstEdge.edgeIndex}.role_id = $${firstEdge.roleParameter}`
|
|
161
|
+
);
|
|
162
|
+
firstJoin.push(
|
|
163
|
+
` JOIN public.fact f${firstEdge.predecessorFactIndex}` +
|
|
164
|
+
` ON f${firstEdge.predecessorFactIndex}.fact_id = e${firstEdge.edgeIndex}.predecessor_fact_id`
|
|
165
|
+
);
|
|
166
|
+
writtenFactIndexes.add(firstEdge.predecessorFactIndex);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
throw new Error("Neither predecessor nor successor fact has been written");
|
|
170
|
+
}
|
|
171
|
+
const tailJoins: string[] = generateJoins(notExistsWhereClause.edges.slice(1), writtenFactIndexes);
|
|
172
|
+
const joins = firstJoin.concat(tailJoins);
|
|
173
|
+
return `SELECT 1 FROM public.edge e${firstEdge.edgeIndex}${joins.join("")} WHERE ${whereClause.join(" AND ")}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parseBookmark(bookmark: string): number[] {
|
|
177
|
+
if (bookmark === undefined || bookmark === null || bookmark === "") {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
return bookmark.split(".").map(str => parseInt(str));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
class DescriptionBuilder {
|
|
186
|
+
constructor(
|
|
187
|
+
private factTypes: FactTypeMap,
|
|
188
|
+
private roleMap: RoleMap
|
|
189
|
+
) { }
|
|
190
|
+
|
|
191
|
+
isSatisfiable(feed: Feed, edges: FeedEdgeDescription[]): boolean {
|
|
192
|
+
for (const edge of edges) {
|
|
193
|
+
const successor = feed.facts.find(f => f.factIndex === edge.successorFactIndex);
|
|
194
|
+
if (!successor) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const predecessor = feed.facts.find(f => f.factIndex === edge.predecessorFactIndex);
|
|
199
|
+
if (!predecessor) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const successorFactTypeId = getFactTypeId(this.factTypes, successor.factType);
|
|
204
|
+
if (!successorFactTypeId) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!getRoleId(this.roleMap, successorFactTypeId, edge.roleName)) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
69
211
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
for (const condition of match.conditions) {
|
|
74
|
-
if (condition.type === "path") {
|
|
75
|
-
({queryDescription, knownFacts} = this.addPathCondition(queryDescription, knownFacts, path, match.unknown, prefix, condition));
|
|
76
|
-
}
|
|
77
|
-
else if (condition.type === "existential") {
|
|
78
|
-
if (condition.exists) {
|
|
79
|
-
// Include the edges of the existential condition into the current
|
|
80
|
-
// query description.
|
|
81
|
-
const { queryDescriptions: newQueryDescriptions } = this.addEdges(queryDescription, knownFacts, path, prefix, condition.matches);
|
|
82
|
-
const last = newQueryDescriptions.length - 1;
|
|
83
|
-
queryDescriptions.push(...newQueryDescriptions.slice(0, last));
|
|
84
|
-
queryDescription = newQueryDescriptions[last];
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
// Branch from the current query description and follow the
|
|
88
|
-
// edges of the existential condition.
|
|
89
|
-
// This will produce tuples that prove the condition false.
|
|
90
|
-
const { queryDescriptions: newQueryDescriptions } = this.addEdges(queryDescription, knownFacts, path, prefix, condition.matches);
|
|
91
|
-
|
|
92
|
-
// Then apply the where clause and continue with the tuple where it is true.
|
|
93
|
-
// The path describes which not-exists condition we are currently building on.
|
|
94
|
-
// Because the path is not empty, labeled facts will be included in the output.
|
|
95
|
-
const { query: queryDescriptionWithNotExist, path: conditionalPath } = queryDescription.withNotExistsCondition(path);
|
|
96
|
-
const { queryDescriptions: newQueryDescriptionsWithNotExists } = this.addEdges(queryDescriptionWithNotExist, knownFacts, conditionalPath, prefix, condition.matches);
|
|
97
|
-
const last = newQueryDescriptionsWithNotExists.length - 1;
|
|
98
|
-
const queryDescriptionConditional = newQueryDescriptionsWithNotExists[last];
|
|
99
|
-
|
|
100
|
-
// If the negative existential condition is not satisfiable, then
|
|
101
|
-
// that means that the condition will always be true.
|
|
102
|
-
// We can therefore skip the branch for the negative existential condition.
|
|
103
|
-
if (queryDescriptionConditional.isSatisfiable()) {
|
|
104
|
-
queryDescriptions.push(...newQueryDescriptions);
|
|
105
|
-
queryDescriptions.push(...newQueryDescriptionsWithNotExists.slice(0, last));
|
|
106
|
-
queryDescription = queryDescriptionConditional;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
212
|
+
const predecessorFactTypeId = getFactTypeId(this.factTypes, predecessor.factType);
|
|
213
|
+
if (!predecessorFactTypeId) {
|
|
214
|
+
return false;
|
|
110
215
|
}
|
|
111
216
|
}
|
|
112
|
-
|
|
113
|
-
return
|
|
217
|
+
|
|
218
|
+
return true;
|
|
114
219
|
}
|
|
115
220
|
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
221
|
+
buildDescription(feed: Feed): QueryDescription {
|
|
222
|
+
const parameters: (string | number)[] = [];
|
|
223
|
+
function addParameter(value: string | number) {
|
|
224
|
+
parameters.push(value);
|
|
225
|
+
return parameters.length;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Allocate parameters for the inputs.
|
|
229
|
+
const inputs: InputDescription[] = feed.inputs.map(input =>
|
|
230
|
+
this.buildInputDescription(feed, input, addParameter)
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// Allocate parameters for the edge roles.
|
|
234
|
+
const edges: EdgeDescription[] = feed.edges.map(edge =>
|
|
235
|
+
this.buildEdgeDescription(feed, edge, addParameter)
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// Allocate parameters for the conditional roles.
|
|
239
|
+
const satisfiableNotExistsConditions = feed.notExistsConditions.filter(condition =>
|
|
240
|
+
this.isSatisfiable(feed, condition.edges)
|
|
241
|
+
);
|
|
242
|
+
const notExistsConditions: NotExistsConditionDescription[] = satisfiableNotExistsConditions.map(condition =>
|
|
243
|
+
this.buildNotExistsConditionDescription(feed, condition, addParameter)
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const outputs: OutputDescription[] = feed.outputs.map(output =>
|
|
247
|
+
this.buildOutputDescription(feed, output)
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
return new QueryDescription(
|
|
251
|
+
inputs,
|
|
252
|
+
parameters,
|
|
253
|
+
outputs,
|
|
254
|
+
edges,
|
|
255
|
+
notExistsConditions
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private buildInputDescription(feed: Feed, input: FeedInputDescription, addParameter: (value: string | number) => number): InputDescription {
|
|
260
|
+
const fact = feed.facts.find(f => f.factIndex === input.factIndex);
|
|
261
|
+
if (!fact) {
|
|
262
|
+
throw new Error(`Fact not found: ${input.factIndex}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const factTypeId = getFactTypeId(this.factTypes, fact.factType);
|
|
266
|
+
if (!factTypeId) {
|
|
267
|
+
throw new Error(`Fact type not found: ${fact.factType}`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const factTypeParameter = addParameter(factTypeId);
|
|
271
|
+
const factHashParameter = addParameter(input.factHash);
|
|
272
|
+
return {
|
|
273
|
+
factIndex: input.factIndex,
|
|
274
|
+
factTypeId,
|
|
275
|
+
factTypeParameter,
|
|
276
|
+
factHash: input.factHash,
|
|
277
|
+
factHashParameter
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private buildEdgeDescription(feed: Feed, edge: FeedEdgeDescription, addParameter: (value: string | number) => number) {
|
|
282
|
+
const fact = feed.facts.find(f => f.factIndex === edge.successorFactIndex);
|
|
283
|
+
if (!fact) {
|
|
284
|
+
throw new Error(`Fact not found: ${edge.successorFactIndex}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const factTypeId = getFactTypeId(this.factTypes, fact.factType);
|
|
288
|
+
if (!factTypeId) {
|
|
289
|
+
throw new Error(`Fact type not found: ${fact.factType}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const roleId = getRoleId(this.roleMap, factTypeId, edge.roleName);
|
|
293
|
+
if (!roleId) {
|
|
294
|
+
throw new Error(`Role not found: ${fact.factType}.${edge.roleName}`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const roleParameter = addParameter(roleId);
|
|
298
|
+
return <EdgeDescription>{
|
|
299
|
+
edgeIndex: edge.edgeIndex,
|
|
300
|
+
predecessorFactIndex: edge.predecessorFactIndex,
|
|
301
|
+
successorFactIndex: edge.successorFactIndex,
|
|
302
|
+
roleParameter
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private buildOutputDescription(feed: Feed, output: FeedOutputDescription) {
|
|
307
|
+
const fact = feed.facts.find(f => f.factIndex === output.factIndex);
|
|
308
|
+
if (!fact) {
|
|
309
|
+
throw new Error(`Fact not found: ${output.factIndex}`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return <OutputDescription>{
|
|
313
|
+
factIndex: output.factIndex,
|
|
314
|
+
type: fact.factType
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private buildNotExistsConditionDescription(feed: Feed, condition: FeedNotExistsConditionDescription, addParameter: (value: string | number) => number): NotExistsConditionDescription {
|
|
319
|
+
const edges = condition.edges.map(edge =>
|
|
320
|
+
this.buildEdgeDescription(feed, edge, addParameter)
|
|
321
|
+
);
|
|
322
|
+
const notExistsConditions = condition.notExistsConditions.map(condition =>
|
|
323
|
+
this.buildNotExistsConditionDescription(feed, condition, addParameter)
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
edges,
|
|
328
|
+
notExistsConditions
|
|
329
|
+
}
|
|
127
330
|
}
|
|
128
331
|
}
|
|
129
332
|
|
|
130
|
-
export function sqlFromSpecification(start: FactReference[], bookmarks:
|
|
333
|
+
export function sqlFromSpecification(start: FactReference[], bookmarks: string[], limit: number, specification: Specification, factTypes: Map<string, number>, roleMap: Map<number, Map<string, number>>): SpecificationSqlQuery[] {
|
|
334
|
+
const feeds = buildFeeds(start, specification);
|
|
131
335
|
const descriptionBuilder = new DescriptionBuilder(factTypes, roleMap);
|
|
132
|
-
const
|
|
336
|
+
const feedAndBookmark = feeds.map((feed, index) => ({
|
|
337
|
+
feed,
|
|
338
|
+
bookmark: bookmarks[index]
|
|
339
|
+
}));
|
|
340
|
+
const satisfiableFeedsAndBookmarks = feedAndBookmark.filter(fb =>
|
|
341
|
+
descriptionBuilder.isSatisfiable(fb.feed, fb.feed.edges));
|
|
342
|
+
const sqlQueries = satisfiableFeedsAndBookmarks.map(fb => {
|
|
343
|
+
const description = descriptionBuilder.buildDescription(fb.feed);
|
|
344
|
+
const sql = description.generateSqlQuery(fb.bookmark, limit);
|
|
345
|
+
return sql;
|
|
346
|
+
});
|
|
347
|
+
return sqlQueries;
|
|
348
|
+
}
|
|
133
349
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
350
|
+
export function sqlFromFeed(feed: Feed, bookmark: string, limit: number, factTypes: Map<string, number>, roleMap: Map<number, Map<string, number>>): SpecificationSqlQuery | null {
|
|
351
|
+
const descriptionBuilder = new DescriptionBuilder(factTypes, roleMap);
|
|
352
|
+
if (!descriptionBuilder.isSatisfiable(feed, feed.edges)) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
const description = descriptionBuilder.buildDescription(feed);
|
|
356
|
+
const sql = description.generateSqlQuery(bookmark, limit);
|
|
357
|
+
return sql;
|
|
138
358
|
}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
AuthorizationRules,
|
|
3
|
+
dehydrateFact,
|
|
4
|
+
ensure,
|
|
5
|
+
FactRecord,
|
|
6
|
+
hydrate,
|
|
7
|
+
Jinaga as j,
|
|
8
|
+
MemoryStore,
|
|
9
|
+
ObservableSourceImpl,
|
|
10
|
+
} from "jinaga";
|
|
2
11
|
|
|
3
12
|
import { AuthorizationKeystore } from "../../src/authorization/authorization-keystore";
|
|
4
13
|
import { MemoryKeystore } from "../../src/memory/memory-keystore";
|
|
@@ -152,7 +161,7 @@ function givenStorage() {
|
|
|
152
161
|
function givenAuthorizationWithStorage(storage: MemoryStore) {
|
|
153
162
|
const keystore = new MemoryKeystore();
|
|
154
163
|
keystore.getOrCreateUserFact(givenMockUserIdentity());
|
|
155
|
-
const feed = new
|
|
164
|
+
const feed = new ObservableSourceImpl(storage);
|
|
156
165
|
const authorizationRules = new AuthorizationRules()
|
|
157
166
|
.any('Hashtag')
|
|
158
167
|
.no('Jinaga.User')
|