@rex0220/kintone-sql-tools 1.1.1 → 1.2.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
@@ -34,7 +34,7 @@ __export(index_exports, {
34
34
  shouldExitOnEmpty: () => shouldExitOnEmpty
35
35
  });
36
36
  module.exports = __toCommonJS(index_exports);
37
- var import_fs = require("fs");
37
+ var import_fs2 = require("fs");
38
38
  var import_path = require("path");
39
39
  var import_readline = require("readline");
40
40
  var import_os = require("os");
@@ -3299,6 +3299,62 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
3299
3299
  };
3300
3300
  }
3301
3301
 
3302
+ // src/core/optimization/wherePredicatePushdown.ts
3303
+ function extractTableCondition(where, tableAlias) {
3304
+ switch (where.type) {
3305
+ case "BINARY":
3306
+ if (!isSingleTableField(where.left, tableAlias)) return null;
3307
+ if (!isPushDownableRight(where.right)) return null;
3308
+ return where;
3309
+ case "NULL_CHECK":
3310
+ if (!isSingleTableField(where.field, tableAlias)) return null;
3311
+ return where;
3312
+ case "LOGICAL":
3313
+ if (where.op === "AND") {
3314
+ const left = extractTableCondition(where.left, tableAlias);
3315
+ const right = extractTableCondition(where.right, tableAlias);
3316
+ if (left && right) return { ...where, left, right };
3317
+ return left ?? right ?? null;
3318
+ }
3319
+ return referencesOnlyTable(where, tableAlias) ? where : null;
3320
+ case "NOT":
3321
+ case "GROUP":
3322
+ return referencesOnlyTable(where, tableAlias) ? where : null;
3323
+ case "EXISTS":
3324
+ return null;
3325
+ }
3326
+ }
3327
+ function isSingleTableField(field, tableAlias) {
3328
+ if (field.type !== "FIELD") return false;
3329
+ return field.tableAlias === tableAlias;
3330
+ }
3331
+ function isPushDownableRight(value) {
3332
+ switch (value.type) {
3333
+ case "STRING":
3334
+ case "NUMBER":
3335
+ case "KINTONE_FUNC":
3336
+ case "IN_LIST":
3337
+ return true;
3338
+ default:
3339
+ return false;
3340
+ }
3341
+ }
3342
+ function referencesOnlyTable(expr, tableAlias) {
3343
+ switch (expr.type) {
3344
+ case "BINARY":
3345
+ return isSingleTableField(expr.left, tableAlias) && isPushDownableRight(expr.right);
3346
+ case "NULL_CHECK":
3347
+ return isSingleTableField(expr.field, tableAlias);
3348
+ case "LOGICAL":
3349
+ return referencesOnlyTable(expr.left, tableAlias) && referencesOnlyTable(expr.right, tableAlias);
3350
+ case "NOT":
3351
+ case "GROUP":
3352
+ return referencesOnlyTable(expr.expr, tableAlias);
3353
+ case "EXISTS":
3354
+ return false;
3355
+ }
3356
+ }
3357
+
3302
3358
  // src/engine/process.ts
3303
3359
  function flatten(record, alias) {
3304
3360
  const row = {};
@@ -3998,7 +4054,21 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
3998
4054
  const parallel = options.fetchParallel ?? 1;
3999
4055
  await resolveSubqueries(stmt.where, client, options, cacheContext);
4000
4056
  await resolveSubqueries(stmt.having, client, options, cacheContext);
4001
- const mainRecords = await fetchTableRecordsForFullScan(
4057
+ const tableConditions = /* @__PURE__ */ new Map();
4058
+ if (stmt.where !== null) {
4059
+ if (stmt.from.alias) {
4060
+ const cond = extractTableCondition(stmt.where, stmt.from.alias);
4061
+ if (cond) tableConditions.set(stmt.from.alias, cond);
4062
+ }
4063
+ for (const join2 of stmt.joins) {
4064
+ if (join2.table.alias) {
4065
+ const cond = extractTableCondition(stmt.where, join2.table.alias);
4066
+ if (cond) tableConditions.set(join2.table.alias, cond);
4067
+ }
4068
+ }
4069
+ }
4070
+ const mainPushDown = stmt.from.alias ? tableConditions.get(stmt.from.alias) ?? null : null;
4071
+ const mainFetch = fetchTableRecordsForFullScan(
4002
4072
  stmt,
4003
4073
  stmt.from,
4004
4074
  client,
@@ -4006,11 +4076,39 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
4006
4076
  parallel,
4007
4077
  true,
4008
4078
  options.onLimitReached ?? "error",
4009
- warnings
4079
+ warnings,
4080
+ mainPushDown
4010
4081
  );
4082
+ const parallelJoins = [];
4083
+ const onOptJoins = [];
4084
+ for (const join2 of stmt.joins) {
4085
+ const jCond = join2.table.alias ? tableConditions.get(join2.table.alias) ?? null : null;
4086
+ if (jCond !== null) {
4087
+ parallelJoins.push({
4088
+ join: join2,
4089
+ promise: fetchTableRecordsForFullScan(
4090
+ stmt,
4091
+ join2.table,
4092
+ client,
4093
+ maxRecords,
4094
+ parallel,
4095
+ false,
4096
+ options.onLimitReached ?? "error",
4097
+ warnings,
4098
+ jCond
4099
+ )
4100
+ });
4101
+ } else {
4102
+ onOptJoins.push(join2);
4103
+ }
4104
+ }
4105
+ const mainRecords = await mainFetch;
4011
4106
  const tables = /* @__PURE__ */ new Map();
4012
4107
  tables.set(stmt.from.alias, mainRecords);
4013
- const joinFetches = stmt.joins.map(async (join2) => {
4108
+ for (const { join: join2, promise } of parallelJoins) {
4109
+ tables.set(join2.table.alias, await promise);
4110
+ }
4111
+ await Promise.all(onOptJoins.map(async (join2) => {
4014
4112
  const optimized = await tryFetchJoinRecordsBySourceKeys(
4015
4113
  stmt,
4016
4114
  join2,
@@ -4019,7 +4117,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
4019
4117
  maxRecords,
4020
4118
  parallel,
4021
4119
  options.onLimitReached ?? "error",
4022
- warnings
4120
+ warnings,
4121
+ null
4023
4122
  );
4024
4123
  const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
4025
4124
  stmt,
@@ -4029,11 +4128,11 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
4029
4128
  parallel,
4030
4129
  false,
4031
4130
  options.onLimitReached ?? "error",
4032
- warnings
4131
+ warnings,
4132
+ null
4033
4133
  );
4034
4134
  tables.set(join2.table.alias, joinRecords);
4035
- });
4036
- await Promise.all(joinFetches);
4135
+ }));
4037
4136
  const scalarCache = await resolveScalarColumns(stmt.columns, client, options, cacheContext);
4038
4137
  const optionOrders = await buildOptionOrdersForSelect(stmt, client, cacheContext);
4039
4138
  const sortKinds = await buildSortKindsForSelect(stmt, client, cacheContext);
@@ -4246,13 +4345,15 @@ function processRowToKintoneRecord(row) {
4246
4345
  Object.entries(row).map(([k, v]) => [k, { value: v ?? "" }])
4247
4346
  );
4248
4347
  }
