@rex0220/kintone-sql-tools 1.1.2 → 1.3.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 +191 -190
- package/dist-cli/ksql.js +769 -559
- package/dist-mcp/ksql-mcp.js +38046 -0
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +19 -6
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
|
|
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");
|
|
@@ -2804,23 +2804,31 @@ function resolveKintoneFunc(name) {
|
|
|
2804
2804
|
return "";
|
|
2805
2805
|
}
|
|
2806
2806
|
}
|
|
2807
|
+
var likeRegexCache = /* @__PURE__ */ new Map();
|
|
2808
|
+
var LIKE_REGEX_CACHE_MAX = 200;
|
|
2807
2809
|
function matchLike(value, pattern) {
|
|
2808
2810
|
if (!pattern.includes("%") && !pattern.includes("_")) {
|
|
2809
2811
|
return value.includes(pattern);
|
|
2810
2812
|
}
|
|
2811
|
-
let
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2813
|
+
let regex = likeRegexCache.get(pattern);
|
|
2814
|
+
if (!regex) {
|
|
2815
|
+
let regexStr = "^";
|
|
2816
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
2817
|
+
const ch = pattern[i];
|
|
2818
|
+
if (ch === "%") {
|
|
2819
|
+
regexStr += ".*";
|
|
2820
|
+
} else if (ch === "_") {
|
|
2821
|
+
regexStr += ".";
|
|
2822
|
+
} else {
|
|
2823
|
+
regexStr += ch.replace(/[.+*?^${}()|[\]\\]/g, "\\$&");
|
|
2824
|
+
}
|
|
2820
2825
|
}
|
|
2826
|
+
regexStr += "$";
|
|
2827
|
+
regex = new RegExp(regexStr, "u");
|
|
2828
|
+
if (likeRegexCache.size >= LIKE_REGEX_CACHE_MAX) likeRegexCache.clear();
|
|
2829
|
+
likeRegexCache.set(pattern, regex);
|
|
2821
2830
|
}
|
|
2822
|
-
|
|
2823
|
-
return new RegExp(regexStr, "u").test(value);
|
|
2831
|
+
return regex.test(value);
|
|
2824
2832
|
}
|
|
2825
2833
|
|
|
2826
2834
|
// src/converter/dmlToKintone.ts
|
|
@@ -3248,10 +3256,12 @@ async function fetchPage(fetcher, app, query, fields, pageSize, offset) {
|
|
|
3248
3256
|
return fetcher({ app, query: pageQuery, fields });
|
|
3249
3257
|
}
|
|
3250
3258
|
function buildCursorQuery(baseQuery, cursorId) {
|
|
3251
|
-
if (cursorId <= 0) return baseQuery.trimEnd();
|
|
3252
|
-
const cursor = `$id > ${cursorId} order by $id asc`;
|
|
3253
3259
|
const base = baseQuery.trimEnd();
|
|
3254
|
-
|
|
3260
|
+
if (cursorId <= 0) {
|
|
3261
|
+
return base ? `${base} order by $id asc` : "order by $id asc";
|
|
3262
|
+
}
|
|
3263
|
+
const cursor = `$id > ${cursorId} order by $id asc`;
|
|
3264
|
+
return base ? `(${base}) and ${cursor}` : cursor;
|
|
3255
3265
|
}
|
|
3256
3266
|
function buildPageQuery(query, pageSize, offset) {
|
|
3257
3267
|
const base = query.trimEnd();
|
|
@@ -3404,6 +3414,8 @@ function applyJoin(leftRows, rightRows, join2) {
|
|
|
3404
3414
|
else rightIndex.set(k, [rRow]);
|
|
3405
3415
|
}
|
|
3406
3416
|
const result = [];
|
|
3417
|
+
const emptyRight = {};
|
|
3418
|
+
for (const key of Object.keys(rightRows[0] ?? {})) emptyRight[key] = "";
|
|
3407
3419
|
for (const lRow of leftRows) {
|
|
3408
3420
|
const k = lRow[leftKey] ?? "";
|
|
3409
3421
|
const matched = rightIndex.get(k) ?? [];
|
|
@@ -3412,8 +3424,6 @@ function applyJoin(leftRows, rightRows, join2) {
|
|
|
3412
3424
|
result.push({ ...lRow, ...rRow });
|
|
3413
3425
|
}
|
|
3414
3426
|
} else if (joinType === "LEFT") {
|
|
3415
|
-
const emptyRight = {};
|
|
3416
|
-
for (const key of Object.keys(rightRows[0] ?? {})) emptyRight[key] = "";
|
|
3417
3427
|
result.push({ ...lRow, ...emptyRight });
|
|
3418
3428
|
}
|
|
3419
3429
|
}
|
|
@@ -3489,12 +3499,23 @@ function evalAggregate(func, distinct, arg, rows) {
|
|
|
3489
3499
|
return nums.reduce((a, b) => a + b, 0);
|
|
3490
3500
|
case "AVG":
|
|
3491
3501
|
return nums.length === 0 ? 0 : nums.reduce((a, b) => a + b, 0) / nums.length;
|
|
3502
|
+
// Math.max(...nums) は要素数が多いと RangeError になるためループで求める
|
|
3492
3503
|
case "MAX":
|
|
3493
|
-
return nums.length === 0 ? 0 :
|
|
3504
|
+
return nums.length === 0 ? 0 : maxOf(nums);
|
|
3494
3505
|
case "MIN":
|
|
3495
|
-
return nums.length === 0 ? 0 :
|
|
3506
|
+
return nums.length === 0 ? 0 : minOf(nums);
|
|
3496
3507
|
}
|
|
3497
3508
|
}
|
|
3509
|
+
function maxOf(nums) {
|
|
3510
|
+
let m = nums[0];
|
|
3511
|
+
for (const n of nums) if (n > m) m = n;
|
|
3512
|
+
return m;
|
|
3513
|
+
}
|
|
3514
|
+
function minOf(nums) {
|
|
3515
|
+
let m = nums[0];
|
|
3516
|
+
for (const n of nums) if (n < m) m = n;
|
|
3517
|
+
return m;
|
|
3518
|
+
}
|
|
3498
3519
|
function evalAggArithExpr(node, rows) {
|
|
3499
3520
|
if (node.type === "NUMBER") return node.value;
|
|
3500
3521
|
if (node.type === "AGG_REF") return evalAggregate(node.func, node.distinct, node.arg, rows);
|
|
@@ -3531,43 +3552,89 @@ function applyHaving(rows, having) {
|
|
|
3531
3552
|
return rows.filter((row) => evalWhere(having, row));
|
|
3532
3553
|
}
|
|
3533
3554
|
function applyDistinct(rows, columns) {
|
|
3555
|
+
if (rows.length === 0) return rows;
|
|
3556
|
+
const keyFor = buildDistinctKeyBuilder(rows, columns);
|
|
3534
3557
|
const seen = /* @__PURE__ */ new Set();
|
|
3535
3558
|
return rows.filter((row) => {
|
|
3536
|
-
const key =
|
|
3559
|
+
const key = keyFor(row);
|
|
3537
3560
|
if (seen.has(key)) return false;
|
|
3538
3561
|
seen.add(key);
|
|
3539
3562
|
return true;
|
|
3540
3563
|
});
|
|
3541
3564
|
}
|
|
3542
|
-
function
|
|
3565
|
+
function buildDistinctKeyBuilder(rows, columns) {
|
|
3543
3566
|
if (columns.some((c) => c.type === "WILDCARD")) {
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3567
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
3568
|
+
for (const row of rows) {
|
|
3569
|
+
for (const k of Object.keys(row)) allKeys.add(k);
|
|
3570
|
+
}
|
|
3571
|
+
const keys = [...allKeys].sort();
|
|
3572
|
+
return (row) => JSON.stringify(keys.map((k) => row[k] !== void 0 ? row[k] : null));
|
|
3573
|
+
}
|
|
3574
|
+
let sortedParentKeys = [];
|
|
3575
|
+
if (columns.some((c) => c.type === "PARENT_WILDCARD")) {
|
|
3576
|
+
const parentKeys = /* @__PURE__ */ new Set();
|
|
3577
|
+
for (const row of rows) {
|
|
3578
|
+
for (const k of Object.keys(row)) {
|
|
3579
|
+
if (k.startsWith("_p.")) parentKeys.add(k);
|
|
3555
3580
|
}
|
|
3556
3581
|
}
|
|
3582
|
+
sortedParentKeys = [...parentKeys].sort();
|
|
3557
3583
|
}
|
|
3558
|
-
return
|
|
3584
|
+
return (row) => {
|
|
3585
|
+
const values = [];
|
|
3586
|
+
for (const col of columns) {
|
|
3587
|
+
if (col.type === "FIELD") {
|
|
3588
|
+
values.push(row[col.field] ?? "");
|
|
3589
|
+
continue;
|
|
3590
|
+
}
|
|
3591
|
+
if (col.type === "PARENT_WILDCARD") {
|
|
3592
|
+
for (const k of sortedParentKeys) {
|
|
3593
|
+
values.push(row[k] !== void 0 ? row[k] : null);
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
return JSON.stringify(values);
|
|
3598
|
+
};
|
|
3559
3599
|
}
|
|
3560
3600
|
function applyOrderBy(rows, orderBy, optionOrders, sortKinds) {
|
|
3561
3601
|
if (orderBy.length === 0) return rows;
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3602
|
+
const keyMeta = orderBy.map(({ key }) => ({
|
|
3603
|
+
orderMap: key.type === "FIELD_NAME" ? optionOrders?.get(key.name) : void 0,
|
|
3604
|
+
sortKind: key.type === "FIELD_NAME" ? sortKinds?.get(key.name) : void 0
|
|
3605
|
+
}));
|
|
3606
|
+
const decorated = rows.map((row) => ({
|
|
3607
|
+
row,
|
|
3608
|
+
keys: orderBy.map(({ key }, i) => {
|
|
3609
|
+
const s = evalOrderKey(key, row);
|
|
3610
|
+
const n = Number(s);
|
|
3611
|
+
const orderMap = keyMeta[i].orderMap;
|
|
3612
|
+
return {
|
|
3613
|
+
s,
|
|
3614
|
+
n,
|
|
3615
|
+
isNum: !Number.isNaN(n),
|
|
3616
|
+
rank: orderMap ? minChoiceIndex(parseChoiceValues(s), orderMap) : 0
|
|
3617
|
+
};
|
|
3618
|
+
})
|
|
3619
|
+
}));
|
|
3620
|
+
decorated.sort((a, b) => {
|
|
3621
|
+
for (let i = 0; i < orderBy.length; i++) {
|
|
3622
|
+
const cmp = compareSortKeys(a.keys[i], b.keys[i], keyMeta[i]);
|
|
3623
|
+
if (cmp !== 0) return orderBy[i].direction === "ASC" ? cmp : -cmp;
|
|
3568
3624
|
}
|
|
3569
3625
|
return 0;
|
|
3570
3626
|
});
|
|
3627
|
+
return decorated.map((d) => d.row);
|
|
3628
|
+
}
|
|
3629
|
+
function compareSortKeys(a, b, meta) {
|
|
3630
|
+
if (meta.orderMap) {
|
|
3631
|
+
if (a.rank !== b.rank) return a.rank - b.rank;
|
|
3632
|
+
return a.s.localeCompare(b.s, "ja");
|
|
3633
|
+
}
|
|
3634
|
+
if (meta.sortKind === "string") {
|
|
3635
|
+
return a.s.localeCompare(b.s, "ja");
|
|
3636
|
+
}
|
|
3637
|
+
return a.isNum && b.isNum ? a.n - b.n : a.s.localeCompare(b.s, "ja");
|
|
3571
3638
|
}
|
|
3572
3639
|
function evalOrderKey(key, row) {
|
|
3573
3640
|
switch (key.type) {
|
|
@@ -3579,32 +3646,6 @@ function evalOrderKey(key, row) {
|
|
|
3579
3646
|
return evalStringFunc(key.expr, row);
|
|
3580
3647
|
}
|
|
3581
3648
|
}
|
|
3582
|
-
function compareOrderValues(av, bv, key, optionOrders, sortKinds) {
|
|
3583
|
-
if (key.type === "FIELD_NAME") {
|
|
3584
|
-
const orderMap = optionOrders?.get(key.name);
|
|
3585
|
-
if (orderMap) {
|
|
3586
|
-
const ac = compareByChoiceOrder(av, bv, orderMap);
|
|
3587
|
-
if (ac !== 0) return ac;
|
|
3588
|
-
return av.localeCompare(bv, "ja");
|
|
3589
|
-
}
|
|
3590
|
-
const sortKind = sortKinds?.get(key.name);
|
|
3591
|
-
if (sortKind === "number") {
|
|
3592
|
-
return compareAsNumber(av, bv);
|
|
3593
|
-
}
|
|
3594
|
-
if (sortKind === "string") {
|
|
3595
|
-
return av.localeCompare(bv, "ja");
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
|
-
return compareAuto(av, bv);
|
|
3599
|
-
}
|
|
3600
|
-
function compareByChoiceOrder(av, bv, orderMap) {
|
|
3601
|
-
const aValues = parseChoiceValues(av);
|
|
3602
|
-
const bValues = parseChoiceValues(bv);
|
|
3603
|
-
const aRank = minChoiceIndex(aValues, orderMap);
|
|
3604
|
-
const bRank = minChoiceIndex(bValues, orderMap);
|
|
3605
|
-
if (aRank !== bRank) return aRank - bRank;
|
|
3606
|
-
return 0;
|
|
3607
|
-
}
|
|
3608
3649
|
function parseChoiceValues(raw) {
|
|
3609
3650
|
const trimmed = raw.trim();
|
|
3610
3651
|
if (trimmed === "") return [""];
|
|
@@ -3628,18 +3669,6 @@ function minChoiceIndex(values, orderMap) {
|
|
|
3628
3669
|
}
|
|
3629
3670
|
return min;
|
|
3630
3671
|
}
|
|
3631
|
-
function compareAsNumber(av, bv) {
|
|
3632
|
-
const an = Number(av);
|
|
3633
|
-
const bn = Number(bv);
|
|
3634
|
-
const numeric = !Number.isNaN(an) && !Number.isNaN(bn);
|
|
3635
|
-
return numeric ? an - bn : av.localeCompare(bv, "ja");
|
|
3636
|
-
}
|
|
3637
|
-
function compareAuto(av, bv) {
|
|
3638
|
-
const an = Number(av);
|
|
3639
|
-
const bn = Number(bv);
|
|
3640
|
-
const numeric = !Number.isNaN(an) && !Number.isNaN(bn);
|
|
3641
|
-
return numeric ? an - bn : av.localeCompare(bv, "ja");
|
|
3642
|
-
}
|
|
3643
3672
|
function applyLimit(rows, limit, offset) {
|
|
3644
3673
|
const start = offset ?? 0;
|
|
3645
3674
|
if (limit === null) return rows.slice(start);
|
|
@@ -3884,6 +3913,56 @@ function toFlatString(value) {
|
|
|
3884
3913
|
|
|
3885
3914
|
// src/execute.ts
|
|
3886
3915
|
async function execute(sql, client, options = {}) {
|
|
3916
|
+
const metrics = createEmptyMetrics();
|
|
3917
|
+
const countedClient = wrapClientWithMetrics(client, metrics);
|
|
3918
|
+
const startedAt = Date.now();
|
|
3919
|
+
const result = await executeStatement(sql, countedClient, options);
|
|
3920
|
+
metrics.elapsedMs = Date.now() - startedAt;
|
|
3921
|
+
return { ...result, metrics };
|
|
3922
|
+
}
|
|
3923
|
+
function createEmptyMetrics() {
|
|
3924
|
+
return {
|
|
3925
|
+
getCalls: 0,
|
|
3926
|
+
postCalls: 0,
|
|
3927
|
+
putCalls: 0,
|
|
3928
|
+
deleteCalls: 0,
|
|
3929
|
+
fieldCalls: 0,
|
|
3930
|
+
appsCalls: 0,
|
|
3931
|
+
fetchedRows: 0,
|
|
3932
|
+
elapsedMs: 0
|
|
3933
|
+
};
|
|
3934
|
+
}
|
|
3935
|
+
function wrapClientWithMetrics(client, metrics) {
|
|
3936
|
+
return {
|
|
3937
|
+
getRecords: async (params) => {
|
|
3938
|
+
metrics.getCalls += 1;
|
|
3939
|
+
const res = await client.getRecords(params);
|
|
3940
|
+
metrics.fetchedRows += res.records.length;
|
|
3941
|
+
return res;
|
|
3942
|
+
},
|
|
3943
|
+
postRecords: (params) => {
|
|
3944
|
+
metrics.postCalls += 1;
|
|
3945
|
+
return client.postRecords(params);
|
|
3946
|
+
},
|
|
3947
|
+
putRecords: (params) => {
|
|
3948
|
+
metrics.putCalls += 1;
|
|
3949
|
+
return client.putRecords(params);
|
|
3950
|
+
},
|
|
3951
|
+
deleteRecords: (params) => {
|
|
3952
|
+
metrics.deleteCalls += 1;
|
|
3953
|
+
return client.deleteRecords(params);
|
|
3954
|
+
},
|
|
3955
|
+
getApps: () => {
|
|
3956
|
+
metrics.appsCalls += 1;
|
|
3957
|
+
return client.getApps();
|
|
3958
|
+
},
|
|
3959
|
+
getFields: (appId) => {
|
|
3960
|
+
metrics.fieldCalls += 1;
|
|
3961
|
+
return client.getFields(appId);
|
|
3962
|
+
}
|
|
3963
|
+
};
|
|
3964
|
+
}
|
|
3965
|
+
async function executeStatement(sql, client, options) {
|
|
3887
3966
|
const cacheContext = options.cacheContext ?? "default";
|
|
3888
3967
|
const stmt = parseSql(sql);
|
|
3889
3968
|
switch (stmt.type) {
|
|
@@ -3894,7 +3973,7 @@ async function execute(sql, client, options = {}) {
|
|
|
3894
3973
|
case "WITH":
|
|
3895
3974
|
return executeWith(stmt, client, options, cacheContext);
|
|
3896
3975
|
case "INSERT":
|
|
3897
|
-
return executeInsert(stmt, client, cacheContext);
|
|
3976
|
+
return executeInsert(stmt, client, options, cacheContext);
|
|
3898
3977
|
case "INSERT_SELECT":
|
|
3899
3978
|
return executeInsertSelect(stmt, client, options, cacheContext);
|
|
3900
3979
|
case "UPSERT":
|
|
@@ -4009,8 +4088,7 @@ async function executeSimpleSelect(stmt, client, options, cacheContext) {
|
|
|
4009
4088
|
}
|
|
4010
4089
|
let rows = records.map((r) => flatten(r, null));
|
|
4011
4090
|
if (!useSingleGet) {
|
|
4012
|
-
const optionOrders = await
|
|
4013
|
-
const sortKinds = await buildSortKindsForSelect(stmt, client, cacheContext);
|
|
4091
|
+
const { optionOrders, sortKinds } = await buildOrderByMetaForSelect(stmt, client, cacheContext);
|
|
4014
4092
|
rows = applyOrderBy(rows, stmt.orderBy, optionOrders, sortKinds);
|
|
4015
4093
|
rows = applyLimit(rows, stmt.limit, stmt.offset);
|
|
4016
4094
|
}
|
|
@@ -4038,11 +4116,12 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
|
|
|
4038
4116
|
}
|
|
4039
4117
|
}
|
|
4040
4118
|
for (const [appId, fields] of appToFields.entries()) {
|
|
4041
|
-
|
|
4119
|
+
const userFields = [...fields].filter((f) => !isSystemLikeFieldCode(f));
|
|
4120
|
+
if (userFields.length === 0) continue;
|
|
4042
4121
|
const defs = await getFieldsCached(appId, client, cacheContext);
|
|
4043
4122
|
if (defs.length === 0) continue;
|
|
4044
4123
|
const validCodes = new Set(defs.map((d) => d.code));
|
|
4045
|
-
const unknown =
|
|
4124
|
+
const unknown = userFields.filter((f) => !validCodes.has(f));
|
|
4046
4125
|
if (unknown.length > 0) {
|
|
4047
4126
|
throw new Error(`ArgumentError: unknown field code(s): ${unknown.join(", ")} (APP${appId})`);
|
|
4048
4127
|
}
|
|
@@ -4052,8 +4131,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
|
|
|
4052
4131
|
const maxRecords = options.maxRecords ?? 1e4;
|
|
4053
4132
|
const warnings = /* @__PURE__ */ new Set();
|
|
4054
4133
|
const parallel = options.fetchParallel ?? 1;
|
|
4055
|
-
await
|
|
4056
|
-
|
|
4134
|
+
await Promise.all([
|
|
4135
|
+
resolveSubqueries(stmt.where, client, options, cacheContext),
|
|
4136
|
+
resolveSubqueries(stmt.having, client, options, cacheContext)
|
|
4137
|
+
]);
|
|
4057
4138
|
const tableConditions = /* @__PURE__ */ new Map();
|
|
4058
4139
|
if (stmt.where !== null) {
|
|
4059
4140
|
if (stmt.from.alias) {
|
|
@@ -4102,6 +4183,12 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
|
|
|
4102
4183
|
onOptJoins.push(join2);
|
|
4103
4184
|
}
|
|
4104
4185
|
}
|
|
4186
|
+
const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext);
|
|
4187
|
+
const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
|
|
4188
|
+
scalarCachePromise.catch(() => {
|
|
4189
|
+
});
|
|
4190
|
+
orderByMetaPromise.catch(() => {
|
|
4191
|
+
});
|
|
4105
4192
|
const mainRecords = await mainFetch;
|
|
4106
4193
|
const tables = /* @__PURE__ */ new Map();
|
|
4107
4194
|
tables.set(stmt.from.alias, mainRecords);
|
|
@@ -4133,15 +4220,16 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
|
|
|
4133
4220
|
);
|
|
4134
4221
|
tables.set(join2.table.alias, joinRecords);
|
|
4135
4222
|
}));
|
|
4136
|
-
const scalarCache = await
|
|
4137
|
-
const optionOrders = await
|
|
4138
|
-
const sortKinds = await buildSortKindsForSelect(stmt, client, cacheContext);
|
|
4223
|
+
const scalarCache = await scalarCachePromise;
|
|
4224
|
+
const { optionOrders, sortKinds } = await orderByMetaPromise;
|
|
4139
4225
|
const { rows, columns } = runFullScan({ tables, stmt, scalarCache, optionOrders, sortKinds });
|
|
4140
4226
|
return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
|
|
4141
4227
|
}
|
|
4142
4228
|
async function executeUnion(stmt, client, options, cacheContext) {
|
|
4143
|
-
const leftResult =
|
|
4144
|
-
|
|
4229
|
+
const [leftResult, rightResult] = await Promise.all([
|
|
4230
|
+
stmt.left.type === "UNION" ? executeUnion(stmt.left, client, options, cacheContext) : executeSelect(stmt.left, client, options, cacheContext),
|
|
4231
|
+
executeSelect(stmt.right, client, options, cacheContext)
|
|
4232
|
+
]);
|
|
4145
4233
|
const leftCols = leftResult.columns;
|
|
4146
4234
|
const rightCols = rightResult.columns;
|
|
4147
4235
|
const remappedRight = rightResult.rows.map((row) => {
|
|
@@ -4158,7 +4246,7 @@ async function executeUnion(stmt, client, options, cacheContext) {
|
|
|
4158
4246
|
function deduplicateRows(rows, columns) {
|
|
4159
4247
|
const seen = /* @__PURE__ */ new Set();
|
|
4160
4248
|
return rows.filter((row) => {
|
|
4161
|
-
const key = columns.map((c) => row[c] ?? "")
|
|
4249
|
+
const key = JSON.stringify(columns.map((c) => row[c] ?? ""));
|
|
4162
4250
|
if (seen.has(key)) return false;
|
|
4163
4251
|
seen.add(key);
|
|
4164
4252
|
return true;
|
|
@@ -4261,8 +4349,10 @@ function stripCteAliasFromFieldValue(fv, alias) {
|
|
|
4261
4349
|
}
|
|
4262
4350
|
async function executeQueryWithCte(query, client, options, cteCache, cacheContext) {
|
|
4263
4351
|
if (query.type === "UNION") {
|
|
4264
|
-
const leftResult = await
|
|
4265
|
-
|
|
4352
|
+
const [leftResult, rightResult] = await Promise.all([
|
|
4353
|
+
executeQueryWithCte(query.left, client, options, cteCache, cacheContext),
|
|
4354
|
+
executeQueryWithCte(query.right, client, options, cteCache, cacheContext)
|
|
4355
|
+
]);
|
|
4266
4356
|
const leftCols = leftResult.columns;
|
|
4267
4357
|
const rightCols = rightResult.columns;
|
|
4268
4358
|
const remapped = rightResult.rows.map((row) => {
|
|
@@ -4286,8 +4376,16 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
4286
4376
|
const maxRecords = options.maxRecords ?? 1e4;
|
|
4287
4377
|
const warnings = /* @__PURE__ */ new Set();
|
|
4288
4378
|
const parallel = options.fetchParallel ?? 1;
|
|
4289
|
-
await
|
|
4290
|
-
|
|
4379
|
+
await Promise.all([
|
|
4380
|
+
resolveSubqueries(stmt.where, client, options, cacheContext),
|
|
4381
|
+
resolveSubqueries(stmt.having, client, options, cacheContext)
|
|
4382
|
+
]);
|
|
4383
|
+
const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext);
|
|
4384
|
+
const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
|
|
4385
|
+
scalarCachePromise.catch(() => {
|
|
4386
|
+
});
|
|
4387
|
+
orderByMetaPromise.catch(() => {
|
|
4388
|
+
});
|
|
4291
4389
|
const tables = /* @__PURE__ */ new Map();
|
|
4292
4390
|
if (stmt.from.cteName != null) {
|
|
4293
4391
|
const rows2 = cteCache.get(stmt.from.cteName) ?? [];
|
|
@@ -4334,9 +4432,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
4334
4432
|
}
|
|
4335
4433
|
});
|
|
4336
4434
|
await Promise.all(joinFetches);
|
|
4337
|
-
const scalarCache = await
|
|
4338
|
-
const optionOrders = await
|
|
4339
|
-
const sortKinds = await buildSortKindsForSelect(stmt, client, cacheContext);
|
|
4435
|
+
const scalarCache = await scalarCachePromise;
|
|
4436
|
+
const { optionOrders, sortKinds } = await orderByMetaPromise;
|
|
4340
4437
|
const { rows, columns } = runFullScan({ tables, stmt, scalarCache, optionOrders, sortKinds });
|
|
4341
4438
|
return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
|
|
4342
4439
|
}
|
|
@@ -4372,6 +4469,78 @@ async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, par
|
|
|
4372
4469
|
const parentRecords = parentResolved.records;
|
|
4373
4470
|
return expandSubtableRecords(parentRecords, table.subtableCode);
|
|
4374
4471
|
}
|
|
4472
|
+
var UPSERT_IN_CHUNK_SIZE = 50;
|
|
4473
|
+
function normalizeKeyPart(v) {
|
|
4474
|
+
const t = v.trim();
|
|
4475
|
+
if (t !== "" && !Number.isNaN(Number(t))) return String(Number(t));
|
|
4476
|
+
return v;
|
|
4477
|
+
}
|
|
4478
|
+
function upsertCompositeKey(parts) {
|
|
4479
|
+
return JSON.stringify(parts);
|
|
4480
|
+
}
|
|
4481
|
+
function upsertNormalizedKey(parts, numericKey) {
|
|
4482
|
+
return JSON.stringify(parts.map((p, i) => numericKey[i] ? normalizeKeyPart(p) : p));
|
|
4483
|
+
}
|
|
4484
|
+
function lookupUpsertTarget(index, keyParts) {
|
|
4485
|
+
const exact = index.raw.get(upsertCompositeKey(keyParts));
|
|
4486
|
+
if (exact !== void 0) return exact;
|
|
4487
|
+
if (!index.numericKey.some(Boolean)) return void 0;
|
|
4488
|
+
return index.normalized.get(upsertNormalizedKey(keyParts, index.numericKey));
|
|
4489
|
+
}
|
|
4490
|
+
async function resolveUpsertTargets(appId, keyFields, rowKeyValues, client, options, fieldTypes) {
|
|
4491
|
+
const maxRecords = options.maxRecords ?? 1e4;
|
|
4492
|
+
const parallel = options.fetchParallel ?? 1;
|
|
4493
|
+
const numericKey = keyFields.map((f) => fieldTypes.get(f) === "NUMBER");
|
|
4494
|
+
const index = { raw: /* @__PURE__ */ new Map(), normalized: /* @__PURE__ */ new Map(), numericKey };
|
|
4495
|
+
const setMax = (map, key, id) => {
|
|
4496
|
+
const cur = map.get(key);
|
|
4497
|
+
if (cur === void 0 || id > cur) map.set(key, id);
|
|
4498
|
+
};
|
|
4499
|
+
const addRecordToIndex = (parts, id) => {
|
|
4500
|
+
setMax(index.raw, upsertCompositeKey(parts), id);
|
|
4501
|
+
if (numericKey.some(Boolean)) {
|
|
4502
|
+
setMax(index.normalized, upsertNormalizedKey(parts, numericKey), id);
|
|
4503
|
+
}
|
|
4504
|
+
};
|
|
4505
|
+
const batchFirstKeys = /* @__PURE__ */ new Set();
|
|
4506
|
+
const perRowKeys = [];
|
|
4507
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4508
|
+
for (const parts of rowKeyValues) {
|
|
4509
|
+
const composite = upsertCompositeKey(parts);
|
|
4510
|
+
if (seen.has(composite)) continue;
|
|
4511
|
+
seen.add(composite);
|
|
4512
|
+
if (parts.some((p) => p === "")) perRowKeys.push(parts);
|
|
4513
|
+
else batchFirstKeys.add(parts[0]);
|
|
4514
|
+
}
|
|
4515
|
+
const fields = ["$id", ...keyFields];
|
|
4516
|
+
for (const chunk2 of splitChunks([...batchFirstKeys], UPSERT_IN_CHUNK_SIZE)) {
|
|
4517
|
+
const query = `${keyFields[0]} in (${chunk2.map(sqlQuote).join(",")})`;
|
|
4518
|
+
const records = await fetchAll(client.getRecords, appId, query, fields, { maxRecords, parallel });
|
|
4519
|
+
for (const rec of records) {
|
|
4520
|
+
const id = Number(rec["$id"]?.value);
|
|
4521
|
+
if (!Number.isFinite(id)) continue;
|
|
4522
|
+
addRecordToIndex(keyFields.map((f) => toScalarText(rec[f]?.value)), id);
|
|
4523
|
+
}
|
|
4524
|
+
}
|
|
4525
|
+
for (const parts of perRowKeys) {
|
|
4526
|
+
const query = keyFields.map((f, i) => `${f} = ${sqlQuote(parts[i])}`).join(" and ");
|
|
4527
|
+
const existing = await fetchAll(client.getRecords, appId, query, ["$id"], { maxRecords, parallel });
|
|
4528
|
+
if (existing.length === 0) continue;
|
|
4529
|
+
addRecordToIndex(parts, maxRecordId(existing));
|
|
4530
|
+
}
|
|
4531
|
+
return index;
|
|
4532
|
+
}
|
|
4533
|
+
function maxRecordId(records) {
|
|
4534
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
4535
|
+
for (const r of records) {
|
|
4536
|
+
const n = Number(r["$id"]?.value);
|
|
4537
|
+
if (Number.isFinite(n) && n > max) max = n;
|
|
4538
|
+
}
|
|
4539
|
+
if (!Number.isFinite(max)) {
|
|
4540
|
+
throw new Error("\u30EC\u30B3\u30FC\u30C9\u306B\u6570\u5024\u306E $id \u304C\u542B\u307E\u308C\u3066\u3044\u307E\u305B\u3093\u3002");
|
|
4541
|
+
}
|
|
4542
|
+
return max;
|
|
4543
|
+
}
|
|
4375
4544
|
function toScalarText(value) {
|
|
4376
4545
|
if (typeof value === "string") return value;
|
|
4377
4546
|
if (value === null || value === void 0) return "";
|
|
@@ -4509,6 +4678,16 @@ async function getSortKindMapByApp(appId, client, cacheContext) {
|
|
|
4509
4678
|
setScopedCacheValue(sortKindCache, cacheContext, appId, map);
|
|
4510
4679
|
return map;
|
|
4511
4680
|
}
|
|
4681
|
+
async function buildOrderByMetaForSelect(stmt, client, cacheContext) {
|
|
4682
|
+
if (stmt.orderBy.length === 0) {
|
|
4683
|
+
return { optionOrders: /* @__PURE__ */ new Map(), sortKinds: /* @__PURE__ */ new Map() };
|
|
4684
|
+
}
|
|
4685
|
+
const [optionOrders, sortKinds] = await Promise.all([
|
|
4686
|
+
buildOptionOrdersForSelect(stmt, client, cacheContext),
|
|
4687
|
+
buildSortKindsForSelect(stmt, client, cacheContext)
|
|
4688
|
+
]);
|
|
4689
|
+
return { optionOrders, sortKinds };
|
|
4690
|
+
}
|
|
4512
4691
|
async function buildOptionOrdersForSelect(stmt, client, cacheContext) {
|
|
4513
4692
|
const optionOrders = /* @__PURE__ */ new Map();
|
|
4514
4693
|
const tables = [stmt.from, ...stmt.joins.map((j) => j.table)];
|
|
@@ -4582,9 +4761,9 @@ function convertProcessRowValue(raw, dstFieldType) {
|
|
|
4582
4761
|
}
|
|
4583
4762
|
return raw;
|
|
4584
4763
|
}
|
|
4585
|
-
async function executeInsert(stmt, client, cacheContext) {
|
|
4764
|
+
async function executeInsert(stmt, client, options, cacheContext) {
|
|
4586
4765
|
if (stmt.subtableCode) {
|
|
4587
|
-
return executeInsertSubtable(stmt, client, cacheContext);
|
|
4766
|
+
return executeInsertSubtable(stmt, client, options, cacheContext);
|
|
4588
4767
|
}
|
|
4589
4768
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
4590
4769
|
const batches = insertToPostBatches(stmt, fieldTypes);
|
|
@@ -4699,26 +4878,19 @@ async function executeDelete(stmt, client, options, cacheContext) {
|
|
|
4699
4878
|
return { type: "DELETE", deletedCount: ids.length };
|
|
4700
4879
|
}
|
|
4701
4880
|
async function executeUpsert(stmt, client, options, cacheContext) {
|
|
4702
|
-
const maxRecords = options.maxRecords ?? 1e4;
|
|
4703
4881
|
const toInsert = [];
|
|
4704
4882
|
const toUpdate = [];
|
|
4705
4883
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
4706
|
-
|
|
4707
|
-
|
|
4884
|
+
const rowKeyValues = stmt.values.map(
|
|
4885
|
+
(row) => stmt.keyFields.map((key) => {
|
|
4708
4886
|
const idx = stmt.fields.indexOf(key);
|
|
4709
4887
|
if (idx === -1) throw new Error(`ON DUPLICATE \u306E\u30AD\u30FC\u300C${key}\u300D\u304C INSERT \u30D5\u30A3\u30FC\u30EB\u30C9\u306B\u542B\u307E\u308C\u3066\u3044\u307E\u305B\u3093`);
|
|
4710
4888
|
const val = row[idx];
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
client.getRecords,
|
|
4717
|
-
stmt.appId,
|
|
4718
|
-
query,
|
|
4719
|
-
["$id"],
|
|
4720
|
-
{ maxRecords, parallel: options.fetchParallel ?? 1 }
|
|
4721
|
-
);
|
|
4889
|
+
return val.type === "STRING" ? val.value : val.type === "NUMBER" ? String(val.value) : val.type === "CASE_VALUE" ? evalCaseWhen(val.expr, {}) : val.elements.map((e) => e.value).join(",");
|
|
4890
|
+
})
|
|
4891
|
+
);
|
|
4892
|
+
const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
|
|
4893
|
+
stmt.values.forEach((row, rowIdx) => {
|
|
4722
4894
|
const record = {};
|
|
4723
4895
|
stmt.fields.forEach((field, i) => {
|
|
4724
4896
|
const val = row[i];
|
|
@@ -4728,13 +4900,13 @@ async function executeUpsert(stmt, client, options, cacheContext) {
|
|
|
4728
4900
|
record[field] = { value: toKintoneValue(val, fieldTypes.get(field)) };
|
|
4729
4901
|
}
|
|
4730
4902
|
});
|
|
4731
|
-
|
|
4732
|
-
|
|
4903
|
+
const id = lookupUpsertTarget(targetIndex, rowKeyValues[rowIdx]);
|
|
4904
|
+
if (id !== void 0) {
|
|
4733
4905
|
toUpdate.push({ id, record });
|
|
4734
4906
|
} else {
|
|
4735
4907
|
toInsert.push(record);
|
|
4736
4908
|
}
|
|
4737
|
-
}
|
|
4909
|
+
});
|
|
4738
4910
|
if (options.confirm && toInsert.length + toUpdate.length > 0) {
|
|
4739
4911
|
const total = toInsert.length + toUpdate.length;
|
|
4740
4912
|
const ok = await options.confirm(total, "UPDATE");
|
|
@@ -4754,18 +4926,17 @@ async function executeUpsert(stmt, client, options, cacheContext) {
|
|
|
4754
4926
|
updatedCount: toUpdate.length
|
|
4755
4927
|
};
|
|
4756
4928
|
}
|
|
4757
|
-
async function executeInsertSubtable(stmt, client, _cacheContext) {
|
|
4929
|
+
async function executeInsertSubtable(stmt, client, options, _cacheContext) {
|
|
4758
4930
|
const subtableCode = stmt.subtableCode;
|
|
4759
4931
|
const pidIndex = stmt.fields.indexOf("_pid");
|
|
4760
4932
|
if (pidIndex < 0) {
|
|
4761
4933
|
throw new Error("\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB INSERT \u306B\u306F _pid \u304C\u5FC5\u9808\u3067\u3059");
|
|
4762
4934
|
}
|
|
4763
|
-
const parents = await fetchAll(client.getRecords, stmt.appId, "", [], {
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
}
|
|
4935
|
+
const parents = await fetchAll(client.getRecords, stmt.appId, "", [], {
|
|
4936
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
4937
|
+
parallel: options.fetchParallel ?? 1
|
|
4938
|
+
});
|
|
4939
|
+
const parentMap = buildParentIdMap(parents);
|
|
4769
4940
|
const insertsByParent = /* @__PURE__ */ new Map();
|
|
4770
4941
|
for (const rowValues of stmt.values) {
|
|
4771
4942
|
const pid = valueToString(rowValues[pidIndex]);
|
|
@@ -4832,8 +5003,9 @@ async function executeUpdateSubtable(stmt, client, options, _cacheContext) {
|
|
|
4832
5003
|
}
|
|
4833
5004
|
byRid.set(t.rowId, updates);
|
|
4834
5005
|
}
|
|
5006
|
+
const parentById = buildParentIdMap(parents);
|
|
4835
5007
|
for (const [pid, updateMap] of updatesByParent.entries()) {
|
|
4836
|
-
const parent =
|
|
5008
|
+
const parent = parentById.get(pid);
|
|
4837
5009
|
if (!parent) continue;
|
|
4838
5010
|
const currentRows = getMutableTableRows(parent, subtableCode);
|
|
4839
5011
|
const payloadRows = currentRows.map((row) => {
|
|
@@ -4873,8 +5045,9 @@ async function executeDeleteSubtable(stmt, client, options, _cacheContext) {
|
|
|
4873
5045
|
if (bucket) bucket.push(t.rowIndex);
|
|
4874
5046
|
else byParent.set(t.parentId, [t.rowIndex]);
|
|
4875
5047
|
}
|
|
5048
|
+
const parentById = buildParentIdMap(parents);
|
|
4876
5049
|
for (const [pid, idxs] of byParent.entries()) {
|
|
4877
|
-
const parent =
|
|
5050
|
+
const parent = parentById.get(pid);
|
|
4878
5051
|
if (!parent) continue;
|
|
4879
5052
|
const rows = getMutableTableRows(parent, subtableCode);
|
|
4880
5053
|
const rm = new Set(idxs);
|
|
@@ -4883,6 +5056,14 @@ async function executeDeleteSubtable(stmt, client, options, _cacheContext) {
|
|
|
4883
5056
|
}
|
|
4884
5057
|
return { type: "DELETE", deletedCount: targets.length };
|
|
4885
5058
|
}
|
|
5059
|
+
function buildParentIdMap(parents) {
|
|
5060
|
+
const map = /* @__PURE__ */ new Map();
|
|
5061
|
+
for (const p of parents) {
|
|
5062
|
+
const pid = String(p["$id"]?.value ?? "");
|
|
5063
|
+
if (pid) map.set(pid, p);
|
|
5064
|
+
}
|
|
5065
|
+
return map;
|
|
5066
|
+
}
|
|
4886
5067
|
function expandRowsForSubtableDml(parents, subtableCode) {
|
|
4887
5068
|
const out = [];
|
|
4888
5069
|
for (const parent of parents) {
|
|
@@ -5018,8 +5199,9 @@ async function executeReorder(stmt, client, options, _cacheContext) {
|
|
|
5018
5199
|
const ok = await options.confirm(targetParentIds.size, "UPDATE");
|
|
5019
5200
|
if (!ok) throw new OperationCancelledError("UPDATE", targetParentIds.size);
|
|
5020
5201
|
}
|
|
5202
|
+
const parentById = buildParentIdMap(parents);
|
|
5021
5203
|
for (const pid of targetParentIds) {
|
|
5022
|
-
const parent =
|
|
5204
|
+
const parent = parentById.get(pid);
|
|
5023
5205
|
if (!parent) continue;
|
|
5024
5206
|
const rows = getMutableTableRows(parent, stmt.subtableCode);
|
|
5025
5207
|
const sortable = rows.map((row, i) => ({ row, i, flat: buildFlatRowForSort(parent, stmt.subtableCode, row, i) }));
|
|
@@ -5067,7 +5249,6 @@ function evalOrderKeyForRow(key, row) {
|
|
|
5067
5249
|
}
|
|
5068
5250
|
}
|
|
5069
5251
|
async function executeUpsertSelect(stmt, client, options, cacheContext) {
|
|
5070
|
-
const maxRecords = options.maxRecords ?? 1e4;
|
|
5071
5252
|
const selectResult = await executeSelect(stmt.select, client, options, cacheContext);
|
|
5072
5253
|
const { rows, columns } = selectResult;
|
|
5073
5254
|
if (columns.length !== stmt.fields.length) {
|
|
@@ -5082,29 +5263,26 @@ async function executeUpsertSelect(stmt, client, options, cacheContext) {
|
|
|
5082
5263
|
}
|
|
5083
5264
|
const toInsert = [];
|
|
5084
5265
|
const toUpdate = [];
|
|
5085
|
-
|
|
5266
|
+
const records = rows.map((row) => {
|
|
5086
5267
|
const record = {};
|
|
5087
5268
|
stmt.fields.forEach((field, i) => {
|
|
5088
5269
|
record[field] = { value: row[columns[i]] ?? "" };
|
|
5089
5270
|
});
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
{
|
|
5101
|
-
);
|
|
5102
|
-
if (existing.length > 0) {
|
|
5103
|
-
toUpdate.push({ id: Number(existing[0]["$id"].value), record });
|
|
5271
|
+
return record;
|
|
5272
|
+
});
|
|
5273
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
5274
|
+
const rowKeyValues = records.map(
|
|
5275
|
+
(record) => stmt.keyFields.map((key) => String(record[key]?.value ?? ""))
|
|
5276
|
+
);
|
|
5277
|
+
const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
|
|
5278
|
+
records.forEach((record, rowIdx) => {
|
|
5279
|
+
const id = lookupUpsertTarget(targetIndex, rowKeyValues[rowIdx]);
|
|
5280
|
+
if (id !== void 0) {
|
|
5281
|
+
toUpdate.push({ id, record });
|
|
5104
5282
|
} else {
|
|
5105
5283
|
toInsert.push(record);
|
|
5106
5284
|
}
|
|
5107
|
-
}
|
|
5285
|
+
});
|
|
5108
5286
|
if (options.confirm && toInsert.length + toUpdate.length > 0) {
|
|
5109
5287
|
const total = toInsert.length + toUpdate.length;
|
|
5110
5288
|
const ok = await options.confirm(total, "UPDATE");
|
|
@@ -5156,36 +5334,44 @@ function parseSql(sql) {
|
|
|
5156
5334
|
}
|
|
5157
5335
|
}
|
|
5158
5336
|
async function resolveSubqueries(where, client, options, cacheContext) {
|
|
5337
|
+
const tasks = [];
|
|
5338
|
+
collectSubqueryTasks(where, client, options, cacheContext, tasks);
|
|
5339
|
+
await Promise.all(tasks);
|
|
5340
|
+
}
|
|
5341
|
+
function collectSubqueryTasks(where, client, options, cacheContext, tasks) {
|
|
5159
5342
|
if (where === null) return;
|
|
5160
5343
|
switch (where.type) {
|
|
5161
5344
|
case "BINARY": {
|
|
5162
5345
|
const right = where.right;
|
|
5163
5346
|
if (right.type === "SUBQUERY_IN_LIST") {
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5347
|
+
tasks.push(executeSelect(right.query, client, options, cacheContext).then((result) => {
|
|
5348
|
+
const col = right.column ?? (result.columns[0] ?? "");
|
|
5349
|
+
right.resolved = new Set(result.rows.map((r) => r[col] ?? ""));
|
|
5350
|
+
}));
|
|
5168
5351
|
}
|
|
5169
5352
|
if (right.type === "SCALAR_SUBQUERY") {
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5353
|
+
tasks.push(executeSelect(right.query, client, options, cacheContext).then((result) => {
|
|
5354
|
+
if (result.rowCount === 0) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u5024\u3092\u8FD4\u3057\u307E\u305B\u3093\u3067\u3057\u305F");
|
|
5355
|
+
if (result.rowCount > 1) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u8907\u6570\u884C\u3092\u8FD4\u3057\u307E\u3057\u305F\uFF081\u884C\u306E\u307F\u8A31\u53EF\uFF09");
|
|
5356
|
+
const col = result.columns[0] ?? "";
|
|
5357
|
+
right.resolved = result.rows[0]?.[col] ?? "";
|
|
5358
|
+
}));
|
|
5175
5359
|
}
|
|
5176
5360
|
break;
|
|
5177
5361
|
}
|
|
5178
5362
|
case "LOGICAL":
|
|
5179
|
-
|
|
5180
|
-
|
|
5363
|
+
collectSubqueryTasks(where.left, client, options, cacheContext, tasks);
|
|
5364
|
+
collectSubqueryTasks(where.right, client, options, cacheContext, tasks);
|
|
5181
5365
|
break;
|
|
5182
5366
|
case "NOT":
|
|
5183
5367
|
case "GROUP":
|
|
5184
|
-
|
|
5368
|
+
collectSubqueryTasks(where.expr, client, options, cacheContext, tasks);
|
|
5185
5369
|
break;
|
|
5186
5370
|
case "EXISTS": {
|
|
5187
|
-
const
|
|
5188
|
-
|
|
5371
|
+
const node = where;
|
|
5372
|
+
tasks.push(executeSelect(node.query, client, options, cacheContext).then((result) => {
|
|
5373
|
+
node.resolved = result.rowCount > 0;
|
|
5374
|
+
}));
|
|
5189
5375
|
break;
|
|
5190
5376
|
}
|
|
5191
5377
|
}
|
|
@@ -5202,16 +5388,27 @@ async function resolveSetSubqueries(assignments, client, options, cacheContext)
|
|
|
5202
5388
|
}
|
|
5203
5389
|
}
|
|
5204
5390
|
async function resolveScalarColumns(columns, client, options, cacheContext) {
|
|
5205
|
-
const
|
|
5391
|
+
const byQuery = /* @__PURE__ */ new Map();
|
|
5392
|
+
const pending = [];
|
|
5206
5393
|
for (let i = 0; i < columns.length; i++) {
|
|
5207
5394
|
const col = columns[i];
|
|
5208
5395
|
if (col.type !== "SCALAR_SUBQUERY_COL") continue;
|
|
5209
|
-
const
|
|
5210
|
-
|
|
5211
|
-
if (
|
|
5212
|
-
|
|
5213
|
-
|
|
5396
|
+
const key = JSON.stringify(col.query);
|
|
5397
|
+
let promise = byQuery.get(key);
|
|
5398
|
+
if (!promise) {
|
|
5399
|
+
promise = executeSelect(col.query, client, options, cacheContext).then((result) => {
|
|
5400
|
+
if (result.rowCount === 0) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u5024\u3092\u8FD4\u3057\u307E\u305B\u3093\u3067\u3057\u305F");
|
|
5401
|
+
if (result.rowCount > 1) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u8907\u6570\u884C\u3092\u8FD4\u3057\u307E\u3057\u305F\uFF081\u884C\u306E\u307F\u8A31\u53EF\uFF09");
|
|
5402
|
+
const firstCol = result.columns[0] ?? "";
|
|
5403
|
+
return result.rows[0]?.[firstCol] ?? "";
|
|
5404
|
+
});
|
|
5405
|
+
byQuery.set(key, promise);
|
|
5406
|
+
}
|
|
5407
|
+
pending.push([i, promise]);
|
|
5214
5408
|
}
|
|
5409
|
+
const values = await Promise.all(pending.map(([, promise]) => promise));
|
|
5410
|
+
const cache = /* @__PURE__ */ new Map();
|
|
5411
|
+
pending.forEach(([i], idx) => cache.set(i, values[idx]));
|
|
5215
5412
|
return cache;
|
|
5216
5413
|
}
|
|
5217
5414
|
function executeExplain(stmt) {
|
|
@@ -5820,149 +6017,386 @@ function detectSortKind(fieldType, calcFormat) {
|
|
|
5820
6017
|
return void 0;
|
|
5821
6018
|
}
|
|
5822
6019
|
|
|
5823
|
-
// src/
|
|
5824
|
-
var
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
const
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
6020
|
+
// src/node/appProfiles.ts
|
|
6021
|
+
var import_fs = require("fs");
|
|
6022
|
+
function parseTokenMap(raw) {
|
|
6023
|
+
const out = {};
|
|
6024
|
+
if (!raw.trim()) return out;
|
|
6025
|
+
const pairs = raw.split(",");
|
|
6026
|
+
for (const pair of pairs) {
|
|
6027
|
+
const idx = pair.indexOf("=");
|
|
6028
|
+
if (idx <= 0) throw new Error("ArgumentError: --token-map must be APPxxx=token pairs.");
|
|
6029
|
+
const key = normalizeAppKey(pair.slice(0, idx).trim());
|
|
6030
|
+
const value = pair.slice(idx + 1).trim();
|
|
6031
|
+
if (!value) throw new Error(`ArgumentError: token is empty for ${key}.`);
|
|
6032
|
+
out[key] = value;
|
|
6033
|
+
}
|
|
6034
|
+
return out;
|
|
6035
|
+
}
|
|
6036
|
+
function parseTokenFile(path) {
|
|
6037
|
+
const raw = (0, import_fs.readFileSync)(path, "utf-8");
|
|
6038
|
+
const parsed = JSON.parse(raw);
|
|
6039
|
+
const out = {};
|
|
6040
|
+
for (const [k, v] of Object.entries(parsed)) out[normalizeAppKey(k)] = String(v);
|
|
6041
|
+
return out;
|
|
6042
|
+
}
|
|
6043
|
+
function normalizeAppKey(v) {
|
|
6044
|
+
const m1 = v.match(/^APP(\d+)$/i);
|
|
6045
|
+
if (m1) return `APP${m1[1]}`;
|
|
6046
|
+
const m2 = v.match(/^(\d+)$/);
|
|
6047
|
+
if (m2) return `APP${m2[1]}`;
|
|
6048
|
+
throw new Error(`ArgumentError: invalid app key "${v}"`);
|
|
6049
|
+
}
|
|
6050
|
+
function extractAppIds(sql) {
|
|
6051
|
+
const out = /* @__PURE__ */ new Set();
|
|
6052
|
+
for (const m of sql.matchAll(/\bAPP(\d+)\b/gi)) out.add(Number(m[1]));
|
|
6053
|
+
return [...out];
|
|
6054
|
+
}
|
|
6055
|
+
function isSqlIdentContinue(ch) {
|
|
6056
|
+
if (!ch) return false;
|
|
6057
|
+
const cp = ch.codePointAt(0);
|
|
6058
|
+
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;
|
|
6059
|
+
}
|
|
6060
|
+
function isProfileNameChar(ch) {
|
|
6061
|
+
if (!ch) return false;
|
|
6062
|
+
const cp = ch.codePointAt(0);
|
|
6063
|
+
return cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp >= 48 && cp <= 57 || ch === "_" || ch === "-" || ch === "." || ch === "$";
|
|
6064
|
+
}
|
|
6065
|
+
function tryParseAppProfileToken(sql, start) {
|
|
6066
|
+
const head = sql.slice(start, start + 3);
|
|
6067
|
+
if (head.toUpperCase() !== "APP") return null;
|
|
6068
|
+
const prev = start > 0 ? sql[start - 1] : "";
|
|
6069
|
+
if (isSqlIdentContinue(prev)) return null;
|
|
6070
|
+
let i = start + 3;
|
|
6071
|
+
const digitStart = i;
|
|
6072
|
+
while (i < sql.length && /[0-9]/.test(sql[i])) i++;
|
|
6073
|
+
const digitEnd = i;
|
|
6074
|
+
if (digitEnd === digitStart) return null;
|
|
6075
|
+
if (sql[i] === "$") {
|
|
6076
|
+
i++;
|
|
6077
|
+
const subStart = i;
|
|
6078
|
+
while (i < sql.length && isSqlIdentContinue(sql[i])) i++;
|
|
6079
|
+
if (i === subStart) return null;
|
|
6080
|
+
}
|
|
6081
|
+
const appEnd = i;
|
|
6082
|
+
let profile = null;
|
|
6083
|
+
if (sql[i] === "@") {
|
|
6084
|
+
i++;
|
|
6085
|
+
const pStart = i;
|
|
6086
|
+
while (i < sql.length && isProfileNameChar(sql[i])) i++;
|
|
6087
|
+
if (i === pStart) return null;
|
|
6088
|
+
profile = sql.slice(pStart, i);
|
|
6089
|
+
}
|
|
6090
|
+
const next = i < sql.length ? sql[i] : "";
|
|
6091
|
+
if (isSqlIdentContinue(next)) return null;
|
|
6092
|
+
return {
|
|
6093
|
+
appId: Number(sql.slice(digitStart, digitEnd)),
|
|
6094
|
+
profile,
|
|
6095
|
+
start,
|
|
6096
|
+
digitStart,
|
|
6097
|
+
digitEnd,
|
|
6098
|
+
appEnd,
|
|
6099
|
+
fullEnd: i
|
|
6100
|
+
};
|
|
6101
|
+
}
|
|
6102
|
+
function collectAppProfileTokens(sql) {
|
|
6103
|
+
const tokens = [];
|
|
6104
|
+
let i = 0;
|
|
6105
|
+
while (i < sql.length) {
|
|
6106
|
+
const ch = sql[i];
|
|
6107
|
+
if (ch === "'") {
|
|
6108
|
+
i++;
|
|
6109
|
+
while (i < sql.length) {
|
|
6110
|
+
if (sql[i] === "'") {
|
|
6111
|
+
i++;
|
|
6112
|
+
if (i < sql.length && sql[i] === "'") {
|
|
6113
|
+
i++;
|
|
6114
|
+
continue;
|
|
6115
|
+
}
|
|
6116
|
+
break;
|
|
6117
|
+
}
|
|
6118
|
+
i++;
|
|
6119
|
+
}
|
|
6120
|
+
continue;
|
|
6121
|
+
}
|
|
6122
|
+
if (ch === "`") {
|
|
6123
|
+
i++;
|
|
6124
|
+
while (i < sql.length && sql[i] !== "`") i++;
|
|
6125
|
+
if (i < sql.length) i++;
|
|
6126
|
+
continue;
|
|
6127
|
+
}
|
|
6128
|
+
if (ch === "-" && sql[i + 1] === "-") {
|
|
6129
|
+
i += 2;
|
|
6130
|
+
while (i < sql.length && sql[i] !== "\n") i++;
|
|
6131
|
+
continue;
|
|
6132
|
+
}
|
|
6133
|
+
if (ch === "/" && sql[i + 1] === "*") {
|
|
6134
|
+
i += 2;
|
|
6135
|
+
while (i < sql.length) {
|
|
6136
|
+
if (sql[i] === "*" && sql[i + 1] === "/") {
|
|
6137
|
+
i += 2;
|
|
6138
|
+
break;
|
|
6139
|
+
}
|
|
6140
|
+
i++;
|
|
6141
|
+
}
|
|
6142
|
+
continue;
|
|
6143
|
+
}
|
|
6144
|
+
const parsed = tryParseAppProfileToken(sql, i);
|
|
6145
|
+
if (!parsed) {
|
|
6146
|
+
i++;
|
|
6147
|
+
continue;
|
|
6148
|
+
}
|
|
6149
|
+
tokens.push(parsed);
|
|
6150
|
+
i = parsed.fullEnd;
|
|
6151
|
+
}
|
|
6152
|
+
return tokens;
|
|
6153
|
+
}
|
|
6154
|
+
function nextVirtualAppId(used) {
|
|
6155
|
+
let id = 9e8;
|
|
6156
|
+
while (used.has(id)) id++;
|
|
6157
|
+
used.add(id);
|
|
6158
|
+
return id;
|
|
6159
|
+
}
|
|
6160
|
+
function normalizeSqlAppProfiles(sql, defaultProfile = "dev") {
|
|
6161
|
+
const tokens = collectAppProfileTokens(sql);
|
|
6162
|
+
const hasProfileSyntax = tokens.some((t) => t.profile !== null);
|
|
6163
|
+
const profilesByApp = /* @__PURE__ */ new Map();
|
|
6164
|
+
const normalizedProfile = (profile) => profile ?? defaultProfile;
|
|
6165
|
+
for (const t of tokens) {
|
|
6166
|
+
const p = normalizedProfile(t.profile);
|
|
6167
|
+
let set = profilesByApp.get(t.appId);
|
|
6168
|
+
if (!set) {
|
|
6169
|
+
set = /* @__PURE__ */ new Set();
|
|
6170
|
+
profilesByApp.set(t.appId, set);
|
|
6171
|
+
}
|
|
6172
|
+
set.add(p.toLowerCase());
|
|
6173
|
+
}
|
|
6174
|
+
const usedAppIds = new Set(tokens.map((t) => t.appId));
|
|
6175
|
+
const pairToMapped = /* @__PURE__ */ new Map();
|
|
6176
|
+
const appBindingByMappedApp = /* @__PURE__ */ new Map();
|
|
6177
|
+
for (const [appId, pSet] of profilesByApp.entries()) {
|
|
6178
|
+
const profiles = [...pSet].sort();
|
|
6179
|
+
if (profiles.length <= 1) continue;
|
|
6180
|
+
for (const pLower of profiles) {
|
|
6181
|
+
const mapped = nextVirtualAppId(usedAppIds);
|
|
6182
|
+
pairToMapped.set(`${appId}@${pLower}`, mapped);
|
|
6183
|
+
appBindingByMappedApp.set(mapped, { appId, profile: pLower });
|
|
6184
|
+
}
|
|
6185
|
+
}
|
|
6186
|
+
const out = [];
|
|
6187
|
+
let cursor = 0;
|
|
6188
|
+
for (const t of tokens) {
|
|
6189
|
+
const p = normalizedProfile(t.profile);
|
|
6190
|
+
const pLower = p.toLowerCase();
|
|
6191
|
+
const mapped = pairToMapped.get(`${t.appId}@${pLower}`) ?? t.appId;
|
|
6192
|
+
appBindingByMappedApp.set(mapped, { appId: t.appId, profile: pLower });
|
|
6193
|
+
out.push(sql.slice(cursor, t.start));
|
|
6194
|
+
out.push(sql.slice(t.start, t.digitStart));
|
|
6195
|
+
out.push(String(mapped));
|
|
6196
|
+
out.push(sql.slice(t.digitEnd, t.appEnd));
|
|
6197
|
+
cursor = t.fullEnd;
|
|
6198
|
+
}
|
|
6199
|
+
out.push(sql.slice(cursor));
|
|
6200
|
+
return {
|
|
6201
|
+
normalizedSql: out.join(""),
|
|
6202
|
+
hasProfileSyntax,
|
|
6203
|
+
appBindingByMappedApp
|
|
6204
|
+
};
|
|
6205
|
+
}
|
|
6206
|
+
function buildCacheContext(defaultProfile, appBindingByMappedApp) {
|
|
6207
|
+
if (appBindingByMappedApp.size === 0) return `default:${defaultProfile.toLowerCase()}`;
|
|
6208
|
+
const pairs = [...appBindingByMappedApp.entries()].sort((a, b) => a[0] - b[0]).map(([mappedAppId, b]) => `M${mappedAppId}=APP${b.appId}@${b.profile}`);
|
|
6209
|
+
return `apps:${pairs.join(",")}`;
|
|
6210
|
+
}
|
|
6211
|
+
function formatResolvedAppProfiles(sql, defaultProfile) {
|
|
6212
|
+
const parsed = normalizeSqlAppProfiles(sql, defaultProfile);
|
|
6213
|
+
if (parsed.appBindingByMappedApp.size === 0) return "(none)";
|
|
6214
|
+
return [...parsed.appBindingByMappedApp.values()].map((b) => `APP${b.appId}->${b.profile}`).join(", ");
|
|
6215
|
+
}
|
|
6216
|
+
|
|
6217
|
+
// src/node/dmlGuard.ts
|
|
6218
|
+
function getStatementType(stmt) {
|
|
6219
|
+
if (!stmt || typeof stmt !== "object") return "UNKNOWN";
|
|
6220
|
+
const obj = stmt;
|
|
6221
|
+
return typeof obj.type === "string" ? obj.type : "UNKNOWN";
|
|
6222
|
+
}
|
|
6223
|
+
function isDmlType(type) {
|
|
6224
|
+
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
|
|
6225
|
+
}
|
|
6226
|
+
function hasWhereClause(stmt) {
|
|
6227
|
+
if (!stmt || typeof stmt !== "object") return false;
|
|
6228
|
+
const obj = stmt;
|
|
6229
|
+
return obj.where !== null && obj.where !== void 0;
|
|
6230
|
+
}
|
|
6231
|
+
function isNoFromSelectStatement(stmt) {
|
|
6232
|
+
if (!stmt || typeof stmt !== "object") return false;
|
|
6233
|
+
const obj = stmt;
|
|
6234
|
+
return obj.type === "SELECT" && obj.from?.appId === 0 && obj.from?.cteName === "__NO_FROM__";
|
|
6235
|
+
}
|
|
6236
|
+
function getInsertValuesCount(stmt) {
|
|
6237
|
+
if (!stmt || typeof stmt !== "object") return null;
|
|
6238
|
+
const obj = stmt;
|
|
6239
|
+
if (obj.type !== "INSERT") return null;
|
|
6240
|
+
return Array.isArray(obj.values) ? obj.values.length : null;
|
|
6241
|
+
}
|
|
6242
|
+
function collectDmlTargetFields(stmt) {
|
|
6243
|
+
if (!stmt || typeof stmt !== "object") return [];
|
|
6244
|
+
const obj = stmt;
|
|
6245
|
+
if (!obj.type) return [];
|
|
6246
|
+
if (obj.type === "UPDATE") {
|
|
6247
|
+
return (obj.assignments ?? []).map((a) => a.field).filter((f) => Boolean(f));
|
|
6248
|
+
}
|
|
6249
|
+
if (obj.type === "INSERT" || obj.type === "INSERT_SELECT" || obj.type === "UPSERT" || obj.type === "UPSERT_SELECT") {
|
|
6250
|
+
return [...obj.fields ?? [], ...obj.keyFields ?? []];
|
|
6251
|
+
}
|
|
6252
|
+
return [];
|
|
6253
|
+
}
|
|
6254
|
+
|
|
6255
|
+
// src/cli/index.ts
|
|
6256
|
+
var HELP_TEXT = `ksql - Execute SQL against kintone apps
|
|
6257
|
+
|
|
6258
|
+
Usage:
|
|
6259
|
+
ksql [options]
|
|
6260
|
+
ksql -e "<SQL>"
|
|
6261
|
+
ksql -f <file.sql>
|
|
6262
|
+
|
|
6263
|
+
Options:
|
|
6264
|
+
-e, --execute <sql> Execute SQL string
|
|
6265
|
+
-f, --file <path> Execute SQL file
|
|
6266
|
+
--console Start interactive console mode
|
|
6267
|
+
--dry-run Parse and show execution plan only
|
|
6268
|
+
--format <type> Output format: table | json | jsonl | csv | markdown | md
|
|
6269
|
+
--max-records <n> Max records to fetch (default: 500)
|
|
6270
|
+
--fetch-parallel <n> Parallel page fetches per query: 1-10 (default: 3)
|
|
6271
|
+
--on-limit <mode> On record limit: error | truncate
|
|
6272
|
+
--timeout <ms> Request timeout in milliseconds (default: 30000)
|
|
6273
|
+
--config <path> Config file path (default: ./ksql.config.json)
|
|
6274
|
+
--profile <name> Profile name in config
|
|
6275
|
+
--base-url <url> kintone base URL
|
|
6276
|
+
--guest-space-id <id> Guest space ID (uses /k/guest/<id>/v1 APIs)
|
|
6277
|
+
--auth <type> Auth type: token | userpass | auto
|
|
6278
|
+
--username <name> Login username (for userpass auth)
|
|
6279
|
+
--password <pass> Login password (for userpass auth)
|
|
6280
|
+
--token <token> Single-app token
|
|
6281
|
+
--token-map <mapping> App token map (APP100=...,APP101=...)
|
|
6282
|
+
--token-file <path> JSON file for app token map
|
|
6283
|
+
--app <id> Default app id context
|
|
6284
|
+
--diag-record-id <id> Diagnostic: GET record.json by app+id
|
|
6285
|
+
--no-header Hide table header
|
|
6286
|
+
--pretty Pretty-print JSON output
|
|
6287
|
+
--user-format <mode> User field format: full | name | code
|
|
6288
|
+
--array-format <mode> Array field format: full | join
|
|
6289
|
+
--table-format <mode> Subtable format: full | count
|
|
6290
|
+
--date-format <mode> Date format: full | local
|
|
6291
|
+
--attachment-format <mode> Attachment format: full | name | fileKey
|
|
6292
|
+
--output <path> Write output to file
|
|
6293
|
+
--no-color Disable ANSI colors
|
|
6294
|
+
--quiet Suppress non-result logs
|
|
6295
|
+
--debug Show request/response debug logs
|
|
6296
|
+
--debug-url Show only HTTP request URL debug logs
|
|
6297
|
+
--debug-headers Show request headers in debug logs (masked)
|
|
6298
|
+
--exit-on-empty Return exit code 1 when rowCount is 0
|
|
6299
|
+
--allow-dml Enable UPDATE/DELETE/INSERT/UPSERT/REORDER execution
|
|
6300
|
+
--yes Skip DML confirmation prompt
|
|
6301
|
+
--allow-without-where Allow UPDATE/DELETE without WHERE
|
|
6302
|
+
--dml-max-rows <n> Max affected rows for DML guard (default: 100)
|
|
6303
|
+
-h, --help Show help
|
|
6304
|
+
-v, --version Show version
|
|
6305
|
+
`;
|
|
6306
|
+
function parseArgs(argv) {
|
|
6307
|
+
const out = {
|
|
6308
|
+
help: false,
|
|
6309
|
+
version: false,
|
|
6310
|
+
executeSql: null,
|
|
6311
|
+
filePath: null,
|
|
6312
|
+
console: false,
|
|
6313
|
+
dryRun: false,
|
|
6314
|
+
format: null,
|
|
6315
|
+
maxRecords: null,
|
|
6316
|
+
fetchParallel: null,
|
|
6317
|
+
onLimit: null,
|
|
6318
|
+
timeout: null,
|
|
6319
|
+
configPath: null,
|
|
6320
|
+
profile: null,
|
|
6321
|
+
baseUrl: null,
|
|
6322
|
+
guestSpaceId: null,
|
|
6323
|
+
auth: null,
|
|
6324
|
+
username: null,
|
|
6325
|
+
password: null,
|
|
6326
|
+
token: null,
|
|
6327
|
+
tokenMap: {},
|
|
6328
|
+
tokenFile: null,
|
|
6329
|
+
app: null,
|
|
6330
|
+
diagRecordId: null,
|
|
6331
|
+
noHeader: false,
|
|
6332
|
+
pretty: false,
|
|
6333
|
+
outputPath: null,
|
|
6334
|
+
noColor: false,
|
|
6335
|
+
quiet: false,
|
|
6336
|
+
debug: false,
|
|
6337
|
+
debugUrl: false,
|
|
6338
|
+
debugHeaders: false,
|
|
6339
|
+
exitOnEmpty: false,
|
|
6340
|
+
allowDml: false,
|
|
6341
|
+
yes: false,
|
|
6342
|
+
allowWithoutWhere: false,
|
|
6343
|
+
dmlMaxRows: null,
|
|
6344
|
+
userFormat: null,
|
|
6345
|
+
arrayFormat: null,
|
|
6346
|
+
tableFormat: null,
|
|
6347
|
+
dateFormat: null,
|
|
6348
|
+
attachmentFormat: null
|
|
6349
|
+
};
|
|
6350
|
+
for (let i = 0; i < argv.length; i++) {
|
|
6351
|
+
const a = argv[i];
|
|
6352
|
+
if (a === "-h" || a === "--help") {
|
|
6353
|
+
out.help = true;
|
|
6354
|
+
continue;
|
|
6355
|
+
}
|
|
6356
|
+
if (a === "-v" || a === "--version") {
|
|
6357
|
+
out.version = true;
|
|
6358
|
+
continue;
|
|
6359
|
+
}
|
|
6360
|
+
if (a === "--console") {
|
|
6361
|
+
out.console = true;
|
|
6362
|
+
continue;
|
|
6363
|
+
}
|
|
6364
|
+
if (a === "--dry-run") {
|
|
6365
|
+
out.dryRun = true;
|
|
6366
|
+
continue;
|
|
6367
|
+
}
|
|
6368
|
+
if (a === "--no-header") {
|
|
6369
|
+
out.noHeader = true;
|
|
6370
|
+
continue;
|
|
6371
|
+
}
|
|
6372
|
+
if (a === "--pretty") {
|
|
6373
|
+
out.pretty = true;
|
|
6374
|
+
continue;
|
|
6375
|
+
}
|
|
6376
|
+
if (a === "--no-color") {
|
|
6377
|
+
out.noColor = true;
|
|
6378
|
+
continue;
|
|
6379
|
+
}
|
|
6380
|
+
if (a === "--quiet") {
|
|
6381
|
+
out.quiet = true;
|
|
6382
|
+
continue;
|
|
6383
|
+
}
|
|
6384
|
+
if (a === "--debug") {
|
|
6385
|
+
out.debug = true;
|
|
6386
|
+
continue;
|
|
6387
|
+
}
|
|
6388
|
+
if (a === "--debug-url") {
|
|
6389
|
+
out.debugUrl = true;
|
|
6390
|
+
continue;
|
|
6391
|
+
}
|
|
6392
|
+
if (a === "--debug-headers") {
|
|
6393
|
+
out.debugHeaders = true;
|
|
6394
|
+
continue;
|
|
6395
|
+
}
|
|
6396
|
+
if (a === "--exit-on-empty") {
|
|
6397
|
+
out.exitOnEmpty = true;
|
|
6398
|
+
continue;
|
|
6399
|
+
}
|
|
5966
6400
|
if (a === "--allow-dml") {
|
|
5967
6401
|
out.allowDml = true;
|
|
5968
6402
|
continue;
|
|
@@ -6094,6 +6528,13 @@ function parseArgs(argv) {
|
|
|
6094
6528
|
i++;
|
|
6095
6529
|
continue;
|
|
6096
6530
|
}
|
|
6531
|
+
if (a === "--fetch-parallel") {
|
|
6532
|
+
const n = Number(v);
|
|
6533
|
+
if (!Number.isInteger(n) || n < 1 || n > 10) throw new Error("ArgumentError: --fetch-parallel must be an integer between 1 and 10.");
|
|
6534
|
+
out.fetchParallel = n;
|
|
6535
|
+
i++;
|
|
6536
|
+
continue;
|
|
6537
|
+
}
|
|
6097
6538
|
if (a === "--timeout") {
|
|
6098
6539
|
const n = Number(v);
|
|
6099
6540
|
if (!Number.isInteger(n) || n <= 0) throw new Error("ArgumentError: --timeout must be a positive integer.");
|
|
@@ -6128,216 +6569,14 @@ function parseArgs(argv) {
|
|
|
6128
6569
|
}
|
|
6129
6570
|
function getVersion() {
|
|
6130
6571
|
const pkgPath = (0, import_path.resolve)(__dirname, "../package.json");
|
|
6131
|
-
const raw = (0,
|
|
6572
|
+
const raw = (0, import_fs2.readFileSync)(pkgPath, "utf-8");
|
|
6132
6573
|
const pkg = JSON.parse(raw);
|
|
6133
6574
|
return pkg.version ?? "0.0.0";
|
|
6134
6575
|
}
|
|
6135
6576
|
function loadConfig(configPath) {
|
|
6136
|
-
const raw = (0,
|
|
6577
|
+
const raw = (0, import_fs2.readFileSync)(configPath, "utf-8");
|
|
6137
6578
|
return JSON.parse(raw);
|
|
6138
6579
|
}
|
|
6139
|
-
function parseTokenMap(raw) {
|
|
6140
|
-
const out = {};
|
|
6141
|
-
if (!raw.trim()) return out;
|
|
6142
|
-
const pairs = raw.split(",");
|
|
6143
|
-
for (const pair of pairs) {
|
|
6144
|
-
const idx = pair.indexOf("=");
|
|
6145
|
-
if (idx <= 0) throw new Error("ArgumentError: --token-map must be APPxxx=token pairs.");
|
|
6146
|
-
const key = normalizeAppKey(pair.slice(0, idx).trim());
|
|
6147
|
-
const value = pair.slice(idx + 1).trim();
|
|
6148
|
-
if (!value) throw new Error(`ArgumentError: token is empty for ${key}.`);
|
|
6149
|
-
out[key] = value;
|
|
6150
|
-
}
|
|
6151
|
-
return out;
|
|
6152
|
-
}
|
|
6153
|
-
function parseTokenFile(path) {
|
|
6154
|
-
const raw = (0, import_fs.readFileSync)(path, "utf-8");
|
|
6155
|
-
const parsed = JSON.parse(raw);
|
|
6156
|
-
const out = {};
|
|
6157
|
-
for (const [k, v] of Object.entries(parsed)) out[normalizeAppKey(k)] = String(v);
|
|
6158
|
-
return out;
|
|
6159
|
-
}
|
|
6160
|
-
function normalizeAppKey(v) {
|
|
6161
|
-
const m1 = v.match(/^APP(\d+)$/i);
|
|
6162
|
-
if (m1) return `APP${m1[1]}`;
|
|
6163
|
-
const m2 = v.match(/^(\d+)$/);
|
|
6164
|
-
if (m2) return `APP${m2[1]}`;
|
|
6165
|
-
throw new Error(`ArgumentError: invalid app key "${v}"`);
|
|
6166
|
-
}
|
|
6167
|
-
function extractAppIds(sql) {
|
|
6168
|
-
const out = /* @__PURE__ */ new Set();
|
|
6169
|
-
for (const m of sql.matchAll(/\bAPP(\d+)\b/gi)) out.add(Number(m[1]));
|
|
6170
|
-
return [...out];
|
|
6171
|
-
}
|
|
6172
|
-
function isSqlIdentContinue(ch) {
|
|
6173
|
-
if (!ch) return false;
|
|
6174
|
-
const cp = ch.codePointAt(0);
|
|
6175
|
-
return cp >= 65 && cp <= 90 || // A-Z
|
|
6176
|
-
cp >= 97 && cp <= 122 || // a-z
|
|
6177
|
-
cp >= 48 && cp <= 57 || // 0-9
|
|
6178
|
-
cp === 95 || // _
|
|
6179
|
-
cp === 36 || // $
|
|
6180
|
-
cp >= 12352 && cp <= 12543 || cp >= 13312 && cp <= 40959 || cp >= 63744 && cp <= 64255 || cp >= 65281 && cp <= 65376;
|
|
6181
|
-
}
|
|
6182
|
-
function isProfileNameChar(ch) {
|
|
6183
|
-
if (!ch) return false;
|
|
6184
|
-
const cp = ch.codePointAt(0);
|
|
6185
|
-
return cp >= 65 && cp <= 90 || // A-Z
|
|
6186
|
-
cp >= 97 && cp <= 122 || // a-z
|
|
6187
|
-
cp >= 48 && cp <= 57 || // 0-9
|
|
6188
|
-
ch === "_" || ch === "-" || ch === "." || ch === "$";
|
|
6189
|
-
}
|
|
6190
|
-
function tryParseAppProfileToken(sql, start) {
|
|
6191
|
-
const head = sql.slice(start, start + 3);
|
|
6192
|
-
if (head.toUpperCase() !== "APP") return null;
|
|
6193
|
-
const prev = start > 0 ? sql[start - 1] : "";
|
|
6194
|
-
if (isSqlIdentContinue(prev)) return null;
|
|
6195
|
-
let i = start + 3;
|
|
6196
|
-
const digitStart = i;
|
|
6197
|
-
while (i < sql.length && /[0-9]/.test(sql[i])) i++;
|
|
6198
|
-
const digitEnd = i;
|
|
6199
|
-
if (digitEnd === digitStart) return null;
|
|
6200
|
-
if (sql[i] === "$") {
|
|
6201
|
-
i++;
|
|
6202
|
-
const subStart = i;
|
|
6203
|
-
while (i < sql.length && isSqlIdentContinue(sql[i])) i++;
|
|
6204
|
-
if (i === subStart) return null;
|
|
6205
|
-
}
|
|
6206
|
-
const appEnd = i;
|
|
6207
|
-
let profile = null;
|
|
6208
|
-
if (sql[i] === "@") {
|
|
6209
|
-
i++;
|
|
6210
|
-
const pStart = i;
|
|
6211
|
-
while (i < sql.length && isProfileNameChar(sql[i])) i++;
|
|
6212
|
-
if (i === pStart) return null;
|
|
6213
|
-
profile = sql.slice(pStart, i);
|
|
6214
|
-
}
|
|
6215
|
-
const next = i < sql.length ? sql[i] : "";
|
|
6216
|
-
if (isSqlIdentContinue(next)) return null;
|
|
6217
|
-
return {
|
|
6218
|
-
appId: Number(sql.slice(digitStart, digitEnd)),
|
|
6219
|
-
profile,
|
|
6220
|
-
start,
|
|
6221
|
-
digitStart,
|
|
6222
|
-
digitEnd,
|
|
6223
|
-
appEnd,
|
|
6224
|
-
fullEnd: i
|
|
6225
|
-
};
|
|
6226
|
-
}
|
|
6227
|
-
function collectAppProfileTokens(sql) {
|
|
6228
|
-
const tokens = [];
|
|
6229
|
-
let i = 0;
|
|
6230
|
-
while (i < sql.length) {
|
|
6231
|
-
const ch = sql[i];
|
|
6232
|
-
if (ch === "'") {
|
|
6233
|
-
i++;
|
|
6234
|
-
while (i < sql.length) {
|
|
6235
|
-
if (sql[i] === "'") {
|
|
6236
|
-
i++;
|
|
6237
|
-
if (i < sql.length && sql[i] === "'") {
|
|
6238
|
-
i++;
|
|
6239
|
-
continue;
|
|
6240
|
-
}
|
|
6241
|
-
break;
|
|
6242
|
-
}
|
|
6243
|
-
i++;
|
|
6244
|
-
}
|
|
6245
|
-
continue;
|
|
6246
|
-
}
|
|
6247
|
-
if (ch === "`") {
|
|
6248
|
-
i++;
|
|
6249
|
-
while (i < sql.length && sql[i] !== "`") i++;
|
|
6250
|
-
if (i < sql.length) i++;
|
|
6251
|
-
continue;
|
|
6252
|
-
}
|
|
6253
|
-
if (ch === "-" && sql[i + 1] === "-") {
|
|
6254
|
-
i += 2;
|
|
6255
|
-
while (i < sql.length && sql[i] !== "\n") i++;
|
|
6256
|
-
continue;
|
|
6257
|
-
}
|
|
6258
|
-
if (ch === "/" && sql[i + 1] === "*") {
|
|
6259
|
-
i += 2;
|
|
6260
|
-
while (i < sql.length) {
|
|
6261
|
-
if (sql[i] === "*" && sql[i + 1] === "/") {
|
|
6262
|
-
i += 2;
|
|
6263
|
-
break;
|
|
6264
|
-
}
|
|
6265
|
-
i++;
|
|
6266
|
-
}
|
|
6267
|
-
continue;
|
|
6268
|
-
}
|
|
6269
|
-
const parsed = tryParseAppProfileToken(sql, i);
|
|
6270
|
-
if (!parsed) {
|
|
6271
|
-
i++;
|
|
6272
|
-
continue;
|
|
6273
|
-
}
|
|
6274
|
-
tokens.push(parsed);
|
|
6275
|
-
i = parsed.fullEnd;
|
|
6276
|
-
}
|
|
6277
|
-
return tokens;
|
|
6278
|
-
}
|
|
6279
|
-
function nextVirtualAppId(used) {
|
|
6280
|
-
let id = 9e8;
|
|
6281
|
-
while (used.has(id)) id++;
|
|
6282
|
-
used.add(id);
|
|
6283
|
-
return id;
|
|
6284
|
-
}
|
|
6285
|
-
function normalizeSqlAppProfiles(sql, defaultProfile = "dev") {
|
|
6286
|
-
const tokens = collectAppProfileTokens(sql);
|
|
6287
|
-
const hasProfileSyntax = tokens.some((t) => t.profile !== null);
|
|
6288
|
-
const profilesByApp = /* @__PURE__ */ new Map();
|
|
6289
|
-
const normalizedProfile = (profile) => profile ?? defaultProfile;
|
|
6290
|
-
for (const t of tokens) {
|
|
6291
|
-
const p = normalizedProfile(t.profile);
|
|
6292
|
-
let set = profilesByApp.get(t.appId);
|
|
6293
|
-
if (!set) {
|
|
6294
|
-
set = /* @__PURE__ */ new Set();
|
|
6295
|
-
profilesByApp.set(t.appId, set);
|
|
6296
|
-
}
|
|
6297
|
-
set.add(p.toLowerCase());
|
|
6298
|
-
}
|
|
6299
|
-
const usedAppIds = new Set(tokens.map((t) => t.appId));
|
|
6300
|
-
const pairToMapped = /* @__PURE__ */ new Map();
|
|
6301
|
-
const appBindingByMappedApp = /* @__PURE__ */ new Map();
|
|
6302
|
-
for (const [appId, pSet] of profilesByApp.entries()) {
|
|
6303
|
-
const profiles = [...pSet].sort();
|
|
6304
|
-
if (profiles.length <= 1) continue;
|
|
6305
|
-
for (const pLower of profiles) {
|
|
6306
|
-
const mapped = nextVirtualAppId(usedAppIds);
|
|
6307
|
-
pairToMapped.set(`${appId}@${pLower}`, mapped);
|
|
6308
|
-
appBindingByMappedApp.set(mapped, { appId, profile: pLower });
|
|
6309
|
-
}
|
|
6310
|
-
}
|
|
6311
|
-
const out = [];
|
|
6312
|
-
let cursor = 0;
|
|
6313
|
-
for (const t of tokens) {
|
|
6314
|
-
const p = normalizedProfile(t.profile);
|
|
6315
|
-
const pLower = p.toLowerCase();
|
|
6316
|
-
const mapped = pairToMapped.get(`${t.appId}@${pLower}`) ?? t.appId;
|
|
6317
|
-
appBindingByMappedApp.set(mapped, { appId: t.appId, profile: pLower });
|
|
6318
|
-
out.push(sql.slice(cursor, t.start));
|
|
6319
|
-
out.push(sql.slice(t.start, t.digitStart));
|
|
6320
|
-
out.push(String(mapped));
|
|
6321
|
-
out.push(sql.slice(t.digitEnd, t.appEnd));
|
|
6322
|
-
cursor = t.fullEnd;
|
|
6323
|
-
}
|
|
6324
|
-
out.push(sql.slice(cursor));
|
|
6325
|
-
return {
|
|
6326
|
-
normalizedSql: out.join(""),
|
|
6327
|
-
hasProfileSyntax,
|
|
6328
|
-
appBindingByMappedApp
|
|
6329
|
-
};
|
|
6330
|
-
}
|
|
6331
|
-
function buildCacheContext(defaultProfile, appBindingByMappedApp) {
|
|
6332
|
-
if (appBindingByMappedApp.size === 0) return `default:${defaultProfile.toLowerCase()}`;
|
|
6333
|
-
const pairs = [...appBindingByMappedApp.entries()].sort((a, b) => a[0] - b[0]).map(([mappedAppId, b]) => `M${mappedAppId}=APP${b.appId}@${b.profile}`);
|
|
6334
|
-
return `apps:${pairs.join(",")}`;
|
|
6335
|
-
}
|
|
6336
|
-
function formatResolvedAppProfiles(sql, defaultProfile) {
|
|
6337
|
-
const parsed = normalizeSqlAppProfiles(sql, defaultProfile);
|
|
6338
|
-
if (parsed.appBindingByMappedApp.size === 0) return "(none)";
|
|
6339
|
-
return [...parsed.appBindingByMappedApp.values()].map((b) => `APP${b.appId}->${b.profile}`).join(", ");
|
|
6340
|
-
}
|
|
6341
6580
|
function resolveTokenValue(raw) {
|
|
6342
6581
|
if (raw.startsWith("env:")) {
|
|
6343
6582
|
const envKey = raw.slice(4);
|
|
@@ -6379,42 +6618,6 @@ function envAuth(name) {
|
|
|
6379
6618
|
if (v === "token" || v === "userpass" || v === "auto") return v;
|
|
6380
6619
|
return null;
|
|
6381
6620
|
}
|
|
6382
|
-
function isDmlType(type) {
|
|
6383
|
-
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT";
|
|
6384
|
-
}
|
|
6385
|
-
function hasWhereClause(stmt) {
|
|
6386
|
-
if (!stmt || typeof stmt !== "object") return false;
|
|
6387
|
-
const obj = stmt;
|
|
6388
|
-
return obj.where !== null && obj.where !== void 0;
|
|
6389
|
-
}
|
|
6390
|
-
function getStatementType(stmt) {
|
|
6391
|
-
if (!stmt || typeof stmt !== "object") return "UNKNOWN";
|
|
6392
|
-
const obj = stmt;
|
|
6393
|
-
return typeof obj.type === "string" ? obj.type : "UNKNOWN";
|
|
6394
|
-
}
|
|
6395
|
-
function isNoFromSelectStatement(stmt) {
|
|
6396
|
-
if (!stmt || typeof stmt !== "object") return false;
|
|
6397
|
-
const obj = stmt;
|
|
6398
|
-
return obj.type === "SELECT" && obj.from?.appId === 0 && obj.from?.cteName === "__NO_FROM__";
|
|
6399
|
-
}
|
|
6400
|
-
function getInsertValuesCount(stmt) {
|
|
6401
|
-
if (!stmt || typeof stmt !== "object") return null;
|
|
6402
|
-
const obj = stmt;
|
|
6403
|
-
if (obj.type !== "INSERT") return null;
|
|
6404
|
-
return Array.isArray(obj.values) ? obj.values.length : null;
|
|
6405
|
-
}
|
|
6406
|
-
function collectDmlTargetFields(stmt) {
|
|
6407
|
-
if (!stmt || typeof stmt !== "object") return [];
|
|
6408
|
-
const obj = stmt;
|
|
6409
|
-
if (!obj.type) return [];
|
|
6410
|
-
if (obj.type === "UPDATE") {
|
|
6411
|
-
return (obj.assignments ?? []).map((a) => a.field).filter((f) => Boolean(f));
|
|
6412
|
-
}
|
|
6413
|
-
if (obj.type === "INSERT" || obj.type === "INSERT_SELECT" || obj.type === "UPSERT" || obj.type === "UPSERT_SELECT") {
|
|
6414
|
-
return [...obj.fields ?? [], ...obj.keyFields ?? []];
|
|
6415
|
-
}
|
|
6416
|
-
return [];
|
|
6417
|
-
}
|
|
6418
6621
|
function normalizeUnique(values) {
|
|
6419
6622
|
const out = [];
|
|
6420
6623
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6683,6 +6886,7 @@ function buildReplExecArgv(base, sql, dryRun, format) {
|
|
|
6683
6886
|
pushOpt(argv, "--token-file", base.tokenFile);
|
|
6684
6887
|
pushOpt(argv, "--app", base.app);
|
|
6685
6888
|
pushOpt(argv, "--max-records", base.maxRecords);
|
|
6889
|
+
pushOpt(argv, "--fetch-parallel", base.fetchParallel);
|
|
6686
6890
|
pushOpt(argv, "--on-limit", base.onLimit);
|
|
6687
6891
|
pushOpt(argv, "--timeout", base.timeout);
|
|
6688
6892
|
pushOpt(argv, "--output", base.outputPath);
|
|
@@ -6799,9 +7003,9 @@ function getHistoryPath() {
|
|
|
6799
7003
|
}
|
|
6800
7004
|
function loadHistory(maxItems = 200) {
|
|
6801
7005
|
const p = getHistoryPath();
|
|
6802
|
-
if (!(0,
|
|
7006
|
+
if (!(0, import_fs2.existsSync)(p)) return [];
|
|
6803
7007
|
try {
|
|
6804
|
-
const raw = (0,
|
|
7008
|
+
const raw = (0, import_fs2.readFileSync)(p, "utf-8");
|
|
6805
7009
|
const lines = raw.split(/\r?\n/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
6806
7010
|
return lines.slice(-maxItems);
|
|
6807
7011
|
} catch {
|
|
@@ -6811,26 +7015,26 @@ function loadHistory(maxItems = 200) {
|
|
|
6811
7015
|
function appendHistory(sql) {
|
|
6812
7016
|
const p = getHistoryPath();
|
|
6813
7017
|
try {
|
|
6814
|
-
(0,
|
|
7018
|
+
(0, import_fs2.appendFileSync)(p, `${sql.replace(/\s+/g, " ").trim()}
|
|
6815
7019
|
`, "utf-8");
|
|
6816
7020
|
} catch {
|
|
6817
7021
|
}
|
|
6818
7022
|
}
|
|
6819
7023
|
function editBufferWithExternalEditor(current) {
|
|
6820
7024
|
const editor = process.env.KSQL_EDITOR ?? process.env.VISUAL ?? process.env.EDITOR ?? (process.platform === "win32" ? "notepad" : "vi");
|
|
6821
|
-
const dir = (0,
|
|
7025
|
+
const dir = (0, import_fs2.mkdtempSync)((0, import_path.join)((0, import_os.tmpdir)(), "ksql-edit-"));
|
|
6822
7026
|
const filePath = (0, import_path.join)(dir, "query.sql");
|
|
6823
7027
|
try {
|
|
6824
|
-
(0,
|
|
7028
|
+
(0, import_fs2.writeFileSync)(filePath, current, "utf-8");
|
|
6825
7029
|
const cmd = `"${editor}" "${filePath}"`;
|
|
6826
7030
|
const res = (0, import_child_process.spawnSync)(cmd, { stdio: "inherit", shell: true });
|
|
6827
7031
|
if (res.error) throw res.error;
|
|
6828
7032
|
if ((res.status ?? 0) !== 0) {
|
|
6829
7033
|
throw new Error(`Editor exited with code ${res.status ?? 1}`);
|
|
6830
7034
|
}
|
|
6831
|
-
return (0,
|
|
7035
|
+
return (0, import_fs2.readFileSync)(filePath, "utf-8").replace(/\r\n/g, "\n");
|
|
6832
7036
|
} finally {
|
|
6833
|
-
(0,
|
|
7037
|
+
(0, import_fs2.rmSync)(dir, { recursive: true, force: true });
|
|
6834
7038
|
}
|
|
6835
7039
|
}
|
|
6836
7040
|
async function runConsole(base) {
|
|
@@ -7012,11 +7216,11 @@ async function runConsole(base) {
|
|
|
7012
7216
|
}
|
|
7013
7217
|
try {
|
|
7014
7218
|
if (meta.append) {
|
|
7015
|
-
(0,
|
|
7219
|
+
(0, import_fs2.appendFileSync)(meta.path, lastOutput, "utf-8");
|
|
7016
7220
|
process.stdout.write(`saved (append): ${meta.path}
|
|
7017
7221
|
`);
|
|
7018
7222
|
} else {
|
|
7019
|
-
(0,
|
|
7223
|
+
(0, import_fs2.writeFileSync)(meta.path, lastOutput, "utf-8");
|
|
7020
7224
|
process.stdout.write(`saved: ${meta.path}
|
|
7021
7225
|
`);
|
|
7022
7226
|
}
|
|
@@ -7127,7 +7331,7 @@ async function run() {
|
|
|
7127
7331
|
let isDmlStatement = false;
|
|
7128
7332
|
if (args.diagRecordId === null) {
|
|
7129
7333
|
sql = args.executeSql;
|
|
7130
|
-
if (!sql && args.filePath) sql = (0,
|
|
7334
|
+
if (!sql && args.filePath) sql = (0, import_fs2.readFileSync)(args.filePath, "utf-8");
|
|
7131
7335
|
if (!sql || !sql.trim()) {
|
|
7132
7336
|
process.stderr.write("ArgumentError: SQL is empty.\n");
|
|
7133
7337
|
return 2;
|
|
@@ -7162,8 +7366,13 @@ async function run() {
|
|
|
7162
7366
|
}
|
|
7163
7367
|
}
|
|
7164
7368
|
const maxRecords = args.maxRecords ?? envInt("KSQL_MAX_RECORDS") ?? profile.query?.maxRecords ?? 500;
|
|
7369
|
+
const fetchParallel = args.fetchParallel ?? envInt("KSQL_FETCH_PARALLEL") ?? profile.query?.fetchParallel ?? 3;
|
|
7165
7370
|
const onLimit = args.onLimit ?? envOnLimit("KSQL_ON_LIMIT") ?? profile.query?.onLimit ?? "error";
|
|
7166
7371
|
const timeout = args.timeout ?? envInt("KSQL_TIMEOUT") ?? profile.query?.timeout ?? 3e4;
|
|
7372
|
+
if (!Number.isInteger(fetchParallel) || fetchParallel < 1 || fetchParallel > 10) {
|
|
7373
|
+
process.stderr.write("ArgumentError: fetch-parallel must be an integer between 1 and 10.\n");
|
|
7374
|
+
return 2;
|
|
7375
|
+
}
|
|
7167
7376
|
const rawFormat = args.format ?? envFormat("KSQL_FORMAT") ?? profile.output?.format ?? "table";
|
|
7168
7377
|
const format = normalizeOutputFormat(rawFormat);
|
|
7169
7378
|
if (!format) {
|
|
@@ -7208,7 +7417,7 @@ async function run() {
|
|
|
7208
7417
|
return 2;
|
|
7209
7418
|
}
|
|
7210
7419
|
if (!allowDml) {
|
|
7211
|
-
process.stderr.write("ArgumentError: DML is disabled. Use --allow-dml to enable UPDATE/DELETE/INSERT/UPSERT.\n");
|
|
7420
|
+
process.stderr.write("ArgumentError: DML is disabled. Use --allow-dml to enable UPDATE/DELETE/INSERT/UPSERT/REORDER.\n");
|
|
7212
7421
|
return 2;
|
|
7213
7422
|
}
|
|
7214
7423
|
if ((stmtType === "UPDATE" || stmtType === "DELETE") && !hasWhere && !allowWithoutWhere) {
|
|
@@ -7478,13 +7687,14 @@ query=${label}`);
|
|
|
7478
7687
|
};
|
|
7479
7688
|
const result = args.dryRun ? await execute(`EXPLAIN ${sql}`, client, { maxRecords, onLimitReached: onLimit, cacheContext }) : await execute(sql, client, {
|
|
7480
7689
|
maxRecords,
|
|
7690
|
+
fetchParallel,
|
|
7481
7691
|
onLimitReached: onLimit,
|
|
7482
7692
|
confirm: isDmlStatement ? confirm : void 0,
|
|
7483
7693
|
cacheContext
|
|
7484
7694
|
});
|
|
7485
7695
|
if (result.type !== "SELECT") {
|
|
7486
7696
|
const output2 = buildMutationOutput(result, format, noHeader, pretty);
|
|
7487
|
-
if (outputPath) (0,
|
|
7697
|
+
if (outputPath) (0, import_fs2.writeFileSync)(outputPath, `${output2}
|
|
7488
7698
|
`, "utf-8");
|
|
7489
7699
|
else if (output2) process.stdout.write(`${output2}
|
|
7490
7700
|
`);
|
|
@@ -7493,7 +7703,7 @@ query=${label}`);
|
|
|
7493
7703
|
return 0;
|
|
7494
7704
|
}
|
|
7495
7705
|
const output = buildOutput(result, format, noHeader, pretty, displayOptions);
|
|
7496
|
-
if (outputPath) (0,
|
|
7706
|
+
if (outputPath) (0, import_fs2.writeFileSync)(outputPath, `${output}
|
|
7497
7707
|
`, "utf-8");
|
|
7498
7708
|
else if (output) process.stdout.write(`${output}
|
|
7499
7709
|
`);
|