@rex0220/kintone-sql-tools 3.28.0 → 3.30.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/README.md CHANGED
@@ -7,8 +7,14 @@ kintone アプリを SQL 風の構文で操作するツールセットです。
7
7
  - MCP サーバー(AI クライアントから kintone を SQL 操作。Claude Desktop 用 MCPB 同梱)
8
8
  - read-only エンジン・ライブラリ(ESM / CJS / UMD)
9
9
 
10
- 他の kintone プラグインやカスタマイズへ read-only kSQL エンジンを組み込む場合は、
11
- [エンジン・ライブラリ利用ガイド](docs/ksql_engine_library.md)を参照してください。
10
+ 他の kintone プラグインやカスタマイズへ read-only kSQL エンジンを組み込む場合は、
11
+ [エンジン・ライブラリ利用ガイド](docs/ksql_engine_library.md)を参照してください。
12
+
13
+ engine ライブラリでは、`runQuery()` が単文の `SELECT` / `WITH` / `UNION` /
14
+ `SHOW APPS` / `DESCRIBE` / 既存レコード `VALIDATE` を、`runBatch()` がそれらに加えて
15
+ `CREATE` / `DROP TEMP TABLE`、`SET` / `DECLARE`、`ASSERT`、`EXPLAIN` を実行します。
16
+ 書き込み DML、DML `VALIDATE ONLY`、`IMPORT`、`APPLY` は対象外です。
17
+ 生成 AI が MCP で作った SQL を library で実行する場合は、この API 別の境界を確認してください。
12
18
 
13
19
  ## 機能概要
14
20
 
package/dist-cli/ksql.js CHANGED
@@ -5341,20 +5341,36 @@ function caseResultHasAggregate(result) {
5341
5341
  if (result.type === "ARRAY" || result.type === "FIELD_REF" || result.type === "ARITH") return false;
5342
5342
  return scalarValueHasAggregate(result);
5343
5343
  }
