@peerbit/indexer-sqlite3 1.1.4 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +688 -168
  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 +31 -7
  17. package/dist/src/schema.d.ts.map +1 -1
  18. package/dist/src/schema.js +370 -123
  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 +4 -4
  38. package/src/engine.ts +143 -68
  39. package/src/query-planner.ts +334 -0
  40. package/src/schema.ts +519 -164
  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,17 +28,27 @@ 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";
33
+ export const u64ToI64 = (u64) => {
34
+ return (typeof u64 === "number" ? BigInt(u64) : u64) - 9223372036854775808n;
35
+ };
36
+ export const i64ToU64 = (i64) => (typeof i64 === "number" ? BigInt(i64) : i64) + 9223372036854775808n;
30
37
  export const convertToSQLType = (value, type) => {
31
38
  // add bigint when https://github.com/TryGhost/node-sqlite3/pull/1501 fixed
32
39
  if (value != null) {
33
40
  if (type === "bool") {
34
41
  return value ? 1 : 0;
35
42
  }
43
+ if (type === "u64") {
44
+ // shift to fit in i64
45
+ return u64ToI64(value);
46
+ }
36
47
  }
37
48
  return value;
38
49
  };
39
50
  const nullAsUndefined = (value) => (value === null ? undefined : value);
40
- export const escapeColumnName = (name) => `"${name}"`;
51
+ export const escapeColumnName = (name, char = '"') => `${char}${name}${char}`;
41
52
  export class MissingFieldError extends Error {
42
53
  constructor(message) {
43
54
  super(message);
@@ -61,9 +72,13 @@ export const convertFromSQLType = (value, type) => {
61
72
  : nullAsUndefined(value);
62
73
  }
63
74
  if (type === "u64") {
64
- return typeof value === "number" || typeof value === "string"
65
- ? BigInt(value)
66
- : nullAsUndefined(value);
75
+ if (typeof value === "number" || typeof value === "bigint") {
76
+ return i64ToU64(value); // TODO is not always value type bigint?
77
+ }
78
+ if (value == null) {
79
+ return nullAsUndefined(value);
80
+ }
81
+ throw new Error(`Unexpected value type for value ${value} expected number or bigint for u64 field`);
67
82
  }
68
83
  return nullAsUndefined(value);
69
84
  };
@@ -115,6 +130,7 @@ export const getSQLTable = (ctor, path, primary, inline, addJoinField, fromOptio
115
130
  referencedInArray: false,
116
131
  isSimpleValue: false,
117
132
  inline,
133
+ indices: new Set(),
118
134
  };
119
135
  ret.push(table);
120
136
  for (const dep of dependencies) {
@@ -146,12 +162,21 @@ const getNameOfClass = (ctor) => {
146
162
  return name;
147
163
  };
148
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
+ }
149
172
  let name = typeof clazz === "string" ? clazz : getNameOfClass(clazz);
150
173
  // prefix the generated table name so that the name is a valid SQL identifier (table name)
151
174
  // choose prefix which is readable and explains that this is a generated table name
152
175
  // leading _ to allow path to have numbers
153
- const ret = (path.length > 0 ? path.join("__") + "__" : "") +
154
- 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
+ }
155
180
  return ret;
156
181
  };
157
182
  export const CHILD_TABLE_ID = "__id";
