is emitted once as per the warning filter settings,
and large string values don't pollute the output.
[ticket:1822]
+
+ - The argument to "ESCAPE" of a LIKE operator or similar
+ is passed through render_literal_value(), which may
+ implement escaping of backslashes. [ticket:1400]
- Fixed bug in Enum type which blew away native_enum
flag when used with TypeDecorators or other adaption
among others, fixes [ticket:1829] regarding declarative
mixins
+- postgresql
+ - render_literal_value() is overridden which escapes
+ backslashes, currently applies to the ESCAPE clause
+ of LIKE and similar expressions.
+ Ultimately this will have to detect the value of
+ "standard_conforming_strings" for full behavior.
+ [ticket:1400]
+
- mysql
- MySQL dialect doesn't emit CAST() for MySQL version
detected < 4.0.2. This allows the unicode
check on connect to proceed. [ticket:1826]
+ - MySQL dialect now detects NO_BACKSLASH_ESCAPES sql
+ mode, in addition to ANSI_QUOTES.
+
+ - render_literal_value() is overridden which escapes
+ backslashes, currently applies to the ESCAPE clause
+ of LIKE and similar expressions. This behavior
+ is derived from detecting the value of
+ NO_BACKSLASH_ESCAPES. [ticket:1400]
+
- oracle:
- Fixed ora-8 compatibility flags such that they
don't cache a stale value from before the first
return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
+ def render_literal_value(self, value, type_):
+ value = super(MySQLCompiler, self).render_literal_value(value, type_)
+ if self.dialect._backslash_escapes:
+ value = value.replace('\\', '\\\\')
+ return value
+
def get_select_precolumns(self, select):
if isinstance(select._distinct, basestring):
return select._distinct.upper() + " "
ischema_names = ischema_names
preparer = MySQLIdentifierPreparer
+ # default SQL compilation settings -
+ # these are modified upon initialize(),
+ # i.e. first connect
+ _backslash_escapes = True
+ _server_ansiquotes = False
+
def __init__(self, use_ansiquotes=None, **kwargs):
default.DefaultDialect.__init__(self, **kwargs)
self._connection_charset = self._detect_charset(connection)
self._server_casing = self._detect_casing(connection)
self._server_collations = self._detect_collations(connection)
- self._server_ansiquotes = self._detect_ansiquotes(connection)
+ self._detect_ansiquotes(connection)
if self._server_ansiquotes:
# if ansiquotes == True, build a new IdentifierPreparer
# with the new setting
mode_no = int(mode)
mode = (mode_no | 4 == mode_no) and 'ANSI_QUOTES' or ''
- return 'ANSI_QUOTES' in mode
-
+ self._server_ansiquotes = 'ANSI_QUOTES' in mode
+
+ # as of MySQL 5.0.1
+ self._backslash_escapes = 'NO_BACKSLASH_ESCAPES' not in mode
+
def _show_create_table(self, connection, table, charset=None,
full_name=None):
"""Run SHOW CREATE TABLE for a ``Table``."""
def visit_ilike_op(self, binary, **kw):
escape = binary.modifiers.get("escape", None)
return '%s ILIKE %s' % (self.process(binary.left), self.process(binary.right)) \
- + (escape and ' ESCAPE \'%s\'' % escape or '')
+ + (escape and
+ (' ESCAPE ' + self.render_literal_value(escape, None))
+ or '')
def visit_notilike_op(self, binary, **kw):
escape = binary.modifiers.get("escape", None)
return '%s NOT ILIKE %s' % (self.process(binary.left), self.process(binary.right)) \
- + (escape and ' ESCAPE \'%s\'' % escape or '')
+ + (escape and
+ (' ESCAPE ' + self.render_literal_value(escape, None))
+ or '')
+
+ def render_literal_value(self, value, type_):
+ value = super(PGCompiler, self).render_literal_value(value, type_)
+ # TODO: need to inspect "standard_conforming_strings"
+ if self.dialect._backslash_escapes:
+ value = value.replace('\\', '\\\\')
+ return value
def visit_sequence(self, seq):
if seq.optional:
inspector = PGInspector
isolation_level = None
+ # TODO: need to inspect "standard_conforming_strings"
+ _backslash_escapes = True
+
def __init__(self, isolation_level=None, **kwargs):
default.DefaultDialect.__init__(self, **kwargs)
self.isolation_level = isolation_level
return '%s LIKE %s' % (
self.process(binary.left, **kw),
self.process(binary.right, **kw)) \
- + (escape and ' ESCAPE \'%s\'' % escape or '')
+ + (escape and
+ (' ESCAPE ' + self.render_literal_value(escape, None))
+ or '')
def visit_notlike_op(self, binary, **kw):
escape = binary.modifiers.get("escape", None)
return '%s NOT LIKE %s' % (
self.process(binary.left, **kw),
self.process(binary.right, **kw)) \
- + (escape and ' ESCAPE \'%s\'' % escape or '')
+ + (escape and
+ (' ESCAPE ' + self.render_literal_value(escape, None))
+ or '')
def visit_ilike_op(self, binary, **kw):
escape = binary.modifiers.get("escape", None)
return 'lower(%s) LIKE lower(%s)' % (
self.process(binary.left, **kw),
self.process(binary.right, **kw)) \
- + (escape and ' ESCAPE \'%s\'' % escape or '')
+ + (escape and
+ (' ESCAPE ' + self.render_literal_value(escape, None))
+ or '')
def visit_notilike_op(self, binary, **kw):
escape = binary.modifiers.get("escape", None)
return 'lower(%s) NOT LIKE lower(%s)' % (
self.process(binary.left, **kw),
self.process(binary.right, **kw)) \
- + (escape and ' ESCAPE \'%s\'' % escape or '')
+ + (escape and
+ (' ESCAPE ' + self.render_literal_value(escape, None))
+ or '')
def _operator_dispatch(self, operator, element, fn, **kw):
if util.callable(operator):
NULLTYPE = NullType()
BOOLEANTYPE = Boolean()
+STRINGTYPE = String()
# using VARCHAR/NCHAR so that we dont get the genericized "String"
# type which usually resolves to TEXT/CLOB
eq_(
gen(True, ['high_priority', sql.text('sql_cache')]),
'SELECT high_priority sql_cache DISTINCT q')
+
+ def test_backslash_escaping(self):
+ self.assert_compile(
+ sql.column('foo').like('bar', escape='\\'),
+ "foo LIKE %s ESCAPE '\\\\'"
+ )
+ dialect = mysql.dialect()
+ dialect._backslash_escapes=False
+ self.assert_compile(
+ sql.column('foo').like('bar', escape='\\'),
+ "foo LIKE %s ESCAPE '\\'",
+ dialect=dialect
+ )
+
def test_limit(self):
t = sql.table('t', sql.column('col1'), sql.column('col2'))
")ENGINE=InnoDB"
)
-
+class SQLModeDetectionTest(TestBase):
+ __only_on__ = 'mysql'
+
+ def _options(self, modes):
+ class SetOptions(object):
+ def first_connect(self, con, record):
+ self.connect(con, record)
+ def connect(self, con, record):
+ cursor = con.cursor()
+ cursor.execute("set sql_mode='%s'" % (",".join(modes)))
+ return engines.testing_engine(options={"listeners":[SetOptions()]})
+
+ def test_backslash_escapes(self):
+ engine = self._options(['NO_BACKSLASH_ESCAPES'])
+ c = engine.connect()
+ assert not engine.dialect._backslash_escapes
+ c.close()
+ engine.dispose()
+
+ engine = self._options([])
+ c = engine.connect()
+ assert engine.dialect._backslash_escapes
+ c.close()
+ engine.dispose()
+
+ def test_ansi_quotes(self):
+ engine = self._options(['ANSI_QUOTES'])
+ c = engine.connect()
+ assert engine.dialect._server_ansiquotes
+ c.close()
+ engine.dispose()
+
+ def test_combination(self):
+ engine = self._options(['ANSI_QUOTES,NO_BACKSLASH_ESCAPES'])
+ c = engine.connect()
+ assert engine.dialect._server_ansiquotes
+ assert not engine.dialect._backslash_escapes
+ c.close()
+ engine.dispose()
+
class RawReflectionTest(TestBase):
def setup(self):
dialect = mysql.dialect()
)
for expr, result in (
- (select([users.c.user_id]).where(users.c.user_name.startswith('apple')), [(1,)]),
- (select([users.c.user_id]).where(users.c.user_name.contains('i % t')), [(5,)]),
- (select([users.c.user_id]).where(users.c.user_name.endswith('anas')), [(3,)]),
+ (select([users.c.user_id]).\
+ where(users.c.user_name.startswith('apple')), [(1,)]),
+ (select([users.c.user_id]).\
+ where(users.c.user_name.contains('i % t')), [(5,)]),
+ (select([users.c.user_id]).\
+ where(
+ users.c.user_name.endswith('anas')
+ ), [(3,)]),
+ (select([users.c.user_id]).\
+ where(
+ users.c.user_name.contains('i % t', escape='\\')
+ ), [(5,)]),
):
eq_(expr.execute().fetchall(), result)
-
@testing.fails_on("firebird", "see dialect.test_firebird:MiscTest.test_percents_in_text")
@testing.fails_on("oracle", "neither % nor %% are accepted")
@testing.fails_on("+pg8000", "can't interpret result column from '%%'")