@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.
Files changed (63) hide show
  1. package/README.md +111 -0
  2. package/dist/benchmark/index.d.ts +2 -0
  3. package/dist/benchmark/index.d.ts.map +1 -0
  4. package/dist/benchmark/index.js +6 -0
  5. package/dist/benchmark/index.js.map +1 -0
  6. package/dist/peerbit/sqlite3-bundler-friendly.mjs +14481 -0
  7. package/dist/peerbit/sqlite3-node.mjs +12561 -0
  8. package/dist/peerbit/sqlite3-opfs-async-proxy.js +826 -0
  9. package/dist/peerbit/sqlite3-worker1-bundler-friendly.mjs +35 -0
  10. package/dist/peerbit/sqlite3-worker1-promiser.js +193 -0
  11. package/dist/peerbit/sqlite3-worker1-promiser.mjs +187 -0
  12. package/dist/peerbit/sqlite3-worker1.js +46 -0
  13. package/dist/peerbit/sqlite3.js +14520 -0
  14. package/dist/peerbit/sqlite3.min.js +21695 -0
  15. package/dist/peerbit/sqlite3.mjs +14483 -0
  16. package/dist/peerbit/sqlite3.wasm +0 -0
  17. package/dist/peerbit/sqlite3.worker.min.js +17995 -0
  18. package/dist/src/engine.d.ts +90 -0
  19. package/dist/src/engine.d.ts.map +1 -0
  20. package/dist/src/engine.js +414 -0
  21. package/dist/src/engine.js.map +1 -0
  22. package/dist/src/index.d.ts +5 -0
  23. package/dist/src/index.d.ts.map +1 -0
  24. package/dist/src/index.js +15 -0
  25. package/dist/src/index.js.map +1 -0
  26. package/dist/src/schema.d.ts +73 -0
  27. package/dist/src/schema.d.ts.map +1 -0
  28. package/dist/src/schema.js +1075 -0
  29. package/dist/src/schema.js.map +1 -0
  30. package/dist/src/sqlite3-messages.worker.d.ts +86 -0
  31. package/dist/src/sqlite3-messages.worker.d.ts.map +1 -0
  32. package/dist/src/sqlite3-messages.worker.js +9 -0
  33. package/dist/src/sqlite3-messages.worker.js.map +1 -0
  34. package/dist/src/sqlite3.browser.d.ts +4 -0
  35. package/dist/src/sqlite3.browser.d.ts.map +1 -0
  36. package/dist/src/sqlite3.browser.js +181 -0
  37. package/dist/src/sqlite3.browser.js.map +1 -0
  38. package/dist/src/sqlite3.d.ts +4 -0
  39. package/dist/src/sqlite3.d.ts.map +1 -0
  40. package/dist/src/sqlite3.js +51 -0
  41. package/dist/src/sqlite3.js.map +1 -0
  42. package/dist/src/sqlite3.wasm.d.ts +30 -0
  43. package/dist/src/sqlite3.wasm.d.ts.map +1 -0
  44. package/dist/src/sqlite3.wasm.js +180 -0
  45. package/dist/src/sqlite3.wasm.js.map +1 -0
  46. package/dist/src/sqlite3.worker.d.ts +2 -0
  47. package/dist/src/sqlite3.worker.d.ts.map +1 -0
  48. package/dist/src/sqlite3.worker.js +105 -0
  49. package/dist/src/sqlite3.worker.js.map +1 -0
  50. package/dist/src/types.d.ts +23 -0
  51. package/dist/src/types.d.ts.map +1 -0
  52. package/dist/src/types.js +2 -0
  53. package/dist/src/types.js.map +1 -0
  54. package/package.json +80 -0
  55. package/src/engine.ts +639 -0
  56. package/src/index.ts +16 -0
  57. package/src/schema.ts +1607 -0
  58. package/src/sqlite3-messages.worker.ts +123 -0
  59. package/src/sqlite3.browser.ts +245 -0
  60. package/src/sqlite3.ts +56 -0
  61. package/src/sqlite3.wasm.ts +211 -0
  62. package/src/sqlite3.worker.ts +109 -0
  63. package/src/types.ts +39 -0
