@rex0220/kintone-sql-tools 3.75.0 → 3.76.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
@@ -267,7 +267,7 @@ npm パッケージは 2 つのサブパスを **semver 対象の公開 API**
267
267
  | サブパス | 用途 | 主な export |
268
268
  |---|---|---|
269
269
  | `@rex0220/kintone-sql-tools/engine` | **read-only** のクエリ実行(ダッシュボード等)。書込 API は構造的に遮断 | `runQuery` / `runBatch` / `explainQuery` / `createReadonlyKintoneClient` / `KsqlEngineError` / `version` |
270
- | `@rex0220/kintone-sql-tools/flow` | **Flow dialect 1**(→ [言語リファレンス §27](docs/ksql_language_reference.md))のスクリプト解析・検証・**文単位実行**(バッチランナー向け・書込可能) | `parseScript` / `validateScript` / `explainScript`(`asOf`/`timezone` 注入可) / `createExecutionContext`(`onChunkWritten` 書込チャンク通知) / `executeStatement` / `previewStatement`(dry-run 差分プレビュー・書込 0 回) / `disposeExecutionContext` / `createImportSourceResolver` / `FlowImportProviderError` / `createKintoneClient` / `isDmlResult`(`FlowDmlResult` 型ガード) / `version` |
270
+ | `@rex0220/kintone-sql-tools/flow` | **Flow dialect 1**(→ [言語リファレンス §27](docs/ksql_language_reference.md))のスクリプト解析・検証・**文単位実行**(バッチランナー向け・書込可能) | `parseScript` / `validateScript` / `explainScript`(`asOf`/`timezone` 注入可) / `createExecutionContext`(`onChunkWritten` 書込チャンク通知・`onImportSourceMaterialized` IMPORT receipt) / `executeStatement` / `previewStatement`(dry-run 差分プレビュー・書込 0 回) / `disposeExecutionContext` / `createImportSourceResolver` / `FlowImportProviderError` / `createKintoneClient` / `isDmlResult`(`FlowDmlResult` 型ガード) / `version` |
271
271
 
272
272
  バッチ実行ランナー **kSQL Flow**(`/flow` API を使った公式ランナー・別リポジトリ): https://github.com/rex0220/ksql-flow
273
273
 
