Skip to content
This repository was archived by the owner on Aug 7, 2018. It is now read-only.

New database options: collation and client_encoding #6

Open
wants to merge 2 commits into
base: django-1.4-encoding
Choose a base branch
from
Open
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
85 changes: 45 additions & 40 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.",
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', 'utf-8')

if user_str:
cstr_parts.append('UID=%s;PWD=%s' % (user_str, passwd_str))
else:
Expand Down Expand Up @@ -277,17 +247,52 @@ 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 = ()

Expand Down Expand Up @@ -358,15 +363,15 @@ def format_row(self, row):
(pyodbc Rows are not sliceable).
"""

if not self.driver_needs_utf8:
if not self.driver_needs_utf8 and self.client_encoding == 'utf-8':
return tuple(row)

# FreeTDS (and other ODBC drivers?) doesn't support Unicode
# yet, so we need to decode utf-8 data coming from the DB
out = []
for f in row:
if isinstance(f, str):
out.append(f.decode('utf-8'))
out.append(f.decode(self.client_encoding))
else:
out.append(f)

Expand Down