Packages update
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
# dialects/__init__.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
|
||||
__all__ = (
|
||||
# 'access',
|
||||
'drizzle',
|
||||
'firebird',
|
||||
# 'informix',
|
||||
# 'maxdb',
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
"""
|
||||
Support for the Microsoft Access database.
|
||||
|
||||
This dialect is *not* ported to SQLAlchemy 0.6.
|
||||
This dialect is *not* ported to SQLAlchemy 0.6 or 0.7.
|
||||
|
||||
This dialect is *not* tested on SQLAlchemy 0.6.
|
||||
This dialect is *not* tested on SQLAlchemy 0.6 or 0.7.
|
||||
|
||||
|
||||
"""
|
||||
@@ -51,15 +51,10 @@ class AcSmallInteger(types.SmallInteger):
|
||||
return "SMALLINT"
|
||||
|
||||
class AcDateTime(types.DateTime):
|
||||
def __init__(self, *a, **kw):
|
||||
super(AcDateTime, self).__init__(False)
|
||||
|
||||
def get_col_spec(self):
|
||||
return "DATETIME"
|
||||
|
||||
class AcDate(types.Date):
|
||||
def __init__(self, *a, **kw):
|
||||
super(AcDate, self).__init__(False)
|
||||
|
||||
def get_col_spec(self):
|
||||
return "DATETIME"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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
|
||||
|
||||
__all__ = (
|
||||
'BIGINT', 'BINARY', 'BLOB', 'BOOLEAN', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'DOUBLE',
|
||||
'ENUM', 'FLOAT', 'INTEGER',
|
||||
'NUMERIC', 'SET', 'REAL', 'TEXT', 'TIME', 'TIMESTAMP',
|
||||
'VARBINARY', 'VARCHAR', 'dialect'
|
||||
)
|
||||
@@ -0,0 +1,582 @@
|
||||
# drizzle/base.py
|
||||
# Copyright (C) 2005-2012 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
|
||||
-------------------------------
|
||||
|
||||
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.
|
||||
|
||||
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)
|
||||
|
||||
"""
|
||||
|
||||
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 types as sqltypes
|
||||
from sqlalchemy.dialects.mysql import base as mysql_dialect
|
||||
|
||||
from sqlalchemy.types import DATE, DATETIME, BOOLEAN, TIME, \
|
||||
BLOB, BINARY, VARBINARY
|
||||
|
||||
class _NumericType(object):
|
||||
"""Base for Drizzle numeric types."""
|
||||
|
||||
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 \
|
||||
(
|
||||
(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.")
|
||||
|
||||
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):
|
||||
kw['national'] = False
|
||||
super(_StringType, self).__init__(collation=collation,
|
||||
binary=binary,
|
||||
**kw)
|
||||
|
||||
|
||||
class NUMERIC(_NumericType, sqltypes.NUMERIC):
|
||||
"""Drizzle NUMERIC type."""
|
||||
|
||||
__visit_name__ = 'NUMERIC'
|
||||
|
||||
def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
|
||||
"""Construct a NUMERIC.
|
||||
|
||||
:param precision: Total digits in this number. If scale and precision
|
||||
are both None, values are stored to limits allowed by the server.
|
||||
|
||||
:param scale: The number of digits after the decimal point.
|
||||
|
||||
"""
|
||||
super(NUMERIC, self).__init__(precision=precision, scale=scale, asdecimal=asdecimal, **kw)
|
||||
|
||||
|
||||
class DECIMAL(_NumericType, sqltypes.DECIMAL):
|
||||
"""Drizzle DECIMAL type."""
|
||||
|
||||
__visit_name__ = 'DECIMAL'
|
||||
|
||||
def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
|
||||
"""Construct a DECIMAL.
|
||||
|
||||
:param precision: Total digits in this number. If scale and precision
|
||||
are both None, values are stored to limits allowed by the server.
|
||||
|
||||
:param scale: The number of digits after the decimal point.
|
||||
|
||||
"""
|
||||
super(DECIMAL, self).__init__(precision=precision, scale=scale,
|
||||
asdecimal=asdecimal, **kw)
|
||||
|
||||
|
||||
class DOUBLE(_FloatType):
|
||||
"""Drizzle DOUBLE type."""
|
||||
|
||||
__visit_name__ = 'DOUBLE'
|
||||
|
||||
def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
|
||||
"""Construct a DOUBLE.
|
||||
|
||||
:param precision: Total digits in this number. If scale and precision
|
||||
are both None, values are stored to limits allowed by the server.
|
||||
|
||||
: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."""
|
||||
|
||||
__visit_name__ = 'REAL'
|
||||
|
||||
def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
|
||||
"""Construct a REAL.
|
||||
|
||||
:param precision: Total digits in this number. If scale and precision
|
||||
are both None, values are stored to limits allowed by the server.
|
||||
|
||||
: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."""
|
||||
|
||||
__visit_name__ = 'FLOAT'
|
||||
|
||||
def __init__(self, precision=None, scale=None, asdecimal=False, **kw):
|
||||
"""Construct a FLOAT.
|
||||
|
||||
:param precision: Total digits in this number. If scale and precision
|
||||
are both None, values are stored to limits allowed by the server.
|
||||
|
||||
: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.
|
||||
|
||||
"""
|
||||
super(INTEGER, self).__init__(**kw)
|
||||
|
||||
class BIGINT(sqltypes.BIGINT):
|
||||
"""Drizzle BIGINTEGER type."""
|
||||
|
||||
__visit_name__ = 'BIGINT'
|
||||
|
||||
def __init__(self, **kw):
|
||||
"""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."""
|
||||
|
||||
__visit_name__ = 'TEXT'
|
||||
|
||||
def __init__(self, length=None, **kw):
|
||||
"""Construct a TEXT.
|
||||
|
||||
:param length: Optional, if provided the server may optimize storage
|
||||
by substituting the smallest TEXT type sufficient to store
|
||||
``length`` characters.
|
||||
|
||||
:param collation: Optional, a column-level collation for this string
|
||||
value. Takes precedence to 'binary' short-hand.
|
||||
|
||||
:param binary: Defaults to False: short-hand, pick the binary
|
||||
collation type that matches the column's character set. Generates
|
||||
BINARY in schema. This does not affect the type of data stored,
|
||||
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."""
|
||||
|
||||
__visit_name__ = 'VARCHAR'
|
||||
|
||||
def __init__(self, length=None, **kwargs):
|
||||
"""Construct a VARCHAR.
|
||||
|
||||
:param collation: Optional, a column-level collation for this string
|
||||
value. Takes precedence to 'binary' short-hand.
|
||||
|
||||
:param binary: Defaults to False: short-hand, pick the binary
|
||||
collation type that matches the column's character set. Generates
|
||||
BINARY in schema. This does not affect the type of data stored,
|
||||
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."""
|
||||
|
||||
__visit_name__ = 'CHAR'
|
||||
|
||||
def __init__(self, length=None, **kwargs):
|
||||
"""Construct a CHAR.
|
||||
|
||||
:param length: Maximum data length, in characters.
|
||||
|
||||
:param binary: Optional, use the default binary collation for the
|
||||
national character set. This does not affect the type of data
|
||||
stored, use a BINARY type for binary data.
|
||||
|
||||
:param collation: Optional, request a particular collation. Must be
|
||||
compatible with the national character set.
|
||||
|
||||
"""
|
||||
super(CHAR, self).__init__(length=length, **kwargs)
|
||||
|
||||
class ENUM(mysql_dialect.ENUM):
|
||||
"""Drizzle ENUM type."""
|
||||
|
||||
def __init__(self, *enums, **kw):
|
||||
"""Construct an ENUM.
|
||||
|
||||
Example:
|
||||
|
||||
Column('myenum', ENUM("foo", "bar", "baz"))
|
||||
|
||||
:param enums: The range of valid values for this ENUM. Values will be
|
||||
quoted when generating the schema according to the quoting flag (see
|
||||
below).
|
||||
|
||||
: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.
|
||||
(See Drizzle ENUM documentation.)
|
||||
|
||||
:param collation: Optional, a column-level collation for this string
|
||||
value. Takes precedence to 'binary' short-hand.
|
||||
|
||||
:param binary: Defaults to False: short-hand, pick the binary
|
||||
collation type that matches the column's character set. Generates
|
||||
BINARY in schema. This does not affect the type of data stored,
|
||||
only the collation of character data.
|
||||
|
||||
:param quoting: Defaults to 'auto': automatically determine enum value
|
||||
quoting. If all enum values are surrounded by the same quoting
|
||||
character, then use 'quoted' mode. Otherwise, use 'unquoted' mode.
|
||||
|
||||
'quoted': values in enums are already quoted, they will be used
|
||||
directly when generating the schema - this usage is deprecated.
|
||||
|
||||
'unquoted': values in enums are not quoted, they will be escaped and
|
||||
surrounded by single quotes when generating the schema.
|
||||
|
||||
Previous versions of this type always required manually quoted
|
||||
values to be supplied; future versions will always quote the string
|
||||
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,
|
||||
sqltypes.Time: _DrizzleTime,
|
||||
sqltypes.Enum: ENUM,
|
||||
sqltypes.Boolean: _DrizzleBoolean,
|
||||
}
|
||||
|
||||
# All the types we have in Drizzle
|
||||
ischema_names = {
|
||||
'BIGINT': BIGINT,
|
||||
'BINARY': BINARY,
|
||||
'BLOB': BLOB,
|
||||
'BOOLEAN': BOOLEAN,
|
||||
'CHAR': CHAR,
|
||||
'DATE': DATE,
|
||||
'DATETIME': DATETIME,
|
||||
'DECIMAL': DECIMAL,
|
||||
'DOUBLE': DOUBLE,
|
||||
'ENUM': ENUM,
|
||||
'FLOAT': FLOAT,
|
||||
'INT': INTEGER,
|
||||
'INTEGER': INTEGER,
|
||||
'NUMERIC': NUMERIC,
|
||||
'TEXT': TEXT,
|
||||
'TIME': TIME,
|
||||
'TIMESTAMP': TIMESTAMP,
|
||||
'VARBINARY': VARBINARY,
|
||||
'VARCHAR': VARCHAR,
|
||||
}
|
||||
|
||||
class DrizzleCompiler(mysql_dialect.MySQLCompiler):
|
||||
|
||||
def visit_typeclause(self, typeclause):
|
||||
type_ = typeclause.type.dialect_impl(self.dialect)
|
||||
if isinstance(type_, sqltypes.Integer):
|
||||
return 'INTEGER'
|
||||
else:
|
||||
return super(DrizzleCompiler, self).visit_typeclause(typeclause)
|
||||
|
||||
def visit_cast(self, cast, **kwargs):
|
||||
type_ = self.process(cast.typeclause)
|
||||
if type_ is None:
|
||||
return self.process(cast.clause)
|
||||
|
||||
return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
|
||||
|
||||
|
||||
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
|
||||
COLLATE annotations and Drizzle specific extensions.
|
||||
|
||||
"""
|
||||
|
||||
def attr(name):
|
||||
return getattr(type_, name, defaults.get(name))
|
||||
|
||||
if attr('collation'):
|
||||
collation = 'COLLATE %s' % type_.collation
|
||||
elif attr('binary'):
|
||||
collation = 'BINARY'
|
||||
else:
|
||||
collation = None
|
||||
|
||||
return ' '.join([c for c in (spec, collation)
|
||||
if c is not None])
|
||||
|
||||
def visit_NCHAR(self, type):
|
||||
raise NotImplementedError("Drizzle does not support NCHAR")
|
||||
|
||||
def visit_NVARCHAR(self, type):
|
||||
raise NotImplementedError("Drizzle does not support NVARCHAR")
|
||||
|
||||
def visit_FLOAT(self, type_):
|
||||
if type_.scale is not None and type_.precision is not None:
|
||||
return "FLOAT(%s, %s)" % (type_.precision, type_.scale)
|
||||
else:
|
||||
return "FLOAT"
|
||||
|
||||
def visit_BOOLEAN(self, type_):
|
||||
return "BOOLEAN"
|
||||
|
||||
def visit_BLOB(self, type_):
|
||||
return "BLOB"
|
||||
|
||||
|
||||
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."""
|
||||
|
||||
name = 'drizzle'
|
||||
|
||||
_supports_cast = True
|
||||
supports_sequences = False
|
||||
supports_native_boolean = True
|
||||
supports_views = False
|
||||
|
||||
|
||||
default_paramstyle = 'format'
|
||||
colspecs = colspecs
|
||||
|
||||
statement_compiler = DrizzleCompiler
|
||||
ddl_compiler = DrizzleDDLCompiler
|
||||
type_compiler = DrizzleTypeCompiler
|
||||
ischema_names = ischema_names
|
||||
preparer = DrizzleIdentifierPreparer
|
||||
|
||||
def on_connect(self):
|
||||
"""Force autocommit - Drizzle Bug#707842 doesn't set this
|
||||
properly"""
|
||||
def connect(conn):
|
||||
conn.autocommit(False)
|
||||
return connect
|
||||
|
||||
def do_commit(self, connection):
|
||||
"""Execute a COMMIT."""
|
||||
|
||||
connection.commit()
|
||||
|
||||
def do_rollback(self, connection):
|
||||
"""Execute a ROLLBACK."""
|
||||
|
||||
connection.rollback()
|
||||
|
||||
@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:
|
||||
current_schema = self.default_schema_name
|
||||
|
||||
charset = 'utf8'
|
||||
rp = connection.execute("SHOW TABLES FROM %s" %
|
||||
self.identifier_preparer.quote_identifier(current_schema))
|
||||
return [row[0] for row in self._compat_fetchall(rp, charset=charset)]
|
||||
|
||||
@reflection.cache
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
raise NotImplementedError
|
||||
|
||||
def _detect_casing(self, connection):
|
||||
"""Sniff out identifier case sensitivity.
|
||||
|
||||
Cached per-connection. This value can not change without a server
|
||||
restart.
|
||||
|
||||
"""
|
||||
return 0
|
||||
|
||||
def _detect_collations(self, connection):
|
||||
"""Pull the active COLLATIONS list from the server.
|
||||
|
||||
Cached per-connection.
|
||||
"""
|
||||
|
||||
collations = {}
|
||||
charset = self._connection_charset
|
||||
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
|
||||
|
||||
def _detect_ansiquotes(self, connection):
|
||||
"""Detect and adjust for the ANSI_QUOTES sql mode."""
|
||||
|
||||
self._server_ansiquotes = False
|
||||
|
||||
self._backslash_escapes = False
|
||||
|
||||
log.class_logger(DrizzleDialect)
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
"""Support for the Drizzle database via the Drizzle-python adapter.
|
||||
|
||||
Drizzle-Python is available at:
|
||||
|
||||
http://sourceforge.net/projects/mysql-python
|
||||
|
||||
At least version 1.2.1 or 1.2.2 should be used.
|
||||
|
||||
Connecting
|
||||
-----------
|
||||
|
||||
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.connectors.mysqldb import (
|
||||
MySQLDBExecutionContext,
|
||||
MySQLDBCompiler,
|
||||
MySQLDBIdentifierPreparer,
|
||||
MySQLDBConnector
|
||||
)
|
||||
|
||||
class DrizzleExecutionContext_mysqldb(
|
||||
MySQLDBExecutionContext,
|
||||
DrizzleExecutionContext):
|
||||
pass
|
||||
|
||||
|
||||
class DrizzleCompiler_mysqldb(MySQLDBCompiler, DrizzleCompiler):
|
||||
pass
|
||||
|
||||
|
||||
class DrizzleIdentifierPreparer_mysqldb(
|
||||
MySQLDBIdentifierPreparer,
|
||||
DrizzleIdentifierPreparer):
|
||||
pass
|
||||
|
||||
class DrizzleDialect_mysqldb(MySQLDBConnector, DrizzleDialect):
|
||||
execution_ctx_cls = DrizzleExecutionContext_mysqldb
|
||||
statement_compiler = DrizzleCompiler_mysqldb
|
||||
preparer = DrizzleIdentifierPreparer_mysqldb
|
||||
|
||||
def _detect_charset(self, connection):
|
||||
"""Sniff out the character set in use for connection results."""
|
||||
return 'utf8'
|
||||
|
||||
|
||||
dialect = DrizzleDialect_mysqldb
|
||||
@@ -1,5 +1,5 @@
|
||||
# firebird/__init__.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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 @@
|
||||
# firebird/base.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -244,6 +244,10 @@ class FBCompiler(sql.compiler.SQLCompiler):
|
||||
visit_char_length_func = visit_length_func
|
||||
|
||||
def function_argspec(self, func, **kw):
|
||||
# TODO: this probably will need to be
|
||||
# narrowed to a fixed list, some no-arg functions
|
||||
# may require parens - see similar example in the oracle
|
||||
# dialect
|
||||
if func.clauses is not None and len(func.clauses):
|
||||
return self.process(func.clause_expr)
|
||||
else:
|
||||
@@ -263,9 +267,9 @@ class FBCompiler(sql.compiler.SQLCompiler):
|
||||
|
||||
result = ""
|
||||
if select._limit:
|
||||
result += "FIRST %d " % select._limit
|
||||
result += "FIRST %s " % self.process(sql.literal(select._limit))
|
||||
if select._offset:
|
||||
result +="SKIP %d " % select._offset
|
||||
result +="SKIP %s " % self.process(sql.literal(select._offset))
|
||||
if select._distinct:
|
||||
result += "DISTINCT "
|
||||
return result
|
||||
@@ -331,12 +335,13 @@ class FBIdentifierPreparer(sql.compiler.IdentifierPreparer):
|
||||
|
||||
|
||||
class FBExecutionContext(default.DefaultExecutionContext):
|
||||
def fire_sequence(self, seq):
|
||||
def fire_sequence(self, seq, type_):
|
||||
"""Get the next value from the sequence using ``gen_id()``."""
|
||||
|
||||
return self._execute_scalar(
|
||||
"SELECT gen_id(%s, 1) FROM rdb$database" %
|
||||
self.dialect.identifier_preparer.format_sequence(seq)
|
||||
self.dialect.identifier_preparer.format_sequence(seq),
|
||||
type_
|
||||
)
|
||||
|
||||
|
||||
@@ -357,7 +362,6 @@ class FBDialect(default.DefaultDialect):
|
||||
requires_name_normalize = True
|
||||
supports_empty_insert = False
|
||||
|
||||
|
||||
statement_compiler = FBCompiler
|
||||
ddl_compiler = FBDDLCompiler
|
||||
preparer = FBIdentifierPreparer
|
||||
@@ -374,7 +378,13 @@ class FBDialect(default.DefaultDialect):
|
||||
|
||||
def initialize(self, connection):
|
||||
super(FBDialect, self).initialize(connection)
|
||||
self._version_two = self.server_version_info > (2, )
|
||||
self._version_two = ('firebird' in self.server_version_info and \
|
||||
self.server_version_info >= (2, )
|
||||
) or \
|
||||
('interbase' in self.server_version_info and \
|
||||
self.server_version_info >= (6, )
|
||||
)
|
||||
|
||||
if not self._version_two:
|
||||
# TODO: whatever other pre < 2.0 stuff goes here
|
||||
self.ischema_names = ischema_names.copy()
|
||||
@@ -382,8 +392,9 @@ class FBDialect(default.DefaultDialect):
|
||||
self.colspecs = {
|
||||
sqltypes.DateTime: sqltypes.DATE
|
||||
}
|
||||
else:
|
||||
self.implicit_returning = True
|
||||
|
||||
self.implicit_returning = self._version_two and \
|
||||
self.__dict__.get('implicit_returning', True)
|
||||
|
||||
def normalize_name(self, name):
|
||||
# Remove trailing spaces: FB uses a CHAR() type,
|
||||
@@ -509,7 +520,7 @@ class FBDialect(default.DefaultDialect):
|
||||
def get_columns(self, connection, table_name, schema=None, **kw):
|
||||
# Query to extract the details of all the fields of the given table
|
||||
tblqry = """
|
||||
SELECT DISTINCT r.rdb$field_name AS fname,
|
||||
SELECT r.rdb$field_name AS fname,
|
||||
r.rdb$null_flag AS null_flag,
|
||||
t.rdb$type_name AS ftype,
|
||||
f.rdb$field_sub_type AS stype,
|
||||
@@ -585,7 +596,8 @@ class FBDialect(default.DefaultDialect):
|
||||
'name' : name,
|
||||
'type' : coltype,
|
||||
'nullable' : not bool(row['null_flag']),
|
||||
'default' : defvalue
|
||||
'default' : defvalue,
|
||||
'autoincrement':defvalue is None
|
||||
}
|
||||
|
||||
if orig_colname.lower() == orig_colname:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# firebird/kinterbasdb.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -48,7 +48,9 @@ __ http://kinterbasdb.sourceforge.net/dist_docs/usage.html#special_issue_concurr
|
||||
from sqlalchemy.dialects.firebird.base import FBDialect, \
|
||||
FBCompiler, FBExecutionContext
|
||||
from sqlalchemy import util, types as sqltypes
|
||||
import decimal
|
||||
from sqlalchemy.util.compat import decimal
|
||||
from re import match
|
||||
|
||||
|
||||
class _FBNumeric_kinterbasdb(sqltypes.Numeric):
|
||||
def bind_processor(self, dialect):
|
||||
@@ -133,20 +135,25 @@ 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. This is more than enough for our purposes,
|
||||
# as this is mainly (only?) used by the testsuite.
|
||||
|
||||
from re import match
|
||||
# Interbase signature.
|
||||
|
||||
fbconn = connection.connection
|
||||
version = fbconn.server_version
|
||||
m = match('\w+-V(\d+)\.(\d+)\.(\d+)\.(\d+) \w+ (\d+)\.(\d+)', version)
|
||||
|
||||
return self._parse_version_info(version)
|
||||
|
||||
def _parse_version_info(self, version):
|
||||
m = match('\w+-V(\d+)\.(\d+)\.(\d+)\.(\d+)( \w+ (\d+)\.(\d+))?', version)
|
||||
if not m:
|
||||
raise AssertionError(
|
||||
"Could not determine version from string '%s'" % version)
|
||||
return tuple([int(x) for x in m.group(5, 6, 4)])
|
||||
|
||||
def is_disconnect(self, e):
|
||||
if m.group(5) != None:
|
||||
return tuple([int(x) for x in m.group(6, 7, 4)] + ['firebird'])
|
||||
else:
|
||||
return tuple([int(x) for x in m.group(1, 2, 3)] + ['interbase'])
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, (self.dbapi.OperationalError,
|
||||
self.dbapi.ProgrammingError)):
|
||||
msg = str(e)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# informix/__init__.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# coding: gbk
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -20,6 +20,124 @@ from sqlalchemy.sql import compiler, text
|
||||
from sqlalchemy.engine import default, reflection
|
||||
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",
|
||||
"array", "as", "asc", "ascii", "asin", "at", "atan", "atan2", "attach",
|
||||
"attributes", "audit", "authentication", "authid", "authorization",
|
||||
"authorized", "auto", "autofree", "auto_reprepare", "auto_stat_mode",
|
||||
"avg", "avoid_execute", "avoid_fact", "avoid_full", "avoid_hash",
|
||||
"avoid_index", "avoid_index_sj", "avoid_multi_index", "avoid_nl",
|
||||
"avoid_star_join", "avoid_subqf", "based", "before", "begin",
|
||||
"between", "bigint", "bigserial", "binary", "bitand", "bitandnot",
|
||||
"bitnot", "bitor", "bitxor", "blob", "blobdir", "boolean", "both",
|
||||
"bound_impl_pdq", "buffered", "builtin", "by", "byte", "cache", "call",
|
||||
"cannothash", "cardinality", "cascade", "case", "cast", "ceil", "char",
|
||||
"character", "character_length", "char_length", "check", "class",
|
||||
"class_origin", "client", "clob", "clobdir", "close", "cluster",
|
||||
"clustersize", "cobol", "codeset", "collation", "collection",
|
||||
"column", "columns", "commit", "committed", "commutator", "component",
|
||||
"components", "concat", "concurrent", "connect", "connection",
|
||||
"connection_name", "connect_by_iscycle", "connect_by_isleaf",
|
||||
"connect_by_rootconst", "constraint", "constraints", "constructor",
|
||||
"context", "continue", "copy", "cos", "costfunc", "count", "crcols",
|
||||
"create", "cross", "current", "current_role", "currval", "cursor",
|
||||
"cycle", "database", "datafiles", "dataskip", "date", "datetime",
|
||||
"day", "dba", "dbdate", "dbinfo", "dbpassword", "dbsecadm",
|
||||
"dbservername", "deallocate", "debug", "debugmode", "debug_env", "dec",
|
||||
"decimal", "declare", "decode", "decrypt_binary", "decrypt_char",
|
||||
"dec_t", "default", "default_role", "deferred", "deferred_prepare",
|
||||
"define", "delay", "delete", "deleting", "delimited", "delimiter",
|
||||
"deluxe", "desc", "describe", "descriptor", "detach", "diagnostics",
|
||||
"directives", "dirty", "disable", "disabled", "disconnect", "disk",
|
||||
"distinct", "distributebinary", "distributesreferences",
|
||||
"distributions", "document", "domain", "donotdistribute", "dormant",
|
||||
"double", "drop", "dtime_t", "each", "elif", "else", "enabled",
|
||||
"encryption", "encrypt_aes", "encrypt_tdes", "end", "enum",
|
||||
"environment", "error", "escape", "exception", "exclusive", "exec",
|
||||
"execute", "executeanywhere", "exemption", "exists", "exit", "exp",
|
||||
"explain", "explicit", "express", "expression", "extdirectives",
|
||||
"extend", "extent", "external", "fact", "false", "far", "fetch",
|
||||
"file", "filetoblob", "filetoclob", "fillfactor", "filtering", "first",
|
||||
"first_rows", "fixchar", "fixed", "float", "floor", "flush", "for",
|
||||
"force", "forced", "force_ddl_exec", "foreach", "foreign", "format",
|
||||
"format_units", "fortran", "found", "fraction", "fragment",
|
||||
"fragments", "free", "from", "full", "function", "general", "get",
|
||||
"gethint", "global", "go", "goto", "grant", "greaterthan",
|
||||
"greaterthanorequal", "group", "handlesnulls", "hash", "having", "hdr",
|
||||
"hex", "high", "hint", "hold", "home", "hour", "idslbacreadarray",
|
||||
"idslbacreadset", "idslbacreadtree", "idslbacrules",
|
||||
"idslbacwritearray", "idslbacwriteset", "idslbacwritetree",
|
||||
"idssecuritylabel", "if", "ifx_auto_reprepare", "ifx_batchedread_table",
|
||||
"ifx_int8_t", "ifx_lo_create_spec_t", "ifx_lo_stat_t", "immediate",
|
||||
"implicit", "implicit_pdq", "in", "inactive", "increment", "index",
|
||||
"indexes", "index_all", "index_sj", "indicator", "informix", "init",
|
||||
"initcap", "inline", "inner", "inout", "insert", "inserting", "instead",
|
||||
"int", "int8", "integ", "integer", "internal", "internallength",
|
||||
"interval", "into", "intrvl_t", "is", "iscanonical", "isolation",
|
||||
"item", "iterator", "java", "join", "keep", "key", "label", "labeleq",
|
||||
"labelge", "labelglb", "labelgt", "labelle", "labellt", "labellub",
|
||||
"labeltostring", "language", "last", "last_day", "leading", "left",
|
||||
"length", "lessthan", "lessthanorequal", "let", "level", "like",
|
||||
"limit", "list", "listing", "load", "local", "locator", "lock", "locks",
|
||||
"locopy", "loc_t", "log", "log10", "logn", "long", "loop", "lotofile",
|
||||
"low", "lower", "lpad", "ltrim", "lvarchar", "matched", "matches",
|
||||
"max", "maxerrors", "maxlen", "maxvalue", "mdy", "median", "medium",
|
||||
"memory", "memory_resident", "merge", "message_length", "message_text",
|
||||
"middle", "min", "minute", "minvalue", "mod", "mode", "moderate",
|
||||
"modify", "module", "money", "month", "months_between", "mounting",
|
||||
"multiset", "multi_index", "name", "nchar", "negator", "new", "next",
|
||||
"nextval", "next_day", "no", "nocache", "nocycle", "nomaxvalue",
|
||||
"nomigrate", "nominvalue", "none", "non_dim", "non_resident", "noorder",
|
||||
"normal", "not", "notemplatearg", "notequal", "null", "nullif",
|
||||
"numeric", "numrows", "numtodsinterval", "numtoyminterval", "nvarchar",
|
||||
"nvl", "octet_length", "of", "off", "old", "on", "online", "only",
|
||||
"opaque", "opclass", "open", "optcompind", "optical", "optimization",
|
||||
"option", "or", "order", "ordered", "out", "outer", "output",
|
||||
"override", "page", "parallelizable", "parameter", "partition",
|
||||
"pascal", "passedbyvalue", "password", "pdqpriority", "percaltl_cos",
|
||||
"pipe", "pli", "pload", "policy", "pow", "power", "precision",
|
||||
"prepare", "previous", "primary", "prior", "private", "privileges",
|
||||
"procedure", "properties", "public", "put", "raise", "range", "raw",
|
||||
"read", "real", "recordend", "references", "referencing", "register",
|
||||
"rejectfile", "relative", "release", "remainder", "rename",
|
||||
"reoptimization", "repeatable", "replace", "replication", "reserve",
|
||||
"resolution", "resource", "restart", "restrict", "resume", "retain",
|
||||
"retainupdatelocks", "return", "returned_sqlstate", "returning",
|
||||
"returns", "reuse", "revoke", "right", "robin", "role", "rollback",
|
||||
"rollforward", "root", "round", "routine", "row", "rowid", "rowids",
|
||||
"rows", "row_count", "rpad", "rtrim", "rule", "sameas", "samples",
|
||||
"sampling", "save", "savepoint", "schema", "scroll", "seclabel_by_comp",
|
||||
"seclabel_by_name", "seclabel_to_char", "second", "secondary",
|
||||
"section", "secured", "security", "selconst", "select", "selecting",
|
||||
"selfunc", "selfuncargs", "sequence", "serial", "serial8",
|
||||
"serializable", "serveruuid", "server_name", "session", "set",
|
||||
"setsessionauth", "share", "short", "siblings", "signed", "sin",
|
||||
"sitename", "size", "skall", "skinhibit", "skip", "skshow",
|
||||
"smallfloat", "smallint", "some", "specific", "sql", "sqlcode",
|
||||
"sqlcontext", "sqlerror", "sqlstate", "sqlwarning", "sqrt",
|
||||
"stability", "stack", "standard", "start", "star_join", "statchange",
|
||||
"statement", "static", "statistics", "statlevel", "status", "stdev",
|
||||
"step", "stop", "storage", "store", "strategies", "string",
|
||||
"stringtolabel", "struct", "style", "subclass_origin", "substr",
|
||||
"substring", "sum", "support", "sync", "synonym", "sysdate",
|
||||
"sysdbclose", "sysdbopen", "system", "sys_connect_by_path", "table",
|
||||
"tables", "tan", "task", "temp", "template", "test", "text", "then",
|
||||
"time", "timeout", "to", "today", "to_char", "to_date",
|
||||
"to_dsinterval", "to_number", "to_yminterval", "trace", "trailing",
|
||||
"transaction", "transition", "tree", "trigger", "triggers", "trim",
|
||||
"true", "trunc", "truncate", "trusted", "type", "typedef", "typeid",
|
||||
"typename", "typeof", "uid", "uncommitted", "under", "union",
|
||||
"unique", "units", "unknown", "unload", "unlock", "unsigned",
|
||||
"update", "updating", "upon", "upper", "usage", "use",
|
||||
"uselastcommitted", "user", "use_hash", "use_nl", "use_subqf",
|
||||
"using", "value", "values", "var", "varchar", "variable", "variance",
|
||||
"variant", "varying", "vercols", "view", "violations", "void",
|
||||
"volatile", "wait", "warning", "weekday", "when", "whenever", "where",
|
||||
"while", "with", "without", "work", "write", "writedown", "writeup",
|
||||
"xadatasource", "xid", "xload", "xunload", "year"
|
||||
])
|
||||
|
||||
class InfoDateTime(sqltypes.DateTime):
|
||||
def bind_processor(self, dialect):
|
||||
@@ -213,6 +331,10 @@ class InfoDDLCompiler(compiler.DDLCompiler):
|
||||
text += "CONSTRAINT %s " % self.preparer.format_constraint(constraint)
|
||||
return text
|
||||
|
||||
class InformixIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
|
||||
reserved_words = RESERVED_WORDS
|
||||
|
||||
|
||||
class InformixDialect(default.DefaultDialect):
|
||||
name = 'informix'
|
||||
@@ -224,6 +346,7 @@ class InformixDialect(default.DefaultDialect):
|
||||
ddl_compiler = InfoDDLCompiler
|
||||
colspecs = colspecs
|
||||
ischema_names = ischema_names
|
||||
preparer = InformixIdentifierPreparer
|
||||
default_paramstyle = 'qmark'
|
||||
|
||||
def __init__(self, has_transactions=True, *args, **kwargs):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# informix/informixdb.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -62,7 +62,7 @@ class InformixDialect_informixdb(InformixDialect):
|
||||
v = VERSION_RE.split(connection.connection.dbms_version)
|
||||
return (int(v[1]), int(v[2]), v[3])
|
||||
|
||||
def is_disconnect(self, e):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, self.dbapi.OperationalError):
|
||||
return 'closed the connection' in str(e) \
|
||||
or 'connection not open' in str(e)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# maxdb/__init__.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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,14 @@
|
||||
# maxdb/base.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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.
|
||||
This dialect is *not* ported to SQLAlchemy 0.6 or 0.7.
|
||||
|
||||
This dialect is *not* tested on SQLAlchemy 0.6.
|
||||
This dialect is *not* tested on SQLAlchemy 0.6 or 0.7.
|
||||
|
||||
Overview
|
||||
--------
|
||||
@@ -31,8 +31,6 @@ use upper case for DB-API.
|
||||
Implementation Notes
|
||||
--------------------
|
||||
|
||||
Also check the DatabaseNotes page on the wiki for detailed information.
|
||||
|
||||
With the 7.6.00.37 driver and Python 2.5, it seems that all DB-API
|
||||
generated exceptions are broken and can cause Python to crash.
|
||||
|
||||
@@ -58,6 +56,62 @@ required components such as an Max-aware 'old oracle style' join compiler
|
||||
integration- email the devel list if you're interested in working on
|
||||
this.
|
||||
|
||||
Versions tested: 7.6.03.07 and 7.6.00.37, native Python DB-API
|
||||
|
||||
* MaxDB has severe limitations on OUTER JOINs, which are essential to ORM
|
||||
eager loading. And rather than raise an error if a SELECT can't be serviced,
|
||||
the database simply returns incorrect results.
|
||||
* Version 7.6.03.07 seems to JOIN properly, however the docs do not show the
|
||||
OUTER restrictions being lifted (as of this writing), and no changelog is
|
||||
available to confirm either. If you are using a different server version and
|
||||
your tasks require the ORM or any semi-advanced SQL through the SQL layer,
|
||||
running the SQLAlchemy test suite against your database is HIGHLY
|
||||
recommended before you begin.
|
||||
* Version 7.6.00.37 is LHS/RHS sensitive in `FROM lhs LEFT OUTER JOIN rhs ON
|
||||
lhs.col=rhs.col` vs `rhs.col=lhs.col`!
|
||||
* Version 7.6.00.37 is confused by `SELECT DISTINCT col as alias FROM t ORDER
|
||||
BY col` - these aliased, DISTINCT, ordered queries need to be re-written to
|
||||
order by the alias name.
|
||||
* Version 7.6.x supports creating a SAVEPOINT but not its RELEASE.
|
||||
* MaxDB supports autoincrement-style columns (DEFAULT SERIAL) and independent
|
||||
sequences. When including a DEFAULT SERIAL column in an insert, 0 needs to
|
||||
be inserted rather than NULL to generate a value.
|
||||
* MaxDB supports ANSI and "old Oracle style" theta joins with (+) outer join
|
||||
indicators.
|
||||
* The SQLAlchemy dialect is schema-aware and probably won't function correctly
|
||||
on server versions (pre-7.6?). Support for schema-less server versions could
|
||||
be added if there's call.
|
||||
* ORDER BY is not supported in subqueries. LIMIT is not supported in
|
||||
subqueries. In 7.6.00.37, TOP does work in subqueries, but without limit not
|
||||
so useful. OFFSET does not work in 7.6 despite being in the docs. Row number
|
||||
tricks in WHERE via ROWNO may be possible but it only seems to allow
|
||||
less-than comparison!
|
||||
* Version 7.6.03.07 can't LIMIT if a derived table is in FROM: `SELECT * FROM
|
||||
(SELECT * FROM a) LIMIT 2`
|
||||
* MaxDB does not support sql's CAST and can only usefullly cast two types.
|
||||
There isn't much implicit type conversion, so be precise when creating
|
||||
`PassiveDefaults` in DDL generation: `'3'` and `3` aren't the same.
|
||||
|
||||
sapdb.dbapi
|
||||
^^^^^^^^^^^
|
||||
|
||||
* As of 2007-10-22 the Python 2.4 and 2.5 compatible versions of the DB-API
|
||||
are no longer available. A forum posting at SAP states that the Python
|
||||
driver will be available again "in the future". The last release from MySQL
|
||||
AB works if you can find it.
|
||||
* sequence.NEXTVAL skips every other value!
|
||||
* No rowcount for executemany()
|
||||
* If an INSERT into a table with a DEFAULT SERIAL column inserts the results
|
||||
of a function `INSERT INTO t VALUES (LENGTH('foo'))`, the cursor won't have
|
||||
the serial id. It needs to be manually yanked from tablename.CURRVAL.
|
||||
* Super-duper picky about where bind params can be placed. Not smart about
|
||||
converting Python types for some functions, such as `MOD(5, ?)`.
|
||||
* LONG (text, binary) values in result sets are read-once. The dialect uses a
|
||||
caching RowProxy when these types are present.
|
||||
* Connection objects seem like they want to be either `close()`d or garbage
|
||||
collected, but not both. There's a warning issued but it seems harmless.
|
||||
|
||||
|
||||
"""
|
||||
import datetime, itertools, re
|
||||
|
||||
@@ -117,15 +171,13 @@ class _StringType(sqltypes.String):
|
||||
class MaxString(_StringType):
|
||||
_type = 'VARCHAR'
|
||||
|
||||
def __init__(self, *a, **kw):
|
||||
super(MaxString, self).__init__(*a, **kw)
|
||||
|
||||
|
||||
class MaxUnicode(_StringType):
|
||||
_type = 'VARCHAR'
|
||||
|
||||
def __init__(self, length=None, **kw):
|
||||
super(MaxUnicode, self).__init__(length=length, encoding='unicode')
|
||||
kw['encoding'] = 'unicode'
|
||||
super(MaxUnicode, self).__init__(length=length, **kw)
|
||||
|
||||
|
||||
class MaxChar(_StringType):
|
||||
@@ -135,8 +187,8 @@ class MaxChar(_StringType):
|
||||
class MaxText(_StringType):
|
||||
_type = 'LONG'
|
||||
|
||||
def __init__(self, *a, **kw):
|
||||
super(MaxText, self).__init__(*a, **kw)
|
||||
def __init__(self, length=None, **kw):
|
||||
super(MaxText, self).__init__(length, **kw)
|
||||
|
||||
def get_col_spec(self):
|
||||
spec = 'LONG'
|
||||
@@ -583,7 +635,7 @@ class MaxDBCompiler(compiler.SQLCompiler):
|
||||
# LIMIT. Right? Other dialects seem to get away with
|
||||
# dropping order.
|
||||
if select._limit:
|
||||
raise exc.InvalidRequestError(
|
||||
raise exc.CompileError(
|
||||
"MaxDB does not support ORDER BY in subqueries")
|
||||
else:
|
||||
return ""
|
||||
@@ -604,6 +656,7 @@ class MaxDBCompiler(compiler.SQLCompiler):
|
||||
def limit_clause(self, select):
|
||||
# The docs say offsets are supported with LIMIT. But they're not.
|
||||
# TODO: maybe emulate by adding a ROWNO/ROWNUM predicate?
|
||||
# TODO: does MaxDB support bind params for LIMIT / TOP ?
|
||||
if self.is_subquery():
|
||||
# sub queries need TOP
|
||||
return ''
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# maxdb/sapdb.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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/adodbapi.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -8,6 +8,7 @@
|
||||
The adodbapi dialect is not implemented for 0.6 at this time.
|
||||
|
||||
"""
|
||||
import datetime
|
||||
from sqlalchemy import types as sqltypes, util
|
||||
from sqlalchemy.dialects.mssql.base import MSDateTime, MSDialect
|
||||
import sys
|
||||
@@ -61,7 +62,7 @@ class MSDialect_adodbapi(MSDialect):
|
||||
connectors.append("Integrated Security=SSPI")
|
||||
return [[";".join (connectors)], {}]
|
||||
|
||||
def is_disconnect(self, e):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
return isinstance(e, self.dbapi.adodbapi.DatabaseError) and \
|
||||
"'connection failure'" in str(e)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/base.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -56,7 +56,7 @@ MSNVarchar, MSText, and MSNText. For example::
|
||||
from sqlalchemy.dialects.mssql import VARCHAR
|
||||
Column('login', VARCHAR(32, collation='Latin1_General_CI_AS'))
|
||||
|
||||
When such a column is associated with a :class:`Table`, the
|
||||
When such a column is associated with a :class:`.Table`, the
|
||||
CREATE TABLE statement for this column will yield::
|
||||
|
||||
login VARCHAR(32) COLLATE Latin1_General_CI_AS NULL
|
||||
@@ -130,17 +130,57 @@ which has triggers::
|
||||
# ...,
|
||||
implicit_returning=False
|
||||
)
|
||||
|
||||
|
||||
Declarative form::
|
||||
|
||||
class MyClass(Base):
|
||||
# ...
|
||||
__table_args__ = {'implicit_returning':False}
|
||||
|
||||
|
||||
|
||||
|
||||
This option can also be specified engine-wide using the
|
||||
``implicit_returning=False`` argument on :func:`.create_engine`.
|
||||
|
||||
Enabling Snapshot Isolation
|
||||
---------------------------
|
||||
|
||||
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
|
||||
following ALTER DATABASE commands executed at the SQL prompt::
|
||||
|
||||
ALTER DATABASE MyDatabase SET ALLOW_SNAPSHOT_ISOLATION ON
|
||||
|
||||
ALTER DATABASE MyDatabase SET READ_COMMITTED_SNAPSHOT ON
|
||||
|
||||
Background on SQL Server snapshot isolation is available at
|
||||
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.
|
||||
|
||||
For the time being, the existing behavior prevents a comparison
|
||||
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::
|
||||
|
||||
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)
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
@@ -149,20 +189,19 @@ Known Issues
|
||||
SQL Server 2005
|
||||
|
||||
"""
|
||||
import datetime, decimal, inspect, operator, sys, re
|
||||
import itertools
|
||||
import datetime, operator, re
|
||||
|
||||
from sqlalchemy import sql, schema as sa_schema, exc, util
|
||||
from sqlalchemy.sql import select, compiler, expression, \
|
||||
operators as sql_operators, \
|
||||
functions as sql_functions, util as sql_util
|
||||
util as sql_util, cast
|
||||
from sqlalchemy.engine import default, base, reflection
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy import processors
|
||||
from sqlalchemy.types import INTEGER, BIGINT, SMALLINT, DECIMAL, NUMERIC, \
|
||||
FLOAT, TIMESTAMP, DATETIME, DATE, BINARY,\
|
||||
VARBINARY, BLOB
|
||||
|
||||
|
||||
from sqlalchemy.dialects.mssql import information_schema as ischema
|
||||
|
||||
MS_2008_VERSION = (10,)
|
||||
@@ -200,14 +239,13 @@ RESERVED_WORDS = set(
|
||||
'writetext',
|
||||
])
|
||||
|
||||
|
||||
class REAL(sqltypes.Float):
|
||||
"""A type for ``real`` numbers."""
|
||||
|
||||
class REAL(sqltypes.REAL):
|
||||
__visit_name__ = 'REAL'
|
||||
|
||||
def __init__(self):
|
||||
super(REAL, self).__init__(precision=24)
|
||||
def __init__(self, **kw):
|
||||
# REAL is a synonym for FLOAT(24) on SQL server
|
||||
kw['precision'] = 24
|
||||
super(REAL, self).__init__(**kw)
|
||||
|
||||
class TINYINT(sqltypes.Integer):
|
||||
__visit_name__ = 'TINYINT'
|
||||
@@ -258,7 +296,7 @@ class TIME(sqltypes.TIME):
|
||||
return value
|
||||
return process
|
||||
|
||||
_reg = re.compile(r"(\d+):(\d+):(\d+)(?:\.(\d+))?")
|
||||
_reg = re.compile(r"(\d+):(\d+):(\d+)(?:\.(\d{0,6}))?")
|
||||
def result_processor(self, dialect, coltype):
|
||||
def process(value):
|
||||
if isinstance(value, datetime.datetime):
|
||||
@@ -289,7 +327,8 @@ class SMALLDATETIME(_DateTimeBase, sqltypes.DateTime):
|
||||
class DATETIME2(_DateTimeBase, sqltypes.DateTime):
|
||||
__visit_name__ = 'DATETIME2'
|
||||
|
||||
def __init__(self, precision=None, **kwargs):
|
||||
def __init__(self, precision=None, **kw):
|
||||
super(DATETIME2, self).__init__(**kw)
|
||||
self.precision = precision
|
||||
|
||||
|
||||
@@ -309,16 +348,15 @@ class _StringType(object):
|
||||
class TEXT(_StringType, sqltypes.TEXT):
|
||||
"""MSSQL TEXT type, for variable-length text up to 2^31 characters."""
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
def __init__(self, length=None, collation=None, **kw):
|
||||
"""Construct a TEXT.
|
||||
|
||||
:param collation: Optional, a column-level collation for this string
|
||||
value. Accepts a Windows Collation Name or a SQL Collation Name.
|
||||
|
||||
"""
|
||||
collation = kw.pop('collation', None)
|
||||
_StringType.__init__(self, collation)
|
||||
sqltypes.Text.__init__(self, *args, **kw)
|
||||
sqltypes.Text.__init__(self, length, **kw)
|
||||
|
||||
class NTEXT(_StringType, sqltypes.UnicodeText):
|
||||
"""MSSQL NTEXT type, for variable-length unicode text up to 2^30
|
||||
@@ -326,24 +364,22 @@ class NTEXT(_StringType, sqltypes.UnicodeText):
|
||||
|
||||
__visit_name__ = 'NTEXT'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, length=None, collation=None, **kw):
|
||||
"""Construct a NTEXT.
|
||||
|
||||
:param collation: Optional, a column-level collation for this string
|
||||
value. Accepts a Windows Collation Name or a SQL Collation Name.
|
||||
|
||||
"""
|
||||
collation = kwargs.pop('collation', None)
|
||||
_StringType.__init__(self, collation)
|
||||
length = kwargs.pop('length', None)
|
||||
sqltypes.UnicodeText.__init__(self, length, **kwargs)
|
||||
sqltypes.UnicodeText.__init__(self, length, **kw)
|
||||
|
||||
|
||||
class VARCHAR(_StringType, sqltypes.VARCHAR):
|
||||
"""MSSQL VARCHAR type, for variable-length non-Unicode data with a maximum
|
||||
of 8,000 characters."""
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
def __init__(self, length=None, collation=None, **kw):
|
||||
"""Construct a VARCHAR.
|
||||
|
||||
:param length: Optinal, maximum data length, in characters.
|
||||
@@ -364,16 +400,15 @@ class VARCHAR(_StringType, sqltypes.VARCHAR):
|
||||
value. Accepts a Windows Collation Name or a SQL Collation Name.
|
||||
|
||||
"""
|
||||
collation = kw.pop('collation', None)
|
||||
_StringType.__init__(self, collation)
|
||||
sqltypes.VARCHAR.__init__(self, *args, **kw)
|
||||
sqltypes.VARCHAR.__init__(self, length, **kw)
|
||||
|
||||
class NVARCHAR(_StringType, sqltypes.NVARCHAR):
|
||||
"""MSSQL NVARCHAR type.
|
||||
|
||||
For variable-length unicode character data up to 4,000 characters."""
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
def __init__(self, length=None, collation=None, **kw):
|
||||
"""Construct a NVARCHAR.
|
||||
|
||||
:param length: Optional, Maximum data length, in characters.
|
||||
@@ -382,15 +417,14 @@ class NVARCHAR(_StringType, sqltypes.NVARCHAR):
|
||||
value. Accepts a Windows Collation Name or a SQL Collation Name.
|
||||
|
||||
"""
|
||||
collation = kw.pop('collation', None)
|
||||
_StringType.__init__(self, collation)
|
||||
sqltypes.NVARCHAR.__init__(self, *args, **kw)
|
||||
sqltypes.NVARCHAR.__init__(self, length, **kw)
|
||||
|
||||
class CHAR(_StringType, sqltypes.CHAR):
|
||||
"""MSSQL CHAR type, for fixed-length non-Unicode data with a maximum
|
||||
of 8,000 characters."""
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
def __init__(self, length=None, collation=None, **kw):
|
||||
"""Construct a CHAR.
|
||||
|
||||
:param length: Optinal, maximum data length, in characters.
|
||||
@@ -411,16 +445,15 @@ class CHAR(_StringType, sqltypes.CHAR):
|
||||
value. Accepts a Windows Collation Name or a SQL Collation Name.
|
||||
|
||||
"""
|
||||
collation = kw.pop('collation', None)
|
||||
_StringType.__init__(self, collation)
|
||||
sqltypes.CHAR.__init__(self, *args, **kw)
|
||||
sqltypes.CHAR.__init__(self, length, **kw)
|
||||
|
||||
class NCHAR(_StringType, sqltypes.NCHAR):
|
||||
"""MSSQL NCHAR type.
|
||||
|
||||
For fixed-length unicode character data up to 4,000 characters."""
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
def __init__(self, length=None, collation=None, **kw):
|
||||
"""Construct an NCHAR.
|
||||
|
||||
:param length: Optional, Maximum data length, in characters.
|
||||
@@ -429,9 +462,8 @@ class NCHAR(_StringType, sqltypes.NCHAR):
|
||||
value. Accepts a Windows Collation Name or a SQL Collation Name.
|
||||
|
||||
"""
|
||||
collation = kw.pop('collation', None)
|
||||
_StringType.__init__(self, collation)
|
||||
sqltypes.NCHAR.__init__(self, *args, **kw)
|
||||
sqltypes.NCHAR.__init__(self, length, **kw)
|
||||
|
||||
class IMAGE(sqltypes.LargeBinary):
|
||||
__visit_name__ = 'IMAGE'
|
||||
@@ -510,7 +542,7 @@ ischema_names = {
|
||||
|
||||
|
||||
class MSTypeCompiler(compiler.GenericTypeCompiler):
|
||||
def _extend(self, spec, type_):
|
||||
def _extend(self, spec, type_, length=None):
|
||||
"""Extend a string-type declaration with standard SQL
|
||||
COLLATE annotations.
|
||||
|
||||
@@ -521,8 +553,11 @@ class MSTypeCompiler(compiler.GenericTypeCompiler):
|
||||
else:
|
||||
collation = None
|
||||
|
||||
if type_.length:
|
||||
spec = spec + "(%d)" % type_.length
|
||||
if not length:
|
||||
length = type_.length
|
||||
|
||||
if length:
|
||||
spec = spec + "(%s)" % length
|
||||
|
||||
return ' '.join([c for c in (spec, collation)
|
||||
if c is not None])
|
||||
@@ -534,9 +569,6 @@ class MSTypeCompiler(compiler.GenericTypeCompiler):
|
||||
else:
|
||||
return "FLOAT(%(precision)s)" % {'precision': precision}
|
||||
|
||||
def visit_REAL(self, type_):
|
||||
return "REAL"
|
||||
|
||||
def visit_TINYINT(self, type_):
|
||||
return "TINYINT"
|
||||
|
||||
@@ -576,7 +608,8 @@ 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_):
|
||||
return self._extend("CHAR", type_)
|
||||
@@ -585,7 +618,8 @@ 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_):
|
||||
if self.dialect.server_version_info < MS_2008_VERSION:
|
||||
@@ -605,6 +639,12 @@ class MSTypeCompiler(compiler.GenericTypeCompiler):
|
||||
def visit_IMAGE(self, type_):
|
||||
return "IMAGE"
|
||||
|
||||
def visit_VARBINARY(self, type_):
|
||||
return self._extend(
|
||||
"VARBINARY",
|
||||
type_,
|
||||
length=type_.length or 'max')
|
||||
|
||||
def visit_boolean(self, type_):
|
||||
return self.visit_BIT(type_)
|
||||
|
||||
@@ -709,8 +749,8 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
})
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MSSQLCompiler, self).__init__(*args, **kwargs)
|
||||
self.tablealiases = {}
|
||||
super(MSSQLCompiler, self).__init__(*args, **kwargs)
|
||||
|
||||
def visit_now_func(self, fn, **kw):
|
||||
return "CURRENT_TIMESTAMP"
|
||||
@@ -736,15 +776,21 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
def get_select_precolumns(self, select):
|
||||
""" MS-SQL puts TOP, it's version of LIMIT here """
|
||||
if select._distinct or select._limit:
|
||||
if select._distinct or select._limit is not None:
|
||||
s = select._distinct and "DISTINCT " or ""
|
||||
|
||||
if select._limit:
|
||||
# ODBC drivers and possibly others
|
||||
# don't support bind params in the SELECT clause on SQL Server.
|
||||
# so have to use literal here.
|
||||
if select._limit is not None:
|
||||
if not select._offset:
|
||||
s += "TOP %s " % (select._limit,)
|
||||
s += "TOP %d " % select._limit
|
||||
return s
|
||||
return compiler.SQLCompiler.get_select_precolumns(self, select)
|
||||
|
||||
def get_from_hint_text(self, table, text):
|
||||
return text
|
||||
|
||||
def limit_clause(self, select):
|
||||
# Limit in mssql is after the select keyword
|
||||
return ""
|
||||
@@ -758,7 +804,7 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
# to use ROW_NUMBER(), an ORDER BY is required.
|
||||
orderby = self.process(select._order_by_clause)
|
||||
if not orderby:
|
||||
raise exc.InvalidRequestError('MSSQL requires an order_by when '
|
||||
raise exc.CompileError('MSSQL requires an order_by when '
|
||||
'using an offset.')
|
||||
|
||||
_offset = select._offset
|
||||
@@ -769,12 +815,12 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
% orderby).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>%d" % _offset)
|
||||
limitselect.append_whereclause(mssql_rn> _offset)
|
||||
if _limit is not None:
|
||||
limitselect.append_whereclause("mssql_rn<=%d" %
|
||||
(_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)
|
||||
@@ -800,7 +846,6 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
def visit_alias(self, alias, **kwargs):
|
||||
# translate for schema-qualified table aliases
|
||||
self.tablealiases[alias.original] = alias
|
||||
kwargs['mssql_aliased'] = alias.original
|
||||
return super(MSSQLCompiler, self).visit_alias(alias, **kwargs)
|
||||
|
||||
@@ -809,6 +854,9 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
return 'DATEPART("%s", %s)' % \
|
||||
(field, self.process(extract.expr, **kw))
|
||||
|
||||
def visit_savepoint(self, savepoint_stmt):
|
||||
return "SAVE TRANSACTION %s" % self.preparer.format_savepoint(savepoint_stmt)
|
||||
|
||||
def visit_rollback_to_savepoint(self, savepoint_stmt):
|
||||
return ("ROLLBACK TRANSACTION %s"
|
||||
% self.preparer.format_savepoint(savepoint_stmt))
|
||||
@@ -866,6 +914,10 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
)
|
||||
):
|
||||
op = binary.operator == operator.eq and "IN" or "NOT IN"
|
||||
util.warn_deprecated("Comparing a scalar select using ``=``/``!=`` will "
|
||||
"no longer produce IN/NOT IN in 0.8. To remove this "
|
||||
"behavior immediately, use the recipe at "
|
||||
"http://www.sqlalchemy.org/docs/07/dialects/mssql.html#scalar-select-comparisons")
|
||||
return self.process(
|
||||
expression._BinaryExpression(binary.left,
|
||||
binary.right, op),
|
||||
@@ -977,7 +1029,7 @@ class MSDDLCompiler(compiler.DDLCompiler):
|
||||
colspec += " NULL"
|
||||
|
||||
if column.table is None:
|
||||
raise exc.InvalidRequestError(
|
||||
raise exc.CompileError(
|
||||
"mssql requires Table-bound columns "
|
||||
"in order to generate DDL")
|
||||
|
||||
@@ -1066,12 +1118,12 @@ class MSDialect(default.DefaultDialect):
|
||||
super(MSDialect, self).__init__(**opts)
|
||||
|
||||
def do_savepoint(self, connection, name):
|
||||
util.warn("Savepoint support in mssql is experimental and "
|
||||
"may lead to data loss.")
|
||||
# give the DBAPI a push
|
||||
connection.execute("IF @@TRANCOUNT = 0 BEGIN TRANSACTION")
|
||||
connection.execute("SAVE TRANSACTION %s" % name)
|
||||
super(MSDialect, self).do_savepoint(connection, name)
|
||||
|
||||
def do_release_savepoint(self, connection, name):
|
||||
# SQL Server does not support RELEASE SAVEPOINT
|
||||
pass
|
||||
|
||||
def initialize(self, connection):
|
||||
@@ -1108,15 +1160,20 @@ class MSDialect(default.DefaultDialect):
|
||||
pass
|
||||
return self.schema_name
|
||||
|
||||
def _unicode_cast(self, column):
|
||||
if self.server_version_info >= MS_2005_VERSION:
|
||||
return cast(column, NVARCHAR(_warn_on_bytestring=False))
|
||||
else:
|
||||
return column
|
||||
|
||||
def has_table(self, connection, tablename, schema=None):
|
||||
current_schema = schema or self.default_schema_name
|
||||
columns = ischema.columns
|
||||
|
||||
whereclause = self._unicode_cast(columns.c.table_name)==tablename
|
||||
if current_schema:
|
||||
whereclause = sql.and_(columns.c.table_name==tablename,
|
||||
whereclause = sql.and_(whereclause,
|
||||
columns.c.table_schema==current_schema)
|
||||
else:
|
||||
whereclause = columns.c.table_name==tablename
|
||||
s = sql.select([columns], whereclause)
|
||||
c = connection.execute(s)
|
||||
return c.first() is not None
|
||||
@@ -1180,7 +1237,10 @@ class MSDialect(default.DefaultDialect):
|
||||
sqltypes.String(convert_unicode=True)),
|
||||
sql.bindparam('schname', current_schema,
|
||||
sqltypes.String(convert_unicode=True))
|
||||
]
|
||||
],
|
||||
typemap = {
|
||||
'name':sqltypes.Unicode()
|
||||
}
|
||||
)
|
||||
)
|
||||
indexes = {}
|
||||
@@ -1206,7 +1266,11 @@ class MSDialect(default.DefaultDialect):
|
||||
sqltypes.String(convert_unicode=True)),
|
||||
sql.bindparam('schname', current_schema,
|
||||
sqltypes.String(convert_unicode=True))
|
||||
]),
|
||||
],
|
||||
typemap = {
|
||||
'name':sqltypes.Unicode()
|
||||
}
|
||||
),
|
||||
)
|
||||
for row in rp:
|
||||
if row['index_id'] in indexes:
|
||||
@@ -1217,14 +1281,25 @@ class MSDialect(default.DefaultDialect):
|
||||
@reflection.cache
|
||||
def get_view_definition(self, connection, viewname, schema=None, **kw):
|
||||
current_schema = schema or self.default_schema_name
|
||||
views = ischema.views
|
||||
s = sql.select([views.c.view_definition],
|
||||
sql.and_(
|
||||
views.c.table_schema == current_schema,
|
||||
views.c.table_name == viewname
|
||||
),
|
||||
|
||||
rp = connection.execute(
|
||||
sql.text(
|
||||
"select definition from sys.sql_modules as mod, "
|
||||
"sys.views as views, "
|
||||
"sys.schemas as sch"
|
||||
" where "
|
||||
"mod.object_id=views.object_id and "
|
||||
"views.schema_id=sch.schema_id and "
|
||||
"views.name=:viewname and sch.name=:schname",
|
||||
bindparams=[
|
||||
sql.bindparam('viewname', viewname,
|
||||
sqltypes.String(convert_unicode=True)),
|
||||
sql.bindparam('schname', current_schema,
|
||||
sqltypes.String(convert_unicode=True))
|
||||
]
|
||||
)
|
||||
)
|
||||
rp = connection.execute(s)
|
||||
|
||||
if rp:
|
||||
view_def = rp.scalar()
|
||||
return view_def
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# mssql/information_schema.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
|
||||
# TODO: should be using the sys. catalog with SQL Server, not information schema
|
||||
|
||||
from sqlalchemy import Table, MetaData, Column, ForeignKey
|
||||
from sqlalchemy import Table, MetaData, Column
|
||||
from sqlalchemy.types import String, Unicode, Integer, TypeDecorator
|
||||
|
||||
ischema = MetaData()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/mxodbc.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -51,15 +51,11 @@ of ``False`` will uncondtionally use string-escaped parameters.
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy import util
|
||||
from sqlalchemy.connectors.mxodbc import MxODBCConnector
|
||||
from sqlalchemy.dialects.mssql.pyodbc import MSExecutionContext_pyodbc
|
||||
from sqlalchemy.dialects.mssql.base import (MSExecutionContext, MSDialect,
|
||||
MSSQLCompiler,
|
||||
from sqlalchemy.dialects.mssql.base import (MSDialect,
|
||||
MSSQLStrictCompiler,
|
||||
_MSDateTime, _MSDate, TIME)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/pymssql.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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,7 +41,6 @@ Please consult the pymssql documentation for further information.
|
||||
from sqlalchemy.dialects.mssql.base import MSDialect
|
||||
from sqlalchemy import types as sqltypes, util, processors
|
||||
import re
|
||||
import decimal
|
||||
|
||||
class _MSNumeric_pymssql(sqltypes.Numeric):
|
||||
def result_processor(self, dialect, type_):
|
||||
@@ -52,7 +51,6 @@ class _MSNumeric_pymssql(sqltypes.Numeric):
|
||||
|
||||
class MSDialect_pymssql(MSDialect):
|
||||
supports_sane_rowcount = False
|
||||
max_identifier_length = 30
|
||||
driver = 'pymssql'
|
||||
|
||||
colspecs = util.update_copy(
|
||||
@@ -96,7 +94,7 @@ class MSDialect_pymssql(MSDialect):
|
||||
opts['host'] = "%s:%s" % (opts['host'], port)
|
||||
return [[], opts]
|
||||
|
||||
def is_disconnect(self, e):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
for msg in (
|
||||
"Error 10054",
|
||||
"Not connected to any MS SQL server",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/pyodbc.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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,27 +35,31 @@ 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 string
|
||||
dynamically created that would appear like::
|
||||
* ``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 that is dynamically created, which also includes the port
|
||||
information using the comma syntax. If your connection string
|
||||
requires the port information to be passed as a ``port`` keyword
|
||||
see the next example. This will create the following connection
|
||||
string::
|
||||
string which includes the port
|
||||
information using the comma syntax. This will create the following
|
||||
connection string::
|
||||
|
||||
DRIVER={SQL Server};Server=host,123;Database=db;UID=user;PWD=pass
|
||||
|
||||
* ``mssql+pyodbc://user:pass@host/db?port=123`` - connects using a connection
|
||||
string that is dynamically created that includes the port
|
||||
string that includes the port
|
||||
information as a separate ``port`` keyword. This will create the
|
||||
following connection string::
|
||||
|
||||
DRIVER={SQL Server};Server=host;Database=db;UID=user;PWD=pass;port=123
|
||||
|
||||
* ``mssql+pyodbc://user:pass@host/db?driver=MyDriver`` - connects using a connection
|
||||
string that includes a custom
|
||||
ODBC driver name. This will create the following connection string::
|
||||
|
||||
DRIVER={MyDriver};Server=host;Database=db;UID=user;PWD=pass
|
||||
|
||||
If you require a connection string that is outside the options
|
||||
presented above, use the ``odbc_connect`` keyword to pass in a
|
||||
urlencoded connection string. What gets passed in will be urldecoded
|
||||
@@ -94,7 +98,12 @@ class _MSNumeric_pyodbc(sqltypes.Numeric):
|
||||
"""
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
super_process = super(_MSNumeric_pyodbc, self).bind_processor(dialect)
|
||||
|
||||
super_process = super(_MSNumeric_pyodbc, self).\
|
||||
bind_processor(dialect)
|
||||
|
||||
if not dialect._need_decimal_fix:
|
||||
return super_process
|
||||
|
||||
def process(value):
|
||||
if self.asdecimal and \
|
||||
@@ -112,31 +121,35 @@ class _MSNumeric_pyodbc(sqltypes.Numeric):
|
||||
return value
|
||||
return process
|
||||
|
||||
# these routines needed for older versions of pyodbc.
|
||||
# as of 2.1.8 this logic is integrated.
|
||||
|
||||
def _small_dec_to_string(self, value):
|
||||
return "%s0.%s%s" % (
|
||||
(value < 0 and '-' or ''),
|
||||
'0' * (abs(value.adjusted()) - 1),
|
||||
"".join([str(nint) for nint in value._int]))
|
||||
"".join([str(nint) for nint in value.as_tuple()[1]]))
|
||||
|
||||
def _large_dec_to_string(self, value):
|
||||
_int = value.as_tuple()[1]
|
||||
if 'E' in str(value):
|
||||
result = "%s%s%s" % (
|
||||
(value < 0 and '-' or ''),
|
||||
"".join([str(s) for s in value._int]),
|
||||
"0" * (value.adjusted() - (len(value._int)-1)))
|
||||
"".join([str(s) for s in _int]),
|
||||
"0" * (value.adjusted() - (len(_int)-1)))
|
||||
else:
|
||||
if (len(value._int) - 1) > value.adjusted():
|
||||
if (len(_int) - 1) > value.adjusted():
|
||||
result = "%s%s.%s" % (
|
||||
(value < 0 and '-' or ''),
|
||||
"".join(
|
||||
[str(s) for s in value._int][0:value.adjusted() + 1]),
|
||||
[str(s) for s in _int][0:value.adjusted() + 1]),
|
||||
"".join(
|
||||
[str(s) for s in value._int][value.adjusted() + 1:]))
|
||||
[str(s) for s in _int][value.adjusted() + 1:]))
|
||||
else:
|
||||
result = "%s%s" % (
|
||||
(value < 0 and '-' or ''),
|
||||
"".join(
|
||||
[str(s) for s in value._int][0:value.adjusted() + 1]))
|
||||
[str(s) for s in _int][0:value.adjusted() + 1]))
|
||||
return result
|
||||
|
||||
|
||||
@@ -206,5 +219,7 @@ class MSDialect_pyodbc(PyODBCConnector, MSDialect):
|
||||
self.description_encoding = description_encoding
|
||||
self.use_scope_identity = self.dbapi and \
|
||||
hasattr(self.dbapi.Cursor, 'nextset')
|
||||
self._need_decimal_fix = self.dbapi and \
|
||||
self._dbapi_version() < (2, 1, 8)
|
||||
|
||||
dialect = MSDialect_pyodbc
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/zxjdbc.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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,11 +1,11 @@
|
||||
# mysql/__init__.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
pyodbc, zxjdbc, mysqlconnector, pymysql
|
||||
|
||||
# default dialect
|
||||
base.dialect = mysqldb.dialect
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/base.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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,6 +68,22 @@ creation option can be specified in this syntax::
|
||||
mysql_charset='utf8'
|
||||
)
|
||||
|
||||
Case Sensitivity and Table Reflection
|
||||
-------------------------------------
|
||||
|
||||
MySQL has inconsistent support for case-sensitive identifier
|
||||
names, basing support on specific details of the underlying
|
||||
operating system. However, it has been observed that no matter
|
||||
what case sensitivity behavior is present, the names of tables in
|
||||
foreign key declarations are *always* received from the database
|
||||
as all-lower case, making it impossible to accurately reflect a
|
||||
schema where inter-related tables use mixed-case identifier names.
|
||||
|
||||
Therefore it is strongly advised that table names be declared as
|
||||
all lower case both within SQLAlchemy as well as on the MySQL
|
||||
database itself, especially if database reflection features are
|
||||
to be used.
|
||||
|
||||
Keys
|
||||
----
|
||||
|
||||
@@ -81,7 +97,7 @@ foreign keys. For these tables, you may supply a
|
||||
autoload=True
|
||||
)
|
||||
|
||||
When creating tables, SQLAlchemy will automatically set ``AUTO_INCREMENT``` on
|
||||
When creating tables, SQLAlchemy will automatically set ``AUTO_INCREMENT`` on
|
||||
an integer primary key column::
|
||||
|
||||
>>> t = Table('mytable', metadata,
|
||||
@@ -152,14 +168,61 @@ available.
|
||||
|
||||
update(..., mysql_limit=10)
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
CAST Support
|
||||
------------
|
||||
|
||||
If you have problems that seem server related, first check that you are
|
||||
using the most recent stable MySQL-Python package available. The Database
|
||||
Notes page on the wiki at http://www.sqlalchemy.org is a good resource for
|
||||
timely information affecting MySQL in SQLAlchemy.
|
||||
MySQL documents the CAST operator as available in version 4.0.2. When using the
|
||||
SQLAlchemy :func:`.cast` function, SQLAlchemy
|
||||
will not render the CAST token on MySQL before this version, based on server version
|
||||
detection, instead rendering the internal expression directly.
|
||||
|
||||
CAST may still not be desirable on an early MySQL version post-4.0.2, as it didn't
|
||||
add all datatype support until 4.1.1. If your application falls into this
|
||||
narrow area, the behavior of CAST can be controlled using the :ref:`sqlalchemy.ext.compiler_toplevel`
|
||||
system, as per the recipe below::
|
||||
|
||||
from sqlalchemy.sql.expression import _Cast
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
|
||||
@compiles(_Cast, 'mysql')
|
||||
def _check_mysql_version(element, compiler, **kw):
|
||||
if compiler.dialect.server_version_info < (4, 1, 0):
|
||||
return compiler.process(element.clause, **kw)
|
||||
else:
|
||||
return compiler.visit_cast(element, **kw)
|
||||
|
||||
The above function, which only needs to be declared once
|
||||
within an application, overrides the compilation of the
|
||||
:func:`.cast` construct to check for version 4.1.0 before
|
||||
fully rendering CAST; else the internal element of the
|
||||
construct is rendered directly.
|
||||
|
||||
|
||||
.. _mysql_indexes:
|
||||
|
||||
MySQL Specific Index Options
|
||||
----------------------------
|
||||
|
||||
MySQL-specific extensions to the :class:`.Index` construct are available.
|
||||
|
||||
Index Length
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
MySQL provides an option to create index entries with a certain length, where
|
||||
"length" refers to the number of characters or bytes in each value which will
|
||||
become part of the index. SQLAlchemy provides this feature via the
|
||||
``mysql_length`` parameter::
|
||||
|
||||
Index('my_index', my_table.c.data, mysql_length=10)
|
||||
|
||||
Prefix lengths are given in characters for nonbinary string types and in bytes
|
||||
for binary string types. The value passed to the keyword argument will be
|
||||
simply passed through to the underlying CREATE INDEX command, so it *must* be
|
||||
an integer. MySQL only allows a length for an index if it is for a CHAR,
|
||||
VARCHAR, TEXT, BINARY, VARBINARY and BLOB.
|
||||
|
||||
More information can be found at:
|
||||
http://dev.mysql.com/doc/refman/5.0/en/create-index.html
|
||||
"""
|
||||
|
||||
import datetime, inspect, re, sys
|
||||
@@ -174,7 +237,7 @@ from array import array as _array
|
||||
from sqlalchemy.engine import reflection
|
||||
from sqlalchemy.engine import base as engine_base, default
|
||||
from sqlalchemy import types as sqltypes
|
||||
|
||||
from sqlalchemy.util import topological
|
||||
from sqlalchemy.types import DATE, DATETIME, BOOLEAN, TIME, \
|
||||
BLOB, BINARY, VARBINARY
|
||||
|
||||
@@ -231,9 +294,9 @@ SET_RE = re.compile(
|
||||
class _NumericType(object):
|
||||
"""Base for MySQL numeric types."""
|
||||
|
||||
def __init__(self, **kw):
|
||||
self.unsigned = kw.pop('unsigned', False)
|
||||
self.zerofill = kw.pop('zerofill', False)
|
||||
def __init__(self, unsigned=False, zerofill=False, **kw):
|
||||
self.unsigned = unsigned
|
||||
self.zerofill = zerofill
|
||||
super(_NumericType, self).__init__(**kw)
|
||||
|
||||
class _FloatType(_NumericType, sqltypes.Float):
|
||||
@@ -362,7 +425,7 @@ class DOUBLE(_FloatType):
|
||||
super(DOUBLE, self).__init__(precision=precision, scale=scale,
|
||||
asdecimal=asdecimal, **kw)
|
||||
|
||||
class REAL(_FloatType):
|
||||
class REAL(_FloatType, sqltypes.REAL):
|
||||
"""MySQL REAL type."""
|
||||
|
||||
__visit_name__ = 'REAL'
|
||||
@@ -747,7 +810,7 @@ class CHAR(_StringType, sqltypes.CHAR):
|
||||
|
||||
__visit_name__ = 'CHAR'
|
||||
|
||||
def __init__(self, length, **kwargs):
|
||||
def __init__(self, length=None, **kwargs):
|
||||
"""Construct a CHAR.
|
||||
|
||||
:param length: Maximum data length, in characters.
|
||||
@@ -942,6 +1005,10 @@ class ENUM(sqltypes.Enum, _StringType):
|
||||
return value
|
||||
return process
|
||||
|
||||
def adapt(self, impltype, **kw):
|
||||
kw['strict'] = self.strict
|
||||
return sqltypes.Enum.adapt(self, impltype, **kw)
|
||||
|
||||
class SET(_StringType):
|
||||
"""MySQL SET type."""
|
||||
|
||||
@@ -988,8 +1055,8 @@ class SET(_StringType):
|
||||
strip_values.append(a)
|
||||
|
||||
self.values = strip_values
|
||||
length = max([len(v) for v in strip_values] + [0])
|
||||
super(SET, self).__init__(length=length, **kw)
|
||||
kw.setdefault('length', max([len(v) for v in strip_values] + [0]))
|
||||
super(SET, self).__init__(**kw)
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
def process(value):
|
||||
@@ -1113,6 +1180,9 @@ class MySQLExecutionContext(default.DefaultExecutionContext):
|
||||
|
||||
class MySQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
render_table_with_column_in_update_from = True
|
||||
"""Overridden from base SQLCompiler value"""
|
||||
|
||||
extract_map = compiler.SQLCompiler.extract_map.copy()
|
||||
extract_map.update ({
|
||||
'milliseconds': 'millisecond',
|
||||
@@ -1157,7 +1227,7 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
return 'CHAR'
|
||||
elif isinstance(type_, sqltypes._Binary):
|
||||
return 'BINARY'
|
||||
elif isinstance(type_, NUMERIC):
|
||||
elif isinstance(type_, sqltypes.NUMERIC):
|
||||
return self.dialect.type_compiler.process(type_).replace('NUMERIC', 'DECIMAL')
|
||||
else:
|
||||
return None
|
||||
@@ -1180,6 +1250,15 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
return value
|
||||
|
||||
def get_select_precolumns(self, select):
|
||||
"""Add special MySQL keywords in place of DISTINCT.
|
||||
|
||||
.. 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() + " "
|
||||
elif select._distinct:
|
||||
@@ -1222,32 +1301,39 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
elif offset is not None:
|
||||
# As suggested by the MySQL docs, need to apply an
|
||||
# artificial limit if one wasn't provided
|
||||
# http://dev.mysql.com/doc/refman/5.0/en/select.html
|
||||
if limit is None:
|
||||
limit = 18446744073709551615
|
||||
return ' \n LIMIT %s, %s' % (offset, limit)
|
||||
# hardwire the upper limit. Currently
|
||||
# needed by OurSQL with Python 3
|
||||
# (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)),
|
||||
"18446744073709551615")
|
||||
else:
|
||||
return ' \n LIMIT %s, %s' % (
|
||||
self.process(sql.literal(offset)),
|
||||
self.process(sql.literal(limit)))
|
||||
else:
|
||||
# No offset provided, so just use the limit
|
||||
return ' \n LIMIT %s' % (limit,)
|
||||
return ' \n LIMIT %s' % (self.process(sql.literal(limit)),)
|
||||
|
||||
def visit_update(self, update_stmt):
|
||||
self.stack.append({'from': set([update_stmt.table])})
|
||||
|
||||
self.isupdate = True
|
||||
colparams = self._get_colparams(update_stmt)
|
||||
|
||||
text = "UPDATE " + self.preparer.format_table(update_stmt.table) + \
|
||||
" SET " + ', '.join(["%s=%s" % (self.preparer.format_column(c[0]), c[1]) for c in colparams])
|
||||
|
||||
if update_stmt._whereclause is not None:
|
||||
text += " WHERE " + self.process(update_stmt._whereclause)
|
||||
|
||||
limit = update_stmt.kwargs.get('mysql_limit', None)
|
||||
def update_limit_clause(self, update_stmt):
|
||||
limit = update_stmt.kwargs.get('%s_limit' % self.dialect.name, None)
|
||||
if limit:
|
||||
text += " LIMIT %s" % limit
|
||||
return "LIMIT %s" % limit
|
||||
else:
|
||||
return None
|
||||
|
||||
self.stack.pop(-1)
|
||||
def update_tables_clause(self, update_stmt, from_table, extra_froms, **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, extra_froms, **kw):
|
||||
return None
|
||||
|
||||
return text
|
||||
|
||||
# ug. "InnoDB needs indexes on foreign keys and referenced keys [...].
|
||||
# Starting with MySQL 4.1.2, these indexes are created automatically.
|
||||
@@ -1259,8 +1345,9 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
"""Get table constraints."""
|
||||
constraint_string = super(MySQLDDLCompiler, self).create_table_constraints(table)
|
||||
|
||||
is_innodb = table.kwargs.has_key('mysql_engine') and \
|
||||
table.kwargs['mysql_engine'].lower() == 'innodb'
|
||||
engine_key = '%s_engine' % self.dialect.name
|
||||
is_innodb = table.kwargs.has_key(engine_key) and \
|
||||
table.kwargs[engine_key].lower() == 'innodb'
|
||||
|
||||
auto_inc_column = table._autoincrement_column
|
||||
|
||||
@@ -1293,16 +1380,8 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
elif column.nullable and is_timestamp and default is None:
|
||||
colspec.append('NULL')
|
||||
|
||||
if column.primary_key and column.autoincrement:
|
||||
try:
|
||||
first = [c for c in column.table.primary_key.columns
|
||||
if (c.autoincrement and
|
||||
isinstance(c.type, sqltypes.Integer) and
|
||||
not c.foreign_keys)].pop(0)
|
||||
if column is first:
|
||||
colspec.append('AUTO_INCREMENT')
|
||||
except IndexError:
|
||||
pass
|
||||
if column is column.table._autoincrement_column and column.server_default is None:
|
||||
colspec.append('AUTO_INCREMENT')
|
||||
|
||||
return ' '.join(colspec)
|
||||
|
||||
@@ -1310,27 +1389,62 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
"""Build table-level CREATE options like ENGINE and COLLATE."""
|
||||
|
||||
table_opts = []
|
||||
for k in table.kwargs:
|
||||
if k.startswith('mysql_'):
|
||||
opt = k[6:].upper()
|
||||
|
||||
arg = table.kwargs[k]
|
||||
if opt in _options_of_type_string:
|
||||
arg = "'%s'" % arg.replace("\\", "\\\\").replace("'", "''")
|
||||
opts = dict(
|
||||
(
|
||||
k[len(self.dialect.name)+1:].upper(),
|
||||
v
|
||||
)
|
||||
for k, v in table.kwargs.items()
|
||||
if k.startswith('%s_' % self.dialect.name)
|
||||
)
|
||||
|
||||
if opt in ('DATA_DIRECTORY', 'INDEX_DIRECTORY',
|
||||
'DEFAULT_CHARACTER_SET', 'CHARACTER_SET', 'DEFAULT_CHARSET',
|
||||
'DEFAULT_COLLATE'):
|
||||
opt = opt.replace('_', ' ')
|
||||
for opt in topological.sort([
|
||||
('DEFAULT_CHARSET', 'COLLATE'),
|
||||
('DEFAULT_CHARACTER_SET', 'COLLATE')
|
||||
], opts):
|
||||
arg = opts[opt]
|
||||
if opt in _options_of_type_string:
|
||||
arg = "'%s'" % arg.replace("\\", "\\\\").replace("'", "''")
|
||||
|
||||
joiner = '='
|
||||
if opt in ('TABLESPACE', 'DEFAULT CHARACTER SET',
|
||||
'CHARACTER SET', 'COLLATE'):
|
||||
joiner = ' '
|
||||
if opt in ('DATA_DIRECTORY', 'INDEX_DIRECTORY',
|
||||
'DEFAULT_CHARACTER_SET', 'CHARACTER_SET',
|
||||
'DEFAULT_CHARSET',
|
||||
'DEFAULT_COLLATE'):
|
||||
opt = opt.replace('_', ' ')
|
||||
|
||||
table_opts.append(joiner.join((opt, arg)))
|
||||
joiner = '='
|
||||
if opt in ('TABLESPACE', 'DEFAULT CHARACTER SET',
|
||||
'CHARACTER SET', 'COLLATE'):
|
||||
joiner = ' '
|
||||
|
||||
table_opts.append(joiner.join((opt, arg)))
|
||||
return ' '.join(table_opts)
|
||||
|
||||
def visit_create_index(self, create):
|
||||
index = create.element
|
||||
preparer = self.preparer
|
||||
text = "CREATE "
|
||||
if index.unique:
|
||||
text += "UNIQUE "
|
||||
text += "INDEX %s ON %s " \
|
||||
% (preparer.quote(self._index_identifier(index.name),
|
||||
index.quote),preparer.format_table(index.table))
|
||||
if 'mysql_length' in index.kwargs:
|
||||
length = index.kwargs['mysql_length']
|
||||
else:
|
||||
length = None
|
||||
if length is not None:
|
||||
text+= "(%s(%d))" \
|
||||
% (', '.join(preparer.quote(c.name, c.quote)
|
||||
for c in index.columns), length)
|
||||
else:
|
||||
text+= "(%s)" \
|
||||
% (', '.join(preparer.quote(c.name, c.quote)
|
||||
for c in index.columns))
|
||||
return text
|
||||
|
||||
|
||||
def visit_drop_index(self, drop):
|
||||
index = drop.element
|
||||
|
||||
@@ -1408,17 +1522,25 @@ 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)" % {'precision': type_.precision})
|
||||
return self._extend_numeric(type_,
|
||||
"NUMERIC(%(precision)s)" %
|
||||
{'precision': type_.precision})
|
||||
else:
|
||||
return self._extend_numeric(type_, "NUMERIC(%(precision)s, %(scale)s)" % {'precision': type_.precision, 'scale' : type_.scale})
|
||||
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)" % {'precision': type_.precision})
|
||||
return self._extend_numeric(type_,
|
||||
"DECIMAL(%(precision)s)" %
|
||||
{'precision': type_.precision})
|
||||
else:
|
||||
return self._extend_numeric(type_, "DECIMAL(%(precision)s, %(scale)s)" % {'precision': type_.precision, 'scale' : type_.scale})
|
||||
return self._extend_numeric(type_,
|
||||
"DECIMAL(%(precision)s, %(scale)s)" %
|
||||
{'precision': type_.precision, 'scale' : type_.scale})
|
||||
|
||||
def visit_DOUBLE(self, type_):
|
||||
if type_.precision is not None and type_.scale is not None:
|
||||
@@ -1437,8 +1559,11 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
return self._extend_numeric(type_, 'REAL')
|
||||
|
||||
def visit_FLOAT(self, type_):
|
||||
if self._mysql_type(type_) and type_.scale is not None and type_.precision is not None:
|
||||
return self._extend_numeric(type_, "FLOAT(%s, %s)" % (type_.precision, type_.scale))
|
||||
if self._mysql_type(type_) and \
|
||||
type_.scale is not None and \
|
||||
type_.precision is not None:
|
||||
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,))
|
||||
else:
|
||||
@@ -1446,19 +1571,25 @@ 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)" % {'display_width': type_.display_width})
|
||||
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)" % {'display_width': type_.display_width})
|
||||
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)" % {'display_width': type_.display_width})
|
||||
return self._extend_numeric(type_,
|
||||
"MEDIUMINT(%(display_width)s)" %
|
||||
{'display_width': type_.display_width})
|
||||
else:
|
||||
return self._extend_numeric(type_, "MEDIUMINT")
|
||||
|
||||
@@ -1470,7 +1601,10 @@ 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)" % {'display_width': type_.display_width})
|
||||
return self._extend_numeric(type_,
|
||||
"SMALLINT(%(display_width)s)" %
|
||||
{'display_width': type_.display_width}
|
||||
)
|
||||
else:
|
||||
return self._extend_numeric(type_, "SMALLINT")
|
||||
|
||||
@@ -1517,7 +1651,9 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
if type_.length:
|
||||
return self._extend_string(type_, {}, "VARCHAR(%d)" % type_.length)
|
||||
else:
|
||||
raise exc.InvalidRequestError("VARCHAR requires a length when rendered on MySQL")
|
||||
raise exc.CompileError(
|
||||
"VARCHAR requires a length on dialect %s" %
|
||||
self.dialect.name)
|
||||
|
||||
def visit_CHAR(self, type_):
|
||||
if type_.length:
|
||||
@@ -1531,7 +1667,9 @@ class MySQLTypeCompiler(compiler.GenericTypeCompiler):
|
||||
if type_.length:
|
||||
return self._extend_string(type_, {'national':True}, "VARCHAR(%(length)s)" % {'length': type_.length})
|
||||
else:
|
||||
raise exc.InvalidRequestError("NVARCHAR requires a length when rendered on MySQL")
|
||||
raise exc.CompileError(
|
||||
"NVARCHAR requires a length on dialect %s" %
|
||||
self.dialect.name)
|
||||
|
||||
def visit_NCHAR(self, type_):
|
||||
# We'll actually generate the equiv. "NATIONAL CHAR" instead of "NCHAR".
|
||||
@@ -1685,7 +1823,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
resultset = connection.execute("XA RECOVER")
|
||||
return [row['data'][0:row['gtrid_length']] for row in resultset]
|
||||
|
||||
def is_disconnect(self, e):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, self.dbapi.OperationalError):
|
||||
return self._extract_error_code(e) in \
|
||||
(2006, 2013, 2014, 2045, 2055)
|
||||
@@ -1741,7 +1879,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
have = rs.rowcount > 0
|
||||
rs.close()
|
||||
return have
|
||||
except exc.SQLError, e:
|
||||
except exc.DBAPIError, e:
|
||||
if self._extract_error_code(e.orig) == 1146:
|
||||
return False
|
||||
raise
|
||||
@@ -1941,17 +2079,6 @@ class MySQLDialect(default.DefaultDialect):
|
||||
sql = parser._describe_to_create(table_name, columns)
|
||||
return parser.parse(sql, charset)
|
||||
|
||||
def _adjust_casing(self, table, charset=None):
|
||||
"""Adjust Table name to the server case sensitivity, if needed."""
|
||||
|
||||
casing = self._server_casing
|
||||
|
||||
# For winxx database hosts. TODO: is this really needed?
|
||||
if casing == 1 and table.name != table.name.lower():
|
||||
table.name = table.name.lower()
|
||||
lc_alias = sa_schema._get_table_key(table.name, table.schema)
|
||||
table.metadata.tables[lc_alias] = table
|
||||
|
||||
def _detect_charset(self, connection):
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -2029,7 +2156,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
rp = None
|
||||
try:
|
||||
rp = connection.execute(st)
|
||||
except exc.SQLError, e:
|
||||
except exc.DBAPIError, e:
|
||||
if self._extract_error_code(e.orig) == 1146:
|
||||
raise exc.NoSuchTableError(full_name)
|
||||
else:
|
||||
@@ -2053,7 +2180,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
try:
|
||||
try:
|
||||
rp = connection.execute(st)
|
||||
except exc.SQLError, e:
|
||||
except exc.DBAPIError, e:
|
||||
if self._extract_error_code(e.orig) == 1146:
|
||||
raise exc.NoSuchTableError(full_name)
|
||||
else:
|
||||
@@ -2185,7 +2312,7 @@ class MySQLTableDefinitionParser(object):
|
||||
options.pop(nope, None)
|
||||
|
||||
for opt, val in options.items():
|
||||
state.table_options['mysql_%s' % opt] = val
|
||||
state.table_options['%s_%s' % (self.dialect.name, opt)] = val
|
||||
|
||||
def _parse_column(self, line, state):
|
||||
"""Extract column details.
|
||||
@@ -2432,9 +2559,7 @@ class MySQLTableDefinitionParser(object):
|
||||
# PARTITION
|
||||
#
|
||||
# punt!
|
||||
self._re_partition = _re_compile(
|
||||
r' '
|
||||
r'(?:SUB)?PARTITION')
|
||||
self._re_partition = _re_compile(r'(?:.*)(?:SUB)?PARTITION(?:.*)')
|
||||
|
||||
# Table-level options (COLLATE, ENGINE, etc.)
|
||||
# Do the string options first, since they have quoted strings we need to get rid of.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/mysqlconnector.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -118,7 +118,7 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
||||
def _extract_error_code(self, exception):
|
||||
return exception.errno
|
||||
|
||||
def is_disconnect(self, e):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
errnos = (2006, 2013, 2014, 2045, 2055, 2048)
|
||||
exceptions = (self.dbapi.OperationalError,self.dbapi.InterfaceError)
|
||||
if isinstance(e, exceptions):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/mysqldb.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -19,187 +19,64 @@ Connect string format::
|
||||
|
||||
mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
|
||||
Character Sets
|
||||
--------------
|
||||
Unicode
|
||||
-------
|
||||
|
||||
Many MySQL server installations default to a ``latin1`` encoding for client
|
||||
connections. All data sent through the connection will be converted into
|
||||
``latin1``, even if you have ``utf8`` or another character set on your tables
|
||||
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
|
||||
a ``latin1`` encoding for client connections, which has the effect
|
||||
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 in the URL used for ``create_engine``. The ``charset``
|
||||
option is passed through to MySQL-Python and has the side-effect of also
|
||||
enabling ``use_unicode`` in the driver by default. For regular encoded
|
||||
strings, also pass ``use_unicode=0`` in the connection arguments::
|
||||
``charset`` parameter. The ``charset``
|
||||
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')
|
||||
# set client encoding to utf8; all strings come back as unicode
|
||||
create_engine('mysql+mysqldb:///mydb?charset=utf8')
|
||||
|
||||
# set client encoding to utf8; all strings come back as utf8 str
|
||||
create_engine('mysql+mysqldb:///mydb?charset=utf8&use_unicode=0')
|
||||
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
|
||||
create_engine('mysql+mysqldb:///mydb?charset=utf8&use_unicode=0')
|
||||
|
||||
Known Issues
|
||||
-------------
|
||||
|
||||
MySQL-python at least as of version 1.2.2 has a serious memory leak related
|
||||
MySQL-python 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)
|
||||
|
||||
It is strongly advised to use the latest version of MySQL-Python.
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from sqlalchemy.dialects.mysql.base import (MySQLDialect, MySQLExecutionContext,
|
||||
MySQLCompiler, MySQLIdentifierPreparer)
|
||||
from sqlalchemy.engine import base as engine_base, default
|
||||
from sqlalchemy.sql import operators as sql_operators
|
||||
from sqlalchemy import exc, log, schema, sql, types as sqltypes, util
|
||||
from sqlalchemy import processors
|
||||
from sqlalchemy.connectors.mysqldb import (
|
||||
MySQLDBExecutionContext,
|
||||
MySQLDBCompiler,
|
||||
MySQLDBIdentifierPreparer,
|
||||
MySQLDBConnector
|
||||
)
|
||||
|
||||
class MySQLExecutionContext_mysqldb(MySQLExecutionContext):
|
||||
|
||||
@property
|
||||
def rowcount(self):
|
||||
if hasattr(self, '_rowcount'):
|
||||
return self._rowcount
|
||||
else:
|
||||
return self.cursor.rowcount
|
||||
class MySQLExecutionContext_mysqldb(MySQLDBExecutionContext, MySQLExecutionContext):
|
||||
pass
|
||||
|
||||
|
||||
class MySQLCompiler_mysqldb(MySQLCompiler):
|
||||
def visit_mod(self, binary, **kw):
|
||||
return self.process(binary.left) + " %% " + self.process(binary.right)
|
||||
|
||||
def post_process_text(self, text):
|
||||
return text.replace('%', '%%')
|
||||
class MySQLCompiler_mysqldb(MySQLDBCompiler, MySQLCompiler):
|
||||
pass
|
||||
|
||||
|
||||
class MySQLIdentifierPreparer_mysqldb(MySQLIdentifierPreparer):
|
||||
class MySQLIdentifierPreparer_mysqldb(MySQLDBIdentifierPreparer, MySQLIdentifierPreparer):
|
||||
pass
|
||||
|
||||
def _escape_identifier(self, value):
|
||||
value = value.replace(self.escape_quote, self.escape_to_quote)
|
||||
return value.replace("%", "%%")
|
||||
|
||||
class MySQLDialect_mysqldb(MySQLDialect):
|
||||
driver = 'mysqldb'
|
||||
supports_unicode_statements = False
|
||||
supports_sane_rowcount = True
|
||||
supports_sane_multi_rowcount = True
|
||||
|
||||
supports_native_decimal = True
|
||||
|
||||
default_paramstyle = 'format'
|
||||
class MySQLDialect_mysqldb(MySQLDBConnector, MySQLDialect):
|
||||
execution_ctx_cls = MySQLExecutionContext_mysqldb
|
||||
statement_compiler = MySQLCompiler_mysqldb
|
||||
preparer = MySQLIdentifierPreparer_mysqldb
|
||||
|
||||
colspecs = util.update_copy(
|
||||
MySQLDialect.colspecs,
|
||||
{
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('MySQLdb')
|
||||
|
||||
def do_executemany(self, cursor, statement, parameters, context=None):
|
||||
rowcount = cursor.executemany(statement, parameters)
|
||||
if context is not None:
|
||||
context._rowcount = rowcount
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(database='db', username='user',
|
||||
password='passwd')
|
||||
opts.update(url.query)
|
||||
|
||||
util.coerce_kw_type(opts, 'compress', bool)
|
||||
util.coerce_kw_type(opts, 'connect_timeout', int)
|
||||
util.coerce_kw_type(opts, 'client_flag', int)
|
||||
util.coerce_kw_type(opts, 'local_infile', int)
|
||||
# Note: using either of the below will cause all strings to be returned
|
||||
# as Unicode, both in raw SQL operations and with column types like
|
||||
# String and MSString.
|
||||
util.coerce_kw_type(opts, 'use_unicode', bool)
|
||||
util.coerce_kw_type(opts, 'charset', str)
|
||||
|
||||
# Rich values 'cursorclass' and 'conv' are not supported via
|
||||
# query string.
|
||||
|
||||
ssl = {}
|
||||
for key in ['ssl_ca', 'ssl_key', 'ssl_cert', 'ssl_capath', 'ssl_cipher']:
|
||||
if key in opts:
|
||||
ssl[key[4:]] = opts[key]
|
||||
util.coerce_kw_type(ssl, key[4:], str)
|
||||
del opts[key]
|
||||
if ssl:
|
||||
opts['ssl'] = ssl
|
||||
|
||||
# FOUND_ROWS must be set in CLIENT_FLAGS to enable
|
||||
# supports_sane_rowcount.
|
||||
client_flag = opts.get('client_flag', 0)
|
||||
if self.dbapi is not None:
|
||||
try:
|
||||
from MySQLdb.constants import CLIENT as CLIENT_FLAGS
|
||||
client_flag |= CLIENT_FLAGS.FOUND_ROWS
|
||||
except:
|
||||
pass
|
||||
opts['client_flag'] = client_flag
|
||||
return [[], opts]
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
dbapi_con = connection.connection
|
||||
version = []
|
||||
r = re.compile('[.\-]')
|
||||
for n in r.split(dbapi_con.get_server_info()):
|
||||
try:
|
||||
version.append(int(n))
|
||||
except ValueError:
|
||||
version.append(n)
|
||||
return tuple(version)
|
||||
|
||||
def _extract_error_code(self, exception):
|
||||
return exception.args[0]
|
||||
|
||||
def _detect_charset(self, connection):
|
||||
"""Sniff out the character set in use for connection results."""
|
||||
|
||||
# Note: MySQL-python 1.2.1c7 seems to ignore changes made
|
||||
# on a connection via set_character_set()
|
||||
if self.server_version_info < (4, 1, 0):
|
||||
try:
|
||||
return connection.connection.character_set_name()
|
||||
except AttributeError:
|
||||
# < 1.2.1 final MySQL-python drivers have no charset support.
|
||||
# a query is needed.
|
||||
pass
|
||||
|
||||
# Prefer 'character_set_results' for the current connection over the
|
||||
# value in the driver. SET NAMES or individual variable SETs will
|
||||
# change the charset without updating the driver's view of the world.
|
||||
#
|
||||
# If it's decided that issuing that sort of SQL leaves you SOL, then
|
||||
# this can prefer the driver value.
|
||||
rs = connection.execute("SHOW VARIABLES LIKE 'character_set%%'")
|
||||
opts = dict([(row[0], row[1]) for row in self._compat_fetchall(rs)])
|
||||
|
||||
if 'character_set_results' in opts:
|
||||
return opts['character_set_results']
|
||||
try:
|
||||
return connection.connection.character_set_name()
|
||||
except AttributeError:
|
||||
# Still no charset on < 1.2.1 final...
|
||||
if 'character_set' in opts:
|
||||
return opts['character_set']
|
||||
else:
|
||||
util.warn(
|
||||
"Could not detect the connection character set with this "
|
||||
"combination of MySQL server and MySQL-python. "
|
||||
"MySQL-python >= 1.2.2 is recommended. Assuming latin1.")
|
||||
return 'latin1'
|
||||
|
||||
|
||||
dialect = MySQLDialect_mysqldb
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/oursql.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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,8 +17,8 @@ Connect string format::
|
||||
|
||||
mysql+oursql://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
|
||||
Character Sets
|
||||
--------------
|
||||
Unicode
|
||||
-------
|
||||
|
||||
oursql defaults to using ``utf8`` as the connection charset, but other
|
||||
encodings may be used instead. Like the MySQL-Python driver, unicode support
|
||||
@@ -64,8 +64,6 @@ class MySQLExecutionContext_oursql(MySQLExecutionContext):
|
||||
|
||||
class MySQLDialect_oursql(MySQLDialect):
|
||||
driver = 'oursql'
|
||||
# Py3K
|
||||
# description_encoding = None
|
||||
# Py2K
|
||||
supports_unicode_binds = True
|
||||
supports_unicode_statements = True
|
||||
@@ -107,6 +105,7 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
# Py3K
|
||||
# charset = self._connection_charset
|
||||
# arg = connection.connection._escape_string(xid.encode(charset)).decode(charset)
|
||||
arg = "'%s'" % arg
|
||||
connection.execution_options(_oursql_plain_query=True).execute(query % arg)
|
||||
|
||||
# Because mysql is bad, these methods have to be
|
||||
@@ -115,23 +114,23 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
# the parameterized query API, or refuse to be parameterized
|
||||
# in the first place.
|
||||
def do_begin_twophase(self, connection, xid):
|
||||
self._xa_query(connection, 'XA BEGIN "%s"', xid)
|
||||
self._xa_query(connection, 'XA BEGIN %s', xid)
|
||||
|
||||
def do_prepare_twophase(self, connection, xid):
|
||||
self._xa_query(connection, 'XA END "%s"', xid)
|
||||
self._xa_query(connection, 'XA PREPARE "%s"', xid)
|
||||
self._xa_query(connection, 'XA END %s', xid)
|
||||
self._xa_query(connection, 'XA PREPARE %s', xid)
|
||||
|
||||
def do_rollback_twophase(self, connection, xid, is_prepared=True,
|
||||
recover=False):
|
||||
if not is_prepared:
|
||||
self._xa_query(connection, 'XA END "%s"', xid)
|
||||
self._xa_query(connection, 'XA ROLLBACK "%s"', xid)
|
||||
self._xa_query(connection, 'XA END %s', xid)
|
||||
self._xa_query(connection, 'XA ROLLBACK %s', xid)
|
||||
|
||||
def do_commit_twophase(self, connection, xid, is_prepared=True,
|
||||
recover=False):
|
||||
if not is_prepared:
|
||||
self.do_prepare_twophase(connection, xid)
|
||||
self._xa_query(connection, 'XA COMMIT "%s"', xid)
|
||||
self._xa_query(connection, 'XA COMMIT %s', xid)
|
||||
|
||||
# Q: why didn't we need all these "plain_query" overrides earlier ?
|
||||
# am i on a newer/older version of OurSQL ?
|
||||
@@ -195,7 +194,7 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
execution_options(_oursql_plain_query=True),
|
||||
table, charset, full_name)
|
||||
|
||||
def is_disconnect(self, e):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, self.dbapi.ProgrammingError):
|
||||
return e.errno is None and 'cursor' not in e.args[1] and e.args[1].endswith('closed')
|
||||
else:
|
||||
@@ -222,6 +221,16 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
# supports_sane_rowcount.
|
||||
opts.setdefault('found_rows', True)
|
||||
|
||||
ssl = {}
|
||||
for key in ['ssl_ca', 'ssl_key', 'ssl_cert',
|
||||
'ssl_capath', 'ssl_cipher']:
|
||||
if key in opts:
|
||||
ssl[key[4:]] = opts[key]
|
||||
util.coerce_kw_type(ssl, key[4:], str)
|
||||
del opts[key]
|
||||
if ssl:
|
||||
opts['ssl'] = ssl
|
||||
|
||||
return [[], opts]
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# mysql/pymysql.py
|
||||
# Copyright (C) 2005-2012 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 MySQL database via the pymysql adapter.
|
||||
|
||||
pymysql is available at:
|
||||
|
||||
http://code.google.com/p/pymysql/
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
Connect string::
|
||||
|
||||
mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
|
||||
|
||||
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 driver as well.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.mysql.mysqldb import MySQLDialect_mysqldb
|
||||
|
||||
class MySQLDialect_pymysql(MySQLDialect_mysqldb):
|
||||
driver = 'pymysql'
|
||||
|
||||
description_encoding = None
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('pymysql')
|
||||
|
||||
dialect = MySQLDialect_pymysql
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/pyodbc.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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 @@
|
||||
# mysql/zxjdbc.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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/base.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -22,6 +22,8 @@ affect the behavior of the dialect regardless of driver in use.
|
||||
|
||||
* *optimize_limits* - defaults to ``False``. see the section on LIMIT/OFFSET.
|
||||
|
||||
* *use_binds_for_limits* - defaults to ``True``. see the section on LIMIT/OFFSET.
|
||||
|
||||
Auto Increment Behavior
|
||||
-----------------------
|
||||
|
||||
@@ -74,13 +76,27 @@ requires NLS_LANG to be set.
|
||||
LIMIT/OFFSET Support
|
||||
--------------------
|
||||
|
||||
Oracle has no support for the LIMIT or OFFSET keywords. Whereas previous versions of SQLAlchemy
|
||||
used the "ROW NUMBER OVER..." construct to simulate LIMIT/OFFSET, SQLAlchemy 0.5 now 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 . Note that the
|
||||
"FIRST ROWS()" optimization keyword mentioned is not used by default, as the user community felt
|
||||
this was stepping into the bounds of optimization that is better left on the DBA side, but this
|
||||
prefix can be added by enabling the optimize_limits=True flag on create_engine().
|
||||
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 .
|
||||
|
||||
There are two options which affect its behavior:
|
||||
|
||||
* the "FIRST ROWS()" optimization keyword is not used by default. To enable the usage of this
|
||||
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
|
||||
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
|
||||
http://www.sqlalchemy.org/trac/wiki/UsageRecipes/WindowFunctionsByDefault
|
||||
which installs a select compiler that overrides the generation of limit/offset with
|
||||
a window function.
|
||||
|
||||
ON UPDATE CASCADE
|
||||
-----------------
|
||||
@@ -133,24 +149,30 @@ from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.types import VARCHAR, NVARCHAR, CHAR, DATE, DATETIME, \
|
||||
BLOB, CLOB, TIMESTAMP, FLOAT
|
||||
|
||||
RESERVED_WORDS = set('SHARE RAW DROP BETWEEN FROM DESC OPTION PRIOR LONG THEN '
|
||||
'DEFAULT ALTER IS INTO MINUS INTEGER NUMBER GRANT IDENTIFIED '
|
||||
'ALL TO ORDER ON FLOAT DATE HAVING CLUSTER NOWAIT RESOURCE ANY '
|
||||
'TABLE INDEX FOR UPDATE WHERE CHECK SMALLINT WITH DELETE BY ASC '
|
||||
'REVOKE LIKE SIZE RENAME NOCOMPRESS NULL GROUP VALUES AS IN VIEW '
|
||||
'EXCLUSIVE COMPRESS SYNONYM SELECT INSERT EXISTS NOT TRIGGER '
|
||||
'ELSE CREATE INTERSECT PCTFREE DISTINCT USER CONNECT SET MODE '
|
||||
'OF UNIQUE VARCHAR2 VARCHAR LOCK OR CHAR DECIMAL UNION PUBLIC '
|
||||
'AND START UID COMMENT'.split())
|
||||
RESERVED_WORDS = \
|
||||
set('SHARE RAW DROP BETWEEN FROM DESC OPTION PRIOR LONG THEN '\
|
||||
'DEFAULT ALTER IS INTO MINUS INTEGER NUMBER GRANT IDENTIFIED '\
|
||||
'ALL TO ORDER ON FLOAT DATE HAVING CLUSTER NOWAIT RESOURCE '\
|
||||
'ANY TABLE INDEX FOR UPDATE WHERE CHECK SMALLINT WITH DELETE '\
|
||||
'BY ASC REVOKE LIKE SIZE RENAME NOCOMPRESS NULL GROUP VALUES '\
|
||||
'AS IN VIEW EXCLUSIVE COMPRESS SYNONYM SELECT INSERT EXISTS '\
|
||||
'NOT TRIGGER ELSE CREATE INTERSECT PCTFREE DISTINCT USER '\
|
||||
'CONNECT SET MODE OF UNIQUE VARCHAR2 VARCHAR LOCK OR CHAR '\
|
||||
'DECIMAL UNION PUBLIC AND START UID COMMENT CURRENT'.split())
|
||||
|
||||
class RAW(sqltypes.LargeBinary):
|
||||
pass
|
||||
NO_ARG_FNS = set('UID CURRENT_DATE SYSDATE USER '
|
||||
'CURRENT_TIME CURRENT_TIMESTAMP'.split())
|
||||
|
||||
class RAW(sqltypes._Binary):
|
||||
__visit_name__ = 'RAW'
|
||||
OracleRaw = RAW
|
||||
|
||||
class NCLOB(sqltypes.Text):
|
||||
__visit_name__ = 'NCLOB'
|
||||
|
||||
VARCHAR2 = VARCHAR
|
||||
class VARCHAR2(VARCHAR):
|
||||
__visit_name__ = 'VARCHAR2'
|
||||
|
||||
NVARCHAR2 = NVARCHAR
|
||||
|
||||
class NUMBER(sqltypes.Numeric, sqltypes.Integer):
|
||||
@@ -216,10 +238,6 @@ class INTERVAL(sqltypes.TypeEngine):
|
||||
return INTERVAL(day_precision=interval.day_precision,
|
||||
second_precision=interval.second_precision)
|
||||
|
||||
def adapt(self, impltype):
|
||||
return impltype(day_precision=self.day_precision,
|
||||
second_precision=self.second_precision)
|
||||
|
||||
@property
|
||||
def _type_affinity(self):
|
||||
return sqltypes.Interval
|
||||
@@ -277,9 +295,9 @@ class OracleTypeCompiler(compiler.GenericTypeCompiler):
|
||||
|
||||
def visit_unicode(self, type_):
|
||||
if self.dialect._supports_nchar:
|
||||
return self.visit_NVARCHAR(type_)
|
||||
return self.visit_NVARCHAR2(type_)
|
||||
else:
|
||||
return self.visit_VARCHAR(type_)
|
||||
return self.visit_VARCHAR2(type_)
|
||||
|
||||
def visit_INTERVAL(self, type_):
|
||||
return "INTERVAL DAY%s TO SECOND%s" % (
|
||||
@@ -317,14 +335,27 @@ class OracleTypeCompiler(compiler.GenericTypeCompiler):
|
||||
else:
|
||||
return "%(name)s(%(precision)s, %(scale)s)" % {'name':name,'precision': precision, 'scale' : scale}
|
||||
|
||||
def visit_VARCHAR(self, type_):
|
||||
if self.dialect._supports_char_length:
|
||||
return "VARCHAR(%(length)s CHAR)" % {'length' : type_.length}
|
||||
else:
|
||||
return "VARCHAR(%(length)s)" % {'length' : type_.length}
|
||||
def visit_string(self, type_):
|
||||
return self.visit_VARCHAR2(type_)
|
||||
|
||||
def visit_NVARCHAR(self, type_):
|
||||
return "NVARCHAR2(%(length)s)" % {'length' : type_.length}
|
||||
def visit_VARCHAR2(self, type_):
|
||||
return self._visit_varchar(type_, '', '2')
|
||||
|
||||
def visit_NVARCHAR2(self, type_):
|
||||
return self._visit_varchar(type_, 'N', '2')
|
||||
visit_NVARCHAR = visit_NVARCHAR2
|
||||
|
||||
def visit_VARCHAR(self, type_):
|
||||
return self._visit_varchar(type_, '', '')
|
||||
|
||||
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,
|
||||
'two':num}
|
||||
else:
|
||||
return "%(n)sVARCHAR%(two)s(%(length)s)" % {'length' : type_.length,
|
||||
'two':num, 'n':n}
|
||||
|
||||
def visit_text(self, type_):
|
||||
return self.visit_CLOB(type_)
|
||||
@@ -345,7 +376,10 @@ class OracleTypeCompiler(compiler.GenericTypeCompiler):
|
||||
return self.visit_SMALLINT(type_)
|
||||
|
||||
def visit_RAW(self, type_):
|
||||
return "RAW(%(length)s)" % {'length' : type_.length}
|
||||
if type_.length:
|
||||
return "RAW(%(length)s)" % {'length' : type_.length}
|
||||
else:
|
||||
return "RAW"
|
||||
|
||||
def visit_ROWID(self, type_):
|
||||
return "ROWID"
|
||||
@@ -364,9 +398,9 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OracleCompiler, self).__init__(*args, **kwargs)
|
||||
self.__wheres = {}
|
||||
self._quoted_bind_names = {}
|
||||
super(OracleCompiler, self).__init__(*args, **kwargs)
|
||||
|
||||
def visit_mod(self, binary, **kw):
|
||||
return "mod(%s, %s)" % (self.process(binary.left), self.process(binary.right))
|
||||
@@ -386,13 +420,14 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
)
|
||||
|
||||
def function_argspec(self, fn, **kw):
|
||||
if len(fn.clauses) > 0:
|
||||
if len(fn.clauses) > 0 or fn.name.upper() not in NO_ARG_FNS:
|
||||
return compiler.SQLCompiler.function_argspec(self, fn, **kw)
|
||||
else:
|
||||
return ""
|
||||
|
||||
def default_from(self):
|
||||
"""Called when a ``SELECT`` statement has no froms, and no ``FROM`` clause is to be appended.
|
||||
"""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.
|
||||
"""
|
||||
@@ -523,6 +558,8 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
max_row = select._limit
|
||||
if select._offset is not None:
|
||||
max_row += select._offset
|
||||
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)
|
||||
|
||||
@@ -541,8 +578,11 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
offsetselect._oracle_visit = True
|
||||
offsetselect._is_wrapper = True
|
||||
|
||||
offset_value = select._offset
|
||||
if not self.dialect.use_binds_for_limits:
|
||||
offset_value = sql.literal_column("%d" % offset_value)
|
||||
offsetselect.append_whereclause(
|
||||
sql.literal_column("ora_rn")>select._offset)
|
||||
sql.literal_column("ora_rn")>offset_value)
|
||||
|
||||
offsetselect.for_update = select.for_update
|
||||
select = offsetselect
|
||||
@@ -597,10 +637,10 @@ class OracleIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
|
||||
|
||||
class OracleExecutionContext(default.DefaultExecutionContext):
|
||||
def fire_sequence(self, seq):
|
||||
return int(self._execute_scalar("SELECT " +
|
||||
def fire_sequence(self, seq, type_):
|
||||
return self._execute_scalar("SELECT " +
|
||||
self.dialect.identifier_preparer.format_sequence(seq) +
|
||||
".nextval FROM DUAL"))
|
||||
".nextval FROM DUAL", type_)
|
||||
|
||||
class OracleDialect(default.DefaultDialect):
|
||||
name = 'oracle'
|
||||
@@ -634,10 +674,12 @@ class OracleDialect(default.DefaultDialect):
|
||||
def __init__(self,
|
||||
use_ansi=True,
|
||||
optimize_limits=False,
|
||||
use_binds_for_limits=True,
|
||||
**kwargs):
|
||||
default.DefaultDialect.__init__(self, **kwargs)
|
||||
self.use_ansi = use_ansi
|
||||
self.optimize_limits = optimize_limits
|
||||
self.use_binds_for_limits = use_binds_for_limits
|
||||
|
||||
def initialize(self, connection):
|
||||
super(OracleDialect, self).initialize(connection)
|
||||
@@ -861,6 +903,7 @@ class OracleDialect(default.DefaultDialect):
|
||||
'type': coltype,
|
||||
'nullable': nullable,
|
||||
'default': default,
|
||||
'autoincrement':default is None
|
||||
}
|
||||
if orig_colname.lower() == orig_colname:
|
||||
cdict['quote'] = True
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# oracle/cx_oracle.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -50,6 +50,8 @@ Unicode
|
||||
cx_oracle 5 fully supports Python unicode objects. SQLAlchemy will pass
|
||||
all unicode strings directly to cx_oracle, and additionally uses an output
|
||||
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
|
||||
observed that issues remain when passing Python unicodes to cx_oracle with Oracle 8.
|
||||
@@ -127,7 +129,8 @@ from sqlalchemy.engine import base
|
||||
from sqlalchemy import types as sqltypes, util, exc, processors
|
||||
from datetime import datetime
|
||||
import random
|
||||
from decimal import Decimal
|
||||
import collections
|
||||
from sqlalchemy.util.compat import decimal
|
||||
import re
|
||||
|
||||
class _OracleNumeric(sqltypes.Numeric):
|
||||
@@ -154,10 +157,10 @@ class _OracleNumeric(sqltypes.Numeric):
|
||||
def to_decimal(value):
|
||||
if value is None:
|
||||
return None
|
||||
elif isinstance(value, Decimal):
|
||||
elif isinstance(value, decimal.Decimal):
|
||||
return value
|
||||
else:
|
||||
return Decimal(fstring % value)
|
||||
return decimal.Decimal(fstring % value)
|
||||
return to_decimal
|
||||
else:
|
||||
if self.precision is None and self.scale is None:
|
||||
@@ -293,10 +296,15 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
quoted_bind_names = \
|
||||
getattr(self.compiled, '_quoted_bind_names', None)
|
||||
if quoted_bind_names:
|
||||
if not self.dialect.supports_unicode_binds:
|
||||
if not self.dialect.supports_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, toname.encode(self.dialect.encoding))
|
||||
(fromname.encode(self.dialect.encoding),
|
||||
toname.encode(self.dialect.encoding))
|
||||
for fromname, toname in
|
||||
quoted_bind_names.items()
|
||||
)
|
||||
@@ -333,7 +341,7 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
self.out_parameters[name]
|
||||
|
||||
def create_cursor(self):
|
||||
c = self._connection.connection.cursor()
|
||||
c = self._dbapi_connection.cursor()
|
||||
if self.dialect.arraysize:
|
||||
c.arraysize = self.dialect.arraysize
|
||||
|
||||
@@ -423,8 +431,8 @@ class ReturningResultProxy(base.FullyBufferedResultProxy):
|
||||
return ret
|
||||
|
||||
def _buffer_rows(self):
|
||||
return [tuple(self._returning_params["ret_%d" % i]
|
||||
for i, c in enumerate(self._returning_params))]
|
||||
return collections.deque([tuple(self._returning_params["ret_%d" % i]
|
||||
for i, c in enumerate(self._returning_params))])
|
||||
|
||||
class OracleDialect_cx_oracle(OracleDialect):
|
||||
execution_ctx_cls = OracleExecutionContext_cx_oracle
|
||||
@@ -575,15 +583,15 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
self._detect_decimal = \
|
||||
lambda value: _detect_decimal(value.replace(char, '.'))
|
||||
self._to_decimal = \
|
||||
lambda value: Decimal(value.replace(char, '.'))
|
||||
lambda value: decimal.Decimal(value.replace(char, '.'))
|
||||
|
||||
def _detect_decimal(self, value):
|
||||
if "." in value:
|
||||
return Decimal(value)
|
||||
return decimal.Decimal(value)
|
||||
else:
|
||||
return int(value)
|
||||
|
||||
_to_decimal = Decimal
|
||||
_to_decimal = decimal.Decimal
|
||||
|
||||
def on_connect(self):
|
||||
if self.cx_oracle_ver < (5,):
|
||||
@@ -679,11 +687,20 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
for x in connection.connection.version.split('.')
|
||||
)
|
||||
|
||||
def is_disconnect(self, e):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
error, = e.args
|
||||
if isinstance(e, self.dbapi.InterfaceError):
|
||||
return "not connected" in str(e)
|
||||
elif hasattr(error, 'code'):
|
||||
# ORA-00028: your session has been killed
|
||||
# ORA-03114: not connected to ORACLE
|
||||
# ORA-03113: end-of-file on communication channel
|
||||
# ORA-03135: connection lost contact
|
||||
# ORA-01033: ORACLE initialization or shutdown in progress
|
||||
# TODO: Others ?
|
||||
return error.code in (28, 3114, 3113, 3135, 1033)
|
||||
else:
|
||||
return "ORA-03114" in str(e) or "ORA-03113" in str(e)
|
||||
return False
|
||||
|
||||
def create_xid(self):
|
||||
"""create a two-phase transaction ID.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# oracle/zxjdbc.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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,6 +21,7 @@ from sqlalchemy.connectors.zxJDBC import ZxJDBCConnector
|
||||
from sqlalchemy.dialects.oracle.base import OracleCompiler, OracleDialect, OracleExecutionContext
|
||||
from sqlalchemy.engine import base, default
|
||||
from sqlalchemy.sql import expression
|
||||
import collections
|
||||
|
||||
SQLException = zxJDBC = None
|
||||
|
||||
@@ -115,7 +116,7 @@ class OracleExecutionContext_zxjdbc(OracleExecutionContext):
|
||||
return base.ResultProxy(self)
|
||||
|
||||
def create_cursor(self):
|
||||
cursor = self._connection.connection.cursor()
|
||||
cursor = self._dbapi_connection.cursor()
|
||||
cursor.datahandler = self.dialect.DataHandler(cursor.datahandler)
|
||||
return cursor
|
||||
|
||||
@@ -138,7 +139,7 @@ class ReturningResultProxy(base.FullyBufferedResultProxy):
|
||||
return ret
|
||||
|
||||
def _buffer_rows(self):
|
||||
return [self._returning_row]
|
||||
return collections.deque([self._returning_row])
|
||||
|
||||
|
||||
class ReturningParam(object):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# dialects/postgres.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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/base.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -51,6 +51,35 @@ parameter are ``READ_COMMITTED``, ``READ_UNCOMMITTED``, ``REPEATABLE_READ``,
|
||||
and ``SERIALIZABLE``. Note that the psycopg2 dialect does *not* use this
|
||||
technique and uses psycopg2-specific APIs (see that dialect for details).
|
||||
|
||||
Remote / Cross-Schema Table Introspection
|
||||
-----------------------------------------
|
||||
|
||||
Tables can be introspected from any accessible schema, including
|
||||
inter-schema foreign key relationships. However, care must be taken
|
||||
when specifying the "schema" argument for a given :class:`.Table`, when
|
||||
the given schema is also present in PostgreSQL's ``search_path`` variable
|
||||
for the current connection.
|
||||
|
||||
If a FOREIGN KEY constraint reports that the remote table's schema is within
|
||||
the current ``search_path``, the "schema" attribute of the resulting
|
||||
:class:`.Table` will be set to ``None``, unless the actual schema of the
|
||||
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
|
||||
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`.
|
||||
|
||||
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
|
||||
-------------------------
|
||||
|
||||
@@ -75,23 +104,64 @@ use the :meth:`._UpdateBase.returning` method on a per-statement basis::
|
||||
where(table.c.name=='foo')
|
||||
print result.fetchall()
|
||||
|
||||
Indexes
|
||||
-------
|
||||
|
||||
PostgreSQL supports partial indexes. To create them pass a postgresql_where
|
||||
option to the Index constructor::
|
||||
.. _postgresql_indexes:
|
||||
|
||||
Postgresql-Specific Index Options
|
||||
---------------------------------
|
||||
|
||||
Several extensions to the :class:`.Index` construct are available, specific
|
||||
to the PostgreSQL dialect.
|
||||
|
||||
Partial Indexes
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
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::
|
||||
|
||||
Index('my_index', my_table.c.id, postgresql_where=tbl.c.value > 10)
|
||||
|
||||
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)::
|
||||
|
||||
Index('my_index', my_table.c.id, my_table.c.data,
|
||||
postgresql_ops={
|
||||
'data': 'text_pattern_ops',
|
||||
'id': 'int4_ops'
|
||||
})
|
||||
|
||||
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
|
||||
of :class:`.Table`, which can be configured to be different than the actual
|
||||
name of the column as expressed in the database.
|
||||
|
||||
Index Types
|
||||
^^^^^^^^^^^^
|
||||
|
||||
PostgreSQL provides several index types: B-Tree, Hash, GiST, and GIN, as well as
|
||||
the ability for users to create their own (see
|
||||
http://www.postgresql.org/docs/8.3/static/indexes-types.html). These can be
|
||||
specified on :class:`.Index` using the ``postgresql_using`` keyword argument::
|
||||
|
||||
Index('my_index', my_table.c.data, postgresql_using='gin')
|
||||
|
||||
The value passed to the keyword argument will be simply passed through to the
|
||||
underlying CREATE INDEX command, so it *must* be a valid index type for your
|
||||
version of PostgreSQL.
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from sqlalchemy import schema as sa_schema
|
||||
from sqlalchemy import sql, schema, exc, util
|
||||
from sqlalchemy.engine import base, default, reflection
|
||||
from sqlalchemy.engine import default, reflection
|
||||
from sqlalchemy.sql import compiler, expression, util as sql_util
|
||||
from sqlalchemy.sql import operators as sql_operators
|
||||
from sqlalchemy import types as sqltypes
|
||||
|
||||
try:
|
||||
@@ -101,15 +171,30 @@ except ImportError:
|
||||
|
||||
from sqlalchemy.types import INTEGER, BIGINT, SMALLINT, VARCHAR, \
|
||||
CHAR, TEXT, FLOAT, NUMERIC, \
|
||||
DATE, BOOLEAN
|
||||
DATE, BOOLEAN, REAL
|
||||
|
||||
RESERVED_WORDS = set(
|
||||
["all", "analyse", "analyze", "and", "any", "array", "as", "asc",
|
||||
"asymmetric", "both", "case", "cast", "check", "collate", "column",
|
||||
"constraint", "create", "current_catalog", "current_date",
|
||||
"current_role", "current_time", "current_timestamp", "current_user",
|
||||
"default", "deferrable", "desc", "distinct", "do", "else", "end",
|
||||
"except", "false", "fetch", "for", "foreign", "from", "grant", "group",
|
||||
"having", "in", "initially", "intersect", "into", "leading", "limit",
|
||||
"localtime", "localtimestamp", "new", "not", "null", "off", "offset",
|
||||
"old", "on", "only", "or", "order", "placing", "primary", "references",
|
||||
"returning", "select", "session_user", "some", "symmetric", "table",
|
||||
"then", "to", "trailing", "true", "union", "unique", "user", "using",
|
||||
"variadic", "when", "where", "window", "with", "authorization",
|
||||
"between", "binary", "cross", "current_schema", "freeze", "full",
|
||||
"ilike", "inner", "is", "isnull", "join", "left", "like", "natural",
|
||||
"notnull", "outer", "over", "overlaps", "right", "similar", "verbose"
|
||||
])
|
||||
|
||||
_DECIMAL_TYPES = (1231, 1700)
|
||||
_FLOAT_TYPES = (700, 701, 1021, 1022)
|
||||
_INT_TYPES = (20, 21, 23, 26, 1005, 1007, 1016)
|
||||
|
||||
class REAL(sqltypes.Float):
|
||||
__visit_name__ = "REAL"
|
||||
|
||||
class BYTEA(sqltypes.LargeBinary):
|
||||
__visit_name__ = 'BYTEA'
|
||||
|
||||
@@ -133,6 +218,7 @@ class TIMESTAMP(sqltypes.TIMESTAMP):
|
||||
super(TIMESTAMP, self).__init__(timezone=timezone)
|
||||
self.precision = precision
|
||||
|
||||
|
||||
class TIME(sqltypes.TIME):
|
||||
def __init__(self, timezone=False, precision=None):
|
||||
super(TIME, self).__init__(timezone=timezone)
|
||||
@@ -149,9 +235,6 @@ class INTERVAL(sqltypes.TypeEngine):
|
||||
def __init__(self, precision=None):
|
||||
self.precision = precision
|
||||
|
||||
def adapt(self, impltype):
|
||||
return impltype(self.precision)
|
||||
|
||||
@classmethod
|
||||
def _adapt_from_generic_interval(cls, interval):
|
||||
return INTERVAL(precision=interval.second_precision)
|
||||
@@ -164,6 +247,15 @@ PGInterval = INTERVAL
|
||||
|
||||
class BIT(sqltypes.TypeEngine):
|
||||
__visit_name__ = 'BIT'
|
||||
def __init__(self, length=None, varying=False):
|
||||
if not varying:
|
||||
# BIT without VARYING defaults to length 1
|
||||
self.length = length or 1
|
||||
else:
|
||||
# but BIT VARYING can be unlimited-length, so no default
|
||||
self.length = length
|
||||
self.varying = varying
|
||||
|
||||
PGBit = BIT
|
||||
|
||||
class UUID(sqltypes.TypeEngine):
|
||||
@@ -224,15 +316,11 @@ class ARRAY(sqltypes.MutableType, sqltypes.Concatenable, sqltypes.TypeEngine):
|
||||
The ARRAY type may not be supported on all DBAPIs.
|
||||
It is known to work on psycopg2 and not pg8000.
|
||||
|
||||
**Note:** be sure to read the notes for
|
||||
:class:`.MutableType` regarding ORM
|
||||
performance implications. The :class:`.ARRAY` type's
|
||||
mutability can be disabled using the "mutable" flag.
|
||||
|
||||
"""
|
||||
__visit_name__ = 'ARRAY'
|
||||
|
||||
def __init__(self, item_type, mutable=True, as_tuple=False):
|
||||
def __init__(self, item_type, mutable=False, as_tuple=False):
|
||||
"""Construct an ARRAY.
|
||||
|
||||
E.g.::
|
||||
@@ -247,14 +335,25 @@ class ARRAY(sqltypes.MutableType, sqltypes.Concatenable, sqltypes.TypeEngine):
|
||||
``ARRAY(ARRAY(Integer))`` or such. The type mapping figures out on
|
||||
the fly
|
||||
|
||||
:param mutable=True: Specify whether lists passed to this
|
||||
class should be considered mutable. If so, generic copy operations
|
||||
(typically used by the ORM) will shallow-copy values.
|
||||
: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).
|
||||
|
||||
: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)
|
||||
.. note::
|
||||
|
||||
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)
|
||||
|
||||
"""
|
||||
if isinstance(item_type, ARRAY):
|
||||
@@ -284,23 +383,8 @@ class ARRAY(sqltypes.MutableType, sqltypes.Concatenable, sqltypes.TypeEngine):
|
||||
def is_mutable(self):
|
||||
return self.mutable
|
||||
|
||||
def dialect_impl(self, dialect, **kwargs):
|
||||
impl = super(ARRAY, self).dialect_impl(dialect, **kwargs)
|
||||
if impl is self:
|
||||
impl = self.__class__.__new__(self.__class__)
|
||||
impl.__dict__.update(self.__dict__)
|
||||
impl.item_type = self.item_type.dialect_impl(dialect)
|
||||
return impl
|
||||
|
||||
def adapt(self, impltype):
|
||||
return impltype(
|
||||
self.item_type,
|
||||
mutable=self.mutable,
|
||||
as_tuple=self.as_tuple
|
||||
)
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
item_proc = self.item_type.bind_processor(dialect)
|
||||
item_proc = self.item_type.dialect_impl(dialect).bind_processor(dialect)
|
||||
if item_proc:
|
||||
def convert_item(item):
|
||||
if isinstance(item, (list, tuple)):
|
||||
@@ -320,7 +404,7 @@ class ARRAY(sqltypes.MutableType, sqltypes.Concatenable, sqltypes.TypeEngine):
|
||||
return process
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
item_proc = self.item_type.result_processor(dialect, coltype)
|
||||
item_proc = self.item_type.dialect_impl(dialect).result_processor(dialect, coltype)
|
||||
if item_proc:
|
||||
def convert_item(item):
|
||||
if isinstance(item, list):
|
||||
@@ -350,8 +434,74 @@ class ARRAY(sqltypes.MutableType, sqltypes.Concatenable, sqltypes.TypeEngine):
|
||||
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
|
||||
using the :class:`.types.Enum` type on PG assuming
|
||||
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
|
||||
``CREATE TYPE`` should be emitted.
|
||||
|
||||
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
|
||||
table is being created; and additionally
|
||||
that ``DROP TYPE`` is called when the table
|
||||
is dropped. When ``False``, no check
|
||||
will be performed and no ``CREATE TYPE``
|
||||
or ``DROP TYPE`` is emitted, unless
|
||||
:meth:`~.postgresql.ENUM.create`
|
||||
or :meth:`~.postgresql.ENUM.drop`
|
||||
are called directly.
|
||||
Setting to ``False`` is helpful
|
||||
when invoking a creation scheme to a SQL file
|
||||
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)
|
||||
|
||||
"""
|
||||
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
|
||||
: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
|
||||
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
|
||||
|
||||
@@ -360,6 +510,20 @@ class ENUM(sqltypes.Enum):
|
||||
bind.execute(CreateEnumType(self))
|
||||
|
||||
def drop(self, bind=None, checkfirst=True):
|
||||
"""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
|
||||
the PG catalog will be first performed to see
|
||||
if the type actually exists before dropping.
|
||||
|
||||
"""
|
||||
if not bind.dialect.supports_native_enum:
|
||||
return
|
||||
|
||||
@@ -367,15 +531,41 @@ class ENUM(sqltypes.Enum):
|
||||
bind.dialect.has_type(bind, self.name, schema=self.schema):
|
||||
bind.execute(DropEnumType(self))
|
||||
|
||||
def _on_table_create(self, event, target, bind, **kw):
|
||||
self.create(bind=bind, checkfirst=True)
|
||||
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".
|
||||
|
||||
def _on_metadata_create(self, event, target, bind, **kw):
|
||||
if self.metadata is not None:
|
||||
self.create(bind=bind, checkfirst=True)
|
||||
"""
|
||||
if not self.create_type:
|
||||
return True
|
||||
if '_ddl_runner' in kw:
|
||||
ddl_runner = kw['_ddl_runner']
|
||||
if '_pg_enums' in ddl_runner.memo:
|
||||
pg_enums = ddl_runner.memo['_pg_enums']
|
||||
else:
|
||||
pg_enums = ddl_runner.memo['_pg_enums'] = set()
|
||||
present = self.name in pg_enums
|
||||
pg_enums.add(self.name)
|
||||
return present
|
||||
else:
|
||||
return False
|
||||
|
||||
def _on_metadata_drop(self, event, target, bind, **kw):
|
||||
self.drop(bind=bind, checkfirst=True)
|
||||
def _on_table_create(self, target, bind, checkfirst, **kw):
|
||||
if not self._check_for_name_in_memos(checkfirst, kw):
|
||||
self.create(bind=bind, checkfirst=checkfirst)
|
||||
|
||||
def _on_metadata_create(self, target, bind, checkfirst, **kw):
|
||||
if self.metadata is not None and \
|
||||
not self._check_for_name_in_memos(checkfirst, kw):
|
||||
self.create(bind=bind, checkfirst=checkfirst)
|
||||
|
||||
def _on_metadata_drop(self, target, bind, checkfirst, **kw):
|
||||
if not self._check_for_name_in_memos(checkfirst, kw):
|
||||
self.drop(bind=bind, checkfirst=checkfirst)
|
||||
|
||||
colspecs = {
|
||||
sqltypes.Interval:INTERVAL,
|
||||
@@ -397,7 +587,8 @@ ischema_names = {
|
||||
'inet': INET,
|
||||
'cidr': CIDR,
|
||||
'uuid': UUID,
|
||||
'bit':BIT,
|
||||
'bit': BIT,
|
||||
'bit varying': BIT,
|
||||
'macaddr': MACADDR,
|
||||
'double precision' : DOUBLE_PRECISION,
|
||||
'timestamp' : TIMESTAMP,
|
||||
@@ -447,19 +638,16 @@ class PGCompiler(compiler.SQLCompiler):
|
||||
return value
|
||||
|
||||
def visit_sequence(self, seq):
|
||||
if seq.optional:
|
||||
return None
|
||||
else:
|
||||
return "nextval('%s')" % self.preparer.format_sequence(seq)
|
||||
return "nextval('%s')" % self.preparer.format_sequence(seq)
|
||||
|
||||
def limit_clause(self, select):
|
||||
text = ""
|
||||
if select._limit is not None:
|
||||
text += " \n LIMIT " + str(select._limit)
|
||||
text += " \n LIMIT " + self.process(sql.literal(select._limit))
|
||||
if select._offset is not None:
|
||||
if select._limit is None:
|
||||
text += " \n LIMIT ALL"
|
||||
text += " OFFSET " + str(select._offset)
|
||||
text += " OFFSET " + self.process(sql.literal(select._offset))
|
||||
return text
|
||||
|
||||
def get_select_precolumns(self, select):
|
||||
@@ -468,11 +656,10 @@ class PGCompiler(compiler.SQLCompiler):
|
||||
return "DISTINCT "
|
||||
elif isinstance(select._distinct, (list, tuple)):
|
||||
return "DISTINCT ON (" + ', '.join(
|
||||
[(isinstance(col, basestring) and col
|
||||
or self.process(col)) for col in select._distinct]
|
||||
[self.process(col) for col in select._distinct]
|
||||
)+ ") "
|
||||
else:
|
||||
return "DISTINCT ON (" + unicode(select._distinct) + ") "
|
||||
return "DISTINCT ON (" + self.process(select._distinct) + ") "
|
||||
else:
|
||||
return ""
|
||||
|
||||
@@ -517,15 +704,18 @@ class PGCompiler(compiler.SQLCompiler):
|
||||
class PGDDLCompiler(compiler.DDLCompiler):
|
||||
def get_column_specification(self, column, **kwargs):
|
||||
colspec = self.preparer.format_column(column)
|
||||
impl_type = column.type.dialect_impl(self.dialect)
|
||||
if column.primary_key and \
|
||||
len(column.foreign_keys)==0 and \
|
||||
column.autoincrement and \
|
||||
isinstance(column.type, sqltypes.Integer) and \
|
||||
not isinstance(column.type, sqltypes.SmallInteger) and \
|
||||
(column.default is None or
|
||||
(isinstance(column.default, schema.Sequence) and
|
||||
column.default.optional)):
|
||||
if isinstance(column.type, sqltypes.BigInteger):
|
||||
column is column.table._autoincrement_column and \
|
||||
not isinstance(impl_type, sqltypes.SmallInteger) and \
|
||||
(
|
||||
column.default is None or
|
||||
(
|
||||
isinstance(column.default, schema.Sequence) and
|
||||
column.default.optional
|
||||
)
|
||||
):
|
||||
if isinstance(impl_type, sqltypes.BigInteger):
|
||||
colspec += " BIGSERIAL"
|
||||
else:
|
||||
colspec += " SERIAL"
|
||||
@@ -560,12 +750,24 @@ class PGDDLCompiler(compiler.DDLCompiler):
|
||||
text = "CREATE "
|
||||
if index.unique:
|
||||
text += "UNIQUE "
|
||||
text += "INDEX %s ON %s (%s)" \
|
||||
% (preparer.quote(
|
||||
self._index_identifier(index.name), index.quote),
|
||||
preparer.format_table(index.table),
|
||||
', '.join([preparer.format_column(c)
|
||||
for c in index.columns]))
|
||||
ops = index.kwargs.get('postgresql_ops', {})
|
||||
text += "INDEX %s ON %s " % (
|
||||
preparer.quote(
|
||||
self._index_identifier(index.name), index.quote),
|
||||
preparer.format_table(index.table)
|
||||
)
|
||||
|
||||
if 'postgresql_using' in index.kwargs:
|
||||
using = index.kwargs['postgresql_using']
|
||||
text += "USING %s " % preparer.quote(using, index.quote)
|
||||
|
||||
text += "(%s)" \
|
||||
% (
|
||||
', '.join([
|
||||
preparer.format_column(c) +
|
||||
(c.key in ops and (' ' + ops[c.key]) or '')
|
||||
for c in index.columns])
|
||||
)
|
||||
|
||||
if "postgres_where" in index.kwargs:
|
||||
whereclause = index.kwargs['postgres_where']
|
||||
@@ -639,7 +841,13 @@ class PGTypeCompiler(compiler.GenericTypeCompiler):
|
||||
return "INTERVAL"
|
||||
|
||||
def visit_BIT(self, type_):
|
||||
return "BIT"
|
||||
if type_.varying:
|
||||
compiled = "BIT VARYING"
|
||||
if type_.length is not None:
|
||||
compiled += "(%d)" % type_.length
|
||||
else:
|
||||
compiled = "BIT(%d)" % type_.length
|
||||
return compiled
|
||||
|
||||
def visit_UUID(self, type_):
|
||||
return "UUID"
|
||||
@@ -650,14 +858,14 @@ class PGTypeCompiler(compiler.GenericTypeCompiler):
|
||||
def visit_BYTEA(self, type_):
|
||||
return "BYTEA"
|
||||
|
||||
def visit_REAL(self, type_):
|
||||
return "REAL"
|
||||
|
||||
def visit_ARRAY(self, type_):
|
||||
return self.process(type_.item_type) + '[]'
|
||||
|
||||
|
||||
class PGIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
|
||||
reserved_words = RESERVED_WORDS
|
||||
|
||||
def _unquote_identifier(self, value):
|
||||
if value[0] == self.initial_quote:
|
||||
value = value[1:-1].\
|
||||
@@ -666,7 +874,7 @@ class PGIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
|
||||
def format_type(self, type_, use_schema=True):
|
||||
if not type_.name:
|
||||
raise exc.ArgumentError("Postgresql ENUM type requires a name.")
|
||||
raise exc.CompileError("Postgresql ENUM type requires a name.")
|
||||
|
||||
name = self.quote(type_.name, type_.quote)
|
||||
if not self.omit_schema and use_schema and type_.schema is not None:
|
||||
@@ -691,40 +899,44 @@ class DropEnumType(schema._CreateDropBase):
|
||||
__visit_name__ = "drop_enum_type"
|
||||
|
||||
class PGExecutionContext(default.DefaultExecutionContext):
|
||||
def fire_sequence(self, seq):
|
||||
if not seq.optional:
|
||||
return self._execute_scalar(("select nextval('%s')" % \
|
||||
self.dialect.identifier_preparer.format_sequence(seq)))
|
||||
else:
|
||||
return None
|
||||
def fire_sequence(self, seq, type_):
|
||||
return self._execute_scalar(("select nextval('%s')" % \
|
||||
self.dialect.identifier_preparer.format_sequence(seq)), type_)
|
||||
|
||||
def get_insert_default(self, column):
|
||||
if column.primary_key:
|
||||
if (isinstance(column.server_default, schema.DefaultClause) and
|
||||
column.server_default.arg is not None):
|
||||
if column.primary_key and column is column.table._autoincrement_column:
|
||||
if column.server_default and column.server_default.has_argument:
|
||||
|
||||
# pre-execute passive defaults on primary key columns
|
||||
return self._execute_scalar("select %s" %
|
||||
column.server_default.arg)
|
||||
column.server_default.arg, column.type)
|
||||
|
||||
elif column is column.table._autoincrement_column \
|
||||
and (column.default is None or
|
||||
(isinstance(column.default, schema.Sequence) and
|
||||
elif (column.default is None or
|
||||
(column.default.is_sequence and
|
||||
column.default.optional)):
|
||||
|
||||
# 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)))]
|
||||
column._postgresql_seq_name = seq_name = "%s_%s_seq" % (tab, col)
|
||||
|
||||
sch = column.table.schema
|
||||
|
||||
if sch is not None:
|
||||
exc = "select nextval('\"%s\".\"%s_%s_seq\"')" % \
|
||||
(sch, column.table.name, column.name)
|
||||
exc = "select nextval('\"%s\".\"%s\"')" % \
|
||||
(sch, seq_name)
|
||||
else:
|
||||
exc = "select nextval('\"%s_%s_seq\"')" % \
|
||||
(column.table.name, column.name)
|
||||
exc = "select nextval('\"%s\"')" % \
|
||||
(seq_name, )
|
||||
|
||||
return self._execute_scalar(exc)
|
||||
return self._execute_scalar(exc, column.type)
|
||||
|
||||
return super(PGExecutionContext, self).get_insert_default(column)
|
||||
|
||||
@@ -778,16 +990,36 @@ class PGDialect(default.DefaultDialect):
|
||||
def on_connect(self):
|
||||
if self.isolation_level is not None:
|
||||
def connect(conn):
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"SET SESSION CHARACTERISTICS AS TRANSACTION "
|
||||
"ISOLATION LEVEL %s" % self.isolation_level)
|
||||
cursor.execute("COMMIT")
|
||||
cursor.close()
|
||||
self.set_isolation_level(conn, self.isolation_level)
|
||||
return connect
|
||||
else:
|
||||
return None
|
||||
|
||||
_isolation_lookup = set(['SERIALIZABLE',
|
||||
'READ UNCOMMITTED', 'READ COMMITTED', 'REPEATABLE READ'])
|
||||
|
||||
def set_isolation_level(self, connection, level):
|
||||
level = level.replace('_', ' ')
|
||||
if level not in self._isolation_lookup:
|
||||
raise exc.ArgumentError(
|
||||
"Invalid value '%s' for isolation_level. "
|
||||
"Valid isolation levels for %s are %s" %
|
||||
(level, self.name, ", ".join(self._isolation_lookup))
|
||||
)
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(
|
||||
"SET SESSION CHARACTERISTICS AS TRANSACTION "
|
||||
"ISOLATION LEVEL %s" % level)
|
||||
cursor.execute("COMMIT")
|
||||
cursor.close()
|
||||
|
||||
def get_isolation_level(self, connection):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('show transaction isolation level')
|
||||
val = cursor.fetchone()[0]
|
||||
cursor.close()
|
||||
return val.upper()
|
||||
|
||||
def do_begin_twophase(self, connection, xid):
|
||||
self.do_begin(connection.connection)
|
||||
|
||||
@@ -828,6 +1060,19 @@ class PGDialect(default.DefaultDialect):
|
||||
def _get_default_schema_name(self, connection):
|
||||
return connection.scalar("select current_schema()")
|
||||
|
||||
def has_schema(self, connection, schema):
|
||||
cursor = connection.execute(
|
||||
sql.text(
|
||||
"select nspname from pg_namespace where lower(nspname)=:schema",
|
||||
bindparams=[
|
||||
sql.bindparam(
|
||||
'schema', unicode(schema.lower()),
|
||||
type_=sqltypes.Unicode)]
|
||||
)
|
||||
)
|
||||
|
||||
return bool(cursor.first())
|
||||
|
||||
def has_table(self, connection, table_name, schema=None):
|
||||
# seems like case gets folded in pg_class...
|
||||
if schema is None:
|
||||
@@ -835,9 +1080,9 @@ class PGDialect(default.DefaultDialect):
|
||||
sql.text(
|
||||
"select relname from pg_class c join pg_namespace n on "
|
||||
"n.oid=c.relnamespace where n.nspname=current_schema() and "
|
||||
"lower(relname)=:name",
|
||||
"relname=:name",
|
||||
bindparams=[
|
||||
sql.bindparam('name', unicode(table_name.lower()),
|
||||
sql.bindparam('name', unicode(table_name),
|
||||
type_=sqltypes.Unicode)]
|
||||
)
|
||||
)
|
||||
@@ -846,10 +1091,10 @@ class PGDialect(default.DefaultDialect):
|
||||
sql.text(
|
||||
"select relname from pg_class c join pg_namespace n on "
|
||||
"n.oid=c.relnamespace where n.nspname=:schema and "
|
||||
"lower(relname)=:name",
|
||||
"relname=:name",
|
||||
bindparams=[
|
||||
sql.bindparam('name',
|
||||
unicode(table_name.lower()), type_=sqltypes.Unicode),
|
||||
unicode(table_name), type_=sqltypes.Unicode),
|
||||
sql.bindparam('schema',
|
||||
unicode(schema), type_=sqltypes.Unicode)]
|
||||
)
|
||||
@@ -863,9 +1108,9 @@ class PGDialect(default.DefaultDialect):
|
||||
"SELECT relname FROM pg_class c join pg_namespace n on "
|
||||
"n.oid=c.relnamespace where relkind='S' and "
|
||||
"n.nspname=current_schema() "
|
||||
"and lower(relname)=:name",
|
||||
"and relname=:name",
|
||||
bindparams=[
|
||||
sql.bindparam('name', unicode(sequence_name.lower()),
|
||||
sql.bindparam('name', unicode(sequence_name),
|
||||
type_=sqltypes.Unicode)
|
||||
]
|
||||
)
|
||||
@@ -875,9 +1120,9 @@ class PGDialect(default.DefaultDialect):
|
||||
sql.text(
|
||||
"SELECT relname FROM pg_class c join pg_namespace n on "
|
||||
"n.oid=c.relnamespace where relkind='S' and "
|
||||
"n.nspname=:schema and lower(relname)=:name",
|
||||
"n.nspname=:schema and relname=:name",
|
||||
bindparams=[
|
||||
sql.bindparam('name', unicode(sequence_name.lower()),
|
||||
sql.bindparam('name', unicode(sequence_name),
|
||||
type_=sqltypes.Unicode),
|
||||
sql.bindparam('schema',
|
||||
unicode(schema), type_=sqltypes.Unicode)
|
||||
@@ -1084,6 +1329,7 @@ class PGDialect(default.DefaultDialect):
|
||||
if charlen:
|
||||
charlen = charlen.group(1)
|
||||
kwargs = {}
|
||||
args = None
|
||||
|
||||
if attype == 'numeric':
|
||||
if charlen:
|
||||
@@ -1094,7 +1340,7 @@ class PGDialect(default.DefaultDialect):
|
||||
elif attype == 'double precision':
|
||||
args = (53, )
|
||||
elif attype == 'integer':
|
||||
args = (32, 0)
|
||||
args = ()
|
||||
elif attype in ('timestamp with time zone',
|
||||
'time with time zone'):
|
||||
kwargs['timezone'] = True
|
||||
@@ -1107,6 +1353,12 @@ class PGDialect(default.DefaultDialect):
|
||||
if charlen:
|
||||
kwargs['precision'] = int(charlen)
|
||||
args = ()
|
||||
elif attype == 'bit varying':
|
||||
kwargs['varying'] = True
|
||||
if charlen:
|
||||
args = (int(charlen),)
|
||||
else:
|
||||
args = ()
|
||||
elif attype in ('interval','interval year to month',
|
||||
'interval day to second'):
|
||||
if charlen:
|
||||
@@ -1177,13 +1429,19 @@ class PGDialect(default.DefaultDialect):
|
||||
def get_primary_keys(self, connection, table_name, schema=None, **kw):
|
||||
table_oid = self.get_table_oid(connection, table_name, schema,
|
||||
info_cache=kw.get('info_cache'))
|
||||
|
||||
PK_SQL = """
|
||||
SELECT attname FROM pg_attribute
|
||||
WHERE attrelid = (
|
||||
SELECT indexrelid FROM pg_index i
|
||||
WHERE i.indrelid = :table_oid
|
||||
AND i.indisprimary = 't')
|
||||
ORDER BY attnum
|
||||
SELECT a.attname
|
||||
FROM
|
||||
pg_class t
|
||||
join pg_index ix on t.oid = ix.indrelid
|
||||
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})
|
||||
c = connection.execute(t, table_oid=table_oid)
|
||||
@@ -1217,10 +1475,19 @@ class PGDialect(default.DefaultDialect):
|
||||
preparer = self.identifier_preparer
|
||||
table_oid = self.get_table_oid(connection, table_name, schema,
|
||||
info_cache=kw.get('info_cache'))
|
||||
|
||||
FK_SQL = """
|
||||
SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef
|
||||
FROM pg_catalog.pg_constraint r
|
||||
WHERE r.conrelid = :table AND r.contype = 'f'
|
||||
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
|
||||
r.contype = 'f' AND
|
||||
c.oid = confrelid AND
|
||||
n.oid = c.relnamespace
|
||||
ORDER BY 1
|
||||
"""
|
||||
|
||||
@@ -1229,20 +1496,24 @@ class PGDialect(default.DefaultDialect):
|
||||
'condef':sqltypes.Unicode})
|
||||
c = connection.execute(t, table=table_oid)
|
||||
fkeys = []
|
||||
for conname, condef in c.fetchall():
|
||||
for conname, condef, conschema in c.fetchall():
|
||||
m = re.search('FOREIGN KEY \((.*?)\) REFERENCES '
|
||||
'(?:(.*?)\.)?(.*?)\((.*?)\)', condef).groups()
|
||||
constrained_columns, referred_schema, \
|
||||
referred_table, referred_columns = m
|
||||
constrained_columns = [preparer._unquote_identifier(x)
|
||||
for x in re.split(r'\s*,\s*', constrained_columns)]
|
||||
|
||||
if referred_schema:
|
||||
referred_schema =\
|
||||
preparer._unquote_identifier(referred_schema)
|
||||
elif schema is not None and schema == self.default_schema_name:
|
||||
# no schema (i.e. its the default schema), and the table we're
|
||||
# reflecting has the default schema explicit, then use that.
|
||||
# i.e. try to use the user's conventions
|
||||
elif schema is not None and schema == conschema:
|
||||
# no schema was returned by pg_get_constraintdef(). This
|
||||
# means the schema is in the search path. We will leave
|
||||
# it as None, unless the actual schema, which we pull out
|
||||
# from pg_namespace even though pg_get_constraintdef() doesn't
|
||||
# want to give it to us, matches that of the referencing table,
|
||||
# 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)
|
||||
@@ -1261,16 +1532,31 @@ class PGDialect(default.DefaultDialect):
|
||||
def get_indexes(self, connection, table_name, schema, **kw):
|
||||
table_oid = self.get_table_oid(connection, table_name, schema,
|
||||
info_cache=kw.get('info_cache'))
|
||||
|
||||
IDX_SQL = """
|
||||
SELECT c.relname, i.indisunique, i.indexprs, i.indpred,
|
||||
a.attname
|
||||
FROM pg_index i, pg_class c, pg_attribute a
|
||||
WHERE i.indrelid = :table_oid AND i.indexrelid = c.oid
|
||||
AND a.attrelid = i.indexrelid AND i.indisprimary = 'f'
|
||||
ORDER BY c.relname, a.attnum
|
||||
SELECT
|
||||
i.relname as relname,
|
||||
ix.indisunique, ix.indexprs, ix.indpred,
|
||||
a.attname
|
||||
FROM
|
||||
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
|
||||
on t.oid=a.attrelid and a.attnum=ANY(ix.indkey)
|
||||
WHERE
|
||||
t.relkind = 'r'
|
||||
and t.oid = :table_oid
|
||||
and ix.indisprimary = 'f'
|
||||
ORDER BY
|
||||
t.relname,
|
||||
i.relname
|
||||
"""
|
||||
|
||||
t = sql.text(IDX_SQL, typemap={'attname':sqltypes.Unicode})
|
||||
c = connection.execute(t, table_oid=table_oid)
|
||||
|
||||
index_names = {}
|
||||
indexes = []
|
||||
sv_idx_name = None
|
||||
@@ -1296,7 +1582,8 @@ class PGDialect(default.DefaultDialect):
|
||||
indexes.append(index_d)
|
||||
index_names[idx_name] = index_d
|
||||
index_d['name'] = idx_name
|
||||
index_d['column_names'].append(col)
|
||||
if col is not None:
|
||||
index_d['column_names'].append(col)
|
||||
index_d['unique'] = unique
|
||||
return indexes
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/pg8000.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -27,10 +27,8 @@ Passing data from/to the Interval type is not supported as of
|
||||
yet.
|
||||
|
||||
"""
|
||||
import decimal
|
||||
|
||||
from sqlalchemy.engine import default
|
||||
from sqlalchemy import util, exc
|
||||
from sqlalchemy.util.compat import decimal
|
||||
from sqlalchemy import processors
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.dialects.postgresql.base import PGDialect, \
|
||||
@@ -58,6 +56,11 @@ class _PGNumeric(sqltypes.Numeric):
|
||||
raise exc.InvalidRequestError(
|
||||
"Unknown PG numeric type: %d" % coltype)
|
||||
|
||||
|
||||
class _PGNumericNoBind(_PGNumeric):
|
||||
def bind_processor(self, dialect):
|
||||
return None
|
||||
|
||||
class PGExecutionContext_pg8000(PGExecutionContext):
|
||||
pass
|
||||
|
||||
@@ -91,11 +94,13 @@ class PGDialect_pg8000(PGDialect):
|
||||
execution_ctx_cls = PGExecutionContext_pg8000
|
||||
statement_compiler = PGCompiler_pg8000
|
||||
preparer = PGIdentifierPreparer_pg8000
|
||||
description_encoding = 'use_encoding'
|
||||
|
||||
colspecs = util.update_copy(
|
||||
PGDialect.colspecs,
|
||||
{
|
||||
sqltypes.Numeric : _PGNumeric,
|
||||
sqltypes.Numeric : _PGNumericNoBind,
|
||||
sqltypes.Float : _PGNumeric
|
||||
}
|
||||
)
|
||||
|
||||
@@ -110,7 +115,7 @@ class PGDialect_pg8000(PGDialect):
|
||||
opts.update(url.query)
|
||||
return ([], opts)
|
||||
|
||||
def is_disconnect(self, e):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
return "connection is closed" in str(e)
|
||||
|
||||
dialect = PGDialect_pg8000
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/psycopg2.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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,31 +9,12 @@
|
||||
Driver
|
||||
------
|
||||
|
||||
The psycopg2 driver is supported, available at http://pypi.python.org/pypi/psycopg2/ .
|
||||
The psycopg2 driver is available at http://pypi.python.org/pypi/psycopg2/ .
|
||||
The dialect has several behaviors which are specifically tailored towards compatibility
|
||||
with this module.
|
||||
|
||||
Note that psycopg1 is **not** supported.
|
||||
|
||||
Unicode
|
||||
-------
|
||||
|
||||
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. Note that this setting requires that the PG client encoding be set to
|
||||
one which can accomodate the kind of character data being passed - typically
|
||||
``utf-8``. If the Postgresql database is configured for ``SQL_ASCII``
|
||||
encoding, which is often the default for PG installations, it may be necessary
|
||||
for non-ascii strings to be encoded into a specific encoding before being
|
||||
passed to the DBAPI. If changing the database's client encoding setting is not
|
||||
an option, specify ``use_native_unicode=False`` as a keyword argument to
|
||||
``create_engine()``, and take note of the ``encoding`` setting as well, which
|
||||
also defaults to ``utf-8``. Note that disabling "native unicode" mode has a
|
||||
slight performance penalty, as SQLAlchemy now must translate unicode strings
|
||||
to/from an encoding such as utf-8, a task that is handled more efficiently
|
||||
within the Psycopg2 driver natively.
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
@@ -46,15 +27,71 @@ psycopg2-specific keyword arguments which are accepted by
|
||||
* *server_side_cursors* - Enable the usage of "server side cursors" for SQL
|
||||
statements which support this feature. What this essentially means from a
|
||||
psycopg2 point of view is that the cursor is created using a name, e.g.
|
||||
`connection.cursor('some name')`, which has the effect that result rows are
|
||||
``connection.cursor('some name')``, which has the effect that result rows are
|
||||
not immediately pre-fetched and buffered after statement execution, but are
|
||||
instead left on the server and only retrieved as needed. SQLAlchemy's
|
||||
:class:`~sqlalchemy.engine.base.ResultProxy` uses special row-buffering
|
||||
behavior when this feature is enabled, such that groups of 100 rows at a
|
||||
time are fetched over the wire to reduce conversational overhead.
|
||||
Note that the ``stream_results=True`` execution option is a more targeted
|
||||
way of enabling this mode on a per-execution basis.
|
||||
* *use_native_unicode* - Enable the usage of Psycopg2 "native unicode" mode
|
||||
per connection. True by default.
|
||||
|
||||
Per-Statement/Connection Execution Options
|
||||
-------------------------------------------
|
||||
|
||||
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
|
||||
: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``.
|
||||
* stream_results - Enable or disable usage of server side cursors.
|
||||
If ``None`` or not set, the ``server_side_cursors`` option of the :class:`.Engine` is used.
|
||||
|
||||
Unicode
|
||||
-------
|
||||
|
||||
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``.
|
||||
Typically, this can be changed to ``utf-8``, as a more useful default::
|
||||
|
||||
#client_encoding = sql_ascii # actually, defaults to database
|
||||
# encoding
|
||||
client_encoding = utf8
|
||||
|
||||
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
|
||||
: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.
|
||||
|
||||
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``
|
||||
to :func:`.create_engine` will disable usage of ``psycopg2.extensions.UNICODE``.
|
||||
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
|
||||
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
|
||||
Python 3; in modern usage psycopg2 should be relied upon to handle unicode.
|
||||
|
||||
Transactions
|
||||
------------
|
||||
|
||||
@@ -79,27 +116,16 @@ The psycopg2 dialect will log Postgresql NOTICE messages via the
|
||||
logging.getLogger('sqlalchemy.dialects.postgresql').setLevel(logging.INFO)
|
||||
|
||||
|
||||
Per-Statement Execution Options
|
||||
-------------------------------
|
||||
|
||||
The following per-statement execution options are respected:
|
||||
|
||||
* *stream_results* - Enable or disable usage of server side cursors for the SELECT-statement.
|
||||
If *None* or not set, the *server_side_cursors* option of the connection is used. If
|
||||
auto-commit is enabled, the option is ignored.
|
||||
|
||||
"""
|
||||
|
||||
import random
|
||||
import re
|
||||
import decimal
|
||||
import logging
|
||||
|
||||
from sqlalchemy import util, exc
|
||||
from sqlalchemy.util.compat import decimal
|
||||
from sqlalchemy import processors
|
||||
from sqlalchemy.engine import base, default
|
||||
from sqlalchemy.engine import base
|
||||
from sqlalchemy.sql import expression
|
||||
from sqlalchemy.sql import operators as sql_operators
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.dialects.postgresql.base import PGDialect, PGCompiler, \
|
||||
PGIdentifierPreparer, PGExecutionContext, \
|
||||
@@ -137,17 +163,21 @@ class _PGNumeric(sqltypes.Numeric):
|
||||
class _PGEnum(ENUM):
|
||||
def __init__(self, *arg, **kw):
|
||||
super(_PGEnum, self).__init__(*arg, **kw)
|
||||
# Py2K
|
||||
if self.convert_unicode:
|
||||
self.convert_unicode = "force"
|
||||
# end Py2K
|
||||
|
||||
class _PGArray(ARRAY):
|
||||
def __init__(self, *arg, **kw):
|
||||
super(_PGArray, self).__init__(*arg, **kw)
|
||||
# Py2K
|
||||
# FIXME: this check won't work for setups that
|
||||
# have convert_unicode only on their create_engine().
|
||||
if isinstance(self.item_type, sqltypes.String) and \
|
||||
self.item_type.convert_unicode:
|
||||
self.item_type.convert_unicode = "force"
|
||||
# end Py2K
|
||||
|
||||
# When we're handed literal SQL, ensure it's a SELECT-query. Since
|
||||
# 8.3, combining cursors and "FOR UPDATE" has been fine.
|
||||
@@ -155,6 +185,8 @@ SERVER_SIDE_CURSOR_RE = re.compile(
|
||||
r'\s*SELECT',
|
||||
re.I | re.UNICODE)
|
||||
|
||||
_server_side_id = util.counter()
|
||||
|
||||
class PGExecutionContext_psycopg2(PGExecutionContext):
|
||||
def create_cursor(self):
|
||||
# TODO: coverage for server side cursors + select.for_update()
|
||||
@@ -177,12 +209,13 @@ class PGExecutionContext_psycopg2(PGExecutionContext):
|
||||
if is_server_side:
|
||||
# use server-side cursors:
|
||||
# http://lists.initd.org/pipermail/psycopg/2007-January/005251.html
|
||||
ident = "c_%s_%s" % (hex(id(self))[2:], hex(random.randint(0, 65535))[2:])
|
||||
return self._connection.connection.cursor(ident)
|
||||
ident = "c_%s_%s" % (hex(id(self))[2:], hex(_server_side_id())[2:])
|
||||
return self._dbapi_connection.cursor(ident)
|
||||
else:
|
||||
return self._connection.connection.cursor()
|
||||
return self._dbapi_connection.cursor()
|
||||
|
||||
def get_result_proxy(self):
|
||||
# TODO: ouch
|
||||
if logger.isEnabledFor(logging.INFO):
|
||||
self._log_notices(self.cursor)
|
||||
|
||||
@@ -215,12 +248,15 @@ class PGIdentifierPreparer_psycopg2(PGIdentifierPreparer):
|
||||
|
||||
class PGDialect_psycopg2(PGDialect):
|
||||
driver = 'psycopg2'
|
||||
# Py2K
|
||||
supports_unicode_statements = False
|
||||
# end Py2K
|
||||
default_paramstyle = 'pyformat'
|
||||
supports_sane_multi_rowcount = False
|
||||
execution_ctx_cls = PGExecutionContext_psycopg2
|
||||
statement_compiler = PGCompiler_psycopg2
|
||||
preparer = PGIdentifierPreparer_psycopg2
|
||||
psycopg2_version = (0, 0)
|
||||
|
||||
colspecs = util.update_copy(
|
||||
PGDialect.colspecs,
|
||||
@@ -232,46 +268,74 @@ class PGDialect_psycopg2(PGDialect):
|
||||
}
|
||||
)
|
||||
|
||||
def __init__(self, server_side_cursors=False, use_native_unicode=True, **kwargs):
|
||||
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
|
||||
self.use_native_unicode = use_native_unicode
|
||||
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+))?',
|
||||
self.dbapi.__version__)
|
||||
if m:
|
||||
self.psycopg2_version = tuple(
|
||||
int(x)
|
||||
for x in m.group(1, 2, 3)
|
||||
if x is not None)
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
psycopg = __import__('psycopg2')
|
||||
return psycopg
|
||||
|
||||
def on_connect(self):
|
||||
if self.isolation_level is not None:
|
||||
extensions = __import__('psycopg2.extensions').extensions
|
||||
isol = {
|
||||
'READ_COMMITTED':extensions.ISOLATION_LEVEL_READ_COMMITTED,
|
||||
'READ_UNCOMMITTED':extensions.ISOLATION_LEVEL_READ_UNCOMMITTED,
|
||||
'REPEATABLE_READ':extensions.ISOLATION_LEVEL_REPEATABLE_READ,
|
||||
@util.memoized_property
|
||||
def _isolation_lookup(self):
|
||||
extensions = __import__('psycopg2.extensions').extensions
|
||||
return {
|
||||
'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
|
||||
}
|
||||
|
||||
}
|
||||
def base_on_connect(conn):
|
||||
try:
|
||||
conn.set_isolation_level(isol[self.isolation_level])
|
||||
except:
|
||||
raise exc.InvalidRequestError(
|
||||
"Invalid isolation level: '%s'" %
|
||||
self.isolation_level)
|
||||
else:
|
||||
base_on_connect = None
|
||||
def set_isolation_level(self, connection, level):
|
||||
try:
|
||||
level = self._isolation_lookup[level.replace('_', ' ')]
|
||||
except KeyError:
|
||||
raise exc.ArgumentError(
|
||||
"Invalid value '%s' for isolation_level. "
|
||||
"Valid isolation levels for %s are %s" %
|
||||
(level, self.name, ", ".join(self._isolation_lookup))
|
||||
)
|
||||
|
||||
connection.set_isolation_level(level)
|
||||
|
||||
def on_connect(self):
|
||||
fns = []
|
||||
if self.client_encoding is not None:
|
||||
def on_connect(conn):
|
||||
conn.set_client_encoding(self.client_encoding)
|
||||
fns.append(on_connect)
|
||||
|
||||
if self.isolation_level is not None:
|
||||
def on_connect(conn):
|
||||
self.set_isolation_level(conn, self.isolation_level)
|
||||
fns.append(on_connect)
|
||||
|
||||
if self.dbapi and self.use_native_unicode:
|
||||
extensions = __import__('psycopg2.extensions').extensions
|
||||
def connect(conn):
|
||||
def on_connect(conn):
|
||||
extensions.register_type(extensions.UNICODE, conn)
|
||||
if base_on_connect:
|
||||
base_on_connect(conn)
|
||||
return connect
|
||||
fns.append(on_connect)
|
||||
|
||||
if fns:
|
||||
def on_connect(conn):
|
||||
for fn in fns:
|
||||
fn(conn)
|
||||
return on_connect
|
||||
else:
|
||||
return base_on_connect
|
||||
return None
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user')
|
||||
@@ -280,13 +344,21 @@ class PGDialect_psycopg2(PGDialect):
|
||||
opts.update(url.query)
|
||||
return ([], opts)
|
||||
|
||||
def is_disconnect(self, e):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, self.dbapi.OperationalError):
|
||||
return 'closed the connection' in str(e) or 'connection not open' in str(e)
|
||||
# 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 \
|
||||
'connection not open' in str(e) or \
|
||||
'could not receive data from server' in str(e)
|
||||
elif isinstance(e, self.dbapi.InterfaceError):
|
||||
return 'connection already closed' in str(e) or 'cursor already closed' in str(e)
|
||||
# psycopg2 client errors, psycopg2/conenction.h, psycopg2/cursor.h
|
||||
return 'connection already closed' in str(e) or \
|
||||
'cursor already closed' in str(e)
|
||||
elif isinstance(e, self.dbapi.ProgrammingError):
|
||||
# yes, it really says "losed", not "closed"
|
||||
# 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:
|
||||
return False
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/pypostgresql.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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,12 +9,10 @@
|
||||
Connecting
|
||||
----------
|
||||
|
||||
URLs are of the form ``postgresql+pypostgresql://user@password@host:port/dbname[?key=value&key=value...]``.
|
||||
URLs are of the form ``postgresql+pypostgresql://user:password@host:port/dbname[?key=value&key=value...]``.
|
||||
|
||||
|
||||
"""
|
||||
from sqlalchemy.engine import default
|
||||
import decimal
|
||||
from sqlalchemy import util
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.dialects.postgresql.base import PGDialect, PGExecutionContext
|
||||
@@ -69,7 +67,7 @@ class PGDialect_pypostgresql(PGDialect):
|
||||
opts.update(url.query)
|
||||
return ([], opts)
|
||||
|
||||
def is_disconnect(self, e):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
return "connection is closed" in str(e)
|
||||
|
||||
dialect = PGDialect_pypostgresql
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/zxjdbc.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -13,12 +13,29 @@ The official Postgresql JDBC driver is at http://jdbc.postgresql.org/.
|
||||
|
||||
"""
|
||||
from sqlalchemy.connectors.zxJDBC import ZxJDBCConnector
|
||||
from sqlalchemy.dialects.postgresql.base import PGDialect
|
||||
from sqlalchemy.dialects.postgresql.base import PGDialect, PGExecutionContext
|
||||
|
||||
class PGExecutionContext_zxjdbc(PGExecutionContext):
|
||||
|
||||
def create_cursor(self):
|
||||
cursor = self._dbapi_connection.cursor()
|
||||
cursor.datahandler = self.dialect.DataHandler(cursor.datahandler)
|
||||
return cursor
|
||||
|
||||
|
||||
class PGDialect_zxjdbc(ZxJDBCConnector, PGDialect):
|
||||
jdbc_db_name = 'postgresql'
|
||||
jdbc_driver_name = 'org.postgresql.Driver'
|
||||
|
||||
execution_ctx_cls = PGExecutionContext_zxjdbc
|
||||
|
||||
supports_native_decimal = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PGDialect_zxjdbc, self).__init__(*args, **kwargs)
|
||||
from com.ziclix.python.sql.handler import PostgresqlDataHandler
|
||||
self.DataHandler = PostgresqlDataHandler
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
return tuple(int(x) for x in connection.connection.dbversion.split('.'))
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sqlite/__init__.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -11,10 +11,10 @@ base.dialect = pysqlite.dialect
|
||||
|
||||
|
||||
from sqlalchemy.dialects.sqlite.base import \
|
||||
BLOB, BOOLEAN, CHAR, DATE, DATETIME, DECIMAL, FLOAT, INTEGER,\
|
||||
BLOB, BOOLEAN, CHAR, DATE, DATETIME, DECIMAL, FLOAT, INTEGER, REAL,\
|
||||
NUMERIC, SMALLINT, TEXT, TIME, TIMESTAMP, VARCHAR, dialect
|
||||
|
||||
__all__ = (
|
||||
'BLOB', 'BOOLEAN', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'FLOAT', 'INTEGER',
|
||||
'NUMERIC', 'SMALLINT', 'TEXT', 'TIME', 'TIMESTAMP', 'VARCHAR', 'dialect'
|
||||
'NUMERIC', 'SMALLINT', 'TEXT', 'TIME', 'TIMESTAMP', 'VARCHAR', 'dialect', 'REAL'
|
||||
)
|
||||
@@ -1,5 +1,5 @@
|
||||
# sqlite/base.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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 @@ SQLite does not have built-in DATE, TIME, or DATETIME types, and pysqlite does n
|
||||
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.
|
||||
The implementation classes are :class:`DATETIME`, :class:`DATE` and :class:`TIME`.
|
||||
The implementation classes are :class:`~.sqlite.DATETIME`, :class:`~.sqlite.DATE` and :class:`~.sqlite.TIME`.
|
||||
These types represent dates and times as ISO formatted strings, which also nicely
|
||||
support ordering. There's no reliance on typical "libc" internals for these functions
|
||||
so historical dates are fully supported.
|
||||
@@ -46,41 +46,74 @@ to the Table construct::
|
||||
Transaction Isolation Level
|
||||
---------------------------
|
||||
|
||||
:func:`create_engine` accepts an ``isolation_level`` parameter which results in
|
||||
: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.
|
||||
|
||||
"""
|
||||
|
||||
import datetime, re, time
|
||||
import datetime, re
|
||||
|
||||
from sqlalchemy import schema as sa_schema
|
||||
from sqlalchemy import sql, exc, pool, DefaultClause
|
||||
from sqlalchemy.engine import default
|
||||
from sqlalchemy.engine import reflection
|
||||
from sqlalchemy import sql, exc
|
||||
from sqlalchemy.engine import default, base, reflection
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy import util
|
||||
from sqlalchemy.sql import compiler, functions as sql_functions
|
||||
from sqlalchemy.util import NoneType
|
||||
from sqlalchemy.sql import compiler
|
||||
from sqlalchemy import processors
|
||||
|
||||
from sqlalchemy.types import BLOB, BOOLEAN, CHAR, DATE, DATETIME, DECIMAL,\
|
||||
FLOAT, INTEGER, NUMERIC, SMALLINT, TEXT, TIME,\
|
||||
TIMESTAMP, VARCHAR
|
||||
|
||||
FLOAT, REAL, INTEGER, NUMERIC, SMALLINT, TEXT, TIME, TIMESTAMP, VARCHAR
|
||||
|
||||
class _DateTimeMixin(object):
|
||||
_reg = None
|
||||
_storage_format = None
|
||||
|
||||
def __init__(self, storage_format=None, regexp=None, **kwargs):
|
||||
def __init__(self, storage_format=None, regexp=None, **kw):
|
||||
super(_DateTimeMixin, self).__init__(**kw)
|
||||
if regexp is not None:
|
||||
self._reg = re.compile(regexp)
|
||||
if storage_format is not None:
|
||||
self._storage_format = storage_format
|
||||
|
||||
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,
|
||||
value.month, value.day,
|
||||
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
|
||||
``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
|
||||
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
|
||||
the Python datetime() constructor via ``*map(int,
|
||||
match_obj.groups(0))``.
|
||||
"""
|
||||
|
||||
_storage_format = "%04d-%02d-%02d %02d:%02d:%02d.%06d"
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
@@ -110,6 +143,38 @@ class DATETIME(_DateTimeMixin, sqltypes.DateTime):
|
||||
return processors.str_to_datetime
|
||||
|
||||
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
|
||||
``storage_format`` and ``regexp`` parameters, such as::
|
||||
|
||||
import re
|
||||
from sqlalchemy.dialects.sqlite import DATE
|
||||
|
||||
d = DATE(
|
||||
storage_format="%02d/%02d/%02d",
|
||||
regexp=re.compile("(\d+)/(\d+)/(\d+)")
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be appled 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
|
||||
the Python date() constructor via ``*map(int,
|
||||
match_obj.groups(0))``.
|
||||
|
||||
"""
|
||||
|
||||
_storage_format = "%04d-%02d-%02d"
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
@@ -133,6 +198,40 @@ class DATE(_DateTimeMixin, sqltypes.Date):
|
||||
return processors.str_to_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,
|
||||
value.second,
|
||||
value.microsecond)
|
||||
|
||||
e.g.::
|
||||
|
||||
12:05:57.10558
|
||||
|
||||
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
|
||||
|
||||
t = TIME(
|
||||
storage_format="%02d-%02d-%02d-%06d",
|
||||
regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?")
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be appled
|
||||
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
|
||||
the Python time() constructor via ``*map(int,
|
||||
match_obj.groups(0))``.
|
||||
|
||||
"""
|
||||
|
||||
_storage_format = "%02d:%02d:%02d.%06d"
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
@@ -174,7 +273,7 @@ ischema_names = {
|
||||
'INT': sqltypes.INTEGER,
|
||||
'INTEGER': sqltypes.INTEGER,
|
||||
'NUMERIC': sqltypes.NUMERIC,
|
||||
'REAL': sqltypes.Numeric,
|
||||
'REAL': sqltypes.REAL,
|
||||
'SMALLINT': sqltypes.SMALLINT,
|
||||
'TEXT': sqltypes.TEXT,
|
||||
'TIME': sqltypes.TIME,
|
||||
@@ -203,6 +302,12 @@ class SQLiteCompiler(compiler.SQLCompiler):
|
||||
def visit_now_func(self, fn, **kw):
|
||||
return "CURRENT_TIMESTAMP"
|
||||
|
||||
def visit_true(self, expr, **kw):
|
||||
return '1'
|
||||
|
||||
def visit_false(self, expr, **kw):
|
||||
return '0'
|
||||
|
||||
def visit_char_length_func(self, fn, **kw):
|
||||
return "length%s" % self.function_argspec(fn)
|
||||
|
||||
@@ -217,19 +322,19 @@ class SQLiteCompiler(compiler.SQLCompiler):
|
||||
return "CAST(STRFTIME('%s', %s) AS INTEGER)" % (
|
||||
self.extract_map[extract.field], self.process(extract.expr, **kw))
|
||||
except KeyError:
|
||||
raise exc.ArgumentError(
|
||||
raise exc.CompileError(
|
||||
"%s is not a valid extract argument." % extract.field)
|
||||
|
||||
def limit_clause(self, select):
|
||||
text = ""
|
||||
if select._limit is not None:
|
||||
text += " \n LIMIT " + str(select._limit)
|
||||
text += "\n LIMIT " + self.process(sql.literal(select._limit))
|
||||
if select._offset is not None:
|
||||
if select._limit is None:
|
||||
text += " \n LIMIT -1"
|
||||
text += " OFFSET " + str(select._offset)
|
||||
text += "\n LIMIT " + self.process(sql.literal(-1))
|
||||
text += " OFFSET " + self.process(sql.literal(select._offset))
|
||||
else:
|
||||
text += " OFFSET 0"
|
||||
text += " OFFSET " + self.process(sql.literal(0))
|
||||
return text
|
||||
|
||||
def for_update_clause(self, select):
|
||||
@@ -251,7 +356,7 @@ class SQLiteDDLCompiler(compiler.DDLCompiler):
|
||||
if column.primary_key and \
|
||||
column.table.kwargs.get('sqlite_autoincrement', False) and \
|
||||
len(column.table.primary_key.columns) == 1 and \
|
||||
isinstance(column.type, sqltypes.Integer) and \
|
||||
issubclass(column.type._type_affinity, sqltypes.Integer) and \
|
||||
not column.foreign_keys:
|
||||
colspec += " PRIMARY KEY AUTOINCREMENT"
|
||||
|
||||
@@ -265,7 +370,7 @@ class SQLiteDDLCompiler(compiler.DDLCompiler):
|
||||
c = list(constraint)[0]
|
||||
if c.primary_key and \
|
||||
c.table.kwargs.get('sqlite_autoincrement', False) and \
|
||||
isinstance(c.type, sqltypes.Integer) and \
|
||||
issubclass(c.type._type_affinity, sqltypes.Integer) and \
|
||||
not c.foreign_keys:
|
||||
return None
|
||||
|
||||
@@ -336,6 +441,20 @@ class SQLiteIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
result = self.quote_schema(index.table.schema, index.table.quote_schema) + "." + result
|
||||
return result
|
||||
|
||||
class SQLiteExecutionContext(default.DefaultExecutionContext):
|
||||
def get_result_proxy(self):
|
||||
rp = base.ResultProxy(self)
|
||||
if rp._metadata:
|
||||
# adjust for dotted column names. SQLite
|
||||
# in the case of UNION may store col names as
|
||||
# "tablename.colname"
|
||||
# in cursor.description
|
||||
for colname in rp._metadata.keys:
|
||||
if "." in colname:
|
||||
trunc_col = colname.split(".")[1]
|
||||
rp._metadata._set_keymap_synonym(trunc_col, colname)
|
||||
return rp
|
||||
|
||||
class SQLiteDialect(default.DefaultDialect):
|
||||
name = 'sqlite'
|
||||
supports_alter = False
|
||||
@@ -353,17 +472,13 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
ischema_names = ischema_names
|
||||
colspecs = colspecs
|
||||
isolation_level = None
|
||||
execution_ctx_cls = SQLiteExecutionContext
|
||||
|
||||
supports_cast = True
|
||||
supports_default_values = True
|
||||
|
||||
def __init__(self, isolation_level=None, native_datetime=False, **kwargs):
|
||||
default.DefaultDialect.__init__(self, **kwargs)
|
||||
if isolation_level and isolation_level not in ('SERIALIZABLE',
|
||||
'READ UNCOMMITTED'):
|
||||
raise exc.ArgumentError("Invalid value for isolation_level. "
|
||||
"Valid isolation levels for sqlite are 'SERIALIZABLE' and "
|
||||
"'READ UNCOMMITTED'.")
|
||||
self.isolation_level = isolation_level
|
||||
|
||||
# this flag used by pysqlite dialect, and perhaps others in the
|
||||
@@ -378,18 +493,49 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
self.supports_cast = \
|
||||
self.dbapi.sqlite_version_info >= (3, 2, 3)
|
||||
|
||||
_isolation_lookup = {
|
||||
'READ UNCOMMITTED':1,
|
||||
'SERIALIZABLE':0
|
||||
}
|
||||
def set_isolation_level(self, connection, level):
|
||||
try:
|
||||
isolation_level = self._isolation_lookup[level.replace('_', ' ')]
|
||||
except KeyError:
|
||||
raise exc.ArgumentError(
|
||||
"Invalid value '%s' for isolation_level. "
|
||||
"Valid isolation levels for %s are %s" %
|
||||
(level, self.name, ", ".join(self._isolation_lookup))
|
||||
)
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("PRAGMA read_uncommitted = %d" % isolation_level)
|
||||
cursor.close()
|
||||
|
||||
def get_isolation_level(self, connection):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('PRAGMA read_uncommitted')
|
||||
res = cursor.fetchone()
|
||||
if res:
|
||||
value = res[0]
|
||||
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
|
||||
# share a common cache.""
|
||||
# pre-SQLite 3.3.0 default to 0
|
||||
value = 0
|
||||
cursor.close()
|
||||
if value == 0:
|
||||
return "SERIALIZABLE"
|
||||
elif value == 1:
|
||||
return "READ UNCOMMITTED"
|
||||
else:
|
||||
assert False, "Unknown isolation level %s" % value
|
||||
|
||||
def on_connect(self):
|
||||
if self.isolation_level is not None:
|
||||
if self.isolation_level == 'READ UNCOMMITTED':
|
||||
isolation_level = 1
|
||||
else:
|
||||
isolation_level = 0
|
||||
|
||||
def connect(conn):
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("PRAGMA read_uncommitted = %d" % isolation_level)
|
||||
cursor.close()
|
||||
self.set_isolation_level(conn, self.isolation_level)
|
||||
return connect
|
||||
else:
|
||||
return None
|
||||
@@ -410,7 +556,6 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
"WHERE type='table' ORDER BY name")
|
||||
rs = connection.execute(s)
|
||||
except exc.DBAPIError:
|
||||
raise
|
||||
s = ("SELECT name FROM sqlite_master "
|
||||
"WHERE type='table' ORDER BY name")
|
||||
rs = connection.execute(s)
|
||||
@@ -429,7 +574,7 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
|
||||
# consume remaining rows, to work around
|
||||
# http://www.sqlite.org/cvstrac/tktview?tn=1884
|
||||
while cursor.fetchone() is not None:
|
||||
while not cursor.closed and cursor.fetchone() is not None:
|
||||
pass
|
||||
|
||||
return (row is not None)
|
||||
@@ -450,7 +595,6 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
"WHERE type='view' ORDER BY name")
|
||||
rs = connection.execute(s)
|
||||
except exc.DBAPIError:
|
||||
raise
|
||||
s = ("SELECT name FROM sqlite_master "
|
||||
"WHERE type='view' ORDER BY name")
|
||||
rs = connection.execute(s)
|
||||
@@ -475,7 +619,6 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
"AND type='view'") % view_name
|
||||
rs = connection.execute(s)
|
||||
except exc.DBAPIError:
|
||||
raise
|
||||
s = ("SELECT sql FROM sqlite_master WHERE name = '%s' "
|
||||
"AND type='view'") % view_name
|
||||
rs = connection.execute(s)
|
||||
@@ -492,17 +635,19 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
else:
|
||||
pragma = "PRAGMA "
|
||||
qtable = quote(table_name)
|
||||
c = _pragma_cursor(connection.execute("%stable_info(%s)" % (pragma, qtable)))
|
||||
c = _pragma_cursor(
|
||||
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, 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)
|
||||
if default:
|
||||
default = re.sub(r"^\'|\'$", '', default)
|
||||
match = re.match(r'(\w+)(\(.*?\))?', type_)
|
||||
if match:
|
||||
coltype = match.group(1)
|
||||
@@ -512,19 +657,20 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
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 args is not None:
|
||||
args = re.findall(r'(\d+)', args)
|
||||
coltype = coltype(*[int(a) for a in args])
|
||||
coltype = sqltypes.NullType()
|
||||
|
||||
columns.append({
|
||||
'name' : name,
|
||||
'type' : coltype,
|
||||
'nullable' : nullable,
|
||||
'default' : default,
|
||||
'autoincrement':default is None,
|
||||
'primary_key': primary_key
|
||||
})
|
||||
return columns
|
||||
@@ -553,22 +699,26 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
row = c.fetchone()
|
||||
if row is None:
|
||||
break
|
||||
(constraint_name, rtbl, lcol, rcol) = (row[0], row[2], row[3], row[4])
|
||||
(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[constraint_name]
|
||||
fk = fks[numerical_id]
|
||||
except KeyError:
|
||||
fk = {
|
||||
'name' : constraint_name,
|
||||
'name' : None,
|
||||
'constrained_columns' : [],
|
||||
'referred_schema' : None,
|
||||
'referred_table' : rtbl,
|
||||
'referred_columns' : []
|
||||
}
|
||||
fkeys.append(fk)
|
||||
fks[constraint_name] = fk
|
||||
fks[numerical_id] = fk
|
||||
|
||||
# look up the table based on the given table's engine, not 'self',
|
||||
# since it could be a ProxyEngine
|
||||
@@ -612,7 +762,8 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
|
||||
|
||||
def _pragma_cursor(cursor):
|
||||
"""work around SQLite issue whereby cursor.description is blank when PRAGMA returns no rows."""
|
||||
"""work around SQLite issue whereby cursor.description
|
||||
is blank when PRAGMA returns no rows."""
|
||||
|
||||
if cursor.closed:
|
||||
cursor.fetchone = lambda: None
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sqlite/pysqlite.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -93,61 +93,130 @@ processing. Execution of "func.current_date()" will return a string.
|
||||
"func.current_timestamp()" is registered as returning a DATETIME type in
|
||||
SQLAlchemy, so this function still receives SQLAlchemy-level result processing.
|
||||
|
||||
Threading Behavior
|
||||
------------------
|
||||
Threading/Pooling Behavior
|
||||
---------------------------
|
||||
|
||||
Pysqlite connections do not support being moved between threads, unless
|
||||
the ``check_same_thread`` Pysqlite flag is set to ``False``. In addition,
|
||||
when using an in-memory SQLite database, the full database exists only within
|
||||
the scope of a single connection. It is reported that an in-memory
|
||||
database does not support being shared between threads regardless of the
|
||||
``check_same_thread`` flag - which means that a multithreaded
|
||||
application **cannot** share data from a ``:memory:`` database across threads
|
||||
unless access to the connection is limited to a single worker thread which communicates
|
||||
through a queueing mechanism to concurrent threads.
|
||||
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
|
||||
various circumstances. In particular, older SQLite versions
|
||||
did not allow a ``:memory:`` database to be used in multiple threads
|
||||
under any circumstances.
|
||||
|
||||
To provide a default which accomodates SQLite's default threading capabilities
|
||||
somewhat reasonably, the SQLite dialect will specify that the :class:`~sqlalchemy.pool.SingletonThreadPool`
|
||||
be used by default. This pool maintains a single SQLite connection per thread
|
||||
that is held open up to a count of five concurrent threads. When more than five threads
|
||||
are used, a cleanup mechanism will dispose of excess unused connections.
|
||||
SQLAlchemy sets up pooling to work with Pysqlite's default behavior:
|
||||
|
||||
Two optional pool implementations that may be appropriate for particular SQLite usage scenarios:
|
||||
* 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
|
||||
``:memory:`` database.
|
||||
* 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.
|
||||
|
||||
* the :class:`sqlalchemy.pool.StaticPool` might be appropriate for a multithreaded
|
||||
application using an in-memory database, assuming the threading issues inherent in
|
||||
pysqlite are somehow accomodated for. This pool holds persistently onto a single connection
|
||||
which is never closed, and is returned for all requests.
|
||||
.. 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.
|
||||
|
||||
* the :class:`sqlalchemy.pool.NullPool` might be appropriate for an application that
|
||||
makes use of a file-based sqlite database. This pool disables any actual "pooling"
|
||||
behavior, and simply opens and closes real connections corresonding to the :func:`connect()`
|
||||
and :func:`close()` methods. SQLite can "connect" to a particular file with very high
|
||||
efficiency, so this option may actually perform better without the extra overhead
|
||||
of :class:`SingletonThreadPool`. NullPool will of course render a ``:memory:`` connection
|
||||
useless since the database would be lost as soon as the connection is "returned" to the pool.
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To use a ``:memory:`` database in a multithreaded scenario, the same connection
|
||||
object must be shared among threads, since the database exists
|
||||
only within the scope of that connection. The :class:`.StaticPool` implementation
|
||||
will maintain a single connection globally, and the ``check_same_thread`` flag
|
||||
can be passed to Pysqlite as ``False``::
|
||||
|
||||
from sqlalchemy.pool import StaticPool
|
||||
engine = create_engine('sqlite://',
|
||||
connect_args={'check_same_thread':False},
|
||||
poolclass=StaticPool)
|
||||
|
||||
Note that using a ``:memory:`` database in multiple threads requires a recent
|
||||
version of SQLite.
|
||||
|
||||
Using Temporary Tables with SQLite
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Due to the way SQLite deals with temporary tables, if you wish to use a temporary table
|
||||
in a file-based SQLite database across multiple checkouts from the connection pool, such
|
||||
as when using an ORM :class:`.Session` where the temporary table should continue to remain
|
||||
after :meth:`.commit` or :meth:`.rollback` is called,
|
||||
a pool which maintains a single connection must be used. Use :class:`.SingletonThreadPool`
|
||||
if the scope is only needed within the current thread, or :class:`.StaticPool` is scope is
|
||||
needed within multiple threads for this case::
|
||||
|
||||
# maintain the same connection per thread
|
||||
from sqlalchemy.pool import SingletonThreadPool
|
||||
engine = create_engine('sqlite:///mydb.db',
|
||||
poolclass=SingletonThreadPool)
|
||||
|
||||
|
||||
# maintain the same connection across all threads
|
||||
from sqlalchemy.pool import StaticPool
|
||||
engine = create_engine('sqlite:///mydb.db',
|
||||
poolclass=StaticPool)
|
||||
|
||||
Note that :class:`.SingletonThreadPool` should be configured for the number of threads
|
||||
that are to be used; beyond that number, connections will be closed out in a non deterministic
|
||||
way.
|
||||
|
||||
Unicode
|
||||
-------
|
||||
|
||||
In contrast to SQLAlchemy's active handling of date and time types for pysqlite, pysqlite's
|
||||
default behavior regarding Unicode is that all strings are returned as Python unicode objects
|
||||
in all cases. So even if the :class:`~sqlalchemy.types.Unicode` type is
|
||||
*not* used, you will still always receive unicode data back from a result set. It is
|
||||
**strongly** recommended that you do use the :class:`~sqlalchemy.types.Unicode` type
|
||||
to represent strings, since it will raise a warning if a non-unicode Python string is
|
||||
passed from the user application. Mixing the usage of non-unicode objects with returned unicode objects can
|
||||
quickly create confusion, particularly when using the ORM as internal data is not
|
||||
always represented by an actual database result string.
|
||||
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.
|
||||
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
|
||||
is passed containing non-ASCII characters.
|
||||
|
||||
.. _pysqlite_serializable:
|
||||
|
||||
Serializable Transaction Isolation
|
||||
----------------------------------
|
||||
|
||||
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
|
||||
locked, it breaks serializable transaction isolation, which requires
|
||||
that the database file be locked upon any SQL being emitted.
|
||||
|
||||
To work around this issue, the ``BEGIN`` keyword can be emitted
|
||||
at the start of each transaction. The following recipe establishes
|
||||
a :meth:`.ConnectionEvents.begin` handler to achieve this::
|
||||
|
||||
from sqlalchemy import create_engine, event
|
||||
|
||||
engine = create_engine("sqlite:///myfile.db", isolation_level='SERIALIZABLE')
|
||||
|
||||
@event.listens_for(engine, "begin")
|
||||
def do_begin(conn):
|
||||
conn.execute("BEGIN")
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.sqlite.base import SQLiteDialect, DATETIME, DATE
|
||||
from sqlalchemy import schema, exc, pool
|
||||
from sqlalchemy.engine import default
|
||||
from sqlalchemy import exc, pool
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy import util
|
||||
|
||||
import os
|
||||
|
||||
class _SQLite_pysqliteTimeStamp(DATETIME):
|
||||
def bind_processor(self, dialect):
|
||||
@@ -177,7 +246,6 @@ class _SQLite_pysqliteDate(DATE):
|
||||
|
||||
class SQLiteDialect_pysqlite(SQLiteDialect):
|
||||
default_paramstyle = 'qmark'
|
||||
poolclass = pool.SingletonThreadPool
|
||||
|
||||
colspecs = util.update_copy(
|
||||
SQLiteDialect.colspecs,
|
||||
@@ -215,6 +283,13 @@ class SQLiteDialect_pysqlite(SQLiteDialect):
|
||||
raise e
|
||||
return sqlite
|
||||
|
||||
@classmethod
|
||||
def get_pool_class(cls, url):
|
||||
if url.database and url.database != ':memory:':
|
||||
return pool.NullPool
|
||||
else:
|
||||
return pool.SingletonThreadPool
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
return self.dbapi.sqlite_version_info
|
||||
|
||||
@@ -227,6 +302,8 @@ class SQLiteDialect_pysqlite(SQLiteDialect):
|
||||
" sqlite:///relative/path/to/file.db\n"
|
||||
" sqlite:////absolute/path/to/file.db" % (url,))
|
||||
filename = url.database or ':memory:'
|
||||
if filename != ':memory:':
|
||||
filename = os.path.abspath(filename)
|
||||
|
||||
opts = url.query.copy()
|
||||
util.coerce_kw_type(opts, 'timeout', float)
|
||||
@@ -237,7 +314,8 @@ class SQLiteDialect_pysqlite(SQLiteDialect):
|
||||
|
||||
return ([filename], opts)
|
||||
|
||||
def is_disconnect(self, e):
|
||||
return isinstance(e, self.dbapi.ProgrammingError) and "Cannot operate on a closed database." in str(e)
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
return isinstance(e, self.dbapi.ProgrammingError) and \
|
||||
"Cannot operate on a closed database." in str(e)
|
||||
|
||||
dialect = SQLiteDialect_pysqlite
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sybase/__init__.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
|
||||
@@ -271,6 +271,8 @@ class SybaseSQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
def get_select_precolumns(self, select):
|
||||
s = select._distinct and "DISTINCT " or ""
|
||||
# TODO: don't think Sybase supports
|
||||
# bind params for FIRST / TOP
|
||||
if select._limit:
|
||||
#if select._limit == 1:
|
||||
#s += "FIRST "
|
||||
@@ -319,7 +321,7 @@ class SybaseDDLCompiler(compiler.DDLCompiler):
|
||||
self.dialect.type_compiler.process(column.type)
|
||||
|
||||
if column.table is None:
|
||||
raise exc.InvalidRequestError(
|
||||
raise exc.CompileError(
|
||||
"The Sybase dialect requires Table-bound "
|
||||
"columns in order to generate DDL")
|
||||
seq_col = column.table._autoincrement_column
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sybase/mxodbc.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
@@ -37,8 +37,8 @@ Currently *not* supported are::
|
||||
from sqlalchemy.dialects.sybase.base import SybaseDialect,\
|
||||
SybaseExecutionContext
|
||||
from sqlalchemy.connectors.pyodbc import PyODBCConnector
|
||||
import decimal
|
||||
from sqlalchemy import types as sqltypes, util, processors
|
||||
from sqlalchemy.util.compat import decimal
|
||||
|
||||
class _SybNumeric_pyodbc(sqltypes.Numeric):
|
||||
"""Turns Decimals with adjusted() < -6 into floats.
|
||||
|
||||
@@ -87,7 +87,7 @@ class SybaseDialect_pysybase(SybaseDialect):
|
||||
# (12, 5, 0, 0)
|
||||
return (vers / 1000, vers % 1000 / 100, vers % 100 / 10, vers % 10)
|
||||
|
||||
def is_disconnect(self, e):
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, (self.dbapi.OperationalError,
|
||||
self.dbapi.ProgrammingError)):
|
||||
msg = str(e)
|
||||
|
||||
Reference in New Issue
Block a user