Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 53 additions & 2 deletions mssql/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
"""
MS SQL Server database backend for Django.
"""
import logging
import os
import re
import time
import struct
import datetime

logger = logging.getLogger('django.db.backends')
from decimal import Decimal
from uuid import UUID

Expand Down Expand Up @@ -279,7 +282,8 @@ def get_connection_params(self):
conn_params['NAME'] = 'master'
return conn_params

def get_new_connection(self, conn_params):
def _build_connection_string(self, conn_params, driver):
"""Build ODBC connection string for the given driver."""
database = conn_params['NAME']
host = conn_params.get('HOST', 'localhost')
user = conn_params.get('USER', None)
Expand All @@ -288,7 +292,6 @@ def get_new_connection(self, conn_params):
trusted_connection = conn_params.get('Trusted_Connection', 'yes')

options = conn_params.get('OPTIONS', {})
driver = options.get('driver', 'ODBC Driver 17 for SQL Server')
dsn = options.get('dsn', None)
options_extra_params = options.get('extra_params', '')

Expand Down Expand Up @@ -345,6 +348,23 @@ def get_new_connection(self, conn_params):
if options.get('extra_params', None):
connstr += ';' + options['extra_params']

return connstr

def _is_driver_not_found_error(self, exception):
"""Check if the exception indicates a driver not found error."""
error_msg = str(exception).lower()
return (
"can't open lib" in error_msg or
"data source name not found" in error_msg or
"driver not found" in error_msg or
"specified driver could not be loaded" in error_msg
)

def get_new_connection(self, conn_params):
options = conn_params.get('OPTIONS', {})
driver = options.get('driver', 'ODBC Driver 18 for SQL Server')
driver_explicitly_set = 'driver' in options

unicode_results = options.get('unicode_results', False)
timeout = options.get('connection_timeout', 0)
retries = options.get('connection_retries', 5)
Expand All @@ -353,6 +373,8 @@ def get_new_connection(self, conn_params):
setencoding = options.get('setencoding', None)
setdecoding = options.get('setdecoding', None)

connstr = self._build_connection_string(conn_params, driver)

conn = None
retry_count = 0
need_to_retry = False
Expand All @@ -364,10 +386,39 @@ def get_new_connection(self, conn_params):
args['attrs_before'] = {
1256: prepare_token_for_odbc(conn_params['TOKEN'])
}

# Track if we've attempted fallback to v17
attempted_v17_fallback = False

while conn is None:
try:
conn = Database.connect(connstr, **args)
except Exception as e:
# If driver not explicitly set and v18 failed with driver not found,
# try falling back to v17
if (not driver_explicitly_set and
not attempted_v17_fallback and
self._is_driver_not_found_error(e) and
'ODBC Driver 18' in driver):

attempted_v17_fallback = True
driver = 'ODBC Driver 17 for SQL Server'
connstr = self._build_connection_string(conn_params, driver)

logger.warning(
"ODBC Driver 18 for SQL Server not found. "
"Falling back to ODBC Driver 17 for SQL Server. "
"SECURITY WARNING: ODBC Driver 18 has more secure defaults. "
"To resolve this warning, consider one of the following options "
"(in order of recommendation): "
"(1) Install ODBC Driver 18 and configure a valid certificate on the server, "
"(2) Install ODBC Driver 18 and set 'TrustServerCertificate': 'yes' in OPTIONS extra_params "
"if you trust the server, "
"(3) Explicitly set 'driver': 'ODBC Driver 17 for SQL Server' in OPTIONS "
"to suppress this warning."
)
continue

for error_number in self._transient_error_numbers:
if error_number in e.args[1]:
if error_number in e.args[1] and retry_count < retries:
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
'pyodbc>=3.0',
'pytz',
],
extras_require={
'test': ['unittest-xml-reporting>=3.2.0'],
},
package_data={'mssql': ['regex_clr.dll']},
classifiers=CLASSIFIERS,
keywords='django',
Expand Down
Loading
Loading