@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
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,15 @@ 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
+
61
+ export const u64ToI64 = (u64: bigint | number) => {
62
+ return (typeof u64 === "number" ? BigInt(u64) : u64) - 9223372036854775808n;
63
+ };
64
+ export const i64ToU64 = (i64: number | bigint) =>
65
+ (typeof i64 === "number" ? BigInt(i64) : i64) + 9223372036854775808n;
66
+
57
67
  export const convertToSQLType = (
58
68
  value: boolean | bigint | string | number | Uint8Array,
59
69
  type?: FieldType,
@@ -64,12 +74,18 @@ export const convertToSQLType = (
64
74
  if (type === "bool") {
65
75
  return value ? 1 : 0;
66
76
  }
77
+ if (type === "u64") {
78
+ // shift to fit in i64
79
+
80
+ return u64ToI64(value as number | bigint);
81
+ }
67
82
  }
68
83
  return value as BindableValue;
69
84
  };
70
85
 
71
86
  const nullAsUndefined = (value: any) => (value === null ? undefined : value);
72
- export const escapeColumnName = (name: string) => `"${name}"`;
87
+ export const escapeColumnName = (name: string, char = '"') =>
88
+ `${char}${name}${char}`;
73
89
 
74
90
  export class MissingFieldError extends Error {
75
91
  constructor(message: string) {
@@ -100,9 +116,15 @@ export const convertFromSQLType = (
100
116
  : nullAsUndefined(value);
101
117
  }
102
118
  if (type === "u64") {
103
- return typeof value === "number" || typeof value === "string"
104
- ? BigInt(value)
105
- : nullAsUndefined(value);
119
+ if (typeof value === "number" || typeof value === "bigint") {
120
+ return i64ToU64(value as number | bigint); // TODO is not always value type bigint?
121
+ }
122
+ if (value == null) {
123
+ return nullAsUndefined(value);
124
+ }
125
+ throw new Error(
126
+ `Unexpected value type for value ${value} expected number or bigint for u64 field`,
127
+ );
106
128
  }
107
129
  return nullAsUndefined(value);
108
130
  };
@@ -135,6 +157,7 @@ type SQLField = {
135
157
  type: string;
136
158
  isPrimary: boolean;
137
159
  from: Field | undefined;
160
+ unwrappedType: FieldType | undefined;
138
161
  path: string[];
139
162
  describesExistenceOfAnother?: string;
140
163
  };
@@ -155,6 +178,7 @@ export interface Table {
155
178
  parent: Table | undefined;
156
179
  referencedInArray: boolean;
157
180
  isSimpleValue: boolean;
181
+ indices: Set<string>;
158
182
  }
159
183
 
160
184
  export const getSQLTable = (
@@ -203,6 +227,7 @@ export const getSQLTable = (
203
227
  referencedInArray: false,
204
228
  isSimpleValue: false,
205
229
  inline,
230
+ indices: new Set<string>(),
206
231
  };
207
232
  ret.push(table);
208
233
  for (const dep of dependencies) {
@@ -242,6 +267,14 @@ export const getTableName = (
242
267
  path: string[] = [],
243
268
  clazz: string | Constructor<any>,
244
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
+
245
278
  let name: string = typeof clazz === "string" ? clazz : getNameOfClass(clazz);
246
279
 
247
280
  // prefix the generated table name so that the name is a valid SQL identifier (table name)
@@ -249,9 +282,11 @@ export const getTableName = (
249
282
 
250
283
  // leading _ to allow path to have numbers
251
284
 
252
- const ret =
253
- (path.length > 0 ? path.join("__") + "__" : "") +
254
- 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
+ }
255
290
  return ret;
256
291
  };
257
292
 
@@ -301,13 +336,14 @@ export const getSQLFields = (
301
336
  ? addJoinFieldFromParent
302
337
  : (fields: SQLField[], contstraints: SQLConstraint[]) => {
303
338
  // we resolve primary field here since it might be unknown until this point
304
- const primaryField =
339
+ const parentPrimaryField =
305
340
  primary != null
306
341
  ? sqlFields.find((field) => field.name === primary)
307
342
  : undefined;
308
- const parentPrimaryFieldName = primaryField?.key || CHILD_TABLE_ID;
309
- const parentPrimaryFieldType = primaryField
310
- ? primaryField.type
343
+ const parentPrimaryFieldName =
344
+ parentPrimaryField?.key || CHILD_TABLE_ID;
345
+ const parentPrimaryFieldType = parentPrimaryField
346
+ ? parentPrimaryField.type
311
347
  : "INTEGER";
312
348
 
313
349
  fields.unshift(
@@ -318,6 +354,7 @@ export const getSQLFields = (
318
354
  type: "INTEGER",
319
355
  isPrimary: true,
320
356
  from: undefined,
357
+ unwrappedType: undefined,
321
358
  path: [CHILD_TABLE_ID],
322
359
  },
323
360
 
@@ -327,8 +364,9 @@ export const getSQLFields = (
327
364
  key: PARENT_TABLE_ID,
328
365
  definition: `${PARENT_TABLE_ID} ${parentPrimaryFieldType}`,
329
366
  type: parentPrimaryFieldType,
367
+ from: parentPrimaryField?.from,
368
+ unwrappedType: parentPrimaryField?.unwrappedType,
330
369
  isPrimary: false,
331
- from: undefined,
332
370
  path: [PARENT_TABLE_ID],
333
371
  },
334
372
  );
@@ -395,6 +433,7 @@ export const getSQLFields = (
395
433
  type: "INTEGER",
396
434
  isPrimary: false,
397
435
  from: undefined,
436
+ unwrappedType: undefined,
398
437
  path: [ARRAY_INDEX_COLUMN],
399
438
  },
400
439
  ...table.fields.slice(2),
@@ -425,6 +464,7 @@ export const getSQLFields = (
425
464
  type: fieldType,
426
465
  isPrimary,
427
466
  from: field,
467
+ unwrappedType: unwrapNestedType(field.type),
428
468
  path: [...path.slice(1), key],
429
469
  });
430
470
  };
@@ -512,6 +552,7 @@ export const getSQLFields = (
512
552
  type: "bool",
513
553
  isPrimary: false,
514
554
  from: undefined,
555
+ unwrappedType: undefined,
515
556
  path: [...path.slice(1), key],
516
557
  describesExistenceOfAnother: path[path.length - 1],
517
558
  });
@@ -613,7 +654,7 @@ const getTableFromValue = (
613
654
  field: Field,
614
655
  value?: any,
615
656
  ): Table => {
616
- let clazzName: string | undefined = undefined;
657
+ let clazzName: string | Constructor<any> | undefined = undefined;
617
658
  if (!isNestedType(field.type)) {
618
659
  clazzName = WRAPPED_SIMPLE_VALUE_VARIANT;
619
660
  } else {
@@ -632,7 +673,7 @@ const getTableFromValue = (
632
673
  continue;
633
674
  }
634
675
  if (ctor) {
635
- clazzName = getNameOfClass(ctor);
676
+ clazzName = ctor;
636
677
  break;
637
678
  }
638
679
  }
@@ -764,7 +805,7 @@ export const insert = async (
764
805
  for (const _field of subTable.fields) {
765
806
  bindableValues.push(null);
766
807
  }
767
- 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
768
809
  continue;
769
810
  }
770
811
 
@@ -773,7 +814,7 @@ export const insert = async (
773
814
  if (table.inline) {
774
815
  bindableValues.push(...values); // insert the bindable values into the parent bindable array
775
816
  if (field.type instanceof OptionKind) {
776
- 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
777
818
  }
778
819
  return undefined;
779
820
  } else {
@@ -889,7 +930,7 @@ export const generateSelectQuery = (
889
930
  table: Table,
890
931
  selects: { from: string; as: string }[],
891
932
  ) => {
892
- 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}`;
893
934
  };
894
935
 
895
936
  export const selectAllFieldsFromTables = (
@@ -901,24 +942,26 @@ export const selectAllFieldsFromTables = (
901
942
  from: string;
902
943
  as: string;
903
944
  }[];
904
- joins: Map<string, JoinTable>;
945
+ joins: Map<string, JoinOrRootTable>;
946
+ groupBy: string | undefined;
905
947
  }[] = [];
906
948
 
907
949
  for (const table of tables) {
908
- const { selects, join: joinFromSelect } = selectAllFieldsFromTable(
909
- table,
910
- shape,
911
- );
912
- 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 });
913
957
  }
914
958
 
915
959
  // pad with empty selects to make sure all selects have the same length
916
- /* const maxSelects = Math.max(...selectsPerTable.map(x => x.selects.length)); */
917
-
918
960
  let newSelects: {
919
961
  from: string;
920
962
  as: string;
921
963
  }[][] = [];
964
+
922
965
  for (const [i, selects] of selectsPerTable.entries()) {
923
966
  const newSelect = [];
924
967
  for (const [j, selectsOther] of selectsPerTable.entries()) {
@@ -931,11 +974,6 @@ export const selectAllFieldsFromTables = (
931
974
  }
932
975
  }
933
976
  newSelects.push(newSelect);
934
-
935
- /* let pad = 0;
936
- while (select.selects.length < maxSelects) {
937
- select.selects.push({ from: "NULL", as: `'pad#${++pad}'` });
938
- } */
939
977
  }
940
978
  // also return table name
941
979
  for (const [i, selects] of selectsPerTable.entries()) {
@@ -952,8 +990,67 @@ export const selectAllFieldsFromTable = (
952
990
  let stack: { table: Table; shape?: types.Shape }[] = [{ table, shape }];
953
991
  let join: Map<string, JoinTable> = new Map();
954
992
  const fieldResolvers: { from: string; as: string }[] = [];
993
+ let groupByParentId = false;
955
994
  for (const tableAndShape of stack) {
956
- 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
+
957
1054
  for (const field of tableAndShape.table.fields) {
958
1055
  if (
959
1056
  field.isPrimary ||
@@ -969,10 +1066,6 @@ export const selectAllFieldsFromTable = (
969
1066
  }
970
1067
 
971
1068
  for (const child of tableAndShape.table.children) {
972
- if (child.referencedInArray) {
973
- continue;
974
- }
975
-
976
1069
  let childShape: types.Shape | undefined = undefined;
977
1070
  if (tableAndShape.shape) {
978
1071
  const parentPath = child.parentPath?.slice(1);
@@ -991,11 +1084,7 @@ export const selectAllFieldsFromTable = (
991
1084
  ? maybeShape[0]
992
1085
  : maybeShape;
993
1086
  }
994
-
995
1087
  stack.push({ table: child, shape: childShape });
996
- if (!child.inline) {
997
- join.set(child.name, { as: child.name, table: child });
998
- }
999
1088
  }
1000
1089
  }
1001
1090
 
@@ -1004,6 +1093,10 @@ export const selectAllFieldsFromTable = (
1004
1093
  }
1005
1094
 
1006
1095
  return {
1096
+ groupBy: groupByParentId
1097
+ ? `${table.name}.${escapeColumnName(table.primary as string)}` ||
1098
+ undefined
1099
+ : undefined,
1007
1100
  selects: fieldResolvers, // `SELECT ${fieldResolvers.join(", ")} FROM ${table.name}`,
1008
1101
  join,
1009
1102
  };
@@ -1062,24 +1155,55 @@ export const resolveInstanceFromValue = async <
1062
1155
  : maybeShape;
1063
1156
 
1064
1157
  if (isArray) {
1065
- let once = false;
1158
+ /* let once = false; */
1066
1159
  let resolvedArr = [];
1067
1160
 
1068
1161
  for (const subtable of subTables) {
1069
- // TODO types
1070
- let rootTable = getNonInlinedTable(table);
1071
- const arr = await resolveChildren(
1072
- fromTablePrefixedValues[
1073
- getTablePrefixedField(
1074
- rootTable,
1075
- rootTable.primary as string,
1076
- !tablePrefixed,
1077
- )
1078
- ],
1079
- subtable,
1080
- );
1081
- if (arr) {
1082
- 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; */
1083
1207
  for (const element of arr) {
1084
1208
  const resolved: SimpleNested | any = await resolveInstanceFromValue(
1085
1209
  element,
@@ -1097,11 +1221,7 @@ export const resolveInstanceFromValue = async <
1097
1221
  }
1098
1222
  }
1099
1223
 
1100
- if (!once) {
1101
- obj[field.key] = undefined;
1102
- } else {
1103
- obj[field.key] = resolvedArr;
1104
- }
1224
+ obj[field.key] = resolvedArr; // we can not do option(vec('T')) since we dont store the option type for Arrays (TODO)
1105
1225
  } else {
1106
1226
  // resolve nested object from row directly
1107
1227
  /* let extracted: any = {} */
@@ -1234,7 +1354,7 @@ export const convertDeleteRequestToQuery = (
1234
1354
  ): { sql: string; bindable: any[] } => {
1235
1355
  const { query, bindable } = convertRequestToQuery(
1236
1356
  "delete",
1237
- request,
1357
+ { query: types.toQuery(request.query) },
1238
1358
  tables,
1239
1359
  table,
1240
1360
  );
@@ -1251,7 +1371,7 @@ export const convertSumRequestToQuery = (
1251
1371
  ): { sql: string; bindable: any[] } => {
1252
1372
  const { query, bindable } = convertRequestToQuery(
1253
1373
  "sum",
1254
- request,
1374
+ { query: types.toQuery(request.query), key: request.key },
1255
1375
  tables,
1256
1376
  table,
1257
1377
  );
@@ -1276,7 +1396,7 @@ export const convertCountRequestToQuery = (
1276
1396
  ): { sql: string; bindable: any[] } => {
1277
1397
  const { query, bindable } = convertRequestToQuery(
1278
1398
  "count",
1279
- request,
1399
+ { query: request?.query ? types.toQuery(request.query) : undefined },
1280
1400
  tables,
1281
1401
  table,
1282
1402
  );
@@ -1286,13 +1406,76 @@ export const convertCountRequestToQuery = (
1286
1406
  };
1287
1407
  };
1288
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
+
1289
1469
  export const convertSearchRequestToQuery = (
1290
- request: types.IterateOptions | undefined,
1470
+ request:
1471
+ | { query: types.Query[]; sort?: types.Sort[] | types.Sort }
1472
+ | undefined,
1291
1473
  tables: Map<string, Table>,
1292
1474
  rootTables: Table[],
1293
1475
  options?: {
1294
1476
  shape?: types.Shape | undefined;
1295
- stable?: boolean;
1477
+ fetchAll?: boolean;
1478
+ planner?: PlanningSession;
1296
1479
  },
1297
1480
  ): { sql: string; bindable: any[] } => {
1298
1481
  let unionBuilder = "";
@@ -1303,30 +1486,32 @@ export const convertSearchRequestToQuery = (
1303
1486
 
1304
1487
  const selectsPerTable = selectAllFieldsFromTables(rootTables, options?.shape);
1305
1488
  let bindableBuilder: any[] = [];
1489
+
1306
1490
  for (const [i, table] of rootTables.entries()) {
1307
- const { selects, joins: joinFromSelect } = selectsPerTable[i];
1308
- const selectQuery = generateSelectQuery(table, selects);
1491
+ const { selects, joins, groupBy } = selectsPerTable[i];
1492
+
1309
1493
  try {
1310
- const { orderBy, query, bindable } = convertRequestToQuery(
1311
- "iterate",
1312
- request,
1494
+ const { orderByBuilder } = buildOrderBy(
1495
+ request?.sort,
1313
1496
  tables,
1314
1497
  table,
1315
- joinFromSelect,
1498
+ joins,
1499
+ selects,
1316
1500
  [],
1317
- {
1318
- stable: options?.stable,
1319
- },
1501
+ options,
1320
1502
  );
1321
- unionBuilder += `${unionBuilder.length > 0 ? " UNION ALL " : ""} ${selectQuery} ${query}`;
1322
- orderByClause =
1323
- orderBy?.length > 0
1324
- ? orderByClause.length > 0
1325
- ? orderByClause + ", " + orderBy
1326
- : orderBy
1327
- : orderByClause;
1328
- matchedOnce = true;
1329
- 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;
1330
1515
  } catch (error) {
1331
1516
  if (error instanceof MissingFieldError) {
1332
1517
  lastError = error;
@@ -1334,6 +1519,33 @@ export const convertSearchRequestToQuery = (
1334
1519
  }
1335
1520
  throw error;
1336
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
+ }
1337
1549
  }
1338
1550
 
1339
1551
  if (!matchedOnce) {
@@ -1341,20 +1553,43 @@ export const convertSearchRequestToQuery = (
1341
1553
  }
1342
1554
 
1343
1555
  return {
1344
- sql: `${unionBuilder} ${orderByClause ? "ORDER BY " + orderByClause : ""} limit ? offset ?`,
1556
+ sql: `${unionBuilder} ${orderByClause ? "ORDER BY " + orderByClause : ""} ${options?.fetchAll ? "" : "limit ? offset ?"}`,
1345
1557
  bindable: bindableBuilder,
1346
1558
  };
1347
1559
  };
1348
1560
 
1349
- type SearchQueryParts = { query: string; orderBy: string; bindable: any[] };
1350
- 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
+ };
1351
1573
 
1352
- function isIterateRequest(
1353
- request: any,
1354
- type: string,
1355
- ): request is types.IterateOptions | undefined {
1356
- return type === "iterate";
1357
- }
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
+ };
1358
1593
 
1359
1594
  const convertRequestToQuery = <
1360
1595
  T extends "iterate" | "count" | "sum" | "delete",
@@ -1363,26 +1598,40 @@ const convertRequestToQuery = <
1363
1598
  type: T,
1364
1599
  request:
1365
1600
  | (T extends "iterate"
1366
- ? types.IterateOptions
1601
+ ? {
1602
+ query?: types.Query[];
1603
+ sort?: types.Sort[] | types.Sort;
1604
+ }
1367
1605
  : T extends "count"
1368
- ? types.CountOptions
1606
+ ? {
1607
+ query?: types.Query[];
1608
+ }
1369
1609
  : T extends "delete"
1370
- ? types.DeleteOptions
1371
- : types.SumOptions)
1610
+ ? {
1611
+ query?: types.Query[];
1612
+ }
1613
+ : {
1614
+ query?: types.Query[];
1615
+ key: string | string[];
1616
+ })
1372
1617
  | undefined,
1373
1618
  tables: Map<string, Table>,
1374
1619
  table: Table,
1375
- extraJoin?: Map<string, JoinTable>,
1620
+ extraJoin?: Map<string, JoinOrRootTable>,
1376
1621
  path: string[] = [],
1377
1622
  options?: {
1378
- stable?: boolean;
1623
+ fetchAll?: boolean;
1624
+ planner?: PlanningSession;
1379
1625
  },
1380
1626
  ): R => {
1381
1627
  let whereBuilder = "";
1382
1628
  let bindableBuilder: any[] = [];
1383
- let orderByBuilder: string | undefined = undefined;
1629
+ /* let orderByBuilder: string | undefined = undefined; */
1384
1630
  /* let tablesToSelect: string[] = [table.name]; */
1385
- let joinBuilder: Map<string, JoinTable> = extraJoin || new Map();
1631
+ let joinBuilder: Map<string, JoinOrRootTable> = extraJoin || new Map();
1632
+
1633
+ getOrSetRootTable(joinBuilder, table);
1634
+
1386
1635
  const coercedQuery = types.toQuery(request?.query);
1387
1636
  if (coercedQuery.length === 1) {
1388
1637
  const { where, bindable } = convertQueryToSQLQuery(
@@ -1391,6 +1640,8 @@ const convertRequestToQuery = <
1391
1640
  table,
1392
1641
  joinBuilder,
1393
1642
  path,
1643
+ undefined,
1644
+ 0,
1394
1645
  );
1395
1646
  whereBuilder += where;
1396
1647
  bindableBuilder.push(...bindable);
@@ -1401,14 +1652,19 @@ const convertRequestToQuery = <
1401
1652
  table,
1402
1653
  joinBuilder,
1403
1654
  path,
1655
+ undefined,
1656
+ 0,
1404
1657
  );
1405
1658
  whereBuilder += where;
1406
1659
  bindableBuilder.push(...bindable);
1407
1660
  }
1408
1661
 
1409
- if (isIterateRequest(request, type)) {
1662
+ /* if (isIterateRequest(request, type)) {
1410
1663
  let sort = request?.sort;
1411
- if (!sort && options?.stable) {
1664
+ if (
1665
+ (!sort || (Array.isArray(sort) && sort.length === 0)) &&
1666
+ !options?.fetchAll
1667
+ ) {
1412
1668
  sort =
1413
1669
  table.primary && path.length === 0
1414
1670
  ? [{ key: [table.primary], direction: types.SortDirection.ASC }]
@@ -1429,61 +1685,97 @@ const convertRequestToQuery = <
1429
1685
  undefined,
1430
1686
  true,
1431
1687
  );
1432
- for (const table of foreignTables) {
1688
+
1689
+ for (const foreignTable of foreignTables) {
1433
1690
  if (once) {
1434
1691
  orderByBuilder += ", ";
1435
1692
  }
1436
1693
  once = true;
1437
- 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"}`;
1438
1698
  }
1439
1699
  }
1440
-
1441
- /* orderByBuilder += request.sort
1442
- .map(
1443
- (sort) =>
1444
- `${table.name}.${sort.key} ${sort.direction === types.SortDirection.ASC ? "ASC" : "DESC"}`
1445
- )
1446
- .join(", "); */
1447
1700
  }
1448
1701
  }
1449
- }
1702
+ } */
1450
1703
  const where = whereBuilder.length > 0 ? "where " + whereBuilder : undefined;
