@danceroutine/tango-orm 1.11.10 → 1.11.12

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.
@@ -463,8 +463,9 @@ var QueryCompiler = class QueryCompiler {
463
463
  ].join(", ");
464
464
  const whereSQL = whereParts.length ? ` WHERE ${whereParts.join(" AND ")}` : "";
465
465
  const orderSQL = ` ORDER BY ${state.order?.length ? state.order.map((order) => `${validatedPlan.orderFields[String(order.by)]} ${order.dir.toUpperCase()}`).join(", ") : `${table}.${validatedPlan.meta.pk} ASC`}`;
466
- const limitSQL = state.limit ? ` LIMIT ${state.limit}` : "";
467
- const offsetSQL = state.offset ? ` OFFSET ${state.offset}` : "";
466
+ const hasOffset = state.offset !== void 0;
467
+ const limitSQL = state.limit !== void 0 ? ` LIMIT ${state.limit}` : hasOffset && this.adapter.dialect === InternalDialect.SQLITE ? " LIMIT -1" : "";
468
+ const offsetSQL = state.offset !== void 0 ? ` OFFSET ${state.offset}` : "";
468
469
  return {
469
470
  sql: `SELECT ${select} FROM ${table}${joinCollection.joins.length ? ` ${joinCollection.joins.join(" ")}` : ""}${whereSQL}${orderSQL}${limitSQL}${offsetSQL}`,
470
471
  params,
@@ -1103,6 +1104,263 @@ var QBuilder = class QBuilder {
1103
1104
  }
1104
1105
  };
1105
1106
  //#endregion
