taurusdb-core 0.1.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 +21 -0
- package/dist/auth/secret-resolver.d.ts +16 -0
- package/dist/auth/secret-resolver.js +64 -0
- package/dist/auth/sql-profile-loader/env-source.d.ts +3 -0
- package/dist/auth/sql-profile-loader/env-source.js +94 -0
- package/dist/auth/sql-profile-loader/file-source.d.ts +6 -0
- package/dist/auth/sql-profile-loader/file-source.js +40 -0
- package/dist/auth/sql-profile-loader/loader.d.ts +16 -0
- package/dist/auth/sql-profile-loader/loader.js +81 -0
- package/dist/auth/sql-profile-loader/parsing.d.ts +14 -0
- package/dist/auth/sql-profile-loader/parsing.js +216 -0
- package/dist/auth/sql-profile-loader/runtime-override.d.ts +14 -0
- package/dist/auth/sql-profile-loader/runtime-override.js +52 -0
- package/dist/auth/sql-profile-loader/types.d.ts +64 -0
- package/dist/auth/sql-profile-loader/types.js +1 -0
- package/dist/auth/sql-profile-loader.d.ts +4 -0
- package/dist/auth/sql-profile-loader.js +3 -0
- package/dist/capability/feature-matrix.d.ts +5 -0
- package/dist/capability/feature-matrix.js +237 -0
- package/dist/capability/probe.d.ts +19 -0
- package/dist/capability/probe.js +139 -0
- package/dist/capability/types.d.ts +49 -0
- package/dist/capability/types.js +16 -0
- package/dist/capability/version.d.ts +3 -0
- package/dist/capability/version.js +47 -0
- package/dist/cloud/auth.d.ts +26 -0
- package/dist/cloud/auth.js +198 -0
- package/dist/cloud/instances.d.ts +46 -0
- package/dist/cloud/instances.js +224 -0
- package/dist/config/env.d.ts +1 -0
- package/dist/config/env.js +194 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.js +21 -0
- package/dist/config/redaction.d.ts +2 -0
- package/dist/config/redaction.js +19 -0
- package/dist/config/schema.d.ts +417 -0
- package/dist/config/schema.js +100 -0
- package/dist/context/datasource-resolver.d.ts +19 -0
- package/dist/context/datasource-resolver.js +71 -0
- package/dist/context/session-context.d.ts +26 -0
- package/dist/context/session-context.js +1 -0
- package/dist/diagnostics/metrics-source.d.ts +65 -0
- package/dist/diagnostics/metrics-source.js +280 -0
- package/dist/diagnostics/slow-sql-source/das-source.d.ts +43 -0
- package/dist/diagnostics/slow-sql-source/das-source.js +170 -0
- package/dist/diagnostics/slow-sql-source/factory.d.ts +5 -0
- package/dist/diagnostics/slow-sql-source/factory.js +87 -0
- package/dist/diagnostics/slow-sql-source/parsers.d.ts +7 -0
- package/dist/diagnostics/slow-sql-source/parsers.js +125 -0
- package/dist/diagnostics/slow-sql-source/taurus-api-source.d.ts +42 -0
- package/dist/diagnostics/slow-sql-source/taurus-api-source.js +149 -0
- package/dist/diagnostics/slow-sql-source/types.d.ts +40 -0
- package/dist/diagnostics/slow-sql-source/types.js +1 -0
- package/dist/diagnostics/slow-sql-source/utils.d.ts +20 -0
- package/dist/diagnostics/slow-sql-source/utils.js +170 -0
- package/dist/diagnostics/slow-sql-source.d.ts +4 -0
- package/dist/diagnostics/slow-sql-source.js +3 -0
- package/dist/diagnostics/types.d.ts +189 -0
- package/dist/diagnostics/types.js +39 -0
- package/dist/engine/data-access/locks.d.ts +8 -0
- package/dist/engine/data-access/locks.js +146 -0
- package/dist/engine/data-access/processlist.d.ts +4 -0
- package/dist/engine/data-access/processlist.js +56 -0
- package/dist/engine/data-access/statements.d.ts +10 -0
- package/dist/engine/data-access/statements.js +203 -0
- package/dist/engine/data-access/storage.d.ts +6 -0
- package/dist/engine/data-access/storage.js +96 -0
- package/dist/engine/data-access.d.ts +4 -0
- package/dist/engine/data-access.js +4 -0
- package/dist/engine/diagnostics.d.ts +7 -0
- package/dist/engine/diagnostics.js +7 -0
- package/dist/engine/helper-modules/diagnostics.d.ts +57 -0
- package/dist/engine/helper-modules/diagnostics.js +322 -0
- package/dist/engine/helper-modules/parsers.d.ts +13 -0
- package/dist/engine/helper-modules/parsers.js +283 -0
- package/dist/engine/helper-modules/sql.d.ts +12 -0
- package/dist/engine/helper-modules/sql.js +119 -0
- package/dist/engine/helper-modules/types.d.ts +103 -0
- package/dist/engine/helper-modules/types.js +1 -0
- package/dist/engine/helpers.d.ts +4 -0
- package/dist/engine/helpers.js +4 -0
- package/dist/engine/runtime.d.ts +20 -0
- package/dist/engine/runtime.js +385 -0
- package/dist/engine/types.d.ts +125 -0
- package/dist/engine/types.js +1 -0
- package/dist/engine/workflows/connection-spike.d.ts +4 -0
- package/dist/engine/workflows/connection-spike.js +316 -0
- package/dist/engine/workflows/db-hotspot.d.ts +4 -0
- package/dist/engine/workflows/db-hotspot.js +182 -0
- package/dist/engine/workflows/lock-contention-helpers/entities.d.ts +9 -0
- package/dist/engine/workflows/lock-contention-helpers/entities.js +58 -0
- package/dist/engine/workflows/lock-contention-helpers/no-match.d.ts +3 -0
- package/dist/engine/workflows/lock-contention-helpers/no-match.js +65 -0
- package/dist/engine/workflows/lock-contention-helpers/report.d.ts +21 -0
- package/dist/engine/workflows/lock-contention-helpers/report.js +104 -0
- package/dist/engine/workflows/lock-contention-helpers/root-cause.d.ts +4 -0
- package/dist/engine/workflows/lock-contention-helpers/root-cause.js +79 -0
- package/dist/engine/workflows/lock-contention-helpers/signals.d.ts +22 -0
- package/dist/engine/workflows/lock-contention-helpers/signals.js +34 -0
- package/dist/engine/workflows/lock-contention-helpers.d.ts +5 -0
- package/dist/engine/workflows/lock-contention-helpers.js +5 -0
- package/dist/engine/workflows/lock-contention.d.ts +4 -0
- package/dist/engine/workflows/lock-contention.js +67 -0
- package/dist/engine/workflows/service-latency.d.ts +4 -0
- package/dist/engine/workflows/service-latency.js +262 -0
- package/dist/engine/workflows/slow-query-helpers.d.ts +41 -0
- package/dist/engine/workflows/slow-query-helpers.js +253 -0
- package/dist/engine/workflows/slow-query.d.ts +4 -0
- package/dist/engine/workflows/slow-query.js +156 -0
- package/dist/engine/workflows/storage-pressure-helpers.d.ts +12 -0
- package/dist/engine/workflows/storage-pressure-helpers.js +281 -0
- package/dist/engine/workflows/storage-pressure.d.ts +4 -0
- package/dist/engine/workflows/storage-pressure.js +27 -0
- package/dist/engine/workflows/top-slow-sql.d.ts +4 -0
- package/dist/engine/workflows/top-slow-sql.js +222 -0
- package/dist/engine.d.ts +77 -0
- package/dist/engine.js +240 -0
- package/dist/executor/adapters/mysql.d.ts +2 -0
- package/dist/executor/adapters/mysql.js +114 -0
- package/dist/executor/connection-pool.d.ts +105 -0
- package/dist/executor/connection-pool.js +236 -0
- package/dist/executor/explain.d.ts +5 -0
- package/dist/executor/explain.js +119 -0
- package/dist/executor/query-tracker.d.ts +45 -0
- package/dist/executor/query-tracker.js +83 -0
- package/dist/executor/result-normalizer.d.ts +6 -0
- package/dist/executor/result-normalizer.js +47 -0
- package/dist/executor/sql-executor.d.ts +32 -0
- package/dist/executor/sql-executor.js +250 -0
- package/dist/executor/types.d.ts +70 -0
- package/dist/executor/types.js +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +21 -0
- package/dist/safety/confirmation-store.d.ts +44 -0
- package/dist/safety/confirmation-store.js +130 -0
- package/dist/safety/guardrail.d.ts +39 -0
- package/dist/safety/guardrail.js +99 -0
- package/dist/safety/parser/adapter.d.ts +10 -0
- package/dist/safety/parser/adapter.js +72 -0
- package/dist/safety/parser/ast-utils.d.ts +10 -0
- package/dist/safety/parser/ast-utils.js +167 -0
- package/dist/safety/parser/features.d.ts +12 -0
- package/dist/safety/parser/features.js +113 -0
- package/dist/safety/parser/index.d.ts +2 -0
- package/dist/safety/parser/index.js +1 -0
- package/dist/safety/parser/types.d.ts +76 -0
- package/dist/safety/parser/types.js +1 -0
- package/dist/safety/redaction.d.ts +34 -0
- package/dist/safety/redaction.js +186 -0
- package/dist/safety/sql-classifier.d.ts +19 -0
- package/dist/safety/sql-classifier.js +43 -0
- package/dist/safety/sql-validator.d.ts +19 -0
- package/dist/safety/sql-validator.js +143 -0
- package/dist/schema/adapters/mysql.d.ts +16 -0
- package/dist/schema/adapters/mysql.js +287 -0
- package/dist/schema/introspector.d.ts +70 -0
- package/dist/schema/introspector.js +40 -0
- package/dist/taurus/flashback.d.ts +36 -0
- package/dist/taurus/flashback.js +149 -0
- package/dist/taurus/recycle-bin.d.ts +14 -0
- package/dist/taurus/recycle-bin.js +61 -0
- package/dist/utils/formatter.d.ts +70 -0
- package/dist/utils/formatter.js +60 -0
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.js +247 -0
- package/dist/utils/id.d.ts +2 -0
- package/dist/utils/id.js +11 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.js +39 -0
- package/package.json +46 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { UnsupportedFeatureError } from "../capability/types.js";
|
|
2
|
+
import { InMemoryConfirmationStore } from "../safety/confirmation-store.js";
|
|
3
|
+
import { buildFlashbackSql, FlashbackNoViewError, flashbackReadonlyOptions, formatTimestamp, resolveRelativeTimestampFromBase, resolveFlashbackTimestamp, } from "../taurus/flashback.js";
|
|
4
|
+
import { buildListRecycleBinSql, buildRestoreRecycleBinTableSql, recycleBinMutationOptions, recycleBinReadonlyOptions } from "../taurus/recycle-bin.js";
|
|
5
|
+
import { normalizeSql, sqlHash } from "../utils/hash.js";
|
|
6
|
+
function resolveConfirmationSql(input) {
|
|
7
|
+
const normalized = input.normalizedSql ?? (input.sql ? normalizeSql(input.sql) : undefined);
|
|
8
|
+
const hash = input.sqlHash ?? (normalized ? sqlHash(normalized) : undefined);
|
|
9
|
+
if (!normalized || !hash) {
|
|
10
|
+
throw new Error("Issue confirmation requires sql, normalizedSql, or sqlHash context.");
|
|
11
|
+
}
|
|
12
|
+
return { normalized, hash };
|
|
13
|
+
}
|
|
14
|
+
function explainExtras(plan) {
|
|
15
|
+
return plan
|
|
16
|
+
.map((row) => {
|
|
17
|
+
if (!row || typeof row !== "object") {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const extra = (row.Extra ?? row.extra);
|
|
21
|
+
return typeof extra === "string" ? extra : undefined;
|
|
22
|
+
})
|
|
23
|
+
.filter((value) => typeof value === "string" && value.trim().length > 0);
|
|
24
|
+
}
|
|
25
|
+
function hasSqlPattern(sql, pattern) {
|
|
26
|
+
return pattern.test(sql);
|
|
27
|
+
}
|
|
28
|
+
const NO_FLASHBACK_VIEW_PATTERN = /No view available for provided TIMESTAMP/i;
|
|
29
|
+
const SIMPLE_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_$]*$/;
|
|
30
|
+
function quoteIdentifier(identifier) {
|
|
31
|
+
if (!SIMPLE_IDENTIFIER_PATTERN.test(identifier)) {
|
|
32
|
+
throw new Error(`Invalid identifier: "${identifier}".`);
|
|
33
|
+
}
|
|
34
|
+
return `\`${identifier}\``;
|
|
35
|
+
}
|
|
36
|
+
function firstRowAsObject(result) {
|
|
37
|
+
if (result.rows.length === 0) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const row = result.rows[0];
|
|
41
|
+
return Object.fromEntries(result.columns.map((column, index) => [column.name, row[index]]));
|
|
42
|
+
}
|
|
43
|
+
function parseLocalTimestampToDate(value) {
|
|
44
|
+
const match = value.trim().match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
|
|
45
|
+
if (!match) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const date = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]), Number(match[4]), Number(match[5]), Number(match[6]));
|
|
49
|
+
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
50
|
+
}
|
|
51
|
+
function shiftTimestamp(timestamp, deltaMs) {
|
|
52
|
+
const date = parseLocalTimestampToDate(timestamp);
|
|
53
|
+
if (!date) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
return formatTimestamp(new Date(date.getTime() + deltaMs));
|
|
57
|
+
}
|
|
58
|
+
async function buildFlashbackNoViewError(engine, ctx, input, database, requestedTimestamp) {
|
|
59
|
+
const details = {
|
|
60
|
+
database,
|
|
61
|
+
table: input.table,
|
|
62
|
+
where: input.where,
|
|
63
|
+
requested_timestamp: requestedTimestamp,
|
|
64
|
+
guidance: [
|
|
65
|
+
"Use the exact pre-update timestamp when validating flashback behavior.",
|
|
66
|
+
"If only an approximate time is known, try a timestamp slightly before the row's current updated_at value.",
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
try {
|
|
70
|
+
const envResult = await engine.executor.executeReadonly("SELECT NOW(6) AS now_time, @@innodb_rds_backquery_window AS backquery_window", ctx, { maxRows: 1, maxColumns: 2, maxFieldChars: 128 });
|
|
71
|
+
const envRow = firstRowAsObject(envResult);
|
|
72
|
+
const nowTime = typeof envRow?.now_time === "string" ? envRow.now_time : undefined;
|
|
73
|
+
const backqueryWindowRaw = envRow?.backquery_window;
|
|
74
|
+
const backqueryWindow = typeof backqueryWindowRaw === "number"
|
|
75
|
+
? backqueryWindowRaw
|
|
76
|
+
: typeof backqueryWindowRaw === "string" &&
|
|
77
|
+
backqueryWindowRaw.trim().length > 0
|
|
78
|
+
? Number(backqueryWindowRaw)
|
|
79
|
+
: undefined;
|
|
80
|
+
details.current_time = nowTime;
|
|
81
|
+
if (Number.isFinite(backqueryWindow)) {
|
|
82
|
+
details.backquery_window_seconds = Number(backqueryWindow);
|
|
83
|
+
}
|
|
84
|
+
if (nowTime && Number.isFinite(backqueryWindow)) {
|
|
85
|
+
details.earliest_supported_timestamp_estimate = shiftTimestamp(nowTime, -Number(backqueryWindow) * 1000);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Best-effort diagnostics only.
|
|
90
|
+
}
|
|
91
|
+
if (input.where?.trim()) {
|
|
92
|
+
try {
|
|
93
|
+
const updatedAtResult = await engine.executor.executeReadonly(`SELECT ${quoteIdentifier("updated_at")} FROM ${quoteIdentifier(database)}.${quoteIdentifier(input.table)} WHERE (${input.where.trim()}) LIMIT 1`, ctx, { maxRows: 1, maxColumns: 1, maxFieldChars: 128 });
|
|
94
|
+
const updatedAtRow = firstRowAsObject(updatedAtResult);
|
|
95
|
+
if (typeof updatedAtRow?.updated_at === "string") {
|
|
96
|
+
details.current_row_updated_at = updatedAtRow.updated_at;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// The target table may not have updated_at, or the query may not be useful here.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const recommendations = new Set();
|
|
104
|
+
const requestedMinusOneSecond = shiftTimestamp(requestedTimestamp, -1000);
|
|
105
|
+
if (requestedMinusOneSecond) {
|
|
106
|
+
recommendations.add(requestedMinusOneSecond);
|
|
107
|
+
}
|
|
108
|
+
if (details.current_row_updated_at) {
|
|
109
|
+
for (const deltaMs of [-1000, -5000, -30000, -60000]) {
|
|
110
|
+
const candidate = shiftTimestamp(details.current_row_updated_at, deltaMs);
|
|
111
|
+
if (candidate) {
|
|
112
|
+
recommendations.add(candidate);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (details.current_time &&
|
|
117
|
+
typeof details.backquery_window_seconds === "number") {
|
|
118
|
+
const withinWindowCandidate = shiftTimestamp(details.current_time, -60_000);
|
|
119
|
+
if (withinWindowCandidate) {
|
|
120
|
+
recommendations.add(withinWindowCandidate);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (recommendations.size > 0) {
|
|
124
|
+
details.recommended_timestamps = [...recommendations].slice(0, 5);
|
|
125
|
+
}
|
|
126
|
+
return new FlashbackNoViewError("No view available for provided TIMESTAMP.", details);
|
|
127
|
+
}
|
|
128
|
+
async function resolveFlashbackInputForExecution(engine, ctx, input) {
|
|
129
|
+
if (!("relative" in input.asOf) || typeof input.asOf.relative !== "string") {
|
|
130
|
+
return input;
|
|
131
|
+
}
|
|
132
|
+
const envResult = await engine.executor.executeReadonly("SELECT NOW(6) AS now_time", ctx, { maxRows: 1, maxColumns: 1, maxFieldChars: 128 });
|
|
133
|
+
const envRow = firstRowAsObject(envResult);
|
|
134
|
+
if (typeof envRow?.now_time !== "string") {
|
|
135
|
+
throw new Error("Unable to resolve database current time for flashback relative timestamp.");
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
...input,
|
|
139
|
+
asOf: {
|
|
140
|
+
timestamp: resolveRelativeTimestampFromBase(input.asOf.relative, envRow.now_time),
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function buildEnhancedExplainSuggestions(sql, features, explainResult) {
|
|
145
|
+
const suggestions = [...explainResult.recommendations];
|
|
146
|
+
if (!features.parallel_query.available) {
|
|
147
|
+
suggestions.push("parallel_query is unavailable on this instance.");
|
|
148
|
+
}
|
|
149
|
+
else if (features.parallel_query.enabled === false) {
|
|
150
|
+
suggestions.push("parallel_query is available but disabled. Consider SET GLOBAL force_parallel_execute=ON.");
|
|
151
|
+
}
|
|
152
|
+
if (!features.flashback_query.available) {
|
|
153
|
+
suggestions.push("flashback_query is unavailable; high-risk mutations have weaker recovery options.");
|
|
154
|
+
}
|
|
155
|
+
if (features.offset_pushdown.available &&
|
|
156
|
+
features.offset_pushdown.enabled !== false) {
|
|
157
|
+
if (hasSqlPattern(sql, /\boffset\s+\d+/i)) {
|
|
158
|
+
suggestions.push("OFFSET detected. TaurusDB offset_pushdown may help reduce coordinator overhead.");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (explainResult.riskSummary.fullTableScanLikely &&
|
|
162
|
+
features.ndp_pushdown.available) {
|
|
163
|
+
suggestions.push("Full table scan is likely. Review whether NDP pushdown can reduce scanned rows.");
|
|
164
|
+
}
|
|
165
|
+
return [...new Set(suggestions)];
|
|
166
|
+
}
|
|
167
|
+
function buildOffsetPushdownExplanation(matched, hasOffset, features) {
|
|
168
|
+
return {
|
|
169
|
+
matched,
|
|
170
|
+
meaning: "offset_pushdown is a TaurusDB pagination optimization for LIMIT/OFFSET queries that pushes row-skipping work closer to the storage layer.",
|
|
171
|
+
whyTriggered: matched
|
|
172
|
+
? "This query uses ORDER BY with LIMIT/OFFSET, so TaurusDB can apply offset handling during indexed row retrieval instead of discarding as many rows at the coordinator layer."
|
|
173
|
+
: !hasOffset
|
|
174
|
+
? "The SQL does not contain an OFFSET clause, so there is no offset workload to push down."
|
|
175
|
+
: features.offset_pushdown.enabled === false
|
|
176
|
+
? "The SQL uses OFFSET, but the instance-level offset_pushdown optimization is disabled."
|
|
177
|
+
: "The SQL uses OFFSET, but the plan did not confirm that TaurusDB could push the offset handling down for this shape.",
|
|
178
|
+
expectedBenefit: matched
|
|
179
|
+
? "Reduces intermediate rows that must be materialized and discarded for deep pagination, which lowers coordinator overhead and makes large OFFSET pages more stable."
|
|
180
|
+
: "No offset_pushdown benefit is expected for this execution path.",
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function buildParallelQueryExplanation(matched, features, standardPlan, sql) {
|
|
184
|
+
const aggregationLike = hasSqlPattern(sql, /\b(group\s+by|order\s+by|join)\b/i);
|
|
185
|
+
return {
|
|
186
|
+
matched,
|
|
187
|
+
meaning: "parallel_query lets TaurusDB split eligible scan or aggregation work across multiple workers to improve throughput on larger analytical reads.",
|
|
188
|
+
whyTriggered: matched
|
|
189
|
+
? "This query shape looks like a larger scan or aggregation, and parallel_query is enabled on the instance, so TaurusDB may benefit from parallel execution."
|
|
190
|
+
: !features.parallel_query.available
|
|
191
|
+
? features.parallel_query.reason ??
|
|
192
|
+
"parallel_query is unavailable on this instance."
|
|
193
|
+
: features.parallel_query.enabled === false
|
|
194
|
+
? "The query shape may benefit from parallel execution, but force_parallel_execute is currently disabled."
|
|
195
|
+
: aggregationLike || standardPlan.riskSummary.fullTableScanLikely
|
|
196
|
+
? "The query shape is compatible with parallel_query, but the estimated work size was not large enough to justify turning it on."
|
|
197
|
+
: "This query is too small or too index-selective to meaningfully benefit from parallel workers.",
|
|
198
|
+
expectedBenefit: matched
|
|
199
|
+
? "Can improve throughput for larger scans, GROUP BY, ORDER BY, and join-heavy reads by spreading work across multiple workers."
|
|
200
|
+
: "No meaningful parallel-query gain is expected for this execution path.",
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function buildNdpPushdownExplanation(matched, features, extras) {
|
|
204
|
+
return {
|
|
205
|
+
matched,
|
|
206
|
+
meaning: "ndp_pushdown lets TaurusDB push filter, projection, or aggregation work down toward the data nodes so less data has to travel back to the coordinator.",
|
|
207
|
+
whyTriggered: matched
|
|
208
|
+
? `The EXPLAIN Extra field shows TaurusDB NDP pushdown markers (${extras.trim() || "NDP pushdown detected"}), which indicates parts of the filter or aggregation are being executed closer to storage.`
|
|
209
|
+
: !features.ndp_pushdown.available
|
|
210
|
+
? features.ndp_pushdown.reason ?? "ndp_pushdown is unavailable on this instance."
|
|
211
|
+
: features.ndp_pushdown.enabled === false
|
|
212
|
+
? "The query shape could use NDP pushdown, but the feature is disabled on this instance."
|
|
213
|
+
: "The EXPLAIN plan did not expose TaurusDB NDP pushdown markers for this SQL, so the result cannot confirm that NDP was used.",
|
|
214
|
+
expectedBenefit: matched
|
|
215
|
+
? "Reduces coordinator CPU and network transfer by shrinking the amount of raw row data that must be returned from the storage side."
|
|
216
|
+
: "No NDP pushdown benefit is expected for this execution path.",
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
export async function explain(engine, sql, ctx) {
|
|
220
|
+
return engine.executor.explain(sql, ctx);
|
|
221
|
+
}
|
|
222
|
+
export async function explainEnhanced(engine, sql, ctx) {
|
|
223
|
+
const [standardPlan, features] = await Promise.all([
|
|
224
|
+
engine.executor.explain(sql, ctx),
|
|
225
|
+
engine.capabilityProbe.listFeatures(ctx),
|
|
226
|
+
]);
|
|
227
|
+
const extras = explainExtras(standardPlan.plan).join(" ");
|
|
228
|
+
const fullScanLikely = standardPlan.riskSummary.fullTableScanLikely;
|
|
229
|
+
const hasOffset = hasSqlPattern(sql, /\boffset\s+\d+/i);
|
|
230
|
+
const ndpMatched = /using pushed ndp condition/i.test(extras) ||
|
|
231
|
+
/using pushed ndp columns/i.test(extras) ||
|
|
232
|
+
/using pushed ndp aggregate/i.test(extras);
|
|
233
|
+
const parallelWouldEnable = features.parallel_query.available &&
|
|
234
|
+
(fullScanLikely ||
|
|
235
|
+
hasSqlPattern(sql, /\b(group\s+by|order\s+by|join)\b/i) ||
|
|
236
|
+
(standardPlan.riskSummary.estimatedRows ?? 0) >= 100_000);
|
|
237
|
+
const offsetMatched = hasOffset &&
|
|
238
|
+
features.offset_pushdown.available &&
|
|
239
|
+
features.offset_pushdown.enabled !== false;
|
|
240
|
+
return {
|
|
241
|
+
standardPlan,
|
|
242
|
+
taurusHints: {
|
|
243
|
+
ndpPushdown: {
|
|
244
|
+
condition: /using pushed ndp condition/i.test(extras),
|
|
245
|
+
columns: /using pushed ndp columns/i.test(extras),
|
|
246
|
+
aggregate: /using pushed ndp aggregate/i.test(extras),
|
|
247
|
+
blockedReason: !features.ndp_pushdown.available
|
|
248
|
+
? features.ndp_pushdown.reason
|
|
249
|
+
: features.ndp_pushdown.enabled === false
|
|
250
|
+
? "ndp_pushdown is available but not enabled."
|
|
251
|
+
: undefined,
|
|
252
|
+
},
|
|
253
|
+
parallelQuery: {
|
|
254
|
+
wouldEnable: parallelWouldEnable,
|
|
255
|
+
estimatedDegree: features.parallel_query.available && features.parallel_query.enabled
|
|
256
|
+
? ctx.limits.maxRows >= 1000
|
|
257
|
+
? 4
|
|
258
|
+
: 2
|
|
259
|
+
: undefined,
|
|
260
|
+
blockedReason: !features.parallel_query.available
|
|
261
|
+
? features.parallel_query.reason
|
|
262
|
+
: features.parallel_query.enabled === false
|
|
263
|
+
? "parallel_query is available but force_parallel_execute is disabled."
|
|
264
|
+
: undefined,
|
|
265
|
+
},
|
|
266
|
+
offsetPushdown: offsetMatched,
|
|
267
|
+
},
|
|
268
|
+
featureExplanations: {
|
|
269
|
+
offsetPushdown: buildOffsetPushdownExplanation(offsetMatched, hasOffset, features),
|
|
270
|
+
parallelQuery: buildParallelQueryExplanation(parallelWouldEnable, features, standardPlan, sql),
|
|
271
|
+
ndpPushdown: buildNdpPushdownExplanation(ndpMatched, features, extras),
|
|
272
|
+
},
|
|
273
|
+
optimizationSuggestions: buildEnhancedExplainSuggestions(sql, features, standardPlan),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
export async function executeReadonly(engine, sql, ctx, opts) {
|
|
277
|
+
return engine.executor.executeReadonly(sql, ctx, opts);
|
|
278
|
+
}
|
|
279
|
+
export async function executeMutation(engine, sql, ctx, opts) {
|
|
280
|
+
return engine.executor.executeMutation(sql, ctx, opts);
|
|
281
|
+
}
|
|
282
|
+
export async function flashbackQuery(engine, input, ctx, opts) {
|
|
283
|
+
const features = await engine.capabilityProbe.listFeatures(ctx);
|
|
284
|
+
const flashbackFeature = features.flashback_query;
|
|
285
|
+
if (!flashbackFeature.available || flashbackFeature.enabled === false) {
|
|
286
|
+
throw new UnsupportedFeatureError("flashback_query", flashbackFeature.reason ??
|
|
287
|
+
`Flashback query requires kernel version >= ${flashbackFeature.minVersion ?? "unknown"}.`, {
|
|
288
|
+
requiredVersion: flashbackFeature.minVersion,
|
|
289
|
+
currentVersion: (await engine.capabilityProbe.getKernelInfo(ctx))
|
|
290
|
+
.kernelVersion,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
const database = input.database ?? ctx.database;
|
|
294
|
+
if (!database) {
|
|
295
|
+
throw new Error("Flashback query requires a database context. Provide input.database or configure a default database.");
|
|
296
|
+
}
|
|
297
|
+
const effectiveInput = await resolveFlashbackInputForExecution(engine, ctx, input);
|
|
298
|
+
const sql = buildFlashbackSql(effectiveInput, database);
|
|
299
|
+
try {
|
|
300
|
+
return await engine.executor.executeReadonly(sql, ctx, {
|
|
301
|
+
...flashbackReadonlyOptions(input.limit),
|
|
302
|
+
...opts,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
if (error instanceof Error &&
|
|
307
|
+
NO_FLASHBACK_VIEW_PATTERN.test(error.message)) {
|
|
308
|
+
const requestedTimestamp = resolveFlashbackTimestamp(effectiveInput.asOf);
|
|
309
|
+
throw await buildFlashbackNoViewError(engine, ctx, effectiveInput, database, requestedTimestamp);
|
|
310
|
+
}
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
export async function listRecycleBin(engine, ctx, opts) {
|
|
315
|
+
const features = await engine.capabilityProbe.listFeatures(ctx);
|
|
316
|
+
const recycleBinFeature = features.recycle_bin;
|
|
317
|
+
if (!recycleBinFeature.available || recycleBinFeature.enabled === false) {
|
|
318
|
+
throw new UnsupportedFeatureError("recycle_bin", recycleBinFeature.reason ??
|
|
319
|
+
`Recycle bin requires kernel version >= ${recycleBinFeature.minVersion ?? "unknown"}.`, {
|
|
320
|
+
requiredVersion: recycleBinFeature.minVersion,
|
|
321
|
+
currentVersion: (await engine.capabilityProbe.getKernelInfo(ctx))
|
|
322
|
+
.kernelVersion,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return engine.executor.executeReadonly(buildListRecycleBinSql(), ctx, recycleBinReadonlyOptions(opts));
|
|
326
|
+
}
|
|
327
|
+
export async function restoreRecycleBinTable(engine, input, ctx, opts) {
|
|
328
|
+
const features = await engine.capabilityProbe.listFeatures(ctx);
|
|
329
|
+
const recycleBinFeature = features.recycle_bin;
|
|
330
|
+
if (!recycleBinFeature.available || recycleBinFeature.enabled === false) {
|
|
331
|
+
throw new UnsupportedFeatureError("recycle_bin", recycleBinFeature.reason ??
|
|
332
|
+
`Recycle bin requires kernel version >= ${recycleBinFeature.minVersion ?? "unknown"}.`, {
|
|
333
|
+
requiredVersion: recycleBinFeature.minVersion,
|
|
334
|
+
currentVersion: (await engine.capabilityProbe.getKernelInfo(ctx))
|
|
335
|
+
.kernelVersion,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return engine.executor.executeMutation(buildRestoreRecycleBinTableSql(input), ctx, {
|
|
339
|
+
...recycleBinMutationOptions(opts),
|
|
340
|
+
allowWithoutGlobalMutations: true,
|
|
341
|
+
allowReadonlyFallbackForMutations: true,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
export async function getQueryStatus(engine, queryId) {
|
|
345
|
+
return engine.executor.getQueryStatus(queryId);
|
|
346
|
+
}
|
|
347
|
+
export async function cancelQuery(engine, queryId) {
|
|
348
|
+
return engine.executor.cancelQuery(queryId);
|
|
349
|
+
}
|
|
350
|
+
export async function issueConfirmation(engine, input) {
|
|
351
|
+
const resolved = resolveConfirmationSql(input);
|
|
352
|
+
return engine.confirmationStore.issue({
|
|
353
|
+
sqlHash: resolved.hash,
|
|
354
|
+
normalizedSql: resolved.normalized,
|
|
355
|
+
context: input.context,
|
|
356
|
+
riskLevel: input.riskLevel,
|
|
357
|
+
ttlSeconds: input.ttlSeconds,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
export async function validateConfirmation(engine, token, sql, ctx) {
|
|
361
|
+
return engine.confirmationStore.validate(token, sql, ctx);
|
|
362
|
+
}
|
|
363
|
+
export async function handleConfirmation(engine, decision, ctx) {
|
|
364
|
+
if (!decision.requiresConfirmation) {
|
|
365
|
+
return { status: "confirmed" };
|
|
366
|
+
}
|
|
367
|
+
const token = await engine.confirmationStore.issue({
|
|
368
|
+
sqlHash: decision.sqlHash,
|
|
369
|
+
normalizedSql: decision.normalizedSql,
|
|
370
|
+
context: ctx,
|
|
371
|
+
riskLevel: decision.riskLevel,
|
|
372
|
+
});
|
|
373
|
+
return {
|
|
374
|
+
status: "token_issued",
|
|
375
|
+
token: token.token,
|
|
376
|
+
issuedAt: token.issuedAt,
|
|
377
|
+
expiresAt: token.expiresAt,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
export async function close(engine) {
|
|
381
|
+
await engine.connectionPool.close();
|
|
382
|
+
if (engine.confirmationStore instanceof InMemoryConfirmationStore) {
|
|
383
|
+
engine.confirmationStore.stop();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { DatabaseEngine, ProfileLoader } from "../auth/sql-profile-loader.js";
|
|
2
|
+
import type { CapabilityProbe } from "../capability/probe.js";
|
|
3
|
+
import type { Config } from "../config/index.js";
|
|
4
|
+
import type { DatasourceResolver, SessionContext } from "../context/session-context.js";
|
|
5
|
+
import type { ConnectionPool } from "../executor/connection-pool.js";
|
|
6
|
+
import type { ExplainResult, SqlExecutor } from "../executor/sql-executor.js";
|
|
7
|
+
import type { MetricsSource } from "../diagnostics/metrics-source.js";
|
|
8
|
+
import type { SlowSqlSource } from "../diagnostics/slow-sql-source.js";
|
|
9
|
+
import type { SecretResolver } from "../auth/secret-resolver.js";
|
|
10
|
+
import type { ConfirmationStore } from "../safety/confirmation-store.js";
|
|
11
|
+
import type { Guardrail } from "../safety/guardrail.js";
|
|
12
|
+
import type { RiskLevel } from "../safety/sql-validator.js";
|
|
13
|
+
import type { SchemaIntrospector } from "../schema/introspector.js";
|
|
14
|
+
export interface DataSourceInfo {
|
|
15
|
+
name: string;
|
|
16
|
+
engine: DatabaseEngine;
|
|
17
|
+
host?: string;
|
|
18
|
+
port: number;
|
|
19
|
+
database?: string;
|
|
20
|
+
hasMutationUser: boolean;
|
|
21
|
+
poolSize?: number;
|
|
22
|
+
isDefault: boolean;
|
|
23
|
+
}
|
|
24
|
+
export type IssueConfirmationInput = {
|
|
25
|
+
context: SessionContext;
|
|
26
|
+
riskLevel: RiskLevel;
|
|
27
|
+
sql?: string;
|
|
28
|
+
normalizedSql?: string;
|
|
29
|
+
sqlHash?: string;
|
|
30
|
+
ttlSeconds?: number;
|
|
31
|
+
};
|
|
32
|
+
export type ConfirmationOutcome = {
|
|
33
|
+
status: "confirmed";
|
|
34
|
+
} | {
|
|
35
|
+
status: "token_issued";
|
|
36
|
+
token: string;
|
|
37
|
+
issuedAt: number;
|
|
38
|
+
expiresAt: number;
|
|
39
|
+
};
|
|
40
|
+
export interface EnhancedExplainResult {
|
|
41
|
+
standardPlan: ExplainResult;
|
|
42
|
+
treePlan?: string;
|
|
43
|
+
taurusHints: {
|
|
44
|
+
ndpPushdown: {
|
|
45
|
+
condition: boolean;
|
|
46
|
+
columns: boolean;
|
|
47
|
+
aggregate: boolean;
|
|
48
|
+
blockedReason?: string;
|
|
49
|
+
};
|
|
50
|
+
parallelQuery: {
|
|
51
|
+
wouldEnable: boolean;
|
|
52
|
+
estimatedDegree?: number;
|
|
53
|
+
blockedReason?: string;
|
|
54
|
+
};
|
|
55
|
+
offsetPushdown: boolean;
|
|
56
|
+
};
|
|
57
|
+
featureExplanations: {
|
|
58
|
+
offsetPushdown: {
|
|
59
|
+
matched: boolean;
|
|
60
|
+
meaning: string;
|
|
61
|
+
whyTriggered: string;
|
|
62
|
+
expectedBenefit: string;
|
|
63
|
+
};
|
|
64
|
+
parallelQuery: {
|
|
65
|
+
matched: boolean;
|
|
66
|
+
meaning: string;
|
|
67
|
+
whyTriggered: string;
|
|
68
|
+
expectedBenefit: string;
|
|
69
|
+
};
|
|
70
|
+
ndpPushdown: {
|
|
71
|
+
matched: boolean;
|
|
72
|
+
meaning: string;
|
|
73
|
+
whyTriggered: string;
|
|
74
|
+
expectedBenefit: string;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
optimizationSuggestions: string[];
|
|
78
|
+
}
|
|
79
|
+
export interface ShowProcesslistInput {
|
|
80
|
+
user?: string;
|
|
81
|
+
host?: string;
|
|
82
|
+
sessionDatabase?: string;
|
|
83
|
+
command?: string;
|
|
84
|
+
minTimeSeconds?: number;
|
|
85
|
+
maxRows?: number;
|
|
86
|
+
includeIdle?: boolean;
|
|
87
|
+
includeSystem?: boolean;
|
|
88
|
+
includeInfo?: boolean;
|
|
89
|
+
infoMaxChars?: number;
|
|
90
|
+
}
|
|
91
|
+
export interface ShowLockWaitsInput {
|
|
92
|
+
table?: string;
|
|
93
|
+
blockerSessionId?: string;
|
|
94
|
+
maxRows?: number;
|
|
95
|
+
includeSql?: boolean;
|
|
96
|
+
sqlMaxChars?: number;
|
|
97
|
+
}
|
|
98
|
+
export interface TaurusDBEngineDeps {
|
|
99
|
+
config: Config;
|
|
100
|
+
profileLoader: ProfileLoader;
|
|
101
|
+
secretResolver: SecretResolver;
|
|
102
|
+
datasourceResolver: DatasourceResolver;
|
|
103
|
+
connectionPool: ConnectionPool;
|
|
104
|
+
schemaIntrospector: SchemaIntrospector;
|
|
105
|
+
guardrail: Guardrail;
|
|
106
|
+
executor: SqlExecutor;
|
|
107
|
+
confirmationStore: ConfirmationStore;
|
|
108
|
+
capabilityProbe: CapabilityProbe;
|
|
109
|
+
slowSqlSource?: SlowSqlSource;
|
|
110
|
+
metricsSource?: MetricsSource;
|
|
111
|
+
}
|
|
112
|
+
export interface TaurusDBEngineCreateOptions {
|
|
113
|
+
config?: Config;
|
|
114
|
+
profileLoader?: ProfileLoader;
|
|
115
|
+
secretResolver?: SecretResolver;
|
|
116
|
+
datasourceResolver?: DatasourceResolver;
|
|
117
|
+
connectionPool?: ConnectionPool;
|
|
118
|
+
schemaIntrospector?: SchemaIntrospector;
|
|
119
|
+
guardrail?: Guardrail;
|
|
120
|
+
executor?: SqlExecutor;
|
|
121
|
+
confirmationStore?: ConfirmationStore;
|
|
122
|
+
capabilityProbe?: CapabilityProbe;
|
|
123
|
+
slowSqlSource?: SlowSqlSource;
|
|
124
|
+
metricsSource?: MetricsSource;
|
|
125
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SessionContext } from "../../context/session-context.js";
|
|
2
|
+
import { type DiagnoseConnectionSpikeInput, type DiagnosticResult } from "../../diagnostics/types.js";
|
|
3
|
+
import type { TaurusDBEngine } from "../../engine.js";
|
|
4
|
+
export declare function diagnoseConnectionSpike(engine: TaurusDBEngine, input: DiagnoseConnectionSpikeInput, ctx: SessionContext): Promise<DiagnosticResult>;
|