orchid-orm 1.77.3 → 1.78.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/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { DynamicRawSQL, QueryHooks, RawSql, VirtualColumn, _appendQuery, _appendQueryOnUpsertCreate, _clone, _createDbSqlMethod, _hookSelectColumns, _initQueryBuilder, _onUpsertUpdate, _orCreate, _prependWith, _prependWithOnUpsertCreate, _queryCreate, _queryCreateMany, _queryCreateManyFrom, _queryDefaults, _queryDelete, _queryFindBy, _queryFindByOptional, _queryHookAfterCreate, _queryHookAfterUpdate, _queryInsert, _queryInsertMany, _queryInsertManyFrom, _queryJoinOn, _queryRows, _querySelect, _queryTake, _queryTakeOptional, _queryUpdate, _queryUpdateOrThrow, _queryUpsert, _queryWhere, _queryWhereExists, _queryWhereIn, applyMixins, cloneQueryBaseUnscoped, defaultSchemaConfig, emptyArray, emptyObject, getCallerFilePath, getClonedQueryData, getColumnTypes, getForeignKeyTableInstance, getFreeAlias, getPrimaryKeys, getQueryAs, getShapeFromSelect, getStackTrace, internalSchemaConfig, isExpression, isQueryReturnsAll, makeColumnTypes, noop, objectHasValues, parseTableData, parseTableDataInput, pick, prepareSubQueryForSql, pushQueryOnForOuter, setQueryObjectValueImmutable, tableDataMethods, toArray, toSnakeCase } from "pqb/internal";
1
+ import { DynamicRawSQL, QueryHooks, RawSql, VirtualColumn, _appendQuery, _appendQueryOnUpsertCreate, _clone, _createDbSqlMethod, _hookSelectColumns, _initQueryBuilder, _onUpsertUpdate, _orCreate, _prependWith, _prependWithOnUpsertCreate, _queryCreate, _queryCreateMany, _queryCreateManyFrom, _queryDefaults, _queryDelete, _queryFindBy, _queryFindByOptional, _queryHookAfterCreate, _queryHookAfterUpdate, _queryInsert, _queryInsertForEachFrom, _queryInsertMany, _queryInsertManyFrom, _queryJoinOn, _querySelect, _queryTake, _queryTakeOptional, _queryUpdate, _queryUpdateOrThrow, _queryUpsert, _queryWhere, _queryWhereExists, _queryWhereIn, applyMixins, cloneQueryBaseUnscoped, defaultSchemaConfig, emptyArray, emptyObject, getCallerFilePath, getClonedQueryData, getColumnTypes, getForeignKeyTableInstance, getFreeAlias, getPrimaryKeys, getQueryAs, getShapeFromSelect, getStackTrace, internalSchemaConfig, isExpression, isQueryReturnsAll, makeColumnTypes, noop, objectHasValues, parseTableData, parseTableDataInput, prepareSubQueryForSql, pushQueryOnForOuter, setQueryObjectValueImmutable, tableDataMethods, toArray, toSnakeCase } from "pqb/internal";
2
2
  import { Db, NotFoundError, OrchidOrmInternalError } from "pqb";
3
3
  import { AsyncLocalStorage } from "node:async_hooks";
4
4
  export * from "pqb";
@@ -587,6 +587,21 @@ const makeNestedUpdateUpsertData = (upsert, setIds) => ({
587
587
  ...setIds
588
588
  }
589
589
  });
590
+ const makeRawSqlPlaceholders = (length) => {
591
+ return Array.from({ length }, () => new RawSql(""));
592
+ };
593
+ const makeRawSqlPlaceholderRecord = (keys) => {
594
+ const placeholders = makeRawSqlPlaceholders(keys.length);
595
+ const record = {};
596
+ for (let i = 0; i < keys.length; i++) record[keys[i]] = placeholders[i];
597
+ return record;
598
+ };
599
+ const setRawSqlPlaceholdersFromCte = (placeholders, cte, primaryKeys, rowIndex, count) => {
600
+ for (let i = 0; i < placeholders.length; i++) placeholders[i]._sql = selectCteColumnFromManySql(cte, primaryKeys[i], rowIndex, count);
601
+ };
602
+ const setRawSqlPlaceholderRecordFromCte = (record, keys, cte, primaryKeys, rowIndex, count) => {
603
+ setRawSqlPlaceholdersFromCte(keys.map((key) => record[key]), cte, primaryKeys, rowIndex, count);
604
+ };
590
605
  const getThroughRelation = (table, through) => {
591
606
  return table.relations[through];
592
607
  };
