linkgress-orm 0.4.56 → 0.4.58

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.
@@ -2208,6 +2208,184 @@ ${extraJoins.join('\n')}${orderBy}`;
2208
2208
  });
2209
2209
  return { parent: parentRow, children: mapChildren(strippedRows) };
2210
2210
  }
2211
+ /**
2212
+ * Bulk sibling of {@link insertWithChildren}: N parent rows plus their child rows in
2213
+ * ONE statement (a task-DAG persist — N task rows + one audit row each — used to
2214
+ * cost two). Parent identity mapping rides the SAME serial-ascend guarantee the
2215
+ * single-parent variant documents: the parent leg inserts `ORDER BY` an input
2216
+ * ordinal, so generated serial ids ascend in input-row order, and a
2217
+ * `row_number() OVER (ORDER BY pk)` CTE recovers each parent's input index for the
2218
+ * child join:
2219
+ *
2220
+ * WITH "__ibwc_parent__" AS (
2221
+ * INSERT INTO parent (cols)
2222
+ * SELECT cols FROM (VALUES (0, ...), (1, ...)) v("__ibwc_ord", cols)
2223
+ * ORDER BY v."__ibwc_ord" RETURNING *
2224
+ * ), "__ibwc_pord__" AS (
2225
+ * SELECT p.*, row_number() OVER (ORDER BY p."pk") - 1 AS "__ibwc_ord"
2226
+ * FROM "__ibwc_parent__" p
2227
+ * ), "__mutation__" AS (
2228
+ * INSERT INTO child ("fk", cols)
2229
+ * SELECT p."pk", v.cols
2230
+ * FROM (VALUES (0, pix, ...), ...) v("__ibwc_cord", "__ibwc_pix", cols)
2231
+ * JOIN "__ibwc_pord__" p ON p."__ibwc_ord" = v."__ibwc_pix"
2232
+ * ORDER BY v."__ibwc_cord" RETURNING <child cols>, "childPk"
2233
+ * )
2234
+ * SELECT "__mutation__".*, <parent cols + ordinal via the fk join>
2235
+ * FROM "__mutation__" JOIN "__ibwc_pord__" ON fk = pk ORDER BY child pk
2236
+ *
2237
+ * Single-statement atomicity: a failing leg rolls back both inserts. Parents come
2238
+ * back in input order; children in child-input order.
2239
+ *
2240
+ * v1 restrictions: FLAT returning selectors on BOTH sides (no navigation
2241
+ * projections); single-column auto/serial primary keys on both tables; no
2242
+ * `unlessExists` guard; child rows must NOT carry the foreign-key property; EVERY
2243
+ * parent must be referenced by at least one child (parents are returned through
2244
+ * the child join — a childless parent would insert but vanish from the result, so
2245
+ * it is rejected up front; use plain `insertBulk` for childless rows); the whole
2246
+ * shape must fit one statement (no chunking).
2247
+ */
2248
+ insertBulkWithChildren(config) {
2249
+ const { rows: childRows, foreignKey } = config.children;
2250
+ if (config.rows.length === 0) {
2251
+ throw new Error('insertBulkWithChildren: rows must be non-empty');
2252
+ }
2253
+ if (childRows.length === 0) {
2254
+ throw new Error('insertBulkWithChildren: children.rows must be non-empty — use insertBulk for childless parents');
2255
+ }
2256
+ const referenced = new Set();
2257
+ for (let i = 0; i < childRows.length; i++) {
2258
+ const { parentIndex, row } = childRows[i];
2259
+ if (!Number.isInteger(parentIndex) || parentIndex < 0 || parentIndex >= config.rows.length) {
2260
+ throw new Error(`insertBulkWithChildren: child row at index ${i} has parentIndex ${parentIndex} outside 0..${config.rows.length - 1}`);
2261
+ }
2262
+ if (row[foreignKey] !== undefined) {
2263
+ throw new Error(`insertBulkWithChildren: child row at index ${i} carries the foreign-key property "${foreignKey}" — it is sourced from its inserted parent`);
2264
+ }
2265
+ referenced.add(parentIndex);
2266
+ }
2267
+ for (let i = 0; i < config.rows.length; i++) {
2268
+ if (!referenced.has(i)) {
2269
+ throw new Error(`insertBulkWithChildren: parent at index ${i} has no child row — parents return through the child join (v1); insert childless rows via insertBulk`);
2270
+ }
2271
+ }
2272
+ const columnCount = Math.max(1, Object.keys(childRows[0].row).length + 2);
2273
+ const singleStatementLimit = Math.floor(Math.floor(65535 / columnCount) * 0.6);
2274
+ if (childRows.length + config.rows.length > singleStatementLimit) {
2275
+ throw new Error(`insertBulkWithChildren: ${config.rows.length} parents + ${childRows.length} children exceed the ~${singleStatementLimit}-row single-statement budget`);
2276
+ }
2277
+ return this.executeInsertBulkWithChildren(config);
2278
+ }
2279
+ /** @internal Async body of {@link insertBulkWithChildren} (validation stays synchronous). */
2280
+ async executeInsertBulkWithChildren(config) {
2281
+ const parentSchema = this._getSchema();
2282
+ const executor = this._getExecutor();
2283
+ const client = this._getClient();
2284
+ const childTable = config.children.table;
2285
+ if (childTable._getClient() !== client || childTable._getExecutor() !== executor) {
2286
+ throw new Error('insertBulkWithChildren: the child table uses a different database client or transaction than the parent — both must share one connection context');
2287
+ }
2288
+ const pkEntries = Object.entries(parentSchema.columns).filter(([, colBuilder]) => colBuilder.build().primaryKey);
2289
+ if (pkEntries.length !== 1) {
2290
+ throw new Error('insertBulkWithChildren requires a single-column parent primary key');
2291
+ }
2292
+ const parentPkDbName = pkEntries[0][1].build().name;
2293
+ // ---- parent leg: ordinal-ordered VALUES so serial ids ascend in input order ----
2294
+ const parentCompiled = this.compileValuesWithCasts(parentSchema, config.rows, null);
2295
+ const parentColNames = parentCompiled.columns.map(c => `"${c.dbName}"`).join(', ');
2296
+ const parentSelectCols = parentCompiled.columns.map(c => `v."${c.dbName}"`).join(', ');
2297
+ const parentValueRows = parentCompiled.valueRows.map((row, ix) => `(${ix}, ${row})`).join(', ');
2298
+ const params = [...parentCompiled.params];
2299
+ const parentMock = this.createMockEntity();
2300
+ const parentSelection = config.returning.parents(parentMock);
2301
+ const parentSelCols = [];
2302
+ for (const [prop, field] of Object.entries(parentSelection)) {
2303
+ const tableAlias = field?.__tableAlias;
2304
+ const dbColumnName = field?.__dbColumnName;
2305
+ if (dbColumnName == null || (tableAlias && tableAlias !== parentSchema.name)) {
2306
+ throw new Error(`insertBulkWithChildren: parents returning supports flat parent columns only — "${prop}" is not one`);
2307
+ }
2308
+ const colEntry = Object.entries(parentSchema.columns).find(([, colBuilder]) => colBuilder.build().name === dbColumnName);
2309
+ parentSelCols.push({ prop, dbName: dbColumnName, mapper: colEntry ? colEntry[1].build().mapper : undefined });
2310
+ }
2311
+ const parentSql = `INSERT INTO ${this._getQualifiedTableName()} (${parentColNames})
2312
+ SELECT ${parentSelectCols} FROM (VALUES ${parentValueRows}) AS v("__ibwc_ord", ${parentColNames})
2313
+ ORDER BY v."__ibwc_ord"
2314
+ RETURNING *`;
2315
+ // ---- child leg: (childOrd, parentIx, cells) VALUES joined to the ordinal CTE ----
2316
+ const childSchema = childTable._getSchema();
2317
+ const fkColBuilder = childSchema.columns[config.children.foreignKey];
2318
+ if (!fkColBuilder) {
2319
+ throw new Error(`insertBulkWithChildren: unknown child foreign-key property "${config.children.foreignKey}"`);
2320
+ }
2321
+ const fkDbName = fkColBuilder.build().name;
2322
+ const childCompiled = childTable.compileValuesWithCasts(childSchema, config.children.rows.map(r => r.row), config.children.foreignKey);
2323
+ const childColNames = childCompiled.columns.map(c => `"${c.dbName}"`).join(', ');
2324
+ const childSelectCols = childCompiled.columns.map(c => `v."${c.dbName}"`).join(', ');
2325
+ const childValueRows = childCompiled.valueRows.map((row, ix) => `(${ix}, ${config.children.rows[ix].parentIndex}, ${row})`).join(', ');
2326
+ const childOffset = params.length;
2327
+ let childSql = `INSERT INTO ${childTable._getQualifiedTableName()} ("${fkDbName}", ${childColNames})
2328
+ SELECT p."${parentPkDbName}", ${childSelectCols}
2329
+ FROM (VALUES ${childValueRows}) AS v("__ibwc_cord", "__ibwc_pix", ${childColNames})
2330
+ JOIN "__ibwc_pord__" p ON p."__ibwc_ord" = v."__ibwc_pix"
2331
+ ORDER BY v."__ibwc_cord"`;
2332
+ childSql = childOffset === 0 ? childSql : (0, sql_utils_1.renumberPlaceholders)(childSql, childOffset);
2333
+ params.push(...childCompiled.params);
2334
+ // ---- assembly: flat child returning + parent cols through the fk join ----
2335
+ if (childTable.detectNavigationInReturning(config.returning.children)) {
2336
+ throw new Error('insertBulkWithChildren: children returning supports flat child columns only (v1)');
2337
+ }
2338
+ const childPkEntries = Object.entries(childSchema.columns).filter(([, colBuilder]) => colBuilder.build().primaryKey);
2339
+ if (childPkEntries.length !== 1) {
2340
+ throw new Error('insertBulkWithChildren requires a single-column child primary key');
2341
+ }
2342
+ const childPkDbName = childPkEntries[0][1].build().name;
2343
+ const returningClause = childTable.buildReturningClause(config.returning.children);
2344
+ const parentJoinSelects = [
2345
+ `"__ibwc_pj__"."__ibwc_ord" AS "__ibwc_parent__.__ord"`,
2346
+ ...parentSelCols.map(c => `"__ibwc_pj__"."${c.dbName}" AS "__ibwc_parent__.${c.prop}"`),
2347
+ ];
2348
+ const sql = `WITH "__ibwc_parent__" AS (
2349
+ ${parentSql}
2350
+ ),
2351
+ "__ibwc_pord__" AS (
2352
+ SELECT p.*, row_number() OVER (ORDER BY p."${parentPkDbName}") - 1 AS "__ibwc_ord"
2353
+ FROM "__ibwc_parent__" p
2354
+ ),
2355
+ "__mutation__" AS (
2356
+ ${childSql}
2357
+ RETURNING ${returningClause.sql}, "${fkDbName}" AS "__ibwc_child_fk__", "${childPkDbName}" AS "__ibwc_child_pk__"
2358
+ )
2359
+ SELECT "__mutation__".*, ${parentJoinSelects.join(', ')}
2360
+ FROM "__mutation__"
2361
+ JOIN "__ibwc_pord__" "__ibwc_pj__" ON "__ibwc_pj__"."${parentPkDbName}" = "__mutation__"."__ibwc_child_fk__"
2362
+ ORDER BY "__mutation__"."__ibwc_child_pk__"`;
2363
+ const result = executor ? await executor.query(sql, params) : await client.query(sql, params);
2364
+ const rawRows = result.rows;
2365
+ const parentsByOrd = new Map();
2366
+ const strippedRows = [];
2367
+ for (const row of rawRows) {
2368
+ const ord = Number(row['__ibwc_parent__.__ord']);
2369
+ if (!parentsByOrd.has(ord)) {
2370
+ const parentRow = {};
2371
+ for (const col of parentSelCols) {
2372
+ const raw = row[`__ibwc_parent__.${col.prop}`];
2373
+ parentRow[col.prop] = col.mapper ? col.mapper.fromDriver(raw) : raw;
2374
+ }
2375
+ parentsByOrd.set(ord, parentRow);
2376
+ }
2377
+ const clean = {};
2378
+ for (const [key, value] of Object.entries(row)) {
2379
+ if (!key.startsWith('__ibwc_parent__.') && key !== '__ibwc_child_fk__' && key !== '__ibwc_child_pk__') {
2380
+ clean[key] = value;
2381
+ }
2382
+ }
2383
+ strippedRows.push(clean);
2384
+ }
2385
+ const parents = [...parentsByOrd.entries()].sort((a, b) => a[0] - b[0]).map(([, parentRow]) => parentRow);
2386
+ const children = childTable.mapReturningResults(strippedRows, returningClause.aliasToProperty);
2387
+ return { parents, children };
2388
+ }
2211
2389
  /**
2212
2390
  * Compile rows into a cast-annotated VALUES fragment (`$n::type` / `NULL::type`
2213
2391
  * per cell — the bulkUpdate technique, so a bare `VALUES` source keeps correct
@@ -2377,6 +2555,32 @@ ${extraJoins.join('\n')}${orderBy}`;
2377
2555
  }
