@peerbit/indexer-sqlite3 0.0.1-cccc078
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 +111 -0
- package/dist/benchmark/index.d.ts +2 -0
- package/dist/benchmark/index.d.ts.map +1 -0
- package/dist/benchmark/index.js +6 -0
- package/dist/benchmark/index.js.map +1 -0
- package/dist/peerbit/sqlite3-bundler-friendly.mjs +14481 -0
- package/dist/peerbit/sqlite3-node.mjs +12561 -0
- package/dist/peerbit/sqlite3-opfs-async-proxy.js +826 -0
- package/dist/peerbit/sqlite3-worker1-bundler-friendly.mjs +35 -0
- package/dist/peerbit/sqlite3-worker1-promiser.js +193 -0
- package/dist/peerbit/sqlite3-worker1-promiser.mjs +187 -0
- package/dist/peerbit/sqlite3-worker1.js +46 -0
- package/dist/peerbit/sqlite3.js +14520 -0
- package/dist/peerbit/sqlite3.min.js +21695 -0
- package/dist/peerbit/sqlite3.mjs +14483 -0
- package/dist/peerbit/sqlite3.wasm +0 -0
- package/dist/peerbit/sqlite3.worker.min.js +17995 -0
- package/dist/src/engine.d.ts +90 -0
- package/dist/src/engine.d.ts.map +1 -0
- package/dist/src/engine.js +414 -0
- package/dist/src/engine.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +15 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/schema.d.ts +73 -0
- package/dist/src/schema.d.ts.map +1 -0
- package/dist/src/schema.js +1075 -0
- package/dist/src/schema.js.map +1 -0
- package/dist/src/sqlite3-messages.worker.d.ts +86 -0
- package/dist/src/sqlite3-messages.worker.d.ts.map +1 -0
- package/dist/src/sqlite3-messages.worker.js +9 -0
- package/dist/src/sqlite3-messages.worker.js.map +1 -0
- package/dist/src/sqlite3.browser.d.ts +4 -0
- package/dist/src/sqlite3.browser.d.ts.map +1 -0
- package/dist/src/sqlite3.browser.js +181 -0
- package/dist/src/sqlite3.browser.js.map +1 -0
- package/dist/src/sqlite3.d.ts +4 -0
- package/dist/src/sqlite3.d.ts.map +1 -0
- package/dist/src/sqlite3.js +51 -0
- package/dist/src/sqlite3.js.map +1 -0
- package/dist/src/sqlite3.wasm.d.ts +30 -0
- package/dist/src/sqlite3.wasm.d.ts.map +1 -0
- package/dist/src/sqlite3.wasm.js +180 -0
- package/dist/src/sqlite3.wasm.js.map +1 -0
- package/dist/src/sqlite3.worker.d.ts +2 -0
- package/dist/src/sqlite3.worker.d.ts.map +1 -0
- package/dist/src/sqlite3.worker.js +105 -0
- package/dist/src/sqlite3.worker.js.map +1 -0
- package/dist/src/types.d.ts +23 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +80 -0
- package/src/engine.ts +639 -0
- package/src/index.ts +16 -0
- package/src/schema.ts +1607 -0
- package/src/sqlite3-messages.worker.ts +123 -0
- package/src/sqlite3.browser.ts +245 -0
- package/src/sqlite3.ts +56 -0
- package/src/sqlite3.wasm.ts +211 -0
- package/src/sqlite3.worker.ts +109 -0
- package/src/types.ts +39 -0
|
@@ -0,0 +1,1075 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { FixedArrayKind, OptionKind, VecKind, WrappedType, deserialize, field as fieldDecalaration, getDependencies, getSchema, serialize, variant, } from "@dao-xyz/borsh";
|
|
11
|
+
import { toHexString } from "@peerbit/crypto";
|
|
12
|
+
import * as types from "@peerbit/indexer-interface";
|
|
13
|
+
const SQLConversionMap = {
|
|
14
|
+
u8: "INTEGER",
|
|
15
|
+
u16: "INTEGER",
|
|
16
|
+
u32: "INTEGER",
|
|
17
|
+
u64: "INTEGER",
|
|
18
|
+
i8: "INTEGER",
|
|
19
|
+
i16: "INTEGER",
|
|
20
|
+
i32: "INTEGER",
|
|
21
|
+
i64: "INTEGER",
|
|
22
|
+
f32: "REAL",
|
|
23
|
+
f64: "REAL",
|
|
24
|
+
bool: "INTEGER",
|
|
25
|
+
string: "TEXT",
|
|
26
|
+
Uint8Array: "BLOB",
|
|
27
|
+
Date: "TEXT",
|
|
28
|
+
};
|
|
29
|
+
const WRAPPED_SIMPLE_VALUE_VARIANT = "wrapped";
|
|
30
|
+
export const convertToSQLType = (value, type) => {
|
|
31
|
+
// add bigint when https://github.com/TryGhost/node-sqlite3/pull/1501 fixed
|
|
32
|
+
if (type === "bool") {
|
|
33
|
+
if (value != null) {
|
|
34
|
+
return value ? 1 : 0;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
};
|
|
40
|
+
const nullAsUndefined = (value) => (value === null ? undefined : value);
|
|
41
|
+
export const escapeColumnName = (name) => `"${name}"`;
|
|
42
|
+
export const convertFromSQLType = (value, type) => {
|
|
43
|
+
if (type === "bool") {
|
|
44
|
+
if (value === 0 ||
|
|
45
|
+
value === 1 ||
|
|
46
|
+
value === 0n ||
|
|
47
|
+
value === 1n ||
|
|
48
|
+
typeof value === "boolean") {
|
|
49
|
+
return value ? true : false;
|
|
50
|
+
}
|
|
51
|
+
return nullAsUndefined(value);
|
|
52
|
+
}
|
|
53
|
+
if (type === "u8" || type === "u16" || type === "u32") {
|
|
54
|
+
return typeof value === "bigint" || typeof value === "string"
|
|
55
|
+
? Number(value)
|
|
56
|
+
: nullAsUndefined(value);
|
|
57
|
+
}
|
|
58
|
+
if (type === "u64") {
|
|
59
|
+
return typeof value === "number" || typeof value === "string"
|
|
60
|
+
? BigInt(value)
|
|
61
|
+
: nullAsUndefined(value);
|
|
62
|
+
}
|
|
63
|
+
return nullAsUndefined(value);
|
|
64
|
+
};
|
|
65
|
+
export const toSQLType = (type, isOptional = false) => {
|
|
66
|
+
let ret;
|
|
67
|
+
if (typeof type === "string") {
|
|
68
|
+
const sqlType = SQLConversionMap[type];
|
|
69
|
+
if (!sqlType) {
|
|
70
|
+
throw new Error(`Type ${type} is not supported in SQL`);
|
|
71
|
+
}
|
|
72
|
+
ret = sqlType;
|
|
73
|
+
}
|
|
74
|
+
else if (isUint8ArrayType(type)) {
|
|
75
|
+
ret = "BLOB";
|
|
76
|
+
}
|
|
77
|
+
else if (type instanceof OptionKind) {
|
|
78
|
+
throw new Error("Unexpected option");
|
|
79
|
+
}
|
|
80
|
+
else if (type instanceof VecKind) {
|
|
81
|
+
throw new Error("Unexpected vec");
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
throw new Error(`Type ${JSON.stringify(type)} is not supported in SQL`);
|
|
85
|
+
}
|
|
86
|
+
return isOptional ? ret : ret + " NOT NULL";
|
|
87
|
+
};
|
|
88
|
+
export const getSQLTable = (ctor, path, primary, inline, addJoinField, fromOptionalField = false) => {
|
|
89
|
+
let clazzes = getDependencies(ctor, 0);
|
|
90
|
+
if (!clazzes) {
|
|
91
|
+
clazzes = [ctor];
|
|
92
|
+
}
|
|
93
|
+
let ret = [];
|
|
94
|
+
for (const ctor of clazzes) {
|
|
95
|
+
const name = getTableName(path, getNameOfClass(ctor));
|
|
96
|
+
const newPath = inline ? path : [name];
|
|
97
|
+
const { constraints, fields, dependencies } = getSQLFields(name, newPath, ctor, primary, addJoinField, [], fromOptionalField);
|
|
98
|
+
const table = {
|
|
99
|
+
name,
|
|
100
|
+
constraints,
|
|
101
|
+
fields,
|
|
102
|
+
ctor,
|
|
103
|
+
parentPath: path,
|
|
104
|
+
path: newPath,
|
|
105
|
+
primary,
|
|
106
|
+
primaryIndex: fields.findIndex((x) => x.isPrimary),
|
|
107
|
+
children: dependencies,
|
|
108
|
+
parent: undefined,
|
|
109
|
+
referencedInArray: false,
|
|
110
|
+
isSimpleValue: false,
|
|
111
|
+
inline,
|
|
112
|
+
};
|
|
113
|
+
ret.push(table);
|
|
114
|
+
for (const dep of dependencies) {
|
|
115
|
+
dep.parent = table;
|
|
116
|
+
// ret.push(dep)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return ret;
|
|
120
|
+
};
|
|
121
|
+
const getNameOfVariant = (variant) => {
|
|
122
|
+
return ("v_" + (typeof variant === "string" ? variant : JSON.stringify(variant)));
|
|
123
|
+
};
|
|
124
|
+
const getNameOfClass = (ctor) => {
|
|
125
|
+
let name;
|
|
126
|
+
const schema = getSchema(ctor);
|
|
127
|
+
if (!schema) {
|
|
128
|
+
throw new Error("Schema not found for " + ctor.name);
|
|
129
|
+
}
|
|
130
|
+
if (schema.variant === undefined) {
|
|
131
|
+
/* TODO when to display warning?
|
|
132
|
+
console.warn(
|
|
133
|
+
`Schema associated with ${ctor.name} has no variant. This will results in SQL table with name generated from the Class name. This is not recommended since changing the class name will result in a new table`
|
|
134
|
+
); */
|
|
135
|
+
name = "class_" + ctor.name;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
name = getNameOfVariant(schema.variant);
|
|
139
|
+
}
|
|
140
|
+
return name;
|
|
141
|
+
};
|
|
142
|
+
export const getTableName = (path = [], clazz) => {
|
|
143
|
+
let name = typeof clazz === "string" ? clazz : getNameOfClass(clazz);
|
|
144
|
+
// prefix the generated table name so that the name is a valid SQL identifier (table name)
|
|
145
|
+
// choose prefix which is readable and explains that this is a generated table name
|
|
146
|
+
// leading _ to allow path to have numbers
|
|
147
|
+
const ret = (path.length > 0 ? path.join("__") + "__" : "") +
|
|
148
|
+
name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
149
|
+
return ret;
|
|
150
|
+
};
|
|
151
|
+
export const CHILD_TABLE_ID = "__id";
|
|
152
|
+
export const ARRAY_INDEX_COLUMN = "__index";
|
|
153
|
+
export const PARENT_TABLE_ID = "__parent_id";
|
|
154
|
+
const FOREIGN_VALUE_PROPERTY = "value";
|
|
155
|
+
/* const clazzHasVariants = (clazz: Constructor<any>) => {
|
|
156
|
+
const schema = getSchema(clazz);
|
|
157
|
+
return schema?.variant !== undefined;
|
|
158
|
+
|
|
159
|
+
}
|
|
160
|
+
*/
|
|
161
|
+
const clazzCanBeInlined = (clazz) => {
|
|
162
|
+
return (
|
|
163
|
+
/* clazzHasVariants(clazz) === false && */ (getDependencies(clazz, 0)
|
|
164
|
+
?.length ?? 0) === 0);
|
|
165
|
+
};
|
|
166
|
+
const getInlineObjectExistColumnName = () => {
|
|
167
|
+
return "_exist";
|
|
168
|
+
};
|
|
169
|
+
export const getSQLFields = (tableName, path, ctor, primary, addJoinFieldFromParent, tables = [], isOptional = false) => {
|
|
170
|
+
const schema = getSchema(ctor);
|
|
171
|
+
const fields = schema.fields;
|
|
172
|
+
const sqlFields = [];
|
|
173
|
+
const sqlConstraints = [];
|
|
174
|
+
let foundPrimary = false;
|
|
175
|
+
const addJoinFields = primary === false
|
|
176
|
+
? addJoinFieldFromParent
|
|
177
|
+
: (fields, contstraints) => {
|
|
178
|
+
// we resolve primary field here since it might be unknown until this point
|
|
179
|
+
const primaryField = primary != null
|
|
180
|
+
? sqlFields.find((field) => field.name === primary)
|
|
181
|
+
: undefined;
|
|
182
|
+
const parentPrimaryFieldName = primaryField?.key || CHILD_TABLE_ID;
|
|
183
|
+
const parentPrimaryFieldType = primaryField
|
|
184
|
+
? primaryField.type
|
|
185
|
+
: "INTEGER";
|
|
186
|
+
fields.unshift({
|
|
187
|
+
name: CHILD_TABLE_ID,
|
|
188
|
+
key: CHILD_TABLE_ID,
|
|
189
|
+
definition: `${CHILD_TABLE_ID} INTEGER PRIMARY KEY`,
|
|
190
|
+
type: "INTEGER",
|
|
191
|
+
isPrimary: true,
|
|
192
|
+
from: undefined,
|
|
193
|
+
path: [CHILD_TABLE_ID],
|
|
194
|
+
},
|
|
195
|
+
// foreign key parent document
|
|
196
|
+
{
|
|
197
|
+
name: PARENT_TABLE_ID,
|
|
198
|
+
key: PARENT_TABLE_ID,
|
|
199
|
+
definition: `${PARENT_TABLE_ID} ${parentPrimaryFieldType}`,
|
|
200
|
+
type: parentPrimaryFieldType,
|
|
201
|
+
isPrimary: false,
|
|
202
|
+
from: undefined,
|
|
203
|
+
path: [PARENT_TABLE_ID],
|
|
204
|
+
});
|
|
205
|
+
contstraints.push({
|
|
206
|
+
name: `${PARENT_TABLE_ID}_fk`,
|
|
207
|
+
definition: `CONSTRAINT ${PARENT_TABLE_ID}_fk FOREIGN KEY(${PARENT_TABLE_ID}) REFERENCES ${tableName}(${parentPrimaryFieldName}) ON DELETE CASCADE`,
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
const handleNestedType = (key, field) => {
|
|
211
|
+
var _a;
|
|
212
|
+
let chilCtor;
|
|
213
|
+
let elementType;
|
|
214
|
+
let isVec = false;
|
|
215
|
+
if (field instanceof VecKind) {
|
|
216
|
+
if (field.elementType instanceof VecKind) {
|
|
217
|
+
throw new Error("vec(vec(...)) is not supported");
|
|
218
|
+
}
|
|
219
|
+
elementType = field.elementType;
|
|
220
|
+
isVec = true;
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
elementType = field;
|
|
224
|
+
}
|
|
225
|
+
let isSimpleValue = false;
|
|
226
|
+
if (typeof elementType === "function" && !isUint8ArrayType(elementType)) {
|
|
227
|
+
chilCtor = elementType;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
let SimpleNested = class SimpleNested {
|
|
231
|
+
[_a = FOREIGN_VALUE_PROPERTY];
|
|
232
|
+
constructor(value) {
|
|
233
|
+
this[FOREIGN_VALUE_PROPERTY] = value;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
__decorate([
|
|
237
|
+
fieldDecalaration({ type: elementType }),
|
|
238
|
+
__metadata("design:type", Object)
|
|
239
|
+
], SimpleNested.prototype, _a, void 0);
|
|
240
|
+
SimpleNested = __decorate([
|
|
241
|
+
variant(WRAPPED_SIMPLE_VALUE_VARIANT),
|
|
242
|
+
__metadata("design:paramtypes", [Object])
|
|
243
|
+
], SimpleNested);
|
|
244
|
+
chilCtor = SimpleNested;
|
|
245
|
+
isSimpleValue = true;
|
|
246
|
+
}
|
|
247
|
+
const subtables = getSQLTable(chilCtor, [...path, key], CHILD_TABLE_ID, false, addJoinFields);
|
|
248
|
+
for (const table of subtables) {
|
|
249
|
+
if (!tables.find((x) => x.name === table.name)) {
|
|
250
|
+
if (isVec) {
|
|
251
|
+
table.referencedInArray = true;
|
|
252
|
+
table.fields = [
|
|
253
|
+
...table.fields.slice(0, 2),
|
|
254
|
+
{
|
|
255
|
+
name: ARRAY_INDEX_COLUMN,
|
|
256
|
+
key: ARRAY_INDEX_COLUMN,
|
|
257
|
+
definition: ARRAY_INDEX_COLUMN + " INTEGER",
|
|
258
|
+
type: "INTEGER",
|
|
259
|
+
isPrimary: false,
|
|
260
|
+
from: undefined,
|
|
261
|
+
path: [ARRAY_INDEX_COLUMN],
|
|
262
|
+
},
|
|
263
|
+
...table.fields.slice(2),
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
table.isSimpleValue = isSimpleValue;
|
|
267
|
+
tables.push(table);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
const handleSimpleField = (key, field, type, isOptional) => {
|
|
272
|
+
let keyString = getInlineTableFieldName(path.slice(1), key);
|
|
273
|
+
const isPrimary = primary != null && keyString === primary;
|
|
274
|
+
foundPrimary = foundPrimary || isPrimary;
|
|
275
|
+
const fieldType = toSQLType(type, isOptional);
|
|
276
|
+
sqlFields.push({
|
|
277
|
+
name: keyString,
|
|
278
|
+
key,
|
|
279
|
+
definition: `${escapeColumnName(keyString)} ${fieldType} ${isPrimary ? "PRIMARY KEY" : ""}`,
|
|
280
|
+
type: fieldType,
|
|
281
|
+
isPrimary,
|
|
282
|
+
from: field,
|
|
283
|
+
path: [...path.slice(1), key],
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
const handleField = (key, field, type, isOptional) => {
|
|
287
|
+
if (type instanceof FixedArrayKind && type.elementType === "u8") {
|
|
288
|
+
type = Uint8Array;
|
|
289
|
+
}
|
|
290
|
+
if (typeof type === "string" || type === Uint8Array) {
|
|
291
|
+
handleSimpleField(key, field, type, true);
|
|
292
|
+
}
|
|
293
|
+
else if (typeof type === "function" &&
|
|
294
|
+
clazzCanBeInlined(type)) {
|
|
295
|
+
// if field is object but is not polymorphic we can do a simple field inlining
|
|
296
|
+
const subPath = [...path, key];
|
|
297
|
+
const subtables = getSQLTable(type, subPath, false, true, addJoinFields, isOptional);
|
|
298
|
+
for (const table of subtables) {
|
|
299
|
+
if (!tables.find((x) => x.name === table.name)) {
|
|
300
|
+
tables.push(table);
|
|
301
|
+
if (table.inline) {
|
|
302
|
+
for (const field of table.fields) {
|
|
303
|
+
const isPrimary = primary != null && field.name === primary;
|
|
304
|
+
foundPrimary = foundPrimary || isPrimary;
|
|
305
|
+
sqlFields.push(field);
|
|
306
|
+
}
|
|
307
|
+
sqlConstraints.push(...table.constraints);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
else if (typeof type === "function") {
|
|
313
|
+
handleNestedType(key, type);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
throw new Error(`Unsupported type: ${JSON.stringify(type)}}`);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
for (const field of fields) {
|
|
320
|
+
if (field.type instanceof VecKind) {
|
|
321
|
+
handleNestedType(field.key, field.type);
|
|
322
|
+
}
|
|
323
|
+
else if (field.type instanceof OptionKind) {
|
|
324
|
+
if (field.type.elementType instanceof VecKind) {
|
|
325
|
+
// TODO but how ?
|
|
326
|
+
throw new Error("option(vec(T)) not supported");
|
|
327
|
+
}
|
|
328
|
+
else if (field.type.elementType instanceof OptionKind) {
|
|
329
|
+
throw new Error("option(option(T)) not supported");
|
|
330
|
+
}
|
|
331
|
+
handleField(field.key, field, field.type.elementType, true);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
handleField(field.key, field, field.type, isOptional);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (primary !== false) {
|
|
338
|
+
// primareKey will be false for nested objects that are inlined
|
|
339
|
+
if (!foundPrimary && primary !== CHILD_TABLE_ID) {
|
|
340
|
+
throw new Error(`Primary key ${primary} not found in schema`);
|
|
341
|
+
}
|
|
342
|
+
addJoinFieldFromParent?.(sqlFields, sqlConstraints);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
// inline
|
|
346
|
+
if (isOptional) {
|
|
347
|
+
// add field indicating if the inline object exists,
|
|
348
|
+
let key = getInlineObjectExistColumnName();
|
|
349
|
+
let keyString = getInlineTableFieldName(path.slice(1), key);
|
|
350
|
+
sqlFields.push({
|
|
351
|
+
name: keyString,
|
|
352
|
+
key,
|
|
353
|
+
definition: `${escapeColumnName(keyString)} INTEGER`,
|
|
354
|
+
type: "bool",
|
|
355
|
+
isPrimary: false,
|
|
356
|
+
from: undefined,
|
|
357
|
+
path: [...path.slice(1), key],
|
|
358
|
+
describesExistenceOfAnother: path[path.length - 1],
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
fields: sqlFields,
|
|
364
|
+
constraints: sqlConstraints,
|
|
365
|
+
dependencies: tables,
|
|
366
|
+
};
|
|
367
|
+
};
|
|
368
|
+
export const resolveTable = (key, tables, clazz, throwOnMissing) => {
|
|
369
|
+
const name = /* key == null ? */ getTableName(key, clazz); /* : getSubTableName(scope, key, ctor); */
|
|
370
|
+
const table = tables.get(name) ||
|
|
371
|
+
tables.get(getTableName(key, getNameOfVariant(WRAPPED_SIMPLE_VALUE_VARIANT)) /* key.join("__") + "__" + getNameOfVariant(WRAPPED_SIMPLE_VALUE_VARIANT) */);
|
|
372
|
+
if (!table && throwOnMissing) {
|
|
373
|
+
throw new Error(`Table not found for ${name}: ${Array.from(tables.keys())}`);
|
|
374
|
+
}
|
|
375
|
+
return table;
|
|
376
|
+
};
|
|
377
|
+
const isNestedType = (type) => {
|
|
378
|
+
const unwrapped = unwrapNestedType(type);
|
|
379
|
+
return typeof unwrapped === "function" && unwrapped !== Uint8Array;
|
|
380
|
+
};
|
|
381
|
+
const unwrapNestedType = (type) => {
|
|
382
|
+
if (type instanceof WrappedType) {
|
|
383
|
+
return type.elementType;
|
|
384
|
+
}
|
|
385
|
+
return type;
|
|
386
|
+
};
|
|
387
|
+
const getTableFromField = (parentTable, tables, field) => {
|
|
388
|
+
if (!field) {
|
|
389
|
+
throw new Error("Field is undefined");
|
|
390
|
+
}
|
|
391
|
+
let clazzNames = [];
|
|
392
|
+
if (!isNestedType(field.type)) {
|
|
393
|
+
clazzNames.push(WRAPPED_SIMPLE_VALUE_VARIANT);
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
const testCtors = [
|
|
397
|
+
unwrapNestedType(field.type),
|
|
398
|
+
...(getDependencies(unwrapNestedType(field.type), 0) ||
|
|
399
|
+
[]),
|
|
400
|
+
];
|
|
401
|
+
for (const ctor of testCtors) {
|
|
402
|
+
if (!ctor) {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const schema = getSchema(ctor);
|
|
406
|
+
if (!schema) {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (ctor) {
|
|
410
|
+
clazzNames.push(getNameOfClass(ctor));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (clazzNames.length === 0) {
|
|
415
|
+
throw new Error("Could not find class name");
|
|
416
|
+
}
|
|
417
|
+
const subTable = clazzNames
|
|
418
|
+
.map((clazzName) => resolveTable([...parentTable.path, field.key], tables, clazzName, false))
|
|
419
|
+
.filter((x) => x != null);
|
|
420
|
+
return subTable;
|
|
421
|
+
};
|
|
422
|
+
const getTableFromValue = (parentTable, tables, field, value) => {
|
|
423
|
+
let clazzName = undefined;
|
|
424
|
+
if (!isNestedType(field.type)) {
|
|
425
|
+
clazzName = WRAPPED_SIMPLE_VALUE_VARIANT;
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
const testCtors = value?.constructor
|
|
429
|
+
? [value?.constructor]
|
|
430
|
+
: [
|
|
431
|
+
unwrapNestedType(field.type),
|
|
432
|
+
...(getDependencies(unwrapNestedType(field.type), 0) || []),
|
|
433
|
+
];
|
|
434
|
+
for (const ctor of testCtors) {
|
|
435
|
+
if (!ctor) {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const schema = getSchema(ctor);
|
|
439
|
+
if (!schema) {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (ctor) {
|
|
443
|
+
clazzName = getNameOfClass(ctor);
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (!clazzName) {
|
|
449
|
+
throw new Error("Could not find class name");
|
|
450
|
+
}
|
|
451
|
+
const subTable = resolveTable([...parentTable.path, field.key], tables, clazzName, true);
|
|
452
|
+
return subTable;
|
|
453
|
+
};
|
|
454
|
+
const isUint8ArrayType = (type) => {
|
|
455
|
+
if (type === Uint8Array) {
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
if (type instanceof FixedArrayKind) {
|
|
459
|
+
return type.elementType === "u8";
|
|
460
|
+
}
|
|
461
|
+
return false;
|
|
462
|
+
};
|
|
463
|
+
export const insert = async (insertFn, obj, tables, table, fields, handleNestedCallback, parentId = undefined, index) => {
|
|
464
|
+
const bindableValues = [];
|
|
465
|
+
let nestedCallbacks = [];
|
|
466
|
+
handleNestedCallback =
|
|
467
|
+
table.primary === false
|
|
468
|
+
? handleNestedCallback
|
|
469
|
+
: (fn) => nestedCallbacks.push(fn);
|
|
470
|
+
const handleElement = async (item, field, parentId, index) => {
|
|
471
|
+
const subTable = getTableFromValue(table, tables, field, item);
|
|
472
|
+
await insert(insertFn, typeof item === "function" && item instanceof Uint8Array === false
|
|
473
|
+
? item
|
|
474
|
+
: subTable.isSimpleValue
|
|
475
|
+
? // eslint-disable-next-line new-cap
|
|
476
|
+
new subTable.ctor(item)
|
|
477
|
+
: Object.assign(Object.create(subTable.ctor.prototype), item), tables, subTable, getSchema(subTable.ctor).fields, handleNestedCallback, parentId, index);
|
|
478
|
+
};
|
|
479
|
+
const handleNested = async (field, optional, parentId) => {
|
|
480
|
+
if (Array.isArray(obj[field.key])) {
|
|
481
|
+
const arr = obj[field.key];
|
|
482
|
+
for (let i = 0; i < arr.length; i++) {
|
|
483
|
+
const item = arr[i];
|
|
484
|
+
await handleElement(item, field, parentId, i);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
if (field instanceof VecKind) {
|
|
489
|
+
if (obj[field.key] == null) {
|
|
490
|
+
if (!optional) {
|
|
491
|
+
throw new Error("Expected array, received null");
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
throw new Error("Expected array");
|
|
498
|
+
}
|
|
499
|
+
const value = obj[field.key];
|
|
500
|
+
if (value == null) {
|
|
501
|
+
if (!optional) {
|
|
502
|
+
throw new Error("Expected object, received null");
|
|
503
|
+
}
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
await handleElement(value, field, parentId);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
let nestedFields = [];
|
|
510
|
+
if (parentId != null) {
|
|
511
|
+
bindableValues.push(undefined);
|
|
512
|
+
bindableValues.push(parentId);
|
|
513
|
+
if (index != null) {
|
|
514
|
+
bindableValues.push(index);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
for (const field of fields) {
|
|
518
|
+
const unwrappedType = unwrapNestedType(field.type);
|
|
519
|
+
if (field.type instanceof VecKind === false) {
|
|
520
|
+
if (typeof unwrappedType === "string" ||
|
|
521
|
+
isUint8ArrayType(unwrappedType)) {
|
|
522
|
+
bindableValues.push(convertToSQLType(obj[field.key], unwrappedType));
|
|
523
|
+
}
|
|
524
|
+
else if (typeof unwrappedType === "function" &&
|
|
525
|
+
clazzCanBeInlined(unwrappedType)) {
|
|
526
|
+
const value = obj[field.key];
|
|
527
|
+
const subTable = getTableFromValue(table, tables, field, value);
|
|
528
|
+
if (subTable.inline && value == null) {
|
|
529
|
+
for (const _field of subTable.fields) {
|
|
530
|
+
bindableValues.push(null);
|
|
531
|
+
}
|
|
532
|
+
bindableValues[bindableValues.length - 1] = false; // assign the value "false" to the exist field column
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
await insert((values, table) => {
|
|
536
|
+
if (table.inline) {
|
|
537
|
+
bindableValues.push(...values); // insert the bindable values into the parent bindable array
|
|
538
|
+
if (field.type instanceof OptionKind) {
|
|
539
|
+
bindableValues.push(true); // assign the value "true" to the exist field column
|
|
540
|
+
}
|
|
541
|
+
return undefined;
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
return insertFn(values, table);
|
|
545
|
+
}
|
|
546
|
+
}, value, tables, subTable, getSchema(unwrappedType).fields, (fn) => nestedCallbacks.push(fn), parentId, index);
|
|
547
|
+
/* await insert(, obj[field.key], tables, subTable, getSchema(unwrappedType).fields, parentId, index); */
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
nestedFields.push(field);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
nestedFields.push(field);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
// we handle nested after self insertion so we have a id defined for 'this'
|
|
558
|
+
// this is important because if we insert a related document in a foreign table
|
|
559
|
+
// we need to know the id of the parent document to insert the foreign key correctly
|
|
560
|
+
for (const nested of nestedFields) {
|
|
561
|
+
const isOptional = nested.type instanceof OptionKind;
|
|
562
|
+
handleNestedCallback((id) => handleNested(nested, isOptional, id));
|
|
563
|
+
}
|
|
564
|
+
const thisId = await insertFn(bindableValues, table);
|
|
565
|
+
if (table.primary === false && nestedCallbacks.length > 0) {
|
|
566
|
+
throw new Error("Unexpected");
|
|
567
|
+
}
|
|
568
|
+
await Promise.all(nestedCallbacks.map((x) => x(thisId)));
|
|
569
|
+
/* return [result, ...ret]; */
|
|
570
|
+
};
|
|
571
|
+
export const getTablePrefixedField = (table, key, skipPrefix = false) => `${skipPrefix ? "" : table.name + "#"}${getInlineTableFieldName(table.path.slice(1), key)}`;
|
|
572
|
+
export const getTableNameFromPrefixedField = (prefixedField) => prefixedField.split("#")[0];
|
|
573
|
+
export const getInlineTableFieldName = (path, key) => (path && path.length > 0 ? `${path.join("_")}__${key}` : key);
|
|
574
|
+
const matchFieldInShape = (shape, path, field) => {
|
|
575
|
+
if (!shape) {
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
let currentShape = shape;
|
|
579
|
+
if (field.path) {
|
|
580
|
+
for (let i = 0; i < field.path.length; i++) {
|
|
581
|
+
if (!currentShape) {
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
let nextShape = currentShape[field.path[i]];
|
|
585
|
+
if (nextShape === undefined) {
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
if (nextShape === true) {
|
|
589
|
+
return true;
|
|
590
|
+
}
|
|
591
|
+
currentShape = nextShape;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
throw new Error("Unexpected");
|
|
595
|
+
};
|
|
596
|
+
export const selectChildren = (childrenTable) => "select * from " + childrenTable.name + " where " + PARENT_TABLE_ID + " = ?";
|
|
597
|
+
export const selectAllFields = (table, shape) => {
|
|
598
|
+
let stack = [{ table, shape }];
|
|
599
|
+
let join = new Map();
|
|
600
|
+
const fieldResolvers = [];
|
|
601
|
+
for (const tableAndShape of stack) {
|
|
602
|
+
if (!tableAndShape.table.inline) {
|
|
603
|
+
for (const field of tableAndShape.table.fields) {
|
|
604
|
+
if (field.isPrimary ||
|
|
605
|
+
!tableAndShape.shape ||
|
|
606
|
+
matchFieldInShape(tableAndShape.shape, [], field)) {
|
|
607
|
+
const value = `${tableAndShape.table.name}.${escapeColumnName(field.name)} as '${getTablePrefixedField(tableAndShape.table, field.name)}'`;
|
|
608
|
+
fieldResolvers.push(value);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
for (const child of tableAndShape.table.children) {
|
|
613
|
+
if (child.referencedInArray) {
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
let childShape = undefined;
|
|
617
|
+
if (tableAndShape.shape) {
|
|
618
|
+
const parentPath = child.parentPath?.slice(1);
|
|
619
|
+
let maybeShape = parentPath
|
|
620
|
+
? tableAndShape.shape?.[parentPath[parentPath.length - 1]]
|
|
621
|
+
: undefined;
|
|
622
|
+
if (!maybeShape) {
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
childShape = maybeShape === true ? undefined : maybeShape;
|
|
626
|
+
}
|
|
627
|
+
stack.push({ table: child, shape: childShape });
|
|
628
|
+
if (!child.inline) {
|
|
629
|
+
join.set(child.name, { as: child.name, table: child });
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (fieldResolvers.length === 0) {
|
|
634
|
+
throw new Error("No fields to resolve");
|
|
635
|
+
}
|
|
636
|
+
return {
|
|
637
|
+
query: `SELECT ${fieldResolvers.join(", ")} FROM ${table.name}`,
|
|
638
|
+
join,
|
|
639
|
+
};
|
|
640
|
+
};
|
|
641
|
+
const getNonInlinedTable = (from) => {
|
|
642
|
+
let current = from;
|
|
643
|
+
while (current.inline) {
|
|
644
|
+
if (!current.parent) {
|
|
645
|
+
throw new Error("No parent found");
|
|
646
|
+
}
|
|
647
|
+
current = current.parent;
|
|
648
|
+
}
|
|
649
|
+
return current;
|
|
650
|
+
};
|
|
651
|
+
// the inverse of resolveFieldValues
|
|
652
|
+
export const resolveInstanceFromValue = async (fromTablePrefixedValues, tables, table, resolveChildren, tablePrefixed, shape) => {
|
|
653
|
+
const fields = getSchema(table.ctor).fields;
|
|
654
|
+
const obj = {};
|
|
655
|
+
const handleNested = async (field, isOptional, isArray) => {
|
|
656
|
+
const subTables = getTableFromField(table, tables, field); // TODO fix
|
|
657
|
+
let maybeShape = shape?.[field.key];
|
|
658
|
+
let subshape = maybeShape === true ? undefined : maybeShape;
|
|
659
|
+
if (isArray) {
|
|
660
|
+
let once = false;
|
|
661
|
+
let resolvedArr = [];
|
|
662
|
+
for (const subtable of subTables) {
|
|
663
|
+
// TODO types
|
|
664
|
+
let rootTable = getNonInlinedTable(table);
|
|
665
|
+
const arr = await resolveChildren(fromTablePrefixedValues[getTablePrefixedField(rootTable, rootTable.primary, !tablePrefixed)], subtable);
|
|
666
|
+
if (arr) {
|
|
667
|
+
once = true;
|
|
668
|
+
for (const element of arr) {
|
|
669
|
+
const resolved = await resolveInstanceFromValue(element, tables, subtable, // TODO fix
|
|
670
|
+
resolveChildren, false, subshape);
|
|
671
|
+
resolvedArr[element[ARRAY_INDEX_COLUMN]] = subtable.isSimpleValue
|
|
672
|
+
? resolved.value
|
|
673
|
+
: resolved;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (!once) {
|
|
678
|
+
obj[field.key] = undefined;
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
obj[field.key] = resolvedArr;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
// resolve nested object from row directly
|
|
686
|
+
/* let extracted: any = {} */
|
|
687
|
+
let subTable = undefined;
|
|
688
|
+
if (subTables.length > 1) {
|
|
689
|
+
for (const table of subTables) {
|
|
690
|
+
// TODO types
|
|
691
|
+
if (fromTablePrefixedValues[getTablePrefixedField(table, table.primary, !tablePrefixed)] != null) {
|
|
692
|
+
subTable = table;
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
subTable = subTables[0];
|
|
699
|
+
}
|
|
700
|
+
if (!subTable) {
|
|
701
|
+
throw new Error("Sub table not found");
|
|
702
|
+
}
|
|
703
|
+
/*
|
|
704
|
+
for (const field of subTable.fields) {
|
|
705
|
+
once = true
|
|
706
|
+
extracted[field.name] = fromTablePrefixedValues[getTablePrefixedField(subTable, field.name, !tablePrefixed)]
|
|
707
|
+
}
|
|
708
|
+
*/
|
|
709
|
+
if (subTable.inline && isOptional) {
|
|
710
|
+
let rootTable = getNonInlinedTable(table);
|
|
711
|
+
const isNull = !fromTablePrefixedValues[getTablePrefixedField(rootTable, subTable.fields[subTable.fields.length - 1].name)];
|
|
712
|
+
if (isNull) {
|
|
713
|
+
obj[field.key] = undefined;
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
// TODO types
|
|
718
|
+
if (subTable.primary !== false &&
|
|
719
|
+
fromTablePrefixedValues[getTablePrefixedField(subTable, subTable.primary, !tablePrefixed)] == null) {
|
|
720
|
+
obj[field.key] = undefined;
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
const resolved = await resolveInstanceFromValue(fromTablePrefixedValues, tables, subTable, resolveChildren, tablePrefixed, subshape);
|
|
724
|
+
obj[field.key] = resolved;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
for (const field of fields) {
|
|
729
|
+
if (shape && !shape[field.key]) {
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
const rootTable = getNonInlinedTable(table);
|
|
733
|
+
const referencedField = rootTable.fields.find((sqlField) => sqlField.from === field);
|
|
734
|
+
const fieldValue = referencedField
|
|
735
|
+
? fromTablePrefixedValues[getTablePrefixedField(rootTable, referencedField.name, !tablePrefixed)]
|
|
736
|
+
: undefined;
|
|
737
|
+
if (typeof field.type === "string" || isUint8ArrayType(field.type)) {
|
|
738
|
+
obj[field.key] = convertFromSQLType(fieldValue, field.type);
|
|
739
|
+
}
|
|
740
|
+
else if (field.type instanceof OptionKind) {
|
|
741
|
+
if (typeof field.type.elementType === "string" ||
|
|
742
|
+
isUint8ArrayType(field.type.elementType)) {
|
|
743
|
+
obj[field.key] = convertFromSQLType(fieldValue, field.type.elementType);
|
|
744
|
+
}
|
|
745
|
+
else if (field.type.elementType instanceof VecKind) {
|
|
746
|
+
await handleNested(field, true, true);
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
await handleNested(field, true, false);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
else if (field.type instanceof VecKind) {
|
|
753
|
+
await handleNested(field, false, true);
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
await handleNested(field, false, false);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return Object.assign(Object.create(table.ctor.prototype), obj);
|
|
760
|
+
};
|
|
761
|
+
export const fromRowToObj = (row, ctor) => {
|
|
762
|
+
const schema = getSchema(ctor);
|
|
763
|
+
const fields = schema.fields;
|
|
764
|
+
const obj = {};
|
|
765
|
+
for (const field of fields) {
|
|
766
|
+
obj[field.key] = row[field.key];
|
|
767
|
+
}
|
|
768
|
+
return Object.assign(Object.create(ctor.prototype), obj);
|
|
769
|
+
};
|
|
770
|
+
export const convertDeleteRequestToQuery = (request, tables, table) => {
|
|
771
|
+
return `DELETE FROM ${table.name} WHERE ${table.primary} IN (SELECT ${table.primary} from ${table.name} ${convertRequestToQuery(request, tables, table).query}) returning ${table.primary}`;
|
|
772
|
+
};
|
|
773
|
+
export const convertSumRequestToQuery = (request, tables, table) => {
|
|
774
|
+
return `SELECT SUM(${table.name}.${request.key.join(".")}) as sum FROM ${table.name} ${convertRequestToQuery(request, tables, table).query}`;
|
|
775
|
+
};
|
|
776
|
+
export const convertCountRequestToQuery = (request, tables, table) => {
|
|
777
|
+
return `SELECT count(*) as count FROM ${table.name} ${convertRequestToQuery(request, tables, table).query}`;
|
|
778
|
+
};
|
|
779
|
+
export const convertSearchRequestToQuery = (request, tables, rootTables, shape) => {
|
|
780
|
+
let unionBuilder = "";
|
|
781
|
+
let orderByClause = undefined;
|
|
782
|
+
for (const table of rootTables) {
|
|
783
|
+
const { query: selectQuery, join: joinFromSelect } = selectAllFields(table, shape);
|
|
784
|
+
const { orderBy, query } = convertRequestToQuery(request, tables, table, joinFromSelect);
|
|
785
|
+
unionBuilder += `${unionBuilder.length > 0 ? " UNION ALL " : ""} ${selectQuery} ${query}`;
|
|
786
|
+
orderByClause = orderBy?.length > 0 ? orderBy : orderByClause;
|
|
787
|
+
}
|
|
788
|
+
return `${unionBuilder} ${orderByClause ? orderByClause : ""} limit ? offset ?`;
|
|
789
|
+
};
|
|
790
|
+
const convertRequestToQuery = (request, tables, table, extraJoin, path = []) => {
|
|
791
|
+
let whereBuilder = "";
|
|
792
|
+
let orderByBuilder = undefined;
|
|
793
|
+
/* let tablesToSelect: string[] = [table.name]; */
|
|
794
|
+
let joinBuilder = extraJoin || new Map();
|
|
795
|
+
if (request.query.length === 1) {
|
|
796
|
+
const { where } = convertQueryToSQLQuery(request.query[0], tables, table, joinBuilder, path);
|
|
797
|
+
whereBuilder += where;
|
|
798
|
+
}
|
|
799
|
+
else if (request.query.length > 1) {
|
|
800
|
+
const { where } = convertQueryToSQLQuery(new types.And(request.query), tables, table, joinBuilder, path);
|
|
801
|
+
whereBuilder += where;
|
|
802
|
+
}
|
|
803
|
+
if (request instanceof types.SearchRequest) {
|
|
804
|
+
if (request.sort.length > 0) {
|
|
805
|
+
if (request.sort.length > 0) {
|
|
806
|
+
orderByBuilder = "ORDER BY ";
|
|
807
|
+
}
|
|
808
|
+
let once = false;
|
|
809
|
+
for (const sort of request.sort) {
|
|
810
|
+
const { foreignTables, queryKey } = resolveTableToQuery(table, tables, joinBuilder, [...path, ...sort.key], undefined, true);
|
|
811
|
+
for (const table of foreignTables) {
|
|
812
|
+
if (once) {
|
|
813
|
+
orderByBuilder += ", ";
|
|
814
|
+
}
|
|
815
|
+
once = true;
|
|
816
|
+
orderByBuilder += `${table.as}.${queryKey} ${sort.direction === types.SortDirection.ASC ? "ASC" : "DESC"}`;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/* orderByBuilder += request.sort
|
|
820
|
+
.map(
|
|
821
|
+
(sort) =>
|
|
822
|
+
`${table.name}.${sort.key} ${sort.direction === types.SortDirection.ASC ? "ASC" : "DESC"}`
|
|
823
|
+
)
|
|
824
|
+
.join(", "); */
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const where = whereBuilder.length > 0 ? "where " + whereBuilder : undefined;
|
|
828
|
+
if (extraJoin && extraJoin.size > 0) {
|
|
829
|
+
insertMapIntoMap(joinBuilder, extraJoin);
|
|
830
|
+
}
|
|
831
|
+
let join = buildJoin(joinBuilder, request instanceof types.SearchRequest ? true : false);
|
|
832
|
+
const query = `${join ? join : ""} ${where ? where : ""}`;
|
|
833
|
+
return {
|
|
834
|
+
query,
|
|
835
|
+
orderBy: orderByBuilder,
|
|
836
|
+
};
|
|
837
|
+
};
|
|
838
|
+
export const buildJoin = (joinBuilder, resolveAllColumns) => {
|
|
839
|
+
let joinTypeDefault = resolveAllColumns
|
|
840
|
+
? /* "FULL OUTER JOIN" */ "LEFT OUTER JOIN"
|
|
841
|
+
: "JOIN";
|
|
842
|
+
let join = "";
|
|
843
|
+
for (const [_key, table] of joinBuilder) {
|
|
844
|
+
let nonInlinedParent = table.table.parent && getNonInlinedTable(table.table.parent);
|
|
845
|
+
if (!nonInlinedParent) {
|
|
846
|
+
throw new Error("Unexpected: missing parent");
|
|
847
|
+
}
|
|
848
|
+
let joinType = table.table.referencedInArray
|
|
849
|
+
? /* "FULL OUTER JOIN" */ "LEFT OUTER JOIN"
|
|
850
|
+
: joinTypeDefault;
|
|
851
|
+
join += `${joinType} ${table.table.name} AS ${table.as} ON ${nonInlinedParent.name}.${nonInlinedParent.primary} = ${table.as}.${PARENT_TABLE_ID} `;
|
|
852
|
+
}
|
|
853
|
+
return join;
|
|
854
|
+
};
|
|
855
|
+
const insertMapIntoMap = (map, insert) => {
|
|
856
|
+
for (const [key, value] of insert) {
|
|
857
|
+
map.set(key, value);
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
export const convertQueryToSQLQuery = (query, tables, table, joinBuilder, path = [], tableAlias = undefined) => {
|
|
861
|
+
let whereBuilder = "";
|
|
862
|
+
/* let tablesToSelect: string[] = []; */
|
|
863
|
+
const handleAnd = (queries, path, tableAlias) => {
|
|
864
|
+
for (const query of queries) {
|
|
865
|
+
const { where } = convertQueryToSQLQuery(query, tables, table, joinBuilder, path, tableAlias);
|
|
866
|
+
whereBuilder =
|
|
867
|
+
whereBuilder.length > 0 ? `(${whereBuilder}) AND (${where})` : where;
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
if (query instanceof types.StateFieldQuery) {
|
|
871
|
+
const { where } = convertStateFieldQuery(query, tables, table, joinBuilder, path, tableAlias);
|
|
872
|
+
whereBuilder += where;
|
|
873
|
+
}
|
|
874
|
+
else if (query instanceof types.Nested) {
|
|
875
|
+
let joinPrefix = "__" + String(tables.size);
|
|
876
|
+
path = [...path, query.path];
|
|
877
|
+
handleAnd(query.query, path, joinPrefix);
|
|
878
|
+
}
|
|
879
|
+
else if (query instanceof types.LogicalQuery) {
|
|
880
|
+
if (query instanceof types.And) {
|
|
881
|
+
handleAnd(query.and, path, tableAlias);
|
|
882
|
+
}
|
|
883
|
+
else if (query instanceof types.Or) {
|
|
884
|
+
for (const subquery of query.or) {
|
|
885
|
+
const { where } = convertQueryToSQLQuery(subquery, tables, table, joinBuilder, path, tableAlias);
|
|
886
|
+
whereBuilder =
|
|
887
|
+
whereBuilder.length > 0 ? `(${whereBuilder}) OR (${where})` : where;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
else if (query instanceof types.Not) {
|
|
891
|
+
const { where } = convertQueryToSQLQuery(query.not, tables, table, joinBuilder, path, tableAlias);
|
|
892
|
+
whereBuilder = `NOT (${where})`;
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
throw new Error("Unsupported query type: " + query.constructor.name);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
throw new Error("Unsupported query type: " + query.constructor.name);
|
|
900
|
+
}
|
|
901
|
+
return {
|
|
902
|
+
where: whereBuilder,
|
|
903
|
+
};
|
|
904
|
+
};
|
|
905
|
+
const cloneQuery = (query) => {
|
|
906
|
+
return deserialize(serialize(query), types.StateFieldQuery);
|
|
907
|
+
};
|
|
908
|
+
const createTableReferenceName = (table, alias, fieldType, joinSize) => {
|
|
909
|
+
if (!alias &&
|
|
910
|
+
(fieldType instanceof VecKind ||
|
|
911
|
+
(fieldType instanceof OptionKind &&
|
|
912
|
+
fieldType.elementType instanceof VecKind))) {
|
|
913
|
+
let aliasSuffix = "_" + String(joinSize);
|
|
914
|
+
alias = aliasSuffix;
|
|
915
|
+
}
|
|
916
|
+
const tableNameAs = alias ? alias + "_" + table.name : table.name;
|
|
917
|
+
return tableNameAs;
|
|
918
|
+
};
|
|
919
|
+
const resolveTableToQuery = (table, tables, join, path, alias, searchSelf) => {
|
|
920
|
+
// we are matching in two ways.
|
|
921
|
+
// 1. joins
|
|
922
|
+
// we go down the path and resolve related tables until the last index
|
|
923
|
+
// the last path value is the query key
|
|
924
|
+
// 2. inline table fields
|
|
925
|
+
// multiple keys in the path can correspond to a field in a inline table
|
|
926
|
+
// this means we need to also check if the key is a field in the current table
|
|
927
|
+
if (searchSelf) {
|
|
928
|
+
const inlineName = getInlineTableFieldName(path.slice(0, -1), path[path.length - 1]);
|
|
929
|
+
let field = table.fields.find((x) => x.name === inlineName);
|
|
930
|
+
if (field) {
|
|
931
|
+
return {
|
|
932
|
+
queryKey: field.name,
|
|
933
|
+
foreignTables: [{ table, as: table.name }],
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
let currentTables = [{ table, as: alias || table.name }];
|
|
938
|
+
let prevTables = undefined;
|
|
939
|
+
// outer:
|
|
940
|
+
for (const [_i, key] of path /* .slice(0, -1) */
|
|
941
|
+
.entries()) {
|
|
942
|
+
let newTables = [];
|
|
943
|
+
for (const currentTable of currentTables.map((x) => x.table)) {
|
|
944
|
+
const schema = getSchema(currentTable.ctor);
|
|
945
|
+
const field = schema.fields.find((x) => x.key === key);
|
|
946
|
+
if (!field && currentTable.children.length > 0) {
|
|
947
|
+
// second arg is needed because of polymorphic fields we might end up here intentially to check what tables to query
|
|
948
|
+
throw new Error(`Property with key "${key}" is not found in the schema ${JSON.stringify(schema.fields.map((x) => x.key))}`);
|
|
949
|
+
}
|
|
950
|
+
for (const child of currentTable.children) {
|
|
951
|
+
const tableNameAs = createTableReferenceName(child, alias, field.type, join.size);
|
|
952
|
+
let isMatching = child.parentPath[child.parentPath.length - 1] === key;
|
|
953
|
+
if (isMatching) {
|
|
954
|
+
const tableWithAlias = { table: child, as: tableNameAs };
|
|
955
|
+
if (child.isSimpleValue) {
|
|
956
|
+
if (!child.inline) {
|
|
957
|
+
join.set(tableNameAs, tableWithAlias);
|
|
958
|
+
}
|
|
959
|
+
return {
|
|
960
|
+
queryKey: FOREIGN_VALUE_PROPERTY,
|
|
961
|
+
foreignTables: [tableWithAlias],
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
newTables.push(tableWithAlias);
|
|
965
|
+
if (!child.inline) {
|
|
966
|
+
join.set(tableNameAs, tableWithAlias);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
prevTables = currentTables;
|
|
972
|
+
currentTables = newTables;
|
|
973
|
+
/* if (currentTables.length > 0 && i === path.length - 2) {
|
|
974
|
+
// we are at the last key in the path
|
|
975
|
+
// the next key should be the query key
|
|
976
|
+
break;
|
|
977
|
+
} */
|
|
978
|
+
if (currentTables.length === 0) {
|
|
979
|
+
currentTables = prevTables;
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (currentTables.length === 0) {
|
|
984
|
+
throw new Error("Unexpected");
|
|
985
|
+
}
|
|
986
|
+
let foreignTables = currentTables.filter((x) => x.table.fields.find((x) => x.key === path[path.length - 1]));
|
|
987
|
+
let tableToQuery = foreignTables[foreignTables.length - 1].table;
|
|
988
|
+
let queryKeyPath = [path[path.length - 1]];
|
|
989
|
+
while (tableToQuery?.inline) {
|
|
990
|
+
queryKeyPath.unshift(tableToQuery.parentPath[tableToQuery.parentPath.length - 1]);
|
|
991
|
+
tableToQuery = tableToQuery.parent;
|
|
992
|
+
}
|
|
993
|
+
let queryKey = queryKeyPath.length > 0
|
|
994
|
+
? getInlineTableFieldName(queryKeyPath.slice(0, -1), queryKeyPath[queryKeyPath.length - 1])
|
|
995
|
+
: FOREIGN_VALUE_PROPERTY;
|
|
996
|
+
return { queryKey, foreignTables };
|
|
997
|
+
};
|
|
998
|
+
const convertStateFieldQuery = (query, tables, table, join, path, tableAlias = undefined) => {
|
|
999
|
+
// if field id represented as foreign table, do join and compare
|
|
1000
|
+
const inlinedName = getInlineTableFieldName(query.key.slice(0, query.key.length - 1), query.key[query.key.length - 1]);
|
|
1001
|
+
const tableField = table.fields.find((x) => x.name === inlinedName); /* stringArraysEquals(query.key, [...table.parentPath, x.name]) )*/
|
|
1002
|
+
const isForeign = !tableField; // table.fields.find(x => x.name === query.key[query.key.length - 1])
|
|
1003
|
+
if (isForeign) {
|
|
1004
|
+
const { queryKey, foreignTables } = resolveTableToQuery(table, tables, join, [...path, ...query.key], tableAlias, false);
|
|
1005
|
+
query = cloneQuery(query);
|
|
1006
|
+
query.key = [queryKey];
|
|
1007
|
+
let whereBuilder = [];
|
|
1008
|
+
for (const ftable of foreignTables) {
|
|
1009
|
+
if (ftable.table === table) {
|
|
1010
|
+
throw new Error("Unexpected");
|
|
1011
|
+
}
|
|
1012
|
+
const { where } = convertQueryToSQLQuery(query, tables, ftable.table, join, path, ftable.as);
|
|
1013
|
+
whereBuilder.push(where);
|
|
1014
|
+
}
|
|
1015
|
+
return { where: whereBuilder.join(" OR ") };
|
|
1016
|
+
}
|
|
1017
|
+
const keyWithTable = (tableAlias || table.name) + "." + escapeColumnName(inlinedName);
|
|
1018
|
+
let where;
|
|
1019
|
+
if (query instanceof types.StringMatch) {
|
|
1020
|
+
let statement = "";
|
|
1021
|
+
if (query.method === types.StringMatchMethod.contains) {
|
|
1022
|
+
statement = `${keyWithTable} LIKE '%${query.value}%'`;
|
|
1023
|
+
}
|
|
1024
|
+
else if (query.method === types.StringMatchMethod.prefix) {
|
|
1025
|
+
statement = `${keyWithTable} LIKE '${query.value}%'`;
|
|
1026
|
+
}
|
|
1027
|
+
else if (query.method === types.StringMatchMethod.exact) {
|
|
1028
|
+
statement = `${keyWithTable} = '${query.value}'`;
|
|
1029
|
+
}
|
|
1030
|
+
if (query.caseInsensitive) {
|
|
1031
|
+
statement += " COLLATE NOCASE";
|
|
1032
|
+
}
|
|
1033
|
+
where = statement;
|
|
1034
|
+
}
|
|
1035
|
+
else if (query instanceof types.ByteMatchQuery) {
|
|
1036
|
+
// compare Blob compule with f.value
|
|
1037
|
+
const statement = `${keyWithTable} = x'${toHexString(query.value)}'`;
|
|
1038
|
+
where = statement;
|
|
1039
|
+
}
|
|
1040
|
+
else if (query instanceof types.IntegerCompare) {
|
|
1041
|
+
if (tableField.type === "BLOB") {
|
|
1042
|
+
// TODO perf
|
|
1043
|
+
where = `hex(${keyWithTable}) LIKE '%${toHexString(new Uint8Array([Number(query.value.value)]))}%'`;
|
|
1044
|
+
}
|
|
1045
|
+
else if (query.compare === types.Compare.Equal) {
|
|
1046
|
+
where = `${keyWithTable} = ${query.value.value}`;
|
|
1047
|
+
}
|
|
1048
|
+
else if (query.compare === types.Compare.Greater) {
|
|
1049
|
+
where = `${keyWithTable} > ${query.value.value}`;
|
|
1050
|
+
}
|
|
1051
|
+
else if (query.compare === types.Compare.Less) {
|
|
1052
|
+
where = `${keyWithTable} < ${query.value.value}`;
|
|
1053
|
+
}
|
|
1054
|
+
else if (query.compare === types.Compare.GreaterOrEqual) {
|
|
1055
|
+
where = `${keyWithTable} >= ${query.value.value}`;
|
|
1056
|
+
}
|
|
1057
|
+
else if (query.compare === types.Compare.LessOrEqual) {
|
|
1058
|
+
where = `${keyWithTable} <= ${query.value.value}`;
|
|
1059
|
+
}
|
|
1060
|
+
else {
|
|
1061
|
+
throw new Error(`Unsupported compare type: ${query.compare}`);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
else if (query instanceof types.IsNull) {
|
|
1065
|
+
where = `${keyWithTable} IS NULL`;
|
|
1066
|
+
}
|
|
1067
|
+
else if (query instanceof types.BoolQuery) {
|
|
1068
|
+
where = `${keyWithTable} = ${query.value}`;
|
|
1069
|
+
}
|
|
1070
|
+
else {
|
|
1071
|
+
throw new Error("Unsupported query type: " + query.constructor.name);
|
|
1072
|
+
}
|
|
1073
|
+
return { where };
|
|
1074
|
+
};
|
|
1075
|
+
//# sourceMappingURL=schema.js.map
|