From: Mike Bayer Date: Sun, 23 Sep 2012 22:09:18 +0000 (-0400) Subject: - got firebird running X-Git-Tag: rel_0_8_0b1~133 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=444abbe84722e52ff453542e65a6d8e2208cbc50;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - got firebird running - add some failure cases - [bug] Firebird now uses strict "ansi bind rules" so that bound parameters don't render in the columns clause of a statement - they render literally instead. - [bug] Support for passing datetime as date when using the DateTime type with Firebird; other dialects support this. --- diff --git a/CHANGES b/CHANGES index a9cd16dd42..a1571c90e2 100644 --- a/CHANGES +++ b/CHANGES @@ -689,6 +689,15 @@ underneath "0.7.xx". no length is attempted to be emitted, same way as MySQL. [ticket:2505] + - [bug] Firebird now uses strict "ansi bind rules" + so that bound parameters don't render in the + columns clause of a statement - they render + literally instead. + + - [bug] Support for passing datetime as date when + using the DateTime type with Firebird; other + dialects support this. + - mysql - [bug] Dialect no longer emits expensive server collations query, as well as server casing, diff --git a/lib/sqlalchemy/dialects/firebird/base.py b/lib/sqlalchemy/dialects/firebird/base.py index e8e60da2b1..c56cde1313 100644 --- a/lib/sqlalchemy/dialects/firebird/base.py +++ b/lib/sqlalchemy/dialects/firebird/base.py @@ -126,7 +126,7 @@ RESERVED_WORDS = set([ class _StringType(sqltypes.String): """Base for Firebird string types.""" - def __init__(self, charset = None, **kw): + def __init__(self, charset=None, **kw): self.charset = charset super(_StringType, self).__init__(**kw) @@ -134,17 +134,28 @@ class VARCHAR(_StringType, sqltypes.VARCHAR): """Firebird VARCHAR type""" __visit_name__ = 'VARCHAR' - def __init__(self, length = None, **kwargs): + def __init__(self, length=None, **kwargs): super(VARCHAR, self).__init__(length=length, **kwargs) class CHAR(_StringType, sqltypes.CHAR): """Firebird CHAR type""" __visit_name__ = 'CHAR' - def __init__(self, length = None, **kwargs): + def __init__(self, length=None, **kwargs): super(CHAR, self).__init__(length=length, **kwargs) + +class _FBDateTime(sqltypes.DateTime): + def bind_processor(self, dialect): + def process(value): + if type(value) == datetime.date: + return datetime.datetime(value.year, value.month, value.day) + else: + return value + return process + colspecs = { + sqltypes.DateTime: _FBDateTime } ischema_names = { @@ -204,12 +215,17 @@ class FBTypeCompiler(compiler.GenericTypeCompiler): class FBCompiler(sql.compiler.SQLCompiler): """Firebird specific idiosyncrasies""" + ansi_bind_rules = True + #def visit_contains_op_binary(self, binary, operator, **kw): # cant use CONTAINING b.c. it's case insensitive. #def visit_notcontains_op_binary(self, binary, operator, **kw): # cant use NOT CONTAINING b.c. it's case insensitive. + def visit_now_func(self, fn, **kw): + return "CURRENT_TIMESTAMP" + def visit_startswith_op_binary(self, binary, operator, **kw): return '%s STARTING WITH %s' % ( binary.left._compiler_dispatch(self, **kw), @@ -261,7 +277,7 @@ class FBCompiler(sql.compiler.SQLCompiler): visit_char_length_func = visit_length_func - def function_argspec(self, func, **kw): + def _function_argspec(self, func, **kw): # TODO: this probably will need to be # narrowed to a fixed list, some no-arg functions # may require parens - see similar example in the oracle diff --git a/test/engine/test_execute.py b/test/engine/test_execute.py index 6a08cab69c..156a9fc35d 100644 --- a/test/engine/test_execute.py +++ b/test/engine/test_execute.py @@ -47,9 +47,9 @@ class ExecuteTest(fixtures.TestBase): @testing.fails_on("postgresql+pg8000", "pg8000 still doesn't allow single % without params") def test_no_params_option(self): - stmt = "SELECT '%'" - if testing.against('oracle'): - stmt += " FROM DUAL" + stmt = "SELECT '%'" + testing.db.dialect.statement_compiler( + testing.db.dialect, None).default_from() + conn = testing.db.connect() result = conn.\ execution_options(no_parameters=True).\ @@ -1181,7 +1181,7 @@ class EngineEventsTest(fixtures.TestBase): ('INSERT INTO t1 (c1, c2)', { 'c2': 'some data', 'c1': 5}, (5, 'some data')), - ('SELECT lower', {'lower_2': 'Foo'}, + ('SELECT lower', {'lower_2': 'Foo'}, ('Foo', )), ('INSERT INTO t1 (c1, c2)', {'c2': 'foo', 'c1': 6}, @@ -1447,7 +1447,7 @@ class ProxyConnectionTest(fixtures.TestBase): ('CREATE TABLE t1', {}, ()), ('INSERT INTO t1 (c1, c2)', {'c2': 'some data', 'c1' : 5}, (5, 'some data')), - ('SELECT lower', {'lower_2': 'Foo'}, + ('SELECT lower', {'lower_2': 'Foo'}, ('Foo', )), ('INSERT INTO t1 (c1, c2)', {'c2': 'foo', 'c1': 6}, (6, 'foo')), diff --git a/test/lib/requires.py b/test/lib/requires.py index a7a7552daa..923dfb80d0 100644 --- a/test/lib/requires.py +++ b/test/lib/requires.py @@ -433,6 +433,8 @@ def selectone(fn): return _chain_decorators_on( fn, skip_if(lambda: testing.against('oracle'), + "non-standard SELECT scalar syntax"), + skip_if(lambda: testing.against('firebird'), "non-standard SELECT scalar syntax") ) diff --git a/test/orm/test_froms.py b/test/orm/test_froms.py index 546c662047..f70f08470b 100644 --- a/test/orm/test_froms.py +++ b/test/orm/test_froms.py @@ -1125,6 +1125,7 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL): @testing.fails_on('postgresql+zxjdbc', "zxjdbc parses the SQL itself before passing on " "to PG, doesn't parse this") + @testing.fails_on("firebird", "unknown") def test_values_with_boolean_selects(self): """Tests a values clause that works with select boolean evaluations""" @@ -1314,6 +1315,7 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL): eq_(results, [(User(name='jack'), 'jack')]) self.assert_sql_count(testing.db, go, 1) + @testing.fails_on("firebird", "unknown") @testing.fails_on('postgresql+pg8000', "'type oid 705 not mapped to py type' (due to literal)") def test_self_referential(self): Order = self.classes.Order diff --git a/test/orm/test_query.py b/test/orm/test_query.py index 6f9ca3c7b4..38739afffd 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -402,16 +402,6 @@ class GetTest(QueryTest): assert u.addresses[0].email_address == 'jack@bean.com' assert u.orders[1].items[2].description == 'item 5' - @testing.fails_on_everything_except('sqlite', '+pyodbc', '+zxjdbc', 'mysql+oursql') - def test_query_str(self): - User = self.classes.User - - s = create_session() - q = s.query(User).filter(User.id==1) - eq_( - str(q).replace('\n',''), - 'SELECT users.id AS users_id, users.name AS users_name FROM users WHERE users.id = ?' - ) class InvalidGenerationsTest(QueryTest, AssertsCompiledSQL): def test_no_limit_offset(self): @@ -1499,7 +1489,7 @@ class SetOpsTest(QueryTest, AssertsCompiledSQL): ) - @testing.fails_on('mysql', "mysql doesn't support intersect") + @testing.requires.intersect def test_intersect(self): User = self.classes.User diff --git a/test/sql/test_case_statement.py b/test/sql/test_case_statement.py index 994016c2ff..7b8ae88db3 100644 --- a/test/sql/test_case_statement.py +++ b/test/sql/test_case_statement.py @@ -98,8 +98,10 @@ class CaseTest(fixtures.TestBase, AssertsCompiledSQL): assert_raises(exc.ArgumentError, case, [("x", "y")]) - self.assert_compile(case([("x", "y")], value=t.c.col1), "CASE test.col1 WHEN :param_1 THEN :param_2 END") - self.assert_compile(case([(t.c.col1==7, "y")], else_="z"), "CASE WHEN (test.col1 = :col1_1) THEN :param_1 ELSE :param_2 END") + self.assert_compile(case([("x", "y")], value=t.c.col1), + "CASE test.col1 WHEN :param_1 THEN :param_2 END") + self.assert_compile(case([(t.c.col1 == 7, "y")], else_="z"), + "CASE WHEN (test.col1 = :col1_1) THEN :param_1 ELSE :param_2 END") def test_text_doesnt_explode(self): @@ -113,10 +115,16 @@ class CaseTest(fixtures.TestBase, AssertsCompiledSQL): ))]).order_by(info_table.c.info), ]: - eq_(s.execute().fetchall(), [ - (u'no', ), (u'no', ), (u'no', ), (u'yes', ), - (u'no', ), (u'no', ), - ]) + if testing.against("firebird"): + eq_(s.execute().fetchall(), [ + ('no ', ), ('no ', ), ('no ', ), ('yes', ), + ('no ', ), ('no ', ), + ]) + else: + eq_(s.execute().fetchall(), [ + ('no', ), ('no', ), ('no', ), ('yes', ), + ('no', ), ('no', ), + ]) diff --git a/test/sql/test_query.py b/test/sql/test_query.py index 642da8dece..70e3e97a88 100644 --- a/test/sql/test_query.py +++ b/test/sql/test_query.py @@ -234,7 +234,6 @@ class QueryTest(fixtures.TestBase): l.append(row) self.assert_(len(l) == 3) - @testing.fails_on('firebird', "kinterbasdb doesn't send full type information") @testing.requires.subqueries def test_anonymous_rows(self): users.insert().execute( @@ -710,7 +709,7 @@ class QueryTest(fixtures.TestBase): use_labels=labels), [(3, 'a'), (2, 'b'), (1, None)]) - @testing.fails_on('mssql+pyodbc', + @testing.fails_on('mssql+pyodbc', "pyodbc result row doesn't support slicing") def test_column_slices(self): users.insert().execute(user_id=1, user_name='john') @@ -1203,7 +1202,6 @@ class QueryTest(fixtures.TestBase): assert len(r) == 0 @testing.emits_warning('.*empty sequence.*') - @testing.fails_on('firebird', 'uses sql-92 bind rules') def test_literal_in(self): """similar to test_bind_in but use a bind with a value.""" @@ -1414,7 +1412,7 @@ class TableInsertTest(fixtures.TablesTest): returning=(1, 5) ) - @testing.fails_on('mssql', + @testing.fails_on('mssql', "lowercase table doesn't support identity insert disable") def test_direct_params(self): t = self._fixture() @@ -1424,7 +1422,7 @@ class TableInsertTest(fixtures.TablesTest): inserted_primary_key=[] ) - @testing.fails_on('mssql', + @testing.fails_on('mssql', "lowercase table doesn't support identity insert disable") @testing.requires.returning def test_direct_params_returning(self): diff --git a/test/sql/test_types.py b/test/sql/test_types.py index e00c08ad24..ff22ea4de6 100644 --- a/test/sql/test_types.py +++ b/test/sql/test_types.py @@ -818,7 +818,7 @@ class UnicodeTest(fixtures.TestBase, AssertsExecutionResults): # lambda: testing.db_spec("postgresql")(testing.db), # "pg8000 and psycopg2 both have issues here in py3k" # ) - @testing.skip_if(lambda: testing.db_spec('mssql+mxodbc'), + @testing.skip_if(lambda: testing.db_spec('mssql+mxodbc'), "unsupported behavior") def test_ignoring_unicode_error(self): """checks String(unicode_error='ignore') is passed to underlying codec.""" @@ -1023,7 +1023,7 @@ class BinaryTest(fixtures.TestBase, AssertsExecutionResults): __excluded_on__ = ( ('mysql', '<', (4, 1, 1)), # screwy varbinary types ) - + @classmethod def setup_class(cls): global binary_table, MyPickleType, metadata @@ -1140,7 +1140,7 @@ class ExpressionTest(fixtures.TestBase, AssertsExecutionResults, AssertsCompiled return value / 10 return process def adapt_operator(self, op): - return {operators.add:operators.sub, + return {operators.add:operators.sub, operators.sub:operators.add}.get(op, op) class MyTypeDec(types.TypeDecorator): @@ -1498,7 +1498,7 @@ class DateTest(fixtures.TestBase, AssertsExecutionResults): def teardown_class(cls): users_with_date.drop() - def testdate(self): + def test_date_roundtrip(self): global insert_data l = map(tuple, @@ -1506,10 +1506,10 @@ class DateTest(fixtures.TestBase, AssertsExecutionResults): self.assert_(l == insert_data, 'DateTest mismatch: got:%s expected:%s' % (l, insert_data)) - def testtextdate(self): + def test_text_date_roundtrip(self): x = testing.db.execute(text( "select user_datetime from query_users_with_date", - typemap={'user_datetime':DateTime})).fetchall() + typemap={'user_datetime': DateTime})).fetchall() self.assert_(isinstance(x[0][0], datetime.datetime)) @@ -1518,13 +1518,14 @@ class DateTest(fixtures.TestBase, AssertsExecutionResults): bindparams=[bindparam('somedate', type_=types.DateTime)]), somedate=datetime.datetime(2005, 11, 10, 11, 52, 35)).fetchall() - def testdate2(self): + def test_date_mixdatetime_roundtrip(self): meta = MetaData(testing.db) t = Table('testdate', meta, - Column('id', Integer, + Column('id', Integer, Sequence('datetest_id_seq', optional=True), primary_key=True), - Column('adate', Date), Column('adatetime', DateTime)) + Column('adate', Date), + Column('adatetime', DateTime)) t.create(checkfirst=True) try: d1 = datetime.date(2007, 10, 30) @@ -1540,8 +1541,14 @@ class DateTest(fixtures.TestBase, AssertsExecutionResults): # test mismatched date/datetime t.insert().execute(adate=d2, adatetime=d2) - eq_(select([t.c.adate, t.c.adatetime], t.c.adate==d1).execute().fetchall(), [(d1, d2)]) - eq_(select([t.c.adate, t.c.adatetime], t.c.adate==d1).execute().fetchall(), [(d1, d2)]) + eq_( + select([t.c.adate, t.c.adatetime], t.c.adate == d1)\ + .execute().fetchall(), + [(d1, d2)]) + eq_( + select([t.c.adate, t.c.adatetime], t.c.adate == d1)\ + .execute().fetchall(), + [(d1, d2)]) finally: t.drop(checkfirst=True) @@ -1705,6 +1712,9 @@ class NumericTest(fixtures.TestBase): ) @testing.fails_on('postgresql+pg8000', "pg-8000 does native decimal but truncates the decimals.") + @testing.fails_on("firebird", + "database and/or driver truncates decimal places." + ) def test_numeric_no_decimal(self): numbers = set([ decimal.Decimal("1.000") @@ -1762,7 +1772,7 @@ class NumericRawSQLTest(fixtures.TestBase): assert isinstance(val, float) # some DBAPIs have unusual float handling - if testing.against('oracle+cx_oracle', 'mysql+oursql'): + if testing.against('oracle+cx_oracle', 'mysql+oursql', 'firebird'): eq_(round_decimal(val, 3), 46.583) else: eq_(val, 46.583)