@peerbit/indexer-sqlite3 1.1.3 → 1.1.4-5cf61cb

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 (46) hide show
  1. package/dist/peerbit/sqlite3-bundler-friendly.mjs +7 -7
  2. package/dist/peerbit/sqlite3-node.mjs +7 -7
  3. package/dist/peerbit/sqlite3.js +7 -7
  4. package/dist/peerbit/sqlite3.min.js +651 -163
  5. package/dist/peerbit/sqlite3.mjs +7 -7
  6. package/dist/peerbit/sqlite3.wasm +0 -0
  7. package/dist/peerbit/sqlite3.worker.min.js +19 -5
  8. package/dist/src/engine.d.ts +4 -1
  9. package/dist/src/engine.d.ts.map +1 -1
  10. package/dist/src/engine.js +125 -48
  11. package/dist/src/engine.js.map +1 -1
  12. package/dist/src/query-planner.d.ts +47 -0
  13. package/dist/src/query-planner.d.ts.map +1 -0
  14. package/dist/src/query-planner.js +290 -0
  15. package/dist/src/query-planner.js.map +1 -0
  16. package/dist/src/schema.d.ts +29 -7
  17. package/dist/src/schema.d.ts.map +1 -1
  18. package/dist/src/schema.js +354 -119
  19. package/dist/src/schema.js.map +1 -1
  20. package/dist/src/sqlite3-messages.worker.d.ts +4 -1
  21. package/dist/src/sqlite3-messages.worker.d.ts.map +1 -1
  22. package/dist/src/sqlite3-messages.worker.js.map +1 -1
  23. package/dist/src/sqlite3.browser.d.ts.map +1 -1
  24. package/dist/src/sqlite3.browser.js +7 -0
  25. package/dist/src/sqlite3.browser.js.map +1 -1
  26. package/dist/src/sqlite3.d.ts.map +1 -1
  27. package/dist/src/sqlite3.js +24 -14
  28. package/dist/src/sqlite3.js.map +1 -1
  29. package/dist/src/sqlite3.wasm.d.ts +1 -0
  30. package/dist/src/sqlite3.wasm.d.ts.map +1 -1
  31. package/dist/src/sqlite3.wasm.js +9 -1
  32. package/dist/src/sqlite3.wasm.js.map +1 -1
  33. package/dist/src/sqlite3.worker.js +7 -0
  34. package/dist/src/sqlite3.worker.js.map +1 -1
  35. package/dist/src/types.d.ts +1 -0
  36. package/dist/src/types.d.ts.map +1 -1
  37. package/package.json +78 -78
  38. package/src/engine.ts +143 -68
  39. package/src/query-planner.ts +334 -0
  40. package/src/schema.ts +498 -160
  41. package/src/sqlite3-messages.worker.ts +5 -0
  42. package/src/sqlite3.browser.ts +8 -0
  43. package/src/sqlite3.ts +24 -13
  44. package/src/sqlite3.wasm.ts +11 -1
  45. package/src/sqlite3.worker.ts +6 -1
  46. package/src/types.ts +1 -0
@@ -8,8 +8,9 @@ var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
10
  import { FixedArrayKind, OptionKind, VecKind, WrappedType, deserialize, field as fieldDecalaration, getDependencies, getSchema, serialize, variant, } from "@dao-xyz/borsh";
11
- import { toHexString } from "@peerbit/crypto";
11
+ import { fromHexString, toHexString } from "@peerbit/crypto";
12
12
  import * as types from "@peerbit/indexer-interface";
