Update SQLAlchemy
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# dialects/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
"""
|
||||
Support for the Microsoft Access database.
|
||||
|
||||
This dialect is *not* ported to SQLAlchemy 0.6 or 0.7.
|
||||
.. note::
|
||||
|
||||
This dialect is *not* tested on SQLAlchemy 0.6 or 0.7.
|
||||
The Access dialect is **non-functional as of SQLAlchemy 0.6**,
|
||||
pending development efforts to bring it up-to-date.
|
||||
|
||||
|
||||
"""
|
||||
@@ -124,7 +125,7 @@ class AccessExecutionContext(default.DefaultExecutionContext):
|
||||
# self._last_inserted_ids[0] is None:
|
||||
self.cursor.execute("SELECT @@identity AS lastrowid")
|
||||
row = self.cursor.fetchone()
|
||||
self._last_inserted_ids = [int(row[0])]
|
||||
self._last_inserted_ids = [int(row[0])]
|
||||
#+ self._last_inserted_ids[1:]
|
||||
# print "LAST ROW ID", self._last_inserted_ids
|
||||
|
||||
@@ -259,7 +260,7 @@ class AccessDialect(default.DefaultDialect):
|
||||
|
||||
colargs = \
|
||||
{
|
||||
'nullable': not(col.Required or
|
||||
'nullable': not(col.Required or
|
||||
col.Attributes & const.dbAutoIncrField),
|
||||
}
|
||||
default = col.DefaultValue
|
||||
@@ -286,7 +287,7 @@ class AccessDialect(default.DefaultDialect):
|
||||
if isinstance(thecol.type, AcInteger) and \
|
||||
not (thecol.default and
|
||||
isinstance(
|
||||
thecol.default.arg,
|
||||
thecol.default.arg,
|
||||
schema.Sequence
|
||||
)):
|
||||
thecol.autoincrement = False
|
||||
@@ -321,7 +322,7 @@ class AccessDialect(default.DefaultDialect):
|
||||
# This is necessary, so we get the latest updates
|
||||
dtbs = daoEngine.OpenDatabase(connection.engine.url.database)
|
||||
|
||||
names = [t.Name for t in dtbs.TableDefs
|
||||
names = [t.Name for t in dtbs.TableDefs
|
||||
if t.Name[:4] != "MSys" and t.Name[:4] != "~TMP"]
|
||||
dtbs.Close()
|
||||
return names
|
||||
@@ -372,7 +373,7 @@ class AccessCompiler(compiler.SQLCompiler):
|
||||
'length': 'len',
|
||||
}
|
||||
def visit_function(self, func):
|
||||
"""Access function names differ from the ANSI SQL names;
|
||||
"""Access function names differ from the ANSI SQL names;
|
||||
rewrite common ones"""
|
||||
func.name = self.function_rewrites.get(func.name, func.name)
|
||||
return super(AccessCompiler, self).visit_function(func)
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
from sqlalchemy.dialects.drizzle import base, mysqldb
|
||||
|
||||
# default dialect
|
||||
base.dialect = mysqldb.dialect
|
||||
|
||||
from sqlalchemy.dialects.drizzle.base import \
|
||||
BIGINT, BINARY, BLOB, BOOLEAN, CHAR, DATE, DATETIME, \
|
||||
DECIMAL, DOUBLE, ENUM, \
|
||||
FLOAT, INTEGER, \
|
||||
NUMERIC, REAL, TEXT, TIME, TIMESTAMP, \
|
||||
VARBINARY, VARCHAR, dialect
|
||||
|
||||
BIGINT, BINARY, BLOB, \
|
||||
BOOLEAN, CHAR, DATE, \
|
||||
DATETIME, DECIMAL, DOUBLE, \
|
||||
ENUM, FLOAT, INTEGER, \
|
||||
NUMERIC, REAL, TEXT, \
|
||||
TIME, TIMESTAMP, VARBINARY, \
|
||||
VARCHAR, dialect
|
||||
|
||||
__all__ = (
|
||||
'BIGINT', 'BINARY', 'BLOB', 'BOOLEAN', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'DOUBLE',
|
||||
'ENUM', 'FLOAT', 'INTEGER',
|
||||
'NUMERIC', 'SET', 'REAL', 'TEXT', 'TIME', 'TIMESTAMP',
|
||||
'VARBINARY', 'VARCHAR', 'dialect'
|
||||
'BIGINT', 'BINARY', 'BLOB',
|
||||
'BOOLEAN', 'CHAR', 'DATE',
|
||||
'DATETIME', 'DECIMAL', 'DOUBLE',
|
||||
'ENUM', 'FLOAT', 'INTEGER',
|
||||
'NUMERIC', 'REAL', 'TEXT',
|
||||
'TIME', 'TIMESTAMP', 'VARBINARY',
|
||||
'VARCHAR', 'dialect'
|
||||
)
|
||||
|
||||
@@ -1,138 +1,36 @@
|
||||
# drizzle/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2010-2011 Monty Taylor <mordred@inaugust.com>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
|
||||
"""Support for the Drizzle database.
|
||||
|
||||
Supported Versions and Features
|
||||
-------------------------------
|
||||
Drizzle is a variant of MySQL. Unlike MySQL, Drizzle's default storage engine
|
||||
is InnoDB (transactions, foreign-keys) rather than MyISAM. For more
|
||||
`Notable Differences <http://docs.drizzle.org/mysql_differences.html>`_, visit
|
||||
the `Drizzle Documentation <http://docs.drizzle.org/index.html>`_.
|
||||
|
||||
SQLAlchemy supports the Drizzle database starting with 2010.08.
|
||||
with capabilities increasing with more modern servers.
|
||||
|
||||
Most available DBAPI drivers are supported; see below.
|
||||
|
||||
===================================== ===============
|
||||
Feature Minimum Version
|
||||
===================================== ===============
|
||||
sqlalchemy.orm 2010.08
|
||||
Table Reflection 2010.08
|
||||
DDL Generation 2010.08
|
||||
utf8/Full Unicode Connections 2010.08
|
||||
Transactions 2010.08
|
||||
Two-Phase Transactions 2010.08
|
||||
Nested Transactions 2010.08
|
||||
===================================== ===============
|
||||
|
||||
See the official Drizzle documentation for detailed information about features
|
||||
supported in any given server release.
|
||||
The SQLAlchemy Drizzle dialect leans heavily on the MySQL dialect, so much of
|
||||
the :doc:`SQLAlchemy MySQL <mysql>` documentation is also relevant.
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
See the API documentation on individual drivers for details on connecting.
|
||||
|
||||
Connection Timeouts
|
||||
-------------------
|
||||
|
||||
Drizzle features an automatic connection close behavior, for connections that
|
||||
have been idle for eight hours or more. To circumvent having this issue, use
|
||||
the ``pool_recycle`` option which controls the maximum age of any connection::
|
||||
|
||||
engine = create_engine('drizzle+mysqldb://...', pool_recycle=3600)
|
||||
|
||||
Storage Engines
|
||||
---------------
|
||||
|
||||
Drizzle defaults to the ``InnoDB`` storage engine, which is transactional.
|
||||
|
||||
Storage engines can be elected when creating tables in SQLAlchemy by supplying
|
||||
a ``drizzle_engine='whatever'`` to the ``Table`` constructor. Any Drizzle table
|
||||
creation option can be specified in this syntax::
|
||||
|
||||
Table('mytable', metadata,
|
||||
Column('data', String(32)),
|
||||
drizzle_engine='InnoDB',
|
||||
)
|
||||
|
||||
Keys
|
||||
----
|
||||
|
||||
Not all Drizzle storage engines support foreign keys. For ``BlitzDB`` and
|
||||
similar engines, the information loaded by table reflection will not include
|
||||
foreign keys. For these tables, you may supply a
|
||||
:class:`~sqlalchemy.ForeignKeyConstraint` at reflection time::
|
||||
|
||||
Table('mytable', metadata,
|
||||
ForeignKeyConstraint(['other_id'], ['othertable.other_id']),
|
||||
autoload=True
|
||||
)
|
||||
|
||||
When creating tables, SQLAlchemy will automatically set ``AUTO_INCREMENT`` on
|
||||
an integer primary key column::
|
||||
|
||||
>>> t = Table('mytable', metadata,
|
||||
... Column('mytable_id', Integer, primary_key=True)
|
||||
... )
|
||||
>>> t.create()
|
||||
CREATE TABLE mytable (
|
||||
id INTEGER NOT NULL AUTO_INCREMENT,
|
||||
PRIMARY KEY (id)
|
||||
)
|
||||
|
||||
You can disable this behavior by supplying ``autoincrement=False`` to the
|
||||
:class:`~sqlalchemy.Column`. This flag can also be used to enable
|
||||
auto-increment on a secondary column in a multi-column key for some storage
|
||||
engines::
|
||||
|
||||
Table('mytable', metadata,
|
||||
Column('gid', Integer, primary_key=True, autoincrement=False),
|
||||
Column('id', Integer, primary_key=True)
|
||||
)
|
||||
|
||||
Drizzle SQL Extensions
|
||||
----------------------
|
||||
|
||||
Many of the Drizzle SQL extensions are handled through SQLAlchemy's generic
|
||||
function and operator support::
|
||||
|
||||
table.select(table.c.password==func.md5('plaintext'))
|
||||
table.select(table.c.username.op('regexp')('^[a-d]'))
|
||||
|
||||
And of course any valid Drizzle statement can be executed as a string as well.
|
||||
|
||||
Some limited direct support for Drizzle extensions to SQL is currently
|
||||
available.
|
||||
|
||||
* SELECT pragma::
|
||||
|
||||
select(..., prefixes=['HIGH_PRIORITY', 'SQL_SMALL_RESULT'])
|
||||
|
||||
* UPDATE with LIMIT::
|
||||
|
||||
update(..., drizzle_limit=10)
|
||||
See the individual driver sections below for details on connecting.
|
||||
|
||||
"""
|
||||
|
||||
import datetime, inspect, re, sys
|
||||
|
||||
from sqlalchemy import schema as sa_schema
|
||||
from sqlalchemy import exc, log, sql, util
|
||||
from sqlalchemy.sql import operators as sql_operators
|
||||
from sqlalchemy.sql import functions as sql_functions
|
||||
from sqlalchemy.sql import compiler
|
||||
from array import array as _array
|
||||
|
||||
from sqlalchemy.engine import reflection
|
||||
from sqlalchemy.engine import base as engine_base, default
|
||||
from sqlalchemy import exc
|
||||
from sqlalchemy import log
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.engine import reflection
|
||||
from sqlalchemy.dialects.mysql import base as mysql_dialect
|
||||
|
||||
from sqlalchemy.types import DATE, DATETIME, BOOLEAN, TIME, \
|
||||
BLOB, BINARY, VARBINARY
|
||||
BLOB, BINARY, VARBINARY
|
||||
|
||||
|
||||
class _NumericType(object):
|
||||
"""Base for Drizzle numeric types."""
|
||||
@@ -140,6 +38,7 @@ class _NumericType(object):
|
||||
def __init__(self, **kw):
|
||||
super(_NumericType, self).__init__(**kw)
|
||||
|
||||
|
||||
class _FloatType(_NumericType, sqltypes.Float):
|
||||
def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
|
||||
if isinstance(self, (REAL, DOUBLE)) and \
|
||||
@@ -147,23 +46,22 @@ class _FloatType(_NumericType, sqltypes.Float):
|
||||
(precision is None and scale is not None) or
|
||||
(precision is not None and scale is None)
|
||||
):
|
||||
raise exc.ArgumentError(
|
||||
"You must specify both precision and scale or omit "
|
||||
"both altogether.")
|
||||
raise exc.ArgumentError(
|
||||
"You must specify both precision and scale or omit "
|
||||
"both altogether.")
|
||||
|
||||
super(_FloatType, self).__init__(precision=precision, asdecimal=asdecimal, **kw)
|
||||
super(_FloatType, self).__init__(precision=precision,
|
||||
asdecimal=asdecimal, **kw)
|
||||
self.scale = scale
|
||||
|
||||
|
||||
class _StringType(mysql_dialect._StringType):
|
||||
"""Base for Drizzle string types."""
|
||||
|
||||
def __init__(self, collation=None,
|
||||
binary=False,
|
||||
**kw):
|
||||
def __init__(self, collation=None, binary=False, **kw):
|
||||
kw['national'] = False
|
||||
super(_StringType, self).__init__(collation=collation,
|
||||
binary=binary,
|
||||
**kw)
|
||||
super(_StringType, self).__init__(collation=collation, binary=binary,
|
||||
**kw)
|
||||
|
||||
|
||||
class NUMERIC(_NumericType, sqltypes.NUMERIC):
|
||||
@@ -180,7 +78,9 @@ class NUMERIC(_NumericType, sqltypes.NUMERIC):
|
||||
:param scale: The number of digits after the decimal point.
|
||||
|
||||
"""
|
||||
super(NUMERIC, self).__init__(precision=precision, scale=scale, asdecimal=asdecimal, **kw)
|
||||
|
||||
super(NUMERIC, self).__init__(precision=precision, scale=scale,
|
||||
asdecimal=asdecimal, **kw)
|
||||
|
||||
|
||||
class DECIMAL(_NumericType, sqltypes.DECIMAL):
|
||||
@@ -215,9 +115,11 @@ class DOUBLE(_FloatType):
|
||||
:param scale: The number of digits after the decimal point.
|
||||
|
||||
"""
|
||||
|
||||
super(DOUBLE, self).__init__(precision=precision, scale=scale,
|
||||
asdecimal=asdecimal, **kw)
|
||||
|
||||
|
||||
class REAL(_FloatType, sqltypes.REAL):
|
||||
"""Drizzle REAL type."""
|
||||
|
||||
@@ -232,9 +134,11 @@ class REAL(_FloatType, sqltypes.REAL):
|
||||
:param scale: The number of digits after the decimal point.
|
||||
|
||||
"""
|
||||
|
||||
super(REAL, self).__init__(precision=precision, scale=scale,
|
||||
asdecimal=asdecimal, **kw)
|
||||
|
||||
|
||||
class FLOAT(_FloatType, sqltypes.FLOAT):
|
||||
"""Drizzle FLOAT type."""
|
||||
|
||||
@@ -249,42 +153,46 @@ class FLOAT(_FloatType, sqltypes.FLOAT):
|
||||
:param scale: The number of digits after the decimal point.
|
||||
|
||||
"""
|
||||
|
||||
super(FLOAT, self).__init__(precision=precision, scale=scale,
|
||||
asdecimal=asdecimal, **kw)
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
return None
|
||||
|
||||
|
||||
class INTEGER(sqltypes.INTEGER):
|
||||
"""Drizzle INTEGER type."""
|
||||
|
||||
__visit_name__ = 'INTEGER'
|
||||
|
||||
def __init__(self, **kw):
|
||||
"""Construct an INTEGER.
|
||||
"""Construct an INTEGER."""
|
||||
|
||||
"""
|
||||
super(INTEGER, self).__init__(**kw)
|
||||
|
||||
|
||||
class BIGINT(sqltypes.BIGINT):
|
||||
"""Drizzle BIGINTEGER type."""
|
||||
|
||||
__visit_name__ = 'BIGINT'
|
||||
|
||||
def __init__(self, **kw):
|
||||
"""Construct a BIGINTEGER.
|
||||
"""Construct a BIGINTEGER."""
|
||||
|
||||
"""
|
||||
super(BIGINT, self).__init__(**kw)
|
||||
|
||||
|
||||
class _DrizzleTime(mysql_dialect._MSTime):
|
||||
"""Drizzle TIME type."""
|
||||
|
||||
|
||||
class TIMESTAMP(sqltypes.TIMESTAMP):
|
||||
"""Drizzle TIMESTAMP type."""
|
||||
|
||||
__visit_name__ = 'TIMESTAMP'
|
||||
|
||||
|
||||
class TEXT(_StringType, sqltypes.TEXT):
|
||||
"""Drizzle TEXT type, for text up to 2^16 characters."""
|
||||
|
||||
@@ -306,8 +214,10 @@ class TEXT(_StringType, sqltypes.TEXT):
|
||||
only the collation of character data.
|
||||
|
||||
"""
|
||||
|
||||
super(TEXT, self).__init__(length=length, **kw)
|
||||
|
||||
|
||||
class VARCHAR(_StringType, sqltypes.VARCHAR):
|
||||
"""Drizzle VARCHAR type, for variable-length character data."""
|
||||
|
||||
@@ -325,8 +235,10 @@ class VARCHAR(_StringType, sqltypes.VARCHAR):
|
||||
only the collation of character data.
|
||||
|
||||
"""
|
||||
|
||||
super(VARCHAR, self).__init__(length=length, **kwargs)
|
||||
|
||||
|
||||
class CHAR(_StringType, sqltypes.CHAR):
|
||||
"""Drizzle CHAR type, for fixed-length character data."""
|
||||
|
||||
@@ -345,8 +257,10 @@ class CHAR(_StringType, sqltypes.CHAR):
|
||||
compatible with the national character set.
|
||||
|
||||
"""
|
||||
|
||||
super(CHAR, self).__init__(length=length, **kwargs)
|
||||
|
||||
|
||||
class ENUM(mysql_dialect.ENUM):
|
||||
"""Drizzle ENUM type."""
|
||||
|
||||
@@ -363,8 +277,9 @@ class ENUM(mysql_dialect.ENUM):
|
||||
|
||||
:param strict: Defaults to False: ensure that a given value is in this
|
||||
ENUM's range of permissible values when inserting or updating rows.
|
||||
Note that Drizzle will not raise a fatal error if you attempt to store
|
||||
an out of range value- an alternate value will be stored instead.
|
||||
Note that Drizzle will not raise a fatal error if you attempt to
|
||||
store an out of range value- an alternate value will be stored
|
||||
instead.
|
||||
(See Drizzle ENUM documentation.)
|
||||
|
||||
:param collation: Optional, a column-level collation for this string
|
||||
@@ -390,12 +305,15 @@ class ENUM(mysql_dialect.ENUM):
|
||||
literals for you. This is a transitional option.
|
||||
|
||||
"""
|
||||
|
||||
super(ENUM, self).__init__(*enums, **kw)
|
||||
|
||||
|
||||
class _DrizzleBoolean(sqltypes.Boolean):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.NUMERIC
|
||||
|
||||
|
||||
colspecs = {
|
||||
sqltypes.Numeric: NUMERIC,
|
||||
sqltypes.Float: FLOAT,
|
||||
@@ -404,6 +322,7 @@ colspecs = {
|
||||
sqltypes.Boolean: _DrizzleBoolean,
|
||||
}
|
||||
|
||||
|
||||
# All the types we have in Drizzle
|
||||
ischema_names = {
|
||||
'BIGINT': BIGINT,
|
||||
@@ -427,6 +346,7 @@ ischema_names = {
|
||||
'VARCHAR': VARCHAR,
|
||||
}
|
||||
|
||||
|
||||
class DrizzleCompiler(mysql_dialect.MySQLCompiler):
|
||||
|
||||
def visit_typeclause(self, typeclause):
|
||||
@@ -439,7 +359,7 @@ class DrizzleCompiler(mysql_dialect.MySQLCompiler):
|
||||
def visit_cast(self, cast, **kwargs):
|
||||
type_ = self.process(cast.typeclause)
|
||||
if type_ is None:
|
||||
return self.process(cast.clause)
|
||||
return self.process(cast.clause)
|
||||
|
||||
return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
|
||||
|
||||
@@ -447,12 +367,13 @@ class DrizzleCompiler(mysql_dialect.MySQLCompiler):
|
||||
class DrizzleDDLCompiler(mysql_dialect.MySQLDDLCompiler):
|
||||
pass
|
||||
|
||||
|
||||
class DrizzleTypeCompiler(mysql_dialect.MySQLTypeCompiler):
|
||||
def _extend_numeric(self, type_, spec):
|
||||
return spec
|
||||
|
||||
def _extend_string(self, type_, defaults, spec):
|
||||
"""Extend a string-type declaration with standard SQL
|
||||
"""Extend a string-type declaration with standard SQL
|
||||
COLLATE annotations and Drizzle specific extensions.
|
||||
|
||||
"""
|
||||
@@ -492,11 +413,16 @@ class DrizzleTypeCompiler(mysql_dialect.MySQLTypeCompiler):
|
||||
class DrizzleExecutionContext(mysql_dialect.MySQLExecutionContext):
|
||||
pass
|
||||
|
||||
|
||||
class DrizzleIdentifierPreparer(mysql_dialect.MySQLIdentifierPreparer):
|
||||
pass
|
||||
|
||||
|
||||
class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
"""Details of the Drizzle dialect. Not used directly in application code."""
|
||||
"""Details of the Drizzle dialect.
|
||||
|
||||
Not used directly in application code.
|
||||
"""
|
||||
|
||||
name = 'drizzle'
|
||||
|
||||
@@ -505,7 +431,6 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
supports_native_boolean = True
|
||||
supports_views = False
|
||||
|
||||
|
||||
default_paramstyle = 'format'
|
||||
colspecs = colspecs
|
||||
|
||||
@@ -516,8 +441,8 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
preparer = DrizzleIdentifierPreparer
|
||||
|
||||
def on_connect(self):
|
||||
"""Force autocommit - Drizzle Bug#707842 doesn't set this
|
||||
properly"""
|
||||
"""Force autocommit - Drizzle Bug#707842 doesn't set this properly"""
|
||||
|
||||
def connect(conn):
|
||||
conn.autocommit(False)
|
||||
return connect
|
||||
@@ -535,6 +460,7 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
@reflection.cache
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
"""Return a Unicode SHOW TABLES from a given schema."""
|
||||
|
||||
if schema is not None:
|
||||
current_schema = schema
|
||||
else:
|
||||
@@ -554,8 +480,8 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
|
||||
Cached per-connection. This value can not change without a server
|
||||
restart.
|
||||
|
||||
"""
|
||||
|
||||
return 0
|
||||
|
||||
def _detect_collations(self, connection):
|
||||
@@ -566,7 +492,9 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
|
||||
collations = {}
|
||||
charset = self._connection_charset
|
||||
rs = connection.execute('SELECT CHARACTER_SET_NAME, COLLATION_NAME from data_dictionary.COLLATIONS')
|
||||
rs = connection.execute(
|
||||
'SELECT CHARACTER_SET_NAME, COLLATION_NAME FROM'
|
||||
' data_dictionary.COLLATIONS')
|
||||
for row in self._compat_fetchall(rs, charset):
|
||||
collations[row[0]] = row[1]
|
||||
return collations
|
||||
@@ -575,8 +503,7 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
"""Detect and adjust for the ANSI_QUOTES sql mode."""
|
||||
|
||||
self._server_ansiquotes = False
|
||||
|
||||
self._backslash_escapes = False
|
||||
|
||||
log.class_logger(DrizzleDialect)
|
||||
|
||||
log.class_logger(DrizzleDialect)
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
"""Support for the Drizzle database via the Drizzle-python adapter.
|
||||
"""Support for the Drizzle database via the mysql-python adapter.
|
||||
|
||||
Drizzle-Python is available at:
|
||||
MySQL-Python is available at:
|
||||
|
||||
http://sourceforge.net/projects/mysql-python
|
||||
|
||||
At least version 1.2.1 or 1.2.2 should be used.
|
||||
|
||||
Connecting
|
||||
-----------
|
||||
|
||||
@@ -13,37 +11,22 @@ Connect string format::
|
||||
|
||||
drizzle+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
|
||||
Unicode
|
||||
-------
|
||||
|
||||
Drizzle accommodates Python ``unicode`` objects directly and
|
||||
uses the ``utf8`` encoding in all cases.
|
||||
|
||||
Known Issues
|
||||
-------------
|
||||
|
||||
Drizzle-python at least as of version 1.2.2 has a serious memory leak related
|
||||
to unicode conversion, a feature which is disabled via ``use_unicode=0``.
|
||||
The recommended connection form with SQLAlchemy is::
|
||||
|
||||
engine = create_engine('mysql://scott:tiger@localhost/test?charset=utf8&use_unicode=0', pool_recycle=3600)
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.drizzle.base import (DrizzleDialect,
|
||||
DrizzleExecutionContext,
|
||||
DrizzleCompiler, DrizzleIdentifierPreparer)
|
||||
from sqlalchemy.dialects.drizzle.base import (
|
||||
DrizzleDialect,
|
||||
DrizzleExecutionContext,
|
||||
DrizzleCompiler,
|
||||
DrizzleIdentifierPreparer)
|
||||
from sqlalchemy.connectors.mysqldb import (
|
||||
MySQLDBExecutionContext,
|
||||
MySQLDBCompiler,
|
||||
MySQLDBIdentifierPreparer,
|
||||
MySQLDBConnector
|
||||
)
|
||||
MySQLDBExecutionContext,
|
||||
MySQLDBCompiler,
|
||||
MySQLDBIdentifierPreparer,
|
||||
MySQLDBConnector)
|
||||
|
||||
class DrizzleExecutionContext_mysqldb(
|
||||
MySQLDBExecutionContext,
|
||||
DrizzleExecutionContext):
|
||||
|
||||
class DrizzleExecutionContext_mysqldb(MySQLDBExecutionContext,
|
||||
DrizzleExecutionContext):
|
||||
pass
|
||||
|
||||
|
||||
@@ -51,11 +34,11 @@ class DrizzleCompiler_mysqldb(MySQLDBCompiler, DrizzleCompiler):
|
||||
pass
|
||||
|
||||
|
||||
class DrizzleIdentifierPreparer_mysqldb(
|
||||
MySQLDBIdentifierPreparer,
|
||||
DrizzleIdentifierPreparer):
|
||||
class DrizzleIdentifierPreparer_mysqldb(MySQLDBIdentifierPreparer,
|
||||
DrizzleIdentifierPreparer):
|
||||
pass
|
||||
|
||||
|
||||
class DrizzleDialect_mysqldb(MySQLDBConnector, DrizzleDialect):
|
||||
execution_ctx_cls = DrizzleExecutionContext_mysqldb
|
||||
statement_compiler = DrizzleCompiler_mysqldb
|
||||
@@ -63,6 +46,7 @@ class DrizzleDialect_mysqldb(MySQLDBConnector, DrizzleDialect):
|
||||
|
||||
def _detect_charset(self, connection):
|
||||
"""Sniff out the character set in use for connection results."""
|
||||
|
||||
return 'utf8'
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# firebird/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -14,7 +14,7 @@ from sqlalchemy.dialects.firebird.base import \
|
||||
dialect
|
||||
|
||||
__all__ = (
|
||||
'SMALLINT', 'BIGINT', 'FLOAT', 'FLOAT', 'DATE', 'TIME',
|
||||
'SMALLINT', 'BIGINT', 'FLOAT', 'FLOAT', 'DATE', 'TIME',
|
||||
'TEXT', 'NUMERIC', 'FLOAT', 'TIMESTAMP', 'VARCHAR', 'CHAR', 'BLOB',
|
||||
'dialect'
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# firebird/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -135,7 +135,7 @@ class VARCHAR(_StringType, sqltypes.VARCHAR):
|
||||
__visit_name__ = 'VARCHAR'
|
||||
|
||||
def __init__(self, length = None, **kwargs):
|
||||
super(VARCHAR, self).__init__(length=length, **kwargs)
|
||||
super(VARCHAR, self).__init__(length=length, **kwargs)
|
||||
|
||||
class CHAR(_StringType, sqltypes.CHAR):
|
||||
"""Firebird CHAR type"""
|
||||
@@ -164,7 +164,7 @@ ischema_names = {
|
||||
}
|
||||
|
||||
|
||||
# TODO: date conversion types (should be implemented as _FBDateTime,
|
||||
# TODO: date conversion types (should be implemented as _FBDateTime,
|
||||
# _FBDate, etc. as bind/result functionality is required)
|
||||
|
||||
class FBTypeCompiler(compiler.GenericTypeCompiler):
|
||||
@@ -198,7 +198,7 @@ class FBTypeCompiler(compiler.GenericTypeCompiler):
|
||||
|
||||
|
||||
class FBCompiler(sql.compiler.SQLCompiler):
|
||||
"""Firebird specific idiosincrasies"""
|
||||
"""Firebird specific idiosyncrasies"""
|
||||
|
||||
def visit_mod(self, binary, **kw):
|
||||
# Firebird lacks a builtin modulo operator, but there is
|
||||
@@ -293,7 +293,7 @@ class FBCompiler(sql.compiler.SQLCompiler):
|
||||
|
||||
|
||||
class FBDDLCompiler(sql.compiler.DDLCompiler):
|
||||
"""Firebird syntactic idiosincrasies"""
|
||||
"""Firebird syntactic idiosyncrasies"""
|
||||
|
||||
def visit_create_sequence(self, create):
|
||||
"""Generate a ``CREATE GENERATOR`` statement for the sequence."""
|
||||
@@ -339,7 +339,7 @@ class FBExecutionContext(default.DefaultExecutionContext):
|
||||
"""Get the next value from the sequence using ``gen_id()``."""
|
||||
|
||||
return self._execute_scalar(
|
||||
"SELECT gen_id(%s, 1) FROM rdb$database" %
|
||||
"SELECT gen_id(%s, 1) FROM rdb$database" %
|
||||
self.dialect.identifier_preparer.format_sequence(seq),
|
||||
type_
|
||||
)
|
||||
@@ -418,7 +418,7 @@ class FBDialect(default.DefaultDialect):
|
||||
return name
|
||||
|
||||
def has_table(self, connection, table_name, schema=None):
|
||||
"""Return ``True`` if the given table exists, ignoring
|
||||
"""Return ``True`` if the given table exists, ignoring
|
||||
the `schema`."""
|
||||
|
||||
tblqry = """
|
||||
@@ -489,8 +489,8 @@ class FBDialect(default.DefaultDialect):
|
||||
return pkfields
|
||||
|
||||
@reflection.cache
|
||||
def get_column_sequence(self, connection,
|
||||
table_name, column_name,
|
||||
def get_column_sequence(self, connection,
|
||||
table_name, column_name,
|
||||
schema=None, **kw):
|
||||
tablename = self.denormalize_name(table_name)
|
||||
colname = self.denormalize_name(column_name)
|
||||
@@ -528,7 +528,7 @@ class FBDialect(default.DefaultDialect):
|
||||
COALESCE(cs.rdb$bytes_per_character,1) AS flen,
|
||||
f.rdb$field_precision AS fprec,
|
||||
f.rdb$field_scale AS fscale,
|
||||
COALESCE(r.rdb$default_source,
|
||||
COALESCE(r.rdb$default_source,
|
||||
f.rdb$default_source) AS fdefault
|
||||
FROM rdb$relation_fields r
|
||||
JOIN rdb$fields f ON r.rdb$field_source=f.rdb$field_name
|
||||
@@ -563,7 +563,7 @@ class FBDialect(default.DefaultDialect):
|
||||
coltype = sqltypes.NULLTYPE
|
||||
elif colspec == 'INT64':
|
||||
coltype = coltype(
|
||||
precision=row['fprec'],
|
||||
precision=row['fprec'],
|
||||
scale=row['fscale'] * -1)
|
||||
elif colspec in ('VARYING', 'CSTRING'):
|
||||
coltype = coltype(row['flen'])
|
||||
@@ -582,7 +582,7 @@ class FBDialect(default.DefaultDialect):
|
||||
if row['fdefault'] is not None:
|
||||
# the value comes down as "DEFAULT 'value'": there may be
|
||||
# more than one whitespace around the "DEFAULT" keyword
|
||||
# and it may also be lower case
|
||||
# and it may also be lower case
|
||||
# (see also http://tracker.firebirdsql.org/browse/CORE-356)
|
||||
defexpr = row['fdefault'].lstrip()
|
||||
assert defexpr[:8].rstrip().upper() == \
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# firebird/kinterbasdb.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -17,20 +17,20 @@ Kinterbasedb backend specific keyword arguments are:
|
||||
SQLAlchemy uses 200 with Unicode, datetime and decimal support (see
|
||||
details__).
|
||||
|
||||
* concurrency_level - set the backend policy with regards to threading
|
||||
* concurrency_level - set the backend policy with regards to threading
|
||||
issues: by default SQLAlchemy uses policy 1 (see details__).
|
||||
|
||||
* enable_rowcount - True by default, setting this to False disables
|
||||
the usage of "cursor.rowcount" with the
|
||||
* enable_rowcount - True by default, setting this to False disables
|
||||
the usage of "cursor.rowcount" with the
|
||||
Kinterbasdb dialect, which SQLAlchemy ordinarily calls upon automatically
|
||||
after any UPDATE or DELETE statement. When disabled, SQLAlchemy's
|
||||
ResultProxy will return -1 for result.rowcount. The rationale here is
|
||||
that Kinterbasdb requires a second round trip to the database when
|
||||
.rowcount is called - since SQLA's resultproxy automatically closes
|
||||
the cursor after a non-result-returning statement, rowcount must be
|
||||
after any UPDATE or DELETE statement. When disabled, SQLAlchemy's
|
||||
ResultProxy will return -1 for result.rowcount. The rationale here is
|
||||
that Kinterbasdb requires a second round trip to the database when
|
||||
.rowcount is called - since SQLA's resultproxy automatically closes
|
||||
the cursor after a non-result-returning statement, rowcount must be
|
||||
called, if at all, before the result object is returned. Additionally,
|
||||
cursor.rowcount may not return correct results with older versions
|
||||
of Firebird, and setting this flag to False will also cause the
|
||||
of Firebird, and setting this flag to False will also cause the
|
||||
SQLAlchemy ORM to ignore its usage. The behavior can also be controlled on a
|
||||
per-execution basis using the `enable_rowcount` option with
|
||||
:meth:`execution_options()`::
|
||||
@@ -64,7 +64,7 @@ class _FBNumeric_kinterbasdb(sqltypes.Numeric):
|
||||
class FBExecutionContext_kinterbasdb(FBExecutionContext):
|
||||
@property
|
||||
def rowcount(self):
|
||||
if self.execution_options.get('enable_rowcount',
|
||||
if self.execution_options.get('enable_rowcount',
|
||||
self.dialect.enable_rowcount):
|
||||
return self.cursor.rowcount
|
||||
else:
|
||||
@@ -135,7 +135,7 @@ class FBDialect_kinterbasdb(FBDialect):
|
||||
# that for backward compatibility reasons returns a string like
|
||||
# LI-V6.3.3.12981 Firebird 2.0
|
||||
# where the first version is a fake one resembling the old
|
||||
# Interbase signature.
|
||||
# Interbase signature.
|
||||
|
||||
fbconn = connection.connection
|
||||
version = fbconn.server_version
|
||||
@@ -159,7 +159,7 @@ class FBDialect_kinterbasdb(FBDialect):
|
||||
msg = str(e)
|
||||
return ('Unable to complete network request to host' in msg or
|
||||
'Invalid connection state' in msg or
|
||||
'Invalid cursor state' in msg or
|
||||
'Invalid cursor state' in msg or
|
||||
'connection shutdown' in msg)
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# informix/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# informix/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# coding: gbk
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -7,8 +7,11 @@
|
||||
|
||||
"""Support for the Informix database.
|
||||
|
||||
This dialect is mostly functional as of SQLAlchemy 0.6.5.
|
||||
.. note::
|
||||
|
||||
The Informix dialect functions on current SQLAlchemy versions
|
||||
but is not regularly tested, and may have many issues and
|
||||
caveats not currently handled.
|
||||
|
||||
"""
|
||||
|
||||
@@ -23,7 +26,7 @@ from sqlalchemy import types as sqltypes
|
||||
RESERVED_WORDS = set(
|
||||
["abs", "absolute", "access", "access_method", "acos", "active", "add",
|
||||
"address", "add_months", "admin", "after", "aggregate", "alignment",
|
||||
"all", "allocate", "all_rows", "altere", "and", "ansi", "any", "append",
|
||||
"all", "allocate", "all_rows", "alter", "and", "ansi", "any", "append",
|
||||
"array", "as", "asc", "ascii", "asin", "at", "atan", "atan2", "attach",
|
||||
"attributes", "audit", "authentication", "authid", "authorization",
|
||||
"authorized", "auto", "autofree", "auto_reprepare", "auto_stat_mode",
|
||||
@@ -463,7 +466,7 @@ class InformixDialect(default.DefaultDialect):
|
||||
c = connection.execute(
|
||||
"""select t1.constrname as cons_name,
|
||||
t4.colname as local_column, t7.tabname as remote_table,
|
||||
t6.colname as remote_column, t7.owner as remote_owner
|
||||
t6.colname as remote_column, t7.owner as remote_owner
|
||||
from sysconstraints as t1 , systables as t2 ,
|
||||
sysindexes as t3 , syscolumns as t4 ,
|
||||
sysreferences as t5 , syscolumns as t6 , systables as t7 ,
|
||||
@@ -472,7 +475,7 @@ class InformixDialect(default.DefaultDialect):
|
||||
and t3.tabid = t2.tabid and t3.idxname = t1.idxname
|
||||
and t4.tabid = t2.tabid and t4.colno in (t3.part1, t3.part2, t3.part3,
|
||||
t3.part4, t3.part5, t3.part6, t3.part7, t3.part8, t3.part9, t3.part10,
|
||||
t3.part11, t3.part11, t3.part12, t3.part13, t3.part4, t3.part15, t3.part16)
|
||||
t3.part11, t3.part11, t3.part12, t3.part13, t3.part4, t3.part15, t3.part16)
|
||||
and t5.constrid = t1.constrid and t8.constrid = t5.primary
|
||||
and t6.tabid = t5.ptabid and t6.colno in (t9.part1, t9.part2, t9.part3,
|
||||
t9.part4, t9.part5, t9.part6, t9.part7, t9.part8, t9.part9, t9.part10,
|
||||
@@ -519,7 +522,7 @@ class InformixDialect(default.DefaultDialect):
|
||||
|
||||
# Select the column positions from sysindexes for sysconstraints
|
||||
data = connection.execute(
|
||||
"""select t2.*
|
||||
"""select t2.*
|
||||
from systables as t1, sysindexes as t2, sysconstraints as t3
|
||||
where t1.tabid=t2.tabid and t1.tabname=? and t1.owner=?
|
||||
and t2.idxname=t3.idxname and t3.constrtype='P'""",
|
||||
@@ -541,7 +544,7 @@ class InformixDialect(default.DefaultDialect):
|
||||
c = connection.execute(
|
||||
"""select t1.colname
|
||||
from syscolumns as t1, systables as t2
|
||||
where t2.tabname=? and t1.tabid = t2.tabid and
|
||||
where t2.tabname=? and t1.tabid = t2.tabid and
|
||||
t1.colno in (%s)""" % place_holder,
|
||||
table_name, *colpositions
|
||||
).fetchall()
|
||||
@@ -565,7 +568,7 @@ class InformixDialect(default.DefaultDialect):
|
||||
c = connection.execute(
|
||||
"""select t1.colname
|
||||
from syscolumns as t1, systables as t2
|
||||
where t2.tabname=? and t1.tabid = t2.tabid and
|
||||
where t2.tabname=? and t1.tabid = t2.tabid and
|
||||
t1.colno in (%s)""" % place_holder,
|
||||
table_name, *colnames
|
||||
).fetchall()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# informix/informixdb.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# maxdb/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
# maxdb/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the MaxDB database.
|
||||
|
||||
This dialect is *not* ported to SQLAlchemy 0.6 or 0.7.
|
||||
.. note::
|
||||
|
||||
This dialect is *not* tested on SQLAlchemy 0.6 or 0.7.
|
||||
The MaxDB dialect is **non-functional as of SQLAlchemy 0.6**,
|
||||
pending development efforts to bring it up-to-date.
|
||||
|
||||
Overview
|
||||
--------
|
||||
@@ -254,7 +255,7 @@ class MaxTimestamp(sqltypes.DateTime):
|
||||
value[20:])])
|
||||
else:
|
||||
raise exc.InvalidRequestError(
|
||||
"datetimeformat '%s' is not supported." %
|
||||
"datetimeformat '%s' is not supported." %
|
||||
dialect.datetimeformat)
|
||||
return process
|
||||
|
||||
@@ -282,18 +283,18 @@ class MaxDate(sqltypes.Date):
|
||||
if value is None:
|
||||
return None
|
||||
else:
|
||||
return datetime.date(int(value[0:4]), int(value[4:6]),
|
||||
return datetime.date(int(value[0:4]), int(value[4:6]),
|
||||
int(value[6:8]))
|
||||
elif dialect.datetimeformat == 'iso':
|
||||
def process(value):
|
||||
if value is None:
|
||||
return None
|
||||
else:
|
||||
return datetime.date(int(value[0:4]), int(value[5:7]),
|
||||
return datetime.date(int(value[0:4]), int(value[5:7]),
|
||||
int(value[8:10]))
|
||||
else:
|
||||
raise exc.InvalidRequestError(
|
||||
"datetimeformat '%s' is not supported." %
|
||||
"datetimeformat '%s' is not supported." %
|
||||
dialect.datetimeformat)
|
||||
return process
|
||||
|
||||
@@ -321,7 +322,7 @@ class MaxTime(sqltypes.Time):
|
||||
if value is None:
|
||||
return None
|
||||
else:
|
||||
return datetime.time(int(value[0:4]), int(value[4:6]),
|
||||
return datetime.time(int(value[0:4]), int(value[4:6]),
|
||||
int(value[6:8]))
|
||||
elif dialect.datetimeformat == 'iso':
|
||||
def process(value):
|
||||
@@ -332,7 +333,7 @@ class MaxTime(sqltypes.Time):
|
||||
int(value[8:10]))
|
||||
else:
|
||||
raise exc.InvalidRequestError(
|
||||
"datetimeformat '%s' is not supported." %
|
||||
"datetimeformat '%s' is not supported." %
|
||||
dialect.datetimeformat)
|
||||
return process
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# maxdb/sapdb.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -18,9 +18,9 @@ from sqlalchemy.dialects.mssql.base import \
|
||||
|
||||
|
||||
__all__ = (
|
||||
'INTEGER', 'BIGINT', 'SMALLINT', 'TINYINT', 'VARCHAR', 'NVARCHAR', 'CHAR',
|
||||
'INTEGER', 'BIGINT', 'SMALLINT', 'TINYINT', 'VARCHAR', 'NVARCHAR', 'CHAR',
|
||||
'NCHAR', 'TEXT', 'NTEXT', 'DECIMAL', 'NUMERIC', 'FLOAT', 'DATETIME',
|
||||
'DATETIME2', 'DATETIMEOFFSET', 'DATE', 'TIME', 'SMALLDATETIME',
|
||||
'DATETIME2', 'DATETIMEOFFSET', 'DATE', 'TIME', 'SMALLDATETIME',
|
||||
'BINARY', 'VARBINARY', 'BIT', 'REAL', 'IMAGE', 'TIMESTAMP',
|
||||
'MONEY', 'SMALLMONEY', 'UNIQUEIDENTIFIER', 'SQL_VARIANT', 'dialect'
|
||||
)
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/adodbapi.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -16,7 +16,7 @@ import sys
|
||||
class MSDateTime_adodbapi(MSDateTime):
|
||||
def result_processor(self, dialect, coltype):
|
||||
def process(value):
|
||||
# adodbapi will return datetimes with empty time
|
||||
# adodbapi will return datetimes with empty time
|
||||
# values as datetime.date() objects.
|
||||
# Promote them back to full datetime.datetime()
|
||||
if type(value) is datetime.date:
|
||||
@@ -49,7 +49,7 @@ class MSDialect_adodbapi(MSDialect):
|
||||
|
||||
connectors = ["Provider=SQLOLEDB"]
|
||||
if 'port' in keys:
|
||||
connectors.append ("Data Source=%s, %s" %
|
||||
connectors.append ("Data Source=%s, %s" %
|
||||
(keys.get("host"), keys.get("port")))
|
||||
else:
|
||||
connectors.append ("Data Source=%s" % keys.get("host"))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -74,7 +74,7 @@ will yield::
|
||||
SELECT TOP n
|
||||
|
||||
If using SQL Server 2005 or above, LIMIT with OFFSET
|
||||
support is available through the ``ROW_NUMBER OVER`` construct.
|
||||
support is available through the ``ROW_NUMBER OVER`` construct.
|
||||
For versions below 2005, LIMIT with OFFSET usage will fail.
|
||||
|
||||
Nullability
|
||||
@@ -107,10 +107,10 @@ Compatibility Levels
|
||||
--------------------
|
||||
MSSQL supports the notion of setting compatibility levels at the
|
||||
database level. This allows, for instance, to run a database that
|
||||
is compatibile with SQL2000 while running on a SQL2005 database
|
||||
is compatible with SQL2000 while running on a SQL2005 database
|
||||
server. ``server_version_info`` will always return the database
|
||||
server version information (in this case SQL2005) and not the
|
||||
compatibiility level information. Because of this, if running under
|
||||
compatibility level information. Because of this, if running under
|
||||
a backwards compatibility mode SQAlchemy may attempt to use T-SQL
|
||||
statements that are unable to be parsed by the database server.
|
||||
|
||||
@@ -119,14 +119,14 @@ Triggers
|
||||
|
||||
SQLAlchemy by default uses OUTPUT INSERTED to get at newly
|
||||
generated primary key values via IDENTITY columns or other
|
||||
server side defaults. MS-SQL does not
|
||||
server side defaults. MS-SQL does not
|
||||
allow the usage of OUTPUT INSERTED on tables that have triggers.
|
||||
To disable the usage of OUTPUT INSERTED on a per-table basis,
|
||||
specify ``implicit_returning=False`` for each :class:`.Table`
|
||||
which has triggers::
|
||||
|
||||
Table('mytable', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Table('mytable', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
# ...,
|
||||
implicit_returning=False
|
||||
)
|
||||
@@ -144,11 +144,11 @@ This option can also be specified engine-wide using the
|
||||
Enabling Snapshot Isolation
|
||||
---------------------------
|
||||
|
||||
Not necessarily specific to SQLAlchemy, SQL Server has a default transaction
|
||||
Not necessarily specific to SQLAlchemy, SQL Server has a default transaction
|
||||
isolation mode that locks entire tables, and causes even mildly concurrent
|
||||
applications to have long held locks and frequent deadlocks.
|
||||
Enabling snapshot isolation for the database as a whole is recommended
|
||||
for modern levels of concurrency support. This is accomplished via the
|
||||
applications to have long held locks and frequent deadlocks.
|
||||
Enabling snapshot isolation for the database as a whole is recommended
|
||||
for modern levels of concurrency support. This is accomplished via the
|
||||
following ALTER DATABASE commands executed at the SQL prompt::
|
||||
|
||||
ALTER DATABASE MyDatabase SET ALLOW_SNAPSHOT_ISOLATION ON
|
||||
@@ -161,14 +161,15 @@ http://msdn.microsoft.com/en-us/library/ms175095.aspx.
|
||||
Scalar Select Comparisons
|
||||
-------------------------
|
||||
|
||||
The MSSQL dialect contains a legacy behavior whereby comparing
|
||||
a scalar select to a value using the ``=`` or ``!=`` operator
|
||||
will resolve to IN or NOT IN, respectively. This behavior is
|
||||
deprecated and will be removed in 0.8 - the ``s.in_()``/``~s.in_()`` operators
|
||||
should be used when IN/NOT IN are desired.
|
||||
.. deprecated:: 0.8
|
||||
The MSSQL dialect contains a legacy behavior whereby comparing
|
||||
a scalar select to a value using the ``=`` or ``!=`` operator
|
||||
will resolve to IN or NOT IN, respectively. This behavior
|
||||
will be removed in 0.8 - the ``s.in_()``/``~s.in_()`` operators
|
||||
should be used when IN/NOT IN are desired.
|
||||
|
||||
For the time being, the existing behavior prevents a comparison
|
||||
between scalar select and another value that actually wants to use ``=``.
|
||||
between scalar select and another value that actually wants to use ``=``.
|
||||
To remove this behavior in a forwards-compatible way, apply this
|
||||
compilation rule by placing the following code at the module import
|
||||
level::
|
||||
@@ -176,7 +177,7 @@ level::
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.sql.expression import _BinaryExpression
|
||||
from sqlalchemy.sql.compiler import SQLCompiler
|
||||
|
||||
|
||||
@compiles(_BinaryExpression, 'mssql')
|
||||
def override_legacy_binary(element, compiler, **kw):
|
||||
return SQLCompiler.visit_binary(compiler, element, **kw)
|
||||
@@ -272,7 +273,7 @@ class _MSDate(sqltypes.Date):
|
||||
return value.date()
|
||||
elif isinstance(value, basestring):
|
||||
return datetime.date(*[
|
||||
int(x or 0)
|
||||
int(x or 0)
|
||||
for x in self._reg.match(value).groups()
|
||||
])
|
||||
else:
|
||||
@@ -303,7 +304,7 @@ class TIME(sqltypes.TIME):
|
||||
return value.time()
|
||||
elif isinstance(value, basestring):
|
||||
return datetime.time(*[
|
||||
int(x or 0)
|
||||
int(x or 0)
|
||||
for x in self._reg.match(value).groups()])
|
||||
else:
|
||||
return value
|
||||
@@ -608,7 +609,7 @@ class MSTypeCompiler(compiler.GenericTypeCompiler):
|
||||
return self._extend("TEXT", type_)
|
||||
|
||||
def visit_VARCHAR(self, type_):
|
||||
return self._extend("VARCHAR", type_,
|
||||
return self._extend("VARCHAR", type_,
|
||||
length = type_.length or 'max')
|
||||
|
||||
def visit_CHAR(self, type_):
|
||||
@@ -618,7 +619,7 @@ class MSTypeCompiler(compiler.GenericTypeCompiler):
|
||||
return self._extend("NCHAR", type_)
|
||||
|
||||
def visit_NVARCHAR(self, type_):
|
||||
return self._extend("NVARCHAR", type_,
|
||||
return self._extend("NVARCHAR", type_,
|
||||
length = type_.length or 'max')
|
||||
|
||||
def visit_date(self, type_):
|
||||
@@ -641,8 +642,8 @@ class MSTypeCompiler(compiler.GenericTypeCompiler):
|
||||
|
||||
def visit_VARBINARY(self, type_):
|
||||
return self._extend(
|
||||
"VARBINARY",
|
||||
type_,
|
||||
"VARBINARY",
|
||||
type_,
|
||||
length=type_.length or 'max')
|
||||
|
||||
def visit_boolean(self, type_):
|
||||
@@ -689,18 +690,22 @@ class MSExecutionContext(default.DefaultExecutionContext):
|
||||
not self.executemany
|
||||
|
||||
if self._enable_identity_insert:
|
||||
self.cursor.execute("SET IDENTITY_INSERT %s ON" %
|
||||
self.dialect.identifier_preparer.format_table(tbl))
|
||||
self.root_connection._cursor_execute(self.cursor,
|
||||
"SET IDENTITY_INSERT %s ON" %
|
||||
self.dialect.identifier_preparer.format_table(tbl),
|
||||
())
|
||||
|
||||
def post_exec(self):
|
||||
"""Disable IDENTITY_INSERT if enabled."""
|
||||
|
||||
conn = self.root_connection
|
||||
if self._select_lastrowid:
|
||||
if self.dialect.use_scope_identity:
|
||||
self.cursor.execute(
|
||||
"SELECT scope_identity() AS lastrowid", ())
|
||||
conn._cursor_execute(self.cursor,
|
||||
"SELECT scope_identity() AS lastrowid", ())
|
||||
else:
|
||||
self.cursor.execute("SELECT @@identity AS lastrowid", ())
|
||||
conn._cursor_execute(self.cursor,
|
||||
"SELECT @@identity AS lastrowid", ())
|
||||
# fetchall() ensures the cursor is consumed without closing it
|
||||
row = self.cursor.fetchall()[0]
|
||||
self._lastrowid = int(row[0])
|
||||
@@ -710,10 +715,11 @@ class MSExecutionContext(default.DefaultExecutionContext):
|
||||
self._result_proxy = base.FullyBufferedResultProxy(self)
|
||||
|
||||
if self._enable_identity_insert:
|
||||
self.cursor.execute(
|
||||
conn._cursor_execute(self.cursor,
|
||||
"SET IDENTITY_INSERT %s OFF" %
|
||||
self.dialect.identifier_preparer.
|
||||
format_table(self.compiled.statement.table)
|
||||
format_table(self.compiled.statement.table),
|
||||
()
|
||||
)
|
||||
|
||||
def get_lastrowid(self):
|
||||
@@ -723,7 +729,7 @@ class MSExecutionContext(default.DefaultExecutionContext):
|
||||
if self._enable_identity_insert:
|
||||
try:
|
||||
self.cursor.execute(
|
||||
"SET IDENTITY_INSERT %s OFF" %
|
||||
"SET IDENTITY_INSERT %s OFF" %
|
||||
self.dialect.identifier_preparer.\
|
||||
format_table(self.compiled.statement.table)
|
||||
)
|
||||
@@ -766,12 +772,12 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
def visit_concat_op(self, binary, **kw):
|
||||
return "%s + %s" % \
|
||||
(self.process(binary.left, **kw),
|
||||
(self.process(binary.left, **kw),
|
||||
self.process(binary.right, **kw))
|
||||
|
||||
def visit_match_op(self, binary, **kw):
|
||||
return "CONTAINS (%s, %s)" % (
|
||||
self.process(binary.left, **kw),
|
||||
self.process(binary.left, **kw),
|
||||
self.process(binary.right, **kw))
|
||||
|
||||
def get_select_precolumns(self, select):
|
||||
@@ -803,27 +809,28 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
so tries to wrap it in a subquery with ``row_number()`` criterion.
|
||||
|
||||
"""
|
||||
if not getattr(select, '_mssql_visit', None) and select._offset:
|
||||
if select._offset and not getattr(select, '_mssql_visit', None):
|
||||
# to use ROW_NUMBER(), an ORDER BY is required.
|
||||
orderby = self.process(select._order_by_clause)
|
||||
if not orderby:
|
||||
if not select._order_by_clause.clauses:
|
||||
raise exc.CompileError('MSSQL requires an order_by when '
|
||||
'using an offset.')
|
||||
|
||||
_offset = select._offset
|
||||
_limit = select._limit
|
||||
_order_by_clauses = select._order_by_clause.clauses
|
||||
select = select._generate()
|
||||
select._mssql_visit = True
|
||||
select = select.column(
|
||||
sql.literal_column("ROW_NUMBER() OVER (ORDER BY %s)" \
|
||||
% orderby).label("mssql_rn")
|
||||
sql.func.ROW_NUMBER().over(order_by=_order_by_clauses)
|
||||
.label("mssql_rn")
|
||||
).order_by(None).alias()
|
||||
|
||||
mssql_rn = sql.column('mssql_rn')
|
||||
limitselect = sql.select([c for c in select.c if
|
||||
c.key!='mssql_rn'])
|
||||
limitselect.append_whereclause(mssql_rn> _offset)
|
||||
c.key != 'mssql_rn'])
|
||||
limitselect.append_whereclause(mssql_rn > _offset)
|
||||
if _limit is not None:
|
||||
limitselect.append_whereclause(mssql_rn<=(_limit + _offset))
|
||||
limitselect.append_whereclause(mssql_rn <= (_limit + _offset))
|
||||
return self.process(limitselect, iswrapper=True, **kwargs)
|
||||
else:
|
||||
return compiler.SQLCompiler.visit_select(self, select, **kwargs)
|
||||
@@ -861,7 +868,7 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
return "SAVE TRANSACTION %s" % self.preparer.format_savepoint(savepoint_stmt)
|
||||
|
||||
def visit_rollback_to_savepoint(self, savepoint_stmt):
|
||||
return ("ROLLBACK TRANSACTION %s"
|
||||
return ("ROLLBACK TRANSACTION %s"
|
||||
% self.preparer.format_savepoint(savepoint_stmt))
|
||||
|
||||
def visit_column(self, column, result_map=None, **kwargs):
|
||||
@@ -875,15 +882,16 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
if result_map is not None:
|
||||
result_map[column.name.lower()] = \
|
||||
(column.name, (column, ),
|
||||
(column.name, (column, column.name,
|
||||
column.key),
|
||||
column.type)
|
||||
|
||||
return super(MSSQLCompiler, self).\
|
||||
visit_column(converted,
|
||||
visit_column(converted,
|
||||
result_map=None, **kwargs)
|
||||
|
||||
return super(MSSQLCompiler, self).visit_column(column,
|
||||
result_map=result_map,
|
||||
return super(MSSQLCompiler, self).visit_column(column,
|
||||
result_map=result_map,
|
||||
**kwargs)
|
||||
|
||||
def visit_binary(self, binary, **kwargs):
|
||||
@@ -892,27 +900,27 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
"""
|
||||
if (
|
||||
isinstance(binary.left, expression._BindParamClause)
|
||||
isinstance(binary.left, expression._BindParamClause)
|
||||
and binary.operator == operator.eq
|
||||
and not isinstance(binary.right, expression._BindParamClause)
|
||||
):
|
||||
return self.process(
|
||||
expression._BinaryExpression(binary.right,
|
||||
binary.left,
|
||||
binary.operator),
|
||||
expression._BinaryExpression(binary.right,
|
||||
binary.left,
|
||||
binary.operator),
|
||||
**kwargs)
|
||||
else:
|
||||
if (
|
||||
(binary.operator is operator.eq or
|
||||
binary.operator is operator.ne)
|
||||
(binary.operator is operator.eq or
|
||||
binary.operator is operator.ne)
|
||||
and (
|
||||
(isinstance(binary.left, expression._FromGrouping)
|
||||
and isinstance(binary.left.element,
|
||||
expression._ScalarSelect))
|
||||
or (isinstance(binary.right, expression._FromGrouping)
|
||||
and isinstance(binary.right.element,
|
||||
expression._ScalarSelect))
|
||||
or isinstance(binary.left, expression._ScalarSelect)
|
||||
(isinstance(binary.left, expression._FromGrouping)
|
||||
and isinstance(binary.left.element,
|
||||
expression._ScalarSelect))
|
||||
or (isinstance(binary.right, expression._FromGrouping)
|
||||
and isinstance(binary.right.element,
|
||||
expression._ScalarSelect))
|
||||
or isinstance(binary.left, expression._ScalarSelect)
|
||||
or isinstance(binary.right, expression._ScalarSelect)
|
||||
)
|
||||
):
|
||||
@@ -944,10 +952,10 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
columns = [
|
||||
self.process(
|
||||
col_label(c),
|
||||
within_columns_clause=True,
|
||||
col_label(c),
|
||||
within_columns_clause=True,
|
||||
result_map=self.result_map
|
||||
)
|
||||
)
|
||||
for c in expression._select_iterables(returning_cols)
|
||||
]
|
||||
return 'OUTPUT ' + ', '.join(columns)
|
||||
@@ -967,7 +975,7 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
label_select_column(select, column, asfrom)
|
||||
|
||||
def for_update_clause(self, select):
|
||||
# "FOR UPDATE" is only allowed on "DECLARE CURSOR" which
|
||||
# "FOR UPDATE" is only allowed on "DECLARE CURSOR" which
|
||||
# SQLAlchemy doesn't use
|
||||
return ''
|
||||
|
||||
@@ -980,6 +988,22 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
else:
|
||||
return ""
|
||||
|
||||
def update_from_clause(self, update_stmt,
|
||||
from_table, extra_froms,
|
||||
from_hints,
|
||||
**kw):
|
||||
"""Render the UPDATE..FROM clause specific to MSSQL.
|
||||
|
||||
In MSSQL, if the UPDATE statement involves an alias of the table to
|
||||
be updated, then the table itself must be added to the FROM list as
|
||||
well. Otherwise, it is optional. Here, we add it regardless.
|
||||
|
||||
"""
|
||||
return "FROM " + ', '.join(
|
||||
t._compiler_dispatch(self, asfrom=True,
|
||||
fromhints=from_hints, **kw)
|
||||
for t in [from_table] + extra_froms)
|
||||
|
||||
class MSSQLStrictCompiler(MSSQLCompiler):
|
||||
"""A subclass of MSSQLCompiler which disables the usage of bind
|
||||
parameters where not allowed natively by MS-SQL.
|
||||
@@ -993,14 +1017,14 @@ class MSSQLStrictCompiler(MSSQLCompiler):
|
||||
def visit_in_op(self, binary, **kw):
|
||||
kw['literal_binds'] = True
|
||||
return "%s IN %s" % (
|
||||
self.process(binary.left, **kw),
|
||||
self.process(binary.left, **kw),
|
||||
self.process(binary.right, **kw)
|
||||
)
|
||||
|
||||
def visit_notin_op(self, binary, **kw):
|
||||
kw['literal_binds'] = True
|
||||
return "%s NOT IN %s" % (
|
||||
self.process(binary.left, **kw),
|
||||
self.process(binary.left, **kw),
|
||||
self.process(binary.right, **kw)
|
||||
)
|
||||
|
||||
@@ -1029,7 +1053,7 @@ class MSSQLStrictCompiler(MSSQLCompiler):
|
||||
|
||||
class MSDDLCompiler(compiler.DDLCompiler):
|
||||
def get_column_specification(self, column, **kwargs):
|
||||
colspec = (self.preparer.format_column(column) + " "
|
||||
colspec = (self.preparer.format_column(column) + " "
|
||||
+ self.dialect.type_compiler.process(column.type))
|
||||
|
||||
if column.nullable is not None:
|
||||
@@ -1040,7 +1064,7 @@ class MSDDLCompiler(compiler.DDLCompiler):
|
||||
|
||||
if column.table is None:
|
||||
raise exc.CompileError(
|
||||
"mssql requires Table-bound columns "
|
||||
"mssql requires Table-bound columns "
|
||||
"in order to generate DDL")
|
||||
|
||||
seq_col = column.table._autoincrement_column
|
||||
@@ -1075,7 +1099,7 @@ class MSIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
reserved_words = RESERVED_WORDS
|
||||
|
||||
def __init__(self, dialect):
|
||||
super(MSIdentifierPreparer, self).__init__(dialect, initial_quote='[',
|
||||
super(MSIdentifierPreparer, self).__init__(dialect, initial_quote='[',
|
||||
final_quote=']')
|
||||
|
||||
def _escape_identifier(self, value):
|
||||
@@ -1140,7 +1164,7 @@ class MSDialect(default.DefaultDialect):
|
||||
super(MSDialect, self).initialize(connection)
|
||||
if self.server_version_info[0] not in range(8, 17):
|
||||
# FreeTDS with version 4.2 seems to report here
|
||||
# a number like "95.10.255". Don't know what
|
||||
# a number like "95.10.255". Don't know what
|
||||
# that is. So emit warning.
|
||||
util.warn(
|
||||
"Unrecognized server version info '%s'. Version specific "
|
||||
@@ -1241,11 +1265,11 @@ class MSDialect(default.DefaultDialect):
|
||||
"join sys.schemas as sch on sch.schema_id=tab.schema_id "
|
||||
"where tab.name = :tabname "
|
||||
"and sch.name=:schname "
|
||||
"and ind.is_primary_key=0",
|
||||
"and ind.is_primary_key=0",
|
||||
bindparams=[
|
||||
sql.bindparam('tabname', tablename,
|
||||
sql.bindparam('tabname', tablename,
|
||||
sqltypes.String(convert_unicode=True)),
|
||||
sql.bindparam('schname', current_schema,
|
||||
sql.bindparam('schname', current_schema,
|
||||
sqltypes.String(convert_unicode=True))
|
||||
],
|
||||
typemap = {
|
||||
@@ -1272,9 +1296,9 @@ class MSDialect(default.DefaultDialect):
|
||||
"where tab.name=:tabname "
|
||||
"and sch.name=:schname",
|
||||
bindparams=[
|
||||
sql.bindparam('tabname', tablename,
|
||||
sql.bindparam('tabname', tablename,
|
||||
sqltypes.String(convert_unicode=True)),
|
||||
sql.bindparam('schname', current_schema,
|
||||
sql.bindparam('schname', current_schema,
|
||||
sqltypes.String(convert_unicode=True))
|
||||
],
|
||||
typemap = {
|
||||
@@ -1302,9 +1326,9 @@ class MSDialect(default.DefaultDialect):
|
||||
"views.schema_id=sch.schema_id and "
|
||||
"views.name=:viewname and sch.name=:schname",
|
||||
bindparams=[
|
||||
sql.bindparam('viewname', viewname,
|
||||
sql.bindparam('viewname', viewname,
|
||||
sqltypes.String(convert_unicode=True)),
|
||||
sql.bindparam('schname', current_schema,
|
||||
sql.bindparam('schname', current_schema,
|
||||
sqltypes.String(convert_unicode=True))
|
||||
]
|
||||
)
|
||||
@@ -1332,7 +1356,7 @@ class MSDialect(default.DefaultDialect):
|
||||
row = c.fetchone()
|
||||
if row is None:
|
||||
break
|
||||
(name, type, nullable, charlen,
|
||||
(name, type, nullable, charlen,
|
||||
numericprec, numericscale, default, collation) = (
|
||||
row[columns.c.column_name],
|
||||
row[columns.c.data_type],
|
||||
@@ -1346,7 +1370,7 @@ class MSDialect(default.DefaultDialect):
|
||||
coltype = self.ischema_names.get(type, None)
|
||||
|
||||
kwargs = {}
|
||||
if coltype in (MSString, MSChar, MSNVarchar, MSNChar, MSText,
|
||||
if coltype in (MSString, MSChar, MSNVarchar, MSNChar, MSText,
|
||||
MSNText, MSBinary, MSVarBinary,
|
||||
sqltypes.LargeBinary):
|
||||
kwargs['length'] = charlen
|
||||
@@ -1358,7 +1382,7 @@ class MSDialect(default.DefaultDialect):
|
||||
|
||||
if coltype is None:
|
||||
util.warn(
|
||||
"Did not recognize type '%s' of column '%s'" %
|
||||
"Did not recognize type '%s' of column '%s'" %
|
||||
(type, name))
|
||||
coltype = sqltypes.NULLTYPE
|
||||
else:
|
||||
@@ -1382,7 +1406,7 @@ class MSDialect(default.DefaultDialect):
|
||||
colmap[col['name']] = col
|
||||
# We also run an sp_columns to check for identity columns:
|
||||
cursor = connection.execute("sp_columns @table_name = '%s', "
|
||||
"@table_owner = '%s'"
|
||||
"@table_owner = '%s'"
|
||||
% (tablename, current_schema))
|
||||
ic = None
|
||||
while True:
|
||||
@@ -1401,7 +1425,7 @@ class MSDialect(default.DefaultDialect):
|
||||
if ic is not None and self.server_version_info >= MS_2005_VERSION:
|
||||
table_fullname = "%s.%s" % (current_schema, tablename)
|
||||
cursor = connection.execute(
|
||||
"select ident_seed('%s'), ident_incr('%s')"
|
||||
"select ident_seed('%s'), ident_incr('%s')"
|
||||
% (table_fullname, table_fullname)
|
||||
)
|
||||
|
||||
@@ -1421,16 +1445,17 @@ class MSDialect(default.DefaultDialect):
|
||||
RR = ischema.ref_constraints
|
||||
# information_schema.table_constraints
|
||||
TC = ischema.constraints
|
||||
# information_schema.constraint_column_usage:
|
||||
# information_schema.constraint_column_usage:
|
||||
# the constrained column
|
||||
C = ischema.key_constraints.alias('C')
|
||||
# information_schema.constraint_column_usage:
|
||||
C = ischema.key_constraints.alias('C')
|
||||
# information_schema.constraint_column_usage:
|
||||
# the referenced column
|
||||
R = ischema.key_constraints.alias('R')
|
||||
R = ischema.key_constraints.alias('R')
|
||||
|
||||
# Primary key constraints
|
||||
s = sql.select([C.c.column_name, TC.c.constraint_type],
|
||||
sql.and_(TC.c.constraint_name == C.c.constraint_name,
|
||||
TC.c.table_schema == C.c.table_schema,
|
||||
C.c.table_name == tablename,
|
||||
C.c.table_schema == current_schema)
|
||||
)
|
||||
@@ -1448,12 +1473,12 @@ class MSDialect(default.DefaultDialect):
|
||||
RR = ischema.ref_constraints
|
||||
# information_schema.table_constraints
|
||||
TC = ischema.constraints
|
||||
# information_schema.constraint_column_usage:
|
||||
# information_schema.constraint_column_usage:
|
||||
# the constrained column
|
||||
C = ischema.key_constraints.alias('C')
|
||||
# information_schema.constraint_column_usage:
|
||||
C = ischema.key_constraints.alias('C')
|
||||
# information_schema.constraint_column_usage:
|
||||
# the referenced column
|
||||
R = ischema.key_constraints.alias('R')
|
||||
R = ischema.key_constraints.alias('R')
|
||||
|
||||
# Foreign key constraints
|
||||
s = sql.select([C.c.column_name,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/information_schema.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -15,8 +15,10 @@ class CoerceUnicode(TypeDecorator):
|
||||
impl = Unicode
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
# Py2K
|
||||
if isinstance(value, str):
|
||||
value = value.decode(dialect.encoding)
|
||||
# end Py2K
|
||||
return value
|
||||
|
||||
schemata = Table("SCHEMATA", ischema,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/mxodbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -41,13 +41,13 @@ simplistic statements.
|
||||
|
||||
For this reason, the mxODBC dialect uses the "native" mode by default only for
|
||||
INSERT, UPDATE, and DELETE statements, and uses the escaped string mode for
|
||||
all other statements.
|
||||
all other statements.
|
||||
|
||||
This behavior can be controlled via
|
||||
:meth:`~sqlalchemy.sql.expression.Executable.execution_options` using the
|
||||
``native_odbc_execute`` flag with a value of ``True`` or ``False``, where a
|
||||
value of ``True`` will unconditionally use native bind parameters and a value
|
||||
of ``False`` will uncondtionally use string-escaped parameters.
|
||||
of ``False`` will unconditionally use string-escaped parameters.
|
||||
|
||||
"""
|
||||
|
||||
@@ -55,7 +55,7 @@ of ``False`` will uncondtionally use string-escaped parameters.
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.connectors.mxodbc import MxODBCConnector
|
||||
from sqlalchemy.dialects.mssql.pyodbc import MSExecutionContext_pyodbc
|
||||
from sqlalchemy.dialects.mssql.base import (MSDialect,
|
||||
from sqlalchemy.dialects.mssql.base import (MSDialect,
|
||||
MSSQLStrictCompiler,
|
||||
_MSDateTime, _MSDate, TIME)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/pymssql.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -21,8 +21,8 @@ Sample connect string::
|
||||
mssql+pymssql://<username>:<password>@<freetds_name>
|
||||
|
||||
Adding "?charset=utf8" or similar will cause pymssql to return
|
||||
strings as Python unicode objects. This can potentially improve
|
||||
performance in some scenarios as decoding of strings is
|
||||
strings as Python unicode objects. This can potentially improve
|
||||
performance in some scenarios as decoding of strings is
|
||||
handled natively.
|
||||
|
||||
Limitations
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/pyodbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -35,14 +35,14 @@ Examples of pyodbc connection string URLs:
|
||||
|
||||
dsn=mydsn;UID=user;PWD=pass;LANGUAGE=us_english
|
||||
|
||||
* ``mssql+pyodbc://user:pass@host/db`` - connects using a connection
|
||||
* ``mssql+pyodbc://user:pass@host/db`` - connects using a connection
|
||||
that would appear like::
|
||||
|
||||
DRIVER={SQL Server};Server=host;Database=db;UID=user;PWD=pass
|
||||
|
||||
* ``mssql+pyodbc://user:pass@host:123/db`` - connects using a connection
|
||||
string which includes the port
|
||||
information using the comma syntax. This will create the following
|
||||
information using the comma syntax. This will create the following
|
||||
connection string::
|
||||
|
||||
DRIVER={SQL Server};Server=host,123;Database=db;UID=user;PWD=pass
|
||||
@@ -80,6 +80,34 @@ the python shell. For example::
|
||||
>>> urllib.quote_plus('dsn=mydsn;Database=db')
|
||||
'dsn%3Dmydsn%3BDatabase%3Ddb'
|
||||
|
||||
Unicode Binds
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
The current state of PyODBC on a unix backend with FreeTDS and/or
|
||||
EasySoft is poor regarding unicode; different OS platforms and versions of UnixODBC
|
||||
versus IODBC versus FreeTDS/EasySoft versus PyODBC itself dramatically
|
||||
alter how strings are received. The PyODBC dialect attempts to use all the information
|
||||
it knows to determine whether or not a Python unicode literal can be
|
||||
passed directly to the PyODBC driver or not; while SQLAlchemy can encode
|
||||
these to bytestrings first, some users have reported that PyODBC mis-handles
|
||||
bytestrings for certain encodings and requires a Python unicode object,
|
||||
while the author has observed widespread cases where a Python unicode
|
||||
is completely misinterpreted by PyODBC, particularly when dealing with
|
||||
the information schema tables used in table reflection, and the value
|
||||
must first be encoded to a bytestring.
|
||||
|
||||
It is for this reason that whether or not unicode literals for bound
|
||||
parameters be sent to PyODBC can be controlled using the
|
||||
``supports_unicode_binds`` parameter to ``create_engine()``. When
|
||||
left at its default of ``None``, the PyODBC dialect will use its
|
||||
best guess as to whether or not the driver deals with unicode literals
|
||||
well. When ``False``, unicode literals will be encoded first, and when
|
||||
``True`` unicode literals will be passed straight through. This is an interim
|
||||
flag that hopefully should not be needed when the unicode situation stabilizes
|
||||
for unix + PyODBC.
|
||||
|
||||
.. versionadded:: 0.7.7
|
||||
``supports_unicode_binds`` parameter to ``create_engine()``\ .
|
||||
|
||||
"""
|
||||
|
||||
@@ -171,7 +199,7 @@ class MSExecutionContext_pyodbc(MSExecutionContext):
|
||||
|
||||
super(MSExecutionContext_pyodbc, self).pre_exec()
|
||||
|
||||
# don't embed the scope_identity select into an
|
||||
# don't embed the scope_identity select into an
|
||||
# "INSERT .. DEFAULT VALUES"
|
||||
if self._select_lastrowid and \
|
||||
self.dialect.use_scope_identity and \
|
||||
@@ -183,11 +211,11 @@ class MSExecutionContext_pyodbc(MSExecutionContext):
|
||||
def post_exec(self):
|
||||
if self._embedded_scope_identity:
|
||||
# Fetch the last inserted id from the manipulated statement
|
||||
# We may have to skip over a number of result sets with
|
||||
# We may have to skip over a number of result sets with
|
||||
# no data (due to triggers, etc.)
|
||||
while True:
|
||||
try:
|
||||
# fetchall() ensures the cursor is consumed
|
||||
# fetchall() ensures the cursor is consumed
|
||||
# without closing it (FreeTDS particularly)
|
||||
row = self.cursor.fetchall()[0]
|
||||
break
|
||||
@@ -217,7 +245,8 @@ class MSDialect_pyodbc(PyODBCConnector, MSDialect):
|
||||
def __init__(self, description_encoding='latin-1', **params):
|
||||
super(MSDialect_pyodbc, self).__init__(**params)
|
||||
self.description_encoding = description_encoding
|
||||
self.use_scope_identity = self.dbapi and \
|
||||
self.use_scope_identity = self.use_scope_identity and \
|
||||
self.dbapi and \
|
||||
hasattr(self.dbapi.Cursor, 'nextset')
|
||||
self._need_decimal_fix = self.dbapi and \
|
||||
self._dbapi_version() < (2, 1, 8)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/zxjdbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -68,7 +68,7 @@ class MSDialect_zxjdbc(ZxJDBCConnector, MSDialect):
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
return tuple(
|
||||
int(x)
|
||||
int(x)
|
||||
for x in connection.connection.dbversion.split('.')
|
||||
)
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# mysql/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.mysql import base, mysqldb, oursql, \
|
||||
pyodbc, zxjdbc, mysqlconnector, pymysql
|
||||
pyodbc, zxjdbc, mysqlconnector, pymysql,\
|
||||
gaerdbms
|
||||
|
||||
# default dialect
|
||||
base.dialect = mysqldb.dialect
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -9,29 +9,11 @@
|
||||
Supported Versions and Features
|
||||
-------------------------------
|
||||
|
||||
SQLAlchemy supports 6 major MySQL versions: 3.23, 4.0, 4.1, 5.0, 5.1 and 6.0,
|
||||
with capabilities increasing with more modern servers.
|
||||
|
||||
Versions 4.1 and higher support the basic SQL functionality that SQLAlchemy
|
||||
uses in the ORM and SQL expressions. These versions pass the applicable tests
|
||||
in the suite 100%. No heroic measures are taken to work around major missing
|
||||
SQL features- if your server version does not support sub-selects, for
|
||||
SQLAlchemy supports MySQL starting with version 4.1 through modern releases.
|
||||
However, no heroic measures are taken to work around major missing
|
||||
SQL features - if your server version does not support sub-selects, for
|
||||
example, they won't work in SQLAlchemy either.
|
||||
|
||||
Most available DBAPI drivers are supported; see below.
|
||||
|
||||
===================================== ===============
|
||||
Feature Minimum Version
|
||||
===================================== ===============
|
||||
sqlalchemy.orm 4.1.1
|
||||
Table Reflection 3.23.x
|
||||
DDL Generation 4.1.1
|
||||
utf8/Full Unicode Connections 4.1.1
|
||||
Transactions 3.23.15
|
||||
Two-Phase Transactions 5.0.3
|
||||
Nested Transactions 5.0.3
|
||||
===================================== ===============
|
||||
|
||||
See the official MySQL documentation for detailed information about features
|
||||
supported in any given server release.
|
||||
|
||||
@@ -44,18 +26,21 @@ Connection Timeouts
|
||||
-------------------
|
||||
|
||||
MySQL features an automatic connection close behavior, for connections that have
|
||||
been idle for eight hours or more. To circumvent having this issue, use the
|
||||
been idle for eight hours or more. To circumvent having this issue, use the
|
||||
``pool_recycle`` option which controls the maximum age of any connection::
|
||||
|
||||
engine = create_engine('mysql+mysqldb://...', pool_recycle=3600)
|
||||
|
||||
.. _mysql_storage_engines:
|
||||
|
||||
Storage Engines
|
||||
---------------
|
||||
|
||||
Most MySQL server installations have a default table type of ``MyISAM``, a
|
||||
non-transactional table type. During a transaction, non-transactional storage
|
||||
engines do not participate and continue to store table changes in autocommit
|
||||
mode. For fully atomic transactions, all participating tables must use a
|
||||
mode. For fully atomic transactions as well as support for foreign key
|
||||
constraints, all participating tables must use a
|
||||
transactional engine such as ``InnoDB``, ``Falcon``, ``SolidDB``, `PBXT`, etc.
|
||||
|
||||
Storage engines can be elected when creating tables in SQLAlchemy by supplying
|
||||
@@ -68,6 +53,10 @@ creation option can be specified in this syntax::
|
||||
mysql_charset='utf8'
|
||||
)
|
||||
|
||||
.. seealso::
|
||||
|
||||
`The InnoDB Storage Engine <http://dev.mysql.com/doc/refman/5.0/en/innodb-storage-engine.html>`_ - on the MySQL website.
|
||||
|
||||
Case Sensitivity and Table Reflection
|
||||
-------------------------------------
|
||||
|
||||
@@ -87,19 +76,19 @@ 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
|
||||
: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``,
|
||||
``READ COMMITTED``, ``READ UNCOMMITTED``,
|
||||
``REPEATABLE READ``, and ``SERIALIZABLE``::
|
||||
|
||||
engine = create_engine(
|
||||
"mysql://scott:tiger@localhost/test",
|
||||
"mysql://scott:tiger@localhost/test",
|
||||
isolation_level="READ UNCOMMITTED"
|
||||
)
|
||||
|
||||
(new in 0.7.6)
|
||||
.. versionadded:: 0.7.6
|
||||
|
||||
Keys
|
||||
----
|
||||
@@ -185,6 +174,24 @@ available.
|
||||
|
||||
update(..., mysql_limit=10)
|
||||
|
||||
rowcount Support
|
||||
----------------
|
||||
|
||||
SQLAlchemy standardizes the DBAPI ``cursor.rowcount`` attribute to be the
|
||||
usual definition of "number of rows matched by an UPDATE or DELETE" statement.
|
||||
This is in contradiction to the default setting on most MySQL DBAPI drivers,
|
||||
which is "number of rows actually modified/deleted". For this reason, the
|
||||
SQLAlchemy MySQL dialects always set the ``constants.CLIENT.FOUND_ROWS`` flag,
|
||||
or whatever is equivalent for the DBAPI in use, on connect, unless the flag value
|
||||
is overridden using DBAPI-specific options
|
||||
(such as ``client_flag`` for the MySQL-Python driver, ``found_rows`` for the
|
||||
OurSQL driver).
|
||||
|
||||
See also:
|
||||
|
||||
:attr:`.ResultProxy.rowcount`
|
||||
|
||||
|
||||
CAST Support
|
||||
------------
|
||||
|
||||
@@ -242,7 +249,7 @@ 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
|
||||
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')
|
||||
@@ -252,7 +259,7 @@ 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
|
||||
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:
|
||||
@@ -344,9 +351,9 @@ class _FloatType(_NumericType, sqltypes.Float):
|
||||
(precision is None and scale is not None) or
|
||||
(precision is not None and scale is None)
|
||||
):
|
||||
raise exc.ArgumentError(
|
||||
"You must specify both precision and scale or omit "
|
||||
"both altogether.")
|
||||
raise exc.ArgumentError(
|
||||
"You must specify both precision and scale or omit "
|
||||
"both altogether.")
|
||||
|
||||
super(_FloatType, self).__init__(precision=precision, asdecimal=asdecimal, **kw)
|
||||
self.scale = scale
|
||||
@@ -1273,11 +1280,11 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
def visit_cast(self, cast, **kwargs):
|
||||
# No cast until 4, no decimals until 5.
|
||||
if not self.dialect._supports_cast:
|
||||
return self.process(cast.clause)
|
||||
return self.process(cast.clause.self_group())
|
||||
|
||||
type_ = self.process(cast.typeclause)
|
||||
if type_ is None:
|
||||
return self.process(cast.clause)
|
||||
return self.process(cast.clause.self_group())
|
||||
|
||||
return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
|
||||
|
||||
@@ -1289,13 +1296,13 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
def get_select_precolumns(self, select):
|
||||
"""Add special MySQL keywords in place of DISTINCT.
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
this usage is deprecated. :meth:`.Select.prefix_with`
|
||||
should be used for special keywords at the start
|
||||
of a SELECT.
|
||||
|
||||
|
||||
"""
|
||||
if isinstance(select._distinct, basestring):
|
||||
return select._distinct.upper() + " "
|
||||
@@ -1343,16 +1350,16 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
if limit is None:
|
||||
# hardwire the upper limit. Currently
|
||||
# needed by OurSQL with Python 3
|
||||
# (https://bugs.launchpad.net/oursql/+bug/686232),
|
||||
# (https://bugs.launchpad.net/oursql/+bug/686232),
|
||||
# but also is consistent with the usage of the upper
|
||||
# bound as part of MySQL's "syntax" for OFFSET with
|
||||
# no LIMIT
|
||||
return ' \n LIMIT %s, %s' % (
|
||||
self.process(sql.literal(offset)),
|
||||
self.process(sql.literal(offset)),
|
||||
"18446744073709551615")
|
||||
else:
|
||||
return ' \n LIMIT %s, %s' % (
|
||||
self.process(sql.literal(offset)),
|
||||
self.process(sql.literal(offset)),
|
||||
self.process(sql.literal(limit)))
|
||||
else:
|
||||
# No offset provided, so just use the limit
|
||||
@@ -1366,10 +1373,10 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
return None
|
||||
|
||||
def update_tables_clause(self, update_stmt, from_table, extra_froms, **kw):
|
||||
return ', '.join(t._compiler_dispatch(self, asfrom=True, **kw)
|
||||
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,
|
||||
def update_from_clause(self, update_stmt, from_table,
|
||||
extra_froms, from_hints, **kw):
|
||||
return None
|
||||
|
||||
@@ -1395,8 +1402,12 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
auto_inc_column is not list(table.primary_key)[0]:
|
||||
if constraint_string:
|
||||
constraint_string += ", \n\t"
|
||||
constraint_string += "KEY `idx_autoinc_%s`(`%s`)" % (auto_inc_column.name, \
|
||||
self.preparer.format_column(auto_inc_column))
|
||||
constraint_string += "KEY %s (%s)" % (
|
||||
self.preparer.quote(
|
||||
"idx_autoinc_%s" % auto_inc_column.name, None
|
||||
),
|
||||
self.preparer.format_column(auto_inc_column)
|
||||
)
|
||||
|
||||
return constraint_string
|
||||
|
||||
@@ -1431,7 +1442,7 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
|
||||
opts = dict(
|
||||
(
|
||||
k[len(self.dialect.name)+1:].upper(),
|
||||
k[len(self.dialect.name)+1:].upper(),
|
||||
v
|
||||
)
|
||||
for k, v in table.kwargs.items()
|
||||
@@ -1447,7 +1458,7 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
arg = "'%s'" % arg.replace("\\", "\\\\").replace("'", "''")
|
||||
|
||||
if opt in ('DATA_DIRECTORY', 'INDEX_DIRECTORY',
|
||||
'DEFAULT_CHARACTER_SET', 'CHARACTER_SET',
|
||||
'DEFAULT_CHARACTER_SET', 'CHARACTER_SET',
|
||||
'DEFAULT_CHARSET',
|
||||
'DEFAULT_COLLATE'):
|
||||
opt = opt.replace('_', ' ')
|
||||
@@ -1467,7 +1478,7 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
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),
|
||||
self._index_identifier(index.name),
|
||||
index.quote)
|
||||
|
||||
text = "CREATE "
|
||||
@@ -1576,24 +1587,24 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
if type_.precision is None:
|
||||
return self._extend_numeric(type_, "NUMERIC")
|
||||
elif type_.scale is None:
|
||||
return self._extend_numeric(type_,
|
||||
"NUMERIC(%(precision)s)" %
|
||||
return self._extend_numeric(type_,
|
||||
"NUMERIC(%(precision)s)" %
|
||||
{'precision': type_.precision})
|
||||
else:
|
||||
return self._extend_numeric(type_,
|
||||
"NUMERIC(%(precision)s, %(scale)s)" %
|
||||
return self._extend_numeric(type_,
|
||||
"NUMERIC(%(precision)s, %(scale)s)" %
|
||||
{'precision': type_.precision, 'scale' : type_.scale})
|
||||
|
||||
def visit_DECIMAL(self, type_):
|
||||
if type_.precision is None:
|
||||
return self._extend_numeric(type_, "DECIMAL")
|
||||
elif type_.scale is None:
|
||||
return self._extend_numeric(type_,
|
||||
"DECIMAL(%(precision)s)" %
|
||||
return self._extend_numeric(type_,
|
||||
"DECIMAL(%(precision)s)" %
|
||||
{'precision': type_.precision})
|
||||
else:
|
||||
return self._extend_numeric(type_,
|
||||
"DECIMAL(%(precision)s, %(scale)s)" %
|
||||
return self._extend_numeric(type_,
|
||||
"DECIMAL(%(precision)s, %(scale)s)" %
|
||||
{'precision': type_.precision, 'scale' : type_.scale})
|
||||
|
||||
def visit_DOUBLE(self, type_):
|
||||
@@ -1616,7 +1627,7 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
if self._mysql_type(type_) and \
|
||||
type_.scale is not None and \
|
||||
type_.precision is not None:
|
||||
return self._extend_numeric(type_,
|
||||
return self._extend_numeric(type_,
|
||||
"FLOAT(%s, %s)" % (type_.precision, type_.scale))
|
||||
elif type_.precision is not None:
|
||||
return self._extend_numeric(type_, "FLOAT(%s)" % (type_.precision,))
|
||||
@@ -1625,24 +1636,24 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
|
||||
def visit_INTEGER(self, type_):
|
||||
if self._mysql_type(type_) and type_.display_width is not None:
|
||||
return self._extend_numeric(type_,
|
||||
"INTEGER(%(display_width)s)" %
|
||||
return self._extend_numeric(type_,
|
||||
"INTEGER(%(display_width)s)" %
|
||||
{'display_width': type_.display_width})
|
||||
else:
|
||||
return self._extend_numeric(type_, "INTEGER")
|
||||
|
||||
def visit_BIGINT(self, type_):
|
||||
if self._mysql_type(type_) and type_.display_width is not None:
|
||||
return self._extend_numeric(type_,
|
||||
"BIGINT(%(display_width)s)" %
|
||||
return self._extend_numeric(type_,
|
||||
"BIGINT(%(display_width)s)" %
|
||||
{'display_width': type_.display_width})
|
||||
else:
|
||||
return self._extend_numeric(type_, "BIGINT")
|
||||
|
||||
def visit_MEDIUMINT(self, type_):
|
||||
if self._mysql_type(type_) and type_.display_width is not None:
|
||||
return self._extend_numeric(type_,
|
||||
"MEDIUMINT(%(display_width)s)" %
|
||||
return self._extend_numeric(type_,
|
||||
"MEDIUMINT(%(display_width)s)" %
|
||||
{'display_width': type_.display_width})
|
||||
else:
|
||||
return self._extend_numeric(type_, "MEDIUMINT")
|
||||
@@ -1655,8 +1666,8 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
|
||||
def visit_SMALLINT(self, type_):
|
||||
if self._mysql_type(type_) and type_.display_width is not None:
|
||||
return self._extend_numeric(type_,
|
||||
"SMALLINT(%(display_width)s)" %
|
||||
return self._extend_numeric(type_,
|
||||
"SMALLINT(%(display_width)s)" %
|
||||
{'display_width': type_.display_width}
|
||||
)
|
||||
else:
|
||||
@@ -1706,7 +1717,7 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
return self._extend_string(type_, {}, "VARCHAR(%d)" % type_.length)
|
||||
else:
|
||||
raise exc.CompileError(
|
||||
"VARCHAR requires a length on dialect %s" %
|
||||
"VARCHAR requires a length on dialect %s" %
|
||||
self.dialect.name)
|
||||
|
||||
def visit_CHAR(self, type_):
|
||||
@@ -1722,7 +1733,7 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
return self._extend_string(type_, {'national':True}, "VARCHAR(%(length)s)" % {'length': type_.length})
|
||||
else:
|
||||
raise exc.CompileError(
|
||||
"NVARCHAR requires a length on dialect %s" %
|
||||
"NVARCHAR requires a length on dialect %s" %
|
||||
self.dialect.name)
|
||||
|
||||
def visit_NCHAR(self, type_):
|
||||
@@ -1783,8 +1794,8 @@ class MySQLIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
quote = '"'
|
||||
|
||||
super(MySQLIdentifierPreparer, self).__init__(
|
||||
dialect,
|
||||
initial_quote=quote,
|
||||
dialect,
|
||||
initial_quote=quote,
|
||||
escape_quote=quote)
|
||||
|
||||
def _quote_free_identifiers(self, *ids):
|
||||
@@ -1817,7 +1828,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
preparer = MySQLIdentifierPreparer
|
||||
|
||||
# default SQL compilation settings -
|
||||
# these are modified upon initialize(),
|
||||
# these are modified upon initialize(),
|
||||
# i.e. first connect
|
||||
_backslash_escapes = True
|
||||
_server_ansiquotes = False
|
||||
@@ -1834,7 +1845,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
else:
|
||||
return None
|
||||
|
||||
_isolation_lookup = set(['SERIALIZABLE',
|
||||
_isolation_lookup = set(['SERIALIZABLE',
|
||||
'READ UNCOMMITTED', 'READ COMMITTED', 'REPEATABLE READ'])
|
||||
|
||||
def set_isolation_level(self, connection, level):
|
||||
@@ -1842,7 +1853,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
if level not in self._isolation_lookup:
|
||||
raise exc.ArgumentError(
|
||||
"Invalid value '%s' for isolation_level. "
|
||||
"Valid isolation levels for %s are %s" %
|
||||
"Valid isolation levels for %s are %s" %
|
||||
(level, self.name, ", ".join(self._isolation_lookup))
|
||||
)
|
||||
cursor = connection.cursor()
|
||||
@@ -1914,7 +1925,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
return self._extract_error_code(e) in \
|
||||
(2006, 2013, 2014, 2045, 2055)
|
||||
elif isinstance(e, self.dbapi.InterfaceError):
|
||||
# if underlying connection is closed,
|
||||
# if underlying connection is closed,
|
||||
# this is the error you get
|
||||
return "(0, '')" in str(e)
|
||||
else:
|
||||
@@ -2017,7 +2028,6 @@ class MySQLDialect(default.DefaultDialect):
|
||||
|
||||
@reflection.cache
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
charset = self._connection_charset
|
||||
if self.server_version_info < (5, 0, 2):
|
||||
raise NotImplementedError
|
||||
if schema is None:
|
||||
@@ -2028,7 +2038,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
rp = connection.execute("SHOW FULL TABLES FROM %s" %
|
||||
self.identifier_preparer.quote_identifier(schema))
|
||||
return [row[0] for row in self._compat_fetchall(rp, charset=charset)\
|
||||
if row[1] == 'VIEW']
|
||||
if row[1] in ('VIEW', 'SYSTEM VIEW')]
|
||||
|
||||
@reflection.cache
|
||||
def get_table_options(self, connection, table_name, schema=None, **kw):
|
||||
@@ -2129,9 +2139,9 @@ class MySQLDialect(default.DefaultDialect):
|
||||
|
||||
def _parsed_state_or_create(self, connection, table_name, schema=None, **kw):
|
||||
return self._setup_parser(
|
||||
connection,
|
||||
table_name,
|
||||
schema,
|
||||
connection,
|
||||
table_name,
|
||||
schema,
|
||||
info_cache=kw.get('info_cache', None)
|
||||
)
|
||||
|
||||
@@ -2139,7 +2149,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
def _tabledef_parser(self):
|
||||
"""return the MySQLTableDefinitionParser, generate if needed.
|
||||
|
||||
The deferred creation ensures that the dialect has
|
||||
The deferred creation ensures that the dialect has
|
||||
retrieved server version information first.
|
||||
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# mysql/gaerdbms.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
"""Support for Google Cloud SQL on Google App Engine.
|
||||
|
||||
This dialect is based primarily on the :mod:`.mysql.mysqldb` dialect with minimal
|
||||
changes.
|
||||
|
||||
.. versionadded:: 0.7.8
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
Connect string format::
|
||||
|
||||
mysql+gaerdbms:///<dbname>
|
||||
|
||||
E.g.::
|
||||
|
||||
create_engine('mysql+gaerdbms:///mydb',
|
||||
connect_args={"instance":"instancename"})
|
||||
|
||||
Pooling
|
||||
-------
|
||||
|
||||
Google App Engine connections appear to be randomly recycled,
|
||||
so the dialect does not pool connections. The :class:`.NullPool`
|
||||
implementation is installed within the :class:`.Engine` by
|
||||
default.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.mysql.mysqldb import MySQLDialect_mysqldb
|
||||
from sqlalchemy.pool import NullPool
|
||||
import re
|
||||
|
||||
|
||||
class MySQLDialect_gaerdbms(MySQLDialect_mysqldb):
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
# from django:
|
||||
# http://code.google.com/p/googleappengine/source/
|
||||
# browse/trunk/python/google/storage/speckle/
|
||||
# python/django/backend/base.py#118
|
||||
# see also [ticket:2649]
|
||||
# see also http://stackoverflow.com/q/14224679/34549
|
||||
from google.appengine.api import apiproxy_stub_map
|
||||
|
||||
if apiproxy_stub_map.apiproxy.GetStub('rdbms'):
|
||||
from google.storage.speckle.python.api import rdbms_apiproxy
|
||||
return rdbms_apiproxy
|
||||
else:
|
||||
from google.storage.speckle.python.api import rdbms_googleapi
|
||||
return rdbms_googleapi
|
||||
|
||||
@classmethod
|
||||
def get_pool_class(cls, url):
|
||||
# Cloud SQL connections die at any moment
|
||||
return NullPool
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args()
|
||||
# 'dsn' and 'instance' are because we are skipping
|
||||
# the traditional google.api.rdbms wrapper
|
||||
|
||||
opts['dsn'] = ''
|
||||
opts['instance'] = url.query['instance']
|
||||
return [], opts
|
||||
|
||||
def _extract_error_code(self, exception):
|
||||
match = re.compile(r"^(\d+):").match(str(exception))
|
||||
# The rdbms api will wrap then re-raise some types of errors
|
||||
# making this regex return no matches.
|
||||
if match:
|
||||
code = match.group(1)
|
||||
else:
|
||||
code = None
|
||||
if code:
|
||||
return int(code)
|
||||
|
||||
dialect = MySQLDialect_gaerdbms
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/mysqlconnector.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -105,15 +105,11 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
dbapi_con = connection.connection
|
||||
|
||||
from mysql.connector.constants import ClientFlag
|
||||
dbapi_con.set_client_flag(ClientFlag.FOUND_ROWS)
|
||||
|
||||
version = dbapi_con.get_server_version()
|
||||
return tuple(version)
|
||||
|
||||
def _detect_charset(self, connection):
|
||||
return connection.connection.get_characterset_info()
|
||||
return connection.connection.charset
|
||||
|
||||
def _extract_error_code(self, exception):
|
||||
return exception.errno
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/mysqldb.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -26,20 +26,20 @@ MySQLdb will accommodate Python ``unicode`` objects if the
|
||||
``use_unicode=1`` parameter, or the ``charset`` parameter,
|
||||
is passed as a connection argument.
|
||||
|
||||
Without this setting, many MySQL server installations default to
|
||||
Without this setting, many MySQL server installations default to
|
||||
a ``latin1`` encoding for client connections, which has the effect
|
||||
of all data being converted into ``latin1``, even if you have ``utf8``
|
||||
of all data being converted into ``latin1``, even if you have ``utf8``
|
||||
or another character set configured on your tables
|
||||
and columns. With versions 4.1 and higher, you can change the connection
|
||||
character set either through server configuration or by including the
|
||||
``charset`` parameter. The ``charset``
|
||||
parameter as received by MySQL-Python also has the side-effect of
|
||||
parameter as received by MySQL-Python also has the side-effect of
|
||||
enabling ``use_unicode=1``::
|
||||
|
||||
# set client encoding to utf8; all strings come back as unicode
|
||||
create_engine('mysql+mysqldb:///mydb?charset=utf8')
|
||||
|
||||
Manually configuring ``use_unicode=0`` will cause MySQL-python to
|
||||
Manually configuring ``use_unicode=0`` will cause MySQL-python to
|
||||
return encoded strings::
|
||||
|
||||
# set client encoding to utf8; all strings come back as utf8 str
|
||||
@@ -57,9 +57,9 @@ It is strongly advised to use the latest version of MySQL-Python.
|
||||
from sqlalchemy.dialects.mysql.base import (MySQLDialect, MySQLExecutionContext,
|
||||
MySQLCompiler, MySQLIdentifierPreparer)
|
||||
from sqlalchemy.connectors.mysqldb import (
|
||||
MySQLDBExecutionContext,
|
||||
MySQLDBCompiler,
|
||||
MySQLDBIdentifierPreparer,
|
||||
MySQLDBExecutionContext,
|
||||
MySQLDBCompiler,
|
||||
MySQLDBIdentifierPreparer,
|
||||
MySQLDBConnector
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/oursql.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -108,9 +108,9 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
arg = "'%s'" % arg
|
||||
connection.execution_options(_oursql_plain_query=True).execute(query % arg)
|
||||
|
||||
# Because mysql is bad, these methods have to be
|
||||
# Because mysql is bad, these methods have to be
|
||||
# reimplemented to use _PlainQuery. Basically, some queries
|
||||
# refuse to return any data if they're run through
|
||||
# refuse to return any data if they're run through
|
||||
# the parameterized query API, or refuse to be parameterized
|
||||
# in the first place.
|
||||
def do_begin_twophase(self, connection, xid):
|
||||
@@ -135,7 +135,7 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
# Q: why didn't we need all these "plain_query" overrides earlier ?
|
||||
# am i on a newer/older version of OurSQL ?
|
||||
def has_table(self, connection, table_name, schema=None):
|
||||
return MySQLDialect.has_table(self,
|
||||
return MySQLDialect.has_table(self,
|
||||
connection.connect().\
|
||||
execution_options(_oursql_plain_query=True),
|
||||
table_name, schema)
|
||||
@@ -183,7 +183,7 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
|
||||
def initialize(self, connection):
|
||||
return MySQLDialect.initialize(
|
||||
self,
|
||||
self,
|
||||
connection.execution_options(_oursql_plain_query=True)
|
||||
)
|
||||
|
||||
@@ -208,6 +208,7 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
util.coerce_kw_type(opts, 'port', int)
|
||||
util.coerce_kw_type(opts, 'compress', bool)
|
||||
util.coerce_kw_type(opts, 'autoping', bool)
|
||||
util.coerce_kw_type(opts, 'raise_on_warnings', bool)
|
||||
|
||||
util.coerce_kw_type(opts, 'default_charset', bool)
|
||||
if opts.pop('default_charset', False):
|
||||
@@ -222,7 +223,7 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
opts.setdefault('found_rows', True)
|
||||
|
||||
ssl = {}
|
||||
for key in ['ssl_ca', 'ssl_key', 'ssl_cert',
|
||||
for key in ['ssl_ca', 'ssl_key', 'ssl_cert',
|
||||
'ssl_capath', 'ssl_cipher']:
|
||||
if key in opts:
|
||||
ssl[key[4:]] = opts[key]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/pymysql.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -20,20 +20,20 @@ Connect string::
|
||||
MySQL-Python Compatibility
|
||||
--------------------------
|
||||
|
||||
The pymysql DBAPI is a pure Python port of the MySQL-python (MySQLdb) driver,
|
||||
and targets 100% compatibility. Most behavioral notes for MySQL-python apply to
|
||||
The pymysql DBAPI is a pure Python port of the MySQL-python (MySQLdb) driver,
|
||||
and targets 100% compatibility. Most behavioral notes for MySQL-python apply to
|
||||
the pymysql driver as well.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.mysql.mysqldb import MySQLDialect_mysqldb
|
||||
from sqlalchemy.dialects.mysql.mysqldb import MySQLDialect_mysqldb
|
||||
|
||||
class MySQLDialect_pymysql(MySQLDialect_mysqldb):
|
||||
class MySQLDialect_pymysql(MySQLDialect_mysqldb):
|
||||
driver = 'pymysql'
|
||||
|
||||
description_encoding = None
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('pymysql')
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('pymysql')
|
||||
|
||||
dialect = MySQLDialect_pymysql
|
||||
dialect = MySQLDialect_pymysql
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/pyodbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -20,7 +20,7 @@ Connect string::
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
The mysql-pyodbc dialect is subject to unresolved character encoding issues
|
||||
The mysql-pyodbc dialect is subject to unresolved character encoding issues
|
||||
which exist within the current ODBC drivers available.
|
||||
(see http://code.google.com/p/pyodbc/issues/detail?id=25). Consider usage
|
||||
of OurSQL, MySQLdb, or MySQL-connector/Python.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/zxjdbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# oracle/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -12,7 +12,7 @@ from sqlalchemy.dialects.oracle.base import \
|
||||
VARCHAR, NVARCHAR, CHAR, DATE, DATETIME, NUMBER,\
|
||||
BLOB, BFILE, CLOB, NCLOB, TIMESTAMP, RAW,\
|
||||
FLOAT, DOUBLE_PRECISION, LONG, dialect, INTERVAL,\
|
||||
VARCHAR2, NVARCHAR2
|
||||
VARCHAR2, NVARCHAR2, ROWID
|
||||
|
||||
|
||||
__all__ = (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# oracle/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -14,7 +14,7 @@ for that driver.
|
||||
Connect Arguments
|
||||
-----------------
|
||||
|
||||
The dialect supports several :func:`~sqlalchemy.create_engine()` arguments which
|
||||
The dialect supports several :func:`~sqlalchemy.create_engine()` arguments which
|
||||
affect the behavior of the dialect regardless of driver in use.
|
||||
|
||||
* *use_ansi* - Use ANSI JOIN constructs (see the section on Oracle 8). Defaults
|
||||
@@ -29,32 +29,32 @@ Auto Increment Behavior
|
||||
|
||||
SQLAlchemy Table objects which include integer primary keys are usually assumed to have
|
||||
"autoincrementing" behavior, meaning they can generate their own primary key values upon
|
||||
INSERT. Since Oracle has no "autoincrement" feature, SQLAlchemy relies upon sequences
|
||||
INSERT. Since Oracle has no "autoincrement" feature, SQLAlchemy relies upon sequences
|
||||
to produce these values. With the Oracle dialect, *a sequence must always be explicitly
|
||||
specified to enable autoincrement*. This is divergent with the majority of documentation
|
||||
specified to enable autoincrement*. This is divergent with the majority of documentation
|
||||
examples which assume the usage of an autoincrement-capable database. To specify sequences,
|
||||
use the sqlalchemy.schema.Sequence object which is passed to a Column construct::
|
||||
|
||||
t = Table('mytable', metadata,
|
||||
t = Table('mytable', metadata,
|
||||
Column('id', Integer, Sequence('id_seq'), primary_key=True),
|
||||
Column(...), ...
|
||||
)
|
||||
|
||||
This step is also required when using table reflection, i.e. autoload=True::
|
||||
|
||||
t = Table('mytable', metadata,
|
||||
t = Table('mytable', metadata,
|
||||
Column('id', Integer, Sequence('id_seq'), primary_key=True),
|
||||
autoload=True
|
||||
)
|
||||
)
|
||||
|
||||
Identifier Casing
|
||||
-----------------
|
||||
|
||||
In Oracle, the data dictionary represents all case insensitive identifier names
|
||||
In Oracle, the data dictionary represents all case insensitive identifier names
|
||||
using UPPERCASE text. SQLAlchemy on the other hand considers an all-lower case identifier
|
||||
name to be case insensitive. The Oracle dialect converts all case insensitive identifiers
|
||||
to and from those two formats during schema level communication, such as reflection of
|
||||
tables and indexes. Using an UPPERCASE name on the SQLAlchemy side indicates a
|
||||
tables and indexes. Using an UPPERCASE name on the SQLAlchemy side indicates a
|
||||
case sensitive identifier, and SQLAlchemy will quote the name - this will cause mismatches
|
||||
against data dictionary data received from Oracle, so unless identifier names have been
|
||||
truly created as case sensitive (i.e. using quoted names), all lowercase names should be
|
||||
@@ -63,23 +63,25 @@ used on the SQLAlchemy side.
|
||||
Unicode
|
||||
-------
|
||||
|
||||
SQLAlchemy 0.6 uses the "native unicode" mode provided as of cx_oracle 5. cx_oracle 5.0.2
|
||||
or greater is recommended for support of NCLOB. If not using cx_oracle 5, the NLS_LANG
|
||||
environment variable needs to be set in order for the oracle client library to use
|
||||
proper encoding, such as "AMERICAN_AMERICA.UTF8".
|
||||
.. versionchanged:: 0.6
|
||||
SQLAlchemy uses the "native unicode" mode provided as of cx_oracle 5.
|
||||
cx_oracle 5.0.2 or greater is recommended for support of NCLOB.
|
||||
If not using cx_oracle 5, the NLS_LANG environment variable needs
|
||||
to be set in order for the oracle client library to use proper encoding,
|
||||
such as "AMERICAN_AMERICA.UTF8".
|
||||
|
||||
Also note that Oracle supports unicode data through the NVARCHAR and NCLOB data types.
|
||||
When using the SQLAlchemy Unicode and UnicodeText types, these DDL types will be used
|
||||
within CREATE TABLE statements. Usage of VARCHAR2 and CLOB with unicode text still
|
||||
within CREATE TABLE statements. Usage of VARCHAR2 and CLOB with unicode text still
|
||||
requires NLS_LANG to be set.
|
||||
|
||||
LIMIT/OFFSET Support
|
||||
--------------------
|
||||
|
||||
Oracle has no support for the LIMIT or OFFSET keywords. SQLAlchemy uses
|
||||
a wrapped subquery approach in conjunction with ROWNUM. The exact methodology
|
||||
Oracle has no support for the LIMIT or OFFSET keywords. SQLAlchemy uses
|
||||
a wrapped subquery approach in conjunction with ROWNUM. The exact methodology
|
||||
is taken from
|
||||
http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html .
|
||||
http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html .
|
||||
|
||||
There are two options which affect its behavior:
|
||||
|
||||
@@ -87,13 +89,13 @@ There are two options which affect its behavior:
|
||||
optimization directive, specify ``optimize_limits=True`` to :func:`.create_engine`.
|
||||
* the values passed for the limit/offset are sent as bound parameters. Some users have observed
|
||||
that Oracle produces a poor query plan when the values are sent as binds and not
|
||||
rendered literally. To render the limit/offset values literally within the SQL
|
||||
rendered literally. To render the limit/offset values literally within the SQL
|
||||
statement, specify ``use_binds_for_limits=False`` to :func:`.create_engine`.
|
||||
|
||||
Some users have reported better performance when the entirely different approach of a
|
||||
window query is used, i.e. ROW_NUMBER() OVER (ORDER BY), to provide LIMIT/OFFSET (note
|
||||
that the majority of users don't observe this). To suit this case the
|
||||
method used for LIMIT/OFFSET can be replaced entirely. See the recipe at
|
||||
Some users have reported better performance when the entirely different approach of a
|
||||
window query is used, i.e. ROW_NUMBER() OVER (ORDER BY), to provide LIMIT/OFFSET (note
|
||||
that the majority of users don't observe this). To suit this case the
|
||||
method used for LIMIT/OFFSET can be replaced entirely. See the recipe at
|
||||
http://www.sqlalchemy.org/trac/wiki/UsageRecipes/WindowFunctionsByDefault
|
||||
which installs a select compiler that overrides the generation of limit/offset with
|
||||
a window function.
|
||||
@@ -101,11 +103,11 @@ a window function.
|
||||
ON UPDATE CASCADE
|
||||
-----------------
|
||||
|
||||
Oracle doesn't have native ON UPDATE CASCADE functionality. A trigger based solution
|
||||
Oracle doesn't have native ON UPDATE CASCADE functionality. A trigger based solution
|
||||
is available at http://asktom.oracle.com/tkyte/update_cascade/index.html .
|
||||
|
||||
When using the SQLAlchemy ORM, the ORM has limited ability to manually issue
|
||||
cascading updates - specify ForeignKey objects using the
|
||||
cascading updates - specify ForeignKey objects using the
|
||||
"deferrable=True, initially='deferred'" keyword arguments,
|
||||
and specify "passive_updates=False" on each relationship().
|
||||
|
||||
@@ -119,21 +121,21 @@ behaviors:
|
||||
JOIN phrases into the WHERE clause, and in the case of LEFT OUTER JOIN
|
||||
makes use of Oracle's (+) operator.
|
||||
|
||||
* the NVARCHAR2 and NCLOB datatypes are no longer generated as DDL when
|
||||
the :class:`~sqlalchemy.types.Unicode` is used - VARCHAR2 and CLOB are issued
|
||||
* the NVARCHAR2 and NCLOB datatypes are no longer generated as DDL when
|
||||
the :class:`~sqlalchemy.types.Unicode` is used - VARCHAR2 and CLOB are issued
|
||||
instead. This because these types don't seem to work correctly on Oracle 8
|
||||
even though they are available. The :class:`~sqlalchemy.types.NVARCHAR`
|
||||
even though they are available. The :class:`~sqlalchemy.types.NVARCHAR`
|
||||
and :class:`~sqlalchemy.dialects.oracle.NCLOB` types will always generate NVARCHAR2 and NCLOB.
|
||||
|
||||
* the "native unicode" mode is disabled when using cx_oracle, i.e. SQLAlchemy
|
||||
* the "native unicode" mode is disabled when using cx_oracle, i.e. SQLAlchemy
|
||||
encodes all Python unicode objects to "string" before passing in as bind parameters.
|
||||
|
||||
Synonym/DBLINK Reflection
|
||||
-------------------------
|
||||
|
||||
When using reflection with Table objects, the dialect can optionally search for tables
|
||||
indicated by synonyms that reference DBLINK-ed tables by passing the flag
|
||||
oracle_resolve_synonyms=True as a keyword argument to the Table construct. If DBLINK
|
||||
indicated by synonyms that reference DBLINK-ed tables by passing the flag
|
||||
oracle_resolve_synonyms=True as a keyword argument to the Table construct. If DBLINK
|
||||
is not in use this flag should be left off.
|
||||
|
||||
"""
|
||||
@@ -215,8 +217,8 @@ class LONG(sqltypes.Text):
|
||||
class INTERVAL(sqltypes.TypeEngine):
|
||||
__visit_name__ = 'INTERVAL'
|
||||
|
||||
def __init__(self,
|
||||
day_precision=None,
|
||||
def __init__(self,
|
||||
day_precision=None,
|
||||
second_precision=None):
|
||||
"""Construct an INTERVAL.
|
||||
|
||||
@@ -301,10 +303,10 @@ class OracleTypeCompiler(compiler.GenericTypeCompiler):
|
||||
|
||||
def visit_INTERVAL(self, type_):
|
||||
return "INTERVAL DAY%s TO SECOND%s" % (
|
||||
type_.day_precision is not None and
|
||||
type_.day_precision is not None and
|
||||
"(%d)" % type_.day_precision or
|
||||
"",
|
||||
type_.second_precision is not None and
|
||||
type_.second_precision is not None and
|
||||
"(%d)" % type_.second_precision or
|
||||
"",
|
||||
)
|
||||
@@ -338,7 +340,7 @@ class OracleTypeCompiler(compiler.GenericTypeCompiler):
|
||||
else:
|
||||
return "%(name)s(%(precision)s, %(scale)s)" % {'name':name,'precision': precision, 'scale' : scale}
|
||||
|
||||
def visit_string(self, type_):
|
||||
def visit_string(self, type_):
|
||||
return self.visit_VARCHAR2(type_)
|
||||
|
||||
def visit_VARCHAR2(self, type_):
|
||||
@@ -354,10 +356,10 @@ class OracleTypeCompiler(compiler.GenericTypeCompiler):
|
||||
def _visit_varchar(self, type_, n, num):
|
||||
if not n and self.dialect._supports_char_length:
|
||||
return "VARCHAR%(two)s(%(length)s CHAR)" % {
|
||||
'length' : type_.length,
|
||||
'length' : type_.length,
|
||||
'two':num}
|
||||
else:
|
||||
return "%(n)sVARCHAR%(two)s(%(length)s)" % {'length' : type_.length,
|
||||
return "%(n)sVARCHAR%(two)s(%(length)s)" % {'length' : type_.length,
|
||||
'two':num, 'n':n}
|
||||
|
||||
def visit_text(self, type_):
|
||||
@@ -429,7 +431,7 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
return ""
|
||||
|
||||
def default_from(self):
|
||||
"""Called when a ``SELECT`` statement has no froms,
|
||||
"""Called when a ``SELECT`` statement has no froms,
|
||||
and no ``FROM`` clause is to be appended.
|
||||
|
||||
The Oracle compiler tacks a "FROM DUAL" to the statement.
|
||||
@@ -564,7 +566,7 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
if not self.dialect.use_binds_for_limits:
|
||||
max_row = sql.literal_column("%d" % max_row)
|
||||
limitselect.append_whereclause(
|
||||
sql.literal_column("ROWNUM")<=max_row)
|
||||
sql.literal_column("ROWNUM") <= max_row)
|
||||
|
||||
# If needed, add the ora_rn, and wrap again with offset.
|
||||
if select._offset is None:
|
||||
@@ -611,7 +613,7 @@ class OracleDDLCompiler(compiler.DDLCompiler):
|
||||
if constraint.ondelete is not None:
|
||||
text += " ON DELETE %s" % constraint.ondelete
|
||||
|
||||
# oracle has no ON UPDATE CASCADE -
|
||||
# oracle has no ON UPDATE CASCADE -
|
||||
# its only available via triggers http://asktom.oracle.com/tkyte/update_cascade/index.html
|
||||
if constraint.onupdate is not None:
|
||||
util.warn(
|
||||
@@ -641,8 +643,8 @@ class OracleIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
|
||||
class OracleExecutionContext(default.DefaultExecutionContext):
|
||||
def fire_sequence(self, seq, type_):
|
||||
return self._execute_scalar("SELECT " +
|
||||
self.dialect.identifier_preparer.format_sequence(seq) +
|
||||
return self._execute_scalar("SELECT " +
|
||||
self.dialect.identifier_preparer.format_sequence(seq) +
|
||||
".nextval FROM DUAL", type_)
|
||||
|
||||
class OracleDialect(default.DefaultDialect):
|
||||
@@ -674,9 +676,9 @@ class OracleDialect(default.DefaultDialect):
|
||||
|
||||
reflection_options = ('oracle_resolve_synonyms', )
|
||||
|
||||
def __init__(self,
|
||||
use_ansi=True,
|
||||
optimize_limits=False,
|
||||
def __init__(self,
|
||||
use_ansi=True,
|
||||
optimize_limits=False,
|
||||
use_binds_for_limits=True,
|
||||
**kwargs):
|
||||
default.DefaultDialect.__init__(self, **kwargs)
|
||||
@@ -806,8 +808,8 @@ class OracleDialect(default.DefaultDialect):
|
||||
|
||||
if resolve_synonyms:
|
||||
actual_name, owner, dblink, synonym = self._resolve_synonym(
|
||||
connection,
|
||||
desired_owner=self.denormalize_name(schema),
|
||||
connection,
|
||||
desired_owner=self.denormalize_name(schema),
|
||||
desired_synonym=self.denormalize_name(table_name)
|
||||
)
|
||||
else:
|
||||
@@ -874,11 +876,11 @@ class OracleDialect(default.DefaultDialect):
|
||||
char_length_col = 'char_length'
|
||||
else:
|
||||
char_length_col = 'data_length'
|
||||
|
||||
|
||||
c = connection.execute(sql.text(
|
||||
"SELECT column_name, data_type, %(char_length_col)s, data_precision, data_scale, "
|
||||
"nullable, data_default FROM ALL_TAB_COLUMNS%(dblink)s "
|
||||
"WHERE table_name = :table_name AND owner = :owner "
|
||||
"WHERE table_name = :table_name AND owner = :owner "
|
||||
"ORDER BY column_id" % {'dblink': dblink, 'char_length_col':char_length_col}),
|
||||
table_name=table_name, owner=schema)
|
||||
|
||||
@@ -890,7 +892,7 @@ class OracleDialect(default.DefaultDialect):
|
||||
coltype = NUMBER(precision, scale)
|
||||
elif coltype in ('VARCHAR2', 'NVARCHAR2', 'CHAR'):
|
||||
coltype = self.ischema_names.get(coltype)(length)
|
||||
elif 'WITH TIME ZONE' in coltype:
|
||||
elif 'WITH TIME ZONE' in coltype:
|
||||
coltype = TIMESTAMP(timezone=True)
|
||||
else:
|
||||
coltype = re.sub(r'\(\d+\)', '', coltype)
|
||||
@@ -927,8 +929,8 @@ class OracleDialect(default.DefaultDialect):
|
||||
indexes = []
|
||||
q = sql.text("""
|
||||
SELECT a.index_name, a.column_name, b.uniqueness
|
||||
FROM ALL_IND_COLUMNS%(dblink)s a,
|
||||
ALL_INDEXES%(dblink)s b
|
||||
FROM ALL_IND_COLUMNS%(dblink)s a,
|
||||
ALL_INDEXES%(dblink)s b
|
||||
WHERE
|
||||
a.index_name = b.index_name
|
||||
AND a.table_owner = b.table_owner
|
||||
@@ -1110,8 +1112,8 @@ class OracleDialect(default.DefaultDialect):
|
||||
if resolve_synonyms:
|
||||
ref_remote_name, ref_remote_owner, ref_dblink, ref_synonym = \
|
||||
self._resolve_synonym(
|
||||
connection,
|
||||
desired_owner=self.denormalize_name(remote_owner),
|
||||
connection,
|
||||
desired_owner=self.denormalize_name(remote_owner),
|
||||
desired_table=self.denormalize_name(remote_table)
|
||||
)
|
||||
if ref_synonym:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# oracle/cx_oracle.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -9,19 +9,19 @@
|
||||
Driver
|
||||
------
|
||||
|
||||
The Oracle dialect uses the cx_oracle driver, available at
|
||||
http://cx-oracle.sourceforge.net/ . The dialect has several behaviors
|
||||
The Oracle dialect uses the cx_oracle driver, available at
|
||||
http://cx-oracle.sourceforge.net/ . The dialect has several behaviors
|
||||
which are specifically tailored towards compatibility with this module.
|
||||
Version 5.0 or greater is **strongly** recommended, as SQLAlchemy makes
|
||||
extensive use of the cx_oracle output converters for numeric and
|
||||
extensive use of the cx_oracle output converters for numeric and
|
||||
string conversions.
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
Connecting with create_engine() uses the standard URL approach of
|
||||
``oracle://user:pass@host:port/dbname[?key=value&key=value...]``. If dbname is present, the
|
||||
host, port, and dbname tokens are converted to a TNS name using the cx_oracle
|
||||
Connecting with create_engine() uses the standard URL approach of
|
||||
``oracle://user:pass@host:port/dbname[?key=value&key=value...]``. If dbname is present, the
|
||||
host, port, and dbname tokens are converted to a TNS name using the cx_oracle
|
||||
:func:`makedsn()` function. Otherwise, the host token is taken directly as a TNS name.
|
||||
|
||||
Additional arguments which may be specified either as query string arguments on the
|
||||
@@ -53,7 +53,7 @@ handler so that all string based result values are returned as unicode as well.
|
||||
Generally, the ``NLS_LANG`` environment variable determines the nature
|
||||
of the encoding to be used.
|
||||
|
||||
Note that this behavior is disabled when Oracle 8 is detected, as it has been
|
||||
Note that this behavior is disabled when Oracle 8 is detected, as it has been
|
||||
observed that issues remain when passing Python unicodes to cx_oracle with Oracle 8.
|
||||
|
||||
LOB Objects
|
||||
@@ -71,8 +71,40 @@ To disable this processing, pass ``auto_convert_lobs=False`` to :func:`create_en
|
||||
Two Phase Transaction Support
|
||||
-----------------------------
|
||||
|
||||
Two Phase transactions are implemented using XA transactions. Success has been reported
|
||||
with this feature but it should be regarded as experimental.
|
||||
Two Phase transactions are implemented using XA transactions, and are known
|
||||
to work in a rudimental fashion with recent versions of cx_Oracle
|
||||
as of SQLAlchemy 0.8.0b2, 0.7.10. However, the mechanism is not yet
|
||||
considered to be robust and should still be regarded as experimental.
|
||||
|
||||
In particular, the cx_Oracle DBAPI as recently as 5.1.2 has a bug regarding
|
||||
two phase which prevents
|
||||
a particular DBAPI connection from being consistently usable in both
|
||||
prepared transactions as well as traditional DBAPI usage patterns; therefore
|
||||
once a particular connection is used via :meth:`.Connection.begin_prepared`,
|
||||
all subsequent usages of the underlying DBAPI connection must be within
|
||||
the context of prepared transactions.
|
||||
|
||||
The default behavior of :class:`.Engine` is to maintain a pool of DBAPI
|
||||
connections. Therefore, due to the above glitch, a DBAPI connection that has
|
||||
been used in a two-phase operation, and is then returned to the pool, will
|
||||
not be usable in a non-two-phase context. To avoid this situation,
|
||||
the application can make one of several choices:
|
||||
|
||||
* Disable connection pooling using :class:`.NullPool`
|
||||
|
||||
* Ensure that the particular :class:`.Engine` in use is only used
|
||||
for two-phase operations. A :class:`.Engine` bound to an ORM
|
||||
:class:`.Session` which includes ``twophase=True`` will consistently
|
||||
use the two-phase transaction style.
|
||||
|
||||
* For ad-hoc two-phase operations without disabling pooling, the DBAPI
|
||||
connection in use can be evicted from the connection pool using the
|
||||
:class:`.Connection.detach` method.
|
||||
|
||||
.. versionchanged:: 0.8.0b2,0.7.10
|
||||
Support for cx_oracle prepared transactions has been implemented
|
||||
and tested.
|
||||
|
||||
|
||||
Precision Numerics
|
||||
------------------
|
||||
@@ -95,13 +127,14 @@ 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",
|
||||
engine = create_engine("oracle+cx_oracle://dsn",
|
||||
coerce_to_decimal=False)
|
||||
|
||||
The ``coerce_to_decimal`` flag is new in 0.7.6.
|
||||
.. versionadded:: 0.7.6
|
||||
Add the ``coerce_to_decimal`` flag.
|
||||
|
||||
Another alternative to performance is to use the
|
||||
`cdecimal <http://pypi.python.org/pypi/cdecimal/>`_ library;
|
||||
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"
|
||||
@@ -128,21 +161,23 @@ environment variable. Upon first connection, the dialect runs a
|
||||
test to determine the current "decimal" character, which can be
|
||||
a comma "," for european locales. From that point forward the
|
||||
outputtypehandler uses that character to represent a decimal
|
||||
point (this behavior is new in version 0.6.6). Note that
|
||||
cx_oracle 5.0.3 or greater is required when dealing with
|
||||
numerics with locale settings that don't use a period "." as the
|
||||
decimal character.
|
||||
point. Note that cx_oracle 5.0.3 or greater is required
|
||||
when dealing with numerics with locale settings that don't use
|
||||
a period "." as the decimal character.
|
||||
|
||||
.. versionchanged:: 0.6.6
|
||||
The outputtypehandler uses a comma "," character to represent
|
||||
a decimal point.
|
||||
|
||||
.. _OCI: http://www.oracle.com/technetwork/database/features/oci/index.html
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.oracle.base import OracleCompiler, OracleDialect, \
|
||||
RESERVED_WORDS, OracleExecutionContext
|
||||
OracleExecutionContext
|
||||
from sqlalchemy.dialects.oracle import base as oracle
|
||||
from sqlalchemy.engine import base
|
||||
from sqlalchemy import types as sqltypes, util, exc, processors
|
||||
from datetime import datetime
|
||||
import random
|
||||
import collections
|
||||
from sqlalchemy.util.compat import decimal
|
||||
@@ -156,7 +191,7 @@ class _OracleNumeric(sqltypes.Numeric):
|
||||
def result_processor(self, dialect, coltype):
|
||||
# we apply a cx_oracle type handler to all connections
|
||||
# that converts floating point strings to Decimal().
|
||||
# However, in some subquery situations, Oracle doesn't
|
||||
# However, in some subquery situations, Oracle doesn't
|
||||
# give us enough information to determine int or Decimal.
|
||||
# It could even be int/Decimal differently on each row,
|
||||
# regardless of the scale given for the originating type.
|
||||
@@ -186,7 +221,7 @@ class _OracleNumeric(sqltypes.Numeric):
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
# cx_oracle 4 behavior, will assume
|
||||
# cx_oracle 4 behavior, will assume
|
||||
# floats
|
||||
return super(_OracleNumeric, self).\
|
||||
result_processor(dialect, coltype)
|
||||
@@ -233,7 +268,7 @@ class _NativeUnicodeMixin(object):
|
||||
# end Py2K
|
||||
|
||||
# we apply a connection output handler that returns
|
||||
# unicode in all cases, so the "native_unicode" flag
|
||||
# unicode in all cases, so the "native_unicode" flag
|
||||
# will be set for the default String.result_processor.
|
||||
|
||||
class _OracleChar(_NativeUnicodeMixin, sqltypes.CHAR):
|
||||
@@ -248,6 +283,13 @@ class _OracleText(_LOBMixin, sqltypes.Text):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.CLOB
|
||||
|
||||
class _OracleLong(oracle.LONG):
|
||||
# a raw LONG is a text type, but does *not*
|
||||
# get the LobMixin with cx_oracle.
|
||||
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.LONG_STRING
|
||||
|
||||
class _OracleString(_NativeUnicodeMixin, sqltypes.String):
|
||||
pass
|
||||
|
||||
@@ -296,13 +338,13 @@ class _OracleRowid(oracle.ROWID):
|
||||
return dbapi.ROWID
|
||||
|
||||
class OracleCompiler_cx_oracle(OracleCompiler):
|
||||
def bindparam_string(self, name):
|
||||
def bindparam_string(self, name, **kw):
|
||||
if self.preparer._bindparam_requires_quotes(name):
|
||||
quoted_name = '"%s"' % name
|
||||
self._quoted_bind_names[name] = quoted_name
|
||||
return OracleCompiler.bindparam_string(self, quoted_name)
|
||||
return OracleCompiler.bindparam_string(self, quoted_name, **kw)
|
||||
else:
|
||||
return OracleCompiler.bindparam_string(self, name)
|
||||
return OracleCompiler.bindparam_string(self, name, **kw)
|
||||
|
||||
|
||||
class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
@@ -312,15 +354,15 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
getattr(self.compiled, '_quoted_bind_names', None)
|
||||
if quoted_bind_names:
|
||||
if not self.dialect.supports_unicode_statements:
|
||||
# if DBAPI doesn't accept unicode statements,
|
||||
# if DBAPI doesn't accept unicode statements,
|
||||
# keys in self.parameters would have been encoded
|
||||
# here. so convert names in quoted_bind_names
|
||||
# to encoded as well.
|
||||
quoted_bind_names = \
|
||||
dict(
|
||||
(fromname.encode(self.dialect.encoding),
|
||||
toname.encode(self.dialect.encoding))
|
||||
for fromname, toname in
|
||||
(fromname.encode(self.dialect.encoding),
|
||||
toname.encode(self.dialect.encoding))
|
||||
for fromname, toname in
|
||||
quoted_bind_names.items()
|
||||
)
|
||||
for param in self.parameters:
|
||||
@@ -329,11 +371,11 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
del param[fromname]
|
||||
|
||||
if self.dialect.auto_setinputsizes:
|
||||
# cx_oracle really has issues when you setinputsizes
|
||||
# cx_oracle really has issues when you setinputsizes
|
||||
# on String, including that outparams/RETURNING
|
||||
# breaks for varchars
|
||||
self.set_input_sizes(quoted_bind_names,
|
||||
exclude_types=self.dialect._cx_oracle_string_types
|
||||
self.set_input_sizes(quoted_bind_names,
|
||||
exclude_types=self.dialect._cx_oracle_exclude_setinputsizes
|
||||
)
|
||||
|
||||
# if a single execute, check for outparams
|
||||
@@ -365,7 +407,7 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
def get_result_proxy(self):
|
||||
if hasattr(self, 'out_parameters') and self.compiled.returning:
|
||||
returning_params = dict(
|
||||
(k, v.getvalue())
|
||||
(k, v.getvalue())
|
||||
for k, v in self.out_parameters.items()
|
||||
)
|
||||
return ReturningResultProxy(self, returning_params)
|
||||
@@ -391,7 +433,7 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
impl_type = type.dialect_impl(self.dialect)
|
||||
dbapi_type = impl_type.get_dbapi_type(self.dialect.dbapi)
|
||||
result_processor = impl_type.\
|
||||
result_processor(self.dialect,
|
||||
result_processor(self.dialect,
|
||||
dbapi_type)
|
||||
if result_processor is not None:
|
||||
out_parameters[name] = \
|
||||
@@ -400,7 +442,7 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
out_parameters[name] = self.out_parameters[name].getvalue()
|
||||
else:
|
||||
result.out_parameters = dict(
|
||||
(k, v.getvalue())
|
||||
(k, v.getvalue())
|
||||
for k, v in self.out_parameters.items()
|
||||
)
|
||||
|
||||
@@ -409,13 +451,13 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
class OracleExecutionContext_cx_oracle_with_unicode(OracleExecutionContext_cx_oracle):
|
||||
"""Support WITH_UNICODE in Python 2.xx.
|
||||
|
||||
WITH_UNICODE allows cx_Oracle's Python 3 unicode handling
|
||||
behavior under Python 2.x. This mode in some cases disallows
|
||||
and in other cases silently passes corrupted data when
|
||||
non-Python-unicode strings (a.k.a. plain old Python strings)
|
||||
are passed as arguments to connect(), the statement sent to execute(),
|
||||
WITH_UNICODE allows cx_Oracle's Python 3 unicode handling
|
||||
behavior under Python 2.x. This mode in some cases disallows
|
||||
and in other cases silently passes corrupted data when
|
||||
non-Python-unicode strings (a.k.a. plain old Python strings)
|
||||
are passed as arguments to connect(), the statement sent to execute(),
|
||||
or any of the bind parameter keys or values sent to execute().
|
||||
This optional context therefore ensures that all statements are
|
||||
This optional context therefore ensures that all statements are
|
||||
passed as Python unicode objects.
|
||||
|
||||
"""
|
||||
@@ -446,7 +488,7 @@ class ReturningResultProxy(base.FullyBufferedResultProxy):
|
||||
return ret
|
||||
|
||||
def _buffer_rows(self):
|
||||
return collections.deque([tuple(self._returning_params["ret_%d" % i]
|
||||
return collections.deque([tuple(self._returning_params["ret_%d" % i]
|
||||
for i, c in enumerate(self._returning_params))])
|
||||
|
||||
class OracleDialect_cx_oracle(OracleDialect):
|
||||
@@ -467,6 +509,11 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
sqltypes.String : _OracleString,
|
||||
sqltypes.UnicodeText : _OracleUnicodeText,
|
||||
sqltypes.CHAR : _OracleChar,
|
||||
|
||||
# a raw LONG is a text type, but does *not*
|
||||
# get the LobMixin with cx_oracle.
|
||||
oracle.LONG: _OracleLong,
|
||||
|
||||
sqltypes.Integer : _OracleInteger, # this is only needed for OUT parameters.
|
||||
# it would be nice if we could not use it otherwise.
|
||||
oracle.RAW: _OracleRaw,
|
||||
@@ -478,11 +525,11 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
|
||||
execute_sequence_format = list
|
||||
|
||||
def __init__(self,
|
||||
auto_setinputsizes=True,
|
||||
auto_convert_lobs=True,
|
||||
threaded=True,
|
||||
allow_twophase=True,
|
||||
def __init__(self,
|
||||
auto_setinputsizes=True,
|
||||
auto_convert_lobs=True,
|
||||
threaded=True,
|
||||
allow_twophase=True,
|
||||
coerce_to_decimal=True,
|
||||
arraysize=50, **kwargs):
|
||||
OracleDialect.__init__(self, **kwargs)
|
||||
@@ -503,13 +550,14 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
getattr(self.dbapi, name, None) for name in names
|
||||
]).difference([None])
|
||||
|
||||
self._cx_oracle_exclude_setinputsizes = types("STRING", "UNICODE")
|
||||
self._cx_oracle_string_types = types("STRING", "UNICODE", "NCLOB", "CLOB")
|
||||
self._cx_oracle_unicode_types = types("UNICODE", "NCLOB")
|
||||
self._cx_oracle_binary_types = types("BFILE", "CLOB", "NCLOB", "BLOB")
|
||||
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) and
|
||||
self.cx_oracle_ver >= (5, 0) and
|
||||
coerce_to_decimal
|
||||
)
|
||||
|
||||
@@ -551,13 +599,13 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
# expect encoded strings or unicodes, etc.
|
||||
self.dbapi_type_map = {
|
||||
self.dbapi.CLOB: oracle.CLOB(),
|
||||
self.dbapi.NCLOB:oracle.NCLOB(),
|
||||
self.dbapi.NCLOB: oracle.NCLOB(),
|
||||
self.dbapi.BLOB: oracle.BLOB(),
|
||||
self.dbapi.BINARY: oracle.RAW(),
|
||||
}
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
import cx_Oracle
|
||||
cx_Oracle = __import__('cx_Oracle')
|
||||
return cx_Oracle
|
||||
|
||||
def initialize(self, connection):
|
||||
@@ -567,12 +615,12 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
self._detect_decimal_char(connection)
|
||||
|
||||
def _detect_decimal_char(self, connection):
|
||||
"""detect if the decimal separator character is not '.', as
|
||||
"""detect if the decimal separator character is not '.', as
|
||||
is the case with european locale settings for NLS_LANG.
|
||||
|
||||
cx_oracle itself uses similar logic when it formats Python
|
||||
Decimal objects to strings on the bind side (as of 5.0.3),
|
||||
as Oracle sends/receives string numerics only in the
|
||||
Decimal objects to strings on the bind side (as of 5.0.3),
|
||||
as Oracle sends/receives string numerics only in the
|
||||
current locale.
|
||||
|
||||
"""
|
||||
@@ -583,14 +631,14 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
cx_Oracle = self.dbapi
|
||||
conn = connection.connection
|
||||
|
||||
# override the output_type_handler that's
|
||||
# on the cx_oracle connection with a plain
|
||||
# override the output_type_handler that's
|
||||
# on the cx_oracle connection with a plain
|
||||
# one on the cursor
|
||||
|
||||
def output_type_handler(cursor, name, defaultType,
|
||||
def output_type_handler(cursor, name, defaultType,
|
||||
size, precision, scale):
|
||||
return cursor.var(
|
||||
cx_Oracle.STRING,
|
||||
cx_Oracle.STRING,
|
||||
255, arraysize=cursor.arraysize)
|
||||
|
||||
cursor = conn.cursor()
|
||||
@@ -620,7 +668,7 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
return
|
||||
|
||||
cx_Oracle = self.dbapi
|
||||
def output_type_handler(cursor, name, defaultType,
|
||||
def output_type_handler(cursor, name, defaultType,
|
||||
size, precision, scale):
|
||||
# convert all NUMBER with precision + positive scale to Decimal
|
||||
# this almost allows "native decimal" mode.
|
||||
@@ -628,22 +676,22 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
defaultType == cx_Oracle.NUMBER and \
|
||||
precision and scale > 0:
|
||||
return cursor.var(
|
||||
cx_Oracle.STRING,
|
||||
255,
|
||||
outconverter=self._to_decimal,
|
||||
cx_Oracle.STRING,
|
||||
255,
|
||||
outconverter=self._to_decimal,
|
||||
arraysize=cursor.arraysize)
|
||||
# if NUMBER with zero precision and 0 or neg scale, this appears
|
||||
# to indicate "ambiguous". Use a slower converter that will
|
||||
# make a decision based on each value received - the type
|
||||
# to indicate "ambiguous". Use a slower converter that will
|
||||
# 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 self.supports_native_decimal and \
|
||||
defaultType == cx_Oracle.NUMBER \
|
||||
and not precision and scale <= 0:
|
||||
return cursor.var(
|
||||
cx_Oracle.STRING,
|
||||
255,
|
||||
outconverter=self._detect_decimal,
|
||||
cx_Oracle.STRING,
|
||||
255,
|
||||
outconverter=self._detect_decimal,
|
||||
arraysize=cursor.arraysize)
|
||||
# allow all strings to come back natively as Unicode
|
||||
elif defaultType in (cx_Oracle.STRING, cx_Oracle.FIXED_CHAR):
|
||||
@@ -707,7 +755,7 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
return tuple(
|
||||
int(x)
|
||||
int(x)
|
||||
for x in connection.connection.version.split('.')
|
||||
)
|
||||
|
||||
@@ -739,15 +787,23 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
connection.connection.begin(*xid)
|
||||
|
||||
def do_prepare_twophase(self, connection, xid):
|
||||
connection.connection.prepare()
|
||||
result = connection.connection.prepare()
|
||||
connection.info['cx_oracle_prepared'] = result
|
||||
|
||||
def do_rollback_twophase(self, connection, xid, is_prepared=True, recover=False):
|
||||
def do_rollback_twophase(self, connection, xid, is_prepared=True,
|
||||
recover=False):
|
||||
self.do_rollback(connection.connection)
|
||||
|
||||
def do_commit_twophase(self, connection, xid, is_prepared=True, recover=False):
|
||||
self.do_commit(connection.connection)
|
||||
def do_commit_twophase(self, connection, xid, is_prepared=True,
|
||||
recover=False):
|
||||
if not is_prepared:
|
||||
self.do_commit(connection.connection)
|
||||
else:
|
||||
oci_prepared = connection.info['cx_oracle_prepared']
|
||||
if oci_prepared:
|
||||
self.do_commit(connection.connection)
|
||||
|
||||
def do_recover_twophase(self, connection):
|
||||
pass
|
||||
connection.info.pop('cx_oracle_prepared', None)
|
||||
|
||||
dialect = OracleDialect_cx_oracle
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# oracle/zxjdbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# dialects/postgres.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -14,7 +14,7 @@ from sqlalchemy.dialects.postgresql.base import \
|
||||
DATE, BYTEA, BOOLEAN, INTERVAL, ARRAY, ENUM, dialect
|
||||
|
||||
__all__ = (
|
||||
'INTEGER', 'BIGINT', 'SMALLINT', 'VARCHAR', 'CHAR', 'TEXT', 'NUMERIC', 'FLOAT', 'REAL', 'INET',
|
||||
'INTEGER', 'BIGINT', 'SMALLINT', 'VARCHAR', 'CHAR', 'TEXT', 'NUMERIC', 'FLOAT', 'REAL', 'INET',
|
||||
'CIDR', 'UUID', 'BIT', 'MACADDR', 'DOUBLE_PRECISION', 'TIMESTAMP', 'TIME',
|
||||
'DATE', 'BYTEA', 'BOOLEAN', 'INTERVAL', 'ARRAY', 'ENUM', 'dialect'
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -21,7 +21,7 @@ default corresponding to the column.
|
||||
To specify a specific named sequence to be used for primary key generation,
|
||||
use the :func:`~sqlalchemy.schema.Sequence` construct::
|
||||
|
||||
Table('sometable', metadata,
|
||||
Table('sometable', metadata,
|
||||
Column('id', Integer, Sequence('some_id_seq'), primary_key=True)
|
||||
)
|
||||
|
||||
@@ -51,7 +51,7 @@ parameter are ``READ COMMITTED``, ``READ UNCOMMITTED``, ``REPEATABLE READ``,
|
||||
and ``SERIALIZABLE``::
|
||||
|
||||
engine = create_engine(
|
||||
"postgresql+pg8000://scott:tiger@localhost/test",
|
||||
"postgresql+pg8000://scott:tiger@localhost/test",
|
||||
isolation_level="READ UNCOMMITTED"
|
||||
)
|
||||
|
||||
@@ -75,18 +75,19 @@ the current ``search_path``, the "schema" attribute of the resulting
|
||||
remote table matches that of the referencing table, and the "schema" argument
|
||||
was explicitly stated on the referencing table.
|
||||
|
||||
The best practice here is to not use the ``schema`` argument
|
||||
The best practice here is to not use the ``schema`` argument
|
||||
on :class:`.Table` for any schemas that are present in ``search_path``.
|
||||
``search_path`` defaults to "public", but care should be taken
|
||||
to inspect the actual value using::
|
||||
|
||||
SHOW search_path;
|
||||
|
||||
Prior to version 0.7.3, cross-schema foreign keys when the schemas
|
||||
were also in the ``search_path`` could make an incorrect assumption
|
||||
if the schemas were explicitly stated on each :class:`.Table`.
|
||||
.. versionchanged:: 0.7.3
|
||||
Prior to this version, cross-schema foreign keys when the schemas
|
||||
were also in the ``search_path`` could make an incorrect assumption
|
||||
if the schemas were explicitly stated on each :class:`.Table`.
|
||||
|
||||
Background on PG's ``search_path`` is at:
|
||||
Background on PG's ``search_path`` is at:
|
||||
http://www.postgresql.org/docs/9.0/static/ddl-schemas.html#DDL-SCHEMAS-PATH
|
||||
|
||||
INSERT/UPDATE...RETURNING
|
||||
@@ -125,7 +126,7 @@ to the PostgreSQL dialect.
|
||||
Partial Indexes
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
Partial indexes add criterion to the index definition so that the index is
|
||||
Partial indexes add criterion to the index definition so that the index is
|
||||
applied to a subset of rows. These can be specified on :class:`.Index`
|
||||
using the ``postgresql_where`` keyword argument::
|
||||
|
||||
@@ -137,13 +138,16 @@ Operator Classes
|
||||
PostgreSQL allows the specification of an *operator class* for each column of
|
||||
an index (see http://www.postgresql.org/docs/8.3/interactive/indexes-opclass.html).
|
||||
The :class:`.Index` construct allows these to be specified via the ``postgresql_ops``
|
||||
keyword argument (new as of SQLAlchemy 0.7.2)::
|
||||
keyword argument::
|
||||
|
||||
Index('my_index', my_table.c.id, my_table.c.data,
|
||||
Index('my_index', my_table.c.id, my_table.c.data,
|
||||
postgresql_ops={
|
||||
'data': 'text_pattern_ops',
|
||||
'data': 'text_pattern_ops',
|
||||
'id': 'int4_ops'
|
||||
})
|
||||
})
|
||||
|
||||
.. versionadded:: 0.7.2
|
||||
``postgresql_ops`` keyword argument to :class:`.Index` construct.
|
||||
|
||||
Note that the keys in the ``postgresql_ops`` dictionary are the "key" name of
|
||||
the :class:`.Column`, i.e. the name used to access it from the ``.c`` collection
|
||||
@@ -345,24 +349,27 @@ class ARRAY(sqltypes.MutableType, sqltypes.Concatenable, sqltypes.TypeEngine):
|
||||
the fly
|
||||
|
||||
:param mutable=False: Specify whether lists passed to this
|
||||
class should be considered mutable - this enables
|
||||
"mutable types" mode in the ORM. Be sure to read the
|
||||
notes for :class:`.MutableType` regarding ORM
|
||||
performance implications (default changed from ``True`` in
|
||||
0.7.0).
|
||||
class should be considered mutable - this enables
|
||||
"mutable types" mode in the ORM. Be sure to read the
|
||||
notes for :class:`.MutableType` regarding ORM
|
||||
performance implications.
|
||||
|
||||
.. note::
|
||||
|
||||
This functionality is now superseded by the
|
||||
``sqlalchemy.ext.mutable`` extension described in
|
||||
:ref:`mutable_toplevel`.
|
||||
.. versionchanged:: 0.7.0
|
||||
Default changed from ``True``\ .
|
||||
|
||||
.. versionchanged:: 0.7
|
||||
This functionality is now superseded by the
|
||||
``sqlalchemy.ext.mutable`` extension described in
|
||||
:ref:`mutable_toplevel`.
|
||||
|
||||
:param as_tuple=False: Specify whether return results
|
||||
should be converted to tuples from lists. DBAPIs such
|
||||
as psycopg2 return lists by default. When tuples are
|
||||
returned, the results are hashable. This flag can only
|
||||
be set to ``True`` when ``mutable`` is set to
|
||||
``False``. (new in 0.6.5)
|
||||
``False``.
|
||||
|
||||
.. versionadded:: 0.6.5
|
||||
|
||||
"""
|
||||
if isinstance(item_type, ARRAY):
|
||||
@@ -444,37 +451,37 @@ PGArray = ARRAY
|
||||
|
||||
class ENUM(sqltypes.Enum):
|
||||
"""Postgresql ENUM type.
|
||||
|
||||
|
||||
This is a subclass of :class:`.types.Enum` which includes
|
||||
support for PG's ``CREATE TYPE``.
|
||||
|
||||
:class:`~.postgresql.ENUM` is used automatically when
|
||||
|
||||
:class:`~.postgresql.ENUM` is used automatically when
|
||||
using the :class:`.types.Enum` type on PG assuming
|
||||
the ``native_enum`` is left as ``True``. However, the
|
||||
the ``native_enum`` is left as ``True``. However, the
|
||||
:class:`~.postgresql.ENUM` class can also be instantiated
|
||||
directly in order to access some additional Postgresql-specific
|
||||
options, namely finer control over whether or not
|
||||
options, namely finer control over whether or not
|
||||
``CREATE TYPE`` should be emitted.
|
||||
|
||||
Note that both :class:`.types.Enum` as well as
|
||||
|
||||
Note that both :class:`.types.Enum` as well as
|
||||
:class:`~.postgresql.ENUM` feature create/drop
|
||||
methods; the base :class:`.types.Enum` type ultimately
|
||||
delegates to the :meth:`~.postgresql.ENUM.create` and
|
||||
:meth:`~.postgresql.ENUM.drop` methods present here.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *enums, **kw):
|
||||
"""Construct an :class:`~.postgresql.ENUM`.
|
||||
|
||||
|
||||
Arguments are the same as that of
|
||||
:class:`.types.Enum`, but also including
|
||||
the following parameters.
|
||||
|
||||
:param create_type: Defaults to True.
|
||||
Indicates that ``CREATE TYPE`` should be
|
||||
emitted, after optionally checking for the
|
||||
presence of the type, when the parent
|
||||
|
||||
:param create_type: Defaults to True.
|
||||
Indicates that ``CREATE TYPE`` should be
|
||||
emitted, after optionally checking for the
|
||||
presence of the type, when the parent
|
||||
table is being created; and additionally
|
||||
that ``DROP TYPE`` is called when the table
|
||||
is dropped. When ``False``, no check
|
||||
@@ -485,31 +492,32 @@ class ENUM(sqltypes.Enum):
|
||||
are called directly.
|
||||
Setting to ``False`` is helpful
|
||||
when invoking a creation scheme to a SQL file
|
||||
without access to the actual database -
|
||||
without access to the actual database -
|
||||
the :meth:`~.postgresql.ENUM.create` and
|
||||
:meth:`~.postgresql.ENUM.drop` methods can
|
||||
be used to emit SQL to a target bind.
|
||||
(new in 0.7.4)
|
||||
|
||||
|
||||
.. versionadded:: 0.7.4
|
||||
|
||||
"""
|
||||
self.create_type = kw.pop("create_type", True)
|
||||
super(ENUM, self).__init__(*enums, **kw)
|
||||
|
||||
def create(self, bind=None, checkfirst=True):
|
||||
"""Emit ``CREATE TYPE`` for this
|
||||
"""Emit ``CREATE TYPE`` for this
|
||||
:class:`~.postgresql.ENUM`.
|
||||
|
||||
|
||||
If the underlying dialect does not support
|
||||
Postgresql CREATE TYPE, no action is taken.
|
||||
|
||||
|
||||
:param bind: a connectable :class:`.Engine`,
|
||||
:class:`.Connection`, or similar object to emit
|
||||
SQL.
|
||||
:param checkfirst: if ``True``, a query against
|
||||
:param checkfirst: if ``True``, a query against
|
||||
the PG catalog will be first performed to see
|
||||
if the type does not exist already before
|
||||
creating.
|
||||
|
||||
|
||||
"""
|
||||
if not bind.dialect.supports_native_enum:
|
||||
return
|
||||
@@ -519,19 +527,19 @@ class ENUM(sqltypes.Enum):
|
||||
bind.execute(CreateEnumType(self))
|
||||
|
||||
def drop(self, bind=None, checkfirst=True):
|
||||
"""Emit ``DROP TYPE`` for this
|
||||
"""Emit ``DROP TYPE`` for this
|
||||
:class:`~.postgresql.ENUM`.
|
||||
|
||||
|
||||
If the underlying dialect does not support
|
||||
Postgresql DROP TYPE, no action is taken.
|
||||
|
||||
|
||||
:param bind: a connectable :class:`.Engine`,
|
||||
:class:`.Connection`, or similar object to emit
|
||||
SQL.
|
||||
:param checkfirst: if ``True``, a query against
|
||||
:param checkfirst: if ``True``, a query against
|
||||
the PG catalog will be first performed to see
|
||||
if the type actually exists before dropping.
|
||||
|
||||
|
||||
"""
|
||||
if not bind.dialect.supports_native_enum:
|
||||
return
|
||||
@@ -543,7 +551,7 @@ class ENUM(sqltypes.Enum):
|
||||
def _check_for_name_in_memos(self, checkfirst, kw):
|
||||
"""Look in the 'ddl runner' for 'memos', then
|
||||
note our name in that collection.
|
||||
|
||||
|
||||
This to ensure a particular named enum is operated
|
||||
upon only once within any kind of create/drop
|
||||
sequence without relying upon "checkfirst".
|
||||
@@ -620,14 +628,14 @@ class PGCompiler(compiler.SQLCompiler):
|
||||
|
||||
def visit_match_op(self, binary, **kw):
|
||||
return "%s @@ to_tsquery(%s)" % (
|
||||
self.process(binary.left),
|
||||
self.process(binary.left),
|
||||
self.process(binary.right))
|
||||
|
||||
def visit_ilike_op(self, binary, **kw):
|
||||
escape = binary.modifiers.get("escape", None)
|
||||
return '%s ILIKE %s' % \
|
||||
(self.process(binary.left), self.process(binary.right)) \
|
||||
+ (escape and
|
||||
+ (escape and
|
||||
(' ESCAPE ' + self.render_literal_value(escape, None))
|
||||
or '')
|
||||
|
||||
@@ -635,7 +643,7 @@ class PGCompiler(compiler.SQLCompiler):
|
||||
escape = binary.modifiers.get("escape", None)
|
||||
return '%s NOT ILIKE %s' % \
|
||||
(self.process(binary.left), self.process(binary.right)) \
|
||||
+ (escape and
|
||||
+ (escape and
|
||||
(' ESCAPE ' + self.render_literal_value(escape, None))
|
||||
or '')
|
||||
|
||||
@@ -675,6 +683,10 @@ class PGCompiler(compiler.SQLCompiler):
|
||||
def for_update_clause(self, select):
|
||||
if select.for_update == 'nowait':
|
||||
return " FOR UPDATE NOWAIT"
|
||||
elif select.for_update == 'read':
|
||||
return " FOR SHARE"
|
||||
elif select.for_update == 'read_nowait':
|
||||
return " FOR SHARE NOWAIT"
|
||||
else:
|
||||
return super(PGCompiler, self).for_update_clause(select)
|
||||
|
||||
@@ -682,9 +694,9 @@ class PGCompiler(compiler.SQLCompiler):
|
||||
|
||||
columns = [
|
||||
self.process(
|
||||
self.label_select_column(None, c, asfrom=False),
|
||||
within_columns_clause=True,
|
||||
result_map=self.result_map)
|
||||
self.label_select_column(None, c, asfrom=False),
|
||||
within_columns_clause=True,
|
||||
result_map=self.result_map)
|
||||
for c in expression._select_iterables(returning_cols)
|
||||
]
|
||||
|
||||
@@ -698,8 +710,8 @@ class PGCompiler(compiler.SQLCompiler):
|
||||
affinity = None
|
||||
|
||||
casts = {
|
||||
sqltypes.Date:'date',
|
||||
sqltypes.DateTime:'timestamp',
|
||||
sqltypes.Date:'date',
|
||||
sqltypes.DateTime:'timestamp',
|
||||
sqltypes.Interval:'interval', sqltypes.Time:'time'
|
||||
}
|
||||
cast = casts.get(affinity, None)
|
||||
@@ -718,7 +730,7 @@ class PGDDLCompiler(compiler.DDLCompiler):
|
||||
column is column.table._autoincrement_column and \
|
||||
not isinstance(impl_type, sqltypes.SmallInteger) and \
|
||||
(
|
||||
column.default is None or
|
||||
column.default is None or
|
||||
(
|
||||
isinstance(column.default, schema.Sequence) and
|
||||
column.default.optional
|
||||
@@ -773,7 +785,7 @@ class PGDDLCompiler(compiler.DDLCompiler):
|
||||
text += "(%s)" \
|
||||
% (
|
||||
', '.join([
|
||||
preparer.format_column(c) +
|
||||
preparer.format_column(c) +
|
||||
(c.key in ops and (' ' + ops[c.key]) or '')
|
||||
for c in index.columns])
|
||||
)
|
||||
@@ -831,14 +843,14 @@ class PGTypeCompiler(compiler.GenericTypeCompiler):
|
||||
|
||||
def visit_TIMESTAMP(self, type_):
|
||||
return "TIMESTAMP%s %s" % (
|
||||
getattr(type_, 'precision', None) and "(%d)" %
|
||||
getattr(type_, 'precision', None) and "(%d)" %
|
||||
type_.precision or "",
|
||||
(type_.timezone and "WITH" or "WITHOUT") + " TIME ZONE"
|
||||
)
|
||||
|
||||
def visit_TIME(self, type_):
|
||||
return "TIME%s %s" % (
|
||||
getattr(type_, 'precision', None) and "(%d)" %
|
||||
getattr(type_, 'precision', None) and "(%d)" %
|
||||
type_.precision or "",
|
||||
(type_.timezone and "WITH" or "WITHOUT") + " TIME ZONE"
|
||||
)
|
||||
@@ -920,21 +932,21 @@ class PGExecutionContext(default.DefaultExecutionContext):
|
||||
return self._execute_scalar("select %s" %
|
||||
column.server_default.arg, column.type)
|
||||
|
||||
elif (column.default is None or
|
||||
elif (column.default is None or
|
||||
(column.default.is_sequence and
|
||||
column.default.optional)):
|
||||
|
||||
# execute the sequence associated with a SERIAL primary
|
||||
# execute the sequence associated with a SERIAL primary
|
||||
# key column. for non-primary-key SERIAL, the ID just
|
||||
# generates server side.
|
||||
|
||||
try:
|
||||
seq_name = column._postgresql_seq_name
|
||||
except AttributeError:
|
||||
tab = column.table.name
|
||||
col = column.name
|
||||
tab = tab[0:29 + max(0, (29 - len(col)))]
|
||||
col = col[0:29 + max(0, (29 - len(tab)))]
|
||||
tab = column.table.name
|
||||
col = column.name
|
||||
tab = tab[0:29 + max(0, (29 - len(col)))]
|
||||
col = col[0:29 + max(0, (29 - len(tab)))]
|
||||
column._postgresql_seq_name = seq_name = "%s_%s_seq" % (tab, col)
|
||||
|
||||
sch = column.table.schema
|
||||
@@ -1004,7 +1016,7 @@ class PGDialect(default.DefaultDialect):
|
||||
else:
|
||||
return None
|
||||
|
||||
_isolation_lookup = set(['SERIALIZABLE',
|
||||
_isolation_lookup = set(['SERIALIZABLE',
|
||||
'READ UNCOMMITTED', 'READ COMMITTED', 'REPEATABLE READ'])
|
||||
|
||||
def set_isolation_level(self, connection, level):
|
||||
@@ -1012,9 +1024,9 @@ class PGDialect(default.DefaultDialect):
|
||||
if level not in self._isolation_lookup:
|
||||
raise exc.ArgumentError(
|
||||
"Invalid value '%s' for isolation_level. "
|
||||
"Valid isolation levels for %s are %s" %
|
||||
"Valid isolation levels for %s are %s" %
|
||||
(level, self.name, ", ".join(self._isolation_lookup))
|
||||
)
|
||||
)
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(
|
||||
"SET SESSION CHARACTERISTICS AS TRANSACTION "
|
||||
@@ -1035,13 +1047,13 @@ class PGDialect(default.DefaultDialect):
|
||||
def do_prepare_twophase(self, connection, xid):
|
||||
connection.execute("PREPARE TRANSACTION '%s'" % xid)
|
||||
|
||||
def do_rollback_twophase(self, connection, xid,
|
||||
def do_rollback_twophase(self, connection, xid,
|
||||
is_prepared=True, recover=False):
|
||||
if is_prepared:
|
||||
if recover:
|
||||
#FIXME: ugly hack to get out of transaction
|
||||
# context when commiting recoverable transactions
|
||||
# Must find out a way how to make the dbapi not
|
||||
#FIXME: ugly hack to get out of transaction
|
||||
# context when committing recoverable transactions
|
||||
# Must find out a way how to make the dbapi not
|
||||
# open a transaction.
|
||||
connection.execute("ROLLBACK")
|
||||
connection.execute("ROLLBACK PREPARED '%s'" % xid)
|
||||
@@ -1050,7 +1062,7 @@ class PGDialect(default.DefaultDialect):
|
||||
else:
|
||||
self.do_rollback(connection.connection)
|
||||
|
||||
def do_commit_twophase(self, connection, xid,
|
||||
def do_commit_twophase(self, connection, xid,
|
||||
is_prepared=True, recover=False):
|
||||
if is_prepared:
|
||||
if recover:
|
||||
@@ -1102,10 +1114,10 @@ class PGDialect(default.DefaultDialect):
|
||||
"n.oid=c.relnamespace where n.nspname=:schema and "
|
||||
"relname=:name",
|
||||
bindparams=[
|
||||
sql.bindparam('name',
|
||||
sql.bindparam('name',
|
||||
unicode(table_name), type_=sqltypes.Unicode),
|
||||
sql.bindparam('schema',
|
||||
unicode(schema), type_=sqltypes.Unicode)]
|
||||
sql.bindparam('schema',
|
||||
unicode(schema), type_=sqltypes.Unicode)]
|
||||
)
|
||||
)
|
||||
return bool(cursor.first())
|
||||
@@ -1121,7 +1133,7 @@ class PGDialect(default.DefaultDialect):
|
||||
bindparams=[
|
||||
sql.bindparam('name', unicode(sequence_name),
|
||||
type_=sqltypes.Unicode)
|
||||
]
|
||||
]
|
||||
)
|
||||
)
|
||||
else:
|
||||
@@ -1133,7 +1145,7 @@ class PGDialect(default.DefaultDialect):
|
||||
bindparams=[
|
||||
sql.bindparam('name', unicode(sequence_name),
|
||||
type_=sqltypes.Unicode),
|
||||
sql.bindparam('schema',
|
||||
sql.bindparam('schema',
|
||||
unicode(schema), type_=sqltypes.Unicode)
|
||||
]
|
||||
)
|
||||
@@ -1261,13 +1273,13 @@ class PGDialect(default.DefaultDialect):
|
||||
SELECT relname
|
||||
FROM pg_class c
|
||||
WHERE relkind = 'v'
|
||||
AND '%(schema)s' = (select nspname from pg_namespace n
|
||||
AND '%(schema)s' = (select nspname from pg_namespace n
|
||||
where n.oid = c.relnamespace)
|
||||
""" % dict(schema=current_schema)
|
||||
# Py3K
|
||||
#view_names = [row[0] for row in connection.execute(s)]
|
||||
# Py2K
|
||||
view_names = [row[0].decode(self.encoding)
|
||||
view_names = [row[0].decode(self.encoding)
|
||||
for row in connection.execute(s)]
|
||||
# end Py2K
|
||||
return view_names
|
||||
@@ -1301,10 +1313,10 @@ class PGDialect(default.DefaultDialect):
|
||||
SQL_COLS = """
|
||||
SELECT a.attname,
|
||||
pg_catalog.format_type(a.atttypid, a.atttypmod),
|
||||
(SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid)
|
||||
for 128)
|
||||
(SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid)
|
||||
for 128)
|
||||
FROM pg_catalog.pg_attrdef d
|
||||
WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum
|
||||
WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum
|
||||
AND a.atthasdef)
|
||||
AS DEFAULT,
|
||||
a.attnotnull, a.attnum, a.attrelid as table_oid
|
||||
@@ -1313,8 +1325,8 @@ class PGDialect(default.DefaultDialect):
|
||||
AND a.attnum > 0 AND NOT a.attisdropped
|
||||
ORDER BY a.attnum
|
||||
"""
|
||||
s = sql.text(SQL_COLS,
|
||||
bindparams=[sql.bindparam('table_oid', type_=sqltypes.Integer)],
|
||||
s = sql.text(SQL_COLS,
|
||||
bindparams=[sql.bindparam('table_oid', type_=sqltypes.Integer)],
|
||||
typemap={'attname':sqltypes.Unicode, 'default':sqltypes.Unicode}
|
||||
)
|
||||
c = connection.execute(s, table_oid=table_oid)
|
||||
@@ -1325,7 +1337,7 @@ class PGDialect(default.DefaultDialect):
|
||||
# format columns
|
||||
columns = []
|
||||
for name, format_type, default, notnull, attnum, table_oid in rows:
|
||||
## strip (5) from character varying(5), timestamp(5)
|
||||
## strip (5) from character varying(5), timestamp(5)
|
||||
# with time zone, etc
|
||||
attype = re.sub(r'\([\d,]+\)', '', format_type)
|
||||
|
||||
@@ -1350,13 +1362,13 @@ class PGDialect(default.DefaultDialect):
|
||||
args = (53, )
|
||||
elif attype == 'integer':
|
||||
args = ()
|
||||
elif attype in ('timestamp with time zone',
|
||||
elif attype in ('timestamp with time zone',
|
||||
'time with time zone'):
|
||||
kwargs['timezone'] = True
|
||||
if charlen:
|
||||
kwargs['precision'] = int(charlen)
|
||||
args = ()
|
||||
elif attype in ('timestamp without time zone',
|
||||
elif attype in ('timestamp without time zone',
|
||||
'time without time zone', 'time'):
|
||||
kwargs['timezone'] = False
|
||||
if charlen:
|
||||
@@ -1397,7 +1409,7 @@ class PGDialect(default.DefaultDialect):
|
||||
# A table can't override whether the domain is nullable.
|
||||
nullable = domain['nullable']
|
||||
if domain['default'] and not default:
|
||||
# It can, however, override the default
|
||||
# It can, however, override the default
|
||||
# value, but can't set it to null.
|
||||
default = domain['default']
|
||||
continue
|
||||
@@ -1423,7 +1435,7 @@ class PGDialect(default.DefaultDialect):
|
||||
sch = schema
|
||||
if '.' not in match.group(2) and sch is not None:
|
||||
# unconditionally quote the schema name. this could
|
||||
# later be enhanced to obey quoting rules /
|
||||
# later be enhanced to obey quoting rules /
|
||||
# "quote schema"
|
||||
default = match.group(1) + \
|
||||
('"%s"' % sch) + '.' + \
|
||||
@@ -1439,27 +1451,40 @@ class PGDialect(default.DefaultDialect):
|
||||
table_oid = self.get_table_oid(connection, table_name, schema,
|
||||
info_cache=kw.get('info_cache'))
|
||||
|
||||
PK_SQL = """
|
||||
SELECT a.attname
|
||||
FROM
|
||||
if self.server_version_info < (8, 4):
|
||||
# unnest() and generate_subscripts() both introduced in
|
||||
# version 8.4
|
||||
PK_SQL = """
|
||||
SELECT a.attname
|
||||
FROM
|
||||
pg_class t
|
||||
join pg_index ix on t.oid = ix.indrelid
|
||||
join pg_attribute a
|
||||
join pg_attribute a
|
||||
on t.oid=a.attrelid and a.attnum=ANY(ix.indkey)
|
||||
WHERE
|
||||
t.oid = :table_oid and
|
||||
ix.indisprimary = 't'
|
||||
ORDER BY
|
||||
a.attnum
|
||||
"""
|
||||
t = sql.text(PK_SQL, typemap={'attname':sqltypes.Unicode})
|
||||
WHERE
|
||||
t.oid = :table_oid and ix.indisprimary = 't'
|
||||
ORDER BY a.attnum
|
||||
"""
|
||||
else:
|
||||
PK_SQL = """
|
||||
SELECT a.attname
|
||||
FROM pg_attribute a JOIN (
|
||||
SELECT unnest(ix.indkey) attnum,
|
||||
generate_subscripts(ix.indkey, 1) ord
|
||||
FROM pg_index ix
|
||||
WHERE ix.indrelid = :table_oid AND ix.indisprimary
|
||||
) k ON a.attnum=k.attnum
|
||||
WHERE a.attrelid = :table_oid
|
||||
ORDER BY k.ord
|
||||
"""
|
||||
t = sql.text(PK_SQL, typemap={'attname': sqltypes.Unicode})
|
||||
c = connection.execute(t, table_oid=table_oid)
|
||||
primary_keys = [r[0] for r in c.fetchall()]
|
||||
return primary_keys
|
||||
|
||||
@reflection.cache
|
||||
def get_pk_constraint(self, connection, table_name, schema=None, **kw):
|
||||
cols = self.get_primary_keys(connection, table_name,
|
||||
cols = self.get_primary_keys(connection, table_name,
|
||||
schema=schema, **kw)
|
||||
|
||||
table_oid = self.get_table_oid(connection, table_name, schema,
|
||||
@@ -1486,14 +1511,14 @@ class PGDialect(default.DefaultDialect):
|
||||
info_cache=kw.get('info_cache'))
|
||||
|
||||
FK_SQL = """
|
||||
SELECT r.conname,
|
||||
SELECT r.conname,
|
||||
pg_catalog.pg_get_constraintdef(r.oid, true) as condef,
|
||||
n.nspname as conschema
|
||||
FROM pg_catalog.pg_constraint r,
|
||||
pg_namespace n,
|
||||
pg_class c
|
||||
|
||||
WHERE r.conrelid = :table AND
|
||||
WHERE r.conrelid = :table AND
|
||||
r.contype = 'f' AND
|
||||
c.oid = confrelid AND
|
||||
n.oid = c.relnamespace
|
||||
@@ -1510,7 +1535,7 @@ class PGDialect(default.DefaultDialect):
|
||||
'(?:(.*?)\.)?(.*?)\((.*?)\)', condef).groups()
|
||||
constrained_columns, referred_schema, \
|
||||
referred_table, referred_columns = m
|
||||
constrained_columns = [preparer._unquote_identifier(x)
|
||||
constrained_columns = [preparer._unquote_identifier(x)
|
||||
for x in re.split(r'\s*,\s*', constrained_columns)]
|
||||
|
||||
if referred_schema:
|
||||
@@ -1525,7 +1550,7 @@ class PGDialect(default.DefaultDialect):
|
||||
# and an explicit schema was given for the referencing table.
|
||||
referred_schema = schema
|
||||
referred_table = preparer._unquote_identifier(referred_table)
|
||||
referred_columns = [preparer._unquote_identifier(x)
|
||||
referred_columns = [preparer._unquote_identifier(x)
|
||||
for x in re.split(r'\s*,\s', referred_columns)]
|
||||
fkey_d = {
|
||||
'name' : conname,
|
||||
@@ -1548,11 +1573,11 @@ class PGDialect(default.DefaultDialect):
|
||||
ix.indisunique, ix.indexprs, ix.indpred,
|
||||
a.attname
|
||||
FROM
|
||||
pg_class t
|
||||
pg_class t
|
||||
join pg_index ix on t.oid = ix.indrelid
|
||||
join pg_class i on i.oid=ix.indexrelid
|
||||
left outer join
|
||||
pg_attribute a
|
||||
left outer join
|
||||
pg_attribute a
|
||||
on t.oid=a.attrelid and a.attnum=ANY(ix.indkey)
|
||||
WHERE
|
||||
t.relkind = 'r'
|
||||
@@ -1604,13 +1629,12 @@ class PGDialect(default.DefaultDialect):
|
||||
SQL_ENUMS = """
|
||||
SELECT t.typname as "name",
|
||||
-- no enum defaults in 8.4 at least
|
||||
-- t.typdefault as "default",
|
||||
-- t.typdefault as "default",
|
||||
pg_catalog.pg_type_is_visible(t.oid) as "visible",
|
||||
n.nspname as "schema",
|
||||
e.enumlabel as "label"
|
||||
FROM pg_catalog.pg_type t
|
||||
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
||||
LEFT JOIN pg_catalog.pg_constraint r ON t.oid = r.contypid
|
||||
LEFT JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid
|
||||
WHERE t.typtype = 'e'
|
||||
ORDER BY "name", e.oid -- e.oid gives us label order
|
||||
@@ -1625,8 +1649,8 @@ class PGDialect(default.DefaultDialect):
|
||||
for enum in c.fetchall():
|
||||
if enum['visible']:
|
||||
# 'visible' just means whether or not the enum is in a
|
||||
# schema that's on the search path -- or not overriden by
|
||||
# a schema with higher presedence. If it's not visible,
|
||||
# schema that's on the search path -- or not overridden by
|
||||
# a schema with higher precedence. If it's not visible,
|
||||
# it will be prefixed with the schema-name when it's used.
|
||||
name = enum['name']
|
||||
else:
|
||||
@@ -1652,7 +1676,6 @@ class PGDialect(default.DefaultDialect):
|
||||
n.nspname as "schema"
|
||||
FROM pg_catalog.pg_type t
|
||||
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
||||
LEFT JOIN pg_catalog.pg_constraint r ON t.oid = r.contypid
|
||||
WHERE t.typtype = 'd'
|
||||
"""
|
||||
|
||||
@@ -1665,16 +1688,16 @@ class PGDialect(default.DefaultDialect):
|
||||
attype = re.search('([^\(]+)', domain['attype']).group(1)
|
||||
if domain['visible']:
|
||||
# 'visible' just means whether or not the domain is in a
|
||||
# schema that's on the search path -- or not overriden by
|
||||
# a schema with higher presedence. If it's not visible,
|
||||
# schema that's on the search path -- or not overridden by
|
||||
# a schema with higher precedence. If it's not visible,
|
||||
# it will be prefixed with the schema-name when it's used.
|
||||
name = domain['name']
|
||||
else:
|
||||
name = "%s.%s" % (domain['schema'], domain['name'])
|
||||
|
||||
domains[name] = {
|
||||
'attype':attype,
|
||||
'nullable': domain['nullable'],
|
||||
'attype':attype,
|
||||
'nullable': domain['nullable'],
|
||||
'default': domain['default']
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/pg8000.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/psycopg2.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -10,7 +10,7 @@ Driver
|
||||
------
|
||||
|
||||
The psycopg2 driver is available at http://pypi.python.org/pypi/psycopg2/ .
|
||||
The dialect has several behaviors which are specifically tailored towards compatibility
|
||||
The dialect has several behaviors which are specifically tailored towards compatibility
|
||||
with this module.
|
||||
|
||||
Note that psycopg1 is **not** supported.
|
||||
@@ -48,7 +48,7 @@ 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
|
||||
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::
|
||||
|
||||
@@ -61,11 +61,11 @@ See also:
|
||||
Per-Statement/Connection Execution Options
|
||||
-------------------------------------------
|
||||
|
||||
The following DBAPI-specific options are respected when used with
|
||||
The following DBAPI-specific options are respected when used with
|
||||
:meth:`.Connection.execution_options`, :meth:`.Executable.execution_options`,
|
||||
:meth:`.Query.execution_options`, in addition to those not specific to DBAPIs:
|
||||
|
||||
* isolation_level - Set the transaction isolation level for the lifespan of a
|
||||
* isolation_level - Set the transaction isolation level for the lifespan of a
|
||||
:class:`.Connection` (can only be set on a connection, not a statement or query).
|
||||
This includes the options ``SERIALIZABLE``, ``READ COMMITTED``,
|
||||
``READ UNCOMMITTED`` and ``REPEATABLE READ``.
|
||||
@@ -79,8 +79,8 @@ By default, the psycopg2 driver uses the ``psycopg2.extensions.UNICODE``
|
||||
extension, such that the DBAPI receives and returns all strings as Python
|
||||
Unicode objects directly - SQLAlchemy passes these values through without
|
||||
change. Psycopg2 here will encode/decode string values based on the
|
||||
current "client encoding" setting; by default this is the value in
|
||||
the ``postgresql.conf`` file, which often defaults to ``SQL_ASCII``.
|
||||
current "client encoding" setting; by default this is the value in
|
||||
the ``postgresql.conf`` file, which often defaults to ``SQL_ASCII``.
|
||||
Typically, this can be changed to ``utf-8``, as a more useful default::
|
||||
|
||||
#client_encoding = sql_ascii # actually, defaults to database
|
||||
@@ -90,26 +90,27 @@ Typically, this can be changed to ``utf-8``, as a more useful default::
|
||||
A second way to affect the client encoding is to set it within Psycopg2
|
||||
locally. SQLAlchemy will call psycopg2's ``set_client_encoding()``
|
||||
method (see: http://initd.org/psycopg/docs/connection.html#connection.set_client_encoding)
|
||||
on all new connections based on the value passed to
|
||||
on all new connections based on the value passed to
|
||||
:func:`.create_engine` using the ``client_encoding`` parameter::
|
||||
|
||||
engine = create_engine("postgresql://user:pass@host/dbname", client_encoding='utf8')
|
||||
|
||||
This overrides the encoding specified in the Postgresql client configuration.
|
||||
The psycopg2-specific ``client_encoding`` parameter to :func:`.create_engine` is new as of
|
||||
SQLAlchemy 0.7.3.
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
The psycopg2-specific ``client_encoding`` parameter to :func:`.create_engine`.
|
||||
|
||||
SQLAlchemy can also be instructed to skip the usage of the psycopg2
|
||||
``UNICODE`` extension and to instead utilize it's own unicode encode/decode
|
||||
services, which are normally reserved only for those DBAPIs that don't
|
||||
fully support unicode directly. Passing ``use_native_unicode=False``
|
||||
services, which are normally reserved only for those DBAPIs that don't
|
||||
fully support unicode directly. Passing ``use_native_unicode=False``
|
||||
to :func:`.create_engine` will disable usage of ``psycopg2.extensions.UNICODE``.
|
||||
SQLAlchemy will instead encode data itself into Python bytestrings on the way
|
||||
SQLAlchemy will instead encode data itself into Python bytestrings on the way
|
||||
in and coerce from bytes on the way back,
|
||||
using the value of the :func:`.create_engine` ``encoding`` parameter, which
|
||||
using the value of the :func:`.create_engine` ``encoding`` parameter, which
|
||||
defaults to ``utf-8``.
|
||||
SQLAlchemy's own unicode encode/decode functionality is steadily becoming
|
||||
obsolete as more DBAPIs support unicode fully along with the approach of
|
||||
obsolete as more DBAPIs support unicode fully along with the approach of
|
||||
Python 3; in modern usage psycopg2 should be relied upon to handle unicode.
|
||||
|
||||
Transactions
|
||||
@@ -131,7 +132,7 @@ at the API level what level should be used.
|
||||
NOTICE logging
|
||||
---------------
|
||||
|
||||
The psycopg2 dialect will log Postgresql NOTICE messages via the
|
||||
The psycopg2 dialect will log Postgresql NOTICE messages via the
|
||||
``sqlalchemy.dialects.postgresql`` logger::
|
||||
|
||||
import logging
|
||||
@@ -219,8 +220,8 @@ class PGExecutionContext_psycopg2(PGExecutionContext):
|
||||
(self.compiled and isinstance(self.compiled.statement, expression.Selectable) \
|
||||
or \
|
||||
(
|
||||
(not self.compiled or
|
||||
isinstance(self.compiled.statement, expression._TextClause))
|
||||
(not self.compiled or
|
||||
isinstance(self.compiled.statement, expression._TextClause))
|
||||
and self.statement and SERVER_SIDE_CURSOR_RE.match(self.statement))
|
||||
)
|
||||
)
|
||||
@@ -248,7 +249,7 @@ class PGExecutionContext_psycopg2(PGExecutionContext):
|
||||
|
||||
def _log_notices(self, cursor):
|
||||
for notice in cursor.connection.notices:
|
||||
# NOTICE messages have a
|
||||
# NOTICE messages have a
|
||||
# newline character at the end
|
||||
logger.info(notice.rstrip())
|
||||
|
||||
@@ -290,7 +291,7 @@ class PGDialect_psycopg2(PGDialect):
|
||||
}
|
||||
)
|
||||
|
||||
def __init__(self, server_side_cursors=False, use_native_unicode=True,
|
||||
def __init__(self, server_side_cursors=False, use_native_unicode=True,
|
||||
client_encoding=None, **kwargs):
|
||||
PGDialect.__init__(self, **kwargs)
|
||||
self.server_side_cursors = server_side_cursors
|
||||
@@ -298,12 +299,12 @@ class PGDialect_psycopg2(PGDialect):
|
||||
self.supports_unicode_binds = use_native_unicode
|
||||
self.client_encoding = client_encoding
|
||||
if self.dbapi and hasattr(self.dbapi, '__version__'):
|
||||
m = re.match(r'(\d+)\.(\d+)(?:\.(\d+))?',
|
||||
m = re.match(r'(\d+)\.(\d+)(?:\.(\d+))?',
|
||||
self.dbapi.__version__)
|
||||
if m:
|
||||
self.psycopg2_version = tuple(
|
||||
int(x)
|
||||
for x in m.group(1, 2, 3)
|
||||
int(x)
|
||||
for x in m.group(1, 2, 3)
|
||||
if x is not None)
|
||||
|
||||
@classmethod
|
||||
@@ -315,8 +316,8 @@ class PGDialect_psycopg2(PGDialect):
|
||||
def _isolation_lookup(self):
|
||||
extensions = __import__('psycopg2.extensions').extensions
|
||||
return {
|
||||
'READ COMMITTED':extensions.ISOLATION_LEVEL_READ_COMMITTED,
|
||||
'READ UNCOMMITTED':extensions.ISOLATION_LEVEL_READ_UNCOMMITTED,
|
||||
'READ COMMITTED':extensions.ISOLATION_LEVEL_READ_COMMITTED,
|
||||
'READ UNCOMMITTED':extensions.ISOLATION_LEVEL_READ_UNCOMMITTED,
|
||||
'REPEATABLE READ':extensions.ISOLATION_LEVEL_REPEATABLE_READ,
|
||||
'SERIALIZABLE':extensions.ISOLATION_LEVEL_SERIALIZABLE
|
||||
}
|
||||
@@ -327,9 +328,9 @@ class PGDialect_psycopg2(PGDialect):
|
||||
except KeyError:
|
||||
raise exc.ArgumentError(
|
||||
"Invalid value '%s' for isolation_level. "
|
||||
"Valid isolation levels for %s are %s" %
|
||||
"Valid isolation levels for %s are %s" %
|
||||
(level, self.name, ", ".join(self._isolation_lookup))
|
||||
)
|
||||
)
|
||||
|
||||
connection.set_isolation_level(level)
|
||||
|
||||
@@ -369,9 +370,10 @@ class PGDialect_psycopg2(PGDialect):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, self.dbapi.OperationalError):
|
||||
# these error messages from libpq: interfaces/libpq/fe-misc.c.
|
||||
# TODO: these are sent through gettext in libpq and we can't
|
||||
# check within other locales - consider using connection.closed
|
||||
return 'closed the connection' in str(e) or \
|
||||
# TODO: these are sent through gettext in libpq and we can't
|
||||
# check within other locales - consider using connection.closed
|
||||
return 'terminating connection' in str(e) or \
|
||||
'closed the connection' in str(e) or \
|
||||
'connection not open' in str(e) or \
|
||||
'could not receive data from server' in str(e)
|
||||
elif isinstance(e, self.dbapi.InterfaceError):
|
||||
@@ -379,7 +381,7 @@ class PGDialect_psycopg2(PGDialect):
|
||||
return 'connection already closed' in str(e) or \
|
||||
'cursor already closed' in str(e)
|
||||
elif isinstance(e, self.dbapi.ProgrammingError):
|
||||
# not sure where this path is originally from, it may
|
||||
# not sure where this path is originally from, it may
|
||||
# be obsolete. It really says "losed", not "closed".
|
||||
return "losed the connection unexpectedly" in str(e)
|
||||
else:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/pypostgresql.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -40,7 +40,7 @@ class PGDialect_pypostgresql(PGDialect):
|
||||
default_paramstyle = 'pyformat'
|
||||
|
||||
# requires trunk version to support sane rowcounts
|
||||
# TODO: use dbapi version information to set this flag appropariately
|
||||
# TODO: use dbapi version information to set this flag appropriately
|
||||
supports_sane_rowcount = True
|
||||
supports_sane_multi_rowcount = False
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/zxjdbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sqlite/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sqlite/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -12,7 +12,7 @@ section regarding that driver.
|
||||
Date and Time Types
|
||||
-------------------
|
||||
|
||||
SQLite does not have built-in DATE, TIME, or DATETIME types, and pysqlite does not provide
|
||||
SQLite does not have built-in DATE, TIME, or DATETIME types, and pysqlite does not provide
|
||||
out of the box functionality for translating values between Python `datetime` objects
|
||||
and a SQLite-supported format. SQLAlchemy's own :class:`~sqlalchemy.types.DateTime`
|
||||
and related types provide date formatting and parsing functionality when SQlite is used.
|
||||
@@ -36,23 +36,91 @@ Two things to note:
|
||||
This is regardless of the AUTOINCREMENT keyword being present or not.
|
||||
|
||||
To specifically render the AUTOINCREMENT keyword on the primary key
|
||||
column when rendering DDL, add the flag ``sqlite_autoincrement=True``
|
||||
column when rendering DDL, add the flag ``sqlite_autoincrement=True``
|
||||
to the Table construct::
|
||||
|
||||
Table('sometable', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('id', Integer, primary_key=True),
|
||||
sqlite_autoincrement=True)
|
||||
|
||||
Transaction Isolation Level
|
||||
---------------------------
|
||||
|
||||
:func:`.create_engine` accepts an ``isolation_level`` parameter which results in
|
||||
the command ``PRAGMA read_uncommitted <level>`` being invoked for every new
|
||||
connection. Valid values for this parameter are ``SERIALIZABLE`` and
|
||||
:func:`.create_engine` accepts an ``isolation_level`` parameter which results in
|
||||
the command ``PRAGMA read_uncommitted <level>`` being invoked for every new
|
||||
connection. Valid values for this parameter are ``SERIALIZABLE`` and
|
||||
``READ UNCOMMITTED`` corresponding to a value of 0 and 1, respectively.
|
||||
See the section :ref:`pysqlite_serializable` for an important workaround
|
||||
when using serializable isolation with Pysqlite.
|
||||
|
||||
Database Locking Behavior / Concurrency
|
||||
---------------------------------------
|
||||
|
||||
Note that SQLite is not designed for a high level of concurrency. The database
|
||||
itself, being a file, is locked completely during write operations and within
|
||||
transactions, meaning exactly one connection has exclusive access to the database
|
||||
during this period - all other connections will be blocked during this time.
|
||||
|
||||
The Python DBAPI specification also calls for a connection model that is always
|
||||
in a transaction; there is no BEGIN method, only commit and rollback. This implies
|
||||
that a SQLite DBAPI driver would technically allow only serialized access to a
|
||||
particular database file at all times. The pysqlite driver attempts to ameliorate this by
|
||||
deferring the actual BEGIN statement until the first DML (INSERT, UPDATE, or
|
||||
DELETE) is received within a transaction. While this breaks serializable isolation,
|
||||
it at least delays the exclusive locking inherent in SQLite's design.
|
||||
|
||||
SQLAlchemy's default mode of usage with the ORM is known
|
||||
as "autocommit=False", which means the moment the :class:`.Session` begins to be
|
||||
used, a transaction is begun. As the :class:`.Session` is used, the autoflush
|
||||
feature, also on by default, will flush out pending changes to the database
|
||||
before each query. The effect of this is that a :class:`.Session` used in its
|
||||
default mode will often emit DML early on, long before the transaction is actually
|
||||
committed. This again will have the effect of serializing access to the SQLite
|
||||
database. If highly concurrent reads are desired against the SQLite database,
|
||||
it is advised that the autoflush feature be disabled, and potentially even
|
||||
that autocommit be re-enabled, which has the effect of each SQL statement and
|
||||
flush committing changes immediately.
|
||||
|
||||
For more information on SQLite's lack of concurrency by design, please
|
||||
see `Situations Where Another RDBMS May Work Better - High Concurrency <http://www.sqlite.org/whentouse.html>`_
|
||||
near the bottom of the page.
|
||||
|
||||
.. _sqlite_foreign_keys:
|
||||
|
||||
Foreign Key Support
|
||||
-------------------
|
||||
|
||||
SQLite supports FOREIGN KEY syntax when emitting CREATE statements for tables,
|
||||
however by default these constraints have no effect on the operation
|
||||
of the table.
|
||||
|
||||
Constraint checking on SQLite has three prerequisites:
|
||||
|
||||
* At least version 3.6.19 of SQLite must be in use
|
||||
* The SQLite libary must be compiled *without* the SQLITE_OMIT_FOREIGN_KEY
|
||||
or SQLITE_OMIT_TRIGGER symbols enabled.
|
||||
* The ``PRAGMA foreign_keys = ON`` statement must be emitted on all connections
|
||||
before use.
|
||||
|
||||
SQLAlchemy allows for the ``PRAGMA`` statement to be emitted automatically
|
||||
for new connections through the usage of events::
|
||||
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy import event
|
||||
|
||||
@event.listens_for(Engine, "connect")
|
||||
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||
cursor = dbapi_connection.cursor()
|
||||
cursor.execute("PRAGMA foreign_keys=ON")
|
||||
cursor.close()
|
||||
|
||||
.. seealso::
|
||||
|
||||
`SQLite Foreign Key Support <http://www.sqlite.org/foreignkeys.html>`_ -
|
||||
on the SQLite web site.
|
||||
|
||||
:ref:`event_toplevel` - SQLAlchemy event API.
|
||||
|
||||
"""
|
||||
|
||||
import datetime, re
|
||||
@@ -80,36 +148,36 @@ class _DateTimeMixin(object):
|
||||
|
||||
class DATETIME(_DateTimeMixin, sqltypes.DateTime):
|
||||
"""Represent a Python datetime object in SQLite using a string.
|
||||
|
||||
|
||||
The default string storage format is::
|
||||
|
||||
"%04d-%02d-%02d %02d:%02d:%02d.%06d" % (value.year,
|
||||
|
||||
"%04d-%02d-%02d %02d:%02d:%02d.%06d" % (value.year,
|
||||
value.month, value.day,
|
||||
value.hour, value.minute,
|
||||
value.hour, value.minute,
|
||||
value.second, value.microsecond)
|
||||
|
||||
|
||||
e.g.::
|
||||
|
||||
|
||||
2011-03-15 12:05:57.10558
|
||||
|
||||
The storage format can be customized to some degree using the
|
||||
|
||||
The storage format can be customized to some degree using the
|
||||
``storage_format`` and ``regexp`` parameters, such as::
|
||||
|
||||
|
||||
import re
|
||||
from sqlalchemy.dialects.sqlite import DATETIME
|
||||
|
||||
|
||||
dt = DATETIME(
|
||||
storage_format="%04d/%02d/%02d %02d-%02d-%02d-%06d",
|
||||
regexp=re.compile("(\d+)/(\d+)/(\d+) (\d+)-(\d+)-(\d+)(?:-(\d+))?")
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be appled to the
|
||||
|
||||
:param storage_format: format string which will be applied to the
|
||||
tuple ``(value.year, value.month, value.day, value.hour,
|
||||
value.minute, value.second, value.microsecond)``, given a
|
||||
Python datetime.datetime() object.
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. The resulting match object is appled to
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. The resulting match object is applied to
|
||||
the Python datetime() constructor via ``*map(int,
|
||||
match_obj.groups(0))``.
|
||||
"""
|
||||
@@ -146,16 +214,16 @@ class DATE(_DateTimeMixin, sqltypes.Date):
|
||||
"""Represent a Python date object in SQLite using a string.
|
||||
|
||||
The default string storage format is::
|
||||
|
||||
|
||||
"%04d-%02d-%02d" % (value.year, value.month, value.day)
|
||||
|
||||
|
||||
e.g.::
|
||||
|
||||
|
||||
2011-03-15
|
||||
|
||||
The storage format can be customized to some degree using the
|
||||
|
||||
The storage format can be customized to some degree using the
|
||||
``storage_format`` and ``regexp`` parameters, such as::
|
||||
|
||||
|
||||
import re
|
||||
from sqlalchemy.dialects.sqlite import DATE
|
||||
|
||||
@@ -163,16 +231,16 @@ class DATE(_DateTimeMixin, sqltypes.Date):
|
||||
storage_format="%02d/%02d/%02d",
|
||||
regexp=re.compile("(\d+)/(\d+)/(\d+)")
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be appled to the
|
||||
|
||||
:param storage_format: format string which will be applied to the
|
||||
tuple ``(value.year, value.month, value.day)``,
|
||||
given a Python datetime.date() object.
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. The resulting match object is appled to
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. The resulting match object is applied to
|
||||
the Python date() constructor via ``*map(int,
|
||||
match_obj.groups(0))``.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
_storage_format = "%04d-%02d-%02d"
|
||||
@@ -199,20 +267,20 @@ class DATE(_DateTimeMixin, sqltypes.Date):
|
||||
|
||||
class TIME(_DateTimeMixin, sqltypes.Time):
|
||||
"""Represent a Python time object in SQLite using a string.
|
||||
|
||||
|
||||
The default string storage format is::
|
||||
|
||||
"%02d:%02d:%02d.%06d" % (value.hour, value.minute,
|
||||
|
||||
"%02d:%02d:%02d.%06d" % (value.hour, value.minute,
|
||||
value.second,
|
||||
value.microsecond)
|
||||
|
||||
|
||||
e.g.::
|
||||
|
||||
|
||||
12:05:57.10558
|
||||
|
||||
The storage format can be customized to some degree using the
|
||||
|
||||
The storage format can be customized to some degree using the
|
||||
``storage_format`` and ``regexp`` parameters, such as::
|
||||
|
||||
|
||||
import re
|
||||
from sqlalchemy.dialects.sqlite import TIME
|
||||
|
||||
@@ -220,13 +288,13 @@ class TIME(_DateTimeMixin, sqltypes.Time):
|
||||
storage_format="%02d-%02d-%02d-%06d",
|
||||
regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?")
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be appled
|
||||
|
||||
:param storage_format: format string which will be applied
|
||||
to the tuple ``(value.hour, value.minute, value.second,
|
||||
value.microsecond)``, given a Python datetime.time() object.
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. The resulting match object is appled to
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. The resulting match object is applied to
|
||||
the Python time() constructor via ``*map(int,
|
||||
match_obj.groups(0))``.
|
||||
|
||||
@@ -302,6 +370,9 @@ class SQLiteCompiler(compiler.SQLCompiler):
|
||||
def visit_now_func(self, fn, **kw):
|
||||
return "CURRENT_TIMESTAMP"
|
||||
|
||||
def visit_localtimestamp_func(self, func, **kw):
|
||||
return 'DATETIME(CURRENT_TIMESTAMP, "localtime")'
|
||||
|
||||
def visit_true(self, expr, **kw):
|
||||
return '1'
|
||||
|
||||
@@ -373,7 +444,7 @@ class SQLiteDDLCompiler(compiler.DDLCompiler):
|
||||
issubclass(c.type._type_affinity, sqltypes.Integer) and \
|
||||
not c.foreign_keys:
|
||||
return None
|
||||
|
||||
|
||||
return super(SQLiteDDLCompiler, self).\
|
||||
visit_primary_key_constraint(constraint)
|
||||
|
||||
@@ -441,6 +512,22 @@ class SQLiteIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
result = self.quote_schema(index.table.schema, index.table.quote_schema) + "." + result
|
||||
return result
|
||||
|
||||
class SQLiteExecutionContext(default.DefaultExecutionContext):
|
||||
@util.memoized_property
|
||||
def _preserve_raw_colnames(self):
|
||||
return self.execution_options.get("sqlite_raw_colnames", False)
|
||||
|
||||
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 not self._preserve_raw_colnames and "." in colname:
|
||||
return colname.split(".")[1], colname
|
||||
else:
|
||||
return colname, None
|
||||
|
||||
|
||||
class SQLiteDialect(default.DefaultDialect):
|
||||
name = 'sqlite'
|
||||
supports_alter = False
|
||||
@@ -451,6 +538,7 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
supports_cast = True
|
||||
|
||||
default_paramstyle = 'qmark'
|
||||
execution_ctx_cls = SQLiteExecutionContext
|
||||
statement_compiler = SQLiteCompiler
|
||||
ddl_compiler = SQLiteDDLCompiler
|
||||
type_compiler = SQLiteTypeCompiler
|
||||
@@ -462,13 +550,15 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
supports_cast = True
|
||||
supports_default_values = True
|
||||
|
||||
_broken_fk_pragma_quotes = False
|
||||
|
||||
def __init__(self, isolation_level=None, native_datetime=False, **kwargs):
|
||||
default.DefaultDialect.__init__(self, **kwargs)
|
||||
self.isolation_level = isolation_level
|
||||
|
||||
# this flag used by pysqlite dialect, and perhaps others in the
|
||||
# future, to indicate the driver is handling date/timestamp
|
||||
# conversions (and perhaps datetime/time as well on some
|
||||
# conversions (and perhaps datetime/time as well on some
|
||||
# hypothetical driver ?)
|
||||
self.native_datetime = native_datetime
|
||||
|
||||
@@ -478,6 +568,12 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
self.supports_cast = \
|
||||
self.dbapi.sqlite_version_info >= (3, 2, 3)
|
||||
|
||||
# see http://www.sqlalchemy.org/trac/ticket/2568
|
||||
# as well as http://www.sqlite.org/src/info/600482d161
|
||||
self._broken_fk_pragma_quotes = \
|
||||
self.dbapi.sqlite_version_info < (3, 6, 14)
|
||||
|
||||
|
||||
_isolation_lookup = {
|
||||
'READ UNCOMMITTED':1,
|
||||
'SERIALIZABLE':0
|
||||
@@ -488,9 +584,9 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
except KeyError:
|
||||
raise exc.ArgumentError(
|
||||
"Invalid value '%s' for isolation_level. "
|
||||
"Valid isolation levels for %s are %s" %
|
||||
"Valid isolation levels for %s are %s" %
|
||||
(level, self.name, ", ".join(self._isolation_lookup))
|
||||
)
|
||||
)
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("PRAGMA read_uncommitted = %d" % isolation_level)
|
||||
cursor.close()
|
||||
@@ -501,11 +597,11 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
res = cursor.fetchone()
|
||||
if res:
|
||||
value = res[0]
|
||||
else:
|
||||
else:
|
||||
# http://www.sqlite.org/changes.html#version_3_3_3
|
||||
# "Optional READ UNCOMMITTED isolation (instead of the
|
||||
# default isolation level of SERIALIZABLE) and
|
||||
# table level locking when database connections
|
||||
# "Optional READ UNCOMMITTED isolation (instead of the
|
||||
# default isolation level of SERIALIZABLE) and
|
||||
# table level locking when database connections
|
||||
# share a common cache.""
|
||||
# pre-SQLite 3.3.0 default to 0
|
||||
value = 0
|
||||
@@ -525,16 +621,6 @@ 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:
|
||||
@@ -631,45 +717,52 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
pragma = "PRAGMA "
|
||||
qtable = quote(table_name)
|
||||
c = _pragma_cursor(
|
||||
connection.execute("%stable_info(%s)" %
|
||||
connection.execute("%stable_info(%s)" %
|
||||
(pragma, qtable)))
|
||||
found_table = False
|
||||
columns = []
|
||||
while True:
|
||||
row = c.fetchone()
|
||||
if row is None:
|
||||
break
|
||||
(name, type_, nullable, default, has_default, primary_key) = \
|
||||
(row[1], row[2].upper(), not row[3],
|
||||
row[4], row[4] is not None, row[5])
|
||||
name = re.sub(r'^\"|\"$', '', name)
|
||||
match = re.match(r'(\w+)(\(.*?\))?', type_)
|
||||
if match:
|
||||
coltype = match.group(1)
|
||||
args = match.group(2)
|
||||
else:
|
||||
coltype = "VARCHAR"
|
||||
args = ''
|
||||
try:
|
||||
coltype = self.ischema_names[coltype]
|
||||
if args is not None:
|
||||
args = re.findall(r'(\d+)', args)
|
||||
coltype = coltype(*[int(a) for a in args])
|
||||
except KeyError:
|
||||
util.warn("Did not recognize type '%s' of column '%s'" %
|
||||
(coltype, name))
|
||||
coltype = sqltypes.NullType()
|
||||
|
||||
columns.append({
|
||||
'name' : name,
|
||||
'type' : coltype,
|
||||
'nullable' : nullable,
|
||||
'default' : default,
|
||||
'autoincrement':default is None,
|
||||
'primary_key': primary_key
|
||||
})
|
||||
rows = c.fetchall()
|
||||
columns = []
|
||||
for row in rows:
|
||||
(name, type_, nullable, default, primary_key) = \
|
||||
(row[1], row[2].upper(), not row[3],
|
||||
row[4], row[5])
|
||||
|
||||
columns.append(self._get_column_info(name, type_, nullable,
|
||||
default, primary_key))
|
||||
return columns
|
||||
|
||||
def _get_column_info(self, name, type_, nullable,
|
||||
default, primary_key):
|
||||
|
||||
match = re.match(r'(\w+)(\(.*?\))?', type_)
|
||||
if match:
|
||||
coltype = match.group(1)
|
||||
args = match.group(2)
|
||||
else:
|
||||
coltype = "VARCHAR"
|
||||
args = ''
|
||||
try:
|
||||
coltype = self.ischema_names[coltype]
|
||||
if args is not None:
|
||||
args = re.findall(r'(\d+)', args)
|
||||
coltype = coltype(*[int(a) for a in args])
|
||||
except KeyError:
|
||||
util.warn("Did not recognize type '%s' of column '%s'" %
|
||||
(coltype, name))
|
||||
coltype = sqltypes.NullType()
|
||||
|
||||
if default is not None:
|
||||
default = unicode(default)
|
||||
|
||||
return {
|
||||
'name': name,
|
||||
'type': coltype,
|
||||
'nullable': nullable,
|
||||
'default': default,
|
||||
'autoincrement': default is None,
|
||||
'primary_key': primary_key
|
||||
}
|
||||
|
||||
@reflection.cache
|
||||
def get_primary_keys(self, connection, table_name, schema=None, **kw):
|
||||
cols = self.get_columns(connection, table_name, schema, **kw)
|
||||
@@ -687,7 +780,8 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
else:
|
||||
pragma = "PRAGMA "
|
||||
qtable = quote(table_name)
|
||||
c = _pragma_cursor(connection.execute("%sforeign_key_list(%s)" % (pragma, qtable)))
|
||||
statement = "%sforeign_key_list(%s)" % (pragma, qtable)
|
||||
c = _pragma_cursor(connection.execute(statement))
|
||||
fkeys = []
|
||||
fks = {}
|
||||
while True:
|
||||
@@ -695,34 +789,38 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
if row is None:
|
||||
break
|
||||
(numerical_id, rtbl, lcol, rcol) = (row[0], row[2], row[3], row[4])
|
||||
# sqlite won't return rcol if the table
|
||||
# was created with REFERENCES <tablename>, no col
|
||||
if rcol is None:
|
||||
rcol = lcol
|
||||
rtbl = re.sub(r'^\"|\"$', '', rtbl)
|
||||
lcol = re.sub(r'^\"|\"$', '', lcol)
|
||||
rcol = re.sub(r'^\"|\"$', '', rcol)
|
||||
try:
|
||||
fk = fks[numerical_id]
|
||||
except KeyError:
|
||||
fk = {
|
||||
'name' : None,
|
||||
'constrained_columns' : [],
|
||||
'referred_schema' : None,
|
||||
'referred_table' : rtbl,
|
||||
'referred_columns' : []
|
||||
}
|
||||
fkeys.append(fk)
|
||||
fks[numerical_id] = fk
|
||||
|
||||
# look up the table based on the given table's engine, not 'self',
|
||||
# since it could be a ProxyEngine
|
||||
if lcol not in fk['constrained_columns']:
|
||||
fk['constrained_columns'].append(lcol)
|
||||
if rcol not in fk['referred_columns']:
|
||||
fk['referred_columns'].append(rcol)
|
||||
self._parse_fk(fks, fkeys, numerical_id, rtbl, lcol, rcol)
|
||||
return fkeys
|
||||
|
||||
def _parse_fk(self, fks, fkeys, numerical_id, rtbl, lcol, rcol):
|
||||
# sqlite won't return rcol if the table
|
||||
# was created with REFERENCES <tablename>, no col
|
||||
if rcol is None:
|
||||
rcol = lcol
|
||||
|
||||
if self._broken_fk_pragma_quotes:
|
||||
rtbl = re.sub(r'^[\"\[`\']|[\"\]`\']$', '', rtbl)
|
||||
|
||||
try:
|
||||
fk = fks[numerical_id]
|
||||
except KeyError:
|
||||
fk = {
|
||||
'name': None,
|
||||
'constrained_columns': [],
|
||||
'referred_schema': None,
|
||||
'referred_table': rtbl,
|
||||
'referred_columns': []
|
||||
}
|
||||
fkeys.append(fk)
|
||||
fks[numerical_id] = fk
|
||||
|
||||
if lcol not in fk['constrained_columns']:
|
||||
fk['constrained_columns'].append(lcol)
|
||||
if rcol not in fk['referred_columns']:
|
||||
fk['referred_columns'].append(rcol)
|
||||
return fk
|
||||
|
||||
@reflection.cache
|
||||
def get_indexes(self, connection, table_name, schema=None, **kw):
|
||||
quote = self.identifier_preparer.quote_identifier
|
||||
@@ -757,9 +855,10 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
|
||||
|
||||
def _pragma_cursor(cursor):
|
||||
"""work around SQLite issue whereby cursor.description
|
||||
"""work around SQLite issue whereby cursor.description
|
||||
is blank when PRAGMA returns no rows."""
|
||||
|
||||
if cursor.closed:
|
||||
cursor.fetchone = lambda: None
|
||||
cursor.fetchall = lambda: []
|
||||
return cursor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sqlite/pysqlite.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -12,15 +12,15 @@ module included with the Python distribution.
|
||||
Driver
|
||||
------
|
||||
|
||||
When using Python 2.5 and above, the built in ``sqlite3`` driver is
|
||||
When using Python 2.5 and above, the built in ``sqlite3`` driver is
|
||||
already installed and no additional installation is needed. Otherwise,
|
||||
the ``pysqlite2`` driver needs to be present. This is the same driver as
|
||||
``sqlite3``, just with a different name.
|
||||
|
||||
The ``pysqlite2`` driver will be loaded first, and if not found, ``sqlite3``
|
||||
is loaded. This allows an explicitly installed pysqlite driver to take
|
||||
precedence over the built in one. As with all dialects, a specific
|
||||
DBAPI module may be provided to :func:`~sqlalchemy.create_engine()` to control
|
||||
precedence over the built in one. As with all dialects, a specific
|
||||
DBAPI module may be provided to :func:`~sqlalchemy.create_engine()` to control
|
||||
this explicitly::
|
||||
|
||||
from sqlite3 import dbapi2 as sqlite
|
||||
@@ -64,25 +64,25 @@ The sqlite ``:memory:`` identifier is the default if no filepath is present. Sp
|
||||
Compatibility with sqlite3 "native" date and datetime types
|
||||
-----------------------------------------------------------
|
||||
|
||||
The pysqlite driver includes the sqlite3.PARSE_DECLTYPES and
|
||||
The pysqlite driver includes the sqlite3.PARSE_DECLTYPES and
|
||||
sqlite3.PARSE_COLNAMES options, which have the effect of any column
|
||||
or expression explicitly cast as "date" or "timestamp" will be converted
|
||||
to a Python date or datetime object. The date and datetime types provided
|
||||
with the pysqlite dialect are not currently compatible with these options,
|
||||
since they render the ISO date/datetime including microseconds, which
|
||||
to a Python date or datetime object. The date and datetime types provided
|
||||
with the pysqlite dialect are not currently compatible with these options,
|
||||
since they render the ISO date/datetime including microseconds, which
|
||||
pysqlite's driver does not. Additionally, SQLAlchemy does not at
|
||||
this time automatically render the "cast" syntax required for the
|
||||
this time automatically render the "cast" syntax required for the
|
||||
freestanding functions "current_timestamp" and "current_date" to return
|
||||
datetime/date types natively. Unfortunately, pysqlite
|
||||
datetime/date types natively. Unfortunately, pysqlite
|
||||
does not provide the standard DBAPI types in ``cursor.description``,
|
||||
leaving SQLAlchemy with no way to detect these types on the fly
|
||||
leaving SQLAlchemy with no way to detect these types on the fly
|
||||
without expensive per-row type checks.
|
||||
|
||||
Keeping in mind that pysqlite's parsing option is not recommended,
|
||||
nor should be necessary, for use with SQLAlchemy, usage of PARSE_DECLTYPES
|
||||
nor should be necessary, for use with SQLAlchemy, usage of PARSE_DECLTYPES
|
||||
can be forced if one configures "native_datetime=True" on create_engine()::
|
||||
|
||||
engine = create_engine('sqlite://',
|
||||
engine = create_engine('sqlite://',
|
||||
connect_args={'detect_types': sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES},
|
||||
native_datetime=True
|
||||
)
|
||||
@@ -97,37 +97,40 @@ Threading/Pooling Behavior
|
||||
---------------------------
|
||||
|
||||
Pysqlite's default behavior is to prohibit the usage of a single connection
|
||||
in more than one thread. This is controlled by the ``check_same_thread``
|
||||
Pysqlite flag. This default is intended to work with older versions
|
||||
of SQLite that did not support multithreaded operation under
|
||||
in more than one thread. This is originally intended to work with older versions
|
||||
of SQLite that did not support multithreaded operation under
|
||||
various circumstances. In particular, older SQLite versions
|
||||
did not allow a ``:memory:`` database to be used in multiple threads
|
||||
under any circumstances.
|
||||
|
||||
Pysqlite does include a now-undocumented flag known as
|
||||
``check_same_thread`` which will disable this check, however note that pysqlite
|
||||
connections are still not safe to use in concurrently in multiple threads.
|
||||
In particular, any statement execution calls would need to be externally
|
||||
mutexed, as Pysqlite does not provide for thread-safe propagation of error
|
||||
messages among other things. So while even ``:memory:`` databases can be
|
||||
shared among threads in modern SQLite, Pysqlite doesn't provide enough
|
||||
thread-safety to make this usage worth it.
|
||||
|
||||
SQLAlchemy sets up pooling to work with Pysqlite's default behavior:
|
||||
|
||||
* When a ``:memory:`` SQLite database is specified, the dialect by default will use
|
||||
:class:`.SingletonThreadPool`. This pool maintains a single connection per
|
||||
thread, so that all access to the engine within the current thread use the
|
||||
same ``:memory:`` database - other threads would access a different
|
||||
same ``:memory:`` database - other threads would access a different
|
||||
``:memory:`` database.
|
||||
* When a file-based database is specified, the dialect will use :class:`.NullPool`
|
||||
* When a file-based database is specified, the dialect will use :class:`.NullPool`
|
||||
as the source of connections. This pool closes and discards connections
|
||||
which are returned to the pool immediately. SQLite file-based connections
|
||||
have extremely low overhead, so pooling is not necessary. The scheme also
|
||||
prevents a connection from being used again in a different thread and works
|
||||
best with SQLite's coarse-grained file locking.
|
||||
|
||||
.. note::
|
||||
|
||||
The default selection of :class:`.NullPool` for SQLite file-based databases
|
||||
is new in SQLAlchemy 0.7. Previous versions
|
||||
select :class:`.SingletonThreadPool` by
|
||||
default for all SQLite databases.
|
||||
.. versionchanged:: 0.7
|
||||
Default selection of :class:`.NullPool` for SQLite file-based databases.
|
||||
Previous versions select :class:`.SingletonThreadPool` by
|
||||
default for all SQLite databases.
|
||||
|
||||
Modern versions of SQLite no longer have the threading restrictions, and assuming
|
||||
the sqlite3/pysqlite library was built with SQLite's default threading mode
|
||||
of "Serialized", even ``:memory:`` databases can be shared among threads.
|
||||
|
||||
Using a Memory Database in Multiple Threads
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -143,7 +146,7 @@ can be passed to Pysqlite as ``False``::
|
||||
connect_args={'check_same_thread':False},
|
||||
poolclass=StaticPool)
|
||||
|
||||
Note that using a ``:memory:`` database in multiple threads requires a recent
|
||||
Note that using a ``:memory:`` database in multiple threads requires a recent
|
||||
version of SQLite.
|
||||
|
||||
Using Temporary Tables with SQLite
|
||||
@@ -177,8 +180,8 @@ Unicode
|
||||
|
||||
The pysqlite driver only returns Python ``unicode`` objects in result sets, never
|
||||
plain strings, and accommodates ``unicode`` objects within bound parameter
|
||||
values in all cases. Regardless of the SQLAlchemy string type in use,
|
||||
string-based result values will by Python ``unicode`` in Python 2.
|
||||
values in all cases. Regardless of the SQLAlchemy string type in use,
|
||||
string-based result values will by Python ``unicode`` in Python 2.
|
||||
The :class:`.Unicode` type should still be used to indicate those columns that
|
||||
require unicode, however, so that non-``unicode`` values passed inadvertently
|
||||
will emit a warning. Pysqlite will emit an error if a non-``unicode`` string
|
||||
@@ -193,7 +196,7 @@ The pysqlite DBAPI driver has a long-standing bug in which transactional
|
||||
state is not begun until the first DML statement, that is INSERT, UPDATE
|
||||
or DELETE, is emitted. A SELECT statement will not cause transactional
|
||||
state to begin. While this mode of usage is fine for typical situations
|
||||
and has the advantage that the SQLite database file is not prematurely
|
||||
and has the advantage that the SQLite database file is not prematurely
|
||||
locked, it breaks serializable transaction isolation, which requires
|
||||
that the database file be locked upon any SQL being emitted.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sybase/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# sybase/base.py
|
||||
# Copyright (C) 2010-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# get_select_precolumns(), limit_clause() implementation
|
||||
# copyright (C) 2007 Fisch Asset Management
|
||||
# AG http://www.fam.ch, with coding by Alexander Houben
|
||||
# copyright (C) 2007 Fisch Asset Management
|
||||
# AG http://www.fam.ch, with coding by Alexander Houben
|
||||
# alexander.houben@thor-solutions.ch
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -10,8 +10,12 @@
|
||||
|
||||
"""Support for Sybase Adaptive Server Enterprise (ASE).
|
||||
|
||||
Note that this dialect is no longer specific to Sybase iAnywhere.
|
||||
ASE is the primary support platform.
|
||||
.. note::
|
||||
|
||||
The Sybase dialect functions on current SQLAlchemy versions
|
||||
but is not regularly tested, and may have many issues and
|
||||
caveats not currently handled. In particular, the table
|
||||
and database reflection features are not implemented.
|
||||
|
||||
"""
|
||||
|
||||
@@ -126,7 +130,7 @@ class UNIQUEIDENTIFIER(sqltypes.TypeEngine):
|
||||
|
||||
class IMAGE(sqltypes.LargeBinary):
|
||||
__visit_name__ = 'IMAGE'
|
||||
|
||||
|
||||
|
||||
class SybaseTypeCompiler(compiler.GenericTypeCompiler):
|
||||
def visit_large_binary(self, type_):
|
||||
@@ -220,12 +224,12 @@ class SybaseExecutionContext(default.DefaultExecutionContext):
|
||||
self._enable_identity_insert = False
|
||||
|
||||
if self._enable_identity_insert:
|
||||
self.cursor.execute("SET IDENTITY_INSERT %s ON" %
|
||||
self.cursor.execute("SET IDENTITY_INSERT %s ON" %
|
||||
self.dialect.identifier_preparer.format_table(tbl))
|
||||
|
||||
if self.isddl:
|
||||
# TODO: to enhance this, we can detect "ddl in tran" on the
|
||||
# database settings. this error message should be improved to
|
||||
# database settings. this error message should be improved to
|
||||
# include a note about that.
|
||||
if not self.should_autocommit:
|
||||
raise exc.InvalidRequestError(
|
||||
@@ -236,7 +240,7 @@ class SybaseExecutionContext(default.DefaultExecutionContext):
|
||||
"AUTOCOMMIT (Assuming no Sybase 'ddl in tran')")
|
||||
|
||||
self.set_ddl_autocommit(
|
||||
self.root_connection.connection.connection,
|
||||
self.root_connection.connection.connection,
|
||||
True)
|
||||
|
||||
|
||||
@@ -300,7 +304,7 @@ class SybaseSQLCompiler(compiler.SQLCompiler):
|
||||
field, self.process(extract.expr, **kw))
|
||||
|
||||
def for_update_clause(self, select):
|
||||
# "FOR UPDATE" is only allowed on "DECLARE CURSOR"
|
||||
# "FOR UPDATE" is only allowed on "DECLARE CURSOR"
|
||||
# which SQLAlchemy doesn't use
|
||||
return ''
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sybase/mxodbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sybase/pyodbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -17,7 +17,7 @@ Connect strings are of the form::
|
||||
Unicode Support
|
||||
---------------
|
||||
|
||||
The pyodbc driver currently supports usage of these Sybase types with
|
||||
The pyodbc driver currently supports usage of these Sybase types with
|
||||
Unicode or multibyte strings::
|
||||
|
||||
CHAR
|
||||
@@ -43,7 +43,7 @@ from sqlalchemy.util.compat import decimal
|
||||
class _SybNumeric_pyodbc(sqltypes.Numeric):
|
||||
"""Turns Decimals with adjusted() < -6 into floats.
|
||||
|
||||
It's not yet known how to get decimals with many
|
||||
It's not yet known how to get decimals with many
|
||||
significant digits or very large adjusted() into Sybase
|
||||
via pyodbc.
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class SybaseExecutionContext_pysybase(SybaseExecutionContext):
|
||||
def set_ddl_autocommit(self, dbapi_connection, value):
|
||||
if value:
|
||||
# call commit() on the Sybase connection directly,
|
||||
# to avoid any side effects of calling a Connection
|
||||
# to avoid any side effects of calling a Connection
|
||||
# transactional method inside of pre_exec()
|
||||
dbapi_connection.commit()
|
||||
|
||||
@@ -52,7 +52,7 @@ class SybaseExecutionContext_pysybase(SybaseExecutionContext):
|
||||
|
||||
|
||||
class SybaseSQLCompiler_pysybase(SybaseSQLCompiler):
|
||||
def bindparam_string(self, name):
|
||||
def bindparam_string(self, name, **kw):
|
||||
return "@" + name
|
||||
|
||||
class SybaseDialect_pysybase(SybaseDialect):
|
||||
@@ -82,10 +82,10 @@ class SybaseDialect_pysybase(SybaseDialect):
|
||||
cursor.execute(statement, param)
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
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)
|
||||
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, connection, cursor):
|
||||
if isinstance(e, (self.dbapi.OperationalError,
|
||||
|
||||
Reference in New Issue
Block a user