@@ -597,7 +612,7 @@ const hasRelationHandleCreate = (q, ctx, items, rowIndexes, key, primaryKeys, ne
597
612
  q.q.wrapInTransaction = true;
598
613
  items.forEach((item, i) => {
599
614
  const value = item[key];
600
- if ((!value.create || Array.isArray(value.create) && value.create.length === 0) && (!value.connect || Array.isArray(value.connect) && value.connect.length === 0) && (!value.connectOrCreate || Array.isArray(value.connectOrCreate) && value.connectOrCreate.length === 0)) return;
615
+ if ((!value.create || Array.isArray(value.create) && value.create.length === 0) && (!value.connect || Array.isArray(value.connect) && value.connect.length === 0) && (!value.connectOrCreate || Array.isArray(value.connectOrCreate) && value.connectOrCreate.length === 0) && (!("upsert" in value) || !value.upsert || Array.isArray(value.upsert) && value.upsert.length === 0)) return;
601
616
  const store = ctx;
602
617
  if (!store.hasRelation) store.hasRelation = {};
603
618
  const values = [rowIndexes[i], value];
@@ -610,14 +625,6 @@ const hasRelationHandleCreate = (q, ctx, items, rowIndexes, key, primaryKeys, ne
610
625
  _queryHookAfterCreate(q, primaryKeys, (rows, q) => nestedInsert(q, relationData.map(([rowIndex, data]) => [rows[rowIndex], data])));
611
626
  });
612
627
  };
613
- const hasRelationHandleUpdate = (q, set, key, primaryKeys, nestedUpdate) => {
614
- const value = set[key];
615
- if (!value.set && !("upsert" in value) && (!value.add || Array.isArray(value.add) && value.add.length === 0) && (!value.disconnect || Array.isArray(value.disconnect) && value.disconnect.length === 0) && (!value.delete || Array.isArray(value.delete) && value.delete.length === 0) && (!value.update || Array.isArray(value.update.where) && value.update.where.length === 0) && (!value.create || Array.isArray(value.create) && value.create.length === 0)) return;
616
- q.q.wrapInTransaction = true;
617
- _queryHookAfterUpdate(q, primaryKeys, (rows, q) => {
618
- return nestedUpdate(q, rows, value);
619
- });
620
- };
621
628
  function joinHasThrough(q, baseQuery, joiningQuery, throughRelation, sourceRelation) {
622
629
  return q.whereExists(throughRelation.joinQuery(throughRelation.query, baseQuery), (() => {
623
630
  const as = getQueryAs(joiningQuery);
@@ -683,17 +690,17 @@ const getColumnKeyFromDbName = (query, name) => {
683
690
  for (const k in query.shape) if (query.shape[k].data.name === name) return k;
684
691
  return name;
685
692
  };
686
- const selectCteColumnsSql$1 = (cteAs, columns) => `(SELECT ${columns.map((c) => `"${cteAs}"."${c}"`).join(", ")} FROM "${cteAs}")`;
687
- const selectCteColumnSql$1 = (cteAs, column) => `(SELECT "${cteAs}"."${column}" FROM "${cteAs}")`;
693
+ const selectCteColumnsSql = (cteAs, columns) => `(SELECT ${columns.map((c) => `"${cteAs}"."${c}"`).join(", ")} FROM "${cteAs}")`;
694
+ const selectCteColumnSql = (cteAs, column) => `(SELECT "${cteAs}"."${column}" FROM "${cteAs}")`;
688
695
  const makeNestedUpdateRelationIds = (q, relQuery, primaryKeys, foreignKeys) => {
689
696
  const selectIdsSql = new RawSql("");
690
697
  const setIds = {};
691
698
  for (const foreignKey of foreignKeys) setIds[foreignKey] = new RawSql("");
692
699
  let appendedAs;
693
700
  _hookSelectColumns(q, primaryKeys, (aliasedPrimaryKeys) => {
694
- selectIdsSql._sql = selectCteColumnsSql$1(appendedAs, aliasedPrimaryKeys);
701
+ selectIdsSql._sql = selectCteColumnsSql(appendedAs, aliasedPrimaryKeys);
695
702
  foreignKeys.forEach((foreignKey, i) => {
696
- setIds[foreignKey]._sql = selectCteColumnSql$1(appendedAs, aliasedPrimaryKeys[i]);
703
+ setIds[foreignKey]._sql = selectCteColumnSql(appendedAs, aliasedPrimaryKeys[i]);
697
704
  });
698
705
  });
699
706
  return {
@@ -713,8 +720,8 @@ const selectCteColumnFromManySql = (cteAs, column, rowIndex, count) => {
713
720
  return sql + ")";
714
721
  };
715
722
  const selectCteColumnMustExistSql = (i, cteAs, column) => {
716
- const selectColumn = selectCteColumnSql$1(cteAs, column);
717
- return i === 0 ? `CASE WHEN (SELECT count(*) FROM "${cteAs}") = 0 AND (SELECT 'not-found')::int = 0 THEN NULL ELSE ${selectColumn} END` : selectColumn;
723
+ const selectColumn = selectCteColumnSql(cteAs, column);
724
+ return i === 0 ? `CASE WHEN (SELECT count(*) FROM "${cteAs}") = 0 AND (SELECT ':not-found:1:0')::int = 0 THEN NULL ELSE ${selectColumn} END` : selectColumn;
718
725
  };
719
726
  const setForeignKeysFromCte = (record, primaryKeys, foreignKeys, mustExist) => {
720
727
  for (const key of foreignKeys) record[key] = new RawSql("");
@@ -722,7 +729,7 @@ const setForeignKeysFromCte = (record, primaryKeys, foreignKeys, mustExist) => {
722
729
  foreignKeys.forEach(mustExist ? (foreignKey, i) => {
723
730
  record[foreignKey]._sql = selectCteColumnMustExistSql(i, as, primaryKeys[i]);
724
731
  } : (foreignKey, i) => {
725
- record[foreignKey]._sql = selectCteColumnSql$1(as, primaryKeys[i]);
732
+ record[foreignKey]._sql = selectCteColumnSql(as, primaryKeys[i]);
726
733
  });
727
734
  };
728
735
  };
@@ -803,7 +810,7 @@ const belongsToCreate = (key, state, q, ctx, items) => {
803
810
  let nestedCreateItems;
804
811
  items.forEach((item) => {
805
812
  const value = item[key];
806
- const kind = value.create ? "create" : value.connect ? "connect" : "connectOrCreate";
813
+ const kind = value.create ? "create" : value.connect ? "connect" : value.connectOrCreate ? "connectOrCreate" : void 0;
807
814
  if (kind) {
808
815
  const nestedCreateItem = (nestedCreateItems ??= {})[kind] ??= {
809
816
  items: [],
@@ -811,7 +818,7 @@ const belongsToCreate = (key, state, q, ctx, items) => {
811
818
  };
812
819
  nestedCreateItem.items.push(item);
813
820
  nestedCreateItem.values.push(value[kind]);
814
- if (kind !== "connect") for (const key of foreignKeys) item[key] = new RawSql("");
821
+ if (kind === "create" || kind === "connectOrCreate") for (const key of foreignKeys) item[key] = new RawSql("");
815
822
  }
816
823
  });
817
824
  if (!nestedCreateItems) return;
@@ -853,7 +860,7 @@ const belongsToCreate = (key, state, q, ctx, items) => {
853
860
  (queryForCreate.q.type === "upsert" ? _prependWithOnUpsertCreate : _prependWith)(queryForCreate, asFn, _orCreate(_queryWhere(selectPKeys, [value.where]), value.create));
854
861
  });
855
862
  };
856
- const nestedUpdate$2 = ({ query, primaryKeys, foreignKeys, len }) => {
863
+ const nestedUpdate = ({ query, primaryKeys, foreignKeys, len }) => {
857
864
  return ((self, update, params) => {
858
865
  if (params.create) {
859
866
  const createQuery = _querySelect(_queryCreate(query.clone(), params.create), primaryKeys);
@@ -862,7 +869,7 @@ const nestedUpdate$2 = ({ query, primaryKeys, foreignKeys, len }) => {
862
869
  } else if (params.update) {
863
870
  let appendedAs;
864
871
  _hookSelectColumns(self, foreignKeys, (aliasedForeignKeys) => {
865
- selectIdsSql._sql = selectCteColumnsSql$1(appendedAs, aliasedForeignKeys);
872
+ selectIdsSql._sql = selectCteColumnsSql(appendedAs, aliasedForeignKeys);
866
873
  });
867
874
  const selectIdsSql = new RawSql("");
868
875
  const updateQuery = _queryUpdate(_queryWhereIn(query.clone(), true, primaryKeys, selectIdsSql), params.update);
@@ -872,6 +879,7 @@ const nestedUpdate$2 = ({ query, primaryKeys, foreignKeys, len }) => {
872
879
  if (isQueryReturnsAll(self)) throw new Error("`upsert` option is not allowed in a batch update");
873
880
  const { relQuery } = relWithSelectIds(self, query, primaryKeys, foreignKeys);
874
881
  const upsertQuery = _querySelect(_queryUpsert(relQuery, params.upsert), primaryKeys);
882
+ upsertQuery.q.returnType = "one";
875
883
  const asFn = setForeignKeysFromCte(update, primaryKeys, foreignKeys);
876
884
  _prependWith(self, asFn, upsertQuery);
877
885
  } else if (params.delete) {
@@ -897,7 +905,9 @@ const nestedUpdate$2 = ({ query, primaryKeys, foreignKeys, len }) => {
897
905
  }
898
906
  if (loadPrimaryKeys) {
899
907
  const asFn = setForeignKeysFromCte(update, loadPrimaryKeys, loadForeignKeys, true);
900
- _prependWith(self, asFn, _queryFindBy(query.select(...loadPrimaryKeys), params.set));
908
+ const findByQuery = _queryFindBy(query.select(...loadPrimaryKeys), params.set);
909
+ findByQuery.q.returnType = "value";
910
+ _prependWith(self, asFn, findByQuery);
901
911
  }
902
912
  }
903
913
  });
@@ -909,7 +919,7 @@ const relWithSelectIds = (self, rel, primaryKeys, foreignKeys) => {
909
919
  const selectIdsQuery = makeSelectIdsQuery(self, foreignKeys);
910
920
  const selectIdsSql = new RawSql("");
911
921
  _prependWith(self, (as) => {
912
- selectIdsSql._sql = selectCteColumnsSql$1(as, foreignKeys);
922
+ selectIdsSql._sql = selectCteColumnsSql(as, foreignKeys);
913
923
  }, selectIdsQuery);
914
924
  return {
915
925
  selectIdsSql,
@@ -929,7 +939,7 @@ var BelongsToVirtualColumn = class extends VirtualColumn {
929
939
  super(schema);
930
940
  this.key = key;
931
941
  this.state = state;
932
- this.nestedUpdate = nestedUpdate$2(this.state);
942
+ this.nestedUpdate = nestedUpdate(this.state);
933
943
  }
934
944
  create(q, ctx, items) {
935
945
  belongsToCreate(this.key, this.state, q, ctx, items);
@@ -1096,7 +1106,7 @@ const hasOneCreate = (key, state, nestedInsertFn, self, ctx, items, rowIndexes,
1096
1106
  const connectWhereExists = querySelf.q.type === "upsert" ? new RawSql("") : void 0;
1097
1107
  const query = _queryUpdateOrThrow(rel.where(value), connect.items[i]);
1098
1108
  if (connectWhereExists) _queryWhere(query, [connectWhereExists]);
1099
- query.q.ensureCount = 1;
1109
+ query.q.ensureCount = { expected: 1 };
1100
1110
  (querySelf.q.type === "upsert" ? _appendQueryOnUpsertCreate : _appendQuery)(querySelf, query, (as) => {
1101
1111
  upsertCreateAs = as;
1102
1112
  setUpsertCreatePlaceholders();
@@ -1249,66 +1259,163 @@ const makeHasOneMethod = (tableConfig, table, relation, relationName, query) =>
1249
1259
  }
1250
1260
  };
1251
1261
  };
1252
- const getWhereForNestedUpdate = (t, data, params, primaryKeys, foreignKeys) => {
1253
- return t.where({
1254
- IN: {
1255
- columns: foreignKeys,
1256
- values: data.map((item) => primaryKeys.map((key) => item[key]))
1257
- },
1258
- OR: params ? toArray(params) : void 0
1259
- });
1260
- };
1261
- const nestedUpdate$1 = ({ query: relQuery, primaryKeys, foreignKeys }) => {
1262
- const len = primaryKeys.length;
1263
- return (async (_, data, params) => {
1264
- const t = relQuery.clone();
1265
- if (params.create) {
1266
- const obj = {};
1267
- for (let i = 0; i < len; i++) obj[foreignKeys[i]] = data[0][primaryKeys[i]];
1268
- await t.insertMany(params.create.map((create) => ({
1269
- ...create,
1270
- ...obj
1271
- })));
1272
- }
1273
- if (params.add) {
1274
- if (data.length > 1) throw new OrchidOrmInternalError(relQuery, "`connect` is not available when updating multiple records, it is only applicable for a single record update");
1275
- const obj = {};
1276
- for (let i = 0; i < len; i++) obj[foreignKeys[i]] = data[0][primaryKeys[i]];
1277
- const relatedWheres = toArray(params.add);
1278
- const count = await _queryUpdate(t.where({ OR: relatedWheres }), obj);
1279
- if (count < relatedWheres.length) throw new OrchidOrmInternalError(relQuery, `Expected to find at least ${relatedWheres.length} record(s) based on \`add\` conditions, but found ${count}`);
1280
- }
1281
- if (params.disconnect || params.set) {
1282
- const obj = {};
1283
- for (const foreignKey of foreignKeys) obj[foreignKey] = null;
1284
- const setConditions = params.set && (Array.isArray(params.set) ? params.set.length : objectHasValues(params.set)) && (Array.isArray(params.set) ? { OR: params.set } : params.set);
1285
- let queryToDisconnect = getWhereForNestedUpdate(t, data, params.disconnect, primaryKeys, foreignKeys);
1286
- if (setConditions) queryToDisconnect = queryToDisconnect.whereNot(setConditions);
1287
- await _queryUpdate(queryToDisconnect, obj);
1288
- if (setConditions) {
1289
- const obj = {};
1290
- for (let i = 0; i < len; i++) obj[foreignKeys[i]] = data[0][primaryKeys[i]];
1291
- await _queryUpdate(t.where(setConditions), obj);
1292
- }
1293
- }
1294
- if (params.delete || params.update) {
1295
- const q = getWhereForNestedUpdate(t, data, params.delete || params.update?.where, primaryKeys, foreignKeys);
1296
- if (params.delete) await _queryDelete(q);
1297
- else if (params.update) await _queryUpdate(q, params.update.data);
1298
- }
1299
- });
1300
- };
1301
- const hasManyUpdate = (key, state, nestedUpdateFn, self, set) => {
1262
+ const hasManyUpdate = (key, state, self, set) => {
1302
1263
  const querySelf = self;
1303
1264
  const params = set[key];
1304
1265
  throwIfQueryReturnsAllForNestedUpdate(querySelf, params);
1305
- hasRelationHandleUpdate(querySelf, set, key, state.primaryKeys, nestedUpdateFn);
1306
- if (params.upsert) {
1266
+ const makeRelationIds = () => makeNestedUpdateRelationIds(querySelf, state.query, state.primaryKeys, state.foreignKeys);
1267
+ const hasDisconnect = !!params.disconnect && (!Array.isArray(params.disconnect) || params.disconnect.length > 0);
1268
+ const hasSetConditions = params.set && (Array.isArray(params.set) ? params.set.length : objectHasValues(params.set));
1269
+ if (hasDisconnect || params.set !== void 0) {
1307
1270
  const { primaryKeys, foreignKeys, query: relQuery } = state;
1308
- const ids = makeNestedUpdateRelationIds(querySelf, relQuery, primaryKeys, foreignKeys);
1309
- const appendedQuery = _queryUpsert(ids.existingRelQuery, makeNestedUpdateUpsertData(params.upsert, ids.setIds));
1310
- appendedQuery.q.returnType = "void";
1311
- _appendQuery(querySelf, appendedQuery, ids.setAppendedAs);
1271
+ const relatedAs = relQuery.q.as || relQuery.table;
1272
+ const joinSql = new RawSql("");
1273
+ let mainAs;
1274
+ let aliasedPrimaryKeys;
1275
+ const setNulls = Object.fromEntries(foreignKeys.map((foreignKey) => [foreignKey, null]));
1276
+ let queryToDisconnect = relQuery.clone().updateFrom("");
1277
+ if (hasDisconnect && params.set === void 0) queryToDisconnect = queryToDisconnect.where({ OR: toArray(params.disconnect) });
1278
+ if (params.set && hasSetConditions) queryToDisconnect = queryToDisconnect.whereNot(Array.isArray(params.set) ? { OR: params.set } : params.set);
1279
+ const disconnectQuery = _queryUpdate(queryToDisconnect.whereSql(joinSql), setNulls);
1280
+ disconnectQuery.q.returnType = "void";
1281
+ const setJoinSql = () => {
1282
+ if (mainAs && aliasedPrimaryKeys) joinSql._sql = foreignKeys.map((foreignKey, i) => `"${relatedAs}"."${relQuery.shape[foreignKey].data.name || foreignKey}" = "${mainAs}"."${aliasedPrimaryKeys[i]}"`).join(" AND ");
1283
+ };
1284
+ _hookSelectColumns(querySelf, primaryKeys, (aliased) => {
1285
+ aliasedPrimaryKeys = aliased;
1286
+ setJoinSql();
1287
+ });
1288
+ _appendQuery(querySelf, disconnectQuery, (as) => {
1289
+ mainAs = as;
1290
+ disconnectQuery.q.updateFrom.w = as;
1291
+ disconnectQuery.q.joinedShapes = {
1292
+ ...disconnectQuery.q.joinedShapes,
1293
+ [as]: {}
1294
+ };
1295
+ setJoinSql();
1296
+ });
1297
+ }
1298
+ const hasAdd = params.add && (!Array.isArray(params.add) || params.add.length > 0);
1299
+ const updates = params.update ? toArray(params.update) : [];
1300
+ const hasUpdate = updates.some((update) => !Array.isArray(update.where) || update.where.length);
1301
+ const relatedIdsAs = hasUpdate && (hasAdd || hasSetConditions) ? [] : void 0;
1302
+ const sourceCondition = relatedIdsAs ? new RawSql("") : void 0;
1303
+ const updateFrom = relatedIdsAs ? new RawSql("") : void 0;
1304
+ const upsertRelatedIdsAs = params.upsert ? [] : void 0;
1305
+ const upsertSourceCondition = upsertRelatedIdsAs ? new RawSql("") : void 0;
1306
+ const upsertUpdateFrom = upsertRelatedIdsAs ? new RawSql("") : void 0;
1307
+ let appendRelatedQuery;
1308
+ const setSourceCondition = (relatedIdsAs, sourceCondition, updateFrom, relatedPrimaryKeys) => {
1309
+ if (!relatedIdsAs) return;
1310
+ const relatedAs = state.query.q.as || state.query.table;
1311
+ const sourceAs = "\"relatedIds\"";
1312
+ sourceCondition._sql = relatedPrimaryKeys.map((primaryKey) => `"${relatedAs}"."${state.query.shape[primaryKey].data.name || primaryKey}" = ${sourceAs}."${primaryKey}"`).join(" AND ");
1313
+ const columns = relatedPrimaryKeys.map((key) => `"${key}"`).join(", ");
1314
+ updateFrom._sql = `(${relatedIdsAs.map((as) => `SELECT ${columns} FROM ${as._sql}`).join(" UNION ALL ")}) AS ${sourceAs}`;
1315
+ };
1316
+ if (hasAdd || hasSetConditions) {
1317
+ const { query: relQuery } = state;
1318
+ const relatedWheres = [...hasAdd ? toArray(params.add) : [], ...hasSetConditions ? toArray(params.set) : []];
1319
+ if (relatedIdsAs || upsertRelatedIdsAs) {
1320
+ const relatedPrimaryKeys = getPrimaryKeys(relQuery);
1321
+ const relatedQuery = _querySelect(relQuery.clone().where({ OR: relatedWheres }), relatedPrimaryKeys);
1322
+ relatedQuery.q.returnType = "void";
1323
+ _appendQuery(querySelf, relatedQuery, () => {}, (as) => {
1324
+ if (relatedIdsAs) {
1325
+ relatedIdsAs.push(new RawSql(`"${as}"`));
1326
+ setSourceCondition(relatedIdsAs, sourceCondition, updateFrom, relatedPrimaryKeys);
1327
+ }
1328
+ if (upsertRelatedIdsAs) {
1329
+ upsertRelatedIdsAs.push(new RawSql(`"${as}"`));
1330
+ setSourceCondition(upsertRelatedIdsAs, upsertSourceCondition, upsertUpdateFrom, relatedPrimaryKeys);
1331
+ }
1332
+ });
1333
+ if (!querySelf.q.upsertUpdate) relatedQuery.q.ensureCount = {
1334
+ expected: relatedWheres.length,
1335
+ message: `based on \`${hasAdd ? "add" : "set"}\` conditions`
1336
+ };
1337
+ }
1338
+ appendRelatedQuery = () => {
1339
+ const ids = makeRelationIds();
1340
+ const addQuery = _queryUpdate(sourceCondition ? relQuery.clone().where({
1341
+ OR: relatedWheres,
1342
+ NOT: { OR: updates.flatMap((update) => toArray(update.where)) }
1343
+ }) : relQuery.clone().where({ OR: relatedWheres }), ids.setIds);
1344
+ addQuery.q.returnType = "void";
1345
+ if (!sourceCondition && !querySelf.q.upsertUpdate) addQuery.q.ensureCount = {
1346
+ expected: relatedWheres.length,
1347
+ message: `based on \`${hasAdd ? "add" : "set"}\` conditions`
1348
+ };
1349
+ _appendQuery(querySelf, addQuery, ids.setAppendedAs);
1350
+ };
1351
+ }
1352
+ if (upsertRelatedIdsAs) {
1353
+ const { query: relQuery } = state;
1354
+ const ids = makeRelationIds();
1355
+ let existingRelQuery = hasDisconnect ? ids.existingRelQuery.whereNot({ OR: toArray(params.disconnect) }) : ids.existingRelQuery;
1356
+ if (params.set !== void 0) existingRelQuery = hasSetConditions ? existingRelQuery.where(Array.isArray(params.set) ? { OR: params.set } : params.set) : existingRelQuery.none();
1357
+ const relatedPrimaryKeys = getPrimaryKeys(relQuery);
1358
+ const relatedQuery = _querySelect(existingRelQuery, relatedPrimaryKeys);
1359
+ relatedQuery.q.returnType = "void";
1360
+ _appendQuery(querySelf, relatedQuery, ids.setAppendedAs, (as) => {
1361
+ upsertRelatedIdsAs.push(new RawSql(`"${as}"`));
1362
+ setSourceCondition(upsertRelatedIdsAs, upsertSourceCondition, upsertUpdateFrom, relatedPrimaryKeys);
1363
+ });
1364
+ }
1365
+ if (params.delete && (!Array.isArray(params.delete) || params.delete.length > 0)) {
1366
+ const ids = makeRelationIds();
1367
+ const deleteQuery = _queryDelete(ids.existingRelQuery.where({ OR: toArray(params.delete) }));
1368
+ deleteQuery.q.returnType = "void";
1369
+ _appendQuery(querySelf, deleteQuery, ids.setAppendedAs);
1370
+ }
1371
+ appendRelatedQuery?.();
1372
+ if (params.upsert) {
1373
+ const { query: relQuery } = state;
1374
+ for (const upsert of toArray(params.upsert)) {
1375
+ const ids = makeRelationIds();
1376
+ const appendedQuery = _queryUpsert(upsertSourceCondition ? relQuery.clone().where(upsert.findBy).whereSql(upsertSourceCondition) : ids.existingRelQuery.where(upsert.findBy), makeNestedUpdateUpsertData(upsertSourceCondition ? {
1377
+ ...upsert,
1378
+ update: {
1379
+ ...upsert.update,
1380
+ ...ids.setIds
1381
+ }
1382
+ } : upsert, ids.setIds));
1383
+ if (upsertUpdateFrom) appendedQuery.q.updateFrom = {
1384
+ u: true,
1385
+ x: upsertUpdateFrom
1386
+ };
1387
+ appendedQuery.q.returnType = "void";
1388
+ _appendQuery(querySelf, appendedQuery, ids.setAppendedAs);
1389
+ }
1390
+ }
1391
+ if (hasUpdate) for (const update of updates) {
1392
+ if (Array.isArray(update.where) && update.where.length === 0) continue;
1393
+ const { query: relQuery } = state;
1394
+ const ids = makeRelationIds();
1395
+ let relatedUpdateQuery = relQuery.clone().where({ OR: toArray(update.where) });
1396
+ if (sourceCondition) {
1397
+ relatedUpdateQuery.q.updateFrom = {
1398
+ u: true,
1399
+ x: updateFrom
1400
+ };
1401
+ relatedUpdateQuery = relatedUpdateQuery.whereSql(sourceCondition);
1402
+ } else relatedUpdateQuery = ids.existingRelQuery.where({ OR: toArray(update.where) });
1403
+ const updateQuery = _queryUpdate(relatedUpdateQuery, sourceCondition ? {
1404
+ ...update.data,
1405
+ ...ids.setIds
1406
+ } : update.data);
1407
+ updateQuery.q.returnType = "void";
1408
+ _appendQuery(querySelf, updateQuery, ids.setAppendedAs);
1409
+ }
1410
+ if (params.create?.length) {
1411
+ const { query: relQuery } = state;
1412
+ const ids = makeRelationIds();
1413
+ const createQuery = _queryInsertMany(relQuery.clone(), params.create.map((data) => ({
1414
+ ...data,
1415
+ ...ids.setIds
1416
+ })));
1417
+ createQuery.q.returnType = "void";
1418
+ _appendQuery(querySelf, createQuery, ids.setAppendedAs);
1312
1419
  }
1313
1420
  };
1314
1421
  const makeHasManyThroughMethod = ({ table, query, relation, relationName, relPKeys }) => {
@@ -1392,12 +1499,29 @@ const nestedInsert$1 = ({ query, primaryKeys, foreignKeys }) => {
1392
1499
  }
1393
1500
  await _queryCreateMany(t, records);
1394
1501
  }
1502
+ items.length = 0;
1503
+ for (const item of data) if (item[1].upsert) items.push(item);
1504
+ if (items.length) {
1505
+ const queries = [];
1506
+ for (const [selfData, { upsert }] of items) {
1507
+ const foreignKeyValues = {};
1508
+ for (let i = 0; i < len; i++) foreignKeyValues[foreignKeys[i]] = selfData[primaryKeys[i]];
1509
+ for (const item of toArray(upsert)) queries.push(_queryUpsert(t.clone().where(item.findBy), {
1510
+ update: {
1511
+ ...item.update,
1512
+ ...foreignKeyValues
1513
+ },
1514
+ create: makeNestedUpdateUpsertData(item, foreignKeyValues).create
1515
+ }));
1516
+ }
1517
+ await Promise.all(queries);
1518
+ }
1395
1519
  });
1396
1520
  };
1397
1521
  const hasManyCreate = (key, state, nestedInsertFn, self, ctx, items, rowIndexes, count) => {
1398
1522
  const querySelf = self;
1399
1523
  if (count <= querySelf.qb.internal.nestedCreateBatchMax) {
1400
- const { query: rel, primaryKeys, foreignKeys } = state;
1524
+ const { query: relQuery, primaryKeys, foreignKeys } = state;
1401
1525
  let nestedCreateItems;
1402
1526
  items.forEach((item, i) => {
1403
1527
  const value = item[key];
@@ -1407,26 +1531,28 @@ const hasManyCreate = (key, state, nestedInsertFn, self, ctx, items, rowIndexes,
1407
1531
  items: []
1408
1532
  };
1409
1533
  nestedCreateItem.indexes.push(rowIndexes[i]);
1410
- const data = value.create.map((obj) => {
1411
- const data = { ...obj };
1412
- for (const key of foreignKeys) data[key] = new RawSql("");
1413
- return data;
1414
- });
1534
+ const data = value.create.map((obj) => ({
1535
+ ...obj,
1536
+ ...makeRawSqlPlaceholderRecord(foreignKeys)
1537
+ }));
1538
+ nestedCreateItem.items.push(data);
1539
+ }
1540
+ for (const kind of [
1541
+ "connect",
1542
+ "connectOrCreate",
1543
+ "upsert"
1544
+ ]) {
1545
+ const values = kind === "upsert" ? value.upsert ? toArray(value.upsert) : void 0 : value[kind];
1546
+ if (!values?.length) continue;
1547
+ const nestedCreateItem = (nestedCreateItems ??= {})[kind] ??= {
1548
+ indexes: [],
1549
+ items: [],
1550
+ values: []
1551
+ };
1552
+ nestedCreateItem.indexes.push(rowIndexes[i]);
1553
+ nestedCreateItem.values.push(values);
1554
+ const data = makeRawSqlPlaceholderRecord(foreignKeys);
1415
1555
  nestedCreateItem.items.push(data);
1416
- } else {
1417
- const kind = value.connect?.length ? "connect" : value.connectOrCreate?.length ? "connectOrCreate" : void 0;
1418
- if (kind) {
1419
- const nestedCreateItem = (nestedCreateItems ??= {})[kind] ??= {
1420
- indexes: [],
1421
- items: [],
1422
- values: []
1423
- };
1424
- nestedCreateItem.indexes.push(rowIndexes[i]);
1425
- nestedCreateItem.values.push(value[kind]);
1426
- const data = {};
1427
- for (const key of foreignKeys) data[key] = new RawSql("");
1428
- nestedCreateItem.items.push(data);
1429
- }
1430
1556
  }
1431
1557
  });
1432
1558
  if (!nestedCreateItems) return;
@@ -1437,6 +1563,7 @@ const hasManyCreate = (key, state, nestedInsertFn, self, ctx, items, rowIndexes,
1437
1563
  let upsertCreateAs;
1438
1564
  let connectAs;
1439
1565
  let connectOrCreateAs;
1566
+ let upsertAs;
1440
1567
  const setUpsertCreatePlaceholders = () => {
1441
1568
  if (!upsertCreateAs || !createAliasedPrimaryKeys) return;
1442
1569
  const aliasedPrimaryKeys = createAliasedPrimaryKeys;
@@ -1444,38 +1571,28 @@ const hasManyCreate = (key, state, nestedInsertFn, self, ctx, items, rowIndexes,
1444
1571
  if (createSelectAs) foreignKeys.forEach((key, keyI) => {
1445
1572
  createSelectAs[key]._sql = selectCteColumnFromManySql(upsertCreateAs, aliasedPrimaryKeys[keyI], 0, 1);
1446
1573
  });
1447
- if (connect) foreignKeys.forEach((key, keyI) => {
1448
- const primaryKey = aliasedPrimaryKeys[keyI];
1449
- for (let i = 0; i < connect.items.length; i++) connect.items[i][key]._sql = selectCteColumnFromManySql(upsertCreateAs, primaryKey, connect.indexes[i], count);
1450
- });
1451
- if (connectOrCreate) foreignKeys.forEach((key, keyI) => {
1452
- const primaryKey = aliasedPrimaryKeys[keyI];
1453
- for (let i = 0; i < connectOrCreate.items.length; i++) connectOrCreate.items[i][key]._sql = selectCteColumnFromManySql(upsertCreateAs, primaryKey, connectOrCreate.indexes[i], count);
1454
- });
1574
+ if (connect) for (let i = 0; i < connect.items.length; i++) setRawSqlPlaceholderRecordFromCte(connect.items[i], foreignKeys, upsertCreateAs, aliasedPrimaryKeys, connect.indexes[i], count);
1575
+ if (connectOrCreate) for (let i = 0; i < connectOrCreate.items.length; i++) setRawSqlPlaceholderRecordFromCte(connectOrCreate.items[i], foreignKeys, upsertCreateAs, aliasedPrimaryKeys, connectOrCreate.indexes[i], count);
1576
+ if (upsert) for (let i = 0; i < upsert.items.length; i++) setRawSqlPlaceholderRecordFromCte(upsert.items[i], foreignKeys, upsertCreateAs, aliasedPrimaryKeys, upsert.indexes[i], count);
1455
1577
  };
1456
1578
  _hookSelectColumns(querySelf, primaryKeys, (aliasedPrimaryKeys) => {
1457
1579
  createAliasedPrimaryKeys = aliasedPrimaryKeys;
1458
- foreignKeys.forEach((key, keyI) => {
1459
- const primaryKey = aliasedPrimaryKeys[keyI];
1460
- if (create && createAs) for (let i = 0; i < create.items.length; i++) {
1461
- const sql = selectCteColumnFromManySql(createAs, primaryKey, create.indexes[i], count);
1462
- for (const item of create.items[i]) item[key]._sql = sql;
1463
- }
1464
- if (connect && connectAs) for (let i = 0; i < connect.items.length; i++) connect.items[i][key]._sql = selectCteColumnFromManySql(connectAs, primaryKey, connect.indexes[i], count);
1465
- if (connectOrCreate && connectOrCreateAs) for (let i = 0; i < connectOrCreate.items.length; i++) connectOrCreate.items[i][key]._sql = selectCteColumnFromManySql(connectOrCreateAs, primaryKey, connectOrCreate.indexes[i], count);
1466
- });
1580
+ if (create && createAs) for (let i = 0; i < create.items.length; i++) for (const item of create.items[i]) setRawSqlPlaceholderRecordFromCte(item, foreignKeys, createAs, aliasedPrimaryKeys, create.indexes[i], count);
1581
+ if (connect && connectAs) for (let i = 0; i < connect.items.length; i++) setRawSqlPlaceholderRecordFromCte(connect.items[i], foreignKeys, connectAs, aliasedPrimaryKeys, connect.indexes[i], count);
1582
+ if (connectOrCreate && connectOrCreateAs) for (let i = 0; i < connectOrCreate.items.length; i++) setRawSqlPlaceholderRecordFromCte(connectOrCreate.items[i], foreignKeys, connectOrCreateAs, aliasedPrimaryKeys, connectOrCreate.indexes[i], count);
1583
+ if (upsert && upsertAs) for (let i = 0; i < upsert.items.length; i++) setRawSqlPlaceholderRecordFromCte(upsert.items[i], foreignKeys, upsertAs, aliasedPrimaryKeys, upsert.indexes[i], count);
1467
1584
  setUpsertCreatePlaceholders();
1468
1585
  });
1469
- const { create, connect, connectOrCreate } = nestedCreateItems;
1586
+ const { create, connect, connectOrCreate, upsert } = nestedCreateItems;
1470
1587
  if (create) {
1471
1588
  let query;
1472
1589
  if (querySelf.q.type === "upsert") {
1473
1590
  createWhereExists = new RawSql("");
1474
1591
  createSelectAs = {};
1475
1592
  for (const key of foreignKeys) createSelectAs[key] = new RawSql("");
1476
- const sourceQuery = _queryWhere(_queryTake(_querySelect(_clone(rel.qb), [createSelectAs])), [createWhereExists]);
1477
- query = _queryInsertManyFrom(_clone(rel), sourceQuery, create.items.flat());
1478
- } else query = _queryInsertMany(_clone(rel), create.items.flat());
1593
+ const sourceQuery = _queryWhere(_queryTake(_querySelect(_clone(relQuery.qb), [createSelectAs])), [createWhereExists]);
1594
+ query = _queryInsertManyFrom(_clone(relQuery), sourceQuery, create.items.flat());
1595
+ } else query = _queryInsertMany(_clone(relQuery), create.items.flat());
1479
1596
  if (querySelf.q.type === "upsert") _appendQueryOnUpsertCreate(querySelf, query, (as) => {
1480
1597
  upsertCreateAs = as;
1481
1598
  setUpsertCreatePlaceholders();
@@ -1484,9 +1601,9 @@ const hasManyCreate = (key, state, nestedInsertFn, self, ctx, items, rowIndexes,
1484
1601
  }
1485
1602
  if (connect) connect.values.forEach((value, i) => {
1486
1603
  const connectWhereExists = querySelf.q.type === "upsert" ? new RawSql("") : void 0;
1487
- const query = _queryUpdateOrThrow(rel.whereOneOf(...value), connect.items[i]);
1604
+ const query = _queryUpdateOrThrow(relQuery.whereOneOf(...value), connect.items[i]);
1488
1605
  if (connectWhereExists) _queryWhere(query, [connectWhereExists]);
1489
- query.q.ensureCount = value.length;
1606
+ query.q.ensureCount = { expected: value.length };
1490
1607
  (querySelf.q.type === "upsert" ? _appendQueryOnUpsertCreate : _appendQuery)(querySelf, query, (as) => {
1491
1608
  upsertCreateAs = as;
1492
1609
  setUpsertCreatePlaceholders();
@@ -1501,10 +1618,10 @@ const hasManyCreate = (key, state, nestedInsertFn, self, ctx, items, rowIndexes,
1501
1618
  const connectOrCreateSelectAs = {};
1502
1619
  if (connectOrCreateWhereExists) for (const key of foreignKeys) {
1503
1620
  const value = foreignKeyValues[key];
1504
- value.result = { value: rel.qb.shape[key] };
1621
+ value.result = { value: relQuery.qb.shape[key] };
1505
1622
  connectOrCreateSelectAs[key] = value;
1506
1623
  }
1507
- const query = _queryUpsert(rel.where(value.where), {
1624
+ const query = _queryUpsert(relQuery.where(value.where), {
1508
1625
  update: foreignKeyValues,
1509
1626
  create: {
1510
1627
  ...value.create,
@@ -1513,8 +1630,8 @@ const hasManyCreate = (key, state, nestedInsertFn, self, ctx, items, rowIndexes,
1513
1630
  });
1514
1631
  if (connectOrCreateWhereExists) {
1515
1632
  _queryWhere(query, [connectOrCreateWhereExists]);
1516
- const guardQuery = _queryWhere(_queryTake(_querySelect(_clone(rel.qb), [{ one: new RawSql("1") }])), [connectOrCreateWhereExists]);
1517
- const sourceQuery = _querySelect(_clone(rel.qb).from(guardQuery), [connectOrCreateSelectAs]);
1633
+ const guardQuery = _queryWhere(_queryTake(_querySelect(_clone(relQuery.qb), [{ one: new RawSql("1") }])), [connectOrCreateWhereExists]);
1634
+ const sourceQuery = _querySelect(_clone(relQuery.qb).from(guardQuery), [connectOrCreateSelectAs]);
1518
1635
  const q = query.q;
1519
1636
  const columns = q.columns || [];
1520
1637
  const foreignKeysSet = new Set(foreignKeys);
@@ -1531,6 +1648,43 @@ const hasManyCreate = (key, state, nestedInsertFn, self, ctx, items, rowIndexes,
1531
1648
  });
1532
1649
  }
1533
1650
  });
1651
+ if (upsert) upsert.values.forEach((array, i) => {
1652
+ const foreignKeyValues = upsert.items[i];
1653
+ for (const value of array) {
1654
+ const upsertWhereExists = querySelf.q.type === "upsert" ? new RawSql("") : void 0;
1655
+ const upsertSelectAs = {};
1656
+ if (upsertWhereExists) for (const key of foreignKeys) {
1657
+ const foreignKeyValue = foreignKeyValues[key];
1658
+ foreignKeyValue.result = { value: relQuery.qb.shape[key] };
1659
+ upsertSelectAs[key] = foreignKeyValue;
1660
+ }
1661
+ const query = _queryUpsert(relQuery.where(value.findBy), {
1662
+ update: {
1663
+ ...value.update,
1664
+ ...foreignKeyValues
1665
+ },
1666
+ create: makeNestedUpdateUpsertData(value, foreignKeyValues).create
1667
+ });
1668
+ if (upsertWhereExists) {
1669
+ _queryWhere(query, [upsertWhereExists]);
1670
+ const guardQuery = _queryWhere(_queryTake(_querySelect(_clone(relQuery.qb), [{ one: new RawSql("1") }])), [upsertWhereExists]);
1671
+ const sourceQuery = _querySelect(_clone(relQuery.qb).from(guardQuery), [upsertSelectAs]);
1672
+ const q = query.q;
1673
+ const columns = q.columns || [];
1674
+ const foreignKeysSet = new Set(foreignKeys);
1675
+ q.columns = [...foreignKeys, ...columns.filter((column) => !foreignKeysSet.has(column))];
1676
+ q.values = q.values.map((row) => row.filter((_, i) => !foreignKeysSet.has(columns[i])));
1677
+ q.queryColumnsCount = foreignKeys.length;
1678
+ q.insertFrom = prepareSubQueryForSql(query, sourceQuery);
1679
+ }
1680
+ (querySelf.q.type === "upsert" ? _appendQueryOnUpsertCreate : _appendQuery)(querySelf, query, (as) => {
1681
+ upsertCreateAs = as;
1682
+ setUpsertCreatePlaceholders();
1683
+ upsertAs = as;
1684
+ if (upsertWhereExists) upsertWhereExists._sql = `EXISTS (SELECT 1 FROM "${as}")`;
1685
+ });
1686
+ }
1687
+ });
1534
1688
  } else hasRelationHandleCreate(querySelf, ctx, items, rowIndexes, key, state.primaryKeys, nestedInsertFn);
1535
1689
  };
1536
1690
  var HasManyVirtualColumn = class extends VirtualColumn {
@@ -1539,7 +1693,6 @@ var HasManyVirtualColumn = class extends VirtualColumn {
1539
1693
  this.key = key;
1540
1694
  this.state = state;
1541
1695
  this.nestedInsert = nestedInsert$1(state);
1542
- this.nestedUpdate = nestedUpdate$1(state);
1543
1696
  this.setNulls = {};
1544
1697
  for (const foreignKey of state.foreignKeys) this.setNulls[foreignKey] = null;
1545
1698
  }
@@ -1547,7 +1700,7 @@ var HasManyVirtualColumn = class extends VirtualColumn {
1547
1700
  hasManyCreate(this.key, this.state, this.nestedInsert, self, ctx, items, rowIndexes, count);
1548
1701
  }
1549
1702
  update(self, set) {
1550
- hasManyUpdate(this.key, this.state, this.nestedUpdate, self, set);
1703
+ hasManyUpdate(this.key, this.state, self, set);
1551
1704
  }
1552
1705
  };
1553
1706
  const makeHasManyMethod = (tableConfig, table, relation, relationName, query) => {
@@ -1606,6 +1759,197 @@ const makeHasManyMethod = (tableConfig, table, relation, relationName, query) =>
1606
1759
  }
1607
1760
  };
1608
1761
  };
1762
+ const hasAndBelongsToManyCreate = (key, state, nestedInsertFn, self, ctx, items, rowIndexes, count) => {
1763
+ const querySelf = self;
1764
+ if (count <= querySelf.qb.internal.nestedCreateBatchMax) {
1765
+ const { relatedTableQuery: relQuery, joinTableQuery, primaryKeys, foreignKeys, throughForeignKeys, throughPrimaryKeys } = state;
1766
+ const nestedRecords = [];
1767
+ const nestedConnects = [];
1768
+ const nestedConnectOrCreates = [];
1769
+ const nestedUpserts = [];
1770
+ for (let i = 0; i < items.length; i++) {
1771
+ const value = items[i][key];
1772
+ if (value.create?.length) nestedRecords.push({
1773
+ rowIndex: rowIndexes[i],
1774
+ records: value.create,
1775
+ foreignKeyValues: makeRawSqlPlaceholders(foreignKeys.length)
1776
+ });
1777
+ if (value.connect?.length) nestedConnects.push({
1778
+ rowIndex: rowIndexes[i],
1779
+ connect: value.connect,
1780
+ foreignKeyValues: makeRawSqlPlaceholders(foreignKeys.length)
1781
+ });
1782
+ if (value.connectOrCreate?.length) nestedConnectOrCreates.push({
1783
+ rowIndex: rowIndexes[i],
1784
+ connectOrCreate: value.connectOrCreate,
1785
+ foreignKeyValues: makeRawSqlPlaceholders(foreignKeys.length)
1786
+ });
1787
+ if (value.upsert) nestedUpserts.push({
1788
+ rowIndex: rowIndexes[i],
1789
+ upsert: value.upsert,
1790
+ foreignKeyValues: makeRawSqlPlaceholders(foreignKeys.length)
1791
+ });
1792
+ }
1793
+ if (nestedRecords.length || nestedConnects.length || nestedConnectOrCreates.length || nestedUpserts.length) {
1794
+ let mainAs;
1795
+ let aliasedPrimaryKeysForMain;
1796
+ const setMainForeignKeyValues = () => {
1797
+ if (!mainAs || !aliasedPrimaryKeysForMain) return;
1798
+ for (const { rowIndex, foreignKeyValues } of [
1799
+ ...nestedRecords,
1800
+ ...nestedConnects,
1801
+ ...nestedConnectOrCreates,
1802
+ ...nestedUpserts
1803
+ ]) setRawSqlPlaceholdersFromCte(foreignKeyValues, mainAs, aliasedPrimaryKeysForMain, rowIndex, count);
1804
+ };
1805
+ const setMainAs = (as) => {
1806
+ mainAs = as;
1807
+ setMainForeignKeyValues();
1808
+ };
1809
+ _hookSelectColumns(querySelf, primaryKeys, (aliasedPrimaryKeys) => {
1810
+ aliasedPrimaryKeysForMain = aliasedPrimaryKeys;
1811
+ setMainForeignKeyValues();
1812
+ });
1813
+ const joinQueries = [];
1814
+ for (const nested of nestedRecords) {
1815
+ const { foreignKeyValues } = nested;
1816
+ const throughForeignKeyValues = makeRawSqlPlaceholders(throughForeignKeys.length);
1817
+ const selectAs = {};
1818
+ for (let i = 0; i < foreignKeys.length; i++) selectAs[foreignKeys[i]] = foreignKeyValues[i];
1819
+ for (let i = 0; i < throughForeignKeys.length; i++) selectAs[throughForeignKeys[i]] = throughForeignKeyValues[i];
1820
+ const joinRows = _querySelect(_clone(relQuery.baseQuery), [selectAs]);
1821
+ const joinQuery = _queryInsertForEachFrom(_clone(joinTableQuery), joinRows);
1822
+ joinQuery.q.returnType = "void";
1823
+ joinQuery.q.ensureCount = { expected: 1 };
1824
+ if (querySelf.q.type === "upsert") {
1825
+ const createWhereExists = new RawSql("");
1826
+ const sourceQuery = _queryWhere(_queryTake(_querySelect(_clone(relQuery.qb), [{}])), [createWhereExists]);
1827
+ const query = _queryInsertManyFrom(_querySelect(_clone(relQuery), throughPrimaryKeys), sourceQuery, nested.records);
1828
+ const queryWithJoin = _appendQuery(query, joinQuery, (as) => {
1829
+ joinRows.q.from = as;
1830
+ joinRows.q.schema = void 0;
1831
+ for (let i = 0; i < throughForeignKeyValues.length; i++) throughForeignKeyValues[i]._sql = `"${as}"."${throughPrimaryKeys[i]}"`;
1832
+ });
1833
+ _appendQueryOnUpsertCreate(querySelf, queryWithJoin, (as) => {
1834
+ setMainAs(as);
1835
+ createWhereExists._sql = `EXISTS (SELECT 1 FROM "${as}")`;
1836
+ });
1837
+ } else {
1838
+ joinQueries.push(joinQuery);
1839
+ const query = _queryInsertMany(_querySelect(_clone(relQuery), throughPrimaryKeys), nested.records);
1840
+ _appendQuery(querySelf, query, setMainAs, (as) => {
1841
+ joinRows.q.from = as;
1842
+ joinRows.q.schema = void 0;
1843
+ for (let i = 0; i < throughForeignKeyValues.length; i++) throughForeignKeyValues[i]._sql = `"${as}"."${throughPrimaryKeys[i]}"`;
1844
+ });
1845
+ }
1846
+ }
1847
+ for (const { connect, foreignKeyValues } of nestedConnects) for (const item of connect) {
1848
+ const selectAs = {};
1849
+ for (let i = 0; i < foreignKeys.length; i++) selectAs[foreignKeys[i]] = foreignKeyValues[i];
1850
+ for (let i = 0; i < throughForeignKeys.length; i++) selectAs[throughForeignKeys[i]] = throughPrimaryKeys[i];
1851
+ const joinRows = _queryFindBy(_querySelect(_clone(relQuery), [selectAs]), item);
1852
+ const joinQuery = _queryInsertForEachFrom(_clone(joinTableQuery), joinRows);
1853
+ joinQuery.q.returnType = "void";
1854
+ joinQuery.q.ensureCount = { expected: 1 };
1855
+ if (querySelf.q.type === "upsert") {
1856
+ const connectWhereExists = new RawSql("");
1857
+ _queryWhere(joinRows, [connectWhereExists]);
1858
+ _appendQueryOnUpsertCreate(querySelf, joinQuery, (as) => {
1859
+ setMainAs(as);
1860
+ connectWhereExists._sql = `EXISTS (SELECT 1 FROM "${as}")`;
1861
+ });
1862
+ } else joinQueries.push(joinQuery);
1863
+ }
1864
+ for (const joinQuery of joinQueries) _appendQuery(querySelf, joinQuery, setMainAs);
1865
+ for (const { connectOrCreate, foreignKeyValues } of nestedConnectOrCreates) for (const value of connectOrCreate) {
1866
+ const selectAs = {};
1867
+ for (let i = 0; i < foreignKeys.length; i++) selectAs[foreignKeys[i]] = foreignKeyValues[i];
1868
+ for (let i = 0; i < throughForeignKeys.length; i++) selectAs[throughForeignKeys[i]] = throughPrimaryKeys[i];
1869
+ const connectRows = _queryFindBy(_querySelect(_clone(relQuery), [selectAs]), value.where);
1870
+ const connectQuery = _queryInsertForEachFrom(_clone(joinTableQuery), connectRows);
1871
+ connectQuery.q.returnType = "void";
1872
+ const createWhereNotExists = new RawSql("");
1873
+ const createWhereExists = querySelf.q.type === "upsert" ? new RawSql("") : void 0;
1874
+ const createSource = _queryWhere(_queryTake(_querySelect(_clone(relQuery.qb), [{}])), [createWhereNotExists, createWhereExists].filter(Boolean));
1875
+ const createQuery = _queryInsertManyFrom(_querySelect(_clone(relQuery), throughPrimaryKeys), createSource, [value.create]);
1876
+ const throughForeignKeyValues = makeRawSqlPlaceholders(throughForeignKeys.length);
1877
+ const createSelectAs = {};
1878
+ for (let i = 0; i < foreignKeys.length; i++) createSelectAs[foreignKeys[i]] = foreignKeyValues[i];
1879
+ for (let i = 0; i < throughForeignKeys.length; i++) createSelectAs[throughForeignKeys[i]] = throughForeignKeyValues[i];
1880
+ const createJoinRows = _querySelect(_clone(relQuery.baseQuery), [createSelectAs]);
1881
+ const createJoinQuery = _queryInsertForEachFrom(_clone(joinTableQuery), createJoinRows);
1882
+ createJoinQuery.q.returnType = "void";
1883
+ if (querySelf.q.type === "upsert") {
1884
+ const connectWhereExists = new RawSql("");
1885
+ _queryWhere(connectRows, [connectWhereExists]);
1886
+ const createQueryWithJoin = _appendQuery(createQuery, createJoinQuery, (as) => {
1887
+ createJoinRows.q.from = as;
1888
+ createJoinRows.q.schema = void 0;
1889
+ for (let i = 0; i < throughForeignKeyValues.length; i++) throughForeignKeyValues[i]._sql = `"${as}"."${throughPrimaryKeys[i]}"`;
1890
+ });
1891
+ const query = _appendQuery(connectQuery, createQueryWithJoin, (as) => {
1892
+ createWhereNotExists._sql = `NOT EXISTS (SELECT 1 FROM "${as}")`;
1893
+ });
1894
+ _appendQueryOnUpsertCreate(querySelf, query, (as) => {
1895
+ setMainAs(as);
1896
+ connectWhereExists._sql = `EXISTS (SELECT 1 FROM "${as}")`;
1897
+ createWhereExists._sql = `EXISTS (SELECT 1 FROM "${as}")`;
1898
+ });
1899
+ } else {
1900
+ _appendQuery(querySelf, connectQuery, setMainAs, (as) => {
1901
+ createWhereNotExists._sql = `NOT EXISTS (SELECT 1 FROM "${as}")`;
1902
+ });
1903
+ _appendQuery(querySelf, createQuery, setMainAs, (as) => {
1904
+ createJoinRows.q.from = as;
1905
+ createJoinRows.q.schema = void 0;
1906
+ for (let i = 0; i < throughForeignKeyValues.length; i++) throughForeignKeyValues[i]._sql = `"${as}"."${throughPrimaryKeys[i]}"`;
1907
+ });
1908
+ _appendQuery(querySelf, createJoinQuery, setMainAs);
1909
+ }
1910
+ }
1911
+ for (const { upsert, foreignKeyValues } of nestedUpserts) for (const value of toArray(upsert)) {
1912
+ const upsertWhereExists = querySelf.q.type === "upsert" ? new RawSql("") : void 0;
1913
+ const makeJoinQuery = () => {
1914
+ const selectAs = {};
1915
+ for (let i = 0; i < foreignKeys.length; i++) selectAs[foreignKeys[i]] = foreignKeyValues[i];
1916
+ for (let i = 0; i < throughForeignKeys.length; i++) selectAs[throughForeignKeys[i]] = new RawSql("");
1917
+ const joinRows = _querySelect(_clone(relQuery.baseQuery), [selectAs]);
1918
+ const joinQuery = _queryInsertForEachFrom(_clone(joinTableQuery), joinRows);
1919
+ joinQuery.q.returnType = "void";
1920
+ return {
1921
+ joinQuery,
1922
+ setJoinRowsFrom: (as) => {
1923
+ joinRows.q.from = as;
1924
+ joinRows.q.schema = void 0;
1925
+ for (let i = 0; i < throughForeignKeys.length; i++) selectAs[throughForeignKeys[i]]._sql = `"${as}"."${throughPrimaryKeys[i]}"`;
1926
+ }
1927
+ };
1928
+ };
1929
+ const updateQuery = _querySelect(_queryUpdate(_clone(relQuery).where(value.findBy), value.update), throughPrimaryKeys);
1930
+ if (upsertWhereExists) _queryWhere(updateQuery, [upsertWhereExists]);
1931
+ const updateJoin = makeJoinQuery();
1932
+ const updateQueryWithJoin = _appendQuery(updateQuery, updateJoin.joinQuery, updateJoin.setJoinRowsFrom);
1933
+ let query = updateQueryWithJoin;
1934
+ if (value.create) {
1935
+ const createWhereNotExists = new RawSql("");
1936
+ const createSource = _queryWhere(_queryTake(_querySelect(_clone(relQuery.qb), [{}])), [createWhereNotExists, upsertWhereExists].filter(Boolean));
1937
+ const create = typeof value.create === "function" ? value.create() : value.create;
1938
+ const createQuery = _queryInsertManyFrom(_querySelect(_clone(relQuery), throughPrimaryKeys), createSource, [create]);
1939
+ const createJoin = makeJoinQuery();
1940
+ const createQueryWithJoin = _appendQuery(createQuery, createJoin.joinQuery, createJoin.setJoinRowsFrom);
1941
+ query = _appendQuery(updateQueryWithJoin, createQueryWithJoin, (as) => {
1942
+ createWhereNotExists._sql = `NOT EXISTS (SELECT 1 FROM "${as}")`;
1943
+ });
1944
+ }
1945
+ (querySelf.q.type === "upsert" ? _appendQueryOnUpsertCreate : _appendQuery)(querySelf, query, (as) => {
1946
+ setMainAs(as);
1947
+ if (upsertWhereExists) upsertWhereExists._sql = `EXISTS (SELECT 1 FROM "${as}")`;
1948
+ });
1949
+ }
1950
+ }
1951
+ } else hasRelationHandleCreate(querySelf, ctx, items, rowIndexes, key, state.primaryKeys, nestedInsertFn);
1952
+ };
1609
1953
  const nestedInsert = ({ relatedTableQuery, joinTableQuery, primaryKeys, foreignKeys, throughPrimaryKeys, throughForeignKeys }) => {
1610
1954
  const len = primaryKeys.length;
1611
1955
  const throughLen = primaryKeys.length;
@@ -1650,6 +1994,15 @@ const nestedInsert = ({ relatedTableQuery, joinTableQuery, primaryKeys, foreignK
1650
1994
  created = await _queryCreateMany(t.select(...throughPrimaryKeys), records);
1651
1995
  } else created = [];
1652
1996
  const allKeys = data;
1997
+ for (let index = 0; index < data.length; index++) {
1998
+ const item = data[index][1];
1999
+ if (!item.upsert) continue;
2000
+ const upserted = await Promise.all(toArray(item.upsert).map((upsert) => _querySelect(_queryUpsert(t.clone().where(upsert.findBy), {
2001
+ update: upsert.update,
2002
+ create: upsert.create || {}
2003
+ }), throughPrimaryKeys)));
2004
+ allKeys[index][1] = upserted.map((record) => Array.isArray(record) ? record[0] : record);
2005
+ }
1653
2006
  let createI = 0;
1654
2007
  let connectI = 0;
1655
2008
  connectOrCreateI = 0;
@@ -1691,57 +2044,42 @@ const nestedInsert = ({ relatedTableQuery, joinTableQuery, primaryKeys, foreignK
1691
2044
  await joinTableQuery.insertMany(records);
1692
2045
  });
1693
2046
  };
1694
- const selectCteColumnsSql = (cteAs, columns) => `(SELECT ${columns.map((c) => `"${cteAs}"."${c}"`).join(", ")} FROM "${cteAs}")`;
1695
- const selectCteColumnSql = (cteAs, column) => `(SELECT "${cteAs}"."${column}" FROM "${cteAs}")`;
1696
- const conditionsToWhereArg = (conditions) => Array.isArray(conditions) ? { OR: conditions } : conditions;
1697
- const queryJoinTable = (state, data, conditions) => {
1698
- const t = state.joinTableQuery.where({ IN: {
1699
- columns: state.foreignKeys,
1700
- values: data.map((item) => state.primaryKeys.map((key) => item[key]))
1701
- } });
1702
- if (conditions) _queryWhere(t, [{ IN: {
1703
- columns: state.throughForeignKeys,
1704
- values: _querySelect(state.relatedTableQuery.where(conditionsToWhereArg(conditions)), state.throughPrimaryKeys)
1705
- } }]);
1706
- if (state.on) _queryWhereExists(t, state.relatedTableQuery, [(q) => {
1707
- for (let i = 0; i < state.throughPrimaryKeys.length; i++) _queryJoinOn(q, [state.throughPrimaryKeysFull[i], state.throughForeignKeysFull[i]]);
1708
- return q;
1709
- }]);
1710
- return t;
1711
- };
1712
- const insertToJoinTable = (state, joinTableTransaction, data, idsRows) => {
1713
- const len = state.primaryKeys.length;
1714
- const throughLen = state.throughPrimaryKeys.length;
1715
- const records = [];
1716
- for (const item of data) {
1717
- const obj = {};
1718
- for (let i = 0; i < len; i++) obj[state.foreignKeys[i]] = item[state.primaryKeys[i]];
1719
- for (const ids of idsRows) {
1720
- const record = { ...obj };
1721
- for (let i = 0; i < throughLen; i++) record[state.throughForeignKeys[i]] = ids[i];
1722
- records.push(record);
1723
- }
1724
- }
1725
- return joinTableTransaction.insertMany(records);
2047
+ const nestedUpdateUpsert = (querySelf, state, upsert, onAppended, updateFrom, sourceCondition, disconnect, set) => {
2048
+ for (const [index, item] of toArray(upsert).entries()) nestedUpdateUpsertOne(querySelf, state, item, (as) => onAppended?.(as, index), updateFrom, sourceCondition, disconnect, set);
1726
2049
  };
1727
- const nestedUpdateUpsert = (querySelf, state, upsert) => {
2050
+ const nestedUpdateUpsertOne = (querySelf, state, upsert, onAppended, updateFrom, sourceCondition, disconnect, set) => {
1728
2051
  const parentIdsSql = new RawSql("");
1729
- const parentValues = state.foreignKeys.map(() => new RawSql(""));
2052
+ const parentValues = makeRawSqlPlaceholders(state.foreignKeys.length);
1730
2053
  const relatedIdsSql = new RawSql("");
1731
- const relatedValues = state.throughPrimaryKeys.map(() => new RawSql(""));
2054
+ const relatedValues = makeRawSqlPlaceholders(state.throughPrimaryKeys.length);
1732
2055
  let parentAs;
1733
2056
  _hookSelectColumns(querySelf, state.primaryKeys, (aliasedPrimaryKeys) => {
1734
2057
  parentIdsSql._sql = selectCteColumnsSql(parentAs, aliasedPrimaryKeys);
1735
2058
  for (let i = 0; i < state.foreignKeys.length; i++) parentValues[i]._sql = selectCteColumnSql(parentAs, aliasedPrimaryKeys[i]);
1736
2059
  });
1737
- const existingRelQuery = _queryWhereExists(_queryWhere(_clone(state.relatedTableQuery), [upsert.findBy]), state.joinTableQuery, [(q) => {
2060
+ let existingRelQuery = _queryWhere(_clone(state.relatedTableQuery), [upsert.findBy]);
2061
+ if (disconnect) existingRelQuery = existingRelQuery.whereNot({ OR: toArray(disconnect) });
2062
+ existingRelQuery = _queryWhereExists(existingRelQuery, state.joinTableQuery, [(q) => {
1738
2063
  for (let i = 0; i < state.throughPrimaryKeys.length; i++) _queryJoinOn(q, [state.throughForeignKeysFull[i], state.throughPrimaryKeysFull[i]]);
1739
2064
  return _queryWhereIn(q, true, state.foreignKeysFull, parentIdsSql);
1740
2065
  }]);
2066
+ if (sourceCondition) {
2067
+ existingRelQuery.q.updateFrom = {
2068
+ u: true,
2069
+ x: updateFrom
2070
+ };
2071
+ const conditions = existingRelQuery.q.and;
2072
+ const existingRelationCondition = conditions.pop();
2073
+ const relationCondition = set ? sourceCondition : { OR: [existingRelationCondition, sourceCondition] };
2074
+ conditions.push(relationCondition);
2075
+ }
1741
2076
  const upsertQuery = _querySelect(_queryUpsert(existingRelQuery, {
1742
2077
  update: upsert.update,
1743
2078
  create: upsert.create || {}
1744
2079
  }), state.throughPrimaryKeys);
2080
+ upsertQuery.q.returnType = "void";
2081
+ _onUpsertUpdate(upsertQuery, (as) => onAppended?.(as));
2082
+ if (onAppended) upsertQuery.q.upsertCreateAsFns = [...upsertQuery.q.upsertCreateAsFns || [], onAppended];
1745
2083
  const selectAs = {};
1746
2084
  for (let i = 0; i < state.foreignKeys.length; i++) selectAs[state.foreignKeys[i]] = parentValues[i];
1747
2085
  for (let i = 0; i < state.throughForeignKeys.length; i++) selectAs[state.throughForeignKeys[i]] = relatedValues[i];
@@ -1750,72 +2088,197 @@ const nestedUpdateUpsert = (querySelf, state, upsert) => {
1750
2088
  const joinQuery = state.joinTableQuery.insertForEachFrom(joinRows);
1751
2089
  joinQuery.q.returnType = "void";
1752
2090
  _appendQuery(querySelf, _appendQueryOnUpsertCreate(upsertQuery, joinQuery, (as) => {
2091
+ onAppended?.(as);
1753
2092
  relatedIdsSql._sql = `EXISTS (SELECT 1 FROM "${as}")`;
1754
2093
  for (let i = 0; i < relatedValues.length; i++) relatedValues[i]._sql = selectCteColumnSql(as, state.throughPrimaryKeys[i]);
1755
2094
  }), (as) => {
1756
2095
  parentAs = as;
1757
2096
  });
1758
2097
  };
1759
- const nestedUpdate = (state) => {
1760
- const len = state.primaryKeys.length;
1761
- const throughLen = state.throughPrimaryKeys.length;
1762
- return (async (query, data, params) => {
1763
- if (params.create) {
1764
- const idsRows = await _queryCreateMany(_queryRows(state.relatedTableQuery.select(...state.throughPrimaryKeys)), params.create);
1765
- const records = [];
1766
- for (const item of data) {
1767
- const obj = {};
1768
- for (let i = 0; i < len; i++) obj[state.foreignKeys[i]] = item[state.primaryKeys[i]];
1769
- for (const ids of idsRows) {
1770
- const record = { ...obj };
1771
- for (let i = 0; i < throughLen; i++) record[state.throughForeignKeys[i]] = ids[i];
1772
- records.push(record);
1773
- }
1774
- }
1775
- await state.joinTableQuery.createMany(records);
1776
- }
1777
- if (params.update) await _queryUpdate(_queryWhere(state.relatedTableQuery.whereExists(state.joinTableQuery, (q) => {
1778
- for (let i = 0; i < throughLen; i++) _queryJoinOn(q, [state.throughForeignKeysFull[i], state.throughPrimaryKeysFull[i]]);
1779
- return _queryWhere(q, [{ IN: {
1780
- columns: state.foreignKeysFull,
1781
- values: data.map((item) => state.primaryKeys.map((key) => item[key]))
1782
- } }]);
1783
- }), [conditionsToWhereArg(params.update.where)]), params.update.data);
1784
- /**
1785
- * Performs `insertForEachFrom` on the joining table,
1786
- * based on a query to the related table with applied filters of `params.connect`,
1787
- * joins the main table data using `joinData`.
1788
- */
1789
- if (params.add) {
1790
- const as = query.table;
1791
- const relatedWheres = toArray(params.add);
1792
- const joinTableColumns = [...state.foreignKeys, ...state.throughForeignKeys];
1793
- try {
1794
- const count = await state.joinTableQuery.insertForEachFrom(_querySelect(state.relatedTableQuery.whereOneOf(...relatedWheres), [Object.fromEntries([...state.primaryKeys.map((key, i) => [state.foreignKeys[i], as + "." + (state.primaryKeysShape[key].data.name || key)]), ...state.throughForeignKeys.map((key, i) => [key, state.throughPrimaryKeys[i]])])]).joinData(as, () => Object.fromEntries(state.primaryKeys.map((key) => [key, state.primaryKeysShape[key]])), data.map((x) => pick(x, state.primaryKeys)))).onConflict(joinTableColumns).merge([state.foreignKeys[0]]);
1795
- if (count < data.length * relatedWheres.length) throw new OrchidOrmInternalError(query, `Expected to find at least ${relatedWheres.length} record(s) based on \`add\` conditions, but found ${count / data.length}`);
1796
- } catch (err) {
1797
- if (err.code === "42P10") throw new OrchidOrmInternalError(query, `"${state.joinTableQuery.table}" must have a primary key or a unique index on columns (${joinTableColumns.join(", ")}) for this kind of query.`);
1798
- throw err;
1799
- }
1800
- }
1801
- if (params.disconnect) await _queryDelete(queryJoinTable(state, data, params.disconnect));
1802
- if (params.delete) {
1803
- const j = queryJoinTable(state, data, params.delete);
1804
- const idsRows = await _queryDelete(_queryRows(_querySelect(j, state.throughForeignKeys)));
1805
- await _queryDelete(state.relatedTableQuery.where({ IN: {
1806
- columns: state.throughPrimaryKeys,
1807
- values: idsRows
1808
- } }));
1809
- }
1810
- if (params.set) {
1811
- const j = queryJoinTable(state, data);
1812
- await _queryDelete(j);
1813
- if (Array.isArray(params.set) ? params.set.length : objectHasValues(params.set)) {
1814
- const idsRows = await _queryRows(_querySelect(state.relatedTableQuery.where(conditionsToWhereArg(params.set)), state.throughPrimaryKeys));
1815
- await insertToJoinTable(state, j, data, idsRows);
1816
- }
1817
- }
2098
+ const nestedUpdateAdd = (querySelf, state, add, onAppended) => {
2099
+ const relatedWheres = toArray(add);
2100
+ const joinTableColumns = [...state.foreignKeys, ...state.throughForeignKeys];
2101
+ const parentValues = makeRawSqlPlaceholders(state.foreignKeys.length);
2102
+ const mainQuery = _clone(state.queryBuilder);
2103
+ const isRootUpsert = querySelf.q.upsertUpdate;
2104
+ let parentCount = 0;
2105
+ if (!isRootUpsert) _queryHookAfterUpdate(querySelf, state.primaryKeys, (rows) => {
2106
+ parentCount = rows.length;
2107
+ });
2108
+ _hookSelectColumns(querySelf, state.primaryKeys, (aliased) => {
2109
+ for (let i = 0; i < parentValues.length; i++) parentValues[i]._sql = `"${mainQuery.q.from}"."${aliased[i]}"`;
2110
+ });
2111
+ const joinRows = _querySelect(state.relatedTableQuery.whereOneOf(...relatedWheres), [Object.fromEntries([...state.foreignKeys.map((key, i) => [key, parentValues[i]]), ...state.throughForeignKeys.map((key, i) => [key, state.throughPrimaryKeys[i]])])]).join(mainQuery, true);
2112
+ const joinQuery = state.joinTableQuery.insertForEachFrom(joinRows).onConflict(joinTableColumns).merge([state.foreignKeys[0]]);
2113
+ if (onAppended) _querySelect(joinQuery, state.throughForeignKeys);
2114
+ else joinQuery.q.returnType = "void";
2115
+ if (!isRootUpsert) {
2116
+ joinQuery.q.ensureCount = {
2117
+ expected: relatedWheres.length,
2118
+ message: "based on `add` conditions"
2119
+ };
2120
+ _queryHookAfterCreate(joinQuery, state.foreignKeys, (rows) => {
2121
+ if (rows.length < parentCount * relatedWheres.length) throw new OrchidOrmInternalError(querySelf, `Expected to find at least ${relatedWheres.length} record(s) based on \`add\` conditions, but found ${rows.length / parentCount}`);
2122
+ });
2123
+ }
2124
+ _appendQuery(querySelf, joinQuery, (as) => {
2125
+ mainQuery.q.from = as;
2126
+ mainQuery.q.schema = void 0;
2127
+ mainQuery.q.as = void 0;
2128
+ }, onAppended);
2129
+ };
2130
+ const nestedUpdateDisconnect = (querySelf, state, disconnect) => {
2131
+ const relatedWheres = toArray(disconnect);
2132
+ const joinTableColumns = [...state.foreignKeys, ...state.throughForeignKeys];
2133
+ const parentValues = makeRawSqlPlaceholders(state.foreignKeys.length);
2134
+ const mainQuery = _clone(state.queryBuilder);
2135
+ _hookSelectColumns(querySelf, state.primaryKeys, (aliased) => {
2136
+ for (let i = 0; i < parentValues.length; i++) parentValues[i]._sql = `"${mainQuery.q.from}"."${aliased[i]}"`;
2137
+ });
2138
+ const joinRows = _querySelect(state.relatedTableQuery.whereOneOf(...relatedWheres), [Object.fromEntries([...state.foreignKeys.map((key, i) => [key, parentValues[i]]), ...state.throughForeignKeys.map((key, i) => [key, state.throughPrimaryKeys[i]])])]).join(mainQuery, true);
2139
+ const joinQuery = _queryDelete(state.joinTableQuery.where({ IN: {
2140
+ columns: joinTableColumns,
2141
+ values: joinRows
2142
+ } }));
2143
+ joinQuery.q.returnType = "void";
2144
+ _appendQuery(querySelf, joinQuery, (as) => {
2145
+ mainQuery.q.from = as;
2146
+ mainQuery.q.schema = void 0;
2147
+ mainQuery.q.as = void 0;
2148
+ });
2149
+ };
2150
+ const nestedUpdateDelete = (querySelf, state, del) => {
2151
+ const relatedWheres = toArray(del);
2152
+ const parentValues = makeRawSqlPlaceholders(state.foreignKeys.length);
2153
+ const relatedValues = makeRawSqlPlaceholders(state.throughForeignKeys.length);
2154
+ const parentIdsSql = new RawSql("");
2155
+ const mainQuery = _clone(state.queryBuilder);
2156
+ const relatedRows = _querySelect(_clone(state.queryBuilder), [Object.fromEntries([...state.foreignKeys.map((key, i) => [key, parentValues[i]]), ...state.throughForeignKeys.map((key, i) => [key, relatedValues[i]])])]).join(mainQuery, true);
2157
+ _hookSelectColumns(querySelf, state.primaryKeys, (aliased) => {
2158
+ parentIdsSql._sql = selectCteColumnsSql(mainQuery.q.from, aliased);
2159
+ for (let i = 0; i < parentValues.length; i++) parentValues[i]._sql = `"${mainQuery.q.from}"."${aliased[i]}"`;
2160
+ });
2161
+ const relatedQuery = _queryWhereExists(state.relatedTableQuery.whereOneOf(...relatedWheres), state.joinTableQuery, [(q) => {
2162
+ for (let i = 0; i < state.throughPrimaryKeys.length; i++) _queryJoinOn(q, [state.throughForeignKeysFull[i], state.throughPrimaryKeysFull[i]]);
2163
+ return _queryWhereIn(q, true, state.foreignKeysFull, parentIdsSql);
2164
+ }]);
2165
+ const relatedDeleteQuery = _queryDelete(_querySelect(relatedQuery, state.throughPrimaryKeys));
2166
+ relatedDeleteQuery.q.returnType = "void";
2167
+ const joinQuery = _queryDelete(state.joinTableQuery.where({ IN: {
2168
+ columns: [...state.foreignKeys, ...state.throughForeignKeys],
2169
+ values: relatedRows
2170
+ } }));
2171
+ joinQuery.q.returnType = "void";
2172
+ _appendQuery(querySelf, relatedDeleteQuery, () => {}, (as) => {
2173
+ relatedRows.q.from = as;
2174
+ relatedRows.q.schema = void 0;
2175
+ relatedRows.q.as = void 0;
2176
+ for (let i = 0; i < relatedValues.length; i++) relatedValues[i]._sql = `"${as}"."${state.throughPrimaryKeys[i]}"`;
2177
+ });
2178
+ _appendQuery(querySelf, joinQuery, (as) => {
2179
+ mainQuery.q.from = as;
2180
+ mainQuery.q.schema = void 0;
2181
+ mainQuery.q.as = void 0;
2182
+ });
2183
+ };
2184
+ const nestedUpdateUpdate = (querySelf, state, update, updateFrom, sourceCondition, disconnect, set) => {
2185
+ const parentIdsSql = new RawSql("");
2186
+ const mainQuery = _clone(state.queryBuilder);
2187
+ const isRootUpsert = querySelf.q.upsertUpdate;
2188
+ _hookSelectColumns(querySelf, state.primaryKeys, (aliased) => {
2189
+ parentIdsSql._sql = selectCteColumnsSql(mainQuery.q.from, aliased);
2190
+ });
2191
+ for (const item of toArray(update)) for (const relatedWhere of toArray(item.where)) {
2192
+ let relatedQuery = _queryWhere(_clone(state.relatedTableQuery), [relatedWhere]);
2193
+ if (disconnect) relatedQuery = relatedQuery.whereNot({ OR: toArray(disconnect) });
2194
+ if (sourceCondition) {
2195
+ relatedQuery.q.updateFrom = {
2196
+ u: true,
2197
+ x: updateFrom
2198
+ };
2199
+ relatedQuery = relatedQuery.whereSql(sourceCondition);
2200
+ } else relatedQuery = _queryWhereExists(relatedQuery, state.joinTableQuery, [(q) => {
2201
+ for (let i = 0; i < state.throughPrimaryKeys.length; i++) _queryJoinOn(q, [state.throughForeignKeysFull[i], state.throughPrimaryKeysFull[i]]);
2202
+ return _queryWhereIn(q, true, state.foreignKeysFull, parentIdsSql);
2203
+ }]);
2204
+ const relatedUpdateQuery = _queryUpdate(relatedQuery, item.data);
2205
+ relatedUpdateQuery.q.returnType = "void";
2206
+ if (!isRootUpsert && !disconnect && !set) relatedUpdateQuery.q.ensureCount = { expected: 1 };
2207
+ _appendQuery(querySelf, relatedUpdateQuery, (as) => {
2208
+ mainQuery.q.from = as;
2209
+ mainQuery.q.schema = void 0;
2210
+ mainQuery.q.as = void 0;
2211
+ });
2212
+ }
2213
+ };
2214
+ const nestedUpdateCreate = (querySelf, state, create) => {
2215
+ const mainQuery = _clone(state.queryBuilder);
2216
+ const parentValues = makeRawSqlPlaceholders(state.foreignKeys.length);
2217
+ const relatedValues = makeRawSqlPlaceholders(state.throughForeignKeys.length);
2218
+ _hookSelectColumns(querySelf, state.primaryKeys, (aliased) => {
2219
+ for (let i = 0; i < parentValues.length; i++) parentValues[i]._sql = `"${mainQuery.q.from}"."${aliased[i]}"`;
2220
+ });
2221
+ const joinRows = _querySelect(_clone(state.relatedTableQuery.baseQuery), [Object.fromEntries([...state.foreignKeys.map((key, i) => [key, parentValues[i]]), ...state.throughForeignKeys.map((key, i) => [key, relatedValues[i]])])]).join(mainQuery, true);
2222
+ const joinQuery = _queryInsertForEachFrom(_clone(state.joinTableQuery), joinRows);
2223
+ joinQuery.q.returnType = "void";
2224
+ const relatedCreateQuery = _queryInsertMany(_querySelect(_clone(state.relatedTableQuery), state.throughPrimaryKeys), create);
2225
+ relatedCreateQuery.q.returnType = "void";
2226
+ _appendQuery(querySelf, relatedCreateQuery, (as) => {
2227
+ mainQuery.q.from = as;
2228
+ mainQuery.q.schema = void 0;
2229
+ mainQuery.q.as = void 0;
2230
+ }, (as) => {
2231
+ joinRows.q.from = as;
2232
+ joinRows.q.schema = void 0;
2233
+ joinRows.q.as = void 0;
2234
+ for (let i = 0; i < relatedValues.length; i++) relatedValues[i]._sql = `"${as}"."${state.throughPrimaryKeys[i]}"`;
2235
+ });
2236
+ _appendQuery(querySelf, joinQuery, () => {});
2237
+ };
2238
+ const nestedUpdateSet = (querySelf, state, set, onAppended) => {
2239
+ const mainQuery = _clone(state.queryBuilder);
2240
+ const parentIdsSql = new RawSql("");
2241
+ const parentValues = makeRawSqlPlaceholders(state.foreignKeys.length);
2242
+ _hookSelectColumns(querySelf, state.primaryKeys, (aliased) => {
2243
+ parentIdsSql._sql = selectCteColumnsSql(mainQuery.q.from, aliased);
2244
+ for (let i = 0; i < parentValues.length; i++) parentValues[i]._sql = `"${mainQuery.q.from}"."${aliased[i]}"`;
2245
+ });
2246
+ const deleteQuery = _queryDelete(state.joinTableQuery.where({ IN: {
2247
+ columns: state.foreignKeys,
2248
+ values: parentIdsSql
2249
+ } }));
2250
+ deleteQuery.q.returnType = "void";
2251
+ if (state.on) _queryWhereExists(deleteQuery, state.relatedTableQuery, [(q) => {
2252
+ for (let i = 0; i < state.throughPrimaryKeys.length; i++) _queryJoinOn(q, [state.throughPrimaryKeysFull[i], state.throughForeignKeysFull[i]]);
2253
+ return q;
2254
+ }]);
2255
+ _appendQuery(querySelf, deleteQuery, (as) => {
2256
+ mainQuery.q.from = as;
2257
+ mainQuery.q.schema = void 0;
2258
+ mainQuery.q.as = void 0;
1818
2259
  });
2260
+ if (Array.isArray(set) ? set.length === 0 : !objectHasValues(set)) return;
2261
+ const relatedWheres = toArray(set);
2262
+ const joinTableColumns = [...state.foreignKeys, ...state.throughForeignKeys];
2263
+ const isRootUpsert = querySelf.q.upsertUpdate;
2264
+ let parentCount = 0;
2265
+ if (!isRootUpsert) _queryHookAfterUpdate(querySelf, state.primaryKeys, (rows) => {
2266
+ parentCount = rows.length;
2267
+ });
2268
+ const joinRows = _querySelect(state.relatedTableQuery.whereOneOf(...relatedWheres), [Object.fromEntries([...state.foreignKeys.map((key, i) => [key, parentValues[i]]), ...state.throughForeignKeys.map((key, i) => [key, state.throughPrimaryKeys[i]])])]).join(mainQuery, true);
2269
+ const joinQuery = state.joinTableQuery.insertForEachFrom(joinRows).onConflict(joinTableColumns).merge([state.foreignKeys[0]]);
2270
+ if (onAppended) _querySelect(joinQuery, state.throughForeignKeys);
2271
+ else joinQuery.q.returnType = "void";
2272
+ if (!isRootUpsert) {
2273
+ joinQuery.q.ensureCount = {
2274
+ expected: relatedWheres.length,
2275
+ message: "based on `set` conditions"
2276
+ };
2277
+ _queryHookAfterCreate(joinQuery, state.foreignKeys, (rows) => {
2278
+ if (rows.length < parentCount * relatedWheres.length) throw new OrchidOrmInternalError(querySelf, `Expected to find at least ${relatedWheres.length} record(s) based on \`set\` conditions, but found ${rows.length / parentCount}`);
2279
+ });
2280
+ }
2281
+ _appendQuery(querySelf, joinQuery, () => {}, onAppended);
1819
2282
  };
1820
2283
  var HasAndBelongsToManyVirtualColumn = class extends VirtualColumn {
1821
2284
  constructor(joinTable, schema, key, state) {
@@ -1824,18 +2287,83 @@ var HasAndBelongsToManyVirtualColumn = class extends VirtualColumn {
1824
2287
  this.key = key;
1825
2288
  this.state = state;
1826
2289
  this.nestedInsert = nestedInsert(state);
1827
- this.nestedUpdate = nestedUpdate(state);
1828
2290
  }
1829
- create(q, ctx, items, rowIndexes) {
1830
- hasRelationHandleCreate(q, ctx, items, rowIndexes, this.key, this.state.primaryKeys, this.nestedInsert);
2291
+ create(self, ctx, items, rowIndexes, count) {
2292
+ hasAndBelongsToManyCreate(this.key, this.state, this.nestedInsert, self, ctx, items, rowIndexes, count);
1831
2293
  }
1832
2294
  update(q, set) {
1833
2295
  const querySelf = q;
1834
2296
  const params = set[this.key];
1835
- hasRelationHandleUpdate(querySelf, set, this.key, this.state.primaryKeys, this.nestedUpdate);
1836
- if (params.upsert) {
2297
+ const hasUpdate = !!params.update && toArray(params.update).some((update) => !Array.isArray(update.where) || update.where.length);
2298
+ const hasAdd = params.add && (!Array.isArray(params.add) || params.add.length > 0);
2299
+ const addedAs = hasUpdate && hasAdd ? new RawSql("") : void 0;
2300
+ const setAs = hasUpdate && params.set ? new RawSql("") : void 0;
2301
+ const sourceCondition = addedAs || setAs ? new RawSql("") : void 0;
2302
+ const updateFrom = sourceCondition ? new RawSql("") : void 0;
2303
+ const setSourceCondition = () => {
2304
+ if (!sourceCondition) return;
2305
+ const relatedAs = this.state.relatedTableQuery.q.as || this.state.relatedTableQuery.table;
2306
+ const joinCondition = (as) => this.state.throughPrimaryKeys.map((key, i) => `"${relatedAs}"."${this.state.relatedTableQuery.shape[key].data.name || key}" = ${as}."${this.state.throughForeignKeys[i]}"`).join(" AND ");
2307
+ const sourceAs = "\"relatedIds\"";
2308
+ sourceCondition._sql = joinCondition(sourceAs);
2309
+ const sourceColumns = this.state.throughForeignKeys.map((key) => `"${key}"`).join(", ");
2310
+ updateFrom._sql = `(${[addedAs, setAs].filter((as) => !!as && !!as._sql).map((as) => `SELECT ${sourceColumns} FROM ${as._sql}`).concat().join(" UNION ALL ")}) AS ${sourceAs}`;
2311
+ };
2312
+ if (params.add) {
2313
+ nestedUpdateAdd(querySelf, this.state, params.add, (as) => {
2314
+ if (addedAs) {
2315
+ addedAs._sql = `"${as}"`;
2316
+ setSourceCondition();
2317
+ }
2318
+ });
2319
+ set[this.key] = {
2320
+ ...params,
2321
+ add: void 0
2322
+ };
2323
+ }
2324
+ if (params.create?.length) {
2325
+ nestedUpdateCreate(querySelf, this.state, params.create);
2326
+ set[this.key] = {
2327
+ ...params,
2328
+ create: void 0
2329
+ };
2330
+ }
2331
+ if (params.disconnect && (!Array.isArray(params.disconnect) || params.disconnect.length > 0)) {
2332
+ nestedUpdateDisconnect(querySelf, this.state, params.disconnect);
2333
+ set[this.key] = {
2334
+ ...params,
2335
+ disconnect: void 0
2336
+ };
2337
+ }
2338
+ if (params.delete && (!Array.isArray(params.delete) || params.delete.length)) {
2339
+ nestedUpdateDelete(querySelf, this.state, params.delete);
2340
+ set[this.key] = {
2341
+ ...params,
2342
+ delete: void 0
2343
+ };
2344
+ }
2345
+ if (params.set) {
2346
+ nestedUpdateSet(querySelf, this.state, params.set, (as) => {
2347
+ if (setAs) {
2348
+ setAs._sql = `"${as}"`;
2349
+ setSourceCondition();
2350
+ }
2351
+ });
2352
+ set[this.key] = {
2353
+ ...params,
2354
+ set: void 0
2355
+ };
2356
+ }
2357
+ if (!!params.upsert && (!Array.isArray(params.upsert) || params.upsert.length > 0)) {
1837
2358
  throwIfQueryReturnsAllForNestedUpdate(querySelf, { upsert: params.upsert });
1838
- nestedUpdateUpsert(querySelf, this.state, params.upsert);
2359
+ nestedUpdateUpsert(querySelf, this.state, params.upsert, void 0, hasAdd || params.set ? updateFrom : void 0, hasAdd || params.set ? sourceCondition : void 0, params.disconnect, params.set);
2360
+ }
2361
+ if (hasUpdate) {
2362
+ nestedUpdateUpdate(querySelf, this.state, params.update, updateFrom, sourceCondition, params.disconnect, params.set);
2363
+ set[this.key] = {
2364
+ ...params,
2365
+ update: void 0
2366
+ };
1839
2367
  }
1840
2368
  }
1841
2369
  };