13
+ import { flattenQuery } from "./query-planner.js";
13
14
  const SQLConversionMap = {
14
15
  u8: "INTEGER",
15
16
  u16: "INTEGER",
@@ -27,6 +28,8 @@ const SQLConversionMap = {
27
28
  Date: "TEXT",
28
29
  };
29
30
  const WRAPPED_SIMPLE_VALUE_VARIANT = "wrapped";
31
+ let JSON_GROUP_ARRAY = "json_group_array";
32
+ let JSON_OBJECT = "distinct json_object";
30
33
  export const u64ToI64 = (u64) => {
31
34
  return (typeof u64 === "number" ? BigInt(u64) : u64) - 9223372036854775808n;
32
35
  };
@@ -45,7 +48,7 @@ export const convertToSQLType = (value, type) => {
45
48
  return value;
46
49
  };
47
50
  const nullAsUndefined = (value) => (value === null ? undefined : value);
48
- export const escapeColumnName = (name) => `"${name}"`;
51
+ export const escapeColumnName = (name, char = '"') => `${char}${name}${char}`;
49
52
  export class MissingFieldError extends Error {
50
53
  constructor(message) {
51
54
  super(message);
@@ -127,6 +130,7 @@ export const getSQLTable = (ctor, path, primary, inline, addJoinField, fromOptio
127
130
  referencedInArray: false,
128
131
  isSimpleValue: false,
129
132
  inline,
133
+ indices: new Set(),
130
134
  };
131
135
  ret.push(table);
132
136
  for (const dep of dependencies) {
@@ -158,12 +162,21 @@ const getNameOfClass = (ctor) => {
158
162
  return name;
159
163
  };
160
164
  export const getTableName = (path = [], clazz) => {
165
+ let pathKey = path.length > 0 ? path.join("__") + "__" : "";
166
+ if (typeof clazz !== "string") {
167
+ const tableName = clazz["__table_" + pathKey];
168
+ if (tableName) {
169
+ return tableName;
170
+ }
171
+ }
161
172
  let name = typeof clazz === "string" ? clazz : getNameOfClass(clazz);
162
173
  // prefix the generated table name so that the name is a valid SQL identifier (table name)
163
174
  // choose prefix which is readable and explains that this is a generated table name
164
175
  // leading _ to allow path to have numbers
165
- const ret = (path.length > 0 ? path.join("__") + "__" : "") +
166
- name.replace(/[^a-zA-Z0-9_]/g, "_");
176
+ const ret = pathKey + name.replace(/[^a-zA-Z0-9_]/g, "_");
177
+ if (typeof clazz !== "string") {
178
+ clazz["__table_" + pathKey] = ret;
179
+ }
167
180
  return ret;
168
181
  };
169
182
  export const CHILD_TABLE_ID = "__id";
@@ -186,12 +199,12 @@ export const getSQLFields = (tableName, path, ctor, primary, addJoinFieldFromPar
186
199
  ? addJoinFieldFromParent
187
200
  : (fields, contstraints) => {
188
201
  // we resolve primary field here since it might be unknown until this point
189
- const primaryField = primary != null
202
+ const parentPrimaryField = primary != null
190
203
  ? sqlFields.find((field) => field.name === primary)
191
204
  : undefined;
192
- const parentPrimaryFieldName = primaryField?.key || CHILD_TABLE_ID;
193
- const parentPrimaryFieldType = primaryField
194
- ? primaryField.type
205
+ const parentPrimaryFieldName = parentPrimaryField?.key || CHILD_TABLE_ID;
206
+ const parentPrimaryFieldType = parentPrimaryField
207
+ ? parentPrimaryField.type
195
208
  : "INTEGER";
196
209
  fields.unshift({
197
210
  name: CHILD_TABLE_ID,
@@ -200,6 +213,7 @@ export const getSQLFields = (tableName, path, ctor, primary, addJoinFieldFromPar
200
213
  type: "INTEGER",
201
214
  isPrimary: true,
202
215
  from: undefined,
216
+ unwrappedType: undefined,
203
217
  path: [CHILD_TABLE_ID],
204
218
  },
205
219
  // foreign key parent document
@@ -208,8 +222,9 @@ export const getSQLFields = (tableName, path, ctor, primary, addJoinFieldFromPar
208
222
  key: PARENT_TABLE_ID,
209
223
  definition: `${PARENT_TABLE_ID} ${parentPrimaryFieldType}`,
210
224
  type: parentPrimaryFieldType,
225
+ from: parentPrimaryField?.from,
226
+ unwrappedType: parentPrimaryField?.unwrappedType,
211
227
  isPrimary: false,
212
- from: undefined,
213
228
  path: [PARENT_TABLE_ID],
214
229
  });
215
230
  contstraints.push({
@@ -268,6 +283,7 @@ export const getSQLFields = (tableName, path, ctor, primary, addJoinFieldFromPar
268
283
  type: "INTEGER",
269
284
  isPrimary: false,
270
285
  from: undefined,
286
+ unwrappedType: undefined,
271
287
  path: [ARRAY_INDEX_COLUMN],
272
288
  },
273
289
  ...table.fields.slice(2),
@@ -290,6 +306,7 @@ export const getSQLFields = (tableName, path, ctor, primary, addJoinFieldFromPar
290
306
  type: fieldType,
291
307
  isPrimary,
292
308
  from: field,
309
+ unwrappedType: unwrapNestedType(field.type),
293
310
  path: [...path.slice(1), key],
294
311
  });
295
312
  };
@@ -364,6 +381,7 @@ export const getSQLFields = (tableName, path, ctor, primary, addJoinFieldFromPar
364
381
  type: "bool",
365
382
  isPrimary: false,
366
383
  from: undefined,
384
+ unwrappedType: undefined,
367
385
  path: [...path.slice(1), key],
368
386
  describesExistenceOfAnother: path[path.length - 1],
369
387
  });
@@ -450,7 +468,7 @@ const getTableFromValue = (parentTable, tables, field, value) => {
450
468
  continue;
451
469
  }
452
470
  if (ctor) {
453
- clazzName = getNameOfClass(ctor);
471
+ clazzName = ctor;
454
472
  break;
455
473
  }
456
474
  }
@@ -539,14 +557,14 @@ export const insert = async (insertFn, obj, tables, table, fields, handleNestedC
539
557
  for (const _field of subTable.fields) {
540
558
  bindableValues.push(null);
541
559
  }
542
- bindableValues[bindableValues.length - 1] = false; // assign the value "false" to the exist field column
560
+ bindableValues[bindableValues.length - 1] = 0; // assign the value "false" to the exist field column
543
561
  continue;
544
562
  }
545
563
  await insert((values, table) => {
546
564
  if (table.inline) {
547
565
  bindableValues.push(...values); // insert the bindable values into the parent bindable array
548
566
  if (field.type instanceof OptionKind) {
549
- bindableValues.push(true); // assign the value "true" to the exist field column
567
+ bindableValues.push(1); // assign the value "true" to the exist field column
550
568
  }
551
569
  return undefined;
552
570
  }
@@ -630,16 +648,15 @@ const matchFieldInShape = (shape, path, field) => {
630
648
  };
631
649
  export const selectChildren = (childrenTable) => "select * from " + childrenTable.name + " where " + PARENT_TABLE_ID + " = ?";
632
650
  export const generateSelectQuery = (table, selects) => {
633
- return `SELECT ${selects.map((x) => `${x.from} as ${x.as}`).join(", ")} FROM ${table.name}`;
651
+ return `select ${selects.map((x) => `${x.from} as ${x.as}`).join(", ")} FROM ${table.name}`;
634
652
  };
635
653
  export const selectAllFieldsFromTables = (tables, shape) => {
636
654
  const selectsPerTable = [];
637
655
  for (const table of tables) {
638
- const { selects, join: joinFromSelect } = selectAllFieldsFromTable(table, shape);
639
- selectsPerTable.push({ selects, joins: joinFromSelect });
656
+ const { selects, join: joinFromSelect, groupBy, } = selectAllFieldsFromTable(table, shape);
657
+ selectsPerTable.push({ selects, joins: joinFromSelect, groupBy });
640
658
  }
641
659
  // pad with empty selects to make sure all selects have the same length
642
- /* const maxSelects = Math.max(...selectsPerTable.map(x => x.selects.length)); */
643
660
  let newSelects = [];
644
661
  for (const [i, selects] of selectsPerTable.entries()) {
645
662
  const newSelect = [];
@@ -654,10 +671,6 @@ export const selectAllFieldsFromTables = (tables, shape) => {
654
671
  }
655
672
  }
656
673
  newSelects.push(newSelect);
657
- /* let pad = 0;
658
- while (select.selects.length < maxSelects) {
659
- select.selects.push({ from: "NULL", as: `'pad#${++pad}'` });
660
- } */
661
674
  }
662
675
  // also return table name
663
676
  for (const [i, selects] of selectsPerTable.entries()) {
@@ -669,8 +682,58 @@ export const selectAllFieldsFromTable = (table, shape) => {
669
682
  let stack = [{ table, shape }];
670
683
  let join = new Map();
671
684
  const fieldResolvers = [];
685
+ let groupByParentId = false;
672
686
  for (const tableAndShape of stack) {
673
- if (!tableAndShape.table.inline) {
687
+ if (tableAndShape.table.referencedInArray) {
688
+ let selectBuilder = `${JSON_GROUP_ARRAY}(${JSON_OBJECT}(`;
689
+ groupByParentId = true; // we need to group by the parent id as else we will not be returned with more than 1 result
690
+ let first = false;
691
+ const as = createReconstructReferenceName(tableAndShape.table);
692
+ for (const field of tableAndShape.table.fields) {
693
+ if ((field.isPrimary ||
694
+ !tableAndShape.shape ||
695
+ matchFieldInShape(tableAndShape.shape, [], field) ||
696
+ // also always include the index field
697
+ field.name === ARRAY_INDEX_COLUMN) &&
698
+ field.name !== PARENT_TABLE_ID) {
699
+ let resolveField = `${as}.${escapeColumnName(field.name)}`;
700
+ // if field is bigint we need to convert it to string, so that later in a JSON.parse scenario it is not converted to a number, but remains a string until we can convert it back to a bigint manually
701
+ if (field.unwrappedType === "u64") {
702
+ resolveField = `CAST(${resolveField} AS TEXT)`;
703
+ }
704
+ // if field is blob we need to convert it to hex string
705
+ if (field.type === "BLOB") {
706
+ resolveField = `HEX(${resolveField})`;
707
+ }
708
+ if (first) {
709
+ selectBuilder += `, `;
710
+ }
711
+ first = true;
712
+ selectBuilder += `${escapeColumnName(field.name, "'")}, ${resolveField}`;
713
+ }
714
+ }
715
+ selectBuilder += `)) `; // FILTER (WHERE ${tableAndShape.table.name}.${tableAndShape.table.primary} IS NOT NULL)
716
+ fieldResolvers.push({
717
+ from: selectBuilder,
718
+ as,
719
+ });
720
+ join.set(createReconstructReferenceName(tableAndShape.table), {
721
+ as,
722
+ table: tableAndShape.table,
723
+ type: "left",
724
+ columns: [],
725
+ });
726
+ }
727
+ else if (!tableAndShape.table.inline) {
728
+ // we end up here when we have simple joins we want to make that are not arrays, and not inlined
729
+ if (tableAndShape.table.parent != null) {
730
+ join.set(createReconstructReferenceName(tableAndShape.table), {
731
+ as: tableAndShape.table.name,
732
+ table: tableAndShape.table,
733
+ type: "left",
734
+ columns: [],
735
+ });
736
+ }
674
737
  for (const field of tableAndShape.table.fields) {
675
738
  if (field.isPrimary ||
676
739
  !tableAndShape.shape ||
@@ -683,9 +746,6 @@ export const selectAllFieldsFromTable = (table, shape) => {
683
746
  }
684
747
  }
685
748
  for (const child of tableAndShape.table.children) {
686
- if (child.referencedInArray) {
687
- continue;
688
- }
689
749
  let childShape = undefined;
690
750
  if (tableAndShape.shape) {
691
751
  const parentPath = child.parentPath?.slice(1);
@@ -703,15 +763,16 @@ export const selectAllFieldsFromTable = (table, shape) => {
703
763
  : maybeShape;
704
764
  }
705
765
  stack.push({ table: child, shape: childShape });
706
- if (!child.inline) {
707
- join.set(child.name, { as: child.name, table: child });
708
- }
709
766
  }
710
767
  }
711
768
  if (fieldResolvers.length === 0) {
712
769
  throw new Error("No fields to resolve");
713
770
  }
714
771
  return {
772
+ groupBy: groupByParentId
773
+ ? `${table.name}.${escapeColumnName(table.primary)}` ||
774
+ undefined
775
+ : undefined,
715
776
  selects: fieldResolvers, // `SELECT ${fieldResolvers.join(", ")} FROM ${table.name}`,
716
777
  join,
717
778
  };
@@ -746,14 +807,48 @@ export const resolveInstanceFromValue = async (fromTablePrefixedValues, tables,
746
807
  ? maybeShape[0]
747
808
  : maybeShape;
748
809
  if (isArray) {
749
- let once = false;
810
+ /* let once = false; */
750
811
  let resolvedArr = [];
751
812
  for (const subtable of subTables) {
752
- // TODO types
753
- let rootTable = getNonInlinedTable(table);
754
- const arr = await resolveChildren(fromTablePrefixedValues[getTablePrefixedField(rootTable, rootTable.primary, !tablePrefixed)], subtable);
755
- if (arr) {
756
- once = true;
813
+ // check if the array already in the provided row
814
+ let arr = undefined;
815
+ const tableName = createReconstructReferenceName(subtable);
816
+ if (fromTablePrefixedValues[tableName]) {
817
+ arr = JSON.parse(fromTablePrefixedValues[tableName]);
818
+ arr = arr.filter((x) => x[subtable.primary] != null);
819
+ // we need to go over all fields that are to be bigints and convert
820
+ // them back to bigints
821
+ // for blob fields we need to convert them back to Uint8Array
822
+ for (const field of subtable.fields) {
823
+ if (field.name === PARENT_TABLE_ID) {
824
+ continue;
825
+ }
826
+ if (field.unwrappedType === "u64") {
827
+ for (const item of arr) {
828
+ item[field.name] = BigInt(item[field.name]);
829
+ }
830
+ }
831
+ else if (field.type === "BLOB") {
832
+ for (const item of arr) {
833
+ item[field.name] = fromHexString(item[field.name]);
834
+ }
835
+ }
836
+ }
837
+ }
838
+ else {
839
+ if (subtable.children) {
840
+ // TODO we only end up where when we resolve nested arrays,
841
+ // which shoulld instead be resolved in a nested select (with json_group_array and json_object)
842
+ let rootTable = getNonInlinedTable(table);
843
+ const parentId = fromTablePrefixedValues[getTablePrefixedField(rootTable, rootTable.primary, !tablePrefixed)];
844
+ arr = await resolveChildren(parentId, subtable);
845
+ }
846
+ else {
847
+ arr = [];
848
+ }
849
+ }
850
+ if (arr && arr.length > 0) {
851
+ /* once = true; */
757
852
  for (const element of arr) {
758
853
  const resolved = await resolveInstanceFromValue(element, tables, subtable, // TODO fix
759
854
  resolveChildren, false, subshape);
@@ -763,12 +858,7 @@ export const resolveInstanceFromValue = async (fromTablePrefixedValues, tables,
763
858
  }
764
859
  }
765
860
  }
766
- if (!once) {
767
- obj[field.key] = undefined;
768
- }
769
- else {
770
- obj[field.key] = resolvedArr;
771
- }
861
+ obj[field.key] = resolvedArr; // we can not do option(vec('T')) since we dont store the option type for Arrays (TODO)
772
862
  }
773
863
  else {
774
864
  // resolve nested object from row directly
@@ -857,14 +947,14 @@ export const fromRowToObj = (row, ctor) => {
857
947
  return Object.assign(Object.create(ctor.prototype), obj);
858
948
  };
859
949
  export const convertDeleteRequestToQuery = (request, tables, table) => {
860
- const { query, bindable } = convertRequestToQuery("delete", request, tables, table);
950
+ const { query, bindable } = convertRequestToQuery("delete", { query: types.toQuery(request.query) }, tables, table);
861
951
  return {
862
952
  sql: `DELETE FROM ${table.name} WHERE ${table.primary} IN (SELECT ${table.primary} from ${table.name} ${query}) returning ${table.primary}`,
863
953
  bindable,
864
954
  };
865
955
  };
866
956
  export const convertSumRequestToQuery = (request, tables, table) => {
867
- const { query, bindable } = convertRequestToQuery("sum", request, tables, table);
957
+ const { query, bindable } = convertRequestToQuery("sum", { query: types.toQuery(request.query), key: request.key }, tables, table);
868
958
  const inlineName = getInlineTableFieldName(request.key);
869
959
  const field = table.fields.find((x) => x.name === inlineName);
870
960
  if (unwrapNestedType(field.from.type) === "u64") {
@@ -877,12 +967,45 @@ export const convertSumRequestToQuery = (request, tables, table) => {
877
967
  };
878
968
  };
879
969
  export const convertCountRequestToQuery = (request, tables, table) => {
880
- const { query, bindable } = convertRequestToQuery("count", request, tables, table);
970
+ const { query, bindable } = convertRequestToQuery("count", { query: request?.query ? types.toQuery(request.query) : undefined }, tables, table);
881
971
  return {
882
972
  sql: `SELECT count(*) as count FROM ${table.name} ${query}`,
883
973
  bindable,
884
974
  };
885
975
  };
976
+ const buildOrderBy = (sort, tables, table, joinBuilder, resolverBuilder, path = [], options) => {
977
+ let orderByBuilder = undefined;
978
+ if ((!sort || (Array.isArray(sort) && sort.length === 0)) &&
979
+ !options?.fetchAll) {
980
+ sort =
981
+ table.primary && path.length === 0
982
+ ? [{ key: [table.primary], direction: types.SortDirection.ASC }]
983
+ : undefined;
984
+ }
985
+ if (sort) {
986
+ let sortArr = Array.isArray(sort) ? sort : [sort];
987
+ if (sortArr.length > 0) {
988
+ orderByBuilder = "";
989
+ let once = false;
990
+ for (const sort of sortArr) {
991
+ const { foreignTables, queryKey } = resolveTableToQuery(table, tables, joinBuilder, [...path, ...sort.key], undefined, true);
992
+ for (const foreignTable of foreignTables) {
993
+ if (once) {
994
+ orderByBuilder += ", ";
995
+ }
996
+ once = true;
997
+ foreignTable.columns.push(queryKey); // add the sort key to the list of columns that will be used for this query
998
+ orderByBuilder += `"${foreignTable.as}#${queryKey}" ${sort.direction === types.SortDirection.ASC ? "ASC" : "DESC"}`;
999
+ resolverBuilder.push({
1000
+ from: `${table.name}.${escapeColumnName(queryKey)}`,
1001
+ as: `'${foreignTable.as}#${queryKey}'`,
1002
+ });
1003
+ }
1004
+ }
1005
+ }
1006
+ }
1007
+ return { orderByBuilder };
1008
+ };
886
1009
  export const convertSearchRequestToQuery = (request, tables, rootTables, options) => {
887
1010
  let unionBuilder = "";
888
1011
  let orderByClause = "";
@@ -891,21 +1014,19 @@ export const convertSearchRequestToQuery = (request, tables, rootTables, options
891
1014
  const selectsPerTable = selectAllFieldsFromTables(rootTables, options?.shape);
892
1015
  let bindableBuilder = [];
893
1016
  for (const [i, table] of rootTables.entries()) {
894
- const { selects, joins: joinFromSelect } = selectsPerTable[i];
895
- const selectQuery = generateSelectQuery(table, selects);
1017
+ const { selects, joins, groupBy } = selectsPerTable[i];
896
1018
  try {
897
- const { orderBy, query, bindable } = convertRequestToQuery("iterate", request, tables, table, joinFromSelect, [], {
898
- stable: options?.stable,
899
- });
900
- unionBuilder += `${unionBuilder.length > 0 ? " UNION ALL " : ""} ${selectQuery} ${query}`;
901
- orderByClause =
902
- orderBy?.length > 0
903
- ? orderByClause.length > 0
904
- ? orderByClause + ", " + orderBy
905
- : orderBy
906
- : orderByClause;
907
- matchedOnce = true;
908
- bindableBuilder.push(...bindable);
1019
+ const { orderByBuilder } = buildOrderBy(request?.sort, tables, table, joins, selects, [], options);
1020
+ if (!orderByClause && orderByBuilder) {
1021
+ // assume all order by clauses will be the same
1022
+ orderByClause =
1023
+ orderByBuilder.length > 0
1024
+ ? orderByClause.length > 0
1025
+ ? orderByClause + ", " + orderByBuilder
1026
+ : orderByBuilder
1027
+ : orderByClause;
1028
+ }
1029
+ //orderByAddedOnce = true;
909
1030
  }
910
1031
  catch (error) {
911
1032
  if (error instanceof MissingFieldError) {
@@ -914,138 +1035,209 @@ export const convertSearchRequestToQuery = (request, tables, rootTables, options
914
1035
  }
915
1036
  throw error;
916
1037
  }
1038
+ const selectQuery = generateSelectQuery(table, selects);
1039
+ for (const flattenRequest of flattenQuery(request)) {
1040
+ try {
1041
+ const { query, bindable } = convertRequestToQuery("iterate", flattenRequest, tables, table, new Map(joins), // copy the map, else we might might do unececessary joins
1042
+ [], options);
1043
+ unionBuilder += `${unionBuilder.length > 0 ? " UNION " : ""} ${selectQuery} ${query} ${groupBy ? "GROUP BY " + groupBy : ""}`;
1044
+ matchedOnce = true;
1045
+ bindableBuilder.push(...bindable);
1046
+ }
1047
+ catch (error) {
1048
+ if (error instanceof MissingFieldError) {
1049
+ lastError = error;
1050
+ orderByClause = "";
1051
+ continue;
1052
+ }
1053
+ throw error;
1054
+ }
1055
+ }
917
1056
  }
918
1057
  if (!matchedOnce) {
919
1058
  throw lastError;
920
1059
  }
921
1060
  return {
922
- sql: `${unionBuilder} ${orderByClause ? "ORDER BY " + orderByClause : ""} limit ? offset ?`,
1061
+ sql: `${unionBuilder} ${orderByClause ? "ORDER BY " + orderByClause : ""} ${options?.fetchAll ? "" : "limit ? offset ?"}`,
923
1062
  bindable: bindableBuilder,
924
1063
  };
925
1064
  };
926
- function isIterateRequest(request, type) {
927
- return type === "iterate";
928
- }
1065
+ const getOrSetRootTable = (joinBuilder, table) => {
1066
+ const refName = createQueryTableReferenceName(table);
1067
+ let ref = joinBuilder.get(refName);
1068
+ if (ref) {
1069
+ return ref;
1070
+ }
1071
+ const join = {
1072
+ // add the root as a join even though it is not, just so we can collect the columns it will be queried
1073
+ table: table,
1074
+ type: "root",
1075
+ as: table.name,
1076
+ columns: [],
1077
+ };
1078
+ joinBuilder.set(refName, join);
1079
+ return join;
1080
+ };
929
1081
  const convertRequestToQuery = (type, request, tables, table, extraJoin, path = [], options) => {
930
1082
  let whereBuilder = "";
931
1083
  let bindableBuilder = [];
932
- let orderByBuilder = undefined;
1084
+ /* let orderByBuilder: string | undefined = undefined; */
933
1085
  /* let tablesToSelect: string[] = [table.name]; */
934
1086
  let joinBuilder = extraJoin || new Map();
1087
+ getOrSetRootTable(joinBuilder, table);
935
1088
  const coercedQuery = types.toQuery(request?.query);
936
1089
  if (coercedQuery.length === 1) {
937
- const { where, bindable } = convertQueryToSQLQuery(coercedQuery[0], tables, table, joinBuilder, path);
1090
+ const { where, bindable } = convertQueryToSQLQuery(coercedQuery[0], tables, table, joinBuilder, path, undefined, 0);
938
1091
  whereBuilder += where;
939
1092
  bindableBuilder.push(...bindable);
940
1093
  }
941
1094
  else if (coercedQuery.length > 1) {
942
- const { where, bindable } = convertQueryToSQLQuery(new types.And(coercedQuery), tables, table, joinBuilder, path);
1095
+ const { where, bindable } = convertQueryToSQLQuery(new types.And(coercedQuery), tables, table, joinBuilder, path, undefined, 0);
943
1096
  whereBuilder += where;
944
1097
  bindableBuilder.push(...bindable);
945
1098
  }
946
- if (isIterateRequest(request, type)) {
1099
+ /* if (isIterateRequest(request, type)) {
947
1100
  let sort = request?.sort;
948
- if (!sort && options?.stable) {
1101
+ if (
1102
+ (!sort || (Array.isArray(sort) && sort.length === 0)) &&
1103
+ !options?.fetchAll
1104
+ ) {
949
1105
  sort =
950
1106
  table.primary && path.length === 0
951
1107
  ? [{ key: [table.primary], direction: types.SortDirection.ASC }]
952
1108
  : undefined;
953
1109
  }
1110
+
954
1111
  if (sort) {
955
1112
  let sortArr = Array.isArray(sort) ? sort : [sort];
956
1113
  if (sortArr.length > 0) {
957
1114
  orderByBuilder = "";
958
1115
  let once = false;
959
1116
  for (const sort of sortArr) {
960
- const { foreignTables, queryKey } = resolveTableToQuery(table, tables, joinBuilder, [...path, ...sort.key], undefined, true);
961
- for (const table of foreignTables) {
1117
+ const { foreignTables, queryKey } = resolveTableToQuery(
1118
+ table,
1119
+ tables,
1120
+ joinBuilder,
1121
+ [...path, ...sort.key],
1122
+ undefined,
1123
+ true,
1124
+ );
1125
+
1126
+ for (const foreignTable of foreignTables) {
962
1127
  if (once) {
963
1128
  orderByBuilder += ", ";
964
1129
  }
965
1130
  once = true;
966
- orderByBuilder += `${table.as}.${queryKey} ${sort.direction === types.SortDirection.ASC ? "ASC" : "DESC"}`;
1131
+
1132
+ foreignTable.columns.push(queryKey); // add the sort key to the list of columns that will be used for this query
1133
+
1134
+ orderByBuilder += `${foreignTable.as}.${queryKey} ${sort.direction === types.SortDirection.ASC ? "ASC" : "DESC"}`;
967
1135
  }
968
1136
  }
969
- /* orderByBuilder += request.sort
970
- .map(
971
- (sort) =>
972
- `${table.name}.${sort.key} ${sort.direction === types.SortDirection.ASC ? "ASC" : "DESC"}`
973
- )
974
- .join(", "); */
975
1137
  }
976
1138
  }
977
- }
1139
+ } */
978
1140
  const where = whereBuilder.length > 0 ? "where " + whereBuilder : undefined;
979
1141
  if (extraJoin && extraJoin.size > 0) {
980
1142
  insertMapIntoMap(joinBuilder, extraJoin);
981
1143
  }
982
- let join = buildJoin(joinBuilder, type === "iterate" ? true : false);
1144
+ let { join } = buildJoin(joinBuilder, options);
983
1145
  const query = `${join ? join : ""} ${where ? where : ""}`;
984
1146
  return {
985
1147
  query,
986
- orderBy: orderByBuilder,
1148
+ /* orderBy: orderByBuilder, */
987
1149
  bindable: bindableBuilder,
988
1150
  };
989
1151
  };
990
- export const buildJoin = (joinBuilder, resolveAllColumns) => {
991
- let joinTypeDefault = resolveAllColumns
992
- ? /* "FULL OUTER JOIN" */ "LEFT OUTER JOIN"
993
- : "JOIN";
1152
+ export const buildJoin = (joinBuilder, options) => {
1153
+ /* let joinTypeDefault = resolveAllColumns
1154
+ ? "CROSS JOIN"
1155
+ : "JOIN"; */
994
1156
  let join = "";
995
1157
  for (const [_key, table] of joinBuilder) {
1158
+ if (table.type !== "root") {
1159
+ continue;
1160
+ }
1161
+ const out = _buildJoin(table, options);
1162
+ join += out.join;
1163
+ }
1164
+ for (const [_key, table] of joinBuilder) {
1165
+ if (table.type === "root") {
1166
+ continue;
1167
+ }
1168
+ const out = _buildJoin(table, options);
1169
+ join += out.join;
1170
+ }
1171
+ return { join };
1172
+ };
1173
+ const _buildJoin = (table, options) => {
1174
+ let join = "";
1175
+ let indexedBy = undefined;
1176
+ if (table.type !== "root") {
1177
+ table.columns.push(PARENT_TABLE_ID); // we unshift because we join on the parent id before where clause
1178
+ }
1179
+ if (table.columns.length > 0) {
1180
+ const usedColumns = removeDuplicatesOrdered(table.columns);
1181
+ indexedBy = options?.planner
1182
+ ? ` INDEXED BY ${options.planner.resolveIndex(table.table.name, usedColumns)} `
1183
+ : "";
1184
+ }
1185
+ if (table.type !== "root") {
996
1186
  let nonInlinedParent = table.table.parent && getNonInlinedTable(table.table.parent);
997
1187
  if (!nonInlinedParent) {
998
1188
  throw new Error("Unexpected: missing parent");
999
1189
  }
1000
- let joinType = table.table.referencedInArray
1001
- ? /* "FULL OUTER JOIN" */ "LEFT OUTER JOIN"
1002
- : joinTypeDefault;
1003
- join += `${joinType} ${table.table.name} AS ${table.as} ON ${nonInlinedParent.name}.${nonInlinedParent.primary} = ${table.as}.${PARENT_TABLE_ID} `;
1190
+ let joinType = table.type === "cross" ? "LEFT JOIN" : "LEFT JOIN";
1191
+ join += ` ${joinType} ${table.table.name} AS ${table.as} ${indexedBy} ON ${nonInlinedParent.name}.${nonInlinedParent.primary} = ${table.as}.${PARENT_TABLE_ID} `;
1004
1192
  }
1005
- return join;
1193
+ else if (indexedBy) {
1194
+ join += indexedBy;
1195
+ }
1196
+ return { join };
1006
1197
  };
1007
1198
  const insertMapIntoMap = (map, insert) => {
1008
1199
  for (const [key, value] of insert) {
1009
1200
  map.set(key, value);
1010
1201
  }
1011
1202
  };
1012
- export const convertQueryToSQLQuery = (query, tables, table, joinBuilder, path = [], tableAlias = undefined) => {
1203
+ export const convertQueryToSQLQuery = (query, tables, table, joinBuilder, path, tableAlias, skipKeys) => {
1013
1204
  let whereBuilder = "";
1014
1205
  let bindableBuilder = [];
1015
1206
  /* let tablesToSelect: string[] = []; */
1016
- const handleAnd = (queries, path, tableAlias) => {
1207
+ const handleAnd = (queries, path, tableAlias, keysOffset) => {
1017
1208
  for (const query of queries) {
1018
- const { where, bindable } = convertQueryToSQLQuery(query, tables, table, joinBuilder, path, tableAlias);
1209
+ const { where, bindable } = convertQueryToSQLQuery(query, tables, table, joinBuilder, path, tableAlias, keysOffset);
1019
1210
  whereBuilder =
1020
1211
  whereBuilder.length > 0 ? `(${whereBuilder}) AND (${where})` : where;
1021
1212
  bindableBuilder.push(...bindable);
1022
1213
  }
1023
1214
  };
1024
1215
  if (query instanceof types.StateFieldQuery) {
1025
- const { where, bindable } = convertStateFieldQuery(query, tables, table, joinBuilder, path, tableAlias);
1216
+ const { where, bindable } = convertStateFieldQuery(query, tables, table, joinBuilder, path, tableAlias, skipKeys);
1026
1217
  whereBuilder += where;
1027
1218
  bindableBuilder.push(...bindable);
1028
1219
  }
1029
1220
  else if (query instanceof types.Nested) {
1030
1221
  let joinPrefix = "__" + String(tables.size);
1031
- path = [...path, query.path];
1032
- handleAnd(query.query, path, joinPrefix);
1222
+ path = [...path, ...query.path];
1223
+ let newSkipKeys = skipKeys + query.path.length;
1224
+ handleAnd(query.query, path, joinPrefix, newSkipKeys);
1033
1225
  }
1034
1226
  else if (query instanceof types.LogicalQuery) {
1035
1227
  if (query instanceof types.And) {
1036
- handleAnd(query.and, path, tableAlias);
1228
+ handleAnd(query.and, path, tableAlias, skipKeys);
1037
1229
  }
1038
1230
  else if (query instanceof types.Or) {
1039
1231
  for (const subquery of query.or) {
1040
- const { where, bindable } = convertQueryToSQLQuery(subquery, tables, table, joinBuilder, path, tableAlias);
1232
+ const { where, bindable } = convertQueryToSQLQuery(subquery, tables, table, joinBuilder, path, tableAlias, skipKeys);
1041
1233
  whereBuilder =
1042
- whereBuilder.length > 0 ? `(${whereBuilder}) OR (${where})` : where;
1234
+ whereBuilder.length > 0 ? `(${whereBuilder}) OR(${where})` : where;
1043
1235
  bindableBuilder.push(...bindable);
1044
1236
  }
1045
1237
  }
1046
1238
  else if (query instanceof types.Not) {
1047
- const { where, bindable } = convertQueryToSQLQuery(query.not, tables, table, joinBuilder, path, tableAlias);
1048
- whereBuilder = `NOT (${where})`;
1239
+ const { where, bindable } = convertQueryToSQLQuery(query.not, tables, table, joinBuilder, path, tableAlias, skipKeys);
1240
+ whereBuilder = `NOT(${where})`;
1049
1241
  bindableBuilder.push(...bindable);
1050
1242
  }
1051
1243
  else {
@@ -1063,16 +1255,26 @@ export const convertQueryToSQLQuery = (query, tables, table, joinBuilder, path =
1063
1255
  const cloneQuery = (query) => {
1064
1256
  return deserialize(serialize(query), types.StateFieldQuery);
1065
1257
  };
1066
- const createTableReferenceName = (table, alias, fieldType, joinSize) => {
1067
- if (!alias &&
1068
- (fieldType instanceof VecKind ||
1069
- (fieldType instanceof OptionKind &&
1070
- fieldType.elementType instanceof VecKind))) {
1071
- let aliasSuffix = "_" + String(joinSize);
1258
+ /* const createQueryTableReferenceName = (
1259
+ table: Table,
1260
+ alias: string | undefined,
1261
+ ) => {
1262
+
1263
+ if (
1264
+ !alias
1265
+ ) {
1266
+ let aliasSuffix =
1267
+ "_query"; // "_" + String(joinSize); TODO this property will make every join unique, which is not wanted unless (ever?) since we can do OR in SQL which means we can do one join and perform AND/OR logic without joining multiple times to apply multiple conditions
1072
1268
  alias = aliasSuffix;
1073
1269
  }
1074
1270
  const tableNameAs = alias ? alias + "_" + table.name : table.name;
1075
1271
  return tableNameAs;
1272
+ }; */
1273
+ const createQueryTableReferenceName = (table) => {
1274
+ return table.parent == null ? table.name : "_query_" + table.name;
1275
+ };
1276
+ const createReconstructReferenceName = (table) => {
1277
+ return table.name; /* table.parent == null ? table.name : "_rec_" + table.name; */
1076
1278
  };
1077
1279
  const resolveTableToQuery = (table, tables, join, path, alias, searchSelf) => {
1078
1280
  // we are matching in two ways.
@@ -1088,11 +1290,18 @@ const resolveTableToQuery = (table, tables, join, path, alias, searchSelf) => {
1088
1290
  if (field) {
1089
1291
  return {
1090
1292
  queryKey: field.name,
1091
- foreignTables: [{ table, as: table.name }],
1293
+ foreignTables: [getOrSetRootTable(join, table)],
1092
1294
  };
1093
1295
  }
1094
1296
  }
1095
- let currentTables = [{ table, as: alias || table.name }];
1297
+ let currentTables = [
1298
+ {
1299
+ table,
1300
+ as: alias || table.name,
1301
+ type: "cross",
1302
+ columns: [],
1303
+ },
1304
+ ];
1096
1305
  let prevTables = undefined;
1097
1306
  // outer:
1098
1307
  for (const [_i, key] of path /* .slice(0, -1) */
@@ -1103,13 +1312,20 @@ const resolveTableToQuery = (table, tables, join, path, alias, searchSelf) => {
1103
1312
  const field = schema.fields.find((x) => x.key === key);
1104
1313
  if (!field && currentTable.children.length > 0) {
1105
1314
  // second arg is needed because of polymorphic fields we might end up here intentially to check what tables to query
1106
- throw new MissingFieldError(`Property with key "${key}" is not found in the schema ${JSON.stringify(schema.fields.map((x) => x.key))}`);
1315
+ throw new MissingFieldError(`Property with key "${key}" is not found in the schema ${JSON.stringify(schema.fields.map((x) => x.key))} `);
1107
1316
  }
1108
1317
  for (const child of currentTable.children) {
1109
- const tableNameAs = createTableReferenceName(child, alias, field.type, join.size);
1318
+ const tableNameAs = createQueryTableReferenceName(child);
1110
1319
  let isMatching = child.parentPath[child.parentPath.length - 1] === key;
1111
1320
  if (isMatching) {
1112
- const tableWithAlias = { table: child, as: tableNameAs };
1321
+ const tableWithAlias = {
1322
+ columns: [],
1323
+ table: child,
1324
+ as: tableNameAs,
1325
+ type: currentTable.children.length > 1
1326
+ ? "left"
1327
+ : "cross",
1328
+ };
1113
1329
  if (child.isSimpleValue) {
1114
1330
  if (!child.inline) {
1115
1331
  join.set(tableNameAs, tableWithAlias);
@@ -1156,13 +1372,17 @@ const resolveTableToQuery = (table, tables, join, path, alias, searchSelf) => {
1156
1372
  : FOREIGN_VALUE_PROPERTY;
1157
1373
  return { queryKey, foreignTables };
1158
1374
  };
1159
- const convertStateFieldQuery = (query, tables, table, join, path, tableAlias = undefined) => {
1375
+ const convertStateFieldQuery = (query, tables, table, join, path, tableAlias, skipKeys) => {
1160
1376
  // if field id represented as foreign table, do join and compare
1161
1377
  const inlinedName = getInlineTableFieldName(query.key);
1162
1378
  const tableField = table.fields.find((x) => x.name === inlinedName); /* stringArraysEquals(query.key, [...table.parentPath, x.name]) )*/
1163
1379
  const isForeign = !tableField; // table.fields.find(x => x.name === query.key[query.key.length - 1])
1164
1380
  if (isForeign) {
1165
- const { queryKey, foreignTables } = resolveTableToQuery(table, tables, join, [...path, ...query.key], tableAlias, false);
1381
+ const tablePath = [...path];
1382
+ for (let i = skipKeys; i < query.key.length; i++) {
1383
+ tablePath.push(query.key[i]);
1384
+ }
1385
+ const { queryKey, foreignTables } = resolveTableToQuery(table, tables, join, tablePath, tableAlias, false);
1166
1386
  query = cloneQuery(query);
1167
1387
  query.key = [queryKey];
1168
1388
  let whereBuilder = [];
@@ -1171,7 +1391,7 @@ const convertStateFieldQuery = (query, tables, table, join, path, tableAlias = u
1171
1391
  if (ftable.table === table) {
1172
1392
  throw new Error("Unexpected");
1173
1393
  }
1174
- const { where, bindable } = convertQueryToSQLQuery(query, tables, ftable.table, join, path, ftable.as);
1394
+ const { where, bindable } = convertQueryToSQLQuery(query, tables, ftable.table, join, path, ftable.as, skipKeys);
1175
1395
  whereBuilder.push(where);
1176
1396
  bindableBuilder.push(bindable);
1177
1397
  }
@@ -1180,17 +1400,22 @@ const convertStateFieldQuery = (query, tables, table, join, path, tableAlias = u
1180
1400
  bindable: bindableBuilder.flat(),
1181
1401
  };
1182
1402
  }
1403
+ const columnAggregator = join.get(createQueryTableReferenceName(table));
1404
+ if (!columnAggregator) {
1405
+ throw new Error("Unexpected");
1406
+ }
1407
+ columnAggregator.columns.push(inlinedName);
1183
1408
  let bindable = [];
1184
1409
  const keyWithTable = (tableAlias || table.name) + "." + escapeColumnName(inlinedName);
1185
1410
  let where;
1186
1411
  if (query instanceof types.StringMatch) {
1187
1412
  let statement = "";
1188
1413
  if (query.method === types.StringMatchMethod.contains) {
1189
- statement = `${keyWithTable} LIKE ?`;
1414
+ statement = `${keyWithTable} LIKE ? `;
1190
1415
  bindable.push(`%${query.value}%`);
1191
1416
  }
1192
1417
  else if (query.method === types.StringMatchMethod.prefix) {
1193
- statement = `${keyWithTable} LIKE ?`;
1418
+ statement = `${keyWithTable} LIKE ? `;
1194
1419
  bindable.push(`${query.value}%`);
1195
1420
  }
1196
1421
  else if (query.method === types.StringMatchMethod.exact) {
@@ -1211,7 +1436,7 @@ const convertStateFieldQuery = (query, tables, table, join, path, tableAlias = u
1211
1436
  else if (query instanceof types.IntegerCompare) {
1212
1437
  if (tableField.type === "BLOB") {
1213
1438
  // TODO perf
1214
- where = `hex(${keyWithTable}) LIKE ?`;
1439
+ where = `hex(${keyWithTable}) LIKE ? `;
1215
1440
  bindable.push(`%${toHexString(new Uint8Array([Number(query.value.value)]))}%`);
1216
1441
  }
1217
1442
  else {
@@ -1219,19 +1444,19 @@ const convertStateFieldQuery = (query, tables, table, join, path, tableAlias = u
1219
1444
  where = `${keyWithTable} = ?`;
1220
1445
  }
1221
1446
  else if (query.compare === types.Compare.Greater) {
1222
- where = `${keyWithTable} > ?`;
1447
+ where = `${keyWithTable} > ? `;
1223
1448
  }
1224
1449
  else if (query.compare === types.Compare.Less) {
1225
- where = `${keyWithTable} < ?`;
1450
+ where = `${keyWithTable} <?`;
1226
1451
  }
1227
1452
  else if (query.compare === types.Compare.GreaterOrEqual) {
1228
- where = `${keyWithTable} >= ?`;
1453
+ where = `${keyWithTable} >= ? `;
1229
1454
  }
1230
1455
  else if (query.compare === types.Compare.LessOrEqual) {
1231
- where = `${keyWithTable} <= ?`;
1456
+ where = `${keyWithTable} <= ? `;
1232
1457
  }
1233
1458
  else {
1234
- throw new Error(`Unsupported compare type: ${query.compare}`);
1459
+ throw new Error(`Unsupported compare type: ${query.compare} `);
1235
1460
  }
1236
1461
  if (unwrapNestedType(tableField.from.type) === "u64") {
1237
1462
  // shift left because that is how we insert the value
@@ -1254,4 +1479,14 @@ const convertStateFieldQuery = (query, tables, table, join, path, tableAlias = u
1254
1479
  }
1255
1480
  return { where, bindable };
1256
1481
  };
1482
+ const removeDuplicatesOrdered = (arr) => {
1483
+ let seen = new Set();
1484
+ return arr.filter((item) => {
1485
+ if (seen.has(item)) {
1486
+ return false;
1487
+ }
1488
+ seen.add(item);
1489
+ return true;
1490
+ });
1491
+ };
1257
1492
  //# sourceMappingURL=schema.js.map