@rex0220/kintone-sql-tools 1.1.1 → 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 +125 -21
- 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 = {};
|
|
@@ -3998,7 +4054,21 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
|
|
|
3998
4054
|
const parallel = options.fetchParallel ?? 1;
|
|
3999
4055
|
await resolveSubqueries(stmt.where, client, options, cacheContext);
|
|
4000
4056
|
await resolveSubqueries(stmt.having, client, options, cacheContext);
|
|
4001
|
-
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(
|
|
4002
4072
|
stmt,
|
|
4003
4073
|
stmt.from,
|
|
4004
4074
|
client,
|
|
@@ -4006,11 +4076,39 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
|
|
|
4006
4076
|
parallel,
|
|
4007
4077
|
true,
|
|
4008
4078
|
options.onLimitReached ?? "error",
|
|
4009
|
-
warnings
|
|
4079
|
+
warnings,
|
|
4080
|
+
mainPushDown
|
|
4010
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;
|
|
4011
4106
|
const tables = /* @__PURE__ */ new Map();
|
|
4012
4107
|
tables.set(stmt.from.alias, mainRecords);
|
|
4013
|
-
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) => {
|
|
4014
4112
|
const optimized = await tryFetchJoinRecordsBySourceKeys(
|
|
4015
4113
|
stmt,
|
|
4016
4114
|
join2,
|
|
@@ -4019,7 +4117,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
|
|
|
4019
4117
|
maxRecords,
|
|
4020
4118
|
parallel,
|
|
4021
4119
|
options.onLimitReached ?? "error",
|
|
4022
|
-
warnings
|
|
4120
|
+
warnings,
|
|
4121
|
+
null
|
|
4023
4122
|
);
|
|
4024
4123
|
const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
|
|
4025
4124
|
stmt,
|
|
@@ -4029,11 +4128,11 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
|
|
|
4029
4128
|
parallel,
|
|
4030
4129
|
false,
|
|
4031
4130
|
options.onLimitReached ?? "error",
|
|
4032
|
-
warnings
|
|
4131
|
+
warnings,
|
|
4132
|
+
null
|
|
4033
4133
|
);
|
|
4034
4134
|
tables.set(join2.table.alias, joinRecords);
|
|
4035
|
-
});
|
|
4036
|
-
await Promise.all(joinFetches);
|
|
4135
|
+
}));
|
|
4037
4136
|
const scalarCache = await resolveScalarColumns(stmt.columns, client, options, cacheContext);
|
|
4038
4137
|
const optionOrders = await buildOptionOrdersForSelect(stmt, client, cacheContext);
|
|
4039
4138
|
const sortKinds = await buildSortKindsForSelect(stmt, client, cacheContext);
|
|
@@ -4246,13 +4345,15 @@ function processRowToKintoneRecord(row) {
|
|
|
4246
4345
|
Object.entries(row).map(([k, v]) => [k, { value: v ?? "" }])
|
|
4247
4346
|
);
|
|
4248
4347
|
}
|
|
4249
|
-
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) {
|
|
4250
4349
|
const fields = selectToFetchAllFields(stmt, table);
|
|
4251
4350
|
const onTruncate = (max) => {
|
|
4252
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`);
|
|
4253
4352
|
};
|
|
4254
4353
|
if (!table.subtableCode) {
|
|
4255
|
-
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;
|
|
4256
4357
|
const resolved = await fetchRecordsForSharedPlan(client.getRecords, table.appId, query, fields, {
|
|
4257
4358
|
parallel,
|
|
4258
4359
|
maxRecords,
|
|
@@ -4287,7 +4388,7 @@ function splitChunks(items, size) {
|
|
|
4287
4388
|
var JOIN_IN_CHUNK_SIZE = 50;
|
|
4288
4389
|
var JOIN_IN_MAX_CHUNKS = 6;
|
|
4289
4390
|
var JOIN_IN_MAX_KEYS = JOIN_IN_CHUNK_SIZE * JOIN_IN_MAX_CHUNKS;
|
|
4290
|
-
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) {
|
|
4291
4392
|
if (join2.type !== "INNER") return null;
|
|
4292
4393
|
if (!join2.table.alias) return null;
|
|
4293
4394
|
if (join2.table.subtableCode) return null;
|
|
@@ -4320,7 +4421,7 @@ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxR
|
|
|
4320
4421
|
if (values.length === 0) return [];
|
|
4321
4422
|
if (values.length > JOIN_IN_MAX_KEYS) {
|
|
4322
4423
|
warnings.add(
|
|
4323
|
-
`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`
|
|
4324
4425
|
);
|
|
4325
4426
|
return null;
|
|
4326
4427
|
}
|
|
@@ -4332,7 +4433,8 @@ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxR
|
|
|
4332
4433
|
const merged = [];
|
|
4333
4434
|
const seen = /* @__PURE__ */ new Set();
|
|
4334
4435
|
for (const chunk2 of chunks) {
|
|
4335
|
-
const
|
|
4436
|
+
const inClause = `${joinField} in (${chunk2.map(sqlQuote).join(",")})`;
|
|
4437
|
+
const query = pushDownCond !== null ? `(${inClause}) and (${whereToKintone(pushDownCond)})` : inClause;
|
|
4336
4438
|
const resolved = await fetchRecordsForSharedPlan(client.getRecords, join2.table.appId, query, fields, {
|
|
4337
4439
|
parallel,
|
|
4338
4440
|
maxRecords,
|
|
@@ -5148,19 +5250,21 @@ function buildSelectPlan(stmt, label) {
|
|
|
5148
5250
|
lines.push(` kintone query: ${params.query || "(\u306A\u3057)"}`);
|
|
5149
5251
|
lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
|
|
5150
5252
|
} else {
|
|
5151
|
-
const mainParams = selectToFetchAllParams(stmt, stmt.from.appId);
|
|
5152
5253
|
const mainFields = selectToFetchAllFields(stmt, stmt.from);
|
|
5153
|
-
const
|
|
5154
|
-
const
|
|
5155
|
-
|
|
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})`);
|
|
5156
5258
|
lines.push(` kintone query: ${mainQ}`);
|
|
5157
5259
|
lines.push(` fields: ${mainFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : mainFields.join(", ")}`);
|
|
5158
5260
|
for (const join2 of stmt.joins) {
|
|
5159
5261
|
const joinFields = selectToFetchAllFields(stmt, join2.table);
|
|
5160
|
-
const
|
|
5262
|
+
const joinAliasStr = join2.table.alias ? ` AS ${join2.table.alias}` : "";
|
|
5161
5263
|
const joinType = join2.type === "INNER" ? "JOIN" : `${join2.type} JOIN`;
|
|
5162
|
-
|
|
5163
|
-
|
|
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}`);
|
|
5164
5268
|
lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
|
|
5165
5269
|
}
|
|
5166
5270
|
}
|
|
@@ -7045,7 +7149,7 @@ async function run() {
|
|
|
7045
7149
|
isDmlStatement = isDmlType(stmtType);
|
|
7046
7150
|
hasWhere = hasWhereClause(stmt);
|
|
7047
7151
|
insertValuesCount = getInsertValuesCount(stmt);
|
|
7048
|
-
const supported = stmtType === "SELECT" || isDmlStatement;
|
|
7152
|
+
const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || isDmlStatement;
|
|
7049
7153
|
if (!supported) {
|
|
7050
7154
|
process.stderr.write(`ArgumentError: unsupported statement type in CLI: ${stmtType}
|
|
7051
7155
|
`);
|
|
@@ -7093,7 +7197,7 @@ async function run() {
|
|
|
7093
7197
|
const appIds = sql ? extractAppIds(sql) : [];
|
|
7094
7198
|
const defaultApp = args.app ?? envInt("KSQL_APP") ?? profile.app ?? null;
|
|
7095
7199
|
if (appIds.length === 0 && defaultApp !== null) appIds.push(defaultApp);
|
|
7096
|
-
const allowNoFromSelect = isNoFromSelectStatement(parsedStmt);
|
|
7200
|
+
const allowNoFromSelect = isNoFromSelectStatement(parsedStmt) || stmtType === "SHOW_APPS";
|
|
7097
7201
|
if (appIds.length === 0 && !allowNoFromSelect && !args.dryRun && args.diagRecordId === null) {
|
|
7098
7202
|
process.stderr.write("ArgumentError: no APPxxx found in SQL and --app is not set.\n");
|
|
7099
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/",
|