package/src/schema.ts ADDED
@@ -0,0 +1,1607 @@
1
+ import {
2
+ type AbstractType,
3
+ type Constructor,
4
+ type Field,
5
+ type FieldType,
6
+ FixedArrayKind,
7
+ OptionKind,
8
+ VecKind,
9
+ WrappedType,
10
+ deserialize,
11
+ field as fieldDecalaration,
12
+ getDependencies,
13
+ getSchema,
14
+ serialize,
15
+ variant,
16
+ } from "@dao-xyz/borsh";
17
+ import { toHexString } from "@peerbit/crypto";
18
+ import * as types from "@peerbit/indexer-interface";
19
+
20
+ const SQLConversionMap: any = {
21
+ u8: "INTEGER",
22
+ u16: "INTEGER",
23
+ u32: "INTEGER",
24
+ u64: "INTEGER",
25
+ i8: "INTEGER",
26
+ i16: "INTEGER",
27
+ i32: "INTEGER",
28
+ i64: "INTEGER",
29
+ f32: "REAL",
30
+ f64: "REAL",
31
+ bool: "INTEGER",
32
+ string: "TEXT",
33
+ Uint8Array: "BLOB",
34
+ Date: "TEXT",
35
+ };
36
+
37
+ const WRAPPED_SIMPLE_VALUE_VARIANT = "wrapped";
38
+
39
+ export type SQLLiteValue =
40
+ | string
41
+ | number
42
+ | null
43
+ | bigint
44
+ | Uint8Array
45
+ | Int8Array
46
+ | ArrayBuffer;
47
+
48
+ export type BindableValue =
49
+ | string
50
+ | bigint
51
+ | number
52
+ | Uint8Array
53
+ | Int8Array
54
+ | ArrayBuffer
55
+ | null;
56
+
57
+ export const convertToSQLType = (
58
+ value: boolean | bigint | string | number | Uint8Array,
59
+ type?: FieldType,
60
+ ): BindableValue => {
61
+ // add bigint when https://github.com/TryGhost/node-sqlite3/pull/1501 fixed
62
+
63
+ if (type === "bool") {
64
+ if (value != null) {
65
+ return value ? 1 : 0;
66
+ }
67
+ return null;
68
+ }
69
+ return value as BindableValue;
70
+ };
71
+
72
+ const nullAsUndefined = (value: any) => (value === null ? undefined : value);
73
+ export const escapeColumnName = (name: string) => `"${name}"`;
74
+
75
+ export const convertFromSQLType = (
76
+ value: boolean | bigint | string | number | Uint8Array,
77
+ type?: FieldType,
78
+ ) => {
79
+ if (type === "bool") {
80
+ if (
81
+ value === 0 ||
82
+ value === 1 ||
83
+ value === 0n ||
84
+ value === 1n ||
85
+ typeof value === "boolean"
86
+ ) {
87
+ return value ? true : false;
88
+ }
89
+ return nullAsUndefined(value);
90
+ }
91
+ if (type === "u8" || type === "u16" || type === "u32") {
92
+ return typeof value === "bigint" || typeof value === "string"
93
+ ? Number(value)
94
+ : nullAsUndefined(value);
95
+ }
96
+ if (type === "u64") {
97
+ return typeof value === "number" || typeof value === "string"
98
+ ? BigInt(value)
99
+ : nullAsUndefined(value);
100
+ }
101
+ return nullAsUndefined(value);
102
+ };
103
+
104
+ export const toSQLType = (type: FieldType, isOptional = false) => {
105
+ let ret: string;
106
+ if (typeof type === "string") {
107
+ const sqlType = SQLConversionMap[type];
108
+ if (!sqlType) {
109
+ throw new Error(`Type ${type} is not supported in SQL`);
110
+ }
111
+ ret = sqlType;
112
+ } else if (isUint8ArrayType(type)) {
113
+ ret = "BLOB";
114
+ } else if (type instanceof OptionKind) {
115
+ throw new Error("Unexpected option");
116
+ } else if (type instanceof VecKind) {
117
+ throw new Error("Unexpected vec");
118
+ } else {
119
+ throw new Error(`Type ${JSON.stringify(type)} is not supported in SQL`);
120
+ }
121
+
122
+ return isOptional ? ret : ret + " NOT NULL";
123
+ };
124
+
125
+ type SQLField = {
126
+ name: string;
127
+ key: string;
128
+ definition: string;
129
+ type: string;
130
+ isPrimary: boolean;
131
+ from: Field | undefined;
132
+ path: string[];
133
+ describesExistenceOfAnother?: string;
134
+ };
135
+ type SQLConstraint = { name: string; definition: string };
136
+
137
+ export interface Table {
138
+ name: string;
139
+ ctor: Constructor<any>;
140
+ primary: string | false;
141
+ primaryIndex: number;
142
+ path: string[];
143
+ parentPath: string[] | undefined; // field path of the parent where this table originates from
144
+ fields: SQLField[];
145
+ constraints: SQLConstraint[];
146
+ children: Table[];
147
+ inline: boolean;
148
+ parent: Table | undefined;
149
+ referencedInArray: boolean;
150
+ isSimpleValue: boolean;
151
+ }
152
+
153
+ export const getSQLTable = (
154
+ ctor: AbstractType<any>,
155
+ path: string[],
156
+ primary: string | false,
157
+ inline: boolean,
158
+ addJoinField:
159
+ | ((fields: SQLField[], constraints: SQLConstraint[]) => void)
160
+ | undefined,
161
+ fromOptionalField: boolean = false,
162
+
163
+ /* name: string */
164
+ ): Table[] => {
165
+ let clazzes = getDependencies(ctor, 0) as any as Constructor<any>[];
166
+ if (!clazzes) {
167
+ clazzes = [ctor as Constructor<any>];
168
+ }
169
+
170
+ let ret: Table[] = [];
171
+ for (const ctor of clazzes) {
172
+ const name = getTableName(path, getNameOfClass(ctor));
173
+ const newPath: string[] = inline ? path : [name];
174
+ const { constraints, fields, dependencies } = getSQLFields(
175
+ name,
176
+ newPath,
177
+ ctor,
178
+ primary,
179
+ addJoinField,
180
+ [],
181
+ fromOptionalField,
182
+ );
183
+
184
+ const table: Table = {
185
+ name,
186
+ constraints,
187
+ fields,
188
+ ctor,
189
+ parentPath: path,
190
+ path: newPath,
191
+ primary,
192
+ primaryIndex: fields.findIndex((x) => x.isPrimary),
193
+ children: dependencies,
194
+ parent: undefined,
195
+ referencedInArray: false,
196
+ isSimpleValue: false,
197
+ inline,
198
+ };
199
+ ret.push(table);
200
+ for (const dep of dependencies) {
201
+ dep.parent = table;
202
+ // ret.push(dep)
203
+ }
204
+ }
205
+
206
+ return ret;
207
+ };
208
+
209
+ const getNameOfVariant = (variant: any) => {
210
+ return (
211
+ "v_" + (typeof variant === "string" ? variant : JSON.stringify(variant))
212
+ );
213
+ };
214
+
215
+ const getNameOfClass = (ctor: AbstractType<any>) => {
216
+ let name: string;
217
+ const schema = getSchema(ctor);
218
+ if (!schema) {
219
+ throw new Error("Schema not found for " + ctor.name);
220
+ }
221
+ if (schema.variant === undefined) {
222
+ /* TODO when to display warning?
223
+ console.warn(
224
+ `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`
225
+ ); */
226
+ name = "class_" + ctor.name;
227
+ } else {
228
+ name = getNameOfVariant(schema.variant);
229
+ }
230
+ return name;
231
+ };
232
+
233
+ export const getTableName = (
234
+ path: string[] = [],
235
+ clazz: string | Constructor<any>,
236
+ ) => {
237
+ let name: string = typeof clazz === "string" ? clazz : getNameOfClass(clazz);
238
+
239
+ // prefix the generated table name so that the name is a valid SQL identifier (table name)
240
+ // choose prefix which is readable and explains that this is a generated table name
241
+
242
+ // leading _ to allow path to have numbers
243
+
244
+ const ret =
245
+ (path.length > 0 ? path.join("__") + "__" : "") +
246
+ name.replace(/[^a-zA-Z0-9_]/g, "_");
247
+ return ret;
248
+ };
249
+
250
+ export const CHILD_TABLE_ID = "__id";
251
+ export const ARRAY_INDEX_COLUMN = "__index";
252
+
253
+ export const PARENT_TABLE_ID = "__parent_id";
254
+ const FOREIGN_VALUE_PROPERTY = "value";
255
+
256
+ /* const clazzHasVariants = (clazz: Constructor<any>) => {
257
+ const schema = getSchema(clazz);
258
+ return schema?.variant !== undefined;
259
+
260
+ }
261
+ */
262
+ const clazzCanBeInlined = (clazz: Constructor<any>) => {
263
+ return (
264
+ /* clazzHasVariants(clazz) === false && */ (getDependencies(clazz, 0)
265
+ ?.length ?? 0) === 0
266
+ );
267
+ };
268
+
269
+ interface SimpleNested {
270
+ [FOREIGN_VALUE_PROPERTY]: any;
271
+ }
272
+
273
+ const getInlineObjectExistColumnName = () => {
274
+ return "_exist";
275
+ };
276
+
277
+ export const getSQLFields = (
278
+ tableName: string,
279
+ path: string[],
280
+ ctor: Constructor<any>,
281
+ primary: string | false,
282
+ addJoinFieldFromParent?: (
283
+ fields: SQLField[],
284
+ constraints: SQLConstraint[],
285
+ ) => void,
286
+ tables: Table[] = [],
287
+ isOptional = false,
288
+ ): {
289
+ fields: SQLField[];
290
+ constraints: SQLConstraint[];
291
+ dependencies: Table[];
292
+ } => {
293
+ const schema = getSchema(ctor);
294
+ const fields = schema.fields;
295
+ const sqlFields: SQLField[] = [];
296
+ const sqlConstraints: SQLConstraint[] = [];
297
+
298
+ let foundPrimary = false;
299
+
300
+ const addJoinFields =
301
+ primary === false
302
+ ? addJoinFieldFromParent
303
+ : (fields: SQLField[], contstraints: SQLConstraint[]) => {
304
+ // we resolve primary field here since it might be unknown until this point
305
+ const primaryField =
306
+ primary != null
307
+ ? sqlFields.find((field) => field.name === primary)
308
+ : undefined;
309
+ const parentPrimaryFieldName = primaryField?.key || CHILD_TABLE_ID;
310
+ const parentPrimaryFieldType = primaryField
311
+ ? primaryField.type
312
+ : "INTEGER";
313
+
314
+ fields.unshift(
315
+ {
316
+ name: CHILD_TABLE_ID,
317
+ key: CHILD_TABLE_ID,
318
+ definition: `${CHILD_TABLE_ID} INTEGER PRIMARY KEY`,
319
+ type: "INTEGER",
320
+ isPrimary: true,
321
+ from: undefined,
322
+ path: [CHILD_TABLE_ID],
323
+ },
324
+
325
+ // foreign key parent document
326
+ {
327
+ name: PARENT_TABLE_ID,
328
+ key: PARENT_TABLE_ID,
329
+ definition: `${PARENT_TABLE_ID} ${parentPrimaryFieldType}`,
330
+ type: parentPrimaryFieldType,
331
+ isPrimary: false,
332
+ from: undefined,
333
+ path: [PARENT_TABLE_ID],
334
+ },
335
+ );
336
+ contstraints.push({
337
+ name: `${PARENT_TABLE_ID}_fk`,
338
+ definition: `CONSTRAINT ${PARENT_TABLE_ID}_fk FOREIGN KEY(${PARENT_TABLE_ID}) REFERENCES ${tableName}(${parentPrimaryFieldName}) ON DELETE CASCADE`,
339
+ });
340
+ };
341
+
342
+ const handleNestedType = (
343
+ key: string,
344
+ field: VecKind | Constructor<any> | AbstractType<any>,
345
+ ) => {
346
+ let chilCtor: Constructor<any>;
347
+
348
+ let elementType: any;
349
+ let isVec = false;
350
+ if (field instanceof VecKind) {
351
+ if (field.elementType instanceof VecKind) {
352
+ throw new Error("vec(vec(...)) is not supported");
353
+ }
354
+ elementType = field.elementType;
355
+ isVec = true;
356
+ } else {
357
+ elementType = field;
358
+ }
359
+
360
+ let isSimpleValue = false;
361
+ if (typeof elementType === "function" && !isUint8ArrayType(elementType)) {
362
+ chilCtor = elementType as Constructor<any>;
363
+ } else {
364
+ @variant(WRAPPED_SIMPLE_VALUE_VARIANT)
365
+ class SimpleNested implements SimpleNested {
366
+ @fieldDecalaration({ type: elementType })
367
+ [FOREIGN_VALUE_PROPERTY]: any;
368
+
369
+ constructor(value: any) {
370
+ this[FOREIGN_VALUE_PROPERTY] = value;
371
+ }
372
+ }
373
+ chilCtor = SimpleNested;
374
+ isSimpleValue = true;
375
+ }
376
+
377
+ const subtables = getSQLTable(
378
+ chilCtor,
379
+ [...path, key],
380
+ CHILD_TABLE_ID,
381
+ false,
382
+ addJoinFields,
383
+ );
384
+
385
+ for (const table of subtables) {
386
+ if (!tables.find((x) => x.name === table.name)) {
387
+ if (isVec) {
388
+ table.referencedInArray = true;
389
+
390
+ table.fields = [
391
+ ...table.fields.slice(0, 2),
392
+ {
393
+ name: ARRAY_INDEX_COLUMN,
394
+ key: ARRAY_INDEX_COLUMN,
395
+ definition: ARRAY_INDEX_COLUMN + " INTEGER",
396
+ type: "INTEGER",
397
+ isPrimary: false,
398
+ from: undefined,
399
+ path: [ARRAY_INDEX_COLUMN],
400
+ },
401
+ ...table.fields.slice(2),
402
+ ];
403
+ }
404
+ table.isSimpleValue = isSimpleValue;
405
+ tables.push(table);
406
+ }
407
+ }
408
+ };
409
+
410
+ const handleSimpleField = (
411
+ key: string,
412
+ field: Field,
413
+ type: FieldType,
414
+ isOptional: boolean,
415
+ ) => {
416
+ let keyString = getInlineTableFieldName(path.slice(1), key);
417
+
418
+ const isPrimary = primary != null && keyString === primary;
419
+ foundPrimary = foundPrimary || isPrimary;
420
+
421
+ const fieldType = toSQLType(type, isOptional);
422
+ sqlFields.push({
423
+ name: keyString,
424
+ key,
425
+ definition: `${escapeColumnName(keyString)} ${fieldType} ${isPrimary ? "PRIMARY KEY" : ""}`,
426
+ type: fieldType,
427
+ isPrimary,
428
+ from: field,
429
+ path: [...path.slice(1), key],
430
+ });
431
+ };
432
+
433
+ const handleField = (
434
+ key: string,
435
+ field: Field,
436
+ type: FieldType,
437
+ isOptional: boolean,
438
+ ) => {
439
+ if (type instanceof FixedArrayKind && type.elementType === "u8") {
440
+ type = Uint8Array;
441
+ }
442
+
443
+ if (typeof type === "string" || type === Uint8Array) {
444
+ handleSimpleField(key, field, type, true);
445
+ } else if (
446
+ typeof type === "function" &&
447
+ clazzCanBeInlined(type as Constructor<any>)
448
+ ) {
449
+ // if field is object but is not polymorphic we can do a simple field inlining
450
+
451
+ const subPath = [...path, key];
452
+ const subtables = getSQLTable(
453
+ type as Constructor<any>,
454
+ subPath,
455
+ false,
456
+ true,
457
+ addJoinFields,
458
+ isOptional,
459
+ );
460
+ for (const table of subtables) {
461
+ if (!tables.find((x) => x.name === table.name)) {
462
+ tables.push(table);
463
+ if (table.inline) {
464
+ for (const field of table.fields) {
465
+ const isPrimary = primary != null && field.name === primary;
466
+ foundPrimary = foundPrimary || isPrimary;
467
+ sqlFields.push(field);
468
+ }
469
+ sqlConstraints.push(...table.constraints);
470
+ }
471
+ }
472
+ }
473
+ } else if (typeof type === "function") {
474
+ handleNestedType(key, type);
475
+ } else {
476
+ throw new Error(`Unsupported type: ${JSON.stringify(type)}}`);
477
+ }
478
+ };
479
+
480
+ for (const field of fields) {
481
+ if (field.type instanceof VecKind) {
482
+ handleNestedType(field.key, field.type);
483
+ } else if (field.type instanceof OptionKind) {
484
+ if (field.type.elementType instanceof VecKind) {
485
+ // TODO but how ?
486
+ throw new Error("option(vec(T)) not supported");
487
+ } else if (field.type.elementType instanceof OptionKind) {
488
+ throw new Error("option(option(T)) not supported");
489
+ }
490
+ handleField(field.key, field, field.type.elementType, true);
491
+ } else {
492
+ handleField(field.key, field, field.type, isOptional);
493
+ }
494
+ }
495
+
496
+ if (primary !== false) {
497
+ // primareKey will be false for nested objects that are inlined
498
+ if (!foundPrimary && primary !== CHILD_TABLE_ID) {
499
+ throw new Error(`Primary key ${primary} not found in schema`);
500
+ }
501
+ addJoinFieldFromParent?.(sqlFields, sqlConstraints);
502
+ } else {
503
+ // inline
504
+ if (isOptional) {
505
+ // add field indicating if the inline object exists,
506
+ let key = getInlineObjectExistColumnName();
507
+ let keyString = getInlineTableFieldName(path.slice(1), key);
508
+
509
+ sqlFields.push({
510
+ name: keyString,
511
+ key,
512
+ definition: `${escapeColumnName(keyString)} INTEGER`,
513
+ type: "bool",
514
+ isPrimary: false,
515
+ from: undefined,
516
+ path: [...path.slice(1), key],
517
+ describesExistenceOfAnother: path[path.length - 1],
518
+ });
519
+ }
520
+ }
521
+
522
+ return {
523
+ fields: sqlFields,
524
+ constraints: sqlConstraints,
525
+ dependencies: tables,
526
+ };
527
+ };
528
+
529
+ export const resolveTable = <
530
+ B extends boolean,
531
+ R = B extends true ? Table : Table | undefined,
532
+ >(
533
+ key: string[],
534
+ tables: Map<string, Table>,
535
+ clazz: string | Constructor<any>,
536
+ throwOnMissing: B,
537
+ ): R => {
538
+ const name = /* key == null ? */ getTableName(
539
+ key,
540
+ clazz,
541
+ ); /* : getSubTableName(scope, key, ctor); */
542
+ const table =
543
+ tables.get(name) ||
544
+ tables.get(
545
+ getTableName(
546
+ key,
547
+ getNameOfVariant(WRAPPED_SIMPLE_VALUE_VARIANT),
548
+ ) /* key.join("__") + "__" + getNameOfVariant(WRAPPED_SIMPLE_VALUE_VARIANT) */,
549
+ );
550
+ if (!table && throwOnMissing) {
551
+ throw new Error(
552
+ `Table not found for ${name}: ${Array.from(tables.keys())}`,
553
+ );
554
+ }
555
+ return table as R;
556
+ };
557
+
558
+ const isNestedType = (type: FieldType): type is AbstractType<any> => {
559
+ const unwrapped = unwrapNestedType(type);
560
+ return typeof unwrapped === "function" && unwrapped !== Uint8Array;
561
+ };
562
+ const unwrapNestedType = (type: FieldType): FieldType => {
563
+ if (type instanceof WrappedType) {
564
+ return type.elementType;
565
+ }
566
+ return type;
567
+ };
568
+
569
+ const getTableFromField = (
570
+ parentTable: Table,
571
+ tables: Map<string, Table>,
572
+ field: Field,
573
+ ) => {
574
+ if (!field) {
575
+ throw new Error("Field is undefined");
576
+ }
577
+ let clazzNames: string[] = [];
578
+ if (!isNestedType(field.type)) {
579
+ clazzNames.push(WRAPPED_SIMPLE_VALUE_VARIANT);
580
+ } else {
581
+ const testCtors: any[] = [
582
+ unwrapNestedType(field.type),
583
+ ...((getDependencies(unwrapNestedType(field.type) as any, 0) ||
584
+ []) as Constructor<any>[]),
585
+ ];
586
+ for (const ctor of testCtors) {
587
+ if (!ctor) {
588
+ continue;
589
+ }
590
+ const schema = getSchema(ctor);
591
+ if (!schema) {
592
+ continue;
593
+ }
594
+ if (ctor) {
595
+ clazzNames.push(getNameOfClass(ctor));
596
+ }
597
+ }
598
+ }
599
+ if (clazzNames.length === 0) {
600
+ throw new Error("Could not find class name");
601
+ }
602
+
603
+ const subTable = clazzNames
604
+ .map((clazzName) =>
605
+ resolveTable([...parentTable.path, field.key], tables, clazzName, false),
606
+ )
607
+ .filter((x) => x != null);
608
+ return subTable;
609
+ };
610
+
611
+ const getTableFromValue = (
612
+ parentTable: Table,
613
+ tables: Map<string, Table>,
614
+ field: Field,
615
+ value?: any,
616
+ ): Table => {
617
+ let clazzName: string | undefined = undefined;
618
+ if (!isNestedType(field.type)) {
619
+ clazzName = WRAPPED_SIMPLE_VALUE_VARIANT;
620
+ } else {
621
+ const testCtors = value?.constructor
622
+ ? [value?.constructor]
623
+ : ([
624
+ unwrapNestedType(field.type),
625
+ ...(getDependencies(unwrapNestedType(field.type) as any, 0) || []),
626
+ ] as Constructor<any>[]);
627
+ for (const ctor of testCtors) {
628
+ if (!ctor) {
629
+ continue;
630
+ }
631
+ const schema = getSchema(ctor);
632
+ if (!schema) {
633
+ continue;
634
+ }
635
+ if (ctor) {
636
+ clazzName = getNameOfClass(ctor);
637
+ break;
638
+ }
639
+ }
640
+ }
641
+ if (!clazzName) {
642
+ throw new Error("Could not find class name");
643
+ }
644
+
645
+ const subTable = resolveTable(
646
+ [...parentTable.path, field.key],
647
+ tables,
648
+ clazzName,
649
+ true,
650
+ );
651
+ return subTable;
652
+ };
653
+
654
+ const isUint8ArrayType = (type: FieldType) => {
655
+ if (type === Uint8Array) {
656
+ return true;
657
+ }
658
+ if (type instanceof FixedArrayKind) {
659
+ return type.elementType === "u8";
660
+ }
661
+ return false;
662
+ };
663
+
664
+ export const insert = async (
665
+ insertFn: (values: any[], table: Table) => Promise<any> | any,
666
+ obj: Record<string, any>,
667
+ tables: Map<string, Table>,
668
+ table: Table,
669
+ fields: Field[],
670
+ handleNestedCallback?: (cb: (parentId: any) => Promise<void>) => void,
671
+ parentId: any = undefined,
672
+ index?: number,
673
+ ): Promise<void> => {
674
+ const bindableValues: any[] = [];
675
+ let nestedCallbacks: ((id: any) => Promise<void>)[] = [];
676
+
677
+ handleNestedCallback =
678
+ table.primary === false
679
+ ? handleNestedCallback
680
+ : (fn) => nestedCallbacks.push(fn);
681
+
682
+ const handleElement = async (
683
+ item: any,
684
+ field: Field,
685
+ parentId: any,
686
+ index?: number,
687
+ ) => {
688
+ const subTable = getTableFromValue(table, tables, field, item);
689
+
690
+ await insert(
691
+ insertFn,
692
+ typeof item === "function" && item instanceof Uint8Array === false
693
+ ? item
694
+ : subTable.isSimpleValue
695
+ ? // eslint-disable-next-line new-cap
696
+ new subTable.ctor(item)
697
+ : Object.assign(Object.create(subTable.ctor.prototype), item),
698
+ tables,
699
+ subTable,
700
+ getSchema(subTable.ctor).fields,
701
+ handleNestedCallback,
702
+ parentId,
703
+ index,
704
+ );
705
+ };
706
+
707
+ const handleNested = async (
708
+ field: Field,
709
+ optional: boolean,
710
+ parentId: any,
711
+ ) => {
712
+ if (Array.isArray(obj[field.key])) {
713
+ const arr = obj[field.key];
714
+ for (let i = 0; i < arr.length; i++) {
715
+ const item = arr[i];
716
+ await handleElement(item, field, parentId, i);
717
+ }
718
+ } else {
719
+ if (field instanceof VecKind) {
720
+ if (obj[field.key] == null) {
721
+ if (!optional) {
722
+ throw new Error("Expected array, received null");
723
+ } else {
724
+ return;
725
+ }
726
+ }
727
+ throw new Error("Expected array");
728
+ }
729
+
730
+ const value = obj[field.key];
731
+ if (value == null) {
732
+ if (!optional) {
733
+ throw new Error("Expected object, received null");
734
+ }
735
+ return;
736
+ }
737
+ await handleElement(value, field, parentId);
738
+ }
739
+ };
740
+
741
+ let nestedFields: Field[] = [];
742
+ if (parentId != null) {
743
+ bindableValues.push(undefined);
744
+ bindableValues.push(parentId);
745
+ if (index != null) {
746
+ bindableValues.push(index);
747
+ }
748
+ }
749
+
750
+ for (const field of fields) {
751
+ const unwrappedType = unwrapNestedType(field.type);
752
+ if (field.type instanceof VecKind === false) {
753
+ if (
754
+ typeof unwrappedType === "string" ||
755
+ isUint8ArrayType(unwrappedType)
756
+ ) {
757
+ bindableValues.push(convertToSQLType(obj[field.key], unwrappedType));
758
+ } else if (
759
+ typeof unwrappedType === "function" &&
760
+ clazzCanBeInlined(unwrappedType as Constructor<any>)
761
+ ) {
762
+ const value = obj[field.key];
763
+ const subTable = getTableFromValue(table, tables, field, value);
764
+ if (subTable.inline && value == null) {
765
+ for (const _field of subTable.fields) {
766
+ bindableValues.push(null);
767
+ }
768
+ bindableValues[bindableValues.length - 1] = false; // assign the value "false" to the exist field column
769
+ continue;
770
+ }
771
+
772
+ await insert(
773
+ (values, table) => {
774
+ if (table.inline) {
775
+ bindableValues.push(...values); // insert the bindable values into the parent bindable array
776
+ if (field.type instanceof OptionKind) {
777
+ bindableValues.push(true); // assign the value "true" to the exist field column
778
+ }
779
+ return undefined;
780
+ } else {
781
+ return insertFn(values, table);
782
+ }
783
+ },
784
+ value,
785
+ tables,
786
+ subTable,
787
+ getSchema(unwrappedType).fields,
788
+ (fn) => nestedCallbacks.push(fn),
789
+ parentId,
790
+ index,
791
+ );
792
+ /* await insert(, obj[field.key], tables, subTable, getSchema(unwrappedType).fields, parentId, index); */
793
+ } else {
794
+ nestedFields.push(field);
795
+ }
796
+ } else {
797
+ nestedFields.push(field);
798
+ }
799
+ }
800
+
801
+ // we handle nested after self insertion so we have a id defined for 'this'
802
+ // this is important because if we insert a related document in a foreign table
803
+ // we need to know the id of the parent document to insert the foreign key correctly
804
+ for (const nested of nestedFields) {
805
+ const isOptional = nested.type instanceof OptionKind;
806
+ handleNestedCallback!((id) => handleNested(nested, isOptional, id));
807
+ }
808
+
809
+ const thisId = await insertFn(bindableValues, table);
810
+ if (table.primary === false && nestedCallbacks.length > 0) {
811
+ throw new Error("Unexpected");
812
+ }
813
+ await Promise.all(nestedCallbacks.map((x) => x(thisId)));
814
+
815
+ /* return [result, ...ret]; */
816
+ };
817
+
818
+ export const getTablePrefixedField = (
819
+ table: Table,
820
+ key: string,
821
+ skipPrefix: boolean = false,
822
+ ) =>
823
+ `${skipPrefix ? "" : table.name + "#"}${getInlineTableFieldName(table.path.slice(1), key)}`;
824
+ export const getTableNameFromPrefixedField = (prefixedField: string) =>
825
+ prefixedField.split("#")[0];
826
+ export const getInlineTableFieldName = (
827
+ path: string[] | undefined,
828
+ key: string,
829
+ ) => (path && path.length > 0 ? `${path.join("_")}__${key}` : key);
830
+
831
+ const matchFieldInShape = (
832
+ shape: types.Shape | undefined,
833
+ path: string[] | undefined,
834
+ field: SQLField,
835
+ ) => {
836
+ if (!shape) {
837
+ return true;
838
+ }
839
+ let currentShape = shape;
840
+
841
+ if (field.path) {
842
+ for (let i = 0; i < field.path.length; i++) {
843
+ if (!currentShape) {
844
+ return false;
845
+ }
846
+ let nextShape = currentShape[field.path[i]];
847
+ if (nextShape === undefined) {
848
+ return false;
849
+ }
850
+ if (nextShape === true) {
851
+ return true;
852
+ }
853
+ currentShape = nextShape;
854
+ }
855
+ }
856
+
857
+ throw new Error("Unexpected");
858
+ };
859
+
860
+ export const selectChildren = (childrenTable: Table) =>
861
+ "select * from " + childrenTable.name + " where " + PARENT_TABLE_ID + " = ?";
862
+
863
+ export const selectAllFields = (
864
+ table: Table,
865
+ shape: types.Shape | undefined,
866
+ ) => {
867
+ let stack: { table: Table; shape?: types.Shape }[] = [{ table, shape }];
868
+ let join: Map<string, JoinTable> = new Map();
869
+ const fieldResolvers: string[] = [];
870
+ for (const tableAndShape of stack) {
871
+ if (!tableAndShape.table.inline) {
872
+ for (const field of tableAndShape.table.fields) {
873
+ if (
874
+ field.isPrimary ||
875
+ !tableAndShape.shape ||
876
+ matchFieldInShape(tableAndShape.shape, [], field)
877
+ ) {
878
+ const value = `${tableAndShape.table.name}.${escapeColumnName(field.name)} as '${getTablePrefixedField(tableAndShape.table, field.name)}'`;
879
+ fieldResolvers.push(value);
880
+ }
881
+ }
882
+ }
883
+
884
+ for (const child of tableAndShape.table.children) {
885
+ if (child.referencedInArray) {
886
+ continue;
887
+ }
888
+
889
+ let childShape: types.Shape | undefined = undefined;
890
+ if (tableAndShape.shape) {
891
+ const parentPath = child.parentPath?.slice(1);
892
+ let maybeShape = parentPath
893
+ ? tableAndShape.shape?.[parentPath[parentPath.length - 1]]
894
+ : undefined;
895
+
896
+ if (!maybeShape) {
897
+ continue;
898
+ }
899
+
900
+ childShape = maybeShape === true ? undefined : maybeShape;
901
+ }
902
+
903
+ stack.push({ table: child, shape: childShape });
904
+ if (!child.inline) {
905
+ join.set(child.name, { as: child.name, table: child });
906
+ }
907
+ }
908
+ }
909
+
910
+ if (fieldResolvers.length === 0) {
911
+ throw new Error("No fields to resolve");
912
+ }
913
+
914
+ return {
915
+ query: `SELECT ${fieldResolvers.join(", ")} FROM ${table.name}`,
916
+ join,
917
+ };
918
+ };
919
+
920
+ const getNonInlinedTable = (from: Table) => {
921
+ let current: Table = from;
922
+ while (current.inline) {
923
+ if (!current.parent) {
924
+ throw new Error("No parent found");
925
+ }
926
+ current = current.parent;
927
+ }
928
+ return current;
929
+ };
930
+
931
+ // the inverse of resolveFieldValues
932
+ export const resolveInstanceFromValue = async <T>(
933
+ fromTablePrefixedValues: Record<string, any>,
934
+ tables: Map<string, Table>,
935
+ table: Table,
936
+ resolveChildren: (parentId: any, table: Table) => Promise<any[]>,
937
+ tablePrefixed: boolean,
938
+ shape: types.Shape | undefined,
939
+ ): Promise<T> => {
940
+ const fields = getSchema(table.ctor).fields;
941
+ const obj: any = {};
942
+
943
+ const handleNested = async (
944
+ field: Field,
945
+ isOptional: boolean,
946
+ isArray: boolean,
947
+ ) => {
948
+ const subTables = getTableFromField(table, tables, field); // TODO fix
949
+
950
+ let maybeShape = shape?.[field.key];
951
+ let subshape = maybeShape === true ? undefined : maybeShape;
952
+
953
+ if (isArray) {
954
+ let once = false;
955
+ let resolvedArr = [];
956
+
957
+ for (const subtable of subTables) {
958
+ // TODO types
959
+ let rootTable = getNonInlinedTable(table);
960
+ const arr = await resolveChildren(
961
+ fromTablePrefixedValues[
962
+ getTablePrefixedField(
963
+ rootTable,
964
+ rootTable.primary as string,
965
+ !tablePrefixed,
966
+ )
967
+ ],
968
+ subtable,
969
+ );
970
+ if (arr) {
971
+ once = true;
972
+ for (const element of arr) {
973
+ const resolved: SimpleNested | any = await resolveInstanceFromValue(
974
+ element,
975
+ tables,
976
+ subtable, // TODO fix
977
+ resolveChildren,
978
+ false,
979
+ subshape,
980
+ );
981
+
982
+ resolvedArr[element[ARRAY_INDEX_COLUMN]] = subtable.isSimpleValue
983
+ ? resolved.value
984
+ : resolved;
985
+ }
986
+ }
987
+ }
988
+
989
+ if (!once) {
990
+ obj[field.key] = undefined;
991
+ } else {
992
+ obj[field.key] = resolvedArr;
993
+ }
994
+ } else {
995
+ // resolve nested object from row directly
996
+ /* let extracted: any = {} */
997
+ let subTable: Table | undefined = undefined;
998
+ if (subTables.length > 1) {
999
+ for (const table of subTables) {
1000
+ // TODO types
1001
+ if (
1002
+ fromTablePrefixedValues[
1003
+ getTablePrefixedField(
1004
+ table,
1005
+ table.primary as string,
1006
+ !tablePrefixed,
1007
+ )
1008
+ ] != null
1009
+ ) {
1010
+ subTable = table;
1011
+ break;
1012
+ }
1013
+ }
1014
+ } else {
1015
+ subTable = subTables[0];
1016
+ }
1017
+
1018
+ if (!subTable) {
1019
+ throw new Error("Sub table not found");
1020
+ }
1021
+ /*
1022
+ for (const field of subTable.fields) {
1023
+ once = true
1024
+ extracted[field.name] = fromTablePrefixedValues[getTablePrefixedField(subTable, field.name, !tablePrefixed)]
1025
+ }
1026
+ */
1027
+
1028
+ if (subTable.inline && isOptional) {
1029
+ let rootTable = getNonInlinedTable(table);
1030
+
1031
+ const isNull =
1032
+ !fromTablePrefixedValues[
1033
+ getTablePrefixedField(
1034
+ rootTable,
1035
+ subTable.fields[subTable.fields.length - 1].name,
1036
+ )
1037
+ ];
1038
+
1039
+ if (isNull) {
1040
+ obj[field.key] = undefined;
1041
+ return;
1042
+ }
1043
+ }
1044
+
1045
+ // TODO types
1046
+ if (
1047
+ subTable.primary !== false &&
1048
+ fromTablePrefixedValues[
1049
+ getTablePrefixedField(subTable, subTable.primary, !tablePrefixed)
1050
+ ] == null
1051
+ ) {
1052
+ obj[field.key] = undefined;
1053
+ } else {
1054
+ const resolved = await resolveInstanceFromValue(
1055
+ fromTablePrefixedValues,
1056
+ tables,
1057
+ subTable,
1058
+ resolveChildren,
1059
+ tablePrefixed,
1060
+ subshape,
1061
+ );
1062
+
1063
+ obj[field.key] = resolved;
1064
+ }
1065
+ }
1066
+ };
1067
+
1068
+ for (const field of fields) {
1069
+ if (shape && !shape[field.key]) {
1070
+ continue;
1071
+ }
1072
+
1073
+ const rootTable = getNonInlinedTable(table);
1074
+ const referencedField = rootTable.fields.find(
1075
+ (sqlField) => sqlField.from === field,
1076
+ );
1077
+ const fieldValue = referencedField
1078
+ ? fromTablePrefixedValues[
1079
+ getTablePrefixedField(
1080
+ rootTable,
1081
+ referencedField!.name,
1082
+ !tablePrefixed,
1083
+ )
1084
+ ]
1085
+ : undefined;
1086
+ if (typeof field.type === "string" || isUint8ArrayType(field.type)) {
1087
+ obj[field.key] = convertFromSQLType(fieldValue, field.type);
1088
+ } else if (field.type instanceof OptionKind) {
1089
+ if (
1090
+ typeof field.type.elementType === "string" ||
1091
+ isUint8ArrayType(field.type.elementType)
1092
+ ) {
1093
+ obj[field.key] = convertFromSQLType(fieldValue, field.type.elementType);
1094
+ } else if (field.type.elementType instanceof VecKind) {
1095
+ await handleNested(field, true, true);
1096
+ } else {
1097
+ await handleNested(field, true, false);
1098
+ }
1099
+ } else if (field.type instanceof VecKind) {
1100
+ await handleNested(field, false, true);
1101
+ } else {
1102
+ await handleNested(field, false, false);
1103
+ }
1104
+ }
1105
+
1106
+ return Object.assign(Object.create(table.ctor.prototype), obj);
1107
+ };
1108
+
1109
+ export const fromRowToObj = (row: any, ctor: Constructor<any>) => {
1110
+ const schema = getSchema(ctor);
1111
+ const fields = schema.fields;
1112
+ const obj: any = {};
1113
+ for (const field of fields) {
1114
+ obj[field.key] = row[field.key];
1115
+ }
1116
+ return Object.assign(Object.create(ctor.prototype), obj);
1117
+ };
1118
+
1119
+ export const convertDeleteRequestToQuery = (
1120
+ request: types.DeleteRequest,
1121
+ tables: Map<string, Table>,
1122
+ table: Table,
1123
+ ) => {
1124
+ return `DELETE FROM ${table.name} WHERE ${table.primary} IN (SELECT ${table.primary} from ${table.name} ${convertRequestToQuery(request, tables, table).query}) returning ${table.primary}`;
1125
+ };
1126
+
1127
+ export const convertSumRequestToQuery = (
1128
+ request: types.SumRequest,
1129
+ tables: Map<string, Table>,
1130
+ table: Table,
1131
+ ) => {
1132
+ return `SELECT SUM(${table.name}.${request.key.join(".")}) as sum FROM ${table.name} ${convertRequestToQuery(request, tables, table).query}`;
1133
+ };
1134
+
1135
+ export const convertCountRequestToQuery = (
1136
+ request: types.CountRequest,
1137
+ tables: Map<string, Table>,
1138
+ table: Table,
1139
+ ) => {
1140
+ return `SELECT count(*) as count FROM ${table.name} ${convertRequestToQuery(request, tables, table).query}`;
1141
+ };
1142
+
1143
+ export const convertSearchRequestToQuery = (
1144
+ request: types.SearchRequest,
1145
+ tables: Map<string, Table>,
1146
+ rootTables: Table[],
1147
+ shape: types.Shape | undefined,
1148
+ ) => {
1149
+ let unionBuilder = "";
1150
+ let orderByClause: string | undefined = undefined;
1151
+ for (const table of rootTables) {
1152
+ const { query: selectQuery, join: joinFromSelect } = selectAllFields(
1153
+ table,
1154
+ shape,
1155
+ );
1156
+ const { orderBy, query } = convertRequestToQuery(
1157
+ request,
1158
+ tables,
1159
+ table,
1160
+ joinFromSelect,
1161
+ );
1162
+ unionBuilder += `${unionBuilder.length > 0 ? " UNION ALL " : ""} ${selectQuery} ${query}`;
1163
+ orderByClause = orderBy?.length > 0 ? orderBy : orderByClause;
1164
+ }
1165
+
1166
+ return `${unionBuilder} ${orderByClause ? orderByClause : ""} limit ? offset ?`;
1167
+ };
1168
+
1169
+ type SearchQueryParts = { query: string; orderBy: string };
1170
+ type CountQueryParts = { query: string; join: string };
1171
+
1172
+ const convertRequestToQuery = <
1173
+ T extends types.SearchRequest | types.CountRequest | types.SumRequest,
1174
+ R = T extends types.SearchRequest ? SearchQueryParts : CountQueryParts,
1175
+ >(
1176
+ request: T,
1177
+ tables: Map<string, Table>,
1178
+ table: Table,
1179
+ extraJoin?: Map<string, JoinTable>,
1180
+ path: string[] = [],
1181
+ ): R => {
1182
+ let whereBuilder = "";
1183
+ let orderByBuilder: string | undefined = undefined;
1184
+ /* let tablesToSelect: string[] = [table.name]; */
1185
+ let joinBuilder: Map<string, JoinTable> = extraJoin || new Map();
1186
+
1187
+ if (request.query.length === 1) {
1188
+ const { where } = convertQueryToSQLQuery(
1189
+ request.query[0],
1190
+ tables,
1191
+ table,
1192
+ joinBuilder,
1193
+ path,
1194
+ );
1195
+ whereBuilder += where;
1196
+ } else if (request.query.length > 1) {
1197
+ const { where } = convertQueryToSQLQuery(
1198
+ new types.And(request.query),
1199
+ tables,
1200
+ table,
1201
+ joinBuilder,
1202
+ path,
1203
+ );
1204
+ whereBuilder += where;
1205
+ }
1206
+
1207
+ if (request instanceof types.SearchRequest) {
1208
+ if (request.sort.length > 0) {
1209
+ if (request.sort.length > 0) {
1210
+ orderByBuilder = "ORDER BY ";
1211
+ }
1212
+ let once = false;
1213
+ for (const sort of request.sort) {
1214
+ const { foreignTables, queryKey } = resolveTableToQuery(
1215
+ table,
1216
+ tables,
1217
+ joinBuilder,
1218
+ [...path, ...sort.key],
1219
+ undefined,
1220
+ true,
1221
+ );
1222
+ for (const table of foreignTables) {
1223
+ if (once) {
1224
+ orderByBuilder += ", ";
1225
+ }
1226
+ once = true;
1227
+ orderByBuilder += `${table.as}.${queryKey} ${sort.direction === types.SortDirection.ASC ? "ASC" : "DESC"}`;
1228
+ }
1229
+ }
1230
+
1231
+ /* orderByBuilder += request.sort
1232
+ .map(
1233
+ (sort) =>
1234
+ `${table.name}.${sort.key} ${sort.direction === types.SortDirection.ASC ? "ASC" : "DESC"}`
1235
+ )
1236
+ .join(", "); */
1237
+ }
1238
+ }
1239
+ const where = whereBuilder.length > 0 ? "where " + whereBuilder : undefined;
1240
+
1241
+ if (extraJoin && extraJoin.size > 0) {
1242
+ insertMapIntoMap(joinBuilder, extraJoin);
1243
+ }
1244
+ let join = buildJoin(
1245
+ joinBuilder,
1246
+ request instanceof types.SearchRequest ? true : false,
1247
+ );
1248
+
1249
+ const query = `${join ? join : ""} ${where ? where : ""}`;
1250
+
1251
+ return {
1252
+ query,
1253
+ orderBy: orderByBuilder,
1254
+ } as R;
1255
+ };
1256
+
1257
+ export const buildJoin = (
1258
+ joinBuilder: Map<string, JoinTable>,
1259
+ resolveAllColumns: boolean,
1260
+ ) => {
1261
+ let joinTypeDefault = resolveAllColumns
1262
+ ? /* "FULL OUTER JOIN" */ "LEFT OUTER JOIN"
1263
+ : "JOIN";
1264
+ let join = "";
1265
+ for (const [_key, table] of joinBuilder) {
1266
+ let nonInlinedParent =
1267
+ table.table.parent && getNonInlinedTable(table.table.parent);
1268
+ if (!nonInlinedParent) {
1269
+ throw new Error("Unexpected: missing parent");
1270
+ }
1271
+
1272
+ let joinType = table.table.referencedInArray
1273
+ ? /* "FULL OUTER JOIN" */ "LEFT OUTER JOIN"
1274
+ : joinTypeDefault;
1275
+ join += `${joinType} ${table.table.name} AS ${table.as} ON ${nonInlinedParent.name}.${nonInlinedParent.primary} = ${table.as}.${PARENT_TABLE_ID} `;
1276
+ }
1277
+ return join;
1278
+ };
1279
+
1280
+ const insertMapIntoMap = (map: Map<string, any>, insert: Map<string, any>) => {
1281
+ for (const [key, value] of insert) {
1282
+ map.set(key, value);
1283
+ }
1284
+ };
1285
+
1286
+ export const convertQueryToSQLQuery = (
1287
+ query: types.Query,
1288
+ tables: Map<string, Table>,
1289
+ table: Table,
1290
+ joinBuilder: Map<string, JoinTable>,
1291
+ path: string[] = [],
1292
+ tableAlias: string | undefined = undefined,
1293
+ ): { where: string } => {
1294
+ let whereBuilder = "";
1295
+ /* let tablesToSelect: string[] = []; */
1296
+
1297
+ const handleAnd = (
1298
+ queries: types.Query[],
1299
+ path: string[],
1300
+ tableAlias?: string,
1301
+ ) => {
1302
+ for (const query of queries) {
1303
+ const { where } = convertQueryToSQLQuery(
1304
+ query,
1305
+ tables,
1306
+ table,
1307
+ joinBuilder,
1308
+ path,
1309
+ tableAlias,
1310
+ );
1311
+ whereBuilder =
1312
+ whereBuilder.length > 0 ? `(${whereBuilder}) AND (${where})` : where;
1313
+ }
1314
+ };
1315
+
1316
+ if (query instanceof types.StateFieldQuery) {
1317
+ const { where } = convertStateFieldQuery(
1318
+ query,
1319
+ tables,
1320
+ table,
1321
+ joinBuilder,
1322
+ path,
1323
+ tableAlias,
1324
+ );
1325
+ whereBuilder += where;
1326
+ } else if (query instanceof types.Nested) {
1327
+ let joinPrefix = "__" + String(tables.size);
1328
+ path = [...path, query.path];
1329
+ handleAnd(query.query, path, joinPrefix);
1330
+ } else if (query instanceof types.LogicalQuery) {
1331
+ if (query instanceof types.And) {
1332
+ handleAnd(query.and, path, tableAlias);
1333
+ } else if (query instanceof types.Or) {
1334
+ for (const subquery of query.or) {
1335
+ const { where } = convertQueryToSQLQuery(
1336
+ subquery,
1337
+ tables,
1338
+ table,
1339
+ joinBuilder,
1340
+ path,
1341
+ tableAlias,
1342
+ );
1343
+ whereBuilder =
1344
+ whereBuilder.length > 0 ? `(${whereBuilder}) OR (${where})` : where;
1345
+ }
1346
+ } else if (query instanceof types.Not) {
1347
+ const { where } = convertQueryToSQLQuery(
1348
+ query.not,
1349
+ tables,
1350
+ table,
1351
+ joinBuilder,
1352
+ path,
1353
+ tableAlias,
1354
+ );
1355
+ whereBuilder = `NOT (${where})`;
1356
+ } else {
1357
+ throw new Error("Unsupported query type: " + query.constructor.name);
1358
+ }
1359
+ } else {
1360
+ throw new Error("Unsupported query type: " + query.constructor.name);
1361
+ }
1362
+
1363
+ return {
1364
+ where: whereBuilder,
1365
+ };
1366
+ };
1367
+
1368
+ const cloneQuery = (query: types.StateFieldQuery) => {
1369
+ return deserialize(serialize(query), types.StateFieldQuery);
1370
+ };
1371
+
1372
+ type JoinTable = {
1373
+ table: Table;
1374
+ as: string;
1375
+ };
1376
+
1377
+ const createTableReferenceName = (
1378
+ table: Table,
1379
+ alias: string | undefined,
1380
+ fieldType: FieldType,
1381
+ joinSize: number,
1382
+ ) => {
1383
+ if (
1384
+ !alias &&
1385
+ (fieldType instanceof VecKind ||
1386
+ (fieldType instanceof OptionKind &&
1387
+ fieldType.elementType instanceof VecKind))
1388
+ ) {
1389
+ let aliasSuffix = "_" + String(joinSize);
1390
+ alias = aliasSuffix;
1391
+ }
1392
+ const tableNameAs = alias ? alias + "_" + table.name : table.name;
1393
+ return tableNameAs;
1394
+ };
1395
+
1396
+ const resolveTableToQuery = (
1397
+ table: Table,
1398
+ tables: Map<string, Table>,
1399
+ join: Map<string, JoinTable>,
1400
+ path: string[],
1401
+ alias: string | undefined,
1402
+ searchSelf: boolean,
1403
+ ) => {
1404
+ // we are matching in two ways.
1405
+
1406
+ // 1. joins
1407
+ // we go down the path and resolve related tables until the last index
1408
+ // the last path value is the query key
1409
+
1410
+ // 2. inline table fields
1411
+ // multiple keys in the path can correspond to a field in a inline table
1412
+ // this means we need to also check if the key is a field in the current table
1413
+
1414
+ if (searchSelf) {
1415
+ const inlineName = getInlineTableFieldName(
1416
+ path.slice(0, -1),
1417
+ path[path.length - 1],
1418
+ );
1419
+ let field = table.fields.find((x) => x.name === inlineName);
1420
+ if (field) {
1421
+ return {
1422
+ queryKey: field.name,
1423
+ foreignTables: [{ table, as: table.name }],
1424
+ };
1425
+ }
1426
+ }
1427
+
1428
+ let currentTables: JoinTable[] = [{ table, as: alias || table.name }];
1429
+ let prevTables: JoinTable[] | undefined = undefined;
1430
+
1431
+ // outer:
1432
+ for (const [_i, key] of path /* .slice(0, -1) */
1433
+ .entries()) {
1434
+ let newTables: JoinTable[] = [];
1435
+ for (const currentTable of currentTables.map((x) => x.table)) {
1436
+ const schema = getSchema(currentTable.ctor);
1437
+ const field = schema.fields.find((x) => x.key === key)!;
1438
+ if (!field && currentTable.children.length > 0) {
1439
+ // second arg is needed because of polymorphic fields we might end up here intentially to check what tables to query
1440
+ throw new Error(
1441
+ `Property with key "${key}" is not found in the schema ${JSON.stringify(schema.fields.map((x) => x.key))}`,
1442
+ );
1443
+ }
1444
+ for (const child of currentTable.children) {
1445
+ const tableNameAs = createTableReferenceName(
1446
+ child,
1447
+ alias,
1448
+ field.type,
1449
+ join.size,
1450
+ );
1451
+ let isMatching =
1452
+ child.parentPath![child.parentPath!.length - 1] === key;
1453
+ if (isMatching) {
1454
+ const tableWithAlias = { table: child, as: tableNameAs };
1455
+ if (child.isSimpleValue) {
1456
+ if (!child.inline) {
1457
+ join.set(tableNameAs, tableWithAlias);
1458
+ }
1459
+ return {
1460
+ queryKey: FOREIGN_VALUE_PROPERTY,
1461
+ foreignTables: [tableWithAlias],
1462
+ };
1463
+ }
1464
+
1465
+ newTables.push(tableWithAlias);
1466
+ if (!child.inline) {
1467
+ join.set(tableNameAs, tableWithAlias);
1468
+ }
1469
+ }
1470
+ }
1471
+ }
1472
+ prevTables = currentTables;
1473
+ currentTables = newTables;
1474
+
1475
+ /* if (currentTables.length > 0 && i === path.length - 2) {
1476
+ // we are at the last key in the path
1477
+ // the next key should be the query key
1478
+ break;
1479
+ } */
1480
+
1481
+ if (currentTables.length === 0) {
1482
+ currentTables = prevTables;
1483
+ break;
1484
+ }
1485
+ }
1486
+
1487
+ if (currentTables.length === 0) {
1488
+ throw new Error("Unexpected");
1489
+ }
1490
+
1491
+ let foreignTables: JoinTable[] = currentTables.filter((x) =>
1492
+ x.table.fields.find((x) => x.key === path[path.length - 1]),
1493
+ );
1494
+ let tableToQuery: Table | undefined =
1495
+ foreignTables[foreignTables.length - 1].table;
1496
+ let queryKeyPath = [path[path.length - 1]];
1497
+ while (tableToQuery?.inline) {
1498
+ queryKeyPath.unshift(
1499
+ tableToQuery!.parentPath![tableToQuery!.parentPath!.length - 1],
1500
+ );
1501
+ tableToQuery = tableToQuery.parent;
1502
+ }
1503
+
1504
+ let queryKey =
1505
+ queryKeyPath.length > 0
1506
+ ? getInlineTableFieldName(
1507
+ queryKeyPath.slice(0, -1),
1508
+ queryKeyPath[queryKeyPath.length - 1],
1509
+ )
1510
+ : FOREIGN_VALUE_PROPERTY;
1511
+ return { queryKey, foreignTables };
1512
+ };
1513
+
1514
+ const convertStateFieldQuery = (
1515
+ query: types.StateFieldQuery,
1516
+ tables: Map<string, Table>,
1517
+ table: Table,
1518
+ join: Map<string, JoinTable>,
1519
+ path: string[],
1520
+ tableAlias: string | undefined = undefined,
1521
+ ): { where: string } => {
1522
+ // if field id represented as foreign table, do join and compare
1523
+ const inlinedName = getInlineTableFieldName(
1524
+ query.key.slice(0, query.key.length - 1),
1525
+ query.key[query.key.length - 1],
1526
+ );
1527
+ const tableField = table.fields.find(
1528
+ (x) => x.name === inlinedName,
1529
+ ); /* stringArraysEquals(query.key, [...table.parentPath, x.name]) )*/
1530
+ const isForeign = !tableField; // table.fields.find(x => x.name === query.key[query.key.length - 1])
1531
+ if (isForeign) {
1532
+ const { queryKey, foreignTables } = resolveTableToQuery(
1533
+ table,
1534
+ tables,
1535
+ join,
1536
+ [...path, ...query.key],
1537
+ tableAlias,
1538
+ false,
1539
+ );
1540
+ query = cloneQuery(query);
1541
+ query.key = [queryKey];
1542
+ let whereBuilder: string[] = [];
1543
+ for (const ftable of foreignTables) {
1544
+ if (ftable.table === table) {
1545
+ throw new Error("Unexpected");
1546
+ }
1547
+ const { where } = convertQueryToSQLQuery(
1548
+ query,
1549
+ tables,
1550
+ ftable.table,
1551
+ join,
1552
+ path,
1553
+ ftable.as,
1554
+ );
1555
+ whereBuilder.push(where);
1556
+ }
1557
+ return { where: whereBuilder.join(" OR ") };
1558
+ }
1559
+
1560
+ const keyWithTable =
1561
+ (tableAlias || table.name) + "." + escapeColumnName(inlinedName);
1562
+ let where: string;
1563
+ if (query instanceof types.StringMatch) {
1564
+ let statement = "";
1565
+
1566
+ if (query.method === types.StringMatchMethod.contains) {
1567
+ statement = `${keyWithTable} LIKE '%${query.value}%'`;
1568
+ } else if (query.method === types.StringMatchMethod.prefix) {
1569
+ statement = `${keyWithTable} LIKE '${query.value}%'`;
1570
+ } else if (query.method === types.StringMatchMethod.exact) {
1571
+ statement = `${keyWithTable} = '${query.value}'`;
1572
+ }
1573
+ if (query.caseInsensitive) {
1574
+ statement += " COLLATE NOCASE";
1575
+ }
1576
+ where = statement;
1577
+ } else if (query instanceof types.ByteMatchQuery) {
1578
+ // compare Blob compule with f.value
1579
+
1580
+ const statement = `${keyWithTable} = x'${toHexString(query.value)}'`;
1581
+ where = statement;
1582
+ } else if (query instanceof types.IntegerCompare) {
1583
+ if (tableField!.type === "BLOB") {
1584
+ // TODO perf
1585
+ where = `hex(${keyWithTable}) LIKE '%${toHexString(new Uint8Array([Number(query.value.value)]))}%'`;
1586
+ } else if (query.compare === types.Compare.Equal) {
1587
+ where = `${keyWithTable} = ${query.value.value}`;
1588
+ } else if (query.compare === types.Compare.Greater) {
1589
+ where = `${keyWithTable} > ${query.value.value}`;
1590
+ } else if (query.compare === types.Compare.Less) {
1591
+ where = `${keyWithTable} < ${query.value.value}`;
1592
+ } else if (query.compare === types.Compare.GreaterOrEqual) {
1593
+ where = `${keyWithTable} >= ${query.value.value}`;
1594
+ } else if (query.compare === types.Compare.LessOrEqual) {
1595
+ where = `${keyWithTable} <= ${query.value.value}`;
1596
+ } else {
1597
+ throw new Error(`Unsupported compare type: ${query.compare}`);
1598
+ }
1599
+ } else if (query instanceof types.IsNull) {
1600
+ where = `${keyWithTable} IS NULL`;
1601
+ } else if (query instanceof types.BoolQuery) {
1602
+ where = `${keyWithTable} = ${query.value}`;
1603
+ } else {
1604
+ throw new Error("Unsupported query type: " + query.constructor.name);
1605
+ }
1606
+ return { where };
1607
+ };