From 8d3714d746a9a5f55d56e105514939c563a4d762 Mon Sep 17 00:00:00 2001 From: Omair Afzal Date: Tue, 10 Feb 2026 05:45:03 +0500 Subject: [PATCH 01/10] fix(adapter-mssql): respect curly brace escaping in connection strings Values wrapped in curly braces (e.g., {pass;word}) are now correctly treated as literals where semicolons and equals signs do not act as delimiters. This matches the MSSQL/JDBC connection string spec and restores v6 behavior. Fixes #28932 --- .../src/connection-string.test.ts | 66 +++++++++++++++++ .../adapter-mssql/src/connection-string.ts | 70 +++++++++++++++++-- 2 files changed, 132 insertions(+), 4 deletions(-) diff --git a/packages/adapter-mssql/src/connection-string.test.ts b/packages/adapter-mssql/src/connection-string.test.ts index 0e3952bfb1aa..34ecfd6b7c7d 100644 --- a/packages/adapter-mssql/src/connection-string.test.ts +++ b/packages/adapter-mssql/src/connection-string.test.ts @@ -190,6 +190,72 @@ describe('parseConnectionString', () => { }) }) + describe('escaped values with curly braces', () => { + it('should parse password with curly braces correctly', () => { + const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password={mypassword}' + const config = parseConnectionString(connectionString) + + expect(config.password).toBe('mypassword') + }) + + it('should handle password with semicolon when escaped', () => { + const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password={pass;word}' + const config = parseConnectionString(connectionString) + + expect(config.password).toBe('pass;word') + }) + + it('should handle password with equals sign when escaped', () => { + const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password={pass=word}' + const config = parseConnectionString(connectionString) + + expect(config.password).toBe('pass=word') + }) + + it('should handle password with both semicolon and equals when escaped', () => { + const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password={pass;word=123}' + const config = parseConnectionString(connectionString) + + expect(config.password).toBe('pass;word=123') + }) + + it('should handle escaped database name', () => { + const connectionString = 'sqlserver://localhost;database={test;db};user=sa;password=mypassword' + const config = parseConnectionString(connectionString) + + expect(config.database).toBe('test;db') + }) + + it('should handle escaped user name', () => { + const connectionString = 'sqlserver://localhost;database=testdb;user={domain\\user;name};password=mypassword' + const config = parseConnectionString(connectionString) + + expect(config.user).toBe('domain\\user;name') + }) + + it('should handle multiple escaped values', () => { + const connectionString = 'sqlserver://localhost;database={test;db};user={my;user};password={my;pass=word}' + const config = parseConnectionString(connectionString) + + expect(config.database).toBe('test;db') + expect(config.user).toBe('my;user') + expect(config.password).toBe('my;pass=word') + }) + + it('should handle complex password with multiple special characters', () => { + const connectionString = + 'sqlserver://localhost:1433;database=testdb;user=sa;password={P@ss;w=rd!123};encrypt=true' + const config = parseConnectionString(connectionString) + + expect(config.server).toBe('localhost') + expect(config.port).toBe(1433) + expect(config.database).toBe('testdb') + expect(config.user).toBe('sa') + expect(config.password).toBe('P@ss;w=rd!123') + expect(config.options?.encrypt).toBe(true) + }) + }) + describe('isolation level parameter', () => { it('should parse isolationLevel parameter correctly', () => { const testCases = [ diff --git a/packages/adapter-mssql/src/connection-string.ts b/packages/adapter-mssql/src/connection-string.ts index 1ed9424e3acd..0b0fc07aa450 100644 --- a/packages/adapter-mssql/src/connection-string.ts +++ b/packages/adapter-mssql/src/connection-string.ts @@ -44,6 +44,61 @@ export function extractSchemaFromConnectionString(connectionString: string): str return undefined } +/** + * Splits a connection string by semicolons while respecting curly brace escaping. + * Values wrapped in curly braces like {value} are treated as literals where + * semicolons and equals signs are not treated as delimiters. + * @param str The string to split + * @returns Array of parts split by semicolon (outside of curly braces) + */ +function splitRespectingBraces(str: string): string[] { + const parts: string[] = [] + let current = '' + let braceDepth = 0 + + for (let i = 0; i < str.length; i++) { + const char = str[i] + + if (char === '{') { + braceDepth++ + current += char + } else if (char === '}') { + braceDepth-- + current += char + } else if (char === ';' && braceDepth === 0) { + // Only split on semicolon if we're not inside curly braces + parts.push(current) + current = '' + } else { + current += char + } + } + + // Add the last part + if (current) { + parts.push(current) + } + + return parts +} + +/** + * Extracts the value from a parameter, removing curly braces if present. + * If the value is wrapped in curly braces like {value}, returns value. + * Otherwise returns the value as-is. + * @param value The value to process + * @returns The unescaped value + */ +function unescapeValue(value: string): string { + const trimmed = value.trim() + // Check if value is wrapped in curly braces + if (trimmed.startsWith('{') && trimmed.endsWith('}') && trimmed.length > 2) { + // Remove the outer braces + return trimmed.slice(1, -1) + } + return trimmed +} + /** * Parses a Prisma SQL Server connection string into a sql.config object. * As per https://www.prisma.io/docs/orm/overview/databases/sql-server#connection-details. @@ -53,8 +108,9 @@ export function extractSchemaFromConnectionString(connectionString: string): str export function parseConnectionString(connectionString: string): sql.config { const withoutProtocol = connectionString.replace(/^sqlserver:\/\//, '') - // Split by semicolon to get key-value pairs - const [hostPart, ...paramParts] = withoutProtocol.split(';') + // Split by semicolon while respecting curly brace escaping + const parts = splitRespectingBraces(withoutProtocol) + const [hostPart, ...paramParts] = parts const config: sql.config = { server: '', @@ -63,6 +119,10 @@ export function parseConnectionString(connectionString: string): sql.config { } // Parse the first part which contains host and port + if (!hostPart || hostPart.trim() === '') { + throw new Error('Server host is required in connection string') + } + const [host, portStr] = hostPart.split(':') config.server = host.trim() @@ -78,14 +138,16 @@ export function parseConnectionString(connectionString: string): sql.config { const parameters: Record = {} for (const part of paramParts) { - const [key, value] = part.split('=', 2) + const [key, ...valueParts] = part.split('=') if (!key) continue const trimmedKey = key.trim() if (trimmedKey in parameters) { throw new Error(`Duplication configuration parameter: ${trimmedKey}`) } - parameters[trimmedKey] = value.trim() + // Join value parts back together (in case there were = signs in the value) + const value = valueParts.join('=') + parameters[trimmedKey] = unescapeValue(value) if (!handledParameters.includes(trimmedKey)) { debug(`Unknown connection string parameter: ${trimmedKey}`) } From de621d19095e47884c4c0119fc8eef8621786407 Mon Sep 17 00:00:00 2001 From: Omair Afzal Date: Tue, 10 Feb 2026 05:53:37 +0500 Subject: [PATCH 02/10] fix(adapter-mssql): apply brace-aware parsing to extractSchemaFromConnectionString --- .../src/connection-string.test.ts | 37 +++++++++++++++++++ .../adapter-mssql/src/connection-string.ts | 6 +-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/packages/adapter-mssql/src/connection-string.test.ts b/packages/adapter-mssql/src/connection-string.test.ts index 34ecfd6b7c7d..2616e2638b89 100644 --- a/packages/adapter-mssql/src/connection-string.test.ts +++ b/packages/adapter-mssql/src/connection-string.test.ts @@ -594,4 +594,41 @@ describe('extractSchemaFromConnectionString', () => { expect(schema).toBe('') }) + + describe('escaped schema values with curly braces', () => { + it('should extract schema with curly braces correctly', () => { + const connectionString = 'sqlserver://localhost;database=testdb;schema={custom}' + const schema = extractSchemaFromConnectionString(connectionString) + + expect(schema).toBe('custom') + }) + + it('should handle schema with semicolon when escaped', () => { + const connectionString = 'sqlserver://localhost;database=testdb;schema={custom;schema}' + const schema = extractSchemaFromConnectionString(connectionString) + + expect(schema).toBe('custom;schema') + }) + + it('should handle schema with equals sign when escaped', () => { + const connectionString = 'sqlserver://localhost;database=testdb;schema={custom=schema}' + const schema = extractSchemaFromConnectionString(connectionString) + + expect(schema).toBe('custom=schema') + }) + + it('should handle schema with both semicolon and equals when escaped', () => { + const connectionString = 'sqlserver://localhost;database=testdb;schema={custom;schema=value}' + const schema = extractSchemaFromConnectionString(connectionString) + + expect(schema).toBe('custom;schema=value') + }) + + it('should handle schema with escaped value and other parameters', () => { + const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password={pass;word};schema={my;schema}' + const schema = extractSchemaFromConnectionString(connectionString) + + expect(schema).toBe('my;schema') + }) + }) }) diff --git a/packages/adapter-mssql/src/connection-string.ts b/packages/adapter-mssql/src/connection-string.ts index 0b0fc07aa450..7e0fd85038f3 100644 --- a/packages/adapter-mssql/src/connection-string.ts +++ b/packages/adapter-mssql/src/connection-string.ts @@ -33,12 +33,12 @@ const debug = Debug('prisma:driver-adapter:mssql:connection-string') */ export function extractSchemaFromConnectionString(connectionString: string): string | undefined { const withoutProtocol = connectionString.replace(/^sqlserver:\/\//, '') - const parts = withoutProtocol.split(';') + const parts = splitRespectingBraces(withoutProtocol) for (const part of parts) { - const [key, value] = part.split('=', 2) + const [key, ...valueParts] = part.split('=') if (key?.trim() === 'schema') { - return value?.trim() + return unescapeValue(valueParts.join('=')) } } return undefined From 110a080135d09b0ed5db794209aac63b1c40d3d4 Mon Sep 17 00:00:00 2001 From: Omair Afzal Date: Tue, 10 Feb 2026 05:55:07 +0500 Subject: [PATCH 03/10] fix(adapter-mssql): clamp braceDepth to prevent negative underflow on malformed input --- packages/adapter-mssql/src/connection-string.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/adapter-mssql/src/connection-string.ts b/packages/adapter-mssql/src/connection-string.ts index 7e0fd85038f3..52579e4f5450 100644 --- a/packages/adapter-mssql/src/connection-string.ts +++ b/packages/adapter-mssql/src/connection-string.ts @@ -63,7 +63,9 @@ function splitRespectingBraces(str: string): string[] { braceDepth++ current += char } else if (char === '}') { - braceDepth-- + if (braceDepth > 0) { + braceDepth-- + } current += char } else if (char === ';' && braceDepth === 0) { // Only split on semicolon if we're not inside curly braces From ecfe1dfbc4a450adad603be55b295a9e2a977180 Mon Sep 17 00:00:00 2001 From: Omair Afzal Date: Tue, 10 Feb 2026 06:05:55 +0500 Subject: [PATCH 04/10] fix(adapter-mssql): handle edge cases in brace escaping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix unescapeValue to handle empty braces {} → empty string - Throw error on unclosed { brace (prevents silent parameter loss) - Remove redundant inline comments - Add tests for empty braces, mid-value braces, and malformed input Addresses CodeRabbit review feedback (comments 2785273792, 2785273798, 2785273800, 2785273803) --- .../src/connection-string.test.ts | 43 +++++++++++++++++++ .../adapter-mssql/src/connection-string.ts | 10 ++--- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/packages/adapter-mssql/src/connection-string.test.ts b/packages/adapter-mssql/src/connection-string.test.ts index 2616e2638b89..8756d9e593ef 100644 --- a/packages/adapter-mssql/src/connection-string.test.ts +++ b/packages/adapter-mssql/src/connection-string.test.ts @@ -254,6 +254,35 @@ describe('parseConnectionString', () => { expect(config.password).toBe('P@ss;w=rd!123') expect(config.options?.encrypt).toBe(true) }) + + it('should handle empty braces as empty string', () => { + const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password={}' + const config = parseConnectionString(connectionString) + + expect(config.password).toBe('') + }) + + it('should not unescape braces that are not at value boundaries', () => { + const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password=my{pass}word' + const config = parseConnectionString(connectionString) + + expect(config.password).toBe('my{pass}word') + }) + + it('should throw error on unclosed opening brace', () => { + const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password={unclosed;value' + + expect(() => { + parseConnectionString(connectionString) + }).toThrow("Malformed connection string: unclosed '{' brace") + }) + + it('should not throw on unmatched closing brace (clamped)', () => { + const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password=pass}word' + const config = parseConnectionString(connectionString) + + expect(config.password).toBe('pass}word') + }) }) describe('isolation level parameter', () => { @@ -630,5 +659,19 @@ describe('extractSchemaFromConnectionString', () => { expect(schema).toBe('my;schema') }) + + it('should handle empty braces in schema as empty string', () => { + const connectionString = 'sqlserver://localhost;database=testdb;schema={}' + const schema = extractSchemaFromConnectionString(connectionString) + + expect(schema).toBe('') + }) + + it('should not unescape braces that are not at schema value boundaries', () => { + const connectionString = 'sqlserver://localhost;database=testdb;schema=my{sche}ma' + const schema = extractSchemaFromConnectionString(connectionString) + + expect(schema).toBe('my{sche}ma') + }) }) }) diff --git a/packages/adapter-mssql/src/connection-string.ts b/packages/adapter-mssql/src/connection-string.ts index 52579e4f5450..f560d23de6ed 100644 --- a/packages/adapter-mssql/src/connection-string.ts +++ b/packages/adapter-mssql/src/connection-string.ts @@ -68,7 +68,6 @@ function splitRespectingBraces(str: string): string[] { } current += char } else if (char === ';' && braceDepth === 0) { - // Only split on semicolon if we're not inside curly braces parts.push(current) current = '' } else { @@ -76,11 +75,14 @@ function splitRespectingBraces(str: string): string[] { } } - // Add the last part if (current) { parts.push(current) } + if (braceDepth !== 0) { + throw new Error(`Malformed connection string: unclosed '{' brace (braceDepth=${braceDepth})`) + } + return parts } @@ -93,9 +95,7 @@ function splitRespectingBraces(str: string): string[] { */ function unescapeValue(value: string): string { const trimmed = value.trim() - // Check if value is wrapped in curly braces - if (trimmed.startsWith('{') && trimmed.endsWith('}') && trimmed.length > 2) { - // Remove the outer braces + if (trimmed.startsWith('{') && trimmed.endsWith('}') && trimmed.length >= 2) { return trimmed.slice(1, -1) } return trimmed From 3bc0493f2711b6275c0267c93a54bd6df6ec8232 Mon Sep 17 00:00:00 2001 From: Omair Afzal Date: Tue, 10 Feb 2026 06:13:20 +0500 Subject: [PATCH 05/10] refactor: remove redundant inline comments per code review --- packages/adapter-mssql/src/connection-string.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/adapter-mssql/src/connection-string.ts b/packages/adapter-mssql/src/connection-string.ts index f560d23de6ed..347b02ed2d67 100644 --- a/packages/adapter-mssql/src/connection-string.ts +++ b/packages/adapter-mssql/src/connection-string.ts @@ -110,7 +110,6 @@ function unescapeValue(value: string): string { export function parseConnectionString(connectionString: string): sql.config { const withoutProtocol = connectionString.replace(/^sqlserver:\/\//, '') - // Split by semicolon while respecting curly brace escaping const parts = splitRespectingBraces(withoutProtocol) const [hostPart, ...paramParts] = parts @@ -147,7 +146,6 @@ export function parseConnectionString(connectionString: string): sql.config { if (trimmedKey in parameters) { throw new Error(`Duplication configuration parameter: ${trimmedKey}`) } - // Join value parts back together (in case there were = signs in the value) const value = valueParts.join('=') parameters[trimmedKey] = unescapeValue(value) if (!handledParameters.includes(trimmedKey)) { From 3a9592d59accd21df6dc79d48629f94223c2545c Mon Sep 17 00:00:00 2001 From: Omair Afzal Date: Tue, 10 Feb 2026 18:13:37 +0500 Subject: [PATCH 06/10] fix: handle curly brace escaping only when { is first character MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes edge case where tedious treats { as a quote character ONLY when it's the FIRST character of a value. For example: - user=u{s → user is 'u{s' (no escaping, { not first) - password=}password → password is '}password' (no opening brace) - user={u;s} → user is 'u;s' (escaping, { is first) This matches the behavior of tedious and @tediousjs/connection-string. Addresses feedback from jacek-prisma: https://github.com/prisma/prisma/pull/29158#discussion_r1943210520 --- .../src/connection-string.test.ts | 43 +++++++++++++++++++ .../adapter-mssql/src/connection-string.ts | 23 +++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/packages/adapter-mssql/src/connection-string.test.ts b/packages/adapter-mssql/src/connection-string.test.ts index 8756d9e593ef..3ed187ca74e8 100644 --- a/packages/adapter-mssql/src/connection-string.test.ts +++ b/packages/adapter-mssql/src/connection-string.test.ts @@ -463,6 +463,49 @@ describe('parseConnectionString', () => { }) describe('edge cases', () => { + it('should handle brace escaping only when { is the first character (tedious compatibility)', () => { + // Edge case from jacek-prisma: https://github.com/prisma/prisma/pull/29158#discussion_r1943210520 + // Tedious treats { as a quote character ONLY when it's the FIRST character of a value + const connectionString = 'sqlserver://localhost;database=testdb;user=u{s;password=}password;' + const config = parseConnectionString(connectionString) + + // According to tedious behavior: + // user=u{s → user is "u{s" (the { is not the first char, so not treated as quote) + // password=}password → password is "}password" (no opening { to match) + expect(config.user).toBe('u{s') + expect(config.password).toBe('}password') + expect(config.database).toBe('testdb') + }) + + it('should handle braces in the middle of values correctly', () => { + const connectionString = 'sqlserver://localhost;database=test{db}name;user=my{user}name;password=pass{word}' + const config = parseConnectionString(connectionString) + + // Braces in the middle should be treated as literal characters + expect(config.database).toBe('test{db}name') + expect(config.user).toBe('my{user}name') + expect(config.password).toBe('pass{word}') + }) + + it('should still escape when { is the first character', () => { + const connectionString = 'sqlserver://localhost;database={test;db};user={my;user};password={my;pass=word}' + const config = parseConnectionString(connectionString) + + // When { is first character, it should escape the content + expect(config.database).toBe('test;db') + expect(config.user).toBe('my;user') + expect(config.password).toBe('my;pass=word') + }) + + it('should handle mixed scenarios with braces', () => { + const connectionString = 'sqlserver://localhost;database={db;1};user=prefix{middle};password={escaped;pwd}' + const config = parseConnectionString(connectionString) + + expect(config.database).toBe('db;1') + expect(config.user).toBe('prefix{middle}') + expect(config.password).toBe('escaped;pwd') + }) + it('should handle connection string with only server', () => { const connectionString = 'sqlserver://localhost' const config = parseConnectionString(connectionString) diff --git a/packages/adapter-mssql/src/connection-string.ts b/packages/adapter-mssql/src/connection-string.ts index 347b02ed2d67..b3e6b6a56213 100644 --- a/packages/adapter-mssql/src/connection-string.ts +++ b/packages/adapter-mssql/src/connection-string.ts @@ -48,6 +48,10 @@ export function extractSchemaFromConnectionString(connectionString: string): str * Splits a connection string by semicolons while respecting curly brace escaping. * Values wrapped in curly braces like {value} are treated as literals where * semicolons and equals signs are not treated as delimiters. + * + * Important: Curly braces are only treated as quote characters when { is the + * FIRST character of a value (after the = sign). This matches tedious behavior. + * * @param str The string to split * @returns Array of parts split by semicolon (outside of curly braces) */ @@ -55,23 +59,38 @@ function splitRespectingBraces(str: string): string[] { const parts: string[] = [] let current = '' let braceDepth = 0 + let afterEquals = false + let valueStartIndex = -1 for (let i = 0; i < str.length; i++) { const char = str[i] - if (char === '{') { - braceDepth++ + if (char === '=') { current += char + afterEquals = true + valueStartIndex = i + 1 + } else if (char === '{') { + // Only treat { as opening brace if it's the first character of a value + const isFirstCharOfValue = afterEquals && i === valueStartIndex + if (isFirstCharOfValue) { + braceDepth++ + } + current += char + afterEquals = false } else if (char === '}') { if (braceDepth > 0) { braceDepth-- } current += char + afterEquals = false } else if (char === ';' && braceDepth === 0) { parts.push(current) current = '' + afterEquals = false + valueStartIndex = -1 } else { current += char + afterEquals = false } } From 7fedc533d16686896bc4fb7a67a68b9616aca70b Mon Sep 17 00:00:00 2001 From: Omair Afzal Date: Wed, 11 Feb 2026 03:04:00 +0500 Subject: [PATCH 07/10] refactor: remove redundant afterEquals flag in splitRespectingBraces --- packages/adapter-mssql/src/connection-string.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/adapter-mssql/src/connection-string.ts b/packages/adapter-mssql/src/connection-string.ts index b3e6b6a56213..4cd30fd87090 100644 --- a/packages/adapter-mssql/src/connection-string.ts +++ b/packages/adapter-mssql/src/connection-string.ts @@ -59,7 +59,6 @@ function splitRespectingBraces(str: string): string[] { const parts: string[] = [] let current = '' let braceDepth = 0 - let afterEquals = false let valueStartIndex = -1 for (let i = 0; i < str.length; i++) { @@ -67,30 +66,25 @@ function splitRespectingBraces(str: string): string[] { if (char === '=') { current += char - afterEquals = true valueStartIndex = i + 1 } else if (char === '{') { // Only treat { as opening brace if it's the first character of a value - const isFirstCharOfValue = afterEquals && i === valueStartIndex + const isFirstCharOfValue = i === valueStartIndex if (isFirstCharOfValue) { braceDepth++ } current += char - afterEquals = false } else if (char === '}') { if (braceDepth > 0) { braceDepth-- } current += char - afterEquals = false } else if (char === ';' && braceDepth === 0) { parts.push(current) current = '' - afterEquals = false valueStartIndex = -1 } else { current += char - afterEquals = false } } From b04d099e6a9bfa401576c729ce1fb813eede303a Mon Sep 17 00:00:00 2001 From: Omair Afzal Date: Wed, 11 Feb 2026 17:20:15 +0500 Subject: [PATCH 08/10] fix: guard valueStartIndex update with braceDepth check --- packages/adapter-mssql/src/connection-string.test.ts | 7 +++++++ packages/adapter-mssql/src/connection-string.ts | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/adapter-mssql/src/connection-string.test.ts b/packages/adapter-mssql/src/connection-string.test.ts index 3ed187ca74e8..4d961856f694 100644 --- a/packages/adapter-mssql/src/connection-string.test.ts +++ b/packages/adapter-mssql/src/connection-string.test.ts @@ -212,6 +212,13 @@ describe('parseConnectionString', () => { expect(config.password).toBe('pass=word') }) + it('should handle equals signs inside braced values without affecting subsequent brace detection', () => { + const connectionString = 'sqlserver://localhost;password={my=pass{nested}};database=testdb' + const config = parseConnectionString(connectionString) + expect(config.password).toBe('my=pass{nested}') + expect(config.database).toBe('testdb') + }) + it('should handle password with both semicolon and equals when escaped', () => { const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password={pass;word=123}' const config = parseConnectionString(connectionString) diff --git a/packages/adapter-mssql/src/connection-string.ts b/packages/adapter-mssql/src/connection-string.ts index 4cd30fd87090..502521de1993 100644 --- a/packages/adapter-mssql/src/connection-string.ts +++ b/packages/adapter-mssql/src/connection-string.ts @@ -66,7 +66,9 @@ function splitRespectingBraces(str: string): string[] { if (char === '=') { current += char - valueStartIndex = i + 1 + if (braceDepth === 0) { + valueStartIndex = i + 1 + } } else if (char === '{') { // Only treat { as opening brace if it's the first character of a value const isFirstCharOfValue = i === valueStartIndex From 987f398ab2bb3a89f51a898cbf4c3e5acd066132 Mon Sep 17 00:00:00 2001 From: Omair Afzal Date: Wed, 11 Feb 2026 19:40:58 +0500 Subject: [PATCH 09/10] fix(adapter-mssql): reject nested curly braces in connection string values Nested braces like {my=pass{nested}} inside brace-quoted values now throw a clear error instead of silently truncating. This aligns with standard connection string behavior where nested braces are invalid. Co-authored-by: Omair Afzal --- packages/adapter-mssql/src/connection-string.test.ts | 8 ++++---- packages/adapter-mssql/src/connection-string.ts | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/adapter-mssql/src/connection-string.test.ts b/packages/adapter-mssql/src/connection-string.test.ts index 4d961856f694..e517a8efc443 100644 --- a/packages/adapter-mssql/src/connection-string.test.ts +++ b/packages/adapter-mssql/src/connection-string.test.ts @@ -212,11 +212,11 @@ describe('parseConnectionString', () => { expect(config.password).toBe('pass=word') }) - it('should handle equals signs inside braced values without affecting subsequent brace detection', () => { + it('should throw error for nested braces inside braced values', () => { const connectionString = 'sqlserver://localhost;password={my=pass{nested}};database=testdb' - const config = parseConnectionString(connectionString) - expect(config.password).toBe('my=pass{nested}') - expect(config.database).toBe('testdb') + expect(() => parseConnectionString(connectionString)).toThrow( + "Malformed connection string: nested '{' braces are not supported", + ) }) it('should handle password with both semicolon and equals when escaped', () => { diff --git a/packages/adapter-mssql/src/connection-string.ts b/packages/adapter-mssql/src/connection-string.ts index 502521de1993..a8de3e40a66c 100644 --- a/packages/adapter-mssql/src/connection-string.ts +++ b/packages/adapter-mssql/src/connection-string.ts @@ -74,6 +74,8 @@ function splitRespectingBraces(str: string): string[] { const isFirstCharOfValue = i === valueStartIndex if (isFirstCharOfValue) { braceDepth++ + } else if (braceDepth > 0) { + throw new Error(`Malformed connection string: nested '{' braces are not supported`) } current += char } else if (char === '}') { From e61f1b1935dccbaae8dad637e80c897688f3a372 Mon Sep 17 00:00:00 2001 From: Jacek Malec Date: Wed, 11 Feb 2026 15:06:21 +0000 Subject: [PATCH 10/10] chore: merge schema and connection string handling --- .../src/connection-string.test.ts | 202 ++++++------------ .../adapter-mssql/src/connection-string.ts | 25 +-- packages/adapter-mssql/src/mssql.ts | 8 +- 3 files changed, 72 insertions(+), 163 deletions(-) diff --git a/packages/adapter-mssql/src/connection-string.test.ts b/packages/adapter-mssql/src/connection-string.test.ts index e517a8efc443..ac940c262d9d 100644 --- a/packages/adapter-mssql/src/connection-string.test.ts +++ b/packages/adapter-mssql/src/connection-string.test.ts @@ -1,12 +1,12 @@ import { describe, expect, it } from 'vitest' -import { extractSchemaFromConnectionString, parseConnectionString } from './connection-string' +import { parseConnectionString } from './connection-string' describe('parseConnectionString', () => { describe('basic connection parameters', () => { it('should parse a basic connection string correctly', () => { const connectionString = 'sqlserver://localhost:1433;database=testdb;user=sa;password=mypassword;encrypt=true' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.server).toBe('localhost') expect(config.port).toBe(1433) @@ -18,7 +18,7 @@ describe('parseConnectionString', () => { it('should parse connection string without port', () => { const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password=mypassword' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.server).toBe('localhost') expect(config.port).toBeUndefined() @@ -36,7 +36,7 @@ describe('parseConnectionString', () => { ] testCases.forEach((connectionString) => { - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.user).toBe('sa') }) }) @@ -48,7 +48,7 @@ describe('parseConnectionString', () => { ] testCases.forEach((connectionString) => { - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.password).toBe('mypassword') }) }) @@ -60,7 +60,7 @@ describe('parseConnectionString', () => { ] testCases.forEach((connectionString) => { - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.database).toBe('testdb') }) }) @@ -74,7 +74,7 @@ describe('parseConnectionString', () => { ] testCases.forEach(({ input, expected }) => { - const config = parseConnectionString(input) + const { config } = parseConnectionString(input) expect(config.options?.encrypt).toBe(expected) }) }) @@ -86,14 +86,14 @@ describe('parseConnectionString', () => { ] testCases.forEach(({ input, expected }) => { - const config = parseConnectionString(input) + const { config } = parseConnectionString(input) expect(config.options?.trustServerCertificate).toBe(expected) }) }) it('should handle both encryption parameters together', () => { const connectionString = 'sqlserver://localhost;database=testdb;encrypt=true;trustServerCertificate=true' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.options?.encrypt).toBe(true) expect(config.options?.trustServerCertificate).toBe(true) @@ -104,21 +104,21 @@ describe('parseConnectionString', () => { { input: 'sqlserver://localhost;database=testdb;multiSubnetFailover=true', expected: true }, { input: 'sqlserver://localhost;database=testdb;multiSubnetFailover=false', expected: false }, ])('should parse multiSubnetFailover parameter correctly for %o', ({ input, expected }) => { - const config = parseConnectionString(input) + const { config } = parseConnectionString(input) expect(config.options?.multiSubnetFailover).toBe(expected) }) describe('connection pool parameters', () => { it('should parse connectionLimit parameter correctly', () => { const connectionString = 'sqlserver://localhost;database=testdb;connectionLimit=10' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.pool?.max).toBe(10) }) it('should parse poolTimeout parameter correctly', () => { const connectionString = 'sqlserver://localhost;database=testdb;poolTimeout=15' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.pool?.acquireTimeoutMillis).toBe(15000) // 15 seconds in milliseconds }) @@ -127,35 +127,35 @@ describe('parseConnectionString', () => { describe('timeout parameters', () => { it('should parse connectTimeout parameter correctly', () => { const connectionString = 'sqlserver://localhost;database=testdb;connectTimeout=30' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.connectionTimeout).toBe(30) }) it('should parse connectionTimeout parameter correctly', () => { const connectionString = 'sqlserver://localhost;database=testdb;connectionTimeout=30' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.connectionTimeout).toBe(30) }) it('should parse loginTimeout parameter correctly', () => { const connectionString = 'sqlserver://localhost;database=testdb;loginTimeout=45' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.connectionTimeout).toBe(45) }) it('should parse socketTimeout parameter correctly', () => { const connectionString = 'sqlserver://localhost;database=testdb;socketTimeout=60' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.requestTimeout).toBe(60) }) it('should handle multiple timeout parameters', () => { const connectionString = 'sqlserver://localhost;database=testdb;connectTimeout=30;socketTimeout=60;poolTimeout=10' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.connectionTimeout).toBe(30) expect(config.requestTimeout).toBe(60) @@ -166,48 +166,60 @@ describe('parseConnectionString', () => { describe('application name parameter', () => { it('should parse applicationName parameter correctly', () => { const connectionString = 'sqlserver://localhost;database=testdb;applicationName=MyApp' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.options?.appName).toBe('MyApp') }) it('should parse application name parameter correctly', () => { const connectionString = 'sqlserver://localhost;database=testdb;application name=MyApp' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.options?.appName).toBe('MyApp') }) }) describe('schema parameter', () => { - it('should ignore schema parameter', () => { + it('should ignore schema parameter in the config', () => { const connectionString = 'sqlserver://localhost;database=testdb;schema=custom' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) // Schema should not be in the config, it's handled separately // The schema parameter is ignored during parsing expect(config.database).toBe('testdb') }) + + it('should return schema parameter separately from config', () => { + const connectionString = 'sqlserver://localhost;database=testdb;schema=custom' + const { schema } = parseConnectionString(connectionString) + expect(schema).toBe('custom') + }) + + it('should return undefined for schema parameter if not provided', () => { + const connectionString = 'sqlserver://localhost;database=testdb' + const { schema } = parseConnectionString(connectionString) + expect(schema).toBeUndefined() + }) }) describe('escaped values with curly braces', () => { it('should parse password with curly braces correctly', () => { const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password={mypassword}' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.password).toBe('mypassword') }) it('should handle password with semicolon when escaped', () => { const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password={pass;word}' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.password).toBe('pass;word') }) it('should handle password with equals sign when escaped', () => { const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password={pass=word}' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.password).toBe('pass=word') }) @@ -221,28 +233,28 @@ describe('parseConnectionString', () => { it('should handle password with both semicolon and equals when escaped', () => { const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password={pass;word=123}' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.password).toBe('pass;word=123') }) it('should handle escaped database name', () => { const connectionString = 'sqlserver://localhost;database={test;db};user=sa;password=mypassword' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.database).toBe('test;db') }) it('should handle escaped user name', () => { const connectionString = 'sqlserver://localhost;database=testdb;user={domain\\user;name};password=mypassword' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.user).toBe('domain\\user;name') }) it('should handle multiple escaped values', () => { const connectionString = 'sqlserver://localhost;database={test;db};user={my;user};password={my;pass=word}' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.database).toBe('test;db') expect(config.user).toBe('my;user') @@ -252,7 +264,7 @@ describe('parseConnectionString', () => { it('should handle complex password with multiple special characters', () => { const connectionString = 'sqlserver://localhost:1433;database=testdb;user=sa;password={P@ss;w=rd!123};encrypt=true' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.server).toBe('localhost') expect(config.port).toBe(1433) @@ -264,14 +276,14 @@ describe('parseConnectionString', () => { it('should handle empty braces as empty string', () => { const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password={}' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.password).toBe('') }) it('should not unescape braces that are not at value boundaries', () => { const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password=my{pass}word' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.password).toBe('my{pass}word') }) @@ -286,7 +298,7 @@ describe('parseConnectionString', () => { it('should not throw on unmatched closing brace (clamped)', () => { const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password=pass}word' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.password).toBe('pass}word') }) @@ -303,7 +315,7 @@ describe('parseConnectionString', () => { ] testCases.forEach(({ input, expected }) => { - const config = parseConnectionString(input) + const { config } = parseConnectionString(input) expect(config.options?.isolationLevel).toBe(expected) }) }) @@ -318,7 +330,7 @@ describe('parseConnectionString', () => { ] testCases.forEach(({ input, expected }) => { - const config = parseConnectionString(input) + const { config } = parseConnectionString(input) expect(config.options?.isolationLevel).toBe(expected) }) }) @@ -331,7 +343,7 @@ describe('parseConnectionString', () => { ] testCases.forEach(({ input, expected }) => { - const config = parseConnectionString(input) + const { config } = parseConnectionString(input) expect(config.options?.isolationLevel).toBe(expected) }) }) @@ -340,7 +352,7 @@ describe('parseConnectionString', () => { describe('case sensitivity', () => { it('should handle case sensitive parameter names correctly', () => { const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password=mypassword;encrypt=true' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.database).toBe('testdb') expect(config.user).toBe('sa') @@ -350,7 +362,7 @@ describe('parseConnectionString', () => { it('should handle case insensitive boolean values', () => { const connectionString = 'sqlserver://localhost;database=testdb;encrypt=TRUE;trustServerCertificate=FALSE' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.options?.encrypt).toBe(true) expect(config.options?.trustServerCertificate).toBe(false) @@ -360,7 +372,7 @@ describe('parseConnectionString', () => { describe('whitespace handling', () => { it('should handle whitespace around parameters', () => { const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password=mypassword' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.database).toBe('testdb') expect(config.user).toBe('sa') @@ -369,7 +381,7 @@ describe('parseConnectionString', () => { it('should handle whitespace around values', () => { const connectionString = 'sqlserver://localhost;database= testdb ;user= sa ;password= mypassword ' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.database).toBe('testdb') expect(config.user).toBe('sa') @@ -380,7 +392,7 @@ describe('parseConnectionString', () => { describe('unknown parameters', () => { it('should ignore unknown parameters without throwing', () => { const connectionString = 'sqlserver://localhost;database=testdb;unknownParam=value;anotherUnknown=123' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.database).toBe('testdb') // Should not throw and should ignore unknown parameters @@ -474,7 +486,7 @@ describe('parseConnectionString', () => { // Edge case from jacek-prisma: https://github.com/prisma/prisma/pull/29158#discussion_r1943210520 // Tedious treats { as a quote character ONLY when it's the FIRST character of a value const connectionString = 'sqlserver://localhost;database=testdb;user=u{s;password=}password;' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) // According to tedious behavior: // user=u{s → user is "u{s" (the { is not the first char, so not treated as quote) @@ -486,7 +498,7 @@ describe('parseConnectionString', () => { it('should handle braces in the middle of values correctly', () => { const connectionString = 'sqlserver://localhost;database=test{db}name;user=my{user}name;password=pass{word}' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) // Braces in the middle should be treated as literal characters expect(config.database).toBe('test{db}name') @@ -496,7 +508,7 @@ describe('parseConnectionString', () => { it('should still escape when { is the first character', () => { const connectionString = 'sqlserver://localhost;database={test;db};user={my;user};password={my;pass=word}' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) // When { is first character, it should escape the content expect(config.database).toBe('test;db') @@ -506,7 +518,7 @@ describe('parseConnectionString', () => { it('should handle mixed scenarios with braces', () => { const connectionString = 'sqlserver://localhost;database={db;1};user=prefix{middle};password={escaped;pwd}' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.database).toBe('db;1') expect(config.user).toBe('prefix{middle}') @@ -515,7 +527,7 @@ describe('parseConnectionString', () => { it('should handle connection string with only server', () => { const connectionString = 'sqlserver://localhost' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.server).toBe('localhost') expect(config.database).toBeUndefined() @@ -525,7 +537,7 @@ describe('parseConnectionString', () => { it('should handle connection string with empty values', () => { const connectionString = 'sqlserver://localhost;database=;user=;password=' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.server).toBe('localhost') expect(config.database).toBe('') @@ -535,7 +547,7 @@ describe('parseConnectionString', () => { it('should handle connection string with malformed key-value pairs', () => { const connectionString = 'sqlserver://localhost;database=testdb;=value;key=;=' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.server).toBe('localhost') expect(config.database).toBe('testdb') @@ -549,7 +561,7 @@ describe('parseConnectionString', () => { 'sqlserver://localhost:1433;database=testdb;authentication=ActiveDirectoryIntegrated', 'sqlserver://localhost:1433;database=testdb;authentication=ActiveDirectoryInteractive', ])('should support authentication parameter for %s', (connectionString) => { - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.user).toBe(undefined) expect(config.password).toBe(undefined) expect(config.authentication?.type).toBe('azure-active-directory-default') @@ -558,7 +570,7 @@ describe('parseConnectionString', () => { it('should support authentication password parameters', () => { const connectionString = 'sqlserver://localhost:1433;database=testdb;authentication=ActiveDirectoryPassword;userName=user1;password=mypassword;clientId=my-client-id' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.authentication?.type).toBe('azure-active-directory-password') if (config.authentication?.type === 'azure-active-directory-password') { @@ -575,7 +587,7 @@ describe('parseConnectionString', () => { 'sqlserver://localhost:1433;database=testdb;authentication=ActiveDirectoryManagedIdentity;clientId=test-client;msiEndpoint=msi-endpoint-1;msiSecret=msi-secret-1', 'sqlserver://localhost:1433;database=testdb;authentication=ActiveDirectoryMSI;clientId=test-client;msiEndpoint=msi-endpoint-1;msiSecret=msi-secret-1', ])('should support authentication managed identity parameters for %s', (connectionString) => { - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.user).toBe(undefined) expect(config.password).toBe(undefined) @@ -594,7 +606,7 @@ describe('parseConnectionString', () => { it('should support authentication service principal parameters', () => { const connectionString = 'sqlserver://localhost:1433;database=testdb;authentication=ActiveDirectoryServicePrincipal;userName=test-client-id;password=mysecret' - const config = parseConnectionString(connectionString) + const { config } = parseConnectionString(connectionString) expect(config.authentication?.type).toBe('azure-active-directory-service-principal-secret') if (config.authentication?.type === 'azure-active-directory-service-principal-secret') { @@ -637,91 +649,3 @@ describe('parseConnectionString', () => { }) }) }) - -describe('extractSchemaFromConnectionString', () => { - it('should extract schema parameter correctly', () => { - const connectionString = 'sqlserver://localhost;database=testdb;schema=custom' - const schema = extractSchemaFromConnectionString(connectionString) - - expect(schema).toBe('custom') - }) - - it('should extract schema parameter without database', () => { - const connectionString = 'sqlserver://localhost;schema=custom' - const schema = extractSchemaFromConnectionString(connectionString) - - expect(schema).toBe('custom') - }) - - it('should return undefined when schema is not provided', () => { - const connectionString = 'sqlserver://localhost;database=testdb' - const schema = extractSchemaFromConnectionString(connectionString) - - expect(schema).toBeUndefined() - }) - - it('should handle schema parameter with whitespace', () => { - const connectionString = 'sqlserver://localhost;database=testdb; schema = custom ' - const schema = extractSchemaFromConnectionString(connectionString) - - expect(schema).toBe('custom') - }) - - it('should handle empty schema value', () => { - const connectionString = 'sqlserver://localhost;database=testdb;schema=' - const schema = extractSchemaFromConnectionString(connectionString) - - expect(schema).toBe('') - }) - - describe('escaped schema values with curly braces', () => { - it('should extract schema with curly braces correctly', () => { - const connectionString = 'sqlserver://localhost;database=testdb;schema={custom}' - const schema = extractSchemaFromConnectionString(connectionString) - - expect(schema).toBe('custom') - }) - - it('should handle schema with semicolon when escaped', () => { - const connectionString = 'sqlserver://localhost;database=testdb;schema={custom;schema}' - const schema = extractSchemaFromConnectionString(connectionString) - - expect(schema).toBe('custom;schema') - }) - - it('should handle schema with equals sign when escaped', () => { - const connectionString = 'sqlserver://localhost;database=testdb;schema={custom=schema}' - const schema = extractSchemaFromConnectionString(connectionString) - - expect(schema).toBe('custom=schema') - }) - - it('should handle schema with both semicolon and equals when escaped', () => { - const connectionString = 'sqlserver://localhost;database=testdb;schema={custom;schema=value}' - const schema = extractSchemaFromConnectionString(connectionString) - - expect(schema).toBe('custom;schema=value') - }) - - it('should handle schema with escaped value and other parameters', () => { - const connectionString = 'sqlserver://localhost;database=testdb;user=sa;password={pass;word};schema={my;schema}' - const schema = extractSchemaFromConnectionString(connectionString) - - expect(schema).toBe('my;schema') - }) - - it('should handle empty braces in schema as empty string', () => { - const connectionString = 'sqlserver://localhost;database=testdb;schema={}' - const schema = extractSchemaFromConnectionString(connectionString) - - expect(schema).toBe('') - }) - - it('should not unescape braces that are not at schema value boundaries', () => { - const connectionString = 'sqlserver://localhost;database=testdb;schema=my{sche}ma' - const schema = extractSchemaFromConnectionString(connectionString) - - expect(schema).toBe('my{sche}ma') - }) - }) -}) diff --git a/packages/adapter-mssql/src/connection-string.ts b/packages/adapter-mssql/src/connection-string.ts index a8de3e40a66c..8bf648f39294 100644 --- a/packages/adapter-mssql/src/connection-string.ts +++ b/packages/adapter-mssql/src/connection-string.ts @@ -26,24 +26,6 @@ function mapIsolationLevelFromString(level: string): number { const debug = Debug('prisma:driver-adapter:mssql:connection-string') -/** - * Extracts the schema parameter from a connection string. - * @param connectionString The connection string. - * @returns The schema value or undefined if not found. - */ -export function extractSchemaFromConnectionString(connectionString: string): string | undefined { - const withoutProtocol = connectionString.replace(/^sqlserver:\/\//, '') - const parts = splitRespectingBraces(withoutProtocol) - - for (const part of parts) { - const [key, ...valueParts] = part.split('=') - if (key?.trim() === 'schema') { - return unescapeValue(valueParts.join('=')) - } - } - return undefined -} - /** * Splits a connection string by semicolons while respecting curly brace escaping. * Values wrapped in curly braces like {value} are treated as literals where @@ -124,7 +106,7 @@ function unescapeValue(value: string): string { * @param connectionString The connection string. * @returns A sql.config object */ -export function parseConnectionString(connectionString: string): sql.config { +export function parseConnectionString(connectionString: string): { config: sql.config; schema?: string } { const withoutProtocol = connectionString.replace(/^sqlserver:\/\//, '') const parts = splitRespectingBraces(withoutProtocol) @@ -271,7 +253,9 @@ export function parseConnectionString(connectionString: string): sql.config { throw new Error('Server host is required in connection string') } - return config + const schema = firstKey(parameters, 'schema') ?? undefined + + return { config, schema } } /** @@ -382,6 +366,7 @@ const handledParameters = [ 'password', 'poolTimeout', 'pwd', + 'schema', 'socketTimeout', 'trustServerCertificate', 'uid', diff --git a/packages/adapter-mssql/src/mssql.ts b/packages/adapter-mssql/src/mssql.ts index 456f579028e9..2e4d828f7727 100644 --- a/packages/adapter-mssql/src/mssql.ts +++ b/packages/adapter-mssql/src/mssql.ts @@ -15,7 +15,7 @@ import { Mutex } from 'async-mutex' import sql from 'mssql' import { name as packageName } from '../package.json' -import { extractSchemaFromConnectionString, parseConnectionString } from './connection-string' +import { parseConnectionString } from './connection-string' import { mapArg, mapColumnType, mapIsolationLevel, mapRow } from './conversion' import { convertDriverError } from './errors' @@ -185,12 +185,12 @@ export class PrismaMssqlAdapterFactory implements SqlDriverAdapterFactory { constructor(configOrString: sql.config | string, options?: PrismaMssqlOptions) { if (typeof configOrString === 'string') { - this.#config = parseConnectionString(configOrString) + const { config, schema } = parseConnectionString(configOrString) + this.#config = config // Extract schema from connection string and merge with provided options - const extractedSchema = extractSchemaFromConnectionString(configOrString) this.#options = { ...options, - schema: options?.schema ?? extractedSchema, + schema: options?.schema ?? schema, } } else { this.#config = configOrString