5344
- function collectRequiredFieldsByTable(stmt, plainGroupByPlan) {
5344
+ function collectSelectFieldReferencesBySource(stmt, plainGroupByPlan) {
5345
+ const unqualified = /* @__PURE__ */ new Set();
5346
+ const states = collectRequiredFieldsByTable(stmt, plainGroupByPlan, {
5347
+ includeMaterialized: true,
5348
+ unqualified
5349
+ });
5350
+ return {
5351
+ bySource: new Map(
5352
+ [...states.entries()].map(([table, state]) => [table, new Set(state.fields)])
5353
+ ),
5354
+ unqualified
5355
+ };
5356
+ }
5357
+ function collectRequiredFieldsByTable(stmt, plainGroupByPlan, sourceAware) {
5345
5358
  const allTables = [stmt.from, ...stmt.joins.map((j) => j.table)];
5346
5359
  const physicalTables = [stmt.from, ...stmt.joins.map((j) => j.table)].filter((t) => t.cteName === null);
5360
+ const targetTables = sourceAware ? allTables : physicalTables;
5347
5361
  const states = /* @__PURE__ */ new Map();
5348
- for (const table of physicalTables) {
5362
+ for (const table of targetTables) {
5349
5363
  states.set(table, { table, allFields: false, fields: /* @__PURE__ */ new Set() });
5350
5364
  }
5351
5365
  if (states.size === 0) return states;
5352
- const firstPhysicalTable = physicalTables[0] ?? null;
5353
- const subtableTable = physicalTables.find((t) => !!t.subtableCode) ?? null;
5366
+ const firstTargetTable = targetTables[0] ?? null;
5367
+ const subtableTable = targetTables.find((t) => !!t.subtableCode) ?? null;
5354
5368
  const aliasToTable = /* @__PURE__ */ new Map();
5355
- for (const table of physicalTables) {
5369
+ for (const table of targetTables) {
5356
5370
  if (table.alias) aliasToTable.set(table.alias, table);
5357
- if (!table.subtableCode) {
5371
+ if (table.cteName !== null) {
5372
+ aliasToTable.set(table.cteName, table);
5373
+ } else if (!table.subtableCode) {
5358
5374
  aliasToTable.set(`APP${table.appId}`, table);
5359
5375
  aliasToTable.set(`app${table.appId}`, table);
5360
5376
  }
@@ -5366,11 +5382,11 @@ function collectRequiredFieldsByTable(stmt, plainGroupByPlan) {
5366
5382
  st.allFields = true;
5367
5383
  st.fields.clear();
5368
5384
  };
5369
- const markAllPhysicalTables = () => {
5370
- for (const t of physicalTables) markAll(t);
5385
+ const markAllTargetTables = () => {
5386
+ for (const t of targetTables) markAll(t);
5371
5387
  };
5372
5388
  const markAllSubtableTables = () => {
5373
- for (const t of physicalTables) {
5389
+ for (const t of targetTables) {
5374
5390
  if (t.subtableCode) markAll(t);
5375
5391
  }
5376
5392
  };
@@ -5400,7 +5416,7 @@ function collectRequiredFieldsByTable(stmt, plainGroupByPlan) {
5400
5416
  if (rawName.endsWith(".*")) {
5401
5417
  const qualifier = rawName.slice(0, -2);
5402
5418
  if (!qualifier) {
5403
- markAllPhysicalTables();
5419
+ markAllTargetTables();
5404
5420
  return;
5405
5421
  }
5406
5422
  if (qualifier === "_p") {
@@ -5417,7 +5433,7 @@ function collectRequiredFieldsByTable(stmt, plainGroupByPlan) {
5417
5433
  if (phase === "groupBy" && groupResolution !== void 0) {
5418
5434
  if (groupResolution.kind === "PHYSICAL") {
5419
5435
  const source = allTables[groupResolution.sourceIndex];
5420
- if (source?.cteName === null) {
5436
+ if (source && (sourceAware || source.cteName === null)) {
5421
5437
  addFieldToTable(source, groupResolution.fieldCode);
5422
5438
  }
5423
5439
  }
@@ -5442,8 +5458,13 @@ function collectRequiredFieldsByTable(stmt, plainGroupByPlan) {
5442
5458
  addFieldToTable(target, field);
5443
5459
  return;
5444
5460
  }
5461
+ if (sourceAware) return;
5462
+ }
5463
+ if (sourceAware) {
5464
+ sourceAware.unqualified.add(rawName);
5465
+ } else if (firstTargetTable) {
5466
+ addFieldToTable(firstTargetTable, rawName);
5445
5467
  }
5446
- if (firstPhysicalTable) addFieldToTable(firstPhysicalTable, rawName);
5447
5468
  };
5448
5469
  const addFieldRef = (field, tableAlias, phase = "select") => {
5449
5470
  if (tableAlias) {
@@ -5456,6 +5477,7 @@ function collectRequiredFieldsByTable(stmt, plainGroupByPlan) {
5456
5477
  addFieldToTable(target, field);
5457
5478
  return;
5458
5479
  }
5480
+ if (sourceAware) return;
5459
5481
  }
5460
5482
  addFieldName(field, phase);
5461
5483
  };
@@ -5611,7 +5633,7 @@ function collectRequiredFieldsByTable(stmt, plainGroupByPlan) {
5611
5633
  for (const col of stmt.columns) {
5612
5634
  switch (col.type) {
5613
5635
  case "WILDCARD":
5614
- markAllPhysicalTables();
5636
+ markAllTargetTables();
5615
5637
  break;
5616
5638
  case "PARENT_WILDCARD":
5617
5639
  markAllSubtableTables();
@@ -9490,6 +9512,20 @@ function renderValidationValue(value) {
9490
9512
  }
9491
9513
 
9492
9514
  // src/core/existingRecordValidation.ts
9515
+ var VALIDATE_CONSTRAINT_CATEGORIES = [
9516
+ "required",
9517
+ "length",
9518
+ "range",
9519
+ "choice"
9520
+ ];
9521
+ function getAuditableConstraintCategories(field) {
9522
+ return [
9523
+ ...field.required === true ? ["required"] : [],
9524
+ ...field.minLength !== void 0 || field.maxLength !== void 0 ? ["length"] : [],
9525
+ ...field.minValue !== void 0 || field.maxValue !== void 0 ? ["range"] : [],
9526
+ ...field.optionOrder !== void 0 ? ["choice"] : []
9527
+ ];
9528
+ }
9493
9529
  function buildValidationFieldMetadataIndex(fieldInfos) {
9494
9530
  const topLevel = fieldInfos.filter((field) => !field.inSubtable);
9495
9531
  const childrenByTable = /* @__PURE__ */ new Map();
@@ -9506,7 +9542,7 @@ function buildValidationFieldMetadataIndex(fieldInfos) {
9506
9542
  };
9507
9543
  }
9508
9544
  function hasAuditableConstraint(field) {
9509
- return field.required === true || field.minValue !== void 0 || field.maxValue !== void 0 || field.minLength !== void 0 || field.maxLength !== void 0 || field.optionOrder !== void 0;
9545
+ return getAuditableConstraintCategories(field).length > 0;
9510
9546
  }
9511
9547
  function isExistingValidationAuditable(field) {
9512
9548
  return field.fieldType === "NUMBER" || hasAuditableConstraint(field);
@@ -15629,6 +15665,13 @@ async function executeExistingRecordValidationCore(stmt, client, options, cacheC
15629
15665
  const infoByCode = new Map(fieldInfos.filter((field) => !field.inSubtable).map((field) => [field.code, field]));
15630
15666
  const childCodes = new Set(fieldInfos.filter((field) => field.inSubtable).map((field) => field.code));
15631
15667
  const targets = resolveExistingValidationTargets(stmt, fieldInfos);
15668
+ const presentConstraintCategories = new Set(
15669
+ targets.flatMap((target) => getAuditableConstraintCategories(target.field))
15670
+ );
15671
+ const constraintMetadata = {
15672
+ present: VALIDATE_CONSTRAINT_CATEGORIES.filter((category) => presentConstraintCategories.has(category)),
15673
+ absent: VALIDATE_CONSTRAINT_CATEGORIES.filter((category) => !presentConstraintCategories.has(category))
15674
+ };
15632
15675
  const checkGroups = stmt.checkGroups ?? [];
15633
15676
  const checkRefs2 = collectCheckFieldRefs(checkGroups);
15634
15677
  for (const ref of checkRefs2) {
@@ -15785,7 +15828,11 @@ async function executeExistingRecordValidationCore(stmt, client, options, cacheC
15785
15828
  columns: [...columns],
15786
15829
  rows,
15787
15830
  rowCount: rows.length,
15788
- validateStats: { errorRecords: errorRecordIds.size, errorCount }
15831
+ validateStats: {
15832
+ errorRecords: errorRecordIds.size,
15833
+ errorCount,
15834
+ constraintMetadata
15835
+ }
15789
15836
  };
15790
15837
  materializedMetaBySelectResult.set(result, existingValidationColumnMeta(stmt.summary === true));
15791
15838
  return result;
@@ -16158,21 +16205,28 @@ async function runWithDeadline(work, remainingMs, onTimeout) {
16158
16205
  }
16159
16206
  }
16160
16207
  function toBatchStatementError(e) {
16208
+ let error;
16161
16209
  if (e instanceof ApplyWritePartialFailureError) {
16162
- return { code: e.name, message: e.message, partialSuccess: e.partialSuccess };
16163
- }
16164
- if (e instanceof Error) {
16210
+ error = { code: e.name, message: e.message, partialSuccess: e.partialSuccess };
16211
+ } else if (e instanceof Error) {
16165
16212
  const name = e.name !== "Error" ? e.name : null;
16166
- return { code: name ?? codeFromMessagePrefix(e.message), message: e.message };
16167
- }
16168
- if (e !== null && typeof e === "object") {
16213
+ error = { code: name ?? codeFromMessagePrefix(e.message), message: e.message };
16214
+ } else if (e !== null && typeof e === "object") {
16169
16215
  const obj = e;
16170
- const message2 = typeof obj.message === "string" && obj.message.length > 0 ? obj.message : safeJsonStringify(e);
16171
- const code = typeof obj.code === "string" && obj.code.length > 0 ? obj.code : codeFromMessagePrefix(message2);
16172
- return { code, message: message2 };
16216
+ const message = typeof obj.message === "string" && obj.message.length > 0 ? obj.message : safeJsonStringify(e);
16217
+ const code = typeof obj.code === "string" && obj.code.length > 0 ? obj.code : codeFromMessagePrefix(message);
16218
+ error = { code, message };
16219
+ } else {
16220
+ const message = String(e);
16221
+ error = { code: codeFromMessagePrefix(message), message };
16173
16222
  }
16174
- const message = String(e);
16175
- return { code: codeFromMessagePrefix(message), message };
16223
+ Object.defineProperty(error, "cause", {
16224
+ value: e,
16225
+ enumerable: false,
16226
+ configurable: false,
16227
+ writable: false
16228
+ });
16229
+ return error;
16176
16230
  }
16177
16231
  function codeFromMessagePrefix(message) {
16178
16232
  return message.match(/^([A-Za-z]+Error):/)?.[1] ?? "Error";
@@ -17086,6 +17140,159 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
17086
17140
  }
17087
17141
  }
17088
17142
  }
17143
+ function b86SourceLabel(table) {
17144
+ return table.cteName ?? `APP${table.appId}`;
17145
+ }
17146
+ function b86SourceAliases(table) {
17147
+ if (table.alias) return [table.alias];
17148
+ if (table.cteName !== null) return [table.cteName];
17149
+ return [`APP${table.appId}`, `app${table.appId}`];
17150
+ }
17151
+ function b86PhysicalFieldExists(schema, field) {
17152
+ return isSystemLikeFieldCode(field) || schema.validCodes.has(field);
17153
+ }
17154
+ function b86FieldExists(schema, field) {
17155
+ if (schema.table.cteName !== null) return schema.validCodes.has(field);
17156
+ return b86PhysicalFieldExists(schema, field);
17157
+ }
17158
+ function collectB86Subqueries(stmt) {
17159
+ const queries = [];
17160
+ const visitWhere = (where) => {
17161
+ if (where === null) return;
17162
+ switch (where.type) {
17163
+ case "BINARY":
17164
+ if (where.right.type === "SUBQUERY_IN_LIST" || where.right.type === "SCALAR_SUBQUERY") {
17165
+ queries.push(where.right.query);
17166
+ }
17167
+ return;
17168
+ case "EXISTS":
17169
+ queries.push(where.query);
17170
+ return;
17171
+ case "LOGICAL":
17172
+ visitWhere(where.left);
17173
+ visitWhere(where.right);
17174
+ return;
17175
+ case "NOT":
17176
+ case "GROUP":
17177
+ visitWhere(where.expr);
17178
+ return;
17179
+ case "NULL_CHECK":
17180
+ case "BOOLEAN":
17181
+ return;
17182
+ }
17183
+ };
17184
+ visitWhere(stmt.where);
17185
+ visitWhere(stmt.having);
17186
+ for (const column of stmt.columns) {
17187
+ if (column.type === "SCALAR_SUBQUERY_COL") {
17188
+ queries.push(column.query);
17189
+ } else if (column.type === "CASE_COL") {
17190
+ for (const branch of column.expr.branches) visitWhere(branch.condition);
17191
+ }
17192
+ }
17193
+ return queries;
17194
+ }
17195
+ async function validateB86SelectFieldCodes(stmt, client, cteCache, cacheContext) {
17196
+ const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
17197
+ const materializedByTable = /* @__PURE__ */ new Map();
17198
+ const effectiveAliases = /* @__PURE__ */ new Set();
17199
+ for (const table of tables) {
17200
+ const alias = effectiveTableAlias(table);
17201
+ if (alias === null) continue;
17202
+ if (effectiveAliases.has(alias)) {
17203
+ throw new Error(`ArgumentError: effective alias ${alias} is used by multiple tables.`);
17204
+ }
17205
+ effectiveAliases.add(alias);
17206
+ }
17207
+ for (const table of tables) {
17208
+ if (table.cteName === null || table.cteName === NO_FROM_CTE_NAME) continue;
17209
+ const materialized = cteCache.get(table.cteName);
17210
+ if (!materialized) {
17211
+ throw new Error(`ArgumentError: materialized source ${table.cteName} is not available.`);
17212
+ }
17213
+ if (materialized.rows.length > 0 && materialized.columns.length === 0) {
17214
+ throw new Error(
17215
+ `InternalError: materialized source ${table.cteName} has rows but no column schema.`
17216
+ );
17217
+ }
17218
+ if (stmt.joins.length > 0 && materialized.rows.length === 0 && materialized.columns.length === 0) {
17219
+ throw new Error(
17220
+ `ArgumentError: column schema is unavailable for materialized JOIN source ${table.cteName}.`
17221
+ );
17222
+ }
17223
+ materializedByTable.set(table, materialized);
17224
+ }
17225
+ const schemas = /* @__PURE__ */ new Map();
17226
+ await Promise.all(tables.map(async (table) => {
17227
+ const materialized = materializedByTable.get(table);
17228
+ if (materialized) {
17229
+ schemas.set(table, {
17230
+ table,
17231
+ label: b86SourceLabel(table),
17232
+ validCodes: new Set(materialized.columns),
17233
+ authoritative: true,
17234
+ schemaUnavailable: materialized.rows.length === 0 && materialized.columns.length === 0
17235
+ });
17236
+ return;
17237
+ }
17238
+ if (table.cteName === NO_FROM_CTE_NAME) return;
17239
+ const defs = await getFieldsCached(table.appId, client, cacheContext);
17240
+ schemas.set(table, {
17241
+ table,
17242
+ label: b86SourceLabel(table),
17243
+ validCodes: new Set(defs.map((def) => def.code)),
17244
+ authoritative: defs.length > 0,
17245
+ schemaUnavailable: false
17246
+ });
17247
+ }));
17248
+ const sourceByAlias = /* @__PURE__ */ new Map();
17249
+ for (const schema of schemas.values()) {
17250
+ for (const alias of b86SourceAliases(schema.table)) sourceByAlias.set(alias, schema);
17251
+ }
17252
+ for (const join2 of stmt.joins) {
17253
+ for (const ref of [join2.on.left, join2.on.right]) {
17254
+ if (!ref.tableAlias) continue;
17255
+ const schema = sourceByAlias.get(ref.tableAlias);
17256
+ if (schema && schema.table.cteName !== null && !schema.schemaUnavailable && !b86FieldExists(schema, ref.field)) {
17257
+ throw new Error(
17258
+ `ArgumentError: JOIN key ${ref.tableAlias}.${ref.field} is not available in the materialized table.`
17259
+ );
17260
+ }
17261
+ }
17262
+ }
17263
+ const references = collectSelectFieldReferencesBySource(stmt);
17264
+ for (const [table, fields] of references.bySource) {
17265
+ const schema = schemas.get(table);
17266
+ if (!schema || schema.schemaUnavailable || !schema.authoritative) continue;
17267
+ const unknown = [...fields].filter((field) => !b86FieldExists(schema, field));
17268
+ if (unknown.length > 0) {
17269
+ throw new Error(
17270
+ `ArgumentError: unknown field code(s): ${unknown.join(", ")} (${schema.label})`
17271
+ );
17272
+ }
17273
+ }
17274
+ for (const field of references.unqualified) {
17275
+ const candidates = [...schemas.values()].filter((schema) => !schema.schemaUnavailable);
17276
+ if (candidates.some((schema) => b86FieldExists(schema, field))) continue;
17277
+ if ([...schemas.values()].some((schema) => schema.schemaUnavailable)) continue;
17278
+ if (candidates.some((schema) => schema.table.cteName === null && !schema.authoritative)) continue;
17279
+ const labels = candidates.map((schema) => schema.label).join(", ");
17280
+ throw new Error(`ArgumentError: unknown field code(s): ${field} (${labels})`);
17281
+ }
17282
+ }
17283
+ async function preflightB86QueryWithCte(query, client, cteCache, cacheContext, seen = /* @__PURE__ */ new Set()) {
17284
+ if (seen.has(query)) return;
17285
+ seen.add(query);
17286
+ if (query.type === "UNION") {
17287
+ await preflightB86QueryWithCte(query.left, client, cteCache, cacheContext, seen);
17288
+ await preflightB86QueryWithCte(query.right, client, cteCache, cacheContext, seen);
17289
+ return;
17290
+ }
17291
+ await validateB86SelectFieldCodes(query, client, cteCache, cacheContext);
17292
+ for (const subquery of collectB86Subqueries(query)) {
17293
+ await preflightB86QueryWithCte(subquery, client, cteCache, cacheContext, seen);
17294
+ }
17295
+ }
17089
17296
  function extractMainTypedPushdownCandidate(stmt) {
17090
17297
  if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
17091
17298
  if (stmt.joins.length === 0) {
@@ -17947,11 +18154,14 @@ async function executeWith(stmt, client, options, cacheContext, seed, captureCol
17947
18154
  }
17948
18155
  return executeQueryWithCte(stmt.query, client, options, cteCache, cacheContext, captureColumnMeta);
17949
18156
  }
17950
- async function executeQueryWithCte(query, client, options, cteCache, cacheContext, captureColumnMeta = false) {
18157
+ async function executeQueryWithCte(query, client, options, cteCache, cacheContext, captureColumnMeta = false, b86PreflightComplete = false) {
18158
+ if (!b86PreflightComplete) {
18159
+ await preflightB86QueryWithCte(query, client, cteCache, cacheContext);
18160
+ }
17951
18161
  if (query.type === "UNION") {
17952
18162
  const [leftResult, rightResult] = await Promise.all([
17953
- executeQueryWithCte(query.left, client, options, cteCache, cacheContext, captureColumnMeta),
17954
- executeQueryWithCte(query.right, client, options, cteCache, cacheContext, captureColumnMeta)
18163
+ executeQueryWithCte(query.left, client, options, cteCache, cacheContext, captureColumnMeta, true),
18164
+ executeQueryWithCte(query.right, client, options, cteCache, cacheContext, captureColumnMeta, true)
17955
18165
  ]);
17956
18166
  const leftCols = leftResult.columns;
17957
18167
  const rightCols = rightResult.columns;
@@ -0,0 +1,13 @@
1
+ import type { BatchResult, RunBatchOptions } from "./publicTypes";
2
+ /**
3
+ * Executes a read-only multi-statement batch.
4
+ *
5
+ * In addition to the row-returning statements accepted by runQuery, this API
6
+ * accepts CREATE/DROP TEMP TABLE, SET, DECLARE, ASSERT, and EXPLAIN. IMPORT,
7
+ * APPLY, DML VALIDATE ONLY, and writing DML remain outside the library boundary.
8
+ *
9
+ * If any statement fails, this function throws KsqlEngineError without
10
+ * returning partial results. statementIndex and statementType identify the
11
+ * failed statement.
12
+ */
13
+ export declare function runBatch(sql: string, options: RunBatchOptions): Promise<BatchResult>;
@@ -1,8 +1,13 @@
1
1
  export declare class KsqlEngineError extends Error {
2
2
  readonly code: "PARSE_ERROR" | "READ_ONLY_VIOLATION" | "SEARCH_ABORTED" | "FETCH_LIMIT_EXCEEDED" | "CLIENT_ERROR" | "EXECUTION_ERROR";
3
3
  readonly cause?: unknown;
4
+ /** Zero-based index of the failed statement when raised by runBatch. */
5
+ readonly statementIndex?: number;
6
+ /** Parser statement type of the failed statement when raised by runBatch. */
7
+ readonly statementType?: string;
4
8
  constructor(code: KsqlEngineError["code"], message: string, cause?: unknown);
5
9
  }
10
+ export declare function withStatementDiagnostic(error: KsqlEngineError, statementIndex: number, statementType: string): KsqlEngineError;
6
11
  export declare function readOnlyViolation(message: string): KsqlEngineError;
7
12
  export declare function parseError(message: string, cause?: unknown): KsqlEngineError;
8
13
  export declare function searchAborted(): KsqlEngineError;