@bytebase/dbhub 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ABTVMHD6.js → chunk-55X2VY5D.js} +1 -1
- package/dist/chunk-5XSBOK3W.js +63 -0
- package/dist/{chunk-MJVIE3QY.js → chunk-PDKPK25P.js} +286 -81
- package/dist/chunk-SJ5BRPQA.js +500 -0
- package/dist/{chunk-YIGJHEU6.js → chunk-X5OCFRNL.js} +170 -27
- package/dist/index.js +116 -32
- package/dist/mariadb-RWOMZTA6.js +270 -0
- package/dist/mysql-ZXO4TWLG.js +312 -0
- package/dist/{oracle-OUKBU52X.js → oracle-UAVN3F4U.js} +2 -2
- package/dist/{postgres-DC5BDEMU.js → postgres-KCFNDQLG.js} +136 -30
- package/dist/{registry-C7X5G7PA.js → registry-ZIVIYCB2.js} +2 -1
- package/dist/{sqlite-AO2BKIC6.js → sqlite-WKY6543Q.js} +2 -2
- package/dist/{sqlserver-AJUK6L55.js → sqlserver-25MAE77Q.js} +312 -372
- package/package.json +1 -1
- package/dist/chunk-6SZKW6GD.js +0 -179
- package/dist/mariadb-L7Z55ROY.js +0 -551
- package/dist/mysql-XXIHWQGR.js +0 -601
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CANCEL_QUERY_TIMEOUT_MS,
|
|
3
|
+
clientQueryTimeoutMs,
|
|
4
|
+
isClientSideTimeout
|
|
5
|
+
} from "./chunk-5XSBOK3W.js";
|
|
1
6
|
import {
|
|
2
7
|
toNullableNumber
|
|
3
8
|
} from "./chunk-FU2ZJE4E.js";
|
|
@@ -7,8 +12,8 @@ import {
|
|
|
7
12
|
import {
|
|
8
13
|
SQLRowLimiter,
|
|
9
14
|
closeQuietly
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-X5OCFRNL.js";
|
|
16
|
+
import "./chunk-55X2VY5D.js";
|
|
12
17
|
import {
|
|
13
18
|
ConnectorRegistry,
|
|
14
19
|
SafeURL,
|
|
@@ -17,10 +22,7 @@ import {
|
|
|
17
22
|
} from "./chunk-IXC4NQGC.js";
|
|
18
23
|
|
|
19
24
|
// src/connectors/postgres/index.ts
|
|
20
|
-
import
|
|
21
|
-
import os from "os";
|
|
22
|
-
import path from "path";
|
|
23
|
-
import pg2 from "pg";
|
|
25
|
+
import pg3 from "pg";
|
|
24
26
|
|
|
25
27
|
// src/connectors/postgres/failed-to-read-certificate.ts
|
|
26
28
|
var FailedToReadCertificate = class extends Error {
|
|
@@ -56,24 +58,83 @@ var postgresTypeParsers = {
|
|
|
56
58
|
}
|
|
57
59
|
};
|
|
58
60
|
|
|
59
|
-
// src/connectors/postgres/
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
// src/connectors/postgres/ssl-pems.ts
|
|
62
|
+
import fs from "fs";
|
|
63
|
+
import os from "os";
|
|
64
|
+
import path from "path";
|
|
65
|
+
import pg2 from "pg";
|
|
62
66
|
var CLIENT_CERT_SSL_MODES = ["require", "verify-ca", "verify-full"];
|
|
63
67
|
var SUPPORTED_SSL_MODES = ["disable", ...CLIENT_CERT_SSL_MODES];
|
|
68
|
+
var SSL_ROOT_CERT_LABEL = "SSL root certificate";
|
|
69
|
+
var SSL_CLIENT_CERT_LABEL = "SSL client certificate";
|
|
70
|
+
var SSL_CLIENT_KEY_LABEL = "SSL client key";
|
|
71
|
+
function resolvePemPath(filePath) {
|
|
72
|
+
return filePath.startsWith("~/") ? path.join(os.homedir(), filePath.slice(2)) : filePath;
|
|
73
|
+
}
|
|
74
|
+
function failedToRead(label, resolved, err) {
|
|
75
|
+
return new FailedToReadCertificate(
|
|
76
|
+
`Failed to read ${label} at '${resolved}': ${err instanceof Error ? err.message : String(err)}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
64
79
|
async function readPemFile(filePath, label) {
|
|
65
|
-
const resolved =
|
|
80
|
+
const resolved = resolvePemPath(filePath);
|
|
66
81
|
try {
|
|
67
82
|
return await fs.promises.readFile(resolved, "utf-8");
|
|
68
83
|
} catch (err) {
|
|
69
|
-
throw
|
|
70
|
-
|
|
71
|
-
|
|
84
|
+
throw failedToRead(label, resolved, err);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function readPemFileSync(filePath, label) {
|
|
88
|
+
const resolved = resolvePemPath(filePath);
|
|
89
|
+
try {
|
|
90
|
+
return fs.readFileSync(resolved, "utf-8");
|
|
91
|
+
} catch (err) {
|
|
92
|
+
throw failedToRead(label, resolved, err);
|
|
72
93
|
}
|
|
73
94
|
}
|
|
74
95
|
function isEncryptedPemKey(pem) {
|
|
75
96
|
return pem.includes("ENCRYPTED PRIVATE KEY") || pem.includes("Proc-Type: 4,ENCRYPTED");
|
|
76
97
|
}
|
|
98
|
+
function encryptedKeyMessage(keyPath) {
|
|
99
|
+
return `SSL client key at '${keyPath}' is encrypted; encrypted private keys are not supported. Decrypt it first, e.g. 'openssl pkey -in client.key -out client-plain.key'`;
|
|
100
|
+
}
|
|
101
|
+
function loadPems(paths) {
|
|
102
|
+
const pems = {};
|
|
103
|
+
if (paths.sslrootcert !== void 0) {
|
|
104
|
+
pems.ca = readPemFileSync(paths.sslrootcert, SSL_ROOT_CERT_LABEL);
|
|
105
|
+
}
|
|
106
|
+
if (paths.sslcert !== void 0 && paths.sslkey !== void 0) {
|
|
107
|
+
const key = readPemFileSync(paths.sslkey, SSL_CLIENT_KEY_LABEL);
|
|
108
|
+
if (isEncryptedPemKey(key)) {
|
|
109
|
+
throw new FailedToReadCertificate(encryptedKeyMessage(paths.sslkey));
|
|
110
|
+
}
|
|
111
|
+
pems.key = key;
|
|
112
|
+
pems.cert = readPemFileSync(paths.sslcert, SSL_CLIENT_CERT_LABEL);
|
|
113
|
+
}
|
|
114
|
+
return pems;
|
|
115
|
+
}
|
|
116
|
+
function rotatingPemClient(paths) {
|
|
117
|
+
return class RotatingPemClient extends pg2.Client {
|
|
118
|
+
connect(callback) {
|
|
119
|
+
let pems;
|
|
120
|
+
try {
|
|
121
|
+
pems = loadPems(paths);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
124
|
+
if (callback) {
|
|
125
|
+
callback(error);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
return Promise.reject(error);
|
|
129
|
+
}
|
|
130
|
+
Object.assign(this.ssl, pems);
|
|
131
|
+
return callback ? super.connect(callback) : super.connect();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/connectors/postgres/index.ts
|
|
137
|
+
var { Pool } = pg3;
|
|
77
138
|
var PostgresDSNParser = class {
|
|
78
139
|
async parse(dsn, config) {
|
|
79
140
|
const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
|
|
@@ -96,7 +157,7 @@ Expected: ${expectedFormat}`
|
|
|
96
157
|
database: url.pathname ? url.pathname.substring(1) : "",
|
|
97
158
|
// Remove leading '/' if exists
|
|
98
159
|
user: url.username,
|
|
99
|
-
password: url.password
|
|
160
|
+
password: config?.password ?? url.password
|
|
100
161
|
};
|
|
101
162
|
let sslmode;
|
|
102
163
|
let sslrootcert;
|
|
@@ -143,29 +204,30 @@ Expected: ${expectedFormat}`
|
|
|
143
204
|
sslConfig.checkServerIdentity = () => void 0;
|
|
144
205
|
}
|
|
145
206
|
if (sslrootcert) {
|
|
146
|
-
sslConfig.ca = await readPemFile(sslrootcert,
|
|
207
|
+
sslConfig.ca = await readPemFile(sslrootcert, SSL_ROOT_CERT_LABEL);
|
|
147
208
|
}
|
|
148
209
|
poolConfig.ssl = sslConfig;
|
|
149
210
|
}
|
|
150
211
|
if (sslcert !== void 0 && sslkey !== void 0) {
|
|
151
|
-
const key = await readPemFile(sslkey,
|
|
212
|
+
const key = await readPemFile(sslkey, SSL_CLIENT_KEY_LABEL);
|
|
152
213
|
if (isEncryptedPemKey(key)) {
|
|
153
|
-
throw new FailedToReadCertificate(
|
|
154
|
-
`SSL client key at '${sslkey}' is encrypted; encrypted private keys are not supported. Decrypt it first, e.g. 'openssl pkey -in client.key -out client-plain.key'`
|
|
155
|
-
);
|
|
214
|
+
throw new FailedToReadCertificate(encryptedKeyMessage(sslkey));
|
|
156
215
|
}
|
|
157
216
|
Object.assign(poolConfig.ssl, {
|
|
158
|
-
cert: await readPemFile(sslcert,
|
|
217
|
+
cert: await readPemFile(sslcert, SSL_CLIENT_CERT_LABEL),
|
|
159
218
|
key
|
|
160
219
|
});
|
|
161
220
|
}
|
|
221
|
+
if (sslrootcert !== void 0 || sslcert !== void 0) {
|
|
222
|
+
poolConfig.Client = rotatingPemClient({ sslrootcert, sslcert, sslkey });
|
|
223
|
+
}
|
|
162
224
|
if (connectionTimeoutSeconds !== void 0) {
|
|
163
225
|
poolConfig.connectionTimeoutMillis = connectionTimeoutSeconds * 1e3;
|
|
164
226
|
}
|
|
165
227
|
if (queryTimeoutSeconds !== void 0) {
|
|
166
228
|
const queryTimeoutMs = queryTimeoutSeconds * 1e3;
|
|
167
229
|
poolConfig.statement_timeout = queryTimeoutMs;
|
|
168
|
-
poolConfig.query_timeout = queryTimeoutMs
|
|
230
|
+
poolConfig.query_timeout = clientQueryTimeoutMs(queryTimeoutMs);
|
|
169
231
|
}
|
|
170
232
|
if (poolMaxConnections !== void 0) {
|
|
171
233
|
poolConfig.max = poolMaxConnections;
|
|
@@ -639,6 +701,8 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
639
701
|
throw new Error("Not connected to database");
|
|
640
702
|
}
|
|
641
703
|
const client = await this.pool.connect();
|
|
704
|
+
const backendPid = client.processID;
|
|
705
|
+
let isClientDiscarded = false;
|
|
642
706
|
try {
|
|
643
707
|
const statements = splitSQLStatements(sql, "postgres");
|
|
644
708
|
if (statements.length === 1) {
|
|
@@ -660,10 +724,7 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
660
724
|
SQLRowLimiter.flagTruncation(resultSet2, options.maxRows, probeApplied);
|
|
661
725
|
return { resultSets: [resultSet2] };
|
|
662
726
|
} catch (error) {
|
|
663
|
-
|
|
664
|
-
await client.query("ROLLBACK");
|
|
665
|
-
} catch {
|
|
666
|
-
}
|
|
727
|
+
await this.rollbackQuietly(client, error);
|
|
667
728
|
throw error;
|
|
668
729
|
}
|
|
669
730
|
}
|
|
@@ -704,16 +765,61 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
704
765
|
}
|
|
705
766
|
await client.query("COMMIT");
|
|
706
767
|
} catch (error) {
|
|
707
|
-
|
|
708
|
-
await client.query("ROLLBACK");
|
|
709
|
-
} catch {
|
|
710
|
-
}
|
|
768
|
+
await this.rollbackQuietly(client, error);
|
|
711
769
|
throw error;
|
|
712
770
|
}
|
|
713
771
|
return { resultSets };
|
|
714
772
|
}
|
|
773
|
+
} catch (error) {
|
|
774
|
+
if (isClientSideTimeout(error)) {
|
|
775
|
+
isClientDiscarded = true;
|
|
776
|
+
client.release(true);
|
|
777
|
+
await this.cancelBackend(backendPid);
|
|
778
|
+
}
|
|
779
|
+
throw error;
|
|
715
780
|
} finally {
|
|
716
|
-
|
|
781
|
+
if (!isClientDiscarded) {
|
|
782
|
+
client.release();
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Best-effort rollback after a failed statement, so a failed ROLLBACK (e.g.
|
|
788
|
+
* a dropped connection) cannot mask the original error. Skipped for a
|
|
789
|
+
* client-side timeout: the client is still busy with the abandoned
|
|
790
|
+
* statement, so the ROLLBACK would queue behind it (see isClientSideTimeout).
|
|
791
|
+
* executeSQL discards that client instead.
|
|
792
|
+
*/
|
|
793
|
+
async rollbackQuietly(client, error) {
|
|
794
|
+
if (isClientSideTimeout(error)) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
await client.query("ROLLBACK");
|
|
799
|
+
} catch {
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Best-effort server-side cancel for a statement abandoned by the
|
|
804
|
+
* client-side timeout, sent over a separate pooled connection. Closing the
|
|
805
|
+
* abandoned client's socket is not enough: PostgreSQL only notices a
|
|
806
|
+
* vanished client when it next writes to it, so a statement that is
|
|
807
|
+
* sleeping or computing would run to completion.
|
|
808
|
+
*
|
|
809
|
+
* Bounded by its own short timeout (CANCEL_QUERY_TIMEOUT_MS), independent
|
|
810
|
+
* of the user's query_timeout. pool.query discards its client on error, so
|
|
811
|
+
* a cancel that itself times out does not leak a stuck client.
|
|
812
|
+
*/
|
|
813
|
+
async cancelBackend(backendPid) {
|
|
814
|
+
if (!this.pool || backendPid === null) return;
|
|
815
|
+
try {
|
|
816
|
+
const cancel = {
|
|
817
|
+
text: "SELECT pg_cancel_backend($1)",
|
|
818
|
+
values: [backendPid],
|
|
819
|
+
query_timeout: CANCEL_QUERY_TIMEOUT_MS
|
|
820
|
+
};
|
|
821
|
+
await this.pool.query(cancel);
|
|
822
|
+
} catch {
|
|
717
823
|
}
|
|
718
824
|
}
|
|
719
825
|
};
|