4249
- async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings) {
4348
+ async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings, pushDownCond = null) {
4250
4349
  const fields = selectToFetchAllFields(stmt, table);
4251
4350
  const onTruncate = (max) => {
4252
4351
  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`);
4253
4352
  };
4254
4353
  if (!table.subtableCode) {
4255
- const query = isMainTable ? selectToFetchAllParams(stmt, table.appId).query : "";
4354
+ const baseQuery = isMainTable ? selectToFetchAllParams(stmt, table.appId).query : "";
4355
+ const pushQuery = pushDownCond !== null ? whereToKintone(pushDownCond) : "";
4356
+ const query = baseQuery && pushQuery ? `(${baseQuery}) and (${pushQuery})` : baseQuery || pushQuery;
4256
4357
  const resolved = await fetchRecordsForSharedPlan(client.getRecords, table.appId, query, fields, {
4257
4358
  parallel,
4258
4359
  maxRecords,
@@ -4287,7 +4388,7 @@ function splitChunks(items, size) {
4287
4388
  var JOIN_IN_CHUNK_SIZE = 50;
4288
4389
  var JOIN_IN_MAX_CHUNKS = 6;
4289
4390
  var JOIN_IN_MAX_KEYS = JOIN_IN_CHUNK_SIZE * JOIN_IN_MAX_CHUNKS;
4290
- async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxRecords, parallel, onLimit, warnings) {
4391
+ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxRecords, parallel, onLimit, warnings, pushDownCond = null) {
4291
4392
  if (join2.type !== "INNER") return null;
4292
4393
  if (!join2.table.alias) return null;
4293
4394
  if (join2.table.subtableCode) return null;
@@ -4320,7 +4421,7 @@ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxR
4320
4421
  if (values.length === 0) return [];
4321
4422
  if (values.length > JOIN_IN_MAX_KEYS) {
4322
4423
  warnings.add(
4323
- `JOIN\u30AD\u30FC\u304C ${values.length} \u4EF6\u306E\u305F\u3081 IN \u6700\u9069\u5316\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u3001JOIN\u5148\u3092\u5168\u4EF6\u53D6\u5F97\u3057\u307E\u3059\uFF08\u4E0A\u9650 ${JOIN_IN_MAX_KEYS} \u4EF6\uFF09\u3002`
4424
+ `JOIN\u30AD\u30FC\u304C ${values.length} \u4EF6\u306E\u305F\u3081 ON \u6700\u9069\u5316\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u3001JOIN\u5148\u3092\u5168\u4EF6\u53D6\u5F97\u3057\u307E\u3059\uFF08\u4E0A\u9650 ${JOIN_IN_MAX_KEYS} \u4EF6\uFF09\u3002`
4324
4425
  );
4325
4426
  return null;
4326
4427
  }
@@ -4332,7 +4433,8 @@ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxR
4332
4433
  const merged = [];
4333
4434
  const seen = /* @__PURE__ */ new Set();
4334
4435
  for (const chunk2 of chunks) {
4335
- const query = `${joinField} in (${chunk2.map(sqlQuote).join(",")})`;
4436
+ const inClause = `${joinField} in (${chunk2.map(sqlQuote).join(",")})`;
4437
+ const query = pushDownCond !== null ? `(${inClause}) and (${whereToKintone(pushDownCond)})` : inClause;
4336
4438
  const resolved = await fetchRecordsForSharedPlan(client.getRecords, join2.table.appId, query, fields, {
4337
4439
  parallel,
4338
4440
  maxRecords,
@@ -5148,19 +5250,21 @@ function buildSelectPlan(stmt, label) {
5148
5250
  lines.push(` kintone query: ${params.query || "(\u306A\u3057)"}`);
5149
5251
  lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
5150
5252
  } else {
5151
- const mainParams = selectToFetchAllParams(stmt, stmt.from.appId);
5152
5253
  const mainFields = selectToFetchAllFields(stmt, stmt.from);
5153
- const mainAlias = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
5154
- const mainQ = mainParams.query ? `${mainParams.query} (\u6B8B\u308A\u306F JS \u8A55\u4FA1)` : "(\u5168\u4EF6\u53D6\u5F97)";
5155
- lines.push(` app: APP${stmt.from.appId}${mainAlias} (${stmt.from.appId})`);
5254
+ const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
5255
+ const mainPushDown = stmt.from.alias && stmt.where ? extractTableCondition(stmt.where, stmt.from.alias) : null;
5256
+ const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
5257
+ lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
5156
5258
  lines.push(` kintone query: ${mainQ}`);
5157
5259
  lines.push(` fields: ${mainFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : mainFields.join(", ")}`);
5158
5260
  for (const join2 of stmt.joins) {
5159
5261
  const joinFields = selectToFetchAllFields(stmt, join2.table);
5160
- const joinAlias = join2.table.alias ? ` AS ${join2.table.alias}` : "";
5262
+ const joinAliasStr = join2.table.alias ? ` AS ${join2.table.alias}` : "";
5161
5263
  const joinType = join2.type === "INNER" ? "JOIN" : `${join2.type} JOIN`;
5162
- lines.push(` ${joinType}: APP${join2.table.appId}${joinAlias} (${join2.table.appId})`);
5163
- lines.push(` kintone query: (\u5168\u4EF6\u53D6\u5F97)`);
5264
+ const joinPushDown = join2.table.alias && stmt.where ? extractTableCondition(stmt.where, join2.table.alias) : null;
5265
+ const joinQ = joinPushDown !== null ? whereToKintone(joinPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
5266
+ lines.push(` ${joinType}: APP${join2.table.appId}${joinAliasStr} (${join2.table.appId})`);
5267
+ lines.push(` kintone query: ${joinQ}`);
5164
5268
  lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
5165
5269
  }
5166
5270
  }
@@ -5716,114 +5820,349 @@ function detectSortKind(fieldType, calcFormat) {
5716
5820
  return void 0;
5717
5821
  }
5718
5822
 
5719
- // src/cli/index.ts
5720
- var HELP_TEXT = `ksql - Execute SQL against kintone apps
5721
-
5722
- Usage:
5723
- ksql [options]
5724
- ksql -e "<SQL>"
5725
- ksql -f <file.sql>
5726
-
5727
- Options:
5728
- -e, --execute <sql> Execute SQL string
5729
- -f, --file <path> Execute SQL file
5730
- --console Start interactive console mode
5731
- --dry-run Parse and show execution plan only
5732
- --format <type> Output format: table | json | jsonl | csv | markdown | md
5733
- --max-records <n> Max records to fetch (default: 500)
5734
- --on-limit <mode> On record limit: error | truncate
5735
- --timeout <ms> Request timeout in milliseconds (default: 30000)
5736
- --config <path> Config file path (default: ./ksql.config.json)
5737
- --profile <name> Profile name in config
5738
- --base-url <url> kintone base URL
5739
- --guest-space-id <id> Guest space ID (uses /k/guest/<id>/v1 APIs)
5740
- --auth <type> Auth type: token | userpass | auto
5741
- --username <name> Login username (for userpass auth)
5742
- --password <pass> Login password (for userpass auth)
5743
- --token <token> Single-app token
5744
- --token-map <mapping> App token map (APP100=...,APP101=...)
5745
- --token-file <path> JSON file for app token map
5746
- --app <id> Default app id context
5747
- --diag-record-id <id> Diagnostic: GET record.json by app+id
5748
- --no-header Hide table header
5749
- --pretty Pretty-print JSON output
5750
- --user-format <mode> User field format: full | name | code
5751
- --array-format <mode> Array field format: full | join
5752
- --table-format <mode> Subtable format: full | count
5753
- --date-format <mode> Date format: full | local
5754
- --attachment-format <mode> Attachment format: full | name | fileKey
5755
- --output <path> Write output to file
5756
- --no-color Disable ANSI colors
5757
- --quiet Suppress non-result logs
5758
- --debug Show request/response debug logs
5759
- --debug-url Show only HTTP request URL debug logs
5760
- --debug-headers Show request headers in debug logs (masked)
5761
- --exit-on-empty Return exit code 1 when rowCount is 0
5762
- --allow-dml Enable UPDATE/DELETE/INSERT/UPSERT execution
5763
- --yes Skip DML confirmation prompt
5764
- --allow-without-where Allow UPDATE/DELETE without WHERE
5765
- --dml-max-rows <n> Max affected rows for DML guard (default: 100)
5766
- -h, --help Show help
5767
- -v, --version Show version
5768
- `;
5769
- function parseArgs(argv) {
5770
- const out = {
5771
- help: false,
5772
- version: false,
5773
- executeSql: null,
5774
- filePath: null,
5775
- console: false,
5776
- dryRun: false,
5777
- format: null,
5778
- maxRecords: null,
5779
- onLimit: null,
5780
- timeout: null,
5781
- configPath: null,
5782
- profile: null,
5783
- baseUrl: null,
5784
- guestSpaceId: null,
5785
- auth: null,
5786
- username: null,
5787
- password: null,
5788
- token: null,
5789
- tokenMap: {},
5790
- tokenFile: null,
5791
- app: null,
5792
- diagRecordId: null,
5793
- noHeader: false,
5794
- pretty: false,
5795
- outputPath: null,
5796
- noColor: false,
5797
- quiet: false,
5798
- debug: false,
5799
- debugUrl: false,
5800
- debugHeaders: false,
5801
- exitOnEmpty: false,
5802
- allowDml: false,
5803
- yes: false,
5804
- allowWithoutWhere: false,
5805
- dmlMaxRows: null,
5806
- userFormat: null,
5807
- arrayFormat: null,
5808
- tableFormat: null,
5809
- dateFormat: null,
5810
- attachmentFormat: null
5823
+ // src/node/appProfiles.ts
5824
+ var import_fs = require("fs");
5825
+ function parseTokenMap(raw) {
5826
+ const out = {};
5827
+ if (!raw.trim()) return out;
5828
+ const pairs = raw.split(",");
5829
+ for (const pair of pairs) {
5830
+ const idx = pair.indexOf("=");
5831
+ if (idx <= 0) throw new Error("ArgumentError: --token-map must be APPxxx=token pairs.");
5832
+ const key = normalizeAppKey(pair.slice(0, idx).trim());
5833
+ const value = pair.slice(idx + 1).trim();
5834
+ if (!value) throw new Error(`ArgumentError: token is empty for ${key}.`);
5835
+ out[key] = value;
5836
+ }
5837
+ return out;
5838
+ }
5839
+ function parseTokenFile(path) {
5840
+ const raw = (0, import_fs.readFileSync)(path, "utf-8");
5841
+ const parsed = JSON.parse(raw);
5842
+ const out = {};
5843
+ for (const [k, v] of Object.entries(parsed)) out[normalizeAppKey(k)] = String(v);
5844
+ return out;
5845
+ }
5846
+ function normalizeAppKey(v) {
5847
+ const m1 = v.match(/^APP(\d+)$/i);
5848
+ if (m1) return `APP${m1[1]}`;
5849
+ const m2 = v.match(/^(\d+)$/);
5850
+ if (m2) return `APP${m2[1]}`;
5851
+ throw new Error(`ArgumentError: invalid app key "${v}"`);
5852
+ }
5853
+ function extractAppIds(sql) {
5854
+ const out = /* @__PURE__ */ new Set();
5855
+ for (const m of sql.matchAll(/\bAPP(\d+)\b/gi)) out.add(Number(m[1]));
5856
+ return [...out];
5857
+ }
5858
+ function isSqlIdentContinue(ch) {
5859
+ if (!ch) return false;
5860
+ const cp = ch.codePointAt(0);
5861
+ return cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp >= 48 && cp <= 57 || cp === 95 || cp === 36 || cp >= 12352 && cp <= 12543 || cp >= 13312 && cp <= 40959 || cp >= 63744 && cp <= 64255 || cp >= 65281 && cp <= 65376;
5862
+ }
5863
+ function isProfileNameChar(ch) {
5864
+ if (!ch) return false;
5865
+ const cp = ch.codePointAt(0);
5866
+ return cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp >= 48 && cp <= 57 || ch === "_" || ch === "-" || ch === "." || ch === "$";
5867
+ }
5868
+ function tryParseAppProfileToken(sql, start) {
5869
+ const head = sql.slice(start, start + 3);
5870
+ if (head.toUpperCase() !== "APP") return null;
5871
+ const prev = start > 0 ? sql[start - 1] : "";
5872
+ if (isSqlIdentContinue(prev)) return null;
5873
+ let i = start + 3;
5874
+ const digitStart = i;
5875
+ while (i < sql.length && /[0-9]/.test(sql[i])) i++;
5876
+ const digitEnd = i;
5877
+ if (digitEnd === digitStart) return null;
5878
+ if (sql[i] === "$") {
5879
+ i++;
5880
+ const subStart = i;
5881
+ while (i < sql.length && isSqlIdentContinue(sql[i])) i++;
5882
+ if (i === subStart) return null;
5883
+ }
5884
+ const appEnd = i;
5885
+ let profile = null;
5886
+ if (sql[i] === "@") {
5887
+ i++;
5888
+ const pStart = i;
5889
+ while (i < sql.length && isProfileNameChar(sql[i])) i++;
5890
+ if (i === pStart) return null;
5891
+ profile = sql.slice(pStart, i);
5892
+ }
5893
+ const next = i < sql.length ? sql[i] : "";
5894
+ if (isSqlIdentContinue(next)) return null;
5895
+ return {
5896
+ appId: Number(sql.slice(digitStart, digitEnd)),
5897
+ profile,
5898
+ start,
5899
+ digitStart,
5900
+ digitEnd,
5901
+ appEnd,
5902
+ fullEnd: i
5811
5903
  };
5812
- for (let i = 0; i < argv.length; i++) {
5813
- const a = argv[i];
5814
- if (a === "-h" || a === "--help") {
5815
- out.help = true;
5904
+ }
5905
+ function collectAppProfileTokens(sql) {
5906
+ const tokens = [];
5907
+ let i = 0;
5908
+ while (i < sql.length) {
5909
+ const ch = sql[i];
5910
+ if (ch === "'") {
5911
+ i++;
5912
+ while (i < sql.length) {
5913
+ if (sql[i] === "'") {
5914
+ i++;
5915
+ if (i < sql.length && sql[i] === "'") {
5916
+ i++;
5917
+ continue;
5918
+ }
5919
+ break;
5920
+ }
5921
+ i++;
5922
+ }
5816
5923
  continue;
5817
5924
  }
5818
- if (a === "-v" || a === "--version") {
5819
- out.version = true;
5925
+ if (ch === "`") {
5926
+ i++;
5927
+ while (i < sql.length && sql[i] !== "`") i++;
5928
+ if (i < sql.length) i++;
5820
5929
  continue;
5821
5930
  }
5822
- if (a === "--console") {
5823
- out.console = true;
5931
+ if (ch === "-" && sql[i + 1] === "-") {
5932
+ i += 2;
5933
+ while (i < sql.length && sql[i] !== "\n") i++;
5824
5934
  continue;
5825
5935
  }
5826
- if (a === "--dry-run") {
5936
+ if (ch === "/" && sql[i + 1] === "*") {
5937
+ i += 2;
5938
+ while (i < sql.length) {
5939
+ if (sql[i] === "*" && sql[i + 1] === "/") {
5940
+ i += 2;
5941
+ break;
5942
+ }
5943
+ i++;
5944
+ }
5945
+ continue;
5946
+ }
5947
+ const parsed = tryParseAppProfileToken(sql, i);
5948
+ if (!parsed) {
5949
+ i++;
5950
+ continue;
5951
+ }
5952
+ tokens.push(parsed);
5953
+ i = parsed.fullEnd;
5954
+ }
5955
+ return tokens;
5956
+ }
5957
+ function nextVirtualAppId(used) {
5958
+ let id = 9e8;
5959
+ while (used.has(id)) id++;
5960
+ used.add(id);
5961
+ return id;
5962
+ }
5963
+ function normalizeSqlAppProfiles(sql, defaultProfile = "dev") {
5964
+ const tokens = collectAppProfileTokens(sql);
5965
+ const hasProfileSyntax = tokens.some((t) => t.profile !== null);
5966
+ const profilesByApp = /* @__PURE__ */ new Map();
5967
+ const normalizedProfile = (profile) => profile ?? defaultProfile;
5968
+ for (const t of tokens) {
5969
+ const p = normalizedProfile(t.profile);
5970
+ let set = profilesByApp.get(t.appId);
5971
+ if (!set) {
5972
+ set = /* @__PURE__ */ new Set();
5973
+ profilesByApp.set(t.appId, set);
5974
+ }
5975
+ set.add(p.toLowerCase());
5976
+ }
5977
+ const usedAppIds = new Set(tokens.map((t) => t.appId));
5978
+ const pairToMapped = /* @__PURE__ */ new Map();
5979
+ const appBindingByMappedApp = /* @__PURE__ */ new Map();
5980
+ for (const [appId, pSet] of profilesByApp.entries()) {
5981
+ const profiles = [...pSet].sort();
5982
+ if (profiles.length <= 1) continue;
5983
+ for (const pLower of profiles) {
5984
+ const mapped = nextVirtualAppId(usedAppIds);
5985
+ pairToMapped.set(`${appId}@${pLower}`, mapped);
5986
+ appBindingByMappedApp.set(mapped, { appId, profile: pLower });
5987
+ }
5988
+ }
5989
+ const out = [];
5990
+ let cursor = 0;
5991
+ for (const t of tokens) {
5992
+ const p = normalizedProfile(t.profile);
5993
+ const pLower = p.toLowerCase();
5994
+ const mapped = pairToMapped.get(`${t.appId}@${pLower}`) ?? t.appId;
5995
+ appBindingByMappedApp.set(mapped, { appId: t.appId, profile: pLower });
5996
+ out.push(sql.slice(cursor, t.start));
5997
+ out.push(sql.slice(t.start, t.digitStart));
5998
+ out.push(String(mapped));
5999
+ out.push(sql.slice(t.digitEnd, t.appEnd));
6000
+ cursor = t.fullEnd;
6001
+ }
6002
+ out.push(sql.slice(cursor));
6003
+ return {
6004
+ normalizedSql: out.join(""),
6005
+ hasProfileSyntax,
6006
+ appBindingByMappedApp
6007
+ };
6008
+ }
6009
+ function buildCacheContext(defaultProfile, appBindingByMappedApp) {
6010
+ if (appBindingByMappedApp.size === 0) return `default:${defaultProfile.toLowerCase()}`;
6011
+ const pairs = [...appBindingByMappedApp.entries()].sort((a, b) => a[0] - b[0]).map(([mappedAppId, b]) => `M${mappedAppId}=APP${b.appId}@${b.profile}`);
6012
+ return `apps:${pairs.join(",")}`;
6013
+ }
6014
+ function formatResolvedAppProfiles(sql, defaultProfile) {
6015
+ const parsed = normalizeSqlAppProfiles(sql, defaultProfile);
6016
+ if (parsed.appBindingByMappedApp.size === 0) return "(none)";
6017
+ return [...parsed.appBindingByMappedApp.values()].map((b) => `APP${b.appId}->${b.profile}`).join(", ");
6018
+ }
6019
+
6020
+ // src/node/dmlGuard.ts
6021
+ function getStatementType(stmt) {
6022
+ if (!stmt || typeof stmt !== "object") return "UNKNOWN";
6023
+ const obj = stmt;
6024
+ return typeof obj.type === "string" ? obj.type : "UNKNOWN";
6025
+ }
6026
+ function isDmlType(type) {
6027
+ return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
6028
+ }
6029
+ function hasWhereClause(stmt) {
6030
+ if (!stmt || typeof stmt !== "object") return false;
6031
+ const obj = stmt;
6032
+ return obj.where !== null && obj.where !== void 0;
6033
+ }
6034
+ function isNoFromSelectStatement(stmt) {
6035
+ if (!stmt || typeof stmt !== "object") return false;
6036
+ const obj = stmt;
6037
+ return obj.type === "SELECT" && obj.from?.appId === 0 && obj.from?.cteName === "__NO_FROM__";
6038
+ }
6039
+ function getInsertValuesCount(stmt) {
6040
+ if (!stmt || typeof stmt !== "object") return null;
6041
+ const obj = stmt;
6042
+ if (obj.type !== "INSERT") return null;
6043
+ return Array.isArray(obj.values) ? obj.values.length : null;
6044
+ }
6045
+ function collectDmlTargetFields(stmt) {
6046
+ if (!stmt || typeof stmt !== "object") return [];
6047
+ const obj = stmt;
6048
+ if (!obj.type) return [];
6049
+ if (obj.type === "UPDATE") {
6050
+ return (obj.assignments ?? []).map((a) => a.field).filter((f) => Boolean(f));
6051
+ }
6052
+ if (obj.type === "INSERT" || obj.type === "INSERT_SELECT" || obj.type === "UPSERT" || obj.type === "UPSERT_SELECT") {
6053
+ return [...obj.fields ?? [], ...obj.keyFields ?? []];
6054
+ }
6055
+ return [];
6056
+ }
6057
+
6058
+ // src/cli/index.ts
6059
+ var HELP_TEXT = `ksql - Execute SQL against kintone apps
6060
+
6061
+ Usage:
6062
+ ksql [options]
6063
+ ksql -e "<SQL>"
6064
+ ksql -f <file.sql>
6065
+
6066
+ Options:
6067
+ -e, --execute <sql> Execute SQL string
6068
+ -f, --file <path> Execute SQL file
6069
+ --console Start interactive console mode
6070
+ --dry-run Parse and show execution plan only
6071
+ --format <type> Output format: table | json | jsonl | csv | markdown | md
6072
+ --max-records <n> Max records to fetch (default: 500)
6073
+ --on-limit <mode> On record limit: error | truncate
6074
+ --timeout <ms> Request timeout in milliseconds (default: 30000)
6075
+ --config <path> Config file path (default: ./ksql.config.json)
6076
+ --profile <name> Profile name in config
6077
+ --base-url <url> kintone base URL
6078
+ --guest-space-id <id> Guest space ID (uses /k/guest/<id>/v1 APIs)
6079
+ --auth <type> Auth type: token | userpass | auto
6080
+ --username <name> Login username (for userpass auth)
6081
+ --password <pass> Login password (for userpass auth)
6082
+ --token <token> Single-app token
6083
+ --token-map <mapping> App token map (APP100=...,APP101=...)
6084
+ --token-file <path> JSON file for app token map
6085
+ --app <id> Default app id context
6086
+ --diag-record-id <id> Diagnostic: GET record.json by app+id
6087
+ --no-header Hide table header
6088
+ --pretty Pretty-print JSON output
6089
+ --user-format <mode> User field format: full | name | code
6090
+ --array-format <mode> Array field format: full | join
6091
+ --table-format <mode> Subtable format: full | count
6092
+ --date-format <mode> Date format: full | local
6093
+ --attachment-format <mode> Attachment format: full | name | fileKey
6094
+ --output <path> Write output to file
6095
+ --no-color Disable ANSI colors
6096
+ --quiet Suppress non-result logs
6097
+ --debug Show request/response debug logs
6098
+ --debug-url Show only HTTP request URL debug logs
6099
+ --debug-headers Show request headers in debug logs (masked)
6100
+ --exit-on-empty Return exit code 1 when rowCount is 0
6101
+ --allow-dml Enable UPDATE/DELETE/INSERT/UPSERT/REORDER execution
6102
+ --yes Skip DML confirmation prompt
6103
+ --allow-without-where Allow UPDATE/DELETE without WHERE
6104
+ --dml-max-rows <n> Max affected rows for DML guard (default: 100)
6105
+ -h, --help Show help
6106
+ -v, --version Show version
6107
+ `;
6108
+ function parseArgs(argv) {
6109
+ const out = {
6110
+ help: false,
6111
+ version: false,
6112
+ executeSql: null,
6113
+ filePath: null,
6114
+ console: false,
6115
+ dryRun: false,
6116
+ format: null,
6117
+ maxRecords: null,
6118
+ onLimit: null,
6119
+ timeout: null,
6120
+ configPath: null,
6121
+ profile: null,
6122
+ baseUrl: null,
6123
+ guestSpaceId: null,
6124
+ auth: null,
6125
+ username: null,
6126
+ password: null,
6127
+ token: null,
6128
+ tokenMap: {},
6129
+ tokenFile: null,
6130
+ app: null,
6131
+ diagRecordId: null,
6132
+ noHeader: false,
6133
+ pretty: false,
6134
+ outputPath: null,
6135
+ noColor: false,
6136
+ quiet: false,
6137
+ debug: false,
6138
+ debugUrl: false,
6139
+ debugHeaders: false,
6140
+ exitOnEmpty: false,
6141
+ allowDml: false,
6142
+ yes: false,
6143
+ allowWithoutWhere: false,
6144
+ dmlMaxRows: null,
6145
+ userFormat: null,
6146
+ arrayFormat: null,
6147
+ tableFormat: null,
6148
+ dateFormat: null,
6149
+ attachmentFormat: null
6150
+ };
6151
+ for (let i = 0; i < argv.length; i++) {
6152
+ const a = argv[i];
6153
+ if (a === "-h" || a === "--help") {
6154
+ out.help = true;
6155
+ continue;
6156
+ }
6157
+ if (a === "-v" || a === "--version") {
6158
+ out.version = true;
6159
+ continue;
6160
+ }
6161
+ if (a === "--console") {
6162
+ out.console = true;
6163
+ continue;
6164
+ }
6165
+ if (a === "--dry-run") {
5827
6166
  out.dryRun = true;
5828
6167
  continue;
5829
6168
  }
@@ -6024,216 +6363,14 @@ function parseArgs(argv) {
6024
6363
  }
6025
6364
  function getVersion() {
6026
6365
  const pkgPath = (0, import_path.resolve)(__dirname, "../package.json");
6027
- const raw = (0, import_fs.readFileSync)(pkgPath, "utf-8");
6366
+ const raw = (0, import_fs2.readFileSync)(pkgPath, "utf-8");
6028
6367
  const pkg = JSON.parse(raw);
6029
6368
  return pkg.version ?? "0.0.0";
6030
6369
  }
6031
6370
  function loadConfig(configPath) {
6032
- const raw = (0, import_fs.readFileSync)(configPath, "utf-8");
6371
+ const raw = (0, import_fs2.readFileSync)(configPath, "utf-8");
6033
6372
  return JSON.parse(raw);
6034
6373
  }
6035
- function parseTokenMap(raw) {
6036
- const out = {};
6037
- if (!raw.trim()) return out;
6038
- const pairs = raw.split(",");
6039
- for (const pair of pairs) {
6040
- const idx = pair.indexOf("=");
6041
- if (idx <= 0) throw new Error("ArgumentError: --token-map must be APPxxx=token pairs.");
6042
- const key = normalizeAppKey(pair.slice(0, idx).trim());
6043
- const value = pair.slice(idx + 1).trim();
6044
- if (!value) throw new Error(`ArgumentError: token is empty for ${key}.`);
6045
- out[key] = value;
6046
- }
6047
- return out;
6048
- }
6049
- function parseTokenFile(path) {
6050
- const raw = (0, import_fs.readFileSync)(path, "utf-8");
6051
- const parsed = JSON.parse(raw);
6052
- const out = {};
6053
- for (const [k, v] of Object.entries(parsed)) out[normalizeAppKey(k)] = String(v);
6054
- return out;
6055
- }
6056
- function normalizeAppKey(v) {
6057
- const m1 = v.match(/^APP(\d+)$/i);
6058
- if (m1) return `APP${m1[1]}`;
6059
- const m2 = v.match(/^(\d+)$/);
6060
- if (m2) return `APP${m2[1]}`;
6061
- throw new Error(`ArgumentError: invalid app key "${v}"`);
6062
- }
6063
- function extractAppIds(sql) {
6064
- const out = /* @__PURE__ */ new Set();
6065
- for (const m of sql.matchAll(/\bAPP(\d+)\b/gi)) out.add(Number(m[1]));
6066
- return [...out];
6067
- }
6068
- function isSqlIdentContinue(ch) {
6069
- if (!ch) return false;
6070
- const cp = ch.codePointAt(0);
6071
- return cp >= 65 && cp <= 90 || // A-Z
6072
- cp >= 97 && cp <= 122 || // a-z
6073
- cp >= 48 && cp <= 57 || // 0-9
6074
- cp === 95 || // _
6075
- cp === 36 || // $
6076
- cp >= 12352 && cp <= 12543 || cp >= 13312 && cp <= 40959 || cp >= 63744 && cp <= 64255 || cp >= 65281 && cp <= 65376;
6077
- }
6078
- function isProfileNameChar(ch) {
6079
- if (!ch) return false;
6080
- const cp = ch.codePointAt(0);
6081
- return cp >= 65 && cp <= 90 || // A-Z
6082
- cp >= 97 && cp <= 122 || // a-z
6083
- cp >= 48 && cp <= 57 || // 0-9
6084
- ch === "_" || ch === "-" || ch === "." || ch === "$";
6085
- }
6086
- function tryParseAppProfileToken(sql, start) {
6087
- const head = sql.slice(start, start + 3);
6088
- if (head.toUpperCase() !== "APP") return null;
6089
- const prev = start > 0 ? sql[start - 1] : "";
6090
- if (isSqlIdentContinue(prev)) return null;
6091
- let i = start + 3;
6092
- const digitStart = i;
6093
- while (i < sql.length && /[0-9]/.test(sql[i])) i++;
6094
- const digitEnd = i;
6095
- if (digitEnd === digitStart) return null;
6096
- if (sql[i] === "$") {
6097
- i++;
6098
- const subStart = i;
6099
- while (i < sql.length && isSqlIdentContinue(sql[i])) i++;
6100
- if (i === subStart) return null;
6101
- }
6102
- const appEnd = i;
6103
- let profile = null;
6104
- if (sql[i] === "@") {
6105
- i++;
6106
- const pStart = i;
6107
- while (i < sql.length && isProfileNameChar(sql[i])) i++;
6108
- if (i === pStart) return null;
6109
- profile = sql.slice(pStart, i);
6110
- }
6111
- const next = i < sql.length ? sql[i] : "";
6112
- if (isSqlIdentContinue(next)) return null;
6113
- return {
6114
- appId: Number(sql.slice(digitStart, digitEnd)),
6115
- profile,
6116
- start,
6117
- digitStart,
6118
- digitEnd,
6119
- appEnd,
6120
- fullEnd: i
6121
- };
6122
- }
6123
- function collectAppProfileTokens(sql) {
6124
- const tokens = [];
6125
- let i = 0;
6126
- while (i < sql.length) {
6127
- const ch = sql[i];
6128
- if (ch === "'") {
6129
- i++;
6130
- while (i < sql.length) {
6131
- if (sql[i] === "'") {
6132
- i++;
6133
- if (i < sql.length && sql[i] === "'") {
6134
- i++;
6135
- continue;
6136
- }
6137
- break;
6138
- }
6139
- i++;
6140
- }
6141
- continue;
6142
- }
6143
- if (ch === "`") {
6144
- i++;
6145
- while (i < sql.length && sql[i] !== "`") i++;
6146
- if (i < sql.length) i++;
6147
- continue;
6148
- }
6149
- if (ch === "-" && sql[i + 1] === "-") {
6150
- i += 2;
6151
- while (i < sql.length && sql[i] !== "\n") i++;
6152
- continue;
6153
- }
6154
- if (ch === "/" && sql[i + 1] === "*") {
6155
- i += 2;
6156
- while (i < sql.length) {
6157
- if (sql[i] === "*" && sql[i + 1] === "/") {
6158
- i += 2;
6159
- break;
6160
- }
6161
- i++;
6162
- }
6163
- continue;
6164
- }
6165
- const parsed = tryParseAppProfileToken(sql, i);
6166
- if (!parsed) {
6167
- i++;
6168
- continue;
6169
- }
6170
- tokens.push(parsed);
6171
- i = parsed.fullEnd;
6172
- }
6173
- return tokens;
6174
- }
6175
- function nextVirtualAppId(used) {
6176
- let id = 9e8;
6177
- while (used.has(id)) id++;
6178
- used.add(id);
6179
- return id;
6180
- }
6181
- function normalizeSqlAppProfiles(sql, defaultProfile = "dev") {
6182
- const tokens = collectAppProfileTokens(sql);
6183
- const hasProfileSyntax = tokens.some((t) => t.profile !== null);
6184
- const profilesByApp = /* @__PURE__ */ new Map();
6185
- const normalizedProfile = (profile) => profile ?? defaultProfile;
6186
- for (const t of tokens) {
6187
- const p = normalizedProfile(t.profile);
6188
- let set = profilesByApp.get(t.appId);
6189
- if (!set) {
6190
- set = /* @__PURE__ */ new Set();
6191
- profilesByApp.set(t.appId, set);
6192
- }
6193
- set.add(p.toLowerCase());
6194
- }
6195
- const usedAppIds = new Set(tokens.map((t) => t.appId));
6196
- const pairToMapped = /* @__PURE__ */ new Map();
6197
- const appBindingByMappedApp = /* @__PURE__ */ new Map();
6198
- for (const [appId, pSet] of profilesByApp.entries()) {
6199
- const profiles = [...pSet].sort();
6200
- if (profiles.length <= 1) continue;
6201
- for (const pLower of profiles) {
6202
- const mapped = nextVirtualAppId(usedAppIds);
6203
- pairToMapped.set(`${appId}@${pLower}`, mapped);
6204
- appBindingByMappedApp.set(mapped, { appId, profile: pLower });
6205
- }
6206
- }
6207
- const out = [];
6208
- let cursor = 0;
6209
- for (const t of tokens) {
6210
- const p = normalizedProfile(t.profile);
6211
- const pLower = p.toLowerCase();
6212
- const mapped = pairToMapped.get(`${t.appId}@${pLower}`) ?? t.appId;
6213
- appBindingByMappedApp.set(mapped, { appId: t.appId, profile: pLower });
6214
- out.push(sql.slice(cursor, t.start));
6215
- out.push(sql.slice(t.start, t.digitStart));
6216
- out.push(String(mapped));
6217
- out.push(sql.slice(t.digitEnd, t.appEnd));
6218
- cursor = t.fullEnd;
6219
- }
6220
- out.push(sql.slice(cursor));
6221
- return {
6222
- normalizedSql: out.join(""),
6223
- hasProfileSyntax,
6224
- appBindingByMappedApp
6225
- };
6226
- }
6227
- function buildCacheContext(defaultProfile, appBindingByMappedApp) {
6228
- if (appBindingByMappedApp.size === 0) return `default:${defaultProfile.toLowerCase()}`;
6229
- const pairs = [...appBindingByMappedApp.entries()].sort((a, b) => a[0] - b[0]).map(([mappedAppId, b]) => `M${mappedAppId}=APP${b.appId}@${b.profile}`);
6230
- return `apps:${pairs.join(",")}`;
6231
- }
6232
- function formatResolvedAppProfiles(sql, defaultProfile) {
6233
- const parsed = normalizeSqlAppProfiles(sql, defaultProfile);
6234
- if (parsed.appBindingByMappedApp.size === 0) return "(none)";
6235
- return [...parsed.appBindingByMappedApp.values()].map((b) => `APP${b.appId}->${b.profile}`).join(", ");
6236
- }
6237
6374
  function resolveTokenValue(raw) {
6238
6375
  if (raw.startsWith("env:")) {
6239
6376
  const envKey = raw.slice(4);
@@ -6275,42 +6412,6 @@ function envAuth(name) {
6275
6412
  if (v === "token" || v === "userpass" || v === "auto") return v;
6276
6413
  return null;
6277
6414
  }
6278
- function isDmlType(type) {
6279
- return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT";
6280
- }
6281
- function hasWhereClause(stmt) {
6282
- if (!stmt || typeof stmt !== "object") return false;
6283
- const obj = stmt;
6284
- return obj.where !== null && obj.where !== void 0;
6285
- }
6286
- function getStatementType(stmt) {
6287
- if (!stmt || typeof stmt !== "object") return "UNKNOWN";
6288
- const obj = stmt;
6289
- return typeof obj.type === "string" ? obj.type : "UNKNOWN";
6290
- }
6291
- function isNoFromSelectStatement(stmt) {
6292
- if (!stmt || typeof stmt !== "object") return false;
6293
- const obj = stmt;
6294
- return obj.type === "SELECT" && obj.from?.appId === 0 && obj.from?.cteName === "__NO_FROM__";
6295
- }
6296
- function getInsertValuesCount(stmt) {
6297
- if (!stmt || typeof stmt !== "object") return null;
6298
- const obj = stmt;
6299
- if (obj.type !== "INSERT") return null;
6300
- return Array.isArray(obj.values) ? obj.values.length : null;
6301
- }
6302
- function collectDmlTargetFields(stmt) {
6303
- if (!stmt || typeof stmt !== "object") return [];
6304
- const obj = stmt;
6305
- if (!obj.type) return [];
6306
- if (obj.type === "UPDATE") {
6307
- return (obj.assignments ?? []).map((a) => a.field).filter((f) => Boolean(f));
6308
- }
6309
- if (obj.type === "INSERT" || obj.type === "INSERT_SELECT" || obj.type === "UPSERT" || obj.type === "UPSERT_SELECT") {
6310
- return [...obj.fields ?? [], ...obj.keyFields ?? []];
6311
- }
6312
- return [];
6313
- }
6314
6415
  function normalizeUnique(values) {
6315
6416
  const out = [];
6316
6417
  const seen = /* @__PURE__ */ new Set();
@@ -6695,9 +6796,9 @@ function getHistoryPath() {
6695
6796
  }
6696
6797
  function loadHistory(maxItems = 200) {
6697
6798
  const p = getHistoryPath();
6698
- if (!(0, import_fs.existsSync)(p)) return [];
6799
+ if (!(0, import_fs2.existsSync)(p)) return [];
6699
6800
  try {
6700
- const raw = (0, import_fs.readFileSync)(p, "utf-8");
6801
+ const raw = (0, import_fs2.readFileSync)(p, "utf-8");
6701
6802
  const lines = raw.split(/\r?\n/).map((s) => s.trim()).filter((s) => s.length > 0);
6702
6803
  return lines.slice(-maxItems);
6703
6804
  } catch {
@@ -6707,26 +6808,26 @@ function loadHistory(maxItems = 200) {
6707
6808
  function appendHistory(sql) {
6708
6809
  const p = getHistoryPath();
6709
6810
  try {
6710
- (0, import_fs.appendFileSync)(p, `${sql.replace(/\s+/g, " ").trim()}
6811
+ (0, import_fs2.appendFileSync)(p, `${sql.replace(/\s+/g, " ").trim()}
6711
6812
  `, "utf-8");
6712
6813
  } catch {
6713
6814
  }
6714
6815
  }
6715
6816
  function editBufferWithExternalEditor(current) {
6716
6817
  const editor = process.env.KSQL_EDITOR ?? process.env.VISUAL ?? process.env.EDITOR ?? (process.platform === "win32" ? "notepad" : "vi");
6717
- const dir = (0, import_fs.mkdtempSync)((0, import_path.join)((0, import_os.tmpdir)(), "ksql-edit-"));
6818
+ const dir = (0, import_fs2.mkdtempSync)((0, import_path.join)((0, import_os.tmpdir)(), "ksql-edit-"));
6718
6819
  const filePath = (0, import_path.join)(dir, "query.sql");
6719
6820
  try {
6720
- (0, import_fs.writeFileSync)(filePath, current, "utf-8");
6821
+ (0, import_fs2.writeFileSync)(filePath, current, "utf-8");
6721
6822
  const cmd = `"${editor}" "${filePath}"`;
6722
6823
  const res = (0, import_child_process.spawnSync)(cmd, { stdio: "inherit", shell: true });
6723
6824
  if (res.error) throw res.error;
6724
6825
  if ((res.status ?? 0) !== 0) {
6725
6826
  throw new Error(`Editor exited with code ${res.status ?? 1}`);
6726
6827
  }
6727
- return (0, import_fs.readFileSync)(filePath, "utf-8").replace(/\r\n/g, "\n");
6828
+ return (0, import_fs2.readFileSync)(filePath, "utf-8").replace(/\r\n/g, "\n");
6728
6829
  } finally {
6729
- (0, import_fs.rmSync)(dir, { recursive: true, force: true });
6830
+ (0, import_fs2.rmSync)(dir, { recursive: true, force: true });
6730
6831
  }
6731
6832
  }
6732
6833
  async function runConsole(base) {
@@ -6908,11 +7009,11 @@ async function runConsole(base) {
6908
7009
  }
6909
7010
  try {
6910
7011
  if (meta.append) {
6911
- (0, import_fs.appendFileSync)(meta.path, lastOutput, "utf-8");
7012
+ (0, import_fs2.appendFileSync)(meta.path, lastOutput, "utf-8");
6912
7013
  process.stdout.write(`saved (append): ${meta.path}
6913
7014
  `);
6914
7015
  } else {
6915
- (0, import_fs.writeFileSync)(meta.path, lastOutput, "utf-8");
7016
+ (0, import_fs2.writeFileSync)(meta.path, lastOutput, "utf-8");
6916
7017
  process.stdout.write(`saved: ${meta.path}
6917
7018
  `);
6918
7019
  }
@@ -7023,7 +7124,7 @@ async function run() {
7023
7124
  let isDmlStatement = false;
7024
7125
  if (args.diagRecordId === null) {
7025
7126
  sql = args.executeSql;
7026
- if (!sql && args.filePath) sql = (0, import_fs.readFileSync)(args.filePath, "utf-8");
7127
+ if (!sql && args.filePath) sql = (0, import_fs2.readFileSync)(args.filePath, "utf-8");
7027
7128
  if (!sql || !sql.trim()) {
7028
7129
  process.stderr.write("ArgumentError: SQL is empty.\n");
7029
7130
  return 2;
@@ -7045,7 +7146,7 @@ async function run() {
7045
7146
  isDmlStatement = isDmlType(stmtType);
7046
7147
  hasWhere = hasWhereClause(stmt);
7047
7148
  insertValuesCount = getInsertValuesCount(stmt);
7048
- const supported = stmtType === "SELECT" || isDmlStatement;
7149
+ const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || isDmlStatement;
7049
7150
  if (!supported) {
7050
7151
  process.stderr.write(`ArgumentError: unsupported statement type in CLI: ${stmtType}
7051
7152
  `);
@@ -7093,7 +7194,7 @@ async function run() {
7093
7194
  const appIds = sql ? extractAppIds(sql) : [];
7094
7195
  const defaultApp = args.app ?? envInt("KSQL_APP") ?? profile.app ?? null;
7095
7196
  if (appIds.length === 0 && defaultApp !== null) appIds.push(defaultApp);
7096
- const allowNoFromSelect = isNoFromSelectStatement(parsedStmt);
7197
+ const allowNoFromSelect = isNoFromSelectStatement(parsedStmt) || stmtType === "SHOW_APPS";
7097
7198
  if (appIds.length === 0 && !allowNoFromSelect && !args.dryRun && args.diagRecordId === null) {
7098
7199
  process.stderr.write("ArgumentError: no APPxxx found in SQL and --app is not set.\n");
7099
7200
  return 2;
@@ -7104,7 +7205,7 @@ async function run() {
7104
7205
  return 2;
7105
7206
  }
7106
7207
  if (!allowDml) {
7107
- process.stderr.write("ArgumentError: DML is disabled. Use --allow-dml to enable UPDATE/DELETE/INSERT/UPSERT.\n");
7208
+ process.stderr.write("ArgumentError: DML is disabled. Use --allow-dml to enable UPDATE/DELETE/INSERT/UPSERT/REORDER.\n");
7108
7209
  return 2;
7109
7210
  }
7110
7211
  if ((stmtType === "UPDATE" || stmtType === "DELETE") && !hasWhere && !allowWithoutWhere) {
@@ -7380,7 +7481,7 @@ query=${label}`);
7380
7481
  });
7381
7482
  if (result.type !== "SELECT") {
7382
7483
  const output2 = buildMutationOutput(result, format, noHeader, pretty);
7383
- if (outputPath) (0, import_fs.writeFileSync)(outputPath, `${output2}
7484
+ if (outputPath) (0, import_fs2.writeFileSync)(outputPath, `${output2}
7384
7485
  `, "utf-8");
7385
7486
  else if (output2) process.stdout.write(`${output2}
7386
7487
  `);
@@ -7389,7 +7490,7 @@ query=${label}`);
7389
7490
  return 0;
7390
7491
  }
7391
7492
  const output = buildOutput(result, format, noHeader, pretty, displayOptions);
7392
- if (outputPath) (0, import_fs.writeFileSync)(outputPath, `${output}
7493
+ if (outputPath) (0, import_fs2.writeFileSync)(outputPath, `${output}
7393
7494
  `, "utf-8");
7394
7495
  else if (output) process.stdout.write(`${output}
7395
7496
  `);