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.
Files changed (170) hide show
  1. package/README.md +21 -0
  2. package/dist/auth/secret-resolver.d.ts +16 -0
  3. package/dist/auth/secret-resolver.js +64 -0
  4. package/dist/auth/sql-profile-loader/env-source.d.ts +3 -0
  5. package/dist/auth/sql-profile-loader/env-source.js +94 -0
  6. package/dist/auth/sql-profile-loader/file-source.d.ts +6 -0
  7. package/dist/auth/sql-profile-loader/file-source.js +40 -0
  8. package/dist/auth/sql-profile-loader/loader.d.ts +16 -0
  9. package/dist/auth/sql-profile-loader/loader.js +81 -0
  10. package/dist/auth/sql-profile-loader/parsing.d.ts +14 -0
  11. package/dist/auth/sql-profile-loader/parsing.js +216 -0
  12. package/dist/auth/sql-profile-loader/runtime-override.d.ts +14 -0
  13. package/dist/auth/sql-profile-loader/runtime-override.js +52 -0
  14. package/dist/auth/sql-profile-loader/types.d.ts +64 -0
  15. package/dist/auth/sql-profile-loader/types.js +1 -0
  16. package/dist/auth/sql-profile-loader.d.ts +4 -0
  17. package/dist/auth/sql-profile-loader.js +3 -0
  18. package/dist/capability/feature-matrix.d.ts +5 -0
  19. package/dist/capability/feature-matrix.js +237 -0
  20. package/dist/capability/probe.d.ts +19 -0
  21. package/dist/capability/probe.js +139 -0
  22. package/dist/capability/types.d.ts +49 -0
  23. package/dist/capability/types.js +16 -0
  24. package/dist/capability/version.d.ts +3 -0
  25. package/dist/capability/version.js +47 -0
  26. package/dist/cloud/auth.d.ts +26 -0
  27. package/dist/cloud/auth.js +198 -0
  28. package/dist/cloud/instances.d.ts +46 -0
  29. package/dist/cloud/instances.js +224 -0
  30. package/dist/config/env.d.ts +1 -0
  31. package/dist/config/env.js +194 -0
  32. package/dist/config/index.d.ts +6 -0
  33. package/dist/config/index.js +21 -0
  34. package/dist/config/redaction.d.ts +2 -0
  35. package/dist/config/redaction.js +19 -0
  36. package/dist/config/schema.d.ts +417 -0
  37. package/dist/config/schema.js +100 -0
  38. package/dist/context/datasource-resolver.d.ts +19 -0
  39. package/dist/context/datasource-resolver.js +71 -0
  40. package/dist/context/session-context.d.ts +26 -0
  41. package/dist/context/session-context.js +1 -0
  42. package/dist/diagnostics/metrics-source.d.ts +65 -0
  43. package/dist/diagnostics/metrics-source.js +280 -0
  44. package/dist/diagnostics/slow-sql-source/das-source.d.ts +43 -0
  45. package/dist/diagnostics/slow-sql-source/das-source.js +170 -0
  46. package/dist/diagnostics/slow-sql-source/factory.d.ts +5 -0
  47. package/dist/diagnostics/slow-sql-source/factory.js +87 -0
  48. package/dist/diagnostics/slow-sql-source/parsers.d.ts +7 -0
  49. package/dist/diagnostics/slow-sql-source/parsers.js +125 -0
  50. package/dist/diagnostics/slow-sql-source/taurus-api-source.d.ts +42 -0
  51. package/dist/diagnostics/slow-sql-source/taurus-api-source.js +149 -0
  52. package/dist/diagnostics/slow-sql-source/types.d.ts +40 -0
  53. package/dist/diagnostics/slow-sql-source/types.js +1 -0
  54. package/dist/diagnostics/slow-sql-source/utils.d.ts +20 -0
  55. package/dist/diagnostics/slow-sql-source/utils.js +170 -0
  56. package/dist/diagnostics/slow-sql-source.d.ts +4 -0
  57. package/dist/diagnostics/slow-sql-source.js +3 -0
  58. package/dist/diagnostics/types.d.ts +189 -0
  59. package/dist/diagnostics/types.js +39 -0
  60. package/dist/engine/data-access/locks.d.ts +8 -0
  61. package/dist/engine/data-access/locks.js +146 -0
  62. package/dist/engine/data-access/processlist.d.ts +4 -0
  63. package/dist/engine/data-access/processlist.js +56 -0
  64. package/dist/engine/data-access/statements.d.ts +10 -0
  65. package/dist/engine/data-access/statements.js +203 -0
  66. package/dist/engine/data-access/storage.d.ts +6 -0
  67. package/dist/engine/data-access/storage.js +96 -0
  68. package/dist/engine/data-access.d.ts +4 -0
  69. package/dist/engine/data-access.js +4 -0
  70. package/dist/engine/diagnostics.d.ts +7 -0
  71. package/dist/engine/diagnostics.js +7 -0
  72. package/dist/engine/helper-modules/diagnostics.d.ts +57 -0
  73. package/dist/engine/helper-modules/diagnostics.js +322 -0
  74. package/dist/engine/helper-modules/parsers.d.ts +13 -0
  75. package/dist/engine/helper-modules/parsers.js +283 -0
  76. package/dist/engine/helper-modules/sql.d.ts +12 -0
  77. package/dist/engine/helper-modules/sql.js +119 -0
  78. package/dist/engine/helper-modules/types.d.ts +103 -0
  79. package/dist/engine/helper-modules/types.js +1 -0
  80. package/dist/engine/helpers.d.ts +4 -0
  81. package/dist/engine/helpers.js +4 -0
  82. package/dist/engine/runtime.d.ts +20 -0
  83. package/dist/engine/runtime.js +385 -0
  84. package/dist/engine/types.d.ts +125 -0
  85. package/dist/engine/types.js +1 -0
  86. package/dist/engine/workflows/connection-spike.d.ts +4 -0
  87. package/dist/engine/workflows/connection-spike.js +316 -0
  88. package/dist/engine/workflows/db-hotspot.d.ts +4 -0
  89. package/dist/engine/workflows/db-hotspot.js +182 -0
  90. package/dist/engine/workflows/lock-contention-helpers/entities.d.ts +9 -0
  91. package/dist/engine/workflows/lock-contention-helpers/entities.js +58 -0
  92. package/dist/engine/workflows/lock-contention-helpers/no-match.d.ts +3 -0
  93. package/dist/engine/workflows/lock-contention-helpers/no-match.js +65 -0
  94. package/dist/engine/workflows/lock-contention-helpers/report.d.ts +21 -0
  95. package/dist/engine/workflows/lock-contention-helpers/report.js +104 -0
  96. package/dist/engine/workflows/lock-contention-helpers/root-cause.d.ts +4 -0
  97. package/dist/engine/workflows/lock-contention-helpers/root-cause.js +79 -0
  98. package/dist/engine/workflows/lock-contention-helpers/signals.d.ts +22 -0
  99. package/dist/engine/workflows/lock-contention-helpers/signals.js +34 -0
  100. package/dist/engine/workflows/lock-contention-helpers.d.ts +5 -0
  101. package/dist/engine/workflows/lock-contention-helpers.js +5 -0
  102. package/dist/engine/workflows/lock-contention.d.ts +4 -0
  103. package/dist/engine/workflows/lock-contention.js +67 -0
  104. package/dist/engine/workflows/service-latency.d.ts +4 -0
  105. package/dist/engine/workflows/service-latency.js +262 -0
  106. package/dist/engine/workflows/slow-query-helpers.d.ts +41 -0
  107. package/dist/engine/workflows/slow-query-helpers.js +253 -0
  108. package/dist/engine/workflows/slow-query.d.ts +4 -0
  109. package/dist/engine/workflows/slow-query.js +156 -0
  110. package/dist/engine/workflows/storage-pressure-helpers.d.ts +12 -0
  111. package/dist/engine/workflows/storage-pressure-helpers.js +281 -0
  112. package/dist/engine/workflows/storage-pressure.d.ts +4 -0
  113. package/dist/engine/workflows/storage-pressure.js +27 -0
  114. package/dist/engine/workflows/top-slow-sql.d.ts +4 -0
  115. package/dist/engine/workflows/top-slow-sql.js +222 -0
  116. package/dist/engine.d.ts +77 -0
  117. package/dist/engine.js +240 -0
  118. package/dist/executor/adapters/mysql.d.ts +2 -0
  119. package/dist/executor/adapters/mysql.js +114 -0
  120. package/dist/executor/connection-pool.d.ts +105 -0
  121. package/dist/executor/connection-pool.js +236 -0
  122. package/dist/executor/explain.d.ts +5 -0
  123. package/dist/executor/explain.js +119 -0
  124. package/dist/executor/query-tracker.d.ts +45 -0
  125. package/dist/executor/query-tracker.js +83 -0
  126. package/dist/executor/result-normalizer.d.ts +6 -0
  127. package/dist/executor/result-normalizer.js +47 -0
  128. package/dist/executor/sql-executor.d.ts +32 -0
  129. package/dist/executor/sql-executor.js +250 -0
  130. package/dist/executor/types.d.ts +70 -0
  131. package/dist/executor/types.js +1 -0
  132. package/dist/index.d.ts +40 -0
  133. package/dist/index.js +21 -0
  134. package/dist/safety/confirmation-store.d.ts +44 -0
  135. package/dist/safety/confirmation-store.js +130 -0
  136. package/dist/safety/guardrail.d.ts +39 -0
  137. package/dist/safety/guardrail.js +99 -0
  138. package/dist/safety/parser/adapter.d.ts +10 -0
  139. package/dist/safety/parser/adapter.js +72 -0
  140. package/dist/safety/parser/ast-utils.d.ts +10 -0
  141. package/dist/safety/parser/ast-utils.js +167 -0
  142. package/dist/safety/parser/features.d.ts +12 -0
  143. package/dist/safety/parser/features.js +113 -0
  144. package/dist/safety/parser/index.d.ts +2 -0
  145. package/dist/safety/parser/index.js +1 -0
  146. package/dist/safety/parser/types.d.ts +76 -0
  147. package/dist/safety/parser/types.js +1 -0
  148. package/dist/safety/redaction.d.ts +34 -0
  149. package/dist/safety/redaction.js +186 -0
  150. package/dist/safety/sql-classifier.d.ts +19 -0
  151. package/dist/safety/sql-classifier.js +43 -0
  152. package/dist/safety/sql-validator.d.ts +19 -0
  153. package/dist/safety/sql-validator.js +143 -0
  154. package/dist/schema/adapters/mysql.d.ts +16 -0
  155. package/dist/schema/adapters/mysql.js +287 -0
  156. package/dist/schema/introspector.d.ts +70 -0
  157. package/dist/schema/introspector.js +40 -0
  158. package/dist/taurus/flashback.d.ts +36 -0
  159. package/dist/taurus/flashback.js +149 -0
  160. package/dist/taurus/recycle-bin.d.ts +14 -0
  161. package/dist/taurus/recycle-bin.js +61 -0
  162. package/dist/utils/formatter.d.ts +70 -0
  163. package/dist/utils/formatter.js +60 -0
  164. package/dist/utils/hash.d.ts +2 -0
  165. package/dist/utils/hash.js +247 -0
  166. package/dist/utils/id.d.ts +2 -0
  167. package/dist/utils/id.js +11 -0
  168. package/dist/utils/logger.d.ts +9 -0
  169. package/dist/utils/logger.js +39 -0
  170. package/package.json +46 -0
@@ -0,0 +1,119 @@
1
+ function asFiniteNumber(value) {
2
+ if (typeof value === "number" && Number.isFinite(value)) {
3
+ return value;
4
+ }
5
+ if (typeof value === "string" && value.trim().length > 0) {
6
+ const parsed = Number(value);
7
+ return Number.isFinite(parsed) ? parsed : undefined;
8
+ }
9
+ return undefined;
10
+ }
11
+ function isObject(value) {
12
+ return value !== null && typeof value === "object" && !Array.isArray(value);
13
+ }
14
+ function readObjectField(row, candidates) {
15
+ for (const candidate of candidates) {
16
+ if (Object.hasOwn(row, candidate)) {
17
+ return row[candidate];
18
+ }
19
+ }
20
+ const lowerMap = new Map();
21
+ for (const [key, value] of Object.entries(row)) {
22
+ lowerMap.set(key.toLowerCase(), value);
23
+ }
24
+ for (const candidate of candidates) {
25
+ const value = lowerMap.get(candidate.toLowerCase());
26
+ if (value !== undefined) {
27
+ return value;
28
+ }
29
+ }
30
+ return undefined;
31
+ }
32
+ export function normalizeExplainRows(result) {
33
+ const rows = Array.isArray(result.rows) ? result.rows : [];
34
+ if (rows.length === 0) {
35
+ return [];
36
+ }
37
+ const first = rows[0];
38
+ if (isObject(first)) {
39
+ return rows.filter((row) => isObject(row));
40
+ }
41
+ if (Array.isArray(first) && Array.isArray(result.fields) && result.fields.length > 0) {
42
+ return rows
43
+ .filter((row) => Array.isArray(row))
44
+ .map((row) => {
45
+ const mapped = {};
46
+ result.fields?.forEach((field, index) => {
47
+ mapped[field.name] = row[index];
48
+ });
49
+ return mapped;
50
+ });
51
+ }
52
+ return [];
53
+ }
54
+ export function summarizeExplainRows(rows) {
55
+ const fullTableScanLikely = rows.some((row) => {
56
+ const scanType = readObjectField(row, ["type", "access_type"]);
57
+ return typeof scanType === "string" && scanType.toUpperCase() === "ALL";
58
+ });
59
+ const indexHitLikely = rows.length > 0 &&
60
+ rows.every((row) => {
61
+ const key = readObjectField(row, ["key", "possible_key", "index_name"]);
62
+ if (key === null || key === undefined) {
63
+ return false;
64
+ }
65
+ return String(key).trim().length > 0;
66
+ });
67
+ const estimatedRows = rows.reduce((sum, row) => {
68
+ const numeric = asFiniteNumber(readObjectField(row, ["rows", "estimated_rows"]));
69
+ return numeric !== undefined ? sum + numeric : sum;
70
+ }, 0);
71
+ const usesTempStructure = rows.some((row) => {
72
+ const extra = readObjectField(row, ["Extra", "extra", "note"]);
73
+ return typeof extra === "string" && /using temporary/i.test(extra);
74
+ });
75
+ const usesFilesort = rows.some((row) => {
76
+ const extra = readObjectField(row, ["Extra", "extra", "note"]);
77
+ return typeof extra === "string" && /using filesort/i.test(extra);
78
+ });
79
+ const riskHints = [];
80
+ if (fullTableScanLikely) {
81
+ riskHints.push("Explain suggests potential full table scan.");
82
+ }
83
+ if (!indexHitLikely && rows.length > 0) {
84
+ riskHints.push("Explain suggests low index hit probability.");
85
+ }
86
+ if (usesTempStructure) {
87
+ riskHints.push("Explain contains Using temporary.");
88
+ }
89
+ if (usesFilesort) {
90
+ riskHints.push("Explain contains Using filesort.");
91
+ }
92
+ return {
93
+ fullTableScanLikely,
94
+ indexHitLikely,
95
+ estimatedRows: rows.length > 0 ? estimatedRows : null,
96
+ usesTempStructure,
97
+ usesFilesort,
98
+ riskHints,
99
+ };
100
+ }
101
+ export function buildExplainRecommendations(summary) {
102
+ const recommendations = [];
103
+ if (summary.fullTableScanLikely) {
104
+ recommendations.push("Consider adding WHERE filters and an index to avoid full table scans.");
105
+ }
106
+ if (!summary.indexHitLikely) {
107
+ recommendations.push("Review indexes for filter and join columns.");
108
+ }
109
+ if (summary.estimatedRows !== null && summary.estimatedRows > 100_000) {
110
+ recommendations.push("Estimated row count is high; consider narrowing predicates or adding selective indexes.");
111
+ }
112
+ if (summary.usesTempStructure) {
113
+ recommendations.push("Avoid large temporary structures by reducing sort/group cardinality.");
114
+ }
115
+ if (summary.usesFilesort) {
116
+ recommendations.push("Consider index support for ORDER BY to reduce filesort.");
117
+ }
118
+ return recommendations;
119
+ }
@@ -0,0 +1,45 @@
1
+ export type QueryState = "running" | "completed" | "failed" | "cancelled";
2
+ export interface QueryInfo {
3
+ queryId: string;
4
+ taskId: string;
5
+ datasource: string;
6
+ mode: "ro" | "rw";
7
+ statementType?: string;
8
+ sqlHash?: string;
9
+ startedAt: number;
10
+ dbConnectionId?: number;
11
+ status: QueryState;
12
+ endedAt?: number;
13
+ durationMs?: number;
14
+ error?: string;
15
+ }
16
+ export interface QueryStatusResult {
17
+ status: Exclude<QueryState, "running">;
18
+ endedAt?: number;
19
+ durationMs?: number;
20
+ error?: string;
21
+ }
22
+ export interface QueryTracker {
23
+ register(queryId: string, info: QueryInfo): void;
24
+ get(queryId: string): QueryInfo | undefined;
25
+ markCompleted(queryId: string, result: QueryStatusResult): void;
26
+ listActive(): QueryInfo[];
27
+ cleanup(olderThanMs: number): void;
28
+ }
29
+ export type QueryTrackerOptions = {
30
+ now?: () => number;
31
+ historyLimit?: number;
32
+ };
33
+ export declare class InMemoryQueryTracker implements QueryTracker {
34
+ private readonly now;
35
+ private readonly historyLimit;
36
+ private readonly items;
37
+ constructor(options?: QueryTrackerOptions);
38
+ register(queryId: string, info: QueryInfo): void;
39
+ get(queryId: string): QueryInfo | undefined;
40
+ markCompleted(queryId: string, result: QueryStatusResult): void;
41
+ listActive(): QueryInfo[];
42
+ cleanup(olderThanMs: number): void;
43
+ private evictIfNeeded;
44
+ }
45
+ export declare function createQueryTracker(options?: QueryTrackerOptions): QueryTracker;
@@ -0,0 +1,83 @@
1
+ function cloneInfo(info) {
2
+ return { ...info };
3
+ }
4
+ export class InMemoryQueryTracker {
5
+ now;
6
+ historyLimit;
7
+ items = new Map();
8
+ constructor(options = {}) {
9
+ this.now = options.now ?? Date.now;
10
+ this.historyLimit = options.historyLimit ?? 500;
11
+ }
12
+ register(queryId, info) {
13
+ this.items.set(queryId, cloneInfo(info));
14
+ this.evictIfNeeded();
15
+ }
16
+ get(queryId) {
17
+ const info = this.items.get(queryId);
18
+ return info ? cloneInfo(info) : undefined;
19
+ }
20
+ markCompleted(queryId, result) {
21
+ const existing = this.items.get(queryId);
22
+ if (!existing) {
23
+ return;
24
+ }
25
+ const endedAt = result.endedAt ?? this.now();
26
+ const durationMs = result.durationMs ?? Math.max(0, endedAt - existing.startedAt);
27
+ existing.status = result.status;
28
+ existing.endedAt = endedAt;
29
+ existing.durationMs = durationMs;
30
+ existing.error = result.error;
31
+ this.items.set(queryId, existing);
32
+ this.evictIfNeeded();
33
+ }
34
+ listActive() {
35
+ const active = [];
36
+ for (const info of this.items.values()) {
37
+ if (info.status === "running") {
38
+ active.push(cloneInfo(info));
39
+ }
40
+ }
41
+ return active.sort((a, b) => a.startedAt - b.startedAt);
42
+ }
43
+ cleanup(olderThanMs) {
44
+ if (!Number.isFinite(olderThanMs) || olderThanMs < 0) {
45
+ return;
46
+ }
47
+ const threshold = this.now() - olderThanMs;
48
+ for (const [queryId, info] of this.items.entries()) {
49
+ if (info.status === "running") {
50
+ continue;
51
+ }
52
+ const referenceTime = info.endedAt ?? info.startedAt;
53
+ if (referenceTime <= threshold) {
54
+ this.items.delete(queryId);
55
+ }
56
+ }
57
+ }
58
+ evictIfNeeded() {
59
+ if (this.items.size <= this.historyLimit) {
60
+ return;
61
+ }
62
+ const removable = [];
63
+ for (const [queryId, info] of this.items.entries()) {
64
+ if (info.status === "running") {
65
+ continue;
66
+ }
67
+ removable.push({
68
+ queryId,
69
+ refTime: info.endedAt ?? info.startedAt,
70
+ });
71
+ }
72
+ removable.sort((a, b) => a.refTime - b.refTime);
73
+ for (const entry of removable) {
74
+ if (this.items.size <= this.historyLimit) {
75
+ break;
76
+ }
77
+ this.items.delete(entry.queryId);
78
+ }
79
+ }
80
+ }
81
+ export function createQueryTracker(options = {}) {
82
+ return new InMemoryQueryTracker(options);
83
+ }
@@ -0,0 +1,6 @@
1
+ import type { RawResult } from "./connection-pool.js";
2
+ import type { ColumnMeta } from "./types.js";
3
+ export declare function asFiniteNumber(value: unknown): number | undefined;
4
+ export declare function isObject(value: unknown): value is Record<string, unknown>;
5
+ export declare function inferColumns(raw: RawResult, rows: unknown[]): ColumnMeta[];
6
+ export declare function normalizeRows(rows: unknown[], columns: ColumnMeta[]): unknown[][];
@@ -0,0 +1,47 @@
1
+ export function asFiniteNumber(value) {
2
+ if (typeof value === "number" && Number.isFinite(value)) {
3
+ return value;
4
+ }
5
+ if (typeof value === "string" && value.trim().length > 0) {
6
+ const parsed = Number(value);
7
+ return Number.isFinite(parsed) ? parsed : undefined;
8
+ }
9
+ return undefined;
10
+ }
11
+ export function isObject(value) {
12
+ return value !== null && typeof value === "object" && !Array.isArray(value);
13
+ }
14
+ export function inferColumns(raw, rows) {
15
+ if (Array.isArray(raw.fields) && raw.fields.length > 0) {
16
+ return raw.fields.map((field) => ({
17
+ name: field.name,
18
+ type: field.type,
19
+ }));
20
+ }
21
+ const first = rows[0];
22
+ if (isObject(first)) {
23
+ return Object.keys(first).map((name) => ({ name }));
24
+ }
25
+ if (Array.isArray(first)) {
26
+ return first.map((_, index) => ({ name: `col_${index + 1}` }));
27
+ }
28
+ return [];
29
+ }
30
+ export function normalizeRows(rows, columns) {
31
+ if (rows.length === 0) {
32
+ return [];
33
+ }
34
+ const first = rows[0];
35
+ if (Array.isArray(first)) {
36
+ return rows.map((row) => (Array.isArray(row) ? row : [row]));
37
+ }
38
+ if (isObject(first)) {
39
+ return rows.map((row) => {
40
+ if (!isObject(row)) {
41
+ return [row];
42
+ }
43
+ return columns.map((column) => row[column.name]);
44
+ });
45
+ }
46
+ return rows.map((row) => [row]);
47
+ }
@@ -0,0 +1,32 @@
1
+ import type { ConnectionPool } from "./connection-pool.js";
2
+ import type { SessionContext } from "../context/session-context.js";
3
+ import { type QueryTracker } from "./query-tracker.js";
4
+ import { type ResultRedactor } from "../safety/redaction.js";
5
+ import type { CancelResult, ExplainResult, MutationOptions, MutationResult, QueryResult, QueryStatus, ReadonlyOptions, SqlExecutor } from "./types.js";
6
+ export type { CancelResult, ColumnMeta, ExplainResult, MutationOptions, MutationResult, QueryResult, QueryStatus, ReadonlyOptions, SqlExecutor, } from "./types.js";
7
+ export type SqlExecutorOptions = {
8
+ connectionPool: ConnectionPool;
9
+ now?: () => number;
10
+ queryIdGenerator?: () => string;
11
+ historyLimit?: number;
12
+ queryTracker?: QueryTracker;
13
+ resultRedactor?: ResultRedactor;
14
+ };
15
+ export declare class SqlExecutorImpl implements SqlExecutor {
16
+ private readonly connectionPool;
17
+ private readonly now;
18
+ private readonly queryIdGenerator;
19
+ private readonly queryTracker;
20
+ private readonly resultRedactor;
21
+ private readonly activeSessions;
22
+ constructor(options: SqlExecutorOptions);
23
+ explain(sql: string, ctx: SessionContext): Promise<ExplainResult>;
24
+ executeReadonly(sql: string, ctx: SessionContext, opts?: ReadonlyOptions): Promise<QueryResult>;
25
+ executeMutation(sql: string, ctx: SessionContext, opts?: MutationOptions): Promise<MutationResult>;
26
+ getQueryStatus(queryId: string): Promise<QueryStatus>;
27
+ cancelQuery(queryId: string): Promise<CancelResult>;
28
+ private beginQuery;
29
+ private completeQuery;
30
+ private endQuerySession;
31
+ }
32
+ export declare function createSqlExecutor(options: SqlExecutorOptions): SqlExecutor;
@@ -0,0 +1,250 @@
1
+ import { generateQueryId } from "../utils/id.js";
2
+ import { buildExplainRecommendations, normalizeExplainRows, summarizeExplainRows, } from "./explain.js";
3
+ import { createQueryTracker, } from "./query-tracker.js";
4
+ import { createResultRedactor, } from "../safety/redaction.js";
5
+ import { asFiniteNumber, inferColumns, normalizeRows } from "./result-normalizer.js";
6
+ export class SqlExecutorImpl {
7
+ connectionPool;
8
+ now;
9
+ queryIdGenerator;
10
+ queryTracker;
11
+ resultRedactor;
12
+ activeSessions = new Map();
13
+ constructor(options) {
14
+ this.connectionPool = options.connectionPool;
15
+ this.now = options.now ?? Date.now;
16
+ this.queryIdGenerator = options.queryIdGenerator ?? (() => generateQueryId());
17
+ this.queryTracker =
18
+ options.queryTracker ??
19
+ createQueryTracker({
20
+ now: this.now,
21
+ historyLimit: options.historyLimit,
22
+ });
23
+ this.resultRedactor = options.resultRedactor ?? createResultRedactor();
24
+ }
25
+ async explain(sql, ctx) {
26
+ const queryId = this.queryIdGenerator();
27
+ const startedAt = this.now();
28
+ const session = await this.connectionPool.acquire(ctx.datasource, "ro");
29
+ const active = this.beginQuery(queryId, session, ctx, "ro", startedAt);
30
+ try {
31
+ const result = await session.execute(`EXPLAIN ${sql}`, {
32
+ timeoutMs: ctx.limits.timeoutMs,
33
+ });
34
+ const plan = normalizeExplainRows(result);
35
+ const riskSummary = summarizeExplainRows(plan);
36
+ const recommendations = buildExplainRecommendations(riskSummary);
37
+ const durationMs = this.now() - startedAt;
38
+ this.completeQuery(active.queryId, "completed", durationMs);
39
+ return {
40
+ queryId,
41
+ plan,
42
+ riskSummary,
43
+ recommendations,
44
+ durationMs,
45
+ };
46
+ }
47
+ catch (error) {
48
+ const durationMs = this.now() - startedAt;
49
+ this.completeQuery(active.queryId, active.cancelRequested ? "cancelled" : "failed", durationMs, error);
50
+ throw error;
51
+ }
52
+ finally {
53
+ await this.endQuerySession(active.queryId);
54
+ }
55
+ }
56
+ async executeReadonly(sql, ctx, opts = {}) {
57
+ const queryId = this.queryIdGenerator();
58
+ const startedAt = this.now();
59
+ const maxRows = opts.maxRows ?? ctx.limits.maxRows;
60
+ const maxColumns = opts.maxColumns ?? ctx.limits.maxColumns;
61
+ const maxFieldChars = opts.maxFieldChars ?? ctx.limits.maxFieldChars ?? 2048;
62
+ const timeoutMs = opts.timeoutMs ?? ctx.limits.timeoutMs;
63
+ const session = await this.connectionPool.acquire(ctx.datasource, "ro");
64
+ const active = this.beginQuery(queryId, session, ctx, "ro", startedAt);
65
+ try {
66
+ const result = await session.execute(sql, { timeoutMs });
67
+ const sourceRows = Array.isArray(result.rows) ? result.rows : [];
68
+ const columns = inferColumns(result, sourceRows);
69
+ const normalizedRows = normalizeRows(sourceRows, columns);
70
+ const rowCount = asFiniteNumber(result.rowCount) ?? normalizedRows.length;
71
+ const redacted = this.resultRedactor.redact({
72
+ columns,
73
+ rows: normalizedRows,
74
+ rowCount,
75
+ }, {
76
+ maxRows,
77
+ maxColumns,
78
+ maxFieldChars,
79
+ sensitiveColumns: opts.sensitiveColumns,
80
+ sensitiveStrategy: opts.sensitiveStrategy,
81
+ });
82
+ const durationMs = this.now() - startedAt;
83
+ this.completeQuery(active.queryId, "completed", durationMs);
84
+ return {
85
+ queryId,
86
+ columns: redacted.columns,
87
+ rows: redacted.rows,
88
+ rowCount: redacted.rowCount,
89
+ originalRowCount: redacted.originalRowCount,
90
+ truncated: redacted.truncated,
91
+ rowTruncated: redacted.rowTruncated,
92
+ columnTruncated: redacted.columnTruncated,
93
+ fieldTruncated: redacted.fieldTruncated,
94
+ redactedColumns: redacted.redactedColumns,
95
+ droppedColumns: redacted.droppedColumns,
96
+ truncatedColumns: redacted.truncatedColumns,
97
+ durationMs,
98
+ };
99
+ }
100
+ catch (error) {
101
+ const durationMs = this.now() - startedAt;
102
+ this.completeQuery(active.queryId, active.cancelRequested ? "cancelled" : "failed", durationMs, error);
103
+ throw error;
104
+ }
105
+ finally {
106
+ await this.endQuerySession(active.queryId);
107
+ }
108
+ }
109
+ async executeMutation(sql, ctx, opts = {}) {
110
+ if (ctx.limits.readonly) {
111
+ throw new Error("Readonly session context cannot execute mutation SQL.");
112
+ }
113
+ const queryId = this.queryIdGenerator();
114
+ const startedAt = this.now();
115
+ const timeoutMs = opts.timeoutMs ?? ctx.limits.timeoutMs;
116
+ const session = await this.connectionPool.acquire(ctx.datasource, "rw", {
117
+ allowWithoutGlobalMutations: opts.allowWithoutGlobalMutations,
118
+ allowReadonlyFallbackForMutations: opts.allowReadonlyFallbackForMutations,
119
+ });
120
+ const active = this.beginQuery(queryId, session, ctx, "rw", startedAt);
121
+ try {
122
+ await session.execute("BEGIN", { timeoutMs });
123
+ const result = await session.execute(sql, { timeoutMs });
124
+ await session.execute("COMMIT", { timeoutMs });
125
+ const affectedRows = asFiniteNumber(result.affectedRows) ??
126
+ asFiniteNumber(result.rowCount) ??
127
+ 0;
128
+ const durationMs = this.now() - startedAt;
129
+ this.completeQuery(active.queryId, "completed", durationMs);
130
+ return {
131
+ queryId,
132
+ affectedRows,
133
+ durationMs,
134
+ };
135
+ }
136
+ catch (error) {
137
+ try {
138
+ await session.execute("ROLLBACK", { timeoutMs });
139
+ }
140
+ catch {
141
+ // Ignore rollback failure and keep original error.
142
+ }
143
+ const durationMs = this.now() - startedAt;
144
+ this.completeQuery(active.queryId, active.cancelRequested ? "cancelled" : "failed", durationMs, error);
145
+ throw error;
146
+ }
147
+ finally {
148
+ await this.endQuerySession(active.queryId);
149
+ }
150
+ }
151
+ async getQueryStatus(queryId) {
152
+ const info = this.queryTracker.get(queryId);
153
+ if (!info) {
154
+ return {
155
+ queryId,
156
+ status: "not_found",
157
+ };
158
+ }
159
+ return {
160
+ queryId,
161
+ taskId: info.taskId,
162
+ datasource: info.datasource,
163
+ mode: info.mode,
164
+ status: info.status,
165
+ startedAt: info.startedAt,
166
+ endedAt: info.endedAt,
167
+ durationMs: info.durationMs,
168
+ error: info.error,
169
+ };
170
+ }
171
+ async cancelQuery(queryId) {
172
+ const active = this.activeSessions.get(queryId);
173
+ if (!active) {
174
+ const info = this.queryTracker.get(queryId);
175
+ if (!info) {
176
+ return { queryId, status: "not_found" };
177
+ }
178
+ if (info.status === "completed") {
179
+ return { queryId, status: "completed" };
180
+ }
181
+ return {
182
+ queryId,
183
+ status: info.status === "failed" ? "failed" : "cancelled",
184
+ message: info.error,
185
+ };
186
+ }
187
+ active.cancelRequested = true;
188
+ try {
189
+ await active.session.cancel();
190
+ const now = this.now();
191
+ this.completeQuery(queryId, "cancelled", now - active.startedAt);
192
+ return {
193
+ queryId,
194
+ status: "cancelled",
195
+ };
196
+ }
197
+ catch (error) {
198
+ const now = this.now();
199
+ this.completeQuery(queryId, "failed", now - active.startedAt, error);
200
+ return {
201
+ queryId,
202
+ status: "failed",
203
+ message: error instanceof Error ? error.message : String(error),
204
+ };
205
+ }
206
+ }
207
+ beginQuery(queryId, session, ctx, mode, startedAt) {
208
+ const active = {
209
+ queryId,
210
+ session,
211
+ startedAt,
212
+ cancelRequested: false,
213
+ };
214
+ this.activeSessions.set(queryId, active);
215
+ this.queryTracker.register(queryId, {
216
+ queryId,
217
+ taskId: ctx.task_id,
218
+ datasource: ctx.datasource,
219
+ mode,
220
+ status: "running",
221
+ startedAt,
222
+ });
223
+ return active;
224
+ }
225
+ completeQuery(queryId, status, durationMs, error) {
226
+ const info = this.queryTracker.get(queryId);
227
+ if (!info) {
228
+ return;
229
+ }
230
+ const endedAt = info.startedAt + durationMs;
231
+ const errorMessage = error instanceof Error ? error.message : error !== undefined ? String(error) : undefined;
232
+ this.queryTracker.markCompleted(queryId, {
233
+ status,
234
+ endedAt,
235
+ durationMs,
236
+ error: errorMessage,
237
+ });
238
+ }
239
+ async endQuerySession(queryId) {
240
+ const active = this.activeSessions.get(queryId);
241
+ if (!active) {
242
+ return;
243
+ }
244
+ this.activeSessions.delete(queryId);
245
+ await this.connectionPool.release(active.session);
246
+ }
247
+ }
248
+ export function createSqlExecutor(options) {
249
+ return new SqlExecutorImpl(options);
250
+ }
@@ -0,0 +1,70 @@
1
+ import type { SessionContext } from "../context/session-context.js";
2
+ import type { SensitiveStrategy } from "../safety/redaction.js";
3
+ import type { ExplainRiskSummary } from "../safety/sql-validator.js";
4
+ export interface ColumnMeta {
5
+ name: string;
6
+ type?: string;
7
+ }
8
+ export interface ReadonlyOptions {
9
+ maxRows?: number;
10
+ maxColumns?: number;
11
+ maxFieldChars?: number;
12
+ timeoutMs?: number;
13
+ sensitiveColumns?: Iterable<string>;
14
+ sensitiveStrategy?: SensitiveStrategy;
15
+ }
16
+ export interface MutationOptions {
17
+ timeoutMs?: number;
18
+ allowWithoutGlobalMutations?: boolean;
19
+ allowReadonlyFallbackForMutations?: boolean;
20
+ }
21
+ export interface QueryResult {
22
+ queryId: string;
23
+ columns: ColumnMeta[];
24
+ rows: unknown[][];
25
+ rowCount: number;
26
+ originalRowCount: number;
27
+ truncated: boolean;
28
+ rowTruncated: boolean;
29
+ columnTruncated: boolean;
30
+ fieldTruncated: boolean;
31
+ redactedColumns: string[];
32
+ droppedColumns: string[];
33
+ truncatedColumns: string[];
34
+ durationMs: number;
35
+ }
36
+ export interface MutationResult {
37
+ queryId: string;
38
+ affectedRows: number;
39
+ durationMs: number;
40
+ }
41
+ export interface ExplainResult {
42
+ queryId: string;
43
+ plan: Record<string, unknown>[];
44
+ riskSummary: ExplainRiskSummary;
45
+ recommendations: string[];
46
+ durationMs: number;
47
+ }
48
+ export interface QueryStatus {
49
+ queryId: string;
50
+ status: "running" | "completed" | "failed" | "cancelled" | "not_found";
51
+ taskId?: string;
52
+ datasource?: string;
53
+ mode?: "ro" | "rw";
54
+ startedAt?: number;
55
+ endedAt?: number;
56
+ durationMs?: number;
57
+ error?: string;
58
+ }
59
+ export interface CancelResult {
60
+ queryId: string;
61
+ status: "cancelled" | "not_found" | "completed" | "failed";
62
+ message?: string;
63
+ }
64
+ export interface SqlExecutor {
65
+ explain(sql: string, ctx: SessionContext): Promise<ExplainResult>;
66
+ executeReadonly(sql: string, ctx: SessionContext, opts?: ReadonlyOptions): Promise<QueryResult>;
67
+ executeMutation(sql: string, ctx: SessionContext, opts?: MutationOptions): Promise<MutationResult>;
68
+ getQueryStatus(queryId: string): Promise<QueryStatus>;
69
+ cancelQuery(queryId: string): Promise<CancelResult>;
70
+ }
@@ -0,0 +1 @@
1
+ export {};