jinaga-server 1.1.3 → 1.1.4
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/postgres/specification-result-sql.d.ts +0 -1
- package/dist/postgres/specification-result-sql.js +109 -92
- package/dist/postgres/specification-result-sql.js.map +1 -1
- package/docs/sql/SQLFromSpecification.md +394 -0
- package/docs/sql/example.sql +49 -0
- package/docs/sql/first-path.gv +9 -0
- package/docs/sql/first-path.png +0 -0
- package/docs/sql/model.gv +20 -0
- package/docs/sql/model.png +0 -0
- package/package.json +1 -1
- package/src/postgres/specification-result-sql.ts +150 -106
- package/test/postgres/postgresReadSpec.ts +289 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { dehydrateReference, getAllFactTypes, getAllRoles, SpecificationParser } from "jinaga";
|
|
2
|
+
import { addFactType, addRole, emptyFactTypeMap, emptyRoleMap, getFactTypeId, getRoleId } from "../../src/postgres/maps";
|
|
3
|
+
import { resultSqlFromSpecification } from "../../src/postgres/specification-result-sql";
|
|
4
|
+
|
|
5
|
+
const company = dehydrateReference({ type: "Company" });
|
|
6
|
+
const companyHash = company.hash;
|
|
7
|
+
const user = dehydrateReference({ type: "Jinaga.User", publicKey: "PUBLIC KEY"});
|
|
8
|
+
const userHash = user.hash;
|
|
9
|
+
|
|
10
|
+
function parseSpecification(input: string) {
|
|
11
|
+
const parser = new SpecificationParser(input);
|
|
12
|
+
parser.skipWhitespace();
|
|
13
|
+
return parser.parseSpecification();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function sqlFor(descriptiveString: string, bookmarks: string[] = []) {
|
|
17
|
+
const specification = parseSpecification(descriptiveString);
|
|
18
|
+
const factTypeNames = getAllFactTypes(specification);
|
|
19
|
+
|
|
20
|
+
// Build a fact type map containing all fact types in the specification.
|
|
21
|
+
// Filter out fact types named "Unknown".
|
|
22
|
+
const factTypes = factTypeNames.filter(t => t !== 'Unknown').reduce(
|
|
23
|
+
(f, factType, i) => addFactType(f, factType, i + 1),
|
|
24
|
+
emptyFactTypeMap());
|
|
25
|
+
|
|
26
|
+
// Build a role map containing all roles in the specification.
|
|
27
|
+
// Filter out the roles named "unknown", and those of unknown fact types.
|
|
28
|
+
let roleMap = getAllRoles(specification).filter(r => r.name !== 'unknown').reduce(
|
|
29
|
+
(r, role, i) => {
|
|
30
|
+
const factTypeId = getFactTypeId(factTypes, role.definingFactType);
|
|
31
|
+
if (!factTypeId) {
|
|
32
|
+
return r;
|
|
33
|
+
}
|
|
34
|
+
return addRole(r, factTypeId, role.name, i + 1);
|
|
35
|
+
},
|
|
36
|
+
emptyRoleMap());
|
|
37
|
+
const start = specification.given.map(input => {
|
|
38
|
+
if (input.type === "Company") {
|
|
39
|
+
return company;
|
|
40
|
+
}
|
|
41
|
+
if (input.type === "Jinaga.User") {
|
|
42
|
+
return user;
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`Unknown input type ${input.type}`);
|
|
45
|
+
});
|
|
46
|
+
const composer = resultSqlFromSpecification(start, specification, factTypes, roleMap);
|
|
47
|
+
return { composer, factTypes, roleMap };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function roleParameter(roleMap: Map<number, Map<string, number>>, factTypes: Map<string, number>, factTypeName: string, roleName: string): number {
|
|
51
|
+
const factTypeId = getFactTypeId(factTypes, factTypeName);
|
|
52
|
+
if (!factTypeId) {
|
|
53
|
+
throw new Error(`Unknown fact type ${factTypeName}`);
|
|
54
|
+
}
|
|
55
|
+
const roleId = getRoleId(roleMap, factTypeId, roleName);
|
|
56
|
+
if (!roleId) {
|
|
57
|
+
throw new Error(`Unknown role ${roleName} in fact type ${factTypeName}`);
|
|
58
|
+
}
|
|
59
|
+
return roleId;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe("Postgres read", () => {
|
|
63
|
+
it("should join successors", () => {
|
|
64
|
+
const { composer } = sqlFor(`
|
|
65
|
+
(company: Company) {
|
|
66
|
+
department: Department [
|
|
67
|
+
department->company: Company = company
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
`);
|
|
71
|
+
|
|
72
|
+
const sql = composer.getSqlQueries().sqlQuery.sql;
|
|
73
|
+
expect(sql).toEqual(
|
|
74
|
+
`SELECT f2.hash as hash2, f2.fact_id as id2, f2.data as data2 ` +
|
|
75
|
+
`FROM public.fact f1 ` +
|
|
76
|
+
`JOIN public.edge e1 ` +
|
|
77
|
+
`ON e1.predecessor_fact_id = f1.fact_id ` +
|
|
78
|
+
`AND e1.role_id = $3 ` +
|
|
79
|
+
`JOIN public.fact f2 ` +
|
|
80
|
+
`ON f2.fact_id = e1.successor_fact_id ` +
|
|
81
|
+
`WHERE f1.fact_type_id = $1 AND f1.hash = $2 ` +
|
|
82
|
+
`ORDER BY f2.fact_id ASC`
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should apply negative existential conditions", () => {
|
|
87
|
+
const { composer, factTypes, roleMap } = sqlFor(`
|
|
88
|
+
(company: Company) {
|
|
89
|
+
project: Project [
|
|
90
|
+
project->department: Department->company: Company = company
|
|
91
|
+
!E {
|
|
92
|
+
deleted: Project.Deleted [
|
|
93
|
+
deleted->project: Project = project
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
`);
|
|
99
|
+
|
|
100
|
+
const tree = composer.getSqlQueries();
|
|
101
|
+
const sql = tree.sqlQuery.sql;
|
|
102
|
+
expect(sql).toEqual(
|
|
103
|
+
`SELECT f3.hash as hash3, f3.fact_id as id3, f3.data as data3 ` +
|
|
104
|
+
`FROM public.fact f1 ` +
|
|
105
|
+
`JOIN public.edge e1 ` +
|
|
106
|
+
`ON e1.predecessor_fact_id = f1.fact_id ` +
|
|
107
|
+
`AND e1.role_id = $3 ` +
|
|
108
|
+
`JOIN public.fact f2 ` +
|
|
109
|
+
`ON f2.fact_id = e1.successor_fact_id ` +
|
|
110
|
+
`JOIN public.edge e2 ` +
|
|
111
|
+
`ON e2.predecessor_fact_id = f2.fact_id ` +
|
|
112
|
+
`AND e2.role_id = $4 ` +
|
|
113
|
+
`JOIN public.fact f3 ` +
|
|
114
|
+
`ON f3.fact_id = e2.successor_fact_id ` +
|
|
115
|
+
`WHERE f1.fact_type_id = $1 AND f1.hash = $2 ` +
|
|
116
|
+
`AND NOT EXISTS (` +
|
|
117
|
+
`SELECT 1 ` +
|
|
118
|
+
`FROM public.edge e3 ` +
|
|
119
|
+
`JOIN public.fact f4 ` +
|
|
120
|
+
`ON f4.fact_id = e3.successor_fact_id ` +
|
|
121
|
+
`WHERE e3.predecessor_fact_id = f3.fact_id ` +
|
|
122
|
+
`AND e3.role_id = $5` +
|
|
123
|
+
`) ` +
|
|
124
|
+
`ORDER BY f3.fact_id ASC`
|
|
125
|
+
);
|
|
126
|
+
expect(tree.sqlQuery.parameters).toEqual([
|
|
127
|
+
getFactTypeId(factTypes, 'Company'),
|
|
128
|
+
companyHash,
|
|
129
|
+
roleParameter(roleMap, factTypes, 'Department', 'company'),
|
|
130
|
+
roleParameter(roleMap, factTypes, 'Project', 'department'),
|
|
131
|
+
roleParameter(roleMap, factTypes, 'Project.Deleted', 'project')
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should apply positive existential conditions", () => {
|
|
136
|
+
const { composer, factTypes, roleMap } = sqlFor(`
|
|
137
|
+
(company: Company) {
|
|
138
|
+
project: Project [
|
|
139
|
+
project->department: Department->company: Company = company
|
|
140
|
+
E {
|
|
141
|
+
deleted: Project.Deleted [
|
|
142
|
+
deleted->project: Project = project
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
`);
|
|
148
|
+
|
|
149
|
+
const tree = composer.getSqlQueries();
|
|
150
|
+
const sql = tree.sqlQuery.sql;
|
|
151
|
+
expect(sql).toEqual(
|
|
152
|
+
`SELECT f3.hash as hash3, f3.fact_id as id3, f3.data as data3 ` +
|
|
153
|
+
`FROM public.fact f1 ` +
|
|
154
|
+
`JOIN public.edge e1 ` +
|
|
155
|
+
`ON e1.predecessor_fact_id = f1.fact_id ` +
|
|
156
|
+
`AND e1.role_id = $3 ` +
|
|
157
|
+
`JOIN public.fact f2 ` +
|
|
158
|
+
`ON f2.fact_id = e1.successor_fact_id ` +
|
|
159
|
+
`JOIN public.edge e2 ` +
|
|
160
|
+
`ON e2.predecessor_fact_id = f2.fact_id ` +
|
|
161
|
+
`AND e2.role_id = $4 ` +
|
|
162
|
+
`JOIN public.fact f3 ` +
|
|
163
|
+
`ON f3.fact_id = e2.successor_fact_id ` +
|
|
164
|
+
`WHERE f1.fact_type_id = $1 AND f1.hash = $2 ` +
|
|
165
|
+
`AND EXISTS (` +
|
|
166
|
+
`SELECT 1 ` +
|
|
167
|
+
`FROM public.edge e3 ` +
|
|
168
|
+
`JOIN public.fact f4 ` +
|
|
169
|
+
`ON f4.fact_id = e3.successor_fact_id ` +
|
|
170
|
+
`WHERE e3.predecessor_fact_id = f3.fact_id ` +
|
|
171
|
+
`AND e3.role_id = $5` +
|
|
172
|
+
`) ` +
|
|
173
|
+
`ORDER BY f3.fact_id ASC`
|
|
174
|
+
);
|
|
175
|
+
expect(tree.sqlQuery.parameters).toEqual([
|
|
176
|
+
getFactTypeId(factTypes, 'Company'),
|
|
177
|
+
companyHash,
|
|
178
|
+
roleParameter(roleMap, factTypes, 'Department', 'company'),
|
|
179
|
+
roleParameter(roleMap, factTypes, 'Project', 'department'),
|
|
180
|
+
roleParameter(roleMap, factTypes, 'Project.Deleted', 'project')
|
|
181
|
+
]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should read complex specifications", () => {
|
|
185
|
+
const { composer, factTypes, roleMap } = sqlFor(`
|
|
186
|
+
(company: Company, user: Jinaga.User) {
|
|
187
|
+
project: Project [
|
|
188
|
+
project->department: Department->company: Company = company
|
|
189
|
+
!E {
|
|
190
|
+
deleted: Project.Deleted [
|
|
191
|
+
deleted->project: Project = project
|
|
192
|
+
!E {
|
|
193
|
+
restored: Project.Restored [
|
|
194
|
+
restored->deleted: Project.Deleted = deleted
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
E {
|
|
200
|
+
assignment: Project.Assignment [
|
|
201
|
+
assignment->project: Project = project
|
|
202
|
+
assignment->user: Jinaga.User = user
|
|
203
|
+
!E {
|
|
204
|
+
revoked: Project.Assignment.Revoked [
|
|
205
|
+
revoked->assignment: Project.Assignment = assignment
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
deliverable: Deliverable [
|
|
212
|
+
deliverable->project: Project = project
|
|
213
|
+
]
|
|
214
|
+
} => {
|
|
215
|
+
assignedTasks = {
|
|
216
|
+
task: Task [
|
|
217
|
+
task->deliverable: Deliverable = deliverable
|
|
218
|
+
E {
|
|
219
|
+
assignment: Task.Assignment [
|
|
220
|
+
assignment->task: Task = task
|
|
221
|
+
assignment->user: Jinaga.User = user
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
} => {
|
|
226
|
+
number = task.number
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
`);
|
|
230
|
+
|
|
231
|
+
const queries = composer.getSqlQueries();
|
|
232
|
+
expect(queries.sqlQuery.sql).toEqual(
|
|
233
|
+
`SELECT ` +
|
|
234
|
+
`f3.hash as hash3, f3.fact_id as id3, f3.data as data3, ` +
|
|
235
|
+
`f9.hash as hash9, f9.fact_id as id9, f9.data as data9 ` +
|
|
236
|
+
`FROM public.fact f1 ` +
|
|
237
|
+
`JOIN public.edge e1 ` +
|
|
238
|
+
`ON e1.predecessor_fact_id = f1.fact_id ` +
|
|
239
|
+
`AND e1.role_id = $3 ` +
|
|
240
|
+
`JOIN public.fact f2 ` +
|
|
241
|
+
`ON f2.fact_id = e1.successor_fact_id ` +
|
|
242
|
+
`JOIN public.edge e2 ` +
|
|
243
|
+
`ON e2.predecessor_fact_id = f2.fact_id ` +
|
|
244
|
+
`AND e2.role_id = $4 ` +
|
|
245
|
+
`JOIN public.fact f3 ` +
|
|
246
|
+
`ON f3.fact_id = e2.successor_fact_id ` +
|
|
247
|
+
`JOIN public.edge e8 ` +
|
|
248
|
+
`ON e8.predecessor_fact_id = f3.fact_id ` +
|
|
249
|
+
`AND e8.role_id = $12 ` +
|
|
250
|
+
`JOIN public.fact f9 ` +
|
|
251
|
+
`ON f9.fact_id = e8.successor_fact_id ` +
|
|
252
|
+
`WHERE f1.fact_type_id = $1 AND f1.hash = $2 ` +
|
|
253
|
+
`AND NOT EXISTS (` +
|
|
254
|
+
`SELECT 1 ` +
|
|
255
|
+
`FROM public.edge e3 ` +
|
|
256
|
+
`JOIN public.fact f4 ` +
|
|
257
|
+
`ON f4.fact_id = e3.successor_fact_id ` +
|
|
258
|
+
`WHERE e3.predecessor_fact_id = f3.fact_id ` +
|
|
259
|
+
`AND e3.role_id = $5 ` +
|
|
260
|
+
`AND NOT EXISTS (` +
|
|
261
|
+
`SELECT 1 ` +
|
|
262
|
+
`FROM public.edge e4 ` +
|
|
263
|
+
`JOIN public.fact f5 ` +
|
|
264
|
+
`ON f5.fact_id = e4.successor_fact_id ` +
|
|
265
|
+
`WHERE e4.predecessor_fact_id = f4.fact_id AND e4.role_id = $6` +
|
|
266
|
+
`)` +
|
|
267
|
+
`) AND EXISTS (` +
|
|
268
|
+
`SELECT 1 ` +
|
|
269
|
+
`FROM public.edge e5 ` +
|
|
270
|
+
`JOIN public.fact f6 ` +
|
|
271
|
+
`ON f6.fact_id = e5.successor_fact_id ` +
|
|
272
|
+
`JOIN public.edge e6 ` +
|
|
273
|
+
`ON e6.successor_fact_id = f6.fact_id ` +
|
|
274
|
+
`AND e6.role_id = $10 ` +
|
|
275
|
+
`JOIN public.fact f7 ` +
|
|
276
|
+
`ON f7.fact_id = e6.predecessor_fact_id ` +
|
|
277
|
+
`WHERE e5.predecessor_fact_id = f3.fact_id AND e5.role_id = $7 ` +
|
|
278
|
+
`AND f7.fact_type_id = $8 AND f7.hash = $9 ` +
|
|
279
|
+
`AND NOT EXISTS (` +
|
|
280
|
+
`SELECT 1 ` +
|
|
281
|
+
`FROM public.edge e7 ` +
|
|
282
|
+
`JOIN public.fact f8 ` +
|
|
283
|
+
`ON f8.fact_id = e7.successor_fact_id ` +
|
|
284
|
+
`WHERE e7.predecessor_fact_id = f6.fact_id AND e7.role_id = $11` +
|
|
285
|
+
`)` +
|
|
286
|
+
`) ` +
|
|
287
|
+
`ORDER BY f3.fact_id ASC, f9.fact_id ASC`);
|
|
288
|
+
});
|
|
289
|
+
});
|