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
package/dist/engine.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { createSqlProfileLoader, } from "./auth/sql-profile-loader.js";
|
|
2
|
+
import { createSecretResolver, } from "./auth/secret-resolver.js";
|
|
3
|
+
import { createCapabilityProbe, } from "./capability/probe.js";
|
|
4
|
+
import { getConfig } from "./config/index.js";
|
|
5
|
+
import { createDatasourceResolver } from "./context/datasource-resolver.js";
|
|
6
|
+
import { createConnectionPoolManager, } from "./executor/connection-pool.js";
|
|
7
|
+
import { createMySqlDriverAdapter } from "./executor/adapters/mysql.js";
|
|
8
|
+
import { createSqlExecutor, } from "./executor/sql-executor.js";
|
|
9
|
+
import { createConfirmationStore, } from "./safety/confirmation-store.js";
|
|
10
|
+
import { createGuardrail, } from "./safety/guardrail.js";
|
|
11
|
+
import { createSchemaIntrospector, } from "./schema/introspector.js";
|
|
12
|
+
import { createMySqlSchemaAdapter } from "./schema/adapters/mysql.js";
|
|
13
|
+
import { createSlowSqlSource, } from "./diagnostics/slow-sql-source.js";
|
|
14
|
+
import { createMetricsSource, } from "./diagnostics/metrics-source.js";
|
|
15
|
+
import { findLatestDeadlock, findMetadataLockWaits, findStatementDigestCandidatesForSqlHints, findStatementDigestSample, findStatementDigestSampleForSql, findStatementWaitEvents, findStorageStatementDigests, findTableStorageStats, findTopStatementDigests, isPerformanceSchemaEnabled, showLockWaits, showProcesslist, } from "./engine/data-access.js";
|
|
16
|
+
import { diagnoseConnectionSpike, diagnoseDbHotspot, diagnoseLockContention, diagnoseServiceLatency, diagnoseSlowQuery, diagnoseStoragePressure, findTopSlowSql, } from "./engine/diagnostics.js";
|
|
17
|
+
import { cancelQuery, close, executeMutation, executeReadonly, explain, explainEnhanced, flashbackQuery, getQueryStatus, handleConfirmation, issueConfirmation, listRecycleBin, restoreRecycleBinTable, validateConfirmation, } from "./engine/runtime.js";
|
|
18
|
+
function toDataSourceInfo(profile, defaultDatasource) {
|
|
19
|
+
return {
|
|
20
|
+
name: profile.name,
|
|
21
|
+
engine: profile.engine,
|
|
22
|
+
host: profile.host,
|
|
23
|
+
port: profile.port,
|
|
24
|
+
database: profile.database,
|
|
25
|
+
hasMutationUser: profile.mutationUser !== undefined,
|
|
26
|
+
poolSize: profile.poolSize,
|
|
27
|
+
isDefault: profile.name === defaultDatasource,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export class TaurusDBEngine {
|
|
31
|
+
config;
|
|
32
|
+
profileLoader;
|
|
33
|
+
secretResolver;
|
|
34
|
+
datasourceResolver;
|
|
35
|
+
connectionPool;
|
|
36
|
+
schemaIntrospector;
|
|
37
|
+
guardrail;
|
|
38
|
+
executor;
|
|
39
|
+
confirmationStore;
|
|
40
|
+
capabilityProbe;
|
|
41
|
+
slowSqlSource;
|
|
42
|
+
metricsSource;
|
|
43
|
+
constructor(deps) {
|
|
44
|
+
this.config = deps.config;
|
|
45
|
+
this.profileLoader = deps.profileLoader;
|
|
46
|
+
this.secretResolver = deps.secretResolver;
|
|
47
|
+
this.datasourceResolver = deps.datasourceResolver;
|
|
48
|
+
this.connectionPool = deps.connectionPool;
|
|
49
|
+
this.schemaIntrospector = deps.schemaIntrospector;
|
|
50
|
+
this.guardrail = deps.guardrail;
|
|
51
|
+
this.executor = deps.executor;
|
|
52
|
+
this.confirmationStore = deps.confirmationStore;
|
|
53
|
+
this.capabilityProbe = deps.capabilityProbe;
|
|
54
|
+
this.slowSqlSource = deps.slowSqlSource;
|
|
55
|
+
this.metricsSource = deps.metricsSource;
|
|
56
|
+
}
|
|
57
|
+
static async create(options = {}) {
|
|
58
|
+
const config = options.config ?? getConfig();
|
|
59
|
+
const profileLoader = options.profileLoader ?? createSqlProfileLoader({ config });
|
|
60
|
+
const secretResolver = options.secretResolver ?? createSecretResolver();
|
|
61
|
+
const datasourceResolver = options.datasourceResolver ??
|
|
62
|
+
createDatasourceResolver({
|
|
63
|
+
config,
|
|
64
|
+
profileLoader,
|
|
65
|
+
});
|
|
66
|
+
const connectionPool = options.connectionPool ??
|
|
67
|
+
createConnectionPoolManager({
|
|
68
|
+
config,
|
|
69
|
+
profileLoader,
|
|
70
|
+
secretResolver,
|
|
71
|
+
adapters: {
|
|
72
|
+
mysql: createMySqlDriverAdapter(),
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
const schemaIntrospector = options.schemaIntrospector ??
|
|
76
|
+
createSchemaIntrospector({
|
|
77
|
+
adapters: {
|
|
78
|
+
mysql: createMySqlSchemaAdapter({ connectionPool }),
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
const executor = options.executor ??
|
|
82
|
+
createSqlExecutor({
|
|
83
|
+
connectionPool,
|
|
84
|
+
});
|
|
85
|
+
const guardrail = options.guardrail ?? createGuardrail();
|
|
86
|
+
const confirmationStore = options.confirmationStore ?? createConfirmationStore();
|
|
87
|
+
const capabilityProbe = options.capabilityProbe ??
|
|
88
|
+
createCapabilityProbe({
|
|
89
|
+
connectionPool,
|
|
90
|
+
});
|
|
91
|
+
const slowSqlSource = options.slowSqlSource ?? createSlowSqlSource(config);
|
|
92
|
+
const metricsSource = options.metricsSource ?? createMetricsSource(config);
|
|
93
|
+
return new TaurusDBEngine({
|
|
94
|
+
config,
|
|
95
|
+
profileLoader,
|
|
96
|
+
secretResolver,
|
|
97
|
+
datasourceResolver,
|
|
98
|
+
connectionPool,
|
|
99
|
+
schemaIntrospector,
|
|
100
|
+
guardrail,
|
|
101
|
+
executor,
|
|
102
|
+
confirmationStore,
|
|
103
|
+
capabilityProbe,
|
|
104
|
+
slowSqlSource,
|
|
105
|
+
metricsSource,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async listDataSources() {
|
|
109
|
+
const [profiles, defaultDatasource] = await Promise.all([
|
|
110
|
+
this.profileLoader.load(),
|
|
111
|
+
this.profileLoader.getDefault(),
|
|
112
|
+
]);
|
|
113
|
+
return [...profiles.values()]
|
|
114
|
+
.map((profile) => toDataSourceInfo(profile, defaultDatasource))
|
|
115
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
116
|
+
}
|
|
117
|
+
async getDefaultDataSource() {
|
|
118
|
+
return this.profileLoader.getDefault();
|
|
119
|
+
}
|
|
120
|
+
async resolveContext(input, taskId) {
|
|
121
|
+
return this.datasourceResolver.resolve(input, taskId);
|
|
122
|
+
}
|
|
123
|
+
async listDatabases(ctx) {
|
|
124
|
+
return this.schemaIntrospector.listDatabases(ctx);
|
|
125
|
+
}
|
|
126
|
+
async listTables(ctx, database) {
|
|
127
|
+
return this.schemaIntrospector.listTables(ctx, database);
|
|
128
|
+
}
|
|
129
|
+
async describeTable(ctx, database, table) {
|
|
130
|
+
return this.schemaIntrospector.describeTable(ctx, database, table);
|
|
131
|
+
}
|
|
132
|
+
async inspectSql(input) {
|
|
133
|
+
return this.guardrail.inspect(input);
|
|
134
|
+
}
|
|
135
|
+
async probeCapabilities(ctx) {
|
|
136
|
+
return this.capabilityProbe.probe(ctx);
|
|
137
|
+
}
|
|
138
|
+
async getKernelInfo(ctx) {
|
|
139
|
+
return this.capabilityProbe.getKernelInfo(ctx);
|
|
140
|
+
}
|
|
141
|
+
async listFeatures(ctx) {
|
|
142
|
+
return this.capabilityProbe.listFeatures(ctx);
|
|
143
|
+
}
|
|
144
|
+
async showProcesslist(input, ctx) {
|
|
145
|
+
return showProcesslist(this, input, ctx);
|
|
146
|
+
}
|
|
147
|
+
async showLockWaits(input, ctx) {
|
|
148
|
+
return showLockWaits(this, input, ctx);
|
|
149
|
+
}
|
|
150
|
+
async findMetadataLockWaits(input, ctx) {
|
|
151
|
+
return findMetadataLockWaits(this, input, ctx);
|
|
152
|
+
}
|
|
153
|
+
async findLatestDeadlock(ctx) {
|
|
154
|
+
return findLatestDeadlock(this, ctx);
|
|
155
|
+
}
|
|
156
|
+
async findStatementDigestSample(digestText, ctx) {
|
|
157
|
+
return findStatementDigestSample(this, digestText, ctx);
|
|
158
|
+
}
|
|
159
|
+
async findStatementDigestSampleForSql(sql, ctx) {
|
|
160
|
+
return findStatementDigestSampleForSql(this, sql, ctx);
|
|
161
|
+
}
|
|
162
|
+
async findStatementDigestCandidatesForSqlHints(sqlText, ctx) {
|
|
163
|
+
return findStatementDigestCandidatesForSqlHints(this, sqlText, ctx);
|
|
164
|
+
}
|
|
165
|
+
async findTopStatementDigests(input, ctx) {
|
|
166
|
+
return findTopStatementDigests(this, input, ctx);
|
|
167
|
+
}
|
|
168
|
+
async isPerformanceSchemaEnabled(ctx) {
|
|
169
|
+
return isPerformanceSchemaEnabled(this, ctx);
|
|
170
|
+
}
|
|
171
|
+
async findStorageStatementDigests(input, ctx) {
|
|
172
|
+
return findStorageStatementDigests(this, input, ctx);
|
|
173
|
+
}
|
|
174
|
+
async findStatementWaitEvents(digestText, ctx) {
|
|
175
|
+
return findStatementWaitEvents(this, digestText, ctx);
|
|
176
|
+
}
|
|
177
|
+
async findTableStorageStats(input, ctx) {
|
|
178
|
+
return findTableStorageStats(this, input, ctx);
|
|
179
|
+
}
|
|
180
|
+
async diagnoseSlowQuery(input, ctx) {
|
|
181
|
+
return diagnoseSlowQuery(this, input, ctx);
|
|
182
|
+
}
|
|
183
|
+
async diagnoseServiceLatency(input, ctx) {
|
|
184
|
+
return diagnoseServiceLatency(this, input, ctx);
|
|
185
|
+
}
|
|
186
|
+
async diagnoseDbHotspot(input, ctx) {
|
|
187
|
+
return diagnoseDbHotspot(this, input, ctx);
|
|
188
|
+
}
|
|
189
|
+
async findTopSlowSql(input, ctx) {
|
|
190
|
+
return findTopSlowSql(this, input, ctx);
|
|
191
|
+
}
|
|
192
|
+
async diagnoseConnectionSpike(input, ctx) {
|
|
193
|
+
return diagnoseConnectionSpike(this, input, ctx);
|
|
194
|
+
}
|
|
195
|
+
async diagnoseLockContention(input, ctx) {
|
|
196
|
+
return diagnoseLockContention(this, input, ctx);
|
|
197
|
+
}
|
|
198
|
+
async diagnoseStoragePressure(input, ctx) {
|
|
199
|
+
return diagnoseStoragePressure(this, input, ctx);
|
|
200
|
+
}
|
|
201
|
+
async explain(sql, ctx) {
|
|
202
|
+
return explain(this, sql, ctx);
|
|
203
|
+
}
|
|
204
|
+
async explainEnhanced(sql, ctx) {
|
|
205
|
+
return explainEnhanced(this, sql, ctx);
|
|
206
|
+
}
|
|
207
|
+
async executeReadonly(sql, ctx, opts) {
|
|
208
|
+
return executeReadonly(this, sql, ctx, opts);
|
|
209
|
+
}
|
|
210
|
+
async executeMutation(sql, ctx, opts) {
|
|
211
|
+
return executeMutation(this, sql, ctx, opts);
|
|
212
|
+
}
|
|
213
|
+
async flashbackQuery(input, ctx, opts) {
|
|
214
|
+
return flashbackQuery(this, input, ctx, opts);
|
|
215
|
+
}
|
|
216
|
+
async listRecycleBin(ctx, opts) {
|
|
217
|
+
return listRecycleBin(this, ctx, opts);
|
|
218
|
+
}
|
|
219
|
+
async restoreRecycleBinTable(input, ctx, opts) {
|
|
220
|
+
return restoreRecycleBinTable(this, input, ctx, opts);
|
|
221
|
+
}
|
|
222
|
+
async getQueryStatus(queryId) {
|
|
223
|
+
return getQueryStatus(this, queryId);
|
|
224
|
+
}
|
|
225
|
+
async cancelQuery(queryId) {
|
|
226
|
+
return cancelQuery(this, queryId);
|
|
227
|
+
}
|
|
228
|
+
async issueConfirmation(input) {
|
|
229
|
+
return issueConfirmation(this, input);
|
|
230
|
+
}
|
|
231
|
+
async validateConfirmation(token, sql, ctx) {
|
|
232
|
+
return validateConfirmation(this, token, sql, ctx);
|
|
233
|
+
}
|
|
234
|
+
async handleConfirmation(decision, ctx) {
|
|
235
|
+
return handleConfirmation(this, decision, ctx);
|
|
236
|
+
}
|
|
237
|
+
async close() {
|
|
238
|
+
return close(this);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import mysql from "mysql2/promise";
|
|
2
|
+
function normalizeRows(value) {
|
|
3
|
+
return Array.isArray(value) ? value : [];
|
|
4
|
+
}
|
|
5
|
+
function normalizeResult(rows, fields) {
|
|
6
|
+
const normalizedRows = normalizeRows(rows);
|
|
7
|
+
if (Array.isArray(rows)) {
|
|
8
|
+
return {
|
|
9
|
+
rows: normalizedRows,
|
|
10
|
+
rowCount: normalizedRows.length,
|
|
11
|
+
fields: fields?.map((field) => ({
|
|
12
|
+
name: field.name,
|
|
13
|
+
type: field.columnType !== undefined ? String(field.columnType) : undefined,
|
|
14
|
+
})),
|
|
15
|
+
raw: rows,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (rows && typeof rows === "object") {
|
|
19
|
+
const header = rows;
|
|
20
|
+
return {
|
|
21
|
+
rows: [],
|
|
22
|
+
rowCount: header.affectedRows ?? 0,
|
|
23
|
+
affectedRows: header.affectedRows ?? 0,
|
|
24
|
+
raw: rows,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
rows: [],
|
|
29
|
+
rowCount: 0,
|
|
30
|
+
raw: rows,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function toSslOptions(tls) {
|
|
34
|
+
if (!tls?.enabled) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
rejectUnauthorized: tls.rejectUnauthorized,
|
|
39
|
+
servername: tls.servername,
|
|
40
|
+
ca: tls.ca,
|
|
41
|
+
cert: tls.cert,
|
|
42
|
+
key: tls.key,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
class MySqlDriverSession {
|
|
46
|
+
connection;
|
|
47
|
+
state;
|
|
48
|
+
constructor(connection, state) {
|
|
49
|
+
this.connection = connection;
|
|
50
|
+
this.state = state;
|
|
51
|
+
}
|
|
52
|
+
async execute(sql, options = {}) {
|
|
53
|
+
const [rows, fields] = await this.connection.query({
|
|
54
|
+
sql,
|
|
55
|
+
timeout: options.timeoutMs,
|
|
56
|
+
rowsAsArray: false,
|
|
57
|
+
});
|
|
58
|
+
return normalizeResult(rows, fields);
|
|
59
|
+
}
|
|
60
|
+
async cancel() {
|
|
61
|
+
if (this.state.destroyed) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.state.destroyed = true;
|
|
65
|
+
this.connection.destroy();
|
|
66
|
+
}
|
|
67
|
+
async release() {
|
|
68
|
+
if (this.state.released) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
this.state.released = true;
|
|
72
|
+
if (this.state.destroyed) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.connection.release();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
class MySqlDriverPool {
|
|
79
|
+
pool;
|
|
80
|
+
constructor(pool) {
|
|
81
|
+
this.pool = pool;
|
|
82
|
+
}
|
|
83
|
+
async acquire() {
|
|
84
|
+
const connection = await this.pool.getConnection();
|
|
85
|
+
return new MySqlDriverSession(connection, {
|
|
86
|
+
destroyed: false,
|
|
87
|
+
released: false,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async close() {
|
|
91
|
+
await this.pool.end();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function createMySqlDriverAdapter() {
|
|
95
|
+
return {
|
|
96
|
+
async createPool(input) {
|
|
97
|
+
const pool = mysql.createPool({
|
|
98
|
+
host: input.host,
|
|
99
|
+
port: input.port,
|
|
100
|
+
database: input.database,
|
|
101
|
+
user: input.username,
|
|
102
|
+
password: input.password,
|
|
103
|
+
dateStrings: true,
|
|
104
|
+
connectionLimit: input.poolSize ?? 4,
|
|
105
|
+
waitForConnections: true,
|
|
106
|
+
queueLimit: 0,
|
|
107
|
+
enableKeepAlive: true,
|
|
108
|
+
multipleStatements: false,
|
|
109
|
+
ssl: toSslOptions(input.tls),
|
|
110
|
+
});
|
|
111
|
+
return new MySqlDriverPool(pool);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Config } from "../config/index.js";
|
|
2
|
+
import type { DatabaseEngine, ProfileLoader } from "../auth/sql-profile-loader.js";
|
|
3
|
+
import type { SecretResolver } from "../auth/secret-resolver.js";
|
|
4
|
+
export type PoolMode = "ro" | "rw";
|
|
5
|
+
export interface ExecOptions {
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface RawResult {
|
|
9
|
+
rows?: unknown[];
|
|
10
|
+
rowCount?: number;
|
|
11
|
+
affectedRows?: number;
|
|
12
|
+
fields?: Array<{
|
|
13
|
+
name: string;
|
|
14
|
+
type?: string;
|
|
15
|
+
}>;
|
|
16
|
+
raw?: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface Session {
|
|
19
|
+
id: string;
|
|
20
|
+
datasource: string;
|
|
21
|
+
mode: PoolMode;
|
|
22
|
+
execute(sql: string, options?: ExecOptions): Promise<RawResult>;
|
|
23
|
+
cancel(): Promise<void>;
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export interface ModeHealth {
|
|
27
|
+
mode: PoolMode;
|
|
28
|
+
status: "ok" | "error" | "skipped";
|
|
29
|
+
message?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface PoolHealth {
|
|
32
|
+
datasource: string;
|
|
33
|
+
checkedAt: string;
|
|
34
|
+
modes: ModeHealth[];
|
|
35
|
+
}
|
|
36
|
+
export interface ConnectionPool {
|
|
37
|
+
acquire(datasource: string, mode: PoolMode, opts?: {
|
|
38
|
+
allowWithoutGlobalMutations?: boolean;
|
|
39
|
+
allowReadonlyFallbackForMutations?: boolean;
|
|
40
|
+
}): Promise<Session>;
|
|
41
|
+
release(session: Session): Promise<void>;
|
|
42
|
+
healthCheck(datasource: string): Promise<PoolHealth>;
|
|
43
|
+
close(): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
export interface DriverSession {
|
|
46
|
+
execute(sql: string, options?: ExecOptions): Promise<RawResult>;
|
|
47
|
+
cancel(): Promise<void>;
|
|
48
|
+
release(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
export interface DriverPool {
|
|
51
|
+
acquire(): Promise<DriverSession>;
|
|
52
|
+
close(): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
export interface DriverPoolCreateInput {
|
|
55
|
+
datasource: string;
|
|
56
|
+
mode: PoolMode;
|
|
57
|
+
engine: DatabaseEngine;
|
|
58
|
+
host: string;
|
|
59
|
+
port: number;
|
|
60
|
+
database?: string;
|
|
61
|
+
username: string;
|
|
62
|
+
password: string;
|
|
63
|
+
poolSize?: number;
|
|
64
|
+
tls?: {
|
|
65
|
+
enabled?: boolean;
|
|
66
|
+
rejectUnauthorized?: boolean;
|
|
67
|
+
servername?: string;
|
|
68
|
+
ca?: string;
|
|
69
|
+
cert?: string;
|
|
70
|
+
key?: string;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export interface DriverAdapter {
|
|
74
|
+
createPool(input: DriverPoolCreateInput): Promise<DriverPool>;
|
|
75
|
+
}
|
|
76
|
+
export type ConnectionPoolManagerOptions = {
|
|
77
|
+
config: Config;
|
|
78
|
+
profileLoader: ProfileLoader;
|
|
79
|
+
secretResolver: SecretResolver;
|
|
80
|
+
adapters: Partial<Record<DatabaseEngine, DriverAdapter>>;
|
|
81
|
+
};
|
|
82
|
+
export declare class ConnectionPoolError extends Error {
|
|
83
|
+
readonly code = "CONNECTION_FAILED";
|
|
84
|
+
constructor(message: string, cause?: unknown);
|
|
85
|
+
}
|
|
86
|
+
export declare class ConnectionPoolManager implements ConnectionPool {
|
|
87
|
+
private readonly config;
|
|
88
|
+
private readonly profileLoader;
|
|
89
|
+
private readonly secretResolver;
|
|
90
|
+
private readonly adapters;
|
|
91
|
+
private readonly pools;
|
|
92
|
+
private readonly activeSessions;
|
|
93
|
+
constructor(options: ConnectionPoolManagerOptions);
|
|
94
|
+
acquire(datasource: string, mode: PoolMode, opts?: {
|
|
95
|
+
allowWithoutGlobalMutations?: boolean;
|
|
96
|
+
allowReadonlyFallbackForMutations?: boolean;
|
|
97
|
+
}): Promise<Session>;
|
|
98
|
+
release(session: Session): Promise<void>;
|
|
99
|
+
healthCheck(datasource: string): Promise<PoolHealth>;
|
|
100
|
+
close(): Promise<void>;
|
|
101
|
+
private healthCheckMode;
|
|
102
|
+
private getOrCreatePool;
|
|
103
|
+
private createPool;
|
|
104
|
+
}
|
|
105
|
+
export declare function createConnectionPoolManager(options: ConnectionPoolManagerOptions): ConnectionPoolManager;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { ulid } from "ulid";
|
|
2
|
+
export class ConnectionPoolError extends Error {
|
|
3
|
+
code = "CONNECTION_FAILED";
|
|
4
|
+
constructor(message, cause) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "ConnectionPoolError";
|
|
7
|
+
if (cause !== undefined) {
|
|
8
|
+
this.cause = cause;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function poolKey(datasource, mode) {
|
|
13
|
+
return `${datasource}:${mode}`;
|
|
14
|
+
}
|
|
15
|
+
async function resolveTls(tls, secretResolver) {
|
|
16
|
+
if (!tls) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const resolved = {
|
|
20
|
+
enabled: tls.enabled,
|
|
21
|
+
rejectUnauthorized: tls.rejectUnauthorized,
|
|
22
|
+
servername: tls.servername,
|
|
23
|
+
};
|
|
24
|
+
if (tls.ca) {
|
|
25
|
+
resolved.ca = await secretResolver.resolve(tls.ca);
|
|
26
|
+
}
|
|
27
|
+
if (tls.cert) {
|
|
28
|
+
resolved.cert = await secretResolver.resolve(tls.cert);
|
|
29
|
+
}
|
|
30
|
+
if (tls.key) {
|
|
31
|
+
resolved.key = await secretResolver.resolve(tls.key);
|
|
32
|
+
}
|
|
33
|
+
return resolved;
|
|
34
|
+
}
|
|
35
|
+
function ensureMutationAllowed(config) {
|
|
36
|
+
if (!config.enableMutations) {
|
|
37
|
+
throw new ConnectionPoolError("Mutation mode is disabled by configuration.");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function selectCredential(profile, mode, opts = {}) {
|
|
41
|
+
if (mode === "ro") {
|
|
42
|
+
return profile.readonlyUser;
|
|
43
|
+
}
|
|
44
|
+
if (!profile.mutationUser) {
|
|
45
|
+
if (opts.allowReadonlyFallbackForMutations) {
|
|
46
|
+
return profile.readonlyUser;
|
|
47
|
+
}
|
|
48
|
+
throw new ConnectionPoolError(`Mutation user is not configured for datasource "${profile.name}".`);
|
|
49
|
+
}
|
|
50
|
+
return profile.mutationUser;
|
|
51
|
+
}
|
|
52
|
+
async function resolveCredentialValue(ref, secretResolver, context) {
|
|
53
|
+
try {
|
|
54
|
+
return await secretResolver.resolve(ref);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw new ConnectionPoolError(`Failed to resolve credential for ${context}.`, error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export class ConnectionPoolManager {
|
|
61
|
+
config;
|
|
62
|
+
profileLoader;
|
|
63
|
+
secretResolver;
|
|
64
|
+
adapters;
|
|
65
|
+
pools = new Map();
|
|
66
|
+
activeSessions = new Map();
|
|
67
|
+
constructor(options) {
|
|
68
|
+
this.config = options.config;
|
|
69
|
+
this.profileLoader = options.profileLoader;
|
|
70
|
+
this.secretResolver = options.secretResolver;
|
|
71
|
+
this.adapters = options.adapters;
|
|
72
|
+
}
|
|
73
|
+
async acquire(datasource, mode, opts = {}) {
|
|
74
|
+
const profile = await this.profileLoader.get(datasource);
|
|
75
|
+
if (!profile) {
|
|
76
|
+
throw new ConnectionPoolError(`Datasource profile not found: "${datasource}".`);
|
|
77
|
+
}
|
|
78
|
+
if (mode === "rw" && !opts.allowWithoutGlobalMutations) {
|
|
79
|
+
ensureMutationAllowed(this.config);
|
|
80
|
+
}
|
|
81
|
+
const entry = await this.getOrCreatePool(profile, mode, opts);
|
|
82
|
+
let driverSession;
|
|
83
|
+
try {
|
|
84
|
+
driverSession = await entry.pool.acquire();
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
throw new ConnectionPoolError(`Failed to acquire database session for datasource "${datasource}".`, error);
|
|
88
|
+
}
|
|
89
|
+
const sessionId = `sess_${ulid().toLowerCase()}`;
|
|
90
|
+
const active = {
|
|
91
|
+
id: sessionId,
|
|
92
|
+
entryKey: entry.key,
|
|
93
|
+
driverSession,
|
|
94
|
+
datasource,
|
|
95
|
+
mode,
|
|
96
|
+
};
|
|
97
|
+
this.activeSessions.set(sessionId, active);
|
|
98
|
+
const self = this;
|
|
99
|
+
return {
|
|
100
|
+
id: sessionId,
|
|
101
|
+
datasource,
|
|
102
|
+
mode,
|
|
103
|
+
async execute(sql, options) {
|
|
104
|
+
return active.driverSession.execute(sql, options);
|
|
105
|
+
},
|
|
106
|
+
async cancel() {
|
|
107
|
+
await active.driverSession.cancel();
|
|
108
|
+
},
|
|
109
|
+
async close() {
|
|
110
|
+
await self.release({ id: sessionId });
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async release(session) {
|
|
115
|
+
const active = this.activeSessions.get(session.id);
|
|
116
|
+
if (!active) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this.activeSessions.delete(session.id);
|
|
120
|
+
await active.driverSession.release();
|
|
121
|
+
}
|
|
122
|
+
async healthCheck(datasource) {
|
|
123
|
+
const modes = [];
|
|
124
|
+
modes.push(await this.healthCheckMode(datasource, "ro"));
|
|
125
|
+
if (!this.config.enableMutations) {
|
|
126
|
+
modes.push({
|
|
127
|
+
mode: "rw",
|
|
128
|
+
status: "skipped",
|
|
129
|
+
message: "Mutation mode disabled by config.",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
modes.push(await this.healthCheckMode(datasource, "rw"));
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
datasource,
|
|
137
|
+
checkedAt: new Date().toISOString(),
|
|
138
|
+
modes,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async close() {
|
|
142
|
+
const activeSessions = [...this.activeSessions.values()];
|
|
143
|
+
this.activeSessions.clear();
|
|
144
|
+
for (const active of activeSessions) {
|
|
145
|
+
try {
|
|
146
|
+
await active.driverSession.release();
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Ignore release failure during shutdown.
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const closers = [];
|
|
153
|
+
for (const value of this.pools.values()) {
|
|
154
|
+
const resolved = await value;
|
|
155
|
+
closers.push(resolved.pool.close().catch(() => {
|
|
156
|
+
// Ignore close failure during shutdown.
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
await Promise.all(closers);
|
|
160
|
+
this.pools.clear();
|
|
161
|
+
}
|
|
162
|
+
async healthCheckMode(datasource, mode) {
|
|
163
|
+
try {
|
|
164
|
+
const session = await this.acquire(datasource, mode);
|
|
165
|
+
await this.release(session);
|
|
166
|
+
return { mode, status: "ok" };
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (mode === "rw" && error instanceof ConnectionPoolError && /not configured/.test(error.message)) {
|
|
170
|
+
return { mode, status: "skipped", message: error.message };
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
mode,
|
|
174
|
+
status: "error",
|
|
175
|
+
message: error instanceof Error ? error.message : "unknown error",
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async getOrCreatePool(profile, mode, opts = {}) {
|
|
180
|
+
const key = poolKey(profile.name, mode);
|
|
181
|
+
const existing = this.pools.get(key);
|
|
182
|
+
if (existing) {
|
|
183
|
+
return existing instanceof Promise ? existing : existing;
|
|
184
|
+
}
|
|
185
|
+
const pending = this.createPool(profile, mode, opts)
|
|
186
|
+
.then((entry) => {
|
|
187
|
+
this.pools.set(key, entry);
|
|
188
|
+
return entry;
|
|
189
|
+
})
|
|
190
|
+
.catch((error) => {
|
|
191
|
+
this.pools.delete(key);
|
|
192
|
+
throw error;
|
|
193
|
+
});
|
|
194
|
+
this.pools.set(key, pending);
|
|
195
|
+
return pending;
|
|
196
|
+
}
|
|
197
|
+
async createPool(profile, mode, opts = {}) {
|
|
198
|
+
const adapter = this.adapters[profile.engine];
|
|
199
|
+
if (!adapter) {
|
|
200
|
+
throw new ConnectionPoolError(`No driver adapter registered for engine "${profile.engine}".`);
|
|
201
|
+
}
|
|
202
|
+
if (!profile.host) {
|
|
203
|
+
throw new ConnectionPoolError(`Datasource "${profile.name}" does not define a host. Select a cloud instance or configure host in the datasource profile.`);
|
|
204
|
+
}
|
|
205
|
+
const credential = selectCredential(profile, mode, opts);
|
|
206
|
+
const password = await resolveCredentialValue(credential.password, this.secretResolver, `${profile.name}.${mode}.password`);
|
|
207
|
+
const tls = await resolveTls(profile.tls, this.secretResolver);
|
|
208
|
+
let pool;
|
|
209
|
+
try {
|
|
210
|
+
pool = await adapter.createPool({
|
|
211
|
+
datasource: profile.name,
|
|
212
|
+
mode,
|
|
213
|
+
engine: profile.engine,
|
|
214
|
+
host: profile.host,
|
|
215
|
+
port: profile.port,
|
|
216
|
+
database: profile.database,
|
|
217
|
+
username: credential.username,
|
|
218
|
+
password,
|
|
219
|
+
poolSize: profile.poolSize,
|
|
220
|
+
tls,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
throw new ConnectionPoolError(`Failed to create ${mode === "ro" ? "readonly" : "mutation"} pool for datasource "${profile.name}".`, error);
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
key: poolKey(profile.name, mode),
|
|
228
|
+
datasource: profile.name,
|
|
229
|
+
mode,
|
|
230
|
+
pool,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
export function createConnectionPoolManager(options) {
|
|
235
|
+
return new ConnectionPoolManager(options);
|
|
236
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RawResult } from "./connection-pool.js";
|
|
2
|
+
import type { ExplainRiskSummary } from "../safety/sql-validator.js";
|
|
3
|
+
export declare function normalizeExplainRows(result: RawResult): Record<string, unknown>[];
|
|
4
|
+
export declare function summarizeExplainRows(rows: Record<string, unknown>[]): ExplainRiskSummary;
|
|
5
|
+
export declare function buildExplainRecommendations(summary: ExplainRiskSummary): string[];
|