Update sqlalchemy

This commit is contained in:
Ruud
2012-05-05 01:13:50 +02:00
parent dcaca7294c
commit 710c2aa05f
38 changed files with 2292 additions and 1025 deletions
+1 -1
View File
@@ -215,7 +215,7 @@ class FBCompiler(sql.compiler.SQLCompiler):
# Override to not use the AS keyword which FB 1.5 does not like
if asfrom:
alias_name = isinstance(alias.name,
expression._generated_label) and \
expression._truncated_label) and \
self._truncated_identifier("alias",
alias.name) or alias.name
+10
View File
@@ -791,6 +791,9 @@ class MSSQLCompiler(compiler.SQLCompiler):
def get_from_hint_text(self, table, text):
return text
def get_crud_hint_text(self, table, text):
return text
def limit_clause(self, select):
# Limit in mssql is after the select keyword
return ""
@@ -949,6 +952,13 @@ class MSSQLCompiler(compiler.SQLCompiler):
]
return 'OUTPUT ' + ', '.join(columns)
def get_cte_preamble(self, recursive):
# SQL Server finds it too inconvenient to accept
# an entirely optional, SQL standard specified,
# "RECURSIVE" word with their "WITH",
# so here we go
return "WITH"
def label_select_column(self, select, column, asfrom):
if isinstance(column, expression.Function):
return column.label(None)
+101 -15
View File
@@ -84,6 +84,23 @@ all lower case both within SQLAlchemy as well as on the MySQL
database itself, especially if database reflection features are
to be used.
Transaction Isolation Level
---------------------------
:func:`.create_engine` accepts an ``isolation_level``
parameter which results in the command ``SET SESSION
TRANSACTION ISOLATION LEVEL <level>`` being invoked for
every new connection. Valid values for this parameter are
``READ COMMITTED``, ``READ UNCOMMITTED``,
``REPEATABLE READ``, and ``SERIALIZABLE``::
engine = create_engine(
"mysql://scott:tiger@localhost/test",
isolation_level="READ UNCOMMITTED"
)
(new in 0.7.6)
Keys
----
@@ -221,8 +238,29 @@ simply passed through to the underlying CREATE INDEX command, so it *must* be
an integer. MySQL only allows a length for an index if it is for a CHAR,
VARCHAR, TEXT, BINARY, VARBINARY and BLOB.
Index Types
~~~~~~~~~~~~~
Some MySQL storage engines permit you to specify an index type when creating
an index or primary key constraint. SQLAlchemy provides this feature via the
``mysql_using`` parameter on :class:`.Index`::
Index('my_index', my_table.c.data, mysql_using='hash')
As well as the ``mysql_using`` parameter on :class:`.PrimaryKeyConstraint`::
PrimaryKeyConstraint("data", mysql_using='hash')
The value passed to the keyword argument will be simply passed through to the
underlying CREATE INDEX or PRIMARY KEY clause, so it *must* be a valid index
type for your MySQL storage engine.
More information can be found at:
http://dev.mysql.com/doc/refman/5.0/en/create-index.html
http://dev.mysql.com/doc/refman/5.0/en/create-table.html
"""
import datetime, inspect, re, sys
@@ -1331,7 +1369,8 @@ class MySQLCompiler(compiler.SQLCompiler):
return ', '.join(t._compiler_dispatch(self, asfrom=True, **kw)
for t in [from_table] + list(extra_froms))
def update_from_clause(self, update_stmt, from_table, extra_froms, **kw):
def update_from_clause(self, update_stmt, from_table,
extra_froms, from_hints, **kw):
return None
@@ -1421,35 +1460,50 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
table_opts.append(joiner.join((opt, arg)))
return ' '.join(table_opts)
def visit_create_index(self, create):
index = create.element
preparer = self.preparer
table = preparer.format_table(index.table)
columns = [preparer.quote(c.name, c.quote) for c in index.columns]
name = preparer.quote(
self._index_identifier(index.name),
index.quote)
text = "CREATE "
if index.unique:
text += "UNIQUE "
text += "INDEX %s ON %s " \
% (preparer.quote(self._index_identifier(index.name),
index.quote),preparer.format_table(index.table))
text += "INDEX %s ON %s " % (name, table)
columns = ', '.join(columns)
if 'mysql_length' in index.kwargs:
length = index.kwargs['mysql_length']
text += "(%s(%d))" % (columns, length)
else:
length = None
if length is not None:
text+= "(%s(%d))" \
% (', '.join(preparer.quote(c.name, c.quote)
for c in index.columns), length)
else:
text+= "(%s)" \
% (', '.join(preparer.quote(c.name, c.quote)
for c in index.columns))
text += "(%s)" % (columns)
if 'mysql_using' in index.kwargs:
using = index.kwargs['mysql_using']
text += " USING %s" % (preparer.quote(using, index.quote))
return text
def visit_primary_key_constraint(self, constraint):
text = super(MySQLDDLCompiler, self).\
visit_primary_key_constraint(constraint)
if "mysql_using" in constraint.kwargs:
using = constraint.kwargs['mysql_using']
text += " USING %s" % (
self.preparer.quote(using, constraint.quote))
return text
def visit_drop_index(self, drop):
index = drop.element
return "\nDROP INDEX %s ON %s" % \
(self.preparer.quote(self._index_identifier(index.name), index.quote),
(self.preparer.quote(
self._index_identifier(index.name), index.quote
),
self.preparer.format_table(index.table))
def visit_drop_constraint(self, drop):
@@ -1768,8 +1822,40 @@ class MySQLDialect(default.DefaultDialect):
_backslash_escapes = True
_server_ansiquotes = False
def __init__(self, use_ansiquotes=None, **kwargs):
def __init__(self, use_ansiquotes=None, isolation_level=None, **kwargs):
default.DefaultDialect.__init__(self, **kwargs)
self.isolation_level = isolation_level
def on_connect(self):
if self.isolation_level is not None:
def connect(conn):
self.set_isolation_level(conn, self.isolation_level)
return connect
else:
return None
_isolation_lookup = set(['SERIALIZABLE',
'READ UNCOMMITTED', 'READ COMMITTED', 'REPEATABLE READ'])
def set_isolation_level(self, connection, level):
level = level.replace('_', ' ')
if level not in self._isolation_lookup:
raise exc.ArgumentError(
"Invalid value '%s' for isolation_level. "
"Valid isolation levels for %s are %s" %
(level, self.name, ", ".join(self._isolation_lookup))
)
cursor = connection.cursor()
cursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL %s" % level)
cursor.execute("COMMIT")
cursor.close()
def get_isolation_level(self, connection):
cursor = connection.cursor()
cursor.execute('SELECT @@tx_isolation')
val = cursor.fetchone()[0]
cursor.close()
return val.upper().replace("-", " ")
def do_commit(self, connection):
"""Execute a COMMIT."""
+5 -2
View File
@@ -158,7 +158,7 @@ RESERVED_WORDS = \
'AS IN VIEW EXCLUSIVE COMPRESS SYNONYM SELECT INSERT EXISTS '\
'NOT TRIGGER ELSE CREATE INTERSECT PCTFREE DISTINCT USER '\
'CONNECT SET MODE OF UNIQUE VARCHAR2 VARCHAR LOCK OR CHAR '\
'DECIMAL UNION PUBLIC AND START UID COMMENT CURRENT'.split())
'DECIMAL UNION PUBLIC AND START UID COMMENT CURRENT LEVEL'.split())
NO_ARG_FNS = set('UID CURRENT_DATE SYSDATE USER '
'CURRENT_TIME CURRENT_TIMESTAMP'.split())
@@ -309,6 +309,9 @@ class OracleTypeCompiler(compiler.GenericTypeCompiler):
"",
)
def visit_LONG(self, type_):
return "LONG"
def visit_TIMESTAMP(self, type_):
if type_.timezone:
return "TIMESTAMP WITH TIME ZONE"
@@ -481,7 +484,7 @@ class OracleCompiler(compiler.SQLCompiler):
"""Oracle doesn't like ``FROM table AS alias``. Is the AS standard SQL??"""
if asfrom or ashint:
alias_name = isinstance(alias.name, expression._generated_label) and \
alias_name = isinstance(alias.name, expression._truncated_label) and \
self._truncated_identifier("alias", alias.name) or alias.name
if ashint:
+28 -4
View File
@@ -77,7 +77,7 @@ with this feature but it should be regarded as experimental.
Precision Numerics
------------------
The SQLAlchemy dialect goes thorugh a lot of steps to ensure
The SQLAlchemy dialect goes through a lot of steps to ensure
that decimal numbers are sent and received with full accuracy.
An "outputtypehandler" callable is associated with each
cx_oracle connection object which detects numeric types and
@@ -89,6 +89,21 @@ this behavior, and will coerce the ``Decimal`` to ``float`` if
the ``asdecimal`` flag is ``False`` (default on :class:`.Float`,
optional on :class:`.Numeric`).
Because the handler coerces to ``Decimal`` in all cases first,
the feature can detract significantly from performance.
If precision numerics aren't required, the decimal handling
can be disabled by passing the flag ``coerce_to_decimal=False``
to :func:`.create_engine`::
engine = create_engine("oracle+cx_oracle://dsn",
coerce_to_decimal=False)
The ``coerce_to_decimal`` flag is new in 0.7.6.
Another alternative to performance is to use the
`cdecimal <http://pypi.python.org/pypi/cdecimal/>`_ library;
see :class:`.Numeric` for additional notes.
The handler attempts to use the "precision" and "scale"
attributes of the result set column to best determine if
subsequent incoming values should be received as ``Decimal`` as
@@ -468,6 +483,7 @@ class OracleDialect_cx_oracle(OracleDialect):
auto_convert_lobs=True,
threaded=True,
allow_twophase=True,
coerce_to_decimal=True,
arraysize=50, **kwargs):
OracleDialect.__init__(self, **kwargs)
self.threaded = threaded
@@ -491,7 +507,12 @@ class OracleDialect_cx_oracle(OracleDialect):
self._cx_oracle_unicode_types = types("UNICODE", "NCLOB")
self._cx_oracle_binary_types = types("BFILE", "CLOB", "NCLOB", "BLOB")
self.supports_unicode_binds = self.cx_oracle_ver >= (5, 0)
self.supports_native_decimal = self.cx_oracle_ver >= (5, 0)
self.supports_native_decimal = (
self.cx_oracle_ver >= (5, 0) and
coerce_to_decimal
)
self._cx_oracle_native_nvarchar = self.cx_oracle_ver >= (5, 0)
if self.cx_oracle_ver is None:
@@ -603,7 +624,9 @@ class OracleDialect_cx_oracle(OracleDialect):
size, precision, scale):
# convert all NUMBER with precision + positive scale to Decimal
# this almost allows "native decimal" mode.
if defaultType == cx_Oracle.NUMBER and precision and scale > 0:
if self.supports_native_decimal and \
defaultType == cx_Oracle.NUMBER and \
precision and scale > 0:
return cursor.var(
cx_Oracle.STRING,
255,
@@ -614,7 +637,8 @@ class OracleDialect_cx_oracle(OracleDialect):
# make a decision based on each value received - the type
# may change from row to row (!). This kills
# off "native decimal" mode, handlers still needed.
elif defaultType == cx_Oracle.NUMBER \
elif self.supports_native_decimal and \
defaultType == cx_Oracle.NUMBER \
and not precision and scale <= 0:
return cursor.var(
cx_Oracle.STRING,
+12 -3
View File
@@ -47,9 +47,18 @@ Transaction Isolation Level
:func:`.create_engine` accepts an ``isolation_level`` parameter which results
in the command ``SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL
<level>`` being invoked for every new connection. Valid values for this
parameter are ``READ_COMMITTED``, ``READ_UNCOMMITTED``, ``REPEATABLE_READ``,
and ``SERIALIZABLE``. Note that the psycopg2 dialect does *not* use this
technique and uses psycopg2-specific APIs (see that dialect for details).
parameter are ``READ COMMITTED``, ``READ UNCOMMITTED``, ``REPEATABLE READ``,
and ``SERIALIZABLE``::
engine = create_engine(
"postgresql+pg8000://scott:tiger@localhost/test",
isolation_level="READ UNCOMMITTED"
)
When using the psycopg2 dialect, a psycopg2-specific method of setting
transaction isolation level is used, but the API of ``isolation_level``
remains the same - see :ref:`psycopg2_isolation`.
Remote / Cross-Schema Table Introspection
-----------------------------------------
@@ -38,6 +38,26 @@ psycopg2-specific keyword arguments which are accepted by
* *use_native_unicode* - Enable the usage of Psycopg2 "native unicode" mode
per connection. True by default.
Unix Domain Connections
------------------------
psycopg2 supports connecting via Unix domain connections. When the ``host``
portion of the URL is omitted, SQLAlchemy passes ``None`` to psycopg2,
which specifies Unix-domain communication rather than TCP/IP communication::
create_engine("postgresql+psycopg2://user:password@/dbname")
By default, the socket file used is to connect to a Unix-domain socket
in ``/tmp``, or whatever socket directory was specified when PostgreSQL
was built. This value can be overridden by passing a pathname to psycopg2,
using ``host`` as an additional keyword argument::
create_engine("postgresql+psycopg2://user:password@/dbname?host=/var/lib/postgresql")
See also:
`PQconnectdbParams <http://www.postgresql.org/docs/9.1/static/libpq-connect.html#LIBPQ-PQCONNECTDBPARAMS>`_
Per-Statement/Connection Execution Options
-------------------------------------------
@@ -97,6 +117,8 @@ Transactions
The psycopg2 dialect fully supports SAVEPOINT and two-phase commit operations.
.. _psycopg2_isolation:
Transaction Isolation Level
---------------------------
+10 -15
View File
@@ -441,20 +441,6 @@ class SQLiteIdentifierPreparer(compiler.IdentifierPreparer):
result = self.quote_schema(index.table.schema, index.table.quote_schema) + "." + result
return result
class SQLiteExecutionContext(default.DefaultExecutionContext):
def get_result_proxy(self):
rp = base.ResultProxy(self)
if rp._metadata:
# adjust for dotted column names. SQLite
# in the case of UNION may store col names as
# "tablename.colname"
# in cursor.description
for colname in rp._metadata.keys:
if "." in colname:
trunc_col = colname.split(".")[1]
rp._metadata._set_keymap_synonym(trunc_col, colname)
return rp
class SQLiteDialect(default.DefaultDialect):
name = 'sqlite'
supports_alter = False
@@ -472,7 +458,6 @@ class SQLiteDialect(default.DefaultDialect):
ischema_names = ischema_names
colspecs = colspecs
isolation_level = None
execution_ctx_cls = SQLiteExecutionContext
supports_cast = True
supports_default_values = True
@@ -540,6 +525,16 @@ class SQLiteDialect(default.DefaultDialect):
else:
return None
def _translate_colname(self, colname):
# adjust for dotted column names. SQLite
# in the case of UNION may store col names as
# "tablename.colname"
# in cursor.description
if "." in colname:
return colname.split(".")[1], colname
else:
return colname, None
@reflection.cache
def get_table_names(self, connection, schema=None, **kw):
if schema is not None: