--- /dev/null
+.. change::
+ :tags: bug, mssql
+ :tickets: 4154
+
+ Fixed regression in 1.2 where newly repaired quoting
+ of collation names in :ticket:`3785` breaks SQL Server,
+ which explicitly does not understand a quoted collation
+ name. Whether or not mixed-case collation names are
+ quoted or not is now deferred down to a dialect-level
+ decision so that each dialect can prepare these identifiers
+ directly.
reserved_words = RESERVED_WORDS
def __init__(self, dialect):
- super(MSIdentifierPreparer, self).__init__(dialect, initial_quote='[',
- final_quote=']')
+ super(MSIdentifierPreparer, self).__init__(
+ dialect, initial_quote='[',
+ final_quote=']', quote_case_sensitive_collations=False)
def _escape_identifier(self, value):
return value
-
def quote_schema(self, schema, force=None):
"""Prepare a quoted table and schema name."""
self.preparer.quote(tablename) + \
"." + name
+ def visit_collation(self, element, **kw):
+ return self.preparer.format_collation(element.collation)
+
def visit_fromclause(self, fromclause, **kwargs):
return fromclause.name
schema_for_object = schema._schema_getter(None)
def __init__(self, dialect, initial_quote='"',
- final_quote=None, escape_quote='"', omit_schema=False):
+ final_quote=None, escape_quote='"',
+ quote_case_sensitive_collations=True, omit_schema=False):
"""Construct a new ``IdentifierPreparer`` object.
initial_quote
self.escape_quote = escape_quote
self.escape_to_quote = self.escape_quote * 2
self.omit_schema = omit_schema
+ self.quote_case_sensitive_collations = quote_case_sensitive_collations
self._strings = {}
self._double_percents = self.dialect.paramstyle in ('format', 'pyformat')
else:
return ident
+ def format_collation(self, collation_name):
+ if self.quote_case_sensitive_collations:
+ return self.quote(collation_name)
+ else:
+ return collation_name
+
def format_sequence(self, sequence, use_schema=True):
name = self.quote(sequence.name)
expr = _literal_as_binds(expression)
return BinaryExpression(
expr,
- ColumnClause(collation),
+ CollationClause(collation),
operators.collate, type_=expr.type)
return c
+class CollationClause(ColumnElement):
+ __visit_name__ = "collation"
+
+ def __init__(self, collation):
+ self.collation = collation
+
+
class _IdentifiedClause(Executable, ClauseElement):
__visit_name__ = 'identified'
"""
return exclusions.closed()
+ @property
+ def order_by_collation(self):
+ def check(config):
+ try:
+ self.get_order_by_collation(config)
+ return False
+ except NotImplementedError:
+ return True
+
+ return exclusions.skip_if(check)
+
+ def get_order_by_collation(self, config):
+ raise NotImplementedError()
+
@property
def unicode_connections(self):
"""Target driver must support non-ASCII characters being passed at
from ..schema import Table, Column
+class CollateTest(fixtures.TablesTest):
+ __backend__ = True
+
+ @classmethod
+ def define_tables(cls, metadata):
+ Table("some_table", metadata,
+ Column('id', Integer, primary_key=True),
+ Column('data', String(100))
+ )
+
+ @classmethod
+ def insert_data(cls):
+ config.db.execute(
+ cls.tables.some_table.insert(),
+ [
+ {"id": 1, "data": "collate data1"},
+ {"id": 2, "data": "collate data2"},
+ ]
+ )
+
+ def _assert_result(self, select, result):
+ eq_(
+ config.db.execute(select).fetchall(),
+ result
+ )
+
+ @testing.requires.order_by_collation
+ def test_collate_order_by(self):
+ collation = testing.requires.get_order_by_collation(testing.config)
+
+ self._assert_result(
+ select([self.tables.some_table]).
+ order_by(self.tables.some_table.c.data.collate(collation).asc()),
+ [
+ (1, "collate data1"),
+ (2, "collate data2"),
+ ]
+ )
+
+
class OrderByLabelTest(fixtures.TablesTest):
"""Test the dialect sends appropriate ORDER BY expressions when
labels are used.
'SELECT test_schema.sometable.somecolumn '
'FROM test_schema.sometable WITH (NOLOCK)')
+ def test_select_w_order_by_collate(self):
+ m = MetaData()
+ t = Table('sometable', m, Column('somecolumn', String))
+
+ self.assert_compile(
+ select([t]).
+ order_by(
+ t.c.somecolumn.collate("Latin1_General_CS_AS_KS_WS_CI").asc()),
+ "SELECT sometable.somecolumn FROM sometable "
+ "ORDER BY sometable.somecolumn COLLATE "
+ "Latin1_General_CS_AS_KS_WS_CI ASC"
+
+ )
+
def test_join_with_hint(self):
t1 = table('t1',
column('a', Integer),
('mssql', None, None, 'only simple labels allowed')
])
+ def get_order_by_collation(self, config):
+ lookup = {
+
+ # will raise without quoting
+ "postgresql": "POSIX",
+
+ "mysql": "latin1_general_ci",
+ "sqlite": "NOCASE",
+
+ # will raise *with* quoting
+ "mssql": "Latin1_General_CI_AS"
+ }
+ try:
+ return lookup[config.db.name]
+ except KeyError:
+ raise NotImplementedError()
+
@property
def skip_mysql_on_windows(self):
"""Catchall for a large variety of MySQL on Windows failures"""
[]).compile,
dialect=empty_in_dialect)
+ def test_collate(self):
+ # columns clause
+ self.assert_compile(
+ select([column('x').collate('bar')]),
+ "SELECT x COLLATE bar AS anon_1"
+ )
+
+ # WHERE clause
+ self.assert_compile(
+ select([column('x')]).where(column('x').collate('bar') == 'foo'),
+ "SELECT x WHERE (x COLLATE bar) = :param_1"
+ )
+
+ # ORDER BY clause
+ self.assert_compile(
+ select([column('x')]).order_by(column('x').collate('bar')),
+ "SELECT x ORDER BY x COLLATE bar"
+ )
+
def test_literal(self):
self.assert_compile(select([literal('foo')]),
from sqlalchemy import MetaData, Table, Column, Integer, select, \
ForeignKey, Index, CheckConstraint, inspect, column
-from sqlalchemy import sql, schema
+from sqlalchemy import sql, schema, types as sqltypes
from sqlalchemy.sql import compiler
from sqlalchemy.testing import fixtures, AssertsCompiledSQL, eq_
from sqlalchemy import testing
self.assert_compile(
column('foo').collate('fr_FR'),
- 'foo COLLATE "fr_FR"'
+ 'foo COLLATE "fr_FR"',
+ dialect="postgresql"
)
self.assert_compile(
dialect="mysql"
)
+ self.assert_compile(
+ column('foo').collate('SQL_Latin1_General_CP1_CI_AS'),
+ 'foo COLLATE SQL_Latin1_General_CP1_CI_AS',
+ dialect="mssql"
+ )
+
def test_join(self):
# Lower case names, should not quote
metadata = MetaData()