From b523c397164dd5174a52d655573e1640404424d7 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Fri, 15 May 2026 11:53:45 +0800 Subject: [PATCH] fix(connection): parse jdbc connection urls --- src/lib/connectionUrl.ts | 43 +++++++++++++++++++++++++++++++++++-- tests/connectionUrl.test.ts | 37 +++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/lib/connectionUrl.ts b/src/lib/connectionUrl.ts index 1b1eb2c838..2dabdadd47 100644 --- a/src/lib/connectionUrl.ts +++ b/src/lib/connectionUrl.ts @@ -69,11 +69,50 @@ function profileForScheme(scheme: string, preferredProfile?: string): Connection return SCHEME_PROFILES[scheme]; } +function parseJdbcSqlServerUrl(source: string): ParsedConnectionUrl | null { + const match = source.match(/^jdbc:sqlserver:\/\/([^;:/]+)(?::(\d+))?(?:;(.*))?$/i); + if (!match) return null; + + const profile = SCHEME_PROFILES.sqlserver; + const props = new Map(); + const urlParams: string[] = []; + for (const part of (match[3] || "").split(";")) { + if (!part) continue; + const [rawKey, ...rest] = part.split("="); + const key = rawKey.trim(); + const value = rest.join("="); + const normalizedKey = key.toLowerCase(); + if (normalizedKey === "databasename" || normalizedKey === "database" || normalizedKey === "user") { + props.set(normalizedKey, value); + } else if (normalizedKey === "password") { + props.set(normalizedKey, value); + } else { + urlParams.push(part); + } + } + + return { + dbType: profile.type, + driverProfile: profile.profile, + driverLabel: profile.label, + host: match[1], + port: match[2] ? Number(match[2]) : profile.defaultPort, + username: decodeUrlPart(props.get("user") || ""), + password: decodeUrlPart(props.get("password") || ""), + database: decodeUrlPart(props.get("databasename") || props.get("database") || "") || undefined, + urlParams: urlParams.join(";"), + ssl: false, + }; +} + export function parseConnectionUrl(value: string, preferredProfile?: string): ParsedConnectionUrl { - const source = value.trim(); - if (!source) { + const input = value.trim(); + if (!input) { throw new Error("Connection URL is empty"); } + const jdbcSqlServer = parseJdbcSqlServerUrl(input); + if (jdbcSqlServer) return jdbcSqlServer; + const source = input.replace(/^jdbc:/i, ""); let parsed: URL; try { diff --git a/tests/connectionUrl.test.ts b/tests/connectionUrl.test.ts index f4e1981b45..d4cf13f2b9 100644 --- a/tests/connectionUrl.test.ts +++ b/tests/connectionUrl.test.ts @@ -30,6 +30,43 @@ test("parses mysql URLs with encoded credentials", () => { assert.equal(parsed.urlParams, "charset=utf8mb4"); }); +test("parses JDBC URLs by using the inner database URL", () => { + const postgres = parseConnectionUrl("jdbc:postgresql://alice:secret@db.example.com:5433/app?sslmode=require"); + assert.equal(postgres.dbType, "postgres"); + assert.equal(postgres.driverProfile, "postgres"); + assert.equal(postgres.host, "db.example.com"); + assert.equal(postgres.port, 5433); + assert.equal(postgres.username, "alice"); + assert.equal(postgres.password, "secret"); + assert.equal(postgres.database, "app"); + assert.equal(postgres.urlParams, "sslmode=require"); + + const mysql = parseConnectionUrl("jdbc:mysql://root:p%40ss@127.0.0.1:3307/shop?charset=utf8mb4"); + assert.equal(mysql.dbType, "mysql"); + assert.equal(mysql.driverProfile, "mysql"); + assert.equal(mysql.host, "127.0.0.1"); + assert.equal(mysql.port, 3307); + assert.equal(mysql.username, "root"); + assert.equal(mysql.password, "p@ss"); + assert.equal(mysql.database, "shop"); + assert.equal(mysql.urlParams, "charset=utf8mb4"); +}); + +test("parses SQL Server JDBC URLs with semicolon properties", () => { + const parsed = parseConnectionUrl( + "jdbc:sqlserver://sql.example.com:1434;databaseName=erp;user=sa;password=s%40cret;encrypt=true", + ); + + assert.equal(parsed.dbType, "sqlserver"); + assert.equal(parsed.driverProfile, "sqlserver"); + assert.equal(parsed.host, "sql.example.com"); + assert.equal(parsed.port, 1434); + assert.equal(parsed.username, "sa"); + assert.equal(parsed.password, "s@cret"); + assert.equal(parsed.database, "erp"); + assert.equal(parsed.urlParams, "encrypt=true"); +}); + test("keeps MongoDB URLs as connection strings", () => { const source = "mongodb+srv://reader:secret@cluster.example.com/app?retryWrites=true"; const parsed = parseConnectionUrl(source);