@ailnaf/zoddb 0.1.0 → 0.3.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.
package/dist/db.js CHANGED
@@ -49,10 +49,22 @@ async function trackQuery(ctx, meta, fn, countRows) {
49
49
  throw error;
50
50
  }
51
51
  }
52
- async function exec(ctx, text, params = [], meta = { operation: 'query' }) {
53
- return trackQuery(ctx, { ...meta, sql: text, params }, () => ctx.raw.unsafe(text, params));
52
+ async function exec(ctx, text, params = [], meta = { operation: 'query' }, countRows) {
53
+ return trackQuery(ctx, { ...meta, sql: text, params }, () => ctx.raw.unsafe(text, params), countRows);
54
54
  }
55
55
  const readCtx = (ctx) => ctx.replica ? { ...ctx, raw: ctx.replica, onReplica: true } : ctx;
56
+ async function rawQueryImpl(ctx, text, params = []) {
57
+ assertParams(params);
58
+ try {
59
+ return await exec(ctx, text, params, { operation: 'raw' }, result => {
60
+ const count = result.count;
61
+ return typeof count === 'number' ? count : result.length;
62
+ });
63
+ }
64
+ catch (e) {
65
+ throw mapError(e);
66
+ }
67
+ }
56
68
  const quoteIdent = (s) => `"${s.replace(/"/g, '""')}"`;
57
69
  /** 顶层用 begin;已在事务里时用 savepoint(postgres.js 的 TransactionSql 没有 begin) */
58
70
  function beginOrSavepoint(conn, cb) {
@@ -412,6 +424,171 @@ async function applyLinkWrite(ctx, parent, table, key, def, value) {
412
424
  await insertLinkRows(ctx, def, sourceValue, targets, `${operation}.connect`);
413
425
  }
414
426
  }
427
+ async function applyOneUpdate(ctx, parent, table, key, def, value) {
428
+ const operation = `update ${table.name}.${key}`;
429
+ if (!isPlainObject(value))
430
+ throw new Error(`${operation} 需要 { connect } 或 { disconnect }`);
431
+ const hasConnect = value.connect !== undefined;
432
+ const hasDisconnect = value.disconnect !== undefined;
433
+ for (const k of Object.keys(value)) {
434
+ if (k !== 'connect' && k !== 'disconnect')
435
+ throw new Error(`${operation} 不支持选项 ${k}`);
436
+ }
437
+ if (hasConnect === hasDisconnect)
438
+ throw new Error(`${operation} 需要 connect/disconnect 二选一`);
439
+ if (hasDisconnect && value.disconnect !== true)
440
+ throw new Error(`${operation}.disconnect 必须是 true`);
441
+ const from = resolveRelationColumn(def.from);
442
+ const to = resolveRelationColumn(def.to);
443
+ const parentPks = table.__plan.filter(p => p.pk);
444
+ if (parentPks.length !== 1)
445
+ throw new Error(`${operation}: 父表 ${table.name} 必须使用单列主键`);
446
+ const parentPk = parentPks[0];
447
+ const parentValue = parent[parentPk.outKey];
448
+ if (parentValue === undefined || parentValue === null)
449
+ throw new Error(`${operation}: ${table.name}.${parentPk.outKey} 不能为空`);
450
+ const fromPlan = table.__plan.find(p => p.outKey === from.outKey);
451
+ if (!fromPlan)
452
+ throw new Error(`${operation}: 找不到 ${table.name}.${from.outKey}`);
453
+ const setParentFk = async (targetValue) => {
454
+ const rows = await updateColumnsImpl(ctx, table, { [from.outKey]: targetValue }, {
455
+ [parentPk.outKey]: parentValue,
456
+ });
457
+ if (rows.length !== 1)
458
+ throw new Error(`${operation}: 父行 ${String(parentValue)} 不存在`);
459
+ };
460
+ if (hasDisconnect) {
461
+ if (!fromPlan.nullable)
462
+ throw new Error(`${operation}: ${table.name}.${from.outKey} 不是 nullable,不能 disconnect`);
463
+ await setParentFk(null);
464
+ return;
465
+ }
466
+ if (!isPlainObject(value.connect))
467
+ throw new Error(`${operation}.connect 必须是对象`);
468
+ const targetPks = def.table.__plan.filter(p => p.pk);
469
+ if (targetPks.length !== 1)
470
+ throw new Error(`${operation}: 目标表 ${def.table.name} 必须使用单列主键`);
471
+ const targetPk = targetPks[0];
472
+ const targetId = value.connect[targetPk.outKey];
473
+ if (targetId === undefined || targetId === null)
474
+ throw new Error(`${operation}.connect 需要 ${def.table.name}.${targetPk.outKey}`);
475
+ const target = await findOneImpl(ctx, def.table, { where: { [targetPk.outKey]: targetId } });
476
+ if (!target)
477
+ throw new Error(`${operation}: 找不到 ${def.table.name}.${String(targetId)}`);
478
+ const targetValue = target[to.outKey];
479
+ if (targetValue === undefined || targetValue === null)
480
+ throw new Error(`${operation}: ${def.table.name}.${to.outKey} 不能为空`);
481
+ await setParentFk(targetValue);
482
+ }
483
+ async function applyManyUpdate(ctx, parent, table, key, def, value) {
484
+ const operation = `update ${table.name}.${key}`;
485
+ if (!isPlainObject(value))
486
+ throw new Error(`${operation} 需要 { create, update, delete } 之一`);
487
+ const allowed = new Set(['create', 'update', 'delete']);
488
+ for (const k of Object.keys(value)) {
489
+ if (!allowed.has(k))
490
+ throw new Error(`${operation} 不支持选项 ${k}`);
491
+ }
492
+ const parseList = (name) => {
493
+ const raw = value[name];
494
+ if (raw === undefined)
495
+ return undefined;
496
+ if (!Array.isArray(raw))
497
+ throw new Error(`${operation}.${name} 必须是数组`);
498
+ return raw;
499
+ };
500
+ const creates = parseList('create');
501
+ const updates = parseList('update');
502
+ const deletes = parseList('delete');
503
+ if (creates === undefined && updates === undefined && deletes === undefined) {
504
+ throw new Error(`${operation} 需要 create/update/delete 之一`);
505
+ }
506
+ const from = resolveRelationColumn(def.from);
507
+ const to = resolveRelationColumn(def.to);
508
+ const parentValue = parent[from.outKey];
509
+ if (parentValue === undefined || parentValue === null)
510
+ throw new Error(`${operation}: ${table.name}.${from.outKey} 不能为空`);
511
+ const childPks = def.table.__plan.filter(p => p.pk);
512
+ if (childPks.length !== 1)
513
+ throw new Error(`${operation}: 目标表 ${def.table.name} 必须使用单列主键`);
514
+ const childPk = childPks[0];
515
+ const updateItems = [];
516
+ const updateIds = new Set();
517
+ if (updates) {
518
+ for (const [index, patch] of updates.entries()) {
519
+ if (!isPlainObject(patch))
520
+ throw new Error(`${operation}.update[${index}] 必须是对象`);
521
+ const id = patch[childPk.outKey];
522
+ if (id === undefined || id === null)
523
+ throw new Error(`${operation}.update[${index}] 需要 ${def.table.name}.${childPk.outKey}`);
524
+ if (updateIds.has(id))
525
+ throw new Error(`${operation}.update[${index}] 重复子行 ${String(id)}`);
526
+ updateIds.add(id);
527
+ const values = { ...patch };
528
+ delete values[childPk.outKey];
529
+ if (Object.keys(values).length === 0)
530
+ throw new Error(`${operation}.update[${index}] patch 至少需要一个字段`);
531
+ if (to.outKey in values)
532
+ throw new Error(`${operation}.update[${index}] 不能修改外键 ${def.table.name}.${to.outKey}`);
533
+ updateItems.push({ id, values });
534
+ }
535
+ }
536
+ const deleteItems = [];
537
+ const deleteIds = new Set();
538
+ if (deletes) {
539
+ for (const [index, spec] of deletes.entries()) {
540
+ if (!isPlainObject(spec))
541
+ throw new Error(`${operation}.delete[${index}] 必须是对象`);
542
+ const keys = Object.keys(spec);
543
+ if (keys.length !== 1 || keys[0] !== childPk.outKey) {
544
+ throw new Error(`${operation}.delete[${index}] 只允许 { ${childPk.outKey} }`);
545
+ }
546
+ const id = spec[childPk.outKey];
547
+ if (id === undefined || id === null)
548
+ throw new Error(`${operation}.delete[${index}] 需要 ${def.table.name}.${childPk.outKey}`);
549
+ if (deleteIds.has(id))
550
+ throw new Error(`${operation}.delete[${index}] 重复子行 ${String(id)}`);
551
+ deleteIds.add(id);
552
+ deleteItems.push(id);
553
+ }
554
+ }
555
+ for (const id of updateIds) {
556
+ if (deleteIds.has(id))
557
+ throw new Error(`${operation}: 子行 ${String(id)} 不能同时 update 和 delete`);
558
+ }
559
+ if (creates) {
560
+ for (const [index, row] of creates.entries()) {
561
+ if (!isPlainObject(row))
562
+ throw new Error(`${operation}.create[${index}] 必须是对象`);
563
+ const id = row[childPk.outKey];
564
+ if (id !== undefined && id !== null && (updateIds.has(id) || deleteIds.has(id))) {
565
+ throw new Error(`${operation}.create[${index}]: 子行 ${String(id)} 与 update/delete 冲突`);
566
+ }
567
+ }
568
+ }
569
+ if (creates && creates.length > 0) {
570
+ const values = creates.map(row => ({ ...row, [to.outKey]: parentValue }));
571
+ await insertManyImpl(ctx, def.table, values);
572
+ }
573
+ for (const { id, values } of updateItems) {
574
+ const rows = await updateColumnsImpl(ctx, def.table, values, {
575
+ [childPk.outKey]: id,
576
+ [to.outKey]: parentValue,
577
+ });
578
+ if (rows.length !== 1) {
579
+ throw new Error(`${operation}: 子行 ${String(id)} 不存在或不属于父行 ${String(parentValue)}`);
580
+ }
581
+ }
582
+ for (const id of deleteItems) {
583
+ const deleted = await deleteImpl(ctx, def.table, {
584
+ [childPk.outKey]: id,
585
+ [to.outKey]: parentValue,
586
+ });
587
+ if (deleted !== 1) {
588
+ throw new Error(`${operation}: 子行 ${String(id)} 不存在或不属于父行 ${String(parentValue)}`);
589
+ }
590
+ }
591
+ }
415
592
  async function createImpl(ctx, table, input) {
416
593
  const rels = ctx.relations.find(r => r.table === table);
417
594
  const columns = {};
@@ -550,6 +727,8 @@ async function updateImpl(ctx, table, values, where) {
550
727
  const rels = ctx.relations.find(r => r.table === table);
551
728
  const columns = {};
552
729
  const links = [];
730
+ const manyUpdates = [];
731
+ const oneUpdates = [];
553
732
  const unknown = [];
554
733
  for (const [k, v] of Object.entries(values)) {
555
734
  if (v === undefined)
@@ -563,8 +742,16 @@ async function updateImpl(ctx, table, values, where) {
563
742
  links.push({ key: k, def, value: v });
564
743
  continue;
565
744
  }
745
+ if (def?._kind === 'many') {
746
+ manyUpdates.push({ key: k, def, value: v });
747
+ continue;
748
+ }
749
+ if (def?._kind === 'one') {
750
+ oneUpdates.push({ key: k, def, value: v });
751
+ continue;
752
+ }
566
753
  if (def)
567
- throw new Error(`update: ${table.name}.${k} 不支持关系写入(只有 M2M through 支持)`);
754
+ throw new Error(`update: ${table.name}.${k} 不支持关系写入(只有 M2M through / many / one 支持)`);
568
755
  unknown.push(k);
569
756
  }
570
757
  // 保留既有 update 的 ZodError 语义:未知列先交给严格 schema 报错。
@@ -573,7 +760,20 @@ async function updateImpl(ctx, table, values, where) {
573
760
  table.updateSchema.parse(values);
574
761
  throw new Error(`update: 未知字段 ${table.name}.${unknown[0]}`);
575
762
  }
576
- if (links.length === 0)
763
+ const relationWrites = links.length > 0 || manyUpdates.length > 0 || oneUpdates.length > 0;
764
+ if (relationWrites) {
765
+ const pkKeys = table.__plan.filter(p => p.pk).map(p => p.outKey);
766
+ const changedPk = pkKeys.find(k => columns[k] !== undefined);
767
+ if (changedPk)
768
+ throw new Error(`update: 关系写入时不能修改父主键 ${table.name}.${changedPk}`);
769
+ for (const { key, def } of oneUpdates) {
770
+ const from = resolveRelationColumn(def.from);
771
+ if (columns[from.outKey] !== undefined) {
772
+ throw new Error(`update: ${table.name}.${key} 与父外键 ${table.name}.${from.outKey} 冲突`);
773
+ }
774
+ }
775
+ }
776
+ if (!relationWrites)
577
777
  return updateColumnsImpl(ctx, table, columns, where);
578
778
  return beginOrSavepoint(ctx.raw, async (txSql) => {
579
779
  const tx = { ...ctx, raw: txSql };
@@ -585,9 +785,15 @@ async function updateImpl(ctx, table, values, where) {
585
785
  rows = await findManyImpl(tx, table, { where: where, limit: 2 });
586
786
  }
587
787
  if (rows.length !== 1) {
588
- throw new Error(`update: M2M 关系写入要求 where 命中恰好 1 行(命中 ${rows.length} 行)`);
788
+ throw new Error(`update: 关系写入要求 where 命中恰好 1 行(命中 ${rows.length} 行)`);
589
789
  }
590
790
  const parent = rows[0];
791
+ for (const { key, def, value } of manyUpdates) {
792
+ await applyManyUpdate(tx, parent, table, key, def, value);
793
+ }
794
+ for (const { key, def, value } of oneUpdates) {
795
+ await applyOneUpdate(tx, parent, table, key, def, value);
796
+ }
591
797
  for (const { key, def, value } of links) {
592
798
  await applyLinkWrite(tx, parent, table, key, def, value);
593
799
  }
@@ -645,7 +851,52 @@ async function countImpl(ctx, table, where) {
645
851
  const rows = await exec(ctx, text, w?.params ?? [], { operation: 'count', table: table.name });
646
852
  return rows[0]?.n ?? 0;
647
853
  }
648
- function resolveWith(ctx, table, withMap) {
854
+ const AGGREGATE_NUMERIC = /^(int|int2|int4|int8|smallint|integer|bigint|numeric|decimal|real|double|float)/;
855
+ function isPlainObject(value) {
856
+ return typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date);
857
+ }
858
+ function parseAggregate(table, raw) {
859
+ if (!isPlainObject(raw))
860
+ throw new Error(`with: ${table.name}.aggregate 必须是对象`);
861
+ const allowed = new Set(['count', 'sum', 'max']);
862
+ for (const key of Object.keys(raw)) {
863
+ if (!allowed.has(key))
864
+ throw new Error(`with: ${table.name}.aggregate 不支持 ${key}`);
865
+ }
866
+ if (raw.count !== undefined && raw.count !== true)
867
+ throw new Error(`with: ${table.name}.aggregate.count 必须是 true`);
868
+ const count = raw.count === true;
869
+ const parseColumns = (name) => {
870
+ const value = raw[name];
871
+ if (value === undefined)
872
+ return [];
873
+ if (!isPlainObject(value))
874
+ throw new Error(`with: ${table.name}.aggregate.${name} 必须是对象`);
875
+ const result = [];
876
+ for (const [outKey, enabled] of Object.entries(value)) {
877
+ if (enabled !== true)
878
+ throw new Error(`with: ${table.name}.aggregate.${name}.${outKey} 必须是 true`);
879
+ const plan = table.__plan.find(p => p.outKey === outKey);
880
+ if (!plan)
881
+ throw new Error(`with: ${table.name}.aggregate.${name} 未知列 ${outKey}`);
882
+ if (name === 'sum' && !AGGREGATE_NUMERIC.test(plan.sqlType)) {
883
+ throw new Error(`with: ${table.name}.aggregate.sum.${outKey} 必须是数值列(${plan.sqlType})`);
884
+ }
885
+ result.push({ outKey, dbKey: plan.dbKey, parse: value => plan.parse.parse(value) });
886
+ }
887
+ return result;
888
+ };
889
+ const sum = parseColumns('sum');
890
+ const max = parseColumns('max');
891
+ if (!count && sum.length === 0 && max.length === 0) {
892
+ throw new Error(`with: ${table.name}.aggregate 至少需要 count/sum/max 之一`);
893
+ }
894
+ return { count, sum, max };
895
+ }
896
+ const MAX_WITH_DEPTH = 3;
897
+ function resolveWith(ctx, table, withMap, depth = 1) {
898
+ if (depth > MAX_WITH_DEPTH)
899
+ throw new Error(`with: 关系加载超过最大深度 ${MAX_WITH_DEPTH}`);
649
900
  const entries = Object.entries(withMap).filter(([, v]) => v !== undefined && v !== false);
650
901
  if (entries.length === 0)
651
902
  return [];
@@ -662,13 +913,13 @@ function resolveWith(ctx, table, withMap) {
662
913
  throw new Error(`with: ${table.name}.${key} 只支持 true/false 或关系选项对象`);
663
914
  }
664
915
  const opts = enabled;
665
- const allowed = new Set(['where', 'orderBy', 'limit', 'offset', 'with']);
916
+ const allowed = new Set(['where', 'orderBy', 'limit', 'offset', 'with', 'aggregate']);
666
917
  for (const k of Object.keys(opts)) {
667
918
  if (!allowed.has(k))
668
919
  throw new Error(`with: ${table.name}.${key} 不支持选项 ${k}`);
669
920
  }
670
921
  if (Object.keys(opts).length === 0)
671
- throw new Error(`with: ${table.name}.${key} 需要 where/orderBy/limit/offset/with 之一`);
922
+ throw new Error(`with: ${table.name}.${key} 需要 where/orderBy/limit/offset/with/aggregate 之一`);
672
923
  if (def.through && (opts.where !== undefined || opts.orderBy !== undefined || opts.limit !== undefined || opts.offset !== undefined)) {
673
924
  throw new Error(`with: M2M through 关系 ${table.name}.${key} 暂不支持 where/orderBy/limit/offset`);
674
925
  }
@@ -678,6 +929,10 @@ function resolveWith(ctx, table, withMap) {
678
929
  if (opts.with !== undefined && (typeof opts.with !== 'object' || opts.with === null || Array.isArray(opts.with))) {
679
930
  throw new Error(`with: ${table.name}.${key}.with 必须是对象`);
680
931
  }
932
+ const aggregate = opts.aggregate === undefined ? undefined : parseAggregate(def.table, opts.aggregate);
933
+ if (aggregate && table.__plan.some(p => p.outKey === `${key}Aggregate`)) {
934
+ throw new Error(`with: 聚合别名 ${key}Aggregate 与 ${table.name} 的真实列冲突`);
935
+ }
681
936
  return {
682
937
  key,
683
938
  def,
@@ -686,10 +941,11 @@ function resolveWith(ctx, table, withMap) {
686
941
  limit: opts.limit,
687
942
  offset: opts.offset,
688
943
  with: opts.with,
944
+ aggregate,
689
945
  };
690
946
  });
691
947
  }
692
- async function attachThroughRelation(ctx, rows, key, def, through, nested) {
948
+ async function attachThroughRelation(ctx, rows, key, def, through, nested, depth = 1) {
693
949
  const from = resolveRelationColumn(def.from);
694
950
  const to = resolveRelationColumn(def.to);
695
951
  const throughFrom = resolveRelationColumn(through.from);
@@ -727,7 +983,7 @@ async function attachThroughRelation(ctx, rows, key, def, through, nested) {
727
983
  row[key] = bucket;
728
984
  }
729
985
  if (nested.length > 0 && children.length > 0)
730
- await attachRelations(ctx, children, nested);
986
+ await attachRelations(ctx, children, nested, depth);
731
987
  }
732
988
  function validateWithNumber(name, value) {
733
989
  if (value === undefined)
@@ -735,11 +991,76 @@ function validateWithNumber(name, value) {
735
991
  if (!Number.isInteger(value) || value < 0)
736
992
  throw new Error(`with: ${name} 必须是非负整数`);
737
993
  }
738
- async function attachRelations(ctx, rows, resolved) {
739
- for (const { key, def, where, orderBy, limit, offset, with: nestedInput } of resolved) {
740
- const nested = nestedInput !== undefined ? resolveWith(ctx, def.table, nestedInput) : [];
994
+ async function attachAggregate(ctx, rows, key, def, aggregate, where) {
995
+ const from = resolveRelationColumn(def.from);
996
+ const to = resolveRelationColumn(def.to);
997
+ const through = def.through;
998
+ const values = [...new Set(rows.map(r => r[from.outKey]).filter(v => v !== null && v !== undefined))];
999
+ const targetAliasName = '__zd_agg_target';
1000
+ const targetAlias = quoteIdent(targetAliasName);
1001
+ let parentRef;
1002
+ let fromClause;
1003
+ if (through) {
1004
+ const throughFrom = resolveRelationColumn(through.from);
1005
+ const throughTo = resolveRelationColumn(through.to);
1006
+ const throughAlias = quoteIdent('__zd_agg_through');
1007
+ parentRef = `${throughAlias}.${quoteIdent(throughFrom.dbKey)}`;
1008
+ fromClause = `from ${quoteIdent(through.table.name)} as ${throughAlias}`
1009
+ + ` join ${quoteIdent(def.table.name)} as ${targetAlias}`
1010
+ + ` on ${targetAlias}.${quoteIdent(to.dbKey)} = ${throughAlias}.${quoteIdent(throughTo.dbKey)}`;
1011
+ }
1012
+ else {
1013
+ parentRef = `${targetAlias}.${quoteIdent(to.dbKey)}`;
1014
+ fromClause = `from ${quoteIdent(def.table.name)} as ${targetAlias}`;
1015
+ }
1016
+ const sumFields = aggregate.sum.map((p, i) => ({ ...p, alias: `__zd_sum_${i}` }));
1017
+ const maxFields = aggregate.max.map((p, i) => ({ ...p, alias: `__zd_max_${i}` }));
1018
+ const select = [`${parentRef} as "__zd_parent"`];
1019
+ if (aggregate.count)
1020
+ select.push('count(*)::int as "__zd_count"');
1021
+ for (const p of sumFields)
1022
+ select.push(`sum(${targetAlias}.${quoteIdent(p.dbKey)})::float8 as ${quoteIdent(p.alias)}`);
1023
+ for (const p of maxFields)
1024
+ select.push(`max(${targetAlias}.${quoteIdent(p.dbKey)}) as ${quoteIdent(p.alias)}`);
1025
+ const wherePart = where === undefined
1026
+ ? { text: '', params: [] }
1027
+ : compileWhere(def.table, where, 1, [], targetAliasName);
1028
+ const params = [values, ...wherePart.params];
1029
+ const predicate = wherePart.text ? ` and (${wherePart.text})` : '';
1030
+ const text = `select ${select.join(', ')} ${fromClause}`
1031
+ + ` where ${parentRef} = any($1)${predicate}`
1032
+ + ` group by ${parentRef}`;
1033
+ const aggregateRows = values.length === 0
1034
+ ? []
1035
+ : await exec(ctx, text, params, { operation: 'with', table: def.table.name });
1036
+ const byParent = new Map();
1037
+ for (const row of aggregateRows) {
1038
+ const parent = row.__zd_parent;
1039
+ if (parent !== null && parent !== undefined)
1040
+ byParent.set(parent, row);
1041
+ }
1042
+ for (const row of rows) {
1043
+ const parent = row[from.outKey];
1044
+ const raw = parent === null || parent === undefined ? undefined : byParent.get(parent);
1045
+ const result = {};
1046
+ if (aggregate.count)
1047
+ result.count = Number(raw?.__zd_count ?? 0);
1048
+ if (sumFields.length > 0) {
1049
+ result.sum = Object.fromEntries(sumFields.map(p => [p.outKey, raw?.[p.alias] == null ? null : Number(raw[p.alias])]));
1050
+ }
1051
+ if (maxFields.length > 0) {
1052
+ result.max = Object.fromEntries(maxFields.map(p => [p.outKey, raw?.[p.alias] == null ? null : p.parse(raw[p.alias])]));
1053
+ }
1054
+ row[`${key}Aggregate`] = result;
1055
+ }
1056
+ }
1057
+ async function attachRelations(ctx, rows, resolved, depth = 1) {
1058
+ for (const { key, def, where, orderBy, limit, offset, with: nestedInput, aggregate } of resolved) {
1059
+ const nested = nestedInput !== undefined ? resolveWith(ctx, def.table, nestedInput, depth + 1) : [];
741
1060
  if (def.through) {
742
- await attachThroughRelation(ctx, rows, key, def, def.through, nested);
1061
+ await attachThroughRelation(ctx, rows, key, def, def.through, nested, depth + 1);
1062
+ if (aggregate)
1063
+ await attachAggregate(ctx, rows, key, def, aggregate, where);
743
1064
  continue;
744
1065
  }
745
1066
  const from = resolveRelationColumn(def.from);
@@ -807,11 +1128,13 @@ async function attachRelations(ctx, rows, resolved) {
807
1128
  row[key] = def._kind === 'many' ? (bucket ?? []) : (bucket?.[0] ?? null);
808
1129
  }
809
1130
  if (nested.length > 0 && children.length > 0)
810
- await attachRelations(ctx, children, nested);
1131
+ await attachRelations(ctx, children, nested, depth + 1);
1132
+ if (aggregate)
1133
+ await attachAggregate(ctx, rows, key, def, aggregate, where);
811
1134
  }
812
1135
  }
813
1136
  async function findManyImpl(ctx, table, opts = {}) {
814
- const resolved = opts.with ? resolveWith(ctx, table, opts.with) : [];
1137
+ const resolved = opts.with ? resolveWith(ctx, table, opts.with, 1) : [];
815
1138
  const selectEntries = opts.select ? Object.entries(opts.select).filter(([, v]) => v) : null;
816
1139
  if (selectEntries && selectEntries.length === 0)
817
1140
  throw new Error('select 不能为空');
@@ -890,6 +1213,7 @@ export function createDb(conn, validate = 'always', relations = [], opts = {}) {
890
1213
  };
891
1214
  const db = {
892
1215
  raw: conn,
1216
+ rawQuery: (text, params) => rawQueryImpl(ctx, text, params),
893
1217
  sql: ((strings, ...params) => {
894
1218
  const args = params.map(p => isSelectFragment(p) ? p.__fragment : p);
895
1219
  assertParams(args);