@rex0220/kintone-sql-tools 2.5.0 → 2.7.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
@@ -3581,10 +3581,12 @@ function typedInContains(leftStr, values, fieldType) {
3581
3581
  if (!Array.isArray(parsed) || !parsed.every((item) => typeof item === "string")) {
3582
3582
  return fallback();
3583
3583
  }
3584
+ if (parsed.length === 0 && values.has("")) return true;
3584
3585
  return parsed.some((item) => values.has(item));
3585
3586
  }
3586
3587
  if (OBJECT_ARRAY_FIELD_TYPES.has(fieldType)) {
3587
3588
  if (!Array.isArray(parsed) || !parsed.every(hasStringCode)) return fallback();
3589
+ if (parsed.length === 0 && values.has("")) return true;
3588
3590
  return parsed.some((item) => values.has(item.code));
3589
3591
  }
3590
3592
  if (!hasStringCode(parsed)) return fallback();
@@ -4197,8 +4199,11 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
4197
4199
  function extractSafePushdownLeaves(where, options = {}) {
4198
4200
  return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
4199
4201
  }
4200
- function extractNumericPushdownCandidates(where, options = {}) {
4201
- return extractAndLeaves(where, (expr) => isNumericCandidate(expr, options));
4202
+ function extractTypedPushdownCandidates(where, options = {}) {
4203
+ return extractAndLeaves(
4204
+ where,
4205
+ (expr) => isNumericCandidate(expr, options) || isSelectionInCandidate(expr, options)
4206
+ );
4202
4207
  }
4203
4208
  function extractAndLeaves(where, accept) {
4204
4209
  switch (where.type) {
@@ -4222,8 +4227,28 @@ function extractAndLeaves(where, accept) {
4222
4227
  }
4223
4228
  function isSafeComparison(expr, options) {
4224
4229
  if (isSafeIdComparison(expr, options)) return true;
4225
- if (!isNumericCandidate(expr, options)) return false;
4226
- return options.fieldTypes?.get(expr.left.field) === "NUMBER";
4230
+ if (isNumericCandidate(expr, options)) {
4231
+ return options.fieldTypes?.get(expr.left.field) === "NUMBER";
4232
+ }
4233
+ return isSelectionInComparison(expr, options);
4234
+ }
4235
+ var SELECTION_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
4236
+ "DROP_DOWN",
4237
+ "RADIO_BUTTON",
4238
+ "CHECK_BOX",
4239
+ "MULTI_SELECT",
4240
+ "STATUS"
4241
+ ]);
4242
+ function isSelectionInComparison(expr, options) {
4243
+ if (!isSelectionInCandidate(expr, options)) return false;
4244
+ if (expr.left.type !== "FIELD" || expr.right.type !== "IN_LIST") return false;
4245
+ const fieldType = options.fieldTypes?.get(expr.left.field);
4246
+ if (fieldType === void 0 || !SELECTION_IN_FIELD_TYPES.has(fieldType)) return false;
4247
+ const validOptions = options.fieldOptions?.get(expr.left.field);
4248
+ if (validOptions === void 0) return false;
4249
+ return expr.right.values.every(
4250
+ (value) => value.type === "STRING" && value.value !== "" && validOptions.has(value.value)
4251
+ );
4227
4252
  }
4228
4253
  function isSafeIdComparison(expr, options) {
4229
4254
  if (!isTargetIdField(expr.left, options)) return false;
@@ -4243,6 +4268,13 @@ function isNumericCandidate(expr, options) {
4243
4268
  if (expr.op === "=") return true;
4244
4269
  return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
4245
4270
  }
4271
+ function isSelectionInCandidate(expr, options) {
4272
+ if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
4273
+ if (!isTargetField(expr.left, options)) return false;
4274
+ if (expr.op !== "IN" && expr.op !== "NOT_IN") return false;
4275
+ if (expr.right.type !== "IN_LIST" || expr.right.values.length === 0) return false;
4276
+ return expr.right.values.every((value) => value.type === "STRING" && value.value !== "");
4277
+ }
4246
4278
  function isTargetField(field, options) {
4247
4279
  const targetAlias = options.tableAlias ?? null;
4248
4280
  if (field.tableAlias === targetAlias) return true;
@@ -4254,7 +4286,7 @@ function flatten(record, alias) {
4254
4286
  const row = {};
4255
4287
  for (const [field, fv] of Object.entries(record)) {
4256
4288
  const val = fv.value;
4257
- const strVal = typeof val === "string" ? val : JSON.stringify(val ?? "");
4289
+ const strVal = val == null ? "" : typeof val === "string" ? val : JSON.stringify(val);
4258
4290
  if (alias) {
4259
4291
  row[`${alias}.${field}`] = strVal;
4260
4292
  row[field] = strVal;
@@ -4856,6 +4888,7 @@ function createEmptyMetrics() {
4856
4888
  deleteCalls: 0,
4857
4889
  fieldCalls: 0,
4858
4890
  appsCalls: 0,
4891
+ processStatusCalls: 0,
4859
4892
  fetchedRows: 0,
4860
4893
  elapsedMs: 0
4861
4894
  };
@@ -4887,6 +4920,10 @@ function wrapClientWithMetrics(client, metrics) {
4887
4920
  getFields: (appId) => {
4888
4921
  metrics.fieldCalls += 1;
4889
4922
  return client.getFields(appId);
4923
+ },
4924
+ getProcessStatuses: (appId) => {
4925
+ metrics.processStatusCalls += 1;
4926
+ return client.getProcessStatuses(appId);
4890
4927
  }
4891
4928
  };
4892
4929
  }
@@ -5464,43 +5501,88 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
5464
5501
  }
5465
5502
  }
5466
5503
  }
5467
- function extractMainSafePushdown(stmt, fieldTypes) {
5504
+ function extractMainSafePushdown(stmt, fieldTypes, fieldOptions) {
5468
5505
  if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
5469
5506
  if (stmt.joins.length === 0) {
5470
5507
  return extractSafePushdownLeaves(stmt.where, {
5471
5508
  tableAlias: stmt.from.alias ?? void 0,
5472
5509
  allowUnqualifiedFields: true,
5473
- fieldTypes
5510
+ fieldTypes,
5511
+ fieldOptions
5474
5512
  });
5475
5513
  }
5476
5514
  if (!stmt.from.alias) return null;
5477
- return extractSafePushdownLeaves(stmt.where, { tableAlias: stmt.from.alias, fieldTypes });
5515
+ return extractSafePushdownLeaves(stmt.where, {
5516
+ tableAlias: stmt.from.alias,
5517
+ fieldTypes,
5518
+ fieldOptions
5519
+ });
5478
5520
  }
5479
- function extractMainNumericPushdownCandidate(stmt) {
5521
+ function extractMainTypedPushdownCandidate(stmt) {
5480
5522
  if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
5481
5523
  if (stmt.joins.length === 0) {
5482
- return extractNumericPushdownCandidates(stmt.where, {
5524
+ return extractTypedPushdownCandidates(stmt.where, {
5483
5525
  tableAlias: stmt.from.alias ?? void 0,
5484
5526
  allowUnqualifiedFields: true
5485
5527
  });
5486
5528
  }
5487
5529
  if (!stmt.from.alias) return null;
5488
- return extractNumericPushdownCandidates(stmt.where, { tableAlias: stmt.from.alias });
5489
- }
5490
- async function loadNumericPushdownFieldTypes(stmt, client, cacheContext) {
5491
- const appIds = /* @__PURE__ */ new Set();
5492
- if (extractMainNumericPushdownCandidate(stmt) !== null) appIds.add(stmt.from.appId);
5530
+ return extractTypedPushdownCandidates(stmt.where, { tableAlias: stmt.from.alias });
5531
+ }
5532
+ async function loadTypedPushdownMeta(stmt, client, cacheContext) {
5533
+ const candidatesByApp = /* @__PURE__ */ new Map();
5534
+ const addCandidate = (appId, candidate) => {
5535
+ if (candidate === null) return;
5536
+ const existing = candidatesByApp.get(appId);
5537
+ if (existing) existing.push(candidate);
5538
+ else candidatesByApp.set(appId, [candidate]);
5539
+ };
5540
+ addCandidate(stmt.from.appId, extractMainTypedPushdownCandidate(stmt));
5493
5541
  if (stmt.where !== null) {
5494
5542
  for (const join2 of stmt.joins) {
5495
5543
  if (!join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
5496
- const candidate = extractNumericPushdownCandidates(stmt.where, {
5544
+ const candidate = extractTypedPushdownCandidates(stmt.where, {
5497
5545
  tableAlias: join2.table.alias
5498
5546
  });
5499
- if (candidate !== null) appIds.add(join2.table.appId);
5547
+ addCandidate(join2.table.appId, candidate);
5500
5548
  }
5501
5549
  }
5502
- const entries = await Promise.all([...appIds].map(async (appId) => [appId, await getFieldTypeMap(appId, client, cacheContext)]));
5503
- return new Map(entries);
5550
+ const entries = await Promise.all([...candidatesByApp.entries()].map(async ([appId, candidates]) => {
5551
+ const [fieldTypes, fieldOptions] = await Promise.all([
5552
+ getFieldTypeMap(appId, client, cacheContext),
5553
+ getFieldOptionSetMapByApp(appId, client, cacheContext)
5554
+ ]);
5555
+ const statusFields = collectCandidateFieldCodes(candidates).filter((fieldCode) => fieldTypes.get(fieldCode) === "STATUS");
5556
+ if (statusFields.length > 0) {
5557
+ const process2 = await getProcessStatusesCached(appId, client, cacheContext);
5558
+ if (process2.enable && process2.states.length > 0) {
5559
+ const states = new Set(process2.states);
5560
+ for (const fieldCode of statusFields) fieldOptions.set(fieldCode, states);
5561
+ }
5562
+ }
5563
+ return [appId, fieldTypes, fieldOptions];
5564
+ }));
5565
+ return {
5566
+ fieldTypesByApp: new Map(entries.map(([appId, fieldTypes]) => [appId, fieldTypes])),
5567
+ fieldOptionsByApp: new Map(entries.map(([appId, , fieldOptions]) => [appId, fieldOptions]))
5568
+ };
5569
+ }
5570
+ function collectCandidateFieldCodes(candidates) {
5571
+ const fields = /* @__PURE__ */ new Set();
5572
+ const visit = (expr) => {
5573
+ if (expr.type === "BINARY") {
5574
+ if (expr.left.type === "FIELD") fields.add(expr.left.field);
5575
+ return;
5576
+ }
5577
+ if (expr.type === "LOGICAL") {
5578
+ visit(expr.left);
5579
+ visit(expr.right);
5580
+ return;
5581
+ }
5582
+ if (expr.type === "GROUP" || expr.type === "NOT") visit(expr.expr);
5583
+ };
5584
+ for (const candidate of candidates) visit(candidate);
5585
+ return [...fields];
5504
5586
  }
5505
5587
  function collectTypedInFieldRefs(expr, out) {
5506
5588
  if (expr === null) return;
@@ -5612,14 +5694,15 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
5612
5694
  resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
5613
5695
  resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
5614
5696
  ]);
5615
- const [pushdownFieldTypes, typedInFieldTypes] = await Promise.all([
5616
- loadNumericPushdownFieldTypes(stmt, client, cacheContext),
5697
+ const [pushdownMeta, typedInFieldTypes] = await Promise.all([
5698
+ loadTypedPushdownMeta(stmt, client, cacheContext),
5617
5699
  loadTypedInFieldTypes(stmt, client, cacheContext)
5618
5700
  ]);
5619
5701
  const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
5620
5702
  const mainPushDown = extractMainSafePushdown(
5621
5703
  stmt,
5622
- pushdownFieldTypes.get(stmt.from.appId)
5704
+ pushdownMeta.fieldTypesByApp.get(stmt.from.appId),
5705
+ pushdownMeta.fieldOptionsByApp.get(stmt.from.appId)
5623
5706
  );
5624
5707
  const tableConditions = /* @__PURE__ */ new Map();
5625
5708
  if (stmt.where !== null) {
@@ -5627,7 +5710,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
5627
5710
  if (!join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
5628
5711
  const cond = extractSafePushdownLeaves(stmt.where, {
5629
5712
  tableAlias: join2.table.alias,
5630
- fieldTypes: pushdownFieldTypes.get(join2.table.appId)
5713
+ fieldTypes: pushdownMeta.fieldTypesByApp.get(join2.table.appId),
5714
+ fieldOptions: pushdownMeta.fieldOptionsByApp.get(join2.table.appId)
5631
5715
  });
5632
5716
  if (cond) tableConditions.set(join2.table.alias, cond);
5633
5717
  }
@@ -6125,6 +6209,7 @@ var fieldTypeCache = /* @__PURE__ */ new Map();
6125
6209
  var optionOrderCache = /* @__PURE__ */ new Map();
6126
6210
  var sortKindCache = /* @__PURE__ */ new Map();
6127
6211
  var fieldInfoCache = /* @__PURE__ */ new Map();
6212
+ var processStatusCache = /* @__PURE__ */ new Map();
6128
6213
  function getScopedCacheValue(root, cacheContext, appId) {
6129
6214
  return root.get(cacheContext)?.get(appId);
6130
6215
  }
@@ -6146,6 +6231,13 @@ async function getFieldsCached(appId, client, cacheContext) {
6146
6231
  setScopedCacheValue(fieldInfoCache, cacheContext, appId, loading);
6147
6232
  return loading;
6148
6233
  }
6234
+ async function getProcessStatusesCached(appId, client, cacheContext) {
6235
+ const cached = getScopedCacheValue(processStatusCache, cacheContext, appId);
6236
+ if (cached) return cached;
6237
+ const loading = client.getProcessStatuses(appId);
6238
+ setScopedCacheValue(processStatusCache, cacheContext, appId, loading);
6239
+ return loading;
6240
+ }
6149
6241
  async function getFieldTypeMap(appId, client, cacheContext) {
6150
6242
  const cached = getScopedCacheValue(fieldTypeCache, cacheContext, appId);
6151
6243
  if (cached) return cached;
@@ -6168,6 +6260,15 @@ async function getOptionOrderMapByApp(appId, client, cacheContext) {
6168
6260
  setScopedCacheValue(optionOrderCache, cacheContext, appId, map);
6169
6261
  return map;
6170
6262
  }
6263
+ async function getFieldOptionSetMapByApp(appId, client, cacheContext) {
6264
+ const optionOrders = await getOptionOrderMapByApp(appId, client, cacheContext);
6265
+ return new Map(
6266
+ [...optionOrders.entries()].map(([fieldCode, order]) => [
6267
+ fieldCode,
6268
+ new Set(order.keys())
6269
+ ])
6270
+ );
6271
+ }
6171
6272
  async function getSortKindMapByApp(appId, client, cacheContext) {
6172
6273
  const cached = getScopedCacheValue(sortKindCache, cacheContext, appId);
6173
6274
  if (cached) return cached;
@@ -7127,12 +7228,12 @@ function buildSelectPlan(stmt, label) {
7127
7228
  const mainFields = selectToFetchAllFields(stmt, stmt.from);
7128
7229
  const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
7129
7230
  const mainPushDown = extractMainSafePushdown(stmt);
7130
- const mainCandidate = extractMainNumericPushdownCandidate(stmt);
7231
+ const mainCandidate = extractMainTypedPushdownCandidate(stmt);
7131
7232
  const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
7132
7233
  lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
7133
7234
  lines.push(` kintone query: ${mainQ}`);
7134
7235
  if (mainCandidate !== null) {
7135
- lines.push(` pushdown candidate: ${whereToKintone(mainCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u78BA\u8A8D\u5F85\u3061\uFF09`);
7236
+ lines.push(` pushdown candidate: ${whereToKintone(mainCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u30FB\u5B9F\u5728\u78BA\u8A8D\u5F85\u3061\uFF09`);
7136
7237
  }
7137
7238
  lines.push(` fields: ${mainFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : mainFields.join(", ")}`);
7138
7239
  for (const join2 of stmt.joins) {
@@ -7140,12 +7241,12 @@ function buildSelectPlan(stmt, label) {
7140
7241
  const joinAliasStr = join2.table.alias ? ` AS ${join2.table.alias}` : "";
7141
7242
  const joinType = join2.type === "INNER" ? "JOIN" : `${join2.type} JOIN`;
7142
7243
  const joinPushDown = join2.table.alias && !join2.table.subtableCode && join2.table.cteName === null && stmt.where ? extractSafePushdownLeaves(stmt.where, { tableAlias: join2.table.alias }) : null;
7143
- const joinCandidate = join2.table.alias && !join2.table.subtableCode && join2.table.cteName === null && stmt.where ? extractNumericPushdownCandidates(stmt.where, { tableAlias: join2.table.alias }) : null;
7244
+ const joinCandidate = join2.table.alias && !join2.table.subtableCode && join2.table.cteName === null && stmt.where ? extractTypedPushdownCandidates(stmt.where, { tableAlias: join2.table.alias }) : null;
7144
7245
  const joinQ = joinPushDown !== null ? whereToKintone(joinPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
7145
7246
  lines.push(` ${joinType}: APP${join2.table.appId}${joinAliasStr} (${join2.table.appId})`);
7146
7247
  lines.push(` kintone query: ${joinQ}`);
7147
7248
  if (joinCandidate !== null) {
7148
- lines.push(` pushdown candidate: ${whereToKintone(joinCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u78BA\u8A8D\u5F85\u3061\uFF09`);
7249
+ lines.push(` pushdown candidate: ${whereToKintone(joinCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u30FB\u5B9F\u5728\u78BA\u8A8D\u5F85\u3061\uFF09`);
7149
7250
  }
7150
7251
  lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
7151
7252
  }
@@ -7805,6 +7906,7 @@ function withRequestGate(client, gate) {
7805
7906
  getRecords: (params) => gate.runReadOnly(() => client.getRecords(params)),
7806
7907
  getApps: () => gate.runReadOnly(() => client.getApps()),
7807
7908
  getFields: (appId) => gate.runReadOnly(() => client.getFields(appId)),
7909
+ getProcessStatuses: (appId) => gate.runReadOnly(() => client.getProcessStatuses(appId)),
7808
7910
  postRecords: (params) => gate.runMutation(() => client.postRecords(params)),
7809
7911
  putRecords: (params) => gate.runMutation(() => client.putRecords(params)),
7810
7912
  deleteRecords: (params) => gate.runMutation(() => client.deleteRecords(params))
@@ -8039,6 +8141,16 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
8039
8141
  appId
8040
8142
  );
8041
8143
  return flattenFormFieldProperties(res.properties);
8144
+ },
8145
+ async getProcessStatuses(appId) {
8146
+ const qs = new URLSearchParams();
8147
+ qs.set("app", String(appId));
8148
+ qs.set("lang", "user");
8149
+ const res = await requestJson(`${apiBasePath}/app/status.json?${qs.toString()}`, { method: "GET" }, appId);
8150
+ return {
8151
+ enable: res.enable,
8152
+ states: Object.values(res.states ?? {}).map((state) => state.name)
8153
+ };
8042
8154
  }
8043
8155
  };
8044
8156
  }
@@ -9126,7 +9238,10 @@ function createDryRunClient() {
9126
9238
  putRecords: notUsed,
9127
9239
  deleteRecords: notUsed,
9128
9240
  getApps: notUsed,
9129
- getFields: notUsed
9241
+ getFields: notUsed,
9242
+ async getProcessStatuses() {
9243
+ return { enable: false, states: [] };
9244
+ }
9130
9245
  };
9131
9246
  }
9132
9247
  async function runDiagnosticRecordGet(params) {
@@ -10143,6 +10258,13 @@ async function run() {
10143
10258
  if (!routed) throw new Error(`AuthError: profile "${pName}" is not resolved for APP${appId}.`);
10144
10259
  return routed.getFields(binding.appId);
10145
10260
  },
10261
+ getProcessStatuses: (appId) => {
10262
+ const binding = appBindingByMappedApp.get(appId) ?? { appId, profile: profileName.toLowerCase() };
10263
+ const pName = binding.profile;
10264
+ const routed = profileClientMap.get(pName);
10265
+ if (!routed) throw new Error(`AuthError: profile "${pName}" is not resolved for APP${appId}.`);
10266
+ return routed.getProcessStatuses(binding.appId);
10267
+ },
10146
10268
  getApps: () => defaultClient.getApps()
10147
10269
  };
10148
10270
  }
@@ -34482,10 +34482,12 @@ function typedInContains(leftStr, values, fieldType) {
34482
34482
  if (!Array.isArray(parsed) || !parsed.every((item) => typeof item === "string")) {
34483
34483
  return fallback();
34484
34484
  }
34485
+ if (parsed.length === 0 && values.has("")) return true;
34485
34486
  return parsed.some((item) => values.has(item));
34486
34487
  }
34487
34488
  if (OBJECT_ARRAY_FIELD_TYPES.has(fieldType)) {
34488
34489
  if (!Array.isArray(parsed) || !parsed.every(hasStringCode)) return fallback();
34490
+ if (parsed.length === 0 && values.has("")) return true;
34489
34491
  return parsed.some((item) => values.has(item.code));
34490
34492
  }
34491
34493
  if (!hasStringCode(parsed)) return fallback();
@@ -35098,8 +35100,11 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
35098
35100
  function extractSafePushdownLeaves(where, options = {}) {
35099
35101
  return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
35100
35102
  }
35101
- function extractNumericPushdownCandidates(where, options = {}) {
35102
- return extractAndLeaves(where, (expr) => isNumericCandidate(expr, options));
35103
+ function extractTypedPushdownCandidates(where, options = {}) {
35104
+ return extractAndLeaves(
35105
+ where,
35106
+ (expr) => isNumericCandidate(expr, options) || isSelectionInCandidate(expr, options)
35107
+ );
35103
35108
  }
35104
35109
  function extractAndLeaves(where, accept) {
35105
35110
  switch (where.type) {
@@ -35123,8 +35128,28 @@ function extractAndLeaves(where, accept) {
35123
35128
  }
35124
35129
  function isSafeComparison(expr, options) {
35125
35130
  if (isSafeIdComparison(expr, options)) return true;
35126
- if (!isNumericCandidate(expr, options)) return false;
35127
- return options.fieldTypes?.get(expr.left.field) === "NUMBER";
35131
+ if (isNumericCandidate(expr, options)) {
35132
+ return options.fieldTypes?.get(expr.left.field) === "NUMBER";
35133
+ }
35134
+ return isSelectionInComparison(expr, options);
35135
+ }
35136
+ var SELECTION_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
35137
+ "DROP_DOWN",
35138
+ "RADIO_BUTTON",
35139
+ "CHECK_BOX",
35140
+ "MULTI_SELECT",
35141
+ "STATUS"
35142
+ ]);
35143
+ function isSelectionInComparison(expr, options) {
35144
+ if (!isSelectionInCandidate(expr, options)) return false;
35145
+ if (expr.left.type !== "FIELD" || expr.right.type !== "IN_LIST") return false;
35146
+ const fieldType = options.fieldTypes?.get(expr.left.field);
35147
+ if (fieldType === void 0 || !SELECTION_IN_FIELD_TYPES.has(fieldType)) return false;
35148
+ const validOptions = options.fieldOptions?.get(expr.left.field);
35149
+ if (validOptions === void 0) return false;
35150
+ return expr.right.values.every(
35151
+ (value) => value.type === "STRING" && value.value !== "" && validOptions.has(value.value)
35152
+ );
35128
35153
  }
35129
35154
  function isSafeIdComparison(expr, options) {
35130
35155
  if (!isTargetIdField(expr.left, options)) return false;
@@ -35144,6 +35169,13 @@ function isNumericCandidate(expr, options) {
35144
35169
  if (expr.op === "=") return true;
35145
35170
  return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
35146
35171
  }
35172
+ function isSelectionInCandidate(expr, options) {
35173
+ if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
35174
+ if (!isTargetField(expr.left, options)) return false;
35175
+ if (expr.op !== "IN" && expr.op !== "NOT_IN") return false;
35176
+ if (expr.right.type !== "IN_LIST" || expr.right.values.length === 0) return false;
35177
+ return expr.right.values.every((value) => value.type === "STRING" && value.value !== "");
35178
+ }
35147
35179
  function isTargetField(field, options) {
35148
35180
  const targetAlias = options.tableAlias ?? null;
35149
35181
  if (field.tableAlias === targetAlias) return true;
@@ -35155,7 +35187,7 @@ function flatten(record2, alias) {
35155
35187
  const row = {};
35156
35188
  for (const [field, fv] of Object.entries(record2)) {
35157
35189
  const val = fv.value;
35158
- const strVal = typeof val === "string" ? val : JSON.stringify(val ?? "");
35190
+ const strVal = val == null ? "" : typeof val === "string" ? val : JSON.stringify(val);
35159
35191
  if (alias) {
35160
35192
  row[`${alias}.${field}`] = strVal;
35161
35193
  row[field] = strVal;
@@ -35757,6 +35789,7 @@ function createEmptyMetrics() {
35757
35789
  deleteCalls: 0,
35758
35790
  fieldCalls: 0,
35759
35791
  appsCalls: 0,
35792
+ processStatusCalls: 0,
35760
35793
  fetchedRows: 0,
35761
35794
  elapsedMs: 0
35762
35795
  };
@@ -35788,6 +35821,10 @@ function wrapClientWithMetrics(client, metrics) {
35788
35821
  getFields: (appId) => {
35789
35822
  metrics.fieldCalls += 1;
35790
35823
  return client.getFields(appId);
35824
+ },
35825
+ getProcessStatuses: (appId) => {
35826
+ metrics.processStatusCalls += 1;
35827
+ return client.getProcessStatuses(appId);
35791
35828
  }
35792
35829
  };
35793
35830
  }
@@ -36365,43 +36402,88 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
36365
36402
  }
36366
36403
  }
36367
36404
  }
36368
- function extractMainSafePushdown(stmt, fieldTypes) {
36405
+ function extractMainSafePushdown(stmt, fieldTypes, fieldOptions) {
36369
36406
  if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
36370
36407
  if (stmt.joins.length === 0) {
36371
36408
  return extractSafePushdownLeaves(stmt.where, {
36372
36409
  tableAlias: stmt.from.alias ?? void 0,
36373
36410
  allowUnqualifiedFields: true,
36374
- fieldTypes
36411
+ fieldTypes,
36412
+ fieldOptions
36375
36413
  });
36376
36414
  }
36377
36415
  if (!stmt.from.alias) return null;
36378
- return extractSafePushdownLeaves(stmt.where, { tableAlias: stmt.from.alias, fieldTypes });
36416
+ return extractSafePushdownLeaves(stmt.where, {
36417
+ tableAlias: stmt.from.alias,
36418
+ fieldTypes,
36419
+ fieldOptions
36420
+ });
36379
36421
  }
36380
- function extractMainNumericPushdownCandidate(stmt) {
36422
+ function extractMainTypedPushdownCandidate(stmt) {
36381
36423
  if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
36382
36424
  if (stmt.joins.length === 0) {
36383
- return extractNumericPushdownCandidates(stmt.where, {
36425
+ return extractTypedPushdownCandidates(stmt.where, {
36384
36426
  tableAlias: stmt.from.alias ?? void 0,
36385
36427
  allowUnqualifiedFields: true
36386
36428
  });
36387
36429
  }
36388
36430
  if (!stmt.from.alias) return null;
36389
- return extractNumericPushdownCandidates(stmt.where, { tableAlias: stmt.from.alias });
36431
+ return extractTypedPushdownCandidates(stmt.where, { tableAlias: stmt.from.alias });
36390
36432
  }
36391
- async function loadNumericPushdownFieldTypes(stmt, client, cacheContext) {
36392
- const appIds = /* @__PURE__ */ new Set();
36393
- if (extractMainNumericPushdownCandidate(stmt) !== null) appIds.add(stmt.from.appId);
36433
+ async function loadTypedPushdownMeta(stmt, client, cacheContext) {
36434
+ const candidatesByApp = /* @__PURE__ */ new Map();
36435
+ const addCandidate = (appId, candidate) => {
36436
+ if (candidate === null) return;
36437
+ const existing = candidatesByApp.get(appId);
36438
+ if (existing) existing.push(candidate);
36439
+ else candidatesByApp.set(appId, [candidate]);
36440
+ };
36441
+ addCandidate(stmt.from.appId, extractMainTypedPushdownCandidate(stmt));
36394
36442
  if (stmt.where !== null) {
36395
36443
  for (const join of stmt.joins) {
36396
36444
  if (!join.table.alias || join.table.subtableCode || join.table.cteName !== null) continue;
36397
- const candidate = extractNumericPushdownCandidates(stmt.where, {
36445
+ const candidate = extractTypedPushdownCandidates(stmt.where, {
36398
36446
  tableAlias: join.table.alias
36399
36447
  });
36400
- if (candidate !== null) appIds.add(join.table.appId);
36448
+ addCandidate(join.table.appId, candidate);
36401
36449
  }
36402
36450
  }
36403
- const entries = await Promise.all([...appIds].map(async (appId) => [appId, await getFieldTypeMap(appId, client, cacheContext)]));
36404
- return new Map(entries);
36451
+ const entries = await Promise.all([...candidatesByApp.entries()].map(async ([appId, candidates]) => {
36452
+ const [fieldTypes, fieldOptions] = await Promise.all([
36453
+ getFieldTypeMap(appId, client, cacheContext),
36454
+ getFieldOptionSetMapByApp(appId, client, cacheContext)
36455
+ ]);
36456
+ const statusFields = collectCandidateFieldCodes(candidates).filter((fieldCode) => fieldTypes.get(fieldCode) === "STATUS");
36457
+ if (statusFields.length > 0) {
36458
+ const process4 = await getProcessStatusesCached(appId, client, cacheContext);
36459
+ if (process4.enable && process4.states.length > 0) {
36460
+ const states = new Set(process4.states);
36461
+ for (const fieldCode of statusFields) fieldOptions.set(fieldCode, states);
36462
+ }
36463
+ }
36464
+ return [appId, fieldTypes, fieldOptions];
36465
+ }));
36466
+ return {
36467
+ fieldTypesByApp: new Map(entries.map(([appId, fieldTypes]) => [appId, fieldTypes])),
36468
+ fieldOptionsByApp: new Map(entries.map(([appId, , fieldOptions]) => [appId, fieldOptions]))
36469
+ };
36470
+ }
36471
+ function collectCandidateFieldCodes(candidates) {
36472
+ const fields = /* @__PURE__ */ new Set();
36473
+ const visit = (expr) => {
36474
+ if (expr.type === "BINARY") {
36475
+ if (expr.left.type === "FIELD") fields.add(expr.left.field);
36476
+ return;
36477
+ }
36478
+ if (expr.type === "LOGICAL") {
36479
+ visit(expr.left);
36480
+ visit(expr.right);
36481
+ return;
36482
+ }
36483
+ if (expr.type === "GROUP" || expr.type === "NOT") visit(expr.expr);
36484
+ };
36485
+ for (const candidate of candidates) visit(candidate);
36486
+ return [...fields];
36405
36487
  }
36406
36488
  function collectTypedInFieldRefs(expr, out) {
36407
36489
  if (expr === null) return;
@@ -36513,14 +36595,15 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
36513
36595
  resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
36514
36596
  resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
36515
36597
  ]);
36516
- const [pushdownFieldTypes, typedInFieldTypes] = await Promise.all([
36517
- loadNumericPushdownFieldTypes(stmt, client, cacheContext),
36598
+ const [pushdownMeta, typedInFieldTypes] = await Promise.all([
36599
+ loadTypedPushdownMeta(stmt, client, cacheContext),
36518
36600
  loadTypedInFieldTypes(stmt, client, cacheContext)
36519
36601
  ]);
36520
36602
  const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
36521
36603
  const mainPushDown = extractMainSafePushdown(
36522
36604
  stmt,
36523
- pushdownFieldTypes.get(stmt.from.appId)
36605
+ pushdownMeta.fieldTypesByApp.get(stmt.from.appId),
36606
+ pushdownMeta.fieldOptionsByApp.get(stmt.from.appId)
36524
36607
  );
36525
36608
  const tableConditions = /* @__PURE__ */ new Map();
36526
36609
  if (stmt.where !== null) {
@@ -36528,7 +36611,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
36528
36611
  if (!join.table.alias || join.table.subtableCode || join.table.cteName !== null) continue;
36529
36612
  const cond = extractSafePushdownLeaves(stmt.where, {
36530
36613
  tableAlias: join.table.alias,
36531
- fieldTypes: pushdownFieldTypes.get(join.table.appId)
36614
+ fieldTypes: pushdownMeta.fieldTypesByApp.get(join.table.appId),
36615
+ fieldOptions: pushdownMeta.fieldOptionsByApp.get(join.table.appId)
36532
36616
  });
36533
36617
  if (cond) tableConditions.set(join.table.alias, cond);
36534
36618
  }
@@ -37026,6 +37110,7 @@ var fieldTypeCache = /* @__PURE__ */ new Map();
37026
37110
  var optionOrderCache = /* @__PURE__ */ new Map();
37027
37111
  var sortKindCache = /* @__PURE__ */ new Map();
37028
37112
  var fieldInfoCache = /* @__PURE__ */ new Map();
37113
+ var processStatusCache = /* @__PURE__ */ new Map();
37029
37114
  function getScopedCacheValue(root, cacheContext, appId) {
37030
37115
  return root.get(cacheContext)?.get(appId);
37031
37116
  }
@@ -37047,6 +37132,13 @@ async function getFieldsCached(appId, client, cacheContext) {
37047
37132
  setScopedCacheValue(fieldInfoCache, cacheContext, appId, loading);
37048
37133
  return loading;
37049
37134
  }
37135
+ async function getProcessStatusesCached(appId, client, cacheContext) {
37136
+ const cached2 = getScopedCacheValue(processStatusCache, cacheContext, appId);
37137
+ if (cached2) return cached2;
37138
+ const loading = client.getProcessStatuses(appId);
37139
+ setScopedCacheValue(processStatusCache, cacheContext, appId, loading);
37140
+ return loading;
37141
+ }
37050
37142
  async function getFieldTypeMap(appId, client, cacheContext) {
37051
37143
  const cached2 = getScopedCacheValue(fieldTypeCache, cacheContext, appId);
37052
37144
  if (cached2) return cached2;
@@ -37069,6 +37161,15 @@ async function getOptionOrderMapByApp(appId, client, cacheContext) {
37069
37161
  setScopedCacheValue(optionOrderCache, cacheContext, appId, map2);
37070
37162
  return map2;
37071
37163
  }
37164
+ async function getFieldOptionSetMapByApp(appId, client, cacheContext) {
37165
+ const optionOrders = await getOptionOrderMapByApp(appId, client, cacheContext);
37166
+ return new Map(
37167
+ [...optionOrders.entries()].map(([fieldCode, order]) => [
37168
+ fieldCode,
37169
+ new Set(order.keys())
37170
+ ])
37171
+ );
37172
+ }
37072
37173
  async function getSortKindMapByApp(appId, client, cacheContext) {
37073
37174
  const cached2 = getScopedCacheValue(sortKindCache, cacheContext, appId);
37074
37175
  if (cached2) return cached2;
@@ -38028,12 +38129,12 @@ function buildSelectPlan(stmt, label) {
38028
38129
  const mainFields = selectToFetchAllFields(stmt, stmt.from);
38029
38130
  const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
38030
38131
  const mainPushDown = extractMainSafePushdown(stmt);
38031
- const mainCandidate = extractMainNumericPushdownCandidate(stmt);
38132
+ const mainCandidate = extractMainTypedPushdownCandidate(stmt);
38032
38133
  const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
38033
38134
  lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
38034
38135
  lines.push(` kintone query: ${mainQ}`);
38035
38136
  if (mainCandidate !== null) {
38036
- lines.push(` pushdown candidate: ${whereToKintone(mainCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u78BA\u8A8D\u5F85\u3061\uFF09`);
38137
+ lines.push(` pushdown candidate: ${whereToKintone(mainCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u30FB\u5B9F\u5728\u78BA\u8A8D\u5F85\u3061\uFF09`);
38037
38138
  }
38038
38139
  lines.push(` fields: ${mainFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : mainFields.join(", ")}`);
38039
38140
  for (const join of stmt.joins) {
@@ -38041,12 +38142,12 @@ function buildSelectPlan(stmt, label) {
38041
38142
  const joinAliasStr = join.table.alias ? ` AS ${join.table.alias}` : "";
38042
38143
  const joinType = join.type === "INNER" ? "JOIN" : `${join.type} JOIN`;
38043
38144
  const joinPushDown = join.table.alias && !join.table.subtableCode && join.table.cteName === null && stmt.where ? extractSafePushdownLeaves(stmt.where, { tableAlias: join.table.alias }) : null;
38044
- const joinCandidate = join.table.alias && !join.table.subtableCode && join.table.cteName === null && stmt.where ? extractNumericPushdownCandidates(stmt.where, { tableAlias: join.table.alias }) : null;
38145
+ const joinCandidate = join.table.alias && !join.table.subtableCode && join.table.cteName === null && stmt.where ? extractTypedPushdownCandidates(stmt.where, { tableAlias: join.table.alias }) : null;
38045
38146
  const joinQ = joinPushDown !== null ? whereToKintone(joinPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
38046
38147
  lines.push(` ${joinType}: APP${join.table.appId}${joinAliasStr} (${join.table.appId})`);
38047
38148
  lines.push(` kintone query: ${joinQ}`);
38048
38149
  if (joinCandidate !== null) {
38049
- lines.push(` pushdown candidate: ${whereToKintone(joinCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u78BA\u8A8D\u5F85\u3061\uFF09`);
38150
+ lines.push(` pushdown candidate: ${whereToKintone(joinCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u30FB\u5B9F\u5728\u78BA\u8A8D\u5F85\u3061\uFF09`);
38050
38151
  }
38051
38152
  lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
38052
38153
  }
@@ -38699,6 +38800,7 @@ function withRequestGate(client, gate) {
38699
38800
  getRecords: (params) => gate.runReadOnly(() => client.getRecords(params)),
38700
38801
  getApps: () => gate.runReadOnly(() => client.getApps()),
38701
38802
  getFields: (appId) => gate.runReadOnly(() => client.getFields(appId)),
38803
+ getProcessStatuses: (appId) => gate.runReadOnly(() => client.getProcessStatuses(appId)),
38702
38804
  postRecords: (params) => gate.runMutation(() => client.postRecords(params)),
38703
38805
  putRecords: (params) => gate.runMutation(() => client.putRecords(params)),
38704
38806
  deleteRecords: (params) => gate.runMutation(() => client.deleteRecords(params))
@@ -38933,6 +39035,16 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
38933
39035
  appId
38934
39036
  );
38935
39037
  return flattenFormFieldProperties(res.properties);
39038
+ },
39039
+ async getProcessStatuses(appId) {
39040
+ const qs = new URLSearchParams();
39041
+ qs.set("app", String(appId));
39042
+ qs.set("lang", "user");
39043
+ const res = await requestJson(`${apiBasePath}/app/status.json?${qs.toString()}`, { method: "GET" }, appId);
39044
+ return {
39045
+ enable: res.enable,
39046
+ states: Object.values(res.states ?? {}).map((state) => state.name)
39047
+ };
38936
39048
  }
38937
39049
  };
38938
39050
  }
@@ -39440,6 +39552,12 @@ async function createKsqlRuntime(serverOptions, input) {
39440
39552
  if (!routed) throw new Error(`AuthError: profile "${binding.profile}" is not resolved for APP${appId}.`);
39441
39553
  return routed.getFields(binding.appId);
39442
39554
  },
39555
+ getProcessStatuses: (appId) => {
39556
+ const binding = resolveRuntimeBinding(runtimeContext.sqlContext, appId);
39557
+ const routed = runtimeContext.clientsByProfile.get(binding.profile);
39558
+ if (!routed) throw new Error(`AuthError: profile "${binding.profile}" is not resolved for APP${appId}.`);
39559
+ return routed.getProcessStatuses(binding.appId);
39560
+ },
39443
39561
  getApps: () => defaultClient.getApps()
39444
39562
  };
39445
39563
  const gatedClient = withRequestGate(
@@ -39664,7 +39782,10 @@ function noOpClient() {
39664
39782
  putRecords: fail,
39665
39783
  deleteRecords: fail,
39666
39784
  getApps: fail,
39667
- getFields: fail
39785
+ getFields: fail,
39786
+ async getProcessStatuses() {
39787
+ return { enable: false, states: [] };
39788
+ }
39668
39789
  };
39669
39790
  }
39670
39791
  function getServerConfigPath(serverOptions) {
@@ -40395,7 +40516,7 @@ Options:
40395
40516
  -h, --help Show help
40396
40517
  `);
40397
40518
  }
40398
- var SERVER_VERSION = true ? "2.5.0" : "0.0.0-dev";
40519
+ var SERVER_VERSION = true ? "2.7.0" : "0.0.0-dev";
40399
40520
  function createServer(args) {
40400
40521
  const server = new McpServer({
40401
40522
  name: "ksql-mcp",
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rex0220/kintone-sql-tools",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "kintone SQL plugin, CLI, and MCP tools (ksql)",
5
5
  "publishConfig": {
6
6
  "access": "public"