summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-04-10 19:45:34 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2010-04-10 19:45:34 -0400
commitcf9cac34ac8df5613ef2d24884030df8aa749580 (patch)
treec96e90ee2283b64b4fa84c12729316c28caeb41c
parent44c67fef8cc578ffbca409ad95e6471b4cb4d02a (diff)
parent36c05de373f9cbb198573704f2bc28fea8ca9113 (diff)
downloadsqlalchemy-cf9cac34ac8df5613ef2d24884030df8aa749580.tar.gz
merge default
-rwxr-xr-x.hgignore1
-rw-r--r--CHANGES7
-rw-r--r--doc/build/mappers.rst4
-rw-r--r--lib/sqlalchemy/dialects/postgresql/base.py4
-rw-r--r--lib/sqlalchemy/dialects/postgresql/pg8000.py11
-rw-r--r--lib/sqlalchemy/dialects/postgresql/psycopg2.py12
-rw-r--r--lib/sqlalchemy/ext/compiler.py19
-rw-r--r--lib/sqlalchemy/test/testing.py17
-rw-r--r--test/dialect/test_postgresql.py30
-rw-r--r--test/engine/test_execute.py31
10 files changed, 115 insertions, 21 deletions
diff --git a/.hgignore b/.hgignore
index 6ecede1f3..0a1571405 100755
--- a/.hgignore
+++ b/.hgignore
@@ -3,3 +3,4 @@ syntax:regexp
^doc/build/output
.pyc$
.orig$
+.egg-info \ No newline at end of file
diff --git a/CHANGES b/CHANGES
index f6beb1ab0..35a4774ce 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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):