Skip to content
This repository was archived by the owner on Aug 7, 2018. It is now read-only.
Closed
Changes from all 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
99 changes: 56 additions & 43 deletions sql_server/pyodbc/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@

warnings.filterwarnings('error', 'The DATABASE_ODBC.+ is deprecated', DeprecationWarning, __name__, 0)

collation = 'Latin1_General_CI_AS'
default_collation = 'Latin1_General_CI_AS'
if hasattr(settings, 'DATABASE_COLLATION'):
warnings.warn(
"The DATABASE_COLLATION setting is going to be deprecated, use DATABASE_OPTIONS['collation'] instead.",
"The DATABASE_COLLATION setting is going to be deprecated, use DATABASE_OPTIONS['default_collation'] instead.",
DeprecationWarning
)
collation = settings.DATABASE_COLLATION
default_collation = settings.DATABASE_COLLATION
elif hasattr(settings, 'DATABASE_OPTIONS') and 'collation' in settings.DATABASE_OPTIONS:
collation = settings.DATABASE_OPTIONS['collation']
default_collation = settings.DATABASE_OPTIONS['collation']

deprecated = (
('DATABASE_ODBC_DRIVER', 'driver'),
Expand Down Expand Up @@ -91,39 +91,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
unicode_results = False
datefirst = 7

# Collations: http://msdn2.microsoft.com/en-us/library/ms184391.aspx
# http://msdn2.microsoft.com/en-us/library/ms179886.aspx
# T-SQL LIKE: http://msdn2.microsoft.com/en-us/library/ms179859.aspx
# Full-Text search: http://msdn2.microsoft.com/en-us/library/ms142571.aspx
# CONTAINS: http://msdn2.microsoft.com/en-us/library/ms187787.aspx
# FREETEXT: http://msdn2.microsoft.com/en-us/library/ms176078.aspx

operators = {
# Since '=' is used not only for string comparision there is no way
# to make it case (in)sensitive. It will simply fallback to the
# database collation.
'exact': '= %s',
'iexact': "= UPPER(%s)",
'contains': "LIKE %s ESCAPE '\\' COLLATE " + collation,
'icontains': "LIKE UPPER(%s) ESCAPE '\\' COLLATE "+ collation,
'gt': '> %s',
'gte': '>= %s',
'lt': '< %s',
'lte': '<= %s',
'startswith': "LIKE %s ESCAPE '\\' COLLATE " + collation,
'endswith': "LIKE %s ESCAPE '\\' COLLATE " + collation,
'istartswith': "LIKE UPPER(%s) ESCAPE '\\' COLLATE " + collation,
'iendswith': "LIKE UPPER(%s) ESCAPE '\\' COLLATE " + collation,

# TODO: remove, keep native T-SQL LIKE wildcards support
# or use a "compatibility layer" and replace '*' with '%'
# and '.' with '_'
'regex': 'LIKE %s COLLATE ' + collation,
'iregex': 'LIKE %s COLLATE ' + collation,

# TODO: freetext, full-text contains...
}

def __init__(self, *args, **kwargs):
super(DatabaseWrapper, self).__init__(*args, **kwargs)

Expand Down Expand Up @@ -220,6 +187,9 @@ def _cursor(self):
else:
cstr_parts.append('SERVERNAME=%s' % host_str)

self.collation = options.get('collation', default_collation)
self.client_encoding = options.get('client_encoding', default_collation)

if user_str:
cstr_parts.append('UID=%s;PWD=%s' % (user_str, passwd_str))
else:
Expand Down Expand Up @@ -277,25 +247,64 @@ def _cursor(self):
if self.drv_name.startswith('LIBTDSODBC') and not self.connection.autocommit:
self.connection.commit()

return CursorWrapper(cursor, self.driver_needs_utf8)
return CursorWrapper(cursor, self.driver_needs_utf8,
self.client_encoding)

# Collations: http://msdn2.microsoft.com/en-us/library/ms184391.aspx
# http://msdn2.microsoft.com/en-us/library/ms179886.aspx
# T-SQL LIKE: http://msdn2.microsoft.com/en-us/library/ms179859.aspx
# Full-Text search: http://msdn2.microsoft.com/en-us/library/ms142571.aspx
# CONTAINS: http://msdn2.microsoft.com/en-us/library/ms187787.aspx
# FREETEXT: http://msdn2.microsoft.com/en-us/library/ms176078.aspx
@property
def operators(self):
return {
# Since '=' is used not only for string comparision there is no way
# to make it case (in)sensitive. It will simply fallback to the
# database collation.
'exact': '= %s',
'iexact': "= UPPER(%s)",
'contains': "LIKE %s ESCAPE '\\' COLLATE " + self.collation,
'icontains': "LIKE UPPER(%s) ESCAPE '\\' COLLATE "+ self.collation,
'gt': '> %s',
'gte': '>= %s',
'lt': '< %s',
'lte': '<= %s',
'startswith': "LIKE %s ESCAPE '\\' COLLATE " + self.collation,
'endswith': "LIKE %s ESCAPE '\\' COLLATE " + self.collation,
'istartswith': "LIKE UPPER(%s) ESCAPE '\\' COLLATE " + self.collation,
'iendswith': "LIKE UPPER(%s) ESCAPE '\\' COLLATE " + self.collation,

# TODO: remove, keep native T-SQL LIKE wildcards support
# or use a "compatibility layer" and replace '*' with '%'
# and '.' with '_'
'regex': 'LIKE %s COLLATE ' + self.collation,
'iregex': 'LIKE %s COLLATE ' + self.collation,

# TODO: freetext, full-text contains...
}


class CursorWrapper(object):
"""
A wrapper around the pyodbc's cursor that takes in account a) some pyodbc
DB-API 2.0 implementation and b) some common ODBC driver particularities.
"""
def __init__(self, cursor, driver_needs_utf8):
def __init__(self, cursor, driver_needs_utf8, client_encoding):
self.cursor = cursor
self.driver_needs_utf8 = driver_needs_utf8
self.client_encoding = client_encoding
self.last_sql = ''
self.last_params = ()

def format_sql(self, sql, n_params=None):
if self.driver_needs_utf8 and isinstance(sql, unicode):
# FreeTDS (and other ODBC drivers?) doesn't support Unicode
# yet, so we need to encode the SQL clause itself in utf-8
sql = sql.encode('utf-8')
try:
sql = sql.encode(self.client_encoding)
except:
print sql
raise

# pyodbc uses '?' instead of '%s' as parameter placeholder.
if n_params is not None:
Expand All @@ -312,7 +321,7 @@ def format_params(self, params):
if self.driver_needs_utf8:
# FreeTDS (and other ODBC drivers?) doesn't support Unicode
# yet, so we need to encode parameters in utf-8
fp.append(p.encode('utf-8'))
fp.append(p.encode(self.client_encoding))
else:
fp.append(p)

Expand Down Expand Up @@ -366,7 +375,11 @@ def format_row(self, row):
out = []
for f in row:
if isinstance(f, str):
out.append(f.decode('utf-8'))
try:
out.append(f.decode(self.client_encoding))
except:
print f
raise
else:
out.append(f)

Expand Down