|
41 | 41 | ) |
42 | 42 | from mssql_python.auth import process_connection_string |
43 | 43 | from mssql_python.constants import ConstantsDDBC, GetInfoConstants |
| 44 | +from mssql_python.connection_string_parser import _ConnectionStringParser |
| 45 | +from mssql_python.connection_string_builder import _ConnectionStringBuilder |
| 46 | +from mssql_python.constants import _RESERVED_PARAMETERS |
44 | 47 |
|
45 | 48 | if TYPE_CHECKING: |
46 | 49 | from mssql_python.row import Row |
@@ -242,39 +245,62 @@ def _construct_connection_string( |
242 | 245 | self, connection_str: str = "", **kwargs: Any |
243 | 246 | ) -> str: |
244 | 247 | """ |
245 | | - Construct the connection string by concatenating the connection string |
246 | | - with key/value pairs from kwargs. |
247 | | -
|
| 248 | + Construct the connection string by parsing, validating, and merging parameters. |
| 249 | + |
| 250 | + This method performs a 6-step process: |
| 251 | + 1. Parse and validate the base connection_str (validates against allowlist) |
| 252 | + 2. Normalize parameter names (e.g., addr/address -> Server, uid -> UID) |
| 253 | + 3. Merge kwargs (which override connection_str params after normalization) |
| 254 | + 4. Build connection string from normalized, merged params |
| 255 | + 5. Add Driver and APP parameters (always controlled by the driver) |
| 256 | + 6. Return the final connection string |
| 257 | + |
248 | 258 | Args: |
249 | 259 | connection_str (str): The base connection string. |
250 | 260 | **kwargs: Additional key/value pairs for the connection string. |
251 | 261 |
|
252 | 262 | Returns: |
253 | | - str: The constructed connection string. |
| 263 | + str: The constructed and validated connection string. |
254 | 264 | """ |
255 | | - # Add the driver attribute to the connection string |
256 | | - conn_str = add_driver_to_connection_str(connection_str) |
257 | | - |
258 | | - # Add additional key-value pairs to the connection string |
| 265 | + |
| 266 | + # Step 1: Parse base connection string with allowlist validation |
| 267 | + # The parser validates everything: unknown params, reserved params, duplicates, syntax |
| 268 | + parser = _ConnectionStringParser(validate_keywords=True) |
| 269 | + parsed_params = parser._parse(connection_str) |
| 270 | + |
| 271 | + # Step 2: Normalize parameter names (e.g., addr/address -> Server, uid -> UID) |
| 272 | + # This handles synonym mapping and deduplication via normalized keys |
| 273 | + normalized_params = _ConnectionStringParser._normalize_params(parsed_params, warn_rejected=False) |
| 274 | + |
| 275 | + # Step 3: Process kwargs and merge with normalized_params |
| 276 | + # kwargs override connection string values (processed after, so they take precedence) |
259 | 277 | for key, value in kwargs.items(): |
260 | | - if key.lower() == "host" or key.lower() == "server": |
261 | | - key = "Server" |
262 | | - elif key.lower() == "user" or key.lower() == "uid": |
263 | | - key = "Uid" |
264 | | - elif key.lower() == "password" or key.lower() == "pwd": |
265 | | - key = "Pwd" |
266 | | - elif key.lower() == "database": |
267 | | - key = "Database" |
268 | | - elif key.lower() == "encrypt": |
269 | | - key = "Encrypt" |
270 | | - elif key.lower() == "trust_server_certificate": |
271 | | - key = "TrustServerCertificate" |
| 278 | + normalized_key = _ConnectionStringParser.normalize_key(key) |
| 279 | + if normalized_key: |
| 280 | + # Driver and APP are reserved - raise error if user tries to set them |
| 281 | + if normalized_key in _RESERVED_PARAMETERS: |
| 282 | + raise ValueError( |
| 283 | + f"Connection parameter '{key}' is reserved and controlled by the driver. " |
| 284 | + f"It cannot be set by the user." |
| 285 | + ) |
| 286 | + # kwargs override any existing values from connection string |
| 287 | + normalized_params[normalized_key] = str(value) |
272 | 288 | else: |
273 | | - continue |
274 | | - conn_str += f"{key}={value};" |
275 | | - |
276 | | - log("info", "Final connection string: %s", sanitize_connection_string(conn_str)) |
277 | | - |
| 289 | + log('warning', f"Ignoring unknown connection parameter from kwargs: {key}") |
| 290 | + |
| 291 | + # Step 4: Build connection string with merged params |
| 292 | + builder = _ConnectionStringBuilder(normalized_params) |
| 293 | + |
| 294 | + # Step 5: Add Driver and APP parameters (always controlled by the driver) |
| 295 | + # These maintain existing behavior: Driver is always hardcoded, APP is always MSSQL-Python |
| 296 | + builder.add_param('Driver', 'ODBC Driver 18 for SQL Server') |
| 297 | + builder.add_param('APP', 'MSSQL-Python') |
| 298 | + |
| 299 | + # Step 6: Build final string |
| 300 | + conn_str = builder.build() |
| 301 | + |
| 302 | + log('info', "Final connection string: %s", sanitize_connection_string(conn_str)) |
| 303 | + |
278 | 304 | return conn_str |
279 | 305 |
|
280 | 306 | @property |
|
0 commit comments