1107
+ //#region src/query/hydration/HydrationEntityRegistry.ts
1108
+ /**
1109
+ * Tracks hydrated related records so repeated rows point at one canonical
1110
+ * object per model primary key.
1111
+ */
1112
+ var HydrationEntityRegistry = class HydrationEntityRegistry {
1113
+ attachPersistedRecordAccessors;
1114
+ static BRAND = "tango.orm.hydration_entity_registry";
1115
+ __tangoBrand = HydrationEntityRegistry.BRAND;
1116
+ recordsByModel = /* @__PURE__ */ new Map();
1117
+ constructor(attachPersistedRecordAccessors) {
1118
+ this.attachPersistedRecordAccessors = attachPersistedRecordAccessors;
1119
+ }
1120
+ static isHydrationEntityRegistry(value) {
1121
+ return typeof value === "object" && value !== null && value.__tangoBrand === HydrationEntityRegistry.BRAND;
1122
+ }
1123
+ canonicalize(node, row) {
1124
+ const primaryKeyValue = row[node.targetPrimaryKey];
1125
+ if (typeof primaryKeyValue !== "string" && typeof primaryKeyValue !== "number") return row;
1126
+ const byModel = this.recordsByModel.get(node.targetModelKey) ?? /* @__PURE__ */ new Map();
1127
+ const existing = byModel.get(primaryKeyValue);
1128
+ if (existing) {
1129
+ Object.assign(existing, row);
1130
+ return existing;
1131
+ }
1132
+ byModel.set(primaryKeyValue, row);
1133
+ this.recordsByModel.set(node.targetModelKey, byModel);
1134
+ this.attachPersistedRecordAccessors?.(row, node.targetModelKey);
1135
+ return row;
1136
+ }
1137
+ };
1138
+ //#endregion
1139
+ //#region src/query/hydration/QueryRowNormalizerStrategy.ts
1140
+ var PassthroughQueryRowNormalizerStrategy = class {
1141
+ normalizeRootRows(rows, _columns) {
1142
+ return [...rows];
1143
+ }
1144
+ normalizeHydratedRowsForParserShape(rows, _columns) {
1145
+ return [...rows];
1146
+ }
1147
+ normalizeTargetRow(row, _targetColumns) {
1148
+ return row;
1149
+ }
1150
+ normalizeColumnValue(_columnType, value) {
1151
+ return value;
1152
+ }
1153
+ };
1154
+ function createQueryRowNormalizerStrategy(dialect) {
1155
+ return dialect === InternalDialect.SQLITE ? new SqliteRowNormalizerStrategy() : new PassthroughQueryRowNormalizerStrategy();
1156
+ }
1157
+ var SqliteRowNormalizerStrategy = class {
1158
+ normalizeRootRows(rows, columns) {
1159
+ return this.normalizeRows(rows, columns);
1160
+ }
1161
+ normalizeHydratedRowsForParserShape(rows, columns) {
1162
+ return this.normalizeRows(rows, columns);
1163
+ }
1164
+ normalizeTargetRow(row, targetColumns) {
1165
+ return this.normalizeRow(row, this.booleanColumns(targetColumns));
1166
+ }
1167
+ normalizeColumnValue(columnType, value) {
1168
+ return this.isBooleanColumnType(columnType) ? this.normalizeSqliteBoolean(value) : value;
1169
+ }
1170
+ normalizeRows(rows, columns) {
1171
+ const booleanColumns = this.booleanColumns(columns);
1172
+ if (booleanColumns.length === 0) return [...rows];
1173
+ return rows.map((row) => this.normalizeRow(row, booleanColumns));
1174
+ }
1175
+ normalizeRow(row, columns) {
1176
+ let normalized = null;
1177
+ for (const column of columns) {
1178
+ const current = row[column];
1179
+ const next = this.normalizeSqliteBoolean(current);
1180
+ if (next === current) continue;
1181
+ normalized ??= { ...row };
1182
+ normalized[column] = next;
1183
+ }
1184
+ return normalized ?? row;
1185
+ }
1186
+ booleanColumns(columns) {
1187
+ return Object.entries(columns).filter(([, value]) => this.isBooleanColumnType(value)).map(([column]) => column);
1188
+ }
1189
+ isBooleanColumnType(value) {
1190
+ return typeof value === "string" && ["bool", "boolean"].includes(value.trim().toLowerCase());
1191
+ }
1192
+ normalizeSqliteBoolean(value) {
1193
+ if (value === 0 || value === "0") return false;
1194
+ if (value === 1 || value === "1") return true;
1195
+ return value;
1196
+ }
1197
+ };
1198
+ //#endregion
1199
+ //#region src/query/hydration/QueryHydrator.ts
1200
+ /**
1201
+ * Coordinates compiled relation hydration for `QuerySet` evaluation.
1202
+ */
1203
+ var QueryHydrator = class QueryHydrator {
1204
+ executor;
1205
+ static BRAND = "tango.orm.query_hydrator";
1206
+ __tangoBrand = QueryHydrator.BRAND;
1207
+ normalizer;
1208
+ constructor(executor, normalizer) {
1209
+ this.executor = executor;
1210
+ this.normalizer = normalizer ?? createQueryRowNormalizerStrategy(executor.adapter.dialect);
1211
+ }
1212
+ static isQueryHydrator(value) {
1213
+ return typeof value === "object" && value !== null && value.__tangoBrand === QueryHydrator.BRAND;
1214
+ }
1215
+ async materializeRows(rows, compiled) {
1216
+ const hydratedRows = await this.hydrateRows(rows, compiled);
1217
+ this.attachRootRecordAccessors(hydratedRows);
1218
+ return hydratedRows;
1219
+ }
1220
+ async hydrateRows(rows, compiled) {
1221
+ if (!compiled.hydrationPlan) return rows;
1222
+ const hydratedRows = rows.map((row) => ({ ...row }));
1223
+ this.attachRootRecordAccessors(hydratedRows);
1224
+ const registry = new HydrationEntityRegistry(this.executor.attachPersistedRecordAccessors);
1225
+ const queuedJoinPrefetchOwners = /* @__PURE__ */ new Map();
1226
+ const compiler = new QueryCompiler(this.executor.meta, this.executor.adapter);
1227
+ for (const row of hydratedRows) this.hydrateJoinNodesForOwner(row, row, compiled.hydrationPlan.joinNodes, registry, queuedJoinPrefetchOwners);
1228
+ for (const node of compiled.hydrationPlan.prefetchNodes) await this.hydratePrefetchNode(node, hydratedRows, registry, compiler);
1229
+ for (const [node, owners] of queuedJoinPrefetchOwners.entries()) await this.hydratePrefetchNode(node, [...owners], registry, compiler);
1230
+ for (const row of hydratedRows) for (const alias of compiled.hydrationPlan.hiddenRootAliases) delete row[alias];
1231
+ return hydratedRows;
1232
+ }
1233
+ attachRootRecordAccessors(rows) {
1234
+ if (!this.executor.attachPersistedRecordAccessors) return;
1235
+ const sourceModelKey = this.executor.meta.modelKey;
1236
+ for (const row of rows) this.executor.attachPersistedRecordAccessors(row, sourceModelKey);
1237
+ }
1238
+ hydrateJoinNodesForOwner(owner, rawRow, nodes, registry, queuedJoinPrefetchOwners) {
1239
+ for (const node of nodes) {
1240
+ if (!node.join) continue;
1241
+ const target = {};
1242
+ let hasTargetValue = false;
1243
+ for (const [column, alias] of Object.entries(node.join.columns)) {
1244
+ const value = rawRow[alias];
1245
+ delete rawRow[alias];
1246
+ target[column] = this.normalizer.normalizeColumnValue(node.targetColumns[column], value);
1247
+ if (value !== null && value !== void 0) hasTargetValue = true;
1248
+ }
1249
+ if (!hasTargetValue) {
1250
+ owner[node.relationName] = null;
1251
+ continue;
1252
+ }
1253
+ const canonical = registry.canonicalize(node, target);
1254
+ owner[node.relationName] = canonical;
1255
+ for (const childNode of node.prefetchChildren) {
1256
+ const queuedOwners = queuedJoinPrefetchOwners?.get(childNode);
1257
+ if (queuedOwners) {
1258
+ queuedOwners.add(canonical);
1259
+ continue;
1260
+ }
1261
+ queuedJoinPrefetchOwners?.set(childNode, new Set([canonical]));
1262
+ }
1263
+ this.hydrateJoinNodesForOwner(canonical, rawRow, node.joinChildren, registry, queuedJoinPrefetchOwners);
1264
+ }
1265
+ }
1266
+ async hydratePrefetchNode(node, owners, registry, compiler) {
1267
+ if (owners.length === 0) return;
1268
+ const groupedOwners = this.groupOwnersByAccessor(owners, node.ownerSourceAccessor);
1269
+ const sourceValues = [...groupedOwners.keys()];
1270
+ if (!!!node.throughTable) for (const owner of owners) owner[node.relationName] = node.cardinality === InternalRelationHydrationCardinality.MANY ? [] : null;
1271
+ if (sourceValues.length === 0) return;
1272
+ const sourceChunks = this.chunkValues(sourceValues, 500);
1273
+ const compiledPrefetch = compiler.compilePrefetch(node, sourceChunks[0]);
1274
+ if (compiledPrefetch.kind === InternalPrefetchQueryKind.MANY_TO_MANY) {
1275
+ const idsByOwner = /* @__PURE__ */ new Map();
1276
+ const uniqueTargetIds = /* @__PURE__ */ new Set();
1277
+ for (const chunk of sourceChunks) {
1278
+ const chunkCompiled = compiler.compilePrefetch(node, chunk);
1279
+ const throughResult = await this.executor.client.query(chunkCompiled.throughSql, chunkCompiled.throughParams);
1280
+ for (const row of throughResult.rows) {
1281
+ const ownerId = row[chunkCompiled.ownerAlias];
1282
+ const targetId = row[chunkCompiled.targetAlias];
1283
+ if (typeof ownerId !== "string" && typeof ownerId !== "number" || typeof targetId !== "string" && typeof targetId !== "number") continue;
1284
+ const bucket = idsByOwner.get(ownerId) ?? [];
1285
+ bucket.push(targetId);
1286
+ idsByOwner.set(ownerId, bucket);
1287
+ uniqueTargetIds.add(targetId);
1288
+ }
1289
+ }
1290
+ const targets = {};
1291
+ const targetIds = [...uniqueTargetIds.values()];
1292
+ if (targetIds.length > 0) for (const targetChunk of this.chunkValues(targetIds, 500)) {
1293
+ const targetQuery = compiler.compileManyToManyTargets(node, targetChunk);
1294
+ const targetResult = await this.executor.client.query(targetQuery.sql, targetQuery.params);
1295
+ for (const rawTargetRow of targetResult.rows) {
1296
+ const normalized = this.normalizer.normalizeTargetRow(rawTargetRow, compiledPrefetch.targetColumns);
1297
+ const canonical = registry.canonicalize(node, normalized);
1298
+ this.hydrateJoinNodesForOwner(canonical, normalized, node.joinChildren, registry);
1299
+ const primaryKey = canonical[node.targetPrimaryKey];
1300
+ if (typeof primaryKey === "string" || typeof primaryKey === "number") targets[primaryKey] = canonical;
1301
+ }
1302
+ }
1303
+ const canonicalChildren = /* @__PURE__ */ new Map();
1304
+ const handledOwners = /* @__PURE__ */ new Set();
1305
+ for (const [ownerId, ids] of idsByOwner.entries()) {
1306
+ const bucket = ids.map((id) => targets[id]).filter((value) => !!value);
1307
+ for (const owner of groupedOwners.get(ownerId) ?? []) {
1308
+ this.primeManyToManyOwnerCache(owner, node.relationName, bucket);
1309
+ handledOwners.add(owner);
1310
+ }
1311
+ for (const child of bucket) canonicalChildren.set(child[node.targetPrimaryKey], child);
1312
+ }
1313
+ for (const owner of owners) if (!handledOwners.has(owner)) this.primeManyToManyOwnerCache(owner, node.relationName, []);
1314
+ const childOwners = [...canonicalChildren.values()];
1315
+ for (const childNode of node.prefetchChildren) await this.hydratePrefetchNode(childNode, childOwners, registry, compiler);
1316
+ return;
1317
+ }
1318
+ const canonicalChildren = /* @__PURE__ */ new Map();
1319
+ for (const chunk of sourceChunks) {
1320
+ const chunkCompiled = compiler.compilePrefetch(node, chunk);
1321
+ const result = await this.executor.client.query(chunkCompiled.sql, chunkCompiled.params);
1322
+ for (const rawResultRow of result.rows) {
1323
+ const normalized = this.normalizer.normalizeTargetRow(rawResultRow, chunkCompiled.targetColumns);
1324
+ const canonical = registry.canonicalize(node, normalized);
1325
+ this.hydrateJoinNodesForOwner(canonical, normalized, node.joinChildren, registry);
1326
+ const key = normalized[chunkCompiled.targetKey];
1327
+ if (typeof key !== "string" && typeof key !== "number") continue;
1328
+ for (const owner of groupedOwners.get(key) ?? []) if (node.cardinality === InternalRelationHydrationCardinality.MANY) owner[node.relationName].push(canonical);
1329
+ else if (owner[node.relationName] === null) owner[node.relationName] = canonical;
1330
+ const childPrimaryKey = canonical[node.targetPrimaryKey];
1331
+ if (typeof childPrimaryKey === "string" || typeof childPrimaryKey === "number") canonicalChildren.set(childPrimaryKey, canonical);
1332
+ }
1333
+ }
1334
+ const childOwners = [...canonicalChildren.values()];
1335
+ for (const childNode of node.prefetchChildren) await this.hydratePrefetchNode(childNode, childOwners, registry, compiler);
1336
+ }
1337
+ primeManyToManyOwnerCache(owner, relationName, bucket) {
1338
+ const existing = owner[relationName];
1339
+ if (existing && typeof existing.primePrefetchCache === "function") {
1340
+ existing.primePrefetchCache(bucket);
1341
+ return;
1342
+ }
1343
+ owner[relationName] = bucket.slice();
1344
+ }
1345
+ chunkValues(values, size) {
1346
+ if (values.length <= size) return [Array.from(values)];
1347
+ const chunks = [];
1348
+ for (let i = 0; i < values.length; i += size) chunks.push(values.slice(i, i + size));
1349
+ return chunks;
1350
+ }
1351
+ groupOwnersByAccessor(owners, accessor) {
1352
+ const grouped = /* @__PURE__ */ new Map();
1353
+ for (const owner of owners) {
1354
+ const key = owner[accessor];
1355
+ if (typeof key !== "string" && typeof key !== "number") continue;
1356
+ const bucket = grouped.get(key) ?? [];
1357
+ bucket.push(owner);
1358
+ grouped.set(key, bucket);
1359
+ }
1360
+ return grouped;
1361
+ }
1362
+ };
1363
+ //#endregion
1106
1364
  //#region src/query/QuerySet.ts
