diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-04-10 19:45:34 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-04-10 19:45:34 -0400 |
commit | cf9cac34ac8df5613ef2d24884030df8aa749580 (patch) | |
tree | c96e90ee2283b64b4fa84c12729316c28caeb41c | |
parent | 44c67fef8cc578ffbca409ad95e6471b4cb4d02a (diff) | |
parent | 36c05de373f9cbb198573704f2bc28fea8ca9113 (diff) | |
download | sqlalchemy-cf9cac34ac8df5613ef2d24884030df8aa749580.tar.gz |
merge default
-rwxr-xr-x | .hgignore | 1 | ||||
-rw-r--r-- | CHANGES | 7 | ||||
-rw-r--r-- | doc/build/mappers.rst | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/dialects/postgresql/base.py | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/dialects/postgresql/pg8000.py | 11 | ||||
-rw-r--r-- | lib/sqlalchemy/dialects/postgresql/psycopg2.py | 12 | ||||
-rw-r--r-- | lib/sqlalchemy/ext/compiler.py | 19 | ||||
-rw-r--r-- | lib/sqlalchemy/test/testing.py | 17 | ||||
-rw-r--r-- | test/dialect/test_postgresql.py | 30 | ||||
-rw-r--r-- | test/engine/test_execute.py | 31 |
10 files changed, 115 insertions, 21 deletions
@@ -3,3 +3,4 @@ syntax:regexp ^doc/build/output .pyc$ .orig$ +.egg-info
\ No newline at end of file @@ -132,6 +132,13 @@ CHANGES has been changed. Thanks to Kumar McMillan for the patch. [ticket:1071] + - Repaired missing import in psycopg2._PGNumeric type when + unknown numeric is received. + + - psycopg2/pg8000 dialects now aware of REAL[], FLOAT[], + DOUBLE_PRECISION[], NUMERIC[] return types without + raising an exception. + - oracle - Now using cx_oracle output converters so that the DBAPI returns natively the kinds of values we prefer: diff --git a/doc/build/mappers.rst b/doc/build/mappers.rst index 7e320c26a..81d67f217 100644 --- a/doc/build/mappers.rst +++ b/doc/build/mappers.rst @@ -477,7 +477,9 @@ It also accepts a second argument ``selectable`` which replaces the automatic jo # custom selectable query.with_polymorphic([Engineer, Manager], employees.outerjoin(managers).outerjoin(engineers)) -:func:`~sqlalchemy.orm.query.Query.with_polymorphic` is also needed when you wish to add filter criterion that is specific to one or more subclasses, so that those columns are available to the WHERE clause: +:func:`~sqlalchemy.orm.query.Query.with_polymorphic` is also needed +when you wish to add filter criteria that are specific to one or more +subclasses; It makes the subclasses' columns available to the WHERE clause: .. sourcecode:: python+sql diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index dcdaa3c7b..312ae9aa8 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -80,6 +80,10 @@ from sqlalchemy.types import INTEGER, BIGINT, SMALLINT, VARCHAR, \ CHAR, TEXT, FLOAT, NUMERIC, \ DATE, BOOLEAN +_DECIMAL_TYPES = (1700, 1231) +_FLOAT_TYPES = (700, 701, 1021, 1022) + + class REAL(sqltypes.Float): __visit_name__ = "REAL" diff --git a/lib/sqlalchemy/dialects/postgresql/pg8000.py b/lib/sqlalchemy/dialects/postgresql/pg8000.py index a620daac6..862c915aa 100644 --- a/lib/sqlalchemy/dialects/postgresql/pg8000.py +++ b/lib/sqlalchemy/dialects/postgresql/pg8000.py @@ -26,23 +26,24 @@ from sqlalchemy import util, exc from sqlalchemy import processors from sqlalchemy import types as sqltypes from sqlalchemy.dialects.postgresql.base import PGDialect, \ - PGCompiler, PGIdentifierPreparer, PGExecutionContext + PGCompiler, PGIdentifierPreparer, PGExecutionContext,\ + _DECIMAL_TYPES, _FLOAT_TYPES class _PGNumeric(sqltypes.Numeric): def result_processor(self, dialect, coltype): if self.asdecimal: - if coltype in (700, 701): + if coltype in _FLOAT_TYPES: return processors.to_decimal_processor_factory(decimal.Decimal) - elif coltype == 1700: + elif coltype in _DECIMAL_TYPES: # pg8000 returns Decimal natively for 1700 return None else: raise exc.InvalidRequestError("Unknown PG numeric type: %d" % coltype) else: - if coltype in (700, 701): + if coltype in _FLOAT_TYPES: # pg8000 returns float natively for 701 return None - elif coltype == 1700: + elif coltype in _DECIMAL_TYPES: return processors.to_float else: raise exc.InvalidRequestError("Unknown PG numeric type: %d" % coltype) diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py index f21c9a558..2a51a7239 100644 --- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py +++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py @@ -60,7 +60,7 @@ import re import decimal import logging -from sqlalchemy import util +from sqlalchemy import util, exc from sqlalchemy import processors from sqlalchemy.engine import base, default from sqlalchemy.sql import expression @@ -68,7 +68,7 @@ from sqlalchemy.sql import operators as sql_operators from sqlalchemy import types as sqltypes from sqlalchemy.dialects.postgresql.base import PGDialect, PGCompiler, \ PGIdentifierPreparer, PGExecutionContext, \ - ENUM, ARRAY + ENUM, ARRAY, _DECIMAL_TYPES, _FLOAT_TYPES logger = logging.getLogger('sqlalchemy.dialects.postgresql') @@ -80,18 +80,18 @@ class _PGNumeric(sqltypes.Numeric): def result_processor(self, dialect, coltype): if self.asdecimal: - if coltype in (700, 701): + if coltype in _FLOAT_TYPES: return processors.to_decimal_processor_factory(decimal.Decimal) - elif coltype == 1700: + elif coltype in _DECIMAL_TYPES: # pg8000 returns Decimal natively for 1700 return None else: raise exc.InvalidRequestError("Unknown PG numeric type: %d" % coltype) else: - if coltype in (700, 701): + if coltype in _FLOAT_TYPES: # pg8000 returns float natively for 701 return None - elif coltype == 1700: + elif coltype in _DECIMAL_TYPES: return processors.to_float else: raise exc.InvalidRequestError("Unknown PG numeric type: %d" % coltype) diff --git a/lib/sqlalchemy/ext/compiler.py b/lib/sqlalchemy/ext/compiler.py index 20d6aa05f..68c434fd9 100644 --- a/lib/sqlalchemy/ext/compiler.py +++ b/lib/sqlalchemy/ext/compiler.py @@ -119,6 +119,23 @@ overriding routine and cause an endless loop. Such as, to add "prefix" to all The above compiler will prefix all INSERT statements with "some prefix" when compiled. +Changing Compilation of Types +============================= + +``compiler`` works for types, too, such as below where we implement the MS-SQL specific 'max' keyword for ``String``/``VARCHAR``:: + + @compiles(String, 'mssql') + @compiles(VARCHAR, 'mssql') + def compile_varchar(element, compiler, **kw): + if element.length == 'max': + return "VARCHAR('max')" + else: + return compiler.visit_VARCHAR(element, **kw) + + foo = Table('foo', metadata, + Column('data', VARCHAR('max')) + ) + Subclassing Guidelines ====================== @@ -175,7 +192,7 @@ A big part of using the compiler extension is subclassing SQLAlchemy expression used with any expression class that represents a "standalone" SQL statement that can be passed directly to an ``execute()`` method. It is already implicit within ``DDLElement`` and ``FunctionElement``. - + """ def compiles(class_, *specs): diff --git a/lib/sqlalchemy/test/testing.py b/lib/sqlalchemy/test/testing.py index 771b8c90f..70ddc7ba2 100644 --- a/lib/sqlalchemy/test/testing.py +++ b/lib/sqlalchemy/test/testing.py @@ -546,6 +546,23 @@ def fixture(table, columns, *rows): for column_values in rows]) table.append_ddl_listener('after-create', onload) +def provide_metadata(fn): + """Provides a bound MetaData object for a single test, + drops it afterwards.""" + def maybe(*args, **kw): + metadata = schema.MetaData(db) + context = dict(fn.func_globals) + context['metadata'] = metadata + # jython bug #1034 + rebound = types.FunctionType( + fn.func_code, context, fn.func_name, fn.func_defaults, + fn.func_closure) + try: + return rebound(*args, **kw) + finally: + metadata.drop_all() + return function_named(maybe, fn.__name__) + def resolve_artifact_names(fn): """Decorator, augment function globals with tables and classes. diff --git a/test/dialect/test_postgresql.py b/test/dialect/test_postgresql.py index fbe62cdec..14814bc20 100644 --- a/test/dialect/test_postgresql.py +++ b/test/dialect/test_postgresql.py @@ -228,7 +228,21 @@ class FloatCoercionTest(TablesTest, AssertsExecutionResults): ).scalar() eq_(round_decimal(ret, 9), result) - + @testing.provide_metadata + def test_arrays(self): + t1 = Table('t', metadata, + Column('x', postgresql.ARRAY(Float)), + Column('y', postgresql.ARRAY(postgresql.REAL)), + Column('z', postgresql.ARRAY(postgresql.DOUBLE_PRECISION)), + Column('q', postgresql.ARRAY(Numeric)) + ) + metadata.create_all() + t1.insert().execute(x=[5], y=[5], z=[6], q=[6.4]) + row = t1.select().execute().first() + eq_( + row, + ([5], [5], [6], [decimal.Decimal("6.4")]) + ) class EnumTest(TestBase, AssertsExecutionResults, AssertsCompiledSQL): __only_on__ = 'postgresql' @@ -1311,8 +1325,18 @@ class MiscTest(TestBase, AssertsExecutionResults, AssertsCompiledSQL): else: exception_cls = eng.dialect.dbapi.ProgrammingError assert_raises(exception_cls, eng.execute, "show transaction isolation level") - - + + @testing.fails_on('+zxjdbc', + "psycopg2/pg8000 specific assertion") + @testing.fails_on('pypostgresql', + "psycopg2/pg8000 specific assertion") + def test_numeric_raise(self): + stmt = text("select cast('hi' as char) as hi", typemap={'hi':Numeric}) + assert_raises( + exc.InvalidRequestError, + testing.db.execute, stmt + ) + class TimezoneTest(TestBase): """Test timezone-aware datetimes. diff --git a/test/engine/test_execute.py b/test/engine/test_execute.py index e83166c9a..04d4a06d5 100644 --- a/test/engine/test_execute.py +++ b/test/engine/test_execute.py @@ -32,7 +32,9 @@ class ExecuteTest(TestBase): def teardown_class(cls): metadata.drop_all() - @testing.fails_on_everything_except('firebird', 'maxdb', 'sqlite', '+pyodbc', '+mxodbc', '+zxjdbc', 'mysql+oursql') + @testing.fails_on_everything_except('firebird', 'maxdb', + 'sqlite', '+pyodbc', + '+mxodbc', '+zxjdbc', 'mysql+oursql') def test_raw_qmark(self): for conn in (testing.db, testing.db.connect()): conn.execute("insert into users (user_id, user_name) values (?, ?)", (1,"jack")) @@ -70,7 +72,8 @@ class ExecuteTest(TestBase): # pyformat is supported for mysql, but skipping because a few driver # versions have a bug that bombs out on this test. (1.2.2b3, 1.2.2c1, 1.2.2) @testing.skip_if(lambda: testing.against('mysql+mysqldb'), 'db-api flaky') - @testing.fails_on_everything_except('postgresql+psycopg2', 'postgresql+pypostgresql', 'mysql+mysqlconnector') + @testing.fails_on_everything_except('postgresql+psycopg2', + 'postgresql+pypostgresql', 'mysql+mysqlconnector') def test_raw_python(self): for conn in (testing.db, testing.db.connect()): conn.execute("insert into users (user_id, user_name) values (%(id)s, %(name)s)", @@ -117,7 +120,7 @@ class CompiledCacheTest(TestBase): global users, metadata metadata = MetaData(testing.db) users = Table('users', metadata, - Column('user_id', INT, primary_key = True), + Column('user_id', INT, primary_key=True, test_needs_autoincrement=True), Column('user_name', VARCHAR(20)), ) metadata.create_all() @@ -269,8 +272,26 @@ class ProxyConnectionTest(TestBase): assert_stmts(compiled, stmts) assert_stmts(cursor, cursor_stmts) - - @testing.fails_on('mysql+oursql', 'oursql dialect has some extra steps here') + + def test_options(self): + track = [] + class TrackProxy(ConnectionProxy): + def __getattribute__(self, key): + fn = object.__getattribute__(self, key) + def go(*arg, **kw): + track.append(fn.__name__) + return fn(*arg, **kw) + return go + engine = engines.testing_engine(options={'proxy':TrackProxy()}) + conn = engine.connect() + c2 = conn.execution_options(foo='bar') + eq_(c2._execution_options, {'foo':'bar'}) + c2.execute(select([1])) + c3 = c2.execution_options(bar='bat') + eq_(c3._execution_options, {'foo':'bar', 'bar':'bat'}) + eq_(track, ['execute', 'cursor_execute']) + + def test_transactional(self): track = [] class TrackProxy(ConnectionProxy): |