2378
2556
  return { sql, params };
2379
2557
  }
2558
+ /**
2559
+ * Builds a bare `DELETE FROM t WHERE "col" IN ($1, …)` statement (no
2560
+ * RETURNING clause) so `MutationBatch` can compose it as a data-modifying
2561
+ * CTE leg. Each value runs through the column's toDriver mapper — the same
2562
+ * fidelity rule the insert/update legs follow. Returns null for an empty
2563
+ * values array (mirroring the other leg builders' empty-input semantics);
2564
+ * an unknown column property throws at build time.
2565
+ * @internal
2566
+ */
2567
+ _buildDeleteWhereInStatement(field, values) {
2568
+ if (values.length === 0) {
2569
+ return null;
2570
+ }
2571
+ const schema = this._getSchema();
2572
+ const colBuilder = schema.columns[field];
2573
+ if (!colBuilder) {
2574
+ throw new Error(`deleteWhereIn: unknown column property "${field}" on entity "${schema.name}"`);
2575
+ }
2576
+ const config = colBuilder.build();
2577
+ const params = values.map(value => (config.mapper ? config.mapper.toDriver(value) : value));
2578
+ const placeholders = params.map((_, ix) => `$${ix + 1}`).join(', ');
2579
+ return {
2580
+ sql: `DELETE FROM ${this._getQualifiedTableName()} WHERE "${config.name}" IN (${placeholders})`,
2581
+ params,
2582
+ };
2583
+ }
2380
2584
  /**
2381
2585
  * Upsert with advanced configuration
2382
2586
  * Auto-detects primary keys and supports chunking
@@ -2494,13 +2698,13 @@ ${extraJoins.join('\n')}${orderBy}`;
2494
2698
  };
2495
2699
  }
2496
2700
  /**
2497
- * Execute a single upsert batch
2701
+ * The bare `INSERT .. ON CONFLICT` assembly (no RETURNING) shared by
2702
+ * {@link upsertBulkSingle} and the MutationBatch upsert-leg compile —
2703
+ * one definition so the two can never drift.
2498
2704
  * @internal
2499
2705
  */