1451
1704
 
1452
1705
  if (extraJoin && extraJoin.size > 0) {
1453
1706
  insertMapIntoMap(joinBuilder, extraJoin);
1454
1707
  }
1455
- let join = buildJoin(joinBuilder, type === "iterate" ? true : false);
1708
+ let { join } = buildJoin(joinBuilder, options);
1456
1709
 
1457
1710
  const query = `${join ? join : ""} ${where ? where : ""}`;
1458
1711
 
1459
1712
  return {
1460
1713
  query,
1461
- orderBy: orderByBuilder,
1714
+ /* orderBy: orderByBuilder, */
1462
1715
  bindable: bindableBuilder,
1463
1716
  } as R;
1464
1717
  };
1465
1718
 
1466
1719
  export const buildJoin = (
1467
- joinBuilder: Map<string, JoinTable>,
1468
- resolveAllColumns: boolean,
1469
- ) => {
1470
- let joinTypeDefault = resolveAllColumns
1471
- ? /* "FULL OUTER JOIN" */ "LEFT OUTER JOIN"
1472
- : "JOIN";
1720
+ joinBuilder: Map<string, JoinOrRootTable>,
1721
+ options?: {
1722
+ planner?: PlanningSession;
1723
+ },
1724
+ ): { join: string } => {
1725
+ /* let joinTypeDefault = resolveAllColumns
1726
+ ? "CROSS JOIN"
1727
+ : "JOIN"; */
1473
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
+ }
1474
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") {
1475
1767
  let nonInlinedParent =
