diff options
| -rw-r--r-- | CHANGES | 44 | ||||
| -rw-r--r-- | lib/sqlalchemy/dialects/sybase/base.py | 8 | ||||
| -rw-r--r-- | lib/sqlalchemy/dialects/sybase/pysybase.py | 17 | ||||
| -rw-r--r-- | lib/sqlalchemy/types.py | 5 | ||||
| -rw-r--r-- | test/sql/test_labels.py | 21 | ||||
| -rw-r--r-- | test/sql/test_types.py | 178 |
6 files changed, 142 insertions, 131 deletions
@@ -15,6 +15,11 @@ CHANGES relationship(), to eliminate confusion over the relational algebra term. relation() however will remain available in equal capacity for the foreseeable future. [ticket:1740] + + - Added "version_id_generator" argument to Mapper, this is a + callable that, given the current value of the "version_id_col", + returns the next version number. Can be used for alternate + versioning schemes such as uuid, timestamps. [ticket:1692] - Fixed bug in session.rollback() which involved not removing formerly "pending" objects from the session before @@ -27,6 +32,13 @@ CHANGES - Removed a lot of logging that nobody really cares about, logging that remains will respond to live changes in the log level. No significant overhead is added. [ticket:1719] + + - Fixed bug in session.merge() which prevented dict-like + collections from merging. + + - session.merge() works with relations that specifically + don't include "merge" in their cascade options - the target + is ignored completely. - session.merge() will not expire existing scalar attributes on an existing target if the target has a value for that @@ -35,6 +47,12 @@ CHANGES on existing items. Will still mark the attr as expired if the destination doesn't have the attr, though, which fulfills some contracts of deferred cols. [ticket:1681] + + - The "allow_null_pks" flag is now called "allow_partial_pks", + defaults to True, acts like it did in 0.5 again. Except, + it also is implemented within merge() such that a SELECT + won't be issued for an incoming instance with partially + NULL primary key if the flag is False. [ticket:1680] - Fixed bug in 0.6-reworked "many-to-one" optimizations such that a many-to-one that is against a non-primary key @@ -45,10 +63,6 @@ CHANGES and we can't pull from the local identity map on a non-primary key column. [ticket:1737] - - session.merge() works with relations that specifically - don't include "merge" in their cascade options - the target - is ignored completely. - - fixed internal error which would occur if calling has() or similar complex expression on a single-table inheritance relation(). [ticket:1731] @@ -96,14 +110,6 @@ CHANGES - Fixed bug in attribute history that inadvertently invoked __eq__ on mapped instances. - - Fixed bug in session.merge() which prevented dict-like - collections from merging. - - - For those who might use debug logging on - sqlalchemy.orm.strategies, most logging calls during row - loading have been removed. These were never very helpful - and cluttered up the code. - - Some internal streamlining of object loading grants a small speedup for large results, estimates are around 10-15%. Gave the "state" internals a good solid @@ -117,12 +123,6 @@ CHANGES was set to None, introduced in r6711 (cascade deleted items into session during add()). - - The "allow_null_pks" flag is now called "allow_partial_pks", - defaults to True, acts like it did in 0.5 again. Except, - it also is implemented within merge() such that a SELECT - won't be issued for an incoming instance with partially - NULL primary key if the flag is False. [ticket:1680] - - Calling query.order_by() or query.distinct() before calling query.select_from(), query.with_polymorphic(), or query.from_statement() raises an exception now instead of @@ -136,11 +136,6 @@ CHANGES INSERT and DELETE replaced by an UPDATE, to fail when version_id_col was in use. [ticket:1692] - - Added "version_id_generator" argument to Mapper, this is a - callable that, given the current value of the "version_id_col", - returns the next version number. Can be used for alternate - versioning schemes such as uuid, timestamps. [ticket:1692] - - sql - The most common result processors conversion function were moved to the new "processors" module. Dialect authors are @@ -350,7 +345,8 @@ CHANGES of MySQL keywords. [ticket:1634] - mssql - - Re-established initial support for pymssql. + - Re-established initial support for pymssql (not functional + yet, though) - Various fixes for implicit returning, reflection, etc. - the MS-SQL dialects aren't quite complete diff --git a/lib/sqlalchemy/dialects/sybase/base.py b/lib/sqlalchemy/dialects/sybase/base.py index 5814f70da..bdaab2eb7 100644 --- a/lib/sqlalchemy/dialects/sybase/base.py +++ b/lib/sqlalchemy/dialects/sybase/base.py @@ -370,6 +370,14 @@ class SybaseDialect(default.DefaultDialect): text("SELECT user_name() as user_name", typemap={'user_name':Unicode}) ) + def initialize(self, connection): + super(SybaseDialect, self).initialize(connection) + if self.server_version_info is not None and\ + self.server_version_info < (15, ): + self.max_identifier_length = 30 + else: + self.max_identifier_length = 255 + @reflection.cache def get_table_names(self, connection, schema=None, **kw): if schema is None: diff --git a/lib/sqlalchemy/dialects/sybase/pysybase.py b/lib/sqlalchemy/dialects/sybase/pysybase.py index 8944465ee..ee1938250 100644 --- a/lib/sqlalchemy/dialects/sybase/pysybase.py +++ b/lib/sqlalchemy/dialects/sybase/pysybase.py @@ -21,10 +21,18 @@ kind at this time. """ +from sqlalchemy import types as sqltypes, processors from sqlalchemy.dialects.sybase.base import SybaseDialect, \ SybaseExecutionContext, SybaseSQLCompiler +class _SybNumeric(sqltypes.Numeric): + def result_processor(self, dialect, type_): + if not self.asdecimal: + return processors.to_float + else: + return sqltypes.Numeric.result_processor(self, dialect, type_) + class SybaseExecutionContext_pysybase(SybaseExecutionContext): def set_ddl_autocommit(self, dbapi_connection, value): @@ -52,6 +60,11 @@ class SybaseDialect_pysybase(SybaseDialect): execution_ctx_cls = SybaseExecutionContext_pysybase statement_compiler = SybaseSQLCompiler_pysybase + colspecs={ + sqltypes.Numeric:_SybNumeric, + sqltypes.Float:sqltypes.Float + } + @classmethod def dbapi(cls): import Sybase @@ -69,7 +82,9 @@ class SybaseDialect_pysybase(SybaseDialect): cursor.execute(statement, param) def _get_server_version_info(self, connection): - return connection.scalar("select @@version_number") + vers = connection.scalar("select @@version_number") + # i.e. 15500, 15000, 12500 == (15, 5, 0, 0), (15, 0, 0, 0), (12, 5, 0, 0) + return (vers / 1000, vers % 1000 / 100, vers % 100 / 10, vers % 10) def is_disconnect(self, e): if isinstance(e, (self.dbapi.OperationalError, self.dbapi.ProgrammingError)): diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py index 3feac8f4f..16cd57f26 100644 --- a/lib/sqlalchemy/types.py +++ b/lib/sqlalchemy/types.py @@ -940,7 +940,10 @@ class Numeric(_DateAffinity, TypeEngine): return None else: # we're a "numeric", DBAPI returns floats, convert. - return processors.to_decimal_processor_factory(_python_Decimal, self.scale) + if self.scale is not None: + return processors.to_decimal_processor_factory(_python_Decimal, self.scale) + else: + return processors.to_decimal_processor_factory(_python_Decimal) else: if dialect.supports_native_decimal: return processors.to_float diff --git a/test/sql/test_labels.py b/test/sql/test_labels.py index bcac7c01d..f67ba9855 100644 --- a/test/sql/test_labels.py +++ b/test/sql/test_labels.py @@ -83,15 +83,18 @@ class LongLabelsTest(TestBase, AssertsCompiledSQL): (2, "data2"), ], repr(result) - r = s.limit(2).offset(1).execute() - result = [] - for row in r: - result.append((row[table1.c.this_is_the_primarykey_column], row[table1.c.this_is_the_data_column])) - assert result == [ - (2, "data2"), - (3, "data3"), - ], repr(result) - + @testing.requires.offset + def go(): + r = s.limit(2).offset(1).execute() + result = [] + for row in r: + result.append((row[table1.c.this_is_the_primarykey_column], row[table1.c.this_is_the_data_column])) + assert result == [ + (2, "data2"), + (3, "data3"), + ], repr(result) + go() + def test_table_alias_names(self): if testing.against('oracle'): self.assert_compile( diff --git a/test/sql/test_types.py b/test/sql/test_types.py index 9e82fc1ef..799e17f43 100644 --- a/test/sql/test_types.py +++ b/test/sql/test_types.py @@ -1075,7 +1075,7 @@ class DateTest(TestBase, AssertsExecutionResults): finally: t.drop(checkfirst=True) -class StringTest(TestBase, AssertsExecutionResults): +class StringTest(TestBase): @testing.requires.unbounded_varchar def test_nolength_string(self): @@ -1085,74 +1085,80 @@ class StringTest(TestBase, AssertsExecutionResults): foo.create() foo.drop() -class NumericTest(TestBase, AssertsExecutionResults): - @classmethod - def setup_class(cls): - global numeric_table, metadata +class NumericTest(TestBase): + def setup(self): + global metadata metadata = MetaData(testing.db) - numeric_table = Table('numeric_table', metadata, - Column('id', Integer, Sequence('numeric_id_seq', optional=True), primary_key=True), - Column('numericcol', Numeric(precision=10, scale=2, asdecimal=False)), - Column('floatcol', Float(precision=10, )), - Column('ncasdec', Numeric(precision=10, scale=2)), - Column('fcasdec', Float(precision=10, asdecimal=True)) + + def teardown(self): + metadata.drop_all() + + def _do_test(self, type_, input_, output, filter_ = None): + t = Table('t', metadata, Column('x', type_)) + t.create() + t.insert().execute([{'x':x} for x in input_]) + + result = set([row[0] for row in t.select().execute()]) + output = set(output) + if filter_: + result = set(filter_(x) for x in result) + output = set(filter_(x) for x in output) + eq_(result, output) + + def test_numeric_as_decimal(self): + self._do_test( + Numeric(precision=8, scale=4), + [15.7563, Decimal("15.7563")], + [Decimal("15.7563")], ) - metadata.create_all() - @classmethod - def teardown_class(cls): - metadata.drop_all() + def test_numeric_as_float(self): + if testing.against("oracle+cx_oracle"): + filter_ = lambda n:round(n, 5) + else: + filter_ = None - @engines.close_first - def teardown(self): - numeric_table.delete().execute() + self._do_test( + Numeric(precision=8, scale=4, asdecimal=False), + [15.7563, Decimal("15.7563")], + [15.7563], + filter_ = filter_ + ) - def test_decimal(self): - from decimal import Decimal - numeric_table.insert().execute( - numericcol=3.5, floatcol=5.6, ncasdec=12.4, fcasdec=15.75) - - numeric_table.insert().execute( - numericcol=Decimal("3.5"), floatcol=Decimal("5.6"), - ncasdec=Decimal("12.4"), fcasdec=Decimal("15.75")) - - l = numeric_table.select().order_by(numeric_table.c.id).execute().fetchall() - rounded = [ - (l[0][0], l[0][1], round(l[0][2], 5), l[0][3], l[0][4]), - (l[1][0], l[1][1], round(l[1][2], 5), l[1][3], l[1][4]), - ] - testing.eq_(rounded, [ - (1, 3.5, 5.6, Decimal("12.4"), Decimal("15.75")), - (2, 3.5, 5.6, Decimal("12.4"), Decimal("15.75")), - ]) + def test_float_as_decimal(self): + self._do_test( + Float(precision=8, asdecimal=True), + [15.7563, Decimal("15.7563")], + [Decimal("15.7563")], + filter_ = lambda n:round(n, 5) + ) + def test_float_as_float(self): + self._do_test( + Float(precision=8), + [15.7563, Decimal("15.7563")], + [15.7563], + filter_ = lambda n:round(n, 5) + ) + def test_precision_decimal(self): + numbers = set([ + decimal.Decimal("54.234246451650"), + decimal.Decimal("876734.594069654000"), + decimal.Decimal("0.004354"), + decimal.Decimal("900.0"), + ]) + if testing.against('sqlite', 'sybase+pysybase', 'oracle+cx_oracle'): + filter_ = lambda n:round_decimal(n, 11) + else: + filter_ = None - t = Table('t', MetaData(), Column('x', Numeric(precision=18, scale=12))) - t.create(testing.db) - try: - numbers = set( - [ - decimal.Decimal("54.234246451650"), - decimal.Decimal("876734.594069654000"), - decimal.Decimal("0.004354"), - decimal.Decimal("900.0"), - ]) - - testing.db.execute(t.insert(), [{'x':x} for x in numbers]) - - ret = set([row[0] for row in testing.db.execute(t.select()).fetchall()]) - - if testing.against('sqlite', 'sybase+pysybase', 'oracle+cx_oracle'): - numbers = set(round_decimal(n, 11) for n in numbers) - ret = set(round_decimal(n, 11) for n in ret) - else: - numbers = set(n for n in numbers) - ret = set(n for n in ret) - - eq_(numbers, ret) - finally: - t.drop(testing.db) + self._do_test( + Numeric(precision=18, scale=12), + numbers, + numbers, + filter_=filter_ + ) def test_enotation_decimal(self): """test exceedingly small decimals. @@ -1161,42 +1167,22 @@ class NumericTest(TestBase, AssertsExecutionResults): is greater than 6. """ - - t = Table('t', MetaData(), Column('x', Numeric(precision=18, scale=12))) - t.create(testing.db) - try: - numbers = set([ - decimal.Decimal('1E-2'), - decimal.Decimal('1E-3'), - decimal.Decimal('1E-4'), - decimal.Decimal('1E-5'), - decimal.Decimal('1E-6'), - decimal.Decimal('1E-7'), - decimal.Decimal('1E-8'), - ]) - - testing.db.execute(t.insert(), [{'x':x} for x in numbers]) - - ret = set([row[0] for row in testing.db.execute(t.select()).fetchall()]) - - numbers = set(n for n in numbers) - ret = set(n for n in ret) - - eq_(numbers, ret) - finally: - t.drop(testing.db) - - def test_decimal_fallback(self): - from decimal import Decimal - - numeric_table.insert().execute(ncasdec=12.4, fcasdec=15.75) - numeric_table.insert().execute(ncasdec=Decimal("12.4"), - fcasdec=Decimal("15.75")) - - for row in numeric_table.select().execute().fetchall(): - assert isinstance(row['ncasdec'], decimal.Decimal) - assert isinstance(row['fcasdec'], decimal.Decimal) + numbers = set([ + decimal.Decimal('1E-2'), + decimal.Decimal('1E-3'), + decimal.Decimal('1E-4'), + decimal.Decimal('1E-5'), + decimal.Decimal('1E-6'), + decimal.Decimal('1E-7'), + decimal.Decimal('1E-8'), + ]) + self._do_test( + Numeric(precision=18, scale=12), + numbers, + numbers + ) + class IntervalTest(TestBase, AssertsExecutionResults): |