@@ -308,6 +308,10 @@ const importSource = createImportSourceResolver([{
308
308
  const parsed = parseScript(sql, capability);
309
309
  const ctx = createExecutionContext({
310
310
  ...capability, client, statements: parsed.statements, meta: parsed.meta, importSource,
311
+ onImportSourceMaterialized(info) {
312
+ // { statementIndex, name, kind, rows, encoding }
313
+ inputFiles.push(info);
314
+ },
311
315
  });
312
316
  ```
313
317
 
@@ -317,6 +321,13 @@ path解決、通常ファイル・symlink・allowlist・hash検査、open/read
317
321
  engineはpathを受け取りません。providerは `FlowImportProviderError` でread不能または通常ファイル外を
318
322
  分類でき、その他のsource境界エラーも `StatementResult.error.code` の安定codeで返ります。
319
323
 
324
+ `onImportSourceMaterialized` はdecode・raw materialize成功直後、projection・validation・
325
+ `ON ERROR SKIP` の選別・mutationより前に1回awaitされます。CSVの `rows` はheaderを除く
326
+ RFC 4180 data record数(subtable CSVは継続行を含む)、JSONはtop-level record数です。
327
+ CSVの `encoding` は上記優先順位の解決後、JSONは `"utf8"` です。callbackがthrow/rejectすると
328
+ 当該文はerrorになり、その文のmutation APIは0回です。通知objectのkeyは
329
+ `statementIndex` / `name` / `kind` / `rows` / `encoding` の5つだけです。
330
+
320
331
  読取上限は既定 10,000 件(`maxRecords`)です。一時テーブルの実体化には**独立の** `tempTableMaxRows`(既定 10,000 行・超過は常にエラー)が適用されるため、大きなバッチでは両方を併せて指定してください:
321
332
 
322
333
  ```ts
package/dist-cli/ksql.js CHANGED
@@ -18046,6 +18046,7 @@ function materializeJsonDmlSource(_source, payload, targets, maxRows) {
18046
18046
  importPresence.push(present);
18047
18047
  });
18048
18048
  return {
18049
+ receipt: { rows: records.length, encoding: "utf8" },
18049
18050
  rows,
18050
18051
  columns: targets.map((target) => target.code),
18051
18052
  columnMeta: new Map(targets.map((target) => [target.code, { fieldType: target.fieldType }])),
@@ -18055,8 +18056,9 @@ function materializeJsonDmlSource(_source, payload, targets, maxRows) {
18055
18056
 
18056
18057
  // src/import/materializeDmlSource.ts
18057
18058
  function materializeCsvDmlSource(source, payload, maxRows, targetCodes, fieldInfos, recordNumberSourceHeader) {
18059
+ const encoding = source.encoding ?? payload.encoding ?? "utf8";
18058
18060
  const decoded = decodeCsv(payload.bytes, {
18059
- encoding: source.encoding ?? payload.encoding ?? "utf8",
18061
+ encoding,
18060
18062
  hasHeader: source.hasHeader,
18061
18063
  columns: source.columns
18062
18064
  });
@@ -18121,6 +18123,7 @@ function materializeCsvDmlSource(source, payload, maxRows, targetCodes, fieldInf
18121
18123
  return row;
18122
18124
  });
18123
18125
  return {
18126
+ receipt: { rows: decoded.rows.length, encoding },
18124
18127
  rows: rows2,
18125
18128
  columns: [...targetCodes],
18126
18129
  columnMeta: new Map(targetCodes.map((code) => [code, { fieldType: infoByCode.get(code)?.fieldType ?? "SINGLE_LINE_TEXT" }])),
@@ -18131,6 +18134,7 @@ function materializeCsvDmlSource(source, payload, maxRows, targetCodes, fieldInf
18131
18134
  }
18132
18135
  const rows = decoded.rows.map((values) => Object.fromEntries(decoded.columns.map((column, i) => [column, values[i]])));
18133
18136
  return {
18137
+ receipt: { rows: decoded.rows.length, encoding },
18134
18138
  rows,
18135
18139
  columns: decoded.columns,
18136
18140
  // CSV cells are decoded as strings; projections such as CAST may replace this metadata downstream.
@@ -18150,6 +18154,7 @@ function materializeJsonImportRecords(_source, payload, targets, maxParents, max
18150
18154
  if (targetByCode.size !== targets.length) throw new ImportSourceError("IMPORT targets contain duplicates.");
18151
18155
  let childTotal = 0;
18152
18156
  return {
18157
+ receipt: { rows: decoded.length, encoding: "utf8" },
18153
18158
  records: decoded.map((record, index) => {
18154
18159
  const parentRow = index + 1;
18155
18160
  for (const code of record.keys()) if (!targetByCode.has(code)) sourceFail(parentRow, code, "unknown key (not declared in INTO).");
@@ -18185,8 +18190,9 @@ function materializeJsonImportRecords(_source, payload, targets, maxParents, max
18185
18190
  };
18186
18191
  }
18187
18192
  function materializeCliKintoneCsvImportRecords(source, payload, targets, replacementTables, maxParents, recordNumberSourceHeader) {
18193
+ const encoding = source.encoding ?? payload.encoding ?? "utf8";
18188
18194
  const decoded = decodeCsv(payload.bytes, {
18189
- encoding: source.encoding ?? payload.encoding ?? "utf8",
18195
+ encoding,
18190
18196
  hasHeader: source.hasHeader,
18191
18197
  columns: source.columns
18192
18198
  });
@@ -18242,7 +18248,7 @@ function materializeCliKintoneCsvImportRecords(source, payload, targets, replace
18242
18248
  }
18243
18249
  });
18244
18250
  if (records.length === 0) throw new ImportSourceError("CSV has no parent rows.");
18245
- return { records };
18251
+ return { receipt: { rows: decoded.rows.length, encoding }, records };
18246
18252
  }
18247
18253
 
18248
18254
  // src/import/importRecordValidation.ts
@@ -18533,6 +18539,7 @@ var materializedMetaBySelectResult = /* @__PURE__ */ new WeakMap();
18533
18539
  var materializedMetaByValidationResult = /* @__PURE__ */ new WeakMap();
18534
18540
  var importSourceByDmlStatement = /* @__PURE__ */ new WeakMap();
18535
18541
  var statementEvaluationContextKey = /* @__PURE__ */ Symbol("statementEvaluationContext");
18542
+ var importSourceMaterializedCallbackKey = /* @__PURE__ */ Symbol("importSourceMaterializedCallback");
18536
18543
  var nativeUpsertExecutionKey = /* @__PURE__ */ Symbol("nativeUpsertExecution");
18537
18544
  var nativeUpsertExplainCapabilityKey = /* @__PURE__ */ Symbol("nativeUpsertExplainCapability");
18538
18545
  function withNativeUpsertExecutionOption(options, enabled, explainClientCapability) {
@@ -18562,6 +18569,10 @@ function bindStatementEvaluationContext(options) {
18562
18569
  function statementEvaluationContext(options) {
18563
18570
  return options[statementEvaluationContextKey] ?? {};
18564
18571
  }
18572
+ async function notifyImportSourceMaterialized(options, source, receipt) {
18573
+ const callback = options[importSourceMaterializedCallbackKey];
18574
+ if (callback) await callback({ name: source.sourceName, kind: source.kind, ...receipt });
18575
+ }
18565
18576
  var defaultCacheContextByClient = /* @__PURE__ */ new WeakMap();
18566
18577
  var nextDefaultCacheContextId = 1;
18567
18578
  var nextCacheInvocationId = 1;
@@ -24772,6 +24783,7 @@ async function executeImport(stmt, client, options, cacheContext, tempTables) {
24772
24783
  const numberPrecision = fieldInfos.some((info) => targetCodes.includes(info.code) && info.fieldType === "NUMBER") ? await getNumberPrecisionCached(stmt.appId, client, cacheContext) : void 0;
24773
24784
  const payload = await loadImportSource(handle, /* @__PURE__ */ new Map());
24774
24785
  const materialized = stmt.source.kind === "JSON" ? materializeJsonImportRecords(stmt.source, payload, targets, options.maxRecords ?? 1e4) : materializeCliKintoneCsvImportRecords(stmt.source, payload, targets, stmt.replaceSubtables ?? [], options.maxRecords ?? 1e4, stmt.recordNumberSourceHeader);
24786
+ await notifyImportSourceMaterialized(options, stmt.source, materialized.receipt);
24775
24787
  const operation = stmt.source.kind === "CSV" ? "UPDATE" : stmt.keyFields ? "UPSERT" : "INSERT";
24776
24788
  const prepared = prepareImportRecords(materialized, targets, fieldInfos, numberPrecision, operation);
24777
24789
  if (stmt.source.kind === "CSV") return executeCsvSubtableReplacement(stmt, materialized, prepared, fieldInfos, client, options, tempTables);
@@ -25074,6 +25086,7 @@ async function executeImportRecordNumberUpdate(stmt, handle, client, options, ca
25074
25086
  fieldInfos,
25075
25087
  stmt.recordNumberSourceHeader
25076
25088
  );
25089
+ await notifyImportSourceMaterialized(options, stmt.source, sourceTable.receipt);
25077
25090
  const keyValues = sourceTable.recordNumberSourceValues;
25078
25091
  if (!keyValues) throw new Error("InternalError: record-number source values were not materialized.");
25079
25092
  const keyPlan = preflightImportRecordNumbers(keyValues, stmt.recordNumberSourceHeader);
@@ -25210,6 +25223,7 @@ async function materializeDmlSource(stmt, client, options, cacheContext, tempTab
25210
25223
  const targetByCode = new Map((targetFields ?? []).map((info) => [info.code, info]));
25211
25224
  const jsonTargets = stmt.fields.map((code) => ({ code, fieldType: targetByCode.get(code)?.fieldType ?? "SINGLE_LINE_TEXT" }));
25212
25225
  const raw = imported.source.kind === "JSON" ? materializeJsonDmlSource(imported.source, payload, jsonTargets, rowLimit) : materializeCsvDmlSource(imported.source, payload, rowLimit, stmt.fields, targetFields);
25226
+ await notifyImportSourceMaterialized(options, imported.source, raw.receipt);
25213
25227
  imported.audit = raw.importAudit;
25214
25228
  if (imported.source.kind === "JSON") return raw;
25215
25229
  if (!imported.source.projection) return raw;