@bytebase/dbhub 0.16.1 → 0.18.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.
@@ -296,7 +296,9 @@ var SSHTunnel = class {
296
296
  privateKey,
297
297
  targetConfig.passphrase,
298
298
  previousStream,
299
- `jump host ${i + 1}`
299
+ `jump host ${i + 1}`,
300
+ targetConfig.keepaliveInterval,
301
+ targetConfig.keepaliveCountMax
300
302
  );
301
303
  console.error(` \u2192 Forwarding through ${jumpHost.host}:${jumpHost.port} to ${nextHost.host}:${nextHost.port}`);
302
304
  forwardStream = await this.forwardTo(client, nextHost.host, nextHost.port);
@@ -322,7 +324,9 @@ var SSHTunnel = class {
322
324
  privateKey,
323
325
  targetConfig.passphrase,
324
326
  previousStream,
325
- jumpHosts.length > 0 ? "target host" : void 0
327
+ jumpHosts.length > 0 ? "target host" : void 0,
328
+ targetConfig.keepaliveInterval,
329
+ targetConfig.keepaliveCountMax
326
330
  );
327
331
  this.sshClients.push(finalClient);
328
332
  return finalClient;
@@ -330,7 +334,7 @@ var SSHTunnel = class {
330
334
  /**
331
335
  * Connect to a single SSH host.
332
336
  */
333
- connectToHost(hostInfo, password, privateKey, passphrase, sock, label) {
337
+ connectToHost(hostInfo, password, privateKey, passphrase, sock, label, keepaliveInterval, keepaliveCountMax) {
334
338
  return new Promise((resolve, reject) => {
335
339
  const client = new Client();
336
340
  const sshConfig = {
@@ -350,6 +354,17 @@ var SSHTunnel = class {
350
354
  if (sock) {
351
355
  sshConfig.sock = sock;
352
356
  }
357
+ if (keepaliveInterval !== void 0) {
358
+ if (Number.isNaN(keepaliveInterval) || keepaliveInterval < 0) {
359
+ const desc = label || `${hostInfo.host}:${hostInfo.port}`;
360
+ console.warn(
361
+ `Invalid SSH keepaliveInterval (${keepaliveInterval}) for ${desc}; keepalive configuration will be ignored.`
362
+ );
363
+ } else if (keepaliveInterval > 0) {
364
+ sshConfig.keepaliveInterval = keepaliveInterval * 1e3;
365
+ sshConfig.keepaliveCountMax = keepaliveCountMax ?? 3;
366
+ }
367
+ }
353
368
  const onError = (err) => {
354
369
  client.removeListener("ready", onReady);
355
370
  client.destroy();
@@ -1011,6 +1026,27 @@ function resolveSSHConfig() {
1011
1026
  config.proxyJump = process.env.SSH_PROXY_JUMP;
1012
1027
  sources.push("SSH_PROXY_JUMP from environment");
1013
1028
  }
1029
+ const parseNonNegativeInteger = (value, name) => {
1030
+ const parsed = Number(value);
1031
+ if (!Number.isInteger(parsed) || parsed < 0) {
1032
+ throw new Error(`Invalid value for ${name}: "${value}". Expected a non-negative integer.`);
1033
+ }
1034
+ return parsed;
1035
+ };
1036
+ if (args["ssh-keepalive-interval"]) {
1037
+ config.keepaliveInterval = parseNonNegativeInteger(args["ssh-keepalive-interval"], "ssh-keepalive-interval");
1038
+ sources.push("ssh-keepalive-interval from command line");
1039
+ } else if (process.env.SSH_KEEPALIVE_INTERVAL) {
1040
+ config.keepaliveInterval = parseNonNegativeInteger(process.env.SSH_KEEPALIVE_INTERVAL, "SSH_KEEPALIVE_INTERVAL");
1041
+ sources.push("SSH_KEEPALIVE_INTERVAL from environment");
1042
+ }
1043
+ if (args["ssh-keepalive-count-max"]) {
1044
+ config.keepaliveCountMax = parseNonNegativeInteger(args["ssh-keepalive-count-max"], "ssh-keepalive-count-max");
1045
+ sources.push("ssh-keepalive-count-max from command line");
1046
+ } else if (process.env.SSH_KEEPALIVE_COUNT_MAX) {
1047
+ config.keepaliveCountMax = parseNonNegativeInteger(process.env.SSH_KEEPALIVE_COUNT_MAX, "SSH_KEEPALIVE_COUNT_MAX");
1048
+ sources.push("SSH_KEEPALIVE_COUNT_MAX from environment");
1049
+ }
1014
1050
  if (!config.host || !config.username) {
1015
1051
  throw new Error("SSH tunnel configuration requires at least --ssh-host and --ssh-user");
1016
1052
  }
@@ -1090,6 +1126,8 @@ async function resolveSourceConfigs() {
1090
1126
  source.ssh_password = sshResult.config.password;
1091
1127
  source.ssh_key = sshResult.config.privateKey;
1092
1128
  source.ssh_passphrase = sshResult.config.passphrase;
1129
+ source.ssh_keepalive_interval = sshResult.config.keepaliveInterval;
1130
+ source.ssh_keepalive_count_max = sshResult.config.keepaliveCountMax;
1093
1131
  }
1094
1132
  if (dsnResult.isDemo) {
1095
1133
  const { getSqliteInMemorySetupSql } = await import("./demo-loader-PSMTLZ2T.js");
@@ -1270,6 +1308,31 @@ function validateSourceConfig(source, configPath) {
1270
1308
  );
1271
1309
  }
1272
1310
  }
1311
+ if (source.aws_iam_auth !== void 0 && typeof source.aws_iam_auth !== "boolean") {
1312
+ throw new Error(
1313
+ `Configuration file ${configPath}: source '${source.id}' has invalid aws_iam_auth. Must be a boolean (true or false).`
1314
+ );
1315
+ }
1316
+ if (source.aws_region !== void 0) {
1317
+ if (typeof source.aws_region !== "string" || source.aws_region.trim().length === 0) {
1318
+ throw new Error(
1319
+ `Configuration file ${configPath}: source '${source.id}' has invalid aws_region. Must be a non-empty string (e.g., "eu-west-1").`
1320
+ );
1321
+ }
1322
+ }
1323
+ if (source.aws_iam_auth === true) {
1324
+ const validIamTypes = ["postgres", "mysql", "mariadb"];
1325
+ if (!source.type || !validIamTypes.includes(source.type)) {
1326
+ throw new Error(
1327
+ `Configuration file ${configPath}: source '${source.id}' has aws_iam_auth enabled, but this is only supported for postgres, mysql, and mariadb sources.`
1328
+ );
1329
+ }
1330
+ if (!source.aws_region) {
1331
+ throw new Error(
1332
+ `Configuration file ${configPath}: source '${source.id}' has aws_iam_auth enabled but aws_region is not specified.`
1333
+ );
1334
+ }
1335
+ }
1273
1336
  if (source.connection_timeout !== void 0) {
1274
1337
  if (typeof source.connection_timeout !== "number" || source.connection_timeout <= 0) {
1275
1338
  throw new Error(
@@ -1339,6 +1402,18 @@ function validateSourceConfig(source, configPath) {
1339
1402
  );
1340
1403
  }
1341
1404
  }
1405
+ if (source.search_path !== void 0) {
1406
+ if (source.type !== "postgres") {
1407
+ throw new Error(
1408
+ `Configuration file ${configPath}: source '${source.id}' has 'search_path' but it is only supported for PostgreSQL sources.`
1409
+ );
1410
+ }
1411
+ if (typeof source.search_path !== "string" || source.search_path.trim().length === 0) {
1412
+ throw new Error(
1413
+ `Configuration file ${configPath}: source '${source.id}' has invalid search_path. Must be a non-empty string of comma-separated schema names (e.g., "myschema,public").`
1414
+ );
1415
+ }
1416
+ }
1342
1417
  if (source.readonly !== void 0) {
1343
1418
  throw new Error(
1344
1419
  `Configuration file ${configPath}: source '${source.id}' has 'readonly' field, but readonly must be configured per-tool, not per-source. Move 'readonly' to [[tools]] configuration instead.`
@@ -1408,7 +1483,8 @@ function buildDSNFromSource(source) {
1408
1483
  }
1409
1484
  return `sqlite:///${source.database}`;
1410
1485
  }
1411
- const passwordRequired = source.authentication !== "azure-active-directory-access-token";
1486
+ const isAwsIamPasswordless = source.aws_iam_auth === true && ["postgres", "mysql", "mariadb"].includes(source.type);
1487
+ const passwordRequired = source.authentication !== "azure-active-directory-access-token" && !isAwsIamPasswordless;
1412
1488
  if (!source.host || !source.user || !source.database) {
1413
1489
  throw new Error(
1414
1490
  `Source '${source.id}': missing required connection parameters. Required: type, host, user, database`
@@ -1416,7 +1492,7 @@ function buildDSNFromSource(source) {
1416
1492
  }
1417
1493
  if (passwordRequired && !source.password) {
1418
1494
  throw new Error(
1419
- `Source '${source.id}': password is required. (Password is optional only for azure-active-directory-access-token authentication)`
1495
+ `Source '${source.id}': password is required. (Password is optional for azure-active-directory-access-token authentication or when aws_iam_auth=true)`
1420
1496
  );
1421
1497
  }
1422
1498
  const port = source.port || getDefaultPortForType(source.type);
@@ -1448,8 +1524,21 @@ function buildDSNFromSource(source) {
1448
1524
  return dsn;
1449
1525
  }
1450
1526
 
1527
+ // src/utils/aws-rds-signer.ts
1528
+ import { Signer } from "@aws-sdk/rds-signer";
1529
+ async function generateRdsAuthToken(params) {
1530
+ const signer = new Signer({
1531
+ hostname: params.hostname,
1532
+ port: params.port,
1533
+ username: params.username,
1534
+ region: params.region
1535
+ });
1536
+ return signer.getAuthToken();
1537
+ }
1538
+
1451
1539
  // src/connectors/manager.ts
1452
1540
  var managerInstance = null;
1541
+ var AWS_IAM_TOKEN_REFRESH_MS = 14 * 60 * 1e3;
1453
1542
  var ConnectorManager = class {
1454
1543
  // Prevent race conditions
1455
1544
  constructor() {
@@ -1460,6 +1549,8 @@ var ConnectorManager = class {
1460
1549
  // Store original source configs
1461
1550
  this.sourceIds = [];
1462
1551
  // Ordered list of source IDs (first is default)
1552
+ this.iamRefreshTimers = /* @__PURE__ */ new Map();
1553
+ this.isDisconnecting = false;
1463
1554
  // Lazy connection support
1464
1555
  this.lazySources = /* @__PURE__ */ new Map();
1465
1556
  // Sources pending lazy connection
@@ -1549,7 +1640,7 @@ var ConnectorManager = class {
1549
1640
  */
1550
1641
  async connectSource(source) {
1551
1642
  const sourceId = source.id;
1552
- const dsn = buildDSNFromSource(source);
1643
+ const dsn = await this.buildConnectionDSN(source);
1553
1644
  console.error(` - ${sourceId}: ${redactDSN(dsn)}`);
1554
1645
  let actualDSN = dsn;
1555
1646
  if (source.ssh_host) {
@@ -1565,7 +1656,9 @@ var ConnectorManager = class {
1565
1656
  password: source.ssh_password,
1566
1657
  privateKey: source.ssh_key,
1567
1658
  passphrase: source.ssh_passphrase,
1568
- proxyJump: source.ssh_proxy_jump
1659
+ proxyJump: source.ssh_proxy_jump,
1660
+ keepaliveInterval: source.ssh_keepalive_interval,
1661
+ keepaliveCountMax: source.ssh_keepalive_count_max
1569
1662
  };
1570
1663
  if (!sshConfig.password && !sshConfig.privateKey) {
1571
1664
  throw new Error(
@@ -1606,17 +1699,26 @@ var ConnectorManager = class {
1606
1699
  if (source.readonly !== void 0) {
1607
1700
  config.readonly = source.readonly;
1608
1701
  }
1702
+ if (source.search_path) {
1703
+ config.searchPath = source.search_path;
1704
+ }
1609
1705
  await connector.connect(actualDSN, source.init_script, config);
1610
1706
  this.connectors.set(sourceId, connector);
1611
1707
  if (!this.sourceIds.includes(sourceId)) {
1612
1708
  this.sourceIds.push(sourceId);
1613
1709
  }
1614
1710
  this.sourceConfigs.set(sourceId, source);
1711
+ this.scheduleIamRefresh(source);
1615
1712
  }
1616
1713
  /**
1617
1714
  * Close all database connections
1618
1715
  */
1619
1716
  async disconnect() {
1717
+ this.isDisconnecting = true;
1718
+ for (const timer of this.iamRefreshTimers.values()) {
1719
+ clearTimeout(timer);
1720
+ }
1721
+ this.iamRefreshTimers.clear();
1620
1722
  for (const [sourceId, connector] of this.connectors.entries()) {
1621
1723
  try {
1622
1724
  await connector.disconnect();
@@ -1638,6 +1740,7 @@ var ConnectorManager = class {
1638
1740
  this.lazySources.clear();
1639
1741
  this.pendingConnections.clear();
1640
1742
  this.sourceIds = [];
1743
+ this.isDisconnecting = false;
1641
1744
  }
1642
1745
  /**
1643
1746
  * Get a connector by source ID
@@ -1738,63 +1841,317 @@ var ConnectorManager = class {
1738
1841
  }
1739
1842
  return getDefaultPortForType(type) ?? 0;
1740
1843
  }
1844
+ scheduleIamRefresh(source) {
1845
+ if (this.isDisconnecting) {
1846
+ return;
1847
+ }
1848
+ const sourceId = source.id;
1849
+ const existingTimer = this.iamRefreshTimers.get(sourceId);
1850
+ if (existingTimer) {
1851
+ clearTimeout(existingTimer);
1852
+ this.iamRefreshTimers.delete(sourceId);
1853
+ }
1854
+ if (!source.aws_iam_auth) {
1855
+ return;
1856
+ }
1857
+ const timer = setTimeout(async () => {
1858
+ if (this.isDisconnecting) {
1859
+ return;
1860
+ }
1861
+ try {
1862
+ await this.refreshIamSourceConnection(source);
1863
+ } catch (error) {
1864
+ console.error(
1865
+ `Error refreshing AWS IAM auth token for source '${sourceId}':`,
1866
+ error
1867
+ );
1868
+ } finally {
1869
+ if (!this.isDisconnecting && this.sourceConfigs.has(sourceId)) {
1870
+ this.scheduleIamRefresh(source);
1871
+ }
1872
+ }
1873
+ }, AWS_IAM_TOKEN_REFRESH_MS);
1874
+ timer.unref?.();
1875
+ this.iamRefreshTimers.set(sourceId, timer);
1876
+ }
1877
+ async refreshIamSourceConnection(source) {
1878
+ const sourceId = source.id;
1879
+ if (this.isDisconnecting || !source.aws_iam_auth || !this.connectors.has(sourceId)) {
1880
+ return;
1881
+ }
1882
+ console.error(`Refreshing AWS IAM auth connection for source '${sourceId}'...`);
1883
+ const existingConnector = this.connectors.get(sourceId);
1884
+ if (existingConnector) {
1885
+ await existingConnector.disconnect();
1886
+ this.connectors.delete(sourceId);
1887
+ }
1888
+ const existingTunnel = this.sshTunnels.get(sourceId);
1889
+ if (existingTunnel) {
1890
+ await existingTunnel.close();
1891
+ this.sshTunnels.delete(sourceId);
1892
+ }
1893
+ if (this.isDisconnecting) {
1894
+ return;
1895
+ }
1896
+ await this.connectSource(source);
1897
+ }
1898
+ /**
1899
+ * Build a connection DSN, optionally replacing password with
1900
+ * an AWS RDS IAM auth token when aws_iam_auth is enabled.
1901
+ */
1902
+ async buildConnectionDSN(source) {
1903
+ const dsn = buildDSNFromSource(source);
1904
+ if (!source.aws_iam_auth) {
1905
+ return dsn;
1906
+ }
1907
+ const supportedIamTypes = ["postgres", "mysql", "mariadb"];
1908
+ if (!source.type || !supportedIamTypes.includes(source.type)) {
1909
+ throw new Error(
1910
+ `Source '${source.id}': aws_iam_auth is only supported for postgres, mysql, and mariadb`
1911
+ );
1912
+ }
1913
+ if (!source.aws_region) {
1914
+ throw new Error(
1915
+ `Source '${source.id}': aws_region is required when aws_iam_auth is enabled`
1916
+ );
1917
+ }
1918
+ const parsed = new SafeURL(dsn);
1919
+ const hostname = parsed.hostname;
1920
+ const username = source.user || parsed.username;
1921
+ const defaultPort = getDefaultPortForType(source.type);
1922
+ const port = parsed.port ? parseInt(parsed.port) : defaultPort;
1923
+ if (!hostname || !username || !port) {
1924
+ throw new Error(
1925
+ `Source '${source.id}': unable to resolve host, username, or port for AWS IAM authentication`
1926
+ );
1927
+ }
1928
+ const token = await generateRdsAuthToken({
1929
+ hostname,
1930
+ port,
1931
+ username,
1932
+ region: source.aws_region
1933
+ });
1934
+ const queryParams = new Map(parsed.searchParams);
1935
+ queryParams.set("sslmode", "require");
1936
+ const protocol = parsed.protocol.endsWith(":") ? parsed.protocol.slice(0, -1) : parsed.protocol;
1937
+ const encodedUser = encodeURIComponent(username);
1938
+ const encodedToken = encodeURIComponent(token);
1939
+ const path3 = parsed.pathname || "/";
1940
+ const query = Array.from(queryParams.entries()).map(
1941
+ ([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
1942
+ ).join("&");
1943
+ return `${protocol}://${encodedUser}:${encodedToken}@${hostname}:${port}${path3}${query ? `?${query}` : ""}`;
1944
+ }
1741
1945
  };
1742
1946
 
1743
1947
  // src/utils/sql-parser.ts
1744
- function stripCommentsAndStrings(sql) {
1745
- let result = "";
1948
+ var TokenType = { Plain: 0, Comment: 1, QuotedBlock: 2 };
1949
+ function plainToken(i) {
1950
+ return { type: TokenType.Plain, end: i + 1 };
1951
+ }
1952
+ function scanSingleLineComment(sql, i) {
1953
+ if (sql[i] !== "-" || sql[i + 1] !== "-") {
1954
+ return null;
1955
+ }
1956
+ let j = i;
1957
+ while (j < sql.length && sql[j] !== "\n") {
1958
+ j++;
1959
+ }
1960
+ return { type: TokenType.Comment, end: j };
1961
+ }
1962
+ function scanMultiLineComment(sql, i) {
1963
+ if (sql[i] !== "/" || sql[i + 1] !== "*") {
1964
+ return null;
1965
+ }
1966
+ let j = i + 2;
1967
+ while (j < sql.length && !(sql[j] === "*" && sql[j + 1] === "/")) {
1968
+ j++;
1969
+ }
1970
+ if (j < sql.length) {
1971
+ j += 2;
1972
+ }
1973
+ return { type: TokenType.Comment, end: j };
1974
+ }
1975
+ function scanNestedMultiLineComment(sql, i) {
1976
+ if (sql[i] !== "/" || sql[i + 1] !== "*") {
1977
+ return null;
1978
+ }
1979
+ let j = i + 2;
1980
+ let depth = 1;
1981
+ while (j < sql.length && depth > 0) {
1982
+ if (sql[j] === "/" && sql[j + 1] === "*") {
1983
+ depth++;
1984
+ j += 2;
1985
+ } else if (sql[j] === "*" && sql[j + 1] === "/") {
1986
+ depth--;
1987
+ j += 2;
1988
+ } else {
1989
+ j++;
1990
+ }
1991
+ }
1992
+ return { type: TokenType.Comment, end: j };
1993
+ }
1994
+ function scanSingleQuotedString(sql, i) {
1995
+ if (sql[i] !== "'") {
1996
+ return null;
1997
+ }
1998
+ let j = i + 1;
1999
+ while (j < sql.length) {
2000
+ if (sql[j] === "'" && sql[j + 1] === "'") {
2001
+ j += 2;
2002
+ } else if (sql[j] === "'") {
2003
+ j++;
2004
+ break;
2005
+ } else {
2006
+ j++;
2007
+ }
2008
+ }
2009
+ return { type: TokenType.QuotedBlock, end: j };
2010
+ }
2011
+ function scanDoubleQuotedString(sql, i) {
2012
+ if (sql[i] !== '"') {
2013
+ return null;
2014
+ }
2015
+ let j = i + 1;
2016
+ while (j < sql.length) {
2017
+ if (sql[j] === '"' && sql[j + 1] === '"') {
2018
+ j += 2;
2019
+ } else if (sql[j] === '"') {
2020
+ j++;
2021
+ break;
2022
+ } else {
2023
+ j++;
2024
+ }
2025
+ }
2026
+ return { type: TokenType.QuotedBlock, end: j };
2027
+ }
2028
+ var dollarQuoteOpenRegex = /^\$([a-zA-Z_]\w*)?\$/;
2029
+ function scanDollarQuotedBlock(sql, i) {
2030
+ if (sql[i] !== "$") {
2031
+ return null;
2032
+ }
2033
+ const next = sql[i + 1];
2034
+ if (next >= "0" && next <= "9") {
2035
+ return null;
2036
+ }
2037
+ const remaining = sql.substring(i);
2038
+ const m = dollarQuoteOpenRegex.exec(remaining);
2039
+ if (!m) {
2040
+ return null;
2041
+ }
2042
+ const tag = m[0];
2043
+ const bodyStart = i + tag.length;
2044
+ const closeIdx = sql.indexOf(tag, bodyStart);
2045
+ const end = closeIdx !== -1 ? closeIdx + tag.length : sql.length;
2046
+ return { type: TokenType.QuotedBlock, end };
2047
+ }
2048
+ function scanBacktickQuotedIdentifier(sql, i) {
2049
+ if (sql[i] !== "`") {
2050
+ return null;
2051
+ }
2052
+ let j = i + 1;
2053
+ while (j < sql.length) {
2054
+ if (sql[j] === "`" && sql[j + 1] === "`") {
2055
+ j += 2;
2056
+ } else if (sql[j] === "`") {
2057
+ j++;
2058
+ break;
2059
+ } else {
2060
+ j++;
2061
+ }
2062
+ }
2063
+ return { type: TokenType.QuotedBlock, end: j };
2064
+ }
2065
+ function scanBracketQuotedIdentifier(sql, i) {
2066
+ if (sql[i] !== "[") {
2067
+ return null;
2068
+ }
2069
+ let j = i + 1;
2070
+ while (j < sql.length) {
2071
+ if (sql[j] === "]" && sql[j + 1] === "]") {
2072
+ j += 2;
2073
+ } else if (sql[j] === "]") {
2074
+ j++;
2075
+ break;
2076
+ } else {
2077
+ j++;
2078
+ }
2079
+ }
2080
+ return { type: TokenType.QuotedBlock, end: j };
2081
+ }
2082
+ function scanTokenAnsi(sql, i) {
2083
+ return scanSingleLineComment(sql, i) ?? scanMultiLineComment(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? plainToken(i);
2084
+ }
2085
+ function scanTokenPostgres(sql, i) {
2086
+ return scanSingleLineComment(sql, i) ?? scanNestedMultiLineComment(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? scanDollarQuotedBlock(sql, i) ?? plainToken(i);
2087
+ }
2088
+ function scanTokenMySQL(sql, i) {
2089
+ return scanSingleLineComment(sql, i) ?? scanMultiLineComment(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? scanBacktickQuotedIdentifier(sql, i) ?? plainToken(i);
2090
+ }
2091
+ function scanTokenSQLite(sql, i) {
2092
+ return scanSingleLineComment(sql, i) ?? scanMultiLineComment(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? scanBacktickQuotedIdentifier(sql, i) ?? scanBracketQuotedIdentifier(sql, i) ?? plainToken(i);
2093
+ }
2094
+ function scanTokenSQLServer(sql, i) {
2095
+ return scanSingleLineComment(sql, i) ?? scanMultiLineComment(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? scanBracketQuotedIdentifier(sql, i) ?? plainToken(i);
2096
+ }
2097
+ var dialectScanners = {
2098
+ postgres: scanTokenPostgres,
2099
+ mysql: scanTokenMySQL,
2100
+ mariadb: scanTokenMySQL,
2101
+ sqlite: scanTokenSQLite,
2102
+ sqlserver: scanTokenSQLServer
2103
+ };
2104
+ function getScanner(dialect) {
2105
+ return dialect ? dialectScanners[dialect] ?? scanTokenAnsi : scanTokenAnsi;
2106
+ }
2107
+ function stripCommentsAndStrings(sql, dialect) {
2108
+ const scanToken = getScanner(dialect);
2109
+ const parts = [];
2110
+ let plainStart = -1;
1746
2111
  let i = 0;
1747
2112
  while (i < sql.length) {
1748
- if (sql[i] === "-" && sql[i + 1] === "-") {
1749
- while (i < sql.length && sql[i] !== "\n") {
1750
- i++;
2113
+ const token = scanToken(sql, i);
2114
+ if (token.type === TokenType.Plain) {
2115
+ if (plainStart === -1) {
2116
+ plainStart = i;
1751
2117
  }
1752
- result += " ";
1753
- continue;
1754
- }
1755
- if (sql[i] === "/" && sql[i + 1] === "*") {
1756
- i += 2;
1757
- while (i < sql.length && !(sql[i] === "*" && sql[i + 1] === "/")) {
1758
- i++;
2118
+ } else {
2119
+ if (plainStart !== -1) {
2120
+ parts.push(sql.substring(plainStart, i));
2121
+ plainStart = -1;
1759
2122
  }
1760
- i += 2;
1761
- result += " ";
1762
- continue;
2123
+ parts.push(" ");
1763
2124
  }
1764
- if (sql[i] === "'") {
1765
- i++;
1766
- while (i < sql.length) {
1767
- if (sql[i] === "'" && sql[i + 1] === "'") {
1768
- i += 2;
1769
- } else if (sql[i] === "'") {
1770
- i++;
1771
- break;
1772
- } else {
1773
- i++;
1774
- }
2125
+ i = token.end;
2126
+ }
2127
+ if (plainStart !== -1) {
2128
+ parts.push(sql.substring(plainStart));
2129
+ }
2130
+ return parts.join("");
2131
+ }
2132
+ function splitSQLStatements(sql, dialect) {
2133
+ const scanToken = getScanner(dialect);
2134
+ const statements = [];
2135
+ let stmtStart = 0;
2136
+ let i = 0;
2137
+ while (i < sql.length) {
2138
+ if (sql[i] === ";") {
2139
+ const trimmed2 = sql.substring(stmtStart, i).trim();
2140
+ if (trimmed2.length > 0) {
2141
+ statements.push(trimmed2);
1775
2142
  }
1776
- result += " ";
1777
- continue;
1778
- }
1779
- if (sql[i] === '"') {
2143
+ stmtStart = i + 1;
1780
2144
  i++;
1781
- while (i < sql.length) {
1782
- if (sql[i] === '"' && sql[i + 1] === '"') {
1783
- i += 2;
1784
- } else if (sql[i] === '"') {
1785
- i++;
1786
- break;
1787
- } else {
1788
- i++;
1789
- }
1790
- }
1791
- result += " ";
1792
2145
  continue;
1793
2146
  }
1794
- result += sql[i];
1795
- i++;
2147
+ const token = scanToken(sql, i);
2148
+ i = token.end;
2149
+ }
2150
+ const trimmed = sql.substring(stmtStart).trim();
2151
+ if (trimmed.length > 0) {
2152
+ statements.push(trimmed);
1796
2153
  }
1797
- return result;
2154
+ return statements;
1798
2155
  }
1799
2156
 
1800
2157
  // src/utils/parameter-mapper.ts
@@ -2130,12 +2487,15 @@ export {
2130
2487
  getDatabaseTypeFromDSN,
2131
2488
  getDefaultPortForType,
2132
2489
  stripCommentsAndStrings,
2490
+ splitSQLStatements,
2133
2491
  isDemoMode,
2134
2492
  resolveTransport,
2135
2493
  resolvePort,
2136
2494
  resolveSourceConfigs,
2137
2495
  BUILTIN_TOOL_EXECUTE_SQL,
2138
2496
  BUILTIN_TOOL_SEARCH_OBJECTS,
2497
+ loadTomlConfig,
2498
+ resolveTomlConfigPath,
2139
2499
  ConnectorManager,
2140
2500
  mapArgumentsToArray,
2141
2501
  ToolRegistry,