1717import warnings
1818from typing import List , Union , Any , Optional , Tuple , Sequence , TYPE_CHECKING , Iterable
1919from mssql_python .constants import ConstantsDDBC as ddbc_sql_const , SQLTypes
20- from mssql_python .helpers import check_error
20+ from mssql_python .helpers import check_error , connstr_to_pycore_params
2121from mssql_python .logging import logger
2222from mssql_python import ddbc_bindings
2323from mssql_python .exceptions import (
@@ -2451,6 +2451,7 @@ def nextset(self) -> Union[bool, None]:
24512451 )
24522452 return True
24532453
2454+ # ── Mapping from ODBC connection-string keywords (lowercase, as _parse returns)
24542455 def _bulkcopy (
24552456 self ,
24562457 table_name : str ,
@@ -2585,38 +2586,10 @@ def _bulkcopy(
25852586 "Specify the target database explicitly to avoid accidentally writing to system databases."
25862587 )
25872588
2588- # Build connection context for bulk copy library
2589- # Note: Password is extracted separately to avoid storing it in the main context
2590- # dict that could be accidentally logged or exposed in error messages.
2591- trust_cert = params .get ("trustservercertificate" , "yes" ).lower () in ("yes" , "true" )
2592-
2593- # Parse encryption setting from connection string
2594- encrypt_param = params .get ("encrypt" )
2595- if encrypt_param is not None :
2596- encrypt_value = encrypt_param .strip ().lower ()
2597- if encrypt_value in ("yes" , "true" , "mandatory" , "required" ):
2598- encryption = "Required"
2599- elif encrypt_value in ("no" , "false" , "optional" ):
2600- encryption = "Optional"
2601- else :
2602- # Pass through unrecognized values (e.g., "Strict") to the underlying driver
2603- encryption = encrypt_param
2604- else :
2605- encryption = "Optional"
2606-
2607- context = {
2608- "server" : params .get ("server" ),
2609- "database" : params .get ("database" ),
2610- "trust_server_certificate" : trust_cert ,
2611- "encryption" : encryption ,
2612- }
2613-
2614- # Build pycore_context with appropriate authentication.
2615- # For Azure AD: acquire a FRESH token right now instead of reusing
2616- # the one from connect() time — avoids expired-token errors when
2617- # bulkcopy() is called long after the original connection.
2618- pycore_context = dict (context )
2589+ # Translate parsed connection string into the dict py-core expects.
2590+ pycore_context = connstr_to_pycore_params (params )
26192591
2592+ # Token acquisition — only thing cursor must handle (needs azure-identity SDK)
26202593 if self .connection ._auth_type :
26212594 # Fresh token acquisition for mssql-py-core connection
26222595 from mssql_python .auth import AADAuth
@@ -2633,10 +2606,6 @@ def _bulkcopy(
26332606 "Bulk copy: acquired fresh Azure AD token for auth_type=%s" ,
26342607 self .connection ._auth_type ,
26352608 )
2636- else :
2637- # SQL Server authentication — use uid/password from connection string
2638- pycore_context ["user_name" ] = params .get ("uid" , "" )
2639- pycore_context ["password" ] = params .get ("pwd" , "" )
26402609
26412610 pycore_connection = None
26422611 pycore_cursor = None
@@ -2675,9 +2644,8 @@ def _bulkcopy(
26752644 finally :
26762645 # Clear sensitive data to minimize memory exposure
26772646 if pycore_context :
2678- pycore_context .pop ("password" , None )
2679- pycore_context .pop ("user_name" , None )
2680- pycore_context .pop ("access_token" , None )
2647+ for key in ("password" , "user_name" , "access_token" ):
2648+ pycore_context .pop (key , None )
26812649 # Clean up bulk copy resources
26822650 for resource in (pycore_cursor , pycore_connection ):
26832651 if resource and hasattr (resource , "close" ):
0 commit comments