@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
package/src/schema.ts CHANGED
@@ -14,8 +14,9 @@ import {
14
14
  serialize,
15
15
  variant,
16
16
  } from "@dao-xyz/borsh";
17
- import { toHexString } from "@peerbit/crypto";
17
+ import { fromHexString, toHexString } from "@peerbit/crypto";
18
18
  import * as types from "@peerbit/indexer-interface";
19
+ import { type PlanningSession, flattenQuery } from "./query-planner.js";
19
20
 
20
21
  const SQLConversionMap: any = {
21
22
  u8: "INTEGER",
@@ -54,6 +55,9 @@ export type BindableValue =
54
55
  | ArrayBuffer
55
56
  | null;
56
57
 
58
+ let JSON_GROUP_ARRAY = "json_group_array";
59
+ let JSON_OBJECT = "distinct json_object";
60
+
57
61
  export const u64ToI64 = (u64: bigint | number) => {
58
62
  return (typeof u64 === "number" ? BigInt(u64) : u64) - 9223372036854775808n;
59
63
  };
@@ -80,7 +84,8 @@ export const convertToSQLType = (
80
84
  };
81
85
 
82
86
  const nullAsUndefined = (value: any) => (value === null ? undefined : value);
83
- export const escapeColumnName = (name: string) => `"${name}"`;
87
+ export const escapeColumnName = (name: string, char = '"') =>
88
+ `${char}${name}${char}`;
84
89
 
85
90
  export class MissingFieldError extends Error {
86
91
  constructor(message: string) {
@@ -152,6 +157,7 @@ type SQLField = {
152
157
  type: string;
153
158
  isPrimary: boolean;
154
159
  from: Field | undefined;
160
+ unwrappedType: FieldType | undefined;
155
161
  path: string[];
156
162
  describesExistenceOfAnother?: string;
157
163
  };
@@ -172,6 +178,7 @@ export interface Table {
172
178
  parent: Table | undefined;
173
179
  referencedInArray: boolean;
174
180
  isSimpleValue: boolean;
181
+ indices: Set<string>;
175
182
  }
176
183
 
177
184
  export const getSQLTable = (
@@ -220,6 +227,7 @@ export const getSQLTable = (
220
227
  referencedInArray: false,
221
228
  isSimpleValue: false,
222
229
  inline,
230
+ indices: new Set<string>(),
223
231
  };
224
232
  ret.push(table);
225
233
  for (const dep of dependencies) {
@@ -259,6 +267,14 @@ export const getTableName = (
259
267
  path: string[] = [],
260
268
  clazz: string | Constructor<any>,
261
269
  ) => {
270
+ let pathKey = path.length > 0 ? path.join("__") + "__" : "";
271
+ if (typeof clazz !== "string") {
272
+ const tableName = (clazz as any)["__table_" + pathKey];
273
+ if (tableName) {
274
+ return tableName;
275
+ }
276
+ }
277
+
262
278
  let name: string = typeof clazz === "string" ? clazz : getNameOfClass(clazz);
263
279
 
264
280
  // prefix the generated table name so that the name is a valid SQL identifier (table name)
@@ -266,9 +282,11 @@ export const getTableName = (
266
282
 
267
283
  // leading _ to allow path to have numbers
268
284
 
269
- const ret =
270
- (path.length > 0 ? path.join("__") + "__" : "") +
271
- name.replace(/[^a-zA-Z0-9_]/g, "_");
285
+ const ret = pathKey + name.replace(/[^a-zA-Z0-9_]/g, "_");
286
+
287
+ if (typeof clazz !== "string") {
288
+ (clazz as any)["__table_" + pathKey] = ret;
289
+ }
272
290
  return ret;
273
291
  };
274
292
 
@@ -318,13 +336,14 @@ export const getSQLFields = (
318
336
  ? addJoinFieldFromParent
319
337
  : (fields: SQLField[], contstraints: SQLConstraint[]) => {
320
338
  // we resolve primary field here since it might be unknown until this point
321
- const primaryField =
339
+ const parentPrimaryField =
322
340
  primary != null
323
341
  ? sqlFields.find((field) => field.name === primary)
324
342
  : undefined;
325
- const parentPrimaryFieldName = primaryField?.key || CHILD_TABLE_ID;
326
- const parentPrimaryFieldType = primaryField
327
- ? primaryField.type
343
+ const parentPrimaryFieldName =
344
+ parentPrimaryField?.key || CHILD_TABLE_ID;
345
+ const parentPrimaryFieldType = parentPrimaryField
346
+ ? parentPrimaryField.type
328
347
  : "INTEGER";
329
348
 
330
349
  fields.unshift(
@@ -335,6 +354,7 @@ export const getSQLFields = (
335
354
  type: "INTEGER",
336
355
  isPrimary: true,
337
356
  from: undefined,
357
+ unwrappedType: undefined,
338
358
  path: [CHILD_TABLE_ID],
339
359
  },
340
360
 
@@ -344,8 +364,9 @@ export const getSQLFields = (
344
364
  key: PARENT_TABLE_ID,
345
365
  definition: `${PARENT_TABLE_ID} ${parentPrimaryFieldType}`,
346
366
  type: parentPrimaryFieldType,
367
+ from: parentPrimaryField?.from,
368
+ unwrappedType: parentPrimaryField?.unwrappedType,
347
369
  isPrimary: false,
348
- from: undefined,
349
370
  path: [PARENT_TABLE_ID],
350
371
  },
351
372
  );
@@ -412,6 +433,7 @@ export const getSQLFields = (
412
433
  type: "INTEGER",
413
434
  isPrimary: false,
414
435
  from: undefined,
436
+ unwrappedType: undefined,
415
437
  path: [ARRAY_INDEX_COLUMN],
416
438
  },
417
439
  ...table.fields.slice(2),
@@ -442,6 +464,7 @@ export const getSQLFields = (
442
464
  type: fieldType,
443
465
  isPrimary,
444
466
  from: field,
467
+ unwrappedType: unwrapNestedType(field.type),
445
468
  path: [...path.slice(1), key],
446
469
  });
447
470
  };
@@ -529,6 +552,7 @@ export const getSQLFields = (
529
552
  type: "bool",
530
553
  isPrimary: false,
531
554
  from: undefined,
555
+ unwrappedType: undefined,
532
556
  path: [...path.slice(1), key],
533
557
  describesExistenceOfAnother: path[path.length - 1],
534
558
  });
@@ -630,7 +654,7 @@ const getTableFromValue = (
630
654
  field: Field,
631
655
  value?: any,
632
656
  ): Table => {
633
- let clazzName: string | undefined = undefined;
657
+ let clazzName: string | Constructor<any> | undefined = undefined;
634
658
  if (!isNestedType(field.type)) {
635
659
  clazzName = WRAPPED_SIMPLE_VALUE_VARIANT;
636
660
  } else {
@@ -649,7 +673,7 @@ const getTableFromValue = (
649
673
  continue;
650
674
  }
651
675
  if (ctor) {
652
- clazzName = getNameOfClass(ctor);
676
+ clazzName = ctor;
653
677
  break;
654
678
  }
655
679
  }
@@ -781,7 +805,7 @@ export const insert = async (
781
805
  for (const _field of subTable.fields) {
782
806
  bindableValues.push(null);
783
807
  }
784
- bindableValues[bindableValues.length - 1] = false; // assign the value "false" to the exist field column
808
+ bindableValues[bindableValues.length - 1] = 0; // assign the value "false" to the exist field column
785
809
  continue;
786
810
  }
787
811
 
@@ -790,7 +814,7 @@ export const insert = async (
790
814
  if (table.inline) {
791
815
  bindableValues.push(...values); // insert the bindable values into the parent bindable array
792
816
  if (field.type instanceof OptionKind) {
793
- bindableValues.push(true); // assign the value "true" to the exist field column
817
+ bindableValues.push(1); // assign the value "true" to the exist field column
794
818
  }
795
819
  return undefined;
796
820
  } else {
@@ -906,7 +930,7 @@ export const generateSelectQuery = (
906
930
  table: Table,
907
931
  selects: { from: string; as: string }[],
908
932
  ) => {
909
- return `SELECT ${selects.map((x) => `${x.from} as ${x.as}`).join(", ")} FROM ${table.name}`;
933
+ return `select ${selects.map((x) => `${x.from} as ${x.as}`).join(", ")} FROM ${table.name}`;
910
934
  };
911
935
 
912
936
  export const selectAllFieldsFromTables = (
@@ -918,24 +942,26 @@ export const selectAllFieldsFromTables = (
918
942
  from: string;
919
943
  as: string;
920
944
  }[];
921
- joins: Map<string, JoinTable>;
945
+ joins: Map<string, JoinOrRootTable>;
946
+ groupBy: string | undefined;
922
947
  }[] = [];
923
948
 
924
949
  for (const table of tables) {
925
- const { selects, join: joinFromSelect } = selectAllFieldsFromTable(
926
- table,
927
- shape,
928
- );
929
- selectsPerTable.push({ selects, joins: joinFromSelect });
950
+ const {
951
+ selects,
952
+ join: joinFromSelect,
953
+ groupBy,
954
+ } = selectAllFieldsFromTable(table, shape);
955
+
956
+ selectsPerTable.push({ selects, joins: joinFromSelect, groupBy });
930
957
  }
931
958
 
932
959
  // pad with empty selects to make sure all selects have the same length
933
- /* const maxSelects = Math.max(...selectsPerTable.map(x => x.selects.length)); */
934
-
935
960
  let newSelects: {
936
961
  from: string;
937
962
  as: string;
938
963
  }[][] = [];
964
+
939
965
  for (const [i, selects] of selectsPerTable.entries()) {
940
966
  const newSelect = [];
941
967
  for (const [j, selectsOther] of selectsPerTable.entries()) {
@@ -948,11 +974,6 @@ export const selectAllFieldsFromTables = (
948
974
  }
949
975
  }
950
976
  newSelects.push(newSelect);
951
-
952
- /* let pad = 0;
953
- while (select.selects.length < maxSelects) {
954
- select.selects.push({ from: "NULL", as: `'pad#${++pad}'` });
955
- } */
956
977
  }
957
978
  // also return table name
958
979
  for (const [i, selects] of selectsPerTable.entries()) {
@@ -969,8 +990,67 @@ export const selectAllFieldsFromTable = (
969
990
  let stack: { table: Table; shape?: types.Shape }[] = [{ table, shape }];
970
991
  let join: Map<string, JoinTable> = new Map();
971
992
  const fieldResolvers: { from: string; as: string }[] = [];
993
+ let groupByParentId = false;
972
994
  for (const tableAndShape of stack) {
973
- if (!tableAndShape.table.inline) {
995
+ if (tableAndShape.table.referencedInArray) {
996
+ let selectBuilder = `${JSON_GROUP_ARRAY}(${JSON_OBJECT}(`;
997
+
998
+ groupByParentId = true; // we need to group by the parent id as else we will not be returned with more than 1 result
999
+
1000
+ let first = false;
1001
+ const as = createReconstructReferenceName(tableAndShape.table);
1002
+
1003
+ for (const field of tableAndShape.table.fields) {
1004
+ if (
1005
+ (field.isPrimary ||
1006
+ !tableAndShape.shape ||
1007
+ matchFieldInShape(tableAndShape.shape, [], field) ||
1008
+ // also always include the index field
1009
+ field.name === ARRAY_INDEX_COLUMN) &&
1010
+ field.name !== PARENT_TABLE_ID
1011
+ ) {
1012
+ let resolveField = `${as}.${escapeColumnName(field.name)}`;
1013
+ // 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
1014
+ if (field.unwrappedType === "u64") {
1015
+ resolveField = `CAST(${resolveField} AS TEXT)`;
1016
+ }
1017
+
1018
+ // if field is blob we need to convert it to hex string
1019
+ if (field.type === "BLOB") {
1020
+ resolveField = `HEX(${resolveField})`;
1021
+ }
1022
+
1023
+ if (first) {
1024
+ selectBuilder += `, `;
1025
+ }
1026
+ first = true;
1027
+ selectBuilder += `${escapeColumnName(field.name, "'")}, ${resolveField}`;
1028
+ }
1029
+ }
1030
+ selectBuilder += `)) `; // FILTER (WHERE ${tableAndShape.table.name}.${tableAndShape.table.primary} IS NOT NULL)
1031
+
1032
+ fieldResolvers.push({
1033
+ from: selectBuilder,
1034
+ as,
1035
+ });
1036
+
1037
+ join.set(createReconstructReferenceName(tableAndShape.table), {
1038
+ as,
1039
+ table: tableAndShape.table,
1040
+ type: "left" as const,
1041
+ columns: [],
1042
+ });
1043
+ } else if (!tableAndShape.table.inline) {
1044
+ // we end up here when we have simple joins we want to make that are not arrays, and not inlined
1045
+ if (tableAndShape.table.parent != null) {
1046
+ join.set(createReconstructReferenceName(tableAndShape.table), {
1047
+ as: tableAndShape.table.name,
1048
+ table: tableAndShape.table,
1049
+ type: "left" as const,
1050
+ columns: [],
1051
+ });
1052
+ }
1053
+
974
1054
  for (const field of tableAndShape.table.fields) {
975
1055
  if (
976
1056
  field.isPrimary ||
@@ -986,10 +1066,6 @@ export const selectAllFieldsFromTable = (
986
1066
  }
987
1067
 
988
1068
  for (const child of tableAndShape.table.children) {
989
- if (child.referencedInArray) {
990
- continue;
991
- }
992
-
993
1069
  let childShape: types.Shape | undefined = undefined;
994
1070
  if (tableAndShape.shape) {
995
1071
  const parentPath = child.parentPath?.slice(1);
@@ -1008,11 +1084,7 @@ export const selectAllFieldsFromTable = (
1008
1084
  ? maybeShape[0]
1009
1085
  : maybeShape;
1010
1086
  }
1011
-
1012
1087
  stack.push({ table: child, shape: childShape });
1013
- if (!child.inline) {
1014
- join.set(child.name, { as: child.name, table: child });
1015
- }
1016
1088
  }
1017
1089
  }
1018
1090
 
@@ -1021,6 +1093,10 @@ export const selectAllFieldsFromTable = (
1021
1093
  }
1022
1094
 
1023
1095
  return {
1096
+ groupBy: groupByParentId
1097
+ ? `${table.name}.${escapeColumnName(table.primary as string)}` ||
1098
+ undefined
1099
+ : undefined,
1024
1100
  selects: fieldResolvers, // `SELECT ${fieldResolvers.join(", ")} FROM ${table.name}`,
1025
1101
  join,
1026
1102
  };
@@ -1079,24 +1155,55 @@ export const resolveInstanceFromValue = async <
1079
1155
  : maybeShape;
1080
1156
 
1081
1157
  if (isArray) {
1082
- let once = false;
1158
+ /* let once = false; */
1083
1159
  let resolvedArr = [];
1084
1160
 
1085
1161
  for (const subtable of subTables) {
1086
- // TODO types
1087
- let rootTable = getNonInlinedTable(table);
1088
- const arr = await resolveChildren(
1089
- fromTablePrefixedValues[
1090
- getTablePrefixedField(
1091
- rootTable,
1092
- rootTable.primary as string,
1093
- !tablePrefixed,
1094
- )
1095
- ],
1096
- subtable,
1097
- );
1098
- if (arr) {
1099
- once = true;
1162
+ // check if the array already in the provided row
1163
+ let arr: any[] | undefined = undefined;
1164
+ const tableName = createReconstructReferenceName(subtable);
1165
+ if (fromTablePrefixedValues[tableName]) {
1166
+ arr = JSON.parse(fromTablePrefixedValues[tableName]) as Array<any>;
1167
+ arr = arr.filter((x) => x[subtable.primary as string] != null);
1168
+
1169
+ // we need to go over all fields that are to be bigints and convert
1170
+ // them back to bigints
1171
+ // for blob fields we need to convert them back to Uint8Array
1172
+ for (const field of subtable.fields) {
1173
+ if (field.name === PARENT_TABLE_ID) {
1174
+ continue;
1175
+ }
1176
+ if (field.unwrappedType === "u64") {
1177
+ for (const item of arr!) {
1178
+ item[field.name] = BigInt(item[field.name]);
1179
+ }
1180
+ } else if (field.type === "BLOB") {
1181
+ for (const item of arr!) {
1182
+ item[field.name] = fromHexString(item[field.name]);
1183
+ }
1184
+ }
1185
+ }
1186
+ } else {
1187
+ if (subtable.children) {
1188
+ // TODO we only end up where when we resolve nested arrays,
1189
+ // which shoulld instead be resolved in a nested select (with json_group_array and json_object)
1190
+ let rootTable = getNonInlinedTable(table);
1191
+ const parentId =
1192
+ fromTablePrefixedValues[
1193
+ getTablePrefixedField(
1194
+ rootTable,
1195
+ rootTable.primary as string,
1196
+ !tablePrefixed,
1197
+ )
1198
+ ];
1199
+
1200
+ arr = await resolveChildren(parentId, subtable);
1201
+ } else {
1202
+ arr = [];
1203
+ }
1204
+ }
1205
+ if (arr && arr.length > 0) {
1206
+ /* once = true; */
1100
1207
  for (const element of arr) {
1101
1208
  const resolved: SimpleNested | any = await resolveInstanceFromValue(
1102
1209
  element,
@@ -1114,11 +1221,7 @@ export const resolveInstanceFromValue = async <
1114
1221
  }
1115
1222
  }
1116
1223
 
1117
- if (!once) {
1118
- obj[field.key] = undefined;
1119
- } else {
1120
- obj[field.key] = resolvedArr;
1121
- }
1224
+ obj[field.key] = resolvedArr; // we can not do option(vec('T')) since we dont store the option type for Arrays (TODO)
1122
1225
  } else {
1123
1226
  // resolve nested object from row directly
1124
1227
  /* let extracted: any = {} */
@@ -1251,7 +1354,7 @@ export const convertDeleteRequestToQuery = (
1251
1354
  ): { sql: string; bindable: any[] } => {
1252
1355
  const { query, bindable } = convertRequestToQuery(
1253
1356
  "delete",
1254
- request,
1357
+ { query: types.toQuery(request.query) },
1255
1358
  tables,
1256
1359
  table,
1257
1360
  );
@@ -1268,7 +1371,7 @@ export const convertSumRequestToQuery = (
1268
1371
  ): { sql: string; bindable: any[] } => {
1269
1372
  const { query, bindable } = convertRequestToQuery(
1270
1373
  "sum",
1271
- request,
1374
+ { query: types.toQuery(request.query), key: request.key },
1272
1375
  tables,
1273
1376
  table,
1274
1377
  );
@@ -1293,7 +1396,7 @@ export const convertCountRequestToQuery = (
1293
1396
  ): { sql: string; bindable: any[] } => {
1294
1397
  const { query, bindable } = convertRequestToQuery(
1295
1398
  "count",
1296
- request,
1399
+ { query: request?.query ? types.toQuery(request.query) : undefined },
1297
1400
  tables,
1298
1401
  table,
1299
1402
  );
@@ -1303,13 +1406,76 @@ export const convertCountRequestToQuery = (
1303
1406
  };
1304
1407
  };
1305
1408
 
1409
+ const buildOrderBy = (
1410
+ sort: types.Sort[] | types.Sort | undefined,
1411
+ tables: Map<string, Table>,
1412
+ table: Table,
1413
+ joinBuilder: Map<string, JoinOrRootTable>,
1414
+ resolverBuilder: { from: string; as: string }[],
1415
+ path: string[] = [],
1416
+ options?: {
1417
+ fetchAll?: boolean;
1418
+ planner?: PlanningSession;
1419
+ },
1420
+ ) => {
1421
+ let orderByBuilder: string | undefined = undefined;
1422
+
1423
+ if (
1424
+ (!sort || (Array.isArray(sort) && sort.length === 0)) &&
1425
+ !options?.fetchAll
1426
+ ) {
1427
+ sort =
1428
+ table.primary && path.length === 0
1429
+ ? [{ key: [table.primary], direction: types.SortDirection.ASC }]
1430
+ : undefined;
1431
+ }
1432
+
1433
+ if (sort) {
1434
+ let sortArr = Array.isArray(sort) ? sort : [sort];
1435
+ if (sortArr.length > 0) {
1436
+ orderByBuilder = "";
1437
+ let once = false;
1438
+ for (const sort of sortArr) {
1439
+ const { foreignTables, queryKey } = resolveTableToQuery(
1440
+ table,
1441
+ tables,
1442
+ joinBuilder,
1443
+ [...path, ...sort.key],
1444
+ undefined,
1445
+ true,
1446
+ );
1447
+
1448
+ for (const foreignTable of foreignTables) {
1449
+ if (once) {
1450
+ orderByBuilder += ", ";
1451
+ }
1452
+ once = true;
1453
+
1454
+ foreignTable.columns.push(queryKey); // add the sort key to the list of columns that will be used for this query
1455
+ orderByBuilder += `"${foreignTable.as}#${queryKey}" ${sort.direction === types.SortDirection.ASC ? "ASC" : "DESC"}`;
1456
+
1457
+ resolverBuilder.push({
1458
+ from: `${table.name}.${escapeColumnName(queryKey)}`,
1459
+ as: `'${foreignTable.as}#${queryKey}'`,
1460
+ });
1461
+ }
1462
+ }
1463
+ }
1464
+ }
1465
+
1466
+ return { orderByBuilder };
1467
+ };
1468
+
1306
1469
  export const convertSearchRequestToQuery = (
1307
- request: types.IterateOptions | undefined,
1470
+ request:
1471
+ | { query: types.Query[]; sort?: types.Sort[] | types.Sort }
1472
+ | undefined,
1308
1473
  tables: Map<string, Table>,
1309
1474
  rootTables: Table[],
1310
1475
  options?: {
1311
1476
  shape?: types.Shape | undefined;
1312
- stable?: boolean;
1477
+ fetchAll?: boolean;
1478
+ planner?: PlanningSession;
1313
1479
  },
1314
1480
  ): { sql: string; bindable: any[] } => {
1315
1481
  let unionBuilder = "";
@@ -1320,30 +1486,32 @@ export const convertSearchRequestToQuery = (
1320
1486
 
1321
1487
  const selectsPerTable = selectAllFieldsFromTables(rootTables, options?.shape);
1322
1488
  let bindableBuilder: any[] = [];
1489
+
1323
1490
  for (const [i, table] of rootTables.entries()) {
1324
- const { selects, joins: joinFromSelect } = selectsPerTable[i];
1325
- const selectQuery = generateSelectQuery(table, selects);
1491
+ const { selects, joins, groupBy } = selectsPerTable[i];
1492
+
1326
1493
  try {
1327
- const { orderBy, query, bindable } = convertRequestToQuery(
1328
- "iterate",
1329
- request,
1494
+ const { orderByBuilder } = buildOrderBy(
1495
+ request?.sort,
1330
1496
  tables,
1331
1497
  table,
1332
- joinFromSelect,
1498
+ joins,
1499
+ selects,
1333
1500
  [],
1334
- {
1335
- stable: options?.stable,
1336
- },
1501
+ options,
1337
1502
  );
1338
- unionBuilder += `${unionBuilder.length > 0 ? " UNION ALL " : ""} ${selectQuery} ${query}`;
1339
- orderByClause =
1340
- orderBy?.length > 0
1341
- ? orderByClause.length > 0
1342
- ? orderByClause + ", " + orderBy
1343
- : orderBy
1344
- : orderByClause;
1345
- matchedOnce = true;
1346
- bindableBuilder.push(...bindable);
1503
+
1504
+ if (!orderByClause && orderByBuilder) {
1505
+ // assume all order by clauses will be the same
1506
+ orderByClause =
1507
+ orderByBuilder.length > 0
1508
+ ? orderByClause.length > 0
1509
+ ? orderByClause + ", " + orderByBuilder
1510
+ : orderByBuilder
1511
+ : orderByClause;
1512
+ }
1513
+
1514
+ //orderByAddedOnce = true;
1347
1515
  } catch (error) {
1348
1516
  if (error instanceof MissingFieldError) {
1349
1517
  lastError = error;
@@ -1351,6 +1519,33 @@ export const convertSearchRequestToQuery = (
1351
1519
  }
1352
1520
  throw error;
1353
1521
  }
1522
+
1523
+ const selectQuery = generateSelectQuery(table, selects);
1524
+
1525
+ for (const flattenRequest of flattenQuery(request)) {
1526
+ try {
1527
+ const { query, bindable } = convertRequestToQuery(
1528
+ "iterate",
1529
+ flattenRequest,
1530
+ tables,
1531
+ table,
1532
+ new Map(joins), // copy the map, else we might might do unececessary joins
1533
+ [],
1534
+ options,
1535
+ );
1536
+
1537
+ unionBuilder += `${unionBuilder.length > 0 ? " UNION " : ""} ${selectQuery} ${query} ${groupBy ? "GROUP BY " + groupBy : ""}`;
1538
+ matchedOnce = true;
1539
+ bindableBuilder.push(...bindable);
1540
+ } catch (error) {
1541
+ if (error instanceof MissingFieldError) {
1542
+ lastError = error;
1543
+ orderByClause = "";
1544
+ continue;
1545
+ }
1546
+ throw error;
1547
+ }
1548
+ }
1354
1549
  }
1355
1550
 
1356
1551
  if (!matchedOnce) {
@@ -1358,20 +1553,43 @@ export const convertSearchRequestToQuery = (
1358
1553
  }
1359
1554
 
1360
1555
  return {
1361
- sql: `${unionBuilder} ${orderByClause ? "ORDER BY " + orderByClause : ""} limit ? offset ?`,
1556
+ sql: `${unionBuilder} ${orderByClause ? "ORDER BY " + orderByClause : ""} ${options?.fetchAll ? "" : "limit ? offset ?"}`,
1362
1557
  bindable: bindableBuilder,
1363
1558
  };
1364
1559
  };
1365
1560
 
1366
- type SearchQueryParts = { query: string; orderBy: string; bindable: any[] };
1367
- type CountQueryParts = { query: string; join: string; bindable: any[] };
1561
+ type SearchQueryParts = {
1562
+ query: string;
1563
+ /* orderBy: string; */
1564
+ bindable: any[];
1565
+ selects: string[];
1566
+ };
1567
+ type CountQueryParts = {
1568
+ query: string;
1569
+ join: string;
1570
+ bindable: any[];
1571
+ selects: string[];
1572
+ };
1368
1573
 
1369
- function isIterateRequest(
1370
- request: any,
1371
- type: string,
1372
- ): request is types.IterateOptions | undefined {
1373
- return type === "iterate";
1374
- }
1574
+ const getOrSetRootTable = (
1575
+ joinBuilder: Map<string, JoinOrRootTable>,
1576
+ table: Table,
1577
+ ) => {
1578
+ const refName = createQueryTableReferenceName(table);
1579
+ let ref = joinBuilder.get(refName);
1580
+ if (ref) {
1581
+ return ref;
1582
+ }
1583
+ const join = {
1584
+ // add the root as a join even though it is not, just so we can collect the columns it will be queried
1585
+ table: table,
1586
+ type: "root" as const,
1587
+ as: table.name,
1588
+ columns: [],
1589
+ };
1590
+ joinBuilder.set(refName, join);
1591
+ return join;
1592
+ };
1375
1593
 
1376
1594
  const convertRequestToQuery = <
1377
1595
  T extends "iterate" | "count" | "sum" | "delete",
@@ -1380,26 +1598,40 @@ const convertRequestToQuery = <
1380
1598
  type: T,
1381
1599
  request:
1382
1600
  | (T extends "iterate"
1383
- ? types.IterateOptions
1601
+ ? {
1602
+ query?: types.Query[];
1603
+ sort?: types.Sort[] | types.Sort;
1604
+ }
1384
1605
  : T extends "count"
1385
- ? types.CountOptions
1606
+ ? {
1607
+ query?: types.Query[];
1608
+ }
1386
1609
  : T extends "delete"
1387
- ? types.DeleteOptions
1388
- : types.SumOptions)
1610
+ ? {
1611
+ query?: types.Query[];
1612
+ }
1613
+ : {
1614
+ query?: types.Query[];
1615
+ key: string | string[];
1616
+ })
1389
1617
  | undefined,
1390
1618
  tables: Map<string, Table>,
1391
1619
  table: Table,
1392
- extraJoin?: Map<string, JoinTable>,
1620
+ extraJoin?: Map<string, JoinOrRootTable>,
1393
1621
  path: string[] = [],
1394
1622
  options?: {
1395
- stable?: boolean;
1623
+ fetchAll?: boolean;
1624
+ planner?: PlanningSession;
1396
1625
  },
1397
1626
  ): R => {
1398
1627
  let whereBuilder = "";
1399
1628
  let bindableBuilder: any[] = [];
1400
- let orderByBuilder: string | undefined = undefined;
1629
+ /* let orderByBuilder: string | undefined = undefined; */
1401
1630
  /* let tablesToSelect: string[] = [table.name]; */
1402
- let joinBuilder: Map<string, JoinTable> = extraJoin || new Map();
1631
+ let joinBuilder: Map<string, JoinOrRootTable> = extraJoin || new Map();
1632
+
1633
+ getOrSetRootTable(joinBuilder, table);
1634
+
1403
1635
  const coercedQuery = types.toQuery(request?.query);
1404
1636
  if (coercedQuery.length === 1) {
1405
1637
  const { where, bindable } = convertQueryToSQLQuery(
@@ -1408,6 +1640,8 @@ const convertRequestToQuery = <
1408
1640
  table,
1409
1641
  joinBuilder,
1410
1642
  path,
1643
+ undefined,
1644
+ 0,
1411
1645
  );
1412
1646
  whereBuilder += where;
1413
1647
  bindableBuilder.push(...bindable);
@@ -1418,14 +1652,19 @@ const convertRequestToQuery = <
1418
1652
  table,
1419
1653
  joinBuilder,
1420
1654
  path,
1655
+ undefined,
1656
+ 0,
1421
1657
  );
1422
1658
  whereBuilder += where;
1423
1659
  bindableBuilder.push(...bindable);
1424
1660
  }
1425
1661
 
1426
- if (isIterateRequest(request, type)) {
1662
+ /* if (isIterateRequest(request, type)) {
1427
1663
  let sort = request?.sort;
1428
- if (!sort && options?.stable) {
1664
+ if (
1665
+ (!sort || (Array.isArray(sort) && sort.length === 0)) &&
1666
+ !options?.fetchAll
1667
+ ) {
1429
1668
  sort =
1430
1669
  table.primary && path.length === 0
1431
1670
  ? [{ key: [table.primary], direction: types.SortDirection.ASC }]
@@ -1446,61 +1685,97 @@ const convertRequestToQuery = <
1446
1685
  undefined,
1447
1686
  true,
1448
1687
  );
1449
- for (const table of foreignTables) {
1688
+
1689
+ for (const foreignTable of foreignTables) {
1450
1690
  if (once) {
1451
1691
  orderByBuilder += ", ";
1452
1692
  }
1453
1693
  once = true;
1454
- orderByBuilder += `${table.as}.${queryKey} ${sort.direction === types.SortDirection.ASC ? "ASC" : "DESC"}`;
1694
+
1695
+ foreignTable.columns.push(queryKey); // add the sort key to the list of columns that will be used for this query
1696
+
1697
+ orderByBuilder += `${foreignTable.as}.${queryKey} ${sort.direction === types.SortDirection.ASC ? "ASC" : "DESC"}`;
1455
1698
  }
1456
1699
  }
1457
-
1458
- /* orderByBuilder += request.sort
1459
- .map(
1460
- (sort) =>
1461
- `${table.name}.${sort.key} ${sort.direction === types.SortDirection.ASC ? "ASC" : "DESC"}`
1462
- )
1463
- .join(", "); */
1464
1700
  }
1465
1701
  }
1466
- }
1702
+ } */
1467
1703
  const where = whereBuilder.length > 0 ? "where " + whereBuilder : undefined;
1468
1704
 
1469
1705
  if (extraJoin && extraJoin.size > 0) {
1470
1706
  insertMapIntoMap(joinBuilder, extraJoin);
1471
1707
  }
1472
- let join = buildJoin(joinBuilder, type === "iterate" ? true : false);
1708
+ let { join } = buildJoin(joinBuilder, options);
1473
1709
 
1474
1710
  const query = `${join ? join : ""} ${where ? where : ""}`;
1475
1711
 
1476
1712
  return {
1477
1713
  query,
1478
- orderBy: orderByBuilder,
1714
+ /* orderBy: orderByBuilder, */
1479
1715
  bindable: bindableBuilder,
1480
1716
  } as R;
1481
1717
  };
1482
1718
 
1483
1719
  export const buildJoin = (
1484
- joinBuilder: Map<string, JoinTable>,
1485
- resolveAllColumns: boolean,
1486
- ) => {
1487
- let joinTypeDefault = resolveAllColumns
1488
- ? /* "FULL OUTER JOIN" */ "LEFT OUTER JOIN"
1489
- : "JOIN";
1720
+ joinBuilder: Map<string, JoinOrRootTable>,
1721
+ options?: {
1722
+ planner?: PlanningSession;
1723
+ },
1724
+ ): { join: string } => {
1725
+ /* let joinTypeDefault = resolveAllColumns
1726
+ ? "CROSS JOIN"
1727
+ : "JOIN"; */
1490
1728
  let join = "";
1729
+
1730
+ for (const [_key, table] of joinBuilder) {
1731
+ if (table.type !== "root") {
1732
+ continue;
1733
+ }
1734
+ const out = _buildJoin(table, options);
1735
+ join += out.join;
1736
+ }
1491
1737
  for (const [_key, table] of joinBuilder) {
1738
+ if (table.type === "root") {
1739
+ continue;
1740
+ }
1741
+ const out = _buildJoin(table, options);
1742
+ join += out.join;
1743
+ }
1744
+ return { join };
1745
+ };
1746
+
1747
+ const _buildJoin = (
1748
+ table: JoinOrRootTable,
1749
+ options?: {
1750
+ planner?: PlanningSession;
1751
+ },
1752
+ ) => {
1753
+ let join = "";
1754
+ let indexedBy: string | undefined = undefined;
1755
+ if (table.type !== "root") {
1756
+ table!.columns.push(PARENT_TABLE_ID); // we unshift because we join on the parent id before where clause
1757
+ }
1758
+
1759
+ if (table!.columns.length > 0) {
1760
+ const usedColumns = removeDuplicatesOrdered(table!.columns);
1761
+ indexedBy = options?.planner
1762
+ ? ` INDEXED BY ${options.planner.resolveIndex(table.table.name, usedColumns)} `
1763
+ : "";
1764
+ }
1765
+
1766
+ if (table.type !== "root") {
1492
1767
  let nonInlinedParent =
1493
1768
  table.table.parent && getNonInlinedTable(table.table.parent);
1494
1769
  if (!nonInlinedParent) {
1495
1770
  throw new Error("Unexpected: missing parent");
1496
1771
  }
1497
-
1498
- let joinType = table.table.referencedInArray
1499
- ? /* "FULL OUTER JOIN" */ "LEFT OUTER JOIN"
1500
- : joinTypeDefault;
1501
- join += `${joinType} ${table.table.name} AS ${table.as} ON ${nonInlinedParent.name}.${nonInlinedParent.primary} = ${table.as}.${PARENT_TABLE_ID} `;
1772
+ let joinType = table.type === "cross" ? "LEFT JOIN" : "LEFT JOIN";
1773
+ join += ` ${joinType} ${table.table.name} AS ${table.as} ${indexedBy} ON ${nonInlinedParent.name}.${nonInlinedParent.primary} = ${table.as}.${PARENT_TABLE_ID} `;
1774
+ } else if (indexedBy) {
1775
+ join += indexedBy;
1502
1776
  }
1503
- return join;
1777
+
1778
+ return { join };
1504
1779
  };
1505
1780
 
1506
1781
  const insertMapIntoMap = (map: Map<string, any>, insert: Map<string, any>) => {
@@ -1513,9 +1788,10 @@ export const convertQueryToSQLQuery = (
1513
1788
  query: types.Query,
1514
1789
  tables: Map<string, Table>,
1515
1790
  table: Table,
1516
- joinBuilder: Map<string, JoinTable>,
1517
- path: string[] = [],
1518
- tableAlias: string | undefined = undefined,
1791
+ joinBuilder: Map<string, JoinOrRootTable>,
1792
+ path: string[],
1793
+ tableAlias: string | undefined,
1794
+ skipKeys: number,
1519
1795
  ): { where: string; bindable: any[] } => {
1520
1796
  let whereBuilder = "";
1521
1797
  let bindableBuilder: any[] = [];
@@ -1524,7 +1800,8 @@ export const convertQueryToSQLQuery = (
1524
1800
  const handleAnd = (
1525
1801
  queries: types.Query[],
1526
1802
  path: string[],
1527
- tableAlias?: string,
1803
+ tableAlias: string | undefined,
1804
+ keysOffset: number,
1528
1805
  ) => {
1529
1806
  for (const query of queries) {
1530
1807
  const { where, bindable } = convertQueryToSQLQuery(
@@ -1534,6 +1811,7 @@ export const convertQueryToSQLQuery = (
1534
1811
  joinBuilder,
1535
1812
  path,
1536
1813
  tableAlias,
1814
+ keysOffset,
1537
1815
  );
1538
1816
  whereBuilder =
1539
1817
  whereBuilder.length > 0 ? `(${whereBuilder}) AND (${where})` : where;
@@ -1549,16 +1827,18 @@ export const convertQueryToSQLQuery = (
1549
1827
  joinBuilder,
1550
1828
  path,
1551
1829
  tableAlias,
1830
+ skipKeys,
1552
1831
  );
1553
1832
  whereBuilder += where;
1554
1833
  bindableBuilder.push(...bindable);
1555
1834
  } else if (query instanceof types.Nested) {
1556
1835
  let joinPrefix = "__" + String(tables.size);
1557
- path = [...path, query.path];
1558
- handleAnd(query.query, path, joinPrefix);
1836
+ path = [...path, ...query.path];
1837
+ let newSkipKeys = skipKeys + query.path.length;
1838
+ handleAnd(query.query, path, joinPrefix, newSkipKeys);
1559
1839
  } else if (query instanceof types.LogicalQuery) {
1560
1840
  if (query instanceof types.And) {
1561
- handleAnd(query.and, path, tableAlias);
1841
+ handleAnd(query.and, path, tableAlias, skipKeys);
1562
1842
  } else if (query instanceof types.Or) {
1563
1843
  for (const subquery of query.or) {
1564
1844
  const { where, bindable } = convertQueryToSQLQuery(
@@ -1568,9 +1848,10 @@ export const convertQueryToSQLQuery = (
1568
1848
  joinBuilder,
1569
1849
  path,
1570
1850
  tableAlias,
1851
+ skipKeys,
1571
1852
  );
1572
1853
  whereBuilder =
1573
- whereBuilder.length > 0 ? `(${whereBuilder}) OR (${where})` : where;
1854
+ whereBuilder.length > 0 ? `(${whereBuilder}) OR(${where})` : where;
1574
1855
  bindableBuilder.push(...bindable);
1575
1856
  }
1576
1857
  } else if (query instanceof types.Not) {
@@ -1581,8 +1862,9 @@ export const convertQueryToSQLQuery = (
1581
1862
  joinBuilder,
1582
1863
  path,
1583
1864
  tableAlias,
1865
+ skipKeys,
1584
1866
  );
1585
- whereBuilder = `NOT (${where})`;
1867
+ whereBuilder = `NOT(${where})`;
1586
1868
  bindableBuilder.push(...bindable);
1587
1869
  } else {
1588
1870
  throw new Error("Unsupported query type: " + query.constructor.name);
@@ -1601,38 +1883,54 @@ const cloneQuery = (query: types.StateFieldQuery) => {
1601
1883
  return deserialize(serialize(query), types.StateFieldQuery);
1602
1884
  };
1603
1885
 
1886
+ type JoinOrRootTable = JoinTable | RootTable;
1887
+
1604
1888
  type JoinTable = {
1605
1889
  table: Table;
1606
1890
  as: string;
1891
+ type: "left" | "cross";
1892
+ columns: string[];
1607
1893
  };
1608
1894
 
1609
- const createTableReferenceName = (
1895
+ type RootTable = {
1896
+ type: "root";
1897
+ table: Table;
1898
+ as: string;
1899
+ columns: string[];
1900
+ };
1901
+
1902
+ /* const createQueryTableReferenceName = (
1610
1903
  table: Table,
1611
1904
  alias: string | undefined,
1612
- fieldType: FieldType,
1613
- joinSize: number,
1614
1905
  ) => {
1906
+
1615
1907
  if (
1616
- !alias &&
1617
- (fieldType instanceof VecKind ||
1618
- (fieldType instanceof OptionKind &&
1619
- fieldType.elementType instanceof VecKind))
1908
+ !alias
1620
1909
  ) {
1621
- let aliasSuffix = "_" + String(joinSize);
1910
+ let aliasSuffix =
1911
+ "_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
1622
1912
  alias = aliasSuffix;
1623
1913
  }
1624
1914
  const tableNameAs = alias ? alias + "_" + table.name : table.name;
1625
1915
  return tableNameAs;
1916
+ }; */
1917
+
1918
+ const createQueryTableReferenceName = (table: Table) => {
1919
+ return table.parent == null ? table.name : "_query_" + table.name;
1920
+ };
1921
+
1922
+ const createReconstructReferenceName = (table: Table) => {
1923
+ return table.name; /* table.parent == null ? table.name : "_rec_" + table.name; */
1626
1924
  };
1627
1925
 
1628
1926
  const resolveTableToQuery = (
1629
1927
  table: Table,
1630
1928
  tables: Map<string, Table>,
1631
- join: Map<string, JoinTable>,
1929
+ join: Map<string, JoinOrRootTable>,
1632
1930
  path: string[],
1633
1931
  alias: string | undefined,
1634
1932
  searchSelf: boolean,
1635
- ) => {
1933
+ ): { queryKey: string; foreignTables: JoinOrRootTable[] } => {
1636
1934
  // we are matching in two ways.
1637
1935
 
1638
1936
  // 1. joins
@@ -1649,12 +1947,19 @@ const resolveTableToQuery = (
1649
1947
  if (field) {
1650
1948
  return {
1651
1949
  queryKey: field.name,
1652
- foreignTables: [{ table, as: table.name }],
1950
+ foreignTables: [getOrSetRootTable(join, table)],
1653
1951
  };
1654
1952
  }
1655
1953
  }
1656
1954
 
1657
- let currentTables: JoinTable[] = [{ table, as: alias || table.name }];
1955
+ let currentTables: JoinTable[] = [
1956
+ {
1957
+ table,
1958
+ as: alias || table.name,
1959
+ type: "cross" as const,
1960
+ columns: [],
1961
+ },
1962
+ ];
1658
1963
  let prevTables: JoinTable[] | undefined = undefined;
1659
1964
 
1660
1965
  // outer:
@@ -1667,20 +1972,29 @@ const resolveTableToQuery = (
1667
1972
  if (!field && currentTable.children.length > 0) {
1668
1973
  // second arg is needed because of polymorphic fields we might end up here intentially to check what tables to query
1669
1974
  throw new MissingFieldError(
1670
- `Property with key "${key}" is not found in the schema ${JSON.stringify(schema.fields.map((x) => x.key))}`,
1975
+ `Property with key "${key}" is not found in the schema ${JSON.stringify(schema.fields.map((x) => x.key))} `,
1671
1976
  );
1672
1977
  }
1673
1978
  for (const child of currentTable.children) {
1674
- const tableNameAs = createTableReferenceName(
1979
+ const tableNameAs = createQueryTableReferenceName(
1675
1980
  child,
1676
- alias,
1981
+ /* alias */ /* ,
1677
1982
  field.type,
1678
- join.size,
1983
+ join.size, */
1679
1984
  );
1985
+
1680
1986
  let isMatching =
1681
1987
  child.parentPath![child.parentPath!.length - 1] === key;
1682
1988
  if (isMatching) {
1683
- const tableWithAlias = { table: child, as: tableNameAs };
1989
+ const tableWithAlias = {
1990
+ columns: [],
1991
+ table: child,
1992
+ as: tableNameAs,
1993
+ type:
1994
+ currentTable.children.length > 1
1995
+ ? ("left" as const)
1996
+ : ("cross" as const),
1997
+ };
1684
1998
  if (child.isSimpleValue) {
1685
1999
  if (!child.inline) {
1686
2000
  join.set(tableNameAs, tableWithAlias);
@@ -1744,9 +2058,10 @@ const convertStateFieldQuery = (
1744
2058
  query: types.StateFieldQuery,
1745
2059
  tables: Map<string, Table>,
1746
2060
  table: Table,
1747
- join: Map<string, JoinTable>,
2061
+ join: Map<string, JoinOrRootTable>,
1748
2062
  path: string[],
1749
- tableAlias: string | undefined = undefined,
2063
+ tableAlias: string | undefined,
2064
+ skipKeys: number,
1750
2065
  ): { where: string; bindable: any[] } => {
1751
2066
  // if field id represented as foreign table, do join and compare
1752
2067
  const inlinedName = getInlineTableFieldName(query.key);
@@ -1755,11 +2070,15 @@ const convertStateFieldQuery = (
1755
2070
  ); /* stringArraysEquals(query.key, [...table.parentPath, x.name]) )*/
1756
2071
  const isForeign = !tableField; // table.fields.find(x => x.name === query.key[query.key.length - 1])
1757
2072
  if (isForeign) {
2073
+ const tablePath: string[] = [...path];
2074
+ for (let i = skipKeys; i < query.key.length; i++) {
2075
+ tablePath.push(query.key[i]);
2076
+ }
1758
2077
  const { queryKey, foreignTables } = resolveTableToQuery(
1759
2078
  table,
1760
2079
  tables,
1761
2080
  join,
1762
- [...path, ...query.key],
2081
+ tablePath,
1763
2082
  tableAlias,
1764
2083
  false,
1765
2084
  );
@@ -1772,6 +2091,7 @@ const convertStateFieldQuery = (
1772
2091
  if (ftable.table === table) {
1773
2092
  throw new Error("Unexpected");
1774
2093
  }
2094
+
1775
2095
  const { where, bindable } = convertQueryToSQLQuery(
1776
2096
  query,
1777
2097
  tables,
@@ -1779,6 +2099,7 @@ const convertStateFieldQuery = (
1779
2099
  join,
1780
2100
  path,
1781
2101
  ftable.as,
2102
+ skipKeys,
1782
2103
  );
1783
2104
  whereBuilder.push(where);
1784
2105
  bindableBuilder.push(bindable);
@@ -1789,6 +2110,12 @@ const convertStateFieldQuery = (
1789
2110
  };
1790
2111
  }
1791
2112
 
2113
+ const columnAggregator = join.get(createQueryTableReferenceName(table))!;
2114
+ if (!columnAggregator) {
2115
+ throw new Error("Unexpected");
2116
+ }
2117
+ columnAggregator.columns.push(inlinedName);
2118
+
1792
2119
  let bindable: any[] = [];
1793
2120
  const keyWithTable =
1794
2121
  (tableAlias || table.name) + "." + escapeColumnName(inlinedName);
@@ -1797,10 +2124,10 @@ const convertStateFieldQuery = (
1797
2124
  let statement = "";
1798
2125
 
1799
2126
  if (query.method === types.StringMatchMethod.contains) {
1800
- statement = `${keyWithTable} LIKE ?`;
2127
+ statement = `${keyWithTable} LIKE ? `;
1801
2128
  bindable.push(`%${query.value}%`);
1802
2129
  } else if (query.method === types.StringMatchMethod.prefix) {
1803
- statement = `${keyWithTable} LIKE ?`;
2130
+ statement = `${keyWithTable} LIKE ? `;
1804
2131
  bindable.push(`${query.value}%`);
1805
2132
  } else if (query.method === types.StringMatchMethod.exact) {
1806
2133
  statement = `${keyWithTable} = ?`;
@@ -1819,7 +2146,7 @@ const convertStateFieldQuery = (
1819
2146
  } else if (query instanceof types.IntegerCompare) {
1820
2147
  if (tableField!.type === "BLOB") {
1821
2148
  // TODO perf
1822
- where = `hex(${keyWithTable}) LIKE ?`;
2149
+ where = `hex(${keyWithTable}) LIKE ? `;
1823
2150
  bindable.push(
1824
2151
  `%${toHexString(new Uint8Array([Number(query.value.value)]))}%`,
1825
2152
  );
@@ -1827,15 +2154,15 @@ const convertStateFieldQuery = (
1827
2154
  if (query.compare === types.Compare.Equal) {
1828
2155
  where = `${keyWithTable} = ?`;
1829
2156
  } else if (query.compare === types.Compare.Greater) {
1830
- where = `${keyWithTable} > ?`;
2157
+ where = `${keyWithTable} > ? `;
1831
2158
  } else if (query.compare === types.Compare.Less) {
1832
- where = `${keyWithTable} < ?`;
2159
+ where = `${keyWithTable} <?`;
1833
2160
  } else if (query.compare === types.Compare.GreaterOrEqual) {
1834
- where = `${keyWithTable} >= ?`;
2161
+ where = `${keyWithTable} >= ? `;
1835
2162
  } else if (query.compare === types.Compare.LessOrEqual) {
1836
- where = `${keyWithTable} <= ?`;
2163
+ where = `${keyWithTable} <= ? `;
1837
2164
  } else {
1838
- throw new Error(`Unsupported compare type: ${query.compare}`);
2165
+ throw new Error(`Unsupported compare type: ${query.compare} `);
1839
2166
  }
1840
2167
 
1841
2168
  if (unwrapNestedType(tableField.from!.type) === "u64") {
@@ -1855,3 +2182,14 @@ const convertStateFieldQuery = (
1855
2182
  }
1856
2183
  return { where, bindable };
1857
2184
  };
2185
+
2186
+ const removeDuplicatesOrdered = (arr: string[]) => {
2187
+ let seen = new Set();
2188
+ return arr.filter((item) => {
2189
+ if (seen.has(item)) {
2190
+ return false;
2191
+ }
2192
+ seen.add(item);
2193
+ return true;
2194
+ });
2195
+ };