@rex0220/kintone-sql-tools 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cli/ksql.js +131 -22
- package/package.json +2 -2
package/dist-cli/ksql.js
CHANGED
|
@@ -3299,6 +3299,62 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
|
|
|
3299
3299
|
};
|
|
3300
3300
|
}
|
|
3301
3301
|
|
|
3302
|
+
// src/core/optimization/wherePredicatePushdown.ts
|
|
3303
|
+
function extractTableCondition(where, tableAlias) {
|
|
3304
|
+
switch (where.type) {
|
|
3305
|
+
case "BINARY":
|
|
3306
|
+
if (!isSingleTableField(where.left, tableAlias)) return null;
|
|
3307
|
+
if (!isPushDownableRight(where.right)) return null;
|
|
3308
|
+
return where;
|
|
3309
|
+
case "NULL_CHECK":
|
|
3310
|
+
if (!isSingleTableField(where.field, tableAlias)) return null;
|
|
3311
|
+
return where;
|
|
3312
|
+
case "LOGICAL":
|
|
3313
|
+
if (where.op === "AND") {
|
|
3314
|
+
const left = extractTableCondition(where.left, tableAlias);
|
|
3315
|
+
const right = extractTableCondition(where.right, tableAlias);
|
|
3316
|
+
if (left && right) return { ...where, left, right };
|
|
3317
|
+
return left ?? right ?? null;
|
|
3318
|
+
}
|
|
3319
|
+
return referencesOnlyTable(where, tableAlias) ? where : null;
|
|
3320
|
+
case "NOT":
|
|
3321
|
+
case "GROUP":
|
|
3322
|
+
return referencesOnlyTable(where, tableAlias) ? where : null;
|
|
3323
|
+
case "EXISTS":
|
|
3324
|
+
return null;
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
function isSingleTableField(field, tableAlias) {
|
|
3328
|
+
if (field.type !== "FIELD") return false;
|
|
3329
|
+
return field.tableAlias === tableAlias;
|
|
3330
|
+
}
|
|
3331
|
+
function isPushDownableRight(value) {
|
|
3332
|
+
switch (value.type) {
|
|
3333
|
+
case "STRING":
|
|
3334
|
+
case "NUMBER":
|
|
3335
|
+
case "KINTONE_FUNC":
|
|
3336
|
+
case "IN_LIST":
|
|
3337
|
+
return true;
|
|
3338
|
+
default:
|
|
3339
|
+
return false;
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
function referencesOnlyTable(expr, tableAlias) {
|
|
3343
|
+
switch (expr.type) {
|
|
3344
|
+
case "BINARY":
|
|
3345
|
+
return isSingleTableField(expr.left, tableAlias) && isPushDownableRight(expr.right);
|
|
3346
|
+
case "NULL_CHECK":
|
|
3347
|
+
return isSingleTableField(expr.field, tableAlias);
|
|
3348
|
+
case "LOGICAL":
|
|
3349
|
+
return referencesOnlyTable(expr.left, tableAlias) && referencesOnlyTable(expr.right, tableAlias);
|
|
3350
|
+
case "NOT":
|
|
3351
|
+
case "GROUP":
|
|
3352
|
+
return referencesOnlyTable(expr.expr, tableAlias);
|
|
3353
|
+
case "EXISTS":
|
|
3354
|
+
return false;
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3302
3358
|
// src/engine/process.ts
|
|
3303
3359
|
function flatten(record, alias) {
|
|
3304
3360
|
const row = {};
|
|
@@ -3652,7 +3708,12 @@ function project(rows, columns, scalarCache) {
|
|
|
3652
3708
|
}
|
|
3653
3709
|
case "STRFUNC_COL": {
|
|
3654
3710
|
const key = col.alias ?? stringFuncDefaultKey(col.expr);
|
|
3655
|
-
|
|
3711
|
+
if (hasAggregateInStringFuncExpr2(col.expr)) {
|
|
3712
|
+
const srcKey = stringFuncDefaultKey(col.expr);
|
|
3713
|
+
out[key] = row[col.alias ?? srcKey] ?? row[srcKey] ?? evalStringFunc(col.expr, row);
|
|
3714
|
+
} else {
|
|
3715
|
+
out[key] = evalStringFunc(col.expr, row);
|
|
3716
|
+
}
|
|
3656
3717
|
if (rowIdx === 0) orderedKeys.push(key);
|
|
3657
3718
|
break;
|
|
3658
3719
|
}
|
|
@@ -3993,7 +4054,21 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
|
|
|
3993
4054
|
const parallel = options.fetchParallel ?? 1;
|
|
3994
4055
|
await resolveSubqueries(stmt.where, client, options, cacheContext);
|
|
3995
4056
|
await resolveSubqueries(stmt.having, client, options, cacheContext);
|
|
3996
|
-
const
|
|
4057
|
+
const tableConditions = /* @__PURE__ */ new Map();
|
|
4058
|
+
if (stmt.where !== null) {
|
|
4059
|
+
if (stmt.from.alias) {
|
|
4060
|
+
const cond = extractTableCondition(stmt.where, stmt.from.alias);
|
|
4061
|
+
if (cond) tableConditions.set(stmt.from.alias, cond);
|
|
4062
|
+
}
|
|
4063
|
+
for (const join2 of stmt.joins) {
|
|
4064
|
+
if (join2.table.alias) {
|
|
4065
|
+
const cond = extractTableCondition(stmt.where, join2.table.alias);
|
|
4066
|
+
if (cond) tableConditions.set(join2.table.alias, cond);
|
|
4067
|
+
}
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
const mainPushDown = stmt.from.alias ? tableConditions.get(stmt.from.alias) ?? null : null;
|
|
4071
|
+
const mainFetch = fetchTableRecordsForFullScan(
|
|
3997
4072
|
stmt,
|
|
3998
4073
|
stmt.from,
|
|
3999
4074
|
client,
|
|
@@ -4001,11 +4076,39 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
|
|
|
4001
4076
|
parallel,
|
|
4002
4077
|
true,
|
|
4003
4078
|
options.onLimitReached ?? "error",
|
|
4004
|
-
warnings
|
|
4079
|
+
warnings,
|
|
4080
|
+
mainPushDown
|
|
4005
4081
|
);
|
|
4082
|
+
const parallelJoins = [];
|
|
4083
|
+
const onOptJoins = [];
|
|
4084
|
+
for (const join2 of stmt.joins) {
|
|
4085
|
+
const jCond = join2.table.alias ? tableConditions.get(join2.table.alias) ?? null : null;
|
|
4086
|
+
if (jCond !== null) {
|
|
4087
|
+
parallelJoins.push({
|
|
4088
|
+
join: join2,
|
|
4089
|
+
promise: fetchTableRecordsForFullScan(
|
|
4090
|
+
stmt,
|
|
4091
|
+
join2.table,
|
|
4092
|
+
client,
|
|
4093
|
+
maxRecords,
|
|
4094
|
+
parallel,
|
|
4095
|
+
false,
|
|
4096
|
+
options.onLimitReached ?? "error",
|
|
4097
|
+
warnings,
|
|
4098
|
+
jCond
|
|
4099
|
+
)
|
|
4100
|
+
});
|
|
4101
|
+
} else {
|
|
4102
|
+
onOptJoins.push(join2);
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
const mainRecords = await mainFetch;
|
|
4006
4106
|
const tables = /* @__PURE__ */ new Map();
|
|
4007
4107
|
tables.set(stmt.from.alias, mainRecords);
|
|
4008
|
-
const
|
|
4108
|
+
for (const { join: join2, promise } of parallelJoins) {
|
|
4109
|
+
tables.set(join2.table.alias, await promise);
|
|
4110
|
+
}
|
|
4111
|
+
await Promise.all(onOptJoins.map(async (join2) => {
|
|
4009
4112
|
const optimized = await tryFetchJoinRecordsBySourceKeys(
|
|
4010
4113
|
stmt,
|
|
4011
4114
|
join2,
|
|
@@ -4014,7 +4117,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
|
|
|
4014
4117
|
maxRecords,
|
|
4015
4118
|
parallel,
|
|
4016
4119
|
options.onLimitReached ?? "error",
|
|
4017
|
-
warnings
|
|
4120
|
+
warnings,
|
|
4121
|
+
null
|
|
4018
4122
|
);
|
|
4019
4123
|
const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
|
|
4020
4124
|
stmt,
|
|
@@ -4024,11 +4128,11 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
|
|
|
4024
4128
|
parallel,
|
|
4025
4129
|
false,
|
|
4026
4130
|
options.onLimitReached ?? "error",
|
|
4027
|
-
warnings
|
|
4131
|
+
warnings,
|
|
4132
|
+
null
|
|
4028
4133
|
);
|
|
4029
4134
|
tables.set(join2.table.alias, joinRecords);
|
|
4030
|
-
});
|
|
4031
|
-
await Promise.all(joinFetches);
|
|
4135
|
+
}));
|
|
4032
4136
|
const scalarCache = await resolveScalarColumns(stmt.columns, client, options, cacheContext);
|
|
4033
4137
|
const optionOrders = await buildOptionOrdersForSelect(stmt, client, cacheContext);
|
|
4034
4138
|
const sortKinds = await buildSortKindsForSelect(stmt, client, cacheContext);
|
|
@@ -4241,13 +4345,15 @@ function processRowToKintoneRecord(row) {
|
|
|
4241
4345
|
Object.entries(row).map(([k, v]) => [k, { value: v ?? "" }])
|
|
4242
4346
|
);
|
|
4243
4347
|
}
|
|
4244
|
-
async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings) {
|
|
4348
|
+
async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings, pushDownCond = null) {
|
|
4245
4349
|
const fields = selectToFetchAllFields(stmt, table);
|
|
4246
4350
|
const onTruncate = (max) => {
|
|
4247
4351
|
warnings.add(`\u53D6\u5F97\u4E0A\u9650\uFF08${max} \u4EF6\uFF09\u306B\u9054\u3057\u305F\u305F\u3081\u3001${max} \u4EF6\u3067\u6253\u3061\u5207\u3063\u3066\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002`);
|
|
4248
4352
|
};
|
|
4249
4353
|
if (!table.subtableCode) {
|
|
4250
|
-
const
|
|
4354
|
+
const baseQuery = isMainTable ? selectToFetchAllParams(stmt, table.appId).query : "";
|
|
4355
|
+
const pushQuery = pushDownCond !== null ? whereToKintone(pushDownCond) : "";
|
|
4356
|
+
const query = baseQuery && pushQuery ? `(${baseQuery}) and (${pushQuery})` : baseQuery || pushQuery;
|
|
4251
4357
|
const resolved = await fetchRecordsForSharedPlan(client.getRecords, table.appId, query, fields, {
|
|
4252
4358
|
parallel,
|
|
4253
4359
|
maxRecords,
|
|
@@ -4282,7 +4388,7 @@ function splitChunks(items, size) {
|
|
|
4282
4388
|
var JOIN_IN_CHUNK_SIZE = 50;
|
|
4283
4389
|
var JOIN_IN_MAX_CHUNKS = 6;
|
|
4284
4390
|
var JOIN_IN_MAX_KEYS = JOIN_IN_CHUNK_SIZE * JOIN_IN_MAX_CHUNKS;
|
|
4285
|
-
async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxRecords, parallel, onLimit, warnings) {
|
|
4391
|
+
async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxRecords, parallel, onLimit, warnings, pushDownCond = null) {
|
|
4286
4392
|
if (join2.type !== "INNER") return null;
|
|
4287
4393
|
if (!join2.table.alias) return null;
|
|
4288
4394
|
if (join2.table.subtableCode) return null;
|
|
@@ -4315,7 +4421,7 @@ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxR
|
|
|
4315
4421
|
if (values.length === 0) return [];
|
|
4316
4422
|
if (values.length > JOIN_IN_MAX_KEYS) {
|
|
4317
4423
|
warnings.add(
|
|
4318
|
-
`JOIN\u30AD\u30FC\u304C ${values.length} \u4EF6\u306E\u305F\u3081
|
|
4424
|
+
`JOIN\u30AD\u30FC\u304C ${values.length} \u4EF6\u306E\u305F\u3081 ON \u6700\u9069\u5316\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u3001JOIN\u5148\u3092\u5168\u4EF6\u53D6\u5F97\u3057\u307E\u3059\uFF08\u4E0A\u9650 ${JOIN_IN_MAX_KEYS} \u4EF6\uFF09\u3002`
|
|
4319
4425
|
);
|
|
4320
4426
|
return null;
|
|
4321
4427
|
}
|
|
@@ -4327,7 +4433,8 @@ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxR
|
|
|
4327
4433
|
const merged = [];
|
|
4328
4434
|
const seen = /* @__PURE__ */ new Set();
|
|
4329
4435
|
for (const chunk2 of chunks) {
|
|
4330
|
-
const
|
|
4436
|
+
const inClause = `${joinField} in (${chunk2.map(sqlQuote).join(",")})`;
|
|
4437
|
+
const query = pushDownCond !== null ? `(${inClause}) and (${whereToKintone(pushDownCond)})` : inClause;
|
|
4331
4438
|
const resolved = await fetchRecordsForSharedPlan(client.getRecords, join2.table.appId, query, fields, {
|
|
4332
4439
|
parallel,
|
|
4333
4440
|
maxRecords,
|
|
@@ -5143,19 +5250,21 @@ function buildSelectPlan(stmt, label) {
|
|
|
5143
5250
|
lines.push(` kintone query: ${params.query || "(\u306A\u3057)"}`);
|
|
5144
5251
|
lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
|
|
5145
5252
|
} else {
|
|
5146
|
-
const mainParams = selectToFetchAllParams(stmt, stmt.from.appId);
|
|
5147
5253
|
const mainFields = selectToFetchAllFields(stmt, stmt.from);
|
|
5148
|
-
const
|
|
5149
|
-
const
|
|
5150
|
-
|
|
5254
|
+
const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
|
|
5255
|
+
const mainPushDown = stmt.from.alias && stmt.where ? extractTableCondition(stmt.where, stmt.from.alias) : null;
|
|
5256
|
+
const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
|
|
5257
|
+
lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
|
|
5151
5258
|
lines.push(` kintone query: ${mainQ}`);
|
|
5152
5259
|
lines.push(` fields: ${mainFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : mainFields.join(", ")}`);
|
|
5153
5260
|
for (const join2 of stmt.joins) {
|
|
5154
5261
|
const joinFields = selectToFetchAllFields(stmt, join2.table);
|
|
5155
|
-
const
|
|
5262
|
+
const joinAliasStr = join2.table.alias ? ` AS ${join2.table.alias}` : "";
|
|
5156
5263
|
const joinType = join2.type === "INNER" ? "JOIN" : `${join2.type} JOIN`;
|
|
5157
|
-
|
|
5158
|
-
|
|
5264
|
+
const joinPushDown = join2.table.alias && stmt.where ? extractTableCondition(stmt.where, join2.table.alias) : null;
|
|
5265
|
+
const joinQ = joinPushDown !== null ? whereToKintone(joinPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
|
|
5266
|
+
lines.push(` ${joinType}: APP${join2.table.appId}${joinAliasStr} (${join2.table.appId})`);
|
|
5267
|
+
lines.push(` kintone query: ${joinQ}`);
|
|
5159
5268
|
lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
|
|
5160
5269
|
}
|
|
5161
5270
|
}
|
|
@@ -7040,7 +7149,7 @@ async function run() {
|
|
|
7040
7149
|
isDmlStatement = isDmlType(stmtType);
|
|
7041
7150
|
hasWhere = hasWhereClause(stmt);
|
|
7042
7151
|
insertValuesCount = getInsertValuesCount(stmt);
|
|
7043
|
-
const supported = stmtType === "SELECT" || isDmlStatement;
|
|
7152
|
+
const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || isDmlStatement;
|
|
7044
7153
|
if (!supported) {
|
|
7045
7154
|
process.stderr.write(`ArgumentError: unsupported statement type in CLI: ${stmtType}
|
|
7046
7155
|
`);
|
|
@@ -7088,7 +7197,7 @@ async function run() {
|
|
|
7088
7197
|
const appIds = sql ? extractAppIds(sql) : [];
|
|
7089
7198
|
const defaultApp = args.app ?? envInt("KSQL_APP") ?? profile.app ?? null;
|
|
7090
7199
|
if (appIds.length === 0 && defaultApp !== null) appIds.push(defaultApp);
|
|
7091
|
-
const allowNoFromSelect = isNoFromSelectStatement(parsedStmt);
|
|
7200
|
+
const allowNoFromSelect = isNoFromSelectStatement(parsedStmt) || stmtType === "SHOW_APPS";
|
|
7092
7201
|
if (appIds.length === 0 && !allowNoFromSelect && !args.dryRun && args.diagRecordId === null) {
|
|
7093
7202
|
process.stderr.write("ArgumentError: no APPxxx found in SQL and --app is not set.\n");
|
|
7094
7203
|
return 2;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rex0220/kintone-sql-tools",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "kintone SQL plugin and CLI tools (ksql)",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"prepack": "npm run build:cli",
|
|
26
26
|
"start": "run-p develop upload",
|
|
27
27
|
"develop": "node build.mjs --watch",
|
|
28
|
-
"upload": "rex0220-plugin-uploader -f dist/ksql-plugin-v1.1.
|
|
28
|
+
"upload": "rex0220-plugin-uploader -f dist/ksql-plugin-v1.1.2.zip --watch"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
"dist-cli/",
|