@rex0220/kintone-sql-tools 2.8.0 → 2.10.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/dist-cli/ksql.js +375 -270
- package/dist-mcp/ksql-mcp.js +376 -271
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-cli/ksql.js
CHANGED
|
@@ -449,6 +449,9 @@ function isJapanese(cp) {
|
|
|
449
449
|
return cp >= 12352 && cp <= 12543 || cp >= 13312 && cp <= 40959 || cp >= 63744 && cp <= 64255 || cp >= 65281 && cp <= 65376;
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
+
// src/types/ast.ts
|
|
453
|
+
var NO_FROM_CTE_NAME = "__NO_FROM__";
|
|
454
|
+
|
|
452
455
|
// src/parser/parser.ts
|
|
453
456
|
var MAX_BATCH_STATEMENTS = 20;
|
|
454
457
|
var FUNC_CALL_PREFIX_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -962,7 +965,7 @@ var Parser = class {
|
|
|
962
965
|
const distinct = this.consume("DISTINCT" /* DISTINCT */);
|
|
963
966
|
const columns = this.parseSelectColumns();
|
|
964
967
|
const hasFrom = this.consume("FROM" /* FROM */);
|
|
965
|
-
const from = hasFrom ? this.parseTableRef() : { appId: 0, alias: null, cteName:
|
|
968
|
+
const from = hasFrom ? this.parseTableRef() : { appId: 0, alias: null, cteName: NO_FROM_CTE_NAME };
|
|
966
969
|
const joins = hasFrom ? this.parseJoins() : [];
|
|
967
970
|
const where = this.consume("WHERE" /* WHERE */) ? this.parseWhereExpr() : null;
|
|
968
971
|
let groupBy = [];
|
|
@@ -2350,7 +2353,7 @@ function hasWhereClause(stmt) {
|
|
|
2350
2353
|
function isNoFromSelectStatement(stmt) {
|
|
2351
2354
|
if (!stmt || typeof stmt !== "object") return false;
|
|
2352
2355
|
const obj = stmt;
|
|
2353
|
-
return obj.type === "SELECT" && obj.from?.appId === 0 && obj.from?.cteName ===
|
|
2356
|
+
return obj.type === "SELECT" && obj.from?.appId === 0 && obj.from?.cteName === NO_FROM_CTE_NAME;
|
|
2354
2357
|
}
|
|
2355
2358
|
function getInsertValuesCount(stmt) {
|
|
2356
2359
|
if (!stmt || typeof stmt !== "object") return null;
|
|
@@ -3116,6 +3119,235 @@ function isAggregateSyntheticName(name) {
|
|
|
3116
3119
|
return /^(COUNT|SUM|AVG|MAX|MIN)\(/i.test(name);
|
|
3117
3120
|
}
|
|
3118
3121
|
|
|
3122
|
+
// src/core/cteInlining.ts
|
|
3123
|
+
function canInlineSingleCte(stmt) {
|
|
3124
|
+
if (stmt.ctes.length !== 1) return false;
|
|
3125
|
+
const cteDef = stmt.ctes[0];
|
|
3126
|
+
if (cteDef.query.type !== "SELECT" || resolveSelectMode(cteDef.query) !== "SIMPLE") return false;
|
|
3127
|
+
const finalQuery = stmt.query;
|
|
3128
|
+
if (finalQuery.type !== "SELECT") return false;
|
|
3129
|
+
if (finalQuery.from.cteName !== cteDef.name || finalQuery.joins.length > 0) return false;
|
|
3130
|
+
if (finalQuery.groupBy.length > 0 || finalQuery.distinct) return false;
|
|
3131
|
+
return !finalQuery.columns.some(
|
|
3132
|
+
(column) => column.type === "AGGREGATE" || column.type === "ARITH_AGG_COL"
|
|
3133
|
+
);
|
|
3134
|
+
}
|
|
3135
|
+
function buildInlinedQuery(stmt) {
|
|
3136
|
+
const cteBody = stmt.ctes[0].query;
|
|
3137
|
+
const final = stmt.query;
|
|
3138
|
+
const finalWhere = stripCteAlias(final.where, final.from.alias);
|
|
3139
|
+
const where = cteBody.where === null ? finalWhere : finalWhere === null ? cteBody.where : { type: "LOGICAL", op: "AND", left: cteBody.where, right: finalWhere };
|
|
3140
|
+
const columns = final.columns.every((column) => column.type === "WILDCARD") ? cteBody.columns : final.columns;
|
|
3141
|
+
return {
|
|
3142
|
+
type: "SELECT",
|
|
3143
|
+
from: cteBody.from,
|
|
3144
|
+
joins: [],
|
|
3145
|
+
columns,
|
|
3146
|
+
where,
|
|
3147
|
+
groupBy: [],
|
|
3148
|
+
having: null,
|
|
3149
|
+
orderBy: final.orderBy.length > 0 ? final.orderBy : cteBody.orderBy,
|
|
3150
|
+
limit: final.limit ?? cteBody.limit,
|
|
3151
|
+
offset: final.offset ?? cteBody.offset,
|
|
3152
|
+
distinct: false
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
function stripCteAlias(where, alias) {
|
|
3156
|
+
if (where === null || alias === null) return where;
|
|
3157
|
+
switch (where.type) {
|
|
3158
|
+
case "BINARY":
|
|
3159
|
+
return { ...where, left: stripCteAliasFromFieldValue(where.left, alias) };
|
|
3160
|
+
case "NULL_CHECK":
|
|
3161
|
+
return { ...where, field: stripCteAliasFromFieldValue(where.field, alias) };
|
|
3162
|
+
case "LOGICAL":
|
|
3163
|
+
return {
|
|
3164
|
+
...where,
|
|
3165
|
+
left: stripCteAlias(where.left, alias),
|
|
3166
|
+
right: stripCteAlias(where.right, alias)
|
|
3167
|
+
};
|
|
3168
|
+
case "NOT":
|
|
3169
|
+
case "GROUP":
|
|
3170
|
+
return { ...where, expr: stripCteAlias(where.expr, alias) };
|
|
3171
|
+
case "EXISTS":
|
|
3172
|
+
return where;
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
function stripCteAliasFromFieldValue(value, alias) {
|
|
3176
|
+
if (value.type === "FIELD" && value.tableAlias === alias) {
|
|
3177
|
+
return { ...value, tableAlias: null };
|
|
3178
|
+
}
|
|
3179
|
+
return value;
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
// src/core/optimization/wherePredicatePushdown.ts
|
|
3183
|
+
function extractSafePushdownLeaves(where, options = {}) {
|
|
3184
|
+
return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
|
|
3185
|
+
}
|
|
3186
|
+
function extractTypedPushdownCandidates(where, options = {}) {
|
|
3187
|
+
return extractAndLeaves(
|
|
3188
|
+
where,
|
|
3189
|
+
(expr) => isNumericCandidate(expr, options) || isSelectionInCandidate(expr, options)
|
|
3190
|
+
);
|
|
3191
|
+
}
|
|
3192
|
+
function extractAndLeaves(where, accept) {
|
|
3193
|
+
switch (where.type) {
|
|
3194
|
+
case "BINARY":
|
|
3195
|
+
return accept(where) ? where : null;
|
|
3196
|
+
case "LOGICAL":
|
|
3197
|
+
if (where.op !== "AND") return null;
|
|
3198
|
+
{
|
|
3199
|
+
const left = extractAndLeaves(where.left, accept);
|
|
3200
|
+
const right = extractAndLeaves(where.right, accept);
|
|
3201
|
+
if (left && right) return { ...where, left, right };
|
|
3202
|
+
return left ?? right ?? null;
|
|
3203
|
+
}
|
|
3204
|
+
case "GROUP":
|
|
3205
|
+
return extractAndLeaves(where.expr, accept);
|
|
3206
|
+
case "NULL_CHECK":
|
|
3207
|
+
case "NOT":
|
|
3208
|
+
case "EXISTS":
|
|
3209
|
+
return null;
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
function isSafeComparison(expr, options) {
|
|
3213
|
+
if (isKlikeComparison(expr, options)) return true;
|
|
3214
|
+
if (isSafeIdComparison(expr, options)) return true;
|
|
3215
|
+
if (isNumericCandidate(expr, options)) {
|
|
3216
|
+
return options.fieldTypes?.get(expr.left.field) === "NUMBER";
|
|
3217
|
+
}
|
|
3218
|
+
return isSelectionInComparison(expr, options);
|
|
3219
|
+
}
|
|
3220
|
+
function isKlikeComparison(expr, options) {
|
|
3221
|
+
if (options.allowKlike === false) return false;
|
|
3222
|
+
if (expr.op !== "KLIKE" && expr.op !== "NOT_KLIKE") return false;
|
|
3223
|
+
if (expr.left.type !== "FIELD" || !isTargetField(expr.left, options)) return false;
|
|
3224
|
+
return expr.right.type === "STRING" || options.allowUnresolvedKlikeVariables === true && expr.right.type === "VARIABLE";
|
|
3225
|
+
}
|
|
3226
|
+
var SELECTION_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
3227
|
+
"DROP_DOWN",
|
|
3228
|
+
"RADIO_BUTTON",
|
|
3229
|
+
"CHECK_BOX",
|
|
3230
|
+
"MULTI_SELECT",
|
|
3231
|
+
"STATUS"
|
|
3232
|
+
]);
|
|
3233
|
+
function isSelectionInComparison(expr, options) {
|
|
3234
|
+
if (!isSelectionInCandidate(expr, options)) return false;
|
|
3235
|
+
if (expr.left.type !== "FIELD" || expr.right.type !== "IN_LIST") return false;
|
|
3236
|
+
const fieldType = options.fieldTypes?.get(expr.left.field);
|
|
3237
|
+
if (fieldType === void 0 || !SELECTION_IN_FIELD_TYPES.has(fieldType)) return false;
|
|
3238
|
+
const validOptions = options.fieldOptions?.get(expr.left.field);
|
|
3239
|
+
if (validOptions === void 0) return false;
|
|
3240
|
+
return expr.right.values.every(
|
|
3241
|
+
(value) => value.type === "STRING" && value.value !== "" && validOptions.has(value.value)
|
|
3242
|
+
);
|
|
3243
|
+
}
|
|
3244
|
+
function isSafeIdComparison(expr, options) {
|
|
3245
|
+
if (!isTargetIdField(expr.left, options)) return false;
|
|
3246
|
+
if (expr.right.type !== "NUMBER") return false;
|
|
3247
|
+
return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
|
|
3248
|
+
}
|
|
3249
|
+
function isTargetIdField(field, options) {
|
|
3250
|
+
if (field.type !== "FIELD" || field.field !== "$id") return false;
|
|
3251
|
+
const targetAlias = options.tableAlias ?? null;
|
|
3252
|
+
if (field.tableAlias === targetAlias) return true;
|
|
3253
|
+
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
3254
|
+
}
|
|
3255
|
+
function isNumericCandidate(expr, options) {
|
|
3256
|
+
if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
|
|
3257
|
+
if (!isTargetField(expr.left, options)) return false;
|
|
3258
|
+
if (expr.right.type !== "NUMBER") return false;
|
|
3259
|
+
if (expr.op === "=") return true;
|
|
3260
|
+
return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
|
|
3261
|
+
}
|
|
3262
|
+
function isSelectionInCandidate(expr, options) {
|
|
3263
|
+
if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
|
|
3264
|
+
if (!isTargetField(expr.left, options)) return false;
|
|
3265
|
+
if (expr.op !== "IN" && expr.op !== "NOT_IN") return false;
|
|
3266
|
+
if (expr.right.type !== "IN_LIST" || expr.right.values.length === 0) return false;
|
|
3267
|
+
return expr.right.values.every((value) => value.type === "STRING" && value.value !== "");
|
|
3268
|
+
}
|
|
3269
|
+
function isTargetField(field, options) {
|
|
3270
|
+
const targetAlias = options.tableAlias ?? null;
|
|
3271
|
+
if (field.tableAlias === targetAlias) return true;
|
|
3272
|
+
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
// src/core/optimization/klikePushdownPlan.ts
|
|
3276
|
+
function buildKlikePushdownPlan(stmt, options = {}) {
|
|
3277
|
+
const joinsAreSafeForKlike = stmt.joins.every((join2) => join2.type === "INNER");
|
|
3278
|
+
const common = {
|
|
3279
|
+
allowKlike: joinsAreSafeForKlike,
|
|
3280
|
+
allowUnresolvedKlikeVariables: options.allowUnresolvedVariables
|
|
3281
|
+
};
|
|
3282
|
+
let mainCondition = null;
|
|
3283
|
+
if (stmt.where !== null && !stmt.from.subtableCode && stmt.from.cteName === null) {
|
|
3284
|
+
if (stmt.joins.length === 0) {
|
|
3285
|
+
mainCondition = extractSafePushdownLeaves(stmt.where, {
|
|
3286
|
+
...common,
|
|
3287
|
+
tableAlias: stmt.from.alias ?? void 0,
|
|
3288
|
+
allowUnqualifiedFields: true,
|
|
3289
|
+
fieldTypes: options.fieldTypesByApp?.get(stmt.from.appId),
|
|
3290
|
+
fieldOptions: options.fieldOptionsByApp?.get(stmt.from.appId)
|
|
3291
|
+
});
|
|
3292
|
+
} else if (stmt.from.alias) {
|
|
3293
|
+
mainCondition = extractSafePushdownLeaves(stmt.where, {
|
|
3294
|
+
...common,
|
|
3295
|
+
tableAlias: stmt.from.alias,
|
|
3296
|
+
fieldTypes: options.fieldTypesByApp?.get(stmt.from.appId),
|
|
3297
|
+
fieldOptions: options.fieldOptionsByApp?.get(stmt.from.appId)
|
|
3298
|
+
});
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
const joinConditions = /* @__PURE__ */ new Map();
|
|
3302
|
+
if (stmt.where !== null) {
|
|
3303
|
+
for (const join2 of stmt.joins) {
|
|
3304
|
+
if (!join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
|
|
3305
|
+
const condition = extractSafePushdownLeaves(stmt.where, {
|
|
3306
|
+
...common,
|
|
3307
|
+
tableAlias: join2.table.alias,
|
|
3308
|
+
fieldTypes: options.fieldTypesByApp?.get(join2.table.appId),
|
|
3309
|
+
fieldOptions: options.fieldOptionsByApp?.get(join2.table.appId)
|
|
3310
|
+
});
|
|
3311
|
+
if (condition !== null) joinConditions.set(join2.table.alias, condition);
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
const appliedKlikes = /* @__PURE__ */ new Set();
|
|
3315
|
+
collectKlikes(mainCondition, appliedKlikes);
|
|
3316
|
+
for (const condition of joinConditions.values()) collectKlikes(condition, appliedKlikes);
|
|
3317
|
+
const allKlikes = /* @__PURE__ */ new Set();
|
|
3318
|
+
collectKlikes(stmt.where, allKlikes);
|
|
3319
|
+
return {
|
|
3320
|
+
mainCondition,
|
|
3321
|
+
joinConditions,
|
|
3322
|
+
appliedKlikes,
|
|
3323
|
+
allKlikes: [...allKlikes]
|
|
3324
|
+
};
|
|
3325
|
+
}
|
|
3326
|
+
function unappliedKlikes(plan) {
|
|
3327
|
+
return plan.allKlikes.filter((expr) => !plan.appliedKlikes.has(expr));
|
|
3328
|
+
}
|
|
3329
|
+
function collectKlikes(where, out) {
|
|
3330
|
+
if (where === null) return;
|
|
3331
|
+
if (isKlike(where)) {
|
|
3332
|
+
out.add(where);
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
3335
|
+
switch (where.type) {
|
|
3336
|
+
case "LOGICAL":
|
|
3337
|
+
collectKlikes(where.left, out);
|
|
3338
|
+
collectKlikes(where.right, out);
|
|
3339
|
+
return;
|
|
3340
|
+
case "NOT":
|
|
3341
|
+
case "GROUP":
|
|
3342
|
+
collectKlikes(where.expr, out);
|
|
3343
|
+
return;
|
|
3344
|
+
case "BINARY":
|
|
3345
|
+
case "NULL_CHECK":
|
|
3346
|
+
case "EXISTS":
|
|
3347
|
+
return;
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3119
3351
|
// src/core/klikeValidation.ts
|
|
3120
3352
|
var KlikeValidationError = class extends Error {
|
|
3121
3353
|
constructor(message) {
|
|
@@ -3126,6 +3358,13 @@ var KlikeValidationError = class extends Error {
|
|
|
3126
3358
|
function validateKlikeStatement(stmt) {
|
|
3127
3359
|
validateStatement(stmt);
|
|
3128
3360
|
}
|
|
3361
|
+
function validateKlikePushdownPlan(plan) {
|
|
3362
|
+
if (unappliedKlikes(plan).length > 0) {
|
|
3363
|
+
throw new KlikeValidationError(
|
|
3364
|
+
"FULL_SCAN \u306E KLIKE / NOT KLIKE \u3092\u5B89\u5168\u306B\u62BC\u3057\u4E0B\u3052\u3089\u308C\u307E\u305B\u3093\u3002OR / NOT \u914D\u4E0B\u3001CTE\u30FB\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u3001LEFT / RIGHT JOIN \u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
3365
|
+
);
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3129
3368
|
function validateStatement(stmt) {
|
|
3130
3369
|
switch (stmt.type) {
|
|
3131
3370
|
case "SELECT":
|
|
@@ -3157,7 +3396,7 @@ function validateStatement(stmt) {
|
|
|
3157
3396
|
case "REORDER":
|
|
3158
3397
|
if (containsKlike(stmt)) {
|
|
3159
3398
|
throw new KlikeValidationError(
|
|
3160
|
-
"KLIKE / NOT KLIKE \u306F
|
|
3399
|
+
"KLIKE / NOT KLIKE \u306F\u5168 DML\uFF08UPDATE / DELETE / INSERT / UPSERT / REORDER\uFF09\u3067\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
|
|
3161
3400
|
);
|
|
3162
3401
|
}
|
|
3163
3402
|
return;
|
|
@@ -3177,9 +3416,8 @@ function validateUnion(stmt) {
|
|
|
3177
3416
|
validateSelect(stmt.right);
|
|
3178
3417
|
}
|
|
3179
3418
|
function validateWith(stmt) {
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
validateSelect(inlined);
|
|
3419
|
+
if (canInlineSingleCte(stmt)) {
|
|
3420
|
+
validateSelect(buildInlinedQuery(stmt));
|
|
3183
3421
|
return;
|
|
3184
3422
|
}
|
|
3185
3423
|
for (const cte of stmt.ctes) {
|
|
@@ -3191,10 +3429,15 @@ function validateWith(stmt) {
|
|
|
3191
3429
|
function validateSelect(stmt) {
|
|
3192
3430
|
validateOwnKlikeExpressions(stmt);
|
|
3193
3431
|
if (whereHasKlike(stmt.where)) {
|
|
3194
|
-
const
|
|
3195
|
-
if (
|
|
3432
|
+
const directKintoneSimple = resolveSelectMode(stmt) === "SIMPLE" && stmt.from.cteName === null && stmt.joins.every((join2) => join2.table.cteName === null);
|
|
3433
|
+
if (!directKintoneSimple) {
|
|
3434
|
+
const plan = buildKlikePushdownPlan(stmt, { allowUnresolvedVariables: true });
|
|
3435
|
+
if (unappliedKlikes(plan).length === 0) {
|
|
3436
|
+
validateNestedSelects(stmt);
|
|
3437
|
+
return;
|
|
3438
|
+
}
|
|
3196
3439
|
throw new KlikeValidationError(
|
|
3197
|
-
"KLIKE / NOT KLIKE \u306F
|
|
3440
|
+
"FULL_SCAN \u306E KLIKE / NOT KLIKE \u306F\u3001\u7269\u7406\u30C6\u30FC\u30D6\u30EB\u306B\u5BFE\u3059\u308B AND \u30EA\u30FC\u30D5\u3068\u3057\u3066\u5FC5\u305A\u62BC\u3057\u4E0B\u3052\u3089\u308C\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002OR / NOT \u914D\u4E0B\u3001CTE\u30FB\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u3001LEFT / RIGHT JOIN \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
|
|
3198
3441
|
);
|
|
3199
3442
|
}
|
|
3200
3443
|
}
|
|
@@ -3224,33 +3467,6 @@ function validateNestedSelects(node) {
|
|
|
3224
3467
|
if (obj.type === "SELECT") validateSelect(obj);
|
|
3225
3468
|
}, true);
|
|
3226
3469
|
}
|
|
3227
|
-
function buildEffectiveInlineSelect(stmt) {
|
|
3228
|
-
if (stmt.ctes.length !== 1) return null;
|
|
3229
|
-
const cte = stmt.ctes[0];
|
|
3230
|
-
if (cte.query.type !== "SELECT" || resolveSelectMode(cte.query) !== "SIMPLE") return null;
|
|
3231
|
-
if (stmt.query.type !== "SELECT") return null;
|
|
3232
|
-
const final = stmt.query;
|
|
3233
|
-
if (final.from.cteName !== cte.name || final.joins.length > 0) return null;
|
|
3234
|
-
if (final.groupBy.length > 0 || final.distinct) return null;
|
|
3235
|
-
if (final.columns.some(
|
|
3236
|
-
(column) => column.type === "AGGREGATE" || column.type === "ARITH_AGG_COL"
|
|
3237
|
-
)) return null;
|
|
3238
|
-
const where = cte.query.where === null ? final.where : final.where === null ? cte.query.where : { type: "LOGICAL", op: "AND", left: cte.query.where, right: final.where };
|
|
3239
|
-
const columns = final.columns.every((column) => column.type === "WILDCARD") ? cte.query.columns : final.columns;
|
|
3240
|
-
return {
|
|
3241
|
-
type: "SELECT",
|
|
3242
|
-
from: cte.query.from,
|
|
3243
|
-
joins: [],
|
|
3244
|
-
columns,
|
|
3245
|
-
where,
|
|
3246
|
-
groupBy: [],
|
|
3247
|
-
having: null,
|
|
3248
|
-
orderBy: final.orderBy.length > 0 ? final.orderBy : cte.query.orderBy,
|
|
3249
|
-
limit: final.limit ?? cte.query.limit,
|
|
3250
|
-
offset: final.offset ?? cte.query.offset,
|
|
3251
|
-
distinct: false
|
|
3252
|
-
};
|
|
3253
|
-
}
|
|
3254
3470
|
function containsKlike(node) {
|
|
3255
3471
|
let found = false;
|
|
3256
3472
|
walkObjects(node, (obj) => {
|
|
@@ -3755,25 +3971,29 @@ function resolveFieldRef(row, field) {
|
|
|
3755
3971
|
}
|
|
3756
3972
|
|
|
3757
3973
|
// src/engine/evalWhere.ts
|
|
3758
|
-
function evalWhere(expr, row, resolveFieldType) {
|
|
3974
|
+
function evalWhere(expr, row, resolveFieldType, appliedKlikes) {
|
|
3759
3975
|
switch (expr.type) {
|
|
3760
3976
|
case "BINARY":
|
|
3761
|
-
return evalBinary(expr, row, resolveFieldType);
|
|
3977
|
+
return evalBinary(expr, row, resolveFieldType, appliedKlikes);
|
|
3762
3978
|
case "NULL_CHECK":
|
|
3763
3979
|
return evalNullCheck(expr, row);
|
|
3764
3980
|
case "LOGICAL":
|
|
3765
|
-
return evalLogical(expr, row, resolveFieldType);
|
|
3981
|
+
return evalLogical(expr, row, resolveFieldType, appliedKlikes);
|
|
3766
3982
|
case "NOT":
|
|
3767
|
-
return !evalWhere(expr.expr, row, resolveFieldType);
|
|
3983
|
+
return !evalWhere(expr.expr, row, resolveFieldType, appliedKlikes);
|
|
3768
3984
|
case "GROUP":
|
|
3769
|
-
return evalWhere(expr.expr, row, resolveFieldType);
|
|
3985
|
+
return evalWhere(expr.expr, row, resolveFieldType, appliedKlikes);
|
|
3770
3986
|
case "EXISTS": {
|
|
3771
3987
|
const exists = expr.resolved;
|
|
3772
3988
|
return expr.not ? !exists : exists;
|
|
3773
3989
|
}
|
|
3774
3990
|
}
|
|
3775
3991
|
}
|
|
3776
|
-
function evalBinary(expr, row, resolveFieldType) {
|
|
3992
|
+
function evalBinary(expr, row, resolveFieldType, appliedKlikes) {
|
|
3993
|
+
if (expr.op === "KLIKE" || expr.op === "NOT_KLIKE") {
|
|
3994
|
+
if (appliedKlikes?.has(expr)) return true;
|
|
3995
|
+
throw new Error("KLIKE / NOT KLIKE \u306F\u62BC\u3057\u4E0B\u3052\u6E08\u307F\u96C6\u5408\u306B\u542B\u307E\u308C\u306A\u3044\u305F\u3081 JavaScript \u5074\u3067\u306F\u8A55\u4FA1\u3067\u304D\u307E\u305B\u3093");
|
|
3996
|
+
}
|
|
3777
3997
|
const left = resolveField(expr.left, row, resolveFieldType);
|
|
3778
3998
|
const fieldType = expr.left.type === "FIELD" ? resolveFieldType?.(expr.left) : void 0;
|
|
3779
3999
|
return evalOp(expr.op, left, expr.right, row, fieldType, resolveFieldType);
|
|
@@ -3855,11 +4075,11 @@ function evalNullCheck(expr, row) {
|
|
|
3855
4075
|
const val = resolveField(expr.field, row);
|
|
3856
4076
|
return expr.not ? val !== "" : val === "";
|
|
3857
4077
|
}
|
|
3858
|
-
function evalLogical(expr, row, resolveFieldType) {
|
|
4078
|
+
function evalLogical(expr, row, resolveFieldType, appliedKlikes) {
|
|
3859
4079
|
if (expr.op === "AND") {
|
|
3860
|
-
return evalWhere(expr.left, row, resolveFieldType) && evalWhere(expr.right, row, resolveFieldType);
|
|
4080
|
+
return evalWhere(expr.left, row, resolveFieldType, appliedKlikes) && evalWhere(expr.right, row, resolveFieldType, appliedKlikes);
|
|
3861
4081
|
}
|
|
3862
|
-
return evalWhere(expr.left, row, resolveFieldType) || evalWhere(expr.right, row, resolveFieldType);
|
|
4082
|
+
return evalWhere(expr.left, row, resolveFieldType, appliedKlikes) || evalWhere(expr.right, row, resolveFieldType, appliedKlikes);
|
|
3863
4083
|
}
|
|
3864
4084
|
function resolveField(field, row, resolveFieldType) {
|
|
3865
4085
|
if (field.type === "FUNC_FIELD") return evalStringFunc(field.expr, row);
|
|
@@ -3960,7 +4180,7 @@ function matchLike(value, pattern) {
|
|
|
3960
4180
|
function assertDmlWhereIsSafe(where) {
|
|
3961
4181
|
if (whereHasKlike(where)) {
|
|
3962
4182
|
throw new DmlConvertError(
|
|
3963
|
-
"UPDATE / DELETE \u306E WHERE \u306B KLIKE / NOT KLIKE \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002kintone \u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u306E\u6253\u3061\u5207\u308A\u3092\u691C\u51FA\u3067\u304D\u306A\u3044\u305F\u3081\
|
|
4183
|
+
"UPDATE / DELETE \u306E WHERE \u306B KLIKE / NOT KLIKE \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002kintone \u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u306E\u6253\u3061\u5207\u308A\u3092\u691C\u51FA\u3067\u304D\u306A\u3044\u305F\u3081\u3001\u5168 DML \u3067\u5B89\u5168\u4E0A\u62D2\u5426\u3057\u3066\u3044\u307E\u3059\u3002"
|
|
3964
4184
|
);
|
|
3965
4185
|
}
|
|
3966
4186
|
if (!whereHasLike(where)) return;
|
|
@@ -4302,6 +4522,7 @@ async function fetchAll(fetcher, app, query, fields, options = {}) {
|
|
|
4302
4522
|
let windowOffset = 0;
|
|
4303
4523
|
const cursorQuery0 = buildCursorQuery(query, cursorId);
|
|
4304
4524
|
const first = await fetchPage(fetcher, app, cursorQuery0, fetchFields, pageSize, windowOffset);
|
|
4525
|
+
notifySearchAborted(first, options);
|
|
4305
4526
|
allRecords.push(...first.records);
|
|
4306
4527
|
if (allRecords.length > maxRecords) {
|
|
4307
4528
|
if (onLimit === "truncate") {
|
|
@@ -4345,6 +4566,7 @@ async function fetchAll(fetcher, app, query, fields, options = {}) {
|
|
|
4345
4566
|
(offset) => fetchPage(fetcher, app, cq, fetchFields, pageSize, offset)
|
|
4346
4567
|
)
|
|
4347
4568
|
);
|
|
4569
|
+
for (const response of responses) notifySearchAborted(response, options);
|
|
4348
4570
|
let done = false;
|
|
4349
4571
|
for (const res of responses) {
|
|
4350
4572
|
allRecords.push(...res.records);
|
|
@@ -4373,6 +4595,9 @@ async function fetchAll(fetcher, app, query, fields, options = {}) {
|
|
|
4373
4595
|
}
|
|
4374
4596
|
return allRecords;
|
|
4375
4597
|
}
|
|
4598
|
+
function notifySearchAborted(response, options) {
|
|
4599
|
+
if (response.searchAborted) options.onSearchAborted?.();
|
|
4600
|
+
}
|
|
4376
4601
|
function extractIds(records) {
|
|
4377
4602
|
return records.map((r) => {
|
|
4378
4603
|
const raw = r["$id"]?.value;
|
|
@@ -4429,7 +4654,8 @@ async function fetchRecordsForSharedPlan(getRecords, app, query, fields, options
|
|
|
4429
4654
|
maxRecords: options.maxRecords,
|
|
4430
4655
|
parallel: options.parallel,
|
|
4431
4656
|
onLimit: options.onLimit ?? "error",
|
|
4432
|
-
onTruncate: options.onTruncate
|
|
4657
|
+
onTruncate: options.onTruncate,
|
|
4658
|
+
onSearchAborted: options.onSearchAborted
|
|
4433
4659
|
});
|
|
4434
4660
|
return {
|
|
4435
4661
|
records,
|
|
@@ -4450,92 +4676,6 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
|
|
|
4450
4676
|
};
|
|
4451
4677
|
}
|
|
4452
4678
|
|
|
4453
|
-
// src/core/optimization/wherePredicatePushdown.ts
|
|
4454
|
-
function extractSafePushdownLeaves(where, options = {}) {
|
|
4455
|
-
return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
|
|
4456
|
-
}
|
|
4457
|
-
function extractTypedPushdownCandidates(where, options = {}) {
|
|
4458
|
-
return extractAndLeaves(
|
|
4459
|
-
where,
|
|
4460
|
-
(expr) => isNumericCandidate(expr, options) || isSelectionInCandidate(expr, options)
|
|
4461
|
-
);
|
|
4462
|
-
}
|
|
4463
|
-
function extractAndLeaves(where, accept) {
|
|
4464
|
-
switch (where.type) {
|
|
4465
|
-
case "BINARY":
|
|
4466
|
-
return accept(where) ? where : null;
|
|
4467
|
-
case "LOGICAL":
|
|
4468
|
-
if (where.op !== "AND") return null;
|
|
4469
|
-
{
|
|
4470
|
-
const left = extractAndLeaves(where.left, accept);
|
|
4471
|
-
const right = extractAndLeaves(where.right, accept);
|
|
4472
|
-
if (left && right) return { ...where, left, right };
|
|
4473
|
-
return left ?? right ?? null;
|
|
4474
|
-
}
|
|
4475
|
-
case "GROUP":
|
|
4476
|
-
return extractAndLeaves(where.expr, accept);
|
|
4477
|
-
case "NULL_CHECK":
|
|
4478
|
-
case "NOT":
|
|
4479
|
-
case "EXISTS":
|
|
4480
|
-
return null;
|
|
4481
|
-
}
|
|
4482
|
-
}
|
|
4483
|
-
function isSafeComparison(expr, options) {
|
|
4484
|
-
if (isSafeIdComparison(expr, options)) return true;
|
|
4485
|
-
if (isNumericCandidate(expr, options)) {
|
|
4486
|
-
return options.fieldTypes?.get(expr.left.field) === "NUMBER";
|
|
4487
|
-
}
|
|
4488
|
-
return isSelectionInComparison(expr, options);
|
|
4489
|
-
}
|
|
4490
|
-
var SELECTION_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
4491
|
-
"DROP_DOWN",
|
|
4492
|
-
"RADIO_BUTTON",
|
|
4493
|
-
"CHECK_BOX",
|
|
4494
|
-
"MULTI_SELECT",
|
|
4495
|
-
"STATUS"
|
|
4496
|
-
]);
|
|
4497
|
-
function isSelectionInComparison(expr, options) {
|
|
4498
|
-
if (!isSelectionInCandidate(expr, options)) return false;
|
|
4499
|
-
if (expr.left.type !== "FIELD" || expr.right.type !== "IN_LIST") return false;
|
|
4500
|
-
const fieldType = options.fieldTypes?.get(expr.left.field);
|
|
4501
|
-
if (fieldType === void 0 || !SELECTION_IN_FIELD_TYPES.has(fieldType)) return false;
|
|
4502
|
-
const validOptions = options.fieldOptions?.get(expr.left.field);
|
|
4503
|
-
if (validOptions === void 0) return false;
|
|
4504
|
-
return expr.right.values.every(
|
|
4505
|
-
(value) => value.type === "STRING" && value.value !== "" && validOptions.has(value.value)
|
|
4506
|
-
);
|
|
4507
|
-
}
|
|
4508
|
-
function isSafeIdComparison(expr, options) {
|
|
4509
|
-
if (!isTargetIdField(expr.left, options)) return false;
|
|
4510
|
-
if (expr.right.type !== "NUMBER") return false;
|
|
4511
|
-
return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
|
|
4512
|
-
}
|
|
4513
|
-
function isTargetIdField(field, options) {
|
|
4514
|
-
if (field.type !== "FIELD" || field.field !== "$id") return false;
|
|
4515
|
-
const targetAlias = options.tableAlias ?? null;
|
|
4516
|
-
if (field.tableAlias === targetAlias) return true;
|
|
4517
|
-
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
4518
|
-
}
|
|
4519
|
-
function isNumericCandidate(expr, options) {
|
|
4520
|
-
if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
|
|
4521
|
-
if (!isTargetField(expr.left, options)) return false;
|
|
4522
|
-
if (expr.right.type !== "NUMBER") return false;
|
|
4523
|
-
if (expr.op === "=") return true;
|
|
4524
|
-
return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
|
|
4525
|
-
}
|
|
4526
|
-
function isSelectionInCandidate(expr, options) {
|
|
4527
|
-
if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
|
|
4528
|
-
if (!isTargetField(expr.left, options)) return false;
|
|
4529
|
-
if (expr.op !== "IN" && expr.op !== "NOT_IN") return false;
|
|
4530
|
-
if (expr.right.type !== "IN_LIST" || expr.right.values.length === 0) return false;
|
|
4531
|
-
return expr.right.values.every((value) => value.type === "STRING" && value.value !== "");
|
|
4532
|
-
}
|
|
4533
|
-
function isTargetField(field, options) {
|
|
4534
|
-
const targetAlias = options.tableAlias ?? null;
|
|
4535
|
-
if (field.tableAlias === targetAlias) return true;
|
|
4536
|
-
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
4537
|
-
}
|
|
4538
|
-
|
|
4539
4679
|
// src/engine/process.ts
|
|
4540
4680
|
function flatten(record, alias) {
|
|
4541
4681
|
const row = {};
|
|
@@ -4600,9 +4740,9 @@ function applyJoin(leftRows, rightRows, join2) {
|
|
|
4600
4740
|
}
|
|
4601
4741
|
return result;
|
|
4602
4742
|
}
|
|
4603
|
-
function applyFilter(rows, where, resolveFieldType) {
|
|
4743
|
+
function applyFilter(rows, where, resolveFieldType, appliedKlikes) {
|
|
4604
4744
|
if (where === null) return rows;
|
|
4605
|
-
return rows.filter((row) => evalWhere(where, row, resolveFieldType));
|
|
4745
|
+
return rows.filter((row) => evalWhere(where, row, resolveFieldType, appliedKlikes));
|
|
4606
4746
|
}
|
|
4607
4747
|
function hasAggregateColumns(columns) {
|
|
4608
4748
|
return columns.some(
|
|
@@ -5059,7 +5199,8 @@ function runFullScan(input) {
|
|
|
5059
5199
|
optionOrders,
|
|
5060
5200
|
sortKinds,
|
|
5061
5201
|
fieldTypeResolver,
|
|
5062
|
-
havingFieldTypeResolver
|
|
5202
|
+
havingFieldTypeResolver,
|
|
5203
|
+
appliedKlikes
|
|
5063
5204
|
} = input;
|
|
5064
5205
|
let rows = [];
|
|
5065
5206
|
const mainAlias = stmt.from.alias;
|
|
@@ -5071,7 +5212,7 @@ function runFullScan(input) {
|
|
|
5071
5212
|
const rightRows = rightRecords.map((r) => flatten(r, rightAlias));
|
|
5072
5213
|
rows = applyJoin(rows, rightRows, join2);
|
|
5073
5214
|
}
|
|
5074
|
-
rows = applyFilter(rows, stmt.where, fieldTypeResolver);
|
|
5215
|
+
rows = applyFilter(rows, stmt.where, fieldTypeResolver, appliedKlikes);
|
|
5075
5216
|
if (stmt.groupBy.length > 0 || hasAggregateColumns(stmt.columns)) {
|
|
5076
5217
|
rows = applyGroupBy(rows, stmt.groupBy, stmt.columns);
|
|
5077
5218
|
}
|
|
@@ -5127,13 +5268,32 @@ function toFlatString(value) {
|
|
|
5127
5268
|
}
|
|
5128
5269
|
|
|
5129
5270
|
// src/execute.ts
|
|
5271
|
+
var SEARCH_ABORTED_WARNING = "\u691C\u7D22\u304C 10 \u4E07\u4EF6\u3067\u6253\u3061\u5207\u3089\u308C\u3001\u7D50\u679C\u304C\u6B20\u843D\u3057\u305F\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002";
|
|
5272
|
+
var SearchAbortedError = class extends Error {
|
|
5273
|
+
constructor() {
|
|
5274
|
+
super("SearchAbortedError: kintone \u306E\u691C\u7D22\u304C 10 \u4E07\u4EF6\u3067\u6253\u3061\u5207\u3089\u308C\u305F\u305F\u3081\u3001\u5B8C\u5168\u306A\u5BFE\u8C61\u96C6\u5408\u3092\u78BA\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002");
|
|
5275
|
+
this.name = "SearchAbortedError";
|
|
5276
|
+
}
|
|
5277
|
+
};
|
|
5130
5278
|
async function execute(sql, client, options = {}) {
|
|
5279
|
+
const startedAt = Date.now();
|
|
5280
|
+
const stmt = parseSql(sql);
|
|
5131
5281
|
const metrics = createEmptyMetrics();
|
|
5132
5282
|
const countedClient = wrapClientWithMetrics(client, metrics);
|
|
5133
|
-
const
|
|
5134
|
-
const
|
|
5283
|
+
const collector = { aborted: false };
|
|
5284
|
+
const guardedClient = wrapClientWithSearchAbort(
|
|
5285
|
+
countedClient,
|
|
5286
|
+
collector,
|
|
5287
|
+
!isSelectLikeStatement(stmt)
|
|
5288
|
+
);
|
|
5289
|
+
const result = await executeParsedStatement(
|
|
5290
|
+
stmt,
|
|
5291
|
+
guardedClient,
|
|
5292
|
+
options,
|
|
5293
|
+
options.cacheContext ?? "default"
|
|
5294
|
+
);
|
|
5135
5295
|
metrics.elapsedMs = Date.now() - startedAt;
|
|
5136
|
-
return { ...result, metrics };
|
|
5296
|
+
return { ...attachSearchAbortWarning(result, collector), metrics };
|
|
5137
5297
|
}
|
|
5138
5298
|
function createEmptyMetrics() {
|
|
5139
5299
|
return {
|
|
@@ -5182,10 +5342,27 @@ function wrapClientWithMetrics(client, metrics) {
|
|
|
5182
5342
|
}
|
|
5183
5343
|
};
|
|
5184
5344
|
}
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5345
|
+
function wrapClientWithSearchAbort(client, collector, failClosed) {
|
|
5346
|
+
return {
|
|
5347
|
+
...client,
|
|
5348
|
+
getRecords: async (params) => {
|
|
5349
|
+
const response = await client.getRecords(params);
|
|
5350
|
+
if (response.searchAborted) {
|
|
5351
|
+
collector.aborted = true;
|
|
5352
|
+
if (failClosed) throw new SearchAbortedError();
|
|
5353
|
+
}
|
|
5354
|
+
return response;
|
|
5355
|
+
}
|
|
5356
|
+
};
|
|
5357
|
+
}
|
|
5358
|
+
function isSelectLikeStatement(stmt) {
|
|
5359
|
+
return stmt.type === "SELECT" || stmt.type === "UNION" || stmt.type === "WITH";
|
|
5360
|
+
}
|
|
5361
|
+
function attachSearchAbortWarning(result, collector) {
|
|
5362
|
+
if (!collector.aborted || result.type !== "SELECT") return result;
|
|
5363
|
+
const warnings = new Set(result.warnings ?? []);
|
|
5364
|
+
warnings.add(SEARCH_ABORTED_WARNING);
|
|
5365
|
+
return { ...result, warnings: [...warnings] };
|
|
5189
5366
|
}
|
|
5190
5367
|
async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
5191
5368
|
const unresolved = findVariableRef(stmt);
|
|
@@ -5298,10 +5475,19 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
5298
5475
|
targetAppId: info.targetAppId
|
|
5299
5476
|
})
|
|
5300
5477
|
} : batchOptions;
|
|
5478
|
+
const searchAbortCollector = { aborted: false };
|
|
5479
|
+
const statementClient = wrapClientWithSearchAbort(
|
|
5480
|
+
countedClient,
|
|
5481
|
+
searchAbortCollector,
|
|
5482
|
+
info.statementType !== "SELECT" && info.statementType !== "UNION" && info.statementType !== "WITH"
|
|
5483
|
+
);
|
|
5301
5484
|
const outcome = await runWithDeadline(
|
|
5302
|
-
executeBatchStatement(statements[i], info,
|
|
5485
|
+
executeBatchStatement(statements[i], info, statementClient, stmtOptions, cacheContext, tempTables, variables),
|
|
5303
5486
|
remaining
|
|
5304
5487
|
);
|
|
5488
|
+
if (outcome.result) {
|
|
5489
|
+
outcome.result = attachSearchAbortWarning(outcome.result, searchAbortCollector);
|
|
5490
|
+
}
|
|
5305
5491
|
results.push({ ...base, status: "success", ...outcome });
|
|
5306
5492
|
} catch (e) {
|
|
5307
5493
|
results.push({ ...base, status: "error", error: toBatchStatementError(e) });
|
|
@@ -5632,7 +5818,7 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache) {
|
|
|
5632
5818
|
}
|
|
5633
5819
|
}
|
|
5634
5820
|
function isNoFromSelect(stmt) {
|
|
5635
|
-
return stmt.from.appId === 0 && stmt.from.cteName ===
|
|
5821
|
+
return stmt.from.appId === 0 && stmt.from.cteName === NO_FROM_CTE_NAME;
|
|
5636
5822
|
}
|
|
5637
5823
|
function arithHasFieldRef(node) {
|
|
5638
5824
|
if (node.type === "FIELD_REF") return true;
|
|
@@ -5759,23 +5945,6 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
|
|
|
5759
5945
|
}
|
|
5760
5946
|
}
|
|
5761
5947
|
}
|
|
5762
|
-
function extractMainSafePushdown(stmt, fieldTypes, fieldOptions) {
|
|
5763
|
-
if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
|
|
5764
|
-
if (stmt.joins.length === 0) {
|
|
5765
|
-
return extractSafePushdownLeaves(stmt.where, {
|
|
5766
|
-
tableAlias: stmt.from.alias ?? void 0,
|
|
5767
|
-
allowUnqualifiedFields: true,
|
|
5768
|
-
fieldTypes,
|
|
5769
|
-
fieldOptions
|
|
5770
|
-
});
|
|
5771
|
-
}
|
|
5772
|
-
if (!stmt.from.alias) return null;
|
|
5773
|
-
return extractSafePushdownLeaves(stmt.where, {
|
|
5774
|
-
tableAlias: stmt.from.alias,
|
|
5775
|
-
fieldTypes,
|
|
5776
|
-
fieldOptions
|
|
5777
|
-
});
|
|
5778
|
-
}
|
|
5779
5948
|
function extractMainTypedPushdownCandidate(stmt) {
|
|
5780
5949
|
if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
|
|
5781
5950
|
if (stmt.joins.length === 0) {
|
|
@@ -5957,23 +6126,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
5957
6126
|
loadTypedInFieldTypes(stmt, client, cacheContext)
|
|
5958
6127
|
]);
|
|
5959
6128
|
const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
|
|
5960
|
-
const
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
);
|
|
5965
|
-
const tableConditions = /* @__PURE__ */ new Map();
|
|
5966
|
-
if (stmt.where !== null) {
|
|
5967
|
-
for (const join2 of stmt.joins) {
|
|
5968
|
-
if (!join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
|
|
5969
|
-
const cond = extractSafePushdownLeaves(stmt.where, {
|
|
5970
|
-
tableAlias: join2.table.alias,
|
|
5971
|
-
fieldTypes: pushdownMeta.fieldTypesByApp.get(join2.table.appId),
|
|
5972
|
-
fieldOptions: pushdownMeta.fieldOptionsByApp.get(join2.table.appId)
|
|
5973
|
-
});
|
|
5974
|
-
if (cond) tableConditions.set(join2.table.alias, cond);
|
|
5975
|
-
}
|
|
5976
|
-
}
|
|
6129
|
+
const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
|
|
6130
|
+
validateKlikePushdownPlan(pushdownPlan);
|
|
6131
|
+
const mainPushDown = pushdownPlan.mainCondition;
|
|
6132
|
+
const tableConditions = pushdownPlan.joinConditions;
|
|
5977
6133
|
const mainFetch = fetchTableRecordsForFullScan(
|
|
5978
6134
|
stmt,
|
|
5979
6135
|
stmt.from,
|
|
@@ -6054,7 +6210,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
6054
6210
|
optionOrders,
|
|
6055
6211
|
sortKinds,
|
|
6056
6212
|
fieldTypeResolver: fieldTypeResolvers.row,
|
|
6057
|
-
havingFieldTypeResolver: fieldTypeResolvers.having
|
|
6213
|
+
havingFieldTypeResolver: fieldTypeResolvers.having,
|
|
6214
|
+
appliedKlikes: pushdownPlan.appliedKlikes
|
|
6058
6215
|
});
|
|
6059
6216
|
return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
|
|
6060
6217
|
}
|
|
@@ -6103,83 +6260,6 @@ async function executeWith(stmt, client, options, cacheContext, seed) {
|
|
|
6103
6260
|
}
|
|
6104
6261
|
return executeQueryWithCte(stmt.query, client, options, cteCache, cacheContext);
|
|
6105
6262
|
}
|
|
6106
|
-
function canInlineSingleCte(stmt) {
|
|
6107
|
-
if (stmt.ctes.length !== 1) return false;
|
|
6108
|
-
const cteDef = stmt.ctes[0];
|
|
6109
|
-
if (cteDef.query.type !== "SELECT") return false;
|
|
6110
|
-
if (resolveSelectMode(cteDef.query) !== "SIMPLE") return false;
|
|
6111
|
-
const finalQuery = stmt.query;
|
|
6112
|
-
if (finalQuery.type !== "SELECT") return false;
|
|
6113
|
-
if (finalQuery.from.cteName !== cteDef.name) return false;
|
|
6114
|
-
if (finalQuery.joins.length > 0) return false;
|
|
6115
|
-
if (finalQuery.groupBy.length > 0) return false;
|
|
6116
|
-
if (finalQuery.distinct) return false;
|
|
6117
|
-
if (finalQuery.columns.some(
|
|
6118
|
-
(c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL"
|
|
6119
|
-
)) return false;
|
|
6120
|
-
return true;
|
|
6121
|
-
}
|
|
6122
|
-
function buildInlinedQuery(stmt) {
|
|
6123
|
-
const cteBody = stmt.ctes[0].query;
|
|
6124
|
-
const final = stmt.query;
|
|
6125
|
-
const cteAlias = final.from.alias;
|
|
6126
|
-
const finalWhere = stripCteAlias(final.where, cteAlias);
|
|
6127
|
-
let mergedWhere;
|
|
6128
|
-
if (cteBody.where === null) mergedWhere = finalWhere;
|
|
6129
|
-
else if (finalWhere === null) mergedWhere = cteBody.where;
|
|
6130
|
-
else mergedWhere = { type: "LOGICAL", op: "AND", left: cteBody.where, right: finalWhere };
|
|
6131
|
-
const columns = final.columns.every((c) => c.type === "WILDCARD") ? cteBody.columns : final.columns;
|
|
6132
|
-
return {
|
|
6133
|
-
type: "SELECT",
|
|
6134
|
-
from: cteBody.from,
|
|
6135
|
-
joins: [],
|
|
6136
|
-
columns,
|
|
6137
|
-
where: mergedWhere,
|
|
6138
|
-
groupBy: [],
|
|
6139
|
-
having: null,
|
|
6140
|
-
orderBy: final.orderBy.length > 0 ? final.orderBy : cteBody.orderBy,
|
|
6141
|
-
limit: final.limit ?? cteBody.limit,
|
|
6142
|
-
offset: final.offset ?? cteBody.offset,
|
|
6143
|
-
distinct: false
|
|
6144
|
-
};
|
|
6145
|
-
}
|
|
6146
|
-
function stripCteAlias(where, alias) {
|
|
6147
|
-
if (where === null || alias === null) return where;
|
|
6148
|
-
switch (where.type) {
|
|
6149
|
-
case "BINARY":
|
|
6150
|
-
return {
|
|
6151
|
-
type: "BINARY",
|
|
6152
|
-
op: where.op,
|
|
6153
|
-
left: stripCteAliasFromFieldValue(where.left, alias),
|
|
6154
|
-
right: where.right
|
|
6155
|
-
};
|
|
6156
|
-
case "NULL_CHECK":
|
|
6157
|
-
return {
|
|
6158
|
-
type: "NULL_CHECK",
|
|
6159
|
-
not: where.not,
|
|
6160
|
-
field: stripCteAliasFromFieldValue(where.field, alias)
|
|
6161
|
-
};
|
|
6162
|
-
case "LOGICAL":
|
|
6163
|
-
return {
|
|
6164
|
-
type: "LOGICAL",
|
|
6165
|
-
op: where.op,
|
|
6166
|
-
left: stripCteAlias(where.left, alias),
|
|
6167
|
-
right: stripCteAlias(where.right, alias)
|
|
6168
|
-
};
|
|
6169
|
-
case "NOT":
|
|
6170
|
-
return { type: "NOT", expr: stripCteAlias(where.expr, alias) };
|
|
6171
|
-
case "GROUP":
|
|
6172
|
-
return { type: "GROUP", expr: stripCteAlias(where.expr, alias) };
|
|
6173
|
-
case "EXISTS":
|
|
6174
|
-
return where;
|
|
6175
|
-
}
|
|
6176
|
-
}
|
|
6177
|
-
function stripCteAliasFromFieldValue(fv, alias) {
|
|
6178
|
-
if (fv.type === "FIELD" && fv.tableAlias === alias) {
|
|
6179
|
-
return { type: "FIELD", field: fv.field, tableAlias: null };
|
|
6180
|
-
}
|
|
6181
|
-
return fv;
|
|
6182
|
-
}
|
|
6183
6263
|
async function executeQueryWithCte(query, client, options, cteCache, cacheContext) {
|
|
6184
6264
|
if (query.type === "UNION") {
|
|
6185
6265
|
const [leftResult, rightResult] = await Promise.all([
|
|
@@ -6199,7 +6279,7 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
|
|
|
6199
6279
|
const rows = query.all ? combined : deduplicateRows(combined, leftCols);
|
|
6200
6280
|
return { type: "SELECT", rows, columns: leftCols, rowCount: rows.length };
|
|
6201
6281
|
}
|
|
6202
|
-
const hasCteRef = query.from.cteName != null || query.joins.some((j) => j.table.cteName != null);
|
|
6282
|
+
const hasCteRef = query.from.cteName != null && query.from.cteName !== NO_FROM_CTE_NAME || query.joins.some((j) => j.table.cteName != null);
|
|
6203
6283
|
if (!hasCteRef) {
|
|
6204
6284
|
return executeSelect(query, client, options, cacheContext, cteCache);
|
|
6205
6285
|
}
|
|
@@ -6214,8 +6294,13 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
6214
6294
|
resolveSubqueries(stmt.having, client, options, cacheContext, cteCache),
|
|
6215
6295
|
resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache)
|
|
6216
6296
|
]);
|
|
6217
|
-
const typedInFieldTypes = await
|
|
6297
|
+
const [pushdownMeta, typedInFieldTypes] = await Promise.all([
|
|
6298
|
+
loadTypedPushdownMeta(stmt, client, cacheContext),
|
|
6299
|
+
loadTypedInFieldTypes(stmt, client, cacheContext)
|
|
6300
|
+
]);
|
|
6218
6301
|
const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
|
|
6302
|
+
const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
|
|
6303
|
+
validateKlikePushdownPlan(pushdownPlan);
|
|
6219
6304
|
const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
|
|
6220
6305
|
const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
|
|
6221
6306
|
scalarCachePromise.catch(() => {
|
|
@@ -6235,7 +6320,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
6235
6320
|
parallel,
|
|
6236
6321
|
true,
|
|
6237
6322
|
options.onLimitReached ?? "error",
|
|
6238
|
-
warnings
|
|
6323
|
+
warnings,
|
|
6324
|
+
pushdownPlan.mainCondition
|
|
6239
6325
|
);
|
|
6240
6326
|
tables.set(stmt.from.alias, mainRecords);
|
|
6241
6327
|
}
|
|
@@ -6244,6 +6330,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
6244
6330
|
const rows2 = cteCache.get(join2.table.cteName) ?? [];
|
|
6245
6331
|
tables.set(join2.table.alias, rows2.map(processRowToKintoneRecord));
|
|
6246
6332
|
} else {
|
|
6333
|
+
const pushDownCond = join2.table.alias ? pushdownPlan.joinConditions.get(join2.table.alias) ?? null : null;
|
|
6247
6334
|
const optimized = await tryFetchJoinRecordsBySourceKeys(
|
|
6248
6335
|
stmt,
|
|
6249
6336
|
join2,
|
|
@@ -6252,7 +6339,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
6252
6339
|
maxRecords,
|
|
6253
6340
|
parallel,
|
|
6254
6341
|
options.onLimitReached ?? "error",
|
|
6255
|
-
warnings
|
|
6342
|
+
warnings,
|
|
6343
|
+
pushDownCond
|
|
6256
6344
|
);
|
|
6257
6345
|
const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
|
|
6258
6346
|
stmt,
|
|
@@ -6262,7 +6350,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
6262
6350
|
parallel,
|
|
6263
6351
|
false,
|
|
6264
6352
|
options.onLimitReached ?? "error",
|
|
6265
|
-
warnings
|
|
6353
|
+
warnings,
|
|
6354
|
+
pushDownCond
|
|
6266
6355
|
);
|
|
6267
6356
|
tables.set(join2.table.alias, joinRecords);
|
|
6268
6357
|
}
|
|
@@ -6277,7 +6366,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
6277
6366
|
optionOrders,
|
|
6278
6367
|
sortKinds,
|
|
6279
6368
|
fieldTypeResolver: fieldTypeResolvers.row,
|
|
6280
|
-
havingFieldTypeResolver: fieldTypeResolvers.having
|
|
6369
|
+
havingFieldTypeResolver: fieldTypeResolvers.having,
|
|
6370
|
+
appliedKlikes: pushdownPlan.appliedKlikes
|
|
6281
6371
|
});
|
|
6282
6372
|
return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
|
|
6283
6373
|
}
|
|
@@ -7486,9 +7576,10 @@ function buildSelectPlan(stmt, label) {
|
|
|
7486
7576
|
lines.push(` kintone query: ${params.query || "(\u306A\u3057)"}`);
|
|
7487
7577
|
lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
|
|
7488
7578
|
} else {
|
|
7579
|
+
const pushdownPlan = buildKlikePushdownPlan(stmt);
|
|
7489
7580
|
const mainFields = selectToFetchAllFields(stmt, stmt.from);
|
|
7490
7581
|
const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
|
|
7491
|
-
const mainPushDown =
|
|
7582
|
+
const mainPushDown = pushdownPlan.mainCondition;
|
|
7492
7583
|
const mainCandidate = extractMainTypedPushdownCandidate(stmt);
|
|
7493
7584
|
const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
|
|
7494
7585
|
lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
|
|
@@ -7501,7 +7592,7 @@ function buildSelectPlan(stmt, label) {
|
|
|
7501
7592
|
const joinFields = selectToFetchAllFields(stmt, join2.table);
|
|
7502
7593
|
const joinAliasStr = join2.table.alias ? ` AS ${join2.table.alias}` : "";
|
|
7503
7594
|
const joinType = join2.type === "INNER" ? "JOIN" : `${join2.type} JOIN`;
|
|
7504
|
-
const joinPushDown = join2.table.alias
|
|
7595
|
+
const joinPushDown = join2.table.alias ? pushdownPlan.joinConditions.get(join2.table.alias) ?? null : null;
|
|
7505
7596
|
const joinCandidate = join2.table.alias && !join2.table.subtableCode && join2.table.cteName === null && stmt.where ? extractTypedPushdownCandidates(stmt.where, { tableAlias: join2.table.alias }) : null;
|
|
7506
7597
|
const joinQ = joinPushDown !== null ? whereToKintone(joinPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
|
|
7507
7598
|
lines.push(` ${joinType}: APP${join2.table.appId}${joinAliasStr} (${join2.table.appId})`);
|
|
@@ -7544,6 +7635,10 @@ function buildWithPlan(stmt) {
|
|
|
7544
7635
|
if (stmt.query.type === "SELECT" || stmt.query.type === "UNION") {
|
|
7545
7636
|
lines.push(...buildExplainPlan(stmt.query, "[main]"));
|
|
7546
7637
|
}
|
|
7638
|
+
if (canInlineSingleCte(stmt)) {
|
|
7639
|
+
lines.push("");
|
|
7640
|
+
lines.push(...buildSelectPlan(buildInlinedQuery(stmt), "[effective: inlined CTE]"));
|
|
7641
|
+
}
|
|
7547
7642
|
return lines;
|
|
7548
7643
|
}
|
|
7549
7644
|
function collectFullScanReasons(stmt) {
|
|
@@ -8228,10 +8323,11 @@ function detectSortKind(fieldType, calcFormat) {
|
|
|
8228
8323
|
}
|
|
8229
8324
|
|
|
8230
8325
|
// src/cli/nodeKintoneClient.ts
|
|
8326
|
+
var SEARCH_ABORTED_HEADER_VALUE = "Filter aborted because of too many search results";
|
|
8231
8327
|
function createNodeKintoneClient(baseUrl, tokenResolver) {
|
|
8232
8328
|
const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
|
|
8233
8329
|
const apiBasePath = tokenResolver.guestSpaceId && tokenResolver.guestSpaceId > 0 ? `/k/guest/${tokenResolver.guestSpaceId}/v1` : "/k/v1";
|
|
8234
|
-
async function
|
|
8330
|
+
async function requestJsonResponse(path, init, appIdForToken) {
|
|
8235
8331
|
const headers = new Headers(init.headers ?? {});
|
|
8236
8332
|
if (tokenResolver.auth.type === "token") {
|
|
8237
8333
|
headers.set("X-Cybozu-API-Token", tokenResolver.auth.resolveToken(appIdForToken));
|
|
@@ -8279,7 +8375,14 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
|
|
|
8279
8375
|
if (tokenResolver.debug) {
|
|
8280
8376
|
tokenResolver.log?.(`[debug] response status=${res.status}`);
|
|
8281
8377
|
}
|
|
8282
|
-
|
|
8378
|
+
const warning = res.headers.get("X-Cybozu-Warning") ?? "";
|
|
8379
|
+
return {
|
|
8380
|
+
body: await res.json(),
|
|
8381
|
+
searchAborted: warning.includes(SEARCH_ABORTED_HEADER_VALUE)
|
|
8382
|
+
};
|
|
8383
|
+
}
|
|
8384
|
+
async function requestJson(path, init, appIdForToken) {
|
|
8385
|
+
return (await requestJsonResponse(path, init, appIdForToken)).body;
|
|
8283
8386
|
}
|
|
8284
8387
|
function shouldRetryWithRecordNumberOrder(path, bodyText) {
|
|
8285
8388
|
if (!path.includes("/v1/records.json?")) return false;
|
|
@@ -8313,11 +8416,12 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
|
|
|
8313
8416
|
}
|
|
8314
8417
|
const path = `${apiBasePath}/records.json?${qs}`;
|
|
8315
8418
|
try {
|
|
8316
|
-
|
|
8419
|
+
const response = await requestJsonResponse(
|
|
8317
8420
|
path,
|
|
8318
8421
|
{ method: "GET" },
|
|
8319
8422
|
params.app
|
|
8320
8423
|
);
|
|
8424
|
+
return response.searchAborted ? { ...response.body, searchAborted: true } : response.body;
|
|
8321
8425
|
} catch (err) {
|
|
8322
8426
|
const msg = err instanceof Error ? err.message : String(err);
|
|
8323
8427
|
if (!shouldRetryWithRecordNumberOrder(path, msg)) throw err;
|
|
@@ -8325,11 +8429,12 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
|
|
|
8325
8429
|
if (tokenResolver.debug) {
|
|
8326
8430
|
tokenResolver.log?.("[debug] retry with fallback query order by \u30EC\u30B3\u30FC\u30C9\u756A\u53F7 asc");
|
|
8327
8431
|
}
|
|
8328
|
-
|
|
8432
|
+
const response = await requestJsonResponse(
|
|
8329
8433
|
retryPath,
|
|
8330
8434
|
{ method: "GET" },
|
|
8331
8435
|
params.app
|
|
8332
8436
|
);
|
|
8437
|
+
return response.searchAborted ? { ...response.body, searchAborted: true } : response.body;
|
|
8333
8438
|
}
|
|
8334
8439
|
},
|
|
8335
8440
|
async postRecords(_params) {
|