@rex0220/kintone-sql-tools 2.10.1 → 2.12.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
@@ -2054,6 +2054,26 @@ var Parser = class {
2054
2054
  const { appId, subtableCode } = extractTableRef(name, this.prev());
2055
2055
  this.expect("SET" /* SET */);
2056
2056
  const assignments = this.parseAssignments();
2057
+ let from = null;
2058
+ if (this.consume("FROM" /* FROM */)) {
2059
+ const table = this.parseTableRef();
2060
+ if (table.subtableCode) {
2061
+ throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306B\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u306F\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093", this.prev());
2062
+ }
2063
+ if (table.cteName !== null && !table.cteName.startsWith("#")) {
2064
+ throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306F #temp \u307E\u305F\u306F APP<n> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08CTE \u306F\u975E\u5BFE\u5FDC\uFF09", this.prev());
2065
+ }
2066
+ if (!table.alias) {
2067
+ throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306B\u306F\u30A8\u30A4\u30EA\u30A2\u30B9\u304C\u5FC5\u8981\u3067\u3059", this.prev());
2068
+ }
2069
+ from = {
2070
+ appId: table.appId,
2071
+ cteName: table.cteName,
2072
+ alias: table.alias,
2073
+ joinKeyField: "",
2074
+ targetFilter: null
2075
+ };
2076
+ }
2057
2077
  const whereTok = this.peek();
2058
2078
  if (!this.consume("WHERE" /* WHERE */)) {
2059
2079
  throw new ParseError(
@@ -2062,8 +2082,129 @@ var Parser = class {
2062
2082
  );
2063
2083
  }
2064
2084
  const where = this.parseWhereExpr();
2085
+ if (from !== null) {
2086
+ if (subtableCode) {
2087
+ throw new ParseError("\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE ... FROM \u306F\u30B5\u30DD\u30FC\u30C8\u3057\u3066\u3044\u307E\u305B\u3093", whereTok);
2088
+ }
2089
+ this.validateUpdateFromAssignments(assignments, from.alias, whereTok);
2090
+ const decomposed = this.decomposeUpdateFromWhere(where, appId, from.alias, whereTok);
2091
+ from.joinKeyField = decomposed.joinKeyField;
2092
+ from.targetFilter = decomposed.targetFilter;
2093
+ } else if (assignments.some((a) => a.value.type === "SOURCE_FIELD")) {
2094
+ throw new ParseError(
2095
+ "SET \u306E\u5024\u306B\u306F\u30EA\u30C6\u30E9\u30EB\u30FB\u7B97\u8853\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306E\u307F\u306F\u4E0D\u53EF\uFF09",
2096
+ whereTok
2097
+ );
2098
+ }
2099
+ if (from !== null) return { type: "UPDATE", appId, assignments, where, from };
2065
2100
  return subtableCode ? { type: "UPDATE", appId, subtableCode, assignments, where } : { type: "UPDATE", appId, assignments, where };
2066
2101
  }
2102
+ validateUpdateFromAssignments(assignments, sourceAlias, tok) {
2103
+ for (const assignment of assignments) {
2104
+ if (assignment.value.type === "SOURCE_FIELD") {
2105
+ if (assignment.value.alias.toLowerCase() !== sourceAlias.toLowerCase()) {
2106
+ throw new ParseError(`UPDATE ... FROM \u306E SET \u53C2\u7167\u306F\u30BD\u30FC\u30B9 alias ${sourceAlias} \u3067\u4FEE\u98FE\u3057\u3066\u304F\u3060\u3055\u3044`, tok);
2107
+ }
2108
+ continue;
2109
+ }
2110
+ if (this.nodeContainsQualifiedField(assignment.value, sourceAlias)) {
2111
+ throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u5217\u306F SET \u306E\u76F4\u63A5\u5024\u3068\u3057\u3066\u306E\u307F\u53C2\u7167\u3067\u304D\u307E\u3059", tok);
2112
+ }
2113
+ if (assignment.value.type === "SCALAR_SUBQUERY") {
2114
+ throw new ParseError("UPDATE ... FROM \u306E SET \u3067\u306F\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
2115
+ }
2116
+ if (this.nodeContainsAnyQualifier(assignment.value)) {
2117
+ throw new ParseError("UPDATE ... FROM \u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u5F0F\u3067\u306F\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u4FEE\u98FE\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044", tok);
2118
+ }
2119
+ }
2120
+ }
2121
+ decomposeUpdateFromWhere(where, targetAppId, sourceAlias, tok) {
2122
+ const leaves = this.flattenTopLevelAnd(where);
2123
+ const joins = [];
2124
+ leaves.forEach((leaf, index) => {
2125
+ const sourceField = this.matchUpdateFromJoin(leaf, targetAppId, sourceAlias);
2126
+ if (sourceField !== null) joins.push({ index, sourceField });
2127
+ });
2128
+ if (joins.length !== 1) {
2129
+ throw new ParseError("UPDATE ... FROM \u306E WHERE \u306B\u306F target.$id = source.key \u306E\u7D50\u5408\u7B49\u5024\u304C\u3061\u3087\u3046\u30691\u3064\u5FC5\u8981\u3067\u3059", tok);
2130
+ }
2131
+ const join2 = joins[0];
2132
+ for (let i = 0; i < leaves.length; i++) {
2133
+ if (i !== join2.index && this.nodeContainsQualifiedField(leaves[i], sourceAlias)) {
2134
+ throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9 alias \u306F\u7D50\u5408\u7B49\u5024\u4EE5\u5916\u306E WHERE \u6761\u4EF6\u3067\u306F\u53C2\u7167\u3067\u304D\u307E\u305B\u3093", tok);
2135
+ }
2136
+ if (i !== join2.index && this.nodeContainsForeignQualifier(leaves[i], targetAppId)) {
2137
+ throw new ParseError(`UPDATE ... FROM \u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u30D5\u30A3\u30EB\u30BF\u306F APP${targetAppId} \u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u3060\u3051\u3092\u53C2\u7167\u3067\u304D\u307E\u3059`, tok);
2138
+ }
2139
+ }
2140
+ const filters = leaves.filter((_, index) => index !== join2.index);
2141
+ const targetFilter = filters.reduce(
2142
+ (acc, expr) => acc === null ? expr : { type: "LOGICAL", op: "AND", left: acc, right: expr },
2143
+ null
2144
+ );
2145
+ return { joinKeyField: join2.sourceField, targetFilter };
2146
+ }
2147
+ flattenTopLevelAnd(expr) {
2148
+ if (expr.type === "GROUP") return this.flattenTopLevelAnd(expr.expr);
2149
+ if (expr.type === "LOGICAL" && expr.op === "AND") {
2150
+ return [...this.flattenTopLevelAnd(expr.left), ...this.flattenTopLevelAnd(expr.right)];
2151
+ }
2152
+ return [expr];
2153
+ }
2154
+ matchUpdateFromJoin(expr, targetAppId, sourceAlias) {
2155
+ if (expr.type !== "BINARY" || expr.op !== "=" || expr.left.type !== "FIELD") return null;
2156
+ const right = expr.right.type === "ARITH_VALUE" && expr.right.expr.type === "FIELD_REF" ? this.splitQualifiedField(expr.right.expr.field) : null;
2157
+ if (right === null) return null;
2158
+ const left = { alias: expr.left.tableAlias, field: expr.left.field };
2159
+ if (this.isTargetIdRef(left, targetAppId) && this.isSourceRef(right, sourceAlias)) return right.field;
2160
+ if (this.isSourceRef(left, sourceAlias) && this.isTargetIdRef(right, targetAppId)) return left.field;
2161
+ return null;
2162
+ }
2163
+ splitQualifiedField(field) {
2164
+ const dot = field.indexOf(".");
2165
+ return dot < 0 ? { alias: null, field } : { alias: field.slice(0, dot), field: field.slice(dot + 1) };
2166
+ }
2167
+ isTargetIdRef(ref, appId) {
2168
+ return ref.field === "$id" && (ref.alias === null || ref.alias.toLowerCase() === `app${appId}`.toLowerCase());
2169
+ }
2170
+ isSourceRef(ref, alias) {
2171
+ return ref.alias?.toLowerCase() === alias.toLowerCase();
2172
+ }
2173
+ nodeContainsQualifiedField(node, alias) {
2174
+ if (Array.isArray(node)) return node.some((v) => this.nodeContainsQualifiedField(v, alias));
2175
+ if (node === null || typeof node !== "object") return false;
2176
+ const obj = node;
2177
+ if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string" && obj["tableAlias"].toLowerCase() === alias.toLowerCase()) return true;
2178
+ if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
2179
+ const ref = this.splitQualifiedField(obj["field"]);
2180
+ if (this.isSourceRef(ref, alias)) return true;
2181
+ }
2182
+ return Object.values(obj).some((v) => this.nodeContainsQualifiedField(v, alias));
2183
+ }
2184
+ nodeContainsForeignQualifier(node, targetAppId) {
2185
+ if (Array.isArray(node)) return node.some((v) => this.nodeContainsForeignQualifier(v, targetAppId));
2186
+ if (node === null || typeof node !== "object") return false;
2187
+ const obj = node;
2188
+ const expected = `app${targetAppId}`.toLowerCase();
2189
+ if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string") {
2190
+ return obj["tableAlias"].toLowerCase() !== expected;
2191
+ }
2192
+ if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
2193
+ const ref = this.splitQualifiedField(obj["field"]);
2194
+ if (ref.alias !== null) return ref.alias.toLowerCase() !== expected;
2195
+ }
2196
+ return Object.values(obj).some((v) => this.nodeContainsForeignQualifier(v, targetAppId));
2197
+ }
2198
+ nodeContainsAnyQualifier(node) {
2199
+ if (Array.isArray(node)) return node.some((v) => this.nodeContainsAnyQualifier(v));
2200
+ if (node === null || typeof node !== "object") return false;
2201
+ const obj = node;
2202
+ if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string") return true;
2203
+ if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
2204
+ if (this.splitQualifiedField(obj["field"]).alias !== null) return true;
2205
+ }
2206
+ return Object.values(obj).some((v) => this.nodeContainsAnyQualifier(v));
2207
+ }
2067
2208
  parseAssignments() {
2068
2209
  const assignments = [];
2069
2210
  do {
@@ -2098,6 +2239,12 @@ var Parser = class {
2098
2239
  const node = this.parseArithAddSub();
2099
2240
  if (node.type === "NUMBER") return node;
2100
2241
  if (node.type === "ARITH") return node;
2242
+ if (node.type === "FIELD_REF") {
2243
+ const dot = node.field.indexOf(".");
2244
+ if (dot > 0 && dot < node.field.length - 1) {
2245
+ return { type: "SOURCE_FIELD", alias: node.field.slice(0, dot), field: node.field.slice(dot + 1) };
2246
+ }
2247
+ }
2101
2248
  throw new ParseError(
2102
2249
  "SET \u306E\u5024\u306B\u306F\u30EA\u30C6\u30E9\u30EB\u30FB\u7B97\u8853\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306E\u307F\u306F\u4E0D\u53EF\uFF09",
2103
2250
  tok
@@ -3685,7 +3832,8 @@ function analyzeBatch(statements) {
3685
3832
  tempTablesDropped: dropped,
3686
3833
  dependsOn: [...dependsOn].sort((a, b) => a - b),
3687
3834
  tempOnlySource,
3688
- targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null
3835
+ targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null,
3836
+ isUpdateFrom: stmt.type === "UPDATE" && stmt.from != null
3689
3837
  });
3690
3838
  });
3691
3839
  const containsDml = results.some((r) => r.isDml);
@@ -4230,7 +4378,7 @@ function updateToPutBatches(stmt, ids, fieldTypes = /* @__PURE__ */ new Map()) {
4230
4378
  function buildUpdateRecord(assignments, fieldTypes) {
4231
4379
  const record = {};
4232
4380
  for (const { field, value } of assignments) {
4233
- if (value.type === "ARITH" || value.type === "CASE_VALUE") continue;
4381
+ if (value.type === "ARITH" || value.type === "CASE_VALUE" || value.type === "SOURCE_FIELD") continue;
4234
4382
  record[field] = { value: toKintoneValue(value, fieldTypes.get(field)) };
4235
4383
  }
4236
4384
  return record;
@@ -4334,6 +4482,8 @@ function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new
4334
4482
  record[field] = { value: String(evalArith(value, raw)) };
4335
4483
  } else if (value.type === "CASE_VALUE") {
4336
4484
  record[field] = { value: evalCaseWhenValue(value.expr, row, fieldTypes.get(field)) };
4485
+ } else if (value.type === "SOURCE_FIELD") {
4486
+ throw new DmlConvertError("SOURCE_FIELD \u306F UPDATE ... FROM \u5C02\u7528\u3067\u3059");
4337
4487
  } else {
4338
4488
  record[field] = { value: toKintoneValue(value, fieldTypes.get(field)) };
4339
4489
  }
@@ -4345,6 +4495,51 @@ function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new
4345
4495
  records: batch
4346
4496
  }));