@@ -174,12 +199,12 @@ export const getSQLFields = (tableName, path, ctor, primary, addJoinFieldFromPar
174
199
  ? addJoinFieldFromParent
175
200
  : (fields, contstraints) => {
176
201
  // we resolve primary field here since it might be unknown until this point
177
- const primaryField = primary != null
202
+ const parentPrimaryField = primary != null
178
203
  ? sqlFields.find((field) => field.name === primary)
179
204
  : undefined;
180
- const parentPrimaryFieldName = primaryField?.key || CHILD_TABLE_ID;
181
- const parentPrimaryFieldType = primaryField
182
- ? primaryField.type
205
+ const parentPrimaryFieldName = parentPrimaryField?.key || CHILD_TABLE_ID;
206
+ const parentPrimaryFieldType = parentPrimaryField
207
+ ? parentPrimaryField.type
183
208
  : "INTEGER";
184
209
  fields.unshift({
185
210
  name: CHILD_TABLE_ID,
@@ -188,6 +213,7 @@ export const getSQLFields = (tableName, path, ctor, primary, addJoinFieldFromPar
188
213
  type: "INTEGER",
189
214
  isPrimary: true,
190
215
  from: undefined,
216
+ unwrappedType: undefined,
191
217
  path: [CHILD_TABLE_ID],
192
218
  },
193
219
  // foreign key parent document
@@ -196,8 +222,9 @@ export const getSQLFields = (tableName, path, ctor, primary, addJoinFieldFromPar
196
222
  key: PARENT_TABLE_ID,
197
223
  definition: `${PARENT_TABLE_ID} ${parentPrimaryFieldType}`,
198
224
  type: parentPrimaryFieldType,
225
+ from: parentPrimaryField?.from,
226
+ unwrappedType: parentPrimaryField?.unwrappedType,
199
227
  isPrimary: false,
200
- from: undefined,
201
228
  path: [PARENT_TABLE_ID],
202
229
  });
203
230
  contstraints.push({
@@ -256,6 +283,7 @@ export const getSQLFields = (tableName, path, ctor, primary, addJoinFieldFromPar
256
283
  type: "INTEGER",
257
284
  isPrimary: false,
258
285
  from: undefined,
286
+ unwrappedType: undefined,
259
287
  path: [ARRAY_INDEX_COLUMN],
260
288
  },
261
289
  ...table.fields.slice(2),
@@ -278,6 +306,7 @@ export const getSQLFields = (tableName, path, ctor, primary, addJoinFieldFromPar
278
306
  type: fieldType,
279
307
  isPrimary,
280
308
  from: field,
309
+ unwrappedType: unwrapNestedType(field.type),
281
310
  path: [...path.slice(1), key],
282
311
  });
283
312
  };
@@ -352,6 +381,7 @@ export const getSQLFields = (tableName, path, ctor, primary, addJoinFieldFromPar
352
381
  type: "bool",
353
382
  isPrimary: false,
354
383
  from: undefined,
384
+ unwrappedType: undefined,
355
385
  path: [...path.slice(1), key],
356
386
  describesExistenceOfAnother: path[path.length - 1],
357
387
  });
@@ -438,7 +468,7 @@ const getTableFromValue = (parentTable, tables, field, value) => {
438
468
  continue;
439
469
  }
440
470
  if (ctor) {
441
- clazzName = getNameOfClass(ctor);
471
+ clazzName = ctor;
442
472
  break;
443
473
  }
444
474
  }
@@ -527,14 +557,14 @@ export const insert = async (insertFn, obj, tables, table, fields, handleNestedC
527
557
  for (const _field of subTable.fields) {
528
558
  bindableValues.push(null);
529
559
  }
530
- 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
531
561
  continue;
532
562
  }
533
563
  await insert((values, table) => {
534
564
  if (table.inline) {
535
565
  bindableValues.push(...values); // insert the bindable values into the parent bindable array
536
566
  if (field.type instanceof OptionKind) {
537
- 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
538
568
  }
539
569
  return undefined;
540
570
  }
@@ -618,16 +648,15 @@ const matchFieldInShape = (shape, path, field) => {
618
648
  };
619
649
  export const selectChildren = (childrenTable) => "select * from " + childrenTable.name + " where " + PARENT_TABLE_ID + " = ?";
620
650
  export const generateSelectQuery = (table, selects) => {
621
- 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}`;
622
652
  };
623
653
  export const selectAllFieldsFromTables = (tables, shape) => {
624
654
  const selectsPerTable = [];
625
655
  for (const table of tables) {
626
- const { selects, join: joinFromSelect } = selectAllFieldsFromTable(table, shape);
627
- selectsPerTable.push({ selects, joins: joinFromSelect });
656
+ const { selects, join: joinFromSelect, groupBy, } = selectAllFieldsFromTable(table, shape);
657
+ selectsPerTable.push({ selects, joins: joinFromSelect, groupBy });
628
658
  }
629
659
  // pad with empty selects to make sure all selects have the same length
630
- /* const maxSelects = Math.max(...selectsPerTable.map(x => x.selects.length)); */
631
660
  let newSelects = [];
632
661
  for (const [i, selects] of selectsPerTable.entries()) {
633
662
  const newSelect = [];
@@ -642,10 +671,6 @@ export const selectAllFieldsFromTables = (tables, shape) => {
642
671
  }
643
672
  }
644
673
  newSelects.push(newSelect);
645
- /* let pad = 0;
646
- while (select.selects.length < maxSelects) {
647
- select.selects.push({ from: "NULL", as: `'pad#${++pad}'` });
648
- } */
649
674
  }
650
675
  // also return table name
651
676
  for (const [i, selects] of selectsPerTable.entries()) {
@@ -657,8 +682,58 @@ export const selectAllFieldsFromTable = (table, shape) => {
657
682
  let stack = [{ table, shape }];
658
683
  let join = new Map();
659
684
  const fieldResolvers = [];
685
+ let groupByParentId = false;
660
686
  for (const tableAndShape of stack) {
661
- 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
+ }
662
737
  for (const field of tableAndShape.table.fields) {
663
738
  if (field.isPrimary ||
664
739
  !tableAndShape.shape ||
@@ -671,9 +746,6 @@ export const selectAllFieldsFromTable = (table, shape) => {
671
746
  }
672
747
  }
673
748
  for (const child of tableAndShape.table.children) {
674
- if (child.referencedInArray) {
675
- continue;
676
- }
677
749
  let childShape = undefined;
678
750
  if (tableAndShape.shape) {
679
751
  const parentPath = child.parentPath?.slice(1);
@@ -691,15 +763,16 @@ export const selectAllFieldsFromTable = (table, shape) => {
691
763
  : maybeShape;
692
764
  }
693
765
  stack.push({ table: child, shape: childShape });
694
- if (!child.inline) {
695
- join.set(child.name, { as: child.name, table: child });
696
- }
697
766
  }
698
767
  }
699
768
  if (fieldResolvers.length === 0) {
700
769
  throw new Error("No fields to resolve");
701
770
  }
702
771
  return {
772
+ groupBy: groupByParentId
773
+ ? `${table.name}.${escapeColumnName(table.primary)}` ||
774
+ undefined
775
+ : undefined,
703
776
  selects: fieldResolvers, // `SELECT ${fieldResolvers.join(", ")} FROM ${table.name}`,
704
777
  join,
705
778
  };
@@ -734,14 +807,48 @@ export const resolveInstanceFromValue = async (fromTablePrefixedValues, tables,
734
807
  ? maybeShape[0]
735
808
  : maybeShape;
736
809
  if (isArray) {
737
- let once = false;
810
+ /* let once = false; */
738
811
  let resolvedArr = [];
739
812
  for (const subtable of subTables) {
740
- // TODO types
741
- let rootTable = getNonInlinedTable(table);
742
- const arr = await resolveChildren(fromTablePrefixedValues[getTablePrefixedField(rootTable, rootTable.primary, !tablePrefixed)], subtable);
743
- if (arr) {
744
- 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; */
745
852
  for (const element of arr) {
746
853
  const resolved = await resolveInstanceFromValue(element, tables, subtable, // TODO fix
747
854
  resolveChildren, false, subshape);
@@ -751,12 +858,7 @@ export const resolveInstanceFromValue = async (fromTablePrefixedValues, tables,
751
858
  }
752
859
  }
753
860
  }
754
- if (!once) {
755
- obj[field.key] = undefined;
756
- }
757
- else {
758
- obj[field.key] = resolvedArr;
759
- }
861
+ obj[field.key] = resolvedArr; // we can not do option(vec('T')) since we dont store the option type for Arrays (TODO)
760
862
  }
761
863
  else {
762
864
  // resolve nested object from row directly
@@ -845,14 +947,14 @@ export const fromRowToObj = (row, ctor) => {
845
947
  return Object.assign(Object.create(ctor.prototype), obj);
846
948
  };
847
949
  export const convertDeleteRequestToQuery = (request, tables, table) => {
848
- const { query, bindable } = convertRequestToQuery("delete", request, tables, table);
950
+ const { query, bindable } = convertRequestToQuery("delete", { query: types.toQuery(request.query) }, tables, table);
849
951
  return {
850
952
  sql: `DELETE FROM ${table.name} WHERE ${table.primary} IN (SELECT ${table.primary} from ${table.name} ${query}) returning ${table.primary}`,
851
953
  bindable,
852
954
  };
853
955
  };
854
956
  export const convertSumRequestToQuery = (request, tables, table) => {
855
- const { query, bindable } = convertRequestToQuery("sum", request, tables, table);
957
+ const { query, bindable } = convertRequestToQuery("sum", { query: types.toQuery(request.query), key: request.key }, tables, table);
856
958
  const inlineName = getInlineTableFieldName(request.key);
857
959
  const field = table.fields.find((x) => x.name === inlineName);
858
960
  if (unwrapNestedType(field.from.type) === "u64") {
@@ -865,12 +967,45 @@ export const convertSumRequestToQuery = (request, tables, table) => {
865
967
  };
866
968
  };
867
969
  export const convertCountRequestToQuery = (request, tables, table) => {
868
- const { query, bindable } = convertRequestToQuery("count", request, tables, table);
970
+ const { query, bindable } = convertRequestToQuery("count", { query: request?.query ? types.toQuery(request.query) : undefined }, tables, table);
869
971
  return {
870
972
  sql: `SELECT count(*) as count FROM ${table.name} ${query}`,
871
973
  bindable,
872
974
  };
873
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
+ };
874
1009
  export const convertSearchRequestToQuery = (request, tables, rootTables, options) => {
875
1010
  let unionBuilder = "";
876
1011
  let orderByClause = "";
@@ -879,21 +1014,19 @@ export const convertSearchRequestToQuery = (request, tables, rootTables, options
879
1014
  const selectsPerTable = selectAllFieldsFromTables(rootTables, options?.shape);
880
1015
  let bindableBuilder = [];
881
1016
  for (const [i, table] of rootTables.entries()) {
882
- const { selects, joins: joinFromSelect } = selectsPerTable[i];
883
- const selectQuery = generateSelectQuery(table, selects);
1017
+ const { selects, joins, groupBy } = selectsPerTable[i];
884
1018
  try {
885
- const { orderBy, query, bindable } = convertRequestToQuery("iterate", request, tables, table, joinFromSelect, [], {
886
- stable: options?.stable,
887
- });
888
- unionBuilder += `${unionBuilder.length > 0 ? " UNION ALL " : ""} ${selectQuery} ${query}`;
889
- orderByClause =
890
- orderBy?.length > 0
891
- ? orderByClause.length > 0
892
- ? orderByClause + ", " + orderBy
893
- : orderBy
894
- : orderByClause;
895
- matchedOnce = true;
896
- 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;
897
1030
  }
898
1031
  catch (error) {
899
1032
  if (error instanceof MissingFieldError) {
@@ -902,138 +1035,209 @@ export const convertSearchRequestToQuery = (request, tables, rootTables, options
902
1035
  }
903
1036
  throw error;
904
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
+ }
905
1056
  }
906
1057
  if (!matchedOnce) {
907
1058
  throw lastError;
908
1059
  }
909
1060
  return {
910
- sql: `${unionBuilder} ${orderByClause ? "ORDER BY " + orderByClause : ""} limit ? offset ?`,
1061
+ sql: `${unionBuilder} ${orderByClause ? "ORDER BY " + orderByClause : ""} ${options?.fetchAll ? "" : "limit ? offset ?"}`,
911
1062
  bindable: bindableBuilder,
912
1063
  };
913
1064
  };
914
- function isIterateRequest(request, type) {
915
- return type === "iterate";
916
- }
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
+ };
917
1081
  const convertRequestToQuery = (type, request, tables, table, extraJoin, path = [], options) => {
918
1082
  let whereBuilder = "";
919
1083
  let bindableBuilder = [];
920
- let orderByBuilder = undefined;
1084
+ /* let orderByBuilder: string | undefined = undefined; */
921
1085
  /* let tablesToSelect: string[] = [table.name]; */
922
1086
  let joinBuilder = extraJoin || new Map();
1087
+ getOrSetRootTable(joinBuilder, table);
923
1088
  const coercedQuery = types.toQuery(request?.query);
924
1089
  if (coercedQuery.length === 1) {
925
- const { where, bindable } = convertQueryToSQLQuery(coercedQuery[0], tables, table, joinBuilder, path);
1090
+ const { where, bindable } = convertQueryToSQLQuery(coercedQuery[0], tables, table, joinBuilder, path, undefined, 0);
926
1091
  whereBuilder += where;
927
1092
  bindableBuilder.push(...bindable);
928
1093
  }
929
1094
  else if (coercedQuery.length > 1) {
930
- 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);
931
1096
  whereBuilder += where;
932
1097
  bindableBuilder.push(...bindable);
933
1098
  }
934
- if (isIterateRequest(request, type)) {
1099
+ /* if (isIterateRequest(request, type)) {
935
1100
  let sort = request?.sort;
936
- if (!sort && options?.stable) {
1101
+ if (
1102
+ (!sort || (Array.isArray(sort) && sort.length === 0)) &&
1103
+ !options?.fetchAll
1104
+ ) {
937
1105
  sort =
938
1106
  table.primary && path.length === 0
939
1107
  ? [{ key: [table.primary], direction: types.SortDirection.ASC }]
940
1108
  : undefined;
941
1109
  }
1110
+
942
1111
  if (sort) {
943
1112
  let sortArr = Array.isArray(sort) ? sort : [sort];
944
1113
  if (sortArr.length > 0) {
945
1114
  orderByBuilder = "";
946
1115
  let once = false;
947
1116
  for (const sort of sortArr) {
948
- const { foreignTables, queryKey } = resolveTableToQuery(table, tables, joinBuilder, [...path, ...sort.key], undefined, true);
949
- 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) {
950
1127
  if (once) {
951
1128
  orderByBuilder += ", ";
952
1129
  }
953
1130
  once = true;
954
- 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"}`;
955
1135
  }
956
1136
  }
957
- /* orderByBuilder += request.sort
958
- .map(
959
- (sort) =>
960
- `${table.name}.${sort.key} ${sort.direction === types.SortDirection.ASC ? "ASC" : "DESC"}`
961
- )
962
- .join(", "); */
963
1137
  }