2500
- async upsertBulkSingle(values, primaryKeys, updateColumns, updateColumnFilter, overridingSystemValue, targetWhere, setWhere, returning) {
2706
+ buildUpsertStatementCore(values, primaryKeys, updateColumns, updateColumnFilter, overridingSystemValue, targetWhere, setWhere) {
2501
2707
  const schema = this._getSchema();
2502
- const executor = this._getExecutor();
2503
- const client = this._getClient();
2504
2708
  const qualifiedTableName = this._getQualifiedTableName();
2505
2709
  // Extract all unique column names from all data objects
2506
2710
  const columnConfigs = [];
@@ -2605,6 +2809,191 @@ ${extraJoins.join('\n')}${orderBy}`;
2605
2809
  sql += ` WHERE ${setWhere}`;
2606
2810
  }
2607
2811
  }
2812
+ return { sql, params };
2813
+ }
2814
+ /**
2815
+ * Builds a bare `UPDATE t SET .. WHERE "col" IN ($1, …)` statement (no
2816
+ * RETURNING) so `MutationBatch.addUpdateWhereIn` can compose it as a
2817
+ * data-modifying-CTE leg. SET semantics mirror the fluent `update()`:
2818
+ * plain values run through column mappers, `SqlFragment` values inline
2819
+ * with their params merged, and the lambda form resolves column refs
2820
+ * against this table's mock row. Returns null for an empty values array.
2821
+ * @internal
2822
+ */
2823
+ _buildUpdateWhereInStatement(field, values, set) {
2824
+ if (values.length === 0) {
2825
+ return null;
2826
+ }
2827
+ const schema = this._getSchema();
2828
+ const whereColBuilder = schema.columns[field];
2829
+ if (!whereColBuilder) {
2830
+ throw new Error(`updateWhereIn: unknown column property "${field}" on entity "${schema.name}"`);
2831
+ }
2832
+ const whereConfig = whereColBuilder.build();
2833
+ const resolvedSet = typeof set === 'function' ? set(this.createMockEntity()) : set;
2834
+ const setClauses = [];
2835
+ const params = [];
2836
+ let paramIndex = 1;
2837
+ for (const [key, value] of Object.entries(resolvedSet)) {
2838
+ const column = schema.columns[key];
2839
+ if (!column) {
2840
+ continue;
2841
+ }
2842
+ const config = column.build();
2843
+ if (value instanceof conditions_1.SqlFragment) {
2844
+ const sqlBuildContext = {
2845
+ paramCounter: paramIndex,
2846
+ params,
2847
+ };
2848
+ const fragmentSql = value.buildSql(sqlBuildContext);
2849
+ paramIndex = sqlBuildContext.paramCounter;
2850
+ setClauses.push(`"${config.name}" = ${fragmentSql}`);
2851
+ continue;
2852
+ }
2853
+ setClauses.push(`"${config.name}" = $${paramIndex++}`);
2854
+ params.push(config.mapper ? config.mapper.toDriver(value) : value);
2855
+ }
2856
+ if (setClauses.length === 0) {
2857
+ throw new Error(`updateWhereIn: no valid columns to update on entity "${schema.name}"`);
2858
+ }
2859
+ const whereParams = values.map(value => (whereConfig.mapper ? whereConfig.mapper.toDriver(value) : value));
2860
+ const placeholders = whereParams.map(() => `$${paramIndex++}`).join(', ');
2861
+ params.push(...whereParams);
2862
+ return {
2863
+ sql: `UPDATE ${this._getQualifiedTableName()} SET ${setClauses.join(', ')} WHERE "${whereConfig.name}" IN (${placeholders})`,
2864
+ params,
2865
+ };
2866
+ }
2867
+ /**
2868
+ * Bare `INSERT .. ON CONFLICT` compile (no RETURNING) for the MutationBatch
2869
+ * upsert leg — the {@link buildUpsertStatementCore} assembly with a narrow
2870
+ * plain config (prop-name primaryKey + updateColumns; no chunking, no
2871
+ * targetWhere/setWhere/system-value overrides in v1).
2872
+ * @internal
2873
+ */
2874
+ _buildUpsertBulkStatement(values, config) {
2875
+ if (values.length === 0) {
2876
+ return null;
2877
+ }
2878
+ const primaryKeys = Array.isArray(config.primaryKey) ? config.primaryKey : [config.primaryKey];
2879
+ return this.buildUpsertStatementCore(values, primaryKeys, config.updateColumns, undefined, false, undefined, undefined);
2880
+ }
2881
+ /**
2882
+ * Bare `INSERT INTO t (..) SELECT <cells> FROM "__MB_PARENT__" WHERE
2883
+ * "__MB_PARENT__"."<col>" <> $n` compile for the MutationBatch dependent
2884
+ * leg — a single row inserted iff the parent leg's exposed column differs
2885
+ * from the sentinel (the conditional-audit-log shape). Cells reuse the
2886
+ * insertWithChildren `$n::type` cast technique so types survive without a
2887
+ * VALUES-derived table; the `__MB_PARENT__` token is rewritten to the
2888
+ * parent's actual CTE name at batch assembly.
2889
+ * @internal
2890
+ */
2891
+ _buildDependentInsertSelectStatement(row, whereColumnAlias, whereNotEquals) {
2892
+ const schema = this._getSchema();
2893
+ const compiled = this.compileValuesWithCasts(schema, [row], null);
2894
+ const columnList = compiled.columns.map(c => `"${c.dbName}"`).join(', ');
2895
+ const params = [...compiled.params];
2896
+ const sentinelIndex = params.length + 1;
2897
+ params.push(whereNotEquals);
2898
+ return {
2899
+ sql: `INSERT INTO ${this._getQualifiedTableName()} (${columnList}) SELECT ${compiled.valueRows[0]} FROM "__MB_PARENT__" WHERE "__MB_PARENT__"."${whereColumnAlias}" <> $${sentinelIndex}`,
2900
+ params,
2901
+ };
2902
+ }
2903
+ /**
2904
+ * Compiles an insertBulkWithChildren persist into the CTE TRIPLE a
2905
+ * MutationBatch leg emits as siblings of the batch statement:
2906
+ *
2907
+ * `_p` — parent INSERT, input-ordinal ORDER BY (serial ids ascend in input
2908
+ * order), RETURNING *;
2909
+ * `_o` — the ordinal recovery (`row_number() OVER (ORDER BY pk) - 1`);
2910
+ * `_c` — child INSERT joining `_o` on the input index for the FK.
2911
+ *
2912
+ * Sibling CTE names are referenced through the `__MB_SELF__` token — the
2913
+ * batch rewrites it to the leg's actual prefix at assembly. Params are ONE
2914
+ * $1-based space across the triple (child placeholders pre-shifted here).
2915
+ * Unlike the standalone form, childless parents are allowed — the leg reads
2916
+ * parents from `_o` directly, not through the child join.
2917
+ * @internal
2918
+ */
2919
+ _buildInsertBulkWithChildrenCtes(config) {
2920
+ const parentSchema = this._getSchema();
2921
+ const childTable = config.children.table;
2922
+ if (childTable._getClient() !== this._getClient() || childTable._getExecutor() !== this._getExecutor()) {
2923
+ throw new Error('insertBulkWithChildren leg: the child table uses a different database client or transaction than the parent — both must share one connection context');
2924
+ }
2925
+ const pkEntries = Object.entries(parentSchema.columns).filter(([, colBuilder]) => colBuilder.build().primaryKey);
2926
+ if (pkEntries.length !== 1) {
2927
+ throw new Error('insertBulkWithChildren leg requires a single-column parent primary key');
2928
+ }
2929
+ for (const child of config.children.rows) {
2930
+ if (child.parentIndex < 0 || child.parentIndex >= config.rows.length) {
2931
+ throw new Error(`insertBulkWithChildren leg: child parentIndex ${child.parentIndex} is out of range for ${config.rows.length} parent row(s)`);
2932
+ }
2933
+ }
2934
+ const parentPkDbName = pkEntries[0][1].build().name;
2935
+ const parentCompiled = this.compileValuesWithCasts(parentSchema, config.rows, null);
2936
+ const parentColNames = parentCompiled.columns.map(c => `"${c.dbName}"`).join(', ');
2937
+ const parentSelectCols = parentCompiled.columns.map(c => `v."${c.dbName}"`).join(', ');
2938
+ const parentValueRows = parentCompiled.valueRows.map((row, ix) => `(${ix}, ${row})`).join(', ');
2939
+ const parentSql = `INSERT INTO ${this._getQualifiedTableName()} (${parentColNames})
2940
+ SELECT ${parentSelectCols} FROM (VALUES ${parentValueRows}) AS v("__mbw_ord", ${parentColNames})
2941
+ ORDER BY v."__mbw_ord"
2942
+ RETURNING *`;
2943
+ const ordinalSql = `SELECT p.*, row_number() OVER (ORDER BY p."${parentPkDbName}") - 1 AS "__mbw_ord"
2944
+ FROM "__MB_SELF___p" p`;
2945
+ const childSchema = childTable._getSchema();
2946
+ const fkColBuilder = childSchema.columns[config.children.foreignKey];
2947
+ if (!fkColBuilder) {
2948
+ throw new Error(`insertBulkWithChildren leg: unknown child foreign-key property "${config.children.foreignKey}"`);
2949
+ }
2950
+ const fkDbName = fkColBuilder.build().name;
2951
+ const childCompiled = childTable.compileValuesWithCasts(childSchema, config.children.rows.map((r) => r.row), config.children.foreignKey);
2952
+ const childColNames = childCompiled.columns.map((c) => `"${c.dbName}"`).join(', ');
2953
+ const childSelectCols = childCompiled.columns.map((c) => `v."${c.dbName}"`).join(', ');
2954
+ const childValueRows = childCompiled.valueRows.map((row, ix) => `(${ix}, ${config.children.rows[ix].parentIndex}, ${row})`).join(', ');
2955
+ let childSql = `INSERT INTO ${childTable._getQualifiedTableName()} ("${fkDbName}", ${childColNames})
2956
+ SELECT o."${parentPkDbName}", ${childSelectCols}
2957
+ FROM (VALUES ${childValueRows}) AS v("__mbw_cord", "__mbw_pix", ${childColNames})
2958
+ JOIN "__MB_SELF___o" o ON o."__mbw_ord" = v."__mbw_pix"
2959
+ ORDER BY v."__mbw_cord"
2960
+ RETURNING 1`;
2961
+ childSql = parentCompiled.params.length === 0 ? childSql : (0, sql_utils_1.renumberPlaceholders)(childSql, parentCompiled.params.length);
2962
+ return {
2963
+ ctes: [
2964
+ { suffix: 'p', sql: parentSql },
2965
+ { suffix: 'o', sql: ordinalSql },
2966
+ { suffix: 'c', sql: childSql },
2967
+ ],
2968
+ params: [...parentCompiled.params, ...childCompiled.params],
2969
+ };
2970
+ }
2971
+ /**
2972
+ * Resolves entity property names to their DB column names (throws on an
2973
+ * unknown property) — the MutationBatch expose/returning lists come in as
2974
+ * prop names and compile to quoted DB identifiers.
2975
+ * @internal
2976
+ */
2977
+ _resolveColumnDbNames(props) {
2978
+ const schema = this._getSchema();
2979
+ return props.map((prop) => {
2980
+ const colBuilder = schema.columns[prop];
2981
+ if (!colBuilder) {
2982
+ throw new Error(`Unknown column property "${prop}" on entity "${schema.name}"`);
2983
+ }
2984
+ return { prop, dbName: colBuilder.build().name };
2985
+ });
2986
+ }
2987
+ /**
2988
+ * Execute a single upsert batch
2989
+ * @internal
2990
+ */
2991
+ async upsertBulkSingle(values, primaryKeys, updateColumns, updateColumnFilter, overridingSystemValue, targetWhere, setWhere, returning) {
2992
+ const executor = this._getExecutor();
2993
+ const client = this._getClient();
2994
+ const built = this.buildUpsertStatementCore(values, primaryKeys, updateColumns, updateColumnFilter, overridingSystemValue, targetWhere, setWhere);
2995
+ let sql = built.sql;
2996
+ const params = built.params;
2608
2997
  // Check if RETURNING uses navigation properties
2609
2998
  const navigationInfo = returning && returning !== true && typeof returning === 'function'
2610
2999
  ? this.detectNavigationInReturning(returning)