@rex0220/kintone-sql-tools 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist-cli/ksql.js CHANGED
@@ -2804,23 +2804,31 @@ function resolveKintoneFunc(name) {
2804
2804
  return "";
2805
2805
  }
2806
2806
  }
2807
+ var likeRegexCache = /* @__PURE__ */ new Map();
2808
+ var LIKE_REGEX_CACHE_MAX = 200;
2807
2809
  function matchLike(value, pattern) {
2808
2810
  if (!pattern.includes("%") && !pattern.includes("_")) {
2809
2811
  return value.includes(pattern);
2810
2812
  }
2811
- let regexStr = "^";
2812
- for (let i = 0; i < pattern.length; i++) {
2813
- const ch = pattern[i];
2814
- if (ch === "%") {
2815
- regexStr += ".*";
2816
- } else if (ch === "_") {
2817
- regexStr += ".";
2818
- } else {
2819
- regexStr += ch.replace(/[.+*?^${}()|[\]\\]/g, "\\$&");
2813
+ let regex = likeRegexCache.get(pattern);
2814
+ if (!regex) {
2815
+ let regexStr = "^";
2816
+ for (let i = 0; i < pattern.length; i++) {
2817
+ const ch = pattern[i];
2818
+ if (ch === "%") {
2819
+ regexStr += ".*";
2820
+ } else if (ch === "_") {
2821
+ regexStr += ".";
2822
+ } else {
2823
+ regexStr += ch.replace(/[.+*?^${}()|[\]\\]/g, "\\$&");
2824
+ }
2820
2825
  }
2826
+ regexStr += "$";
2827
+ regex = new RegExp(regexStr, "u");
2828
+ if (likeRegexCache.size >= LIKE_REGEX_CACHE_MAX) likeRegexCache.clear();
2829
+ likeRegexCache.set(pattern, regex);
2821
2830
  }
2822
- regexStr += "$";
2823
- return new RegExp(regexStr, "u").test(value);
2831
+ return regex.test(value);
2824
2832
  }
2825
2833
 
2826
2834
  // src/converter/dmlToKintone.ts
@@ -3248,10 +3256,12 @@ async function fetchPage(fetcher, app, query, fields, pageSize, offset) {
3248
3256
  return fetcher({ app, query: pageQuery, fields });
3249
3257
  }
3250
3258
  function buildCursorQuery(baseQuery, cursorId) {
3251
- if (cursorId <= 0) return baseQuery.trimEnd();
3252
- const cursor = `$id > ${cursorId} order by $id asc`;
3253
3259
  const base = baseQuery.trimEnd();
3254
- return base ? `${base} and ${cursor}` : cursor;
3260
+ if (cursorId <= 0) {
3261
+ return base ? `${base} order by $id asc` : "order by $id asc";
3262
+ }
3263
+ const cursor = `$id > ${cursorId} order by $id asc`;
3264
+ return base ? `(${base}) and ${cursor}` : cursor;
3255
3265
  }
3256
3266
  function buildPageQuery(query, pageSize, offset) {
3257
3267
  const base = query.trimEnd();
@@ -3404,6 +3414,8 @@ function applyJoin(leftRows, rightRows, join2) {
3404
3414
  else rightIndex.set(k, [rRow]);
3405
3415
  }
3406
3416
  const result = [];
3417
+ const emptyRight = {};
3418
+ for (const key of Object.keys(rightRows[0] ?? {})) emptyRight[key] = "";
3407
3419
  for (const lRow of leftRows) {
3408
3420
  const k = lRow[leftKey] ?? "";
3409
3421
  const matched = rightIndex.get(k) ?? [];
@@ -3412,8 +3424,6 @@ function applyJoin(leftRows, rightRows, join2) {
3412
3424
  result.push({ ...lRow, ...rRow });
3413
3425
  }
3414
3426
  } else if (joinType === "LEFT") {
3415
- const emptyRight = {};
3416
- for (const key of Object.keys(rightRows[0] ?? {})) emptyRight[key] = "";
3417
3427
  result.push({ ...lRow, ...emptyRight });
3418
3428
  }
3419
3429
  }
@@ -3489,12 +3499,23 @@ function evalAggregate(func, distinct, arg, rows) {
3489
3499
  return nums.reduce((a, b) => a + b, 0);
3490
3500
  case "AVG":
3491
3501
  return nums.length === 0 ? 0 : nums.reduce((a, b) => a + b, 0) / nums.length;
3502
+ // Math.max(...nums) は要素数が多いと RangeError になるためループで求める
3492
3503
  case "MAX":
3493
- return nums.length === 0 ? 0 : Math.max(...nums);
3504
+ return nums.length === 0 ? 0 : maxOf(nums);
3494
3505
  case "MIN":
3495
- return nums.length === 0 ? 0 : Math.min(...nums);
3506
+ return nums.length === 0 ? 0 : minOf(nums);
3496
3507
  }
3497
3508
  }
3509
+ function maxOf(nums) {
3510
+ let m = nums[0];
3511
+ for (const n of nums) if (n > m) m = n;
3512
+ return m;
3513
+ }
3514
+ function minOf(nums) {
3515
+ let m = nums[0];
3516
+ for (const n of nums) if (n < m) m = n;
3517
+ return m;
3518
+ }
3498
3519
  function evalAggArithExpr(node, rows) {
3499
3520
  if (node.type === "NUMBER") return node.value;
3500
3521
  if (node.type === "AGG_REF") return evalAggregate(node.func, node.distinct, node.arg, rows);
@@ -3531,43 +3552,89 @@ function applyHaving(rows, having) {
3531
3552
  return rows.filter((row) => evalWhere(having, row));
3532
3553
  }
3533
3554
  function applyDistinct(rows, columns) {
3555
+ if (rows.length === 0) return rows;
3556
+ const keyFor = buildDistinctKeyBuilder(rows, columns);
3534
3557
  const seen = /* @__PURE__ */ new Set();
3535
3558
  return rows.filter((row) => {
3536
- const key = buildDistinctKey(row, columns);
3559
+ const key = keyFor(row);
3537
3560
  if (seen.has(key)) return false;
3538
3561
  seen.add(key);
3539
3562
  return true;
3540
3563
  });
3541
3564
  }
3542
- function buildDistinctKey(row, columns) {
3565
+ function buildDistinctKeyBuilder(rows, columns) {
3543
3566
  if (columns.some((c) => c.type === "WILDCARD")) {
3544
- return JSON.stringify(Object.entries(row).sort());
3545
- }
3546
- const values = [];
3547
- for (const col of columns) {
3548
- if (col.type === "FIELD") {
3549
- values.push(row[col.field] ?? "");
3550
- continue;
3551
- }
3552
- if (col.type === "PARENT_WILDCARD") {
3553
- for (const key of Object.keys(row).filter((k) => k.startsWith("_p.")).sort()) {
3554
- values.push(row[key] ?? "");
3567
+ const allKeys = /* @__PURE__ */ new Set();
3568
+ for (const row of rows) {
3569
+ for (const k of Object.keys(row)) allKeys.add(k);
3570
+ }
3571
+ const keys = [...allKeys].sort();
3572
+ return (row) => JSON.stringify(keys.map((k) => row[k] !== void 0 ? row[k] : null));
3573
+ }
3574
+ let sortedParentKeys = [];
3575
+ if (columns.some((c) => c.type === "PARENT_WILDCARD")) {
3576
+ const parentKeys = /* @__PURE__ */ new Set();
3577
+ for (const row of rows) {
3578
+ for (const k of Object.keys(row)) {
3579
+ if (k.startsWith("_p.")) parentKeys.add(k);
3555
3580
  }
3556
3581
  }
3582
+ sortedParentKeys = [...parentKeys].sort();
3557
3583
  }
3558
- return values.join("\0");
3584
+ return (row) => {
3585
+ const values = [];
3586
+ for (const col of columns) {
3587
+ if (col.type === "FIELD") {
3588
+ values.push(row[col.field] ?? "");
3589
+ continue;
3590
+ }
3591
+ if (col.type === "PARENT_WILDCARD") {
3592
+ for (const k of sortedParentKeys) {
3593
+ values.push(row[k] !== void 0 ? row[k] : null);
3594
+ }
3595
+ }
3596
+ }
3597
+ return JSON.stringify(values);
3598
+ };
3559
3599
  }
3560
3600
  function applyOrderBy(rows, orderBy, optionOrders, sortKinds) {
3561
3601
  if (orderBy.length === 0) return rows;
3562
- return [...rows].sort((a, b) => {
3563
- for (const { key, direction } of orderBy) {
3564
- const av = evalOrderKey(key, a);
3565
- const bv = evalOrderKey(key, b);
3566
- const cmp = compareOrderValues(av, bv, key, optionOrders, sortKinds);
3567
- if (cmp !== 0) return direction === "ASC" ? cmp : -cmp;
3602
+ const keyMeta = orderBy.map(({ key }) => ({
3603
+ orderMap: key.type === "FIELD_NAME" ? optionOrders?.get(key.name) : void 0,
3604
+ sortKind: key.type === "FIELD_NAME" ? sortKinds?.get(key.name) : void 0
3605
+ }));
3606
+ const decorated = rows.map((row) => ({
3607
+ row,
3608
+ keys: orderBy.map(({ key }, i) => {
3609
+ const s = evalOrderKey(key, row);
3610
+ const n = Number(s);
3611
+ const orderMap = keyMeta[i].orderMap;
3612
+ return {
3613
+ s,
3614
+ n,
3615
+ isNum: !Number.isNaN(n),
3616
+ rank: orderMap ? minChoiceIndex(parseChoiceValues(s), orderMap) : 0
3617
+ };
3618
+ })
3619
+ }));
3620
+ decorated.sort((a, b) => {
3621
+ for (let i = 0; i < orderBy.length; i++) {
3622
+ const cmp = compareSortKeys(a.keys[i], b.keys[i], keyMeta[i]);
3623
+ if (cmp !== 0) return orderBy[i].direction === "ASC" ? cmp : -cmp;
3568
3624
  }
3569
3625
  return 0;
3570
3626
  });
3627
+ return decorated.map((d) => d.row);
3628
+ }
3629
+ function compareSortKeys(a, b, meta) {
3630
+ if (meta.orderMap) {
3631
+ if (a.rank !== b.rank) return a.rank - b.rank;
3632
+ return a.s.localeCompare(b.s, "ja");
3633
+ }
3634
+ if (meta.sortKind === "string") {
3635
+ return a.s.localeCompare(b.s, "ja");
3636
+ }
3637
+ return a.isNum && b.isNum ? a.n - b.n : a.s.localeCompare(b.s, "ja");
3571
3638
  }
3572
3639
  function evalOrderKey(key, row) {
3573
3640
  switch (key.type) {
@@ -3579,32 +3646,6 @@ function evalOrderKey(key, row) {
3579
3646
  return evalStringFunc(key.expr, row);
3580
3647
  }
3581
3648
  }
3582
- function compareOrderValues(av, bv, key, optionOrders, sortKinds) {
3583
- if (key.type === "FIELD_NAME") {
3584
- const orderMap = optionOrders?.get(key.name);
3585
- if (orderMap) {
3586
- const ac = compareByChoiceOrder(av, bv, orderMap);
3587
- if (ac !== 0) return ac;
3588
- return av.localeCompare(bv, "ja");
3589
- }
3590
- const sortKind = sortKinds?.get(key.name);
3591
- if (sortKind === "number") {
3592
- return compareAsNumber(av, bv);
3593
- }
3594
- if (sortKind === "string") {
3595
- return av.localeCompare(bv, "ja");
3596
- }
3597
- }
3598
- return compareAuto(av, bv);
3599
- }
3600
- function compareByChoiceOrder(av, bv, orderMap) {
3601
- const aValues = parseChoiceValues(av);
3602
- const bValues = parseChoiceValues(bv);
3603
- const aRank = minChoiceIndex(aValues, orderMap);
3604
- const bRank = minChoiceIndex(bValues, orderMap);
3605
- if (aRank !== bRank) return aRank - bRank;
3606
- return 0;
3607
- }
3608
3649
  function parseChoiceValues(raw) {
3609
3650
  const trimmed = raw.trim();
3610
3651
  if (trimmed === "") return [""];
@@ -3628,18 +3669,6 @@ function minChoiceIndex(values, orderMap) {
3628
3669
  }
3629
3670
  return min;
3630
3671
  }
3631
- function compareAsNumber(av, bv) {
3632
- const an = Number(av);
3633
- const bn = Number(bv);
3634
- const numeric = !Number.isNaN(an) && !Number.isNaN(bn);
3635
- return numeric ? an - bn : av.localeCompare(bv, "ja");
3636
- }
3637
- function compareAuto(av, bv) {
3638
- const an = Number(av);
3639
- const bn = Number(bv);
3640
- const numeric = !Number.isNaN(an) && !Number.isNaN(bn);
3641
- return numeric ? an - bn : av.localeCompare(bv, "ja");
3642
- }
3643
3672
  function applyLimit(rows, limit, offset) {
3644
3673
  const start = offset ?? 0;
3645
3674
  if (limit === null) return rows.slice(start);
@@ -3884,6 +3913,56 @@ function toFlatString(value) {
3884
3913
 
3885
3914
  // src/execute.ts
3886
3915
  async function execute(sql, client, options = {}) {
3916
+ const metrics = createEmptyMetrics();
3917
+ const countedClient = wrapClientWithMetrics(client, metrics);
3918
+ const startedAt = Date.now();
3919
+ const result = await executeStatement(sql, countedClient, options);
3920
+ metrics.elapsedMs = Date.now() - startedAt;
3921
+ return { ...result, metrics };
3922
+ }
3923
+ function createEmptyMetrics() {
3924
+ return {
3925
+ getCalls: 0,
3926
+ postCalls: 0,
3927
+ putCalls: 0,
3928
+ deleteCalls: 0,
3929
+ fieldCalls: 0,
3930
+ appsCalls: 0,
3931
+ fetchedRows: 0,
3932
+ elapsedMs: 0
3933
+ };
3934
+ }
3935
+ function wrapClientWithMetrics(client, metrics) {
3936
+ return {
3937
+ getRecords: async (params) => {
3938
+ metrics.getCalls += 1;
3939
+ const res = await client.getRecords(params);
3940
+ metrics.fetchedRows += res.records.length;
3941
+ return res;
3942
+ },
3943
+ postRecords: (params) => {
3944
+ metrics.postCalls += 1;
3945
+ return client.postRecords(params);
3946
+ },
3947
+ putRecords: (params) => {
3948
+ metrics.putCalls += 1;
3949
+ return client.putRecords(params);
3950
+ },
3951
+ deleteRecords: (params) => {
3952
+ metrics.deleteCalls += 1;
3953
+ return client.deleteRecords(params);
3954
+ },
3955
+ getApps: () => {
3956
+ metrics.appsCalls += 1;
3957
+ return client.getApps();
3958
+ },
3959
+ getFields: (appId) => {
3960
+ metrics.fieldCalls += 1;
3961
+ return client.getFields(appId);
3962
+ }
3963
+ };
3964
+ }
3965
+ async function executeStatement(sql, client, options) {
3887
3966
  const cacheContext = options.cacheContext ?? "default";
3888
3967
  const stmt = parseSql(sql);
3889
3968
  switch (stmt.type) {
@@ -3894,7 +3973,7 @@ async function execute(sql, client, options = {}) {
3894
3973
  case "WITH":
3895
3974
  return executeWith(stmt, client, options, cacheContext);
3896
3975
  case "INSERT":
3897
- return executeInsert(stmt, client, cacheContext);
3976
+ return executeInsert(stmt, client, options, cacheContext);
3898
3977
  case "INSERT_SELECT":
3899
3978
  return executeInsertSelect(stmt, client, options, cacheContext);
3900
3979
  case "UPSERT":
@@ -4009,8 +4088,7 @@ async function executeSimpleSelect(stmt, client, options, cacheContext) {
4009
4088
  }
4010
4089
  let rows = records.map((r) => flatten(r, null));
4011
4090
  if (!useSingleGet) {
4012
- const optionOrders = await buildOptionOrdersForSelect(stmt, client, cacheContext);
4013
- const sortKinds = await buildSortKindsForSelect(stmt, client, cacheContext);
4091
+ const { optionOrders, sortKinds } = await buildOrderByMetaForSelect(stmt, client, cacheContext);
4014
4092
  rows = applyOrderBy(rows, stmt.orderBy, optionOrders, sortKinds);
4015
4093
  rows = applyLimit(rows, stmt.limit, stmt.offset);
4016
4094
  }
@@ -4038,11 +4116,12 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
4038
4116
  }
4039
4117
  }
4040
4118
  for (const [appId, fields] of appToFields.entries()) {
4041
- if (fields.size === 0) continue;
4119
+ const userFields = [...fields].filter((f) => !isSystemLikeFieldCode(f));
4120
+ if (userFields.length === 0) continue;
4042
4121
  const defs = await getFieldsCached(appId, client, cacheContext);
4043
4122
  if (defs.length === 0) continue;
4044
4123
  const validCodes = new Set(defs.map((d) => d.code));
4045
- const unknown = [...fields].filter((f) => !isSystemLikeFieldCode(f) && !validCodes.has(f));
4124
+ const unknown = userFields.filter((f) => !validCodes.has(f));
4046
4125
  if (unknown.length > 0) {
4047
4126
  throw new Error(`ArgumentError: unknown field code(s): ${unknown.join(", ")} (APP${appId})`);
4048
4127
  }
@@ -4052,8 +4131,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
4052
4131
  const maxRecords = options.maxRecords ?? 1e4;
4053
4132
  const warnings = /* @__PURE__ */ new Set();
4054
4133
  const parallel = options.fetchParallel ?? 1;
4055
- await resolveSubqueries(stmt.where, client, options, cacheContext);
4056
- await resolveSubqueries(stmt.having, client, options, cacheContext);
4134
+ await Promise.all([
4135
+ resolveSubqueries(stmt.where, client, options, cacheContext),
4136
+ resolveSubqueries(stmt.having, client, options, cacheContext)
4137
+ ]);
4057
4138
  const tableConditions = /* @__PURE__ */ new Map();
4058
4139
  if (stmt.where !== null) {
4059
4140
  if (stmt.from.alias) {
@@ -4102,6 +4183,12 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
4102
4183
  onOptJoins.push(join2);
4103
4184
  }
4104
4185
  }
4186
+ const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext);
4187
+ const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
4188
+ scalarCachePromise.catch(() => {
4189
+ });
4190
+ orderByMetaPromise.catch(() => {
4191
+ });
4105
4192
  const mainRecords = await mainFetch;
4106
4193
  const tables = /* @__PURE__ */ new Map();
4107
4194
  tables.set(stmt.from.alias, mainRecords);
@@ -4133,15 +4220,16 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
4133
4220
  );
4134
4221
  tables.set(join2.table.alias, joinRecords);
4135
4222
  }));
4136
- const scalarCache = await resolveScalarColumns(stmt.columns, client, options, cacheContext);
4137
- const optionOrders = await buildOptionOrdersForSelect(stmt, client, cacheContext);
4138
- const sortKinds = await buildSortKindsForSelect(stmt, client, cacheContext);
4223
+ const scalarCache = await scalarCachePromise;
4224
+ const { optionOrders, sortKinds } = await orderByMetaPromise;
4139
4225
  const { rows, columns } = runFullScan({ tables, stmt, scalarCache, optionOrders, sortKinds });
4140
4226
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
4141
4227
  }
4142
4228
  async function executeUnion(stmt, client, options, cacheContext) {
4143
- const leftResult = stmt.left.type === "UNION" ? await executeUnion(stmt.left, client, options, cacheContext) : await executeSelect(stmt.left, client, options, cacheContext);
4144
- const rightResult = await executeSelect(stmt.right, client, options, cacheContext);
4229
+ const [leftResult, rightResult] = await Promise.all([
4230
+ stmt.left.type === "UNION" ? executeUnion(stmt.left, client, options, cacheContext) : executeSelect(stmt.left, client, options, cacheContext),
4231
+ executeSelect(stmt.right, client, options, cacheContext)
4232
+ ]);
4145
4233
  const leftCols = leftResult.columns;
4146
4234
  const rightCols = rightResult.columns;
4147
4235
  const remappedRight = rightResult.rows.map((row) => {
@@ -4158,7 +4246,7 @@ async function executeUnion(stmt, client, options, cacheContext) {
4158
4246
  function deduplicateRows(rows, columns) {
4159
4247
  const seen = /* @__PURE__ */ new Set();
4160
4248
  return rows.filter((row) => {
4161
- const key = columns.map((c) => row[c] ?? "").join("\0");
4249
+ const key = JSON.stringify(columns.map((c) => row[c] ?? ""));
4162
4250
  if (seen.has(key)) return false;
4163
4251
  seen.add(key);
4164
4252
  return true;
@@ -4261,8 +4349,10 @@ function stripCteAliasFromFieldValue(fv, alias) {
4261
4349
  }
4262
4350
  async function executeQueryWithCte(query, client, options, cteCache, cacheContext) {
4263
4351
  if (query.type === "UNION") {
4264
- const leftResult = await executeQueryWithCte(query.left, client, options, cteCache, cacheContext);
4265
- const rightResult = await executeQueryWithCte(query.right, client, options, cteCache, cacheContext);
4352
+ const [leftResult, rightResult] = await Promise.all([
4353
+ executeQueryWithCte(query.left, client, options, cteCache, cacheContext),
4354
+ executeQueryWithCte(query.right, client, options, cteCache, cacheContext)
4355
+ ]);
4266
4356
  const leftCols = leftResult.columns;
4267
4357
  const rightCols = rightResult.columns;
4268
4358
  const remapped = rightResult.rows.map((row) => {
@@ -4286,8 +4376,16 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
4286
4376
  const maxRecords = options.maxRecords ?? 1e4;
4287
4377
  const warnings = /* @__PURE__ */ new Set();
4288
4378
  const parallel = options.fetchParallel ?? 1;
4289
- await resolveSubqueries(stmt.where, client, options, cacheContext);
4290
- await resolveSubqueries(stmt.having, client, options, cacheContext);
4379
+ await Promise.all([
4380
+ resolveSubqueries(stmt.where, client, options, cacheContext),
4381
+ resolveSubqueries(stmt.having, client, options, cacheContext)
4382
+ ]);
4383
+ const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext);
4384
+ const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
4385
+ scalarCachePromise.catch(() => {
4386
+ });
4387
+ orderByMetaPromise.catch(() => {
4388
+ });
4291
4389
  const tables = /* @__PURE__ */ new Map();
4292
4390
  if (stmt.from.cteName != null) {
4293
4391
  const rows2 = cteCache.get(stmt.from.cteName) ?? [];
@@ -4334,9 +4432,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
4334
4432
  }
4335
4433
  });
4336
4434
  await Promise.all(joinFetches);
4337
- const scalarCache = await resolveScalarColumns(stmt.columns, client, options, cacheContext);
4338
- const optionOrders = await buildOptionOrdersForSelect(stmt, client, cacheContext);
4339
- const sortKinds = await buildSortKindsForSelect(stmt, client, cacheContext);
4435
+ const scalarCache = await scalarCachePromise;
4436
+ const { optionOrders, sortKinds } = await orderByMetaPromise;
4340
4437
  const { rows, columns } = runFullScan({ tables, stmt, scalarCache, optionOrders, sortKinds });
4341
4438
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
4342
4439
  }
@@ -4372,6 +4469,78 @@ async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, par
4372
4469
  const parentRecords = parentResolved.records;
4373
4470
  return expandSubtableRecords(parentRecords, table.subtableCode);
4374
4471
  }
4472
+ var UPSERT_IN_CHUNK_SIZE = 50;
4473
+ function normalizeKeyPart(v) {
4474
+ const t = v.trim();
4475
+ if (t !== "" && !Number.isNaN(Number(t))) return String(Number(t));
4476
+ return v;
4477
+ }
4478
+ function upsertCompositeKey(parts) {
4479
+ return JSON.stringify(parts);
4480
+ }
4481
+ function upsertNormalizedKey(parts, numericKey) {
4482
+ return JSON.stringify(parts.map((p, i) => numericKey[i] ? normalizeKeyPart(p) : p));
4483
+ }
4484
+ function lookupUpsertTarget(index, keyParts) {
4485
+ const exact = index.raw.get(upsertCompositeKey(keyParts));
4486
+ if (exact !== void 0) return exact;
4487
+ if (!index.numericKey.some(Boolean)) return void 0;
4488
+ return index.normalized.get(upsertNormalizedKey(keyParts, index.numericKey));
4489
+ }
4490
+ async function resolveUpsertTargets(appId, keyFields, rowKeyValues, client, options, fieldTypes) {
4491
+ const maxRecords = options.maxRecords ?? 1e4;
4492
+ const parallel = options.fetchParallel ?? 1;
4493
+ const numericKey = keyFields.map((f) => fieldTypes.get(f) === "NUMBER");
4494
+ const index = { raw: /* @__PURE__ */ new Map(), normalized: /* @__PURE__ */ new Map(), numericKey };
4495
+ const setMax = (map, key, id) => {
4496
+ const cur = map.get(key);
4497
+ if (cur === void 0 || id > cur) map.set(key, id);
4498
+ };
4499
+ const addRecordToIndex = (parts, id) => {
4500
+ setMax(index.raw, upsertCompositeKey(parts), id);
4501
+ if (numericKey.some(Boolean)) {
4502
+ setMax(index.normalized, upsertNormalizedKey(parts, numericKey), id);
4503
+ }
4504
+ };
4505
+ const batchFirstKeys = /* @__PURE__ */ new Set();
4506
+ const perRowKeys = [];
4507
+ const seen = /* @__PURE__ */ new Set();
4508
+ for (const parts of rowKeyValues) {
4509
+ const composite = upsertCompositeKey(parts);
4510
+ if (seen.has(composite)) continue;
4511
+ seen.add(composite);
4512
+ if (parts.some((p) => p === "")) perRowKeys.push(parts);
4513
+ else batchFirstKeys.add(parts[0]);
4514
+ }
4515
+ const fields = ["$id", ...keyFields];
4516
+ for (const chunk2 of splitChunks([...batchFirstKeys], UPSERT_IN_CHUNK_SIZE)) {
4517
+ const query = `${keyFields[0]} in (${chunk2.map(sqlQuote).join(",")})`;
4518
+ const records = await fetchAll(client.getRecords, appId, query, fields, { maxRecords, parallel });
4519
+ for (const rec of records) {
4520
+ const id = Number(rec["$id"]?.value);
4521
+ if (!Number.isFinite(id)) continue;
4522
+ addRecordToIndex(keyFields.map((f) => toScalarText(rec[f]?.value)), id);
4523
+ }
4524
+ }
4525
+ for (const parts of perRowKeys) {
4526
+ const query = keyFields.map((f, i) => `${f} = ${sqlQuote(parts[i])}`).join(" and ");
4527
+ const existing = await fetchAll(client.getRecords, appId, query, ["$id"], { maxRecords, parallel });
4528
+ if (existing.length === 0) continue;
4529
+ addRecordToIndex(parts, maxRecordId(existing));
4530
+ }
4531
+ return index;
4532
+ }
4533
+ function maxRecordId(records) {
4534
+ let max = Number.NEGATIVE_INFINITY;
4535
+ for (const r of records) {
4536
+ const n = Number(r["$id"]?.value);
4537
+ if (Number.isFinite(n) && n > max) max = n;
4538
+ }
4539
+ if (!Number.isFinite(max)) {
4540
+ throw new Error("\u30EC\u30B3\u30FC\u30C9\u306B\u6570\u5024\u306E $id \u304C\u542B\u307E\u308C\u3066\u3044\u307E\u305B\u3093\u3002");
4541
+ }
4542
+ return max;
4543
+ }
4375
4544
  function toScalarText(value) {
4376
4545
  if (typeof value === "string") return value;
4377
4546
  if (value === null || value === void 0) return "";
@@ -4509,6 +4678,16 @@ async function getSortKindMapByApp(appId, client, cacheContext) {
4509
4678
  setScopedCacheValue(sortKindCache, cacheContext, appId, map);
4510
4679
  return map;
4511
4680
  }
4681
+ async function buildOrderByMetaForSelect(stmt, client, cacheContext) {
4682
+ if (stmt.orderBy.length === 0) {
4683
+ return { optionOrders: /* @__PURE__ */ new Map(), sortKinds: /* @__PURE__ */ new Map() };
4684
+ }
4685
+ const [optionOrders, sortKinds] = await Promise.all([
4686
+ buildOptionOrdersForSelect(stmt, client, cacheContext),
4687
+ buildSortKindsForSelect(stmt, client, cacheContext)
4688
+ ]);
4689
+ return { optionOrders, sortKinds };
4690
+ }
4512
4691
  async function buildOptionOrdersForSelect(stmt, client, cacheContext) {
4513
4692
  const optionOrders = /* @__PURE__ */ new Map();
4514
4693
  const tables = [stmt.from, ...stmt.joins.map((j) => j.table)];
@@ -4582,9 +4761,9 @@ function convertProcessRowValue(raw, dstFieldType) {
4582
4761
  }
4583
4762
  return raw;
4584
4763
  }
4585
- async function executeInsert(stmt, client, cacheContext) {
4764
+ async function executeInsert(stmt, client, options, cacheContext) {
4586
4765
  if (stmt.subtableCode) {
4587
- return executeInsertSubtable(stmt, client, cacheContext);
4766
+ return executeInsertSubtable(stmt, client, options, cacheContext);
4588
4767
  }
4589
4768
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
4590
4769
  const batches = insertToPostBatches(stmt, fieldTypes);
@@ -4699,26 +4878,19 @@ async function executeDelete(stmt, client, options, cacheContext) {
4699
4878
  return { type: "DELETE", deletedCount: ids.length };
4700
4879
  }
4701
4880
  async function executeUpsert(stmt, client, options, cacheContext) {
4702
- const maxRecords = options.maxRecords ?? 1e4;
4703
4881
  const toInsert = [];
4704
4882
  const toUpdate = [];
4705
4883
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
4706
- for (const row of stmt.values) {
4707
- const keyConditions = stmt.keyFields.map((key) => {
4884
+ const rowKeyValues = stmt.values.map(
4885
+ (row) => stmt.keyFields.map((key) => {
4708
4886
  const idx = stmt.fields.indexOf(key);
4709
4887
  if (idx === -1) throw new Error(`ON DUPLICATE \u306E\u30AD\u30FC\u300C${key}\u300D\u304C INSERT \u30D5\u30A3\u30FC\u30EB\u30C9\u306B\u542B\u307E\u308C\u3066\u3044\u307E\u305B\u3093`);
4710
4888
  const val = row[idx];
4711
- const valStr = val.type === "STRING" ? val.value : val.type === "NUMBER" ? String(val.value) : val.type === "CASE_VALUE" ? evalCaseWhen(val.expr, {}) : val.elements.map((e) => e.value).join(",");
4712
- return `${key} = "${valStr.replace(/"/g, '\\"')}"`;
4713
- });
4714
- const query = keyConditions.join(" and ");
4715
- const existing = await fetchAll(
4716
- client.getRecords,
4717
- stmt.appId,
4718
- query,
4719
- ["$id"],
4720
- { maxRecords, parallel: options.fetchParallel ?? 1 }
4721
- );
4889
+ return val.type === "STRING" ? val.value : val.type === "NUMBER" ? String(val.value) : val.type === "CASE_VALUE" ? evalCaseWhen(val.expr, {}) : val.elements.map((e) => e.value).join(",");
4890
+ })
4891
+ );
4892
+ const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
4893
+ stmt.values.forEach((row, rowIdx) => {
4722
4894
  const record = {};
4723
4895
  stmt.fields.forEach((field, i) => {
4724
4896
  const val = row[i];
@@ -4728,13 +4900,13 @@ async function executeUpsert(stmt, client, options, cacheContext) {
4728
4900
  record[field] = { value: toKintoneValue(val, fieldTypes.get(field)) };
4729
4901
  }
4730
4902
  });
4731
- if (existing.length > 0) {
4732
- const id = Number(existing[0]["$id"].value);
4903
+ const id = lookupUpsertTarget(targetIndex, rowKeyValues[rowIdx]);
4904
+ if (id !== void 0) {
4733
4905
  toUpdate.push({ id, record });
4734
4906
  } else {
4735
4907
  toInsert.push(record);
4736
4908
  }
4737
- }
4909
+ });
4738
4910
  if (options.confirm && toInsert.length + toUpdate.length > 0) {
4739
4911
  const total = toInsert.length + toUpdate.length;
4740
4912
  const ok = await options.confirm(total, "UPDATE");
@@ -4754,18 +4926,17 @@ async function executeUpsert(stmt, client, options, cacheContext) {
4754
4926
  updatedCount: toUpdate.length
4755
4927
  };
4756
4928
  }
4757
- async function executeInsertSubtable(stmt, client, _cacheContext) {
4929
+ async function executeInsertSubtable(stmt, client, options, _cacheContext) {
4758
4930
  const subtableCode = stmt.subtableCode;
4759
4931
  const pidIndex = stmt.fields.indexOf("_pid");
4760
4932
  if (pidIndex < 0) {
4761
4933
  throw new Error("\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB INSERT \u306B\u306F _pid \u304C\u5FC5\u9808\u3067\u3059");
4762
4934
  }
4763
- const parents = await fetchAll(client.getRecords, stmt.appId, "", [], { maxRecords: 1e4, parallel: 1 });
4764
- const parentMap = /* @__PURE__ */ new Map();
4765
- for (const p of parents) {
4766
- const pid = String(p["$id"]?.value ?? "");
4767
- if (pid) parentMap.set(pid, p);
4768
- }
4935
+ const parents = await fetchAll(client.getRecords, stmt.appId, "", [], {
4936
+ maxRecords: options.maxRecords ?? 1e4,
4937
+ parallel: options.fetchParallel ?? 1
4938
+ });
4939
+ const parentMap = buildParentIdMap(parents);
4769
4940
  const insertsByParent = /* @__PURE__ */ new Map();
4770
4941
  for (const rowValues of stmt.values) {
4771
4942
  const pid = valueToString(rowValues[pidIndex]);
@@ -4832,8 +5003,9 @@ async function executeUpdateSubtable(stmt, client, options, _cacheContext) {
4832
5003
  }
4833
5004
  byRid.set(t.rowId, updates);
4834
5005
  }
5006
+ const parentById = buildParentIdMap(parents);
4835
5007
  for (const [pid, updateMap] of updatesByParent.entries()) {
4836
- const parent = parents.find((p) => String(p["$id"]?.value ?? "") === pid);
5008
+ const parent = parentById.get(pid);
4837
5009
  if (!parent) continue;
4838
5010
  const currentRows = getMutableTableRows(parent, subtableCode);
4839
5011
  const payloadRows = currentRows.map((row) => {
@@ -4873,8 +5045,9 @@ async function executeDeleteSubtable(stmt, client, options, _cacheContext) {
4873
5045
  if (bucket) bucket.push(t.rowIndex);
4874
5046
  else byParent.set(t.parentId, [t.rowIndex]);
4875
5047
  }
5048
+ const parentById = buildParentIdMap(parents);
4876
5049
  for (const [pid, idxs] of byParent.entries()) {
4877
- const parent = parents.find((p) => String(p["$id"]?.value ?? "") === pid);
5050
+ const parent = parentById.get(pid);
4878
5051
  if (!parent) continue;
4879
5052
  const rows = getMutableTableRows(parent, subtableCode);
4880
5053
  const rm = new Set(idxs);
@@ -4883,6 +5056,14 @@ async function executeDeleteSubtable(stmt, client, options, _cacheContext) {
4883
5056
  }
4884
5057
  return { type: "DELETE", deletedCount: targets.length };
4885
5058
  }
5059
+ function buildParentIdMap(parents) {
5060
+ const map = /* @__PURE__ */ new Map();
5061
+ for (const p of parents) {
5062
+ const pid = String(p["$id"]?.value ?? "");
5063
+ if (pid) map.set(pid, p);
5064
+ }
5065
+ return map;
5066
+ }
4886
5067
  function expandRowsForSubtableDml(parents, subtableCode) {
4887
5068
  const out = [];
4888
5069
  for (const parent of parents) {
@@ -5018,8 +5199,9 @@ async function executeReorder(stmt, client, options, _cacheContext) {
5018
5199
  const ok = await options.confirm(targetParentIds.size, "UPDATE");
5019
5200
  if (!ok) throw new OperationCancelledError("UPDATE", targetParentIds.size);
5020
5201
  }
5202
+ const parentById = buildParentIdMap(parents);
5021
5203
  for (const pid of targetParentIds) {
5022
- const parent = parents.find((p) => String(p["$id"]?.value ?? "") === pid);
5204
+ const parent = parentById.get(pid);
5023
5205
  if (!parent) continue;
5024
5206
  const rows = getMutableTableRows(parent, stmt.subtableCode);
5025
5207
  const sortable = rows.map((row, i) => ({ row, i, flat: buildFlatRowForSort(parent, stmt.subtableCode, row, i) }));
@@ -5067,7 +5249,6 @@ function evalOrderKeyForRow(key, row) {
5067
5249
  }
5068
5250
  }
5069
5251
  async function executeUpsertSelect(stmt, client, options, cacheContext) {
5070
- const maxRecords = options.maxRecords ?? 1e4;
5071
5252
  const selectResult = await executeSelect(stmt.select, client, options, cacheContext);
5072
5253
  const { rows, columns } = selectResult;
5073
5254
  if (columns.length !== stmt.fields.length) {
@@ -5082,29 +5263,26 @@ async function executeUpsertSelect(stmt, client, options, cacheContext) {
5082
5263
  }
5083
5264
  const toInsert = [];
5084
5265
  const toUpdate = [];
5085
- for (const row of rows) {
5266
+ const records = rows.map((row) => {
5086
5267
  const record = {};
5087
5268
  stmt.fields.forEach((field, i) => {
5088
5269
  record[field] = { value: row[columns[i]] ?? "" };
5089
5270
  });
5090
- const keyConditions = stmt.keyFields.map((key) => {
5091
- const val = String(record[key]?.value ?? "");
5092
- return `${key} = "${val.replace(/"/g, '\\"')}"`;
5093
- });
5094
- const query = keyConditions.join(" and ");
5095
- const existing = await fetchAll(
5096
- client.getRecords,
5097
- stmt.appId,
5098
- query,
5099
- ["$id"],
5100
- { maxRecords, parallel: options.fetchParallel ?? 1 }
5101
- );
5102
- if (existing.length > 0) {
5103
- toUpdate.push({ id: Number(existing[0]["$id"].value), record });
5271
+ return record;
5272
+ });
5273
+ const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
5274
+ const rowKeyValues = records.map(
5275
+ (record) => stmt.keyFields.map((key) => String(record[key]?.value ?? ""))
5276
+ );
5277
+ const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
5278
+ records.forEach((record, rowIdx) => {
5279
+ const id = lookupUpsertTarget(targetIndex, rowKeyValues[rowIdx]);
5280
+ if (id !== void 0) {
5281
+ toUpdate.push({ id, record });
5104
5282
  } else {
5105
5283
  toInsert.push(record);
5106
5284
  }
5107
- }
5285
+ });
5108
5286
  if (options.confirm && toInsert.length + toUpdate.length > 0) {
5109
5287
  const total = toInsert.length + toUpdate.length;
5110
5288
  const ok = await options.confirm(total, "UPDATE");
@@ -5156,36 +5334,44 @@ function parseSql(sql) {
5156
5334
  }
5157
5335
  }
5158
5336
  async function resolveSubqueries(where, client, options, cacheContext) {
5337
+ const tasks = [];
5338
+ collectSubqueryTasks(where, client, options, cacheContext, tasks);
5339
+ await Promise.all(tasks);
5340
+ }
5341
+ function collectSubqueryTasks(where, client, options, cacheContext, tasks) {
5159
5342
  if (where === null) return;
5160
5343
  switch (where.type) {
5161
5344
  case "BINARY": {
5162
5345
  const right = where.right;
5163
5346
  if (right.type === "SUBQUERY_IN_LIST") {
5164
- const result = await executeSelect(right.query, client, options, cacheContext);
5165
- const col = right.column ?? (result.columns[0] ?? "");
5166
- const resolved = new Set(result.rows.map((r) => r[col] ?? ""));
5167
- right.resolved = resolved;
5347
+ tasks.push(executeSelect(right.query, client, options, cacheContext).then((result) => {
5348
+ const col = right.column ?? (result.columns[0] ?? "");
5349
+ right.resolved = new Set(result.rows.map((r) => r[col] ?? ""));
5350
+ }));
5168
5351
  }
5169
5352
  if (right.type === "SCALAR_SUBQUERY") {
5170
- const result = await executeSelect(right.query, client, options, cacheContext);
5171
- if (result.rowCount === 0) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u5024\u3092\u8FD4\u3057\u307E\u305B\u3093\u3067\u3057\u305F");
5172
- if (result.rowCount > 1) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u8907\u6570\u884C\u3092\u8FD4\u3057\u307E\u3057\u305F\uFF081\u884C\u306E\u307F\u8A31\u53EF\uFF09");
5173
- const col = result.columns[0] ?? "";
5174
- right.resolved = result.rows[0]?.[col] ?? "";
5353
+ tasks.push(executeSelect(right.query, client, options, cacheContext).then((result) => {
5354
+ if (result.rowCount === 0) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u5024\u3092\u8FD4\u3057\u307E\u305B\u3093\u3067\u3057\u305F");
5355
+ if (result.rowCount > 1) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u8907\u6570\u884C\u3092\u8FD4\u3057\u307E\u3057\u305F\uFF081\u884C\u306E\u307F\u8A31\u53EF\uFF09");
5356
+ const col = result.columns[0] ?? "";
5357
+ right.resolved = result.rows[0]?.[col] ?? "";
5358
+ }));
5175
5359
  }
5176
5360
  break;
5177
5361
  }
5178
5362
  case "LOGICAL":
5179
- await resolveSubqueries(where.left, client, options, cacheContext);
5180
- await resolveSubqueries(where.right, client, options, cacheContext);
5363
+ collectSubqueryTasks(where.left, client, options, cacheContext, tasks);
5364
+ collectSubqueryTasks(where.right, client, options, cacheContext, tasks);
5181
5365
  break;
5182
5366
  case "NOT":
5183
5367
  case "GROUP":
5184
- await resolveSubqueries(where.expr, client, options, cacheContext);
5368
+ collectSubqueryTasks(where.expr, client, options, cacheContext, tasks);
5185
5369
  break;
5186
5370
  case "EXISTS": {
5187
- const result = await executeSelect(where.query, client, options, cacheContext);
5188
- where.resolved = result.rowCount > 0;
5371
+ const node = where;
5372
+ tasks.push(executeSelect(node.query, client, options, cacheContext).then((result) => {
5373
+ node.resolved = result.rowCount > 0;
5374
+ }));
5189
5375
  break;
5190
5376
  }
5191
5377
  }
@@ -5202,16 +5388,27 @@ async function resolveSetSubqueries(assignments, client, options, cacheContext)
5202
5388
  }
5203
5389
  }
5204
5390
  async function resolveScalarColumns(columns, client, options, cacheContext) {
5205
- const cache = /* @__PURE__ */ new Map();
5391
+ const byQuery = /* @__PURE__ */ new Map();
5392
+ const pending = [];
5206
5393
  for (let i = 0; i < columns.length; i++) {
5207
5394
  const col = columns[i];
5208
5395
  if (col.type !== "SCALAR_SUBQUERY_COL") continue;
5209
- const result = await executeSelect(col.query, client, options, cacheContext);
5210
- if (result.rowCount === 0) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u5024\u3092\u8FD4\u3057\u307E\u305B\u3093\u3067\u3057\u305F");
5211
- if (result.rowCount > 1) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u8907\u6570\u884C\u3092\u8FD4\u3057\u307E\u3057\u305F\uFF081\u884C\u306E\u307F\u8A31\u53EF\uFF09");
5212
- const firstCol = result.columns[0] ?? "";
5213
- cache.set(i, result.rows[0]?.[firstCol] ?? "");
5396
+ const key = JSON.stringify(col.query);
5397
+ let promise = byQuery.get(key);
5398
+ if (!promise) {
5399
+ promise = executeSelect(col.query, client, options, cacheContext).then((result) => {
5400
+ if (result.rowCount === 0) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u5024\u3092\u8FD4\u3057\u307E\u305B\u3093\u3067\u3057\u305F");
5401
+ if (result.rowCount > 1) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u8907\u6570\u884C\u3092\u8FD4\u3057\u307E\u3057\u305F\uFF081\u884C\u306E\u307F\u8A31\u53EF\uFF09");
5402
+ const firstCol = result.columns[0] ?? "";
5403
+ return result.rows[0]?.[firstCol] ?? "";
5404
+ });
5405
+ byQuery.set(key, promise);
5406
+ }
5407
+ pending.push([i, promise]);
5214
5408
  }
5409
+ const values = await Promise.all(pending.map(([, promise]) => promise));
5410
+ const cache = /* @__PURE__ */ new Map();
5411
+ pending.forEach(([i], idx) => cache.set(i, values[idx]));
5215
5412
  return cache;
5216
5413
  }
5217
5414
  function executeExplain(stmt) {
@@ -6070,6 +6267,7 @@ Options:
6070
6267
  --dry-run Parse and show execution plan only
6071
6268
  --format <type> Output format: table | json | jsonl | csv | markdown | md
6072
6269
  --max-records <n> Max records to fetch (default: 500)
6270
+ --fetch-parallel <n> Parallel page fetches per query: 1-10 (default: 3)
6073
6271
  --on-limit <mode> On record limit: error | truncate
6074
6272
  --timeout <ms> Request timeout in milliseconds (default: 30000)
6075
6273
  --config <path> Config file path (default: ./ksql.config.json)
@@ -6115,6 +6313,7 @@ function parseArgs(argv) {
6115
6313
  dryRun: false,
6116
6314
  format: null,
6117
6315
  maxRecords: null,
6316
+ fetchParallel: null,
6118
6317
  onLimit: null,
6119
6318
  timeout: null,
6120
6319
  configPath: null,
@@ -6329,6 +6528,13 @@ function parseArgs(argv) {
6329
6528
  i++;
6330
6529
  continue;
6331
6530
  }
6531
+ if (a === "--fetch-parallel") {
6532
+ const n = Number(v);
6533
+ if (!Number.isInteger(n) || n < 1 || n > 10) throw new Error("ArgumentError: --fetch-parallel must be an integer between 1 and 10.");
6534
+ out.fetchParallel = n;
6535
+ i++;
6536
+ continue;
6537
+ }
6332
6538
  if (a === "--timeout") {
6333
6539
  const n = Number(v);
6334
6540
  if (!Number.isInteger(n) || n <= 0) throw new Error("ArgumentError: --timeout must be a positive integer.");
@@ -6680,6 +6886,7 @@ function buildReplExecArgv(base, sql, dryRun, format) {
6680
6886
  pushOpt(argv, "--token-file", base.tokenFile);
6681
6887
  pushOpt(argv, "--app", base.app);
6682
6888
  pushOpt(argv, "--max-records", base.maxRecords);
6889
+ pushOpt(argv, "--fetch-parallel", base.fetchParallel);
6683
6890
  pushOpt(argv, "--on-limit", base.onLimit);
6684
6891
  pushOpt(argv, "--timeout", base.timeout);
6685
6892
  pushOpt(argv, "--output", base.outputPath);
@@ -7159,8 +7366,13 @@ async function run() {
7159
7366
  }
7160
7367
  }
7161
7368
  const maxRecords = args.maxRecords ?? envInt("KSQL_MAX_RECORDS") ?? profile.query?.maxRecords ?? 500;
7369
+ const fetchParallel = args.fetchParallel ?? envInt("KSQL_FETCH_PARALLEL") ?? profile.query?.fetchParallel ?? 3;
7162
7370
  const onLimit = args.onLimit ?? envOnLimit("KSQL_ON_LIMIT") ?? profile.query?.onLimit ?? "error";
7163
7371
  const timeout = args.timeout ?? envInt("KSQL_TIMEOUT") ?? profile.query?.timeout ?? 3e4;
7372
+ if (!Number.isInteger(fetchParallel) || fetchParallel < 1 || fetchParallel > 10) {
7373
+ process.stderr.write("ArgumentError: fetch-parallel must be an integer between 1 and 10.\n");
7374
+ return 2;
7375
+ }
7164
7376
  const rawFormat = args.format ?? envFormat("KSQL_FORMAT") ?? profile.output?.format ?? "table";
7165
7377
  const format = normalizeOutputFormat(rawFormat);
7166
7378
  if (!format) {
@@ -7475,6 +7687,7 @@ query=${label}`);
7475
7687
  };
7476
7688
  const result = args.dryRun ? await execute(`EXPLAIN ${sql}`, client, { maxRecords, onLimitReached: onLimit, cacheContext }) : await execute(sql, client, {
7477
7689
  maxRecords,
7690
+ fetchParallel,
7478
7691
  onLimitReached: onLimit,
7479
7692
  confirm: isDmlStatement ? confirm : void 0,
7480
7693
  cacheContext