1476
1768
  table.table.parent && getNonInlinedTable(table.table.parent);
1477
1769
  if (!nonInlinedParent) {
1478
1770
  throw new Error("Unexpected: missing parent");
1479
1771
  }
1480
-
1481
- let joinType = table.table.referencedInArray
1482
- ? /* "FULL OUTER JOIN" */ "LEFT OUTER JOIN"
1483
- : joinTypeDefault;
1484
- 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;
1485
1776
  }
1486
- return join;
1777
+
1778
+ return { join };
1487
1779
  };
1488
1780
 
1489
1781
  const insertMapIntoMap = (map: Map<string, any>, insert: Map<string, any>) => {
@@ -1496,9 +1788,10 @@ export const convertQueryToSQLQuery = (
1496
1788
  query: types.Query,
1497
1789
  tables: Map<string, Table>,
1498
1790
  table: Table,
1499
- joinBuilder: Map<string, JoinTable>,
1500
- path: string[] = [],
1501
- tableAlias: string | undefined = undefined,
1791
+ joinBuilder: Map<string, JoinOrRootTable>,
1792
+ path: string[],
1793
+ tableAlias: string | undefined,
1794
+ skipKeys: number,
1502
1795
  ): { where: string; bindable: any[] } => {
1503
1796
  let whereBuilder = "";
1504
1797
  let bindableBuilder: any[] = [];
@@ -1507,7 +1800,8 @@ export const convertQueryToSQLQuery = (
1507
1800
  const handleAnd = (
1508
1801
  queries: types.Query[],
1509
1802
  path: string[],
1510
- tableAlias?: string,
1803
+ tableAlias: string | undefined,
1804
+ keysOffset: number,
1511
1805
  ) => {
1512
1806
  for (const query of queries) {
1513
1807
  const { where, bindable } = convertQueryToSQLQuery(
@@ -1517,6 +1811,7 @@ export const convertQueryToSQLQuery = (
1517
1811
  joinBuilder,
1518
1812
  path,
1519
1813
  tableAlias,
1814
+ keysOffset,
1520
1815
  );
1521
1816
  whereBuilder =
1522
1817
  whereBuilder.length > 0 ? `(${whereBuilder}) AND (${where})` : where;
@@ -1532,16 +1827,18 @@ export const convertQueryToSQLQuery = (
1532
1827
  joinBuilder,
1533
1828
  path,
1534
1829
  tableAlias,
1830
+ skipKeys,
1535
1831
  );
1536
1832
  whereBuilder += where;
1537
1833
  bindableBuilder.push(...bindable);
1538
1834
  } else if (query instanceof types.Nested) {
1539
1835
  let joinPrefix = "__" + String(tables.size);
1540
- path = [...path, query.path];
1541
- handleAnd(query.query, path, joinPrefix);
1836
+ path = [...path, ...query.path];
1837
+ let newSkipKeys = skipKeys + query.path.length;
1838
+ handleAnd(query.query, path, joinPrefix, newSkipKeys);
1542
1839
  } else if (query instanceof types.LogicalQuery) {
1543
1840
  if (query instanceof types.And) {
1544
- handleAnd(query.and, path, tableAlias);
1841
+ handleAnd(query.and, path, tableAlias, skipKeys);
1545
1842
  } else if (query instanceof types.Or) {
1546
1843
  for (const subquery of query.or) {
1547
1844
  const { where, bindable } = convertQueryToSQLQuery(
@@ -1551,9 +1848,10 @@ export const convertQueryToSQLQuery = (
1551
1848
  joinBuilder,
1552
1849
  path,
1553
1850
  tableAlias,
1851
+ skipKeys,
1554
1852
  );
1555
1853
  whereBuilder =
1556
- whereBuilder.length > 0 ? `(${whereBuilder}) OR (${where})` : where;
1854
+ whereBuilder.length > 0 ? `(${whereBuilder}) OR(${where})` : where;
1557
1855
  bindableBuilder.push(...bindable);
1558
1856
  }
1559
1857
  } else if (query instanceof types.Not) {
@@ -1564,8 +1862,9 @@ export const convertQueryToSQLQuery = (
1564
1862
  joinBuilder,
1565
1863
  path,
1566
1864
  tableAlias,
1865
+ skipKeys,
1567
1866
  );
1568
- whereBuilder = `NOT (${where})`;
1867
+ whereBuilder = `NOT(${where})`;
1569
1868
  bindableBuilder.push(...bindable);
1570
1869
  } else {
1571
1870
  throw new Error("Unsupported query type: " + query.constructor.name);
@@ -1584,38 +1883,54 @@ const cloneQuery = (query: types.StateFieldQuery) => {
1584
1883
  return deserialize(serialize(query), types.StateFieldQuery);
1585
1884
  };
1586
1885
 
1886
+ type JoinOrRootTable = JoinTable | RootTable;
1887
+
1587
1888
  type JoinTable = {
1588
1889
  table: Table;
1589
1890
  as: string;
1891
+ type: "left" | "cross";
1892
+ columns: string[];
1893
+ };
1894
+
1895
+ type RootTable = {
1896
+ type: "root";
1897
+ table: Table;
1898
+ as: string;
1899
+ columns: string[];
1590
1900
  };
1591
1901
 
1592
- const createTableReferenceName = (
1902
+ /* const createQueryTableReferenceName = (
1593
1903
  table: Table,
1594
1904
  alias: string | undefined,
1595
- fieldType: FieldType,
1596
- joinSize: number,
1597
1905
  ) => {
1906
+
1598
1907
  if (
1599
- !alias &&
1600
- (fieldType instanceof VecKind ||
1601
- (fieldType instanceof OptionKind &&
1602
- fieldType.elementType instanceof VecKind))
1908
+ !alias
1603
1909
  ) {
1604
- 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
1605
1912
  alias = aliasSuffix;
1606
1913
  }
1607
1914
  const tableNameAs = alias ? alias + "_" + table.name : table.name;
1608
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; */
1609
1924
  };
1610
1925
 
1611
1926
  const resolveTableToQuery = (
1612
1927
  table: Table,
1613
1928
  tables: Map<string, Table>,
1614
- join: Map<string, JoinTable>,
1929
+ join: Map<string, JoinOrRootTable>,
1615
1930
  path: string[],
1616
1931
  alias: string | undefined,
1617
1932
  searchSelf: boolean,
1618
- ) => {
1933
+ ): { queryKey: string; foreignTables: JoinOrRootTable[] } => {
1619
1934
  // we are matching in two ways.
1620
1935
 
1621
1936
  // 1. joins
@@ -1632,12 +1947,19 @@ const resolveTableToQuery = (
1632
1947
  if (field) {
1633
1948
  return {
1634
1949
  queryKey: field.name,
1635
- foreignTables: [{ table, as: table.name }],
1950
+ foreignTables: [getOrSetRootTable(join, table)],
1636
1951
  };
1637
1952
  }
1638
1953
  }
1639
1954
 
1640
- 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
+ ];
1641
1963
  let prevTables: JoinTable[] | undefined = undefined;
1642
1964
 
1643
1965
  // outer:
@@ -1650,20 +1972,29 @@ const resolveTableToQuery = (
1650
1972
  if (!field && currentTable.children.length > 0) {
1651
1973
  // second arg is needed because of polymorphic fields we might end up here intentially to check what tables to query
1652
1974
  throw new MissingFieldError(
1653
- `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))} `,
1654
1976
  );
1655
1977
  }
1656
1978
  for (const child of currentTable.children) {
1657
- const tableNameAs = createTableReferenceName(
1979
+ const tableNameAs = createQueryTableReferenceName(
1658
1980
  child,
1659
- alias,
1981
+ /* alias */ /* ,
1660
1982
  field.type,
1661
- join.size,
1983
+ join.size, */
1662
1984
  );
1985
+
1663
1986
  let isMatching =
1664
1987
  child.parentPath![child.parentPath!.length - 1] === key;
1665
1988
  if (isMatching) {
1666
- 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
+ };
1667
1998
  if (child.isSimpleValue) {
1668
1999
  if (!child.inline) {
1669
2000
  join.set(tableNameAs, tableWithAlias);
@@ -1727,9 +2058,10 @@ const convertStateFieldQuery = (
1727
2058
  query: types.StateFieldQuery,
1728
2059
  tables: Map<string, Table>,
1729
2060
  table: Table,
1730
- join: Map<string, JoinTable>,
2061
+ join: Map<string, JoinOrRootTable>,
1731
2062
  path: string[],
1732
- tableAlias: string | undefined = undefined,
2063
+ tableAlias: string | undefined,
2064
+ skipKeys: number,
1733
2065
  ): { where: string; bindable: any[] } => {
1734
2066
  // if field id represented as foreign table, do join and compare
1735
2067
  const inlinedName = getInlineTableFieldName(query.key);
@@ -1738,11 +2070,15 @@ const convertStateFieldQuery = (
1738
2070
  ); /* stringArraysEquals(query.key, [...table.parentPath, x.name]) )*/
1739
2071
  const isForeign = !tableField; // table.fields.find(x => x.name === query.key[query.key.length - 1])
1740
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
+ }
1741
2077
  const { queryKey, foreignTables } = resolveTableToQuery(
1742
2078
  table,
1743
2079
  tables,
1744
2080
  join,
1745
- [...path, ...query.key],
2081
+ tablePath,
1746
2082
  tableAlias,
1747
2083
  false,
1748
2084
  );
@@ -1755,6 +2091,7 @@ const convertStateFieldQuery = (
1755
2091
  if (ftable.table === table) {
1756
2092
  throw new Error("Unexpected");
1757
2093
  }
2094
+
1758
2095
  const { where, bindable } = convertQueryToSQLQuery(
1759
2096
  query,
1760
2097
  tables,
@@ -1762,6 +2099,7 @@ const convertStateFieldQuery = (
1762
2099
  join,
1763
2100
  path,
1764
2101
  ftable.as,
2102
+ skipKeys,
1765
2103
  );
1766
2104
  whereBuilder.push(where);
1767
2105
  bindableBuilder.push(bindable);
@@ -1772,6 +2110,12 @@ const convertStateFieldQuery = (
1772
2110
  };
1773
2111
  }
1774
2112
 
2113
+ const columnAggregator = join.get(createQueryTableReferenceName(table))!;
2114
+ if (!columnAggregator) {
2115
+ throw new Error("Unexpected");
2116
+ }
2117
+ columnAggregator.columns.push(inlinedName);
2118
+
1775
2119
  let bindable: any[] = [];
1776
2120
  const keyWithTable =
1777
2121
  (tableAlias || table.name) + "." + escapeColumnName(inlinedName);
@@ -1780,10 +2124,10 @@ const convertStateFieldQuery = (
1780
2124
  let statement = "";
1781
2125
 
1782
2126
  if (query.method === types.StringMatchMethod.contains) {
1783
- statement = `${keyWithTable} LIKE ?`;
2127
+ statement = `${keyWithTable} LIKE ? `;
1784
2128
  bindable.push(`%${query.value}%`);
1785
2129
  } else if (query.method === types.StringMatchMethod.prefix) {
1786
- statement = `${keyWithTable} LIKE ?`;
2130
+ statement = `${keyWithTable} LIKE ? `;
1787
2131
  bindable.push(`${query.value}%`);
1788
2132
  } else if (query.method === types.StringMatchMethod.exact) {
1789
2133
  statement = `${keyWithTable} = ?`;
@@ -1802,7 +2146,7 @@ const convertStateFieldQuery = (
1802
2146
  } else if (query instanceof types.IntegerCompare) {
1803
2147
  if (tableField!.type === "BLOB") {
1804
2148
  // TODO perf
1805
- where = `hex(${keyWithTable}) LIKE ?`;
2149
+ where = `hex(${keyWithTable}) LIKE ? `;
1806
2150
  bindable.push(
1807
2151
  `%${toHexString(new Uint8Array([Number(query.value.value)]))}%`,
1808
2152
  );
@@ -1810,20 +2154,20 @@ const convertStateFieldQuery = (
1810
2154
  if (query.compare === types.Compare.Equal) {
1811
2155
  where = `${keyWithTable} = ?`;
1812
2156
  } else if (query.compare === types.Compare.Greater) {
1813
- where = `${keyWithTable} > ?`;
2157
+ where = `${keyWithTable} > ? `;
1814
2158
  } else if (query.compare === types.Compare.Less) {
1815
- where = `${keyWithTable} < ?`;
2159
+ where = `${keyWithTable} <?`;
1816
2160
  } else if (query.compare === types.Compare.GreaterOrEqual) {
1817
- where = `${keyWithTable} >= ?`;
2161
+ where = `${keyWithTable} >= ? `;
1818
2162
  } else if (query.compare === types.Compare.LessOrEqual) {
1819
- where = `${keyWithTable} <= ?`;
2163
+ where = `${keyWithTable} <= ? `;
1820
2164
  } else {
1821
- throw new Error(`Unsupported compare type: ${query.compare}`);
2165
+ throw new Error(`Unsupported compare type: ${query.compare} `);
1822
2166
  }
1823
2167
 
1824
2168
  if (unwrapNestedType(tableField.from!.type) === "u64") {
1825
2169
  // shift left because that is how we insert the value
1826
- bindable.push(query.value.value);
2170
+ bindable.push(u64ToI64(query.value.value));
1827
2171
  } else {
1828
2172
  bindable.push(query.value.value);
1829
2173
  }
@@ -1838,3 +2182,14 @@ const convertStateFieldQuery = (
1838
2182
  }
1839
2183
  return { where, bindable };
1840
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
+ };