1107
1365
  /**
1108
1366
  * Django-inspired query builder for constructing and executing database queries.
@@ -1131,9 +1389,13 @@ var QuerySet = class QuerySet {
1131
1389
  static BRAND = "tango.orm.query_set";
1132
1390
  __tangoBrand = QuerySet.BRAND;
1133
1391
  evaluationCache;
1392
+ hydrator;
1393
+ rowNormalizer;
1134
1394
  constructor(executor, state = {}) {
1135
1395
  this.executor = executor;
1136
1396
  this.state = state;
1397
+ this.rowNormalizer = createQueryRowNormalizerStrategy(executor.adapter.dialect);
1398
+ this.hydrator = new QueryHydrator(executor, this.rowNormalizer);
1137
1399
  }
1138
1400
  /**
1139
1401
  * Narrow an unknown value to `QuerySet`.
@@ -1162,6 +1424,11 @@ var QuerySet = class QuerySet {
1162
1424
  };
1163
1425
  });
1164
1426
  }
1427
+ static validateQueryWindowBound(kind, value) {
1428
+ if (typeof value !== "number") throw new TypeError(`QuerySet.${kind}() expects a number.`);
1429
+ if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`QuerySet.${kind}() expects a non-negative safe integer.`);
1430
+ return value;
1431
+ }
1165
1432
  static invertOrderSpec(order) {
1166
1433
  if (!order?.length) return [];
1167
1434
  return order.map((spec) => ({
@@ -1216,7 +1483,7 @@ var QuerySet = class QuerySet {
1216
1483
  limit(n) {
1217
1484
  return this.spawn({
1218
1485
  ...this.state,
1219
- limit: n
1486
+ limit: QuerySet.validateQueryWindowBound("limit", n)
1220
1487
  });
1221
1488
  }
1222
1489
  /**
@@ -1225,7 +1492,7 @@ var QuerySet = class QuerySet {
1225
1492
  offset(n) {
1226
1493
  return this.spawn({
1227
1494
  ...this.state,
1228
- offset: n
1495
+ offset: QuerySet.validateQueryWindowBound("offset", n)
1229
1496
  });
1230
1497
  }
1231
1498
  select(fields) {
@@ -1269,7 +1536,7 @@ var QuerySet = class QuerySet {
1269
1536
  async fetch(shape) {
1270
1537
  const baseResult = await this.getOrCreateEvaluationCache();
1271
1538
  if (!shape) return baseResult;
1272
- return new QueryResult(typeof shape === "function" ? baseResult.items.map(shape) : this.normalizeHydratedRowsForParserShape(baseResult.items).map((row) => shape.parse(row)));
1539
+ return new QueryResult(typeof shape === "function" ? baseResult.items.map(shape) : this.rowNormalizer.normalizeHydratedRowsForParserShape(baseResult.items, this.executor.meta.columns).map((row) => shape.parse(row)));
1273
1540
  }
1274
1541
  /**
1275
1542
  * Async iterable surface for `for await (... of queryset)`.
@@ -1333,7 +1600,7 @@ var QuerySet = class QuerySet {
1333
1600
  shapeFetchedRow(row, shape) {
1334
1601
  if (!shape) return row;
1335
1602
  if (typeof shape === "function") return shape(row);
1336
- const normalizedRow = this.normalizeHydratedRowsForParserShape([row])[0] ?? row;
1603
+ const normalizedRow = this.rowNormalizer.normalizeHydratedRowsForParserShape([row], this.executor.meta.columns)[0];
1337
1604
  return shape.parse(normalizedRow);
1338
1605
  }
1339
1606
  getOrCreateEvaluationCache() {
@@ -1346,213 +1613,8 @@ var QuerySet = class QuerySet {
1346
1613
  async evaluateRows() {
1347
1614
  const compiled = new QueryCompiler(this.executor.meta, this.executor.adapter).compile(this.state);
1348
1615
  const rows = await this.executor.run(compiled);
1349
- const normalizedRows = this.normalizeRowsForSchemaParsing(rows);
1350
- const hydratedRows = await this.hydrateRows(normalizedRows, compiled);
1351
- this.attachRootRecordAccessors(hydratedRows);
1352
- return new QueryResult(hydratedRows);
1353
- }
1354
- normalizeRowsForSchemaParsing(rows) {
1355
- if (this.executor.adapter.dialect !== InternalDialect.SQLITE) return [...rows];
1356
- const booleanColumns = Object.entries(this.executor.meta.columns).filter(([, value]) => this.isBooleanColumnType(value)).map(([column]) => column);
1357
- if (booleanColumns.length === 0) return [...rows];
1358
- return rows.map((row) => this.normalizeBooleanColumns(row, booleanColumns));
1359
- }
1360
- normalizeHydratedRowsForParserShape(rows) {
1361
- if (this.executor.adapter.dialect !== InternalDialect.SQLITE) return [...rows];
1362
- const booleanColumns = Object.entries(this.executor.meta.columns).filter(([, value]) => this.isBooleanColumnType(value)).map(([column]) => column);
1363
- if (booleanColumns.length === 0) return [...rows];
1364
- return rows.map((row) => this.normalizeBooleanColumns(row, booleanColumns));
1365
- }
1366
- async hydrateRows(rows, compiled) {
1367
- if (!compiled.hydrationPlan) return rows;
1368
- const hydratedRows = rows.map((row) => ({ ...row }));
1369
- this.attachRootRecordAccessors(hydratedRows);
1370
- const canonicalEntities = /* @__PURE__ */ new Map();
1371
- const queuedJoinPrefetchOwners = /* @__PURE__ */ new Map();
1372
- const compiler = new QueryCompiler(this.executor.meta, this.executor.adapter);
1373
- for (const row of hydratedRows) this.hydrateJoinNodesForOwner(row, row, compiled.hydrationPlan.joinNodes, canonicalEntities, queuedJoinPrefetchOwners);
1374
- for (const node of compiled.hydrationPlan.prefetchNodes) await this.hydratePrefetchNode(node, hydratedRows, canonicalEntities, compiler);
1375
- for (const [node, owners] of queuedJoinPrefetchOwners.entries()) await this.hydratePrefetchNode(node, [...owners], canonicalEntities, compiler);
1376
- for (const row of hydratedRows) for (const alias of compiled.hydrationPlan.hiddenRootAliases) delete row[alias];
1377
- return hydratedRows;
1378
- }
1379
- primeManyToManyOwnerCache(owner, relationName, bucket) {
1380
- const existing = owner[relationName];
1381
- if (existing && typeof existing.primePrefetchCache === "function") {
1382
- existing.primePrefetchCache(bucket);
1383
- return;
1384
- }
1385
- owner[relationName] = bucket.slice();
1386
- }
1387
- attachRootRecordAccessors(rows) {
1388
- if (!this.executor.attachPersistedRecordAccessors) return;
1389
- const sourceModelKey = this.executor.meta.modelKey;
1390
- for (const row of rows) this.executor.attachPersistedRecordAccessors(row, sourceModelKey);
1391
- }
1392
- hydrateJoinNodesForOwner(owner, rawRow, nodes, canonicalEntities, queuedJoinPrefetchOwners) {
1393
- for (const node of nodes) {
1394
- if (!node.join) continue;
1395
- const target = {};
1396
- let hasTargetValue = false;
1397
- for (const [column, alias] of Object.entries(node.join.columns)) {
1398
- const value = rawRow[alias];
1399
- delete rawRow[alias];
1400
- target[column] = this.normalizeColumnValue(node.targetColumns[column], value);
1401
- if (value !== null && value !== void 0) hasTargetValue = true;
1402
- }
1403
- if (!hasTargetValue) {
1404
- owner[node.relationName] = null;
1405
- continue;
1406
- }
1407
- const canonical = this.canonicalizeEntity(node, target, canonicalEntities);
1408
- owner[node.relationName] = canonical;
1409
- for (const childNode of node.prefetchChildren) {
1410
- const queuedOwners = queuedJoinPrefetchOwners?.get(childNode);
1411
- if (queuedOwners) {
1412
- queuedOwners.add(canonical);
1413
- continue;
1414
- }
1415
- queuedJoinPrefetchOwners?.set(childNode, new Set([canonical]));
1416
- }
1417
- this.hydrateJoinNodesForOwner(canonical, rawRow, node.joinChildren, canonicalEntities, queuedJoinPrefetchOwners);
1418
- }
1419
- }
1420
- async hydratePrefetchNode(node, owners, canonicalEntities, compiler) {
1421
- if (owners.length === 0) return;
1422
- const groupedOwners = this.groupOwnersByAccessor(owners, node.ownerSourceAccessor);
1423
- const sourceValues = [...groupedOwners.keys()];
1424
- if (!!!node.throughTable) for (const owner of owners) owner[node.relationName] = node.cardinality === InternalRelationHydrationCardinality.MANY ? [] : null;
1425
- if (sourceValues.length === 0) return;
1426
- const sourceChunks = this.chunkValues(sourceValues, 500);
1427
- const compiledPrefetch = compiler.compilePrefetch(node, sourceChunks[0]);
1428
- if (compiledPrefetch.kind === InternalPrefetchQueryKind.MANY_TO_MANY) {
1429
- const idsByOwner = /* @__PURE__ */ new Map();
1430
- const uniqueTargetIds = /* @__PURE__ */ new Set();
1431
- for (const chunk of sourceChunks) {
1432
- const chunkCompiled = compiler.compilePrefetch(node, chunk);
1433
- const throughResult = await this.executor.client.query(chunkCompiled.throughSql, chunkCompiled.throughParams);
1434
- for (const row of throughResult.rows) {
1435
- const ownerId = row[chunkCompiled.ownerAlias];
1436
- const targetId = row[chunkCompiled.targetAlias];
1437
- if (typeof ownerId !== "string" && typeof ownerId !== "number" || typeof targetId !== "string" && typeof targetId !== "number") continue;
1438
- const bucket = idsByOwner.get(ownerId) ?? [];
1439
- bucket.push(targetId);
1440
- idsByOwner.set(ownerId, bucket);
1441
- uniqueTargetIds.add(targetId);
1442
- }
1443
- }
1444
- const targets = {};
1445
- const targetIds = [...uniqueTargetIds.values()];
1446
- if (targetIds.length > 0) for (const targetChunk of this.chunkValues(targetIds, 500)) {
1447
- const targetQuery = compiler.compileManyToManyTargets(node, targetChunk);
1448
- const targetResult = await this.executor.client.query(targetQuery.sql, targetQuery.params);
1449
- for (const rawTargetRow of targetResult.rows) {
1450
- const normalized = this.normalizeTargetRow({ targetColumns: compiledPrefetch.targetColumns }, rawTargetRow);
1451
- const canonical = this.canonicalizeEntity(node, normalized, canonicalEntities);
1452
- this.hydrateJoinNodesForOwner(canonical, normalized, node.joinChildren, canonicalEntities);
1453
- const primaryKey = canonical[node.targetPrimaryKey];
1454
- if (typeof primaryKey === "string" || typeof primaryKey === "number") targets[primaryKey] = canonical;
1455
- }
1456
- }
1457
- const canonicalChildren = /* @__PURE__ */ new Map();
1458
- const handledOwners = /* @__PURE__ */ new Set();
1459
- for (const [ownerId, ids] of idsByOwner.entries()) {
1460
- const bucket = ids.map((id) => targets[id]).filter((value) => !!value);
1461
- for (const owner of groupedOwners.get(ownerId) ?? []) {
1462
- this.primeManyToManyOwnerCache(owner, node.relationName, bucket);
1463
- handledOwners.add(owner);
1464
- }
1465
- for (const child of bucket) canonicalChildren.set(child[node.targetPrimaryKey], child);
1466
- }
1467
- for (const owner of owners) if (!handledOwners.has(owner)) this.primeManyToManyOwnerCache(owner, node.relationName, []);
1468
- const childOwners = [...canonicalChildren.values()];
1469
- for (const childNode of node.prefetchChildren) await this.hydratePrefetchNode(childNode, childOwners, canonicalEntities, compiler);
1470
- return;
1471
- }
1472
- const canonicalChildren = /* @__PURE__ */ new Map();
1473
- for (const chunk of sourceChunks) {
1474
- const chunkCompiled = compiler.compilePrefetch(node, chunk);
1475
- const result = await this.executor.client.query(chunkCompiled.sql, chunkCompiled.params);
1476
- for (const rawResultRow of result.rows) {
1477
- const normalized = this.normalizeTargetRow(chunkCompiled, rawResultRow);
1478
- const canonical = this.canonicalizeEntity(node, normalized, canonicalEntities);
1479
- this.hydrateJoinNodesForOwner(canonical, normalized, node.joinChildren, canonicalEntities);
1480
- const key = normalized[chunkCompiled.targetKey];
1481
- if (typeof key !== "string" && typeof key !== "number") continue;
1482
- for (const owner of groupedOwners.get(key) ?? []) if (node.cardinality === InternalRelationHydrationCardinality.MANY) owner[node.relationName].push(canonical);
1483
- else if (owner[node.relationName] === null) owner[node.relationName] = canonical;
1484
- const childPrimaryKey = canonical[node.targetPrimaryKey];
1485
- if (typeof childPrimaryKey === "string" || typeof childPrimaryKey === "number") canonicalChildren.set(childPrimaryKey, canonical);
1486
- }
1487
- }
1488
- const childOwners = [...canonicalChildren.values()];
1489
- for (const childNode of node.prefetchChildren) await this.hydratePrefetchNode(childNode, childOwners, canonicalEntities, compiler);
1490
- }
1491
- chunkValues(values, size) {
1492
- if (values.length === 0) return [];
1493
- if (values.length <= size) return [Array.from(values)];
1494
- const chunks = [];
1495
- for (let i = 0; i < values.length; i += size) chunks.push(values.slice(i, i + size));
1496
- return chunks;
1497
- }
1498
- groupOwnersByAccessor(owners, accessor) {
1499
- const grouped = /* @__PURE__ */ new Map();
1500
- for (const owner of owners) {
1501
- const key = owner[accessor];
1502
- if (typeof key !== "string" && typeof key !== "number") continue;
1503
- const bucket = grouped.get(key) ?? [];
1504
- bucket.push(owner);
1505
- grouped.set(key, bucket);
1506
- }
1507
- return grouped;
1508
- }
1509
- canonicalizeEntity(node, row, canonicalEntities) {
1510
- const primaryKeyValue = row[node.targetPrimaryKey];
1511
- if (typeof primaryKeyValue !== "string" && typeof primaryKeyValue !== "number") return row;
1512
- const byModel = canonicalEntities.get(node.targetModelKey) ?? /* @__PURE__ */ new Map();
1513
- const existing = byModel.get(primaryKeyValue);
1514
- if (existing) {
1515
- Object.assign(existing, row);
1516
- return existing;
1517
- }
1518
- byModel.set(primaryKeyValue, row);
1519
- canonicalEntities.set(node.targetModelKey, byModel);
1520
- this.executor.attachPersistedRecordAccessors?.(row, node.targetModelKey);
1521
- return row;
1522
- }
1523
- normalizeTargetRow(prefetch, row) {
1524
- if (this.executor.adapter.dialect !== InternalDialect.SQLITE) return row;
1525
- let normalized = null;
1526
- for (const [column, type] of Object.entries(prefetch.targetColumns)) {
1527
- if (!this.isBooleanColumnType(type)) continue;
1528
- const next = this.normalizeSqliteBoolean(row[column]);
1529
- if (next === row[column]) continue;
1530
- normalized ??= { ...row };
1531
- normalized[column] = next;
1532
- }
1533
- return normalized ?? row;
1534
- }
1535
- normalizeColumnValue(columnType, value) {
1536
- return this.executor.adapter.dialect === InternalDialect.SQLITE && this.isBooleanColumnType(columnType) ? this.normalizeSqliteBoolean(value) : value;
1537
- }
1538
- isBooleanColumnType(value) {
1539
- return typeof value === "string" && ["bool", "boolean"].includes(value.trim().toLowerCase());
1540
- }
1541
- normalizeSqliteBoolean(value) {
1542
- if (value === 0 || value === "0") return false;
1543
- if (value === 1 || value === "1") return true;
1544
- return value;
1545
- }
1546
- normalizeBooleanColumns(row, columns) {
1547
- let normalized = null;
1548
- for (const column of columns) {
1549
- const current = row[column];
1550
- const next = this.normalizeSqliteBoolean(current);
1551
- if (next === current) continue;
1552
- if (!normalized) normalized = { ...row };
1553
- normalized[column] = next;
1554
- }
1555
- return normalized ?? row;
1616
+ const normalizedRows = this.rowNormalizer.normalizeRootRows(rows, this.executor.meta.columns);
1617
+ return new QueryResult(await this.hydrator.materializeRows(normalizedRows, compiled));
1556
1618
  }
1557
1619
  withoutHydrationState() {
1558
1620
  const { selectRelated: _selectRelated, prefetchRelated: _prefetchRelated, ...rest } = this.state;
@@ -1590,4 +1652,4 @@ var query_exports = /* @__PURE__ */ __exportAll({
1590
1652
  //#endregion
1591
1653
  export { isQNodeLike as a, InternalRelationKind as c, QueryCompiler as d, OrmSqlSafetyAdapter as f, QBuilder as i, QueryResult as l, InternalQNodeType as m, ModelQuerySet as n, domain_exports as o, InternalSqlValidationPlanKind as p, QuerySet as r, TableMetaFactory as s, query_exports as t, compiler_exports as u };
1592
1654
 
1593
- //# sourceMappingURL=query-B-ooqvSG.js.map
1655
+ //# sourceMappingURL=query-hvcqTb9O.js.map