964
1138
  }
965
- }
1139
+ } */
966
1140
  const where = whereBuilder.length > 0 ? "where " + whereBuilder : undefined;
967
1141
  if (extraJoin && extraJoin.size > 0) {
968
1142
  insertMapIntoMap(joinBuilder, extraJoin);
969
1143
  }
970
- let join = buildJoin(joinBuilder, type === "iterate" ? true : false);
1144
+ let { join } = buildJoin(joinBuilder, options);
971
1145
  const query = `${join ? join : ""} ${where ? where : ""}`;
972
1146
  return {
973
1147
  query,
974
- orderBy: orderByBuilder,
1148
+ /* orderBy: orderByBuilder, */
975
1149
  bindable: bindableBuilder,
976
1150
  };
977
1151
  };
978
- export const buildJoin = (joinBuilder, resolveAllColumns) => {
979
- let joinTypeDefault = resolveAllColumns
980
- ? /* "FULL OUTER JOIN" */ "LEFT OUTER JOIN"
981
- : "JOIN";
1152
+ export const buildJoin = (joinBuilder, options) => {
1153
+ /* let joinTypeDefault = resolveAllColumns
1154
+ ? "CROSS JOIN"
1155
+ : "JOIN"; */
982
1156
  let join = "";
983
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") {
984
1186
  let nonInlinedParent = table.table.parent && getNonInlinedTable(table.table.parent);
985
1187
  if (!nonInlinedParent) {
986
1188
  throw new Error("Unexpected: missing parent");
987
1189
  }
988
- let joinType = table.table.referencedInArray
989
- ? /* "FULL OUTER JOIN" */ "LEFT OUTER JOIN"
990
- : joinTypeDefault;
991
- 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} `;
992
1192
  }
993
- return join;
1193
+ else if (indexedBy) {
1194
+ join += indexedBy;
1195
+ }
1196
+ return { join };
994
1197
  };
995
1198
  const insertMapIntoMap = (map, insert) => {
996
1199
  for (const [key, value] of insert) {
997
1200
  map.set(key, value);
998
1201
  }
999
1202
  };
1000
- export const convertQueryToSQLQuery = (query, tables, table, joinBuilder, path = [], tableAlias = undefined) => {
1203
+ export const convertQueryToSQLQuery = (query, tables, table, joinBuilder, path, tableAlias, skipKeys) => {
1001
1204
  let whereBuilder = "";
1002
1205
  let bindableBuilder = [];
1003
1206
  /* let tablesToSelect: string[] = []; */
1004
- const handleAnd = (queries, path, tableAlias) => {
1207
+ const handleAnd = (queries, path, tableAlias, keysOffset) => {
1005
1208
  for (const query of queries) {
1006
- const { where, bindable } = convertQueryToSQLQuery(query, tables, table, joinBuilder, path, tableAlias);
1209
+ const { where, bindable } = convertQueryToSQLQuery(query, tables, table, joinBuilder, path, tableAlias, keysOffset);
1007
1210
  whereBuilder =
1008
1211
  whereBuilder.length > 0 ? `(${whereBuilder}) AND (${where})` : where;
1009
1212
  bindableBuilder.push(...bindable);
1010
1213
  }
1011
1214
  };
1012
1215
  if (query instanceof types.StateFieldQuery) {
1013
- const { where, bindable } = convertStateFieldQuery(query, tables, table, joinBuilder, path, tableAlias);
1216
+ const { where, bindable } = convertStateFieldQuery(query, tables, table, joinBuilder, path, tableAlias, skipKeys);
1014
1217
  whereBuilder += where;
1015
1218
  bindableBuilder.push(...bindable);
1016
1219
  }
1017
1220
  else if (query instanceof types.Nested) {
1018
1221
  let joinPrefix = "__" + String(tables.size);
1019
- path = [...path, query.path];
1020
- handleAnd(query.query, path, joinPrefix);
1222
+ path = [...path, ...query.path];
1223
+ let newSkipKeys = skipKeys + query.path.length;
1224
+ handleAnd(query.query, path, joinPrefix, newSkipKeys);
1021
1225
  }
1022
1226
  else if (query instanceof types.LogicalQuery) {
1023
1227
  if (query instanceof types.And) {
1024
- handleAnd(query.and, path, tableAlias);
1228
+ handleAnd(query.and, path, tableAlias, skipKeys);
1025
1229
  }
1026
1230
  else if (query instanceof types.Or) {
1027
1231
  for (const subquery of query.or) {
1028
- const { where, bindable } = convertQueryToSQLQuery(subquery, tables, table, joinBuilder, path, tableAlias);
1232
+ const { where, bindable } = convertQueryToSQLQuery(subquery, tables, table, joinBuilder, path, tableAlias, skipKeys);
1029
1233
  whereBuilder =
1030
- whereBuilder.length > 0 ? `(${whereBuilder}) OR (${where})` : where;
1234
+ whereBuilder.length > 0 ? `(${whereBuilder}) OR(${where})` : where;
1031
1235
  bindableBuilder.push(...bindable);
1032
1236
  }
1033
1237
  }
1034
1238
  else if (query instanceof types.Not) {
1035
- const { where, bindable } = convertQueryToSQLQuery(query.not, tables, table, joinBuilder, path, tableAlias);
1036
- whereBuilder = `NOT (${where})`;
1239
+ const { where, bindable } = convertQueryToSQLQuery(query.not, tables, table, joinBuilder, path, tableAlias, skipKeys);
1240
+ whereBuilder = `NOT(${where})`;
1037
1241
  bindableBuilder.push(...bindable);
1038
1242
  }
1039
1243
  else {
@@ -1051,16 +1255,26 @@ export const convertQueryToSQLQuery = (query, tables, table, joinBuilder, path =
1051
1255
  const cloneQuery = (query) => {
1052
1256
  return deserialize(serialize(query), types.StateFieldQuery);
1053
1257
  };
1054
- const createTableReferenceName = (table, alias, fieldType, joinSize) => {
1055
- if (!alias &&
1056
- (fieldType instanceof VecKind ||
1057
- (fieldType instanceof OptionKind &&
1058
- fieldType.elementType instanceof VecKind))) {
1059
- 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
1060
1268
  alias = aliasSuffix;
1061
1269
  }
1062
1270
  const tableNameAs = alias ? alias + "_" + table.name : table.name;
1063
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; */
1064
1278
  };
1065
1279
  const resolveTableToQuery = (table, tables, join, path, alias, searchSelf) => {
1066
1280
  // we are matching in two ways.
@@ -1076,11 +1290,18 @@ const resolveTableToQuery = (table, tables, join, path, alias, searchSelf) => {
1076
1290
  if (field) {
1077
1291
  return {
1078
1292
  queryKey: field.name,
1079
- foreignTables: [{ table, as: table.name }],
1293
+ foreignTables: [getOrSetRootTable(join, table)],
1080
1294
  };
1081
1295
  }
1082
1296
  }
1083
- 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
+ ];
1084
1305
  let prevTables = undefined;
1085
1306
  // outer:
1086
1307
  for (const [_i, key] of path /* .slice(0, -1) */
@@ -1091,13 +1312,20 @@ const resolveTableToQuery = (table, tables, join, path, alias, searchSelf) => {
1091
1312
  const field = schema.fields.find((x) => x.key === key);
1092
1313
  if (!field && currentTable.children.length > 0) {
1093
1314
  // second arg is needed because of polymorphic fields we might end up here intentially to check what tables to query
1094
- 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))} `);
1095
1316
  }
