summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES44
-rw-r--r--lib/sqlalchemy/dialects/sybase/base.py8
-rw-r--r--lib/sqlalchemy/dialects/sybase/pysybase.py17
-rw-r--r--lib/sqlalchemy/types.py5
-rw-r--r--test/sql/test_labels.py21
-rw-r--r--test/sql/test_types.py178
6 files changed, 142 insertions, 131 deletions
diff --git a/CHANGES b/CHANGES
index d6fc6e7f0..d7103893f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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):