4347
4497
  }
4498
+ var UPDATE_FROM_UNSUPPORTED_TYPES = /* @__PURE__ */ new Set([
4499
+ "CHECK_BOX",
4500
+ "MULTI_SELECT",
4501
+ "USER_SELECT",
4502
+ "ORGANIZATION_SELECT",
4503
+ "GROUP_SELECT",
4504
+ "FILE"
4505
+ ]);
4506
+ function updateFromToPutBatches(stmt, matched, fieldTypes = /* @__PURE__ */ new Map()) {
4507
+ const updateRecords = matched.map(({ target, source }) => {
4508
+ const id = Number(target["$id"]?.value);
4509
+ if (!Number.isSafeInteger(id) || id <= 0) {
4510
+ throw new DmlConvertError("UPDATE ... FROM \u306E\u5BFE\u8C61\u30EC\u30B3\u30FC\u30C9\u756A\u53F7\u304C\u4E0D\u6B63\u3067\u3059");
4511
+ }
4512
+ const targetRow = kintoneRecordToProcessRow(target);
4513
+ const record = {};
4514
+ for (const { field, value } of stmt.assignments) {
4515
+ const fieldType = fieldTypes.get(field);
4516
+ if (value.type === "SOURCE_FIELD") {
4517
+ if (UPDATE_FROM_UNSUPPORTED_TYPES.has(fieldType ?? "")) {
4518
+ throw new DmlConvertError(`UPDATE ... FROM \u306E SOURCE_FIELD \u306F ${fieldType} \u30D5\u30A3\u30FC\u30EB\u30C9\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9: ${field}\uFF09`);
4519
+ }
4520
+ if (!Object.prototype.hasOwnProperty.call(source, value.field)) {
4521
+ throw new DmlConvertError(`UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u5217 ${value.field} \u304C\u5B58\u5728\u3057\u307E\u305B\u3093`);
4522
+ }
4523
+ const raw = source[value.field];
4524
+ if (typeof raw !== "string") {
4525
+ throw new DmlConvertError(`UPDATE ... FROM \u306E SOURCE_FIELD \u306F\u30B9\u30AB\u30E9\u30FC\u5024\u306E\u307F\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059\uFF08\u5217: ${value.field}\uFF09`);
4526
+ }
4527
+ if ((fieldType === "NUMBER" || fieldType === "CALC") && raw !== "" && !Number.isFinite(Number(raw))) {
4528
+ throw new DmlConvertError(`\u6570\u5024\u30D5\u30A3\u30FC\u30EB\u30C9 ${field} \u306B\u5909\u63DB\u3067\u304D\u306A\u3044\u5024\u3067\u3059: ${raw}`);
4529
+ }
4530
+ record[field] = { value: toKintoneValue({ type: "STRING", value: raw }, fieldType) };
4531
+ } else if (value.type === "ARITH") {
4532
+ record[field] = { value: String(evalArith(value, target)) };
4533
+ } else if (value.type === "CASE_VALUE") {
4534
+ record[field] = { value: evalCaseWhenValue(value.expr, targetRow, fieldType) };
4535
+ } else {
4536
+ record[field] = { value: toKintoneValue(value, fieldType) };
4537
+ }
4538
+ }
4539
+ return { id, record };
4540
+ });
4541
+ return chunk(updateRecords, 100).map((records) => ({ app: stmt.appId, records }));
4542
+ }
4348
4543
  function kintoneRecordToProcessRow(raw) {
4349
4544
  return Object.fromEntries(
4350
4545
  Object.entries(raw).map(([k, v]) => [
@@ -4514,6 +4709,11 @@ async function fetchAll(fetcher, app, query, fields, options = {}) {
4514
4709
  const parallel = Math.max(1, options.parallel ?? PARALLEL_DEFAULT);
4515
4710
  const maxRecords = options.maxRecords ?? MAX_RECORDS_DEFAULT;
4516
4711
  const onLimit = options.onLimit ?? "error";
4712
+ const stopAfter = options.stopAfter;
4713
+ if (stopAfter !== void 0 && (!Number.isSafeInteger(stopAfter) || stopAfter <= 0 || stopAfter > maxRecords)) {
4714
+ throw new RangeError("stopAfter must be a positive safe integer <= maxRecords");
4715
+ }
4716
+ const fetchCap = stopAfter ?? maxRecords;
4517
4717
  const fetchFields = fields.length > 0 && !fields.includes("$id") ? [...fields, "$id"] : fields;
4518
4718
  const allRecords = [];
4519
4719
  let notified = false;
@@ -4524,6 +4724,9 @@ async function fetchAll(fetcher, app, query, fields, options = {}) {
4524
4724
  const first = await fetchPage(fetcher, app, cursorQuery0, fetchFields, pageSize, windowOffset);
4525
4725
  notifySearchAborted(first, options);
4526
4726
  allRecords.push(...first.records);
4727
+ if (stopAfter !== void 0 && allRecords.length >= stopAfter) {
4728
+ return allRecords.slice(0, stopAfter);
4729
+ }
4527
4730
  if (allRecords.length > maxRecords) {
4528
4731
  if (onLimit === "truncate") {
4529
4732
  if (!notified && options.onTruncate) {
@@ -4541,6 +4744,9 @@ async function fetchAll(fetcher, app, query, fields, options = {}) {
4541
4744
  windowOffset = 0;
4542
4745
  }
4543
4746
  while (true) {
4747
+ if (stopAfter !== void 0 && allRecords.length >= stopAfter) {
4748
+ return allRecords.slice(0, stopAfter);
4749
+ }
4544
4750
  if (allRecords.length >= maxRecords) {
4545
4751
  if (onLimit === "truncate") {
4546
4752
  if (!notified && options.onTruncate) {
@@ -4551,7 +4757,7 @@ async function fetchAll(fetcher, app, query, fields, options = {}) {
4551
4757
  }
4552
4758
  throw new FetchAllLimitError(limitMessage);
4553
4759
  }
4554
- const remaining = maxRecords - allRecords.length;
4760
+ const remaining = fetchCap - allRecords.length;
4555
4761
  const maxPagesByLimit = Math.ceil(remaining / pageSize);
4556
4762
  const batchParallel = Math.max(1, Math.min(parallel, maxPagesByLimit));
4557
4763
  const batchOffsets = [];
@@ -4570,6 +4776,9 @@ async function fetchAll(fetcher, app, query, fields, options = {}) {
4570
4776
  let done = false;
4571
4777
  for (const res of responses) {
4572
4778
  allRecords.push(...res.records);
4779
+ if (stopAfter !== void 0 && allRecords.length >= stopAfter) {
4780
+ return allRecords.slice(0, stopAfter);
4781
+ }
4573
4782
  if (allRecords.length > maxRecords) {
4574
4783
  if (onLimit === "truncate") {
4575
4784
  if (!notified && options.onTruncate) {
@@ -4995,10 +5204,10 @@ function applyLimit(rows, limit, offset) {
4995
5204
  if (limit === null) return rows.slice(start);
4996
5205
  return rows.slice(start, start + limit);
4997
5206
  }
4998
- function project(rows, columns, scalarCache, resolveFieldType) {
5207
+ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns) {
4999
5208
  if (columns.length === 1 && columns[0].type === "WILDCARD") {
5000
5209
  const projected2 = rows.map((row) => stripParentShortcutColumns(row));
5001
- const cols = projected2.length > 0 ? Object.keys(projected2[0]) : [];
5210
+ const cols = projected2.length > 0 ? Object.keys(projected2[0]) : [...sourceColumns ?? []];
5002
5211
  return { rows: projected2, columns: cols };
5003
5212
  }
5004
5213
  const defaultFieldKeys = buildDefaultFieldOutputKeys(columns);
@@ -5007,6 +5216,9 @@ function project(rows, columns, scalarCache, resolveFieldType) {
5007
5216
  );
5008
5217
  const outputKeys = hasWildcard ? null : computeOutputKeys(columns, defaultFieldKeys);
5009
5218
  const orderedKeys = outputKeys ?? [];
5219
+ if (hasWildcard && rows.length === 0) {
5220
+ return { rows: [], columns: computeExplicitOutputKeys(columns, defaultFieldKeys) };
5221
+ }
5010
5222
  const projected = rows.map((row, rowIdx) => {
5011
5223
  const out = {};
5012
5224
  for (const [colIdx, col] of columns.entries()) {
@@ -5084,29 +5296,43 @@ function project(rows, columns, scalarCache, resolveFieldType) {
5084
5296
  return { rows: projected, columns: orderedKeys };
5085
5297
  }
5086
5298
  function computeOutputKeys(columns, defaultFieldKeys) {
5087
- return columns.map((col, colIdx) => {
5088
- switch (col.type) {
5089
- case "FIELD":
5090
- return col.alias ?? defaultFieldKeys.get(colIdx) ?? col.field;
5091
- case "LITERAL_COL":
5092
- return col.alias ?? `'${col.value}'`;
5093
- case "AGGREGATE":
5094
- return col.alias ?? aggregateSyntheticName2(col.func, col.distinct, col.arg);
5095
- case "ARITH_AGG_COL":
5096
- return col.alias ?? aggArithDefaultKey(col.expr);
5097
- case "ARITH_COL":
5098
- return col.alias ?? arithColDefaultKey(col.expr);
5099
- case "CASE_COL":
5100
- return col.alias ?? "case";
5101
- case "STRFUNC_COL":
5102
- return col.alias ?? stringFuncDefaultKey(col.expr);
5103
- case "SCALAR_SUBQUERY_COL":
5104
- return col.alias ?? "(subquery)";
5105
- case "WILDCARD":
5106
- case "PARENT_WILDCARD":
5107
- throw new Error("internal: computeOutputKeys received a wildcard column");
5299
+ return columns.map((col, colIdx) => computeOutputKey(col, colIdx, defaultFieldKeys));
5300
+ }
5301
+ function computeExplicitOutputKeys(columns, defaultFieldKeys) {
5302
+ const keys = [];
5303
+ const seen = /* @__PURE__ */ new Set();
5304
+ for (const [colIdx, col] of columns.entries()) {
5305
+ if (col.type === "WILDCARD" || col.type === "PARENT_WILDCARD") continue;
5306
+ const key = computeOutputKey(col, colIdx, defaultFieldKeys);
5307
+ if (!seen.has(key)) {
5308
+ seen.add(key);
5309
+ keys.push(key);
5108
5310
  }
5109
- });
5311
+ }
5312
+ return keys;
5313
+ }
5314
+ function computeOutputKey(col, colIdx, defaultFieldKeys) {
5315
+ switch (col.type) {
5316
+ case "FIELD":
5317
+ return col.alias ?? defaultFieldKeys.get(colIdx) ?? col.field;
5318
+ case "LITERAL_COL":
5319
+ return col.alias ?? `'${col.value}'`;
5320
+ case "AGGREGATE":
5321
+ return col.alias ?? aggregateSyntheticName2(col.func, col.distinct, col.arg);
5322
+ case "ARITH_AGG_COL":
5323
+ return col.alias ?? aggArithDefaultKey(col.expr);
5324
+ case "ARITH_COL":
5325
+ return col.alias ?? arithColDefaultKey(col.expr);
5326
+ case "CASE_COL":
5327
+ return col.alias ?? "case";
5328
+ case "STRFUNC_COL":
5329
+ return col.alias ?? stringFuncDefaultKey(col.expr);
5330
+ case "SCALAR_SUBQUERY_COL":
5331
+ return col.alias ?? "(subquery)";
5332
+ case "WILDCARD":
5333
+ case "PARENT_WILDCARD":
5334
+ throw new Error("internal: computeOutputKey received a wildcard column");
5335
+ }
5110
5336
  }
5111
5337
  function buildDefaultFieldOutputKeys(columns) {
5112
5338
  const qualifierCollisionCount = /* @__PURE__ */ new Map();
@@ -5200,7 +5426,8 @@ function runFullScan(input) {
5200
5426
  sortKinds,
5201
5427
  fieldTypeResolver,
5202
5428
  havingFieldTypeResolver,
5203
- appliedKlikes
5429
+ appliedKlikes,
5430
+ sourceColumns
5204
5431
  } = input;
5205
5432
  let rows = [];
5206
5433
  const mainAlias = stmt.from.alias;
@@ -5222,7 +5449,7 @@ function runFullScan(input) {
5222
5449
  }
5223
5450
  rows = applyOrderBy(rows, stmt.orderBy, optionOrders, sortKinds);
5224
5451
  rows = applyLimit(rows, stmt.limit, stmt.offset);
5225
- return project(rows, stmt.columns, scalarCache, fieldTypeResolver);
5452
+ return project(rows, stmt.columns, scalarCache, fieldTypeResolver, sourceColumns);
5226
5453
  }
5227
5454
 
5228
5455
  // src/converter/subtableAdapter.ts
@@ -5427,6 +5654,8 @@ async function executeBatch(sql, client, options = {}) {
5427
5654
  for (const s of analysis.statements) {
5428
5655
  if (!s.isDml || s.tempTablesReferenced.length === 0) continue;
5429
5656
  if (s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT") continue;
5657
+ const parsed = statements[s.index];
5658
+ if (parsed?.type === "UPDATE" && parsed.from?.cteName != null) continue;
5430
5659
  throw new BatchAnalysisError(
5431
5660
  `ArgumentError: temp table references in ${s.statementType} are not supported yet.`,
5432
5661
  s.index
@@ -5556,7 +5785,7 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
5556
5785
  onLimitReached: "error"
5557
5786
  };
5558
5787
  const result = await runSelectLike(resolvedStmt.query, client, materializeOptions, cacheContext, tempTables);
5559
- tempTables.set(resolvedStmt.name, result.rows);
5788
+ tempTables.set(resolvedStmt.name, { rows: result.rows, columns: result.columns });
5560
5789
  return { tempTable: resolvedStmt.name, rowCount: result.rows.length };
5561
5790
  }
5562
5791
  if (stmt.type === "DROP_TEMP_TABLE") {
@@ -5583,6 +5812,9 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
5583
5812
  if (resolvedStmt.type === "UPSERT_SELECT") {
5584
5813
  return { result: await executeUpsertSelect(resolvedStmt, client, options, cacheContext, tempTables) };
5585
5814
  }
5815
+ if (resolvedStmt.type === "UPDATE" && resolvedStmt.from?.cteName != null) {
5816
+ return { result: await executeUpdate(resolvedStmt, client, options, cacheContext, tempTables) };
5817
+ }
5586
5818
  throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
5587
5819
  }
5588
5820
  return { result: await executeParsedStatement(resolvedStmt, client, options, cacheContext) };
@@ -5874,6 +6106,8 @@ async function executeSimpleSelect(stmt, client, options, cacheContext) {
5874
6106
  const onLimit = options.onLimitReached ?? "error";
5875
6107
  const parallel = options.fetchParallel ?? 1;
5876
6108
  const useSingleGet = stmt.limit !== null && stmt.limit <= 500;
6109
+ const needed = stmt.limit === null ? null : (stmt.offset ?? 0) + stmt.limit;
6110
+ const stopAfter = stmt.orderBy.length === 0 && needed !== null && needed <= maxRecords && !whereHasKlike(stmt.where) ? needed : void 0;
5877
6111
  let records;
5878
6112
  if (useSingleGet) {
5879
6113
  const res = await client.getRecords({
@@ -5892,6 +6126,7 @@ async function executeSimpleSelect(stmt, client, options, cacheContext) {
5892
6126
  {
5893
6127
  parallel,
5894
6128
  maxRecords,
6129
+ stopAfter,
5895
6130
  onLimit,
5896
6131
  onTruncate: (max) => {
5897
6132
  warnings.add(`\u53D6\u5F97\u4E0A\u9650\uFF08${max} \u4EF6\uFF09\u306B\u9054\u3057\u305F\u305F\u3081\u3001${max} \u4EF6\u3067\u6253\u3061\u5207\u3063\u3066\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002`);
@@ -6256,7 +6491,7 @@ async function executeWith(stmt, client, options, cacheContext, seed) {
6256
6491
  } else {
6257
6492
  result = await executeQueryWithCte(cte.query, client, options, cteCache, cacheContext);
6258
6493
  }
6259
- cteCache.set(cte.name, result.rows);
6494
+ cteCache.set(cte.name, { rows: result.rows, columns: result.columns });
6260
6495
  }
6261
6496
  return executeQueryWithCte(stmt.query, client, options, cteCache, cacheContext);
6262
6497
  }
@@ -6309,8 +6544,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
6309
6544
  });
6310
6545
  const tables = /* @__PURE__ */ new Map();
6311
6546
  if (stmt.from.cteName != null) {
6312
- const rows2 = cteCache.get(stmt.from.cteName) ?? [];
6313
- tables.set(stmt.from.alias, rows2.map(processRowToKintoneRecord));
6547
+ const table = cteCache.get(stmt.from.cteName);
6548
+ tables.set(stmt.from.alias, (table?.rows ?? []).map(processRowToKintoneRecord));
6314
6549
  } else {
6315
6550
  const mainRecords = await fetchTableRecordsForFullScan(
6316
6551
  stmt,
@@ -6327,8 +6562,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
6327
6562
  }
6328
6563
  const joinFetches = stmt.joins.map(async (join2) => {
6329
6564
  if (join2.table.cteName != null) {
6330
- const rows2 = cteCache.get(join2.table.cteName) ?? [];
6331
- tables.set(join2.table.alias, rows2.map(processRowToKintoneRecord));
6565
+ const table = cteCache.get(join2.table.cteName);
6566
+ tables.set(join2.table.alias, (table?.rows ?? []).map(processRowToKintoneRecord));
6332
6567
  } else {
6333
6568
  const pushDownCond = join2.table.alias ? pushdownPlan.joinConditions.get(join2.table.alias) ?? null : null;
6334
6569
  const optimized = await tryFetchJoinRecordsBySourceKeys(
@@ -6359,6 +6594,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
6359
6594
  await Promise.all(joinFetches);
6360
6595
  const scalarCache = await scalarCachePromise;
6361
6596
  const { optionOrders, sortKinds } = await orderByMetaPromise;
6597
+ const sourceColumns = stmt.joins.length === 0 && stmt.from.cteName != null ? cteCache.get(stmt.from.cteName)?.columns : void 0;
6362
6598
  const { rows, columns } = runFullScan({
6363
6599
  tables,
6364
6600
  stmt,
@@ -6367,7 +6603,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
6367
6603
  sortKinds,
6368
6604
  fieldTypeResolver: fieldTypeResolvers.row,
6369
6605
  havingFieldTypeResolver: fieldTypeResolvers.having,
6370
- appliedKlikes: pushdownPlan.appliedKlikes
6606
+ appliedKlikes: pushdownPlan.appliedKlikes,
6607
+ sourceColumns
6371
6608
  });
6372
6609
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
6373
6610
  }
@@ -6763,10 +7000,13 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
6763
7000
  insertedCount: createdIds.flat().length
6764
7001
  };
6765
7002
  }
6766
- async function executeUpdate(stmt, client, options, cacheContext) {
7003
+ async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
6767
7004
  if (stmt.subtableCode) {
6768
7005
  return executeUpdateSubtable(stmt, client, options, cacheContext);
6769
7006
  }
7007
+ if (stmt.from != null) {
7008
+ return executeUpdateFrom(stmt, stmt.from, client, options, cacheContext, tempTables);
7009
+ }
6770
7010
  const maxRecords = options.maxRecords ?? 1e4;
6771
7011
  await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
6772
7012
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
@@ -6808,6 +7048,113 @@ async function executeUpdate(stmt, client, options, cacheContext) {
6808
7048
  }
6809
7049
  return { type: "UPDATE", updatedCount: ids.length };
6810
7050
  }
7051
+ var UPDATE_FROM_ID_CHUNK_SIZE = 50;
7052
+ var UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES = /* @__PURE__ */ new Set([
7053
+ "CHECK_BOX",
7054
+ "MULTI_SELECT",
7055
+ "USER_SELECT",
7056
+ "ORGANIZATION_SELECT",
7057
+ "GROUP_SELECT",
7058
+ "FILE"
7059
+ ]);
7060
+ async function executeUpdateFrom(stmt, from, client, options, cacheContext, tempTables) {
7061
+ const sourceFields = [...new Set(stmt.assignments.filter((a) => a.value.type === "SOURCE_FIELD").map((a) => a.value.type === "SOURCE_FIELD" ? a.value.field : ""))];
7062
+ const requiredSourceFields = [.../* @__PURE__ */ new Set([from.joinKeyField, ...sourceFields])];
7063
+ let sourceRows;
7064
+ if (from.cteName !== null) {
7065
+ const table = tempTables?.get(from.cteName);
7066
+ if (!table) throw new Error(`ArgumentError: temp table ${from.cteName} is not available.`);
7067
+ for (const field of requiredSourceFields) {
7068
+ if (!table.columns.includes(field)) {
7069
+ throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
7070
+ }
7071
+ }
7072
+ sourceRows = table.rows;
7073
+ } else {
7074
+ const sourceTypes = await getFieldTypeMap(from.appId, client, cacheContext);
7075
+ for (const field of requiredSourceFields) {
7076
+ if (field !== "$id" && !sourceTypes.has(field)) {
7077
+ throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
7078
+ }
7079
+ const type = sourceTypes.get(field);
7080
+ if (UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES.has(type ?? "")) {
7081
+ throw new Error(`ArgumentError: UPDATE ... FROM does not support source field type ${type} (${field}).`);
7082
+ }
7083
+ }
7084
+ const maxRecords = options.maxRecords ?? 1e4;
7085
+ const resolved = await fetchRecordsForSharedPlan(
7086
+ client.getRecords,
7087
+ from.appId,
7088
+ "",
7089
+ requiredSourceFields,
7090
+ { maxRecords, parallel: options.fetchParallel ?? 1, onLimit: "error" }
7091
+ );
7092
+ sourceRows = resolved.records.map((record) => flatten(record, null));
7093
+ }
7094
+ const sourceById = /* @__PURE__ */ new Map();
7095
+ for (const row of sourceRows) {
7096
+ if (!Object.prototype.hasOwnProperty.call(row, from.joinKeyField)) {
7097
+ throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
7098
+ }
7099
+ const raw = row[from.joinKeyField];
7100
+ const text = typeof raw === "string" ? raw.trim() : "";
7101
+ const id = Number(text);
7102
+ if (text === "" || !Number.isSafeInteger(id) || id <= 0) {
7103
+ throw new Error(`ArgumentError: UPDATE ... FROM source key must be a positive safe integer: ${String(raw)}`);
7104
+ }
7105
+ if (sourceById.has(id)) {
7106
+ throw new Error(`ArgumentError: UPDATE ... FROM source has multiple rows for target $id ${id}.`);
7107
+ }
7108
+ sourceById.set(id, row);
7109
+ }
7110
+ const targetIds = [...sourceById.keys()];
7111
+ const targetFields = collectUpdateFromTargetFields(stmt);
7112
+ const filterQuery = from.targetFilter === null ? "" : updateToGetQuery({ ...stmt, from: null, where: from.targetFilter }).query;
7113
+ const targetRecords = [];
7114
+ for (const ids of splitChunks(targetIds, UPDATE_FROM_ID_CHUNK_SIZE)) {
7115
+ const idQuery = `$id in (${ids.map((id) => sqlQuote(String(id))).join(",")})`;
7116
+ const query = filterQuery ? `(${idQuery}) and (${filterQuery})` : idQuery;
7117
+ const resolved = await fetchRecordsForSharedPlan(
7118
+ client.getRecords,
7119
+ stmt.appId,
7120
+ query,
7121
+ targetFields,
7122
+ { maxRecords: Math.max(ids.length, 1), parallel: options.fetchParallel ?? 1, onLimit: "error" }
7123
+ );
7124
+ targetRecords.push(...resolved.records);
7125
+ }
7126
+ if (options.confirm) {
7127
+ const ok = await options.confirm(targetRecords.length, "UPDATE");
7128
+ if (!ok) throw new OperationCancelledError("UPDATE", targetRecords.length);
7129
+ }
7130
+ const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
7131
+ const matched = targetRecords.map((target) => {
7132
+ const id = Number(target["$id"]?.value);
7133
+ const source = sourceById.get(id);
7134
+ if (!source) throw new Error(`ArgumentError: UPDATE ... FROM could not resolve source row for target $id ${id}.`);
7135
+ return { target, source };
7136
+ });
7137
+ const batches = updateFromToPutBatches(stmt, matched, fieldTypes);
7138
+ for (const batch of batches) await client.putRecords(batch);
7139
+ return { type: "UPDATE", updatedCount: targetRecords.length };
7140
+ }
7141
+ function collectUpdateFromTargetFields(stmt) {
7142
+ const fields = /* @__PURE__ */ new Set(["$id"]);
7143
+ const visit = (node) => {
7144
+ if (Array.isArray(node)) {
7145
+ node.forEach(visit);
7146
+ return;
7147
+ }
7148
+ if (node === null || typeof node !== "object") return;
7149
+ const obj = node;
7150
+ if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") fields.add(obj["field"]);
7151
+ for (const value of Object.values(obj)) visit(value);
7152
+ };
7153
+ for (const assignment of stmt.assignments) {
7154
+ if (assignment.value.type !== "SOURCE_FIELD") visit(assignment.value);
7155
+ }
7156
+ return [...fields];
7157
+ }
6811
7158
  async function executeDelete(stmt, client, options, cacheContext) {
6812
7159
  if (stmt.subtableCode) {
6813
7160
  return executeDeleteSubtable(stmt, client, options, cacheContext);
@@ -7733,9 +8080,16 @@ function buildUpdatePlan(stmt, label) {
7733
8080
  const isSubq = stmt.assignments.some((a) => a.value.type === "SCALAR_SUBQUERY");
7734
8081
  const lines = [];
7735
8082
  if (label) lines.push(label);
7736
- lines.push(` [UPDATE]`);
8083
+ lines.push(stmt.from ? ` [UPDATE FROM]` : ` [UPDATE]`);
7737
8084
  lines.push(` target: APP${stmt.appId} (${stmt.appId})`);
7738
- lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
8085
+ if (stmt.from) {
8086
+ const source = stmt.from.cteName ?? `APP${stmt.from.appId}`;
8087
+ lines.push(` source: ${source} AS ${stmt.from.alias}`);
8088
+ lines.push(` join: APP${stmt.appId}.$id = ${stmt.from.alias}.${stmt.from.joinKeyField}`);
8089
+ lines.push(` target filter: ${stmt.from.targetFilter ? safeWhereToKintone(stmt.from.targetFilter) : "(none)"}`);
8090
+ } else {
8091
+ lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
8092
+ }
7739
8093
  lines.push(` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
7740
8094
  const setTypes = [];
7741
8095
  if (isArith) setTypes.push("\u7B97\u8853 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A08\u7B97\uFF09");
@@ -7848,6 +8202,7 @@ function formatAssignment(a) {
7848
8202
  if (v.type === "ARITH") return `${a.field} = ${formatArithExprStr(v)}`;
7849
8203
  if (v.type === "CASE_VALUE") return `${a.field} = CASE WHEN ...`;
7850
8204
  if (v.type === "SCALAR_SUBQUERY") return `${a.field} = (SELECT ...)`;
8205
+ if (v.type === "SOURCE_FIELD") return `${a.field} = ${v.alias}.${v.field}`;
7851
8206
  return `${a.field} = (${v.type})`;
7852
8207
  }
7853
8208
  function formatArithExprStr(expr) {
@@ -10333,6 +10688,11 @@ async function run() {
10333
10688
  const yes = args.yes || envBool("KSQL_YES") === true || Boolean(profile.dml?.yes);
10334
10689
  const allowWithoutWhere = args.allowWithoutWhere || envBool("KSQL_ALLOW_WITHOUT_WHERE") === true || Boolean(profile.dml?.allowWithoutWhere);
10335
10690
  const dmlMaxRows = args.dmlMaxRows ?? envInt2("KSQL_DML_MAX_ROWS") ?? profile.dml?.maxRows ?? 100;
10691
+ const dmlForcesOnLimitError = isDmlStatement || batchContainsDml;
10692
+ const effectiveOnLimit = dmlForcesOnLimitError ? "error" : onLimit;
10693
+ if (dmlForcesOnLimitError && onLimit === "truncate" && !quiet && !args.dryRun) {
10694
+ process.stderr.write("note: onLimit=truncate is ignored for DML (forced to error)\n");
10695
+ }
10336
10696
  if (format === "markdown" && noHeader) {
10337
10697
  process.stderr.write("ArgumentError: --no-header cannot be used with --format markdown|md.\n");
10338
10698
  return 2;
@@ -10684,7 +11044,7 @@ query=${label}`);
10684
11044
  const batchResult = await executeBatch(sql, client, {
10685
11045
  maxRecords,
10686
11046
  fetchParallel,
10687
- onLimitReached: onLimit,
11047
+ onLimitReached: effectiveOnLimit,
10688
11048
  cacheContext,
10689
11049
  continueOnError: args.continueOnError,
10690
11050
  tempTableMaxRows,
@@ -10702,7 +11062,7 @@ query=${label}`);
10702
11062
  let result = args.dryRun ? await execute(`EXPLAIN ${sql}`, client, { maxRecords, onLimitReached: onLimit, cacheContext }) : await execute(sql, client, {
10703
11063
  maxRecords,
10704
11064
  fetchParallel,
10705
- onLimitReached: onLimit,
11065
+ onLimitReached: effectiveOnLimit,
10706
11066
  confirm: isDmlStatement ? confirm : void 0,
10707
11067
  cacheContext
10708
11068
  });