1096
1317
  for (const child of currentTable.children) {
1097
- const tableNameAs = createTableReferenceName(child, alias, field.type, join.size);
1318
+ const tableNameAs = createQueryTableReferenceName(child);
1098
1319
  let isMatching = child.parentPath[child.parentPath.length - 1] === key;
1099
1320
  if (isMatching) {
1100
- 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
+ };
1101
1329
  if (child.isSimpleValue) {
1102
1330
  if (!child.inline) {
1103
1331
  join.set(tableNameAs, tableWithAlias);
@@ -1144,13 +1372,17 @@ const resolveTableToQuery = (table, tables, join, path, alias, searchSelf) => {
1144
1372
  : FOREIGN_VALUE_PROPERTY;
1145
1373
  return { queryKey, foreignTables };
1146
1374
  };
1147
- const convertStateFieldQuery = (query, tables, table, join, path, tableAlias = undefined) => {
1375
+ const convertStateFieldQuery = (query, tables, table, join, path, tableAlias, skipKeys) => {
1148
1376
  // if field id represented as foreign table, do join and compare
1149
1377
  const inlinedName = getInlineTableFieldName(query.key);
1150
1378
  const tableField = table.fields.find((x) => x.name === inlinedName); /* stringArraysEquals(query.key, [...table.parentPath, x.name]) )*/
1151
1379
  const isForeign = !tableField; // table.fields.find(x => x.name === query.key[query.key.length - 1])
1152
1380
  if (isForeign) {
1153
- 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);
1154
1386
  query = cloneQuery(query);
1155
1387
  query.key = [queryKey];
1156
1388
  let whereBuilder = [];
@@ -1159,7 +1391,7 @@ const convertStateFieldQuery = (query, tables, table, join, path, tableAlias = u
1159
1391
  if (ftable.table === table) {
1160
1392
  throw new Error("Unexpected");
1161
1393
  }
1162
- 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);
1163
1395
  whereBuilder.push(where);
1164
1396
  bindableBuilder.push(bindable);
1165
1397
  }
@@ -1168,17 +1400,22 @@ const convertStateFieldQuery = (query, tables, table, join, path, tableAlias = u
1168
1400
  bindable: bindableBuilder.flat(),
1169
1401
  };
1170
1402
  }
1403
+ const columnAggregator = join.get(createQueryTableReferenceName(table));
1404
+ if (!columnAggregator) {
1405
+ throw new Error("Unexpected");
1406
+ }
1407
+ columnAggregator.columns.push(inlinedName);
1171
1408
  let bindable = [];
1172
1409
  const keyWithTable = (tableAlias || table.name) + "." + escapeColumnName(inlinedName);
1173
1410
  let where;
1174
1411
  if (query instanceof types.StringMatch) {
1175
1412
  let statement = "";
1176
1413
  if (query.method === types.StringMatchMethod.contains) {
1177
- statement = `${keyWithTable} LIKE ?`;
1414
+ statement = `${keyWithTable} LIKE ? `;
1178
1415
  bindable.push(`%${query.value}%`);
1179
1416
  }
1180
1417
  else if (query.method === types.StringMatchMethod.prefix) {
1181
- statement = `${keyWithTable} LIKE ?`;
1418
+ statement = `${keyWithTable} LIKE ? `;
1182
1419
  bindable.push(`${query.value}%`);
1183
1420
  }
1184
1421
  else if (query.method === types.StringMatchMethod.exact) {
@@ -1199,7 +1436,7 @@ const convertStateFieldQuery = (query, tables, table, join, path, tableAlias = u
1199
1436
  else if (query instanceof types.IntegerCompare) {
1200
1437
  if (tableField.type === "BLOB") {
1201
1438
  // TODO perf
1202
- where = `hex(${keyWithTable}) LIKE ?`;
1439
+ where = `hex(${keyWithTable}) LIKE ? `;
1203
1440
  bindable.push(`%${toHexString(new Uint8Array([Number(query.value.value)]))}%`);
1204
1441
  }
1205
1442
  else {
@@ -1207,23 +1444,23 @@ const convertStateFieldQuery = (query, tables, table, join, path, tableAlias = u
1207
1444
  where = `${keyWithTable} = ?`;
1208
1445
  }
1209
1446
  else if (query.compare === types.Compare.Greater) {
1210
- where = `${keyWithTable} > ?`;
1447
+ where = `${keyWithTable} > ? `;
1211
1448
  }
1212
1449
  else if (query.compare === types.Compare.Less) {
1213
- where = `${keyWithTable} < ?`;
1450
+ where = `${keyWithTable} <?`;
1214
1451
  }
1215
1452
  else if (query.compare === types.Compare.GreaterOrEqual) {
1216
- where = `${keyWithTable} >= ?`;
1453
+ where = `${keyWithTable} >= ? `;
1217
1454
  }
1218
1455
  else if (query.compare === types.Compare.LessOrEqual) {
1219
- where = `${keyWithTable} <= ?`;
1456
+ where = `${keyWithTable} <= ? `;
1220
1457
  }
1221
1458
  else {
1222
- throw new Error(`Unsupported compare type: ${query.compare}`);
1459
+ throw new Error(`Unsupported compare type: ${query.compare} `);
1223
1460
  }
1224
1461
  if (unwrapNestedType(tableField.from.type) === "u64") {
1225
1462
  // shift left because that is how we insert the value
1226
- bindable.push(query.value.value);
1463
+ bindable.push(u64ToI64(query.value.value));
1227
1464
  }
1228
1465
  else {
1229
1466
  bindable.push(query.value.value);
@@ -1242,4 +1479,14 @@ const convertStateFieldQuery = (query, tables, table, join, path, tableAlias = u
1242
1479
  }
1243
1480
  return { where, bindable };
1244
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
+ };
1245
1492
  //# sourceMappingURL=schema.js.map