@rex0220/kintone-sql-tools 3.73.0 → 3.75.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 +29 -1
- package/dist-cli/ksql.js +144 -14
- package/dist-engine/index.cjs +14 -14
- package/dist-engine/index.mjs +14 -14
- package/dist-engine/ksql-engine.umd.js +14 -14
- package/dist-engine/meta/bundle-baseline.json +7 -7
- package/dist-engine/meta/cjs.json +6 -6
- package/dist-engine/meta/esm.json +6 -6
- package/dist-engine/meta/umd.json +6 -6
- package/dist-flow/flow-library/importSources.d.ts +7 -0
- package/dist-flow/flow-library/index.d.ts +3 -2
- package/dist-flow/flow-library/publicTypes.d.ts +19 -0
- package/dist-flow/index.cjs +11 -11
- package/dist-flow/index.mjs +11 -11
- package/dist-flow/meta/cjs.json +30 -11
- package/dist-flow/meta/esm.json +32 -11
- package/dist-mcp/ksql-mcp.js +110 -14
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
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` / `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` 書込チャンク通知) / `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
|
|
|
@@ -289,6 +289,34 @@ try {
|
|
|
289
289
|
}
|
|
290
290
|
```
|
|
291
291
|
|
|
292
|
+
`/flow` から named `IMPORT` source を使う場合は、全APIへ `enableImport: true` を明示し、
|
|
293
|
+
pathではなくlazy loaderが返す `Uint8Array` を登録します。省略または `false` は従来どおり
|
|
294
|
+
`KSQL1202` で拒否し、resolverを渡すだけでは有効になりません。
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
import {
|
|
298
|
+
createImportSourceResolver,
|
|
299
|
+
parseScript,
|
|
300
|
+
createExecutionContext,
|
|
301
|
+
} from "@rex0220/kintone-sql-tools/flow";
|
|
302
|
+
|
|
303
|
+
const capability = { enableImport: true } as const;
|
|
304
|
+
const importSource = createImportSourceResolver([{
|
|
305
|
+
name: "orders",
|
|
306
|
+
loader: { async load() { return { bytes, encoding: "sjis" }; } },
|
|
307
|
+
}]);
|
|
308
|
+
const parsed = parseScript(sql, capability);
|
|
309
|
+
const ctx = createExecutionContext({
|
|
310
|
+
...capability, client, statements: parsed.statements, meta: parsed.meta, importSource,
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
source名は完全一致・case-sensitiveです。loaderは対象文の実行到達時だけ呼ばれます。
|
|
315
|
+
CSVの文字コードは `SQL ENCODING` > loader metadata > UTF-8、payload上限は10 MiBです。
|
|
316
|
+
path解決、通常ファイル・symlink・allowlist・hash検査、open/readは呼出側の責務であり、
|
|
317
|
+
engineはpathを受け取りません。providerは `FlowImportProviderError` でread不能または通常ファイル外を
|
|
318
|
+
分類でき、その他のsource境界エラーも `StatementResult.error.code` の安定codeで返ります。
|
|
319
|
+
|
|
292
320
|
読取上限は既定 10,000 件(`maxRecords`)です。一時テーブルの実体化には**独立の** `tempTableMaxRows`(既定 10,000 行・超過は常にエラー)が適用されるため、大きなバッチでは両方を併せて指定してください:
|
|
293
321
|
|
|
294
322
|
```ts
|
package/dist-cli/ksql.js
CHANGED
|
@@ -17606,19 +17606,86 @@ var ImportSourceError = class extends Error {
|
|
|
17606
17606
|
this.name = "ImportSourceError";
|
|
17607
17607
|
}
|
|
17608
17608
|
};
|
|
17609
|
+
var ImportSourceBoundaryError = class extends Error {
|
|
17610
|
+
constructor(code, message, cause) {
|
|
17611
|
+
super(`${code}: ${message}`);
|
|
17612
|
+
this.name = code;
|
|
17613
|
+
this.code = code;
|
|
17614
|
+
if (cause !== void 0) {
|
|
17615
|
+
Object.defineProperty(this, "cause", {
|
|
17616
|
+
value: cause,
|
|
17617
|
+
enumerable: false,
|
|
17618
|
+
configurable: false,
|
|
17619
|
+
writable: false
|
|
17620
|
+
});
|
|
17621
|
+
}
|
|
17622
|
+
}
|
|
17623
|
+
};
|
|
17624
|
+
function providerError(error) {
|
|
17625
|
+
if (error === null || typeof error !== "object") return void 0;
|
|
17626
|
+
const shaped = error;
|
|
17627
|
+
const code = shaped.code === "ImportSourceReadError" || shaped.code === "ImportSourceNotRegularFileError" ? shaped.code : shaped.name === "ImportSourceReadError" || shaped.name === "ImportSourceNotRegularFileError" ? shaped.name : void 0;
|
|
17628
|
+
if (!code) return void 0;
|
|
17629
|
+
const fallback = code === "ImportSourceReadError" ? "IMPORT source could not be read." : "IMPORT source is not a regular file.";
|
|
17630
|
+
return new ImportSourceBoundaryError(
|
|
17631
|
+
code,
|
|
17632
|
+
typeof shaped.message === "string" && shaped.message.length > 0 ? shaped.message : fallback,
|
|
17633
|
+
error
|
|
17634
|
+
);
|
|
17635
|
+
}
|
|
17636
|
+
function isUint8Array(value) {
|
|
17637
|
+
return value !== null && typeof value === "object" && ArrayBuffer.isView(value) && Object.prototype.toString.call(value) === "[object Uint8Array]";
|
|
17638
|
+
}
|
|
17609
17639
|
function resolveImportSource(name, resolver) {
|
|
17610
|
-
if (!resolver)
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17640
|
+
if (!resolver) {
|
|
17641
|
+
throw new ImportSourceBoundaryError(
|
|
17642
|
+
"ImportSourceNotSuppliedError",
|
|
17643
|
+
`the named IMPORT source ${JSON.stringify(name)} was not supplied.`
|
|
17644
|
+
);
|
|
17645
|
+
}
|
|
17646
|
+
try {
|
|
17647
|
+
const handle = resolver(name);
|
|
17648
|
+
if (!handle) {
|
|
17649
|
+
throw new ImportSourceBoundaryError(
|
|
17650
|
+
"ImportSourceNotSuppliedError",
|
|
17651
|
+
`the named IMPORT source ${JSON.stringify(name)} was not supplied.`
|
|
17652
|
+
);
|
|
17653
|
+
}
|
|
17654
|
+
if (typeof handle !== "object" || typeof handle.load !== "function") {
|
|
17655
|
+
throw new ImportSourceBoundaryError(
|
|
17656
|
+
"ImportSourceInvalidPayloadError",
|
|
17657
|
+
"resolver must return a handle with a load function."
|
|
17658
|
+
);
|
|
17659
|
+
}
|
|
17660
|
+
return handle;
|
|
17661
|
+
} catch (error) {
|
|
17662
|
+
if (error instanceof ImportSourceBoundaryError) throw error;
|
|
17663
|
+
throw providerError(error) ?? new ImportSourceBoundaryError("ImportSourceReadError", "IMPORT source resolution failed.", error);
|
|
17664
|
+
}
|
|
17614
17665
|
}
|
|
17615
17666
|
async function loadImportSource(handle, cache) {
|
|
17616
17667
|
let pending = cache.get(handle);
|
|
17617
17668
|
if (!pending) {
|
|
17618
|
-
pending = handle.load().
|
|
17619
|
-
|
|
17669
|
+
pending = Promise.resolve().then(() => handle.load()).catch((error) => {
|
|
17670
|
+
throw providerError(error) ?? new ImportSourceBoundaryError("ImportSourceReadError", "IMPORT source could not be read.", error);
|
|
17671
|
+
}).then((payload) => {
|
|
17672
|
+
if (payload === null || typeof payload !== "object" || !isUint8Array(payload.bytes)) {
|
|
17673
|
+
throw new ImportSourceBoundaryError(
|
|
17674
|
+
"ImportSourceInvalidPayloadError",
|
|
17675
|
+
"loader must return Uint8Array bytes."
|
|
17676
|
+
);
|
|
17677
|
+
}
|
|
17678
|
+
if (payload.encoding !== void 0 && payload.encoding !== "utf8" && payload.encoding !== "sjis") {
|
|
17679
|
+
throw new ImportSourceBoundaryError(
|
|
17680
|
+
"ImportSourceInvalidPayloadError",
|
|
17681
|
+
"loader returned an unsupported encoding."
|
|
17682
|
+
);
|
|
17683
|
+
}
|
|
17620
17684
|
if (payload.bytes.byteLength > IMPORT_MAX_BYTES) {
|
|
17621
|
-
throw new
|
|
17685
|
+
throw new ImportSourceBoundaryError(
|
|
17686
|
+
"ImportSourceTooLargeError",
|
|
17687
|
+
`source exceeds the ${IMPORT_MAX_BYTES} byte limit.`
|
|
17688
|
+
);
|
|
17622
17689
|
}
|
|
17623
17690
|
return payload;
|
|
17624
17691
|
});
|
|
@@ -18837,6 +18904,7 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
18837
18904
|
options.recursiveCteMaxDepth,
|
|
18838
18905
|
options.recursiveCteMaxRows,
|
|
18839
18906
|
options.recursiveCteMaxExpansions,
|
|
18907
|
+
options.resolveMetadata !== false,
|
|
18840
18908
|
hasNativeUpsertExecutionOption(options) ? {
|
|
18841
18909
|
surface: "CLI",
|
|
18842
18910
|
enableNativeUpsert: nativeUpsertExecutionEnabled(options),
|
|
@@ -26784,6 +26852,10 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
26784
26852
|
return;
|
|
26785
26853
|
}
|
|
26786
26854
|
const typed = node;
|
|
26855
|
+
if (typed["type"] === "UPSERT" || typed["type"] === "UPSERT_SELECT") {
|
|
26856
|
+
const upsert = node;
|
|
26857
|
+
await getFieldsCached(upsert.appId, tracedClient, cacheContext);
|
|
26858
|
+
}
|
|
26787
26859
|
if (typed["type"] === "SELECT") {
|
|
26788
26860
|
const select = node;
|
|
26789
26861
|
await validateSelectGroupingPlanning(select, tracedClient, cacheContext);
|
|
@@ -27827,21 +27899,29 @@ var explainMaterializedTables = /* @__PURE__ */ new WeakMap();
|
|
|
27827
27899
|
function defaultRecursiveExplainContext() {
|
|
27828
27900
|
return { maxRecords: 1e4, recursiveLimits: resolveRecursiveCteLimits({}) };
|
|
27829
27901
|
}
|
|
27830
|
-
async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxActive, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, relativeDatePlan, recursiveCteMaxDepth, recursiveCteMaxRows, recursiveCteMaxExpansions, nativeUpsertOptions = { surface: "DOCUMENT_ONLY" }) {
|
|
27902
|
+
async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxActive, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, relativeDatePlan, recursiveCteMaxDepth, recursiveCteMaxRows, recursiveCteMaxExpansions, resolveMetadata = true, nativeUpsertOptions = { surface: "DOCUMENT_ONLY" }) {
|
|
27831
27903
|
const recursiveLimits = resolveRecursiveCteLimits({
|
|
27832
27904
|
recursiveCteMaxDepth,
|
|
27833
27905
|
recursiveCteMaxRows,
|
|
27834
27906
|
recursiveCteMaxExpansions
|
|
27835
27907
|
});
|
|
27836
27908
|
const sharedPlan = relativeDatePlan ?? await resolveRelativeDateExecutionPlan(stmt.query, client, cacheContext);
|
|
27837
|
-
const analysis = await buildExplainWhereAnalysis(
|
|
27909
|
+
const analysis = resolveMetadata ? await buildExplainWhereAnalysis(
|
|
27838
27910
|
stmt.query,
|
|
27839
27911
|
client,
|
|
27840
27912
|
cacheContext,
|
|
27841
27913
|
maxRecords,
|
|
27842
27914
|
sharedPlan,
|
|
27843
27915
|
explainMaterializedTables.get(stmt)
|
|
27844
|
-
)
|
|
27916
|
+
) : {
|
|
27917
|
+
capabilities: /* @__PURE__ */ new Map(),
|
|
27918
|
+
orderPlans: /* @__PURE__ */ new Map(),
|
|
27919
|
+
plainGroupByPlans: /* @__PURE__ */ new Map(),
|
|
27920
|
+
fieldApps: /* @__PURE__ */ new Set(),
|
|
27921
|
+
processStatusApps: /* @__PURE__ */ new Set(),
|
|
27922
|
+
numberPrecisionApps: /* @__PURE__ */ new Set(),
|
|
27923
|
+
relativeDatePlan: sharedPlan
|
|
27924
|
+
};
|
|
27845
27925
|
const fetchCollector = { sources: [] };
|
|
27846
27926
|
const relativeLines = relativeDateExplainLines(sharedPlan);
|
|
27847
27927
|
const planLines = sharedPlan.hasServerOnlyWhereFunction && !sharedPlan.allowed ? [...explainMetadataLines(analysis), ...relativeLines] : [
|
|
@@ -29159,6 +29239,23 @@ function isSubtableRow(v) {
|
|
|
29159
29239
|
return typeof obj.id === "string" && typeof obj.value === "object" && obj.value !== null;
|
|
29160
29240
|
}
|
|
29161
29241
|
|
|
29242
|
+
// src/flow-library/importSources.ts
|
|
29243
|
+
var FlowImportProviderError = class extends Error {
|
|
29244
|
+
constructor(code, message, cause) {
|
|
29245
|
+
super(message);
|
|
29246
|
+
this.name = code;
|
|
29247
|
+
this.code = code;
|
|
29248
|
+
if (cause !== void 0) {
|
|
29249
|
+
Object.defineProperty(this, "cause", {
|
|
29250
|
+
value: cause,
|
|
29251
|
+
enumerable: false,
|
|
29252
|
+
configurable: false,
|
|
29253
|
+
writable: false
|
|
29254
|
+
});
|
|
29255
|
+
}
|
|
29256
|
+
}
|
|
29257
|
+
};
|
|
29258
|
+
|
|
29162
29259
|
// src/output/batchEnvelope.ts
|
|
29163
29260
|
function toMutationSummary(result) {
|
|
29164
29261
|
if (result.type === "INSERT") {
|
|
@@ -32626,7 +32723,8 @@ async function run() {
|
|
|
32626
32723
|
appProfileByApp.set(appId, appBindingByMappedApp.get(appId)?.profile ?? profileName.toLowerCase());
|
|
32627
32724
|
}
|
|
32628
32725
|
const cacheContext = buildCacheContext(profileName, appBindingByMappedApp);
|
|
32629
|
-
|
|
32726
|
+
const fullyOfflineDryRun = args.dryRun && (!dryRunNeedsMetadata || dryRunUsesStaticTypedPlan);
|
|
32727
|
+
if (fullyOfflineDryRun) {
|
|
32630
32728
|
client = createDryRunClient();
|
|
32631
32729
|
} else {
|
|
32632
32730
|
for (const explicitProfile of appProfileByApp.values()) {
|
|
@@ -32894,7 +32992,7 @@ async function run() {
|
|
|
32894
32992
|
Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0,
|
|
32895
32993
|
dmlMaxRows,
|
|
32896
32994
|
dmlMaxSubtableRows,
|
|
32897
|
-
!
|
|
32995
|
+
!fullyOfflineDryRun,
|
|
32898
32996
|
recursiveCteMaxDepth,
|
|
32899
32997
|
recursiveCteMaxRows,
|
|
32900
32998
|
recursiveCteMaxExpansions,
|
|
@@ -32946,7 +33044,38 @@ async function run() {
|
|
|
32946
33044
|
const importEnabled = Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0;
|
|
32947
33045
|
const importSource = importEnabled ? (name) => {
|
|
32948
33046
|
const sourcePath = args.importCsv[name] ?? args.importJson[name];
|
|
32949
|
-
|
|
33047
|
+
if (sourcePath === void 0) return void 0;
|
|
33048
|
+
return {
|
|
33049
|
+
load: async () => {
|
|
33050
|
+
let sourceStat;
|
|
33051
|
+
try {
|
|
33052
|
+
sourceStat = (0, import_fs2.statSync)(sourcePath);
|
|
33053
|
+
} catch (error) {
|
|
33054
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
33055
|
+
throw new FlowImportProviderError(
|
|
33056
|
+
"ImportSourceReadError",
|
|
33057
|
+
`failed to inspect IMPORT source "${sourcePath}": ${reason}`,
|
|
33058
|
+
error
|
|
33059
|
+
);
|
|
33060
|
+
}
|
|
33061
|
+
if (!sourceStat.isFile()) {
|
|
33062
|
+
throw new FlowImportProviderError(
|
|
33063
|
+
"ImportSourceNotRegularFileError",
|
|
33064
|
+
`IMPORT source "${sourcePath}" is not a regular file.`
|
|
33065
|
+
);
|
|
33066
|
+
}
|
|
33067
|
+
try {
|
|
33068
|
+
return { bytes: new Uint8Array((0, import_fs2.readFileSync)(sourcePath)) };
|
|
33069
|
+
} catch (error) {
|
|
33070
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
33071
|
+
throw new FlowImportProviderError(
|
|
33072
|
+
"ImportSourceReadError",
|
|
33073
|
+
`failed to read IMPORT source "${sourcePath}": ${reason}`,
|
|
33074
|
+
error
|
|
33075
|
+
);
|
|
33076
|
+
}
|
|
33077
|
+
}
|
|
33078
|
+
};
|
|
32950
33079
|
} : void 0;
|
|
32951
33080
|
const confirm = async (count, operation, context) => {
|
|
32952
33081
|
if (count > dmlMaxRows) {
|
|
@@ -33051,7 +33180,8 @@ query=${label}`);
|
|
|
33051
33180
|
dmlMaxSubtableRows,
|
|
33052
33181
|
recursiveCteMaxDepth,
|
|
33053
33182
|
recursiveCteMaxRows,
|
|
33054
|
-
recursiveCteMaxExpansions
|
|
33183
|
+
recursiveCteMaxExpansions,
|
|
33184
|
+
resolveMetadata: !fullyOfflineDryRun
|
|
33055
33185
|
}, args.nativeUpsert, true)) : await execute(sql, client, withNativeUpsertExecutionOption({
|
|
33056
33186
|
maxRecords,
|
|
33057
33187
|
fetchParallel,
|