mirror of
https://github.com/janeczku/calibre-web
synced 2026-09-17 23:31:20 +00:00
Initial Fork from https://bitbucket.org/raphaelmutschler/calibreserver/
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
# sqlalchemy/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import inspect as _inspect
|
||||
import sys
|
||||
|
||||
from .sql import (
|
||||
alias,
|
||||
and_,
|
||||
asc,
|
||||
between,
|
||||
bindparam,
|
||||
case,
|
||||
cast,
|
||||
collate,
|
||||
delete,
|
||||
desc,
|
||||
distinct,
|
||||
except_,
|
||||
except_all,
|
||||
exists,
|
||||
extract,
|
||||
func,
|
||||
insert,
|
||||
intersect,
|
||||
intersect_all,
|
||||
join,
|
||||
literal,
|
||||
literal_column,
|
||||
modifier,
|
||||
not_,
|
||||
null,
|
||||
or_,
|
||||
outerjoin,
|
||||
outparam,
|
||||
over,
|
||||
select,
|
||||
subquery,
|
||||
text,
|
||||
tuple_,
|
||||
type_coerce,
|
||||
union,
|
||||
union_all,
|
||||
update,
|
||||
)
|
||||
|
||||
from .types import (
|
||||
BIGINT,
|
||||
BINARY,
|
||||
BLOB,
|
||||
BOOLEAN,
|
||||
BigInteger,
|
||||
Binary,
|
||||
Boolean,
|
||||
CHAR,
|
||||
CLOB,
|
||||
DATE,
|
||||
DATETIME,
|
||||
DECIMAL,
|
||||
Date,
|
||||
DateTime,
|
||||
Enum,
|
||||
FLOAT,
|
||||
Float,
|
||||
INT,
|
||||
INTEGER,
|
||||
Integer,
|
||||
Interval,
|
||||
LargeBinary,
|
||||
NCHAR,
|
||||
NVARCHAR,
|
||||
NUMERIC,
|
||||
Numeric,
|
||||
PickleType,
|
||||
REAL,
|
||||
SMALLINT,
|
||||
SmallInteger,
|
||||
String,
|
||||
TEXT,
|
||||
TIME,
|
||||
TIMESTAMP,
|
||||
Text,
|
||||
Time,
|
||||
TypeDecorator,
|
||||
Unicode,
|
||||
UnicodeText,
|
||||
VARBINARY,
|
||||
VARCHAR,
|
||||
)
|
||||
|
||||
|
||||
from .schema import (
|
||||
CheckConstraint,
|
||||
Column,
|
||||
ColumnDefault,
|
||||
Constraint,
|
||||
DDL,
|
||||
DefaultClause,
|
||||
FetchedValue,
|
||||
ForeignKey,
|
||||
ForeignKeyConstraint,
|
||||
Index,
|
||||
MetaData,
|
||||
PassiveDefault,
|
||||
PrimaryKeyConstraint,
|
||||
Sequence,
|
||||
Table,
|
||||
ThreadLocalMetaData,
|
||||
UniqueConstraint,
|
||||
)
|
||||
|
||||
from .inspection import inspect
|
||||
|
||||
from .engine import create_engine, engine_from_config
|
||||
|
||||
|
||||
__all__ = sorted(name for name, obj in locals().items()
|
||||
if not (name.startswith('_') or _inspect.ismodule(obj)))
|
||||
|
||||
__version__ = '0.8.3'
|
||||
|
||||
del _inspect, sys
|
||||
|
||||
from . import util as _sa_util
|
||||
_sa_util.importlater.resolve_all()
|
||||
@@ -0,0 +1,9 @@
|
||||
# connectors/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
|
||||
class Connector(object):
|
||||
pass
|
||||
@@ -0,0 +1,149 @@
|
||||
# connectors/mxodbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
Provide an SQLALchemy connector for the eGenix mxODBC commercial
|
||||
Python adapter for ODBC. This is not a free product, but eGenix
|
||||
provides SQLAlchemy with a license for use in continuous integration
|
||||
testing.
|
||||
|
||||
This has been tested for use with mxODBC 3.1.2 on SQL Server 2005
|
||||
and 2008, using the SQL Server Native driver. However, it is
|
||||
possible for this to be used on other database platforms.
|
||||
|
||||
For more info on mxODBC, see http://www.egenix.com/
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from . import Connector
|
||||
|
||||
|
||||
class MxODBCConnector(Connector):
|
||||
driver = 'mxodbc'
|
||||
|
||||
supports_sane_multi_rowcount = False
|
||||
supports_unicode_statements = True
|
||||
supports_unicode_binds = True
|
||||
|
||||
supports_native_decimal = True
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
# this classmethod will normally be replaced by an instance
|
||||
# attribute of the same name, so this is normally only called once.
|
||||
cls._load_mx_exceptions()
|
||||
platform = sys.platform
|
||||
if platform == 'win32':
|
||||
from mx.ODBC import Windows as module
|
||||
# this can be the string "linux2", and possibly others
|
||||
elif 'linux' in platform:
|
||||
from mx.ODBC import unixODBC as module
|
||||
elif platform == 'darwin':
|
||||
from mx.ODBC import iODBC as module
|
||||
else:
|
||||
raise ImportError("Unrecognized platform for mxODBC import")
|
||||
return module
|
||||
|
||||
@classmethod
|
||||
def _load_mx_exceptions(cls):
|
||||
""" Import mxODBC exception classes into the module namespace,
|
||||
as if they had been imported normally. This is done here
|
||||
to avoid requiring all SQLAlchemy users to install mxODBC.
|
||||
"""
|
||||
global InterfaceError, ProgrammingError
|
||||
from mx.ODBC import InterfaceError
|
||||
from mx.ODBC import ProgrammingError
|
||||
|
||||
def on_connect(self):
|
||||
def connect(conn):
|
||||
conn.stringformat = self.dbapi.MIXED_STRINGFORMAT
|
||||
conn.datetimeformat = self.dbapi.PYDATETIME_DATETIMEFORMAT
|
||||
conn.decimalformat = self.dbapi.DECIMAL_DECIMALFORMAT
|
||||
conn.errorhandler = self._error_handler()
|
||||
return connect
|
||||
|
||||
def _error_handler(self):
|
||||
""" Return a handler that adjusts mxODBC's raised Warnings to
|
||||
emit Python standard warnings.
|
||||
"""
|
||||
from mx.ODBC.Error import Warning as MxOdbcWarning
|
||||
|
||||
def error_handler(connection, cursor, errorclass, errorvalue):
|
||||
if issubclass(errorclass, MxOdbcWarning):
|
||||
errorclass.__bases__ = (Warning,)
|
||||
warnings.warn(message=str(errorvalue),
|
||||
category=errorclass,
|
||||
stacklevel=2)
|
||||
else:
|
||||
raise errorclass, errorvalue
|
||||
return error_handler
|
||||
|
||||
def create_connect_args(self, url):
|
||||
""" Return a tuple of *args,**kwargs for creating a connection.
|
||||
|
||||
The mxODBC 3.x connection constructor looks like this:
|
||||
|
||||
connect(dsn, user='', password='',
|
||||
clear_auto_commit=1, errorhandler=None)
|
||||
|
||||
This method translates the values in the provided uri
|
||||
into args and kwargs needed to instantiate an mxODBC Connection.
|
||||
|
||||
The arg 'errorhandler' is not used by SQLAlchemy and will
|
||||
not be populated.
|
||||
|
||||
"""
|
||||
opts = url.translate_connect_args(username='user')
|
||||
opts.update(url.query)
|
||||
args = opts.pop('host')
|
||||
opts.pop('port', None)
|
||||
opts.pop('database', None)
|
||||
return (args,), opts
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
# TODO: eGenix recommends checking connection.closed here
|
||||
# Does that detect dropped connections ?
|
||||
if isinstance(e, self.dbapi.ProgrammingError):
|
||||
return "connection already closed" in str(e)
|
||||
elif isinstance(e, self.dbapi.Error):
|
||||
return '[08S01]' in str(e)
|
||||
else:
|
||||
return False
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
# eGenix suggests using conn.dbms_version instead
|
||||
# of what we're doing here
|
||||
dbapi_con = connection.connection
|
||||
version = []
|
||||
r = re.compile('[.\-]')
|
||||
# 18 == pyodbc.SQL_DBMS_VER
|
||||
for n in r.split(dbapi_con.getinfo(18)[1]):
|
||||
try:
|
||||
version.append(int(n))
|
||||
except ValueError:
|
||||
version.append(n)
|
||||
return tuple(version)
|
||||
|
||||
def _get_direct(self, context):
|
||||
if context:
|
||||
native_odbc_execute = context.execution_options.\
|
||||
get('native_odbc_execute', 'auto')
|
||||
# default to direct=True in all cases, is more generally
|
||||
# compatible especially with SQL Server
|
||||
return False if native_odbc_execute is True else True
|
||||
else:
|
||||
return True
|
||||
|
||||
def do_executemany(self, cursor, statement, parameters, context=None):
|
||||
cursor.executemany(
|
||||
statement, parameters, direct=self._get_direct(context))
|
||||
|
||||
def do_execute(self, cursor, statement, parameters, context=None):
|
||||
cursor.execute(statement, parameters, direct=self._get_direct(context))
|
||||
@@ -0,0 +1,156 @@
|
||||
"""Define behaviors common to MySQLdb dialects.
|
||||
|
||||
Currently includes MySQL and Drizzle.
|
||||
|
||||
"""
|
||||
|
||||
from . import Connector
|
||||
from ..engine import base as engine_base, default
|
||||
from ..sql import operators as sql_operators
|
||||
from .. import exc, log, schema, sql, types as sqltypes, util, processors
|
||||
import re
|
||||
|
||||
|
||||
# the subclassing of Connector by all classes
|
||||
# here is not strictly necessary
|
||||
|
||||
|
||||
class MySQLDBExecutionContext(Connector):
|
||||
|
||||
@property
|
||||
def rowcount(self):
|
||||
if hasattr(self, '_rowcount'):
|
||||
return self._rowcount
|
||||
else:
|
||||
return self.cursor.rowcount
|
||||
|
||||
|
||||
class MySQLDBCompiler(Connector):
|
||||
def visit_mod_binary(self, binary, operator, **kw):
|
||||
return self.process(binary.left, **kw) + " %% " + \
|
||||
self.process(binary.right, **kw)
|
||||
|
||||
def post_process_text(self, text):
|
||||
return text.replace('%', '%%')
|
||||
|
||||
|
||||
class MySQLDBIdentifierPreparer(Connector):
|
||||
|
||||
def _escape_identifier(self, value):
|
||||
value = value.replace(self.escape_quote, self.escape_to_quote)
|
||||
return value.replace("%", "%%")
|
||||
|
||||
|
||||
class MySQLDBConnector(Connector):
|
||||
driver = 'mysqldb'
|
||||
supports_unicode_statements = False
|
||||
supports_sane_rowcount = True
|
||||
supports_sane_multi_rowcount = True
|
||||
|
||||
supports_native_decimal = True
|
||||
|
||||
default_paramstyle = 'format'
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
# is overridden when pymysql is used
|
||||
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, 'read_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 = {}
|
||||
keys = ['ssl_ca', 'ssl_key', 'ssl_cert', 'ssl_capath', 'ssl_cipher']
|
||||
for key in keys:
|
||||
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:
|
||||
CLIENT_FLAGS = __import__(
|
||||
self.dbapi.__name__ + '.constants.CLIENT'
|
||||
).constants.CLIENT
|
||||
client_flag |= CLIENT_FLAGS.FOUND_ROWS
|
||||
except (AttributeError, ImportError):
|
||||
self.supports_sane_rowcount = False
|
||||
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'
|
||||
@@ -0,0 +1,166 @@
|
||||
# connectors/pyodbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from . import Connector
|
||||
from ..util import asbool
|
||||
|
||||
import sys
|
||||
import re
|
||||
import urllib
|
||||
|
||||
|
||||
class PyODBCConnector(Connector):
|
||||
driver = 'pyodbc'
|
||||
|
||||
supports_sane_multi_rowcount = False
|
||||
# PyODBC unicode is broken on UCS-4 builds
|
||||
supports_unicode = sys.maxunicode == 65535
|
||||
supports_unicode_statements = supports_unicode
|
||||
supports_native_decimal = True
|
||||
default_paramstyle = 'named'
|
||||
|
||||
# for non-DSN connections, this should
|
||||
# hold the desired driver name
|
||||
pyodbc_driver_name = None
|
||||
|
||||
# will be set to True after initialize()
|
||||
# if the freetds.so is detected
|
||||
freetds = False
|
||||
|
||||
# will be set to the string version of
|
||||
# the FreeTDS driver if freetds is detected
|
||||
freetds_driver_version = None
|
||||
|
||||
# will be set to True after initialize()
|
||||
# if the libessqlsrv.so is detected
|
||||
easysoft = False
|
||||
|
||||
def __init__(self, supports_unicode_binds=None, **kw):
|
||||
super(PyODBCConnector, self).__init__(**kw)
|
||||
self._user_supports_unicode_binds = supports_unicode_binds
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('pyodbc')
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user')
|
||||
opts.update(url.query)
|
||||
|
||||
keys = opts
|
||||
query = url.query
|
||||
|
||||
connect_args = {}
|
||||
for param in ('ansi', 'unicode_results', 'autocommit'):
|
||||
if param in keys:
|
||||
connect_args[param] = asbool(keys.pop(param))
|
||||
|
||||
if 'odbc_connect' in keys:
|
||||
connectors = [urllib.unquote_plus(keys.pop('odbc_connect'))]
|
||||
else:
|
||||
dsn_connection = 'dsn' in keys or \
|
||||
('host' in keys and 'database' not in keys)
|
||||
if dsn_connection:
|
||||
connectors = ['dsn=%s' % (keys.pop('host', '') or \
|
||||
keys.pop('dsn', ''))]
|
||||
else:
|
||||
port = ''
|
||||
if 'port' in keys and not 'port' in query:
|
||||
port = ',%d' % int(keys.pop('port'))
|
||||
|
||||
connectors = ["DRIVER={%s}" %
|
||||
keys.pop('driver', self.pyodbc_driver_name),
|
||||
'Server=%s%s' % (keys.pop('host', ''), port),
|
||||
'Database=%s' % keys.pop('database', '')]
|
||||
|
||||
user = keys.pop("user", None)
|
||||
if user:
|
||||
connectors.append("UID=%s" % user)
|
||||
connectors.append("PWD=%s" % keys.pop('password', ''))
|
||||
else:
|
||||
connectors.append("Trusted_Connection=Yes")
|
||||
|
||||
# if set to 'Yes', the ODBC layer will try to automagically
|
||||
# convert textual data from your database encoding to your
|
||||
# client encoding. This should obviously be set to 'No' if
|
||||
# you query a cp1253 encoded database from a latin1 client...
|
||||
if 'odbc_autotranslate' in keys:
|
||||
connectors.append("AutoTranslate=%s" %
|
||||
keys.pop("odbc_autotranslate"))
|
||||
|
||||
connectors.extend(['%s=%s' % (k, v) for k, v in keys.iteritems()])
|
||||
return [[";".join(connectors)], connect_args]
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, self.dbapi.ProgrammingError):
|
||||
return "The cursor's connection has been closed." in str(e) or \
|
||||
'Attempt to use a closed connection.' in str(e)
|
||||
elif isinstance(e, self.dbapi.Error):
|
||||
return '[08S01]' in str(e)
|
||||
else:
|
||||
return False
|
||||
|
||||
def initialize(self, connection):
|
||||
# determine FreeTDS first. can't issue SQL easily
|
||||
# without getting unicode_statements/binds set up.
|
||||
|
||||
pyodbc = self.dbapi
|
||||
|
||||
dbapi_con = connection.connection
|
||||
|
||||
_sql_driver_name = dbapi_con.getinfo(pyodbc.SQL_DRIVER_NAME)
|
||||
self.freetds = bool(re.match(r".*libtdsodbc.*\.so", _sql_driver_name
|
||||
))
|
||||
self.easysoft = bool(re.match(r".*libessqlsrv.*\.so", _sql_driver_name
|
||||
))
|
||||
|
||||
if self.freetds:
|
||||
self.freetds_driver_version = dbapi_con.getinfo(
|
||||
pyodbc.SQL_DRIVER_VER)
|
||||
|
||||
# the "Py2K only" part here is theoretical.
|
||||
# have not tried pyodbc + python3.1 yet.
|
||||
# Py2K
|
||||
self.supports_unicode_statements = (
|
||||
not self.freetds and not self.easysoft)
|
||||
if self._user_supports_unicode_binds is not None:
|
||||
self.supports_unicode_binds = self._user_supports_unicode_binds
|
||||
else:
|
||||
self.supports_unicode_binds = (
|
||||
not self.freetds or self.freetds_driver_version >= '0.91'
|
||||
) and not self.easysoft
|
||||
# end Py2K
|
||||
|
||||
# run other initialization which asks for user name, etc.
|
||||
super(PyODBCConnector, self).initialize(connection)
|
||||
|
||||
def _dbapi_version(self):
|
||||
if not self.dbapi:
|
||||
return ()
|
||||
return self._parse_dbapi_version(self.dbapi.version)
|
||||
|
||||
def _parse_dbapi_version(self, vers):
|
||||
m = re.match(
|
||||
r'(?:py.*-)?([\d\.]+)(?:-(\w+))?',
|
||||
vers
|
||||
)
|
||||
if not m:
|
||||
return ()
|
||||
vers = tuple([int(x) for x in m.group(1).split(".")])
|
||||
if m.group(2):
|
||||
vers += (m.group(2),)
|
||||
return vers
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
dbapi_con = connection.connection
|
||||
version = []
|
||||
r = re.compile('[.\-]')
|
||||
for n in r.split(dbapi_con.getinfo(self.dbapi.SQL_DBMS_VER)):
|
||||
try:
|
||||
version.append(int(n))
|
||||
except ValueError:
|
||||
version.append(n)
|
||||
return tuple(version)
|
||||
@@ -0,0 +1,59 @@
|
||||
# connectors/zxJDBC.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import sys
|
||||
from . import Connector
|
||||
|
||||
|
||||
class ZxJDBCConnector(Connector):
|
||||
driver = 'zxjdbc'
|
||||
|
||||
supports_sane_rowcount = False
|
||||
supports_sane_multi_rowcount = False
|
||||
|
||||
supports_unicode_binds = True
|
||||
supports_unicode_statements = sys.version > '2.5.0+'
|
||||
description_encoding = None
|
||||
default_paramstyle = 'qmark'
|
||||
|
||||
jdbc_db_name = None
|
||||
jdbc_driver_name = None
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
from com.ziclix.python.sql import zxJDBC
|
||||
return zxJDBC
|
||||
|
||||
def _driver_kwargs(self):
|
||||
"""Return kw arg dict to be sent to connect()."""
|
||||
return {}
|
||||
|
||||
def _create_jdbc_url(self, url):
|
||||
"""Create a JDBC url from a :class:`~sqlalchemy.engine.url.URL`"""
|
||||
return 'jdbc:%s://%s%s/%s' % (self.jdbc_db_name, url.host,
|
||||
url.port is not None
|
||||
and ':%s' % url.port or '',
|
||||
url.database)
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = self._driver_kwargs()
|
||||
opts.update(url.query)
|
||||
return [
|
||||
[self._create_jdbc_url(url),
|
||||
url.username, url.password,
|
||||
self.jdbc_driver_name],
|
||||
opts]
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if not isinstance(e, self.dbapi.ProgrammingError):
|
||||
return False
|
||||
e = str(e)
|
||||
return 'connection is closed' in e or 'cursor is closed' in e
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
# use connection.connection.dbversion, and parse appropriately
|
||||
# to get a tuple
|
||||
raise NotImplementedError()
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
# databases/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Include imports from the sqlalchemy.dialects package for backwards
|
||||
compatibility with pre 0.6 versions.
|
||||
|
||||
"""
|
||||
from ..dialects.sqlite import base as sqlite
|
||||
from ..dialects.postgresql import base as postgresql
|
||||
postgres = postgresql
|
||||
from ..dialects.mysql import base as mysql
|
||||
from ..dialects.drizzle import base as drizzle
|
||||
from ..dialects.oracle import base as oracle
|
||||
from ..dialects.firebird import base as firebird
|
||||
from ..dialects.informix import base as informix
|
||||
from ..dialects.mssql import base as mssql
|
||||
from ..dialects.sybase import base as sybase
|
||||
|
||||
|
||||
__all__ = (
|
||||
'drizzle',
|
||||
'firebird',
|
||||
'informix',
|
||||
'mssql',
|
||||
'mysql',
|
||||
'postgresql',
|
||||
'sqlite',
|
||||
'oracle',
|
||||
'sybase',
|
||||
)
|
||||
@@ -0,0 +1,45 @@
|
||||
# dialects/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
__all__ = (
|
||||
'drizzle',
|
||||
'firebird',
|
||||
# 'informix',
|
||||
'mssql',
|
||||
'mysql',
|
||||
'oracle',
|
||||
'postgresql',
|
||||
'sqlite',
|
||||
'sybase',
|
||||
)
|
||||
|
||||
from .. import util
|
||||
|
||||
def _auto_fn(name):
|
||||
"""default dialect importer.
|
||||
|
||||
plugs into the :class:`.PluginLoader`
|
||||
as a first-hit system.
|
||||
|
||||
"""
|
||||
if "." in name:
|
||||
dialect, driver = name.split(".")
|
||||
else:
|
||||
dialect = name
|
||||
driver = "base"
|
||||
try:
|
||||
module = __import__('sqlalchemy.dialects.%s' % (dialect, )).dialects
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
module = getattr(module, dialect)
|
||||
if hasattr(module, driver):
|
||||
module = getattr(module, driver)
|
||||
return lambda: module.dialect
|
||||
else:
|
||||
return None
|
||||
|
||||
registry = util.PluginLoader("sqlalchemy.dialects", auto_fn=_auto_fn)
|
||||
@@ -0,0 +1,22 @@
|
||||
from sqlalchemy.dialects.drizzle import base, mysqldb
|
||||
|
||||
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', 'REAL', 'TEXT',
|
||||
'TIME', 'TIMESTAMP', 'VARBINARY',
|
||||
'VARCHAR', 'dialect'
|
||||
)
|
||||
@@ -0,0 +1,498 @@
|
||||
# drizzle/base.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2010-2011 Monty Taylor <mordred@inaugust.com>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
|
||||
"""
|
||||
|
||||
.. dialect:: drizzle
|
||||
:name: Drizzle
|
||||
|
||||
Drizzle is a variant of MySQL. Unlike MySQL, Drizzle's default storage engine
|
||||
is InnoDB (transactions, foreign-keys) rather than MyISAM. For more
|
||||
`Notable Differences <http://docs.drizzle.org/mysql_differences.html>`_, visit
|
||||
the `Drizzle Documentation <http://docs.drizzle.org/index.html>`_.
|
||||
|
||||
The SQLAlchemy Drizzle dialect leans heavily on the MySQL dialect, so much of
|
||||
the :doc:`SQLAlchemy MySQL <mysql>` documentation is also relevant.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy import exc
|
||||
from sqlalchemy import log
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.engine import reflection
|
||||
from sqlalchemy.dialects.mysql import base as mysql_dialect
|
||||
from sqlalchemy.types import DATE, DATETIME, BOOLEAN, TIME, \
|
||||
BLOB, BINARY, VARBINARY
|
||||
|
||||
|
||||
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 TIME(mysql_dialect.TIME):
|
||||
"""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: TIME,
|
||||
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
|
||||
|
||||
@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,48 @@
|
||||
"""
|
||||
.. dialect:: drizzle+mysqldb
|
||||
:name: MySQL-Python
|
||||
:dbapi: mysqldb
|
||||
:connectstring: drizzle+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
:url: http://sourceforge.net/projects/mysql-python
|
||||
|
||||
|
||||
"""
|
||||
|
||||
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
|
||||
@@ -0,0 +1,20 @@
|
||||
# firebird/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.firebird import base, kinterbasdb, fdb
|
||||
|
||||
base.dialect = kinterbasdb.dialect
|
||||
|
||||
from sqlalchemy.dialects.firebird.base import \
|
||||
SMALLINT, BIGINT, FLOAT, FLOAT, DATE, TIME, \
|
||||
TEXT, NUMERIC, FLOAT, TIMESTAMP, VARCHAR, CHAR, BLOB,\
|
||||
dialect
|
||||
|
||||
__all__ = (
|
||||
'SMALLINT', 'BIGINT', 'FLOAT', 'FLOAT', 'DATE', 'TIME',
|
||||
'TEXT', 'NUMERIC', 'FLOAT', 'TIMESTAMP', 'VARCHAR', 'CHAR', 'BLOB',
|
||||
'dialect'
|
||||
)
|
||||
@@ -0,0 +1,719 @@
|
||||
# firebird/base.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
|
||||
.. dialect:: firebird
|
||||
:name: Firebird
|
||||
|
||||
Firebird Dialects
|
||||
-----------------
|
||||
|
||||
Firebird offers two distinct dialects_ (not to be confused with a
|
||||
SQLAlchemy ``Dialect``):
|
||||
|
||||
dialect 1
|
||||
This is the old syntax and behaviour, inherited from Interbase pre-6.0.
|
||||
|
||||
dialect 3
|
||||
This is the newer and supported syntax, introduced in Interbase 6.0.
|
||||
|
||||
The SQLAlchemy Firebird dialect detects these versions and
|
||||
adjusts its representation of SQL accordingly. However,
|
||||
support for dialect 1 is not well tested and probably has
|
||||
incompatibilities.
|
||||
|
||||
Locking Behavior
|
||||
----------------
|
||||
|
||||
Firebird locks tables aggressively. For this reason, a DROP TABLE may
|
||||
hang until other transactions are released. SQLAlchemy does its best
|
||||
to release transactions as quickly as possible. The most common cause
|
||||
of hanging transactions is a non-fully consumed result set, i.e.::
|
||||
|
||||
result = engine.execute("select * from table")
|
||||
row = result.fetchone()
|
||||
return
|
||||
|
||||
Where above, the ``ResultProxy`` has not been fully consumed. The
|
||||
connection will be returned to the pool and the transactional state
|
||||
rolled back once the Python garbage collector reclaims the objects
|
||||
which hold onto the connection, which often occurs asynchronously.
|
||||
The above use case can be alleviated by calling ``first()`` on the
|
||||
``ResultProxy`` which will fetch the first row and immediately close
|
||||
all remaining cursor/connection resources.
|
||||
|
||||
RETURNING support
|
||||
-----------------
|
||||
|
||||
Firebird 2.0 supports returning a result set from inserts, and 2.1
|
||||
extends that to deletes and updates. This is generically exposed by
|
||||
the SQLAlchemy ``returning()`` method, such as::
|
||||
|
||||
# INSERT..RETURNING
|
||||
result = table.insert().returning(table.c.col1, table.c.col2).\\
|
||||
values(name='foo')
|
||||
print result.fetchall()
|
||||
|
||||
# UPDATE..RETURNING
|
||||
raises = empl.update().returning(empl.c.id, empl.c.salary).\\
|
||||
where(empl.c.sales>100).\\
|
||||
values(dict(salary=empl.c.salary * 1.1))
|
||||
print raises.fetchall()
|
||||
|
||||
|
||||
.. _dialects: http://mc-computing.com/Databases/Firebird/SQL_Dialect.html
|
||||
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
from sqlalchemy import schema as sa_schema
|
||||
from sqlalchemy import exc, types as sqltypes, sql, util
|
||||
from sqlalchemy.sql import expression
|
||||
from sqlalchemy.engine import base, default, reflection
|
||||
from sqlalchemy.sql import compiler
|
||||
|
||||
|
||||
from sqlalchemy.types import (BIGINT, BLOB, DATE, FLOAT, INTEGER, NUMERIC,
|
||||
SMALLINT, TEXT, TIME, TIMESTAMP, Integer)
|
||||
|
||||
|
||||
RESERVED_WORDS = set([
|
||||
"active", "add", "admin", "after", "all", "alter", "and", "any", "as",
|
||||
"asc", "ascending", "at", "auto", "avg", "before", "begin", "between",
|
||||
"bigint", "bit_length", "blob", "both", "by", "case", "cast", "char",
|
||||
"character", "character_length", "char_length", "check", "close",
|
||||
"collate", "column", "commit", "committed", "computed", "conditional",
|
||||
"connect", "constraint", "containing", "count", "create", "cross",
|
||||
"cstring", "current", "current_connection", "current_date",
|
||||
"current_role", "current_time", "current_timestamp",
|
||||
"current_transaction", "current_user", "cursor", "database", "date",
|
||||
"day", "dec", "decimal", "declare", "default", "delete", "desc",
|
||||
"descending", "disconnect", "distinct", "do", "domain", "double",
|
||||
"drop", "else", "end", "entry_point", "escape", "exception",
|
||||
"execute", "exists", "exit", "external", "extract", "fetch", "file",
|
||||
"filter", "float", "for", "foreign", "from", "full", "function",
|
||||
"gdscode", "generator", "gen_id", "global", "grant", "group",
|
||||
"having", "hour", "if", "in", "inactive", "index", "inner",
|
||||
"input_type", "insensitive", "insert", "int", "integer", "into", "is",
|
||||
"isolation", "join", "key", "leading", "left", "length", "level",
|
||||
"like", "long", "lower", "manual", "max", "maximum_segment", "merge",
|
||||
"min", "minute", "module_name", "month", "names", "national",
|
||||
"natural", "nchar", "no", "not", "null", "numeric", "octet_length",
|
||||
"of", "on", "only", "open", "option", "or", "order", "outer",
|
||||
"output_type", "overflow", "page", "pages", "page_size", "parameter",
|
||||
"password", "plan", "position", "post_event", "precision", "primary",
|
||||
"privileges", "procedure", "protected", "rdb$db_key", "read", "real",
|
||||
"record_version", "recreate", "recursive", "references", "release",
|
||||
"reserv", "reserving", "retain", "returning_values", "returns",
|
||||
"revoke", "right", "rollback", "rows", "row_count", "savepoint",
|
||||
"schema", "second", "segment", "select", "sensitive", "set", "shadow",
|
||||
"shared", "singular", "size", "smallint", "snapshot", "some", "sort",
|
||||
"sqlcode", "stability", "start", "starting", "starts", "statistics",
|
||||
"sub_type", "sum", "suspend", "table", "then", "time", "timestamp",
|
||||
"to", "trailing", "transaction", "trigger", "trim", "uncommitted",
|
||||
"union", "unique", "update", "upper", "user", "using", "value",
|
||||
"values", "varchar", "variable", "varying", "view", "wait", "when",
|
||||
"where", "while", "with", "work", "write", "year",
|
||||
])
|
||||
|
||||
|
||||
class _StringType(sqltypes.String):
|
||||
"""Base for Firebird string types."""
|
||||
|
||||
def __init__(self, charset=None, **kw):
|
||||
self.charset = charset
|
||||
super(_StringType, self).__init__(**kw)
|
||||
|
||||
|
||||
class VARCHAR(_StringType, sqltypes.VARCHAR):
|
||||
"""Firebird VARCHAR type"""
|
||||
__visit_name__ = 'VARCHAR'
|
||||
|
||||
def __init__(self, length=None, **kwargs):
|
||||
super(VARCHAR, self).__init__(length=length, **kwargs)
|
||||
|
||||
|
||||
class CHAR(_StringType, sqltypes.CHAR):
|
||||
"""Firebird CHAR type"""
|
||||
__visit_name__ = 'CHAR'
|
||||
|
||||
def __init__(self, length=None, **kwargs):
|
||||
super(CHAR, self).__init__(length=length, **kwargs)
|
||||
|
||||
|
||||
class _FBDateTime(sqltypes.DateTime):
|
||||
def bind_processor(self, dialect):
|
||||
def process(value):
|
||||
if type(value) == datetime.date:
|
||||
return datetime.datetime(value.year, value.month, value.day)
|
||||
else:
|
||||
return value
|
||||
return process
|
||||
|
||||
colspecs = {
|
||||
sqltypes.DateTime: _FBDateTime
|
||||
}
|
||||
|
||||
ischema_names = {
|
||||
'SHORT': SMALLINT,
|
||||
'LONG': INTEGER,
|
||||
'QUAD': FLOAT,
|
||||
'FLOAT': FLOAT,
|
||||
'DATE': DATE,
|
||||
'TIME': TIME,
|
||||
'TEXT': TEXT,
|
||||
'INT64': BIGINT,
|
||||
'DOUBLE': FLOAT,
|
||||
'TIMESTAMP': TIMESTAMP,
|
||||
'VARYING': VARCHAR,
|
||||
'CSTRING': CHAR,
|
||||
'BLOB': BLOB,
|
||||
}
|
||||
|
||||
|
||||
# TODO: date conversion types (should be implemented as _FBDateTime,
|
||||
# _FBDate, etc. as bind/result functionality is required)
|
||||
|
||||
class FBTypeCompiler(compiler.GenericTypeCompiler):
|
||||
def visit_boolean(self, type_):
|
||||
return self.visit_SMALLINT(type_)
|
||||
|
||||
def visit_datetime(self, type_):
|
||||
return self.visit_TIMESTAMP(type_)
|
||||
|
||||
def visit_TEXT(self, type_):
|
||||
return "BLOB SUB_TYPE 1"
|
||||
|
||||
def visit_BLOB(self, type_):
|
||||
return "BLOB SUB_TYPE 0"
|
||||
|
||||
def _extend_string(self, type_, basic):
|
||||
charset = getattr(type_, 'charset', None)
|
||||
if charset is None:
|
||||
return basic
|
||||
else:
|
||||
return '%s CHARACTER SET %s' % (basic, charset)
|
||||
|
||||
def visit_CHAR(self, type_):
|
||||
basic = super(FBTypeCompiler, self).visit_CHAR(type_)
|
||||
return self._extend_string(type_, basic)
|
||||
|
||||
def visit_VARCHAR(self, type_):
|
||||
if not type_.length:
|
||||
raise exc.CompileError(
|
||||
"VARCHAR requires a length on dialect %s" %
|
||||
self.dialect.name)
|
||||
basic = super(FBTypeCompiler, self).visit_VARCHAR(type_)
|
||||
return self._extend_string(type_, basic)
|
||||
|
||||
|
||||
class FBCompiler(sql.compiler.SQLCompiler):
|
||||
"""Firebird specific idiosyncrasies"""
|
||||
|
||||
ansi_bind_rules = True
|
||||
|
||||
#def visit_contains_op_binary(self, binary, operator, **kw):
|
||||
# cant use CONTAINING b.c. it's case insensitive.
|
||||
|
||||
#def visit_notcontains_op_binary(self, binary, operator, **kw):
|
||||
# cant use NOT CONTAINING b.c. it's case insensitive.
|
||||
|
||||
def visit_now_func(self, fn, **kw):
|
||||
return "CURRENT_TIMESTAMP"
|
||||
|
||||
def visit_startswith_op_binary(self, binary, operator, **kw):
|
||||
return '%s STARTING WITH %s' % (
|
||||
binary.left._compiler_dispatch(self, **kw),
|
||||
binary.right._compiler_dispatch(self, **kw))
|
||||
|
||||
def visit_notstartswith_op_binary(self, binary, operator, **kw):
|
||||
return '%s NOT STARTING WITH %s' % (
|
||||
binary.left._compiler_dispatch(self, **kw),
|
||||
binary.right._compiler_dispatch(self, **kw))
|
||||
|
||||
def visit_mod_binary(self, binary, operator, **kw):
|
||||
return "mod(%s, %s)" % (
|
||||
self.process(binary.left, **kw),
|
||||
self.process(binary.right, **kw))
|
||||
|
||||
def visit_alias(self, alias, asfrom=False, **kwargs):
|
||||
if self.dialect._version_two:
|
||||
return super(FBCompiler, self).\
|
||||
visit_alias(alias, asfrom=asfrom, **kwargs)
|
||||
else:
|
||||
# Override to not use the AS keyword which FB 1.5 does not like
|
||||
if asfrom:
|
||||
alias_name = isinstance(alias.name,
|
||||
expression._truncated_label) and \
|
||||
self._truncated_identifier("alias",
|
||||
alias.name) or alias.name
|
||||
|
||||
return self.process(
|
||||
alias.original, asfrom=asfrom, **kwargs) + \
|
||||
" " + \
|
||||
self.preparer.format_alias(alias, alias_name)
|
||||
else:
|
||||
return self.process(alias.original, **kwargs)
|
||||
|
||||
def visit_substring_func(self, func, **kw):
|
||||
s = self.process(func.clauses.clauses[0])
|
||||
start = self.process(func.clauses.clauses[1])
|
||||
if len(func.clauses.clauses) > 2:
|
||||
length = self.process(func.clauses.clauses[2])
|
||||
return "SUBSTRING(%s FROM %s FOR %s)" % (s, start, length)
|
||||
else:
|
||||
return "SUBSTRING(%s FROM %s)" % (s, start)
|
||||
|
||||
def visit_length_func(self, function, **kw):
|
||||
if self.dialect._version_two:
|
||||
return "char_length" + self.function_argspec(function)
|
||||
else:
|
||||
return "strlen" + self.function_argspec(function)
|
||||
|
||||
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, **kw)
|
||||
else:
|
||||
return ""
|
||||
|
||||
def default_from(self):
|
||||
return " FROM rdb$database"
|
||||
|
||||
def visit_sequence(self, seq):
|
||||
return "gen_id(%s, 1)" % self.preparer.format_sequence(seq)
|
||||
|
||||
def get_select_precolumns(self, select):
|
||||
"""Called when building a ``SELECT`` statement, position is just
|
||||
before column list Firebird puts the limit and offset right
|
||||
after the ``SELECT``...
|
||||
"""
|
||||
|
||||
result = ""
|
||||
if select._limit:
|
||||
result += "FIRST %s " % self.process(sql.literal(select._limit))
|
||||
if select._offset:
|
||||
result += "SKIP %s " % self.process(sql.literal(select._offset))
|
||||
if select._distinct:
|
||||
result += "DISTINCT "
|
||||
return result
|
||||
|
||||
def limit_clause(self, select):
|
||||
"""Already taken care of in the `get_select_precolumns` method."""
|
||||
|
||||
return ""
|
||||
|
||||
def returning_clause(self, stmt, returning_cols):
|
||||
columns = [
|
||||
self._label_select_column(None, c, True, False, {})
|
||||
for c in expression._select_iterables(returning_cols)
|
||||
]
|
||||
|
||||
return 'RETURNING ' + ', '.join(columns)
|
||||
|
||||
|
||||
class FBDDLCompiler(sql.compiler.DDLCompiler):
|
||||
"""Firebird syntactic idiosyncrasies"""
|
||||
|
||||
def visit_create_sequence(self, create):
|
||||
"""Generate a ``CREATE GENERATOR`` statement for the sequence."""
|
||||
|
||||
# no syntax for these
|
||||
# http://www.firebirdsql.org/manual/generatorguide-sqlsyntax.html
|
||||
if create.element.start is not None:
|
||||
raise NotImplemented(
|
||||
"Firebird SEQUENCE doesn't support START WITH")
|
||||
if create.element.increment is not None:
|
||||
raise NotImplemented(
|
||||
"Firebird SEQUENCE doesn't support INCREMENT BY")
|
||||
|
||||
if self.dialect._version_two:
|
||||
return "CREATE SEQUENCE %s" % \
|
||||
self.preparer.format_sequence(create.element)
|
||||
else:
|
||||
return "CREATE GENERATOR %s" % \
|
||||
self.preparer.format_sequence(create.element)
|
||||
|
||||
def visit_drop_sequence(self, drop):
|
||||
"""Generate a ``DROP GENERATOR`` statement for the sequence."""
|
||||
|
||||
if self.dialect._version_two:
|
||||
return "DROP SEQUENCE %s" % \
|
||||
self.preparer.format_sequence(drop.element)
|
||||
else:
|
||||
return "DROP GENERATOR %s" % \
|
||||
self.preparer.format_sequence(drop.element)
|
||||
|
||||
|
||||
class FBIdentifierPreparer(sql.compiler.IdentifierPreparer):
|
||||
"""Install Firebird specific reserved words."""
|
||||
|
||||
reserved_words = RESERVED_WORDS
|
||||
|
||||
def __init__(self, dialect):
|
||||
super(FBIdentifierPreparer, self).__init__(dialect, omit_schema=True)
|
||||
|
||||
|
||||
class FBExecutionContext(default.DefaultExecutionContext):
|
||||
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),
|
||||
type_
|
||||
)
|
||||
|
||||
|
||||
class FBDialect(default.DefaultDialect):
|
||||
"""Firebird dialect"""
|
||||
|
||||
name = 'firebird'
|
||||
|
||||
max_identifier_length = 31
|
||||
|
||||
supports_sequences = True
|
||||
sequences_optional = False
|
||||
supports_default_values = True
|
||||
postfetch_lastrowid = False
|
||||
|
||||
supports_native_boolean = False
|
||||
|
||||
requires_name_normalize = True
|
||||
supports_empty_insert = False
|
||||
|
||||
statement_compiler = FBCompiler
|
||||
ddl_compiler = FBDDLCompiler
|
||||
preparer = FBIdentifierPreparer
|
||||
type_compiler = FBTypeCompiler
|
||||
execution_ctx_cls = FBExecutionContext
|
||||
|
||||
colspecs = colspecs
|
||||
ischema_names = ischema_names
|
||||
|
||||
# defaults to dialect ver. 3,
|
||||
# will be autodetected off upon
|
||||
# first connect
|
||||
_version_two = True
|
||||
|
||||
def initialize(self, connection):
|
||||
super(FBDialect, self).initialize(connection)
|
||||
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()
|
||||
self.ischema_names['TIMESTAMP'] = sqltypes.DATE
|
||||
self.colspecs = {
|
||||
sqltypes.DateTime: sqltypes.DATE
|
||||
}
|
||||
|
||||
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,
|
||||
# that is padded with spaces
|
||||
name = name and name.rstrip()
|
||||
if name is None:
|
||||
return None
|
||||
elif name.upper() == name and \
|
||||
not self.identifier_preparer._requires_quotes(name.lower()):
|
||||
return name.lower()
|
||||
else:
|
||||
return name
|
||||
|
||||
def denormalize_name(self, name):
|
||||
if name is None:
|
||||
return None
|
||||
elif name.lower() == name and \
|
||||
not self.identifier_preparer._requires_quotes(name.lower()):
|
||||
return name.upper()
|
||||
else:
|
||||
return name
|
||||
|
||||
def has_table(self, connection, table_name, schema=None):
|
||||
"""Return ``True`` if the given table exists, ignoring
|
||||
the `schema`."""
|
||||
|
||||
tblqry = """
|
||||
SELECT 1 AS has_table FROM rdb$database
|
||||
WHERE EXISTS (SELECT rdb$relation_name
|
||||
FROM rdb$relations
|
||||
WHERE rdb$relation_name=?)
|
||||
"""
|
||||
c = connection.execute(tblqry, [self.denormalize_name(table_name)])
|
||||
return c.first() is not None
|
||||
|
||||
def has_sequence(self, connection, sequence_name, schema=None):
|
||||
"""Return ``True`` if the given sequence (generator) exists."""
|
||||
|
||||
genqry = """
|
||||
SELECT 1 AS has_sequence FROM rdb$database
|
||||
WHERE EXISTS (SELECT rdb$generator_name
|
||||
FROM rdb$generators
|
||||
WHERE rdb$generator_name=?)
|
||||
"""
|
||||
c = connection.execute(genqry, [self.denormalize_name(sequence_name)])
|
||||
return c.first() is not None
|
||||
|
||||
@reflection.cache
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
s = """
|
||||
SELECT DISTINCT rdb$relation_name
|
||||
FROM rdb$relation_fields
|
||||
WHERE rdb$system_flag=0 AND rdb$view_context IS NULL
|
||||
"""
|
||||
return [self.normalize_name(row[0]) for row in connection.execute(s)]
|
||||
|
||||
@reflection.cache
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
s = """
|
||||
SELECT distinct rdb$view_name
|
||||
FROM rdb$view_relations
|
||||
"""
|
||||
return [self.normalize_name(row[0]) for row in connection.execute(s)]
|
||||
|
||||
@reflection.cache
|
||||
def get_view_definition(self, connection, view_name, schema=None, **kw):
|
||||
qry = """
|
||||
SELECT rdb$view_source AS view_source
|
||||
FROM rdb$relations
|
||||
WHERE rdb$relation_name=?
|
||||
"""
|
||||
rp = connection.execute(qry, [self.denormalize_name(view_name)])
|
||||
row = rp.first()
|
||||
if row:
|
||||
return row['view_source']
|
||||
else:
|
||||
return None
|
||||
|
||||
@reflection.cache
|
||||
def get_pk_constraint(self, connection, table_name, schema=None, **kw):
|
||||
# Query to extract the PK/FK constrained fields of the given table
|
||||
keyqry = """
|
||||
SELECT se.rdb$field_name AS fname
|
||||
FROM rdb$relation_constraints rc
|
||||
JOIN rdb$index_segments se ON rc.rdb$index_name=se.rdb$index_name
|
||||
WHERE rc.rdb$constraint_type=? AND rc.rdb$relation_name=?
|
||||
"""
|
||||
tablename = self.denormalize_name(table_name)
|
||||
# get primary key fields
|
||||
c = connection.execute(keyqry, ["PRIMARY KEY", tablename])
|
||||
pkfields = [self.normalize_name(r['fname']) for r in c.fetchall()]
|
||||
return {'constrained_columns': pkfields, 'name': None}
|
||||
|
||||
@reflection.cache
|
||||
def get_column_sequence(self, connection,
|
||||
table_name, column_name,
|
||||
schema=None, **kw):
|
||||
tablename = self.denormalize_name(table_name)
|
||||
colname = self.denormalize_name(column_name)
|
||||
# Heuristic-query to determine the generator associated to a PK field
|
||||
genqry = """
|
||||
SELECT trigdep.rdb$depended_on_name AS fgenerator
|
||||
FROM rdb$dependencies tabdep
|
||||
JOIN rdb$dependencies trigdep
|
||||
ON tabdep.rdb$dependent_name=trigdep.rdb$dependent_name
|
||||
AND trigdep.rdb$depended_on_type=14
|
||||
AND trigdep.rdb$dependent_type=2
|
||||
JOIN rdb$triggers trig ON
|
||||
trig.rdb$trigger_name=tabdep.rdb$dependent_name
|
||||
WHERE tabdep.rdb$depended_on_name=?
|
||||
AND tabdep.rdb$depended_on_type=0
|
||||
AND trig.rdb$trigger_type=1
|
||||
AND tabdep.rdb$field_name=?
|
||||
AND (SELECT count(*)
|
||||
FROM rdb$dependencies trigdep2
|
||||
WHERE trigdep2.rdb$dependent_name = trigdep.rdb$dependent_name) = 2
|
||||
"""
|
||||
genr = connection.execute(genqry, [tablename, colname]).first()
|
||||
if genr is not None:
|
||||
return dict(name=self.normalize_name(genr['fgenerator']))
|
||||
|
||||
@reflection.cache
|
||||
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 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,
|
||||
f.rdb$field_length/
|
||||
COALESCE(cs.rdb$bytes_per_character,1) AS flen,
|
||||
f.rdb$field_precision AS fprec,
|
||||
f.rdb$field_scale AS fscale,
|
||||
COALESCE(r.rdb$default_source,
|
||||
f.rdb$default_source) AS fdefault
|
||||
FROM rdb$relation_fields r
|
||||
JOIN rdb$fields f ON r.rdb$field_source=f.rdb$field_name
|
||||
JOIN rdb$types t
|
||||
ON t.rdb$type=f.rdb$field_type AND
|
||||
t.rdb$field_name='RDB$FIELD_TYPE'
|
||||
LEFT JOIN rdb$character_sets cs ON
|
||||
f.rdb$character_set_id=cs.rdb$character_set_id
|
||||
WHERE f.rdb$system_flag=0 AND r.rdb$relation_name=?
|
||||
ORDER BY r.rdb$field_position
|
||||
"""
|
||||
# get the PK, used to determine the eventual associated sequence
|
||||
pk_constraint = self.get_pk_constraint(connection, table_name)
|
||||
pkey_cols = pk_constraint['constrained_columns']
|
||||
|
||||
tablename = self.denormalize_name(table_name)
|
||||
# get all of the fields for this table
|
||||
c = connection.execute(tblqry, [tablename])
|
||||
cols = []
|
||||
while True:
|
||||
row = c.fetchone()
|
||||
if row is None:
|
||||
break
|
||||
name = self.normalize_name(row['fname'])
|
||||
orig_colname = row['fname']
|
||||
|
||||
# get the data type
|
||||
colspec = row['ftype'].rstrip()
|
||||
coltype = self.ischema_names.get(colspec)
|
||||
if coltype is None:
|
||||
util.warn("Did not recognize type '%s' of column '%s'" %
|
||||
(colspec, name))
|
||||
coltype = sqltypes.NULLTYPE
|
||||
elif issubclass(coltype, Integer) and row['fprec'] != 0:
|
||||
coltype = NUMERIC(
|
||||
precision=row['fprec'],
|
||||
scale=row['fscale'] * -1)
|
||||
elif colspec in ('VARYING', 'CSTRING'):
|
||||
coltype = coltype(row['flen'])
|
||||
elif colspec == 'TEXT':
|
||||
coltype = TEXT(row['flen'])
|
||||
elif colspec == 'BLOB':
|
||||
if row['stype'] == 1:
|
||||
coltype = TEXT()
|
||||
else:
|
||||
coltype = BLOB()
|
||||
else:
|
||||
coltype = coltype()
|
||||
|
||||
# does it have a default value?
|
||||
defvalue = None
|
||||
if row['fdefault'] is not None:
|
||||
# the value comes down as "DEFAULT 'value'": there may be
|
||||
# more than one whitespace around the "DEFAULT" keyword
|
||||
# and it may also be lower case
|
||||
# (see also http://tracker.firebirdsql.org/browse/CORE-356)
|
||||
defexpr = row['fdefault'].lstrip()
|
||||
assert defexpr[:8].rstrip().upper() == \
|
||||
'DEFAULT', "Unrecognized default value: %s" % \
|
||||
defexpr
|
||||
defvalue = defexpr[8:].strip()
|
||||
if defvalue == 'NULL':
|
||||
# Redundant
|
||||
defvalue = None
|
||||
col_d = {
|
||||
'name': name,
|
||||
'type': coltype,
|
||||
'nullable': not bool(row['null_flag']),
|
||||
'default': defvalue,
|
||||
'autoincrement': defvalue is None
|
||||
}
|
||||
|
||||
if orig_colname.lower() == orig_colname:
|
||||
col_d['quote'] = True
|
||||
|
||||
# if the PK is a single field, try to see if its linked to
|
||||
# a sequence thru a trigger
|
||||
if len(pkey_cols) == 1 and name == pkey_cols[0]:
|
||||
seq_d = self.get_column_sequence(connection, tablename, name)
|
||||
if seq_d is not None:
|
||||
col_d['sequence'] = seq_d
|
||||
|
||||
cols.append(col_d)
|
||||
return cols
|
||||
|
||||
@reflection.cache
|
||||
def get_foreign_keys(self, connection, table_name, schema=None, **kw):
|
||||
# Query to extract the details of each UK/FK of the given table
|
||||
fkqry = """
|
||||
SELECT rc.rdb$constraint_name AS cname,
|
||||
cse.rdb$field_name AS fname,
|
||||
ix2.rdb$relation_name AS targetrname,
|
||||
se.rdb$field_name AS targetfname
|
||||
FROM rdb$relation_constraints rc
|
||||
JOIN rdb$indices ix1 ON ix1.rdb$index_name=rc.rdb$index_name
|
||||
JOIN rdb$indices ix2 ON ix2.rdb$index_name=ix1.rdb$foreign_key
|
||||
JOIN rdb$index_segments cse ON
|
||||
cse.rdb$index_name=ix1.rdb$index_name
|
||||
JOIN rdb$index_segments se
|
||||
ON se.rdb$index_name=ix2.rdb$index_name
|
||||
AND se.rdb$field_position=cse.rdb$field_position
|
||||
WHERE rc.rdb$constraint_type=? AND rc.rdb$relation_name=?
|
||||
ORDER BY se.rdb$index_name, se.rdb$field_position
|
||||
"""
|
||||
tablename = self.denormalize_name(table_name)
|
||||
|
||||
c = connection.execute(fkqry, ["FOREIGN KEY", tablename])
|
||||
fks = util.defaultdict(lambda: {
|
||||
'name': None,
|
||||
'constrained_columns': [],
|
||||
'referred_schema': None,
|
||||
'referred_table': None,
|
||||
'referred_columns': []
|
||||
})
|
||||
|
||||
for row in c:
|
||||
cname = self.normalize_name(row['cname'])
|
||||
fk = fks[cname]
|
||||
if not fk['name']:
|
||||
fk['name'] = cname
|
||||
fk['referred_table'] = self.normalize_name(row['targetrname'])
|
||||
fk['constrained_columns'].append(
|
||||
self.normalize_name(row['fname']))
|
||||
fk['referred_columns'].append(
|
||||
self.normalize_name(row['targetfname']))
|
||||
return fks.values()
|
||||
|
||||
@reflection.cache
|
||||
def get_indexes(self, connection, table_name, schema=None, **kw):
|
||||
qry = """
|
||||
SELECT ix.rdb$index_name AS index_name,
|
||||
ix.rdb$unique_flag AS unique_flag,
|
||||
ic.rdb$field_name AS field_name
|
||||
FROM rdb$indices ix
|
||||
JOIN rdb$index_segments ic
|
||||
ON ix.rdb$index_name=ic.rdb$index_name
|
||||
LEFT OUTER JOIN rdb$relation_constraints
|
||||
ON rdb$relation_constraints.rdb$index_name =
|
||||
ic.rdb$index_name
|
||||
WHERE ix.rdb$relation_name=? AND ix.rdb$foreign_key IS NULL
|
||||
AND rdb$relation_constraints.rdb$constraint_type IS NULL
|
||||
ORDER BY index_name, field_name
|
||||
"""
|
||||
c = connection.execute(qry, [self.denormalize_name(table_name)])
|
||||
|
||||
indexes = util.defaultdict(dict)
|
||||
for row in c:
|
||||
indexrec = indexes[row['index_name']]
|
||||
if 'name' not in indexrec:
|
||||
indexrec['name'] = self.normalize_name(row['index_name'])
|
||||
indexrec['column_names'] = []
|
||||
indexrec['unique'] = bool(row['unique_flag'])
|
||||
|
||||
indexrec['column_names'].append(
|
||||
self.normalize_name(row['field_name']))
|
||||
|
||||
return indexes.values()
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
# firebird/fdb.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: firebird+fdb
|
||||
:name: fdb
|
||||
:dbapi: pyodbc
|
||||
:connectstring: firebird+fdb://user:password@host:port/path/to/db[?key=value&key=value...]
|
||||
:url: http://pypi.python.org/pypi/fdb/
|
||||
|
||||
fdb is a kinterbasdb compatible DBAPI for Firebird.
|
||||
|
||||
.. versionadded:: 0.8 - Support for the fdb Firebird driver.
|
||||
|
||||
Status
|
||||
------
|
||||
|
||||
The fdb dialect is new and not yet tested (can't get fdb to build).
|
||||
|
||||
Arguments
|
||||
----------
|
||||
|
||||
The ``fdb`` dialect is based on the :mod:`sqlalchemy.dialects.firebird.kinterbasdb`
|
||||
dialect, however does not accept every argument that Kinterbasdb does.
|
||||
|
||||
* ``enable_rowcount`` - True by default, setting this to False disables
|
||||
the usage of "cursor.rowcount" with the
|
||||
Kinterbasdb dialect, which SQLAlchemy ordinarily calls upon automatically
|
||||
after any UPDATE or DELETE statement. When disabled, SQLAlchemy's
|
||||
ResultProxy will return -1 for result.rowcount. The rationale here is
|
||||
that Kinterbasdb requires a second round trip to the database when
|
||||
.rowcount is called - since SQLA's resultproxy automatically closes
|
||||
the cursor after a non-result-returning statement, rowcount must be
|
||||
called, if at all, before the result object is returned. Additionally,
|
||||
cursor.rowcount may not return correct results with older versions
|
||||
of Firebird, and setting this flag to False will also cause the
|
||||
SQLAlchemy ORM to ignore its usage. The behavior can also be controlled on a
|
||||
per-execution basis using the ``enable_rowcount`` option with
|
||||
:meth:`.Connection.execution_options`::
|
||||
|
||||
conn = engine.connect().execution_options(enable_rowcount=True)
|
||||
r = conn.execute(stmt)
|
||||
print r.rowcount
|
||||
|
||||
* ``retaining`` - True by default. Leaving this on True will pass the
|
||||
``retaining=True`` keyword argument to the ``.commit()`` and ``.rollback()``
|
||||
methods of the DBAPI connection, which can improve performance in some
|
||||
situations, but apparently with significant caveats.
|
||||
Please read the fdb and/or kinterbasdb DBAPI documentation in order to
|
||||
understand the implications of this flag.
|
||||
|
||||
.. versionadded:: 0.8.2 - ``retaining`` keyword argument specifying
|
||||
transaction retaining behavior. This flag will **default to False**
|
||||
in 0.9.
|
||||
|
||||
.. seealso::
|
||||
|
||||
http://pythonhosted.org/fdb/usage-guide.html#retaining-transactions - information
|
||||
on the "retaining" flag.
|
||||
|
||||
"""
|
||||
|
||||
from .kinterbasdb import FBDialect_kinterbasdb
|
||||
from ... import util
|
||||
|
||||
|
||||
class FBDialect_fdb(FBDialect_kinterbasdb):
|
||||
|
||||
def __init__(self, enable_rowcount=True,
|
||||
retaining=True, **kwargs):
|
||||
super(FBDialect_fdb, self).__init__(
|
||||
enable_rowcount=enable_rowcount,
|
||||
retaining=retaining, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('fdb')
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user')
|
||||
if opts.get('port'):
|
||||
opts['host'] = "%s/%s" % (opts['host'], opts['port'])
|
||||
del opts['port']
|
||||
opts.update(url.query)
|
||||
|
||||
util.coerce_kw_type(opts, 'type_conv', int)
|
||||
|
||||
return ([], opts)
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
"""Get the version of the Firebird server used by a connection.
|
||||
|
||||
Returns a tuple of (`major`, `minor`, `build`), three integers
|
||||
representing the version of the attached server.
|
||||
"""
|
||||
|
||||
# This is the simpler approach (the other uses the services api),
|
||||
# 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.
|
||||
|
||||
isc_info_firebird_version = 103
|
||||
fbconn = connection.connection
|
||||
|
||||
version = fbconn.db_info(isc_info_firebird_version)
|
||||
|
||||
return self._parse_version_info(version)
|
||||
|
||||
dialect = FBDialect_fdb
|
||||
@@ -0,0 +1,172 @@
|
||||
# firebird/kinterbasdb.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: firebird+kinterbasdb
|
||||
:name: kinterbasdb
|
||||
:dbapi: kinterbasdb
|
||||
:connectstring: firebird+kinterbasdb://user:password@host:port/path/to/db[?key=value&key=value...]
|
||||
:url: http://firebirdsql.org/index.php?op=devel&sub=python
|
||||
|
||||
Arguments
|
||||
----------
|
||||
|
||||
The Kinterbasdb backend accepts the ``enable_rowcount`` and ``retaining``
|
||||
arguments accepted by the :mod:`sqlalchemy.dialects.firebird.fdb` dialect. In addition, it
|
||||
also accepts the following:
|
||||
|
||||
* ``type_conv`` - select the kind of mapping done on the types: by default
|
||||
SQLAlchemy uses 200 with Unicode, datetime and decimal support. See
|
||||
the linked documents below for further information.
|
||||
|
||||
* ``concurrency_level`` - set the backend policy with regards to threading
|
||||
issues: by default SQLAlchemy uses policy 1. See the linked documents
|
||||
below for futher information.
|
||||
|
||||
.. seealso::
|
||||
|
||||
http://sourceforge.net/projects/kinterbasdb
|
||||
|
||||
http://kinterbasdb.sourceforge.net/dist_docs/usage.html#adv_param_conv_dynamic_type_translation
|
||||
|
||||
http://kinterbasdb.sourceforge.net/dist_docs/usage.html#special_issue_concurrency
|
||||
|
||||
"""
|
||||
|
||||
from .base import FBDialect, FBExecutionContext
|
||||
from ... import util, types as sqltypes
|
||||
from re import match
|
||||
import decimal
|
||||
|
||||
|
||||
class _FBNumeric_kinterbasdb(sqltypes.Numeric):
|
||||
def bind_processor(self, dialect):
|
||||
def process(value):
|
||||
if isinstance(value, decimal.Decimal):
|
||||
return str(value)
|
||||
else:
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
class FBExecutionContext_kinterbasdb(FBExecutionContext):
|
||||
@property
|
||||
def rowcount(self):
|
||||
if self.execution_options.get('enable_rowcount',
|
||||
self.dialect.enable_rowcount):
|
||||
return self.cursor.rowcount
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
class FBDialect_kinterbasdb(FBDialect):
|
||||
driver = 'kinterbasdb'
|
||||
supports_sane_rowcount = False
|
||||
supports_sane_multi_rowcount = False
|
||||
execution_ctx_cls = FBExecutionContext_kinterbasdb
|
||||
|
||||
supports_native_decimal = True
|
||||
|
||||
colspecs = util.update_copy(
|
||||
FBDialect.colspecs,
|
||||
{
|
||||
sqltypes.Numeric: _FBNumeric_kinterbasdb,
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
def __init__(self, type_conv=200, concurrency_level=1,
|
||||
enable_rowcount=True,
|
||||
retaining=True, **kwargs):
|
||||
super(FBDialect_kinterbasdb, self).__init__(**kwargs)
|
||||
self.enable_rowcount = enable_rowcount
|
||||
self.type_conv = type_conv
|
||||
self.concurrency_level = concurrency_level
|
||||
self.retaining = retaining
|
||||
if enable_rowcount:
|
||||
self.supports_sane_rowcount = True
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('kinterbasdb')
|
||||
|
||||
def do_execute(self, cursor, statement, parameters, context=None):
|
||||
# kinterbase does not accept a None, but wants an empty list
|
||||
# when there are no arguments.
|
||||
cursor.execute(statement, parameters or [])
|
||||
|
||||
def do_rollback(self, dbapi_connection):
|
||||
dbapi_connection.rollback(self.retaining)
|
||||
|
||||
def do_commit(self, dbapi_connection):
|
||||
dbapi_connection.commit(self.retaining)
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user')
|
||||
if opts.get('port'):
|
||||
opts['host'] = "%s/%s" % (opts['host'], opts['port'])
|
||||
del opts['port']
|
||||
opts.update(url.query)
|
||||
|
||||
util.coerce_kw_type(opts, 'type_conv', int)
|
||||
|
||||
type_conv = opts.pop('type_conv', self.type_conv)
|
||||
concurrency_level = opts.pop('concurrency_level',
|
||||
self.concurrency_level)
|
||||
|
||||
if self.dbapi is not None:
|
||||
initialized = getattr(self.dbapi, 'initialized', None)
|
||||
if initialized is None:
|
||||
# CVS rev 1.96 changed the name of the attribute:
|
||||
# http://kinterbasdb.cvs.sourceforge.net/viewvc/kinterbasdb/
|
||||
# Kinterbasdb-3.0/__init__.py?r1=1.95&r2=1.96
|
||||
initialized = getattr(self.dbapi, '_initialized', False)
|
||||
if not initialized:
|
||||
self.dbapi.init(type_conv=type_conv,
|
||||
concurrency_level=concurrency_level)
|
||||
return ([], opts)
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
"""Get the version of the Firebird server used by a connection.
|
||||
|
||||
Returns a tuple of (`major`, `minor`, `build`), three integers
|
||||
representing the version of the attached server.
|
||||
"""
|
||||
|
||||
# This is the simpler approach (the other uses the services api),
|
||||
# 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.
|
||||
|
||||
fbconn = connection.connection
|
||||
version = fbconn.server_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)
|
||||
|
||||
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)
|
||||
return ('Unable to complete network request to host' in msg or
|
||||
'Invalid connection state' in msg or
|
||||
'Invalid cursor state' in msg or
|
||||
'connection shutdown' in msg)
|
||||
else:
|
||||
return False
|
||||
|
||||
dialect = FBDialect_kinterbasdb
|
||||
@@ -0,0 +1,9 @@
|
||||
# informix/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.informix import base, informixdb
|
||||
|
||||
base.dialect = informixdb.dialect
|
||||
@@ -0,0 +1,589 @@
|
||||
# informix/base.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# coding: gbk
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: informix
|
||||
:name: Informix
|
||||
|
||||
.. note::
|
||||
|
||||
The Informix dialect functions on current SQLAlchemy versions
|
||||
but is not regularly tested, and may have many issues and
|
||||
caveats not currently handled.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from sqlalchemy import sql, schema, exc, pool, util
|
||||
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", "alter", "and", "ansi", "any", "append",
|
||||
"array", "as", "asc", "ascii", "asin", "at", "atan", "atan2", "attach",
|
||||
"attributes", "audit", "authentication", "authid", "authorization",
|
||||
"authorized", "auto", "autofree", "auto_reprepare", "auto_stat_mode",
|
||||
"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):
|
||||
def process(value):
|
||||
if value is not None:
|
||||
if value.microsecond:
|
||||
value = value.replace(microsecond=0)
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
class InfoTime(sqltypes.Time):
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
def process(value):
|
||||
if value is not None:
|
||||
if value.microsecond:
|
||||
value = value.replace(microsecond=0)
|
||||
return value
|
||||
return process
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
def process(value):
|
||||
if isinstance(value, datetime.datetime):
|
||||
return value.time()
|
||||
else:
|
||||
return value
|
||||
return process
|
||||
|
||||
colspecs = {
|
||||
sqltypes.DateTime: InfoDateTime,
|
||||
sqltypes.TIMESTAMP: InfoDateTime,
|
||||
sqltypes.Time: InfoTime,
|
||||
}
|
||||
|
||||
|
||||
ischema_names = {
|
||||
0: sqltypes.CHAR, # CHAR
|
||||
1: sqltypes.SMALLINT, # SMALLINT
|
||||
2: sqltypes.INTEGER, # INT
|
||||
3: sqltypes.FLOAT, # Float
|
||||
3: sqltypes.Float, # SmallFloat
|
||||
5: sqltypes.DECIMAL, # DECIMAL
|
||||
6: sqltypes.Integer, # Serial
|
||||
7: sqltypes.DATE, # DATE
|
||||
8: sqltypes.Numeric, # MONEY
|
||||
10: sqltypes.DATETIME, # DATETIME
|
||||
11: sqltypes.LargeBinary, # BYTE
|
||||
12: sqltypes.TEXT, # TEXT
|
||||
13: sqltypes.VARCHAR, # VARCHAR
|
||||
15: sqltypes.NCHAR, # NCHAR
|
||||
16: sqltypes.NVARCHAR, # NVARCHAR
|
||||
17: sqltypes.Integer, # INT8
|
||||
18: sqltypes.Integer, # Serial8
|
||||
43: sqltypes.String, # LVARCHAR
|
||||
-1: sqltypes.BLOB, # BLOB
|
||||
-1: sqltypes.CLOB, # CLOB
|
||||
}
|
||||
|
||||
|
||||
class InfoTypeCompiler(compiler.GenericTypeCompiler):
|
||||
def visit_DATETIME(self, type_):
|
||||
return "DATETIME YEAR TO SECOND"
|
||||
|
||||
def visit_TIME(self, type_):
|
||||
return "DATETIME HOUR TO SECOND"
|
||||
|
||||
def visit_TIMESTAMP(self, type_):
|
||||
return "DATETIME YEAR TO SECOND"
|
||||
|
||||
def visit_large_binary(self, type_):
|
||||
return "BYTE"
|
||||
|
||||
def visit_boolean(self, type_):
|
||||
return "SMALLINT"
|
||||
|
||||
|
||||
class InfoSQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
def default_from(self):
|
||||
return " from systables where tabname = 'systables' "
|
||||
|
||||
def get_select_precolumns(self, select):
|
||||
s = ""
|
||||
if select._offset:
|
||||
s += "SKIP %s " % select._offset
|
||||
if select._limit:
|
||||
s += "FIRST %s " % select._limit
|
||||
s += select._distinct and "DISTINCT " or ""
|
||||
return s
|
||||
|
||||
def visit_select(self, select, asfrom=False, parens=True, **kw):
|
||||
text = compiler.SQLCompiler.visit_select(self, select, asfrom, parens, **kw)
|
||||
if asfrom and parens and self.dialect.server_version_info < (11,):
|
||||
#assuming that 11 version doesn't need this, not tested
|
||||
return "table(multiset" + text + ")"
|
||||
else:
|
||||
return text
|
||||
|
||||
def limit_clause(self, select):
|
||||
return ""
|
||||
|
||||
def visit_function(self, func, **kw):
|
||||
if func.name.lower() == 'current_date':
|
||||
return "today"
|
||||
elif func.name.lower() == 'current_time':
|
||||
return "CURRENT HOUR TO SECOND"
|
||||
elif func.name.lower() in ('current_timestamp', 'now'):
|
||||
return "CURRENT YEAR TO SECOND"
|
||||
else:
|
||||
return compiler.SQLCompiler.visit_function(self, func, **kw)
|
||||
|
||||
def visit_mod_binary(self, binary, operator, **kw):
|
||||
return "MOD(%s, %s)" % (self.process(binary.left, **kw),
|
||||
self.process(binary.right, **kw))
|
||||
|
||||
|
||||
class InfoDDLCompiler(compiler.DDLCompiler):
|
||||
|
||||
def visit_add_constraint(self, create):
|
||||
preparer = self.preparer
|
||||
return "ALTER TABLE %s ADD CONSTRAINT %s" % (
|
||||
self.preparer.format_table(create.element.table),
|
||||
self.process(create.element)
|
||||
)
|
||||
|
||||
def get_column_specification(self, column, **kw):
|
||||
colspec = self.preparer.format_column(column)
|
||||
first = None
|
||||
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)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if column is first:
|
||||
colspec += " SERIAL"
|
||||
else:
|
||||
colspec += " " + self.dialect.type_compiler.process(column.type)
|
||||
default = self.get_column_default_string(column)
|
||||
if default is not None:
|
||||
colspec += " DEFAULT " + default
|
||||
|
||||
if not column.nullable:
|
||||
colspec += " NOT NULL"
|
||||
|
||||
return colspec
|
||||
|
||||
def get_column_default_string(self, column):
|
||||
if (isinstance(column.server_default, schema.DefaultClause) and
|
||||
isinstance(column.server_default.arg, basestring)):
|
||||
if isinstance(column.type, (sqltypes.Integer, sqltypes.Numeric)):
|
||||
return self.sql_compiler.process(text(column.server_default.arg))
|
||||
|
||||
return super(InfoDDLCompiler, self).get_column_default_string(column)
|
||||
|
||||
### Informix wants the constraint name at the end, hence this ist c&p from sql/compiler.py
|
||||
def visit_primary_key_constraint(self, constraint):
|
||||
if len(constraint) == 0:
|
||||
return ''
|
||||
text = "PRIMARY KEY "
|
||||
text += "(%s)" % ', '.join(self.preparer.quote(c.name, c.quote)
|
||||
for c in constraint)
|
||||
text += self.define_constraint_deferrability(constraint)
|
||||
|
||||
if constraint.name is not None:
|
||||
text += " CONSTRAINT %s" % self.preparer.format_constraint(constraint)
|
||||
return text
|
||||
|
||||
def visit_foreign_key_constraint(self, constraint):
|
||||
preparer = self.dialect.identifier_preparer
|
||||
remote_table = list(constraint._elements.values())[0].column.table
|
||||
text = "FOREIGN KEY (%s) REFERENCES %s (%s)" % (
|
||||
', '.join(preparer.quote(f.parent.name, f.parent.quote)
|
||||
for f in constraint._elements.values()),
|
||||
preparer.format_table(remote_table),
|
||||
', '.join(preparer.quote(f.column.name, f.column.quote)
|
||||
for f in constraint._elements.values())
|
||||
)
|
||||
text += self.define_constraint_cascades(constraint)
|
||||
text += self.define_constraint_deferrability(constraint)
|
||||
|
||||
if constraint.name is not None:
|
||||
text += " CONSTRAINT %s " % \
|
||||
preparer.format_constraint(constraint)
|
||||
return text
|
||||
|
||||
def visit_unique_constraint(self, constraint):
|
||||
text = "UNIQUE (%s)" % (', '.join(self.preparer.quote(c.name, c.quote) for c in constraint))
|
||||
text += self.define_constraint_deferrability(constraint)
|
||||
|
||||
if constraint.name is not None:
|
||||
text += "CONSTRAINT %s " % self.preparer.format_constraint(constraint)
|
||||
return text
|
||||
|
||||
|
||||
class InformixIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
|
||||
reserved_words = RESERVED_WORDS
|
||||
|
||||
|
||||
class InformixDialect(default.DefaultDialect):
|
||||
name = 'informix'
|
||||
|
||||
max_identifier_length = 128 # adjusts at runtime based on server version
|
||||
|
||||
type_compiler = InfoTypeCompiler
|
||||
statement_compiler = InfoSQLCompiler
|
||||
ddl_compiler = InfoDDLCompiler
|
||||
colspecs = colspecs
|
||||
ischema_names = ischema_names
|
||||
preparer = InformixIdentifierPreparer
|
||||
default_paramstyle = 'qmark'
|
||||
|
||||
def initialize(self, connection):
|
||||
super(InformixDialect, self).initialize(connection)
|
||||
|
||||
# http://www.querix.com/support/knowledge-base/error_number_message/error_200
|
||||
if self.server_version_info < (9, 2):
|
||||
self.max_identifier_length = 18
|
||||
else:
|
||||
self.max_identifier_length = 128
|
||||
|
||||
def _get_table_names(self, connection, schema, type, **kw):
|
||||
schema = schema or self.default_schema_name
|
||||
s = "select tabname, owner from systables where owner=? and tabtype=?"
|
||||
return [row[0] for row in connection.execute(s, schema, type)]
|
||||
|
||||
@reflection.cache
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
return self._get_table_names(connection, schema, 'T', **kw)
|
||||
|
||||
@reflection.cache
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
return self._get_table_names(connection, schema, 'V', **kw)
|
||||
|
||||
@reflection.cache
|
||||
def get_schema_names(self, connection, **kw):
|
||||
s = "select owner from systables"
|
||||
return [row[0] for row in connection.execute(s)]
|
||||
|
||||
def has_table(self, connection, table_name, schema=None):
|
||||
schema = schema or self.default_schema_name
|
||||
cursor = connection.execute(
|
||||
"""select tabname from systables where tabname=? and owner=?""",
|
||||
table_name, schema)
|
||||
return cursor.first() is not None
|
||||
|
||||
@reflection.cache
|
||||
def get_columns(self, connection, table_name, schema=None, **kw):
|
||||
schema = schema or self.default_schema_name
|
||||
c = connection.execute(
|
||||
"""select colname, coltype, collength, t3.default, t1.colno from
|
||||
syscolumns as t1 , systables as t2 , OUTER sysdefaults as t3
|
||||
where t1.tabid = t2.tabid and t2.tabname=? and t2.owner=?
|
||||
and t3.tabid = t2.tabid and t3.colno = t1.colno
|
||||
order by t1.colno""", table_name, schema)
|
||||
|
||||
pk_constraint = self.get_pk_constraint(connection, table_name, schema, **kw)
|
||||
primary_cols = pk_constraint['constrained_columns']
|
||||
|
||||
columns = []
|
||||
rows = c.fetchall()
|
||||
for name, colattr, collength, default, colno in rows:
|
||||
name = name.lower()
|
||||
|
||||
autoincrement = False
|
||||
primary_key = False
|
||||
|
||||
if name in primary_cols:
|
||||
primary_key = True
|
||||
|
||||
# in 7.31, coltype = 0x000
|
||||
# ^^-- column type
|
||||
# ^-- 1 not null, 0 null
|
||||
not_nullable, coltype = divmod(colattr, 256)
|
||||
if coltype not in (0, 13) and default:
|
||||
default = default.split()[-1]
|
||||
|
||||
if coltype == 6: # Serial, mark as autoincrement
|
||||
autoincrement = True
|
||||
|
||||
if coltype == 0 or coltype == 13: # char, varchar
|
||||
coltype = ischema_names[coltype](collength)
|
||||
if default:
|
||||
default = "'%s'" % default
|
||||
elif coltype == 5: # decimal
|
||||
precision, scale = (collength & 0xFF00) >> 8, collength & 0xFF
|
||||
if scale == 255:
|
||||
scale = 0
|
||||
coltype = sqltypes.Numeric(precision, scale)
|
||||
else:
|
||||
try:
|
||||
coltype = ischema_names[coltype]
|
||||
except KeyError:
|
||||
util.warn("Did not recognize type '%s' of column '%s'" %
|
||||
(coltype, name))
|
||||
coltype = sqltypes.NULLTYPE
|
||||
|
||||
column_info = dict(name=name, type=coltype, nullable=not not_nullable,
|
||||
default=default, autoincrement=autoincrement,
|
||||
primary_key=primary_key)
|
||||
columns.append(column_info)
|
||||
return columns
|
||||
|
||||
@reflection.cache
|
||||
def get_foreign_keys(self, connection, table_name, schema=None, **kw):
|
||||
schema_sel = schema or self.default_schema_name
|
||||
c = connection.execute(
|
||||
"""select t1.constrname as cons_name,
|
||||
t4.colname as local_column, t7.tabname as remote_table,
|
||||
t6.colname as remote_column, t7.owner as remote_owner
|
||||
from sysconstraints as t1 , systables as t2 ,
|
||||
sysindexes as t3 , syscolumns as t4 ,
|
||||
sysreferences as t5 , syscolumns as t6 , systables as t7 ,
|
||||
sysconstraints as t8 , sysindexes as t9
|
||||
where t1.tabid = t2.tabid and t2.tabname=? and t2.owner=? and t1.constrtype = 'R'
|
||||
and t3.tabid = t2.tabid and t3.idxname = t1.idxname
|
||||
and t4.tabid = t2.tabid and t4.colno in (t3.part1, t3.part2, t3.part3,
|
||||
t3.part4, t3.part5, t3.part6, t3.part7, t3.part8, t3.part9, t3.part10,
|
||||
t3.part11, t3.part11, t3.part12, t3.part13, t3.part4, t3.part15, t3.part16)
|
||||
and t5.constrid = t1.constrid and t8.constrid = t5.primary
|
||||
and t6.tabid = t5.ptabid and t6.colno in (t9.part1, t9.part2, t9.part3,
|
||||
t9.part4, t9.part5, t9.part6, t9.part7, t9.part8, t9.part9, t9.part10,
|
||||
t9.part11, t9.part11, t9.part12, t9.part13, t9.part4, t9.part15, t9.part16) and t9.idxname =
|
||||
t8.idxname
|
||||
and t7.tabid = t5.ptabid""", table_name, schema_sel)
|
||||
|
||||
def fkey_rec():
|
||||
return {
|
||||
'name': None,
|
||||
'constrained_columns': [],
|
||||
'referred_schema': None,
|
||||
'referred_table': None,
|
||||
'referred_columns': []
|
||||
}
|
||||
|
||||
fkeys = util.defaultdict(fkey_rec)
|
||||
|
||||
rows = c.fetchall()
|
||||
for cons_name, local_column, \
|
||||
remote_table, remote_column, remote_owner in rows:
|
||||
|
||||
rec = fkeys[cons_name]
|
||||
rec['name'] = cons_name
|
||||
local_cols, remote_cols = \
|
||||
rec['constrained_columns'], rec['referred_columns']
|
||||
|
||||
if not rec['referred_table']:
|
||||
rec['referred_table'] = remote_table
|
||||
if schema is not None:
|
||||
rec['referred_schema'] = remote_owner
|
||||
|
||||
if local_column not in local_cols:
|
||||
local_cols.append(local_column)
|
||||
if remote_column not in remote_cols:
|
||||
remote_cols.append(remote_column)
|
||||
|
||||
return fkeys.values()
|
||||
|
||||
@reflection.cache
|
||||
def get_pk_constraint(self, connection, table_name, schema=None, **kw):
|
||||
schema = schema or self.default_schema_name
|
||||
|
||||
# Select the column positions from sysindexes for sysconstraints
|
||||
data = connection.execute(
|
||||
"""select t2.*
|
||||
from systables as t1, sysindexes as t2, sysconstraints as t3
|
||||
where t1.tabid=t2.tabid and t1.tabname=? and t1.owner=?
|
||||
and t2.idxname=t3.idxname and t3.constrtype='P'""",
|
||||
table_name, schema
|
||||
).fetchall()
|
||||
|
||||
colpositions = set()
|
||||
|
||||
for row in data:
|
||||
colpos = set([getattr(row, 'part%d' % x) for x in range(1, 16)])
|
||||
colpositions |= colpos
|
||||
|
||||
if not len(colpositions):
|
||||
return {'constrained_columns': [], 'name': None}
|
||||
|
||||
# Select the column names using the columnpositions
|
||||
# TODO: Maybe cache a bit of those col infos (eg select all colnames for one table)
|
||||
place_holder = ','.join('?' * len(colpositions))
|
||||
c = connection.execute(
|
||||
"""select t1.colname
|
||||
from syscolumns as t1, systables as t2
|
||||
where t2.tabname=? and t1.tabid = t2.tabid and
|
||||
t1.colno in (%s)""" % place_holder,
|
||||
table_name, *colpositions
|
||||
).fetchall()
|
||||
|
||||
cols = reduce(lambda x, y: list(x) + list(y), c, [])
|
||||
return {'constrained_columns': cols, 'name': None}
|
||||
|
||||
@reflection.cache
|
||||
def get_indexes(self, connection, table_name, schema, **kw):
|
||||
# TODO: schema...
|
||||
c = connection.execute(
|
||||
"""select t1.*
|
||||
from sysindexes as t1 , systables as t2
|
||||
where t1.tabid = t2.tabid and t2.tabname=?""",
|
||||
table_name)
|
||||
|
||||
indexes = []
|
||||
for row in c.fetchall():
|
||||
colnames = [getattr(row, 'part%d' % x) for x in range(1, 16)]
|
||||
colnames = [x for x in colnames if x]
|
||||
place_holder = ','.join('?' * len(colnames))
|
||||
c = connection.execute(
|
||||
"""select t1.colname
|
||||
from syscolumns as t1, systables as t2
|
||||
where t2.tabname=? and t1.tabid = t2.tabid and
|
||||
t1.colno in (%s)""" % place_holder,
|
||||
table_name, *colnames
|
||||
).fetchall()
|
||||
c = reduce(lambda x, y: list(x) + list(y), c, [])
|
||||
indexes.append({
|
||||
'name': row.idxname,
|
||||
'unique': row.idxtype.lower() == 'u',
|
||||
'column_names': c
|
||||
})
|
||||
return indexes
|
||||
|
||||
@reflection.cache
|
||||
def get_view_definition(self, connection, view_name, schema=None, **kw):
|
||||
schema = schema or self.default_schema_name
|
||||
c = connection.execute(
|
||||
"""select t1.viewtext
|
||||
from sysviews as t1 , systables as t2
|
||||
where t1.tabid=t2.tabid and t2.tabname=?
|
||||
and t2.owner=? order by seqno""",
|
||||
view_name, schema).fetchall()
|
||||
|
||||
return ''.join([row[0] for row in c])
|
||||
|
||||
def _get_default_schema_name(self, connection):
|
||||
return connection.execute('select CURRENT_ROLE from systables').scalar()
|
||||
@@ -0,0 +1,69 @@
|
||||
# informix/informixdb.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
|
||||
.. dialect:: informix+informixdb
|
||||
:name: informixdb
|
||||
:dbapi: informixdb
|
||||
:connectstring: informix+informixdb://user:password@host/dbname
|
||||
:url: http://informixdb.sourceforge.net/
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from sqlalchemy.dialects.informix.base import InformixDialect
|
||||
from sqlalchemy.engine import default
|
||||
|
||||
VERSION_RE = re.compile(r'(\d+)\.(\d+)(.+\d+)')
|
||||
|
||||
|
||||
class InformixExecutionContext_informixdb(default.DefaultExecutionContext):
|
||||
|
||||
def post_exec(self):
|
||||
if self.isinsert:
|
||||
self._lastrowid = self.cursor.sqlerrd[1]
|
||||
|
||||
def get_lastrowid(self):
|
||||
return self._lastrowid
|
||||
|
||||
|
||||
class InformixDialect_informixdb(InformixDialect):
|
||||
driver = 'informixdb'
|
||||
execution_ctx_cls = InformixExecutionContext_informixdb
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('informixdb')
|
||||
|
||||
def create_connect_args(self, url):
|
||||
if url.host:
|
||||
dsn = '%s@%s' % (url.database, url.host)
|
||||
else:
|
||||
dsn = url.database
|
||||
|
||||
if url.username:
|
||||
opt = {'user': url.username, 'password': url.password}
|
||||
else:
|
||||
opt = {}
|
||||
|
||||
return ([dsn], opt)
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
# http://informixdb.sourceforge.net/manual.html#inspecting-version-numbers
|
||||
v = VERSION_RE.split(connection.connection.dbms_version)
|
||||
return (int(v[1]), int(v[2]), v[3])
|
||||
|
||||
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)
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
dialect = InformixDialect_informixdb
|
||||
@@ -0,0 +1,26 @@
|
||||
# mssql/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.mssql import base, pyodbc, adodbapi, \
|
||||
pymssql, zxjdbc, mxodbc
|
||||
|
||||
base.dialect = pyodbc.dialect
|
||||
|
||||
from sqlalchemy.dialects.mssql.base import \
|
||||
INTEGER, BIGINT, SMALLINT, TINYINT, VARCHAR, NVARCHAR, CHAR, \
|
||||
NCHAR, TEXT, NTEXT, DECIMAL, NUMERIC, FLOAT, DATETIME,\
|
||||
DATETIME2, DATETIMEOFFSET, DATE, TIME, SMALLDATETIME, \
|
||||
BINARY, VARBINARY, BIT, REAL, IMAGE, TIMESTAMP,\
|
||||
MONEY, SMALLMONEY, UNIQUEIDENTIFIER, SQL_VARIANT, dialect
|
||||
|
||||
|
||||
__all__ = (
|
||||
'INTEGER', 'BIGINT', 'SMALLINT', 'TINYINT', 'VARCHAR', 'NVARCHAR', 'CHAR',
|
||||
'NCHAR', 'TEXT', 'NTEXT', 'DECIMAL', 'NUMERIC', 'FLOAT', 'DATETIME',
|
||||
'DATETIME2', 'DATETIMEOFFSET', 'DATE', 'TIME', 'SMALLDATETIME',
|
||||
'BINARY', 'VARBINARY', 'BIT', 'REAL', 'IMAGE', 'TIMESTAMP',
|
||||
'MONEY', 'SMALLMONEY', 'UNIQUEIDENTIFIER', 'SQL_VARIANT', 'dialect'
|
||||
)
|
||||
@@ -0,0 +1,79 @@
|
||||
# mssql/adodbapi.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: mssql+adodbapi
|
||||
:name: adodbapi
|
||||
:dbapi: adodbapi
|
||||
:connectstring: mssql+adodbapi://<username>:<password>@<dsnname>
|
||||
:url: http://adodbapi.sourceforge.net/
|
||||
|
||||
.. note::
|
||||
|
||||
The adodbapi dialect is not implemented SQLAlchemy versions 0.6 and
|
||||
above at this time.
|
||||
|
||||
"""
|
||||
import datetime
|
||||
from sqlalchemy import types as sqltypes, util
|
||||
from sqlalchemy.dialects.mssql.base import MSDateTime, MSDialect
|
||||
import sys
|
||||
|
||||
|
||||
class MSDateTime_adodbapi(MSDateTime):
|
||||
def result_processor(self, dialect, coltype):
|
||||
def process(value):
|
||||
# adodbapi will return datetimes with empty time
|
||||
# values as datetime.date() objects.
|
||||
# Promote them back to full datetime.datetime()
|
||||
if type(value) is datetime.date:
|
||||
return datetime.datetime(value.year, value.month, value.day)
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
class MSDialect_adodbapi(MSDialect):
|
||||
supports_sane_rowcount = True
|
||||
supports_sane_multi_rowcount = True
|
||||
supports_unicode = sys.maxunicode == 65535
|
||||
supports_unicode_statements = True
|
||||
driver = 'adodbapi'
|
||||
|
||||
@classmethod
|
||||
def import_dbapi(cls):
|
||||
import adodbapi as module
|
||||
return module
|
||||
|
||||
colspecs = util.update_copy(
|
||||
MSDialect.colspecs,
|
||||
{
|
||||
sqltypes.DateTime: MSDateTime_adodbapi
|
||||
}
|
||||
)
|
||||
|
||||
def create_connect_args(self, url):
|
||||
keys = url.query
|
||||
|
||||
connectors = ["Provider=SQLOLEDB"]
|
||||
if 'port' in keys:
|
||||
connectors.append("Data Source=%s, %s" %
|
||||
(keys.get("host"), keys.get("port")))
|
||||
else:
|
||||
connectors.append("Data Source=%s" % keys.get("host"))
|
||||
connectors.append("Initial Catalog=%s" % keys.get("database"))
|
||||
user = keys.get("user")
|
||||
if user:
|
||||
connectors.append("User Id=%s" % user)
|
||||
connectors.append("Password=%s" % keys.get("password", ""))
|
||||
else:
|
||||
connectors.append("Integrated Security=SSPI")
|
||||
return [[";".join(connectors)], {}]
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
return isinstance(e, self.dbapi.adodbapi.DatabaseError) and \
|
||||
"'connection failure'" in str(e)
|
||||
|
||||
dialect = MSDialect_adodbapi
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,116 @@
|
||||
# mssql/information_schema.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
# TODO: should be using the sys. catalog with SQL Server, not information schema
|
||||
|
||||
from ... import Table, MetaData, Column
|
||||
from ...types import String, Unicode, Integer, TypeDecorator
|
||||
from ... import cast
|
||||
from ... import util
|
||||
from ...sql import expression
|
||||
from ...ext.compiler import compiles
|
||||
|
||||
ischema = MetaData()
|
||||
|
||||
class CoerceUnicode(TypeDecorator):
|
||||
impl = Unicode
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
# Py2K
|
||||
if isinstance(value, str):
|
||||
value = value.decode(dialect.encoding)
|
||||
# end Py2K
|
||||
return value
|
||||
|
||||
def bind_expression(self, bindvalue):
|
||||
return _cast_on_2005(bindvalue)
|
||||
|
||||
class _cast_on_2005(expression.ColumnElement):
|
||||
def __init__(self, bindvalue):
|
||||
self.bindvalue = bindvalue
|
||||
|
||||
@compiles(_cast_on_2005)
|
||||
def _compile(element, compiler, **kw):
|
||||
from . import base
|
||||
if compiler.dialect.server_version_info < base.MS_2005_VERSION:
|
||||
return compiler.process(element.bindvalue, **kw)
|
||||
else:
|
||||
return compiler.process(cast(element.bindvalue, Unicode), **kw)
|
||||
|
||||
schemata = Table("SCHEMATA", ischema,
|
||||
Column("CATALOG_NAME", CoerceUnicode, key="catalog_name"),
|
||||
Column("SCHEMA_NAME", CoerceUnicode, key="schema_name"),
|
||||
Column("SCHEMA_OWNER", CoerceUnicode, key="schema_owner"),
|
||||
schema="INFORMATION_SCHEMA")
|
||||
|
||||
tables = Table("TABLES", ischema,
|
||||
Column("TABLE_CATALOG", CoerceUnicode, key="table_catalog"),
|
||||
Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"),
|
||||
Column("TABLE_NAME", CoerceUnicode, key="table_name"),
|
||||
Column("TABLE_TYPE", String(convert_unicode=True), key="table_type"),
|
||||
schema="INFORMATION_SCHEMA")
|
||||
|
||||
columns = Table("COLUMNS", ischema,
|
||||
Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"),
|
||||
Column("TABLE_NAME", CoerceUnicode, key="table_name"),
|
||||
Column("COLUMN_NAME", CoerceUnicode, key="column_name"),
|
||||
Column("IS_NULLABLE", Integer, key="is_nullable"),
|
||||
Column("DATA_TYPE", String, key="data_type"),
|
||||
Column("ORDINAL_POSITION", Integer, key="ordinal_position"),
|
||||
Column("CHARACTER_MAXIMUM_LENGTH", Integer, key="character_maximum_length"),
|
||||
Column("NUMERIC_PRECISION", Integer, key="numeric_precision"),
|
||||
Column("NUMERIC_SCALE", Integer, key="numeric_scale"),
|
||||
Column("COLUMN_DEFAULT", Integer, key="column_default"),
|
||||
Column("COLLATION_NAME", String, key="collation_name"),
|
||||
schema="INFORMATION_SCHEMA")
|
||||
|
||||
constraints = Table("TABLE_CONSTRAINTS", ischema,
|
||||
Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"),
|
||||
Column("TABLE_NAME", CoerceUnicode, key="table_name"),
|
||||
Column("CONSTRAINT_NAME", CoerceUnicode, key="constraint_name"),
|
||||
Column("CONSTRAINT_TYPE", String(convert_unicode=True), key="constraint_type"),
|
||||
schema="INFORMATION_SCHEMA")
|
||||
|
||||
column_constraints = Table("CONSTRAINT_COLUMN_USAGE", ischema,
|
||||
Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"),
|
||||
Column("TABLE_NAME", CoerceUnicode, key="table_name"),
|
||||
Column("COLUMN_NAME", CoerceUnicode, key="column_name"),
|
||||
Column("CONSTRAINT_NAME", CoerceUnicode, key="constraint_name"),
|
||||
schema="INFORMATION_SCHEMA")
|
||||
|
||||
key_constraints = Table("KEY_COLUMN_USAGE", ischema,
|
||||
Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"),
|
||||
Column("TABLE_NAME", CoerceUnicode, key="table_name"),
|
||||
Column("COLUMN_NAME", CoerceUnicode, key="column_name"),
|
||||
Column("CONSTRAINT_NAME", CoerceUnicode, key="constraint_name"),
|
||||
Column("ORDINAL_POSITION", Integer, key="ordinal_position"),
|
||||
schema="INFORMATION_SCHEMA")
|
||||
|
||||
ref_constraints = Table("REFERENTIAL_CONSTRAINTS", ischema,
|
||||
Column("CONSTRAINT_CATALOG", CoerceUnicode, key="constraint_catalog"),
|
||||
Column("CONSTRAINT_SCHEMA", CoerceUnicode, key="constraint_schema"),
|
||||
Column("CONSTRAINT_NAME", CoerceUnicode, key="constraint_name"),
|
||||
# TODO: is CATLOG misspelled ?
|
||||
Column("UNIQUE_CONSTRAINT_CATLOG", CoerceUnicode,
|
||||
key="unique_constraint_catalog"),
|
||||
|
||||
Column("UNIQUE_CONSTRAINT_SCHEMA", CoerceUnicode,
|
||||
key="unique_constraint_schema"),
|
||||
Column("UNIQUE_CONSTRAINT_NAME", CoerceUnicode,
|
||||
key="unique_constraint_name"),
|
||||
Column("MATCH_OPTION", String, key="match_option"),
|
||||
Column("UPDATE_RULE", String, key="update_rule"),
|
||||
Column("DELETE_RULE", String, key="delete_rule"),
|
||||
schema="INFORMATION_SCHEMA")
|
||||
|
||||
views = Table("VIEWS", ischema,
|
||||
Column("TABLE_CATALOG", CoerceUnicode, key="table_catalog"),
|
||||
Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"),
|
||||
Column("TABLE_NAME", CoerceUnicode, key="table_name"),
|
||||
Column("VIEW_DEFINITION", CoerceUnicode, key="view_definition"),
|
||||
Column("CHECK_OPTION", String, key="check_option"),
|
||||
Column("IS_UPDATABLE", String, key="is_updatable"),
|
||||
schema="INFORMATION_SCHEMA")
|
||||
@@ -0,0 +1,111 @@
|
||||
# mssql/mxodbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: mssql+mxodbc
|
||||
:name: mxODBC
|
||||
:dbapi: mxodbc
|
||||
:connectstring: mssql+mxodbc://<username>:<password>@<dsnname>
|
||||
:url: http://www.egenix.com/
|
||||
|
||||
Execution Modes
|
||||
---------------
|
||||
|
||||
mxODBC features two styles of statement execution, using the
|
||||
``cursor.execute()`` and ``cursor.executedirect()`` methods (the second being
|
||||
an extension to the DBAPI specification). The former makes use of a particular
|
||||
API call specific to the SQL Server Native Client ODBC driver known
|
||||
SQLDescribeParam, while the latter does not.
|
||||
|
||||
mxODBC apparently only makes repeated use of a single prepared statement
|
||||
when SQLDescribeParam is used. The advantage to prepared statement reuse is
|
||||
one of performance. The disadvantage is that SQLDescribeParam has a limited
|
||||
set of scenarios in which bind parameters are understood, including that they
|
||||
cannot be placed within the argument lists of function calls, anywhere outside
|
||||
the FROM, or even within subqueries within the FROM clause - making the usage
|
||||
of bind parameters within SELECT statements impossible for all but the most
|
||||
simplistic statements.
|
||||
|
||||
For this reason, the mxODBC dialect uses the "native" mode by default only for
|
||||
INSERT, UPDATE, and DELETE statements, and uses the escaped string mode for
|
||||
all other statements.
|
||||
|
||||
This behavior can be controlled via
|
||||
:meth:`~sqlalchemy.sql.expression.Executable.execution_options` using the
|
||||
``native_odbc_execute`` flag with a value of ``True`` or ``False``, where a
|
||||
value of ``True`` will unconditionally use native bind parameters and a value
|
||||
of ``False`` will unconditionally use string-escaped parameters.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from ... import types as sqltypes
|
||||
from ...connectors.mxodbc import MxODBCConnector
|
||||
from .pyodbc import MSExecutionContext_pyodbc, _MSNumeric_pyodbc
|
||||
from .base import (MSDialect,
|
||||
MSSQLStrictCompiler,
|
||||
_MSDateTime, _MSDate, _MSTime)
|
||||
|
||||
|
||||
class _MSNumeric_mxodbc(_MSNumeric_pyodbc):
|
||||
"""Include pyodbc's numeric processor.
|
||||
"""
|
||||
|
||||
|
||||
class _MSDate_mxodbc(_MSDate):
|
||||
def bind_processor(self, dialect):
|
||||
def process(value):
|
||||
if value is not None:
|
||||
return "%s-%s-%s" % (value.year, value.month, value.day)
|
||||
else:
|
||||
return None
|
||||
return process
|
||||
|
||||
|
||||
class _MSTime_mxodbc(_MSTime):
|
||||
def bind_processor(self, dialect):
|
||||
def process(value):
|
||||
if value is not None:
|
||||
return "%s:%s:%s" % (value.hour, value.minute, value.second)
|
||||
else:
|
||||
return None
|
||||
return process
|
||||
|
||||
|
||||
class MSExecutionContext_mxodbc(MSExecutionContext_pyodbc):
|
||||
"""
|
||||
The pyodbc execution context is useful for enabling
|
||||
SELECT SCOPE_IDENTITY in cases where OUTPUT clause
|
||||
does not work (tables with insert triggers).
|
||||
"""
|
||||
#todo - investigate whether the pyodbc execution context
|
||||
# is really only being used in cases where OUTPUT
|
||||
# won't work.
|
||||
|
||||
|
||||
class MSDialect_mxodbc(MxODBCConnector, MSDialect):
|
||||
|
||||
# this is only needed if "native ODBC" mode is used,
|
||||
# which is now disabled by default.
|
||||
#statement_compiler = MSSQLStrictCompiler
|
||||
|
||||
execution_ctx_cls = MSExecutionContext_mxodbc
|
||||
|
||||
# flag used by _MSNumeric_mxodbc
|
||||
_need_decimal_fix = True
|
||||
|
||||
colspecs = {
|
||||
sqltypes.Numeric: _MSNumeric_mxodbc,
|
||||
sqltypes.DateTime: _MSDateTime,
|
||||
sqltypes.Date: _MSDate_mxodbc,
|
||||
sqltypes.Time: _MSTime_mxodbc,
|
||||
}
|
||||
|
||||
def __init__(self, description_encoding=None, **params):
|
||||
super(MSDialect_mxodbc, self).__init__(**params)
|
||||
self.description_encoding = description_encoding
|
||||
|
||||
dialect = MSDialect_mxodbc
|
||||
@@ -0,0 +1,99 @@
|
||||
# mssql/pymssql.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: mssql+pymssql
|
||||
:name: pymssql
|
||||
:dbapi: pymssql
|
||||
:connectstring: mssql+pymssql://<username>:<password>@<freetds_name>?charset=utf8
|
||||
:url: http://pymssql.sourceforge.net/
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
pymssql inherits a lot of limitations from FreeTDS, including:
|
||||
|
||||
* no support for multibyte schema identifiers
|
||||
* poor support for large decimals
|
||||
* poor support for binary fields
|
||||
* poor support for VARCHAR/CHAR fields over 255 characters
|
||||
|
||||
Please consult the pymssql documentation for further information.
|
||||
|
||||
"""
|
||||
from .base import MSDialect
|
||||
from ... import types as sqltypes, util, processors
|
||||
import re
|
||||
|
||||
|
||||
class _MSNumeric_pymssql(sqltypes.Numeric):
|
||||
def result_processor(self, dialect, type_):
|
||||
if not self.asdecimal:
|
||||
return processors.to_float
|
||||
else:
|
||||
return sqltypes.Numeric.result_processor(self, dialect, type_)
|
||||
|
||||
|
||||
class MSDialect_pymssql(MSDialect):
|
||||
supports_sane_rowcount = False
|
||||
driver = 'pymssql'
|
||||
|
||||
colspecs = util.update_copy(
|
||||
MSDialect.colspecs,
|
||||
{
|
||||
sqltypes.Numeric: _MSNumeric_pymssql,
|
||||
sqltypes.Float: sqltypes.Float,
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
module = __import__('pymssql')
|
||||
# pymmsql doesn't have a Binary method. we use string
|
||||
# TODO: monkeypatching here is less than ideal
|
||||
module.Binary = lambda x: x if hasattr(x, 'decode') else str(x)
|
||||
|
||||
client_ver = tuple(int(x) for x in module.__version__.split("."))
|
||||
if client_ver < (1, ):
|
||||
util.warn("The pymssql dialect expects at least "
|
||||
"the 1.0 series of the pymssql DBAPI.")
|
||||
return module
|
||||
|
||||
def __init__(self, **params):
|
||||
super(MSDialect_pymssql, self).__init__(**params)
|
||||
self.use_scope_identity = True
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
vers = connection.scalar("select @@version")
|
||||
m = re.match(
|
||||
r"Microsoft SQL Server.*? - (\d+).(\d+).(\d+).(\d+)", vers)
|
||||
if m:
|
||||
return tuple(int(x) for x in m.group(1, 2, 3, 4))
|
||||
else:
|
||||
return None
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user')
|
||||
opts.update(url.query)
|
||||
port = opts.pop('port', None)
|
||||
if port and 'host' in opts:
|
||||
opts['host'] = "%s:%s" % (opts['host'], port)
|
||||
return [[], opts]
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
for msg in (
|
||||
"Adaptive Server connection timed out",
|
||||
"message 20003", # connection timeout
|
||||
"Error 10054",
|
||||
"Not connected to any MS SQL server",
|
||||
"Connection is closed"
|
||||
):
|
||||
if msg in str(e):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
dialect = MSDialect_pymssql
|
||||
@@ -0,0 +1,254 @@
|
||||
# mssql/pyodbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: mssql+pyodbc
|
||||
:name: PyODBC
|
||||
:dbapi: pyodbc
|
||||
:connectstring: mssql+pyodbc://<username>:<password>@<dsnname>
|
||||
:url: http://pypi.python.org/pypi/pyodbc/
|
||||
|
||||
Additional Connection Examples
|
||||
-------------------------------
|
||||
|
||||
Examples of pyodbc connection string URLs:
|
||||
|
||||
* ``mssql+pyodbc://mydsn`` - connects using the specified DSN named ``mydsn``.
|
||||
The connection string that is created will appear like::
|
||||
|
||||
dsn=mydsn;Trusted_Connection=Yes
|
||||
|
||||
* ``mssql+pyodbc://user:pass@mydsn`` - connects using the DSN named
|
||||
``mydsn`` passing in the ``UID`` and ``PWD`` information. The
|
||||
connection string that is created will appear like::
|
||||
|
||||
dsn=mydsn;UID=user;PWD=pass
|
||||
|
||||
* ``mssql+pyodbc://user:pass@mydsn/?LANGUAGE=us_english`` - connects
|
||||
using the DSN named ``mydsn`` passing in the ``UID`` and ``PWD``
|
||||
information, plus the additional connection configuration option
|
||||
``LANGUAGE``. The connection string that is created will appear
|
||||
like::
|
||||
|
||||
dsn=mydsn;UID=user;PWD=pass;LANGUAGE=us_english
|
||||
|
||||
* ``mssql+pyodbc://user:pass@host/db`` - connects using a connection
|
||||
that would appear like::
|
||||
|
||||
DRIVER={SQL Server};Server=host;Database=db;UID=user;PWD=pass
|
||||
|
||||
* ``mssql+pyodbc://user:pass@host:123/db`` - connects using a connection
|
||||
string which includes the port
|
||||
information using the comma syntax. This will create the following
|
||||
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 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
|
||||
and passed directly.
|
||||
|
||||
For example::
|
||||
|
||||
mssql+pyodbc:///?odbc_connect=dsn%3Dmydsn%3BDatabase%3Ddb
|
||||
|
||||
would create the following connection string::
|
||||
|
||||
dsn=mydsn;Database=db
|
||||
|
||||
Encoding your connection string can be easily accomplished through
|
||||
the python shell. For example::
|
||||
|
||||
>>> import urllib
|
||||
>>> urllib.quote_plus('dsn=mydsn;Database=db')
|
||||
'dsn%3Dmydsn%3BDatabase%3Ddb'
|
||||
|
||||
Unicode Binds
|
||||
-------------
|
||||
|
||||
The current state of PyODBC on a unix backend with FreeTDS and/or
|
||||
EasySoft is poor regarding unicode; different OS platforms and versions of UnixODBC
|
||||
versus IODBC versus FreeTDS/EasySoft versus PyODBC itself dramatically
|
||||
alter how strings are received. The PyODBC dialect attempts to use all the information
|
||||
it knows to determine whether or not a Python unicode literal can be
|
||||
passed directly to the PyODBC driver or not; while SQLAlchemy can encode
|
||||
these to bytestrings first, some users have reported that PyODBC mis-handles
|
||||
bytestrings for certain encodings and requires a Python unicode object,
|
||||
while the author has observed widespread cases where a Python unicode
|
||||
is completely misinterpreted by PyODBC, particularly when dealing with
|
||||
the information schema tables used in table reflection, and the value
|
||||
must first be encoded to a bytestring.
|
||||
|
||||
It is for this reason that whether or not unicode literals for bound
|
||||
parameters be sent to PyODBC can be controlled using the
|
||||
``supports_unicode_binds`` parameter to ``create_engine()``. When
|
||||
left at its default of ``None``, the PyODBC dialect will use its
|
||||
best guess as to whether or not the driver deals with unicode literals
|
||||
well. When ``False``, unicode literals will be encoded first, and when
|
||||
``True`` unicode literals will be passed straight through. This is an interim
|
||||
flag that hopefully should not be needed when the unicode situation stabilizes
|
||||
for unix + PyODBC.
|
||||
|
||||
.. versionadded:: 0.7.7
|
||||
``supports_unicode_binds`` parameter to ``create_engine()``\ .
|
||||
|
||||
"""
|
||||
|
||||
from .base import MSExecutionContext, MSDialect
|
||||
from ...connectors.pyodbc import PyODBCConnector
|
||||
from ... import types as sqltypes, util
|
||||
import decimal
|
||||
|
||||
|
||||
class _MSNumeric_pyodbc(sqltypes.Numeric):
|
||||
"""Turns Decimals with adjusted() < 0 or > 7 into strings.
|
||||
|
||||
The routines here are needed for older pyodbc versions
|
||||
as well as current mxODBC versions.
|
||||
|
||||
"""
|
||||
|
||||
def bind_processor(self, 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 \
|
||||
isinstance(value, decimal.Decimal):
|
||||
|
||||
adjusted = value.adjusted()
|
||||
if adjusted < 0:
|
||||
return self._small_dec_to_string(value)
|
||||
elif adjusted > 7:
|
||||
return self._large_dec_to_string(value)
|
||||
|
||||
if super_process:
|
||||
return super_process(value)
|
||||
else:
|
||||
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.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 _int]),
|
||||
"0" * (value.adjusted() - (len(_int) - 1)))
|
||||
else:
|
||||
if (len(_int) - 1) > value.adjusted():
|
||||
result = "%s%s.%s" % (
|
||||
(value < 0 and '-' or ''),
|
||||
"".join(
|
||||
[str(s) for s in _int][0:value.adjusted() + 1]),
|
||||
"".join(
|
||||
[str(s) for s in _int][value.adjusted() + 1:]))
|
||||
else:
|
||||
result = "%s%s" % (
|
||||
(value < 0 and '-' or ''),
|
||||
"".join(
|
||||
[str(s) for s in _int][0:value.adjusted() + 1]))
|
||||
return result
|
||||
|
||||
|
||||
class MSExecutionContext_pyodbc(MSExecutionContext):
|
||||
_embedded_scope_identity = False
|
||||
|
||||
def pre_exec(self):
|
||||
"""where appropriate, issue "select scope_identity()" in the same
|
||||
statement.
|
||||
|
||||
Background on why "scope_identity()" is preferable to "@@identity":
|
||||
http://msdn.microsoft.com/en-us/library/ms190315.aspx
|
||||
|
||||
Background on why we attempt to embed "scope_identity()" into the same
|
||||
statement as the INSERT:
|
||||
http://code.google.com/p/pyodbc/wiki/FAQs#How_do_I_retrieve_autogenerated/identity_values?
|
||||
|
||||
"""
|
||||
|
||||
super(MSExecutionContext_pyodbc, self).pre_exec()
|
||||
|
||||
# don't embed the scope_identity select into an
|
||||
# "INSERT .. DEFAULT VALUES"
|
||||
if self._select_lastrowid and \
|
||||
self.dialect.use_scope_identity and \
|
||||
len(self.parameters[0]):
|
||||
self._embedded_scope_identity = True
|
||||
|
||||
self.statement += "; select scope_identity()"
|
||||
|
||||
def post_exec(self):
|
||||
if self._embedded_scope_identity:
|
||||
# Fetch the last inserted id from the manipulated statement
|
||||
# We may have to skip over a number of result sets with
|
||||
# no data (due to triggers, etc.)
|
||||
while True:
|
||||
try:
|
||||
# fetchall() ensures the cursor is consumed
|
||||
# without closing it (FreeTDS particularly)
|
||||
row = self.cursor.fetchall()[0]
|
||||
break
|
||||
except self.dialect.dbapi.Error, e:
|
||||
# no way around this - nextset() consumes the previous set
|
||||
# so we need to just keep flipping
|
||||
self.cursor.nextset()
|
||||
|
||||
self._lastrowid = int(row[0])
|
||||
else:
|
||||
super(MSExecutionContext_pyodbc, self).post_exec()
|
||||
|
||||
|
||||
class MSDialect_pyodbc(PyODBCConnector, MSDialect):
|
||||
|
||||
execution_ctx_cls = MSExecutionContext_pyodbc
|
||||
|
||||
pyodbc_driver_name = 'SQL Server'
|
||||
|
||||
colspecs = util.update_copy(
|
||||
MSDialect.colspecs,
|
||||
{
|
||||
sqltypes.Numeric: _MSNumeric_pyodbc
|
||||
}
|
||||
)
|
||||
|
||||
def __init__(self, description_encoding=None, **params):
|
||||
super(MSDialect_pyodbc, self).__init__(**params)
|
||||
self.description_encoding = description_encoding
|
||||
self.use_scope_identity = self.use_scope_identity and \
|
||||
self.dbapi and \
|
||||
hasattr(self.dbapi.Cursor, 'nextset')
|
||||
self._need_decimal_fix = self.dbapi and \
|
||||
self._dbapi_version() < (2, 1, 8)
|
||||
|
||||
dialect = MSDialect_pyodbc
|
||||
@@ -0,0 +1,65 @@
|
||||
# mssql/zxjdbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: mssql+zxjdbc
|
||||
:name: zxJDBC for Jython
|
||||
:dbapi: zxjdbc
|
||||
:connectstring: mssql+zxjdbc://user:pass@host:port/dbname[?key=value&key=value...]
|
||||
:driverurl: http://jtds.sourceforge.net/
|
||||
|
||||
|
||||
"""
|
||||
from ...connectors.zxJDBC import ZxJDBCConnector
|
||||
from .base import MSDialect, MSExecutionContext
|
||||
from ... import engine
|
||||
|
||||
|
||||
class MSExecutionContext_zxjdbc(MSExecutionContext):
|
||||
|
||||
_embedded_scope_identity = False
|
||||
|
||||
def pre_exec(self):
|
||||
super(MSExecutionContext_zxjdbc, self).pre_exec()
|
||||
# scope_identity after the fact returns null in jTDS so we must
|
||||
# embed it
|
||||
if self._select_lastrowid and self.dialect.use_scope_identity:
|
||||
self._embedded_scope_identity = True
|
||||
self.statement += "; SELECT scope_identity()"
|
||||
|
||||
def post_exec(self):
|
||||
if self._embedded_scope_identity:
|
||||
while True:
|
||||
try:
|
||||
row = self.cursor.fetchall()[0]
|
||||
break
|
||||
except self.dialect.dbapi.Error:
|
||||
self.cursor.nextset()
|
||||
self._lastrowid = int(row[0])
|
||||
|
||||
if (self.isinsert or self.isupdate or self.isdelete) and \
|
||||
self.compiled.returning:
|
||||
self._result_proxy = engine.FullyBufferedResultProxy(self)
|
||||
|
||||
if self._enable_identity_insert:
|
||||
table = self.dialect.identifier_preparer.format_table(
|
||||
self.compiled.statement.table)
|
||||
self.cursor.execute("SET IDENTITY_INSERT %s OFF" % table)
|
||||
|
||||
|
||||
class MSDialect_zxjdbc(ZxJDBCConnector, MSDialect):
|
||||
jdbc_db_name = 'jtds:sqlserver'
|
||||
jdbc_driver_name = 'net.sourceforge.jtds.jdbc.Driver'
|
||||
|
||||
execution_ctx_cls = MSExecutionContext_zxjdbc
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
return tuple(
|
||||
int(x)
|
||||
for x in connection.connection.dbversion.split('.')
|
||||
)
|
||||
|
||||
dialect = MSDialect_zxjdbc
|
||||
@@ -0,0 +1,28 @@
|
||||
# mysql/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from . import base, mysqldb, oursql, \
|
||||
pyodbc, zxjdbc, mysqlconnector, pymysql,\
|
||||
gaerdbms, cymysql
|
||||
|
||||
# default dialect
|
||||
base.dialect = mysqldb.dialect
|
||||
|
||||
from .base import \
|
||||
BIGINT, BINARY, BIT, BLOB, BOOLEAN, CHAR, DATE, DATETIME, \
|
||||
DECIMAL, DOUBLE, ENUM, DECIMAL,\
|
||||
FLOAT, INTEGER, INTEGER, LONGBLOB, LONGTEXT, MEDIUMBLOB, \
|
||||
MEDIUMINT, MEDIUMTEXT, NCHAR, \
|
||||
NVARCHAR, NUMERIC, SET, SMALLINT, REAL, TEXT, TIME, TIMESTAMP, \
|
||||
TINYBLOB, TINYINT, TINYTEXT,\
|
||||
VARBINARY, VARCHAR, YEAR, dialect
|
||||
|
||||
__all__ = (
|
||||
'BIGINT', 'BINARY', 'BIT', 'BLOB', 'BOOLEAN', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'DOUBLE',
|
||||
'ENUM', 'DECIMAL', 'FLOAT', 'INTEGER', 'INTEGER', 'LONGBLOB', 'LONGTEXT', 'MEDIUMBLOB', 'MEDIUMINT',
|
||||
'MEDIUMTEXT', 'NCHAR', 'NVARCHAR', 'NUMERIC', 'SET', 'SMALLINT', 'REAL', 'TEXT', 'TIME', 'TIMESTAMP',
|
||||
'TINYBLOB', 'TINYINT', 'TINYTEXT', 'VARBINARY', 'VARCHAR', 'YEAR', 'dialect'
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
# mysql/cymysql.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
|
||||
.. dialect:: mysql+cymysql
|
||||
:name: CyMySQL
|
||||
:dbapi: cymysql
|
||||
:connectstring: mysql+cymysql://<username>:<password>@<host>/<dbname>[?<options>]
|
||||
:url: https://github.com/nakagami/CyMySQL
|
||||
|
||||
"""
|
||||
|
||||
from .mysqldb import MySQLDialect_mysqldb
|
||||
from .base import (BIT, MySQLDialect)
|
||||
from ... import util
|
||||
|
||||
class _cymysqlBIT(BIT):
|
||||
def result_processor(self, dialect, coltype):
|
||||
"""Convert a MySQL's 64 bit, variable length binary string to a long.
|
||||
"""
|
||||
|
||||
def process(value):
|
||||
if value is not None:
|
||||
# Py2K
|
||||
v = 0L
|
||||
for i in map(ord, value):
|
||||
v = v << 8 | i
|
||||
# end Py2K
|
||||
# Py3K
|
||||
#v = 0
|
||||
#for i in value:
|
||||
# v = v << 8 | i
|
||||
return v
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
class MySQLDialect_cymysql(MySQLDialect_mysqldb):
|
||||
driver = 'cymysql'
|
||||
|
||||
description_encoding = None
|
||||
supports_sane_rowcount = False
|
||||
|
||||
colspecs = util.update_copy(
|
||||
MySQLDialect.colspecs,
|
||||
{
|
||||
BIT: _cymysqlBIT,
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('cymysql')
|
||||
|
||||
def _extract_error_code(self, exception):
|
||||
return exception.errno
|
||||
|
||||
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)
|
||||
elif isinstance(e, self.dbapi.InterfaceError):
|
||||
# if underlying connection is closed,
|
||||
# this is the error you get
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
dialect = MySQLDialect_cymysql
|
||||
@@ -0,0 +1,84 @@
|
||||
# mysql/gaerdbms.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
"""
|
||||
.. dialect:: mysql+gaerdbms
|
||||
:name: Google Cloud SQL
|
||||
:dbapi: rdbms
|
||||
:connectstring: mysql+gaerdbms:///<dbname>?instance=<instancename>
|
||||
:url: https://developers.google.com/appengine/docs/python/cloud-sql/developers-guide
|
||||
|
||||
This dialect is based primarily on the :mod:`.mysql.mysqldb` dialect with minimal
|
||||
changes.
|
||||
|
||||
.. versionadded:: 0.7.8
|
||||
|
||||
|
||||
Pooling
|
||||
-------
|
||||
|
||||
Google App Engine connections appear to be randomly recycled,
|
||||
so the dialect does not pool connections. The :class:`.NullPool`
|
||||
implementation is installed within the :class:`.Engine` by
|
||||
default.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from .mysqldb import MySQLDialect_mysqldb
|
||||
from ...pool import NullPool
|
||||
import re
|
||||
|
||||
|
||||
def _is_dev_environment():
|
||||
return os.environ.get('SERVER_SOFTWARE', '').startswith('Development/')
|
||||
|
||||
|
||||
class MySQLDialect_gaerdbms(MySQLDialect_mysqldb):
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
# from django:
|
||||
# http://code.google.com/p/googleappengine/source/
|
||||
# browse/trunk/python/google/storage/speckle/
|
||||
# python/django/backend/base.py#118
|
||||
# see also [ticket:2649]
|
||||
# see also http://stackoverflow.com/q/14224679/34549
|
||||
from google.appengine.api import apiproxy_stub_map
|
||||
|
||||
if _is_dev_environment():
|
||||
from google.appengine.api import rdbms_mysqldb
|
||||
return rdbms_mysqldb
|
||||
elif apiproxy_stub_map.apiproxy.GetStub('rdbms'):
|
||||
from google.storage.speckle.python.api import rdbms_apiproxy
|
||||
return rdbms_apiproxy
|
||||
else:
|
||||
from google.storage.speckle.python.api import rdbms_googleapi
|
||||
return rdbms_googleapi
|
||||
|
||||
@classmethod
|
||||
def get_pool_class(cls, url):
|
||||
# Cloud SQL connections die at any moment
|
||||
return NullPool
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args()
|
||||
if not _is_dev_environment():
|
||||
# 'dsn' and 'instance' are because we are skipping
|
||||
# the traditional google.api.rdbms wrapper
|
||||
opts['dsn'] = ''
|
||||
opts['instance'] = url.query['instance']
|
||||
return [], opts
|
||||
|
||||
def _extract_error_code(self, exception):
|
||||
match = re.compile(r"^(\d+)L?:|^\((\d+)L?,").match(str(exception))
|
||||
# The rdbms api will wrap then re-raise some types of errors
|
||||
# making this regex return no matches.
|
||||
code = match.group(1) or match.group(2) if match else None
|
||||
if code:
|
||||
return int(code)
|
||||
|
||||
dialect = MySQLDialect_gaerdbms
|
||||
@@ -0,0 +1,128 @@
|
||||
# mysql/mysqlconnector.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: mysql+mysqlconnector
|
||||
:name: MySQL Connector/Python
|
||||
:dbapi: myconnpy
|
||||
:connectstring: mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
:url: https://launchpad.net/myconnpy
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from .base import (MySQLDialect,
|
||||
MySQLExecutionContext, MySQLCompiler, MySQLIdentifierPreparer,
|
||||
BIT)
|
||||
|
||||
from ... import util
|
||||
|
||||
|
||||
class MySQLExecutionContext_mysqlconnector(MySQLExecutionContext):
|
||||
|
||||
def get_lastrowid(self):
|
||||
return self.cursor.lastrowid
|
||||
|
||||
|
||||
class MySQLCompiler_mysqlconnector(MySQLCompiler):
|
||||
def visit_mod_binary(self, binary, operator, **kw):
|
||||
return self.process(binary.left, **kw) + " %% " + \
|
||||
self.process(binary.right, **kw)
|
||||
|
||||
def post_process_text(self, text):
|
||||
return text.replace('%', '%%')
|
||||
|
||||
|
||||
class MySQLIdentifierPreparer_mysqlconnector(MySQLIdentifierPreparer):
|
||||
|
||||
def _escape_identifier(self, value):
|
||||
value = value.replace(self.escape_quote, self.escape_to_quote)
|
||||
return value.replace("%", "%%")
|
||||
|
||||
|
||||
class _myconnpyBIT(BIT):
|
||||
def result_processor(self, dialect, coltype):
|
||||
"""MySQL-connector already converts mysql bits, so."""
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class MySQLDialect_mysqlconnector(MySQLDialect):
|
||||
driver = 'mysqlconnector'
|
||||
supports_unicode_statements = True
|
||||
supports_unicode_binds = True
|
||||
supports_sane_rowcount = True
|
||||
supports_sane_multi_rowcount = True
|
||||
|
||||
supports_native_decimal = True
|
||||
|
||||
default_paramstyle = 'format'
|
||||
execution_ctx_cls = MySQLExecutionContext_mysqlconnector
|
||||
statement_compiler = MySQLCompiler_mysqlconnector
|
||||
|
||||
preparer = MySQLIdentifierPreparer_mysqlconnector
|
||||
|
||||
colspecs = util.update_copy(
|
||||
MySQLDialect.colspecs,
|
||||
{
|
||||
BIT: _myconnpyBIT,
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
from mysql import connector
|
||||
return connector
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user')
|
||||
|
||||
opts.update(url.query)
|
||||
|
||||
util.coerce_kw_type(opts, 'buffered', bool)
|
||||
util.coerce_kw_type(opts, 'raise_on_warnings', bool)
|
||||
opts.setdefault('buffered', True)
|
||||
opts.setdefault('raise_on_warnings', True)
|
||||
|
||||
# FOUND_ROWS must be set in ClientFlag to enable
|
||||
# supports_sane_rowcount.
|
||||
if self.dbapi is not None:
|
||||
try:
|
||||
from mysql.connector.constants import ClientFlag
|
||||
client_flags = opts.get('client_flags', ClientFlag.get_default())
|
||||
client_flags |= ClientFlag.FOUND_ROWS
|
||||
opts['client_flags'] = client_flags
|
||||
except:
|
||||
pass
|
||||
return [[], opts]
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
dbapi_con = connection.connection
|
||||
version = dbapi_con.get_server_version()
|
||||
return tuple(version)
|
||||
|
||||
def _detect_charset(self, connection):
|
||||
return connection.connection.charset
|
||||
|
||||
def _extract_error_code(self, exception):
|
||||
return exception.errno
|
||||
|
||||
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):
|
||||
return e.errno in errnos or \
|
||||
"MySQL Connection not available." in str(e)
|
||||
else:
|
||||
return False
|
||||
|
||||
def _compat_fetchall(self, rp, charset=None):
|
||||
return rp.fetchall()
|
||||
|
||||
def _compat_fetchone(self, rp, charset=None):
|
||||
return rp.fetchone()
|
||||
|
||||
dialect = MySQLDialect_mysqlconnector
|
||||
@@ -0,0 +1,78 @@
|
||||
# mysql/mysqldb.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
|
||||
.. dialect:: mysql+mysqldb
|
||||
:name: MySQL-Python
|
||||
:dbapi: mysqldb
|
||||
:connectstring: mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
:url: http://sourceforge.net/projects/mysql-python
|
||||
|
||||
|
||||
Unicode
|
||||
-------
|
||||
|
||||
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. 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')
|
||||
|
||||
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 version 1.2.2 has a serious memory leak related
|
||||
to unicode conversion, a feature which is disabled via ``use_unicode=0``.
|
||||
It is strongly advised to use the latest version of MySQL-Python.
|
||||
|
||||
"""
|
||||
|
||||
from .base import (MySQLDialect, MySQLExecutionContext,
|
||||
MySQLCompiler, MySQLIdentifierPreparer)
|
||||
from ...connectors.mysqldb import (
|
||||
MySQLDBExecutionContext,
|
||||
MySQLDBCompiler,
|
||||
MySQLDBIdentifierPreparer,
|
||||
MySQLDBConnector
|
||||
)
|
||||
|
||||
|
||||
class MySQLExecutionContext_mysqldb(MySQLDBExecutionContext, MySQLExecutionContext):
|
||||
pass
|
||||
|
||||
|
||||
class MySQLCompiler_mysqldb(MySQLDBCompiler, MySQLCompiler):
|
||||
pass
|
||||
|
||||
|
||||
class MySQLIdentifierPreparer_mysqldb(MySQLDBIdentifierPreparer, MySQLIdentifierPreparer):
|
||||
pass
|
||||
|
||||
|
||||
class MySQLDialect_mysqldb(MySQLDBConnector, MySQLDialect):
|
||||
execution_ctx_cls = MySQLExecutionContext_mysqldb
|
||||
statement_compiler = MySQLCompiler_mysqldb
|
||||
preparer = MySQLIdentifierPreparer_mysqldb
|
||||
|
||||
dialect = MySQLDialect_mysqldb
|
||||
@@ -0,0 +1,262 @@
|
||||
# mysql/oursql.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
|
||||
.. dialect:: mysql+oursql
|
||||
:name: OurSQL
|
||||
:dbapi: oursql
|
||||
:connectstring: mysql+oursql://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
:url: http://packages.python.org/oursql/
|
||||
|
||||
Unicode
|
||||
-------
|
||||
|
||||
oursql defaults to using ``utf8`` as the connection charset, but other
|
||||
encodings may be used instead. Like the MySQL-Python driver, unicode support
|
||||
can be completely disabled::
|
||||
|
||||
# oursql sets the connection charset to utf8 automatically; all strings come
|
||||
# back as utf8 str
|
||||
create_engine('mysql+oursql:///mydb?use_unicode=0')
|
||||
|
||||
To not automatically use ``utf8`` and instead use whatever the connection
|
||||
defaults to, there is a separate parameter::
|
||||
|
||||
# use the default connection charset; all strings come back as unicode
|
||||
create_engine('mysql+oursql:///mydb?default_charset=1')
|
||||
|
||||
# use latin1 as the connection charset; all strings come back as unicode
|
||||
create_engine('mysql+oursql:///mydb?charset=latin1')
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from .base import (BIT, MySQLDialect, MySQLExecutionContext)
|
||||
from ... import types as sqltypes, util
|
||||
|
||||
|
||||
class _oursqlBIT(BIT):
|
||||
def result_processor(self, dialect, coltype):
|
||||
"""oursql already converts mysql bits, so."""
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class MySQLExecutionContext_oursql(MySQLExecutionContext):
|
||||
|
||||
@property
|
||||
def plain_query(self):
|
||||
return self.execution_options.get('_oursql_plain_query', False)
|
||||
|
||||
|
||||
class MySQLDialect_oursql(MySQLDialect):
|
||||
driver = 'oursql'
|
||||
# Py2K
|
||||
supports_unicode_binds = True
|
||||
supports_unicode_statements = True
|
||||
# end Py2K
|
||||
|
||||
supports_native_decimal = True
|
||||
|
||||
supports_sane_rowcount = True
|
||||
supports_sane_multi_rowcount = True
|
||||
execution_ctx_cls = MySQLExecutionContext_oursql
|
||||
|
||||
colspecs = util.update_copy(
|
||||
MySQLDialect.colspecs,
|
||||
{
|
||||
sqltypes.Time: sqltypes.Time,
|
||||
BIT: _oursqlBIT,
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('oursql')
|
||||
|
||||
def do_execute(self, cursor, statement, parameters, context=None):
|
||||
"""Provide an implementation of *cursor.execute(statement, parameters)*."""
|
||||
|
||||
if context and context.plain_query:
|
||||
cursor.execute(statement, plain_query=True)
|
||||
else:
|
||||
cursor.execute(statement, parameters)
|
||||
|
||||
def do_begin(self, connection):
|
||||
connection.cursor().execute('BEGIN', plain_query=True)
|
||||
|
||||
def _xa_query(self, connection, query, xid):
|
||||
# Py2K
|
||||
arg = connection.connection._escape_string(xid)
|
||||
# end Py2K
|
||||
# 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
|
||||
# reimplemented to use _PlainQuery. Basically, some queries
|
||||
# refuse to return any data if they're run through
|
||||
# the parameterized query API, or refuse to be parameterized
|
||||
# in the first place.
|
||||
def do_begin_twophase(self, connection, xid):
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
# Q: why didn't we need all these "plain_query" overrides earlier ?
|
||||
# am i on a newer/older version of OurSQL ?
|
||||
def has_table(self, connection, table_name, schema=None):
|
||||
return MySQLDialect.has_table(
|
||||
self,
|
||||
connection.connect().execution_options(_oursql_plain_query=True),
|
||||
table_name,
|
||||
schema
|
||||
)
|
||||
|
||||
def get_table_options(self, connection, table_name, schema=None, **kw):
|
||||
return MySQLDialect.get_table_options(
|
||||
self,
|
||||
connection.connect().execution_options(_oursql_plain_query=True),
|
||||
table_name,
|
||||
schema=schema,
|
||||
**kw
|
||||
)
|
||||
|
||||
def get_columns(self, connection, table_name, schema=None, **kw):
|
||||
return MySQLDialect.get_columns(
|
||||
self,
|
||||
connection.connect().execution_options(_oursql_plain_query=True),
|
||||
table_name,
|
||||
schema=schema,
|
||||
**kw
|
||||
)
|
||||
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
return MySQLDialect.get_view_names(
|
||||
self,
|
||||
connection.connect().execution_options(_oursql_plain_query=True),
|
||||
schema=schema,
|
||||
**kw
|
||||
)
|
||||
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
return MySQLDialect.get_table_names(
|
||||
self,
|
||||
connection.connect().execution_options(_oursql_plain_query=True),
|
||||
schema
|
||||
)
|
||||
|
||||
def get_schema_names(self, connection, **kw):
|
||||
return MySQLDialect.get_schema_names(
|
||||
self,
|
||||
connection.connect().execution_options(_oursql_plain_query=True),
|
||||
**kw
|
||||
)
|
||||
|
||||
def initialize(self, connection):
|
||||
return MySQLDialect.initialize(
|
||||
self,
|
||||
connection.execution_options(_oursql_plain_query=True)
|
||||
)
|
||||
|
||||
def _show_create_table(self, connection, table, charset=None,
|
||||
full_name=None):
|
||||
return MySQLDialect._show_create_table(
|
||||
self,
|
||||
connection.contextual_connect(close_with_result=True).
|
||||
execution_options(_oursql_plain_query=True),
|
||||
table, charset, full_name
|
||||
)
|
||||
|
||||
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:
|
||||
return e.errno in (2006, 2013, 2014, 2045, 2055)
|
||||
|
||||
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, 'port', int)
|
||||
util.coerce_kw_type(opts, 'compress', bool)
|
||||
util.coerce_kw_type(opts, 'autoping', bool)
|
||||
util.coerce_kw_type(opts, 'raise_on_warnings', bool)
|
||||
|
||||
util.coerce_kw_type(opts, 'default_charset', bool)
|
||||
if opts.pop('default_charset', False):
|
||||
opts['charset'] = None
|
||||
else:
|
||||
util.coerce_kw_type(opts, 'charset', str)
|
||||
opts['use_unicode'] = opts.get('use_unicode', True)
|
||||
util.coerce_kw_type(opts, 'use_unicode', bool)
|
||||
|
||||
# FOUND_ROWS must be set in CLIENT_FLAGS to enable
|
||||
# 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):
|
||||
dbapi_con = connection.connection
|
||||
version = []
|
||||
r = re.compile('[.\-]')
|
||||
for n in r.split(dbapi_con.server_info):
|
||||
try:
|
||||
version.append(int(n))
|
||||
except ValueError:
|
||||
version.append(n)
|
||||
return tuple(version)
|
||||
|
||||
def _extract_error_code(self, exception):
|
||||
return exception.errno
|
||||
|
||||
def _detect_charset(self, connection):
|
||||
"""Sniff out the character set in use for connection results."""
|
||||
|
||||
return connection.connection.charset
|
||||
|
||||
def _compat_fetchall(self, rp, charset=None):
|
||||
"""oursql isn't super-broken like MySQLdb, yaaay."""
|
||||
return rp.fetchall()
|
||||
|
||||
def _compat_fetchone(self, rp, charset=None):
|
||||
"""oursql isn't super-broken like MySQLdb, yaaay."""
|
||||
return rp.fetchone()
|
||||
|
||||
def _compat_first(self, rp, charset=None):
|
||||
return rp.first()
|
||||
|
||||
|
||||
dialect = MySQLDialect_oursql
|
||||
@@ -0,0 +1,44 @@
|
||||
# mysql/pymysql.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
|
||||
.. dialect:: mysql+pymysql
|
||||
:name: PyMySQL
|
||||
:dbapi: pymysql
|
||||
:connectstring: mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
|
||||
:url: http://code.google.com/p/pymysql/
|
||||
|
||||
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 .mysqldb import MySQLDialect_mysqldb
|
||||
from ...util import py3k
|
||||
|
||||
class MySQLDialect_pymysql(MySQLDialect_mysqldb):
|
||||
driver = 'pymysql'
|
||||
|
||||
description_encoding = None
|
||||
if py3k:
|
||||
supports_unicode_statements = True
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('pymysql')
|
||||
|
||||
if py3k:
|
||||
def _extract_error_code(self, exception):
|
||||
if isinstance(exception.args[0], Exception):
|
||||
exception = exception.args[0]
|
||||
return exception.args[0]
|
||||
|
||||
dialect = MySQLDialect_pymysql
|
||||
@@ -0,0 +1,80 @@
|
||||
# mysql/pyodbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
|
||||
|
||||
.. dialect:: mysql+pyodbc
|
||||
:name: PyODBC
|
||||
:dbapi: pyodbc
|
||||
:connectstring: mysql+pyodbc://<username>:<password>@<dsnname>
|
||||
:url: http://pypi.python.org/pypi/pyodbc/
|
||||
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
The mysql-pyodbc dialect is subject to unresolved character encoding issues
|
||||
which exist within the current ODBC drivers available.
|
||||
(see http://code.google.com/p/pyodbc/issues/detail?id=25). Consider usage
|
||||
of OurSQL, MySQLdb, or MySQL-connector/Python.
|
||||
|
||||
"""
|
||||
|
||||
from .base import MySQLDialect, MySQLExecutionContext
|
||||
from ...connectors.pyodbc import PyODBCConnector
|
||||
from ... import util
|
||||
import re
|
||||
|
||||
|
||||
class MySQLExecutionContext_pyodbc(MySQLExecutionContext):
|
||||
|
||||
def get_lastrowid(self):
|
||||
cursor = self.create_cursor()
|
||||
cursor.execute("SELECT LAST_INSERT_ID()")
|
||||
lastrowid = cursor.fetchone()[0]
|
||||
cursor.close()
|
||||
return lastrowid
|
||||
|
||||
|
||||
class MySQLDialect_pyodbc(PyODBCConnector, MySQLDialect):
|
||||
supports_unicode_statements = False
|
||||
execution_ctx_cls = MySQLExecutionContext_pyodbc
|
||||
|
||||
pyodbc_driver_name = "MySQL"
|
||||
|
||||
def __init__(self, **kw):
|
||||
# deal with http://code.google.com/p/pyodbc/issues/detail?id=25
|
||||
kw.setdefault('convert_unicode', True)
|
||||
super(MySQLDialect_pyodbc, self).__init__(**kw)
|
||||
|
||||
def _detect_charset(self, connection):
|
||||
"""Sniff out the character set in use for connection results."""
|
||||
|
||||
# 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)])
|
||||
for key in ('character_set_connection', 'character_set'):
|
||||
if opts.get(key, None):
|
||||
return opts[key]
|
||||
|
||||
util.warn("Could not detect the connection character set. Assuming latin1.")
|
||||
return 'latin1'
|
||||
|
||||
def _extract_error_code(self, exception):
|
||||
m = re.compile(r"\((\d+)\)").search(str(exception.args))
|
||||
c = m.group(1)
|
||||
if c:
|
||||
return int(c)
|
||||
else:
|
||||
return None
|
||||
|
||||
dialect = MySQLDialect_pyodbc
|
||||
@@ -0,0 +1,111 @@
|
||||
# mysql/zxjdbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
|
||||
.. dialect:: mysql+zxjdbc
|
||||
:name: zxjdbc for Jython
|
||||
:dbapi: zxjdbc
|
||||
:connectstring: mysql+zxjdbc://<user>:<password>@<hostname>[:<port>]/<database>
|
||||
:driverurl: http://dev.mysql.com/downloads/connector/j/
|
||||
|
||||
Character Sets
|
||||
--------------
|
||||
|
||||
SQLAlchemy zxjdbc dialects pass unicode straight through to the
|
||||
zxjdbc/JDBC layer. To allow multiple character sets to be sent from the
|
||||
MySQL Connector/J JDBC driver, by default SQLAlchemy sets its
|
||||
``characterEncoding`` connection property to ``UTF-8``. It may be
|
||||
overriden via a ``create_engine`` URL parameter.
|
||||
|
||||
"""
|
||||
import re
|
||||
|
||||
from ... import types as sqltypes, util
|
||||
from ...connectors.zxJDBC import ZxJDBCConnector
|
||||
from .base import BIT, MySQLDialect, MySQLExecutionContext
|
||||
|
||||
|
||||
class _ZxJDBCBit(BIT):
|
||||
def result_processor(self, dialect, coltype):
|
||||
"""Converts boolean or byte arrays from MySQL Connector/J to longs."""
|
||||
def process(value):
|
||||
if value is None:
|
||||
return value
|
||||
if isinstance(value, bool):
|
||||
return int(value)
|
||||
v = 0L
|
||||
for i in value:
|
||||
v = v << 8 | (i & 0xff)
|
||||
value = v
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
class MySQLExecutionContext_zxjdbc(MySQLExecutionContext):
|
||||
def get_lastrowid(self):
|
||||
cursor = self.create_cursor()
|
||||
cursor.execute("SELECT LAST_INSERT_ID()")
|
||||
lastrowid = cursor.fetchone()[0]
|
||||
cursor.close()
|
||||
return lastrowid
|
||||
|
||||
|
||||
class MySQLDialect_zxjdbc(ZxJDBCConnector, MySQLDialect):
|
||||
jdbc_db_name = 'mysql'
|
||||
jdbc_driver_name = 'com.mysql.jdbc.Driver'
|
||||
|
||||
execution_ctx_cls = MySQLExecutionContext_zxjdbc
|
||||
|
||||
colspecs = util.update_copy(
|
||||
MySQLDialect.colspecs,
|
||||
{
|
||||
sqltypes.Time: sqltypes.Time,
|
||||
BIT: _ZxJDBCBit
|
||||
}
|
||||
)
|
||||
|
||||
def _detect_charset(self, connection):
|
||||
"""Sniff out the character set in use for connection results."""
|
||||
# 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))
|
||||
for key in ('character_set_connection', 'character_set'):
|
||||
if opts.get(key, None):
|
||||
return opts[key]
|
||||
|
||||
util.warn("Could not detect the connection character set. Assuming latin1.")
|
||||
return 'latin1'
|
||||
|
||||
def _driver_kwargs(self):
|
||||
"""return kw arg dict to be sent to connect()."""
|
||||
return dict(characterEncoding='UTF-8', yearIsDateType='false')
|
||||
|
||||
def _extract_error_code(self, exception):
|
||||
# e.g.: DBAPIError: (Error) Table 'test.u2' doesn't exist
|
||||
# [SQLCode: 1146], [SQLState: 42S02] 'DESCRIBE `u2`' ()
|
||||
m = re.compile(r"\[SQLCode\: (\d+)\]").search(str(exception.args))
|
||||
c = m.group(1)
|
||||
if c:
|
||||
return int(c)
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
dbapi_con = connection.connection
|
||||
version = []
|
||||
r = re.compile('[.\-]')
|
||||
for n in r.split(dbapi_con.dbversion):
|
||||
try:
|
||||
version.append(int(n))
|
||||
except ValueError:
|
||||
version.append(n)
|
||||
return tuple(version)
|
||||
|
||||
dialect = MySQLDialect_zxjdbc
|
||||
@@ -0,0 +1,23 @@
|
||||
# oracle/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.oracle import base, cx_oracle, zxjdbc
|
||||
|
||||
base.dialect = cx_oracle.dialect
|
||||
|
||||
from sqlalchemy.dialects.oracle.base import \
|
||||
VARCHAR, NVARCHAR, CHAR, DATE, DATETIME, NUMBER,\
|
||||
BLOB, BFILE, CLOB, NCLOB, TIMESTAMP, RAW,\
|
||||
FLOAT, DOUBLE_PRECISION, LONG, dialect, INTERVAL,\
|
||||
VARCHAR2, NVARCHAR2, ROWID, dialect
|
||||
|
||||
|
||||
__all__ = (
|
||||
'VARCHAR', 'NVARCHAR', 'CHAR', 'DATE', 'DATETIME', 'NUMBER',
|
||||
'BLOB', 'BFILE', 'CLOB', 'NCLOB', 'TIMESTAMP', 'RAW',
|
||||
'FLOAT', 'DOUBLE_PRECISION', 'LONG', 'dialect', 'INTERVAL',
|
||||
'VARCHAR2', 'NVARCHAR2', 'ROWID'
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,845 @@
|
||||
# oracle/cx_oracle.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
|
||||
.. dialect:: oracle+cx_oracle
|
||||
:name: cx-Oracle
|
||||
:dbapi: cx_oracle
|
||||
:connectstring: oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]
|
||||
:url: http://cx-oracle.sourceforge.net/
|
||||
|
||||
Additional Connect Arguments
|
||||
----------------------------
|
||||
|
||||
When connecting with ``dbname`` present, the host, port, and dbname tokens are
|
||||
converted to a TNS name using
|
||||
the cx_oracle :func:`makedsn()` function. Otherwise, the host token is taken
|
||||
directly as a TNS name.
|
||||
|
||||
Additional arguments which may be specified either as query string arguments
|
||||
on the URL, or as keyword arguments to :func:`~sqlalchemy.create_engine()` are:
|
||||
|
||||
* allow_twophase - enable two-phase transactions. Defaults to ``True``.
|
||||
|
||||
* arraysize - set the cx_oracle.arraysize value on cursors, in SQLAlchemy
|
||||
it defaults to 50. See the section on "LOB Objects" below.
|
||||
|
||||
* auto_convert_lobs - defaults to True, see the section on LOB objects.
|
||||
|
||||
* auto_setinputsizes - the cx_oracle.setinputsizes() call is issued for
|
||||
all bind parameters. This is required for LOB datatypes but can be
|
||||
disabled to reduce overhead. Defaults to ``True``. Specific types
|
||||
can be excluded from this process using the ``exclude_setinputsizes``
|
||||
parameter.
|
||||
|
||||
* exclude_setinputsizes - a tuple or list of string DBAPI type names to
|
||||
be excluded from the "auto setinputsizes" feature. The type names here
|
||||
must match DBAPI types that are found in the "cx_Oracle" module namespace,
|
||||
such as cx_Oracle.UNICODE, cx_Oracle.NCLOB, etc. Defaults to
|
||||
``(STRING, UNICODE)``.
|
||||
|
||||
.. versionadded:: 0.8 specific DBAPI types can be excluded from the
|
||||
auto_setinputsizes feature via the exclude_setinputsizes attribute.
|
||||
|
||||
* mode - This is given the string value of SYSDBA or SYSOPER, or alternatively
|
||||
an integer value. This value is only available as a URL query string
|
||||
argument.
|
||||
|
||||
* threaded - enable multithreaded access to cx_oracle connections. Defaults
|
||||
to ``True``. Note that this is the opposite default of the cx_Oracle DBAPI
|
||||
itself.
|
||||
|
||||
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.
|
||||
|
||||
LOB Objects
|
||||
-----------
|
||||
|
||||
cx_oracle returns oracle LOBs using the cx_oracle.LOB object. SQLAlchemy converts
|
||||
these to strings so that the interface of the Binary type is consistent with that of
|
||||
other backends, and so that the linkage to a live cursor is not needed in scenarios
|
||||
like result.fetchmany() and result.fetchall(). This means that by default, LOB
|
||||
objects are fully fetched unconditionally by SQLAlchemy, and the linkage to a live
|
||||
cursor is broken.
|
||||
|
||||
To disable this processing, pass ``auto_convert_lobs=False`` to :func:`create_engine()`.
|
||||
|
||||
Two Phase Transaction Support
|
||||
-----------------------------
|
||||
|
||||
Two Phase transactions are implemented using XA transactions, and are known
|
||||
to work in a rudimental fashion with recent versions of cx_Oracle
|
||||
as of SQLAlchemy 0.8.0b2, 0.7.10. However, the mechanism is not yet
|
||||
considered to be robust and should still be regarded as experimental.
|
||||
|
||||
In particular, the cx_Oracle DBAPI as recently as 5.1.2 has a bug regarding
|
||||
two phase which prevents
|
||||
a particular DBAPI connection from being consistently usable in both
|
||||
prepared transactions as well as traditional DBAPI usage patterns; therefore
|
||||
once a particular connection is used via :meth:`.Connection.begin_prepared`,
|
||||
all subsequent usages of the underlying DBAPI connection must be within
|
||||
the context of prepared transactions.
|
||||
|
||||
The default behavior of :class:`.Engine` is to maintain a pool of DBAPI
|
||||
connections. Therefore, due to the above glitch, a DBAPI connection that has
|
||||
been used in a two-phase operation, and is then returned to the pool, will
|
||||
not be usable in a non-two-phase context. To avoid this situation,
|
||||
the application can make one of several choices:
|
||||
|
||||
* Disable connection pooling using :class:`.NullPool`
|
||||
|
||||
* Ensure that the particular :class:`.Engine` in use is only used
|
||||
for two-phase operations. A :class:`.Engine` bound to an ORM
|
||||
:class:`.Session` which includes ``twophase=True`` will consistently
|
||||
use the two-phase transaction style.
|
||||
|
||||
* For ad-hoc two-phase operations without disabling pooling, the DBAPI
|
||||
connection in use can be evicted from the connection pool using the
|
||||
:class:`.Connection.detach` method.
|
||||
|
||||
.. versionchanged:: 0.8.0b2,0.7.10
|
||||
Support for cx_oracle prepared transactions has been implemented
|
||||
and tested.
|
||||
|
||||
|
||||
Precision Numerics
|
||||
------------------
|
||||
|
||||
The SQLAlchemy dialect goes through a lot of steps to ensure
|
||||
that decimal numbers are sent and received with full accuracy.
|
||||
An "outputtypehandler" callable is associated with each
|
||||
cx_oracle connection object which detects numeric types and
|
||||
receives them as string values, instead of receiving a Python
|
||||
``float`` directly, which is then passed to the Python
|
||||
``Decimal`` constructor. The :class:`.Numeric` and
|
||||
:class:`.Float` types under the cx_oracle dialect are aware of
|
||||
this behavior, and will coerce the ``Decimal`` to ``float`` if
|
||||
the ``asdecimal`` flag is ``False`` (default on :class:`.Float`,
|
||||
optional on :class:`.Numeric`).
|
||||
|
||||
Because the handler coerces to ``Decimal`` in all cases first,
|
||||
the feature can detract significantly from performance.
|
||||
If precision numerics aren't required, the decimal handling
|
||||
can be disabled by passing the flag ``coerce_to_decimal=False``
|
||||
to :func:`.create_engine`::
|
||||
|
||||
engine = create_engine("oracle+cx_oracle://dsn",
|
||||
coerce_to_decimal=False)
|
||||
|
||||
.. versionadded:: 0.7.6
|
||||
Add the ``coerce_to_decimal`` flag.
|
||||
|
||||
Another alternative to performance is to use the
|
||||
`cdecimal <http://pypi.python.org/pypi/cdecimal/>`_ library;
|
||||
see :class:`.Numeric` for additional notes.
|
||||
|
||||
The handler attempts to use the "precision" and "scale"
|
||||
attributes of the result set column to best determine if
|
||||
subsequent incoming values should be received as ``Decimal`` as
|
||||
opposed to int (in which case no processing is added). There are
|
||||
several scenarios where OCI_ does not provide unambiguous data
|
||||
as to the numeric type, including some situations where
|
||||
individual rows may return a combination of floating point and
|
||||
integer values. Certain values for "precision" and "scale" have
|
||||
been observed to determine this scenario. When it occurs, the
|
||||
outputtypehandler receives as string and then passes off to a
|
||||
processing function which detects, for each returned value, if a
|
||||
decimal point is present, and if so converts to ``Decimal``,
|
||||
otherwise to int. The intention is that simple int-based
|
||||
statements like "SELECT my_seq.nextval() FROM DUAL" continue to
|
||||
return ints and not ``Decimal`` objects, and that any kind of
|
||||
floating point value is received as a string so that there is no
|
||||
floating point loss of precision.
|
||||
|
||||
The "decimal point is present" logic itself is also sensitive to
|
||||
locale. Under OCI_, this is controlled by the NLS_LANG
|
||||
environment variable. Upon first connection, the dialect runs a
|
||||
test to determine the current "decimal" character, which can be
|
||||
a comma "," for european locales. From that point forward the
|
||||
outputtypehandler uses that character to represent a decimal
|
||||
point. Note that cx_oracle 5.0.3 or greater is required
|
||||
when dealing with numerics with locale settings that don't use
|
||||
a period "." as the decimal character.
|
||||
|
||||
.. versionchanged:: 0.6.6
|
||||
The outputtypehandler uses a comma "," character to represent
|
||||
a decimal point.
|
||||
|
||||
.. _OCI: http://www.oracle.com/technetwork/database/features/oci/index.html
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .base import OracleCompiler, OracleDialect, OracleExecutionContext
|
||||
from . import base as oracle
|
||||
from ...engine import result as _result
|
||||
from sqlalchemy import types as sqltypes, util, exc, processors
|
||||
import random
|
||||
import collections
|
||||
import decimal
|
||||
import re
|
||||
|
||||
|
||||
class _OracleNumeric(sqltypes.Numeric):
|
||||
def bind_processor(self, dialect):
|
||||
# cx_oracle accepts Decimal objects and floats
|
||||
return None
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
# we apply a cx_oracle type handler to all connections
|
||||
# that converts floating point strings to Decimal().
|
||||
# However, in some subquery situations, Oracle doesn't
|
||||
# give us enough information to determine int or Decimal.
|
||||
# It could even be int/Decimal differently on each row,
|
||||
# regardless of the scale given for the originating type.
|
||||
# So we still need an old school isinstance() handler
|
||||
# here for decimals.
|
||||
|
||||
if dialect.supports_native_decimal:
|
||||
if self.asdecimal:
|
||||
if self.scale is None:
|
||||
fstring = "%.10f"
|
||||
else:
|
||||
fstring = "%%.%df" % self.scale
|
||||
|
||||
def to_decimal(value):
|
||||
if value is None:
|
||||
return None
|
||||
elif isinstance(value, decimal.Decimal):
|
||||
return value
|
||||
else:
|
||||
return decimal.Decimal(fstring % value)
|
||||
|
||||
return to_decimal
|
||||
else:
|
||||
if self.precision is None and self.scale is None:
|
||||
return processors.to_float
|
||||
elif not getattr(self, '_is_oracle_number', False) \
|
||||
and self.scale is not None:
|
||||
return processors.to_float
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
# cx_oracle 4 behavior, will assume
|
||||
# floats
|
||||
return super(_OracleNumeric, self).\
|
||||
result_processor(dialect, coltype)
|
||||
|
||||
|
||||
class _OracleDate(sqltypes.Date):
|
||||
def bind_processor(self, dialect):
|
||||
return None
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
def process(value):
|
||||
if value is not None:
|
||||
return value.date()
|
||||
else:
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
class _LOBMixin(object):
|
||||
def result_processor(self, dialect, coltype):
|
||||
if not dialect.auto_convert_lobs:
|
||||
# return the cx_oracle.LOB directly.
|
||||
return None
|
||||
|
||||
def process(value):
|
||||
if value is not None:
|
||||
return value.read()
|
||||
else:
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
class _NativeUnicodeMixin(object):
|
||||
# Py3K
|
||||
#pass
|
||||
# Py2K
|
||||
def bind_processor(self, dialect):
|
||||
if dialect._cx_oracle_with_unicode:
|
||||
def process(value):
|
||||
if value is None:
|
||||
return value
|
||||
else:
|
||||
return unicode(value)
|
||||
return process
|
||||
else:
|
||||
return super(_NativeUnicodeMixin, self).bind_processor(dialect)
|
||||
# end Py2K
|
||||
|
||||
# we apply a connection output handler that returns
|
||||
# unicode in all cases, so the "native_unicode" flag
|
||||
# will be set for the default String.result_processor.
|
||||
|
||||
|
||||
class _OracleChar(_NativeUnicodeMixin, sqltypes.CHAR):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.FIXED_CHAR
|
||||
|
||||
|
||||
class _OracleNVarChar(_NativeUnicodeMixin, sqltypes.NVARCHAR):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return getattr(dbapi, 'UNICODE', dbapi.STRING)
|
||||
|
||||
|
||||
class _OracleText(_LOBMixin, sqltypes.Text):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.CLOB
|
||||
|
||||
|
||||
class _OracleLong(oracle.LONG):
|
||||
# a raw LONG is a text type, but does *not*
|
||||
# get the LobMixin with cx_oracle.
|
||||
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.LONG_STRING
|
||||
|
||||
class _OracleString(_NativeUnicodeMixin, sqltypes.String):
|
||||
pass
|
||||
|
||||
|
||||
class _OracleUnicodeText(_LOBMixin, _NativeUnicodeMixin, sqltypes.UnicodeText):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.NCLOB
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
lob_processor = _LOBMixin.result_processor(self, dialect, coltype)
|
||||
if lob_processor is None:
|
||||
return None
|
||||
|
||||
string_processor = sqltypes.UnicodeText.result_processor(self, dialect, coltype)
|
||||
|
||||
if string_processor is None:
|
||||
return lob_processor
|
||||
else:
|
||||
def process(value):
|
||||
return string_processor(lob_processor(value))
|
||||
return process
|
||||
|
||||
|
||||
class _OracleInteger(sqltypes.Integer):
|
||||
def result_processor(self, dialect, coltype):
|
||||
def to_int(val):
|
||||
if val is not None:
|
||||
val = int(val)
|
||||
return val
|
||||
return to_int
|
||||
|
||||
|
||||
class _OracleBinary(_LOBMixin, sqltypes.LargeBinary):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.BLOB
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
return None
|
||||
|
||||
|
||||
class _OracleInterval(oracle.INTERVAL):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.INTERVAL
|
||||
|
||||
|
||||
class _OracleRaw(oracle.RAW):
|
||||
pass
|
||||
|
||||
|
||||
class _OracleRowid(oracle.ROWID):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.ROWID
|
||||
|
||||
|
||||
class OracleCompiler_cx_oracle(OracleCompiler):
|
||||
def bindparam_string(self, name, quote=None, **kw):
|
||||
if quote is True or quote is not False and \
|
||||
self.preparer._bindparam_requires_quotes(name):
|
||||
quoted_name = '"%s"' % name
|
||||
self._quoted_bind_names[name] = quoted_name
|
||||
return OracleCompiler.bindparam_string(self, quoted_name, **kw)
|
||||
else:
|
||||
return OracleCompiler.bindparam_string(self, name, **kw)
|
||||
|
||||
|
||||
class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
|
||||
def pre_exec(self):
|
||||
quoted_bind_names = \
|
||||
getattr(self.compiled, '_quoted_bind_names', None)
|
||||
if quoted_bind_names:
|
||||
if not self.dialect.supports_unicode_statements:
|
||||
# if DBAPI doesn't accept unicode statements,
|
||||
# keys in self.parameters would have been encoded
|
||||
# here. so convert names in quoted_bind_names
|
||||
# to encoded as well.
|
||||
quoted_bind_names = \
|
||||
dict(
|
||||
(fromname.encode(self.dialect.encoding),
|
||||
toname.encode(self.dialect.encoding))
|
||||
for fromname, toname in
|
||||
quoted_bind_names.items()
|
||||
)
|
||||
for param in self.parameters:
|
||||
for fromname, toname in quoted_bind_names.items():
|
||||
param[toname] = param[fromname]
|
||||
del param[fromname]
|
||||
|
||||
if self.dialect.auto_setinputsizes:
|
||||
# cx_oracle really has issues when you setinputsizes
|
||||
# on String, including that outparams/RETURNING
|
||||
# breaks for varchars
|
||||
self.set_input_sizes(quoted_bind_names,
|
||||
exclude_types=self.dialect.exclude_setinputsizes
|
||||
)
|
||||
|
||||
# if a single execute, check for outparams
|
||||
if len(self.compiled_parameters) == 1:
|
||||
for bindparam in self.compiled.binds.values():
|
||||
if bindparam.isoutparam:
|
||||
dbtype = bindparam.type.dialect_impl(self.dialect).\
|
||||
get_dbapi_type(self.dialect.dbapi)
|
||||
if not hasattr(self, 'out_parameters'):
|
||||
self.out_parameters = {}
|
||||
if dbtype is None:
|
||||
raise exc.InvalidRequestError(
|
||||
"Cannot create out parameter for parameter "
|
||||
"%r - it's type %r is not supported by"
|
||||
" cx_oracle" %
|
||||
(bindparam.key, bindparam.type)
|
||||
)
|
||||
name = self.compiled.bind_names[bindparam]
|
||||
self.out_parameters[name] = self.cursor.var(dbtype)
|
||||
self.parameters[0][quoted_bind_names.get(name, name)] = \
|
||||
self.out_parameters[name]
|
||||
|
||||
def create_cursor(self):
|
||||
c = self._dbapi_connection.cursor()
|
||||
if self.dialect.arraysize:
|
||||
c.arraysize = self.dialect.arraysize
|
||||
|
||||
return c
|
||||
|
||||
def get_result_proxy(self):
|
||||
if hasattr(self, 'out_parameters') and self.compiled.returning:
|
||||
returning_params = dict(
|
||||
(k, v.getvalue())
|
||||
for k, v in self.out_parameters.items()
|
||||
)
|
||||
return ReturningResultProxy(self, returning_params)
|
||||
|
||||
result = None
|
||||
if self.cursor.description is not None:
|
||||
for column in self.cursor.description:
|
||||
type_code = column[1]
|
||||
if type_code in self.dialect._cx_oracle_binary_types:
|
||||
result = _result.BufferedColumnResultProxy(self)
|
||||
|
||||
if result is None:
|
||||
result = _result.ResultProxy(self)
|
||||
|
||||
if hasattr(self, 'out_parameters'):
|
||||
if self.compiled_parameters is not None and \
|
||||
len(self.compiled_parameters) == 1:
|
||||
result.out_parameters = out_parameters = {}
|
||||
|
||||
for bind, name in self.compiled.bind_names.items():
|
||||
if name in self.out_parameters:
|
||||
type = bind.type
|
||||
impl_type = type.dialect_impl(self.dialect)
|
||||
dbapi_type = impl_type.get_dbapi_type(self.dialect.dbapi)
|
||||
result_processor = impl_type.\
|
||||
result_processor(self.dialect,
|
||||
dbapi_type)
|
||||
if result_processor is not None:
|
||||
out_parameters[name] = \
|
||||
result_processor(self.out_parameters[name].getvalue())
|
||||
else:
|
||||
out_parameters[name] = self.out_parameters[name].getvalue()
|
||||
else:
|
||||
result.out_parameters = dict(
|
||||
(k, v.getvalue())
|
||||
for k, v in self.out_parameters.items()
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class OracleExecutionContext_cx_oracle_with_unicode(OracleExecutionContext_cx_oracle):
|
||||
"""Support WITH_UNICODE in Python 2.xx.
|
||||
|
||||
WITH_UNICODE allows cx_Oracle's Python 3 unicode handling
|
||||
behavior under Python 2.x. This mode in some cases disallows
|
||||
and in other cases silently passes corrupted data when
|
||||
non-Python-unicode strings (a.k.a. plain old Python strings)
|
||||
are passed as arguments to connect(), the statement sent to execute(),
|
||||
or any of the bind parameter keys or values sent to execute().
|
||||
This optional context therefore ensures that all statements are
|
||||
passed as Python unicode objects.
|
||||
|
||||
"""
|
||||
def __init__(self, *arg, **kw):
|
||||
OracleExecutionContext_cx_oracle.__init__(self, *arg, **kw)
|
||||
self.statement = unicode(self.statement)
|
||||
|
||||
def _execute_scalar(self, stmt):
|
||||
return super(OracleExecutionContext_cx_oracle_with_unicode, self).\
|
||||
_execute_scalar(unicode(stmt))
|
||||
|
||||
|
||||
class ReturningResultProxy(_result.FullyBufferedResultProxy):
|
||||
"""Result proxy which stuffs the _returning clause + outparams into the fetch."""
|
||||
|
||||
def __init__(self, context, returning_params):
|
||||
self._returning_params = returning_params
|
||||
super(ReturningResultProxy, self).__init__(context)
|
||||
|
||||
def _cursor_description(self):
|
||||
returning = self.context.compiled.returning
|
||||
|
||||
return [
|
||||
("ret_%d" % i, None)
|
||||
for i, col in enumerate(returning)
|
||||
]
|
||||
|
||||
def _buffer_rows(self):
|
||||
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
|
||||
statement_compiler = OracleCompiler_cx_oracle
|
||||
|
||||
driver = "cx_oracle"
|
||||
|
||||
colspecs = colspecs = {
|
||||
sqltypes.Numeric: _OracleNumeric,
|
||||
sqltypes.Date: _OracleDate, # generic type, assume datetime.date is desired
|
||||
oracle.DATE: oracle.DATE, # non generic type - passthru
|
||||
sqltypes.LargeBinary: _OracleBinary,
|
||||
sqltypes.Boolean: oracle._OracleBoolean,
|
||||
sqltypes.Interval: _OracleInterval,
|
||||
oracle.INTERVAL: _OracleInterval,
|
||||
sqltypes.Text: _OracleText,
|
||||
sqltypes.String: _OracleString,
|
||||
sqltypes.UnicodeText: _OracleUnicodeText,
|
||||
sqltypes.CHAR: _OracleChar,
|
||||
|
||||
# a raw LONG is a text type, but does *not*
|
||||
# get the LobMixin with cx_oracle.
|
||||
oracle.LONG: _OracleLong,
|
||||
|
||||
# this is only needed for OUT parameters.
|
||||
# it would be nice if we could not use it otherwise.
|
||||
sqltypes.Integer: _OracleInteger,
|
||||
|
||||
oracle.RAW: _OracleRaw,
|
||||
sqltypes.Unicode: _OracleNVarChar,
|
||||
sqltypes.NVARCHAR: _OracleNVarChar,
|
||||
oracle.ROWID: _OracleRowid,
|
||||
}
|
||||
|
||||
execute_sequence_format = list
|
||||
|
||||
def __init__(self,
|
||||
auto_setinputsizes=True,
|
||||
exclude_setinputsizes=("STRING", "UNICODE"),
|
||||
auto_convert_lobs=True,
|
||||
threaded=True,
|
||||
allow_twophase=True,
|
||||
coerce_to_decimal=True,
|
||||
arraysize=50, **kwargs):
|
||||
OracleDialect.__init__(self, **kwargs)
|
||||
self.threaded = threaded
|
||||
self.arraysize = arraysize
|
||||
self.allow_twophase = allow_twophase
|
||||
self.supports_timestamp = self.dbapi is None or \
|
||||
hasattr(self.dbapi, 'TIMESTAMP')
|
||||
self.auto_setinputsizes = auto_setinputsizes
|
||||
self.auto_convert_lobs = auto_convert_lobs
|
||||
|
||||
if hasattr(self.dbapi, 'version'):
|
||||
self.cx_oracle_ver = tuple([int(x) for x in
|
||||
self.dbapi.version.split('.')])
|
||||
else:
|
||||
self.cx_oracle_ver = (0, 0, 0)
|
||||
|
||||
def types(*names):
|
||||
return set(
|
||||
getattr(self.dbapi, name, None) for name in names
|
||||
).difference([None])
|
||||
|
||||
self.exclude_setinputsizes = types(*(exclude_setinputsizes or ()))
|
||||
self._cx_oracle_string_types = types("STRING", "UNICODE",
|
||||
"NCLOB", "CLOB")
|
||||
self._cx_oracle_unicode_types = types("UNICODE", "NCLOB")
|
||||
self._cx_oracle_binary_types = types("BFILE", "CLOB", "NCLOB", "BLOB")
|
||||
self.supports_unicode_binds = self.cx_oracle_ver >= (5, 0)
|
||||
|
||||
self.supports_native_decimal = (
|
||||
self.cx_oracle_ver >= (5, 0) and
|
||||
coerce_to_decimal
|
||||
)
|
||||
|
||||
self._cx_oracle_native_nvarchar = self.cx_oracle_ver >= (5, 0)
|
||||
|
||||
if self.cx_oracle_ver is None:
|
||||
# this occurs in tests with mock DBAPIs
|
||||
self._cx_oracle_string_types = set()
|
||||
self._cx_oracle_with_unicode = False
|
||||
elif self.cx_oracle_ver >= (5,) and not hasattr(self.dbapi, 'UNICODE'):
|
||||
# cx_Oracle WITH_UNICODE mode. *only* python
|
||||
# unicode objects accepted for anything
|
||||
self.supports_unicode_statements = True
|
||||
self.supports_unicode_binds = True
|
||||
self._cx_oracle_with_unicode = True
|
||||
# Py2K
|
||||
# There's really no reason to run with WITH_UNICODE under Python 2.x.
|
||||
# Give the user a hint.
|
||||
util.warn("cx_Oracle is compiled under Python 2.xx using the "
|
||||
"WITH_UNICODE flag. Consider recompiling cx_Oracle without "
|
||||
"this flag, which is in no way necessary for full support of Unicode. "
|
||||
"Otherwise, all string-holding bind parameters must "
|
||||
"be explicitly typed using SQLAlchemy's String type or one of its subtypes,"
|
||||
"or otherwise be passed as Python unicode. Plain Python strings "
|
||||
"passed as bind parameters will be silently corrupted by cx_Oracle."
|
||||
)
|
||||
self.execution_ctx_cls = OracleExecutionContext_cx_oracle_with_unicode
|
||||
# end Py2K
|
||||
else:
|
||||
self._cx_oracle_with_unicode = False
|
||||
|
||||
if self.cx_oracle_ver is None or \
|
||||
not self.auto_convert_lobs or \
|
||||
not hasattr(self.dbapi, 'CLOB'):
|
||||
self.dbapi_type_map = {}
|
||||
else:
|
||||
# only use this for LOB objects. using it for strings, dates
|
||||
# etc. leads to a little too much magic, reflection doesn't know if it should
|
||||
# expect encoded strings or unicodes, etc.
|
||||
self.dbapi_type_map = {
|
||||
self.dbapi.CLOB: oracle.CLOB(),
|
||||
self.dbapi.NCLOB: oracle.NCLOB(),
|
||||
self.dbapi.BLOB: oracle.BLOB(),
|
||||
self.dbapi.BINARY: oracle.RAW(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
import cx_Oracle
|
||||
return cx_Oracle
|
||||
|
||||
def initialize(self, connection):
|
||||
super(OracleDialect_cx_oracle, self).initialize(connection)
|
||||
if self._is_oracle_8:
|
||||
self.supports_unicode_binds = False
|
||||
self._detect_decimal_char(connection)
|
||||
|
||||
def _detect_decimal_char(self, connection):
|
||||
"""detect if the decimal separator character is not '.', as
|
||||
is the case with european locale settings for NLS_LANG.
|
||||
|
||||
cx_oracle itself uses similar logic when it formats Python
|
||||
Decimal objects to strings on the bind side (as of 5.0.3),
|
||||
as Oracle sends/receives string numerics only in the
|
||||
current locale.
|
||||
|
||||
"""
|
||||
if self.cx_oracle_ver < (5,):
|
||||
# no output type handlers before version 5
|
||||
return
|
||||
|
||||
cx_Oracle = self.dbapi
|
||||
conn = connection.connection
|
||||
|
||||
# override the output_type_handler that's
|
||||
# on the cx_oracle connection with a plain
|
||||
# one on the cursor
|
||||
|
||||
def output_type_handler(cursor, name, defaultType,
|
||||
size, precision, scale):
|
||||
return cursor.var(
|
||||
cx_Oracle.STRING,
|
||||
255, arraysize=cursor.arraysize)
|
||||
|
||||
cursor = conn.cursor()
|
||||
cursor.outputtypehandler = output_type_handler
|
||||
cursor.execute("SELECT 0.1 FROM DUAL")
|
||||
val = cursor.fetchone()[0]
|
||||
cursor.close()
|
||||
char = re.match(r"([\.,])", val).group(1)
|
||||
if char != '.':
|
||||
_detect_decimal = self._detect_decimal
|
||||
self._detect_decimal = \
|
||||
lambda value: _detect_decimal(value.replace(char, '.'))
|
||||
self._to_decimal = \
|
||||
lambda value: decimal.Decimal(value.replace(char, '.'))
|
||||
|
||||
def _detect_decimal(self, value):
|
||||
if "." in value:
|
||||
return decimal.Decimal(value)
|
||||
else:
|
||||
return int(value)
|
||||
|
||||
_to_decimal = decimal.Decimal
|
||||
|
||||
def on_connect(self):
|
||||
if self.cx_oracle_ver < (5,):
|
||||
# no output type handlers before version 5
|
||||
return
|
||||
|
||||
cx_Oracle = self.dbapi
|
||||
|
||||
def output_type_handler(cursor, name, defaultType,
|
||||
size, precision, scale):
|
||||
# convert all NUMBER with precision + positive scale to Decimal
|
||||
# this almost allows "native decimal" mode.
|
||||
if self.supports_native_decimal and \
|
||||
defaultType == cx_Oracle.NUMBER and \
|
||||
precision and scale > 0:
|
||||
return cursor.var(
|
||||
cx_Oracle.STRING,
|
||||
255,
|
||||
outconverter=self._to_decimal,
|
||||
arraysize=cursor.arraysize)
|
||||
# if NUMBER with zero precision and 0 or neg scale, this appears
|
||||
# to indicate "ambiguous". Use a slower converter that will
|
||||
# make a decision based on each value received - the type
|
||||
# may change from row to row (!). This kills
|
||||
# off "native decimal" mode, handlers still needed.
|
||||
elif self.supports_native_decimal and \
|
||||
defaultType == cx_Oracle.NUMBER \
|
||||
and not precision and scale <= 0:
|
||||
return cursor.var(
|
||||
cx_Oracle.STRING,
|
||||
255,
|
||||
outconverter=self._detect_decimal,
|
||||
arraysize=cursor.arraysize)
|
||||
# allow all strings to come back natively as Unicode
|
||||
elif defaultType in (cx_Oracle.STRING, cx_Oracle.FIXED_CHAR):
|
||||
return cursor.var(unicode, size, cursor.arraysize)
|
||||
|
||||
def on_connect(conn):
|
||||
conn.outputtypehandler = output_type_handler
|
||||
|
||||
return on_connect
|
||||
|
||||
def create_connect_args(self, url):
|
||||
dialect_opts = dict(url.query)
|
||||
for opt in ('use_ansi', 'auto_setinputsizes', 'auto_convert_lobs',
|
||||
'threaded', 'allow_twophase'):
|
||||
if opt in dialect_opts:
|
||||
util.coerce_kw_type(dialect_opts, opt, bool)
|
||||
setattr(self, opt, dialect_opts[opt])
|
||||
|
||||
if url.database:
|
||||
# if we have a database, then we have a remote host
|
||||
port = url.port
|
||||
if port:
|
||||
port = int(port)
|
||||
else:
|
||||
port = 1521
|
||||
dsn = self.dbapi.makedsn(url.host, port, url.database)
|
||||
else:
|
||||
# we have a local tnsname
|
||||
dsn = url.host
|
||||
|
||||
opts = dict(
|
||||
user=url.username,
|
||||
password=url.password,
|
||||
dsn=dsn,
|
||||
threaded=self.threaded,
|
||||
twophase=self.allow_twophase,
|
||||
)
|
||||
|
||||
# Py2K
|
||||
if self._cx_oracle_with_unicode:
|
||||
for k, v in opts.items():
|
||||
if isinstance(v, str):
|
||||
opts[k] = unicode(v)
|
||||
else:
|
||||
for k, v in opts.items():
|
||||
if isinstance(v, unicode):
|
||||
opts[k] = str(v)
|
||||
# end Py2K
|
||||
|
||||
if 'mode' in url.query:
|
||||
opts['mode'] = url.query['mode']
|
||||
if isinstance(opts['mode'], basestring):
|
||||
mode = opts['mode'].upper()
|
||||
if mode == 'SYSDBA':
|
||||
opts['mode'] = self.dbapi.SYSDBA
|
||||
elif mode == 'SYSOPER':
|
||||
opts['mode'] = self.dbapi.SYSOPER
|
||||
else:
|
||||
util.coerce_kw_type(opts, 'mode', int)
|
||||
return ([], opts)
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
return tuple(
|
||||
int(x)
|
||||
for x in connection.connection.version.split('.')
|
||||
)
|
||||
|
||||
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 False
|
||||
|
||||
def create_xid(self):
|
||||
"""create a two-phase transaction ID.
|
||||
|
||||
this id will be passed to do_begin_twophase(), do_rollback_twophase(),
|
||||
do_commit_twophase(). its format is unspecified."""
|
||||
|
||||
id = random.randint(0, 2 ** 128)
|
||||
return (0x1234, "%032x" % id, "%032x" % 9)
|
||||
|
||||
def do_begin_twophase(self, connection, xid):
|
||||
connection.connection.begin(*xid)
|
||||
|
||||
def do_prepare_twophase(self, connection, xid):
|
||||
result = connection.connection.prepare()
|
||||
connection.info['cx_oracle_prepared'] = result
|
||||
|
||||
def do_rollback_twophase(self, connection, xid, is_prepared=True,
|
||||
recover=False):
|
||||
self.do_rollback(connection.connection)
|
||||
|
||||
def do_commit_twophase(self, connection, xid, is_prepared=True,
|
||||
recover=False):
|
||||
if not is_prepared:
|
||||
self.do_commit(connection.connection)
|
||||
else:
|
||||
oci_prepared = connection.info['cx_oracle_prepared']
|
||||
if oci_prepared:
|
||||
self.do_commit(connection.connection)
|
||||
|
||||
def do_recover_twophase(self, connection):
|
||||
connection.info.pop('cx_oracle_prepared', None)
|
||||
|
||||
dialect = OracleDialect_cx_oracle
|
||||
@@ -0,0 +1,218 @@
|
||||
# oracle/zxjdbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: oracle+zxjdbc
|
||||
:name: zxJDBC for Jython
|
||||
:dbapi: zxjdbc
|
||||
:connectstring: oracle+zxjdbc://user:pass@host/dbname
|
||||
:driverurl: http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/index.html.
|
||||
|
||||
"""
|
||||
import decimal
|
||||
import re
|
||||
|
||||
from sqlalchemy import sql, types as sqltypes, util
|
||||
from sqlalchemy.connectors.zxJDBC import ZxJDBCConnector
|
||||
from sqlalchemy.dialects.oracle.base import OracleCompiler, OracleDialect, OracleExecutionContext
|
||||
from sqlalchemy.engine import result as _result
|
||||
from sqlalchemy.sql import expression
|
||||
import collections
|
||||
|
||||
SQLException = zxJDBC = None
|
||||
|
||||
|
||||
class _ZxJDBCDate(sqltypes.Date):
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
def process(value):
|
||||
if value is None:
|
||||
return None
|
||||
else:
|
||||
return value.date()
|
||||
return process
|
||||
|
||||
|
||||
class _ZxJDBCNumeric(sqltypes.Numeric):
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
#XXX: does the dialect return Decimal or not???
|
||||
# if it does (in all cases), we could use a None processor as well as
|
||||
# the to_float generic processor
|
||||
if self.asdecimal:
|
||||
def process(value):
|
||||
if isinstance(value, decimal.Decimal):
|
||||
return value
|
||||
else:
|
||||
return decimal.Decimal(str(value))
|
||||
else:
|
||||
def process(value):
|
||||
if isinstance(value, decimal.Decimal):
|
||||
return float(value)
|
||||
else:
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
class OracleCompiler_zxjdbc(OracleCompiler):
|
||||
|
||||
def returning_clause(self, stmt, returning_cols):
|
||||
self.returning_cols = list(expression._select_iterables(returning_cols))
|
||||
|
||||
# within_columns_clause=False so that labels (foo AS bar) don't render
|
||||
columns = [self.process(c, within_columns_clause=False, result_map=self.result_map)
|
||||
for c in self.returning_cols]
|
||||
|
||||
if not hasattr(self, 'returning_parameters'):
|
||||
self.returning_parameters = []
|
||||
|
||||
binds = []
|
||||
for i, col in enumerate(self.returning_cols):
|
||||
dbtype = col.type.dialect_impl(self.dialect).get_dbapi_type(self.dialect.dbapi)
|
||||
self.returning_parameters.append((i + 1, dbtype))
|
||||
|
||||
bindparam = sql.bindparam("ret_%d" % i, value=ReturningParam(dbtype))
|
||||
self.binds[bindparam.key] = bindparam
|
||||
binds.append(self.bindparam_string(self._truncate_bindparam(bindparam)))
|
||||
|
||||
return 'RETURNING ' + ', '.join(columns) + " INTO " + ", ".join(binds)
|
||||
|
||||
|
||||
class OracleExecutionContext_zxjdbc(OracleExecutionContext):
|
||||
|
||||
def pre_exec(self):
|
||||
if hasattr(self.compiled, 'returning_parameters'):
|
||||
# prepare a zxJDBC statement so we can grab its underlying
|
||||
# OraclePreparedStatement's getReturnResultSet later
|
||||
self.statement = self.cursor.prepare(self.statement)
|
||||
|
||||
def get_result_proxy(self):
|
||||
if hasattr(self.compiled, 'returning_parameters'):
|
||||
rrs = None
|
||||
try:
|
||||
try:
|
||||
rrs = self.statement.__statement__.getReturnResultSet()
|
||||
rrs.next()
|
||||
except SQLException, sqle:
|
||||
msg = '%s [SQLCode: %d]' % (sqle.getMessage(), sqle.getErrorCode())
|
||||
if sqle.getSQLState() is not None:
|
||||
msg += ' [SQLState: %s]' % sqle.getSQLState()
|
||||
raise zxJDBC.Error(msg)
|
||||
else:
|
||||
row = tuple(self.cursor.datahandler.getPyObject(rrs, index, dbtype)
|
||||
for index, dbtype in self.compiled.returning_parameters)
|
||||
return ReturningResultProxy(self, row)
|
||||
finally:
|
||||
if rrs is not None:
|
||||
try:
|
||||
rrs.close()
|
||||
except SQLException:
|
||||
pass
|
||||
self.statement.close()
|
||||
|
||||
return _result.ResultProxy(self)
|
||||
|
||||
def create_cursor(self):
|
||||
cursor = self._dbapi_connection.cursor()
|
||||
cursor.datahandler = self.dialect.DataHandler(cursor.datahandler)
|
||||
return cursor
|
||||
|
||||
|
||||
class ReturningResultProxy(_result.FullyBufferedResultProxy):
|
||||
|
||||
"""ResultProxy backed by the RETURNING ResultSet results."""
|
||||
|
||||
def __init__(self, context, returning_row):
|
||||
self._returning_row = returning_row
|
||||
super(ReturningResultProxy, self).__init__(context)
|
||||
|
||||
def _cursor_description(self):
|
||||
ret = []
|
||||
for c in self.context.compiled.returning_cols:
|
||||
if hasattr(c, 'name'):
|
||||
ret.append((c.name, c.type))
|
||||
else:
|
||||
ret.append((c.anon_label, c.type))
|
||||
return ret
|
||||
|
||||
def _buffer_rows(self):
|
||||
return collections.deque([self._returning_row])
|
||||
|
||||
|
||||
class ReturningParam(object):
|
||||
|
||||
"""A bindparam value representing a RETURNING parameter.
|
||||
|
||||
Specially handled by OracleReturningDataHandler.
|
||||
"""
|
||||
|
||||
def __init__(self, type):
|
||||
self.type = type
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, ReturningParam):
|
||||
return self.type == other.type
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(self, other):
|
||||
if isinstance(other, ReturningParam):
|
||||
return self.type != other.type
|
||||
return NotImplemented
|
||||
|
||||
def __repr__(self):
|
||||
kls = self.__class__
|
||||
return '<%s.%s object at 0x%x type=%s>' % (kls.__module__, kls.__name__, id(self),
|
||||
self.type)
|
||||
|
||||
|
||||
class OracleDialect_zxjdbc(ZxJDBCConnector, OracleDialect):
|
||||
jdbc_db_name = 'oracle'
|
||||
jdbc_driver_name = 'oracle.jdbc.OracleDriver'
|
||||
|
||||
statement_compiler = OracleCompiler_zxjdbc
|
||||
execution_ctx_cls = OracleExecutionContext_zxjdbc
|
||||
|
||||
colspecs = util.update_copy(
|
||||
OracleDialect.colspecs,
|
||||
{
|
||||
sqltypes.Date: _ZxJDBCDate,
|
||||
sqltypes.Numeric: _ZxJDBCNumeric
|
||||
}
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OracleDialect_zxjdbc, self).__init__(*args, **kwargs)
|
||||
global SQLException, zxJDBC
|
||||
from java.sql import SQLException
|
||||
from com.ziclix.python.sql import zxJDBC
|
||||
from com.ziclix.python.sql.handler import OracleDataHandler
|
||||
|
||||
class OracleReturningDataHandler(OracleDataHandler):
|
||||
"""zxJDBC DataHandler that specially handles ReturningParam."""
|
||||
|
||||
def setJDBCObject(self, statement, index, object, dbtype=None):
|
||||
if type(object) is ReturningParam:
|
||||
statement.registerReturnParameter(index, object.type)
|
||||
elif dbtype is None:
|
||||
OracleDataHandler.setJDBCObject(
|
||||
self, statement, index, object)
|
||||
else:
|
||||
OracleDataHandler.setJDBCObject(
|
||||
self, statement, index, object, dbtype)
|
||||
self.DataHandler = OracleReturningDataHandler
|
||||
|
||||
def initialize(self, connection):
|
||||
super(OracleDialect_zxjdbc, self).initialize(connection)
|
||||
self.implicit_returning = connection.connection.driverversion >= '10.2'
|
||||
|
||||
def _create_jdbc_url(self, url):
|
||||
return 'jdbc:oracle:thin:@%s:%s:%s' % (url.host, url.port or 1521, url.database)
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
version = re.search(r'Release ([\d\.]+)', connection.connection.dbversion).group(1)
|
||||
return tuple(int(x) for x in version.split('.'))
|
||||
|
||||
dialect = OracleDialect_zxjdbc
|
||||
@@ -0,0 +1,16 @@
|
||||
# dialects/postgres.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
# backwards compat with the old name
|
||||
from sqlalchemy.util import warn_deprecated
|
||||
|
||||
warn_deprecated(
|
||||
"The SQLAlchemy PostgreSQL dialect has been renamed from 'postgres' to 'postgresql'. "
|
||||
"The new URL format is postgresql[+driver]://<user>:<pass>@<host>/<dbname>"
|
||||
)
|
||||
|
||||
from sqlalchemy.dialects.postgresql import *
|
||||
from sqlalchemy.dialects.postgresql import base
|
||||
@@ -0,0 +1,27 @@
|
||||
# postgresql/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from . import base, psycopg2, pg8000, pypostgresql, zxjdbc
|
||||
|
||||
base.dialect = psycopg2.dialect
|
||||
|
||||
from .base import \
|
||||
INTEGER, BIGINT, SMALLINT, VARCHAR, CHAR, TEXT, NUMERIC, FLOAT, REAL, \
|
||||
INET, CIDR, UUID, BIT, MACADDR, DOUBLE_PRECISION, TIMESTAMP, TIME, \
|
||||
DATE, BYTEA, BOOLEAN, INTERVAL, ARRAY, ENUM, dialect, array, Any, All
|
||||
from .constraints import ExcludeConstraint
|
||||
from .hstore import HSTORE, hstore
|
||||
from .ranges import INT4RANGE, INT8RANGE, NUMRANGE, DATERANGE, TSRANGE, \
|
||||
TSTZRANGE
|
||||
|
||||
__all__ = (
|
||||
'INTEGER', 'BIGINT', 'SMALLINT', 'VARCHAR', 'CHAR', 'TEXT', 'NUMERIC',
|
||||
'FLOAT', 'REAL', 'INET', 'CIDR', 'UUID', 'BIT', 'MACADDR',
|
||||
'DOUBLE_PRECISION', 'TIMESTAMP', 'TIME', 'DATE', 'BYTEA', 'BOOLEAN',
|
||||
'INTERVAL', 'ARRAY', 'ENUM', 'dialect', 'Any', 'All', 'array', 'HSTORE',
|
||||
'hstore', 'INT4RANGE', 'INT8RANGE', 'NUMRANGE', 'DATERANGE',
|
||||
'TSRANGE', 'TSTZRANGE'
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
# Copyright (C) 2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
from sqlalchemy.schema import ColumnCollectionConstraint
|
||||
from sqlalchemy.sql import expression
|
||||
|
||||
class ExcludeConstraint(ColumnCollectionConstraint):
|
||||
"""A table-level EXCLUDE constraint.
|
||||
|
||||
Defines an EXCLUDE constraint as described in the `postgres
|
||||
documentation`__.
|
||||
|
||||
__ http://www.postgresql.org/docs/9.0/static/sql-createtable.html#SQL-CREATETABLE-EXCLUDE
|
||||
"""
|
||||
|
||||
__visit_name__ = 'exclude_constraint'
|
||||
|
||||
where = None
|
||||
|
||||
def __init__(self, *elements, **kw):
|
||||
"""
|
||||
:param \*elements:
|
||||
A sequence of two tuples of the form ``(column, operator)`` where
|
||||
column must be a column name or Column object and operator must
|
||||
be a string containing the operator to use.
|
||||
|
||||
:param name:
|
||||
Optional, the in-database name of this constraint.
|
||||
|
||||
:param deferrable:
|
||||
Optional bool. If set, emit DEFERRABLE or NOT DEFERRABLE when
|
||||
issuing DDL for this constraint.
|
||||
|
||||
:param initially:
|
||||
Optional string. If set, emit INITIALLY <value> when issuing DDL
|
||||
for this constraint.
|
||||
|
||||
:param using:
|
||||
Optional string. If set, emit USING <index_method> when issuing DDL
|
||||
for this constraint. Defaults to 'gist'.
|
||||
|
||||
:param where:
|
||||
Optional string. If set, emit WHERE <predicate> when issuing DDL
|
||||
for this constraint.
|
||||
|
||||
"""
|
||||
ColumnCollectionConstraint.__init__(
|
||||
self,
|
||||
name=kw.get('name'),
|
||||
deferrable=kw.get('deferrable'),
|
||||
initially=kw.get('initially'),
|
||||
*[col for col, op in elements]
|
||||
)
|
||||
self.operators = {}
|
||||
for col_or_string, op in elements:
|
||||
name = getattr(col_or_string, 'name', col_or_string)
|
||||
self.operators[name] = op
|
||||
self.using = kw.get('using', 'gist')
|
||||
where = kw.get('where')
|
||||
if where:
|
||||
self.where = expression._literal_as_text(where)
|
||||
|
||||
def copy(self, **kw):
|
||||
elements = [(col, self.operators[col])
|
||||
for col in self.columns.keys()]
|
||||
c = self.__class__(name=self.name,
|
||||
deferrable=self.deferrable,
|
||||
initially=self.initially,
|
||||
*elements)
|
||||
c.dispatch._update(self.dispatch)
|
||||
return c
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
# postgresql/hstore.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import re
|
||||
|
||||
from .base import ARRAY, ischema_names
|
||||
from ... import types as sqltypes
|
||||
from ...sql import functions as sqlfunc
|
||||
from ...sql.operators import custom_op
|
||||
from ... import util
|
||||
|
||||
__all__ = ('HSTORE', 'hstore')
|
||||
|
||||
# My best guess at the parsing rules of hstore literals, since no formal
|
||||
# grammar is given. This is mostly reverse engineered from PG's input parser
|
||||
# behavior.
|
||||
HSTORE_PAIR_RE = re.compile(r"""
|
||||
(
|
||||
"(?P<key> (\\ . | [^"])* )" # Quoted key
|
||||
)
|
||||
[ ]* => [ ]* # Pair operator, optional adjoining whitespace
|
||||
(
|
||||
(?P<value_null> NULL ) # NULL value
|
||||
| "(?P<value> (\\ . | [^"])* )" # Quoted value
|
||||
)
|
||||
""", re.VERBOSE)
|
||||
|
||||
HSTORE_DELIMITER_RE = re.compile(r"""
|
||||
[ ]* , [ ]*
|
||||
""", re.VERBOSE)
|
||||
|
||||
|
||||
def _parse_error(hstore_str, pos):
|
||||
"""format an unmarshalling error."""
|
||||
|
||||
ctx = 20
|
||||
hslen = len(hstore_str)
|
||||
|
||||
parsed_tail = hstore_str[max(pos - ctx - 1, 0):min(pos, hslen)]
|
||||
residual = hstore_str[min(pos, hslen):min(pos + ctx + 1, hslen)]
|
||||
|
||||
if len(parsed_tail) > ctx:
|
||||
parsed_tail = '[...]' + parsed_tail[1:]
|
||||
if len(residual) > ctx:
|
||||
residual = residual[:-1] + '[...]'
|
||||
|
||||
return "After %r, could not parse residual at position %d: %r" % (
|
||||
parsed_tail, pos, residual)
|
||||
|
||||
|
||||
def _parse_hstore(hstore_str):
|
||||
"""Parse an hstore from it's literal string representation.
|
||||
|
||||
Attempts to approximate PG's hstore input parsing rules as closely as
|
||||
possible. Although currently this is not strictly necessary, since the
|
||||
current implementation of hstore's output syntax is stricter than what it
|
||||
accepts as input, the documentation makes no guarantees that will always
|
||||
be the case.
|
||||
|
||||
|
||||
|
||||
"""
|
||||
result = {}
|
||||
pos = 0
|
||||
pair_match = HSTORE_PAIR_RE.match(hstore_str)
|
||||
|
||||
while pair_match is not None:
|
||||
key = pair_match.group('key').replace(r'\"', '"').replace("\\\\", "\\")
|
||||
if pair_match.group('value_null'):
|
||||
value = None
|
||||
else:
|
||||
value = pair_match.group('value').replace(r'\"', '"').replace("\\\\", "\\")
|
||||
result[key] = value
|
||||
|
||||
pos += pair_match.end()
|
||||
|
||||
delim_match = HSTORE_DELIMITER_RE.match(hstore_str[pos:])
|
||||
if delim_match is not None:
|
||||
pos += delim_match.end()
|
||||
|
||||
pair_match = HSTORE_PAIR_RE.match(hstore_str[pos:])
|
||||
|
||||
if pos != len(hstore_str):
|
||||
raise ValueError(_parse_error(hstore_str, pos))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _serialize_hstore(val):
|
||||
"""Serialize a dictionary into an hstore literal. Keys and values must
|
||||
both be strings (except None for values).
|
||||
|
||||
"""
|
||||
def esc(s, position):
|
||||
if position == 'value' and s is None:
|
||||
return 'NULL'
|
||||
elif isinstance(s, basestring):
|
||||
return '"%s"' % s.replace("\\", "\\\\").replace('"', r'\"')
|
||||
else:
|
||||
raise ValueError("%r in %s position is not a string." %
|
||||
(s, position))
|
||||
|
||||
return ', '.join('%s=>%s' % (esc(k, 'key'), esc(v, 'value'))
|
||||
for k, v in val.iteritems())
|
||||
|
||||
|
||||
class HSTORE(sqltypes.Concatenable, sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql HSTORE type.
|
||||
|
||||
The :class:`.HSTORE` type stores dictionaries containing strings, e.g.::
|
||||
|
||||
data_table = Table('data_table', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('data', HSTORE)
|
||||
)
|
||||
|
||||
with engine.connect() as conn:
|
||||
conn.execute(
|
||||
data_table.insert(),
|
||||
data = {"key1": "value1", "key2": "value2"}
|
||||
)
|
||||
|
||||
:class:`.HSTORE` provides for a wide range of operations, including:
|
||||
|
||||
* Index operations::
|
||||
|
||||
data_table.c.data['some key'] == 'some value'
|
||||
|
||||
* Containment operations::
|
||||
|
||||
data_table.c.data.has_key('some key')
|
||||
|
||||
data_table.c.data.has_all(['one', 'two', 'three'])
|
||||
|
||||
* Concatenation::
|
||||
|
||||
data_table.c.data + {"k1": "v1"}
|
||||
|
||||
For a full list of special methods see :class:`.HSTORE.comparator_factory`.
|
||||
|
||||
For usage with the SQLAlchemy ORM, it may be desirable to combine
|
||||
the usage of :class:`.HSTORE` with :class:`.MutableDict` dictionary
|
||||
now part of the :mod:`sqlalchemy.ext.mutable`
|
||||
extension. This extension will allow "in-place" changes to the
|
||||
dictionary, e.g. addition of new keys or replacement/removal of existing
|
||||
keys to/from the current dictionary, to produce events which will be detected
|
||||
by the unit of work::
|
||||
|
||||
from sqlalchemy.ext.mutable import MutableDict
|
||||
|
||||
class MyClass(Base):
|
||||
__tablename__ = 'data_table'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
data = Column(MutableDict.as_mutable(HSTORE))
|
||||
|
||||
my_object = session.query(MyClass).one()
|
||||
|
||||
# in-place mutation, requires Mutable extension
|
||||
# in order for the ORM to detect
|
||||
my_object.data['some_key'] = 'some value'
|
||||
|
||||
session.commit()
|
||||
|
||||
When the :mod:`sqlalchemy.ext.mutable` extension is not used, the ORM
|
||||
will not be alerted to any changes to the contents of an existing dictionary,
|
||||
unless that dictionary value is re-assigned to the HSTORE-attribute itself,
|
||||
thus generating a change event.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
.. seealso::
|
||||
|
||||
:class:`.hstore` - render the Postgresql ``hstore()`` function.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'HSTORE'
|
||||
|
||||
class comparator_factory(sqltypes.Concatenable.Comparator):
|
||||
"""Define comparison operations for :class:`.HSTORE`."""
|
||||
|
||||
def has_key(self, other):
|
||||
"""Boolean expression. Test for presence of a key. Note that the
|
||||
key may be a SQLA expression.
|
||||
"""
|
||||
return self.expr.op('?')(other)
|
||||
|
||||
def has_all(self, other):
|
||||
"""Boolean expression. Test for presence of all keys in the PG
|
||||
array.
|
||||
"""
|
||||
return self.expr.op('?&')(other)
|
||||
|
||||
def has_any(self, other):
|
||||
"""Boolean expression. Test for presence of any key in the PG
|
||||
array.
|
||||
"""
|
||||
return self.expr.op('?|')(other)
|
||||
|
||||
def defined(self, key):
|
||||
"""Boolean expression. Test for presence of a non-NULL value for
|
||||
the key. Note that the key may be a SQLA expression.
|
||||
"""
|
||||
return _HStoreDefinedFunction(self.expr, key)
|
||||
|
||||
def contains(self, other, **kwargs):
|
||||
"""Boolean expression. Test if keys are a superset of the keys of
|
||||
the argument hstore expression.
|
||||
"""
|
||||
return self.expr.op('@>')(other)
|
||||
|
||||
def contained_by(self, other):
|
||||
"""Boolean expression. Test if keys are a proper subset of the
|
||||
keys of the argument hstore expression.
|
||||
"""
|
||||
return self.expr.op('<@')(other)
|
||||
|
||||
def __getitem__(self, other):
|
||||
"""Text expression. Get the value at a given key. Note that the
|
||||
key may be a SQLA expression.
|
||||
"""
|
||||
return self.expr.op('->', precedence=5)(other)
|
||||
|
||||
def delete(self, key):
|
||||
"""HStore expression. Returns the contents of this hstore with the
|
||||
given key deleted. Note that the key may be a SQLA expression.
|
||||
"""
|
||||
if isinstance(key, dict):
|
||||
key = _serialize_hstore(key)
|
||||
return _HStoreDeleteFunction(self.expr, key)
|
||||
|
||||
def slice(self, array):
|
||||
"""HStore expression. Returns a subset of an hstore defined by
|
||||
array of keys.
|
||||
"""
|
||||
return _HStoreSliceFunction(self.expr, array)
|
||||
|
||||
def keys(self):
|
||||
"""Text array expression. Returns array of keys."""
|
||||
return _HStoreKeysFunction(self.expr)
|
||||
|
||||
def vals(self):
|
||||
"""Text array expression. Returns array of values."""
|
||||
return _HStoreValsFunction(self.expr)
|
||||
|
||||
def array(self):
|
||||
"""Text array expression. Returns array of alternating keys and
|
||||
values.
|
||||
"""
|
||||
return _HStoreArrayFunction(self.expr)
|
||||
|
||||
def matrix(self):
|
||||
"""Text array expression. Returns array of [key, value] pairs."""
|
||||
return _HStoreMatrixFunction(self.expr)
|
||||
|
||||
def _adapt_expression(self, op, other_comparator):
|
||||
if isinstance(op, custom_op):
|
||||
if op.opstring in ['?', '?&', '?|', '@>', '<@']:
|
||||
return op, sqltypes.Boolean
|
||||
elif op.opstring == '->':
|
||||
return op, sqltypes.Text
|
||||
return sqltypes.Concatenable.Comparator.\
|
||||
_adapt_expression(self, op, other_comparator)
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
if util.py2k:
|
||||
encoding = dialect.encoding
|
||||
def process(value):
|
||||
if isinstance(value, dict):
|
||||
return _serialize_hstore(value).encode(encoding)
|
||||
else:
|
||||
return value
|
||||
else:
|
||||
def process(value):
|
||||
if isinstance(value, dict):
|
||||
return _serialize_hstore(value)
|
||||
else:
|
||||
return value
|
||||
return process
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
if util.py2k:
|
||||
encoding = dialect.encoding
|
||||
def process(value):
|
||||
if value is not None:
|
||||
return _parse_hstore(value.decode(encoding))
|
||||
else:
|
||||
return value
|
||||
else:
|
||||
def process(value):
|
||||
if value is not None:
|
||||
return _parse_hstore(value)
|
||||
else:
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
ischema_names['hstore'] = HSTORE
|
||||
|
||||
|
||||
class hstore(sqlfunc.GenericFunction):
|
||||
"""Construct an hstore value within a SQL expression using the
|
||||
Postgresql ``hstore()`` function.
|
||||
|
||||
The :class:`.hstore` function accepts one or two arguments as described
|
||||
in the Postgresql documentation.
|
||||
|
||||
E.g.::
|
||||
|
||||
from sqlalchemy.dialects.postgresql import array, hstore
|
||||
|
||||
select([hstore('key1', 'value1')])
|
||||
|
||||
select([
|
||||
hstore(
|
||||
array(['key1', 'key2', 'key3']),
|
||||
array(['value1', 'value2', 'value3'])
|
||||
)
|
||||
])
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
.. seealso::
|
||||
|
||||
:class:`.HSTORE` - the Postgresql ``HSTORE`` datatype.
|
||||
|
||||
"""
|
||||
type = HSTORE
|
||||
name = 'hstore'
|
||||
|
||||
|
||||
class _HStoreDefinedFunction(sqlfunc.GenericFunction):
|
||||
type = sqltypes.Boolean
|
||||
name = 'defined'
|
||||
|
||||
|
||||
class _HStoreDeleteFunction(sqlfunc.GenericFunction):
|
||||
type = HSTORE
|
||||
name = 'delete'
|
||||
|
||||
|
||||
class _HStoreSliceFunction(sqlfunc.GenericFunction):
|
||||
type = HSTORE
|
||||
name = 'slice'
|
||||
|
||||
|
||||
class _HStoreKeysFunction(sqlfunc.GenericFunction):
|
||||
type = ARRAY(sqltypes.Text)
|
||||
name = 'akeys'
|
||||
|
||||
|
||||
class _HStoreValsFunction(sqlfunc.GenericFunction):
|
||||
type = ARRAY(sqltypes.Text)
|
||||
name = 'avals'
|
||||
|
||||
|
||||
class _HStoreArrayFunction(sqlfunc.GenericFunction):
|
||||
type = ARRAY(sqltypes.Text)
|
||||
name = 'hstore_to_array'
|
||||
|
||||
|
||||
class _HStoreMatrixFunction(sqlfunc.GenericFunction):
|
||||
type = ARRAY(sqltypes.Text)
|
||||
name = 'hstore_to_matrix'
|
||||
@@ -0,0 +1,124 @@
|
||||
# postgresql/pg8000.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: postgresql+pg8000
|
||||
:name: pg8000
|
||||
:dbapi: pg8000
|
||||
:connectstring: postgresql+pg8000://user:password@host:port/dbname[?key=value&key=value...]
|
||||
:url: http://pybrary.net/pg8000/
|
||||
|
||||
Unicode
|
||||
-------
|
||||
|
||||
pg8000 requires that the postgresql client encoding be
|
||||
configured in the postgresql.conf file in order to use encodings
|
||||
other than ascii. Set this value to the same value as the
|
||||
"encoding" parameter on create_engine(), usually "utf-8".
|
||||
|
||||
Interval
|
||||
--------
|
||||
|
||||
Passing data from/to the Interval type is not supported as of
|
||||
yet.
|
||||
|
||||
"""
|
||||
from ... import util, exc
|
||||
import decimal
|
||||
from ... import processors
|
||||
from ... import types as sqltypes
|
||||
from .base import PGDialect, \
|
||||
PGCompiler, PGIdentifierPreparer, PGExecutionContext,\
|
||||
_DECIMAL_TYPES, _FLOAT_TYPES, _INT_TYPES
|
||||
|
||||
|
||||
class _PGNumeric(sqltypes.Numeric):
|
||||
def result_processor(self, dialect, coltype):
|
||||
if self.asdecimal:
|
||||
if coltype in _FLOAT_TYPES:
|
||||
return processors.to_decimal_processor_factory(decimal.Decimal)
|
||||
elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
|
||||
# pg8000 returns Decimal natively for 1700
|
||||
return None
|
||||
else:
|
||||
raise exc.InvalidRequestError(
|
||||
"Unknown PG numeric type: %d" % coltype)
|
||||
else:
|
||||
if coltype in _FLOAT_TYPES:
|
||||
# pg8000 returns float natively for 701
|
||||
return None
|
||||
elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
|
||||
return processors.to_float
|
||||
else:
|
||||
raise exc.InvalidRequestError(
|
||||
"Unknown PG numeric type: %d" % coltype)
|
||||
|
||||
|
||||
class _PGNumericNoBind(_PGNumeric):
|
||||
def bind_processor(self, dialect):
|
||||
return None
|
||||
|
||||
|
||||
class PGExecutionContext_pg8000(PGExecutionContext):
|
||||
pass
|
||||
|
||||
|
||||
class PGCompiler_pg8000(PGCompiler):
|
||||
def visit_mod_binary(self, binary, operator, **kw):
|
||||
return self.process(binary.left, **kw) + " %% " + \
|
||||
self.process(binary.right, **kw)
|
||||
|
||||
def post_process_text(self, text):
|
||||
if '%%' in text:
|
||||
util.warn("The SQLAlchemy postgresql dialect "
|
||||
"now automatically escapes '%' in text() "
|
||||
"expressions to '%%'.")
|
||||
return text.replace('%', '%%')
|
||||
|
||||
|
||||
class PGIdentifierPreparer_pg8000(PGIdentifierPreparer):
|
||||
def _escape_identifier(self, value):
|
||||
value = value.replace(self.escape_quote, self.escape_to_quote)
|
||||
return value.replace('%', '%%')
|
||||
|
||||
|
||||
class PGDialect_pg8000(PGDialect):
|
||||
driver = 'pg8000'
|
||||
|
||||
supports_unicode_statements = True
|
||||
|
||||
supports_unicode_binds = True
|
||||
|
||||
default_paramstyle = 'format'
|
||||
supports_sane_multi_rowcount = False
|
||||
execution_ctx_cls = PGExecutionContext_pg8000
|
||||
statement_compiler = PGCompiler_pg8000
|
||||
preparer = PGIdentifierPreparer_pg8000
|
||||
description_encoding = 'use_encoding'
|
||||
|
||||
colspecs = util.update_copy(
|
||||
PGDialect.colspecs,
|
||||
{
|
||||
sqltypes.Numeric: _PGNumericNoBind,
|
||||
sqltypes.Float: _PGNumeric
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('pg8000').dbapi
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user')
|
||||
if 'port' in opts:
|
||||
opts['port'] = int(opts['port'])
|
||||
opts.update(url.query)
|
||||
return ([], opts)
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
return "connection is closed" in str(e)
|
||||
|
||||
dialect = PGDialect_pg8000
|
||||
@@ -0,0 +1,475 @@
|
||||
# postgresql/psycopg2.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: postgresql+psycopg2
|
||||
:name: psycopg2
|
||||
:dbapi: psycopg2
|
||||
:connectstring: postgresql+psycopg2://user:password@host:port/dbname[?key=value&key=value...]
|
||||
:url: http://pypi.python.org/pypi/psycopg2/
|
||||
|
||||
psycopg2 Connect Arguments
|
||||
-----------------------------------
|
||||
|
||||
psycopg2-specific keyword arguments which are accepted by
|
||||
:func:`.create_engine()` are:
|
||||
|
||||
* ``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
|
||||
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.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.
|
||||
* ``isolation_level``: This option, available for all Posgtresql dialects,
|
||||
includes the ``AUTOCOMMIT`` isolation level when using the psycopg2
|
||||
dialect. See :ref:`psycopg2_isolation_level`.
|
||||
|
||||
|
||||
Unix Domain Connections
|
||||
------------------------
|
||||
|
||||
psycopg2 supports connecting via Unix domain connections. When the ``host``
|
||||
portion of the URL is omitted, SQLAlchemy passes ``None`` to psycopg2,
|
||||
which specifies Unix-domain communication rather than TCP/IP communication::
|
||||
|
||||
create_engine("postgresql+psycopg2://user:password@/dbname")
|
||||
|
||||
By default, the socket file used is to connect to a Unix-domain socket
|
||||
in ``/tmp``, or whatever socket directory was specified when PostgreSQL
|
||||
was built. This value can be overridden by passing a pathname to psycopg2,
|
||||
using ``host`` as an additional keyword argument::
|
||||
|
||||
create_engine("postgresql+psycopg2://user:password@/dbname?host=/var/lib/postgresql")
|
||||
|
||||
See also:
|
||||
|
||||
`PQconnectdbParams <http://www.postgresql.org/docs/9.1/static/libpq-connect.html#LIBPQ-PQCONNECTDBPARAMS>`_
|
||||
|
||||
Per-Statement/Connection Execution Options
|
||||
-------------------------------------------
|
||||
|
||||
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). See :ref:`psycopg2_isolation_level`.
|
||||
|
||||
* stream_results - Enable or disable usage of psycopg2 server side cursors -
|
||||
this feature makes use of "named" cursors in combination with special
|
||||
result handling methods so that result rows are not fully buffered.
|
||||
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.
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
The psycopg2-specific ``client_encoding`` parameter to
|
||||
:func:`.create_engine`.
|
||||
|
||||
SQLAlchemy can also be instructed to skip the usage of the psycopg2
|
||||
``UNICODE`` extension and to instead utilize it's own unicode encode/decode
|
||||
services, which are normally reserved only for those DBAPIs that don't
|
||||
fully support unicode directly. Passing ``use_native_unicode=False`` 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
|
||||
------------
|
||||
|
||||
The psycopg2 dialect fully supports SAVEPOINT and two-phase commit operations.
|
||||
|
||||
.. _psycopg2_isolation_level:
|
||||
|
||||
Psycopg2 Transaction Isolation Level
|
||||
-------------------------------------
|
||||
|
||||
As discussed in :ref:`postgresql_isolation_level`,
|
||||
all Postgresql dialects support setting of transaction isolation level
|
||||
both via the ``isolation_level`` parameter passed to :func:`.create_engine`,
|
||||
as well as the ``isolation_level`` argument used by :meth:`.Connection.execution_options`.
|
||||
When using the psycopg2 dialect, these options make use of
|
||||
psycopg2's ``set_isolation_level()`` connection method, rather than
|
||||
emitting a Postgresql directive; this is because psycopg2's API-level
|
||||
setting is always emitted at the start of each transaction in any case.
|
||||
|
||||
The psycopg2 dialect supports these constants for isolation level:
|
||||
|
||||
* ``READ COMMITTED``
|
||||
* ``READ UNCOMMITTED``
|
||||
* ``REPEATABLE READ``
|
||||
* ``SERIALIZABLE``
|
||||
* ``AUTOCOMMIT``
|
||||
|
||||
.. versionadded:: 0.8.2 support for AUTOCOMMIT isolation level when using
|
||||
psycopg2.
|
||||
|
||||
|
||||
NOTICE logging
|
||||
---------------
|
||||
|
||||
The psycopg2 dialect will log Postgresql NOTICE messages via the
|
||||
``sqlalchemy.dialects.postgresql`` logger::
|
||||
|
||||
import logging
|
||||
logging.getLogger('sqlalchemy.dialects.postgresql').setLevel(logging.INFO)
|
||||
|
||||
HSTORE type
|
||||
------------
|
||||
|
||||
The psycopg2 dialect will make use of the
|
||||
``psycopg2.extensions.register_hstore()`` extension when using the HSTORE
|
||||
type. This replaces SQLAlchemy's pure-Python HSTORE coercion which takes
|
||||
effect for other DBAPIs.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import re
|
||||
import logging
|
||||
|
||||
from ... import util, exc
|
||||
import decimal
|
||||
from ... import processors
|
||||
from ...engine import result as _result
|
||||
from ...sql import expression
|
||||
from ... import types as sqltypes
|
||||
from .base import PGDialect, PGCompiler, \
|
||||
PGIdentifierPreparer, PGExecutionContext, \
|
||||
ENUM, ARRAY, _DECIMAL_TYPES, _FLOAT_TYPES,\
|
||||
_INT_TYPES
|
||||
from .hstore import HSTORE
|
||||
|
||||
|
||||
logger = logging.getLogger('sqlalchemy.dialects.postgresql')
|
||||
|
||||
|
||||
class _PGNumeric(sqltypes.Numeric):
|
||||
def bind_processor(self, dialect):
|
||||
return None
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
if self.asdecimal:
|
||||
if coltype in _FLOAT_TYPES:
|
||||
return processors.to_decimal_processor_factory(decimal.Decimal)
|
||||
elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
|
||||
# pg8000 returns Decimal natively for 1700
|
||||
return None
|
||||
else:
|
||||
raise exc.InvalidRequestError(
|
||||
"Unknown PG numeric type: %d" % coltype)
|
||||
else:
|
||||
if coltype in _FLOAT_TYPES:
|
||||
# pg8000 returns float natively for 701
|
||||
return None
|
||||
elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
|
||||
return processors.to_float
|
||||
else:
|
||||
raise exc.InvalidRequestError(
|
||||
"Unknown PG numeric type: %d" % coltype)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
class _PGHStore(HSTORE):
|
||||
def bind_processor(self, dialect):
|
||||
if dialect._has_native_hstore:
|
||||
return None
|
||||
else:
|
||||
return super(_PGHStore, self).bind_processor(dialect)
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
if dialect._has_native_hstore:
|
||||
return None
|
||||
else:
|
||||
return super(_PGHStore, self).result_processor(dialect, coltype)
|
||||
|
||||
# When we're handed literal SQL, ensure it's a SELECT-query. Since
|
||||
# 8.3, combining cursors and "FOR UPDATE" has been fine.
|
||||
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()
|
||||
|
||||
if self.dialect.server_side_cursors:
|
||||
is_server_side = \
|
||||
self.execution_options.get('stream_results', True) and (
|
||||
(self.compiled and isinstance(self.compiled.statement, expression.Selectable) \
|
||||
or \
|
||||
(
|
||||
(not self.compiled or
|
||||
isinstance(self.compiled.statement, expression.TextClause))
|
||||
and self.statement and SERVER_SIDE_CURSOR_RE.match(self.statement))
|
||||
)
|
||||
)
|
||||
else:
|
||||
is_server_side = \
|
||||
self.execution_options.get('stream_results', False)
|
||||
|
||||
self.__is_server_side = is_server_side
|
||||
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(_server_side_id())[2:])
|
||||
return self._dbapi_connection.cursor(ident)
|
||||
else:
|
||||
return self._dbapi_connection.cursor()
|
||||
|
||||
def get_result_proxy(self):
|
||||
# TODO: ouch
|
||||
if logger.isEnabledFor(logging.INFO):
|
||||
self._log_notices(self.cursor)
|
||||
|
||||
if self.__is_server_side:
|
||||
return _result.BufferedRowResultProxy(self)
|
||||
else:
|
||||
return _result.ResultProxy(self)
|
||||
|
||||
def _log_notices(self, cursor):
|
||||
for notice in cursor.connection.notices:
|
||||
# NOTICE messages have a
|
||||
# newline character at the end
|
||||
logger.info(notice.rstrip())
|
||||
|
||||
cursor.connection.notices[:] = []
|
||||
|
||||
|
||||
class PGCompiler_psycopg2(PGCompiler):
|
||||
def visit_mod_binary(self, binary, operator, **kw):
|
||||
return self.process(binary.left, **kw) + " %% " + \
|
||||
self.process(binary.right, **kw)
|
||||
|
||||
def post_process_text(self, text):
|
||||
return text.replace('%', '%%')
|
||||
|
||||
|
||||
class PGIdentifierPreparer_psycopg2(PGIdentifierPreparer):
|
||||
def _escape_identifier(self, value):
|
||||
value = value.replace(self.escape_quote, self.escape_to_quote)
|
||||
return value.replace('%', '%%')
|
||||
|
||||
|
||||
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)
|
||||
|
||||
_has_native_hstore = False
|
||||
|
||||
colspecs = util.update_copy(
|
||||
PGDialect.colspecs,
|
||||
{
|
||||
sqltypes.Numeric: _PGNumeric,
|
||||
ENUM: _PGEnum, # needs force_unicode
|
||||
sqltypes.Enum: _PGEnum, # needs force_unicode
|
||||
ARRAY: _PGArray, # needs force_unicode
|
||||
HSTORE: _PGHStore,
|
||||
}
|
||||
)
|
||||
|
||||
def __init__(self, server_side_cursors=False, use_native_unicode=True,
|
||||
client_encoding=None,
|
||||
use_native_hstore=True,
|
||||
**kwargs):
|
||||
PGDialect.__init__(self, **kwargs)
|
||||
self.server_side_cursors = server_side_cursors
|
||||
self.use_native_unicode = use_native_unicode
|
||||
self.use_native_hstore = use_native_hstore
|
||||
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)
|
||||
|
||||
def initialize(self, connection):
|
||||
super(PGDialect_psycopg2, self).initialize(connection)
|
||||
self._has_native_hstore = self.use_native_hstore and \
|
||||
self._hstore_oids(connection.connection) \
|
||||
is not None
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
import psycopg2
|
||||
return psycopg2
|
||||
|
||||
@util.memoized_property
|
||||
def _isolation_lookup(self):
|
||||
extensions = __import__('psycopg2.extensions').extensions
|
||||
return {
|
||||
'AUTOCOMMIT': extensions.ISOLATION_LEVEL_AUTOCOMMIT,
|
||||
'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 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):
|
||||
from psycopg2 import extras, extensions
|
||||
|
||||
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:
|
||||
def on_connect(conn):
|
||||
extensions.register_type(extensions.UNICODE, conn)
|
||||
fns.append(on_connect)
|
||||
|
||||
if self.dbapi and self.use_native_hstore:
|
||||
def on_connect(conn):
|
||||
hstore_oids = self._hstore_oids(conn)
|
||||
if hstore_oids is not None:
|
||||
oid, array_oid = hstore_oids
|
||||
if util.py2k:
|
||||
extras.register_hstore(conn, oid=oid,
|
||||
array_oid=array_oid,
|
||||
unicode=True)
|
||||
else:
|
||||
extras.register_hstore(conn, oid=oid,
|
||||
array_oid=array_oid)
|
||||
fns.append(on_connect)
|
||||
|
||||
if fns:
|
||||
def on_connect(conn):
|
||||
for fn in fns:
|
||||
fn(conn)
|
||||
return on_connect
|
||||
else:
|
||||
return None
|
||||
|
||||
@util.memoized_instancemethod
|
||||
def _hstore_oids(self, conn):
|
||||
if self.psycopg2_version >= (2, 4):
|
||||
from psycopg2 import extras
|
||||
oids = extras.HstoreAdapter.get_oids(conn)
|
||||
if oids is not None and oids[0]:
|
||||
return oids[0:2]
|
||||
return None
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user')
|
||||
if 'port' in opts:
|
||||
opts['port'] = int(opts['port'])
|
||||
opts.update(url.query)
|
||||
return ([], opts)
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, self.dbapi.Error):
|
||||
str_e = str(e).partition("\n")[0]
|
||||
for msg in [
|
||||
# these error messages from libpq: interfaces/libpq/fe-misc.c
|
||||
# and interfaces/libpq/fe-secure.c.
|
||||
# TODO: these are sent through gettext in libpq and we can't
|
||||
# check within other locales - consider using connection.closed
|
||||
'terminating connection',
|
||||
'closed the connection',
|
||||
'connection not open',
|
||||
'could not receive data from server',
|
||||
# psycopg2 client errors, psycopg2/conenction.h, psycopg2/cursor.h
|
||||
'connection already closed',
|
||||
'cursor already closed',
|
||||
# not sure where this path is originally from, it may
|
||||
# be obsolete. It really says "losed", not "closed".
|
||||
'losed the connection unexpectedly'
|
||||
]:
|
||||
idx = str_e.find(msg)
|
||||
if idx >= 0 and '"' not in str_e[:idx]:
|
||||
return True
|
||||
return False
|
||||
|
||||
dialect = PGDialect_psycopg2
|
||||
@@ -0,0 +1,78 @@
|
||||
# postgresql/pypostgresql.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: postgresql+pypostgresql
|
||||
:name: py-postgresql
|
||||
:dbapi: pypostgresql
|
||||
:connectstring: postgresql+pypostgresql://user:password@host:port/dbname[?key=value&key=value...]
|
||||
:url: http://python.projects.pgfoundry.org/
|
||||
|
||||
|
||||
"""
|
||||
from ... import util
|
||||
from ... import types as sqltypes
|
||||
from .base import PGDialect, PGExecutionContext
|
||||
from ... import processors
|
||||
|
||||
|
||||
class PGNumeric(sqltypes.Numeric):
|
||||
def bind_processor(self, dialect):
|
||||
return processors.to_str
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
if self.asdecimal:
|
||||
return None
|
||||
else:
|
||||
return processors.to_float
|
||||
|
||||
|
||||
class PGExecutionContext_pypostgresql(PGExecutionContext):
|
||||
pass
|
||||
|
||||
|
||||
class PGDialect_pypostgresql(PGDialect):
|
||||
driver = 'pypostgresql'
|
||||
|
||||
supports_unicode_statements = True
|
||||
supports_unicode_binds = True
|
||||
description_encoding = None
|
||||
default_paramstyle = 'pyformat'
|
||||
|
||||
# requires trunk version to support sane rowcounts
|
||||
# TODO: use dbapi version information to set this flag appropriately
|
||||
supports_sane_rowcount = True
|
||||
supports_sane_multi_rowcount = False
|
||||
|
||||
execution_ctx_cls = PGExecutionContext_pypostgresql
|
||||
colspecs = util.update_copy(
|
||||
PGDialect.colspecs,
|
||||
{
|
||||
sqltypes.Numeric: PGNumeric,
|
||||
|
||||
# prevents PGNumeric from being used
|
||||
sqltypes.Float: sqltypes.Float,
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
from postgresql.driver import dbapi20
|
||||
return dbapi20
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user')
|
||||
if 'port' in opts:
|
||||
opts['port'] = int(opts['port'])
|
||||
else:
|
||||
opts['port'] = 5432
|
||||
opts.update(url.query)
|
||||
return ([], opts)
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
return "connection is closed" in str(e)
|
||||
|
||||
dialect = PGDialect_pypostgresql
|
||||
@@ -0,0 +1,160 @@
|
||||
# Copyright (C) 2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from .base import ischema_names
|
||||
from ... import types as sqltypes
|
||||
|
||||
__all__ = ('INT4RANGE', 'INT8RANGE', 'NUMRANGE')
|
||||
|
||||
class RangeOperators(object):
|
||||
"""
|
||||
This mixin provides functionality for the Range Operators
|
||||
listed in Table 9-44 of the `postgres documentation`__ for Range
|
||||
Functions and Operators. It is used by all the range types
|
||||
provided in the ``postgres`` dialect and can likely be used for
|
||||
any range types you create yourself.
|
||||
|
||||
__ http://www.postgresql.org/docs/devel/static/functions-range.html
|
||||
|
||||
No extra support is provided for the Range Functions listed in
|
||||
Table 9-45 of the postgres documentation. For these, the normal
|
||||
:func:`~sqlalchemy.sql.expression.func` object should be used.
|
||||
|
||||
.. versionadded:: 0.8.2 Support for Postgresql RANGE operations.
|
||||
|
||||
"""
|
||||
|
||||
class comparator_factory(sqltypes.Concatenable.Comparator):
|
||||
"""Define comparison operations for range types."""
|
||||
|
||||
def __ne__(self, other):
|
||||
"Boolean expression. Returns true if two ranges are not equal"
|
||||
return self.expr.op('<>')(other)
|
||||
|
||||
def contains(self, other, **kw):
|
||||
"""Boolean expression. Returns true if the right hand operand,
|
||||
which can be an element or a range, is contained within the
|
||||
column.
|
||||
"""
|
||||
return self.expr.op('@>')(other)
|
||||
|
||||
def contained_by(self, other):
|
||||
"""Boolean expression. Returns true if the column is contained
|
||||
within the right hand operand.
|
||||
"""
|
||||
return self.expr.op('<@')(other)
|
||||
|
||||
def overlaps(self, other):
|
||||
"""Boolean expression. Returns true if the column overlaps
|
||||
(has points in common with) the right hand operand.
|
||||
"""
|
||||
return self.expr.op('&&')(other)
|
||||
|
||||
def strictly_left_of(self, other):
|
||||
"""Boolean expression. Returns true if the column is strictly
|
||||
left of the right hand operand.
|
||||
"""
|
||||
return self.expr.op('<<')(other)
|
||||
|
||||
__lshift__ = strictly_left_of
|
||||
|
||||
def strictly_right_of(self, other):
|
||||
"""Boolean expression. Returns true if the column is strictly
|
||||
right of the right hand operand.
|
||||
"""
|
||||
return self.expr.op('>>')(other)
|
||||
|
||||
__rshift__ = strictly_right_of
|
||||
|
||||
def not_extend_right_of(self, other):
|
||||
"""Boolean expression. Returns true if the range in the column
|
||||
does not extend right of the range in the operand.
|
||||
"""
|
||||
return self.expr.op('&<')(other)
|
||||
|
||||
def not_extend_left_of(self, other):
|
||||
"""Boolean expression. Returns true if the range in the column
|
||||
does not extend left of the range in the operand.
|
||||
"""
|
||||
return self.expr.op('&>')(other)
|
||||
|
||||
def adjacent_to(self, other):
|
||||
"""Boolean expression. Returns true if the range in the column
|
||||
is adjacent to the range in the operand.
|
||||
"""
|
||||
return self.expr.op('-|-')(other)
|
||||
|
||||
def __add__(self, other):
|
||||
"""Range expression. Returns the union of the two ranges.
|
||||
Will raise an exception if the resulting range is not
|
||||
contigous.
|
||||
"""
|
||||
return self.expr.op('+')(other)
|
||||
|
||||
class INT4RANGE(RangeOperators, sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql INT4RANGE type.
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'INT4RANGE'
|
||||
|
||||
ischema_names['int4range'] = INT4RANGE
|
||||
|
||||
class INT8RANGE(RangeOperators, sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql INT8RANGE type.
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'INT8RANGE'
|
||||
|
||||
ischema_names['int8range'] = INT8RANGE
|
||||
|
||||
class NUMRANGE(RangeOperators, sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql NUMRANGE type.
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'NUMRANGE'
|
||||
|
||||
ischema_names['numrange'] = NUMRANGE
|
||||
|
||||
class DATERANGE(RangeOperators, sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql DATERANGE type.
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'DATERANGE'
|
||||
|
||||
ischema_names['daterange'] = DATERANGE
|
||||
|
||||
class TSRANGE(RangeOperators, sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql TSRANGE type.
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'TSRANGE'
|
||||
|
||||
ischema_names['tsrange'] = TSRANGE
|
||||
|
||||
class TSTZRANGE(RangeOperators, sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql TSTZRANGE type.
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'TSTZRANGE'
|
||||
|
||||
ischema_names['tstzrange'] = TSTZRANGE
|
||||
@@ -0,0 +1,45 @@
|
||||
# postgresql/zxjdbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: postgresql+zxjdbc
|
||||
:name: zxJDBC for Jython
|
||||
:dbapi: zxjdbc
|
||||
:connectstring: postgresql+zxjdbc://scott:tiger@localhost/db
|
||||
:driverurl: http://jdbc.postgresql.org/
|
||||
|
||||
|
||||
"""
|
||||
from ...connectors.zxJDBC import ZxJDBCConnector
|
||||
from .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):
|
||||
parts = connection.connection.dbversion.split('.')
|
||||
return tuple(int(x) for x in parts)
|
||||
|
||||
dialect = PGDialect_zxjdbc
|
||||
@@ -0,0 +1,21 @@
|
||||
# sqlite/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.sqlite import base, pysqlite
|
||||
|
||||
# default dialect
|
||||
base.dialect = pysqlite.dialect
|
||||
|
||||
|
||||
from sqlalchemy.dialects.sqlite.base import \
|
||||
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',
|
||||
'REAL', 'dialect'
|
||||
)
|
||||
@@ -0,0 +1,935 @@
|
||||
# sqlite/base.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: sqlite
|
||||
:name: SQLite
|
||||
|
||||
|
||||
Date and Time Types
|
||||
-------------------
|
||||
|
||||
SQLite does not have built-in DATE, TIME, or DATETIME types, and pysqlite
|
||||
does not provide out of the box functionality for translating values between
|
||||
Python `datetime` objects and a SQLite-supported format. SQLAlchemy's own
|
||||
:class:`~sqlalchemy.types.DateTime` and related types provide date formatting
|
||||
and parsing functionality when SQlite is used. 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.
|
||||
|
||||
Auto Incrementing Behavior
|
||||
--------------------------
|
||||
|
||||
Background on SQLite's autoincrement is at: http://sqlite.org/autoinc.html
|
||||
|
||||
Two things to note:
|
||||
|
||||
* The AUTOINCREMENT keyword is **not** required for SQLite tables to
|
||||
generate primary key values automatically. AUTOINCREMENT only means that
|
||||
the algorithm used to generate ROWID values should be slightly different.
|
||||
* SQLite does **not** generate primary key (i.e. ROWID) values, even for
|
||||
one column, if the table has a composite (i.e. multi-column) primary key.
|
||||
This is regardless of the AUTOINCREMENT keyword being present or not.
|
||||
|
||||
To specifically render the AUTOINCREMENT keyword on the primary key
|
||||
column when rendering DDL, add the flag ``sqlite_autoincrement=True``
|
||||
to the Table construct::
|
||||
|
||||
Table('sometable', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
sqlite_autoincrement=True)
|
||||
|
||||
Transaction Isolation Level
|
||||
---------------------------
|
||||
|
||||
:func:`.create_engine` accepts an ``isolation_level`` parameter which
|
||||
results in the command ``PRAGMA read_uncommitted <level>`` being invoked for
|
||||
every new connection. Valid values for this parameter are ``SERIALIZABLE``
|
||||
and ``READ UNCOMMITTED`` corresponding to a value of 0 and 1, respectively.
|
||||
See the section :ref:`pysqlite_serializable` for an important workaround
|
||||
when using serializable isolation with Pysqlite.
|
||||
|
||||
Database Locking Behavior / Concurrency
|
||||
---------------------------------------
|
||||
|
||||
Note that SQLite is not designed for a high level of concurrency. The
|
||||
database itself, being a file, is locked completely during write operations
|
||||
and within transactions, meaning exactly one connection has exclusive access
|
||||
to the database during this period - all other connections will be blocked
|
||||
during this time.
|
||||
|
||||
The Python DBAPI specification also calls for a connection model that is always
|
||||
in a transaction; there is no BEGIN method, only commit and rollback. This
|
||||
implies that a SQLite DBAPI driver would technically allow only serialized
|
||||
access to a particular database file at all times. The pysqlite driver
|
||||
attempts to ameliorate this by deferring the actual BEGIN statement until
|
||||
the first DML (INSERT, UPDATE, or DELETE) is received within a
|
||||
transaction. While this breaks serializable isolation, it at least delays
|
||||
the exclusive locking inherent in SQLite's design.
|
||||
|
||||
SQLAlchemy's default mode of usage with the ORM is known
|
||||
as "autocommit=False", which means the moment the :class:`.Session` begins to
|
||||
be used, a transaction is begun. As the :class:`.Session` is used, the
|
||||
autoflush feature, also on by default, will flush out pending changes to the
|
||||
database before each query. The effect of this is that a :class:`.Session`
|
||||
used in its default mode will often emit DML early on, long before the
|
||||
transaction is actually committed. This again will have the effect of
|
||||
serializing access to the SQLite database. If highly concurrent reads are
|
||||
desired against the SQLite database, it is advised that the autoflush feature
|
||||
be disabled, and potentially even that autocommit be re-enabled, which has
|
||||
the effect of each SQL statement and flush committing changes immediately.
|
||||
|
||||
For more information on SQLite's lack of concurrency by design, please
|
||||
see `Situations Where Another RDBMS May Work Better - High
|
||||
Concurrency <http://www.sqlite.org/whentouse.html>`_ near the bottom of
|
||||
the page.
|
||||
|
||||
.. _sqlite_foreign_keys:
|
||||
|
||||
Foreign Key Support
|
||||
-------------------
|
||||
|
||||
SQLite supports FOREIGN KEY syntax when emitting CREATE statements for tables,
|
||||
however by default these constraints have no effect on the operation
|
||||
of the table.
|
||||
|
||||
Constraint checking on SQLite has three prerequisites:
|
||||
|
||||
* At least version 3.6.19 of SQLite must be in use
|
||||
* The SQLite libary must be compiled *without* the SQLITE_OMIT_FOREIGN_KEY
|
||||
or SQLITE_OMIT_TRIGGER symbols enabled.
|
||||
* The ``PRAGMA foreign_keys = ON`` statement must be emitted on all connections
|
||||
before use.
|
||||
|
||||
SQLAlchemy allows for the ``PRAGMA`` statement to be emitted automatically
|
||||
for new connections through the usage of events::
|
||||
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy import event
|
||||
|
||||
@event.listens_for(Engine, "connect")
|
||||
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||
cursor = dbapi_connection.cursor()
|
||||
cursor.execute("PRAGMA foreign_keys=ON")
|
||||
cursor.close()
|
||||
|
||||
.. seealso::
|
||||
|
||||
`SQLite Foreign Key Support <http://www.sqlite.org/foreignkeys.html>`_ -
|
||||
on the SQLite web site.
|
||||
|
||||
:ref:`event_toplevel` - SQLAlchemy event API.
|
||||
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import re
|
||||
|
||||
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
|
||||
from sqlalchemy import processors
|
||||
|
||||
from sqlalchemy.types import BIGINT, BLOB, BOOLEAN, CHAR,\
|
||||
DECIMAL, FLOAT, REAL, INTEGER, NUMERIC, SMALLINT, TEXT,\
|
||||
TIMESTAMP, VARCHAR
|
||||
|
||||
|
||||
class _DateTimeMixin(object):
|
||||
_reg = None
|
||||
_storage_format = None
|
||||
|
||||
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
|
||||
|
||||
def adapt(self, cls, **kw):
|
||||
if self._storage_format:
|
||||
kw["storage_format"] = self._storage_format
|
||||
if self._reg:
|
||||
kw["regexp"] = self._reg
|
||||
return util.constructor_copy(self, cls, **kw)
|
||||
|
||||
class DATETIME(_DateTimeMixin, sqltypes.DateTime):
|
||||
"""Represent a Python datetime object in SQLite using a string.
|
||||
|
||||
The default string storage format is::
|
||||
|
||||
"%(year)04d-%(month)02d-%(day)02d %(hour)02d:%(min)02d:%(second)02d.%(microsecond)06d"
|
||||
|
||||
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="%(year)04d/%(month)02d/%(day)02d %(hour)02d:%(min)02d:%(second)02d",
|
||||
regexp=r"(\d+)/(\d+)/(\d+) (\d+)-(\d+)-(\d+)"
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be applied to the
|
||||
dict with keys year, month, day, hour, minute, second, and microsecond.
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. If the regexp contains named groups, the
|
||||
resulting match dict is applied to the Python datetime() constructor
|
||||
as keyword arguments. Otherwise, if positional groups are used, the
|
||||
the datetime() constructor is called with positional arguments via
|
||||
``*map(int, match_obj.groups(0))``.
|
||||
"""
|
||||
|
||||
_storage_format = (
|
||||
"%(year)04d-%(month)02d-%(day)02d "
|
||||
"%(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d"
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
truncate_microseconds = kwargs.pop('truncate_microseconds', False)
|
||||
super(DATETIME, self).__init__(*args, **kwargs)
|
||||
if truncate_microseconds:
|
||||
assert 'storage_format' not in kwargs, "You can specify only "\
|
||||
"one of truncate_microseconds or storage_format."
|
||||
assert 'regexp' not in kwargs, "You can specify only one of "\
|
||||
"truncate_microseconds or regexp."
|
||||
self._storage_format = (
|
||||
"%(year)04d-%(month)02d-%(day)02d "
|
||||
"%(hour)02d:%(minute)02d:%(second)02d"
|
||||
)
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
datetime_datetime = datetime.datetime
|
||||
datetime_date = datetime.date
|
||||
format = self._storage_format
|
||||
|
||||
def process(value):
|
||||
if value is None:
|
||||
return None
|
||||
elif isinstance(value, datetime_datetime):
|
||||
return format % {
|
||||
'year': value.year,
|
||||
'month': value.month,
|
||||
'day': value.day,
|
||||
'hour': value.hour,
|
||||
'minute': value.minute,
|
||||
'second': value.second,
|
||||
'microsecond': value.microsecond,
|
||||
}
|
||||
elif isinstance(value, datetime_date):
|
||||
return format % {
|
||||
'year': value.year,
|
||||
'month': value.month,
|
||||
'day': value.day,
|
||||
'hour': 0,
|
||||
'minute': 0,
|
||||
'second': 0,
|
||||
'microsecond': 0,
|
||||
}
|
||||
else:
|
||||
raise TypeError("SQLite DateTime type only accepts Python "
|
||||
"datetime and date objects as input.")
|
||||
return process
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
if self._reg:
|
||||
return processors.str_to_datetime_processor_factory(
|
||||
self._reg, datetime.datetime)
|
||||
else:
|
||||
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::
|
||||
|
||||
"%(year)04d-%(month)02d-%(day)02d"
|
||||
|
||||
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="%(month)02d/%(day)02d/%(year)04d",
|
||||
regexp=re.compile("(?P<month>\d+)/(?P<day>\d+)/(?P<year>\d+)")
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be applied to the
|
||||
dict with keys year, month, and day.
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. If the regexp contains named groups, the
|
||||
resulting match dict is applied to the Python date() constructor
|
||||
as keyword arguments. Otherwise, if positional groups are used, the
|
||||
the date() constructor is called with positional arguments via
|
||||
``*map(int, match_obj.groups(0))``.
|
||||
"""
|
||||
|
||||
_storage_format = "%(year)04d-%(month)02d-%(day)02d"
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
datetime_date = datetime.date
|
||||
format = self._storage_format
|
||||
|
||||
def process(value):
|
||||
if value is None:
|
||||
return None
|
||||
elif isinstance(value, datetime_date):
|
||||
return format % {
|
||||
'year': value.year,
|
||||
'month': value.month,
|
||||
'day': value.day,
|
||||
}
|
||||
else:
|
||||
raise TypeError("SQLite Date type only accepts Python "
|
||||
"date objects as input.")
|
||||
return process
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
if self._reg:
|
||||
return processors.str_to_datetime_processor_factory(
|
||||
self._reg, datetime.date)
|
||||
else:
|
||||
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::
|
||||
|
||||
"%(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d"
|
||||
|
||||
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="%(hour)02d-%(minute)02d-%(second)02d-%(microsecond)06d",
|
||||
regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?")
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be applied to the
|
||||
dict with keys hour, minute, second, and microsecond.
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. If the regexp contains named groups, the
|
||||
resulting match dict is applied to the Python time() constructor
|
||||
as keyword arguments. Otherwise, if positional groups are used, the
|
||||
the time() constructor is called with positional arguments via
|
||||
``*map(int, match_obj.groups(0))``.
|
||||
"""
|
||||
|
||||
_storage_format = "%(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
truncate_microseconds = kwargs.pop('truncate_microseconds', False)
|
||||
super(TIME, self).__init__(*args, **kwargs)
|
||||
if truncate_microseconds:
|
||||
assert 'storage_format' not in kwargs, "You can specify only "\
|
||||
"one of truncate_microseconds or storage_format."
|
||||
assert 'regexp' not in kwargs, "You can specify only one of "\
|
||||
"truncate_microseconds or regexp."
|
||||
self._storage_format = "%(hour)02d:%(minute)02d:%(second)02d"
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
datetime_time = datetime.time
|
||||
format = self._storage_format
|
||||
|
||||
def process(value):
|
||||
if value is None:
|
||||
return None
|
||||
elif isinstance(value, datetime_time):
|
||||
return format % {
|
||||
'hour': value.hour,
|
||||
'minute': value.minute,
|
||||
'second': value.second,
|
||||
'microsecond': value.microsecond,
|
||||
}
|
||||
else:
|
||||
raise TypeError("SQLite Time type only accepts Python "
|
||||
"time objects as input.")
|
||||
return process
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
if self._reg:
|
||||
return processors.str_to_datetime_processor_factory(
|
||||
self._reg, datetime.time)
|
||||
else:
|
||||
return processors.str_to_time
|
||||
|
||||
colspecs = {
|
||||
sqltypes.Date: DATE,
|
||||
sqltypes.DateTime: DATETIME,
|
||||
sqltypes.Time: TIME,
|
||||
}
|
||||
|
||||
ischema_names = {
|
||||
'BIGINT': sqltypes.BIGINT,
|
||||
'BLOB': sqltypes.BLOB,
|
||||
'BOOL': sqltypes.BOOLEAN,
|
||||
'BOOLEAN': sqltypes.BOOLEAN,
|
||||
'CHAR': sqltypes.CHAR,
|
||||
'DATE': sqltypes.DATE,
|
||||
'DATETIME': sqltypes.DATETIME,
|
||||
'DECIMAL': sqltypes.DECIMAL,
|
||||
'FLOAT': sqltypes.FLOAT,
|
||||
'INT': sqltypes.INTEGER,
|
||||
'INTEGER': sqltypes.INTEGER,
|
||||
'NUMERIC': sqltypes.NUMERIC,
|
||||
'REAL': sqltypes.REAL,
|
||||
'SMALLINT': sqltypes.SMALLINT,
|
||||
'TEXT': sqltypes.TEXT,
|
||||
'TIME': sqltypes.TIME,
|
||||
'TIMESTAMP': sqltypes.TIMESTAMP,
|
||||
'VARCHAR': sqltypes.VARCHAR,
|
||||
'NVARCHAR': sqltypes.NVARCHAR,
|
||||
'NCHAR': sqltypes.NCHAR,
|
||||
}
|
||||
|
||||
|
||||
class SQLiteCompiler(compiler.SQLCompiler):
|
||||
extract_map = util.update_copy(
|
||||
compiler.SQLCompiler.extract_map,
|
||||
{
|
||||
'month': '%m',
|
||||
'day': '%d',
|
||||
'year': '%Y',
|
||||
'second': '%S',
|
||||
'hour': '%H',
|
||||
'doy': '%j',
|
||||
'minute': '%M',
|
||||
'epoch': '%s',
|
||||
'dow': '%w',
|
||||
'week': '%W'
|
||||
})
|
||||
|
||||
def visit_now_func(self, fn, **kw):
|
||||
return "CURRENT_TIMESTAMP"
|
||||
|
||||
def visit_localtimestamp_func(self, func, **kw):
|
||||
return 'DATETIME(CURRENT_TIMESTAMP, "localtime")'
|
||||
|
||||
def visit_true(self, expr, **kw):
|
||||
return '1'
|
||||
|
||||
def visit_false(self, expr, **kw):
|
||||
return '0'
|
||||
|
||||
def visit_char_length_func(self, fn, **kw):
|
||||
return "length%s" % self.function_argspec(fn)
|
||||
|
||||
def visit_cast(self, cast, **kwargs):
|
||||
if self.dialect.supports_cast:
|
||||
return super(SQLiteCompiler, self).visit_cast(cast)
|
||||
else:
|
||||
return self.process(cast.clause)
|
||||
|
||||
def visit_extract(self, extract, **kw):
|
||||
try:
|
||||
return "CAST(STRFTIME('%s', %s) AS INTEGER)" % (
|
||||
self.extract_map[extract.field],
|
||||
self.process(extract.expr, **kw)
|
||||
)
|
||||
except KeyError:
|
||||
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 " + self.process(sql.literal(select._limit))
|
||||
if select._offset is not None:
|
||||
if select._limit is None:
|
||||
text += "\n LIMIT " + self.process(sql.literal(-1))
|
||||
text += " OFFSET " + self.process(sql.literal(select._offset))
|
||||
else:
|
||||
text += " OFFSET " + self.process(sql.literal(0))
|
||||
return text
|
||||
|
||||
def for_update_clause(self, select):
|
||||
# sqlite has no "FOR UPDATE" AFAICT
|
||||
return ''
|
||||
|
||||
|
||||
class SQLiteDDLCompiler(compiler.DDLCompiler):
|
||||
|
||||
def get_column_specification(self, column, **kwargs):
|
||||
coltype = self.dialect.type_compiler.process(column.type)
|
||||
colspec = self.preparer.format_column(column) + " " + coltype
|
||||
default = self.get_column_default_string(column)
|
||||
if default is not None:
|
||||
colspec += " DEFAULT " + default
|
||||
|
||||
if not column.nullable:
|
||||
colspec += " NOT NULL"
|
||||
|
||||
if (column.primary_key and
|
||||
column.table.kwargs.get('sqlite_autoincrement', False) and
|
||||
len(column.table.primary_key.columns) == 1 and
|
||||
issubclass(column.type._type_affinity, sqltypes.Integer) and
|
||||
not column.foreign_keys):
|
||||
colspec += " PRIMARY KEY AUTOINCREMENT"
|
||||
|
||||
return colspec
|
||||
|
||||
def visit_primary_key_constraint(self, constraint):
|
||||
# for columns with sqlite_autoincrement=True,
|
||||
# the PRIMARY KEY constraint can only be inline
|
||||
# with the column itself.
|
||||
if len(constraint.columns) == 1:
|
||||
c = list(constraint)[0]
|
||||
if c.primary_key and \
|
||||
c.table.kwargs.get('sqlite_autoincrement', False) and \
|
||||
issubclass(c.type._type_affinity, sqltypes.Integer) and \
|
||||
not c.foreign_keys:
|
||||
return None
|
||||
|
||||
return super(SQLiteDDLCompiler, self).\
|
||||
visit_primary_key_constraint(constraint)
|
||||
|
||||
def visit_foreign_key_constraint(self, constraint):
|
||||
|
||||
local_table = constraint._elements.values()[0].parent.table
|
||||
remote_table = list(constraint._elements.values())[0].column.table
|
||||
|
||||
if local_table.schema != remote_table.schema:
|
||||
return None
|
||||
else:
|
||||
return super(SQLiteDDLCompiler, self).visit_foreign_key_constraint(constraint)
|
||||
|
||||
def define_constraint_remote_table(self, constraint, table, preparer):
|
||||
"""Format the remote table clause of a CREATE CONSTRAINT clause."""
|
||||
|
||||
return preparer.format_table(table, use_schema=False)
|
||||
|
||||
def visit_create_index(self, create):
|
||||
return super(SQLiteDDLCompiler, self).\
|
||||
visit_create_index(create, include_table_schema=False)
|
||||
|
||||
|
||||
class SQLiteTypeCompiler(compiler.GenericTypeCompiler):
|
||||
def visit_large_binary(self, type_):
|
||||
return self.visit_BLOB(type_)
|
||||
|
||||
|
||||
class SQLiteIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
reserved_words = set([
|
||||
'add', 'after', 'all', 'alter', 'analyze', 'and', 'as', 'asc',
|
||||
'attach', 'autoincrement', 'before', 'begin', 'between', 'by',
|
||||
'cascade', 'case', 'cast', 'check', 'collate', 'column', 'commit',
|
||||
'conflict', 'constraint', 'create', 'cross', 'current_date',
|
||||
'current_time', 'current_timestamp', 'database', 'default',
|
||||
'deferrable', 'deferred', 'delete', 'desc', 'detach', 'distinct',
|
||||
'drop', 'each', 'else', 'end', 'escape', 'except', 'exclusive',
|
||||
'explain', 'false', 'fail', 'for', 'foreign', 'from', 'full', 'glob',
|
||||
'group', 'having', 'if', 'ignore', 'immediate', 'in', 'index',
|
||||
'indexed', 'initially', 'inner', 'insert', 'instead', 'intersect',
|
||||
'into', 'is', 'isnull', 'join', 'key', 'left', 'like', 'limit',
|
||||
'match', 'natural', 'not', 'notnull', 'null', 'of', 'offset', 'on',
|
||||
'or', 'order', 'outer', 'plan', 'pragma', 'primary', 'query',
|
||||
'raise', 'references', 'reindex', 'rename', 'replace', 'restrict',
|
||||
'right', 'rollback', 'row', 'select', 'set', 'table', 'temp',
|
||||
'temporary', 'then', 'to', 'transaction', 'trigger', 'true', 'union',
|
||||
'unique', 'update', 'using', 'vacuum', 'values', 'view', 'virtual',
|
||||
'when', 'where',
|
||||
])
|
||||
|
||||
def format_index(self, index, use_schema=True, name=None):
|
||||
"""Prepare a quoted index and schema name."""
|
||||
|
||||
if name is None:
|
||||
name = index.name
|
||||
result = self.quote(name, index.quote)
|
||||
if (not self.omit_schema and
|
||||
use_schema and
|
||||
getattr(index.table, "schema", None)):
|
||||
result = self.quote_schema(
|
||||
index.table.schema, index.table.quote_schema) + "." + result
|
||||
return result
|
||||
|
||||
|
||||
class SQLiteExecutionContext(default.DefaultExecutionContext):
|
||||
@util.memoized_property
|
||||
def _preserve_raw_colnames(self):
|
||||
return self.execution_options.get("sqlite_raw_colnames", False)
|
||||
|
||||
def _translate_colname(self, colname):
|
||||
# adjust for dotted column names. SQLite
|
||||
# in the case of UNION may store col names as
|
||||
# "tablename.colname"
|
||||
# in cursor.description
|
||||
if not self._preserve_raw_colnames and "." in colname:
|
||||
return colname.split(".")[1], colname
|
||||
else:
|
||||
return colname, None
|
||||
|
||||
|
||||
class SQLiteDialect(default.DefaultDialect):
|
||||
name = 'sqlite'
|
||||
supports_alter = False
|
||||
supports_unicode_statements = True
|
||||
supports_unicode_binds = True
|
||||
supports_default_values = True
|
||||
supports_empty_insert = False
|
||||
supports_cast = True
|
||||
supports_multivalues_insert = True
|
||||
|
||||
default_paramstyle = 'qmark'
|
||||
execution_ctx_cls = SQLiteExecutionContext
|
||||
statement_compiler = SQLiteCompiler
|
||||
ddl_compiler = SQLiteDDLCompiler
|
||||
type_compiler = SQLiteTypeCompiler
|
||||
preparer = SQLiteIdentifierPreparer
|
||||
ischema_names = ischema_names
|
||||
colspecs = colspecs
|
||||
isolation_level = None
|
||||
|
||||
supports_cast = True
|
||||
supports_default_values = True
|
||||
|
||||
_broken_fk_pragma_quotes = False
|
||||
|
||||
def __init__(self, isolation_level=None, native_datetime=False, **kwargs):
|
||||
default.DefaultDialect.__init__(self, **kwargs)
|
||||
self.isolation_level = isolation_level
|
||||
|
||||
# this flag used by pysqlite dialect, and perhaps others in the
|
||||
# future, to indicate the driver is handling date/timestamp
|
||||
# conversions (and perhaps datetime/time as well on some
|
||||
# hypothetical driver ?)
|
||||
self.native_datetime = native_datetime
|
||||
|
||||
if self.dbapi is not None:
|
||||
self.supports_default_values = \
|
||||
self.dbapi.sqlite_version_info >= (3, 3, 8)
|
||||
self.supports_cast = \
|
||||
self.dbapi.sqlite_version_info >= (3, 2, 3)
|
||||
self.supports_multivalues_insert = \
|
||||
self.dbapi.sqlite_version_info >= (3, 7, 11)
|
||||
# http://www.sqlite.org/releaselog/3_7_11.html
|
||||
|
||||
# see http://www.sqlalchemy.org/trac/ticket/2568
|
||||
# as well as http://www.sqlite.org/src/info/600482d161
|
||||
self._broken_fk_pragma_quotes = \
|
||||
self.dbapi.sqlite_version_info < (3, 6, 14)
|
||||
|
||||
|
||||
_isolation_lookup = {
|
||||
'READ UNCOMMITTED': 1,
|
||||
'SERIALIZABLE': 0
|
||||
}
|
||||
|
||||
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:
|
||||
def connect(conn):
|
||||
self.set_isolation_level(conn, self.isolation_level)
|
||||
return connect
|
||||
else:
|
||||
return None
|
||||
|
||||
@reflection.cache
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
if schema is not None:
|
||||
qschema = self.identifier_preparer.quote_identifier(schema)
|
||||
master = '%s.sqlite_master' % qschema
|
||||
s = ("SELECT name FROM %s "
|
||||
"WHERE type='table' ORDER BY name") % (master,)
|
||||
rs = connection.execute(s)
|
||||
else:
|
||||
try:
|
||||
s = ("SELECT name FROM "
|
||||
" (SELECT * FROM sqlite_master UNION ALL "
|
||||
" SELECT * FROM sqlite_temp_master) "
|
||||
"WHERE type='table' ORDER BY name")
|
||||
rs = connection.execute(s)
|
||||
except exc.DBAPIError:
|
||||
s = ("SELECT name FROM sqlite_master "
|
||||
"WHERE type='table' ORDER BY name")
|
||||
rs = connection.execute(s)
|
||||
|
||||
return [row[0] for row in rs]
|
||||
|
||||
def has_table(self, connection, table_name, schema=None):
|
||||
quote = self.identifier_preparer.quote_identifier
|
||||
if schema is not None:
|
||||
pragma = "PRAGMA %s." % quote(schema)
|
||||
else:
|
||||
pragma = "PRAGMA "
|
||||
qtable = quote(table_name)
|
||||
statement = "%stable_info(%s)" % (pragma, qtable)
|
||||
cursor = _pragma_cursor(connection.execute(statement))
|
||||
row = cursor.fetchone()
|
||||
|
||||
# consume remaining rows, to work around
|
||||
# http://www.sqlite.org/cvstrac/tktview?tn=1884
|
||||
while not cursor.closed and cursor.fetchone() is not None:
|
||||
pass
|
||||
|
||||
return (row is not None)
|
||||
|
||||
@reflection.cache
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
if schema is not None:
|
||||
qschema = self.identifier_preparer.quote_identifier(schema)
|
||||
master = '%s.sqlite_master' % qschema
|
||||
s = ("SELECT name FROM %s "
|
||||
"WHERE type='view' ORDER BY name") % (master,)
|
||||
rs = connection.execute(s)
|
||||
else:
|
||||
try:
|
||||
s = ("SELECT name FROM "
|
||||
" (SELECT * FROM sqlite_master UNION ALL "
|
||||
" SELECT * FROM sqlite_temp_master) "
|
||||
"WHERE type='view' ORDER BY name")
|
||||
rs = connection.execute(s)
|
||||
except exc.DBAPIError:
|
||||
s = ("SELECT name FROM sqlite_master "
|
||||
"WHERE type='view' ORDER BY name")
|
||||
rs = connection.execute(s)
|
||||
|
||||
return [row[0] for row in rs]
|
||||
|
||||
@reflection.cache
|
||||
def get_view_definition(self, connection, view_name, schema=None, **kw):
|
||||
quote = self.identifier_preparer.quote_identifier
|
||||
if schema is not None:
|
||||
qschema = self.identifier_preparer.quote_identifier(schema)
|
||||
master = '%s.sqlite_master' % qschema
|
||||
s = ("SELECT sql FROM %s WHERE name = '%s'"
|
||||
"AND type='view'") % (master, view_name)
|
||||
rs = connection.execute(s)
|
||||
else:
|
||||
try:
|
||||
s = ("SELECT sql FROM "
|
||||
" (SELECT * FROM sqlite_master UNION ALL "
|
||||
" SELECT * FROM sqlite_temp_master) "
|
||||
"WHERE name = '%s' "
|
||||
"AND type='view'") % view_name
|
||||
rs = connection.execute(s)
|
||||
except exc.DBAPIError:
|
||||
s = ("SELECT sql FROM sqlite_master WHERE name = '%s' "
|
||||
"AND type='view'") % view_name
|
||||
rs = connection.execute(s)
|
||||
|
||||
result = rs.fetchall()
|
||||
if result:
|
||||
return result[0].sql
|
||||
|
||||
@reflection.cache
|
||||
def get_columns(self, connection, table_name, schema=None, **kw):
|
||||
quote = self.identifier_preparer.quote_identifier
|
||||
if schema is not None:
|
||||
pragma = "PRAGMA %s." % quote(schema)
|
||||
else:
|
||||
pragma = "PRAGMA "
|
||||
qtable = quote(table_name)
|
||||
statement = "%stable_info(%s)" % (pragma, qtable)
|
||||
c = _pragma_cursor(connection.execute(statement))
|
||||
|
||||
rows = c.fetchall()
|
||||
columns = []
|
||||
for row in rows:
|
||||
(name, type_, nullable, default, primary_key) = \
|
||||
(row[1], row[2].upper(), not row[3],
|
||||
row[4], row[5])
|
||||
|
||||
columns.append(self._get_column_info(name, type_, nullable,
|
||||
default, primary_key))
|
||||
return columns
|
||||
|
||||
def _get_column_info(self, name, type_, nullable,
|
||||
default, primary_key):
|
||||
|
||||
match = re.match(r'(\w+)(\(.*?\))?', type_)
|
||||
if match:
|
||||
coltype = match.group(1)
|
||||
args = match.group(2)
|
||||
else:
|
||||
coltype = "VARCHAR"
|
||||
args = ''
|
||||
try:
|
||||
coltype = self.ischema_names[coltype]
|
||||
if args is not None:
|
||||
args = re.findall(r'(\d+)', args)
|
||||
coltype = coltype(*[int(a) for a in args])
|
||||
except KeyError:
|
||||
util.warn("Did not recognize type '%s' of column '%s'" %
|
||||
(coltype, name))
|
||||
coltype = sqltypes.NullType()
|
||||
|
||||
if default is not None:
|
||||
default = unicode(default)
|
||||
|
||||
return {
|
||||
'name': name,
|
||||
'type': coltype,
|
||||
'nullable': nullable,
|
||||
'default': default,
|
||||
'autoincrement': default is None,
|
||||
'primary_key': primary_key
|
||||
}
|
||||
|
||||
@reflection.cache
|
||||
def get_pk_constraint(self, connection, table_name, schema=None, **kw):
|
||||
cols = self.get_columns(connection, table_name, schema, **kw)
|
||||
pkeys = []
|
||||
for col in cols:
|
||||
if col['primary_key']:
|
||||
pkeys.append(col['name'])
|
||||
return {'constrained_columns': pkeys, 'name': None}
|
||||
|
||||
@reflection.cache
|
||||
def get_foreign_keys(self, connection, table_name, schema=None, **kw):
|
||||
quote = self.identifier_preparer.quote_identifier
|
||||
if schema is not None:
|
||||
pragma = "PRAGMA %s." % quote(schema)
|
||||
else:
|
||||
pragma = "PRAGMA "
|
||||
qtable = quote(table_name)
|
||||
statement = "%sforeign_key_list(%s)" % (pragma, qtable)
|
||||
c = _pragma_cursor(connection.execute(statement))
|
||||
fkeys = []
|
||||
fks = {}
|
||||
while True:
|
||||
row = c.fetchone()
|
||||
if row is None:
|
||||
break
|
||||
(numerical_id, rtbl, lcol, rcol) = (row[0], row[2], row[3], row[4])
|
||||
|
||||
self._parse_fk(fks, fkeys, numerical_id, rtbl, lcol, rcol)
|
||||
return fkeys
|
||||
|
||||
def _parse_fk(self, fks, fkeys, numerical_id, rtbl, lcol, rcol):
|
||||
# sqlite won't return rcol if the table
|
||||
# was created with REFERENCES <tablename>, no col
|
||||
if rcol is None:
|
||||
rcol = lcol
|
||||
|
||||
if self._broken_fk_pragma_quotes:
|
||||
rtbl = re.sub(r'^[\"\[`\']|[\"\]`\']$', '', rtbl)
|
||||
|
||||
try:
|
||||
fk = fks[numerical_id]
|
||||
except KeyError:
|
||||
fk = {
|
||||
'name': None,
|
||||
'constrained_columns': [],
|
||||
'referred_schema': None,
|
||||
'referred_table': rtbl,
|
||||
'referred_columns': []
|
||||
}
|
||||
fkeys.append(fk)
|
||||
fks[numerical_id] = fk
|
||||
|
||||
if lcol not in fk['constrained_columns']:
|
||||
fk['constrained_columns'].append(lcol)
|
||||
if rcol not in fk['referred_columns']:
|
||||
fk['referred_columns'].append(rcol)
|
||||
return fk
|
||||
|
||||
@reflection.cache
|
||||
def get_indexes(self, connection, table_name, schema=None, **kw):
|
||||
quote = self.identifier_preparer.quote_identifier
|
||||
if schema is not None:
|
||||
pragma = "PRAGMA %s." % quote(schema)
|
||||
else:
|
||||
pragma = "PRAGMA "
|
||||
include_auto_indexes = kw.pop('include_auto_indexes', False)
|
||||
qtable = quote(table_name)
|
||||
statement = "%sindex_list(%s)" % (pragma, qtable)
|
||||
c = _pragma_cursor(connection.execute(statement))
|
||||
indexes = []
|
||||
while True:
|
||||
row = c.fetchone()
|
||||
if row is None:
|
||||
break
|
||||
# ignore implicit primary key index.
|
||||
# http://www.mail-archive.com/sqlite-users@sqlite.org/msg30517.html
|
||||
elif (not include_auto_indexes and
|
||||
row[1].startswith('sqlite_autoindex')):
|
||||
continue
|
||||
|
||||
indexes.append(dict(name=row[1], column_names=[], unique=row[2]))
|
||||
# loop thru unique indexes to get the column names.
|
||||
for idx in indexes:
|
||||
statement = "%sindex_info(%s)" % (pragma, quote(idx['name']))
|
||||
c = connection.execute(statement)
|
||||
cols = idx['column_names']
|
||||
while True:
|
||||
row = c.fetchone()
|
||||
if row is None:
|
||||
break
|
||||
cols.append(row[2])
|
||||
return indexes
|
||||
|
||||
|
||||
def _pragma_cursor(cursor):
|
||||
"""work around SQLite issue whereby cursor.description
|
||||
is blank when PRAGMA returns no rows."""
|
||||
|
||||
if cursor.closed:
|
||||
cursor.fetchone = lambda: None
|
||||
cursor.fetchall = lambda: []
|
||||
return cursor
|
||||
@@ -0,0 +1,335 @@
|
||||
# sqlite/pysqlite.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: sqlite+pysqlite
|
||||
:name: pysqlite
|
||||
:dbapi: sqlite3
|
||||
:connectstring: sqlite+pysqlite:///file_path
|
||||
:url: http://docs.python.org/library/sqlite3.html
|
||||
|
||||
Note that ``pysqlite`` is the same driver as the ``sqlite3``
|
||||
module included with the Python distribution.
|
||||
|
||||
Driver
|
||||
------
|
||||
|
||||
When using Python 2.5 and above, the built in ``sqlite3`` driver is
|
||||
already installed and no additional installation is needed. Otherwise,
|
||||
the ``pysqlite2`` driver needs to be present. This is the same driver as
|
||||
``sqlite3``, just with a different name.
|
||||
|
||||
The ``pysqlite2`` driver will be loaded first, and if not found, ``sqlite3``
|
||||
is loaded. This allows an explicitly installed pysqlite driver to take
|
||||
precedence over the built in one. As with all dialects, a specific
|
||||
DBAPI module may be provided to :func:`~sqlalchemy.create_engine()` to control
|
||||
this explicitly::
|
||||
|
||||
from sqlite3 import dbapi2 as sqlite
|
||||
e = create_engine('sqlite+pysqlite:///file.db', module=sqlite)
|
||||
|
||||
|
||||
Connect Strings
|
||||
---------------
|
||||
|
||||
The file specification for the SQLite database is taken as the "database"
|
||||
portion of the URL. Note that the format of a SQLAlchemy url is::
|
||||
|
||||
driver://user:pass@host/database
|
||||
|
||||
This means that the actual filename to be used starts with the characters to
|
||||
the **right** of the third slash. So connecting to a relative filepath
|
||||
looks like::
|
||||
|
||||
# relative path
|
||||
e = create_engine('sqlite:///path/to/database.db')
|
||||
|
||||
An absolute path, which is denoted by starting with a slash, means you
|
||||
need **four** slashes::
|
||||
|
||||
# absolute path
|
||||
e = create_engine('sqlite:////path/to/database.db')
|
||||
|
||||
To use a Windows path, regular drive specifications and backslashes can be
|
||||
used. Double backslashes are probably needed::
|
||||
|
||||
# absolute path on Windows
|
||||
e = create_engine('sqlite:///C:\\\\path\\\\to\\\\database.db')
|
||||
|
||||
The sqlite ``:memory:`` identifier is the default if no filepath is
|
||||
present. Specify ``sqlite://`` and nothing else::
|
||||
|
||||
# in-memory database
|
||||
e = create_engine('sqlite://')
|
||||
|
||||
Compatibility with sqlite3 "native" date and datetime types
|
||||
-----------------------------------------------------------
|
||||
|
||||
The pysqlite driver includes the sqlite3.PARSE_DECLTYPES and
|
||||
sqlite3.PARSE_COLNAMES options, which have the effect of any column
|
||||
or expression explicitly cast as "date" or "timestamp" will be converted
|
||||
to a Python date or datetime object. The date and datetime types provided
|
||||
with the pysqlite dialect are not currently compatible with these options,
|
||||
since they render the ISO date/datetime including microseconds, which
|
||||
pysqlite's driver does not. Additionally, SQLAlchemy does not at
|
||||
this time automatically render the "cast" syntax required for the
|
||||
freestanding functions "current_timestamp" and "current_date" to return
|
||||
datetime/date types natively. Unfortunately, pysqlite
|
||||
does not provide the standard DBAPI types in ``cursor.description``,
|
||||
leaving SQLAlchemy with no way to detect these types on the fly
|
||||
without expensive per-row type checks.
|
||||
|
||||
Keeping in mind that pysqlite's parsing option is not recommended,
|
||||
nor should be necessary, for use with SQLAlchemy, usage of PARSE_DECLTYPES
|
||||
can be forced if one configures "native_datetime=True" on create_engine()::
|
||||
|
||||
engine = create_engine('sqlite://',
|
||||
connect_args={'detect_types': sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES},
|
||||
native_datetime=True
|
||||
)
|
||||
|
||||
With this flag enabled, the DATE and TIMESTAMP types (but note - not the
|
||||
DATETIME or TIME types...confused yet ?) will not perform any bind parameter
|
||||
or result 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.
|
||||
|
||||
.. _pysqlite_threading_pooling:
|
||||
|
||||
Threading/Pooling Behavior
|
||||
---------------------------
|
||||
|
||||
Pysqlite's default behavior is to prohibit the usage of a single connection
|
||||
in more than one thread. This is originally intended to work with older
|
||||
versions of SQLite that did not support multithreaded operation under
|
||||
various circumstances. In particular, older SQLite versions
|
||||
did not allow a ``:memory:`` database to be used in multiple threads
|
||||
under any circumstances.
|
||||
|
||||
Pysqlite does include a now-undocumented flag known as
|
||||
``check_same_thread`` which will disable this check, however note that pysqlite
|
||||
connections are still not safe to use in concurrently in multiple threads.
|
||||
In particular, any statement execution calls would need to be externally
|
||||
mutexed, as Pysqlite does not provide for thread-safe propagation of error
|
||||
messages among other things. So while even ``:memory:`` databases can be
|
||||
shared among threads in modern SQLite, Pysqlite doesn't provide enough
|
||||
thread-safety to make this usage worth it.
|
||||
|
||||
SQLAlchemy sets up pooling to work with Pysqlite's default behavior:
|
||||
|
||||
* When a ``:memory:`` SQLite database is specified, the dialect by default
|
||||
will use :class:`.SingletonThreadPool`. This pool maintains a single
|
||||
connection per thread, so that all access to the engine within the current
|
||||
thread use the same ``:memory:`` database - other threads would access a
|
||||
different ``: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.
|
||||
|
||||
.. versionchanged:: 0.7
|
||||
Default selection of :class:`.NullPool` for SQLite file-based databases.
|
||||
Previous versions select :class:`.SingletonThreadPool` by
|
||||
default for all SQLite databases.
|
||||
|
||||
|
||||
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
|
||||
-------
|
||||
|
||||
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 exc, pool
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy import util
|
||||
|
||||
import os
|
||||
|
||||
|
||||
class _SQLite_pysqliteTimeStamp(DATETIME):
|
||||
def bind_processor(self, dialect):
|
||||
if dialect.native_datetime:
|
||||
return None
|
||||
else:
|
||||
return DATETIME.bind_processor(self, dialect)
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
if dialect.native_datetime:
|
||||
return None
|
||||
else:
|
||||
return DATETIME.result_processor(self, dialect, coltype)
|
||||
|
||||
|
||||
class _SQLite_pysqliteDate(DATE):
|
||||
def bind_processor(self, dialect):
|
||||
if dialect.native_datetime:
|
||||
return None
|
||||
else:
|
||||
return DATE.bind_processor(self, dialect)
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
if dialect.native_datetime:
|
||||
return None
|
||||
else:
|
||||
return DATE.result_processor(self, dialect, coltype)
|
||||
|
||||
|
||||
class SQLiteDialect_pysqlite(SQLiteDialect):
|
||||
default_paramstyle = 'qmark'
|
||||
|
||||
colspecs = util.update_copy(
|
||||
SQLiteDialect.colspecs,
|
||||
{
|
||||
sqltypes.Date: _SQLite_pysqliteDate,
|
||||
sqltypes.TIMESTAMP: _SQLite_pysqliteTimeStamp,
|
||||
}
|
||||
)
|
||||
|
||||
# Py3K
|
||||
#description_encoding = None
|
||||
|
||||
driver = 'pysqlite'
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
SQLiteDialect.__init__(self, **kwargs)
|
||||
|
||||
if self.dbapi is not None:
|
||||
sqlite_ver = self.dbapi.version_info
|
||||
if sqlite_ver < (2, 1, 3):
|
||||
util.warn(
|
||||
("The installed version of pysqlite2 (%s) is out-dated "
|
||||
"and will cause errors in some cases. Version 2.1.3 "
|
||||
"or greater is recommended.") %
|
||||
'.'.join([str(subver) for subver in sqlite_ver]))
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
try:
|
||||
from pysqlite2 import dbapi2 as sqlite
|
||||
except ImportError, e:
|
||||
try:
|
||||
from sqlite3 import dbapi2 as sqlite # try 2.5+ stdlib name.
|
||||
except ImportError:
|
||||
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
|
||||
|
||||
def create_connect_args(self, url):
|
||||
if url.username or url.password or url.host or url.port:
|
||||
raise exc.ArgumentError(
|
||||
"Invalid SQLite URL: %s\n"
|
||||
"Valid SQLite URL forms are:\n"
|
||||
" sqlite:///:memory: (or, sqlite://)\n"
|
||||
" 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)
|
||||
util.coerce_kw_type(opts, 'isolation_level', str)
|
||||
util.coerce_kw_type(opts, 'detect_types', int)
|
||||
util.coerce_kw_type(opts, 'check_same_thread', bool)
|
||||
util.coerce_kw_type(opts, 'cached_statements', int)
|
||||
|
||||
return ([filename], opts)
|
||||
|
||||
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
|
||||
@@ -0,0 +1,27 @@
|
||||
# sybase/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.sybase import base, pysybase, pyodbc
|
||||
|
||||
# default dialect
|
||||
base.dialect = pyodbc.dialect
|
||||
|
||||
from base import CHAR, VARCHAR, TIME, NCHAR, NVARCHAR,\
|
||||
TEXT, DATE, DATETIME, FLOAT, NUMERIC,\
|
||||
BIGINT, INT, INTEGER, SMALLINT, BINARY,\
|
||||
VARBINARY, UNITEXT, UNICHAR, UNIVARCHAR,\
|
||||
IMAGE, BIT, MONEY, SMALLMONEY, TINYINT,\
|
||||
dialect
|
||||
|
||||
|
||||
__all__ = (
|
||||
'CHAR', 'VARCHAR', 'TIME', 'NCHAR', 'NVARCHAR',
|
||||
'TEXT', 'DATE', 'DATETIME', 'FLOAT', 'NUMERIC',
|
||||
'BIGINT', 'INT', 'INTEGER', 'SMALLINT', 'BINARY',
|
||||
'VARBINARY', 'UNITEXT', 'UNICHAR', 'UNIVARCHAR',
|
||||
'IMAGE', 'BIT', 'MONEY', 'SMALLMONEY', 'TINYINT',
|
||||
'dialect'
|
||||
)
|
||||
@@ -0,0 +1,816 @@
|
||||
# sybase/base.py
|
||||
# Copyright (C) 2010-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# get_select_precolumns(), limit_clause() implementation
|
||||
# copyright (C) 2007 Fisch Asset Management
|
||||
# AG http://www.fam.ch, with coding by Alexander Houben
|
||||
# alexander.houben@thor-solutions.ch
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
|
||||
.. dialect:: sybase
|
||||
:name: Sybase
|
||||
|
||||
.. note::
|
||||
|
||||
The Sybase dialect functions on current SQLAlchemy versions
|
||||
but is not regularly tested, and may have many issues and
|
||||
caveats not currently handled.
|
||||
|
||||
"""
|
||||
import operator
|
||||
import re
|
||||
|
||||
from sqlalchemy.sql import compiler, expression, text, bindparam
|
||||
from sqlalchemy.engine import default, base, reflection
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.sql import operators as sql_operators
|
||||
from sqlalchemy import schema as sa_schema
|
||||
from sqlalchemy import util, sql, exc
|
||||
|
||||
from sqlalchemy.types import CHAR, VARCHAR, TIME, NCHAR, NVARCHAR,\
|
||||
TEXT, DATE, DATETIME, FLOAT, NUMERIC,\
|
||||
BIGINT, INT, INTEGER, SMALLINT, BINARY,\
|
||||
VARBINARY, DECIMAL, TIMESTAMP, Unicode,\
|
||||
UnicodeText, REAL
|
||||
|
||||
RESERVED_WORDS = set([
|
||||
"add", "all", "alter", "and",
|
||||
"any", "as", "asc", "backup",
|
||||
"begin", "between", "bigint", "binary",
|
||||
"bit", "bottom", "break", "by",
|
||||
"call", "capability", "cascade", "case",
|
||||
"cast", "char", "char_convert", "character",
|
||||
"check", "checkpoint", "close", "comment",
|
||||
"commit", "connect", "constraint", "contains",
|
||||
"continue", "convert", "create", "cross",
|
||||
"cube", "current", "current_timestamp", "current_user",
|
||||
"cursor", "date", "dbspace", "deallocate",
|
||||
"dec", "decimal", "declare", "default",
|
||||
"delete", "deleting", "desc", "distinct",
|
||||
"do", "double", "drop", "dynamic",
|
||||
"else", "elseif", "encrypted", "end",
|
||||
"endif", "escape", "except", "exception",
|
||||
"exec", "execute", "existing", "exists",
|
||||
"externlogin", "fetch", "first", "float",
|
||||
"for", "force", "foreign", "forward",
|
||||
"from", "full", "goto", "grant",
|
||||
"group", "having", "holdlock", "identified",
|
||||
"if", "in", "index", "index_lparen",
|
||||
"inner", "inout", "insensitive", "insert",
|
||||
"inserting", "install", "instead", "int",
|
||||
"integer", "integrated", "intersect", "into",
|
||||
"iq", "is", "isolation", "join",
|
||||
"key", "lateral", "left", "like",
|
||||
"lock", "login", "long", "match",
|
||||
"membership", "message", "mode", "modify",
|
||||
"natural", "new", "no", "noholdlock",
|
||||
"not", "notify", "null", "numeric",
|
||||
"of", "off", "on", "open",
|
||||
"option", "options", "or", "order",
|
||||
"others", "out", "outer", "over",
|
||||
"passthrough", "precision", "prepare", "primary",
|
||||
"print", "privileges", "proc", "procedure",
|
||||
"publication", "raiserror", "readtext", "real",
|
||||
"reference", "references", "release", "remote",
|
||||
"remove", "rename", "reorganize", "resource",
|
||||
"restore", "restrict", "return", "revoke",
|
||||
"right", "rollback", "rollup", "save",
|
||||
"savepoint", "scroll", "select", "sensitive",
|
||||
"session", "set", "setuser", "share",
|
||||
"smallint", "some", "sqlcode", "sqlstate",
|
||||
"start", "stop", "subtrans", "subtransaction",
|
||||
"synchronize", "syntax_error", "table", "temporary",
|
||||
"then", "time", "timestamp", "tinyint",
|
||||
"to", "top", "tran", "trigger",
|
||||
"truncate", "tsequal", "unbounded", "union",
|
||||
"unique", "unknown", "unsigned", "update",
|
||||
"updating", "user", "using", "validate",
|
||||
"values", "varbinary", "varchar", "variable",
|
||||
"varying", "view", "wait", "waitfor",
|
||||
"when", "where", "while", "window",
|
||||
"with", "with_cube", "with_lparen", "with_rollup",
|
||||
"within", "work", "writetext",
|
||||
])
|
||||
|
||||
|
||||
class _SybaseUnitypeMixin(object):
|
||||
"""these types appear to return a buffer object."""
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
def process(value):
|
||||
if value is not None:
|
||||
return str(value) # decode("ucs-2")
|
||||
else:
|
||||
return None
|
||||
return process
|
||||
|
||||
|
||||
class UNICHAR(_SybaseUnitypeMixin, sqltypes.Unicode):
|
||||
__visit_name__ = 'UNICHAR'
|
||||
|
||||
|
||||
class UNIVARCHAR(_SybaseUnitypeMixin, sqltypes.Unicode):
|
||||
__visit_name__ = 'UNIVARCHAR'
|
||||
|
||||
|
||||
class UNITEXT(_SybaseUnitypeMixin, sqltypes.UnicodeText):
|
||||
__visit_name__ = 'UNITEXT'
|
||||
|
||||
|
||||
class TINYINT(sqltypes.Integer):
|
||||
__visit_name__ = 'TINYINT'
|
||||
|
||||
|
||||
class BIT(sqltypes.TypeEngine):
|
||||
__visit_name__ = 'BIT'
|
||||
|
||||
|
||||
class MONEY(sqltypes.TypeEngine):
|
||||
__visit_name__ = "MONEY"
|
||||
|
||||
|
||||
class SMALLMONEY(sqltypes.TypeEngine):
|
||||
__visit_name__ = "SMALLMONEY"
|
||||
|
||||
|
||||
class UNIQUEIDENTIFIER(sqltypes.TypeEngine):
|
||||
__visit_name__ = "UNIQUEIDENTIFIER"
|
||||
|
||||
|
||||
class IMAGE(sqltypes.LargeBinary):
|
||||
__visit_name__ = 'IMAGE'
|
||||
|
||||
|
||||
class SybaseTypeCompiler(compiler.GenericTypeCompiler):
|
||||
def visit_large_binary(self, type_):
|
||||
return self.visit_IMAGE(type_)
|
||||
|
||||
def visit_boolean(self, type_):
|
||||
return self.visit_BIT(type_)
|
||||
|
||||
def visit_unicode(self, type_):
|
||||
return self.visit_NVARCHAR(type_)
|
||||
|
||||
def visit_UNICHAR(self, type_):
|
||||
return "UNICHAR(%d)" % type_.length
|
||||
|
||||
def visit_UNIVARCHAR(self, type_):
|
||||
return "UNIVARCHAR(%d)" % type_.length
|
||||
|
||||
def visit_UNITEXT(self, type_):
|
||||
return "UNITEXT"
|
||||
|
||||
def visit_TINYINT(self, type_):
|
||||
return "TINYINT"
|
||||
|
||||
def visit_IMAGE(self, type_):
|
||||
return "IMAGE"
|
||||
|
||||
def visit_BIT(self, type_):
|
||||
return "BIT"
|
||||
|
||||
def visit_MONEY(self, type_):
|
||||
return "MONEY"
|
||||
|
||||
def visit_SMALLMONEY(self, type_):
|
||||
return "SMALLMONEY"
|
||||
|
||||
def visit_UNIQUEIDENTIFIER(self, type_):
|
||||
return "UNIQUEIDENTIFIER"
|
||||
|
||||
ischema_names = {
|
||||
'bigint': BIGINT,
|
||||
'int': INTEGER,
|
||||
'integer': INTEGER,
|
||||
'smallint': SMALLINT,
|
||||
'tinyint': TINYINT,
|
||||
'unsigned bigint': BIGINT, # TODO: unsigned flags
|
||||
'unsigned int': INTEGER, # TODO: unsigned flags
|
||||
'unsigned smallint': SMALLINT, # TODO: unsigned flags
|
||||
'numeric': NUMERIC,
|
||||
'decimal': DECIMAL,
|
||||
'dec': DECIMAL,
|
||||
'float': FLOAT,
|
||||
'double': NUMERIC, # TODO
|
||||
'double precision': NUMERIC, # TODO
|
||||
'real': REAL,
|
||||
'smallmoney': SMALLMONEY,
|
||||
'money': MONEY,
|
||||
'smalldatetime': DATETIME,
|
||||
'datetime': DATETIME,
|
||||
'date': DATE,
|
||||
'time': TIME,
|
||||
'char': CHAR,
|
||||
'character': CHAR,
|
||||
'varchar': VARCHAR,
|
||||
'character varying': VARCHAR,
|
||||
'char varying': VARCHAR,
|
||||
'unichar': UNICHAR,
|
||||
'unicode character': UNIVARCHAR,
|
||||
'nchar': NCHAR,
|
||||
'national char': NCHAR,
|
||||
'national character': NCHAR,
|
||||
'nvarchar': NVARCHAR,
|
||||
'nchar varying': NVARCHAR,
|
||||
'national char varying': NVARCHAR,
|
||||
'national character varying': NVARCHAR,
|
||||
'text': TEXT,
|
||||
'unitext': UNITEXT,
|
||||
'binary': BINARY,
|
||||
'varbinary': VARBINARY,
|
||||
'image': IMAGE,
|
||||
'bit': BIT,
|
||||
|
||||
# not in documentation for ASE 15.7
|
||||
'long varchar': TEXT, # TODO
|
||||
'timestamp': TIMESTAMP,
|
||||
'uniqueidentifier': UNIQUEIDENTIFIER,
|
||||
|
||||
}
|
||||
|
||||
|
||||
class SybaseInspector(reflection.Inspector):
|
||||
|
||||
def __init__(self, conn):
|
||||
reflection.Inspector.__init__(self, conn)
|
||||
|
||||
def get_table_id(self, table_name, schema=None):
|
||||
"""Return the table id from `table_name` and `schema`."""
|
||||
|
||||
return self.dialect.get_table_id(self.bind, table_name, schema,
|
||||
info_cache=self.info_cache)
|
||||
|
||||
|
||||
class SybaseExecutionContext(default.DefaultExecutionContext):
|
||||
_enable_identity_insert = False
|
||||
|
||||
def set_ddl_autocommit(self, connection, value):
|
||||
"""Must be implemented by subclasses to accommodate DDL executions.
|
||||
|
||||
"connection" is the raw unwrapped DBAPI connection. "value"
|
||||
is True or False. when True, the connection should be configured
|
||||
such that a DDL can take place subsequently. when False,
|
||||
a DDL has taken place and the connection should be resumed
|
||||
into non-autocommit mode.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def pre_exec(self):
|
||||
if self.isinsert:
|
||||
tbl = self.compiled.statement.table
|
||||
seq_column = tbl._autoincrement_column
|
||||
insert_has_sequence = seq_column is not None
|
||||
|
||||
if insert_has_sequence:
|
||||
self._enable_identity_insert = \
|
||||
seq_column.key in self.compiled_parameters[0]
|
||||
else:
|
||||
self._enable_identity_insert = False
|
||||
|
||||
if self._enable_identity_insert:
|
||||
self.cursor.execute("SET IDENTITY_INSERT %s ON" %
|
||||
self.dialect.identifier_preparer.format_table(tbl))
|
||||
|
||||
if self.isddl:
|
||||
# TODO: to enhance this, we can detect "ddl in tran" on the
|
||||
# database settings. this error message should be improved to
|
||||
# include a note about that.
|
||||
if not self.should_autocommit:
|
||||
raise exc.InvalidRequestError(
|
||||
"The Sybase dialect only supports "
|
||||
"DDL in 'autocommit' mode at this time.")
|
||||
|
||||
self.root_connection.engine.logger.info(
|
||||
"AUTOCOMMIT (Assuming no Sybase 'ddl in tran')")
|
||||
|
||||
self.set_ddl_autocommit(
|
||||
self.root_connection.connection.connection,
|
||||
True)
|
||||
|
||||
def post_exec(self):
|
||||
if self.isddl:
|
||||
self.set_ddl_autocommit(self.root_connection, False)
|
||||
|
||||
if self._enable_identity_insert:
|
||||
self.cursor.execute(
|
||||
"SET IDENTITY_INSERT %s OFF" %
|
||||
self.dialect.identifier_preparer.
|
||||
format_table(self.compiled.statement.table)
|
||||
)
|
||||
|
||||
def get_lastrowid(self):
|
||||
cursor = self.create_cursor()
|
||||
cursor.execute("SELECT @@identity AS lastrowid")
|
||||
lastrowid = cursor.fetchone()[0]
|
||||
cursor.close()
|
||||
return lastrowid
|
||||
|
||||
|
||||
class SybaseSQLCompiler(compiler.SQLCompiler):
|
||||
ansi_bind_rules = True
|
||||
|
||||
extract_map = util.update_copy(
|
||||
compiler.SQLCompiler.extract_map,
|
||||
{
|
||||
'doy': 'dayofyear',
|
||||
'dow': 'weekday',
|
||||
'milliseconds': 'millisecond'
|
||||
})
|
||||
|
||||
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 "
|
||||
#else:
|
||||
#s += "TOP %s " % (select._limit,)
|
||||
s += "TOP %s " % (select._limit,)
|
||||
if select._offset:
|
||||
if not select._limit:
|
||||
# FIXME: sybase doesn't allow an offset without a limit
|
||||
# so use a huge value for TOP here
|
||||
s += "TOP 1000000 "
|
||||
s += "START AT %s " % (select._offset + 1,)
|
||||
return s
|
||||
|
||||
def get_from_hint_text(self, table, text):
|
||||
return text
|
||||
|
||||
def limit_clause(self, select):
|
||||
# Limit in sybase is after the select keyword
|
||||
return ""
|
||||
|
||||
def visit_extract(self, extract, **kw):
|
||||
field = self.extract_map.get(extract.field, extract.field)
|
||||
return 'DATEPART("%s", %s)' % (
|
||||
field, self.process(extract.expr, **kw))
|
||||
|
||||
def visit_now_func(self, fn, **kw):
|
||||
return "GETDATE()"
|
||||
|
||||
def for_update_clause(self, select):
|
||||
# "FOR UPDATE" is only allowed on "DECLARE CURSOR"
|
||||
# which SQLAlchemy doesn't use
|
||||
return ''
|
||||
|
||||
def order_by_clause(self, select, **kw):
|
||||
kw['literal_binds'] = True
|
||||
order_by = self.process(select._order_by_clause, **kw)
|
||||
|
||||
# SybaseSQL only allows ORDER BY in subqueries if there is a LIMIT
|
||||
if order_by and (not self.is_subquery() or select._limit):
|
||||
return " ORDER BY " + order_by
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
class SybaseDDLCompiler(compiler.DDLCompiler):
|
||||
def get_column_specification(self, column, **kwargs):
|
||||
colspec = self.preparer.format_column(column) + " " + \
|
||||
self.dialect.type_compiler.process(column.type)
|
||||
|
||||
if column.table is None:
|
||||
raise exc.CompileError(
|
||||
"The Sybase dialect requires Table-bound "
|
||||
"columns in order to generate DDL")
|
||||
seq_col = column.table._autoincrement_column
|
||||
|
||||
# install a IDENTITY Sequence if we have an implicit IDENTITY column
|
||||
if seq_col is column:
|
||||
sequence = isinstance(column.default, sa_schema.Sequence) \
|
||||
and column.default
|
||||
if sequence:
|
||||
start, increment = sequence.start or 1, \
|
||||
sequence.increment or 1
|
||||
else:
|
||||
start, increment = 1, 1
|
||||
if (start, increment) == (1, 1):
|
||||
colspec += " IDENTITY"
|
||||
else:
|
||||
# TODO: need correct syntax for this
|
||||
colspec += " IDENTITY(%s,%s)" % (start, increment)
|
||||
else:
|
||||
default = self.get_column_default_string(column)
|
||||
if default is not None:
|
||||
colspec += " DEFAULT " + default
|
||||
|
||||
if column.nullable is not None:
|
||||
if not column.nullable or column.primary_key:
|
||||
colspec += " NOT NULL"
|
||||
else:
|
||||
colspec += " NULL"
|
||||
|
||||
return colspec
|
||||
|
||||
def visit_drop_index(self, drop):
|
||||
index = drop.element
|
||||
return "\nDROP INDEX %s.%s" % (
|
||||
self.preparer.quote_identifier(index.table.name),
|
||||
self._prepared_index_name(drop.element,
|
||||
include_schema=False)
|
||||
)
|
||||
|
||||
|
||||
class SybaseIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
reserved_words = RESERVED_WORDS
|
||||
|
||||
|
||||
class SybaseDialect(default.DefaultDialect):
|
||||
name = 'sybase'
|
||||
supports_unicode_statements = False
|
||||
supports_sane_rowcount = False
|
||||
supports_sane_multi_rowcount = False
|
||||
|
||||
supports_native_boolean = False
|
||||
supports_unicode_binds = False
|
||||
postfetch_lastrowid = True
|
||||
|
||||
colspecs = {}
|
||||
ischema_names = ischema_names
|
||||
|
||||
type_compiler = SybaseTypeCompiler
|
||||
statement_compiler = SybaseSQLCompiler
|
||||
ddl_compiler = SybaseDDLCompiler
|
||||
preparer = SybaseIdentifierPreparer
|
||||
inspector = SybaseInspector
|
||||
|
||||
def _get_default_schema_name(self, connection):
|
||||
return connection.scalar(
|
||||
text("SELECT user_name() as user_name",
|
||||
typemap={'user_name': Unicode})
|
||||
)
|
||||
|
||||
def initialize(self, connection):
|
||||
super(SybaseDialect, self).initialize(connection)
|
||||
if self.server_version_info is not None and\
|
||||
self.server_version_info < (15, ):
|
||||
self.max_identifier_length = 30
|
||||
else:
|
||||
self.max_identifier_length = 255
|
||||
|
||||
def get_table_id(self, connection, table_name, schema=None, **kw):
|
||||
"""Fetch the id for schema.table_name.
|
||||
|
||||
Several reflection methods require the table id. The idea for using
|
||||
this method is that it can be fetched one time and cached for
|
||||
subsequent calls.
|
||||
|
||||
"""
|
||||
|
||||
table_id = None
|
||||
if schema is None:
|
||||
schema = self.default_schema_name
|
||||
|
||||
TABLEID_SQL = text("""
|
||||
SELECT o.id AS id
|
||||
FROM sysobjects o JOIN sysusers u ON o.uid=u.uid
|
||||
WHERE u.name = :schema_name
|
||||
AND o.name = :table_name
|
||||
AND o.type in ('U', 'V')
|
||||
""")
|
||||
|
||||
# Py2K
|
||||
if isinstance(schema, unicode):
|
||||
schema = schema.encode("ascii")
|
||||
if isinstance(table_name, unicode):
|
||||
table_name = table_name.encode("ascii")
|
||||
# end Py2K
|
||||
result = connection.execute(TABLEID_SQL,
|
||||
schema_name=schema,
|
||||
table_name=table_name)
|
||||
table_id = result.scalar()
|
||||
if table_id is None:
|
||||
raise exc.NoSuchTableError(table_name)
|
||||
return table_id
|
||||
|
||||
@reflection.cache
|
||||
def get_columns(self, connection, table_name, schema=None, **kw):
|
||||
table_id = self.get_table_id(connection, table_name, schema,
|
||||
info_cache=kw.get("info_cache"))
|
||||
|
||||
COLUMN_SQL = text("""
|
||||
SELECT col.name AS name,
|
||||
t.name AS type,
|
||||
(col.status & 8) AS nullable,
|
||||
(col.status & 128) AS autoincrement,
|
||||
com.text AS 'default',
|
||||
col.prec AS precision,
|
||||
col.scale AS scale,
|
||||
col.length AS length
|
||||
FROM systypes t, syscolumns col LEFT OUTER JOIN syscomments com ON
|
||||
col.cdefault = com.id
|
||||
WHERE col.usertype = t.usertype
|
||||
AND col.id = :table_id
|
||||
ORDER BY col.colid
|
||||
""")
|
||||
|
||||
results = connection.execute(COLUMN_SQL, table_id=table_id)
|
||||
|
||||
columns = []
|
||||
for (name, type_, nullable, autoincrement, default, precision, scale,
|
||||
length) in results:
|
||||
col_info = self._get_column_info(name, type_, bool(nullable),
|
||||
bool(autoincrement), default, precision, scale,
|
||||
length)
|
||||
columns.append(col_info)
|
||||
|
||||
return columns
|
||||
|
||||
def _get_column_info(self, name, type_, nullable, autoincrement, default,
|
||||
precision, scale, length):
|
||||
|
||||
coltype = self.ischema_names.get(type_, None)
|
||||
|
||||
kwargs = {}
|
||||
|
||||
if coltype in (NUMERIC, DECIMAL):
|
||||
args = (precision, scale)
|
||||
elif coltype == FLOAT:
|
||||
args = (precision,)
|
||||
elif coltype in (CHAR, VARCHAR, UNICHAR, UNIVARCHAR, NCHAR, NVARCHAR):
|
||||
args = (length,)
|
||||
else:
|
||||
args = ()
|
||||
|
||||
if coltype:
|
||||
coltype = coltype(*args, **kwargs)
|
||||
#is this necessary
|
||||
#if is_array:
|
||||
# coltype = ARRAY(coltype)
|
||||
else:
|
||||
util.warn("Did not recognize type '%s' of column '%s'" %
|
||||
(type_, name))
|
||||
coltype = sqltypes.NULLTYPE
|
||||
|
||||
if default:
|
||||
default = re.sub("DEFAULT", "", default).strip()
|
||||
default = re.sub("^'(.*)'$", lambda m: m.group(1), default)
|
||||
else:
|
||||
default = None
|
||||
|
||||
column_info = dict(name=name, type=coltype, nullable=nullable,
|
||||
default=default, autoincrement=autoincrement)
|
||||
return column_info
|
||||
|
||||
@reflection.cache
|
||||
def get_foreign_keys(self, connection, table_name, schema=None, **kw):
|
||||
|
||||
table_id = self.get_table_id(connection, table_name, schema,
|
||||
info_cache=kw.get("info_cache"))
|
||||
|
||||
table_cache = {}
|
||||
column_cache = {}
|
||||
foreign_keys = []
|
||||
|
||||
table_cache[table_id] = {"name": table_name, "schema": schema}
|
||||
|
||||
COLUMN_SQL = text("""
|
||||
SELECT c.colid AS id, c.name AS name
|
||||
FROM syscolumns c
|
||||
WHERE c.id = :table_id
|
||||
""")
|
||||
|
||||
results = connection.execute(COLUMN_SQL, table_id=table_id)
|
||||
columns = {}
|
||||
for col in results:
|
||||
columns[col["id"]] = col["name"]
|
||||
column_cache[table_id] = columns
|
||||
|
||||
REFCONSTRAINT_SQL = text("""
|
||||
SELECT o.name AS name, r.reftabid AS reftable_id,
|
||||
r.keycnt AS 'count',
|
||||
r.fokey1 AS fokey1, r.fokey2 AS fokey2, r.fokey3 AS fokey3,
|
||||
r.fokey4 AS fokey4, r.fokey5 AS fokey5, r.fokey6 AS fokey6,
|
||||
r.fokey7 AS fokey7, r.fokey1 AS fokey8, r.fokey9 AS fokey9,
|
||||
r.fokey10 AS fokey10, r.fokey11 AS fokey11, r.fokey12 AS fokey12,
|
||||
r.fokey13 AS fokey13, r.fokey14 AS fokey14, r.fokey15 AS fokey15,
|
||||
r.fokey16 AS fokey16,
|
||||
r.refkey1 AS refkey1, r.refkey2 AS refkey2, r.refkey3 AS refkey3,
|
||||
r.refkey4 AS refkey4, r.refkey5 AS refkey5, r.refkey6 AS refkey6,
|
||||
r.refkey7 AS refkey7, r.refkey1 AS refkey8, r.refkey9 AS refkey9,
|
||||
r.refkey10 AS refkey10, r.refkey11 AS refkey11,
|
||||
r.refkey12 AS refkey12, r.refkey13 AS refkey13,
|
||||
r.refkey14 AS refkey14, r.refkey15 AS refkey15,
|
||||
r.refkey16 AS refkey16
|
||||
FROM sysreferences r JOIN sysobjects o on r.tableid = o.id
|
||||
WHERE r.tableid = :table_id
|
||||
""")
|
||||
referential_constraints = connection.execute(REFCONSTRAINT_SQL,
|
||||
table_id=table_id)
|
||||
|
||||
REFTABLE_SQL = text("""
|
||||
SELECT o.name AS name, u.name AS 'schema'
|
||||
FROM sysobjects o JOIN sysusers u ON o.uid = u.uid
|
||||
WHERE o.id = :table_id
|
||||
""")
|
||||
|
||||
for r in referential_constraints:
|
||||
reftable_id = r["reftable_id"]
|
||||
|
||||
if reftable_id not in table_cache:
|
||||
c = connection.execute(REFTABLE_SQL, table_id=reftable_id)
|
||||
reftable = c.fetchone()
|
||||
c.close()
|
||||
table_info = {"name": reftable["name"], "schema": None}
|
||||
if (schema is not None or
|
||||
reftable["schema"] != self.default_schema_name):
|
||||
table_info["schema"] = reftable["schema"]
|
||||
|
||||
table_cache[reftable_id] = table_info
|
||||
results = connection.execute(COLUMN_SQL, table_id=reftable_id)
|
||||
reftable_columns = {}
|
||||
for col in results:
|
||||
reftable_columns[col["id"]] = col["name"]
|
||||
column_cache[reftable_id] = reftable_columns
|
||||
|
||||
reftable = table_cache[reftable_id]
|
||||
reftable_columns = column_cache[reftable_id]
|
||||
|
||||
constrained_columns = []
|
||||
referred_columns = []
|
||||
for i in range(1, r["count"] + 1):
|
||||
constrained_columns.append(columns[r["fokey%i" % i]])
|
||||
referred_columns.append(reftable_columns[r["refkey%i" % i]])
|
||||
|
||||
fk_info = {
|
||||
"constrained_columns": constrained_columns,
|
||||
"referred_schema": reftable["schema"],
|
||||
"referred_table": reftable["name"],
|
||||
"referred_columns": referred_columns,
|
||||
"name": r["name"]
|
||||
}
|
||||
|
||||
foreign_keys.append(fk_info)
|
||||
|
||||
return foreign_keys
|
||||
|
||||
@reflection.cache
|
||||
def get_indexes(self, connection, table_name, schema=None, **kw):
|
||||
table_id = self.get_table_id(connection, table_name, schema,
|
||||
info_cache=kw.get("info_cache"))
|
||||
|
||||
INDEX_SQL = text("""
|
||||
SELECT object_name(i.id) AS table_name,
|
||||
i.keycnt AS 'count',
|
||||
i.name AS name,
|
||||
(i.status & 0x2) AS 'unique',
|
||||
index_col(object_name(i.id), i.indid, 1) AS col_1,
|
||||
index_col(object_name(i.id), i.indid, 2) AS col_2,
|
||||
index_col(object_name(i.id), i.indid, 3) AS col_3,
|
||||
index_col(object_name(i.id), i.indid, 4) AS col_4,
|
||||
index_col(object_name(i.id), i.indid, 5) AS col_5,
|
||||
index_col(object_name(i.id), i.indid, 6) AS col_6,
|
||||
index_col(object_name(i.id), i.indid, 7) AS col_7,
|
||||
index_col(object_name(i.id), i.indid, 8) AS col_8,
|
||||
index_col(object_name(i.id), i.indid, 9) AS col_9,
|
||||
index_col(object_name(i.id), i.indid, 10) AS col_10,
|
||||
index_col(object_name(i.id), i.indid, 11) AS col_11,
|
||||
index_col(object_name(i.id), i.indid, 12) AS col_12,
|
||||
index_col(object_name(i.id), i.indid, 13) AS col_13,
|
||||
index_col(object_name(i.id), i.indid, 14) AS col_14,
|
||||
index_col(object_name(i.id), i.indid, 15) AS col_15,
|
||||
index_col(object_name(i.id), i.indid, 16) AS col_16
|
||||
FROM sysindexes i, sysobjects o
|
||||
WHERE o.id = i.id
|
||||
AND o.id = :table_id
|
||||
AND (i.status & 2048) = 0
|
||||
AND i.indid BETWEEN 1 AND 254
|
||||
""")
|
||||
|
||||
results = connection.execute(INDEX_SQL, table_id=table_id)
|
||||
indexes = []
|
||||
for r in results:
|
||||
column_names = []
|
||||
for i in range(1, r["count"]):
|
||||
column_names.append(r["col_%i" % (i,)])
|
||||
index_info = {"name": r["name"],
|
||||
"unique": bool(r["unique"]),
|
||||
"column_names": column_names}
|
||||
indexes.append(index_info)
|
||||
|
||||
return indexes
|
||||
|
||||
@reflection.cache
|
||||
def get_pk_constraint(self, connection, table_name, schema=None, **kw):
|
||||
table_id = self.get_table_id(connection, table_name, schema,
|
||||
info_cache=kw.get("info_cache"))
|
||||
|
||||
PK_SQL = text("""
|
||||
SELECT object_name(i.id) AS table_name,
|
||||
i.keycnt AS 'count',
|
||||
i.name AS name,
|
||||
index_col(object_name(i.id), i.indid, 1) AS pk_1,
|
||||
index_col(object_name(i.id), i.indid, 2) AS pk_2,
|
||||
index_col(object_name(i.id), i.indid, 3) AS pk_3,
|
||||
index_col(object_name(i.id), i.indid, 4) AS pk_4,
|
||||
index_col(object_name(i.id), i.indid, 5) AS pk_5,
|
||||
index_col(object_name(i.id), i.indid, 6) AS pk_6,
|
||||
index_col(object_name(i.id), i.indid, 7) AS pk_7,
|
||||
index_col(object_name(i.id), i.indid, 8) AS pk_8,
|
||||
index_col(object_name(i.id), i.indid, 9) AS pk_9,
|
||||
index_col(object_name(i.id), i.indid, 10) AS pk_10,
|
||||
index_col(object_name(i.id), i.indid, 11) AS pk_11,
|
||||
index_col(object_name(i.id), i.indid, 12) AS pk_12,
|
||||
index_col(object_name(i.id), i.indid, 13) AS pk_13,
|
||||
index_col(object_name(i.id), i.indid, 14) AS pk_14,
|
||||
index_col(object_name(i.id), i.indid, 15) AS pk_15,
|
||||
index_col(object_name(i.id), i.indid, 16) AS pk_16
|
||||
FROM sysindexes i, sysobjects o
|
||||
WHERE o.id = i.id
|
||||
AND o.id = :table_id
|
||||
AND (i.status & 2048) = 2048
|
||||
AND i.indid BETWEEN 1 AND 254
|
||||
""")
|
||||
|
||||
results = connection.execute(PK_SQL, table_id=table_id)
|
||||
pks = results.fetchone()
|
||||
results.close()
|
||||
|
||||
constrained_columns = []
|
||||
for i in range(1, pks["count"] + 1):
|
||||
constrained_columns.append(pks["pk_%i" % (i,)])
|
||||
return {"constrained_columns": constrained_columns,
|
||||
"name": pks["name"]}
|
||||
|
||||
@reflection.cache
|
||||
def get_schema_names(self, connection, **kw):
|
||||
|
||||
SCHEMA_SQL = text("SELECT u.name AS name FROM sysusers u")
|
||||
|
||||
schemas = connection.execute(SCHEMA_SQL)
|
||||
|
||||
return [s["name"] for s in schemas]
|
||||
|
||||
@reflection.cache
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
if schema is None:
|
||||
schema = self.default_schema_name
|
||||
|
||||
TABLE_SQL = text("""
|
||||
SELECT o.name AS name
|
||||
FROM sysobjects o JOIN sysusers u ON o.uid = u.uid
|
||||
WHERE u.name = :schema_name
|
||||
AND o.type = 'U'
|
||||
""")
|
||||
|
||||
# Py2K
|
||||
if isinstance(schema, unicode):
|
||||
schema = schema.encode("ascii")
|
||||
# end Py2K
|
||||
tables = connection.execute(TABLE_SQL, schema_name=schema)
|
||||
|
||||
return [t["name"] for t in tables]
|
||||
|
||||
@reflection.cache
|
||||
def get_view_definition(self, connection, view_name, schema=None, **kw):
|
||||
if schema is None:
|
||||
schema = self.default_schema_name
|
||||
|
||||
VIEW_DEF_SQL = text("""
|
||||
SELECT c.text
|
||||
FROM syscomments c JOIN sysobjects o ON c.id = o.id
|
||||
WHERE o.name = :view_name
|
||||
AND o.type = 'V'
|
||||
""")
|
||||
|
||||
# Py2K
|
||||
if isinstance(view_name, unicode):
|
||||
view_name = view_name.encode("ascii")
|
||||
# end Py2K
|
||||
view = connection.execute(VIEW_DEF_SQL, view_name=view_name)
|
||||
|
||||
return view.scalar()
|
||||
|
||||
@reflection.cache
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
if schema is None:
|
||||
schema = self.default_schema_name
|
||||
|
||||
VIEW_SQL = text("""
|
||||
SELECT o.name AS name
|
||||
FROM sysobjects o JOIN sysusers u ON o.uid = u.uid
|
||||
WHERE u.name = :schema_name
|
||||
AND o.type = 'V'
|
||||
""")
|
||||
|
||||
# Py2K
|
||||
if isinstance(schema, unicode):
|
||||
schema = schema.encode("ascii")
|
||||
# end Py2K
|
||||
views = connection.execute(VIEW_SQL, schema_name=schema)
|
||||
|
||||
return [v["name"] for v in views]
|
||||
|
||||
def has_table(self, connection, table_name, schema=None):
|
||||
try:
|
||||
self.get_table_id(connection, table_name, schema)
|
||||
except exc.NoSuchTableError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
@@ -0,0 +1,32 @@
|
||||
# sybase/mxodbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
"""
|
||||
|
||||
.. dialect:: sybase+mxodbc
|
||||
:name: mxODBC
|
||||
:dbapi: mxodbc
|
||||
:connectstring: sybase+mxodbc://<username>:<password>@<dsnname>
|
||||
:url: http://www.egenix.com/
|
||||
|
||||
.. note::
|
||||
|
||||
This dialect is a stub only and is likely non functional at this time.
|
||||
|
||||
|
||||
"""
|
||||
from sqlalchemy.dialects.sybase.base import SybaseDialect
|
||||
from sqlalchemy.dialects.sybase.base import SybaseExecutionContext
|
||||
from sqlalchemy.connectors.mxodbc import MxODBCConnector
|
||||
|
||||
|
||||
class SybaseExecutionContext_mxodbc(SybaseExecutionContext):
|
||||
pass
|
||||
|
||||
|
||||
class SybaseDialect_mxodbc(MxODBCConnector, SybaseDialect):
|
||||
execution_ctx_cls = SybaseExecutionContext_mxodbc
|
||||
|
||||
dialect = SybaseDialect_mxodbc
|
||||
@@ -0,0 +1,84 @@
|
||||
# sybase/pyodbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: sybase+pyodbc
|
||||
:name: PyODBC
|
||||
:dbapi: pyodbc
|
||||
:connectstring: sybase+pyodbc://<username>:<password>@<dsnname>[/<database>]
|
||||
:url: http://pypi.python.org/pypi/pyodbc/
|
||||
|
||||
|
||||
Unicode Support
|
||||
---------------
|
||||
|
||||
The pyodbc driver currently supports usage of these Sybase types with
|
||||
Unicode or multibyte strings::
|
||||
|
||||
CHAR
|
||||
NCHAR
|
||||
NVARCHAR
|
||||
TEXT
|
||||
VARCHAR
|
||||
|
||||
Currently *not* supported are::
|
||||
|
||||
UNICHAR
|
||||
UNITEXT
|
||||
UNIVARCHAR
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.sybase.base import SybaseDialect,\
|
||||
SybaseExecutionContext
|
||||
from sqlalchemy.connectors.pyodbc import PyODBCConnector
|
||||
from sqlalchemy import types as sqltypes, processors
|
||||
import decimal
|
||||
|
||||
|
||||
class _SybNumeric_pyodbc(sqltypes.Numeric):
|
||||
"""Turns Decimals with adjusted() < -6 into floats.
|
||||
|
||||
It's not yet known how to get decimals with many
|
||||
significant digits or very large adjusted() into Sybase
|
||||
via pyodbc.
|
||||
|
||||
"""
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
super_process = super(_SybNumeric_pyodbc, self).\
|
||||
bind_processor(dialect)
|
||||
|
||||
def process(value):
|
||||
if self.asdecimal and \
|
||||
isinstance(value, decimal.Decimal):
|
||||
|
||||
if value.adjusted() < -6:
|
||||
return processors.to_float(value)
|
||||
|
||||
if super_process:
|
||||
return super_process(value)
|
||||
else:
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
class SybaseExecutionContext_pyodbc(SybaseExecutionContext):
|
||||
def set_ddl_autocommit(self, connection, value):
|
||||
if value:
|
||||
connection.autocommit = True
|
||||
else:
|
||||
connection.autocommit = False
|
||||
|
||||
|
||||
class SybaseDialect_pyodbc(PyODBCConnector, SybaseDialect):
|
||||
execution_ctx_cls = SybaseExecutionContext_pyodbc
|
||||
|
||||
colspecs = {
|
||||
sqltypes.Numeric: _SybNumeric_pyodbc,
|
||||
}
|
||||
|
||||
dialect = SybaseDialect_pyodbc
|
||||
@@ -0,0 +1,100 @@
|
||||
# sybase/pysybase.py
|
||||
# Copyright (C) 2010-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: sybase+pysybase
|
||||
:name: Python-Sybase
|
||||
:dbapi: Sybase
|
||||
:connectstring: sybase+pysybase://<username>:<password>@<dsn>/[database name]
|
||||
:url: http://python-sybase.sourceforge.net/
|
||||
|
||||
Unicode Support
|
||||
---------------
|
||||
|
||||
The python-sybase driver does not appear to support non-ASCII strings of any
|
||||
kind at this time.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy import types as sqltypes, processors
|
||||
from sqlalchemy.dialects.sybase.base import SybaseDialect, \
|
||||
SybaseExecutionContext, SybaseSQLCompiler
|
||||
|
||||
|
||||
class _SybNumeric(sqltypes.Numeric):
|
||||
def result_processor(self, dialect, type_):
|
||||
if not self.asdecimal:
|
||||
return processors.to_float
|
||||
else:
|
||||
return sqltypes.Numeric.result_processor(self, dialect, type_)
|
||||
|
||||
|
||||
class SybaseExecutionContext_pysybase(SybaseExecutionContext):
|
||||
|
||||
def set_ddl_autocommit(self, dbapi_connection, value):
|
||||
if value:
|
||||
# call commit() on the Sybase connection directly,
|
||||
# to avoid any side effects of calling a Connection
|
||||
# transactional method inside of pre_exec()
|
||||
dbapi_connection.commit()
|
||||
|
||||
def pre_exec(self):
|
||||
SybaseExecutionContext.pre_exec(self)
|
||||
|
||||
for param in self.parameters:
|
||||
for key in list(param):
|
||||
param["@" + key] = param[key]
|
||||
del param[key]
|
||||
|
||||
|
||||
class SybaseSQLCompiler_pysybase(SybaseSQLCompiler):
|
||||
def bindparam_string(self, name, **kw):
|
||||
return "@" + name
|
||||
|
||||
|
||||
class SybaseDialect_pysybase(SybaseDialect):
|
||||
driver = 'pysybase'
|
||||
execution_ctx_cls = SybaseExecutionContext_pysybase
|
||||
statement_compiler = SybaseSQLCompiler_pysybase
|
||||
|
||||
colspecs = {
|
||||
sqltypes.Numeric: _SybNumeric,
|
||||
sqltypes.Float: sqltypes.Float
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
import Sybase
|
||||
return Sybase
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user', password='passwd')
|
||||
|
||||
return ([opts.pop('host')], opts)
|
||||
|
||||
def do_executemany(self, cursor, statement, parameters, context=None):
|
||||
# calling python-sybase executemany yields:
|
||||
# TypeError: string too long for buffer
|
||||
for param in parameters:
|
||||
cursor.execute(statement, param)
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
vers = connection.scalar("select @@version_number")
|
||||
# i.e. 15500, 15000, 12500 == (15, 5, 0, 0), (15, 0, 0, 0),
|
||||
# (12, 5, 0, 0)
|
||||
return (vers / 1000, vers % 1000 / 100, vers % 100 / 10, vers % 10)
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, (self.dbapi.OperationalError,
|
||||
self.dbapi.ProgrammingError)):
|
||||
msg = str(e)
|
||||
return ('Unable to complete network request to host' in msg or
|
||||
'Invalid connection state' in msg or
|
||||
'Invalid cursor state' in msg)
|
||||
else:
|
||||
return False
|
||||
|
||||
dialect = SybaseDialect_pysybase
|
||||
@@ -0,0 +1,357 @@
|
||||
# engine/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""SQL connections, SQL execution and high-level DB-API interface.
|
||||
|
||||
The engine package defines the basic components used to interface
|
||||
DB-API modules with higher-level statement construction,
|
||||
connection-management, execution and result contexts. The primary
|
||||
"entry point" class into this package is the Engine and it's public
|
||||
constructor ``create_engine()``.
|
||||
|
||||
This package includes:
|
||||
|
||||
base.py
|
||||
Defines interface classes and some implementation classes which
|
||||
comprise the basic components used to interface between a DB-API,
|
||||
constructed and plain-text statements, connections, transactions,
|
||||
and results.
|
||||
|
||||
default.py
|
||||
Contains default implementations of some of the components defined
|
||||
in base.py. All current database dialects use the classes in
|
||||
default.py as base classes for their own database-specific
|
||||
implementations.
|
||||
|
||||
strategies.py
|
||||
The mechanics of constructing ``Engine`` objects are represented
|
||||
here. Defines the ``EngineStrategy`` class which represents how
|
||||
to go from arguments specified to the ``create_engine()``
|
||||
function, to a fully constructed ``Engine``, including
|
||||
initialization of connection pooling, dialects, and specific
|
||||
subclasses of ``Engine``.
|
||||
|
||||
threadlocal.py
|
||||
The ``TLEngine`` class is defined here, which is a subclass of
|
||||
the generic ``Engine`` and tracks ``Connection`` and
|
||||
``Transaction`` objects against the identity of the current
|
||||
thread. This allows certain programming patterns based around
|
||||
the concept of a "thread-local connection" to be possible.
|
||||
The ``TLEngine`` is created by using the "threadlocal" engine
|
||||
strategy in conjunction with the ``create_engine()`` function.
|
||||
|
||||
url.py
|
||||
Defines the ``URL`` class which represents the individual
|
||||
components of a string URL passed to ``create_engine()``. Also
|
||||
defines a basic module-loading strategy for the dialect specifier
|
||||
within a URL.
|
||||
"""
|
||||
|
||||
# not sure what this was used for
|
||||
#import sqlalchemy.databases
|
||||
|
||||
from .interfaces import (
|
||||
Compiled,
|
||||
Connectable,
|
||||
Dialect,
|
||||
ExecutionContext,
|
||||
TypeCompiler
|
||||
)
|
||||
|
||||
from .base import (
|
||||
Connection,
|
||||
Engine,
|
||||
NestedTransaction,
|
||||
RootTransaction,
|
||||
Transaction,
|
||||
TwoPhaseTransaction,
|
||||
)
|
||||
|
||||
from .result import (
|
||||
BufferedColumnResultProxy,
|
||||
BufferedColumnRow,
|
||||
BufferedRowResultProxy,
|
||||
FullyBufferedResultProxy,
|
||||
ResultProxy,
|
||||
RowProxy,
|
||||
)
|
||||
|
||||
from .util import (
|
||||
connection_memoize
|
||||
)
|
||||
|
||||
from . import util, strategies
|
||||
|
||||
default_strategy = 'plain'
|
||||
|
||||
|
||||
def create_engine(*args, **kwargs):
|
||||
"""Create a new :class:`.Engine` instance.
|
||||
|
||||
The standard calling form is to send the URL as the
|
||||
first positional argument, usually a string
|
||||
that indicates database dialect and connection arguments.
|
||||
Additional keyword arguments may then follow it which
|
||||
establish various options on the resulting :class:`.Engine`
|
||||
and its underlying :class:`.Dialect` and :class:`.Pool`
|
||||
constructs.
|
||||
|
||||
The string form of the URL is
|
||||
``dialect+driver://user:password@host/dbname[?key=value..]``, where
|
||||
``dialect`` is a database name such as ``mysql``, ``oracle``,
|
||||
``postgresql``, etc., and ``driver`` the name of a DBAPI, such as
|
||||
``psycopg2``, ``pyodbc``, ``cx_oracle``, etc. Alternatively,
|
||||
the URL can be an instance of :class:`~sqlalchemy.engine.url.URL`.
|
||||
|
||||
``**kwargs`` takes a wide variety of options which are routed
|
||||
towards their appropriate components. Arguments may be specific
|
||||
to the :class:`.Engine`, the underlying :class:`.Dialect`, as well as
|
||||
the :class:`.Pool`. Specific dialects also accept keyword
|
||||
arguments that are unique to that dialect. Here, we describe the
|
||||
parameters that are common to most :func:`.create_engine()` usage.
|
||||
|
||||
Once established, the newly resulting :class:`.Engine` will
|
||||
request a connection from the underlying :class:`.Pool` once
|
||||
:meth:`.Engine.connect` is called, or a method which depends on it
|
||||
such as :meth:`.Engine.execute` is invoked. The :class:`.Pool` in turn
|
||||
will establish the first actual DBAPI connection when this request
|
||||
is received. The :func:`.create_engine` call itself does **not**
|
||||
establish any actual DBAPI connections directly.
|
||||
|
||||
See also:
|
||||
|
||||
:doc:`/core/engines`
|
||||
|
||||
:ref:`connections_toplevel`
|
||||
|
||||
:param case_sensitive=True: if False, result column names
|
||||
will match in a case-insensitive fashion, that is,
|
||||
``row['SomeColumn']``.
|
||||
|
||||
.. versionchanged:: 0.8
|
||||
By default, result row names match case-sensitively.
|
||||
In version 0.7 and prior, all matches were case-insensitive.
|
||||
|
||||
:param connect_args: a dictionary of options which will be
|
||||
passed directly to the DBAPI's ``connect()`` method as
|
||||
additional keyword arguments. See the example
|
||||
at :ref:`custom_dbapi_args`.
|
||||
|
||||
:param convert_unicode=False: if set to True, sets
|
||||
the default behavior of ``convert_unicode`` on the
|
||||
:class:`.String` type to ``True``, regardless
|
||||
of a setting of ``False`` on an individual
|
||||
:class:`.String` type, thus causing all :class:`.String`
|
||||
-based columns
|
||||
to accommodate Python ``unicode`` objects. This flag
|
||||
is useful as an engine-wide setting when using a
|
||||
DBAPI that does not natively support Python
|
||||
``unicode`` objects and raises an error when
|
||||
one is received (such as pyodbc with FreeTDS).
|
||||
|
||||
See :class:`.String` for further details on
|
||||
what this flag indicates.
|
||||
|
||||
:param creator: a callable which returns a DBAPI connection.
|
||||
This creation function will be passed to the underlying
|
||||
connection pool and will be used to create all new database
|
||||
connections. Usage of this function causes connection
|
||||
parameters specified in the URL argument to be bypassed.
|
||||
|
||||
:param echo=False: if True, the Engine will log all statements
|
||||
as well as a repr() of their parameter lists to the engines
|
||||
logger, which defaults to sys.stdout. The ``echo`` attribute of
|
||||
``Engine`` can be modified at any time to turn logging on and
|
||||
off. If set to the string ``"debug"``, result rows will be
|
||||
printed to the standard output as well. This flag ultimately
|
||||
controls a Python logger; see :ref:`dbengine_logging` for
|
||||
information on how to configure logging directly.
|
||||
|
||||
:param echo_pool=False: if True, the connection pool will log
|
||||
all checkouts/checkins to the logging stream, which defaults to
|
||||
sys.stdout. This flag ultimately controls a Python logger; see
|
||||
:ref:`dbengine_logging` for information on how to configure logging
|
||||
directly.
|
||||
|
||||
:param encoding: Defaults to ``utf-8``. This is the string
|
||||
encoding used by SQLAlchemy for string encode/decode
|
||||
operations which occur within SQLAlchemy, **outside of
|
||||
the DBAPI.** Most modern DBAPIs feature some degree of
|
||||
direct support for Python ``unicode`` objects,
|
||||
what you see in Python 2 as a string of the form
|
||||
``u'some string'``. For those scenarios where the
|
||||
DBAPI is detected as not supporting a Python ``unicode``
|
||||
object, this encoding is used to determine the
|
||||
source/destination encoding. It is **not used**
|
||||
for those cases where the DBAPI handles unicode
|
||||
directly.
|
||||
|
||||
To properly configure a system to accommodate Python
|
||||
``unicode`` objects, the DBAPI should be
|
||||
configured to handle unicode to the greatest
|
||||
degree as is appropriate - see
|
||||
the notes on unicode pertaining to the specific
|
||||
target database in use at :ref:`dialect_toplevel`.
|
||||
|
||||
Areas where string encoding may need to be accommodated
|
||||
outside of the DBAPI include zero or more of:
|
||||
|
||||
* the values passed to bound parameters, corresponding to
|
||||
the :class:`.Unicode` type or the :class:`.String` type
|
||||
when ``convert_unicode`` is ``True``;
|
||||
* the values returned in result set columns corresponding
|
||||
to the :class:`.Unicode` type or the :class:`.String`
|
||||
type when ``convert_unicode`` is ``True``;
|
||||
* the string SQL statement passed to the DBAPI's
|
||||
``cursor.execute()`` method;
|
||||
* the string names of the keys in the bound parameter
|
||||
dictionary passed to the DBAPI's ``cursor.execute()``
|
||||
as well as ``cursor.setinputsizes()`` methods;
|
||||
* the string column names retrieved from the DBAPI's
|
||||
``cursor.description`` attribute.
|
||||
|
||||
When using Python 3, the DBAPI is required to support
|
||||
*all* of the above values as Python ``unicode`` objects,
|
||||
which in Python 3 are just known as ``str``. In Python 2,
|
||||
the DBAPI does not specify unicode behavior at all,
|
||||
so SQLAlchemy must make decisions for each of the above
|
||||
values on a per-DBAPI basis - implementations are
|
||||
completely inconsistent in their behavior.
|
||||
|
||||
:param execution_options: Dictionary execution options which will
|
||||
be applied to all connections. See
|
||||
:meth:`~sqlalchemy.engine.Connection.execution_options`
|
||||
|
||||
:param implicit_returning=True: When ``True``, a RETURNING-
|
||||
compatible construct, if available, will be used to
|
||||
fetch newly generated primary key values when a single row
|
||||
INSERT statement is emitted with no existing returning()
|
||||
clause. This applies to those backends which support RETURNING
|
||||
or a compatible construct, including Postgresql, Firebird, Oracle,
|
||||
Microsoft SQL Server. Set this to ``False`` to disable
|
||||
the automatic usage of RETURNING.
|
||||
|
||||
:param label_length=None: optional integer value which limits
|
||||
the size of dynamically generated column labels to that many
|
||||
characters. If less than 6, labels are generated as
|
||||
"_(counter)". If ``None``, the value of
|
||||
``dialect.max_identifier_length`` is used instead.
|
||||
|
||||
:param listeners: A list of one or more
|
||||
:class:`~sqlalchemy.interfaces.PoolListener` objects which will
|
||||
receive connection pool events.
|
||||
|
||||
:param logging_name: String identifier which will be used within
|
||||
the "name" field of logging records generated within the
|
||||
"sqlalchemy.engine" logger. Defaults to a hexstring of the
|
||||
object's id.
|
||||
|
||||
:param max_overflow=10: the number of connections to allow in
|
||||
connection pool "overflow", that is connections that can be
|
||||
opened above and beyond the pool_size setting, which defaults
|
||||
to five. this is only used with :class:`~sqlalchemy.pool.QueuePool`.
|
||||
|
||||
:param module=None: reference to a Python module object (the module
|
||||
itself, not its string name). Specifies an alternate DBAPI module to
|
||||
be used by the engine's dialect. Each sub-dialect references a
|
||||
specific DBAPI which will be imported before first connect. This
|
||||
parameter causes the import to be bypassed, and the given module to
|
||||
be used instead. Can be used for testing of DBAPIs as well as to
|
||||
inject "mock" DBAPI implementations into the :class:`.Engine`.
|
||||
|
||||
:param pool=None: an already-constructed instance of
|
||||
:class:`~sqlalchemy.pool.Pool`, such as a
|
||||
:class:`~sqlalchemy.pool.QueuePool` instance. If non-None, this
|
||||
pool will be used directly as the underlying connection pool
|
||||
for the engine, bypassing whatever connection parameters are
|
||||
present in the URL argument. For information on constructing
|
||||
connection pools manually, see :ref:`pooling_toplevel`.
|
||||
|
||||
:param poolclass=None: a :class:`~sqlalchemy.pool.Pool`
|
||||
subclass, which will be used to create a connection pool
|
||||
instance using the connection parameters given in the URL. Note
|
||||
this differs from ``pool`` in that you don't actually
|
||||
instantiate the pool in this case, you just indicate what type
|
||||
of pool to be used.
|
||||
|
||||
:param pool_logging_name: String identifier which will be used within
|
||||
the "name" field of logging records generated within the
|
||||
"sqlalchemy.pool" logger. Defaults to a hexstring of the object's
|
||||
id.
|
||||
|
||||
:param pool_size=5: the number of connections to keep open
|
||||
inside the connection pool. This used with
|
||||
:class:`~sqlalchemy.pool.QueuePool` as
|
||||
well as :class:`~sqlalchemy.pool.SingletonThreadPool`. With
|
||||
:class:`~sqlalchemy.pool.QueuePool`, a ``pool_size`` setting
|
||||
of 0 indicates no limit; to disable pooling, set ``poolclass`` to
|
||||
:class:`~sqlalchemy.pool.NullPool` instead.
|
||||
|
||||
:param pool_recycle=-1: this setting causes the pool to recycle
|
||||
connections after the given number of seconds has passed. It
|
||||
defaults to -1, or no timeout. For example, setting to 3600
|
||||
means connections will be recycled after one hour. Note that
|
||||
MySQL in particular will disconnect automatically if no
|
||||
activity is detected on a connection for eight hours (although
|
||||
this is configurable with the MySQLDB connection itself and the
|
||||
server configuration as well).
|
||||
|
||||
:param pool_reset_on_return='rollback': set the "reset on return"
|
||||
behavior of the pool, which is whether ``rollback()``,
|
||||
``commit()``, or nothing is called upon connections
|
||||
being returned to the pool. See the docstring for
|
||||
``reset_on_return`` at :class:`.Pool`.
|
||||
|
||||
.. versionadded:: 0.7.6
|
||||
|
||||
:param pool_timeout=30: number of seconds to wait before giving
|
||||
up on getting a connection from the pool. This is only used
|
||||
with :class:`~sqlalchemy.pool.QueuePool`.
|
||||
|
||||
:param strategy='plain': selects alternate engine implementations.
|
||||
Currently available are:
|
||||
|
||||
* the ``threadlocal`` strategy, which is described in
|
||||
:ref:`threadlocal_strategy`;
|
||||
* the ``mock`` strategy, which dispatches all statement
|
||||
execution to a function passed as the argument ``executor``.
|
||||
See `example in the FAQ
|
||||
<http://www.sqlalchemy.org/trac/wiki/FAQ#HowcanIgettheCREATETABLEDROPTABLEoutputasastring>`_.
|
||||
|
||||
:param executor=None: a function taking arguments
|
||||
``(sql, *multiparams, **params)``, to which the ``mock`` strategy will
|
||||
dispatch all statement execution. Used only by ``strategy='mock'``.
|
||||
|
||||
"""
|
||||
|
||||
strategy = kwargs.pop('strategy', default_strategy)
|
||||
strategy = strategies.strategies[strategy]
|
||||
return strategy.create(*args, **kwargs)
|
||||
|
||||
|
||||
def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
|
||||
"""Create a new Engine instance using a configuration dictionary.
|
||||
|
||||
The dictionary is typically produced from a config file where keys
|
||||
are prefixed, such as sqlalchemy.url, sqlalchemy.echo, etc. The
|
||||
'prefix' argument indicates the prefix to be searched for.
|
||||
|
||||
A select set of keyword arguments will be "coerced" to their
|
||||
expected type based on string values. In a future release, this
|
||||
functionality will be expanded and include dialect-specific
|
||||
arguments.
|
||||
"""
|
||||
|
||||
opts = util._coerce_config(configuration, prefix)
|
||||
opts.update(kwargs)
|
||||
url = opts.pop('url')
|
||||
return create_engine(url, **opts)
|
||||
|
||||
|
||||
__all__ = (
|
||||
'create_engine',
|
||||
'engine_from_config',
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,193 @@
|
||||
# engine/ddl.py
|
||||
# Copyright (C) 2009-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Routines to handle CREATE/DROP workflow."""
|
||||
|
||||
from .. import schema
|
||||
from ..sql import util as sql_util
|
||||
|
||||
|
||||
class DDLBase(schema.SchemaVisitor):
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
|
||||
|
||||
class SchemaGenerator(DDLBase):
|
||||
|
||||
def __init__(self, dialect, connection, checkfirst=False,
|
||||
tables=None, **kwargs):
|
||||
super(SchemaGenerator, self).__init__(connection, **kwargs)
|
||||
self.checkfirst = checkfirst
|
||||
self.tables = tables
|
||||
self.preparer = dialect.identifier_preparer
|
||||
self.dialect = dialect
|
||||
self.memo = {}
|
||||
|
||||
def _can_create_table(self, table):
|
||||
self.dialect.validate_identifier(table.name)
|
||||
if table.schema:
|
||||
self.dialect.validate_identifier(table.schema)
|
||||
return not self.checkfirst or \
|
||||
not self.dialect.has_table(self.connection,
|
||||
table.name, schema=table.schema)
|
||||
|
||||
def _can_create_sequence(self, sequence):
|
||||
return self.dialect.supports_sequences and \
|
||||
(
|
||||
(not self.dialect.sequences_optional or
|
||||
not sequence.optional) and
|
||||
(
|
||||
not self.checkfirst or
|
||||
not self.dialect.has_sequence(
|
||||
self.connection,
|
||||
sequence.name,
|
||||
schema=sequence.schema)
|
||||
)
|
||||
)
|
||||
|
||||
def visit_metadata(self, metadata):
|
||||
if self.tables is not None:
|
||||
tables = self.tables
|
||||
else:
|
||||
tables = metadata.tables.values()
|
||||
collection = [t for t in sql_util.sort_tables(tables)
|
||||
if self._can_create_table(t)]
|
||||
seq_coll = [s for s in metadata._sequences.values()
|
||||
if s.column is None and self._can_create_sequence(s)]
|
||||
|
||||
metadata.dispatch.before_create(metadata, self.connection,
|
||||
tables=collection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
for seq in seq_coll:
|
||||
self.traverse_single(seq, create_ok=True)
|
||||
|
||||
for table in collection:
|
||||
self.traverse_single(table, create_ok=True)
|
||||
|
||||
metadata.dispatch.after_create(metadata, self.connection,
|
||||
tables=collection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
def visit_table(self, table, create_ok=False):
|
||||
if not create_ok and not self._can_create_table(table):
|
||||
return
|
||||
|
||||
table.dispatch.before_create(table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
for column in table.columns:
|
||||
if column.default is not None:
|
||||
self.traverse_single(column.default)
|
||||
|
||||
self.connection.execute(schema.CreateTable(table))
|
||||
|
||||
if hasattr(table, 'indexes'):
|
||||
for index in table.indexes:
|
||||
self.traverse_single(index)
|
||||
|
||||
table.dispatch.after_create(table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
def visit_sequence(self, sequence, create_ok=False):
|
||||
if not create_ok and not self._can_create_sequence(sequence):
|
||||
return
|
||||
self.connection.execute(schema.CreateSequence(sequence))
|
||||
|
||||
def visit_index(self, index):
|
||||
self.connection.execute(schema.CreateIndex(index))
|
||||
|
||||
|
||||
class SchemaDropper(DDLBase):
|
||||
|
||||
def __init__(self, dialect, connection, checkfirst=False,
|
||||
tables=None, **kwargs):
|
||||
super(SchemaDropper, self).__init__(connection, **kwargs)
|
||||
self.checkfirst = checkfirst
|
||||
self.tables = tables
|
||||
self.preparer = dialect.identifier_preparer
|
||||
self.dialect = dialect
|
||||
self.memo = {}
|
||||
|
||||
def visit_metadata(self, metadata):
|
||||
if self.tables is not None:
|
||||
tables = self.tables
|
||||
else:
|
||||
tables = metadata.tables.values()
|
||||
|
||||
collection = [
|
||||
t
|
||||
for t in reversed(sql_util.sort_tables(tables))
|
||||
if self._can_drop_table(t)
|
||||
]
|
||||
|
||||
seq_coll = [
|
||||
s
|
||||
for s in metadata._sequences.values()
|
||||
if s.column is None and self._can_drop_sequence(s)
|
||||
]
|
||||
|
||||
metadata.dispatch.before_drop(
|
||||
metadata, self.connection, tables=collection,
|
||||
checkfirst=self.checkfirst, _ddl_runner=self)
|
||||
|
||||
for table in collection:
|
||||
self.traverse_single(table, drop_ok=True)
|
||||
|
||||
for seq in seq_coll:
|
||||
self.traverse_single(seq, drop_ok=True)
|
||||
|
||||
metadata.dispatch.after_drop(
|
||||
metadata, self.connection, tables=collection,
|
||||
checkfirst=self.checkfirst, _ddl_runner=self)
|
||||
|
||||
def _can_drop_table(self, table):
|
||||
self.dialect.validate_identifier(table.name)
|
||||
if table.schema:
|
||||
self.dialect.validate_identifier(table.schema)
|
||||
return not self.checkfirst or self.dialect.has_table(self.connection,
|
||||
table.name, schema=table.schema)
|
||||
|
||||
def _can_drop_sequence(self, sequence):
|
||||
return self.dialect.supports_sequences and \
|
||||
((not self.dialect.sequences_optional or
|
||||
not sequence.optional) and
|
||||
(not self.checkfirst or
|
||||
self.dialect.has_sequence(
|
||||
self.connection,
|
||||
sequence.name,
|
||||
schema=sequence.schema))
|
||||
)
|
||||
|
||||
def visit_index(self, index):
|
||||
self.connection.execute(schema.DropIndex(index))
|
||||
|
||||
def visit_table(self, table, drop_ok=False):
|
||||
if not drop_ok and not self._can_drop_table(table):
|
||||
return
|
||||
|
||||
table.dispatch.before_drop(table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
for column in table.columns:
|
||||
if column.default is not None:
|
||||
self.traverse_single(column.default)
|
||||
|
||||
self.connection.execute(schema.DropTable(table))
|
||||
|
||||
table.dispatch.after_drop(table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
def visit_sequence(self, sequence, drop_ok=False):
|
||||
if not drop_ok and not self._can_drop_sequence(sequence):
|
||||
return
|
||||
self.connection.execute(schema.DropSequence(sequence))
|
||||
@@ -0,0 +1,837 @@
|
||||
# engine/default.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Default implementations of per-dialect sqlalchemy.engine classes.
|
||||
|
||||
These are semi-private implementation classes which are only of importance
|
||||
to database dialect authors; dialects will usually use the classes here
|
||||
as the base class for their own corresponding classes.
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
import random
|
||||
from . import reflection, interfaces, result
|
||||
from ..sql import compiler, expression
|
||||
from .. import exc, types as sqltypes, util, pool, processors
|
||||
import codecs
|
||||
import weakref
|
||||
|
||||
AUTOCOMMIT_REGEXP = re.compile(
|
||||
r'\s*(?:UPDATE|INSERT|CREATE|DELETE|DROP|ALTER)',
|
||||
re.I | re.UNICODE)
|
||||
|
||||
|
||||
class DefaultDialect(interfaces.Dialect):
|
||||
"""Default implementation of Dialect"""
|
||||
|
||||
statement_compiler = compiler.SQLCompiler
|
||||
ddl_compiler = compiler.DDLCompiler
|
||||
type_compiler = compiler.GenericTypeCompiler
|
||||
preparer = compiler.IdentifierPreparer
|
||||
supports_alter = True
|
||||
|
||||
# the first value we'd get for an autoincrement
|
||||
# column.
|
||||
default_sequence_base = 1
|
||||
|
||||
# most DBAPIs happy with this for execute().
|
||||
# not cx_oracle.
|
||||
execute_sequence_format = tuple
|
||||
|
||||
supports_views = True
|
||||
supports_sequences = False
|
||||
sequences_optional = False
|
||||
preexecute_autoincrement_sequences = False
|
||||
postfetch_lastrowid = True
|
||||
implicit_returning = False
|
||||
|
||||
supports_native_enum = False
|
||||
supports_native_boolean = False
|
||||
|
||||
# if the NUMERIC type
|
||||
# returns decimal.Decimal.
|
||||
# *not* the FLOAT type however.
|
||||
supports_native_decimal = False
|
||||
|
||||
# Py3K
|
||||
#supports_unicode_statements = True
|
||||
#supports_unicode_binds = True
|
||||
#returns_unicode_strings = True
|
||||
#description_encoding = None
|
||||
# Py2K
|
||||
supports_unicode_statements = False
|
||||
supports_unicode_binds = False
|
||||
returns_unicode_strings = False
|
||||
description_encoding = 'use_encoding'
|
||||
# end Py2K
|
||||
|
||||
name = 'default'
|
||||
|
||||
# length at which to truncate
|
||||
# any identifier.
|
||||
max_identifier_length = 9999
|
||||
|
||||
# length at which to truncate
|
||||
# the name of an index.
|
||||
# Usually None to indicate
|
||||
# 'use max_identifier_length'.
|
||||
# thanks to MySQL, sigh
|
||||
max_index_name_length = None
|
||||
|
||||
supports_sane_rowcount = True
|
||||
supports_sane_multi_rowcount = True
|
||||
dbapi_type_map = {}
|
||||
colspecs = {}
|
||||
default_paramstyle = 'named'
|
||||
supports_default_values = False
|
||||
supports_empty_insert = True
|
||||
supports_multivalues_insert = False
|
||||
|
||||
server_version_info = None
|
||||
|
||||
# indicates symbol names are
|
||||
# UPPERCASEd if they are case insensitive
|
||||
# within the database.
|
||||
# if this is True, the methods normalize_name()
|
||||
# and denormalize_name() must be provided.
|
||||
requires_name_normalize = False
|
||||
|
||||
reflection_options = ()
|
||||
|
||||
def __init__(self, convert_unicode=False,
|
||||
encoding='utf-8', paramstyle=None, dbapi=None,
|
||||
implicit_returning=None,
|
||||
case_sensitive=True,
|
||||
label_length=None, **kwargs):
|
||||
|
||||
if not getattr(self, 'ported_sqla_06', True):
|
||||
util.warn(
|
||||
"The %s dialect is not yet ported to the 0.6 format" %
|
||||
self.name)
|
||||
|
||||
self.convert_unicode = convert_unicode
|
||||
self.encoding = encoding
|
||||
self.positional = False
|
||||
self._ischema = None
|
||||
self.dbapi = dbapi
|
||||
if paramstyle is not None:
|
||||
self.paramstyle = paramstyle
|
||||
elif self.dbapi is not None:
|
||||
self.paramstyle = self.dbapi.paramstyle
|
||||
else:
|
||||
self.paramstyle = self.default_paramstyle
|
||||
if implicit_returning is not None:
|
||||
self.implicit_returning = implicit_returning
|
||||
self.positional = self.paramstyle in ('qmark', 'format', 'numeric')
|
||||
self.identifier_preparer = self.preparer(self)
|
||||
self.type_compiler = self.type_compiler(self)
|
||||
|
||||
self.case_sensitive = case_sensitive
|
||||
|
||||
if label_length and label_length > self.max_identifier_length:
|
||||
raise exc.ArgumentError(
|
||||
"Label length of %d is greater than this dialect's"
|
||||
" maximum identifier length of %d" %
|
||||
(label_length, self.max_identifier_length))
|
||||
self.label_length = label_length
|
||||
|
||||
if self.description_encoding == 'use_encoding':
|
||||
self._description_decoder = \
|
||||
processors.to_unicode_processor_factory(
|
||||
encoding
|
||||
)
|
||||
elif self.description_encoding is not None:
|
||||
self._description_decoder = \
|
||||
processors.to_unicode_processor_factory(
|
||||
self.description_encoding
|
||||
)
|
||||
self._encoder = codecs.getencoder(self.encoding)
|
||||
self._decoder = processors.to_unicode_processor_factory(self.encoding)
|
||||
|
||||
@util.memoized_property
|
||||
def _type_memos(self):
|
||||
return weakref.WeakKeyDictionary()
|
||||
|
||||
@property
|
||||
def dialect_description(self):
|
||||
return self.name + "+" + self.driver
|
||||
|
||||
@classmethod
|
||||
def get_pool_class(cls, url):
|
||||
return getattr(cls, 'poolclass', pool.QueuePool)
|
||||
|
||||
def initialize(self, connection):
|
||||
try:
|
||||
self.server_version_info = \
|
||||
self._get_server_version_info(connection)
|
||||
except NotImplementedError:
|
||||
self.server_version_info = None
|
||||
try:
|
||||
self.default_schema_name = \
|
||||
self._get_default_schema_name(connection)
|
||||
except NotImplementedError:
|
||||
self.default_schema_name = None
|
||||
|
||||
try:
|
||||
self.default_isolation_level = \
|
||||
self.get_isolation_level(connection.connection)
|
||||
except NotImplementedError:
|
||||
self.default_isolation_level = None
|
||||
|
||||
self.returns_unicode_strings = self._check_unicode_returns(connection)
|
||||
|
||||
self.do_rollback(connection.connection)
|
||||
|
||||
def on_connect(self):
|
||||
"""return a callable which sets up a newly created DBAPI connection.
|
||||
|
||||
This is used to set dialect-wide per-connection options such as
|
||||
isolation modes, unicode modes, etc.
|
||||
|
||||
If a callable is returned, it will be assembled into a pool listener
|
||||
that receives the direct DBAPI connection, with all wrappers removed.
|
||||
|
||||
If None is returned, no listener will be generated.
|
||||
|
||||
"""
|
||||
return None
|
||||
|
||||
def _check_unicode_returns(self, connection):
|
||||
# Py2K
|
||||
if self.supports_unicode_statements:
|
||||
cast_to = unicode
|
||||
else:
|
||||
cast_to = str
|
||||
# end Py2K
|
||||
# Py3K
|
||||
#cast_to = str
|
||||
|
||||
def check_unicode(formatstr, type_):
|
||||
cursor = connection.connection.cursor()
|
||||
try:
|
||||
try:
|
||||
cursor.execute(
|
||||
cast_to(
|
||||
expression.select(
|
||||
[expression.cast(
|
||||
expression.literal_column(
|
||||
"'test %s returns'" % formatstr),
|
||||
type_)
|
||||
]).compile(dialect=self)
|
||||
)
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
|
||||
return isinstance(row[0], unicode)
|
||||
except self.dbapi.Error, de:
|
||||
util.warn("Exception attempting to "
|
||||
"detect unicode returns: %r" % de)
|
||||
return False
|
||||
finally:
|
||||
cursor.close()
|
||||
|
||||
# detect plain VARCHAR
|
||||
unicode_for_varchar = check_unicode("plain", sqltypes.VARCHAR(60))
|
||||
|
||||
# detect if there's an NVARCHAR type with different behavior available
|
||||
unicode_for_unicode = check_unicode("unicode", sqltypes.Unicode(60))
|
||||
|
||||
if unicode_for_unicode and not unicode_for_varchar:
|
||||
return "conditional"
|
||||
else:
|
||||
return unicode_for_varchar
|
||||
|
||||
def type_descriptor(self, typeobj):
|
||||
"""Provide a database-specific :class:`.TypeEngine` object, given
|
||||
the generic object which comes from the types module.
|
||||
|
||||
This method looks for a dictionary called
|
||||
``colspecs`` as a class or instance-level variable,
|
||||
and passes on to :func:`.types.adapt_type`.
|
||||
|
||||
"""
|
||||
return sqltypes.adapt_type(typeobj, self.colspecs)
|
||||
|
||||
def reflecttable(self, connection, table, include_columns,
|
||||
exclude_columns=None):
|
||||
insp = reflection.Inspector.from_engine(connection)
|
||||
return insp.reflecttable(table, include_columns, exclude_columns)
|
||||
|
||||
def get_pk_constraint(self, conn, table_name, schema=None, **kw):
|
||||
"""Compatibility method, adapts the result of get_primary_keys()
|
||||
for those dialects which don't implement get_pk_constraint().
|
||||
|
||||
"""
|
||||
return {
|
||||
'constrained_columns':
|
||||
self.get_primary_keys(conn, table_name,
|
||||
schema=schema, **kw)
|
||||
}
|
||||
|
||||
def validate_identifier(self, ident):
|
||||
if len(ident) > self.max_identifier_length:
|
||||
raise exc.IdentifierError(
|
||||
"Identifier '%s' exceeds maximum length of %d characters" %
|
||||
(ident, self.max_identifier_length)
|
||||
)
|
||||
|
||||
def connect(self, *cargs, **cparams):
|
||||
return self.dbapi.connect(*cargs, **cparams)
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args()
|
||||
opts.update(url.query)
|
||||
return [[], opts]
|
||||
|
||||
def do_begin(self, dbapi_connection):
|
||||
pass
|
||||
|
||||
def do_rollback(self, dbapi_connection):
|
||||
dbapi_connection.rollback()
|
||||
|
||||
def do_commit(self, dbapi_connection):
|
||||
dbapi_connection.commit()
|
||||
|
||||
def do_close(self, dbapi_connection):
|
||||
dbapi_connection.close()
|
||||
|
||||
def create_xid(self):
|
||||
"""Create a random two-phase transaction ID.
|
||||
|
||||
This id will be passed to do_begin_twophase(), do_rollback_twophase(),
|
||||
do_commit_twophase(). Its format is unspecified.
|
||||
"""
|
||||
|
||||
return "_sa_%032x" % random.randint(0, 2 ** 128)
|
||||
|
||||
def do_savepoint(self, connection, name):
|
||||
connection.execute(expression.SavepointClause(name))
|
||||
|
||||
def do_rollback_to_savepoint(self, connection, name):
|
||||
connection.execute(expression.RollbackToSavepointClause(name))
|
||||
|
||||
def do_release_savepoint(self, connection, name):
|
||||
connection.execute(expression.ReleaseSavepointClause(name))
|
||||
|
||||
def do_executemany(self, cursor, statement, parameters, context=None):
|
||||
cursor.executemany(statement, parameters)
|
||||
|
||||
def do_execute(self, cursor, statement, parameters, context=None):
|
||||
cursor.execute(statement, parameters)
|
||||
|
||||
def do_execute_no_params(self, cursor, statement, context=None):
|
||||
cursor.execute(statement)
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
return False
|
||||
|
||||
def reset_isolation_level(self, dbapi_conn):
|
||||
# default_isolation_level is read from the first connection
|
||||
# after the initial set of 'isolation_level', if any, so is
|
||||
# the configured default of this dialect.
|
||||
self.set_isolation_level(dbapi_conn, self.default_isolation_level)
|
||||
|
||||
|
||||
class DefaultExecutionContext(interfaces.ExecutionContext):
|
||||
isinsert = False
|
||||
isupdate = False
|
||||
isdelete = False
|
||||
isddl = False
|
||||
executemany = False
|
||||
result_map = None
|
||||
compiled = None
|
||||
statement = None
|
||||
postfetch_cols = None
|
||||
prefetch_cols = None
|
||||
_is_implicit_returning = False
|
||||
_is_explicit_returning = False
|
||||
|
||||
# a hook for SQLite's translation of
|
||||
# result column names
|
||||
_translate_colname = None
|
||||
|
||||
@classmethod
|
||||
def _init_ddl(cls, dialect, connection, dbapi_connection, compiled_ddl):
|
||||
"""Initialize execution context for a DDLElement construct."""
|
||||
|
||||
self = cls.__new__(cls)
|
||||
self.dialect = dialect
|
||||
self.root_connection = connection
|
||||
self._dbapi_connection = dbapi_connection
|
||||
self.engine = connection.engine
|
||||
|
||||
self.compiled = compiled = compiled_ddl
|
||||
self.isddl = True
|
||||
|
||||
self.execution_options = compiled.statement._execution_options
|
||||
if connection._execution_options:
|
||||
self.execution_options = dict(self.execution_options)
|
||||
self.execution_options.update(connection._execution_options)
|
||||
|
||||
if not dialect.supports_unicode_statements:
|
||||
self.unicode_statement = unicode(compiled)
|
||||
self.statement = dialect._encoder(self.unicode_statement)[0]
|
||||
else:
|
||||
self.statement = self.unicode_statement = unicode(compiled)
|
||||
|
||||
self.cursor = self.create_cursor()
|
||||
self.compiled_parameters = []
|
||||
|
||||
if dialect.positional:
|
||||
self.parameters = [dialect.execute_sequence_format()]
|
||||
else:
|
||||
self.parameters = [{}]
|
||||
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def _init_compiled(cls, dialect, connection, dbapi_connection,
|
||||
compiled, parameters):
|
||||
"""Initialize execution context for a Compiled construct."""
|
||||
|
||||
self = cls.__new__(cls)
|
||||
self.dialect = dialect
|
||||
self.root_connection = connection
|
||||
self._dbapi_connection = dbapi_connection
|
||||
self.engine = connection.engine
|
||||
|
||||
self.compiled = compiled
|
||||
|
||||
if not compiled.can_execute:
|
||||
raise exc.ArgumentError("Not an executable clause")
|
||||
|
||||
self.execution_options = compiled.statement._execution_options
|
||||
if connection._execution_options:
|
||||
self.execution_options = dict(self.execution_options)
|
||||
self.execution_options.update(connection._execution_options)
|
||||
|
||||
# compiled clauseelement. process bind params, process table defaults,
|
||||
# track collections used by ResultProxy to target and process results
|
||||
|
||||
self.result_map = compiled.result_map
|
||||
|
||||
self.unicode_statement = unicode(compiled)
|
||||
if not dialect.supports_unicode_statements:
|
||||
self.statement = self.unicode_statement.encode(
|
||||
self.dialect.encoding)
|
||||
else:
|
||||
self.statement = self.unicode_statement
|
||||
|
||||
self.isinsert = compiled.isinsert
|
||||
self.isupdate = compiled.isupdate
|
||||
self.isdelete = compiled.isdelete
|
||||
|
||||
if self.isinsert or self.isupdate or self.isdelete:
|
||||
self._is_explicit_returning = bool(compiled.statement._returning)
|
||||
self._is_implicit_returning = bool(compiled.returning and \
|
||||
not compiled.statement._returning)
|
||||
|
||||
if not parameters:
|
||||
self.compiled_parameters = [compiled.construct_params()]
|
||||
else:
|
||||
self.compiled_parameters = \
|
||||
[compiled.construct_params(m, _group_number=grp) for
|
||||
grp, m in enumerate(parameters)]
|
||||
|
||||
self.executemany = len(parameters) > 1
|
||||
|
||||
self.cursor = self.create_cursor()
|
||||
if self.isinsert or self.isupdate:
|
||||
self.postfetch_cols = self.compiled.postfetch
|
||||
self.prefetch_cols = self.compiled.prefetch
|
||||
self.__process_defaults()
|
||||
|
||||
processors = compiled._bind_processors
|
||||
|
||||
# Convert the dictionary of bind parameter values
|
||||
# into a dict or list to be sent to the DBAPI's
|
||||
# execute() or executemany() method.
|
||||
parameters = []
|
||||
if dialect.positional:
|
||||
for compiled_params in self.compiled_parameters:
|
||||
param = []
|
||||
for key in self.compiled.positiontup:
|
||||
if key in processors:
|
||||
param.append(processors[key](compiled_params[key]))
|
||||
else:
|
||||
param.append(compiled_params[key])
|
||||
parameters.append(dialect.execute_sequence_format(param))
|
||||
else:
|
||||
encode = not dialect.supports_unicode_statements
|
||||
for compiled_params in self.compiled_parameters:
|
||||
param = {}
|
||||
if encode:
|
||||
for key in compiled_params:
|
||||
if key in processors:
|
||||
param[dialect._encoder(key)[0]] = \
|
||||
processors[key](compiled_params[key])
|
||||
else:
|
||||
param[dialect._encoder(key)[0]] = \
|
||||
compiled_params[key]
|
||||
else:
|
||||
for key in compiled_params:
|
||||
if key in processors:
|
||||
param[key] = processors[key](compiled_params[key])
|
||||
else:
|
||||
param[key] = compiled_params[key]
|
||||
parameters.append(param)
|
||||
self.parameters = dialect.execute_sequence_format(parameters)
|
||||
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def _init_statement(cls, dialect, connection, dbapi_connection,
|
||||
statement, parameters):
|
||||
"""Initialize execution context for a string SQL statement."""
|
||||
|
||||
self = cls.__new__(cls)
|
||||
self.dialect = dialect
|
||||
self.root_connection = connection
|
||||
self._dbapi_connection = dbapi_connection
|
||||
self.engine = connection.engine
|
||||
|
||||
# plain text statement
|
||||
self.execution_options = connection._execution_options
|
||||
|
||||
if not parameters:
|
||||
if self.dialect.positional:
|
||||
self.parameters = [dialect.execute_sequence_format()]
|
||||
else:
|
||||
self.parameters = [{}]
|
||||
elif isinstance(parameters[0], dialect.execute_sequence_format):
|
||||
self.parameters = parameters
|
||||
elif isinstance(parameters[0], dict):
|
||||
if dialect.supports_unicode_statements:
|
||||
self.parameters = parameters
|
||||
else:
|
||||
self.parameters = [
|
||||
dict((dialect._encoder(k)[0], d[k]) for k in d)
|
||||
for d in parameters
|
||||
] or [{}]
|
||||
else:
|
||||
self.parameters = [dialect.execute_sequence_format(p)
|
||||
for p in parameters]
|
||||
|
||||
self.executemany = len(parameters) > 1
|
||||
|
||||
if not dialect.supports_unicode_statements and \
|
||||
isinstance(statement, unicode):
|
||||
self.unicode_statement = statement
|
||||
self.statement = dialect._encoder(statement)[0]
|
||||
else:
|
||||
self.statement = self.unicode_statement = statement
|
||||
|
||||
self.cursor = self.create_cursor()
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def _init_default(cls, dialect, connection, dbapi_connection):
|
||||
"""Initialize execution context for a ColumnDefault construct."""
|
||||
|
||||
self = cls.__new__(cls)
|
||||
self.dialect = dialect
|
||||
self.root_connection = connection
|
||||
self._dbapi_connection = dbapi_connection
|
||||
self.engine = connection.engine
|
||||
self.execution_options = connection._execution_options
|
||||
self.cursor = self.create_cursor()
|
||||
return self
|
||||
|
||||
@util.memoized_property
|
||||
def no_parameters(self):
|
||||
return self.execution_options.get("no_parameters", False)
|
||||
|
||||
@util.memoized_property
|
||||
def is_crud(self):
|
||||
return self.isinsert or self.isupdate or self.isdelete
|
||||
|
||||
@util.memoized_property
|
||||
def should_autocommit(self):
|
||||
autocommit = self.execution_options.get('autocommit',
|
||||
not self.compiled and
|
||||
self.statement and
|
||||
expression.PARSE_AUTOCOMMIT
|
||||
or False)
|
||||
|
||||
if autocommit is expression.PARSE_AUTOCOMMIT:
|
||||
return self.should_autocommit_text(self.unicode_statement)
|
||||
else:
|
||||
return autocommit
|
||||
|
||||
def _execute_scalar(self, stmt, type_):
|
||||
"""Execute a string statement on the current cursor, returning a
|
||||
scalar result.
|
||||
|
||||
Used to fire off sequences, default phrases, and "select lastrowid"
|
||||
types of statements individually or in the context of a parent INSERT
|
||||
or UPDATE statement.
|
||||
|
||||
"""
|
||||
|
||||
conn = self.root_connection
|
||||
if isinstance(stmt, unicode) and \
|
||||
not self.dialect.supports_unicode_statements:
|
||||
stmt = self.dialect._encoder(stmt)[0]
|
||||
|
||||
if self.dialect.positional:
|
||||
default_params = self.dialect.execute_sequence_format()
|
||||
else:
|
||||
default_params = {}
|
||||
|
||||
conn._cursor_execute(self.cursor, stmt, default_params, context=self)
|
||||
r = self.cursor.fetchone()[0]
|
||||
if type_ is not None:
|
||||
# apply type post processors to the result
|
||||
proc = type_._cached_result_processor(
|
||||
self.dialect,
|
||||
self.cursor.description[0][1]
|
||||
)
|
||||
if proc:
|
||||
return proc(r)
|
||||
return r
|
||||
|
||||
@property
|
||||
def connection(self):
|
||||
return self.root_connection._branch()
|
||||
|
||||
def should_autocommit_text(self, statement):
|
||||
return AUTOCOMMIT_REGEXP.match(statement)
|
||||
|
||||
def create_cursor(self):
|
||||
return self._dbapi_connection.cursor()
|
||||
|
||||
def pre_exec(self):
|
||||
pass
|
||||
|
||||
def post_exec(self):
|
||||
pass
|
||||
|
||||
def get_result_processor(self, type_, colname, coltype):
|
||||
"""Return a 'result processor' for a given type as present in
|
||||
cursor.description.
|
||||
|
||||
This has a default implementation that dialects can override
|
||||
for context-sensitive result type handling.
|
||||
|
||||
"""
|
||||
return type_._cached_result_processor(self.dialect, coltype)
|
||||
|
||||
def get_lastrowid(self):
|
||||
"""return self.cursor.lastrowid, or equivalent, after an INSERT.
|
||||
|
||||
This may involve calling special cursor functions,
|
||||
issuing a new SELECT on the cursor (or a new one),
|
||||
or returning a stored value that was
|
||||
calculated within post_exec().
|
||||
|
||||
This function will only be called for dialects
|
||||
which support "implicit" primary key generation,
|
||||
keep preexecute_autoincrement_sequences set to False,
|
||||
and when no explicit id value was bound to the
|
||||
statement.
|
||||
|
||||
The function is called once, directly after
|
||||
post_exec() and before the transaction is committed
|
||||
or ResultProxy is generated. If the post_exec()
|
||||
method assigns a value to `self._lastrowid`, the
|
||||
value is used in place of calling get_lastrowid().
|
||||
|
||||
Note that this method is *not* equivalent to the
|
||||
``lastrowid`` method on ``ResultProxy``, which is a
|
||||
direct proxy to the DBAPI ``lastrowid`` accessor
|
||||
in all cases.
|
||||
|
||||
"""
|
||||
return self.cursor.lastrowid
|
||||
|
||||
def handle_dbapi_exception(self, e):
|
||||
pass
|
||||
|
||||
def get_result_proxy(self):
|
||||
return result.ResultProxy(self)
|
||||
|
||||
@property
|
||||
def rowcount(self):
|
||||
return self.cursor.rowcount
|
||||
|
||||
def supports_sane_rowcount(self):
|
||||
return self.dialect.supports_sane_rowcount
|
||||
|
||||
def supports_sane_multi_rowcount(self):
|
||||
return self.dialect.supports_sane_multi_rowcount
|
||||
|
||||
def post_insert(self):
|
||||
if not self._is_implicit_returning and \
|
||||
not self._is_explicit_returning and \
|
||||
not self.compiled.inline and \
|
||||
self.dialect.postfetch_lastrowid and \
|
||||
(not self.inserted_primary_key or \
|
||||
None in self.inserted_primary_key):
|
||||
|
||||
table = self.compiled.statement.table
|
||||
lastrowid = self.get_lastrowid()
|
||||
autoinc_col = table._autoincrement_column
|
||||
if autoinc_col is not None:
|
||||
# apply type post processors to the lastrowid
|
||||
proc = autoinc_col.type._cached_result_processor(
|
||||
self.dialect, None)
|
||||
if proc is not None:
|
||||
lastrowid = proc(lastrowid)
|
||||
|
||||
self.inserted_primary_key = [
|
||||
lastrowid if c is autoinc_col else v
|
||||
for c, v in zip(
|
||||
table.primary_key,
|
||||
self.inserted_primary_key)
|
||||
]
|
||||
|
||||
def _fetch_implicit_returning(self, resultproxy):
|
||||
table = self.compiled.statement.table
|
||||
row = resultproxy.fetchone()
|
||||
|
||||
ipk = []
|
||||
for c, v in zip(table.primary_key, self.inserted_primary_key):
|
||||
if v is not None:
|
||||
ipk.append(v)
|
||||
else:
|
||||
ipk.append(row[c])
|
||||
|
||||
self.inserted_primary_key = ipk
|
||||
|
||||
def lastrow_has_defaults(self):
|
||||
return (self.isinsert or self.isupdate) and \
|
||||
bool(self.postfetch_cols)
|
||||
|
||||
def set_input_sizes(self, translate=None, exclude_types=None):
|
||||
"""Given a cursor and ClauseParameters, call the appropriate
|
||||
style of ``setinputsizes()`` on the cursor, using DB-API types
|
||||
from the bind parameter's ``TypeEngine`` objects.
|
||||
|
||||
This method only called by those dialects which require it,
|
||||
currently cx_oracle.
|
||||
|
||||
"""
|
||||
|
||||
if not hasattr(self.compiled, 'bind_names'):
|
||||
return
|
||||
|
||||
types = dict(
|
||||
(self.compiled.bind_names[bindparam], bindparam.type)
|
||||
for bindparam in self.compiled.bind_names)
|
||||
|
||||
if self.dialect.positional:
|
||||
inputsizes = []
|
||||
for key in self.compiled.positiontup:
|
||||
typeengine = types[key]
|
||||
dbtype = typeengine.dialect_impl(self.dialect).\
|
||||
get_dbapi_type(self.dialect.dbapi)
|
||||
if dbtype is not None and \
|
||||
(not exclude_types or dbtype not in exclude_types):
|
||||
inputsizes.append(dbtype)
|
||||
try:
|
||||
self.cursor.setinputsizes(*inputsizes)
|
||||
except Exception, e:
|
||||
self.root_connection._handle_dbapi_exception(
|
||||
e, None, None, None, self)
|
||||
else:
|
||||
inputsizes = {}
|
||||
for key in self.compiled.bind_names.values():
|
||||
typeengine = types[key]
|
||||
dbtype = typeengine.dialect_impl(self.dialect).\
|
||||
get_dbapi_type(self.dialect.dbapi)
|
||||
if dbtype is not None and \
|
||||
(not exclude_types or dbtype not in exclude_types):
|
||||
if translate:
|
||||
key = translate.get(key, key)
|
||||
if not self.dialect.supports_unicode_binds:
|
||||
key = self.dialect._encoder(key)[0]
|
||||
inputsizes[key] = dbtype
|
||||
try:
|
||||
self.cursor.setinputsizes(**inputsizes)
|
||||
except Exception, e:
|
||||
self.root_connection._handle_dbapi_exception(
|
||||
e, None, None, None, self)
|
||||
|
||||
def _exec_default(self, default, type_):
|
||||
if default.is_sequence:
|
||||
return self.fire_sequence(default, type_)
|
||||
elif default.is_callable:
|
||||
return default.arg(self)
|
||||
elif default.is_clause_element:
|
||||
# TODO: expensive branching here should be
|
||||
# pulled into _exec_scalar()
|
||||
conn = self.connection
|
||||
c = expression.select([default.arg]).compile(bind=conn)
|
||||
return conn._execute_compiled(c, (), {}).scalar()
|
||||
else:
|
||||
return default.arg
|
||||
|
||||
def get_insert_default(self, column):
|
||||
if column.default is None:
|
||||
return None
|
||||
else:
|
||||
return self._exec_default(column.default, column.type)
|
||||
|
||||
def get_update_default(self, column):
|
||||
if column.onupdate is None:
|
||||
return None
|
||||
else:
|
||||
return self._exec_default(column.onupdate, column.type)
|
||||
|
||||
def __process_defaults(self):
|
||||
"""Generate default values for compiled insert/update statements,
|
||||
and generate inserted_primary_key collection.
|
||||
"""
|
||||
|
||||
if self.executemany:
|
||||
if len(self.compiled.prefetch):
|
||||
scalar_defaults = {}
|
||||
|
||||
# pre-determine scalar Python-side defaults
|
||||
# to avoid many calls of get_insert_default()/
|
||||
# get_update_default()
|
||||
for c in self.prefetch_cols:
|
||||
if self.isinsert and c.default and c.default.is_scalar:
|
||||
scalar_defaults[c] = c.default.arg
|
||||
elif self.isupdate and c.onupdate and c.onupdate.is_scalar:
|
||||
scalar_defaults[c] = c.onupdate.arg
|
||||
|
||||
for param in self.compiled_parameters:
|
||||
self.current_parameters = param
|
||||
for c in self.prefetch_cols:
|
||||
if c in scalar_defaults:
|
||||
val = scalar_defaults[c]
|
||||
elif self.isinsert:
|
||||
val = self.get_insert_default(c)
|
||||
else:
|
||||
val = self.get_update_default(c)
|
||||
if val is not None:
|
||||
param[c.key] = val
|
||||
del self.current_parameters
|
||||
else:
|
||||
self.current_parameters = compiled_parameters = \
|
||||
self.compiled_parameters[0]
|
||||
|
||||
for c in self.compiled.prefetch:
|
||||
if self.isinsert:
|
||||
val = self.get_insert_default(c)
|
||||
else:
|
||||
val = self.get_update_default(c)
|
||||
|
||||
if val is not None:
|
||||
compiled_parameters[c.key] = val
|
||||
del self.current_parameters
|
||||
|
||||
if self.isinsert:
|
||||
self.inserted_primary_key = [
|
||||
self.compiled_parameters[0].get(c.key, None)
|
||||
for c in self.compiled.\
|
||||
statement.table.primary_key
|
||||
]
|
||||
|
||||
|
||||
DefaultDialect.execution_ctx_cls = DefaultExecutionContext
|
||||
@@ -0,0 +1,925 @@
|
||||
# engine/interfaces.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Define core interfaces used by the engine system."""
|
||||
|
||||
from .. import util, event, events
|
||||
|
||||
|
||||
class Dialect(object):
|
||||
"""Define the behavior of a specific database and DB-API combination.
|
||||
|
||||
Any aspect of metadata definition, SQL query generation,
|
||||
execution, result-set handling, or anything else which varies
|
||||
between databases is defined under the general category of the
|
||||
Dialect. The Dialect acts as a factory for other
|
||||
database-specific object implementations including
|
||||
ExecutionContext, Compiled, DefaultGenerator, and TypeEngine.
|
||||
|
||||
All Dialects implement the following attributes:
|
||||
|
||||
name
|
||||
identifying name for the dialect from a DBAPI-neutral point of view
|
||||
(i.e. 'sqlite')
|
||||
|
||||
driver
|
||||
identifying name for the dialect's DBAPI
|
||||
|
||||
positional
|
||||
True if the paramstyle for this Dialect is positional.
|
||||
|
||||
paramstyle
|
||||
the paramstyle to be used (some DB-APIs support multiple
|
||||
paramstyles).
|
||||
|
||||
convert_unicode
|
||||
True if Unicode conversion should be applied to all ``str``
|
||||
types.
|
||||
|
||||
encoding
|
||||
type of encoding to use for unicode, usually defaults to
|
||||
'utf-8'.
|
||||
|
||||
statement_compiler
|
||||
a :class:`.Compiled` class used to compile SQL statements
|
||||
|
||||
ddl_compiler
|
||||
a :class:`.Compiled` class used to compile DDL statements
|
||||
|
||||
server_version_info
|
||||
a tuple containing a version number for the DB backend in use.
|
||||
This value is only available for supporting dialects, and is
|
||||
typically populated during the initial connection to the database.
|
||||
|
||||
default_schema_name
|
||||
the name of the default schema. This value is only available for
|
||||
supporting dialects, and is typically populated during the
|
||||
initial connection to the database.
|
||||
|
||||
execution_ctx_cls
|
||||
a :class:`.ExecutionContext` class used to handle statement execution
|
||||
|
||||
execute_sequence_format
|
||||
either the 'tuple' or 'list' type, depending on what cursor.execute()
|
||||
accepts for the second argument (they vary).
|
||||
|
||||
preparer
|
||||
a :class:`~sqlalchemy.sql.compiler.IdentifierPreparer` class used to
|
||||
quote identifiers.
|
||||
|
||||
supports_alter
|
||||
``True`` if the database supports ``ALTER TABLE``.
|
||||
|
||||
max_identifier_length
|
||||
The maximum length of identifier names.
|
||||
|
||||
supports_unicode_statements
|
||||
Indicate whether the DB-API can receive SQL statements as Python
|
||||
unicode strings
|
||||
|
||||
supports_unicode_binds
|
||||
Indicate whether the DB-API can receive string bind parameters
|
||||
as Python unicode strings
|
||||
|
||||
supports_sane_rowcount
|
||||
Indicate whether the dialect properly implements rowcount for
|
||||
``UPDATE`` and ``DELETE`` statements.
|
||||
|
||||
supports_sane_multi_rowcount
|
||||
Indicate whether the dialect properly implements rowcount for
|
||||
``UPDATE`` and ``DELETE`` statements when executed via
|
||||
executemany.
|
||||
|
||||
preexecute_autoincrement_sequences
|
||||
True if 'implicit' primary key functions must be executed separately
|
||||
in order to get their value. This is currently oriented towards
|
||||
Postgresql.
|
||||
|
||||
implicit_returning
|
||||
use RETURNING or equivalent during INSERT execution in order to load
|
||||
newly generated primary keys and other column defaults in one execution,
|
||||
which are then available via inserted_primary_key.
|
||||
If an insert statement has returning() specified explicitly,
|
||||
the "implicit" functionality is not used and inserted_primary_key
|
||||
will not be available.
|
||||
|
||||
dbapi_type_map
|
||||
A mapping of DB-API type objects present in this Dialect's
|
||||
DB-API implementation mapped to TypeEngine implementations used
|
||||
by the dialect.
|
||||
|
||||
This is used to apply types to result sets based on the DB-API
|
||||
types present in cursor.description; it only takes effect for
|
||||
result sets against textual statements where no explicit
|
||||
typemap was present.
|
||||
|
||||
colspecs
|
||||
A dictionary of TypeEngine classes from sqlalchemy.types mapped
|
||||
to subclasses that are specific to the dialect class. This
|
||||
dictionary is class-level only and is not accessed from the
|
||||
dialect instance itself.
|
||||
|
||||
supports_default_values
|
||||
Indicates if the construct ``INSERT INTO tablename DEFAULT
|
||||
VALUES`` is supported
|
||||
|
||||
supports_sequences
|
||||
Indicates if the dialect supports CREATE SEQUENCE or similar.
|
||||
|
||||
sequences_optional
|
||||
If True, indicates if the "optional" flag on the Sequence() construct
|
||||
should signal to not generate a CREATE SEQUENCE. Applies only to
|
||||
dialects that support sequences. Currently used only to allow Postgresql
|
||||
SERIAL to be used on a column that specifies Sequence() for usage on
|
||||
other backends.
|
||||
|
||||
supports_native_enum
|
||||
Indicates if the dialect supports a native ENUM construct.
|
||||
This will prevent types.Enum from generating a CHECK
|
||||
constraint when that type is used.
|
||||
|
||||
supports_native_boolean
|
||||
Indicates if the dialect supports a native boolean construct.
|
||||
This will prevent types.Boolean from generating a CHECK
|
||||
constraint when that type is used.
|
||||
|
||||
"""
|
||||
|
||||
def create_connect_args(self, url):
|
||||
"""Build DB-API compatible connection arguments.
|
||||
|
||||
Given a :class:`~sqlalchemy.engine.url.URL` object, returns a tuple
|
||||
consisting of a `*args`/`**kwargs` suitable to send directly
|
||||
to the dbapi's connect function.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def type_descriptor(cls, typeobj):
|
||||
"""Transform a generic type to a dialect-specific type.
|
||||
|
||||
Dialect classes will usually use the
|
||||
:func:`.types.adapt_type` function in the types module to
|
||||
accomplish this.
|
||||
|
||||
The returned result is cached *per dialect class* so can
|
||||
contain no dialect-instance state.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def initialize(self, connection):
|
||||
"""Called during strategized creation of the dialect with a
|
||||
connection.
|
||||
|
||||
Allows dialects to configure options based on server version info or
|
||||
other properties.
|
||||
|
||||
The connection passed here is a SQLAlchemy Connection object,
|
||||
with full capabilities.
|
||||
|
||||
The initalize() method of the base dialect should be called via
|
||||
super().
|
||||
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def reflecttable(self, connection, table, include_columns=None):
|
||||
"""Load table description from the database.
|
||||
|
||||
Given a :class:`.Connection` and a
|
||||
:class:`~sqlalchemy.schema.Table` object, reflect its columns and
|
||||
properties from the database. If include_columns (a list or
|
||||
set) is specified, limit the autoload to the given column
|
||||
names.
|
||||
|
||||
The default implementation uses the
|
||||
:class:`~sqlalchemy.engine.reflection.Inspector` interface to
|
||||
provide the output, building upon the granular table/column/
|
||||
constraint etc. methods of :class:`.Dialect`.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_columns(self, connection, table_name, schema=None, **kw):
|
||||
"""Return information about columns in `table_name`.
|
||||
|
||||
Given a :class:`.Connection`, a string
|
||||
`table_name`, and an optional string `schema`, return column
|
||||
information as a list of dictionaries with these keys:
|
||||
|
||||
name
|
||||
the column's name
|
||||
|
||||
type
|
||||
[sqlalchemy.types#TypeEngine]
|
||||
|
||||
nullable
|
||||
boolean
|
||||
|
||||
default
|
||||
the column's default value
|
||||
|
||||
autoincrement
|
||||
boolean
|
||||
|
||||
sequence
|
||||
a dictionary of the form
|
||||
{'name' : str, 'start' :int, 'increment': int}
|
||||
|
||||
Additional column attributes may be present.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_primary_keys(self, connection, table_name, schema=None, **kw):
|
||||
"""Return information about primary keys in `table_name`.
|
||||
|
||||
|
||||
Deprecated. This method is only called by the default
|
||||
implementation of :meth:`.Dialect.get_pk_constraint`. Dialects should
|
||||
instead implement this method directly.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_pk_constraint(self, connection, table_name, schema=None, **kw):
|
||||
"""Return information about the primary key constraint on
|
||||
table_name`.
|
||||
|
||||
Given a :class:`.Connection`, a string
|
||||
`table_name`, and an optional string `schema`, return primary
|
||||
key information as a dictionary with these keys:
|
||||
|
||||
constrained_columns
|
||||
a list of column names that make up the primary key
|
||||
|
||||
name
|
||||
optional name of the primary key constraint.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_foreign_keys(self, connection, table_name, schema=None, **kw):
|
||||
"""Return information about foreign_keys in `table_name`.
|
||||
|
||||
Given a :class:`.Connection`, a string
|
||||
`table_name`, and an optional string `schema`, return foreign
|
||||
key information as a list of dicts with these keys:
|
||||
|
||||
name
|
||||
the constraint's name
|
||||
|
||||
constrained_columns
|
||||
a list of column names that make up the foreign key
|
||||
|
||||
referred_schema
|
||||
the name of the referred schema
|
||||
|
||||
referred_table
|
||||
the name of the referred table
|
||||
|
||||
referred_columns
|
||||
a list of column names in the referred table that correspond to
|
||||
constrained_columns
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
"""Return a list of table names for `schema`."""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
"""Return a list of all view names available in the database.
|
||||
|
||||
schema:
|
||||
Optional, retrieve names from a non-default schema.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_view_definition(self, connection, view_name, schema=None, **kw):
|
||||
"""Return view definition.
|
||||
|
||||
Given a :class:`.Connection`, a string
|
||||
`view_name`, and an optional string `schema`, return the view
|
||||
definition.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_indexes(self, connection, table_name, schema=None, **kw):
|
||||
"""Return information about indexes in `table_name`.
|
||||
|
||||
Given a :class:`.Connection`, a string
|
||||
`table_name` and an optional string `schema`, return index
|
||||
information as a list of dictionaries with these keys:
|
||||
|
||||
name
|
||||
the index's name
|
||||
|
||||
column_names
|
||||
list of column names in order
|
||||
|
||||
unique
|
||||
boolean
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def normalize_name(self, name):
|
||||
"""convert the given name to lowercase if it is detected as
|
||||
case insensitive.
|
||||
|
||||
this method is only used if the dialect defines
|
||||
requires_name_normalize=True.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def denormalize_name(self, name):
|
||||
"""convert the given name to a case insensitive identifier
|
||||
for the backend if it is an all-lowercase name.
|
||||
|
||||
this method is only used if the dialect defines
|
||||
requires_name_normalize=True.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def has_table(self, connection, table_name, schema=None):
|
||||
"""Check the existence of a particular table in the database.
|
||||
|
||||
Given a :class:`.Connection` object and a string
|
||||
`table_name`, return True if the given table (possibly within
|
||||
the specified `schema`) exists in the database, False
|
||||
otherwise.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def has_sequence(self, connection, sequence_name, schema=None):
|
||||
"""Check the existence of a particular sequence in the database.
|
||||
|
||||
Given a :class:`.Connection` object and a string
|
||||
`sequence_name`, return True if the given sequence exists in
|
||||
the database, False otherwise.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
"""Retrieve the server version info from the given connection.
|
||||
|
||||
This is used by the default implementation to populate the
|
||||
"server_version_info" attribute and is called exactly
|
||||
once upon first connect.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def _get_default_schema_name(self, connection):
|
||||
"""Return the string name of the currently selected schema from
|
||||
the given connection.
|
||||
|
||||
This is used by the default implementation to populate the
|
||||
"default_schema_name" attribute and is called exactly
|
||||
once upon first connect.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_begin(self, dbapi_connection):
|
||||
"""Provide an implementation of ``connection.begin()``, given a
|
||||
DB-API connection.
|
||||
|
||||
The DBAPI has no dedicated "begin" method and it is expected
|
||||
that transactions are implicit. This hook is provided for those
|
||||
DBAPIs that might need additional help in this area.
|
||||
|
||||
Note that :meth:`.Dialect.do_begin` is not called unless a
|
||||
:class:`.Transaction` object is in use. The
|
||||
:meth:`.Dialect.do_autocommit`
|
||||
hook is provided for DBAPIs that need some extra commands emitted
|
||||
after a commit in order to enter the next transaction, when the
|
||||
SQLAlchemy :class:`.Connection` is used in it's default "autocommit"
|
||||
mode.
|
||||
|
||||
:param dbapi_connection: a DBAPI connection, typically
|
||||
proxied within a :class:`.ConnectionFairy`.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_rollback(self, dbapi_connection):
|
||||
"""Provide an implementation of ``connection.rollback()``, given
|
||||
a DB-API connection.
|
||||
|
||||
:param dbapi_connection: a DBAPI connection, typically
|
||||
proxied within a :class:`.ConnectionFairy`.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def do_commit(self, dbapi_connection):
|
||||
"""Provide an implementation of ``connection.commit()``, given a
|
||||
DB-API connection.
|
||||
|
||||
:param dbapi_connection: a DBAPI connection, typically
|
||||
proxied within a :class:`.ConnectionFairy`.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_close(self, dbapi_connection):
|
||||
"""Provide an implementation of ``connection.close()``, given a DBAPI
|
||||
connection.
|
||||
|
||||
This hook is called by the :class:`.Pool` when a connection has been
|
||||
detached from the pool, or is being returned beyond the normal
|
||||
capacity of the pool.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_xid(self):
|
||||
"""Create a two-phase transaction ID.
|
||||
|
||||
This id will be passed to do_begin_twophase(),
|
||||
do_rollback_twophase(), do_commit_twophase(). Its format is
|
||||
unspecified.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_savepoint(self, connection, name):
|
||||
"""Create a savepoint with the given name.
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
:param name: savepoint name.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_rollback_to_savepoint(self, connection, name):
|
||||
"""Rollback a connection to the named savepoint.
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
:param name: savepoint name.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_release_savepoint(self, connection, name):
|
||||
"""Release the named savepoint on a connection.
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
:param name: savepoint name.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_begin_twophase(self, connection, xid):
|
||||
"""Begin a two phase transaction on the given connection.
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
:param xid: xid
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_prepare_twophase(self, connection, xid):
|
||||
"""Prepare a two phase transaction on the given connection.
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
:param xid: xid
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_rollback_twophase(self, connection, xid, is_prepared=True,
|
||||
recover=False):
|
||||
"""Rollback a two phase transaction on the given connection.
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
:param xid: xid
|
||||
:param is_prepared: whether or not
|
||||
:meth:`.TwoPhaseTransaction.prepare` was called.
|
||||
:param recover: if the recover flag was passed.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_commit_twophase(self, connection, xid, is_prepared=True,
|
||||
recover=False):
|
||||
"""Commit a two phase transaction on the given connection.
|
||||
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
:param xid: xid
|
||||
:param is_prepared: whether or not
|
||||
:meth:`.TwoPhaseTransaction.prepare` was called.
|
||||
:param recover: if the recover flag was passed.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_recover_twophase(self, connection):
|
||||
"""Recover list of uncommited prepared two phase transaction
|
||||
identifiers on the given connection.
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_executemany(self, cursor, statement, parameters, context=None):
|
||||
"""Provide an implementation of ``cursor.executemany(statement,
|
||||
parameters)``."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_execute(self, cursor, statement, parameters, context=None):
|
||||
"""Provide an implementation of ``cursor.execute(statement,
|
||||
parameters)``."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_execute_no_params(self, cursor, statement, parameters,
|
||||
context=None):
|
||||
"""Provide an implementation of ``cursor.execute(statement)``.
|
||||
|
||||
The parameter collection should not be sent.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
"""Return True if the given DB-API error indicates an invalid
|
||||
connection"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def connect(self):
|
||||
"""return a callable which sets up a newly created DBAPI connection.
|
||||
|
||||
The callable accepts a single argument "conn" which is the
|
||||
DBAPI connection itself. It has no return value.
|
||||
|
||||
This is used to set dialect-wide per-connection options such as
|
||||
isolation modes, unicode modes, etc.
|
||||
|
||||
If a callable is returned, it will be assembled into a pool listener
|
||||
that receives the direct DBAPI connection, with all wrappers removed.
|
||||
|
||||
If None is returned, no listener will be generated.
|
||||
|
||||
"""
|
||||
return None
|
||||
|
||||
def reset_isolation_level(self, dbapi_conn):
|
||||
"""Given a DBAPI connection, revert its isolation to the default."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_isolation_level(self, dbapi_conn, level):
|
||||
"""Given a DBAPI connection, set its isolation level."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_isolation_level(self, dbapi_conn):
|
||||
"""Given a DBAPI connection, return its isolation level."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class ExecutionContext(object):
|
||||
"""A messenger object for a Dialect that corresponds to a single
|
||||
execution.
|
||||
|
||||
ExecutionContext should have these data members:
|
||||
|
||||
connection
|
||||
Connection object which can be freely used by default value
|
||||
generators to execute SQL. This Connection should reference the
|
||||
same underlying connection/transactional resources of
|
||||
root_connection.
|
||||
|
||||
root_connection
|
||||
Connection object which is the source of this ExecutionContext. This
|
||||
Connection may have close_with_result=True set, in which case it can
|
||||
only be used once.
|
||||
|
||||
dialect
|
||||
dialect which created this ExecutionContext.
|
||||
|
||||
cursor
|
||||
DB-API cursor procured from the connection,
|
||||
|
||||
compiled
|
||||
if passed to constructor, sqlalchemy.engine.base.Compiled object
|
||||
being executed,
|
||||
|
||||
statement
|
||||
string version of the statement to be executed. Is either
|
||||
passed to the constructor, or must be created from the
|
||||
sql.Compiled object by the time pre_exec() has completed.
|
||||
|
||||
parameters
|
||||
bind parameters passed to the execute() method. For compiled
|
||||
statements, this is a dictionary or list of dictionaries. For
|
||||
textual statements, it should be in a format suitable for the
|
||||
dialect's paramstyle (i.e. dict or list of dicts for non
|
||||
positional, list or list of lists/tuples for positional).
|
||||
|
||||
isinsert
|
||||
True if the statement is an INSERT.
|
||||
|
||||
isupdate
|
||||
True if the statement is an UPDATE.
|
||||
|
||||
should_autocommit
|
||||
True if the statement is a "committable" statement.
|
||||
|
||||
prefetch_cols
|
||||
a list of Column objects for which a client-side default
|
||||
was fired off. Applies to inserts and updates.
|
||||
|
||||
postfetch_cols
|
||||
a list of Column objects for which a server-side default or
|
||||
inline SQL expression value was fired off. Applies to inserts
|
||||
and updates.
|
||||
"""
|
||||
|
||||
def create_cursor(self):
|
||||
"""Return a new cursor generated from this ExecutionContext's
|
||||
connection.
|
||||
|
||||
Some dialects may wish to change the behavior of
|
||||
connection.cursor(), such as postgresql which may return a PG
|
||||
"server side" cursor.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def pre_exec(self):
|
||||
"""Called before an execution of a compiled statement.
|
||||
|
||||
If a compiled statement was passed to this ExecutionContext,
|
||||
the `statement` and `parameters` datamembers must be
|
||||
initialized after this statement is complete.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def post_exec(self):
|
||||
"""Called after the execution of a compiled statement.
|
||||
|
||||
If a compiled statement was passed to this ExecutionContext,
|
||||
the `last_insert_ids`, `last_inserted_params`, etc.
|
||||
datamembers should be available after this method completes.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def result(self):
|
||||
"""Return a result object corresponding to this ExecutionContext.
|
||||
|
||||
Returns a ResultProxy.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def handle_dbapi_exception(self, e):
|
||||
"""Receive a DBAPI exception which occurred upon execute, result
|
||||
fetch, etc."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def should_autocommit_text(self, statement):
|
||||
"""Parse the given textual statement and return True if it refers to
|
||||
a "committable" statement"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def lastrow_has_defaults(self):
|
||||
"""Return True if the last INSERT or UPDATE row contained
|
||||
inlined or database-side defaults.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_rowcount(self):
|
||||
"""Return the DBAPI ``cursor.rowcount`` value, or in some
|
||||
cases an interpreted value.
|
||||
|
||||
See :attr:`.ResultProxy.rowcount` for details on this.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class Compiled(object):
|
||||
"""Represent a compiled SQL or DDL expression.
|
||||
|
||||
The ``__str__`` method of the ``Compiled`` object should produce
|
||||
the actual text of the statement. ``Compiled`` objects are
|
||||
specific to their underlying database dialect, and also may
|
||||
or may not be specific to the columns referenced within a
|
||||
particular set of bind parameters. In no case should the
|
||||
``Compiled`` object be dependent on the actual values of those
|
||||
bind parameters, even though it may reference those values as
|
||||
defaults.
|
||||
"""
|
||||
|
||||
def __init__(self, dialect, statement, bind=None,
|
||||
compile_kwargs=util.immutabledict()):
|
||||
"""Construct a new ``Compiled`` object.
|
||||
|
||||
:param dialect: ``Dialect`` to compile against.
|
||||
|
||||
:param statement: ``ClauseElement`` to be compiled.
|
||||
|
||||
:param bind: Optional Engine or Connection to compile this
|
||||
statement against.
|
||||
|
||||
:param compile_kwargs: additional kwargs that will be
|
||||
passed to the initial call to :meth:`.Compiled.process`.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
"""
|
||||
|
||||
self.dialect = dialect
|
||||
self.bind = bind
|
||||
if statement is not None:
|
||||
self.statement = statement
|
||||
self.can_execute = statement.supports_execution
|
||||
self.string = self.process(self.statement, **compile_kwargs)
|
||||
|
||||
@util.deprecated("0.7", ":class:`.Compiled` objects now compile "
|
||||
"within the constructor.")
|
||||
def compile(self):
|
||||
"""Produce the internal string representation of this element."""
|
||||
pass
|
||||
|
||||
@property
|
||||
def sql_compiler(self):
|
||||
"""Return a Compiled that is capable of processing SQL expressions.
|
||||
|
||||
If this compiler is one, it would likely just return 'self'.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def process(self, obj, **kwargs):
|
||||
return obj._compiler_dispatch(self, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
"""Return the string text of the generated SQL or DDL."""
|
||||
|
||||
return self.string or ''
|
||||
|
||||
def construct_params(self, params=None):
|
||||
"""Return the bind params for this compiled object.
|
||||
|
||||
:param params: a dict of string/object pairs whose values will
|
||||
override bind values compiled in to the
|
||||
statement.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
"""Return the bind params for this compiled object."""
|
||||
return self.construct_params()
|
||||
|
||||
def execute(self, *multiparams, **params):
|
||||
"""Execute this compiled object."""
|
||||
|
||||
e = self.bind
|
||||
if e is None:
|
||||
raise exc.UnboundExecutionError(
|
||||
"This Compiled object is not bound to any Engine "
|
||||
"or Connection.")
|
||||
return e._execute_compiled(self, multiparams, params)
|
||||
|
||||
def scalar(self, *multiparams, **params):
|
||||
"""Execute this compiled object and return the result's
|
||||
scalar value."""
|
||||
|
||||
return self.execute(*multiparams, **params).scalar()
|
||||
|
||||
|
||||
class TypeCompiler(object):
|
||||
"""Produces DDL specification for TypeEngine objects."""
|
||||
|
||||
def __init__(self, dialect):
|
||||
self.dialect = dialect
|
||||
|
||||
def process(self, type_):
|
||||
return type_._compiler_dispatch(self)
|
||||
|
||||
|
||||
class Connectable(object):
|
||||
"""Interface for an object which supports execution of SQL constructs.
|
||||
|
||||
The two implementations of :class:`.Connectable` are
|
||||
:class:`.Connection` and :class:`.Engine`.
|
||||
|
||||
Connectable must also implement the 'dialect' member which references a
|
||||
:class:`.Dialect` instance.
|
||||
|
||||
"""
|
||||
|
||||
dispatch = event.dispatcher(events.ConnectionEvents)
|
||||
|
||||
def connect(self, **kwargs):
|
||||
"""Return a :class:`.Connection` object.
|
||||
|
||||
Depending on context, this may be ``self`` if this object
|
||||
is already an instance of :class:`.Connection`, or a newly
|
||||
procured :class:`.Connection` if this object is an instance
|
||||
of :class:`.Engine`.
|
||||
|
||||
"""
|
||||
|
||||
def contextual_connect(self):
|
||||
"""Return a :class:`.Connection` object which may be part of an ongoing
|
||||
context.
|
||||
|
||||
Depending on context, this may be ``self`` if this object
|
||||
is already an instance of :class:`.Connection`, or a newly
|
||||
procured :class:`.Connection` if this object is an instance
|
||||
of :class:`.Engine`.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
@util.deprecated("0.7",
|
||||
"Use the create() method on the given schema "
|
||||
"object directly, i.e. :meth:`.Table.create`, "
|
||||
":meth:`.Index.create`, :meth:`.MetaData.create_all`")
|
||||
def create(self, entity, **kwargs):
|
||||
"""Emit CREATE statements for the given schema entity."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
@util.deprecated("0.7",
|
||||
"Use the drop() method on the given schema "
|
||||
"object directly, i.e. :meth:`.Table.drop`, "
|
||||
":meth:`.Index.drop`, :meth:`.MetaData.drop_all`")
|
||||
def drop(self, entity, **kwargs):
|
||||
"""Emit DROP statements for the given schema entity."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def execute(self, object, *multiparams, **params):
|
||||
"""Executes the given construct and returns a :class:`.ResultProxy`."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def scalar(self, object, *multiparams, **params):
|
||||
"""Executes and returns the first column of the first row.
|
||||
|
||||
The underlying cursor is closed after execution.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _run_visitor(self, visitorcallable, element,
|
||||
**kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _execute_clauseelement(self, elem, multiparams=None, params=None):
|
||||
raise NotImplementedError()
|
||||
@@ -0,0 +1,526 @@
|
||||
# engine/reflection.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Provides an abstraction for obtaining database schema information.
|
||||
|
||||
Usage Notes:
|
||||
|
||||
Here are some general conventions when accessing the low level inspector
|
||||
methods such as get_table_names, get_columns, etc.
|
||||
|
||||
1. Inspector methods return lists of dicts in most cases for the following
|
||||
reasons:
|
||||
|
||||
* They're both standard types that can be serialized.
|
||||
* Using a dict instead of a tuple allows easy expansion of attributes.
|
||||
* Using a list for the outer structure maintains order and is easy to work
|
||||
with (e.g. list comprehension [d['name'] for d in cols]).
|
||||
|
||||
2. Records that contain a name, such as the column name in a column record
|
||||
use the key 'name'. So for most return values, each record will have a
|
||||
'name' attribute..
|
||||
"""
|
||||
|
||||
from .. import exc, sql
|
||||
from .. import schema as sa_schema
|
||||
from .. import util
|
||||
from ..types import TypeEngine
|
||||
from ..util import deprecated
|
||||
from ..util import topological
|
||||
from .. import inspection
|
||||
from .base import Connectable
|
||||
|
||||
|
||||
@util.decorator
|
||||
def cache(fn, self, con, *args, **kw):
|
||||
info_cache = kw.get('info_cache', None)
|
||||
if info_cache is None:
|
||||
return fn(self, con, *args, **kw)
|
||||
key = (
|
||||
fn.__name__,
|
||||
tuple(a for a in args if isinstance(a, basestring)),
|
||||
tuple((k, v) for k, v in kw.iteritems() if isinstance(v, (basestring, int, float)))
|
||||
)
|
||||
ret = info_cache.get(key)
|
||||
if ret is None:
|
||||
ret = fn(self, con, *args, **kw)
|
||||
info_cache[key] = ret
|
||||
return ret
|
||||
|
||||
|
||||
class Inspector(object):
|
||||
"""Performs database schema inspection.
|
||||
|
||||
The Inspector acts as a proxy to the reflection methods of the
|
||||
:class:`~sqlalchemy.engine.interfaces.Dialect`, providing a
|
||||
consistent interface as well as caching support for previously
|
||||
fetched metadata.
|
||||
|
||||
A :class:`.Inspector` object is usually created via the
|
||||
:func:`.inspect` function::
|
||||
|
||||
from sqlalchemy import inspect, create_engine
|
||||
engine = create_engine('...')
|
||||
insp = inspect(engine)
|
||||
|
||||
The inspection method above is equivalent to using the
|
||||
:meth:`.Inspector.from_engine` method, i.e.::
|
||||
|
||||
engine = create_engine('...')
|
||||
insp = Inspector.from_engine(engine)
|
||||
|
||||
Where above, the :class:`~sqlalchemy.engine.interfaces.Dialect` may opt
|
||||
to return an :class:`.Inspector` subclass that provides additional
|
||||
methods specific to the dialect's target database.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, bind):
|
||||
"""Initialize a new :class:`.Inspector`.
|
||||
|
||||
:param bind: a :class:`~sqlalchemy.engine.Connectable`,
|
||||
which is typically an instance of
|
||||
:class:`~sqlalchemy.engine.Engine` or
|
||||
:class:`~sqlalchemy.engine.Connection`.
|
||||
|
||||
For a dialect-specific instance of :class:`.Inspector`, see
|
||||
:meth:`.Inspector.from_engine`
|
||||
|
||||
"""
|
||||
# this might not be a connection, it could be an engine.
|
||||
self.bind = bind
|
||||
|
||||
# set the engine
|
||||
if hasattr(bind, 'engine'):
|
||||
self.engine = bind.engine
|
||||
else:
|
||||
self.engine = bind
|
||||
|
||||
if self.engine is bind:
|
||||
# if engine, ensure initialized
|
||||
bind.connect().close()
|
||||
|
||||
self.dialect = self.engine.dialect
|
||||
self.info_cache = {}
|
||||
|
||||
@classmethod
|
||||
def from_engine(cls, bind):
|
||||
"""Construct a new dialect-specific Inspector object from the given
|
||||
engine or connection.
|
||||
|
||||
:param bind: a :class:`~sqlalchemy.engine.Connectable`,
|
||||
which is typically an instance of
|
||||
:class:`~sqlalchemy.engine.Engine` or
|
||||
:class:`~sqlalchemy.engine.Connection`.
|
||||
|
||||
This method differs from direct a direct constructor call of
|
||||
:class:`.Inspector` in that the
|
||||
:class:`~sqlalchemy.engine.interfaces.Dialect` is given a chance to
|
||||
provide a dialect-specific :class:`.Inspector` instance, which may
|
||||
provide additional methods.
|
||||
|
||||
See the example at :class:`.Inspector`.
|
||||
|
||||
"""
|
||||
if hasattr(bind.dialect, 'inspector'):
|
||||
return bind.dialect.inspector(bind)
|
||||
return Inspector(bind)
|
||||
|
||||
@inspection._inspects(Connectable)
|
||||
def _insp(bind):
|
||||
return Inspector.from_engine(bind)
|
||||
|
||||
@property
|
||||
def default_schema_name(self):
|
||||
"""Return the default schema name presented by the dialect
|
||||
for the current engine's database user.
|
||||
|
||||
E.g. this is typically ``public`` for Postgresql and ``dbo``
|
||||
for SQL Server.
|
||||
|
||||
"""
|
||||
return self.dialect.default_schema_name
|
||||
|
||||
def get_schema_names(self):
|
||||
"""Return all schema names.
|
||||
"""
|
||||
|
||||
if hasattr(self.dialect, 'get_schema_names'):
|
||||
return self.dialect.get_schema_names(self.bind,
|
||||
info_cache=self.info_cache)
|
||||
return []
|
||||
|
||||
def get_table_names(self, schema=None, order_by=None):
|
||||
"""Return all table names in referred to within a particular schema.
|
||||
|
||||
The names are expected to be real tables only, not views.
|
||||
Views are instead returned using the :meth:`.get_view_names`
|
||||
method.
|
||||
|
||||
|
||||
:param schema: Schema name. If ``schema`` is left at ``None``, the
|
||||
database's default schema is
|
||||
used, else the named schema is searched. If the database does not
|
||||
support named schemas, behavior is undefined if ``schema`` is not
|
||||
passed as ``None``.
|
||||
|
||||
:param order_by: Optional, may be the string "foreign_key" to sort
|
||||
the result on foreign key dependencies.
|
||||
|
||||
.. versionchanged:: 0.8 the "foreign_key" sorting sorts tables
|
||||
in order of dependee to dependent; that is, in creation
|
||||
order, rather than in drop order. This is to maintain
|
||||
consistency with similar features such as
|
||||
:attr:`.MetaData.sorted_tables` and :func:`.util.sort_tables`.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:attr:`.MetaData.sorted_tables`
|
||||
|
||||
"""
|
||||
|
||||
if hasattr(self.dialect, 'get_table_names'):
|
||||
tnames = self.dialect.get_table_names(self.bind,
|
||||
schema, info_cache=self.info_cache)
|
||||
else:
|
||||
tnames = self.engine.table_names(schema)
|
||||
if order_by == 'foreign_key':
|
||||
tuples = []
|
||||
for tname in tnames:
|
||||
for fkey in self.get_foreign_keys(tname, schema):
|
||||
if tname != fkey['referred_table']:
|
||||
tuples.append((fkey['referred_table'], tname))
|
||||
tnames = list(topological.sort(tuples, tnames))
|
||||
return tnames
|
||||
|
||||
def get_table_options(self, table_name, schema=None, **kw):
|
||||
"""Return a dictionary of options specified when the table of the
|
||||
given name was created.
|
||||
|
||||
This currently includes some options that apply to MySQL tables.
|
||||
|
||||
"""
|
||||
if hasattr(self.dialect, 'get_table_options'):
|
||||
return self.dialect.get_table_options(
|
||||
self.bind, table_name, schema,
|
||||
info_cache=self.info_cache, **kw)
|
||||
return {}
|
||||
|
||||
def get_view_names(self, schema=None):
|
||||
"""Return all view names in `schema`.
|
||||
|
||||
:param schema: Optional, retrieve names from a non-default schema.
|
||||
"""
|
||||
|
||||
return self.dialect.get_view_names(self.bind, schema,
|
||||
info_cache=self.info_cache)
|
||||
|
||||
def get_view_definition(self, view_name, schema=None):
|
||||
"""Return definition for `view_name`.
|
||||
|
||||
:param schema: Optional, retrieve names from a non-default schema.
|
||||
"""
|
||||
|
||||
return self.dialect.get_view_definition(
|
||||
self.bind, view_name, schema, info_cache=self.info_cache)
|
||||
|
||||
def get_columns(self, table_name, schema=None, **kw):
|
||||
"""Return information about columns in `table_name`.
|
||||
|
||||
Given a string `table_name` and an optional string `schema`, return
|
||||
column information as a list of dicts with these keys:
|
||||
|
||||
name
|
||||
the column's name
|
||||
|
||||
type
|
||||
:class:`~sqlalchemy.types.TypeEngine`
|
||||
|
||||
nullable
|
||||
boolean
|
||||
|
||||
default
|
||||
the column's default value
|
||||
|
||||
attrs
|
||||
dict containing optional column attributes
|
||||
"""
|
||||
|
||||
col_defs = self.dialect.get_columns(self.bind, table_name, schema,
|
||||
info_cache=self.info_cache,
|
||||
**kw)
|
||||
for col_def in col_defs:
|
||||
# make this easy and only return instances for coltype
|
||||
coltype = col_def['type']
|
||||
if not isinstance(coltype, TypeEngine):
|
||||
col_def['type'] = coltype()
|
||||
return col_defs
|
||||
|
||||
@deprecated('0.7', 'Call to deprecated method get_primary_keys.'
|
||||
' Use get_pk_constraint instead.')
|
||||
def get_primary_keys(self, table_name, schema=None, **kw):
|
||||
"""Return information about primary keys in `table_name`.
|
||||
|
||||
Given a string `table_name`, and an optional string `schema`, return
|
||||
primary key information as a list of column names.
|
||||
"""
|
||||
|
||||
return self.dialect.get_pk_constraint(self.bind, table_name, schema,
|
||||
info_cache=self.info_cache,
|
||||
**kw)['constrained_columns']
|
||||
|
||||
def get_pk_constraint(self, table_name, schema=None, **kw):
|
||||
"""Return information about primary key constraint on `table_name`.
|
||||
|
||||
Given a string `table_name`, and an optional string `schema`, return
|
||||
primary key information as a dictionary with these keys:
|
||||
|
||||
constrained_columns
|
||||
a list of column names that make up the primary key
|
||||
|
||||
name
|
||||
optional name of the primary key constraint.
|
||||
|
||||
"""
|
||||
return self.dialect.get_pk_constraint(self.bind, table_name, schema,
|
||||
info_cache=self.info_cache,
|
||||
**kw)
|
||||
|
||||
def get_foreign_keys(self, table_name, schema=None, **kw):
|
||||
"""Return information about foreign_keys in `table_name`.
|
||||
|
||||
Given a string `table_name`, and an optional string `schema`, return
|
||||
foreign key information as a list of dicts with these keys:
|
||||
|
||||
constrained_columns
|
||||
a list of column names that make up the foreign key
|
||||
|
||||
referred_schema
|
||||
the name of the referred schema
|
||||
|
||||
referred_table
|
||||
the name of the referred table
|
||||
|
||||
referred_columns
|
||||
a list of column names in the referred table that correspond to
|
||||
constrained_columns
|
||||
|
||||
name
|
||||
optional name of the foreign key constraint.
|
||||
|
||||
\**kw
|
||||
other options passed to the dialect's get_foreign_keys() method.
|
||||
|
||||
"""
|
||||
|
||||
return self.dialect.get_foreign_keys(self.bind, table_name, schema,
|
||||
info_cache=self.info_cache,
|
||||
**kw)
|
||||
|
||||
def get_indexes(self, table_name, schema=None, **kw):
|
||||
"""Return information about indexes in `table_name`.
|
||||
|
||||
Given a string `table_name` and an optional string `schema`, return
|
||||
index information as a list of dicts with these keys:
|
||||
|
||||
name
|
||||
the index's name
|
||||
|
||||
column_names
|
||||
list of column names in order
|
||||
|
||||
unique
|
||||
boolean
|
||||
|
||||
\**kw
|
||||
other options passed to the dialect's get_indexes() method.
|
||||
"""
|
||||
|
||||
return self.dialect.get_indexes(self.bind, table_name,
|
||||
schema,
|
||||
info_cache=self.info_cache, **kw)
|
||||
|
||||
def reflecttable(self, table, include_columns, exclude_columns=()):
|
||||
"""Given a Table object, load its internal constructs based on
|
||||
introspection.
|
||||
|
||||
This is the underlying method used by most dialects to produce
|
||||
table reflection. Direct usage is like::
|
||||
|
||||
from sqlalchemy import create_engine, MetaData, Table
|
||||
from sqlalchemy.engine import reflection
|
||||
|
||||
engine = create_engine('...')
|
||||
meta = MetaData()
|
||||
user_table = Table('user', meta)
|
||||
insp = Inspector.from_engine(engine)
|
||||
insp.reflecttable(user_table, None)
|
||||
|
||||
:param table: a :class:`~sqlalchemy.schema.Table` instance.
|
||||
:param include_columns: a list of string column names to include
|
||||
in the reflection process. If ``None``, all columns are reflected.
|
||||
|
||||
"""
|
||||
dialect = self.bind.dialect
|
||||
|
||||
# table attributes we might need.
|
||||
reflection_options = dict(
|
||||
(k, table.kwargs.get(k))
|
||||
for k in dialect.reflection_options if k in table.kwargs)
|
||||
|
||||
schema = table.schema
|
||||
table_name = table.name
|
||||
|
||||
# apply table options
|
||||
tbl_opts = self.get_table_options(table_name, schema, **table.kwargs)
|
||||
if tbl_opts:
|
||||
table.kwargs.update(tbl_opts)
|
||||
|
||||
# table.kwargs will need to be passed to each reflection method. Make
|
||||
# sure keywords are strings.
|
||||
tblkw = table.kwargs.copy()
|
||||
for (k, v) in tblkw.items():
|
||||
del tblkw[k]
|
||||
tblkw[str(k)] = v
|
||||
|
||||
# Py2K
|
||||
if isinstance(schema, str):
|
||||
schema = schema.decode(dialect.encoding)
|
||||
if isinstance(table_name, str):
|
||||
table_name = table_name.decode(dialect.encoding)
|
||||
# end Py2K
|
||||
|
||||
# columns
|
||||
found_table = False
|
||||
cols_by_orig_name = {}
|
||||
|
||||
for col_d in self.get_columns(table_name, schema, **tblkw):
|
||||
found_table = True
|
||||
orig_name = col_d['name']
|
||||
|
||||
table.dispatch.column_reflect(self, table, col_d)
|
||||
|
||||
name = col_d['name']
|
||||
if include_columns and name not in include_columns:
|
||||
continue
|
||||
if exclude_columns and name in exclude_columns:
|
||||
continue
|
||||
|
||||
coltype = col_d['type']
|
||||
col_kw = {
|
||||
'nullable': col_d['nullable'],
|
||||
}
|
||||
for k in ('autoincrement', 'quote', 'info', 'key'):
|
||||
if k in col_d:
|
||||
col_kw[k] = col_d[k]
|
||||
|
||||
colargs = []
|
||||
if col_d.get('default') is not None:
|
||||
# the "default" value is assumed to be a literal SQL
|
||||
# expression, so is wrapped in text() so that no quoting
|
||||
# occurs on re-issuance.
|
||||
colargs.append(
|
||||
sa_schema.DefaultClause(
|
||||
sql.text(col_d['default']), _reflected=True
|
||||
)
|
||||
)
|
||||
|
||||
if 'sequence' in col_d:
|
||||
# TODO: mssql, maxdb and sybase are using this.
|
||||
seq = col_d['sequence']
|
||||
sequence = sa_schema.Sequence(seq['name'], 1, 1)
|
||||
if 'start' in seq:
|
||||
sequence.start = seq['start']
|
||||
if 'increment' in seq:
|
||||
sequence.increment = seq['increment']
|
||||
colargs.append(sequence)
|
||||
|
||||
cols_by_orig_name[orig_name] = col = \
|
||||
sa_schema.Column(name, coltype, *colargs, **col_kw)
|
||||
|
||||
table.append_column(col)
|
||||
|
||||
if not found_table:
|
||||
raise exc.NoSuchTableError(table.name)
|
||||
|
||||
# Primary keys
|
||||
pk_cons = self.get_pk_constraint(table_name, schema, **tblkw)
|
||||
if pk_cons:
|
||||
pk_cols = [
|
||||
cols_by_orig_name[pk]
|
||||
for pk in pk_cons['constrained_columns']
|
||||
if pk in cols_by_orig_name and pk not in exclude_columns
|
||||
]
|
||||
pk_cols += [
|
||||
pk
|
||||
for pk in table.primary_key
|
||||
if pk.key in exclude_columns
|
||||
]
|
||||
primary_key_constraint = sa_schema.PrimaryKeyConstraint(
|
||||
name=pk_cons.get('name'),
|
||||
*pk_cols
|
||||
)
|
||||
|
||||
table.append_constraint(primary_key_constraint)
|
||||
|
||||
# Foreign keys
|
||||
fkeys = self.get_foreign_keys(table_name, schema, **tblkw)
|
||||
for fkey_d in fkeys:
|
||||
conname = fkey_d['name']
|
||||
# look for columns by orig name in cols_by_orig_name,
|
||||
# but support columns that are in-Python only as fallback
|
||||
constrained_columns = [
|
||||
cols_by_orig_name[c].key
|
||||
if c in cols_by_orig_name else c
|
||||
for c in fkey_d['constrained_columns']
|
||||
]
|
||||
if exclude_columns and set(constrained_columns).intersection(
|
||||
exclude_columns):
|
||||
continue
|
||||
referred_schema = fkey_d['referred_schema']
|
||||
referred_table = fkey_d['referred_table']
|
||||
referred_columns = fkey_d['referred_columns']
|
||||
refspec = []
|
||||
if referred_schema is not None:
|
||||
sa_schema.Table(referred_table, table.metadata,
|
||||
autoload=True, schema=referred_schema,
|
||||
autoload_with=self.bind,
|
||||
**reflection_options
|
||||
)
|
||||
for column in referred_columns:
|
||||
refspec.append(".".join(
|
||||
[referred_schema, referred_table, column]))
|
||||
else:
|
||||
sa_schema.Table(referred_table, table.metadata, autoload=True,
|
||||
autoload_with=self.bind,
|
||||
**reflection_options
|
||||
)
|
||||
for column in referred_columns:
|
||||
refspec.append(".".join([referred_table, column]))
|
||||
table.append_constraint(
|
||||
sa_schema.ForeignKeyConstraint(constrained_columns, refspec,
|
||||
conname, link_to_name=True))
|
||||
# Indexes
|
||||
indexes = self.get_indexes(table_name, schema)
|
||||
for index_d in indexes:
|
||||
name = index_d['name']
|
||||
columns = index_d['column_names']
|
||||
unique = index_d['unique']
|
||||
flavor = index_d.get('type', 'unknown type')
|
||||
if include_columns and \
|
||||
not set(columns).issubset(include_columns):
|
||||
util.warn(
|
||||
"Omitting %s KEY for (%s), key covers omitted columns." %
|
||||
(flavor, ', '.join(columns)))
|
||||
continue
|
||||
# look for columns by orig name in cols_by_orig_name,
|
||||
# but support columns that are in-Python only as fallback
|
||||
sa_schema.Index(name, *[
|
||||
cols_by_orig_name[c] if c in cols_by_orig_name
|
||||
else table.c[c]
|
||||
for c in columns
|
||||
],
|
||||
**dict(unique=unique))
|
||||
@@ -0,0 +1,997 @@
|
||||
# engine/result.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Define result set constructs including :class:`.ResultProxy`
|
||||
and :class:`.RowProxy."""
|
||||
|
||||
|
||||
from itertools import izip
|
||||
from .. import exc, types, util
|
||||
from ..sql import expression
|
||||
import collections
|
||||
|
||||
# This reconstructor is necessary so that pickles with the C extension or
|
||||
# without use the same Binary format.
|
||||
try:
|
||||
# We need a different reconstructor on the C extension so that we can
|
||||
# add extra checks that fields have correctly been initialized by
|
||||
# __setstate__.
|
||||
from sqlalchemy.cresultproxy import safe_rowproxy_reconstructor
|
||||
|
||||
# The extra function embedding is needed so that the
|
||||
# reconstructor function has the same signature whether or not
|
||||
# the extension is present.
|
||||
def rowproxy_reconstructor(cls, state):
|
||||
return safe_rowproxy_reconstructor(cls, state)
|
||||
except ImportError:
|
||||
def rowproxy_reconstructor(cls, state):
|
||||
obj = cls.__new__(cls)
|
||||
obj.__setstate__(state)
|
||||
return obj
|
||||
|
||||
try:
|
||||
from sqlalchemy.cresultproxy import BaseRowProxy
|
||||
except ImportError:
|
||||
class BaseRowProxy(object):
|
||||
__slots__ = ('_parent', '_row', '_processors', '_keymap')
|
||||
|
||||
def __init__(self, parent, row, processors, keymap):
|
||||
"""RowProxy objects are constructed by ResultProxy objects."""
|
||||
|
||||
self._parent = parent
|
||||
self._row = row
|
||||
self._processors = processors
|
||||
self._keymap = keymap
|
||||
|
||||
def __reduce__(self):
|
||||
return (rowproxy_reconstructor,
|
||||
(self.__class__, self.__getstate__()))
|
||||
|
||||
def values(self):
|
||||
"""Return the values represented by this RowProxy as a list."""
|
||||
return list(self)
|
||||
|
||||
def __iter__(self):
|
||||
for processor, value in izip(self._processors, self._row):
|
||||
if processor is None:
|
||||
yield value
|
||||
else:
|
||||
yield processor(value)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._row)
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
processor, obj, index = self._keymap[key]
|
||||
except KeyError:
|
||||
processor, obj, index = self._parent._key_fallback(key)
|
||||
except TypeError:
|
||||
if isinstance(key, slice):
|
||||
l = []
|
||||
for processor, value in izip(self._processors[key],
|
||||
self._row[key]):
|
||||
if processor is None:
|
||||
l.append(value)
|
||||
else:
|
||||
l.append(processor(value))
|
||||
return tuple(l)
|
||||
else:
|
||||
raise
|
||||
if index is None:
|
||||
raise exc.InvalidRequestError(
|
||||
"Ambiguous column name '%s' in result set! "
|
||||
"try 'use_labels' option on select statement." % key)
|
||||
if processor is not None:
|
||||
return processor(self._row[index])
|
||||
else:
|
||||
return self._row[index]
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self[name]
|
||||
except KeyError, e:
|
||||
raise AttributeError(e.args[0])
|
||||
|
||||
|
||||
class RowProxy(BaseRowProxy):
|
||||
"""Proxy values from a single cursor row.
|
||||
|
||||
Mostly follows "ordered dictionary" behavior, mapping result
|
||||
values to the string-based column name, the integer position of
|
||||
the result in the row, as well as Column instances which can be
|
||||
mapped to the original Columns that produced this result set (for
|
||||
results that correspond to constructed SQL expressions).
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def __contains__(self, key):
|
||||
return self._parent._has_key(self._row, key)
|
||||
|
||||
def __getstate__(self):
|
||||
return {
|
||||
'_parent': self._parent,
|
||||
'_row': tuple(self)
|
||||
}
|
||||
|
||||
def __setstate__(self, state):
|
||||
self._parent = parent = state['_parent']
|
||||
self._row = state['_row']
|
||||
self._processors = parent._processors
|
||||
self._keymap = parent._keymap
|
||||
|
||||
__hash__ = None
|
||||
|
||||
def __eq__(self, other):
|
||||
return other is self or other == tuple(self)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(tuple(self))
|
||||
|
||||
def has_key(self, key):
|
||||
"""Return True if this RowProxy contains the given key."""
|
||||
|
||||
return self._parent._has_key(self._row, key)
|
||||
|
||||
def items(self):
|
||||
"""Return a list of tuples, each tuple containing a key/value pair."""
|
||||
# TODO: no coverage here
|
||||
return [(key, self[key]) for key in self.iterkeys()]
|
||||
|
||||
def keys(self):
|
||||
"""Return the list of keys as strings represented by this RowProxy."""
|
||||
|
||||
return self._parent.keys
|
||||
|
||||
def iterkeys(self):
|
||||
return iter(self._parent.keys)
|
||||
|
||||
def itervalues(self):
|
||||
return iter(self)
|
||||
|
||||
try:
|
||||
# Register RowProxy with Sequence,
|
||||
# so sequence protocol is implemented
|
||||
from collections import Sequence
|
||||
Sequence.register(RowProxy)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class ResultMetaData(object):
|
||||
"""Handle cursor.description, applying additional info from an execution
|
||||
context."""
|
||||
|
||||
def __init__(self, parent, metadata):
|
||||
self._processors = processors = []
|
||||
|
||||
# We do not strictly need to store the processor in the key mapping,
|
||||
# though it is faster in the Python version (probably because of the
|
||||
# saved attribute lookup self._processors)
|
||||
self._keymap = keymap = {}
|
||||
self.keys = []
|
||||
context = parent.context
|
||||
dialect = context.dialect
|
||||
typemap = dialect.dbapi_type_map
|
||||
translate_colname = context._translate_colname
|
||||
self.case_sensitive = dialect.case_sensitive
|
||||
|
||||
# high precedence key values.
|
||||
primary_keymap = {}
|
||||
|
||||
for i, rec in enumerate(metadata):
|
||||
colname = rec[0]
|
||||
coltype = rec[1]
|
||||
|
||||
if dialect.description_encoding:
|
||||
colname = dialect._description_decoder(colname)
|
||||
|
||||
if translate_colname:
|
||||
colname, untranslated = translate_colname(colname)
|
||||
|
||||
if dialect.requires_name_normalize:
|
||||
colname = dialect.normalize_name(colname)
|
||||
|
||||
if context.result_map:
|
||||
try:
|
||||
name, obj, type_ = context.result_map[colname
|
||||
if self.case_sensitive
|
||||
else colname.lower()]
|
||||
except KeyError:
|
||||
name, obj, type_ = \
|
||||
colname, None, typemap.get(coltype, types.NULLTYPE)
|
||||
else:
|
||||
name, obj, type_ = \
|
||||
colname, None, typemap.get(coltype, types.NULLTYPE)
|
||||
|
||||
processor = context.get_result_processor(type_, colname, coltype)
|
||||
|
||||
processors.append(processor)
|
||||
rec = (processor, obj, i)
|
||||
|
||||
# indexes as keys. This is only needed for the Python version of
|
||||
# RowProxy (the C version uses a faster path for integer indexes).
|
||||
primary_keymap[i] = rec
|
||||
|
||||
# populate primary keymap, looking for conflicts.
|
||||
if primary_keymap.setdefault(
|
||||
name if self.case_sensitive
|
||||
else name.lower(),
|
||||
rec) is not rec:
|
||||
# place a record that doesn't have the "index" - this
|
||||
# is interpreted later as an AmbiguousColumnError,
|
||||
# but only when actually accessed. Columns
|
||||
# colliding by name is not a problem if those names
|
||||
# aren't used; integer access is always
|
||||
# unambiguous.
|
||||
primary_keymap[name
|
||||
if self.case_sensitive
|
||||
else name.lower()] = rec = (None, obj, None)
|
||||
|
||||
self.keys.append(colname)
|
||||
if obj:
|
||||
for o in obj:
|
||||
keymap[o] = rec
|
||||
# technically we should be doing this but we
|
||||
# are saving on callcounts by not doing so.
|
||||
# if keymap.setdefault(o, rec) is not rec:
|
||||
# keymap[o] = (None, obj, None)
|
||||
|
||||
if translate_colname and \
|
||||
untranslated:
|
||||
keymap[untranslated] = rec
|
||||
|
||||
# overwrite keymap values with those of the
|
||||
# high precedence keymap.
|
||||
keymap.update(primary_keymap)
|
||||
|
||||
if parent._echo:
|
||||
context.engine.logger.debug(
|
||||
"Col %r", tuple(x[0] for x in metadata))
|
||||
|
||||
@util.pending_deprecation("0.8", "sqlite dialect uses "
|
||||
"_translate_colname() now")
|
||||
def _set_keymap_synonym(self, name, origname):
|
||||
"""Set a synonym for the given name.
|
||||
|
||||
Some dialects (SQLite at the moment) may use this to
|
||||
adjust the column names that are significant within a
|
||||
row.
|
||||
|
||||
"""
|
||||
rec = (processor, obj, i) = self._keymap[origname if
|
||||
self.case_sensitive
|
||||
else origname.lower()]
|
||||
if self._keymap.setdefault(name, rec) is not rec:
|
||||
self._keymap[name] = (processor, obj, None)
|
||||
|
||||
def _key_fallback(self, key, raiseerr=True):
|
||||
map = self._keymap
|
||||
result = None
|
||||
if isinstance(key, basestring):
|
||||
result = map.get(key if self.case_sensitive else key.lower())
|
||||
# fallback for targeting a ColumnElement to a textual expression
|
||||
# this is a rare use case which only occurs when matching text()
|
||||
# or colummn('name') constructs to ColumnElements, or after a
|
||||
# pickle/unpickle roundtrip
|
||||
elif isinstance(key, expression.ColumnElement):
|
||||
if key._label and (
|
||||
key._label
|
||||
if self.case_sensitive
|
||||
else key._label.lower()) in map:
|
||||
result = map[key._label
|
||||
if self.case_sensitive
|
||||
else key._label.lower()]
|
||||
elif hasattr(key, 'name') and (
|
||||
key.name
|
||||
if self.case_sensitive
|
||||
else key.name.lower()) in map:
|
||||
# match is only on name.
|
||||
result = map[key.name
|
||||
if self.case_sensitive
|
||||
else key.name.lower()]
|
||||
# search extra hard to make sure this
|
||||
# isn't a column/label name overlap.
|
||||
# this check isn't currently available if the row
|
||||
# was unpickled.
|
||||
if result is not None and \
|
||||
result[1] is not None:
|
||||
for obj in result[1]:
|
||||
if key._compare_name_for_result(obj):
|
||||
break
|
||||
else:
|
||||
result = None
|
||||
if result is None:
|
||||
if raiseerr:
|
||||
raise exc.NoSuchColumnError(
|
||||
"Could not locate column in row for column '%s'" %
|
||||
expression._string_or_unprintable(key))
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
map[key] = result
|
||||
return result
|
||||
|
||||
def _has_key(self, row, key):
|
||||
if key in self._keymap:
|
||||
return True
|
||||
else:
|
||||
return self._key_fallback(key, False) is not None
|
||||
|
||||
def __getstate__(self):
|
||||
return {
|
||||
'_pickled_keymap': dict(
|
||||
(key, index)
|
||||
for key, (processor, obj, index) in self._keymap.iteritems()
|
||||
if isinstance(key, (basestring, int))
|
||||
),
|
||||
'keys': self.keys,
|
||||
"case_sensitive": self.case_sensitive,
|
||||
}
|
||||
|
||||
def __setstate__(self, state):
|
||||
# the row has been processed at pickling time so we don't need any
|
||||
# processor anymore
|
||||
self._processors = [None for _ in xrange(len(state['keys']))]
|
||||
self._keymap = keymap = {}
|
||||
for key, index in state['_pickled_keymap'].iteritems():
|
||||
# not preserving "obj" here, unfortunately our
|
||||
# proxy comparison fails with the unpickle
|
||||
keymap[key] = (None, None, index)
|
||||
self.keys = state['keys']
|
||||
self.case_sensitive = state['case_sensitive']
|
||||
self._echo = False
|
||||
|
||||
|
||||
class ResultProxy(object):
|
||||
"""Wraps a DB-API cursor object to provide easier access to row columns.
|
||||
|
||||
Individual columns may be accessed by their integer position,
|
||||
case-insensitive column name, or by ``schema.Column``
|
||||
object. e.g.::
|
||||
|
||||
row = fetchone()
|
||||
|
||||
col1 = row[0] # access via integer position
|
||||
|
||||
col2 = row['col2'] # access via name
|
||||
|
||||
col3 = row[mytable.c.mycol] # access via Column object.
|
||||
|
||||
``ResultProxy`` also handles post-processing of result column
|
||||
data using ``TypeEngine`` objects, which are referenced from
|
||||
the originating SQL statement that produced this result set.
|
||||
|
||||
"""
|
||||
|
||||
_process_row = RowProxy
|
||||
out_parameters = None
|
||||
_can_close_connection = False
|
||||
_metadata = None
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
self.dialect = context.dialect
|
||||
self.closed = False
|
||||
self.cursor = self._saved_cursor = context.cursor
|
||||
self.connection = context.root_connection
|
||||
self._echo = self.connection._echo and \
|
||||
context.engine._should_log_debug()
|
||||
self._init_metadata()
|
||||
|
||||
def _init_metadata(self):
|
||||
metadata = self._cursor_description()
|
||||
if metadata is not None:
|
||||
self._metadata = ResultMetaData(self, metadata)
|
||||
|
||||
def keys(self):
|
||||
"""Return the current set of string keys for rows."""
|
||||
if self._metadata:
|
||||
return self._metadata.keys
|
||||
else:
|
||||
return []
|
||||
|
||||
@util.memoized_property
|
||||
def rowcount(self):
|
||||
"""Return the 'rowcount' for this result.
|
||||
|
||||
The 'rowcount' reports the number of rows *matched*
|
||||
by the WHERE criterion of an UPDATE or DELETE statement.
|
||||
|
||||
.. note::
|
||||
|
||||
Notes regarding :attr:`.ResultProxy.rowcount`:
|
||||
|
||||
|
||||
* This attribute returns the number of rows *matched*,
|
||||
which is not necessarily the same as the number of rows
|
||||
that were actually *modified* - an UPDATE statement, for example,
|
||||
may have no net change on a given row if the SET values
|
||||
given are the same as those present in the row already.
|
||||
Such a row would be matched but not modified.
|
||||
On backends that feature both styles, such as MySQL,
|
||||
rowcount is configured by default to return the match
|
||||
count in all cases.
|
||||
|
||||
* :attr:`.ResultProxy.rowcount` is *only* useful in conjunction
|
||||
with an UPDATE or DELETE statement. Contrary to what the Python
|
||||
DBAPI says, it does *not* return the
|
||||
number of rows available from the results of a SELECT statement
|
||||
as DBAPIs cannot support this functionality when rows are
|
||||
unbuffered.
|
||||
|
||||
* :attr:`.ResultProxy.rowcount` may not be fully implemented by
|
||||
all dialects. In particular, most DBAPIs do not support an
|
||||
aggregate rowcount result from an executemany call.
|
||||
The :meth:`.ResultProxy.supports_sane_rowcount` and
|
||||
:meth:`.ResultProxy.supports_sane_multi_rowcount` methods
|
||||
will report from the dialect if each usage is known to be
|
||||
supported.
|
||||
|
||||
* Statements that use RETURNING may not return a correct
|
||||
rowcount.
|
||||
|
||||
"""
|
||||
try:
|
||||
return self.context.rowcount
|
||||
except Exception, e:
|
||||
self.connection._handle_dbapi_exception(
|
||||
e, None, None, self.cursor, self.context)
|
||||
|
||||
@property
|
||||
def lastrowid(self):
|
||||
"""return the 'lastrowid' accessor on the DBAPI cursor.
|
||||
|
||||
This is a DBAPI specific method and is only functional
|
||||
for those backends which support it, for statements
|
||||
where it is appropriate. It's behavior is not
|
||||
consistent across backends.
|
||||
|
||||
Usage of this method is normally unnecessary when
|
||||
using insert() expression constructs; the
|
||||
:attr:`~ResultProxy.inserted_primary_key` attribute provides a
|
||||
tuple of primary key values for a newly inserted row,
|
||||
regardless of database backend.
|
||||
|
||||
"""
|
||||
try:
|
||||
return self._saved_cursor.lastrowid
|
||||
except Exception, e:
|
||||
self.connection._handle_dbapi_exception(
|
||||
e, None, None,
|
||||
self._saved_cursor, self.context)
|
||||
|
||||
@property
|
||||
def returns_rows(self):
|
||||
"""True if this :class:`.ResultProxy` returns rows.
|
||||
|
||||
I.e. if it is legal to call the methods
|
||||
:meth:`~.ResultProxy.fetchone`,
|
||||
:meth:`~.ResultProxy.fetchmany`
|
||||
:meth:`~.ResultProxy.fetchall`.
|
||||
|
||||
"""
|
||||
return self._metadata is not None
|
||||
|
||||
@property
|
||||
def is_insert(self):
|
||||
"""True if this :class:`.ResultProxy` is the result
|
||||
of a executing an expression language compiled
|
||||
:func:`.expression.insert` construct.
|
||||
|
||||
When True, this implies that the
|
||||
:attr:`inserted_primary_key` attribute is accessible,
|
||||
assuming the statement did not include
|
||||
a user defined "returning" construct.
|
||||
|
||||
"""
|
||||
return self.context.isinsert
|
||||
|
||||
def _cursor_description(self):
|
||||
"""May be overridden by subclasses."""
|
||||
|
||||
return self._saved_cursor.description
|
||||
|
||||
def close(self, _autoclose_connection=True):
|
||||
"""Close this ResultProxy.
|
||||
|
||||
Closes the underlying DBAPI cursor corresponding to the execution.
|
||||
|
||||
Note that any data cached within this ResultProxy is still available.
|
||||
For some types of results, this may include buffered rows.
|
||||
|
||||
If this ResultProxy was generated from an implicit execution,
|
||||
the underlying Connection will also be closed (returns the
|
||||
underlying DBAPI connection to the connection pool.)
|
||||
|
||||
This method is called automatically when:
|
||||
|
||||
* all result rows are exhausted using the fetchXXX() methods.
|
||||
* cursor.description is None.
|
||||
|
||||
"""
|
||||
|
||||
if not self.closed:
|
||||
self.closed = True
|
||||
self.connection._safe_close_cursor(self.cursor)
|
||||
if _autoclose_connection and \
|
||||
self.connection.should_close_with_result:
|
||||
self.connection.close()
|
||||
# allow consistent errors
|
||||
self.cursor = None
|
||||
|
||||
def __iter__(self):
|
||||
while True:
|
||||
row = self.fetchone()
|
||||
if row is None:
|
||||
raise StopIteration
|
||||
else:
|
||||
yield row
|
||||
|
||||
@util.memoized_property
|
||||
def inserted_primary_key(self):
|
||||
"""Return the primary key for the row just inserted.
|
||||
|
||||
The return value is a list of scalar values
|
||||
corresponding to the list of primary key columns
|
||||
in the target table.
|
||||
|
||||
This only applies to single row :func:`.insert`
|
||||
constructs which did not explicitly specify
|
||||
:meth:`.Insert.returning`.
|
||||
|
||||
Note that primary key columns which specify a
|
||||
server_default clause,
|
||||
or otherwise do not qualify as "autoincrement"
|
||||
columns (see the notes at :class:`.Column`), and were
|
||||
generated using the database-side default, will
|
||||
appear in this list as ``None`` unless the backend
|
||||
supports "returning" and the insert statement executed
|
||||
with the "implicit returning" enabled.
|
||||
|
||||
Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed
|
||||
statement is not a compiled expression construct
|
||||
or is not an insert() construct.
|
||||
|
||||
"""
|
||||
|
||||
if not self.context.compiled:
|
||||
raise exc.InvalidRequestError(
|
||||
"Statement is not a compiled "
|
||||
"expression construct.")
|
||||
elif not self.context.isinsert:
|
||||
raise exc.InvalidRequestError(
|
||||
"Statement is not an insert() "
|
||||
"expression construct.")
|
||||
elif self.context._is_explicit_returning:
|
||||
raise exc.InvalidRequestError(
|
||||
"Can't call inserted_primary_key "
|
||||
"when returning() "
|
||||
"is used.")
|
||||
|
||||
return self.context.inserted_primary_key
|
||||
|
||||
def last_updated_params(self):
|
||||
"""Return the collection of updated parameters from this
|
||||
execution.
|
||||
|
||||
Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed
|
||||
statement is not a compiled expression construct
|
||||
or is not an update() construct.
|
||||
|
||||
"""
|
||||
if not self.context.compiled:
|
||||
raise exc.InvalidRequestError(
|
||||
"Statement is not a compiled "
|
||||
"expression construct.")
|
||||
elif not self.context.isupdate:
|
||||
raise exc.InvalidRequestError(
|
||||
"Statement is not an update() "
|
||||
"expression construct.")
|
||||
elif self.context.executemany:
|
||||
return self.context.compiled_parameters
|
||||
else:
|
||||
return self.context.compiled_parameters[0]
|
||||
|
||||
def last_inserted_params(self):
|
||||
"""Return the collection of inserted parameters from this
|
||||
execution.
|
||||
|
||||
Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed
|
||||
statement is not a compiled expression construct
|
||||
or is not an insert() construct.
|
||||
|
||||
"""
|
||||
if not self.context.compiled:
|
||||
raise exc.InvalidRequestError(
|
||||
"Statement is not a compiled "
|
||||
"expression construct.")
|
||||
elif not self.context.isinsert:
|
||||
raise exc.InvalidRequestError(
|
||||
"Statement is not an insert() "
|
||||
"expression construct.")
|
||||
elif self.context.executemany:
|
||||
return self.context.compiled_parameters
|
||||
else:
|
||||
return self.context.compiled_parameters[0]
|
||||
|
||||
def lastrow_has_defaults(self):
|
||||
"""Return ``lastrow_has_defaults()`` from the underlying
|
||||
:class:`.ExecutionContext`.
|
||||
|
||||
See :class:`.ExecutionContext` for details.
|
||||
|
||||
"""
|
||||
|
||||
return self.context.lastrow_has_defaults()
|
||||
|
||||
def postfetch_cols(self):
|
||||
"""Return ``postfetch_cols()`` from the underlying
|
||||
:class:`.ExecutionContext`.
|
||||
|
||||
See :class:`.ExecutionContext` for details.
|
||||
|
||||
Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed
|
||||
statement is not a compiled expression construct
|
||||
or is not an insert() or update() construct.
|
||||
|
||||
"""
|
||||
|
||||
if not self.context.compiled:
|
||||
raise exc.InvalidRequestError(
|
||||
"Statement is not a compiled "
|
||||
"expression construct.")
|
||||
elif not self.context.isinsert and not self.context.isupdate:
|
||||
raise exc.InvalidRequestError(
|
||||
"Statement is not an insert() or update() "
|
||||
"expression construct.")
|
||||
return self.context.postfetch_cols
|
||||
|
||||
def prefetch_cols(self):
|
||||
"""Return ``prefetch_cols()`` from the underlying
|
||||
:class:`.ExecutionContext`.
|
||||
|
||||
See :class:`.ExecutionContext` for details.
|
||||
|
||||
Raises :class:`~sqlalchemy.exc.InvalidRequestError` if the executed
|
||||
statement is not a compiled expression construct
|
||||
or is not an insert() or update() construct.
|
||||
|
||||
"""
|
||||
|
||||
if not self.context.compiled:
|
||||
raise exc.InvalidRequestError(
|
||||
"Statement is not a compiled "
|
||||
"expression construct.")
|
||||
elif not self.context.isinsert and not self.context.isupdate:
|
||||
raise exc.InvalidRequestError(
|
||||
"Statement is not an insert() or update() "
|
||||
"expression construct.")
|
||||
return self.context.prefetch_cols
|
||||
|
||||
def supports_sane_rowcount(self):
|
||||
"""Return ``supports_sane_rowcount`` from the dialect.
|
||||
|
||||
See :attr:`.ResultProxy.rowcount` for background.
|
||||
|
||||
"""
|
||||
|
||||
return self.dialect.supports_sane_rowcount
|
||||
|
||||
def supports_sane_multi_rowcount(self):
|
||||
"""Return ``supports_sane_multi_rowcount`` from the dialect.
|
||||
|
||||
See :attr:`.ResultProxy.rowcount` for background.
|
||||
|
||||
"""
|
||||
|
||||
return self.dialect.supports_sane_multi_rowcount
|
||||
|
||||
def _fetchone_impl(self):
|
||||
try:
|
||||
return self.cursor.fetchone()
|
||||
except AttributeError:
|
||||
self._non_result()
|
||||
|
||||
def _fetchmany_impl(self, size=None):
|
||||
try:
|
||||
if size is None:
|
||||
return self.cursor.fetchmany()
|
||||
else:
|
||||
return self.cursor.fetchmany(size)
|
||||
except AttributeError:
|
||||
self._non_result()
|
||||
|
||||
def _fetchall_impl(self):
|
||||
try:
|
||||
return self.cursor.fetchall()
|
||||
except AttributeError:
|
||||
self._non_result()
|
||||
|
||||
def _non_result(self):
|
||||
if self._metadata is None:
|
||||
raise exc.ResourceClosedError(
|
||||
"This result object does not return rows. "
|
||||
"It has been closed automatically.",
|
||||
)
|
||||
else:
|
||||
raise exc.ResourceClosedError("This result object is closed.")
|
||||
|
||||
def process_rows(self, rows):
|
||||
process_row = self._process_row
|
||||
metadata = self._metadata
|
||||
keymap = metadata._keymap
|
||||
processors = metadata._processors
|
||||
if self._echo:
|
||||
log = self.context.engine.logger.debug
|
||||
l = []
|
||||
for row in rows:
|
||||
log("Row %r", row)
|
||||
l.append(process_row(metadata, row, processors, keymap))
|
||||
return l
|
||||
else:
|
||||
return [process_row(metadata, row, processors, keymap)
|
||||
for row in rows]
|
||||
|
||||
def fetchall(self):
|
||||
"""Fetch all rows, just like DB-API ``cursor.fetchall()``."""
|
||||
|
||||
try:
|
||||
l = self.process_rows(self._fetchall_impl())
|
||||
self.close()
|
||||
return l
|
||||
except Exception, e:
|
||||
self.connection._handle_dbapi_exception(
|
||||
e, None, None,
|
||||
self.cursor, self.context)
|
||||
|
||||
def fetchmany(self, size=None):
|
||||
"""Fetch many rows, just like DB-API
|
||||
``cursor.fetchmany(size=cursor.arraysize)``.
|
||||
|
||||
If rows are present, the cursor remains open after this is called.
|
||||
Else the cursor is automatically closed and an empty list is returned.
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
l = self.process_rows(self._fetchmany_impl(size))
|
||||
if len(l) == 0:
|
||||
self.close()
|
||||
return l
|
||||
except Exception, e:
|
||||
self.connection._handle_dbapi_exception(
|
||||
e, None, None,
|
||||
self.cursor, self.context)
|
||||
|
||||
def fetchone(self):
|
||||
"""Fetch one row, just like DB-API ``cursor.fetchone()``.
|
||||
|
||||
If a row is present, the cursor remains open after this is called.
|
||||
Else the cursor is automatically closed and None is returned.
|
||||
|
||||
"""
|
||||
try:
|
||||
row = self._fetchone_impl()
|
||||
if row is not None:
|
||||
return self.process_rows([row])[0]
|
||||
else:
|
||||
self.close()
|
||||
return None
|
||||
except Exception, e:
|
||||
self.connection._handle_dbapi_exception(
|
||||
e, None, None,
|
||||
self.cursor, self.context)
|
||||
|
||||
def first(self):
|
||||
"""Fetch the first row and then close the result set unconditionally.
|
||||
|
||||
Returns None if no row is present.
|
||||
|
||||
"""
|
||||
if self._metadata is None:
|
||||
self._non_result()
|
||||
|
||||
try:
|
||||
row = self._fetchone_impl()
|
||||
except Exception, e:
|
||||
self.connection._handle_dbapi_exception(
|
||||
e, None, None,
|
||||
self.cursor, self.context)
|
||||
|
||||
try:
|
||||
if row is not None:
|
||||
return self.process_rows([row])[0]
|
||||
else:
|
||||
return None
|
||||
finally:
|
||||
self.close()
|
||||
|
||||
def scalar(self):
|
||||
"""Fetch the first column of the first row, and close the result set.
|
||||
|
||||
Returns None if no row is present.
|
||||
|
||||
"""
|
||||
row = self.first()
|
||||
if row is not None:
|
||||
return row[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class BufferedRowResultProxy(ResultProxy):
|
||||
"""A ResultProxy with row buffering behavior.
|
||||
|
||||
``ResultProxy`` that buffers the contents of a selection of rows
|
||||
before ``fetchone()`` is called. This is to allow the results of
|
||||
``cursor.description`` to be available immediately, when
|
||||
interfacing with a DB-API that requires rows to be consumed before
|
||||
this information is available (currently psycopg2, when used with
|
||||
server-side cursors).
|
||||
|
||||
The pre-fetching behavior fetches only one row initially, and then
|
||||
grows its buffer size by a fixed amount with each successive need
|
||||
for additional rows up to a size of 100.
|
||||
"""
|
||||
|
||||
def _init_metadata(self):
|
||||
self.__buffer_rows()
|
||||
super(BufferedRowResultProxy, self)._init_metadata()
|
||||
|
||||
# this is a "growth chart" for the buffering of rows.
|
||||
# each successive __buffer_rows call will use the next
|
||||
# value in the list for the buffer size until the max
|
||||
# is reached
|
||||
size_growth = {
|
||||
1: 5,
|
||||
5: 10,
|
||||
10: 20,
|
||||
20: 50,
|
||||
50: 100,
|
||||
100: 250,
|
||||
250: 500,
|
||||
500: 1000
|
||||
}
|
||||
|
||||
def __buffer_rows(self):
|
||||
size = getattr(self, '_bufsize', 1)
|
||||
self.__rowbuffer = collections.deque(self.cursor.fetchmany(size))
|
||||
self._bufsize = self.size_growth.get(size, size)
|
||||
|
||||
def _fetchone_impl(self):
|
||||
if self.closed:
|
||||
return None
|
||||
if not self.__rowbuffer:
|
||||
self.__buffer_rows()
|
||||
if not self.__rowbuffer:
|
||||
return None
|
||||
return self.__rowbuffer.popleft()
|
||||
|
||||
def _fetchmany_impl(self, size=None):
|
||||
if size is None:
|
||||
return self._fetchall_impl()
|
||||
result = []
|
||||
for x in range(0, size):
|
||||
row = self._fetchone_impl()
|
||||
if row is None:
|
||||
break
|
||||
result.append(row)
|
||||
return result
|
||||
|
||||
def _fetchall_impl(self):
|
||||
self.__rowbuffer.extend(self.cursor.fetchall())
|
||||
ret = self.__rowbuffer
|
||||
self.__rowbuffer = collections.deque()
|
||||
return ret
|
||||
|
||||
|
||||
class FullyBufferedResultProxy(ResultProxy):
|
||||
"""A result proxy that buffers rows fully upon creation.
|
||||
|
||||
Used for operations where a result is to be delivered
|
||||
after the database conversation can not be continued,
|
||||
such as MSSQL INSERT...OUTPUT after an autocommit.
|
||||
|
||||
"""
|
||||
def _init_metadata(self):
|
||||
super(FullyBufferedResultProxy, self)._init_metadata()
|
||||
self.__rowbuffer = self._buffer_rows()
|
||||
|
||||
def _buffer_rows(self):
|
||||
return collections.deque(self.cursor.fetchall())
|
||||
|
||||
def _fetchone_impl(self):
|
||||
if self.__rowbuffer:
|
||||
return self.__rowbuffer.popleft()
|
||||
else:
|
||||
return None
|
||||
|
||||
def _fetchmany_impl(self, size=None):
|
||||
if size is None:
|
||||
return self._fetchall_impl()
|
||||
result = []
|
||||
for x in range(0, size):
|
||||
row = self._fetchone_impl()
|
||||
if row is None:
|
||||
break
|
||||
result.append(row)
|
||||
return result
|
||||
|
||||
def _fetchall_impl(self):
|
||||
ret = self.__rowbuffer
|
||||
self.__rowbuffer = collections.deque()
|
||||
return ret
|
||||
|
||||
|
||||
class BufferedColumnRow(RowProxy):
|
||||
def __init__(self, parent, row, processors, keymap):
|
||||
# preprocess row
|
||||
row = list(row)
|
||||
# this is a tad faster than using enumerate
|
||||
index = 0
|
||||
for processor in parent._orig_processors:
|
||||
if processor is not None:
|
||||
row[index] = processor(row[index])
|
||||
index += 1
|
||||
row = tuple(row)
|
||||
super(BufferedColumnRow, self).__init__(parent, row,
|
||||
processors, keymap)
|
||||
|
||||
|
||||
class BufferedColumnResultProxy(ResultProxy):
|
||||
"""A ResultProxy with column buffering behavior.
|
||||
|
||||
``ResultProxy`` that loads all columns into memory each time
|
||||
fetchone() is called. If fetchmany() or fetchall() are called,
|
||||
the full grid of results is fetched. This is to operate with
|
||||
databases where result rows contain "live" results that fall out
|
||||
of scope unless explicitly fetched. Currently this includes
|
||||
cx_Oracle LOB objects.
|
||||
|
||||
"""
|
||||
|
||||
_process_row = BufferedColumnRow
|
||||
|
||||
def _init_metadata(self):
|
||||
super(BufferedColumnResultProxy, self)._init_metadata()
|
||||
metadata = self._metadata
|
||||
# orig_processors will be used to preprocess each row when they are
|
||||
# constructed.
|
||||
metadata._orig_processors = metadata._processors
|
||||
# replace the all type processors by None processors.
|
||||
metadata._processors = [None for _ in xrange(len(metadata.keys))]
|
||||
keymap = {}
|
||||
for k, (func, obj, index) in metadata._keymap.iteritems():
|
||||
keymap[k] = (None, obj, index)
|
||||
self._metadata._keymap = keymap
|
||||
|
||||
def fetchall(self):
|
||||
# can't call cursor.fetchall(), since rows must be
|
||||
# fully processed before requesting more from the DBAPI.
|
||||
l = []
|
||||
while True:
|
||||
row = self.fetchone()
|
||||
if row is None:
|
||||
break
|
||||
l.append(row)
|
||||
return l
|
||||
|
||||
def fetchmany(self, size=None):
|
||||
# can't call cursor.fetchmany(), since rows must be
|
||||
# fully processed before requesting more from the DBAPI.
|
||||
if size is None:
|
||||
return self.fetchall()
|
||||
l = []
|
||||
for i in xrange(size):
|
||||
row = self.fetchone()
|
||||
if row is None:
|
||||
break
|
||||
l.append(row)
|
||||
return l
|
||||
@@ -0,0 +1,261 @@
|
||||
# engine/strategies.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Strategies for creating new instances of Engine types.
|
||||
|
||||
These are semi-private implementation classes which provide the
|
||||
underlying behavior for the "strategy" keyword argument available on
|
||||
:func:`~sqlalchemy.engine.create_engine`. Current available options are
|
||||
``plain``, ``threadlocal``, and ``mock``.
|
||||
|
||||
New strategies can be added via new ``EngineStrategy`` classes.
|
||||
"""
|
||||
|
||||
from operator import attrgetter
|
||||
|
||||
from sqlalchemy.engine import base, threadlocal, url
|
||||
from sqlalchemy import util, exc, event
|
||||
from sqlalchemy import pool as poollib
|
||||
|
||||
strategies = {}
|
||||
|
||||
|
||||
class EngineStrategy(object):
|
||||
"""An adaptor that processes input arguments and produces an Engine.
|
||||
|
||||
Provides a ``create`` method that receives input arguments and
|
||||
produces an instance of base.Engine or a subclass.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
strategies[self.name] = self
|
||||
|
||||
def create(self, *args, **kwargs):
|
||||
"""Given arguments, returns a new Engine instance."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class DefaultEngineStrategy(EngineStrategy):
|
||||
"""Base class for built-in strategies."""
|
||||
|
||||
def create(self, name_or_url, **kwargs):
|
||||
# create url.URL object
|
||||
u = url.make_url(name_or_url)
|
||||
|
||||
dialect_cls = u.get_dialect()
|
||||
|
||||
dialect_args = {}
|
||||
# consume dialect arguments from kwargs
|
||||
for k in util.get_cls_kwargs(dialect_cls):
|
||||
if k in kwargs:
|
||||
dialect_args[k] = kwargs.pop(k)
|
||||
|
||||
dbapi = kwargs.pop('module', None)
|
||||
if dbapi is None:
|
||||
dbapi_args = {}
|
||||
for k in util.get_func_kwargs(dialect_cls.dbapi):
|
||||
if k in kwargs:
|
||||
dbapi_args[k] = kwargs.pop(k)
|
||||
dbapi = dialect_cls.dbapi(**dbapi_args)
|
||||
|
||||
dialect_args['dbapi'] = dbapi
|
||||
|
||||
# create dialect
|
||||
dialect = dialect_cls(**dialect_args)
|
||||
|
||||
# assemble connection arguments
|
||||
(cargs, cparams) = dialect.create_connect_args(u)
|
||||
cparams.update(kwargs.pop('connect_args', {}))
|
||||
|
||||
# look for existing pool or create
|
||||
pool = kwargs.pop('pool', None)
|
||||
if pool is None:
|
||||
def connect():
|
||||
try:
|
||||
return dialect.connect(*cargs, **cparams)
|
||||
except Exception, e:
|
||||
invalidated = dialect.is_disconnect(e, None, None)
|
||||
# Py3K
|
||||
#raise exc.DBAPIError.instance(None, None,
|
||||
# e, dialect.dbapi.Error,
|
||||
# connection_invalidated=invalidated
|
||||
#) from e
|
||||
# Py2K
|
||||
import sys
|
||||
raise exc.DBAPIError.instance(
|
||||
None, None, e, dialect.dbapi.Error,
|
||||
connection_invalidated=invalidated
|
||||
), None, sys.exc_info()[2]
|
||||
# end Py2K
|
||||
|
||||
creator = kwargs.pop('creator', connect)
|
||||
|
||||
poolclass = kwargs.pop('poolclass', None)
|
||||
if poolclass is None:
|
||||
poolclass = dialect_cls.get_pool_class(u)
|
||||
pool_args = {}
|
||||
|
||||
# consume pool arguments from kwargs, translating a few of
|
||||
# the arguments
|
||||
translate = {'logging_name': 'pool_logging_name',
|
||||
'echo': 'echo_pool',
|
||||
'timeout': 'pool_timeout',
|
||||
'recycle': 'pool_recycle',
|
||||
'events': 'pool_events',
|
||||
'use_threadlocal': 'pool_threadlocal',
|
||||
'reset_on_return': 'pool_reset_on_return'}
|
||||
for k in util.get_cls_kwargs(poolclass):
|
||||
tk = translate.get(k, k)
|
||||
if tk in kwargs:
|
||||
pool_args[k] = kwargs.pop(tk)
|
||||
pool = poolclass(creator, **pool_args)
|
||||
else:
|
||||
if isinstance(pool, poollib._DBProxy):
|
||||
pool = pool.get_pool(*cargs, **cparams)
|
||||
else:
|
||||
pool = pool
|
||||
|
||||
# create engine.
|
||||
engineclass = self.engine_cls
|
||||
engine_args = {}
|
||||
for k in util.get_cls_kwargs(engineclass):
|
||||
if k in kwargs:
|
||||
engine_args[k] = kwargs.pop(k)
|
||||
|
||||
_initialize = kwargs.pop('_initialize', True)
|
||||
|
||||
# all kwargs should be consumed
|
||||
if kwargs:
|
||||
raise TypeError(
|
||||
"Invalid argument(s) %s sent to create_engine(), "
|
||||
"using configuration %s/%s/%s. Please check that the "
|
||||
"keyword arguments are appropriate for this combination "
|
||||
"of components." % (','.join("'%s'" % k for k in kwargs),
|
||||
dialect.__class__.__name__,
|
||||
pool.__class__.__name__,
|
||||
engineclass.__name__))
|
||||
|
||||
engine = engineclass(pool, dialect, u, **engine_args)
|
||||
|
||||
if _initialize:
|
||||
do_on_connect = dialect.on_connect()
|
||||
if do_on_connect:
|
||||
def on_connect(dbapi_connection, connection_record):
|
||||
conn = getattr(
|
||||
dbapi_connection, '_sqla_unwrap', dbapi_connection)
|
||||
if conn is None:
|
||||
return
|
||||
do_on_connect(conn)
|
||||
|
||||
event.listen(pool, 'first_connect', on_connect)
|
||||
event.listen(pool, 'connect', on_connect)
|
||||
|
||||
@util.only_once
|
||||
def first_connect(dbapi_connection, connection_record):
|
||||
c = base.Connection(engine, connection=dbapi_connection)
|
||||
|
||||
# TODO: removing this allows the on connect activities
|
||||
# to generate events. tests currently assume these aren't
|
||||
# sent. do we want users to get all the initial connect
|
||||
# activities as events ?
|
||||
c._has_events = False
|
||||
|
||||
dialect.initialize(c)
|
||||
event.listen(pool, 'first_connect', first_connect)
|
||||
|
||||
return engine
|
||||
|
||||
|
||||
class PlainEngineStrategy(DefaultEngineStrategy):
|
||||
"""Strategy for configuring a regular Engine."""
|
||||
|
||||
name = 'plain'
|
||||
engine_cls = base.Engine
|
||||
|
||||
PlainEngineStrategy()
|
||||
|
||||
|
||||
class ThreadLocalEngineStrategy(DefaultEngineStrategy):
|
||||
"""Strategy for configuring an Engine with threadlocal behavior."""
|
||||
|
||||
name = 'threadlocal'
|
||||
engine_cls = threadlocal.TLEngine
|
||||
|
||||
ThreadLocalEngineStrategy()
|
||||
|
||||
|
||||
class MockEngineStrategy(EngineStrategy):
|
||||
"""Strategy for configuring an Engine-like object with mocked execution.
|
||||
|
||||
Produces a single mock Connectable object which dispatches
|
||||
statement execution to a passed-in function.
|
||||
|
||||
"""
|
||||
|
||||
name = 'mock'
|
||||
|
||||
def create(self, name_or_url, executor, **kwargs):
|
||||
# create url.URL object
|
||||
u = url.make_url(name_or_url)
|
||||
|
||||
dialect_cls = u.get_dialect()
|
||||
|
||||
dialect_args = {}
|
||||
# consume dialect arguments from kwargs
|
||||
for k in util.get_cls_kwargs(dialect_cls):
|
||||
if k in kwargs:
|
||||
dialect_args[k] = kwargs.pop(k)
|
||||
|
||||
# create dialect
|
||||
dialect = dialect_cls(**dialect_args)
|
||||
|
||||
return MockEngineStrategy.MockConnection(dialect, executor)
|
||||
|
||||
class MockConnection(base.Connectable):
|
||||
def __init__(self, dialect, execute):
|
||||
self._dialect = dialect
|
||||
self.execute = execute
|
||||
|
||||
engine = property(lambda s: s)
|
||||
dialect = property(attrgetter('_dialect'))
|
||||
name = property(lambda s: s._dialect.name)
|
||||
|
||||
def contextual_connect(self, **kwargs):
|
||||
return self
|
||||
|
||||
def execution_options(self, **kw):
|
||||
return self
|
||||
|
||||
def compiler(self, statement, parameters, **kwargs):
|
||||
return self._dialect.compiler(
|
||||
statement, parameters, engine=self, **kwargs)
|
||||
|
||||
def create(self, entity, **kwargs):
|
||||
kwargs['checkfirst'] = False
|
||||
from sqlalchemy.engine import ddl
|
||||
|
||||
ddl.SchemaGenerator(
|
||||
self.dialect, self, **kwargs).traverse_single(entity)
|
||||
|
||||
def drop(self, entity, **kwargs):
|
||||
kwargs['checkfirst'] = False
|
||||
from sqlalchemy.engine import ddl
|
||||
ddl.SchemaDropper(
|
||||
self.dialect, self, **kwargs).traverse_single(entity)
|
||||
|
||||
def _run_visitor(self, visitorcallable, element,
|
||||
connection=None,
|
||||
**kwargs):
|
||||
kwargs['checkfirst'] = False
|
||||
visitorcallable(self.dialect, self,
|
||||
**kwargs).traverse_single(element)
|
||||
|
||||
def execute(self, object, *multiparams, **params):
|
||||
raise NotImplementedError()
|
||||
|
||||
MockEngineStrategy()
|
||||
@@ -0,0 +1,134 @@
|
||||
# engine/threadlocal.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Provides a thread-local transactional wrapper around the root Engine class.
|
||||
|
||||
The ``threadlocal`` module is invoked when using the
|
||||
``strategy="threadlocal"`` flag with :func:`~sqlalchemy.engine.create_engine`.
|
||||
This module is semi-private and is invoked automatically when the threadlocal
|
||||
engine strategy is used.
|
||||
"""
|
||||
|
||||
from .. import util
|
||||
from . import base
|
||||
import weakref
|
||||
|
||||
|
||||
class TLConnection(base.Connection):
|
||||
|
||||
def __init__(self, *arg, **kw):
|
||||
super(TLConnection, self).__init__(*arg, **kw)
|
||||
self.__opencount = 0
|
||||
|
||||
def _increment_connect(self):
|
||||
self.__opencount += 1
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
if self.__opencount == 1:
|
||||
base.Connection.close(self)
|
||||
self.__opencount -= 1
|
||||
|
||||
def _force_close(self):
|
||||
self.__opencount = 0
|
||||
base.Connection.close(self)
|
||||
|
||||
|
||||
class TLEngine(base.Engine):
|
||||
"""An Engine that includes support for thread-local managed
|
||||
transactions.
|
||||
|
||||
"""
|
||||
_tl_connection_cls = TLConnection
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TLEngine, self).__init__(*args, **kwargs)
|
||||
self._connections = util.threading.local()
|
||||
|
||||
def contextual_connect(self, **kw):
|
||||
if not hasattr(self._connections, 'conn'):
|
||||
connection = None
|
||||
else:
|
||||
connection = self._connections.conn()
|
||||
|
||||
if connection is None or connection.closed:
|
||||
# guards against pool-level reapers, if desired.
|
||||
# or not connection.connection.is_valid:
|
||||
connection = self._tl_connection_cls(
|
||||
self, self.pool.connect(), **kw)
|
||||
self._connections.conn = weakref.ref(connection)
|
||||
|
||||
return connection._increment_connect()
|
||||
|
||||
def begin_twophase(self, xid=None):
|
||||
if not hasattr(self._connections, 'trans'):
|
||||
self._connections.trans = []
|
||||
self._connections.trans.append(
|
||||
self.contextual_connect().begin_twophase(xid=xid))
|
||||
return self
|
||||
|
||||
def begin_nested(self):
|
||||
if not hasattr(self._connections, 'trans'):
|
||||
self._connections.trans = []
|
||||
self._connections.trans.append(
|
||||
self.contextual_connect().begin_nested())
|
||||
return self
|
||||
|
||||
def begin(self):
|
||||
if not hasattr(self._connections, 'trans'):
|
||||
self._connections.trans = []
|
||||
self._connections.trans.append(self.contextual_connect().begin())
|
||||
return self
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
if type is None:
|
||||
self.commit()
|
||||
else:
|
||||
self.rollback()
|
||||
|
||||
def prepare(self):
|
||||
if not hasattr(self._connections, 'trans') or \
|
||||
not self._connections.trans:
|
||||
return
|
||||
self._connections.trans[-1].prepare()
|
||||
|
||||
def commit(self):
|
||||
if not hasattr(self._connections, 'trans') or \
|
||||
not self._connections.trans:
|
||||
return
|
||||
trans = self._connections.trans.pop(-1)
|
||||
trans.commit()
|
||||
|
||||
def rollback(self):
|
||||
if not hasattr(self._connections, 'trans') or \
|
||||
not self._connections.trans:
|
||||
return
|
||||
trans = self._connections.trans.pop(-1)
|
||||
trans.rollback()
|
||||
|
||||
def dispose(self):
|
||||
self._connections = util.threading.local()
|
||||
super(TLEngine, self).dispose()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return not hasattr(self._connections, 'conn') or \
|
||||
self._connections.conn() is None or \
|
||||
self._connections.conn().closed
|
||||
|
||||
def close(self):
|
||||
if not self.closed:
|
||||
self.contextual_connect().close()
|
||||
connection = self._connections.conn()
|
||||
connection._force_close()
|
||||
del self._connections.conn
|
||||
self._connections.trans = []
|
||||
|
||||
def __repr__(self):
|
||||
return 'TLEngine(%s)' % str(self.url)
|
||||
@@ -0,0 +1,222 @@
|
||||
# engine/url.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Provides the :class:`~sqlalchemy.engine.url.URL` class which encapsulates
|
||||
information about a database connection specification.
|
||||
|
||||
The URL object is created automatically when
|
||||
:func:`~sqlalchemy.engine.create_engine` is called with a string
|
||||
argument; alternatively, the URL is a public-facing construct which can
|
||||
be used directly and is also accepted directly by ``create_engine()``.
|
||||
"""
|
||||
|
||||
import re
|
||||
import urllib
|
||||
from .. import exc, util
|
||||
from . import Dialect
|
||||
|
||||
|
||||
class URL(object):
|
||||
"""
|
||||
Represent the components of a URL used to connect to a database.
|
||||
|
||||
This object is suitable to be passed directly to a
|
||||
``create_engine()`` call. The fields of the URL are parsed from a
|
||||
string by the ``module-level make_url()`` function. the string
|
||||
format of the URL is an RFC-1738-style string.
|
||||
|
||||
All initialization parameters are available as public attributes.
|
||||
|
||||
:param drivername: the name of the database backend.
|
||||
This name will correspond to a module in sqlalchemy/databases
|
||||
or a third party plug-in.
|
||||
|
||||
:param username: The user name.
|
||||
|
||||
:param password: database password.
|
||||
|
||||
:param host: The name of the host.
|
||||
|
||||
:param port: The port number.
|
||||
|
||||
:param database: The database name.
|
||||
|
||||
:param query: A dictionary of options to be passed to the
|
||||
dialect and/or the DBAPI upon connect.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, drivername, username=None, password=None,
|
||||
host=None, port=None, database=None, query=None):
|
||||
self.drivername = drivername
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.host = host
|
||||
if port is not None:
|
||||
self.port = int(port)
|
||||
else:
|
||||
self.port = None
|
||||
self.database = database
|
||||
self.query = query or {}
|
||||
|
||||
def __to_string__(self, hide_password=True):
|
||||
s = self.drivername + "://"
|
||||
if self.username is not None:
|
||||
s += self.username
|
||||
if self.password is not None:
|
||||
s += ':' + ('***' if hide_password
|
||||
else urllib.quote_plus(self.password))
|
||||
s += "@"
|
||||
if self.host is not None:
|
||||
if ':' in self.host:
|
||||
s += "[%s]" % self.host
|
||||
else:
|
||||
s += self.host
|
||||
if self.port is not None:
|
||||
s += ':' + str(self.port)
|
||||
if self.database is not None:
|
||||
s += '/' + self.database
|
||||
if self.query:
|
||||
keys = self.query.keys()
|
||||
keys.sort()
|
||||
s += '?' + "&".join("%s=%s" % (k, self.query[k]) for k in keys)
|
||||
return s
|
||||
|
||||
def __str__(self):
|
||||
return self.__to_string__(hide_password=False)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__to_string__()
|
||||
|
||||
def __hash__(self):
|
||||
return hash(str(self))
|
||||
|
||||
def __eq__(self, other):
|
||||
return \
|
||||
isinstance(other, URL) and \
|
||||
self.drivername == other.drivername and \
|
||||
self.username == other.username and \
|
||||
self.password == other.password and \
|
||||
self.host == other.host and \
|
||||
self.database == other.database and \
|
||||
self.query == other.query
|
||||
|
||||
def get_dialect(self):
|
||||
"""Return the SQLAlchemy database dialect class corresponding
|
||||
to this URL's driver name.
|
||||
"""
|
||||
|
||||
if '+' not in self.drivername:
|
||||
name = self.drivername
|
||||
else:
|
||||
name = self.drivername.replace('+', '.')
|
||||
from sqlalchemy.dialects import registry
|
||||
cls = registry.load(name)
|
||||
# check for legacy dialects that
|
||||
# would return a module with 'dialect' as the
|
||||
# actual class
|
||||
if hasattr(cls, 'dialect') and \
|
||||
isinstance(cls.dialect, type) and \
|
||||
issubclass(cls.dialect, Dialect):
|
||||
return cls.dialect
|
||||
else:
|
||||
return cls
|
||||
|
||||
def translate_connect_args(self, names=[], **kw):
|
||||
"""Translate url attributes into a dictionary of connection arguments.
|
||||
|
||||
Returns attributes of this url (`host`, `database`, `username`,
|
||||
`password`, `port`) as a plain dictionary. The attribute names are
|
||||
used as the keys by default. Unset or false attributes are omitted
|
||||
from the final dictionary.
|
||||
|
||||
:param \**kw: Optional, alternate key names for url attributes.
|
||||
|
||||
:param names: Deprecated. Same purpose as the keyword-based alternate
|
||||
names, but correlates the name to the original positionally.
|
||||
"""
|
||||
|
||||
translated = {}
|
||||
attribute_names = ['host', 'database', 'username', 'password', 'port']
|
||||
for sname in attribute_names:
|
||||
if names:
|
||||
name = names.pop(0)
|
||||
elif sname in kw:
|
||||
name = kw[sname]
|
||||
else:
|
||||
name = sname
|
||||
if name is not None and getattr(self, sname, False):
|
||||
translated[name] = getattr(self, sname)
|
||||
return translated
|
||||
|
||||
|
||||
def make_url(name_or_url):
|
||||
"""Given a string or unicode instance, produce a new URL instance.
|
||||
|
||||
The given string is parsed according to the RFC 1738 spec. If an
|
||||
existing URL object is passed, just returns the object.
|
||||
"""
|
||||
|
||||
if isinstance(name_or_url, basestring):
|
||||
return _parse_rfc1738_args(name_or_url)
|
||||
else:
|
||||
return name_or_url
|
||||
|
||||
|
||||
def _parse_rfc1738_args(name):
|
||||
pattern = re.compile(r'''
|
||||
(?P<name>[\w\+]+)://
|
||||
(?:
|
||||
(?P<username>[^:/]*)
|
||||
(?::(?P<password>[^/]*))?
|
||||
@)?
|
||||
(?:
|
||||
(?:
|
||||
\[(?P<ipv6host>[^/]+)\] |
|
||||
(?P<ipv4host>[^/:]+)
|
||||
)?
|
||||
(?::(?P<port>[^/]*))?
|
||||
)?
|
||||
(?:/(?P<database>.*))?
|
||||
''', re.X)
|
||||
|
||||
m = pattern.match(name)
|
||||
if m is not None:
|
||||
components = m.groupdict()
|
||||
if components['database'] is not None:
|
||||
tokens = components['database'].split('?', 2)
|
||||
components['database'] = tokens[0]
|
||||
query = (len(tokens) > 1 and dict(util.parse_qsl(tokens[1]))) or None
|
||||
# Py2K
|
||||
if query is not None:
|
||||
query = dict((k.encode('ascii'), query[k]) for k in query)
|
||||
# end Py2K
|
||||
else:
|
||||
query = None
|
||||
components['query'] = query
|
||||
|
||||
if components['password'] is not None:
|
||||
components['password'] = \
|
||||
urllib.unquote_plus(components['password'])
|
||||
|
||||
ipv4host = components.pop('ipv4host')
|
||||
ipv6host = components.pop('ipv6host')
|
||||
components['host'] = ipv4host or ipv6host
|
||||
name = components.pop('name')
|
||||
return URL(name, **components)
|
||||
else:
|
||||
raise exc.ArgumentError(
|
||||
"Could not parse rfc1738 URL from string '%s'" % name)
|
||||
|
||||
|
||||
def _parse_keyvalue_args(name):
|
||||
m = re.match(r'(\w+)://(.*)', name)
|
||||
if m is not None:
|
||||
(name, args) = m.group(1, 2)
|
||||
opts = dict(util.parse_qsl(args))
|
||||
return URL(name, *opts)
|
||||
else:
|
||||
return None
|
||||
@@ -0,0 +1,94 @@
|
||||
# engine/util.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from .. import util
|
||||
|
||||
|
||||
def _coerce_config(configuration, prefix):
|
||||
"""Convert configuration values to expected types."""
|
||||
|
||||
options = dict((key[len(prefix):], configuration[key])
|
||||
for key in configuration
|
||||
if key.startswith(prefix))
|
||||
for option, type_ in (
|
||||
('convert_unicode', util.bool_or_str('force')),
|
||||
('pool_timeout', int),
|
||||
('echo', util.bool_or_str('debug')),
|
||||
('echo_pool', util.bool_or_str('debug')),
|
||||
('pool_recycle', int),
|
||||
('pool_size', int),
|
||||
('max_overflow', int),
|
||||
('pool_threadlocal', bool),
|
||||
('use_native_unicode', bool),
|
||||
):
|
||||
util.coerce_kw_type(options, option, type_)
|
||||
return options
|
||||
|
||||
|
||||
def connection_memoize(key):
|
||||
"""Decorator, memoize a function in a connection.info stash.
|
||||
|
||||
Only applicable to functions which take no arguments other than a
|
||||
connection. The memo will be stored in ``connection.info[key]``.
|
||||
"""
|
||||
|
||||
@util.decorator
|
||||
def decorated(fn, self, connection):
|
||||
connection = connection.connect()
|
||||
try:
|
||||
return connection.info[key]
|
||||
except KeyError:
|
||||
connection.info[key] = val = fn(self, connection)
|
||||
return val
|
||||
|
||||
return decorated
|
||||
|
||||
|
||||
def py_fallback():
|
||||
def _distill_params(multiparams, params):
|
||||
"""Given arguments from the calling form *multiparams, **params,
|
||||
return a list of bind parameter structures, usually a list of
|
||||
dictionaries.
|
||||
|
||||
In the case of 'raw' execution which accepts positional parameters,
|
||||
it may be a list of tuples or lists.
|
||||
|
||||
"""
|
||||
|
||||
if not multiparams:
|
||||
if params:
|
||||
return [params]
|
||||
else:
|
||||
return []
|
||||
elif len(multiparams) == 1:
|
||||
zero = multiparams[0]
|
||||
if isinstance(zero, (list, tuple)):
|
||||
if not zero or hasattr(zero[0], '__iter__') and \
|
||||
not hasattr(zero[0], 'strip'):
|
||||
# execute(stmt, [{}, {}, {}, ...])
|
||||
# execute(stmt, [(), (), (), ...])
|
||||
return zero
|
||||
else:
|
||||
# execute(stmt, ("value", "value"))
|
||||
return [zero]
|
||||
elif hasattr(zero, 'keys'):
|
||||
# execute(stmt, {"key":"value"})
|
||||
return [zero]
|
||||
else:
|
||||
# execute(stmt, "value")
|
||||
return [[zero]]
|
||||
else:
|
||||
if hasattr(multiparams[0], '__iter__') and \
|
||||
not hasattr(multiparams[0], 'strip'):
|
||||
return multiparams
|
||||
else:
|
||||
return [multiparams]
|
||||
|
||||
return locals()
|
||||
try:
|
||||
from sqlalchemy.cutils import _distill_params
|
||||
except ImportError:
|
||||
globals().update(py_fallback())
|
||||
@@ -0,0 +1,555 @@
|
||||
# sqlalchemy/event.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Base event API."""
|
||||
|
||||
from . import util, exc
|
||||
from itertools import chain
|
||||
import weakref
|
||||
|
||||
CANCEL = util.symbol('CANCEL')
|
||||
NO_RETVAL = util.symbol('NO_RETVAL')
|
||||
|
||||
|
||||
def listen(target, identifier, fn, *args, **kw):
|
||||
"""Register a listener function for the given target.
|
||||
|
||||
e.g.::
|
||||
|
||||
from sqlalchemy import event
|
||||
from sqlalchemy.schema import UniqueConstraint
|
||||
|
||||
def unique_constraint_name(const, table):
|
||||
const.name = "uq_%s_%s" % (
|
||||
table.name,
|
||||
list(const.columns)[0].name
|
||||
)
|
||||
event.listen(
|
||||
UniqueConstraint,
|
||||
"after_parent_attach",
|
||||
unique_constraint_name)
|
||||
|
||||
"""
|
||||
|
||||
for evt_cls in _registrars[identifier]:
|
||||
tgt = evt_cls._accept_with(target)
|
||||
if tgt is not None:
|
||||
tgt.dispatch._listen(tgt, identifier, fn, *args, **kw)
|
||||
return
|
||||
raise exc.InvalidRequestError("No such event '%s' for target '%s'" %
|
||||
(identifier, target))
|
||||
|
||||
|
||||
def listens_for(target, identifier, *args, **kw):
|
||||
"""Decorate a function as a listener for the given target + identifier.
|
||||
|
||||
e.g.::
|
||||
|
||||
from sqlalchemy import event
|
||||
from sqlalchemy.schema import UniqueConstraint
|
||||
|
||||
@event.listens_for(UniqueConstraint, "after_parent_attach")
|
||||
def unique_constraint_name(const, table):
|
||||
const.name = "uq_%s_%s" % (
|
||||
table.name,
|
||||
list(const.columns)[0].name
|
||||
)
|
||||
"""
|
||||
def decorate(fn):
|
||||
listen(target, identifier, fn, *args, **kw)
|
||||
return fn
|
||||
return decorate
|
||||
|
||||
|
||||
def remove(target, identifier, fn):
|
||||
"""Remove an event listener.
|
||||
|
||||
Note that some event removals, particularly for those event dispatchers
|
||||
which create wrapper functions and secondary even listeners, may not yet
|
||||
be supported.
|
||||
|
||||
"""
|
||||
for evt_cls in _registrars[identifier]:
|
||||
for tgt in evt_cls._accept_with(target):
|
||||
tgt.dispatch._remove(identifier, tgt, fn)
|
||||
return
|
||||
|
||||
_registrars = util.defaultdict(list)
|
||||
|
||||
|
||||
def _is_event_name(name):
|
||||
return not name.startswith('_') and name != 'dispatch'
|
||||
|
||||
|
||||
class _UnpickleDispatch(object):
|
||||
"""Serializable callable that re-generates an instance of
|
||||
:class:`_Dispatch` given a particular :class:`.Events` subclass.
|
||||
|
||||
"""
|
||||
def __call__(self, _parent_cls):
|
||||
for cls in _parent_cls.__mro__:
|
||||
if 'dispatch' in cls.__dict__:
|
||||
return cls.__dict__['dispatch'].dispatch_cls(_parent_cls)
|
||||
else:
|
||||
raise AttributeError("No class with a 'dispatch' member present.")
|
||||
|
||||
|
||||
class _Dispatch(object):
|
||||
"""Mirror the event listening definitions of an Events class with
|
||||
listener collections.
|
||||
|
||||
Classes which define a "dispatch" member will return a
|
||||
non-instantiated :class:`._Dispatch` subclass when the member
|
||||
is accessed at the class level. When the "dispatch" member is
|
||||
accessed at the instance level of its owner, an instance
|
||||
of the :class:`._Dispatch` class is returned.
|
||||
|
||||
A :class:`._Dispatch` class is generated for each :class:`.Events`
|
||||
class defined, by the :func:`._create_dispatcher_class` function.
|
||||
The original :class:`.Events` classes remain untouched.
|
||||
This decouples the construction of :class:`.Events` subclasses from
|
||||
the implementation used by the event internals, and allows
|
||||
inspecting tools like Sphinx to work in an unsurprising
|
||||
way against the public API.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, _parent_cls):
|
||||
self._parent_cls = _parent_cls
|
||||
|
||||
def _join(self, other):
|
||||
"""Create a 'join' of this :class:`._Dispatch` and another.
|
||||
|
||||
This new dispatcher will dispatch events to both
|
||||
:class:`._Dispatch` objects.
|
||||
|
||||
Once constructed, the joined dispatch will respond to new events
|
||||
added to this dispatcher, but may not be aware of events
|
||||
added to the other dispatcher after creation of the join. This is
|
||||
currently for performance reasons so that both dispatchers need
|
||||
not be "evaluated" fully on each call.
|
||||
|
||||
"""
|
||||
if '_joined_dispatch_cls' not in self.__class__.__dict__:
|
||||
cls = type(
|
||||
"Joined%s" % self.__class__.__name__,
|
||||
(_JoinedDispatcher, self.__class__), {}
|
||||
)
|
||||
for ls in _event_descriptors(self):
|
||||
setattr(cls, ls.name, _JoinedDispatchDescriptor(ls.name))
|
||||
|
||||
self.__class__._joined_dispatch_cls = cls
|
||||
return self._joined_dispatch_cls(self, other)
|
||||
|
||||
def __reduce__(self):
|
||||
return _UnpickleDispatch(), (self._parent_cls, )
|
||||
|
||||
def _update(self, other, only_propagate=True):
|
||||
"""Populate from the listeners in another :class:`_Dispatch`
|
||||
object."""
|
||||
|
||||
for ls in _event_descriptors(other):
|
||||
getattr(self, ls.name).\
|
||||
for_modify(self)._update(ls, only_propagate=only_propagate)
|
||||
|
||||
@util.hybridmethod
|
||||
def _clear(self):
|
||||
for attr in dir(self):
|
||||
if _is_event_name(attr):
|
||||
getattr(self, attr).for_modify(self).clear()
|
||||
|
||||
|
||||
def _event_descriptors(target):
|
||||
return [getattr(target, k) for k in dir(target) if _is_event_name(k)]
|
||||
|
||||
|
||||
class _EventMeta(type):
|
||||
"""Intercept new Event subclasses and create
|
||||
associated _Dispatch classes."""
|
||||
|
||||
def __init__(cls, classname, bases, dict_):
|
||||
_create_dispatcher_class(cls, classname, bases, dict_)
|
||||
return type.__init__(cls, classname, bases, dict_)
|
||||
|
||||
|
||||
def _create_dispatcher_class(cls, classname, bases, dict_):
|
||||
"""Create a :class:`._Dispatch` class corresponding to an
|
||||
:class:`.Events` class."""
|
||||
|
||||
# there's all kinds of ways to do this,
|
||||
# i.e. make a Dispatch class that shares the '_listen' method
|
||||
# of the Event class, this is the straight monkeypatch.
|
||||
dispatch_base = getattr(cls, 'dispatch', _Dispatch)
|
||||
cls.dispatch = dispatch_cls = type("%sDispatch" % classname,
|
||||
(dispatch_base, ), {})
|
||||
dispatch_cls._listen = cls._listen
|
||||
|
||||
for k in dict_:
|
||||
if _is_event_name(k):
|
||||
setattr(dispatch_cls, k, _DispatchDescriptor(dict_[k]))
|
||||
_registrars[k].append(cls)
|
||||
|
||||
|
||||
def _remove_dispatcher(cls):
|
||||
for k in dir(cls):
|
||||
if _is_event_name(k):
|
||||
_registrars[k].remove(cls)
|
||||
if not _registrars[k]:
|
||||
del _registrars[k]
|
||||
|
||||
|
||||
class Events(object):
|
||||
"""Define event listening functions for a particular target type."""
|
||||
|
||||
__metaclass__ = _EventMeta
|
||||
|
||||
@classmethod
|
||||
def _accept_with(cls, target):
|
||||
# Mapper, ClassManager, Session override this to
|
||||
# also accept classes, scoped_sessions, sessionmakers, etc.
|
||||
if hasattr(target, 'dispatch') and (
|
||||
isinstance(target.dispatch, cls.dispatch) or \
|
||||
isinstance(target.dispatch, type) and \
|
||||
issubclass(target.dispatch, cls.dispatch)
|
||||
):
|
||||
return target
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _listen(cls, target, identifier, fn, propagate=False, insert=False):
|
||||
if insert:
|
||||
getattr(target.dispatch, identifier).\
|
||||
for_modify(target.dispatch).insert(fn, target, propagate)
|
||||
else:
|
||||
getattr(target.dispatch, identifier).\
|
||||
for_modify(target.dispatch).append(fn, target, propagate)
|
||||
|
||||
@classmethod
|
||||
def _remove(cls, target, identifier, fn):
|
||||
getattr(target.dispatch, identifier).remove(fn, target)
|
||||
|
||||
@classmethod
|
||||
def _clear(cls):
|
||||
cls.dispatch._clear()
|
||||
|
||||
|
||||
class _DispatchDescriptor(object):
|
||||
"""Class-level attributes on :class:`._Dispatch` classes."""
|
||||
|
||||
def __init__(self, fn):
|
||||
self.__name__ = fn.__name__
|
||||
self.__doc__ = fn.__doc__
|
||||
self._clslevel = weakref.WeakKeyDictionary()
|
||||
self._empty_listeners = weakref.WeakKeyDictionary()
|
||||
|
||||
def _contains(self, cls, evt):
|
||||
return cls in self._clslevel and \
|
||||
evt in self._clslevel[cls]
|
||||
|
||||
def insert(self, obj, target, propagate):
|
||||
assert isinstance(target, type), \
|
||||
"Class-level Event targets must be classes."
|
||||
stack = [target]
|
||||
while stack:
|
||||
cls = stack.pop(0)
|
||||
stack.extend(cls.__subclasses__())
|
||||
if cls is not target and cls not in self._clslevel:
|
||||
self.update_subclass(cls)
|
||||
else:
|
||||
if cls not in self._clslevel:
|
||||
self._clslevel[cls] = []
|
||||
self._clslevel[cls].insert(0, obj)
|
||||
|
||||
def append(self, obj, target, propagate):
|
||||
assert isinstance(target, type), \
|
||||
"Class-level Event targets must be classes."
|
||||
|
||||
stack = [target]
|
||||
while stack:
|
||||
cls = stack.pop(0)
|
||||
stack.extend(cls.__subclasses__())
|
||||
if cls is not target and cls not in self._clslevel:
|
||||
self.update_subclass(cls)
|
||||
else:
|
||||
if cls not in self._clslevel:
|
||||
self._clslevel[cls] = []
|
||||
self._clslevel[cls].append(obj)
|
||||
|
||||
def update_subclass(self, target):
|
||||
if target not in self._clslevel:
|
||||
self._clslevel[target] = []
|
||||
clslevel = self._clslevel[target]
|
||||
for cls in target.__mro__[1:]:
|
||||
if cls in self._clslevel:
|
||||
clslevel.extend([
|
||||
fn for fn
|
||||
in self._clslevel[cls]
|
||||
if fn not in clslevel
|
||||
])
|
||||
|
||||
def remove(self, obj, target):
|
||||
stack = [target]
|
||||
while stack:
|
||||
cls = stack.pop(0)
|
||||
stack.extend(cls.__subclasses__())
|
||||
if cls in self._clslevel:
|
||||
self._clslevel[cls].remove(obj)
|
||||
|
||||
def clear(self):
|
||||
"""Clear all class level listeners"""
|
||||
|
||||
for dispatcher in self._clslevel.values():
|
||||
dispatcher[:] = []
|
||||
|
||||
def for_modify(self, obj):
|
||||
"""Return an event collection which can be modified.
|
||||
|
||||
For _DispatchDescriptor at the class level of
|
||||
a dispatcher, this returns self.
|
||||
|
||||
"""
|
||||
return self
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
return self
|
||||
elif obj._parent_cls in self._empty_listeners:
|
||||
ret = self._empty_listeners[obj._parent_cls]
|
||||
else:
|
||||
self._empty_listeners[obj._parent_cls] = ret = \
|
||||
_EmptyListener(self, obj._parent_cls)
|
||||
# assigning it to __dict__ means
|
||||
# memoized for fast re-access. but more memory.
|
||||
obj.__dict__[self.__name__] = ret
|
||||
return ret
|
||||
|
||||
|
||||
class _EmptyListener(object):
|
||||
"""Serves as a class-level interface to the events
|
||||
served by a _DispatchDescriptor, when there are no
|
||||
instance-level events present.
|
||||
|
||||
Is replaced by _ListenerCollection when instance-level
|
||||
events are added.
|
||||
|
||||
"""
|
||||
def __init__(self, parent, target_cls):
|
||||
if target_cls not in parent._clslevel:
|
||||
parent.update_subclass(target_cls)
|
||||
self.parent = parent
|
||||
self.parent_listeners = parent._clslevel[target_cls]
|
||||
self.name = parent.__name__
|
||||
self.propagate = frozenset()
|
||||
self.listeners = ()
|
||||
|
||||
def for_modify(self, obj):
|
||||
"""Return an event collection which can be modified.
|
||||
|
||||
For _EmptyListener at the instance level of
|
||||
a dispatcher, this generates a new
|
||||
_ListenerCollection, applies it to the instance,
|
||||
and returns it.
|
||||
|
||||
"""
|
||||
result = _ListenerCollection(self.parent, obj._parent_cls)
|
||||
if obj.__dict__[self.name] is self:
|
||||
obj.__dict__[self.name] = result
|
||||
return result
|
||||
|
||||
def _needs_modify(self, *args, **kw):
|
||||
raise NotImplementedError("need to call for_modify()")
|
||||
|
||||
exec_once = insert = append = remove = clear = _needs_modify
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
"""Execute this event."""
|
||||
|
||||
for fn in self.parent_listeners:
|
||||
fn(*args, **kw)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.parent_listeners)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.parent_listeners)
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.parent_listeners)
|
||||
|
||||
|
||||
class _CompoundListener(object):
|
||||
_exec_once = False
|
||||
|
||||
def exec_once(self, *args, **kw):
|
||||
"""Execute this event, but only if it has not been
|
||||
executed already for this collection."""
|
||||
|
||||
if not self._exec_once:
|
||||
self(*args, **kw)
|
||||
self._exec_once = True
|
||||
|
||||
# I'm not entirely thrilled about the overhead here,
|
||||
# but this allows class-level listeners to be added
|
||||
# at any point.
|
||||
#
|
||||
# In the absense of instance-level listeners,
|
||||
# we stay with the _EmptyListener object when called
|
||||
# at the instance level.
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
"""Execute this event."""
|
||||
|
||||
for fn in self.parent_listeners:
|
||||
fn(*args, **kw)
|
||||
for fn in self.listeners:
|
||||
fn(*args, **kw)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.parent_listeners) + len(self.listeners)
|
||||
|
||||
def __iter__(self):
|
||||
return chain(self.parent_listeners, self.listeners)
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.listeners or self.parent_listeners)
|
||||
|
||||
|
||||
class _ListenerCollection(_CompoundListener):
|
||||
"""Instance-level attributes on instances of :class:`._Dispatch`.
|
||||
|
||||
Represents a collection of listeners.
|
||||
|
||||
As of 0.7.9, _ListenerCollection is only first
|
||||
created via the _EmptyListener.for_modify() method.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, parent, target_cls):
|
||||
if target_cls not in parent._clslevel:
|
||||
parent.update_subclass(target_cls)
|
||||
self.parent_listeners = parent._clslevel[target_cls]
|
||||
self.name = parent.__name__
|
||||
self.listeners = []
|
||||
self.propagate = set()
|
||||
|
||||
def for_modify(self, obj):
|
||||
"""Return an event collection which can be modified.
|
||||
|
||||
For _ListenerCollection at the instance level of
|
||||
a dispatcher, this returns self.
|
||||
|
||||
"""
|
||||
return self
|
||||
|
||||
def _update(self, other, only_propagate=True):
|
||||
"""Populate from the listeners in another :class:`_Dispatch`
|
||||
object."""
|
||||
|
||||
existing_listeners = self.listeners
|
||||
existing_listener_set = set(existing_listeners)
|
||||
self.propagate.update(other.propagate)
|
||||
existing_listeners.extend([l for l
|
||||
in other.listeners
|
||||
if l not in existing_listener_set
|
||||
and not only_propagate or l in self.propagate
|
||||
])
|
||||
|
||||
def insert(self, obj, target, propagate):
|
||||
if obj not in self.listeners:
|
||||
self.listeners.insert(0, obj)
|
||||
if propagate:
|
||||
self.propagate.add(obj)
|
||||
|
||||
def append(self, obj, target, propagate):
|
||||
if obj not in self.listeners:
|
||||
self.listeners.append(obj)
|
||||
if propagate:
|
||||
self.propagate.add(obj)
|
||||
|
||||
def remove(self, obj, target):
|
||||
if obj in self.listeners:
|
||||
self.listeners.remove(obj)
|
||||
self.propagate.discard(obj)
|
||||
|
||||
def clear(self):
|
||||
self.listeners[:] = []
|
||||
self.propagate.clear()
|
||||
|
||||
|
||||
class _JoinedDispatcher(object):
|
||||
"""Represent a connection between two _Dispatch objects."""
|
||||
|
||||
def __init__(self, local, parent):
|
||||
self.local = local
|
||||
self.parent = parent
|
||||
self._parent_cls = local._parent_cls
|
||||
|
||||
|
||||
class _JoinedDispatchDescriptor(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
return self
|
||||
else:
|
||||
obj.__dict__[self.name] = ret = _JoinedListener(
|
||||
obj.parent, self.name,
|
||||
getattr(obj.local, self.name)
|
||||
)
|
||||
return ret
|
||||
|
||||
|
||||
class _JoinedListener(_CompoundListener):
|
||||
_exec_once = False
|
||||
|
||||
def __init__(self, parent, name, local):
|
||||
self.parent = parent
|
||||
self.name = name
|
||||
self.local = local
|
||||
self.parent_listeners = self.local
|
||||
|
||||
# fix .listeners for the parent. This means
|
||||
# new events added to the parent won't be picked
|
||||
# up here. Alternatively, the listeners can
|
||||
# be via @property to just return getattr(self.parent, self.name)
|
||||
# each time. less performant.
|
||||
self.listeners = list(getattr(self.parent, self.name))
|
||||
|
||||
def for_modify(self, obj):
|
||||
self.local = self.parent_listeners = self.local.for_modify(obj)
|
||||
return self
|
||||
|
||||
def insert(self, obj, target, propagate):
|
||||
self.local.insert(obj, target, propagate)
|
||||
|
||||
def append(self, obj, target, propagate):
|
||||
self.local.append(obj, target, propagate)
|
||||
|
||||
def remove(self, obj, target):
|
||||
self.local.remove(obj, target)
|
||||
|
||||
def clear(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class dispatcher(object):
|
||||
"""Descriptor used by target classes to
|
||||
deliver the _Dispatch class at the class level
|
||||
and produce new _Dispatch instances for target
|
||||
instances.
|
||||
|
||||
"""
|
||||
def __init__(self, events):
|
||||
self.dispatch_cls = events.dispatch
|
||||
self.events = events
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
return self.dispatch_cls
|
||||
obj.__dict__['dispatch'] = disp = self.dispatch_cls(cls)
|
||||
return disp
|
||||
@@ -0,0 +1,714 @@
|
||||
# sqlalchemy/events.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Core event interfaces."""
|
||||
|
||||
from . import event, exc, util
|
||||
engine = util.importlater('sqlalchemy', 'engine')
|
||||
pool = util.importlater('sqlalchemy', 'pool')
|
||||
|
||||
|
||||
class DDLEvents(event.Events):
|
||||
"""
|
||||
Define event listeners for schema objects,
|
||||
that is, :class:`.SchemaItem` and :class:`.SchemaEvent`
|
||||
subclasses, including :class:`.MetaData`, :class:`.Table`,
|
||||
:class:`.Column`.
|
||||
|
||||
:class:`.MetaData` and :class:`.Table` support events
|
||||
specifically regarding when CREATE and DROP
|
||||
DDL is emitted to the database.
|
||||
|
||||
Attachment events are also provided to customize
|
||||
behavior whenever a child schema element is associated
|
||||
with a parent, such as, when a :class:`.Column` is associated
|
||||
with its :class:`.Table`, when a :class:`.ForeignKeyConstraint`
|
||||
is associated with a :class:`.Table`, etc.
|
||||
|
||||
Example using the ``after_create`` event::
|
||||
|
||||
from sqlalchemy import event
|
||||
from sqlalchemy import Table, Column, Metadata, Integer
|
||||
|
||||
m = MetaData()
|
||||
some_table = Table('some_table', m, Column('data', Integer))
|
||||
|
||||
def after_create(target, connection, **kw):
|
||||
connection.execute("ALTER TABLE %s SET name=foo_%s" %
|
||||
(target.name, target.name))
|
||||
|
||||
event.listen(some_table, "after_create", after_create)
|
||||
|
||||
DDL events integrate closely with the
|
||||
:class:`.DDL` class and the :class:`.DDLElement` hierarchy
|
||||
of DDL clause constructs, which are themselves appropriate
|
||||
as listener callables::
|
||||
|
||||
from sqlalchemy import DDL
|
||||
event.listen(
|
||||
some_table,
|
||||
"after_create",
|
||||
DDL("ALTER TABLE %(table)s SET name=foo_%(table)s")
|
||||
)
|
||||
|
||||
The methods here define the name of an event as well
|
||||
as the names of members that are passed to listener
|
||||
functions.
|
||||
|
||||
See also:
|
||||
|
||||
:ref:`event_toplevel`
|
||||
|
||||
:class:`.DDLElement`
|
||||
|
||||
:class:`.DDL`
|
||||
|
||||
:ref:`schema_ddl_sequences`
|
||||
|
||||
"""
|
||||
|
||||
def before_create(self, target, connection, **kw):
|
||||
"""Called before CREATE statments are emitted.
|
||||
|
||||
:param target: the :class:`.MetaData` or :class:`.Table`
|
||||
object which is the target of the event.
|
||||
:param connection: the :class:`.Connection` where the
|
||||
CREATE statement or statements will be emitted.
|
||||
:param \**kw: additional keyword arguments relevant
|
||||
to the event. The contents of this dictionary
|
||||
may vary across releases, and include the
|
||||
list of tables being generated for a metadata-level
|
||||
event, the checkfirst flag, and other
|
||||
elements used by internal events.
|
||||
|
||||
"""
|
||||
|
||||
def after_create(self, target, connection, **kw):
|
||||
"""Called after CREATE statments are emitted.
|
||||
|
||||
:param target: the :class:`.MetaData` or :class:`.Table`
|
||||
object which is the target of the event.
|
||||
:param connection: the :class:`.Connection` where the
|
||||
CREATE statement or statements have been emitted.
|
||||
:param \**kw: additional keyword arguments relevant
|
||||
to the event. The contents of this dictionary
|
||||
may vary across releases, and include the
|
||||
list of tables being generated for a metadata-level
|
||||
event, the checkfirst flag, and other
|
||||
elements used by internal events.
|
||||
|
||||
"""
|
||||
|
||||
def before_drop(self, target, connection, **kw):
|
||||
"""Called before DROP statments are emitted.
|
||||
|
||||
:param target: the :class:`.MetaData` or :class:`.Table`
|
||||
object which is the target of the event.
|
||||
:param connection: the :class:`.Connection` where the
|
||||
DROP statement or statements will be emitted.
|
||||
:param \**kw: additional keyword arguments relevant
|
||||
to the event. The contents of this dictionary
|
||||
may vary across releases, and include the
|
||||
list of tables being generated for a metadata-level
|
||||
event, the checkfirst flag, and other
|
||||
elements used by internal events.
|
||||
|
||||
"""
|
||||
|
||||
def after_drop(self, target, connection, **kw):
|
||||
"""Called after DROP statments are emitted.
|
||||
|
||||
:param target: the :class:`.MetaData` or :class:`.Table`
|
||||
object which is the target of the event.
|
||||
:param connection: the :class:`.Connection` where the
|
||||
DROP statement or statements have been emitted.
|
||||
:param \**kw: additional keyword arguments relevant
|
||||
to the event. The contents of this dictionary
|
||||
may vary across releases, and include the
|
||||
list of tables being generated for a metadata-level
|
||||
event, the checkfirst flag, and other
|
||||
elements used by internal events.
|
||||
|
||||
"""
|
||||
|
||||
def before_parent_attach(self, target, parent):
|
||||
"""Called before a :class:`.SchemaItem` is associated with
|
||||
a parent :class:`.SchemaItem`.
|
||||
|
||||
:param target: the target object
|
||||
:param parent: the parent to which the target is being attached.
|
||||
|
||||
:func:`.event.listen` also accepts a modifier for this event:
|
||||
|
||||
:param propagate=False: When True, the listener function will
|
||||
be established for any copies made of the target object,
|
||||
i.e. those copies that are generated when
|
||||
:meth:`.Table.tometadata` is used.
|
||||
|
||||
"""
|
||||
|
||||
def after_parent_attach(self, target, parent):
|
||||
"""Called after a :class:`.SchemaItem` is associated with
|
||||
a parent :class:`.SchemaItem`.
|
||||
|
||||
:param target: the target object
|
||||
:param parent: the parent to which the target is being attached.
|
||||
|
||||
:func:`.event.listen` also accepts a modifier for this event:
|
||||
|
||||
:param propagate=False: When True, the listener function will
|
||||
be established for any copies made of the target object,
|
||||
i.e. those copies that are generated when
|
||||
:meth:`.Table.tometadata` is used.
|
||||
|
||||
"""
|
||||
|
||||
def column_reflect(self, inspector, table, column_info):
|
||||
"""Called for each unit of 'column info' retrieved when
|
||||
a :class:`.Table` is being reflected.
|
||||
|
||||
The dictionary of column information as returned by the
|
||||
dialect is passed, and can be modified. The dictionary
|
||||
is that returned in each element of the list returned
|
||||
by :meth:`.reflection.Inspector.get_columns`.
|
||||
|
||||
The event is called before any action is taken against
|
||||
this dictionary, and the contents can be modified.
|
||||
The :class:`.Column` specific arguments ``info``, ``key``,
|
||||
and ``quote`` can also be added to the dictionary and
|
||||
will be passed to the constructor of :class:`.Column`.
|
||||
|
||||
Note that this event is only meaningful if either
|
||||
associated with the :class:`.Table` class across the
|
||||
board, e.g.::
|
||||
|
||||
from sqlalchemy.schema import Table
|
||||
from sqlalchemy import event
|
||||
|
||||
def listen_for_reflect(inspector, table, column_info):
|
||||
"receive a column_reflect event"
|
||||
# ...
|
||||
|
||||
event.listen(
|
||||
Table,
|
||||
'column_reflect',
|
||||
listen_for_reflect)
|
||||
|
||||
...or with a specific :class:`.Table` instance using
|
||||
the ``listeners`` argument::
|
||||
|
||||
def listen_for_reflect(inspector, table, column_info):
|
||||
"receive a column_reflect event"
|
||||
# ...
|
||||
|
||||
t = Table(
|
||||
'sometable',
|
||||
autoload=True,
|
||||
listeners=[
|
||||
('column_reflect', listen_for_reflect)
|
||||
])
|
||||
|
||||
This because the reflection process initiated by ``autoload=True``
|
||||
completes within the scope of the constructor for :class:`.Table`.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class SchemaEventTarget(object):
|
||||
"""Base class for elements that are the targets of :class:`.DDLEvents`
|
||||
events.
|
||||
|
||||
This includes :class:`.SchemaItem` as well as :class:`.SchemaType`.
|
||||
|
||||
"""
|
||||
dispatch = event.dispatcher(DDLEvents)
|
||||
|
||||
def _set_parent(self, parent):
|
||||
"""Associate with this SchemaEvent's parent object."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def _set_parent_with_dispatch(self, parent):
|
||||
self.dispatch.before_parent_attach(self, parent)
|
||||
self._set_parent(parent)
|
||||
self.dispatch.after_parent_attach(self, parent)
|
||||
|
||||
|
||||
class PoolEvents(event.Events):
|
||||
"""Available events for :class:`.Pool`.
|
||||
|
||||
The methods here define the name of an event as well
|
||||
as the names of members that are passed to listener
|
||||
functions.
|
||||
|
||||
e.g.::
|
||||
|
||||
from sqlalchemy import event
|
||||
|
||||
def my_on_checkout(dbapi_conn, connection_rec, connection_proxy):
|
||||
"handle an on checkout event"
|
||||
|
||||
event.listen(Pool, 'checkout', my_on_checkout)
|
||||
|
||||
In addition to accepting the :class:`.Pool` class and
|
||||
:class:`.Pool` instances, :class:`.PoolEvents` also accepts
|
||||
:class:`.Engine` objects and the :class:`.Engine` class as
|
||||
targets, which will be resolved to the ``.pool`` attribute of the
|
||||
given engine or the :class:`.Pool` class::
|
||||
|
||||
engine = create_engine("postgresql://scott:tiger@localhost/test")
|
||||
|
||||
# will associate with engine.pool
|
||||
event.listen(engine, 'checkout', my_on_checkout)
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _accept_with(cls, target):
|
||||
if isinstance(target, type):
|
||||
if issubclass(target, engine.Engine):
|
||||
return pool.Pool
|
||||
elif issubclass(target, pool.Pool):
|
||||
return target
|
||||
elif isinstance(target, engine.Engine):
|
||||
return target.pool
|
||||
else:
|
||||
return target
|
||||
|
||||
def connect(self, dbapi_connection, connection_record):
|
||||
"""Called once for each new DB-API connection or Pool's ``creator()``.
|
||||
|
||||
:param dbapi_con:
|
||||
A newly connected raw DB-API connection (not a SQLAlchemy
|
||||
``Connection`` wrapper).
|
||||
|
||||
:param con_record:
|
||||
The ``_ConnectionRecord`` that persistently manages the connection
|
||||
|
||||
"""
|
||||
|
||||
def first_connect(self, dbapi_connection, connection_record):
|
||||
"""Called exactly once for the first DB-API connection.
|
||||
|
||||
:param dbapi_con:
|
||||
A newly connected raw DB-API connection (not a SQLAlchemy
|
||||
``Connection`` wrapper).
|
||||
|
||||
:param con_record:
|
||||
The ``_ConnectionRecord`` that persistently manages the connection
|
||||
|
||||
"""
|
||||
|
||||
def checkout(self, dbapi_connection, connection_record, connection_proxy):
|
||||
"""Called when a connection is retrieved from the Pool.
|
||||
|
||||
:param dbapi_con:
|
||||
A raw DB-API connection
|
||||
|
||||
:param con_record:
|
||||
The ``_ConnectionRecord`` that persistently manages the connection
|
||||
|
||||
:param con_proxy:
|
||||
The ``_ConnectionFairy`` which manages the connection for the span of
|
||||
the current checkout.
|
||||
|
||||
If you raise a :class:`~sqlalchemy.exc.DisconnectionError`, the current
|
||||
connection will be disposed and a fresh connection retrieved.
|
||||
Processing of all checkout listeners will abort and restart
|
||||
using the new connection.
|
||||
"""
|
||||
|
||||
def checkin(self, dbapi_connection, connection_record):
|
||||
"""Called when a connection returns to the pool.
|
||||
|
||||
Note that the connection may be closed, and may be None if the
|
||||
connection has been invalidated. ``checkin`` will not be called
|
||||
for detached connections. (They do not return to the pool.)
|
||||
|
||||
:param dbapi_con:
|
||||
A raw DB-API connection
|
||||
|
||||
:param con_record:
|
||||
The ``_ConnectionRecord`` that persistently manages the connection
|
||||
|
||||
"""
|
||||
|
||||
def reset(self, dbapi_con, con_record):
|
||||
"""Called before the "reset" action occurs for a pooled connection.
|
||||
|
||||
This event represents
|
||||
when the ``rollback()`` method is called on the DBAPI connection
|
||||
before it is returned to the pool. The behavior of "reset" can
|
||||
be controlled, including disabled, using the ``reset_on_return``
|
||||
pool argument.
|
||||
|
||||
|
||||
The :meth:`.PoolEvents.reset` event is usually followed by the
|
||||
the :meth:`.PoolEvents.checkin` event is called, except in those
|
||||
cases where the connection is discarded immediately after reset.
|
||||
|
||||
:param dbapi_con:
|
||||
A raw DB-API connection
|
||||
|
||||
:param con_record:
|
||||
The ``_ConnectionRecord`` that persistently manages the connection
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.ConnectionEvents.rollback`
|
||||
|
||||
:meth:`.ConnectionEvents.commit`
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class ConnectionEvents(event.Events):
|
||||
"""Available events for :class:`.Connectable`, which includes
|
||||
:class:`.Connection` and :class:`.Engine`.
|
||||
|
||||
The methods here define the name of an event as well as the names of
|
||||
members that are passed to listener functions.
|
||||
|
||||
An event listener can be associated with any :class:`.Connectable`
|
||||
class or instance, such as an :class:`.Engine`, e.g.::
|
||||
|
||||
from sqlalchemy import event, create_engine
|
||||
|
||||
def before_cursor_execute(conn, cursor, statement, parameters, context,
|
||||
executemany):
|
||||
log.info("Received statement: %s" % statement)
|
||||
|
||||
engine = create_engine('postgresql://scott:tiger@localhost/test')
|
||||
event.listen(engine, "before_cursor_execute", before_cursor_execute)
|
||||
|
||||
or with a specific :class:`.Connection`::
|
||||
|
||||
with engine.begin() as conn:
|
||||
@event.listens_for(conn, 'before_cursor_execute')
|
||||
def before_cursor_execute(conn, cursor, statement, parameters,
|
||||
context, executemany):
|
||||
log.info("Received statement: %s" % statement)
|
||||
|
||||
The :meth:`.before_execute` and :meth:`.before_cursor_execute`
|
||||
events can also be established with the ``retval=True`` flag, which
|
||||
allows modification of the statement and parameters to be sent
|
||||
to the database. The :meth:`.before_cursor_execute` event is
|
||||
particularly useful here to add ad-hoc string transformations, such
|
||||
as comments, to all executions::
|
||||
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy import event
|
||||
|
||||
@event.listens_for(Engine, "before_cursor_execute", retval=True)
|
||||
def comment_sql_calls(conn, cursor, statement, parameters,
|
||||
context, executemany):
|
||||
statement = statement + " -- some comment"
|
||||
return statement, parameters
|
||||
|
||||
.. note:: :class:`.ConnectionEvents` can be established on any
|
||||
combination of :class:`.Engine`, :class:`.Connection`, as well
|
||||
as instances of each of those classes. Events across all
|
||||
four scopes will fire off for a given instance of
|
||||
:class:`.Connection`. However, for performance reasons, the
|
||||
:class:`.Connection` object determines at instantiation time
|
||||
whether or not its parent :class:`.Engine` has event listeners
|
||||
established. Event listeners added to the :class:`.Engine`
|
||||
class or to an instance of :class:`.Engine` *after* the instantiation
|
||||
of a dependent :class:`.Connection` instance will usually
|
||||
*not* be available on that :class:`.Connection` instance. The newly
|
||||
added listeners will instead take effect for :class:`.Connection`
|
||||
instances created subsequent to those event listeners being
|
||||
established on the parent :class:`.Engine` class or instance.
|
||||
|
||||
:param retval=False: Applies to the :meth:`.before_execute` and
|
||||
:meth:`.before_cursor_execute` events only. When True, the
|
||||
user-defined event function must have a return value, which
|
||||
is a tuple of parameters that replace the given statement
|
||||
and parameters. See those methods for a description of
|
||||
specific return arguments.
|
||||
|
||||
.. versionchanged:: 0.8 :class:`.ConnectionEvents` can now be associated
|
||||
with any :class:`.Connectable` including :class:`.Connection`,
|
||||
in addition to the existing support for :class:`.Engine`.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _listen(cls, target, identifier, fn, retval=False):
|
||||
target._has_events = True
|
||||
|
||||
if not retval:
|
||||
if identifier == 'before_execute':
|
||||
orig_fn = fn
|
||||
|
||||
def wrap_before_execute(conn, clauseelement,
|
||||
multiparams, params):
|
||||
orig_fn(conn, clauseelement, multiparams, params)
|
||||
return clauseelement, multiparams, params
|
||||
fn = wrap_before_execute
|
||||
elif identifier == 'before_cursor_execute':
|
||||
orig_fn = fn
|
||||
|
||||
def wrap_before_cursor_execute(conn, cursor, statement,
|
||||
parameters, context, executemany):
|
||||
orig_fn(conn, cursor, statement,
|
||||
parameters, context, executemany)
|
||||
return statement, parameters
|
||||
fn = wrap_before_cursor_execute
|
||||
|
||||
elif retval and \
|
||||
identifier not in ('before_execute', 'before_cursor_execute'):
|
||||
raise exc.ArgumentError(
|
||||
"Only the 'before_execute' and "
|
||||
"'before_cursor_execute' engine "
|
||||
"event listeners accept the 'retval=True' "
|
||||
"argument.")
|
||||
event.Events._listen(target, identifier, fn)
|
||||
|
||||
def before_execute(self, conn, clauseelement, multiparams, params):
|
||||
"""Intercept high level execute() events, receiving uncompiled
|
||||
SQL constructs and other objects prior to rendering into SQL.
|
||||
|
||||
This event is good for debugging SQL compilation issues as well
|
||||
as early manipulation of the parameters being sent to the database,
|
||||
as the parameter lists will be in a consistent format here.
|
||||
|
||||
This event can be optionally established with the ``retval=True``
|
||||
flag. The ``clauseelement``, ``multiparams``, and ``params``
|
||||
arguments should be returned as a three-tuple in this case::
|
||||
|
||||
@event.listens_for(Engine, "before_execute", retval=True)
|
||||
def before_execute(conn, conn, clauseelement, multiparams, params):
|
||||
# do something with clauseelement, multiparams, params
|
||||
return clauseelement, multiparams, params
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
:param clauseelement: SQL expression construct, :class:`.Compiled`
|
||||
instance, or string statement passed to :meth:`.Connection.execute`.
|
||||
:param multiparams: Multiple parameter sets, a list of dictionaries.
|
||||
:param params: Single parameter set, a single dictionary.
|
||||
|
||||
See also:
|
||||
|
||||
:meth:`.before_cursor_execute`
|
||||
|
||||
"""
|
||||
|
||||
def after_execute(self, conn, clauseelement, multiparams, params, result):
|
||||
"""Intercept high level execute() events after execute.
|
||||
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
:param clauseelement: SQL expression construct, :class:`.Compiled`
|
||||
instance, or string statement passed to :meth:`.Connection.execute`.
|
||||
:param multiparams: Multiple parameter sets, a list of dictionaries.
|
||||
:param params: Single parameter set, a single dictionary.
|
||||
:param result: :class:`.ResultProxy` generated by the execution.
|
||||
|
||||
"""
|
||||
|
||||
def before_cursor_execute(self, conn, cursor, statement,
|
||||
parameters, context, executemany):
|
||||
"""Intercept low-level cursor execute() events before execution,
|
||||
receiving the string
|
||||
SQL statement and DBAPI-specific parameter list to be invoked
|
||||
against a cursor.
|
||||
|
||||
This event is a good choice for logging as well as late modifications
|
||||
to the SQL string. It's less ideal for parameter modifications except
|
||||
for those which are specific to a target backend.
|
||||
|
||||
This event can be optionally established with the ``retval=True``
|
||||
flag. The ``statement`` and ``parameters`` arguments should be
|
||||
returned as a two-tuple in this case::
|
||||
|
||||
@event.listens_for(Engine, "before_cursor_execute", retval=True)
|
||||
def before_cursor_execute(conn, cursor, statement,
|
||||
parameters, context, executemany):
|
||||
# do something with statement, parameters
|
||||
return statement, parameters
|
||||
|
||||
See the example at :class:`.ConnectionEvents`.
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
:param cursor: DBAPI cursor object
|
||||
:param statement: string SQL statement
|
||||
:param parameters: Dictionary, tuple, or list of parameters being
|
||||
passed to the ``execute()`` or ``executemany()`` method of the
|
||||
DBAPI ``cursor``. In some cases may be ``None``.
|
||||
:param context: :class:`.ExecutionContext` object in use. May
|
||||
be ``None``.
|
||||
:param executemany: boolean, if ``True``, this is an ``executemany()``
|
||||
call, if ``False``, this is an ``execute()`` call.
|
||||
|
||||
See also:
|
||||
|
||||
:meth:`.before_execute`
|
||||
|
||||
:meth:`.after_cursor_execute`
|
||||
|
||||
"""
|
||||
|
||||
def after_cursor_execute(self, conn, cursor, statement,
|
||||
parameters, context, executemany):
|
||||
"""Intercept low-level cursor execute() events after execution.
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
:param cursor: DBAPI cursor object. Will have results pending
|
||||
if the statement was a SELECT, but these should not be consumed
|
||||
as they will be needed by the :class:`.ResultProxy`.
|
||||
:param statement: string SQL statement
|
||||
:param parameters: Dictionary, tuple, or list of parameters being
|
||||
passed to the ``execute()`` or ``executemany()`` method of the
|
||||
DBAPI ``cursor``. In some cases may be ``None``.
|
||||
:param context: :class:`.ExecutionContext` object in use. May
|
||||
be ``None``.
|
||||
:param executemany: boolean, if ``True``, this is an ``executemany()``
|
||||
call, if ``False``, this is an ``execute()`` call.
|
||||
|
||||
"""
|
||||
|
||||
def dbapi_error(self, conn, cursor, statement, parameters,
|
||||
context, exception):
|
||||
"""Intercept a raw DBAPI error.
|
||||
|
||||
This event is called with the DBAPI exception instance
|
||||
received from the DBAPI itself, *before* SQLAlchemy wraps the
|
||||
exception with it's own exception wrappers, and before any
|
||||
other operations are performed on the DBAPI cursor; the
|
||||
existing transaction remains in effect as well as any state
|
||||
on the cursor.
|
||||
|
||||
The use case here is to inject low-level exception handling
|
||||
into an :class:`.Engine`, typically for logging and
|
||||
debugging purposes. In general, user code should **not** modify
|
||||
any state or throw any exceptions here as this will
|
||||
interfere with SQLAlchemy's cleanup and error handling
|
||||
routines.
|
||||
|
||||
Subsequent to this hook, SQLAlchemy may attempt any
|
||||
number of operations on the connection/cursor, including
|
||||
closing the cursor, rolling back of the transaction in the
|
||||
case of connectionless execution, and disposing of the entire
|
||||
connection pool if a "disconnect" was detected. The
|
||||
exception is then wrapped in a SQLAlchemy DBAPI exception
|
||||
wrapper and re-thrown.
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
:param cursor: DBAPI cursor object
|
||||
:param statement: string SQL statement
|
||||
:param parameters: Dictionary, tuple, or list of parameters being
|
||||
passed to the ``execute()`` or ``executemany()`` method of the
|
||||
DBAPI ``cursor``. In some cases may be ``None``.
|
||||
:param context: :class:`.ExecutionContext` object in use. May
|
||||
be ``None``.
|
||||
:param exception: The **unwrapped** exception emitted directly from the
|
||||
DBAPI. The class here is specific to the DBAPI module in use.
|
||||
|
||||
.. versionadded:: 0.7.7
|
||||
|
||||
"""
|
||||
|
||||
def begin(self, conn):
|
||||
"""Intercept begin() events.
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
|
||||
"""
|
||||
|
||||
def rollback(self, conn):
|
||||
"""Intercept rollback() events, as initiated by a
|
||||
:class:`.Transaction`.
|
||||
|
||||
Note that the :class:`.Pool` also "auto-rolls back"
|
||||
a DBAPI connection upon checkin, if the ``reset_on_return``
|
||||
flag is set to its default value of ``'rollback'``.
|
||||
To intercept this
|
||||
rollback, use the :meth:`.PoolEvents.reset` hook.
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.PoolEvents.reset`
|
||||
|
||||
"""
|
||||
|
||||
def commit(self, conn):
|
||||
"""Intercept commit() events, as initiated by a
|
||||
:class:`.Transaction`.
|
||||
|
||||
Note that the :class:`.Pool` may also "auto-commit"
|
||||
a DBAPI connection upon checkin, if the ``reset_on_return``
|
||||
flag is set to the value ``'commit'``. To intercept this
|
||||
commit, use the :meth:`.PoolEvents.reset` hook.
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
"""
|
||||
|
||||
def savepoint(self, conn, name=None):
|
||||
"""Intercept savepoint() events.
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
:param name: specified name used for the savepoint.
|
||||
|
||||
"""
|
||||
|
||||
def rollback_savepoint(self, conn, name, context):
|
||||
"""Intercept rollback_savepoint() events.
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
:param name: specified name used for the savepoint.
|
||||
:param context: :class:`.ExecutionContext` in use. May be ``None``.
|
||||
|
||||
"""
|
||||
|
||||
def release_savepoint(self, conn, name, context):
|
||||
"""Intercept release_savepoint() events.
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
:param name: specified name used for the savepoint.
|
||||
:param context: :class:`.ExecutionContext` in use. May be ``None``.
|
||||
|
||||
"""
|
||||
|
||||
def begin_twophase(self, conn, xid):
|
||||
"""Intercept begin_twophase() events.
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
:param xid: two-phase XID identifier
|
||||
|
||||
"""
|
||||
|
||||
def prepare_twophase(self, conn, xid):
|
||||
"""Intercept prepare_twophase() events.
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
:param xid: two-phase XID identifier
|
||||
"""
|
||||
|
||||
def rollback_twophase(self, conn, xid, is_prepared):
|
||||
"""Intercept rollback_twophase() events.
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
:param xid: two-phase XID identifier
|
||||
:param is_prepared: boolean, indicates if
|
||||
:meth:`.TwoPhaseTransaction.prepare` was called.
|
||||
|
||||
"""
|
||||
|
||||
def commit_twophase(self, conn, xid, is_prepared):
|
||||
"""Intercept commit_twophase() events.
|
||||
|
||||
:param conn: :class:`.Connection` object
|
||||
:param xid: two-phase XID identifier
|
||||
:param is_prepared: boolean, indicates if
|
||||
:meth:`.TwoPhaseTransaction.prepare` was called.
|
||||
|
||||
"""
|
||||
@@ -0,0 +1,355 @@
|
||||
# sqlalchemy/exc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Exceptions used with SQLAlchemy.
|
||||
|
||||
The base exception class is :class:`.SQLAlchemyError`. Exceptions which are
|
||||
raised as a result of DBAPI exceptions are all subclasses of
|
||||
:class:`.DBAPIError`.
|
||||
|
||||
"""
|
||||
|
||||
import traceback
|
||||
|
||||
|
||||
class SQLAlchemyError(Exception):
|
||||
"""Generic error class."""
|
||||
|
||||
|
||||
class ArgumentError(SQLAlchemyError):
|
||||
"""Raised when an invalid or conflicting function argument is supplied.
|
||||
|
||||
This error generally corresponds to construction time state errors.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class NoForeignKeysError(ArgumentError):
|
||||
"""Raised when no foreign keys can be located between two selectables
|
||||
during a join."""
|
||||
|
||||
|
||||
class AmbiguousForeignKeysError(ArgumentError):
|
||||
"""Raised when more than one foreign key matching can be located
|
||||
between two selectables during a join."""
|
||||
|
||||
|
||||
class CircularDependencyError(SQLAlchemyError):
|
||||
"""Raised by topological sorts when a circular dependency is detected.
|
||||
|
||||
There are two scenarios where this error occurs:
|
||||
|
||||
* In a Session flush operation, if two objects are mutually dependent
|
||||
on each other, they can not be inserted or deleted via INSERT or
|
||||
DELETE statements alone; an UPDATE will be needed to post-associate
|
||||
or pre-deassociate one of the foreign key constrained values.
|
||||
The ``post_update`` flag described at :ref:`post_update` can resolve
|
||||
this cycle.
|
||||
* In a :meth:`.MetaData.create_all`, :meth:`.MetaData.drop_all`,
|
||||
:attr:`.MetaData.sorted_tables` operation, two :class:`.ForeignKey`
|
||||
or :class:`.ForeignKeyConstraint` objects mutually refer to each
|
||||
other. Apply the ``use_alter=True`` flag to one or both,
|
||||
see :ref:`use_alter`.
|
||||
|
||||
"""
|
||||
def __init__(self, message, cycles, edges, msg=None):
|
||||
if msg is None:
|
||||
message += " Cycles: %r all edges: %r" % (cycles, edges)
|
||||
else:
|
||||
message = msg
|
||||
SQLAlchemyError.__init__(self, message)
|
||||
self.cycles = cycles
|
||||
self.edges = edges
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, (None, self.cycles,
|
||||
self.edges, self.args[0])
|
||||
|
||||
|
||||
class CompileError(SQLAlchemyError):
|
||||
"""Raised when an error occurs during SQL compilation"""
|
||||
|
||||
class UnsupportedCompilationError(CompileError):
|
||||
"""Raised when an operation is not supported by the given compiler.
|
||||
|
||||
|
||||
.. versionadded:: 0.8.3
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, compiler, element_type):
|
||||
super(UnsupportedCompilationError, self).__init__(
|
||||
"Compiler %r can't render element of type %s" %
|
||||
(compiler, element_type))
|
||||
|
||||
class IdentifierError(SQLAlchemyError):
|
||||
"""Raised when a schema name is beyond the max character limit"""
|
||||
|
||||
|
||||
class DisconnectionError(SQLAlchemyError):
|
||||
"""A disconnect is detected on a raw DB-API connection.
|
||||
|
||||
This error is raised and consumed internally by a connection pool. It can
|
||||
be raised by the :meth:`.PoolEvents.checkout` event so that the host pool
|
||||
forces a retry; the exception will be caught three times in a row before
|
||||
the pool gives up and raises :class:`~sqlalchemy.exc.InvalidRequestError`
|
||||
regarding the connection attempt.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class TimeoutError(SQLAlchemyError):
|
||||
"""Raised when a connection pool times out on getting a connection."""
|
||||
|
||||
|
||||
class InvalidRequestError(SQLAlchemyError):
|
||||
"""SQLAlchemy was asked to do something it can't do.
|
||||
|
||||
This error generally corresponds to runtime state errors.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class NoInspectionAvailable(InvalidRequestError):
|
||||
"""A subject passed to :func:`sqlalchemy.inspection.inspect` produced
|
||||
no context for inspection."""
|
||||
|
||||
|
||||
class ResourceClosedError(InvalidRequestError):
|
||||
"""An operation was requested from a connection, cursor, or other
|
||||
object that's in a closed state."""
|
||||
|
||||
|
||||
class NoSuchColumnError(KeyError, InvalidRequestError):
|
||||
"""A nonexistent column is requested from a ``RowProxy``."""
|
||||
|
||||
|
||||
class NoReferenceError(InvalidRequestError):
|
||||
"""Raised by ``ForeignKey`` to indicate a reference cannot be resolved."""
|
||||
|
||||
|
||||
class NoReferencedTableError(NoReferenceError):
|
||||
"""Raised by ``ForeignKey`` when the referred ``Table`` cannot be
|
||||
located.
|
||||
|
||||
"""
|
||||
def __init__(self, message, tname):
|
||||
NoReferenceError.__init__(self, message)
|
||||
self.table_name = tname
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, (self.args[0], self.table_name)
|
||||
|
||||
|
||||
class NoReferencedColumnError(NoReferenceError):
|
||||
"""Raised by ``ForeignKey`` when the referred ``Column`` cannot be
|
||||
located.
|
||||
|
||||
"""
|
||||
def __init__(self, message, tname, cname):
|
||||
NoReferenceError.__init__(self, message)
|
||||
self.table_name = tname
|
||||
self.column_name = cname
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, (self.args[0], self.table_name,
|
||||
self.column_name)
|
||||
|
||||
|
||||
class NoSuchTableError(InvalidRequestError):
|
||||
"""Table does not exist or is not visible to a connection."""
|
||||
|
||||
|
||||
class UnboundExecutionError(InvalidRequestError):
|
||||
"""SQL was attempted without a database connection to execute it on."""
|
||||
|
||||
|
||||
class DontWrapMixin(object):
|
||||
"""A mixin class which, when applied to a user-defined Exception class,
|
||||
will not be wrapped inside of :class:`.StatementError` if the error is
|
||||
emitted within the process of executing a statement.
|
||||
|
||||
E.g.::
|
||||
|
||||
from sqlalchemy.exc import DontWrapMixin
|
||||
|
||||
class MyCustomException(Exception, DontWrapMixin):
|
||||
pass
|
||||
|
||||
class MySpecialType(TypeDecorator):
|
||||
impl = String
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
if value == 'invalid':
|
||||
raise MyCustomException("invalid!")
|
||||
|
||||
"""
|
||||
import sys
|
||||
if sys.version_info < (2, 5):
|
||||
class DontWrapMixin:
|
||||
pass
|
||||
|
||||
# Moved to orm.exc; compatibility definition installed by orm import until 0.6
|
||||
UnmappedColumnError = None
|
||||
|
||||
|
||||
class StatementError(SQLAlchemyError):
|
||||
"""An error occurred during execution of a SQL statement.
|
||||
|
||||
:class:`StatementError` wraps the exception raised
|
||||
during execution, and features :attr:`.statement`
|
||||
and :attr:`.params` attributes which supply context regarding
|
||||
the specifics of the statement which had an issue.
|
||||
|
||||
The wrapped exception object is available in
|
||||
the :attr:`.orig` attribute.
|
||||
|
||||
"""
|
||||
|
||||
statement = None
|
||||
"""The string SQL statement being invoked when this exception occurred."""
|
||||
|
||||
params = None
|
||||
"""The parameter list being used when this exception occurred."""
|
||||
|
||||
orig = None
|
||||
"""The DBAPI exception object."""
|
||||
|
||||
def __init__(self, message, statement, params, orig):
|
||||
SQLAlchemyError.__init__(self, message)
|
||||
self.statement = statement
|
||||
self.params = params
|
||||
self.orig = orig
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, (self.args[0], self.statement,
|
||||
self.params, self.orig)
|
||||
|
||||
def __str__(self):
|
||||
from sqlalchemy.sql import util
|
||||
params_repr = util._repr_params(self.params, 10)
|
||||
return ' '.join((SQLAlchemyError.__str__(self),
|
||||
repr(self.statement), repr(params_repr)))
|
||||
|
||||
def __unicode__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
class DBAPIError(StatementError):
|
||||
"""Raised when the execution of a database operation fails.
|
||||
|
||||
Wraps exceptions raised by the DB-API underlying the
|
||||
database operation. Driver-specific implementations of the standard
|
||||
DB-API exception types are wrapped by matching sub-types of SQLAlchemy's
|
||||
:class:`DBAPIError` when possible. DB-API's ``Error`` type maps to
|
||||
:class:`DBAPIError` in SQLAlchemy, otherwise the names are identical. Note
|
||||
that there is no guarantee that different DB-API implementations will
|
||||
raise the same exception type for any given error condition.
|
||||
|
||||
:class:`DBAPIError` features :attr:`~.StatementError.statement`
|
||||
and :attr:`~.StatementError.params` attributes which supply context
|
||||
regarding the specifics of the statement which had an issue, for the
|
||||
typical case when the error was raised within the context of
|
||||
emitting a SQL statement.
|
||||
|
||||
The wrapped exception object is available in the
|
||||
:attr:`~.StatementError.orig` attribute. Its type and properties are
|
||||
DB-API implementation specific.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def instance(cls, statement, params,
|
||||
orig,
|
||||
dbapi_base_err,
|
||||
connection_invalidated=False):
|
||||
# Don't ever wrap these, just return them directly as if
|
||||
# DBAPIError didn't exist.
|
||||
if isinstance(orig, (KeyboardInterrupt, SystemExit, DontWrapMixin)):
|
||||
return orig
|
||||
|
||||
if orig is not None:
|
||||
# not a DBAPI error, statement is present.
|
||||
# raise a StatementError
|
||||
if not isinstance(orig, dbapi_base_err) and statement:
|
||||
msg = traceback.format_exception_only(
|
||||
orig.__class__, orig)[-1].strip()
|
||||
return StatementError(
|
||||
"%s (original cause: %s)" % (str(orig), msg),
|
||||
statement, params, orig
|
||||
)
|
||||
|
||||
name, glob = orig.__class__.__name__, globals()
|
||||
if name in glob and issubclass(glob[name], DBAPIError):
|
||||
cls = glob[name]
|
||||
|
||||
return cls(statement, params, orig, connection_invalidated)
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, (self.statement, self.params,
|
||||
self.orig, self.connection_invalidated)
|
||||
|
||||
def __init__(self, statement, params, orig, connection_invalidated=False):
|
||||
try:
|
||||
text = str(orig)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception, e:
|
||||
text = 'Error in str() of DB-API-generated exception: ' + str(e)
|
||||
StatementError.__init__(
|
||||
self,
|
||||
'(%s) %s' % (orig.__class__.__name__, text),
|
||||
statement,
|
||||
params,
|
||||
orig
|
||||
)
|
||||
self.connection_invalidated = connection_invalidated
|
||||
|
||||
|
||||
class InterfaceError(DBAPIError):
|
||||
"""Wraps a DB-API InterfaceError."""
|
||||
|
||||
|
||||
class DatabaseError(DBAPIError):
|
||||
"""Wraps a DB-API DatabaseError."""
|
||||
|
||||
|
||||
class DataError(DatabaseError):
|
||||
"""Wraps a DB-API DataError."""
|
||||
|
||||
|
||||
class OperationalError(DatabaseError):
|
||||
"""Wraps a DB-API OperationalError."""
|
||||
|
||||
|
||||
class IntegrityError(DatabaseError):
|
||||
"""Wraps a DB-API IntegrityError."""
|
||||
|
||||
|
||||
class InternalError(DatabaseError):
|
||||
"""Wraps a DB-API InternalError."""
|
||||
|
||||
|
||||
class ProgrammingError(DatabaseError):
|
||||
"""Wraps a DB-API ProgrammingError."""
|
||||
|
||||
|
||||
class NotSupportedError(DatabaseError):
|
||||
"""Wraps a DB-API NotSupportedError."""
|
||||
|
||||
|
||||
# Warnings
|
||||
|
||||
class SADeprecationWarning(DeprecationWarning):
|
||||
"""Issued once per usage of a deprecated API."""
|
||||
|
||||
|
||||
class SAPendingDeprecationWarning(PendingDeprecationWarning):
|
||||
"""Issued once per usage of a deprecated API."""
|
||||
|
||||
|
||||
class SAWarning(RuntimeWarning):
|
||||
"""Issued at runtime."""
|
||||
@@ -0,0 +1,5 @@
|
||||
# ext/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,448 @@
|
||||
# ext/compiler.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Provides an API for creation of custom ClauseElements and compilers.
|
||||
|
||||
Synopsis
|
||||
========
|
||||
|
||||
Usage involves the creation of one or more
|
||||
:class:`~sqlalchemy.sql.expression.ClauseElement` subclasses and one or
|
||||
more callables defining its compilation::
|
||||
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.sql.expression import ColumnClause
|
||||
|
||||
class MyColumn(ColumnClause):
|
||||
pass
|
||||
|
||||
@compiles(MyColumn)
|
||||
def compile_mycolumn(element, compiler, **kw):
|
||||
return "[%s]" % element.name
|
||||
|
||||
Above, ``MyColumn`` extends :class:`~sqlalchemy.sql.expression.ColumnClause`,
|
||||
the base expression element for named column objects. The ``compiles``
|
||||
decorator registers itself with the ``MyColumn`` class so that it is invoked
|
||||
when the object is compiled to a string::
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
s = select([MyColumn('x'), MyColumn('y')])
|
||||
print str(s)
|
||||
|
||||
Produces::
|
||||
|
||||
SELECT [x], [y]
|
||||
|
||||
Dialect-specific compilation rules
|
||||
==================================
|
||||
|
||||
Compilers can also be made dialect-specific. The appropriate compiler will be
|
||||
invoked for the dialect in use::
|
||||
|
||||
from sqlalchemy.schema import DDLElement
|
||||
|
||||
class AlterColumn(DDLElement):
|
||||
|
||||
def __init__(self, column, cmd):
|
||||
self.column = column
|
||||
self.cmd = cmd
|
||||
|
||||
@compiles(AlterColumn)
|
||||
def visit_alter_column(element, compiler, **kw):
|
||||
return "ALTER COLUMN %s ..." % element.column.name
|
||||
|
||||
@compiles(AlterColumn, 'postgresql')
|
||||
def visit_alter_column(element, compiler, **kw):
|
||||
return "ALTER TABLE %s ALTER COLUMN %s ..." % (element.table.name, element.column.name)
|
||||
|
||||
The second ``visit_alter_table`` will be invoked when any ``postgresql``
|
||||
dialect is used.
|
||||
|
||||
Compiling sub-elements of a custom expression construct
|
||||
=======================================================
|
||||
|
||||
The ``compiler`` argument is the
|
||||
:class:`~sqlalchemy.engine.interfaces.Compiled` object in use. This object
|
||||
can be inspected for any information about the in-progress compilation,
|
||||
including ``compiler.dialect``, ``compiler.statement`` etc. The
|
||||
:class:`~sqlalchemy.sql.compiler.SQLCompiler` and
|
||||
:class:`~sqlalchemy.sql.compiler.DDLCompiler` both include a ``process()``
|
||||
method which can be used for compilation of embedded attributes::
|
||||
|
||||
from sqlalchemy.sql.expression import Executable, ClauseElement
|
||||
|
||||
class InsertFromSelect(Executable, ClauseElement):
|
||||
def __init__(self, table, select):
|
||||
self.table = table
|
||||
self.select = select
|
||||
|
||||
@compiles(InsertFromSelect)
|
||||
def visit_insert_from_select(element, compiler, **kw):
|
||||
return "INSERT INTO %s (%s)" % (
|
||||
compiler.process(element.table, asfrom=True),
|
||||
compiler.process(element.select)
|
||||
)
|
||||
|
||||
insert = InsertFromSelect(t1, select([t1]).where(t1.c.x>5))
|
||||
print insert
|
||||
|
||||
Produces::
|
||||
|
||||
"INSERT INTO mytable (SELECT mytable.x, mytable.y, mytable.z FROM mytable WHERE mytable.x > :x_1)"
|
||||
|
||||
.. note::
|
||||
|
||||
The above ``InsertFromSelect`` construct is only an example, this actual
|
||||
functionality is already available using the
|
||||
:meth:`.Insert.from_select` method.
|
||||
|
||||
.. note::
|
||||
|
||||
The above ``InsertFromSelect`` construct probably wants to have "autocommit"
|
||||
enabled. See :ref:`enabling_compiled_autocommit` for this step.
|
||||
|
||||
Cross Compiling between SQL and DDL compilers
|
||||
---------------------------------------------
|
||||
|
||||
SQL and DDL constructs are each compiled using different base compilers -
|
||||
``SQLCompiler`` and ``DDLCompiler``. A common need is to access the
|
||||
compilation rules of SQL expressions from within a DDL expression. The
|
||||
``DDLCompiler`` includes an accessor ``sql_compiler`` for this reason, such as
|
||||
below where we generate a CHECK constraint that embeds a SQL expression::
|
||||
|
||||
@compiles(MyConstraint)
|
||||
def compile_my_constraint(constraint, ddlcompiler, **kw):
|
||||
return "CONSTRAINT %s CHECK (%s)" % (
|
||||
constraint.name,
|
||||
ddlcompiler.sql_compiler.process(constraint.expression)
|
||||
)
|
||||
|
||||
.. _enabling_compiled_autocommit:
|
||||
|
||||
Enabling Autocommit on a Construct
|
||||
==================================
|
||||
|
||||
Recall from the section :ref:`autocommit` that the :class:`.Engine`, when
|
||||
asked to execute a construct in the absence of a user-defined transaction,
|
||||
detects if the given construct represents DML or DDL, that is, a data
|
||||
modification or data definition statement, which requires (or may require,
|
||||
in the case of DDL) that the transaction generated by the DBAPI be committed
|
||||
(recall that DBAPI always has a transaction going on regardless of what
|
||||
SQLAlchemy does). Checking for this is actually accomplished by checking for
|
||||
the "autocommit" execution option on the construct. When building a
|
||||
construct like an INSERT derivation, a new DDL type, or perhaps a stored
|
||||
procedure that alters data, the "autocommit" option needs to be set in order
|
||||
for the statement to function with "connectionless" execution
|
||||
(as described in :ref:`dbengine_implicit`).
|
||||
|
||||
Currently a quick way to do this is to subclass :class:`.Executable`, then
|
||||
add the "autocommit" flag to the ``_execution_options`` dictionary (note this
|
||||
is a "frozen" dictionary which supplies a generative ``union()`` method)::
|
||||
|
||||
from sqlalchemy.sql.expression import Executable, ClauseElement
|
||||
|
||||
class MyInsertThing(Executable, ClauseElement):
|
||||
_execution_options = \\
|
||||
Executable._execution_options.union({'autocommit': True})
|
||||
|
||||
More succinctly, if the construct is truly similar to an INSERT, UPDATE, or
|
||||
DELETE, :class:`.UpdateBase` can be used, which already is a subclass
|
||||
of :class:`.Executable`, :class:`.ClauseElement` and includes the
|
||||
``autocommit`` flag::
|
||||
|
||||
from sqlalchemy.sql.expression import UpdateBase
|
||||
|
||||
class MyInsertThing(UpdateBase):
|
||||
def __init__(self, ...):
|
||||
...
|
||||
|
||||
|
||||
|
||||
|
||||
DDL elements that subclass :class:`.DDLElement` already have the
|
||||
"autocommit" flag turned on.
|
||||
|
||||
|
||||
|
||||
|
||||
Changing the default compilation of existing constructs
|
||||
=======================================================
|
||||
|
||||
The compiler extension applies just as well to the existing constructs. When
|
||||
overriding the compilation of a built in SQL construct, the @compiles
|
||||
decorator is invoked upon the appropriate class (be sure to use the class,
|
||||
i.e. ``Insert`` or ``Select``, instead of the creation function such
|
||||
as ``insert()`` or ``select()``).
|
||||
|
||||
Within the new compilation function, to get at the "original" compilation
|
||||
routine, use the appropriate visit_XXX method - this
|
||||
because compiler.process() will call upon the overriding routine and cause
|
||||
an endless loop. Such as, to add "prefix" to all insert statements::
|
||||
|
||||
from sqlalchemy.sql.expression import Insert
|
||||
|
||||
@compiles(Insert)
|
||||
def prefix_inserts(insert, compiler, **kw):
|
||||
return compiler.visit_insert(insert.prefix_with("some prefix"), **kw)
|
||||
|
||||
The above compiler will prefix all INSERT statements with "some prefix" when
|
||||
compiled.
|
||||
|
||||
.. _type_compilation_extension:
|
||||
|
||||
Changing Compilation of Types
|
||||
=============================
|
||||
|
||||
``compiler`` works for types, too, such as below where we implement the
|
||||
MS-SQL specific 'max' keyword for ``String``/``VARCHAR``::
|
||||
|
||||
@compiles(String, 'mssql')
|
||||
@compiles(VARCHAR, 'mssql')
|
||||
def compile_varchar(element, compiler, **kw):
|
||||
if element.length == 'max':
|
||||
return "VARCHAR('max')"
|
||||
else:
|
||||
return compiler.visit_VARCHAR(element, **kw)
|
||||
|
||||
foo = Table('foo', metadata,
|
||||
Column('data', VARCHAR('max'))
|
||||
)
|
||||
|
||||
Subclassing Guidelines
|
||||
======================
|
||||
|
||||
A big part of using the compiler extension is subclassing SQLAlchemy
|
||||
expression constructs. To make this easier, the expression and
|
||||
schema packages feature a set of "bases" intended for common tasks.
|
||||
A synopsis is as follows:
|
||||
|
||||
* :class:`~sqlalchemy.sql.expression.ClauseElement` - This is the root
|
||||
expression class. Any SQL expression can be derived from this base, and is
|
||||
probably the best choice for longer constructs such as specialized INSERT
|
||||
statements.
|
||||
|
||||
* :class:`~sqlalchemy.sql.expression.ColumnElement` - The root of all
|
||||
"column-like" elements. Anything that you'd place in the "columns" clause of
|
||||
a SELECT statement (as well as order by and group by) can derive from this -
|
||||
the object will automatically have Python "comparison" behavior.
|
||||
|
||||
:class:`~sqlalchemy.sql.expression.ColumnElement` classes want to have a
|
||||
``type`` member which is expression's return type. This can be established
|
||||
at the instance level in the constructor, or at the class level if its
|
||||
generally constant::
|
||||
|
||||
class timestamp(ColumnElement):
|
||||
type = TIMESTAMP()
|
||||
|
||||
* :class:`~sqlalchemy.sql.expression.FunctionElement` - This is a hybrid of a
|
||||
``ColumnElement`` and a "from clause" like object, and represents a SQL
|
||||
function or stored procedure type of call. Since most databases support
|
||||
statements along the line of "SELECT FROM <some function>"
|
||||
``FunctionElement`` adds in the ability to be used in the FROM clause of a
|
||||
``select()`` construct::
|
||||
|
||||
from sqlalchemy.sql.expression import FunctionElement
|
||||
|
||||
class coalesce(FunctionElement):
|
||||
name = 'coalesce'
|
||||
|
||||
@compiles(coalesce)
|
||||
def compile(element, compiler, **kw):
|
||||
return "coalesce(%s)" % compiler.process(element.clauses)
|
||||
|
||||
@compiles(coalesce, 'oracle')
|
||||
def compile(element, compiler, **kw):
|
||||
if len(element.clauses) > 2:
|
||||
raise TypeError("coalesce only supports two arguments on Oracle")
|
||||
return "nvl(%s)" % compiler.process(element.clauses)
|
||||
|
||||
* :class:`~sqlalchemy.schema.DDLElement` - The root of all DDL expressions,
|
||||
like CREATE TABLE, ALTER TABLE, etc. Compilation of ``DDLElement``
|
||||
subclasses is issued by a ``DDLCompiler`` instead of a ``SQLCompiler``.
|
||||
``DDLElement`` also features ``Table`` and ``MetaData`` event hooks via the
|
||||
``execute_at()`` method, allowing the construct to be invoked during CREATE
|
||||
TABLE and DROP TABLE sequences.
|
||||
|
||||
* :class:`~sqlalchemy.sql.expression.Executable` - This is a mixin which
|
||||
should be used with any expression class that represents a "standalone"
|
||||
SQL statement that can be passed directly to an ``execute()`` method. It
|
||||
is already implicit within ``DDLElement`` and ``FunctionElement``.
|
||||
|
||||
Further Examples
|
||||
================
|
||||
|
||||
"UTC timestamp" function
|
||||
-------------------------
|
||||
|
||||
A function that works like "CURRENT_TIMESTAMP" except applies the
|
||||
appropriate conversions so that the time is in UTC time. Timestamps are best
|
||||
stored in relational databases as UTC, without time zones. UTC so that your
|
||||
database doesn't think time has gone backwards in the hour when daylight
|
||||
savings ends, without timezones because timezones are like character
|
||||
encodings - they're best applied only at the endpoints of an application
|
||||
(i.e. convert to UTC upon user input, re-apply desired timezone upon display).
|
||||
|
||||
For Postgresql and Microsoft SQL Server::
|
||||
|
||||
from sqlalchemy.sql import expression
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.types import DateTime
|
||||
|
||||
class utcnow(expression.FunctionElement):
|
||||
type = DateTime()
|
||||
|
||||
@compiles(utcnow, 'postgresql')
|
||||
def pg_utcnow(element, compiler, **kw):
|
||||
return "TIMEZONE('utc', CURRENT_TIMESTAMP)"
|
||||
|
||||
@compiles(utcnow, 'mssql')
|
||||
def ms_utcnow(element, compiler, **kw):
|
||||
return "GETUTCDATE()"
|
||||
|
||||
Example usage::
|
||||
|
||||
from sqlalchemy import (
|
||||
Table, Column, Integer, String, DateTime, MetaData
|
||||
)
|
||||
metadata = MetaData()
|
||||
event = Table("event", metadata,
|
||||
Column("id", Integer, primary_key=True),
|
||||
Column("description", String(50), nullable=False),
|
||||
Column("timestamp", DateTime, server_default=utcnow())
|
||||
)
|
||||
|
||||
"GREATEST" function
|
||||
-------------------
|
||||
|
||||
The "GREATEST" function is given any number of arguments and returns the one
|
||||
that is of the highest value - it's equivalent to Python's ``max``
|
||||
function. A SQL standard version versus a CASE based version which only
|
||||
accommodates two arguments::
|
||||
|
||||
from sqlalchemy.sql import expression
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.types import Numeric
|
||||
|
||||
class greatest(expression.FunctionElement):
|
||||
type = Numeric()
|
||||
name = 'greatest'
|
||||
|
||||
@compiles(greatest)
|
||||
def default_greatest(element, compiler, **kw):
|
||||
return compiler.visit_function(element)
|
||||
|
||||
@compiles(greatest, 'sqlite')
|
||||
@compiles(greatest, 'mssql')
|
||||
@compiles(greatest, 'oracle')
|
||||
def case_greatest(element, compiler, **kw):
|
||||
arg1, arg2 = list(element.clauses)
|
||||
return "CASE WHEN %s > %s THEN %s ELSE %s END" % (
|
||||
compiler.process(arg1),
|
||||
compiler.process(arg2),
|
||||
compiler.process(arg1),
|
||||
compiler.process(arg2),
|
||||
)
|
||||
|
||||
Example usage::
|
||||
|
||||
Session.query(Account).\\
|
||||
filter(
|
||||
greatest(
|
||||
Account.checking_balance,
|
||||
Account.savings_balance) > 10000
|
||||
)
|
||||
|
||||
"false" expression
|
||||
------------------
|
||||
|
||||
Render a "false" constant expression, rendering as "0" on platforms that
|
||||
don't have a "false" constant::
|
||||
|
||||
from sqlalchemy.sql import expression
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
|
||||
class sql_false(expression.ColumnElement):
|
||||
pass
|
||||
|
||||
@compiles(sql_false)
|
||||
def default_false(element, compiler, **kw):
|
||||
return "false"
|
||||
|
||||
@compiles(sql_false, 'mssql')
|
||||
@compiles(sql_false, 'mysql')
|
||||
@compiles(sql_false, 'oracle')
|
||||
def int_false(element, compiler, **kw):
|
||||
return "0"
|
||||
|
||||
Example usage::
|
||||
|
||||
from sqlalchemy import select, union_all
|
||||
|
||||
exp = union_all(
|
||||
select([users.c.name, sql_false().label("enrolled")]),
|
||||
select([customers.c.name, customers.c.enrolled])
|
||||
)
|
||||
|
||||
"""
|
||||
from .. import exc
|
||||
from ..sql import visitors
|
||||
|
||||
|
||||
def compiles(class_, *specs):
|
||||
"""Register a function as a compiler for a
|
||||
given :class:`.ClauseElement` type."""
|
||||
|
||||
def decorate(fn):
|
||||
existing = class_.__dict__.get('_compiler_dispatcher', None)
|
||||
existing_dispatch = class_.__dict__.get('_compiler_dispatch')
|
||||
if not existing:
|
||||
existing = _dispatcher()
|
||||
|
||||
if existing_dispatch:
|
||||
existing.specs['default'] = existing_dispatch
|
||||
|
||||
# TODO: why is the lambda needed ?
|
||||
setattr(class_, '_compiler_dispatch',
|
||||
lambda *arg, **kw: existing(*arg, **kw))
|
||||
setattr(class_, '_compiler_dispatcher', existing)
|
||||
|
||||
if specs:
|
||||
for s in specs:
|
||||
existing.specs[s] = fn
|
||||
|
||||
else:
|
||||
existing.specs['default'] = fn
|
||||
return fn
|
||||
return decorate
|
||||
|
||||
|
||||
def deregister(class_):
|
||||
"""Remove all custom compilers associated with a given
|
||||
:class:`.ClauseElement` type."""
|
||||
|
||||
if hasattr(class_, '_compiler_dispatcher'):
|
||||
# regenerate default _compiler_dispatch
|
||||
visitors._generate_dispatch(class_)
|
||||
# remove custom directive
|
||||
del class_._compiler_dispatcher
|
||||
|
||||
|
||||
class _dispatcher(object):
|
||||
def __init__(self):
|
||||
self.specs = {}
|
||||
|
||||
def __call__(self, element, compiler, **kw):
|
||||
# TODO: yes, this could also switch off of DBAPI in use.
|
||||
fn = self.specs.get(compiler.dialect.name, None)
|
||||
if not fn:
|
||||
try:
|
||||
fn = self.specs['default']
|
||||
except KeyError:
|
||||
raise exc.CompileError(
|
||||
"%s construct has no default "
|
||||
"compilation handler." % type(element))
|
||||
return fn(element, compiler, **kw)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,486 @@
|
||||
# ext/declarative/api.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
"""Public API functions and helpers for declarative."""
|
||||
|
||||
|
||||
from ...schema import Table, MetaData
|
||||
from ...orm import synonym as _orm_synonym, mapper,\
|
||||
comparable_property,\
|
||||
interfaces
|
||||
from ...orm.util import polymorphic_union, _mapper_or_none
|
||||
from ... import exc
|
||||
import weakref
|
||||
|
||||
from .base import _as_declarative, \
|
||||
_declarative_constructor,\
|
||||
_MapperConfig, _add_attribute
|
||||
|
||||
|
||||
def instrument_declarative(cls, registry, metadata):
|
||||
"""Given a class, configure the class declaratively,
|
||||
using the given registry, which can be any dictionary, and
|
||||
MetaData object.
|
||||
|
||||
"""
|
||||
if '_decl_class_registry' in cls.__dict__:
|
||||
raise exc.InvalidRequestError(
|
||||
"Class %r already has been "
|
||||
"instrumented declaratively" % cls)
|
||||
cls._decl_class_registry = registry
|
||||
cls.metadata = metadata
|
||||
_as_declarative(cls, cls.__name__, cls.__dict__)
|
||||
|
||||
|
||||
def has_inherited_table(cls):
|
||||
"""Given a class, return True if any of the classes it inherits from has a
|
||||
mapped table, otherwise return False.
|
||||
"""
|
||||
for class_ in cls.__mro__[1:]:
|
||||
if getattr(class_, '__table__', None) is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class DeclarativeMeta(type):
|
||||
def __init__(cls, classname, bases, dict_):
|
||||
if '_decl_class_registry' not in cls.__dict__:
|
||||
_as_declarative(cls, classname, cls.__dict__)
|
||||
type.__init__(cls, classname, bases, dict_)
|
||||
|
||||
def __setattr__(cls, key, value):
|
||||
_add_attribute(cls, key, value)
|
||||
|
||||
|
||||
def synonym_for(name, map_column=False):
|
||||
"""Decorator, make a Python @property a query synonym for a column.
|
||||
|
||||
A decorator version of :func:`~sqlalchemy.orm.synonym`. The function being
|
||||
decorated is the 'descriptor', otherwise passes its arguments through to
|
||||
synonym()::
|
||||
|
||||
@synonym_for('col')
|
||||
@property
|
||||
def prop(self):
|
||||
return 'special sauce'
|
||||
|
||||
The regular ``synonym()`` is also usable directly in a declarative setting
|
||||
and may be convenient for read/write properties::
|
||||
|
||||
prop = synonym('col', descriptor=property(_read_prop, _write_prop))
|
||||
|
||||
"""
|
||||
def decorate(fn):
|
||||
return _orm_synonym(name, map_column=map_column, descriptor=fn)
|
||||
return decorate
|
||||
|
||||
|
||||
def comparable_using(comparator_factory):
|
||||
"""Decorator, allow a Python @property to be used in query criteria.
|
||||
|
||||
This is a decorator front end to
|
||||
:func:`~sqlalchemy.orm.comparable_property` that passes
|
||||
through the comparator_factory and the function being decorated::
|
||||
|
||||
@comparable_using(MyComparatorType)
|
||||
@property
|
||||
def prop(self):
|
||||
return 'special sauce'
|
||||
|
||||
The regular ``comparable_property()`` is also usable directly in a
|
||||
declarative setting and may be convenient for read/write properties::
|
||||
|
||||
prop = comparable_property(MyComparatorType)
|
||||
|
||||
"""
|
||||
def decorate(fn):
|
||||
return comparable_property(comparator_factory, fn)
|
||||
return decorate
|
||||
|
||||
|
||||
class declared_attr(interfaces._MappedAttribute, property):
|
||||
"""Mark a class-level method as representing the definition of
|
||||
a mapped property or special declarative member name.
|
||||
|
||||
@declared_attr turns the attribute into a scalar-like
|
||||
property that can be invoked from the uninstantiated class.
|
||||
Declarative treats attributes specifically marked with
|
||||
@declared_attr as returning a construct that is specific
|
||||
to mapping or declarative table configuration. The name
|
||||
of the attribute is that of what the non-dynamic version
|
||||
of the attribute would be.
|
||||
|
||||
@declared_attr is more often than not applicable to mixins,
|
||||
to define relationships that are to be applied to different
|
||||
implementors of the class::
|
||||
|
||||
class ProvidesUser(object):
|
||||
"A mixin that adds a 'user' relationship to classes."
|
||||
|
||||
@declared_attr
|
||||
def user(self):
|
||||
return relationship("User")
|
||||
|
||||
It also can be applied to mapped classes, such as to provide
|
||||
a "polymorphic" scheme for inheritance::
|
||||
|
||||
class Employee(Base):
|
||||
id = Column(Integer, primary_key=True)
|
||||
type = Column(String(50), nullable=False)
|
||||
|
||||
@declared_attr
|
||||
def __tablename__(cls):
|
||||
return cls.__name__.lower()
|
||||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
if cls.__name__ == 'Employee':
|
||||
return {
|
||||
"polymorphic_on":cls.type,
|
||||
"polymorphic_identity":"Employee"
|
||||
}
|
||||
else:
|
||||
return {"polymorphic_identity":cls.__name__}
|
||||
|
||||
.. versionchanged:: 0.8 :class:`.declared_attr` can be used with
|
||||
non-ORM or extension attributes, such as user-defined attributes
|
||||
or :func:`.association_proxy` objects, which will be assigned
|
||||
to the class at class construction time.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, fget, *arg, **kw):
|
||||
super(declared_attr, self).__init__(fget, *arg, **kw)
|
||||
self.__doc__ = fget.__doc__
|
||||
|
||||
def __get__(desc, self, cls):
|
||||
return desc.fget(cls)
|
||||
|
||||
|
||||
def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
|
||||
name='Base', constructor=_declarative_constructor,
|
||||
class_registry=None,
|
||||
metaclass=DeclarativeMeta):
|
||||
"""Construct a base class for declarative class definitions.
|
||||
|
||||
The new base class will be given a metaclass that produces
|
||||
appropriate :class:`~sqlalchemy.schema.Table` objects and makes
|
||||
the appropriate :func:`~sqlalchemy.orm.mapper` calls based on the
|
||||
information provided declaratively in the class and any subclasses
|
||||
of the class.
|
||||
|
||||
:param bind: An optional
|
||||
:class:`~sqlalchemy.engine.base.Connectable`, will be assigned
|
||||
the ``bind`` attribute on the :class:`~sqlalchemy.MetaData`
|
||||
instance.
|
||||
|
||||
:param metadata:
|
||||
An optional :class:`~sqlalchemy.MetaData` instance. All
|
||||
:class:`~sqlalchemy.schema.Table` objects implicitly declared by
|
||||
subclasses of the base will share this MetaData. A MetaData instance
|
||||
will be created if none is provided. The
|
||||
:class:`~sqlalchemy.MetaData` instance will be available via the
|
||||
`metadata` attribute of the generated declarative base class.
|
||||
|
||||
:param mapper:
|
||||
An optional callable, defaults to :func:`~sqlalchemy.orm.mapper`. Will
|
||||
be used to map subclasses to their Tables.
|
||||
|
||||
:param cls:
|
||||
Defaults to :class:`object`. A type to use as the base for the generated
|
||||
declarative base class. May be a class or tuple of classes.
|
||||
|
||||
:param name:
|
||||
Defaults to ``Base``. The display name for the generated
|
||||
class. Customizing this is not required, but can improve clarity in
|
||||
tracebacks and debugging.
|
||||
|
||||
:param constructor:
|
||||
Defaults to
|
||||
:func:`~sqlalchemy.ext.declarative._declarative_constructor`, an
|
||||
__init__ implementation that assigns \**kwargs for declared
|
||||
fields and relationships to an instance. If ``None`` is supplied,
|
||||
no __init__ will be provided and construction will fall back to
|
||||
cls.__init__ by way of the normal Python semantics.
|
||||
|
||||
:param class_registry: optional dictionary that will serve as the
|
||||
registry of class names-> mapped classes when string names
|
||||
are used to identify classes inside of :func:`.relationship`
|
||||
and others. Allows two or more declarative base classes
|
||||
to share the same registry of class names for simplified
|
||||
inter-base relationships.
|
||||
|
||||
:param metaclass:
|
||||
Defaults to :class:`.DeclarativeMeta`. A metaclass or __metaclass__
|
||||
compatible callable to use as the meta type of the generated
|
||||
declarative base class.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:func:`.as_declarative`
|
||||
|
||||
"""
|
||||
lcl_metadata = metadata or MetaData()
|
||||
if bind:
|
||||
lcl_metadata.bind = bind
|
||||
|
||||
if class_registry is None:
|
||||
class_registry = weakref.WeakValueDictionary()
|
||||
|
||||
bases = not isinstance(cls, tuple) and (cls,) or cls
|
||||
class_dict = dict(_decl_class_registry=class_registry,
|
||||
metadata=lcl_metadata)
|
||||
|
||||
if constructor:
|
||||
class_dict['__init__'] = constructor
|
||||
if mapper:
|
||||
class_dict['__mapper_cls__'] = mapper
|
||||
|
||||
return metaclass(name, bases, class_dict)
|
||||
|
||||
def as_declarative(**kw):
|
||||
"""
|
||||
Class decorator for :func:`.declarative_base`.
|
||||
|
||||
Provides a syntactical shortcut to the ``cls`` argument
|
||||
sent to :func:`.declarative_base`, allowing the base class
|
||||
to be converted in-place to a "declarative" base::
|
||||
|
||||
from sqlalchemy.ext.declarative import as_declarative
|
||||
|
||||
@as_declarative()
|
||||
class Base(object)
|
||||
@declared_attr
|
||||
def __tablename__(cls):
|
||||
return cls.__name__.lower()
|
||||
id = Column(Integer, primary_key=True)
|
||||
|
||||
class MyMappedClass(Base):
|
||||
# ...
|
||||
|
||||
All keyword arguments passed to :func:`.as_declarative` are passed
|
||||
along to :func:`.declarative_base`.
|
||||
|
||||
.. versionadded:: 0.8.3
|
||||
|
||||
.. seealso::
|
||||
|
||||
:func:`.declarative_base`
|
||||
|
||||
"""
|
||||
def decorate(cls):
|
||||
kw['cls'] = cls
|
||||
kw['name'] = cls.__name__
|
||||
return declarative_base(**kw)
|
||||
|
||||
return decorate
|
||||
|
||||
class ConcreteBase(object):
|
||||
"""A helper class for 'concrete' declarative mappings.
|
||||
|
||||
:class:`.ConcreteBase` will use the :func:`.polymorphic_union`
|
||||
function automatically, against all tables mapped as a subclass
|
||||
to this class. The function is called via the
|
||||
``__declare_last__()`` function, which is essentially
|
||||
a hook for the :func:`.MapperEvents.after_configured` event.
|
||||
|
||||
:class:`.ConcreteBase` produces a mapped
|
||||
table for the class itself. Compare to :class:`.AbstractConcreteBase`,
|
||||
which does not.
|
||||
|
||||
Example::
|
||||
|
||||
from sqlalchemy.ext.declarative import ConcreteBase
|
||||
|
||||
class Employee(ConcreteBase, Base):
|
||||
__tablename__ = 'employee'
|
||||
employee_id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50))
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity':'employee',
|
||||
'concrete':True}
|
||||
|
||||
class Manager(Employee):
|
||||
__tablename__ = 'manager'
|
||||
employee_id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50))
|
||||
manager_data = Column(String(40))
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity':'manager',
|
||||
'concrete':True}
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _create_polymorphic_union(cls, mappers):
|
||||
return polymorphic_union(dict(
|
||||
(mp.polymorphic_identity, mp.local_table)
|
||||
for mp in mappers
|
||||
), 'type', 'pjoin')
|
||||
|
||||
@classmethod
|
||||
def __declare_last__(cls):
|
||||
m = cls.__mapper__
|
||||
if m.with_polymorphic:
|
||||
return
|
||||
|
||||
mappers = list(m.self_and_descendants)
|
||||
pjoin = cls._create_polymorphic_union(mappers)
|
||||
m._set_with_polymorphic(("*", pjoin))
|
||||
m._set_polymorphic_on(pjoin.c.type)
|
||||
|
||||
|
||||
class AbstractConcreteBase(ConcreteBase):
|
||||
"""A helper class for 'concrete' declarative mappings.
|
||||
|
||||
:class:`.AbstractConcreteBase` will use the :func:`.polymorphic_union`
|
||||
function automatically, against all tables mapped as a subclass
|
||||
to this class. The function is called via the
|
||||
``__declare_last__()`` function, which is essentially
|
||||
a hook for the :func:`.MapperEvents.after_configured` event.
|
||||
|
||||
:class:`.AbstractConcreteBase` does not produce a mapped
|
||||
table for the class itself. Compare to :class:`.ConcreteBase`,
|
||||
which does.
|
||||
|
||||
Example::
|
||||
|
||||
from sqlalchemy.ext.declarative import AbstractConcreteBase
|
||||
|
||||
class Employee(AbstractConcreteBase, Base):
|
||||
pass
|
||||
|
||||
class Manager(Employee):
|
||||
__tablename__ = 'manager'
|
||||
employee_id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50))
|
||||
manager_data = Column(String(40))
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity':'manager',
|
||||
'concrete':True}
|
||||
|
||||
"""
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
@classmethod
|
||||
def __declare_last__(cls):
|
||||
if hasattr(cls, '__mapper__'):
|
||||
return
|
||||
|
||||
# can't rely on 'self_and_descendants' here
|
||||
# since technically an immediate subclass
|
||||
# might not be mapped, but a subclass
|
||||
# may be.
|
||||
mappers = []
|
||||
stack = list(cls.__subclasses__())
|
||||
while stack:
|
||||
klass = stack.pop()
|
||||
stack.extend(klass.__subclasses__())
|
||||
mn = _mapper_or_none(klass)
|
||||
if mn is not None:
|
||||
mappers.append(mn)
|
||||
pjoin = cls._create_polymorphic_union(mappers)
|
||||
cls.__mapper__ = m = mapper(cls, pjoin, polymorphic_on=pjoin.c.type)
|
||||
|
||||
for scls in cls.__subclasses__():
|
||||
sm = _mapper_or_none(scls)
|
||||
if sm.concrete and cls in scls.__bases__:
|
||||
sm._set_concrete_base(m)
|
||||
|
||||
|
||||
class DeferredReflection(object):
|
||||
"""A helper class for construction of mappings based on
|
||||
a deferred reflection step.
|
||||
|
||||
Normally, declarative can be used with reflection by
|
||||
setting a :class:`.Table` object using autoload=True
|
||||
as the ``__table__`` attribute on a declarative class.
|
||||
The caveat is that the :class:`.Table` must be fully
|
||||
reflected, or at the very least have a primary key column,
|
||||
at the point at which a normal declarative mapping is
|
||||
constructed, meaning the :class:`.Engine` must be available
|
||||
at class declaration time.
|
||||
|
||||
The :class:`.DeferredReflection` mixin moves the construction
|
||||
of mappers to be at a later point, after a specific
|
||||
method is called which first reflects all :class:`.Table`
|
||||
objects created so far. Classes can define it as such::
|
||||
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.ext.declarative import DeferredReflection
|
||||
Base = declarative_base()
|
||||
|
||||
class MyClass(DeferredReflection, Base):
|
||||
__tablename__ = 'mytable'
|
||||
|
||||
Above, ``MyClass`` is not yet mapped. After a series of
|
||||
classes have been defined in the above fashion, all tables
|
||||
can be reflected and mappings created using
|
||||
:meth:`.DeferredReflection.prepare`::
|
||||
|
||||
engine = create_engine("someengine://...")
|
||||
DeferredReflection.prepare(engine)
|
||||
|
||||
The :class:`.DeferredReflection` mixin can be applied to individual
|
||||
classes, used as the base for the declarative base itself,
|
||||
or used in a custom abstract class. Using an abstract base
|
||||
allows that only a subset of classes to be prepared for a
|
||||
particular prepare step, which is necessary for applications
|
||||
that use more than one engine. For example, if an application
|
||||
has two engines, you might use two bases, and prepare each
|
||||
separately, e.g.::
|
||||
|
||||
class ReflectedOne(DeferredReflection, Base):
|
||||
__abstract__ = True
|
||||
|
||||
class ReflectedTwo(DeferredReflection, Base):
|
||||
__abstract__ = True
|
||||
|
||||
class MyClass(ReflectedOne):
|
||||
__tablename__ = 'mytable'
|
||||
|
||||
class MyOtherClass(ReflectedOne):
|
||||
__tablename__ = 'myothertable'
|
||||
|
||||
class YetAnotherClass(ReflectedTwo):
|
||||
__tablename__ = 'yetanothertable'
|
||||
|
||||
# ... etc.
|
||||
|
||||
Above, the class hierarchies for ``ReflectedOne`` and
|
||||
``ReflectedTwo`` can be configured separately::
|
||||
|
||||
ReflectedOne.prepare(engine_one)
|
||||
ReflectedTwo.prepare(engine_two)
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
"""
|
||||
@classmethod
|
||||
def prepare(cls, engine):
|
||||
"""Reflect all :class:`.Table` objects for all current
|
||||
:class:`.DeferredReflection` subclasses"""
|
||||
to_map = [m for m in _MapperConfig.configs.values()
|
||||
if issubclass(m.cls, cls)]
|
||||
for thingy in to_map:
|
||||
cls._sa_decl_prepare(thingy.local_table, engine)
|
||||
thingy.map()
|
||||
|
||||
@classmethod
|
||||
def _sa_decl_prepare(cls, local_table, engine):
|
||||
# autoload Table, which is already
|
||||
# present in the metadata. This
|
||||
# will fill in db-loaded columns
|
||||
# into the existing Table object.
|
||||
if local_table is not None:
|
||||
Table(local_table.name,
|
||||
local_table.metadata,
|
||||
extend_existing=True,
|
||||
autoload_replace=False,
|
||||
autoload=True,
|
||||
autoload_with=engine,
|
||||
schema=local_table.schema)
|
||||
@@ -0,0 +1,433 @@
|
||||
# ext/declarative/base.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
"""Internal implementation for declarative."""
|
||||
|
||||
from ...schema import Table, Column
|
||||
from ...orm import mapper, class_mapper
|
||||
from ...orm.interfaces import MapperProperty
|
||||
from ...orm.properties import ColumnProperty, CompositeProperty
|
||||
from ...orm.util import _is_mapped_class
|
||||
from ... import util, exc
|
||||
from ...sql import expression
|
||||
from ... import event
|
||||
from . import clsregistry
|
||||
|
||||
|
||||
def _declared_mapping_info(cls):
|
||||
# deferred mapping
|
||||
if cls in _MapperConfig.configs:
|
||||
return _MapperConfig.configs[cls]
|
||||
# regular mapping
|
||||
elif _is_mapped_class(cls):
|
||||
return class_mapper(cls, configure=False)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def _as_declarative(cls, classname, dict_):
|
||||
from .api import declared_attr
|
||||
|
||||
# dict_ will be a dictproxy, which we can't write to, and we need to!
|
||||
dict_ = dict(dict_)
|
||||
|
||||
column_copies = {}
|
||||
potential_columns = {}
|
||||
|
||||
mapper_args_fn = None
|
||||
table_args = inherited_table_args = None
|
||||
tablename = None
|
||||
|
||||
declarative_props = (declared_attr, util.classproperty)
|
||||
|
||||
for base in cls.__mro__:
|
||||
_is_declarative_inherits = hasattr(base, '_decl_class_registry')
|
||||
|
||||
if '__declare_last__' in base.__dict__:
|
||||
@event.listens_for(mapper, "after_configured")
|
||||
def go():
|
||||
cls.__declare_last__()
|
||||
if '__abstract__' in base.__dict__:
|
||||
if (base is cls or
|
||||
(base in cls.__bases__ and not _is_declarative_inherits)
|
||||
):
|
||||
return
|
||||
|
||||
class_mapped = _declared_mapping_info(base) is not None
|
||||
|
||||
for name, obj in vars(base).items():
|
||||
if name == '__mapper_args__':
|
||||
if not mapper_args_fn and (
|
||||
not class_mapped or
|
||||
isinstance(obj, declarative_props)
|
||||
):
|
||||
# don't even invoke __mapper_args__ until
|
||||
# after we've determined everything about the
|
||||
# mapped table.
|
||||
mapper_args_fn = lambda: cls.__mapper_args__
|
||||
elif name == '__tablename__':
|
||||
if not tablename and (
|
||||
not class_mapped or
|
||||
isinstance(obj, declarative_props)
|
||||
):
|
||||
tablename = cls.__tablename__
|
||||
elif name == '__table_args__':
|
||||
if not table_args and (
|
||||
not class_mapped or
|
||||
isinstance(obj, declarative_props)
|
||||
):
|
||||
table_args = cls.__table_args__
|
||||
if not isinstance(table_args, (tuple, dict, type(None))):
|
||||
raise exc.ArgumentError(
|
||||
"__table_args__ value must be a tuple, "
|
||||
"dict, or None")
|
||||
if base is not cls:
|
||||
inherited_table_args = True
|
||||
elif class_mapped:
|
||||
if isinstance(obj, declarative_props):
|
||||
util.warn("Regular (i.e. not __special__) "
|
||||
"attribute '%s.%s' uses @declared_attr, "
|
||||
"but owning class %s is mapped - "
|
||||
"not applying to subclass %s."
|
||||
% (base.__name__, name, base, cls))
|
||||
continue
|
||||
elif base is not cls:
|
||||
# we're a mixin.
|
||||
if isinstance(obj, Column):
|
||||
if getattr(cls, name) is not obj:
|
||||
# if column has been overridden
|
||||
# (like by the InstrumentedAttribute of the
|
||||
# superclass), skip
|
||||
continue
|
||||
if obj.foreign_keys:
|
||||
raise exc.InvalidRequestError(
|
||||
"Columns with foreign keys to other columns "
|
||||
"must be declared as @declared_attr callables "
|
||||
"on declarative mixin classes. ")
|
||||
if name not in dict_ and not (
|
||||
'__table__' in dict_ and
|
||||
(obj.name or name) in dict_['__table__'].c
|
||||
) and name not in potential_columns:
|
||||
potential_columns[name] = \
|
||||
column_copies[obj] = \
|
||||
obj.copy()
|
||||
column_copies[obj]._creation_order = \
|
||||
obj._creation_order
|
||||
elif isinstance(obj, MapperProperty):
|
||||
raise exc.InvalidRequestError(
|
||||
"Mapper properties (i.e. deferred,"
|
||||
"column_property(), relationship(), etc.) must "
|
||||
"be declared as @declared_attr callables "
|
||||
"on declarative mixin classes.")
|
||||
elif isinstance(obj, declarative_props):
|
||||
dict_[name] = ret = \
|
||||
column_copies[obj] = getattr(cls, name)
|
||||
if isinstance(ret, (Column, MapperProperty)) and \
|
||||
ret.doc is None:
|
||||
ret.doc = obj.__doc__
|
||||
|
||||
# apply inherited columns as we should
|
||||
for k, v in potential_columns.items():
|
||||
dict_[k] = v
|
||||
|
||||
if inherited_table_args and not tablename:
|
||||
table_args = None
|
||||
|
||||
clsregistry.add_class(classname, cls)
|
||||
our_stuff = util.OrderedDict()
|
||||
|
||||
for k in list(dict_):
|
||||
|
||||
# TODO: improve this ? all dunders ?
|
||||
if k in ('__table__', '__tablename__', '__mapper_args__'):
|
||||
continue
|
||||
|
||||
value = dict_[k]
|
||||
if isinstance(value, declarative_props):
|
||||
value = getattr(cls, k)
|
||||
|
||||
if (isinstance(value, tuple) and len(value) == 1 and
|
||||
isinstance(value[0], (Column, MapperProperty))):
|
||||
util.warn("Ignoring declarative-like tuple value of attribute "
|
||||
"%s: possibly a copy-and-paste error with a comma "
|
||||
"left at the end of the line?" % k)
|
||||
continue
|
||||
if not isinstance(value, (Column, MapperProperty)):
|
||||
if not k.startswith('__'):
|
||||
dict_.pop(k)
|
||||
setattr(cls, k, value)
|
||||
continue
|
||||
if k == 'metadata':
|
||||
raise exc.InvalidRequestError(
|
||||
"Attribute name 'metadata' is reserved "
|
||||
"for the MetaData instance when using a "
|
||||
"declarative base class."
|
||||
)
|
||||
prop = clsregistry._deferred_relationship(cls, value)
|
||||
our_stuff[k] = prop
|
||||
|
||||
# set up attributes in the order they were created
|
||||
our_stuff.sort(key=lambda key: our_stuff[key]._creation_order)
|
||||
|
||||
# extract columns from the class dict
|
||||
declared_columns = set()
|
||||
for key, c in our_stuff.iteritems():
|
||||
if isinstance(c, (ColumnProperty, CompositeProperty)):
|
||||
for col in c.columns:
|
||||
if isinstance(col, Column) and \
|
||||
col.table is None:
|
||||
_undefer_column_name(key, col)
|
||||
declared_columns.add(col)
|
||||
elif isinstance(c, Column):
|
||||
_undefer_column_name(key, c)
|
||||
declared_columns.add(c)
|
||||
# if the column is the same name as the key,
|
||||
# remove it from the explicit properties dict.
|
||||
# the normal rules for assigning column-based properties
|
||||
# will take over, including precedence of columns
|
||||
# in multi-column ColumnProperties.
|
||||
if key == c.key:
|
||||
del our_stuff[key]
|
||||
declared_columns = sorted(
|
||||
declared_columns, key=lambda c: c._creation_order)
|
||||
table = None
|
||||
|
||||
if hasattr(cls, '__table_cls__'):
|
||||
table_cls = util.unbound_method_to_callable(cls.__table_cls__)
|
||||
else:
|
||||
table_cls = Table
|
||||
|
||||
if '__table__' not in dict_:
|
||||
if tablename is not None:
|
||||
|
||||
args, table_kw = (), {}
|
||||
if table_args:
|
||||
if isinstance(table_args, dict):
|
||||
table_kw = table_args
|
||||
elif isinstance(table_args, tuple):
|
||||
if isinstance(table_args[-1], dict):
|
||||
args, table_kw = table_args[0:-1], table_args[-1]
|
||||
else:
|
||||
args = table_args
|
||||
|
||||
autoload = dict_.get('__autoload__')
|
||||
if autoload:
|
||||
table_kw['autoload'] = True
|
||||
|
||||
cls.__table__ = table = table_cls(
|
||||
tablename, cls.metadata,
|
||||
*(tuple(declared_columns) + tuple(args)),
|
||||
**table_kw)
|
||||
else:
|
||||
table = cls.__table__
|
||||
if declared_columns:
|
||||
for c in declared_columns:
|
||||
if not table.c.contains_column(c):
|
||||
raise exc.ArgumentError(
|
||||
"Can't add additional column %r when "
|
||||
"specifying __table__" % c.key
|
||||
)
|
||||
|
||||
if hasattr(cls, '__mapper_cls__'):
|
||||
mapper_cls = util.unbound_method_to_callable(cls.__mapper_cls__)
|
||||
else:
|
||||
mapper_cls = mapper
|
||||
|
||||
for c in cls.__bases__:
|
||||
if _declared_mapping_info(c) is not None:
|
||||
inherits = c
|
||||
break
|
||||
else:
|
||||
inherits = None
|
||||
|
||||
if table is None and inherits is None:
|
||||
raise exc.InvalidRequestError(
|
||||
"Class %r does not have a __table__ or __tablename__ "
|
||||
"specified and does not inherit from an existing "
|
||||
"table-mapped class." % cls
|
||||
)
|
||||
elif inherits:
|
||||
inherited_mapper = _declared_mapping_info(inherits)
|
||||
inherited_table = inherited_mapper.local_table
|
||||
inherited_mapped_table = inherited_mapper.mapped_table
|
||||
|
||||
if table is None:
|
||||
# single table inheritance.
|
||||
# ensure no table args
|
||||
if table_args:
|
||||
raise exc.ArgumentError(
|
||||
"Can't place __table_args__ on an inherited class "
|
||||
"with no table."
|
||||
)
|
||||
# add any columns declared here to the inherited table.
|
||||
for c in declared_columns:
|
||||
if c.primary_key:
|
||||
raise exc.ArgumentError(
|
||||
"Can't place primary key columns on an inherited "
|
||||
"class with no table."
|
||||
)
|
||||
if c.name in inherited_table.c:
|
||||
if inherited_table.c[c.name] is c:
|
||||
continue
|
||||
raise exc.ArgumentError(
|
||||
"Column '%s' on class %s conflicts with "
|
||||
"existing column '%s'" %
|
||||
(c, cls, inherited_table.c[c.name])
|
||||
)
|
||||
inherited_table.append_column(c)
|
||||
if inherited_mapped_table is not None and \
|
||||
inherited_mapped_table is not inherited_table:
|
||||
inherited_mapped_table._refresh_for_new_column(c)
|
||||
|
||||
mt = _MapperConfig(mapper_cls,
|
||||
cls, table,
|
||||
inherits,
|
||||
declared_columns,
|
||||
column_copies,
|
||||
our_stuff,
|
||||
mapper_args_fn)
|
||||
if not hasattr(cls, '_sa_decl_prepare'):
|
||||
mt.map()
|
||||
|
||||
|
||||
class _MapperConfig(object):
|
||||
configs = util.OrderedDict()
|
||||
mapped_table = None
|
||||
|
||||
def __init__(self, mapper_cls,
|
||||
cls,
|
||||
table,
|
||||
inherits,
|
||||
declared_columns,
|
||||
column_copies,
|
||||
properties, mapper_args_fn):
|
||||
self.mapper_cls = mapper_cls
|
||||
self.cls = cls
|
||||
self.local_table = table
|
||||
self.inherits = inherits
|
||||
self.properties = properties
|
||||
self.mapper_args_fn = mapper_args_fn
|
||||
self.declared_columns = declared_columns
|
||||
self.column_copies = column_copies
|
||||
self.configs[cls] = self
|
||||
|
||||
def _prepare_mapper_arguments(self):
|
||||
properties = self.properties
|
||||
if self.mapper_args_fn:
|
||||
mapper_args = self.mapper_args_fn()
|
||||
else:
|
||||
mapper_args = {}
|
||||
|
||||
# make sure that column copies are used rather
|
||||
# than the original columns from any mixins
|
||||
for k in ('version_id_col', 'polymorphic_on',):
|
||||
if k in mapper_args:
|
||||
v = mapper_args[k]
|
||||
mapper_args[k] = self.column_copies.get(v, v)
|
||||
|
||||
assert 'inherits' not in mapper_args, \
|
||||
"Can't specify 'inherits' explicitly with declarative mappings"
|
||||
|
||||
if self.inherits:
|
||||
mapper_args['inherits'] = self.inherits
|
||||
|
||||
if self.inherits and not mapper_args.get('concrete', False):
|
||||
# single or joined inheritance
|
||||
# exclude any cols on the inherited table which are
|
||||
# not mapped on the parent class, to avoid
|
||||
# mapping columns specific to sibling/nephew classes
|
||||
inherited_mapper = _declared_mapping_info(self.inherits)
|
||||
inherited_table = inherited_mapper.local_table
|
||||
|
||||
if 'exclude_properties' not in mapper_args:
|
||||
mapper_args['exclude_properties'] = exclude_properties = \
|
||||
set([c.key for c in inherited_table.c
|
||||
if c not in inherited_mapper._columntoproperty])
|
||||
exclude_properties.difference_update(
|
||||
[c.key for c in self.declared_columns])
|
||||
|
||||
# look through columns in the current mapper that
|
||||
# are keyed to a propname different than the colname
|
||||
# (if names were the same, we'd have popped it out above,
|
||||
# in which case the mapper makes this combination).
|
||||
# See if the superclass has a similar column property.
|
||||
# If so, join them together.
|
||||
for k, col in properties.items():
|
||||
if not isinstance(col, expression.ColumnElement):
|
||||
continue
|
||||
if k in inherited_mapper._props:
|
||||
p = inherited_mapper._props[k]
|
||||
if isinstance(p, ColumnProperty):
|
||||
# note here we place the subclass column
|
||||
# first. See [ticket:1892] for background.
|
||||
properties[k] = [col] + p.columns
|
||||
result_mapper_args = mapper_args.copy()
|
||||
result_mapper_args['properties'] = properties
|
||||
return result_mapper_args
|
||||
|
||||
def map(self):
|
||||
self.configs.pop(self.cls, None)
|
||||
mapper_args = self._prepare_mapper_arguments()
|
||||
self.cls.__mapper__ = self.mapper_cls(
|
||||
self.cls,
|
||||
self.local_table,
|
||||
**mapper_args
|
||||
)
|
||||
|
||||
|
||||
def _add_attribute(cls, key, value):
|
||||
"""add an attribute to an existing declarative class.
|
||||
|
||||
This runs through the logic to determine MapperProperty,
|
||||
adds it to the Mapper, adds a column to the mapped Table, etc.
|
||||
|
||||
"""
|
||||
if '__mapper__' in cls.__dict__:
|
||||
if isinstance(value, Column):
|
||||
_undefer_column_name(key, value)
|
||||
cls.__table__.append_column(value)
|
||||
cls.__mapper__.add_property(key, value)
|
||||
elif isinstance(value, ColumnProperty):
|
||||
for col in value.columns:
|
||||
if isinstance(col, Column) and col.table is None:
|
||||
_undefer_column_name(key, col)
|
||||
cls.__table__.append_column(col)
|
||||
cls.__mapper__.add_property(key, value)
|
||||
elif isinstance(value, MapperProperty):
|
||||
cls.__mapper__.add_property(
|
||||
key,
|
||||
clsregistry._deferred_relationship(cls, value)
|
||||
)
|
||||
else:
|
||||
type.__setattr__(cls, key, value)
|
||||
else:
|
||||
type.__setattr__(cls, key, value)
|
||||
|
||||
|
||||
def _declarative_constructor(self, **kwargs):
|
||||
"""A simple constructor that allows initialization from kwargs.
|
||||
|
||||
Sets attributes on the constructed instance using the names and
|
||||
values in ``kwargs``.
|
||||
|
||||
Only keys that are present as
|
||||
attributes of the instance's class are allowed. These could be,
|
||||
for example, any mapped columns or relationships.
|
||||
"""
|
||||
cls_ = type(self)
|
||||
for k in kwargs:
|
||||
if not hasattr(cls_, k):
|
||||
raise TypeError(
|
||||
"%r is an invalid keyword argument for %s" %
|
||||
(k, cls_.__name__))
|
||||
setattr(self, k, kwargs[k])
|
||||
_declarative_constructor.__name__ = '__init__'
|
||||
|
||||
|
||||
def _undefer_column_name(key, column):
|
||||
if column.key is None:
|
||||
column.key = key
|
||||
if column.name is None:
|
||||
column.name = key
|
||||
@@ -0,0 +1,290 @@
|
||||
# ext/declarative/clsregistry.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
"""Routines to handle the string class registry used by declarative.
|
||||
|
||||
This system allows specification of classes and expressions used in
|
||||
:func:`.relationship` using strings.
|
||||
|
||||
"""
|
||||
from ...orm.properties import ColumnProperty, RelationshipProperty, \
|
||||
SynonymProperty
|
||||
from ...schema import _get_table_key
|
||||
from ...orm import class_mapper, interfaces
|
||||
from ... import util
|
||||
from ... import exc
|
||||
import weakref
|
||||
|
||||
# strong references to registries which we place in
|
||||
# the _decl_class_registry, which is usually weak referencing.
|
||||
# the internal registries here link to classes with weakrefs and remove
|
||||
# themselves when all references to contained classes are removed.
|
||||
_registries = set()
|
||||
|
||||
|
||||
def add_class(classname, cls):
|
||||
"""Add a class to the _decl_class_registry associated with the
|
||||
given declarative class.
|
||||
|
||||
"""
|
||||
if classname in cls._decl_class_registry:
|
||||
# class already exists.
|
||||
existing = cls._decl_class_registry[classname]
|
||||
if not isinstance(existing, _MultipleClassMarker):
|
||||
existing = \
|
||||
cls._decl_class_registry[classname] = \
|
||||
_MultipleClassMarker([cls, existing])
|
||||
else:
|
||||
cls._decl_class_registry[classname] = cls
|
||||
|
||||
try:
|
||||
root_module = cls._decl_class_registry['_sa_module_registry']
|
||||
except KeyError:
|
||||
cls._decl_class_registry['_sa_module_registry'] = \
|
||||
root_module = _ModuleMarker('_sa_module_registry', None)
|
||||
|
||||
tokens = cls.__module__.split(".")
|
||||
|
||||
# build up a tree like this:
|
||||
# modulename: myapp.snacks.nuts
|
||||
#
|
||||
# myapp->snack->nuts->(classes)
|
||||
# snack->nuts->(classes)
|
||||
# nuts->(classes)
|
||||
#
|
||||
# this allows partial token paths to be used.
|
||||
while tokens:
|
||||
token = tokens.pop(0)
|
||||
module = root_module.get_module(token)
|
||||
for token in tokens:
|
||||
module = module.get_module(token)
|
||||
module.add_class(classname, cls)
|
||||
|
||||
|
||||
class _MultipleClassMarker(object):
|
||||
"""refers to multiple classes of the same name
|
||||
within _decl_class_registry.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, classes, on_remove=None):
|
||||
self.on_remove = on_remove
|
||||
self.contents = set([
|
||||
weakref.ref(item, self._remove_item) for item in classes])
|
||||
_registries.add(self)
|
||||
|
||||
def __iter__(self):
|
||||
return (ref() for ref in self.contents)
|
||||
|
||||
def attempt_get(self, path, key):
|
||||
if len(self.contents) > 1:
|
||||
raise exc.InvalidRequestError(
|
||||
"Multiple classes found for path \"%s\" "
|
||||
"in the registry of this declarative "
|
||||
"base. Please use a fully module-qualified path." %
|
||||
(".".join(path + [key]))
|
||||
)
|
||||
else:
|
||||
ref = list(self.contents)[0]
|
||||
cls = ref()
|
||||
if cls is None:
|
||||
raise NameError(key)
|
||||
return cls
|
||||
|
||||
def _remove_item(self, ref):
|
||||
self.contents.remove(ref)
|
||||
if not self.contents:
|
||||
_registries.discard(self)
|
||||
if self.on_remove:
|
||||
self.on_remove()
|
||||
|
||||
def add_item(self, item):
|
||||
modules = set([cls().__module__ for cls in self.contents])
|
||||
if item.__module__ in modules:
|
||||
util.warn(
|
||||
"This declarative base already contains a class with the "
|
||||
"same class name and module name as %s.%s, and will "
|
||||
"be replaced in the string-lookup table." % (
|
||||
item.__module__,
|
||||
item.__name__
|
||||
)
|
||||
)
|
||||
self.contents.add(weakref.ref(item, self._remove_item))
|
||||
|
||||
|
||||
class _ModuleMarker(object):
|
||||
""""refers to a module name within
|
||||
_decl_class_registry.
|
||||
|
||||
"""
|
||||
def __init__(self, name, parent):
|
||||
self.parent = parent
|
||||
self.name = name
|
||||
self.contents = {}
|
||||
self.mod_ns = _ModNS(self)
|
||||
if self.parent:
|
||||
self.path = self.parent.path + [self.name]
|
||||
else:
|
||||
self.path = []
|
||||
_registries.add(self)
|
||||
|
||||
def __contains__(self, name):
|
||||
return name in self.contents
|
||||
|
||||
def __getitem__(self, name):
|
||||
return self.contents[name]
|
||||
|
||||
def _remove_item(self, name):
|
||||
self.contents.pop(name, None)
|
||||
if not self.contents and self.parent is not None:
|
||||
self.parent._remove_item(self.name)
|
||||
_registries.discard(self)
|
||||
|
||||
def resolve_attr(self, key):
|
||||
return getattr(self.mod_ns, key)
|
||||
|
||||
def get_module(self, name):
|
||||
if name not in self.contents:
|
||||
marker = _ModuleMarker(name, self)
|
||||
self.contents[name] = marker
|
||||
else:
|
||||
marker = self.contents[name]
|
||||
return marker
|
||||
|
||||
def add_class(self, name, cls):
|
||||
if name in self.contents:
|
||||
existing = self.contents[name]
|
||||
existing.add_item(cls)
|
||||
else:
|
||||
existing = self.contents[name] = \
|
||||
_MultipleClassMarker([cls],
|
||||
on_remove=lambda: self._remove_item(name))
|
||||
|
||||
|
||||
class _ModNS(object):
|
||||
def __init__(self, parent):
|
||||
self.__parent = parent
|
||||
|
||||
def __getattr__(self, key):
|
||||
try:
|
||||
value = self.__parent.contents[key]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if value is not None:
|
||||
if isinstance(value, _ModuleMarker):
|
||||
return value.mod_ns
|
||||
else:
|
||||
assert isinstance(value, _MultipleClassMarker)
|
||||
return value.attempt_get(self.__parent.path, key)
|
||||
raise AttributeError("Module %r has no mapped classes "
|
||||
"registered under the name %r" % (self.__parent.name, key))
|
||||
|
||||
|
||||
class _GetColumns(object):
|
||||
def __init__(self, cls):
|
||||
self.cls = cls
|
||||
|
||||
def __getattr__(self, key):
|
||||
mp = class_mapper(self.cls, configure=False)
|
||||
if mp:
|
||||
if key not in mp.all_orm_descriptors:
|
||||
raise exc.InvalidRequestError(
|
||||
"Class %r does not have a mapped column named %r"
|
||||
% (self.cls, key))
|
||||
|
||||
desc = mp.all_orm_descriptors[key]
|
||||
if desc.extension_type is interfaces.NOT_EXTENSION:
|
||||
prop = desc.property
|
||||
if isinstance(prop, SynonymProperty):
|
||||
key = prop.name
|
||||
elif not isinstance(prop, ColumnProperty):
|
||||
raise exc.InvalidRequestError(
|
||||
"Property %r is not an instance of"
|
||||
" ColumnProperty (i.e. does not correspond"
|
||||
" directly to a Column)." % key)
|
||||
return getattr(self.cls, key)
|
||||
|
||||
|
||||
class _GetTable(object):
|
||||
def __init__(self, key, metadata):
|
||||
self.key = key
|
||||
self.metadata = metadata
|
||||
|
||||
def __getattr__(self, key):
|
||||
return self.metadata.tables[
|
||||
_get_table_key(key, self.key)
|
||||
]
|
||||
|
||||
|
||||
def _determine_container(key, value):
|
||||
if isinstance(value, _MultipleClassMarker):
|
||||
value = value.attempt_get([], key)
|
||||
return _GetColumns(value)
|
||||
|
||||
|
||||
def _resolver(cls, prop):
|
||||
def resolve_arg(arg):
|
||||
import sqlalchemy
|
||||
from sqlalchemy.orm import foreign, remote
|
||||
|
||||
fallback = sqlalchemy.__dict__.copy()
|
||||
fallback.update({'foreign': foreign, 'remote': remote})
|
||||
|
||||
def access_cls(key):
|
||||
if key in cls._decl_class_registry:
|
||||
return _determine_container(key, cls._decl_class_registry[key])
|
||||
elif key in cls.metadata.tables:
|
||||
return cls.metadata.tables[key]
|
||||
elif key in cls.metadata._schemas:
|
||||
return _GetTable(key, cls.metadata)
|
||||
elif '_sa_module_registry' in cls._decl_class_registry and \
|
||||
key in cls._decl_class_registry['_sa_module_registry']:
|
||||
registry = cls._decl_class_registry['_sa_module_registry']
|
||||
return registry.resolve_attr(key)
|
||||
else:
|
||||
return fallback[key]
|
||||
|
||||
d = util.PopulateDict(access_cls)
|
||||
|
||||
def return_cls():
|
||||
try:
|
||||
x = eval(arg, globals(), d)
|
||||
|
||||
if isinstance(x, _GetColumns):
|
||||
return x.cls
|
||||
else:
|
||||
return x
|
||||
except NameError, n:
|
||||
raise exc.InvalidRequestError(
|
||||
"When initializing mapper %s, expression %r failed to "
|
||||
"locate a name (%r). If this is a class name, consider "
|
||||
"adding this relationship() to the %r class after "
|
||||
"both dependent classes have been defined." %
|
||||
(prop.parent, arg, n.args[0], cls)
|
||||
)
|
||||
return return_cls
|
||||
return resolve_arg
|
||||
|
||||
|
||||
def _deferred_relationship(cls, prop):
|
||||
|
||||
if isinstance(prop, RelationshipProperty):
|
||||
resolve_arg = _resolver(cls, prop)
|
||||
|
||||
for attr in ('argument', 'order_by', 'primaryjoin', 'secondaryjoin',
|
||||
'secondary', '_user_defined_foreign_keys', 'remote_side'):
|
||||
v = getattr(prop, attr)
|
||||
if isinstance(v, basestring):
|
||||
setattr(prop, attr, resolve_arg(v))
|
||||
|
||||
if prop.backref and isinstance(prop.backref, tuple):
|
||||
key, kwargs = prop.backref
|
||||
for attr in ('primaryjoin', 'secondaryjoin', 'secondary',
|
||||
'foreign_keys', 'remote_side', 'order_by'):
|
||||
if attr in kwargs and isinstance(kwargs[attr], basestring):
|
||||
kwargs[attr] = resolve_arg(kwargs[attr])
|
||||
|
||||
return prop
|
||||
@@ -0,0 +1,128 @@
|
||||
# ext/horizontal_shard.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Horizontal sharding support.
|
||||
|
||||
Defines a rudimental 'horizontal sharding' system which allows a Session to
|
||||
distribute queries and persistence operations across multiple databases.
|
||||
|
||||
For a usage example, see the :ref:`examples_sharding` example included in
|
||||
the source distribution.
|
||||
|
||||
"""
|
||||
|
||||
from .. import util
|
||||
from ..orm.session import Session
|
||||
from ..orm.query import Query
|
||||
|
||||
__all__ = ['ShardedSession', 'ShardedQuery']
|
||||
|
||||
|
||||
class ShardedQuery(Query):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ShardedQuery, self).__init__(*args, **kwargs)
|
||||
self.id_chooser = self.session.id_chooser
|
||||
self.query_chooser = self.session.query_chooser
|
||||
self._shard_id = None
|
||||
|
||||
def set_shard(self, shard_id):
|
||||
"""return a new query, limited to a single shard ID.
|
||||
|
||||
all subsequent operations with the returned query will
|
||||
be against the single shard regardless of other state.
|
||||
"""
|
||||
|
||||
q = self._clone()
|
||||
q._shard_id = shard_id
|
||||
return q
|
||||
|
||||
def _execute_and_instances(self, context):
|
||||
def iter_for_shard(shard_id):
|
||||
context.attributes['shard_id'] = shard_id
|
||||
result = self._connection_from_session(
|
||||
mapper=self._mapper_zero(),
|
||||
shard_id=shard_id).execute(
|
||||
context.statement,
|
||||
self._params)
|
||||
return self.instances(result, context)
|
||||
|
||||
if self._shard_id is not None:
|
||||
return iter_for_shard(self._shard_id)
|
||||
else:
|
||||
partial = []
|
||||
for shard_id in self.query_chooser(self):
|
||||
partial.extend(iter_for_shard(shard_id))
|
||||
|
||||
# if some kind of in memory 'sorting'
|
||||
# were done, this is where it would happen
|
||||
return iter(partial)
|
||||
|
||||
def get(self, ident, **kwargs):
|
||||
if self._shard_id is not None:
|
||||
return super(ShardedQuery, self).get(ident)
|
||||
else:
|
||||
ident = util.to_list(ident)
|
||||
for shard_id in self.id_chooser(self, ident):
|
||||
o = self.set_shard(shard_id).get(ident, **kwargs)
|
||||
if o is not None:
|
||||
return o
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class ShardedSession(Session):
|
||||
def __init__(self, shard_chooser, id_chooser, query_chooser, shards=None,
|
||||
query_cls=ShardedQuery, **kwargs):
|
||||
"""Construct a ShardedSession.
|
||||
|
||||
:param shard_chooser: A callable which, passed a Mapper, a mapped
|
||||
instance, and possibly a SQL clause, returns a shard ID. This id
|
||||
may be based off of the attributes present within the object, or on
|
||||
some round-robin scheme. If the scheme is based on a selection, it
|
||||
should set whatever state on the instance to mark it in the future as
|
||||
participating in that shard.
|
||||
|
||||
:param id_chooser: A callable, passed a query and a tuple of identity
|
||||
values, which should return a list of shard ids where the ID might
|
||||
reside. The databases will be queried in the order of this listing.
|
||||
|
||||
:param query_chooser: For a given Query, returns the list of shard_ids
|
||||
where the query should be issued. Results from all shards returned
|
||||
will be combined together into a single listing.
|
||||
|
||||
:param shards: A dictionary of string shard names
|
||||
to :class:`~sqlalchemy.engine.Engine` objects.
|
||||
|
||||
"""
|
||||
super(ShardedSession, self).__init__(query_cls=query_cls, **kwargs)
|
||||
self.shard_chooser = shard_chooser
|
||||
self.id_chooser = id_chooser
|
||||
self.query_chooser = query_chooser
|
||||
self.__binds = {}
|
||||
self.connection_callable = self.connection
|
||||
if shards is not None:
|
||||
for k in shards:
|
||||
self.bind_shard(k, shards[k])
|
||||
|
||||
def connection(self, mapper=None, instance=None, shard_id=None, **kwargs):
|
||||
if shard_id is None:
|
||||
shard_id = self.shard_chooser(mapper, instance)
|
||||
|
||||
if self.transaction is not None:
|
||||
return self.transaction.connection(mapper, shard_id=shard_id)
|
||||
else:
|
||||
return self.get_bind(mapper,
|
||||
shard_id=shard_id,
|
||||
instance=instance).contextual_connect(**kwargs)
|
||||
|
||||
def get_bind(self, mapper, shard_id=None,
|
||||
instance=None, clause=None, **kw):
|
||||
if shard_id is None:
|
||||
shard_id = self.shard_chooser(mapper, instance, clause=clause)
|
||||
return self.__binds[shard_id]
|
||||
|
||||
def bind_shard(self, shard_id, bind):
|
||||
self.__binds[shard_id] = bind
|
||||
@@ -0,0 +1,808 @@
|
||||
# ext/hybrid.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Define attributes on ORM-mapped classes that have "hybrid" behavior.
|
||||
|
||||
"hybrid" means the attribute has distinct behaviors defined at the
|
||||
class level and at the instance level.
|
||||
|
||||
The :mod:`~sqlalchemy.ext.hybrid` extension provides a special form of
|
||||
method decorator, is around 50 lines of code and has almost no
|
||||
dependencies on the rest of SQLAlchemy. It can, in theory, work with
|
||||
any descriptor-based expression system.
|
||||
|
||||
Consider a mapping ``Interval``, representing integer ``start`` and ``end``
|
||||
values. We can define higher level functions on mapped classes that produce
|
||||
SQL expressions at the class level, and Python expression evaluation at the
|
||||
instance level. Below, each function decorated with :class:`.hybrid_method` or
|
||||
:class:`.hybrid_property` may receive ``self`` as an instance of the class, or
|
||||
as the class itself::
|
||||
|
||||
from sqlalchemy import Column, Integer
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import Session, aliased
|
||||
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class Interval(Base):
|
||||
__tablename__ = 'interval'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
start = Column(Integer, nullable=False)
|
||||
end = Column(Integer, nullable=False)
|
||||
|
||||
def __init__(self, start, end):
|
||||
self.start = start
|
||||
self.end = end
|
||||
|
||||
@hybrid_property
|
||||
def length(self):
|
||||
return self.end - self.start
|
||||
|
||||
@hybrid_method
|
||||
def contains(self,point):
|
||||
return (self.start <= point) & (point < self.end)
|
||||
|
||||
@hybrid_method
|
||||
def intersects(self, other):
|
||||
return self.contains(other.start) | self.contains(other.end)
|
||||
|
||||
Above, the ``length`` property returns the difference between the
|
||||
``end`` and ``start`` attributes. With an instance of ``Interval``,
|
||||
this subtraction occurs in Python, using normal Python descriptor
|
||||
mechanics::
|
||||
|
||||
>>> i1 = Interval(5, 10)
|
||||
>>> i1.length
|
||||
5
|
||||
|
||||
When dealing with the ``Interval`` class itself, the :class:`.hybrid_property`
|
||||
descriptor evaluates the function body given the ``Interval`` class as
|
||||
the argument, which when evaluated with SQLAlchemy expression mechanics
|
||||
returns a new SQL expression::
|
||||
|
||||
>>> print Interval.length
|
||||
interval."end" - interval.start
|
||||
|
||||
>>> print Session().query(Interval).filter(Interval.length > 10)
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end
|
||||
FROM interval
|
||||
WHERE interval."end" - interval.start > :param_1
|
||||
|
||||
ORM methods such as :meth:`~.Query.filter_by` generally use ``getattr()`` to
|
||||
locate attributes, so can also be used with hybrid attributes::
|
||||
|
||||
>>> print Session().query(Interval).filter_by(length=5)
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end
|
||||
FROM interval
|
||||
WHERE interval."end" - interval.start = :param_1
|
||||
|
||||
The ``Interval`` class example also illustrates two methods,
|
||||
``contains()`` and ``intersects()``, decorated with
|
||||
:class:`.hybrid_method`. This decorator applies the same idea to
|
||||
methods that :class:`.hybrid_property` applies to attributes. The
|
||||
methods return boolean values, and take advantage of the Python ``|``
|
||||
and ``&`` bitwise operators to produce equivalent instance-level and
|
||||
SQL expression-level boolean behavior::
|
||||
|
||||
>>> i1.contains(6)
|
||||
True
|
||||
>>> i1.contains(15)
|
||||
False
|
||||
>>> i1.intersects(Interval(7, 18))
|
||||
True
|
||||
>>> i1.intersects(Interval(25, 29))
|
||||
False
|
||||
|
||||
>>> print Session().query(Interval).filter(Interval.contains(15))
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end
|
||||
FROM interval
|
||||
WHERE interval.start <= :start_1 AND interval."end" > :end_1
|
||||
|
||||
>>> ia = aliased(Interval)
|
||||
>>> print Session().query(Interval, ia).filter(Interval.intersects(ia))
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end, interval_1.id AS interval_1_id,
|
||||
interval_1.start AS interval_1_start, interval_1."end" AS interval_1_end
|
||||
FROM interval, interval AS interval_1
|
||||
WHERE interval.start <= interval_1.start
|
||||
AND interval."end" > interval_1.start
|
||||
OR interval.start <= interval_1."end"
|
||||
AND interval."end" > interval_1."end"
|
||||
|
||||
Defining Expression Behavior Distinct from Attribute Behavior
|
||||
--------------------------------------------------------------
|
||||
|
||||
Our usage of the ``&`` and ``|`` bitwise operators above was
|
||||
fortunate, considering our functions operated on two boolean values to
|
||||
return a new one. In many cases, the construction of an in-Python
|
||||
function and a SQLAlchemy SQL expression have enough differences that
|
||||
two separate Python expressions should be defined. The
|
||||
:mod:`~sqlalchemy.ext.hybrid` decorators define the
|
||||
:meth:`.hybrid_property.expression` modifier for this purpose. As an
|
||||
example we'll define the radius of the interval, which requires the
|
||||
usage of the absolute value function::
|
||||
|
||||
from sqlalchemy import func
|
||||
|
||||
class Interval(object):
|
||||
# ...
|
||||
|
||||
@hybrid_property
|
||||
def radius(self):
|
||||
return abs(self.length) / 2
|
||||
|
||||
@radius.expression
|
||||
def radius(cls):
|
||||
return func.abs(cls.length) / 2
|
||||
|
||||
Above the Python function ``abs()`` is used for instance-level
|
||||
operations, the SQL function ``ABS()`` is used via the :attr:`.func`
|
||||
object for class-level expressions::
|
||||
|
||||
>>> i1.radius
|
||||
2
|
||||
|
||||
>>> print Session().query(Interval).filter(Interval.radius > 5)
|
||||
SELECT interval.id AS interval_id, interval.start AS interval_start,
|
||||
interval."end" AS interval_end
|
||||
FROM interval
|
||||
WHERE abs(interval."end" - interval.start) / :abs_1 > :param_1
|
||||
|
||||
Defining Setters
|
||||
----------------
|
||||
|
||||
Hybrid properties can also define setter methods. If we wanted
|
||||
``length`` above, when set, to modify the endpoint value::
|
||||
|
||||
class Interval(object):
|
||||
# ...
|
||||
|
||||
@hybrid_property
|
||||
def length(self):
|
||||
return self.end - self.start
|
||||
|
||||
@length.setter
|
||||
def length(self, value):
|
||||
self.end = self.start + value
|
||||
|
||||
The ``length(self, value)`` method is now called upon set::
|
||||
|
||||
>>> i1 = Interval(5, 10)
|
||||
>>> i1.length
|
||||
5
|
||||
>>> i1.length = 12
|
||||
>>> i1.end
|
||||
17
|
||||
|
||||
Working with Relationships
|
||||
--------------------------
|
||||
|
||||
There's no essential difference when creating hybrids that work with
|
||||
related objects as opposed to column-based data. The need for distinct
|
||||
expressions tends to be greater. Two variants of we'll illustrate
|
||||
are the "join-dependent" hybrid, and the "correlated subquery" hybrid.
|
||||
|
||||
Join-Dependent Relationship Hybrid
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Consider the following declarative
|
||||
mapping which relates a ``User`` to a ``SavingsAccount``::
|
||||
|
||||
from sqlalchemy import Column, Integer, ForeignKey, Numeric, String
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class SavingsAccount(Base):
|
||||
__tablename__ = 'account'
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
|
||||
balance = Column(Numeric(15, 5))
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'user'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
|
||||
accounts = relationship("SavingsAccount", backref="owner")
|
||||
|
||||
@hybrid_property
|
||||
def balance(self):
|
||||
if self.accounts:
|
||||
return self.accounts[0].balance
|
||||
else:
|
||||
return None
|
||||
|
||||
@balance.setter
|
||||
def balance(self, value):
|
||||
if not self.accounts:
|
||||
account = Account(owner=self)
|
||||
else:
|
||||
account = self.accounts[0]
|
||||
account.balance = value
|
||||
|
||||
@balance.expression
|
||||
def balance(cls):
|
||||
return SavingsAccount.balance
|
||||
|
||||
The above hybrid property ``balance`` works with the first
|
||||
``SavingsAccount`` entry in the list of accounts for this user. The
|
||||
in-Python getter/setter methods can treat ``accounts`` as a Python
|
||||
list available on ``self``.
|
||||
|
||||
However, at the expression level, it's expected that the ``User`` class will
|
||||
be used in an appropriate context such that an appropriate join to
|
||||
``SavingsAccount`` will be present::
|
||||
|
||||
>>> print Session().query(User, User.balance).\\
|
||||
... join(User.accounts).filter(User.balance > 5000)
|
||||
SELECT "user".id AS user_id, "user".name AS user_name,
|
||||
account.balance AS account_balance
|
||||
FROM "user" JOIN account ON "user".id = account.user_id
|
||||
WHERE account.balance > :balance_1
|
||||
|
||||
Note however, that while the instance level accessors need to worry
|
||||
about whether ``self.accounts`` is even present, this issue expresses
|
||||
itself differently at the SQL expression level, where we basically
|
||||
would use an outer join::
|
||||
|
||||
>>> from sqlalchemy import or_
|
||||
>>> print (Session().query(User, User.balance).outerjoin(User.accounts).
|
||||
... filter(or_(User.balance < 5000, User.balance == None)))
|
||||
SELECT "user".id AS user_id, "user".name AS user_name,
|
||||
account.balance AS account_balance
|
||||
FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
|
||||
WHERE account.balance < :balance_1 OR account.balance IS NULL
|
||||
|
||||
Correlated Subquery Relationship Hybrid
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
We can, of course, forego being dependent on the enclosing query's usage
|
||||
of joins in favor of the correlated subquery, which can portably be packed
|
||||
into a single column expression. A correlated subquery is more portable, but
|
||||
often performs more poorly at the SQL level. Using the same technique
|
||||
illustrated at :ref:`mapper_column_property_sql_expressions`,
|
||||
we can adjust our ``SavingsAccount`` example to aggregate the balances for
|
||||
*all* accounts, and use a correlated subquery for the column expression::
|
||||
|
||||
from sqlalchemy import Column, Integer, ForeignKey, Numeric, String
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy import select, func
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class SavingsAccount(Base):
|
||||
__tablename__ = 'account'
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
|
||||
balance = Column(Numeric(15, 5))
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'user'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
|
||||
accounts = relationship("SavingsAccount", backref="owner")
|
||||
|
||||
@hybrid_property
|
||||
def balance(self):
|
||||
return sum(acc.balance for acc in self.accounts)
|
||||
|
||||
@balance.expression
|
||||
def balance(cls):
|
||||
return select([func.sum(SavingsAccount.balance)]).\\
|
||||
where(SavingsAccount.user_id==cls.id).\\
|
||||
label('total_balance')
|
||||
|
||||
The above recipe will give us the ``balance`` column which renders
|
||||
a correlated SELECT::
|
||||
|
||||
>>> print s.query(User).filter(User.balance > 400)
|
||||
SELECT "user".id AS user_id, "user".name AS user_name
|
||||
FROM "user"
|
||||
WHERE (SELECT sum(account.balance) AS sum_1
|
||||
FROM account
|
||||
WHERE account.user_id = "user".id) > :param_1
|
||||
|
||||
.. _hybrid_custom_comparators:
|
||||
|
||||
Building Custom Comparators
|
||||
---------------------------
|
||||
|
||||
The hybrid property also includes a helper that allows construction of
|
||||
custom comparators. A comparator object allows one to customize the
|
||||
behavior of each SQLAlchemy expression operator individually. They
|
||||
are useful when creating custom types that have some highly
|
||||
idiosyncratic behavior on the SQL side.
|
||||
|
||||
The example class below allows case-insensitive comparisons on the attribute
|
||||
named ``word_insensitive``::
|
||||
|
||||
from sqlalchemy.ext.hybrid import Comparator, hybrid_property
|
||||
from sqlalchemy import func, Column, Integer, String
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class CaseInsensitiveComparator(Comparator):
|
||||
def __eq__(self, other):
|
||||
return func.lower(self.__clause_element__()) == func.lower(other)
|
||||
|
||||
class SearchWord(Base):
|
||||
__tablename__ = 'searchword'
|
||||
id = Column(Integer, primary_key=True)
|
||||
word = Column(String(255), nullable=False)
|
||||
|
||||
@hybrid_property
|
||||
def word_insensitive(self):
|
||||
return self.word.lower()
|
||||
|
||||
@word_insensitive.comparator
|
||||
def word_insensitive(cls):
|
||||
return CaseInsensitiveComparator(cls.word)
|
||||
|
||||
Above, SQL expressions against ``word_insensitive`` will apply the ``LOWER()``
|
||||
SQL function to both sides::
|
||||
|
||||
>>> print Session().query(SearchWord).filter_by(word_insensitive="Trucks")
|
||||
SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
|
||||
FROM searchword
|
||||
WHERE lower(searchword.word) = lower(:lower_1)
|
||||
|
||||
The ``CaseInsensitiveComparator`` above implements part of the
|
||||
:class:`.ColumnOperators` interface. A "coercion" operation like
|
||||
lowercasing can be applied to all comparison operations (i.e. ``eq``,
|
||||
``lt``, ``gt``, etc.) using :meth:`.Operators.operate`::
|
||||
|
||||
class CaseInsensitiveComparator(Comparator):
|
||||
def operate(self, op, other):
|
||||
return op(func.lower(self.__clause_element__()), func.lower(other))
|
||||
|
||||
Hybrid Value Objects
|
||||
--------------------
|
||||
|
||||
Note in our previous example, if we were to compare the
|
||||
``word_insensitive`` attribute of a ``SearchWord`` instance to a plain
|
||||
Python string, the plain Python string would not be coerced to lower
|
||||
case - the ``CaseInsensitiveComparator`` we built, being returned by
|
||||
``@word_insensitive.comparator``, only applies to the SQL side.
|
||||
|
||||
A more comprehensive form of the custom comparator is to construct a
|
||||
*Hybrid Value Object*. This technique applies the target value or
|
||||
expression to a value object which is then returned by the accessor in
|
||||
all cases. The value object allows control of all operations upon
|
||||
the value as well as how compared values are treated, both on the SQL
|
||||
expression side as well as the Python value side. Replacing the
|
||||
previous ``CaseInsensitiveComparator`` class with a new
|
||||
``CaseInsensitiveWord`` class::
|
||||
|
||||
class CaseInsensitiveWord(Comparator):
|
||||
"Hybrid value representing a lower case representation of a word."
|
||||
|
||||
def __init__(self, word):
|
||||
if isinstance(word, basestring):
|
||||
self.word = word.lower()
|
||||
elif isinstance(word, CaseInsensitiveWord):
|
||||
self.word = word.word
|
||||
else:
|
||||
self.word = func.lower(word)
|
||||
|
||||
def operate(self, op, other):
|
||||
if not isinstance(other, CaseInsensitiveWord):
|
||||
other = CaseInsensitiveWord(other)
|
||||
return op(self.word, other.word)
|
||||
|
||||
def __clause_element__(self):
|
||||
return self.word
|
||||
|
||||
def __str__(self):
|
||||
return self.word
|
||||
|
||||
key = 'word'
|
||||
"Label to apply to Query tuple results"
|
||||
|
||||
Above, the ``CaseInsensitiveWord`` object represents ``self.word``,
|
||||
which may be a SQL function, or may be a Python native. By
|
||||
overriding ``operate()`` and ``__clause_element__()`` to work in terms
|
||||
of ``self.word``, all comparison operations will work against the
|
||||
"converted" form of ``word``, whether it be SQL side or Python side.
|
||||
Our ``SearchWord`` class can now deliver the ``CaseInsensitiveWord``
|
||||
object unconditionally from a single hybrid call::
|
||||
|
||||
class SearchWord(Base):
|
||||
__tablename__ = 'searchword'
|
||||
id = Column(Integer, primary_key=True)
|
||||
word = Column(String(255), nullable=False)
|
||||
|
||||
@hybrid_property
|
||||
def word_insensitive(self):
|
||||
return CaseInsensitiveWord(self.word)
|
||||
|
||||
The ``word_insensitive`` attribute now has case-insensitive comparison
|
||||
behavior universally, including SQL expression vs. Python expression
|
||||
(note the Python value is converted to lower case on the Python side
|
||||
here)::
|
||||
|
||||
>>> print Session().query(SearchWord).filter_by(word_insensitive="Trucks")
|
||||
SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
|
||||
FROM searchword
|
||||
WHERE lower(searchword.word) = :lower_1
|
||||
|
||||
SQL expression versus SQL expression::
|
||||
|
||||
>>> sw1 = aliased(SearchWord)
|
||||
>>> sw2 = aliased(SearchWord)
|
||||
>>> print Session().query(
|
||||
... sw1.word_insensitive,
|
||||
... sw2.word_insensitive).\\
|
||||
... filter(
|
||||
... sw1.word_insensitive > sw2.word_insensitive
|
||||
... )
|
||||
SELECT lower(searchword_1.word) AS lower_1,
|
||||
lower(searchword_2.word) AS lower_2
|
||||
FROM searchword AS searchword_1, searchword AS searchword_2
|
||||
WHERE lower(searchword_1.word) > lower(searchword_2.word)
|
||||
|
||||
Python only expression::
|
||||
|
||||
>>> ws1 = SearchWord(word="SomeWord")
|
||||
>>> ws1.word_insensitive == "sOmEwOrD"
|
||||
True
|
||||
>>> ws1.word_insensitive == "XOmEwOrX"
|
||||
False
|
||||
>>> print ws1.word_insensitive
|
||||
someword
|
||||
|
||||
The Hybrid Value pattern is very useful for any kind of value that may
|
||||
have multiple representations, such as timestamps, time deltas, units
|
||||
of measurement, currencies and encrypted passwords.
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Hybrids and Value Agnostic Types
|
||||
<http://techspot.zzzeek.org/2011/10/21/hybrids-and-value-agnostic-types/>`_ -
|
||||
on the techspot.zzzeek.org blog
|
||||
|
||||
`Value Agnostic Types, Part II
|
||||
<http://techspot.zzzeek.org/2011/10/29/value-agnostic-types-part-ii/>`_ -
|
||||
on the techspot.zzzeek.org blog
|
||||
|
||||
.. _hybrid_transformers:
|
||||
|
||||
Building Transformers
|
||||
----------------------
|
||||
|
||||
A *transformer* is an object which can receive a :class:`.Query`
|
||||
object and return a new one. The :class:`.Query` object includes a
|
||||
method :meth:`.with_transformation` that returns a new :class:`.Query`
|
||||
transformed by the given function.
|
||||
|
||||
We can combine this with the :class:`.Comparator` class to produce one type
|
||||
of recipe which can both set up the FROM clause of a query as well as assign
|
||||
filtering criterion.
|
||||
|
||||
Consider a mapped class ``Node``, which assembles using adjacency list
|
||||
into a hierarchical tree pattern::
|
||||
|
||||
from sqlalchemy import Column, Integer, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
Base = declarative_base()
|
||||
|
||||
class Node(Base):
|
||||
__tablename__ = 'node'
|
||||
id =Column(Integer, primary_key=True)
|
||||
parent_id = Column(Integer, ForeignKey('node.id'))
|
||||
parent = relationship("Node", remote_side=id)
|
||||
|
||||
Suppose we wanted to add an accessor ``grandparent``. This would
|
||||
return the ``parent`` of ``Node.parent``. When we have an instance of
|
||||
``Node``, this is simple::
|
||||
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
|
||||
class Node(Base):
|
||||
# ...
|
||||
|
||||
@hybrid_property
|
||||
def grandparent(self):
|
||||
return self.parent.parent
|
||||
|
||||
For the expression, things are not so clear. We'd need to construct
|
||||
a :class:`.Query` where we :meth:`~.Query.join` twice along
|
||||
``Node.parent`` to get to the ``grandparent``. We can instead return
|
||||
a transforming callable that we'll combine with the
|
||||
:class:`.Comparator` class to receive any :class:`.Query` object, and
|
||||
return a new one that's joined to the ``Node.parent`` attribute and
|
||||
filtered based on the given criterion::
|
||||
|
||||
from sqlalchemy.ext.hybrid import Comparator
|
||||
|
||||
class GrandparentTransformer(Comparator):
|
||||
def operate(self, op, other):
|
||||
def transform(q):
|
||||
cls = self.__clause_element__()
|
||||
parent_alias = aliased(cls)
|
||||
return q.join(parent_alias, cls.parent).\\
|
||||
filter(op(parent_alias.parent, other))
|
||||
return transform
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class Node(Base):
|
||||
__tablename__ = 'node'
|
||||
id =Column(Integer, primary_key=True)
|
||||
parent_id = Column(Integer, ForeignKey('node.id'))
|
||||
parent = relationship("Node", remote_side=id)
|
||||
|
||||
@hybrid_property
|
||||
def grandparent(self):
|
||||
return self.parent.parent
|
||||
|
||||
@grandparent.comparator
|
||||
def grandparent(cls):
|
||||
return GrandparentTransformer(cls)
|
||||
|
||||
The ``GrandparentTransformer`` overrides the core
|
||||
:meth:`.Operators.operate` method at the base of the
|
||||
:class:`.Comparator` hierarchy to return a query-transforming
|
||||
callable, which then runs the given comparison operation in a
|
||||
particular context. Such as, in the example above, the ``operate``
|
||||
method is called, given the :attr:`.Operators.eq` callable as well as
|
||||
the right side of the comparison ``Node(id=5)``. A function
|
||||
``transform`` is then returned which will transform a :class:`.Query`
|
||||
first to join to ``Node.parent``, then to compare ``parent_alias``
|
||||
using :attr:`.Operators.eq` against the left and right sides, passing
|
||||
into :class:`.Query.filter`:
|
||||
|
||||
.. sourcecode:: pycon+sql
|
||||
|
||||
>>> from sqlalchemy.orm import Session
|
||||
>>> session = Session()
|
||||
{sql}>>> session.query(Node).\\
|
||||
... with_transformation(Node.grandparent==Node(id=5)).\\
|
||||
... all()
|
||||
SELECT node.id AS node_id, node.parent_id AS node_parent_id
|
||||
FROM node JOIN node AS node_1 ON node_1.id = node.parent_id
|
||||
WHERE :param_1 = node_1.parent_id
|
||||
{stop}
|
||||
|
||||
We can modify the pattern to be more verbose but flexible by separating
|
||||
the "join" step from the "filter" step. The tricky part here is ensuring
|
||||
that successive instances of ``GrandparentTransformer`` use the same
|
||||
:class:`.AliasedClass` object against ``Node``. Below we use a simple
|
||||
memoizing approach that associates a ``GrandparentTransformer``
|
||||
with each class::
|
||||
|
||||
class Node(Base):
|
||||
|
||||
# ...
|
||||
|
||||
@grandparent.comparator
|
||||
def grandparent(cls):
|
||||
# memoize a GrandparentTransformer
|
||||
# per class
|
||||
if '_gp' not in cls.__dict__:
|
||||
cls._gp = GrandparentTransformer(cls)
|
||||
return cls._gp
|
||||
|
||||
class GrandparentTransformer(Comparator):
|
||||
|
||||
def __init__(self, cls):
|
||||
self.parent_alias = aliased(cls)
|
||||
|
||||
@property
|
||||
def join(self):
|
||||
def go(q):
|
||||
return q.join(self.parent_alias, Node.parent)
|
||||
return go
|
||||
|
||||
def operate(self, op, other):
|
||||
return op(self.parent_alias.parent, other)
|
||||
|
||||
.. sourcecode:: pycon+sql
|
||||
|
||||
{sql}>>> session.query(Node).\\
|
||||
... with_transformation(Node.grandparent.join).\\
|
||||
... filter(Node.grandparent==Node(id=5))
|
||||
SELECT node.id AS node_id, node.parent_id AS node_parent_id
|
||||
FROM node JOIN node AS node_1 ON node_1.id = node.parent_id
|
||||
WHERE :param_1 = node_1.parent_id
|
||||
{stop}
|
||||
|
||||
The "transformer" pattern is an experimental pattern that starts
|
||||
to make usage of some functional programming paradigms.
|
||||
While it's only recommended for advanced and/or patient developers,
|
||||
there's probably a whole lot of amazing things it can be used for.
|
||||
|
||||
"""
|
||||
from .. import util
|
||||
from ..orm import attributes, interfaces
|
||||
|
||||
HYBRID_METHOD = util.symbol('HYBRID_METHOD')
|
||||
"""Symbol indicating an :class:`_InspectionAttr` that's
|
||||
of type :class:`.hybrid_method`.
|
||||
|
||||
Is assigned to the :attr:`._InspectionAttr.extension_type`
|
||||
attibute.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:attr:`.Mapper.all_orm_attributes`
|
||||
|
||||
"""
|
||||
|
||||
HYBRID_PROPERTY = util.symbol('HYBRID_PROPERTY')
|
||||
"""Symbol indicating an :class:`_InspectionAttr` that's
|
||||
of type :class:`.hybrid_method`.
|
||||
|
||||
Is assigned to the :attr:`._InspectionAttr.extension_type`
|
||||
attibute.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:attr:`.Mapper.all_orm_attributes`
|
||||
|
||||
"""
|
||||
|
||||
class hybrid_method(interfaces._InspectionAttr):
|
||||
"""A decorator which allows definition of a Python object method with both
|
||||
instance-level and class-level behavior.
|
||||
|
||||
"""
|
||||
|
||||
is_attribute = True
|
||||
extension_type = HYBRID_METHOD
|
||||
|
||||
def __init__(self, func, expr=None):
|
||||
"""Create a new :class:`.hybrid_method`.
|
||||
|
||||
Usage is typically via decorator::
|
||||
|
||||
from sqlalchemy.ext.hybrid import hybrid_method
|
||||
|
||||
class SomeClass(object):
|
||||
@hybrid_method
|
||||
def value(self, x, y):
|
||||
return self._value + x + y
|
||||
|
||||
@value.expression
|
||||
def value(self, x, y):
|
||||
return func.some_function(self._value, x, y)
|
||||
|
||||
"""
|
||||
self.func = func
|
||||
self.expr = expr or func
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self.expr.__get__(owner, owner.__class__)
|
||||
else:
|
||||
return self.func.__get__(instance, owner)
|
||||
|
||||
def expression(self, expr):
|
||||
"""Provide a modifying decorator that defines a
|
||||
SQL-expression producing method."""
|
||||
|
||||
self.expr = expr
|
||||
return self
|
||||
|
||||
|
||||
class hybrid_property(interfaces._InspectionAttr):
|
||||
"""A decorator which allows definition of a Python descriptor with both
|
||||
instance-level and class-level behavior.
|
||||
|
||||
"""
|
||||
|
||||
is_attribute = True
|
||||
extension_type = HYBRID_PROPERTY
|
||||
|
||||
def __init__(self, fget, fset=None, fdel=None, expr=None):
|
||||
"""Create a new :class:`.hybrid_property`.
|
||||
|
||||
Usage is typically via decorator::
|
||||
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
|
||||
class SomeClass(object):
|
||||
@hybrid_property
|
||||
def value(self):
|
||||
return self._value
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
self._value = value
|
||||
|
||||
"""
|
||||
self.fget = fget
|
||||
self.fset = fset
|
||||
self.fdel = fdel
|
||||
self.expr = expr or fget
|
||||
util.update_wrapper(self, fget)
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self.expr(owner)
|
||||
else:
|
||||
return self.fget(instance)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if self.fset is None:
|
||||
raise AttributeError("can't set attribute")
|
||||
self.fset(instance, value)
|
||||
|
||||
def __delete__(self, instance):
|
||||
if self.fdel is None:
|
||||
raise AttributeError("can't delete attribute")
|
||||
self.fdel(instance)
|
||||
|
||||
def setter(self, fset):
|
||||
"""Provide a modifying decorator that defines a value-setter method."""
|
||||
|
||||
self.fset = fset
|
||||
return self
|
||||
|
||||
def deleter(self, fdel):
|
||||
"""Provide a modifying decorator that defines a
|
||||
value-deletion method."""
|
||||
|
||||
self.fdel = fdel
|
||||
return self
|
||||
|
||||
def expression(self, expr):
|
||||
"""Provide a modifying decorator that defines a SQL-expression
|
||||
producing method."""
|
||||
|
||||
self.expr = expr
|
||||
return self
|
||||
|
||||
def comparator(self, comparator):
|
||||
"""Provide a modifying decorator that defines a custom
|
||||
comparator producing method.
|
||||
|
||||
The return value of the decorated method should be an instance of
|
||||
:class:`~.hybrid.Comparator`.
|
||||
|
||||
"""
|
||||
|
||||
proxy_attr = attributes.\
|
||||
create_proxied_attribute(self)
|
||||
|
||||
def expr(owner):
|
||||
return proxy_attr(owner, self.__name__, self, comparator(owner))
|
||||
self.expr = expr
|
||||
return self
|
||||
|
||||
|
||||
class Comparator(interfaces.PropComparator):
|
||||
"""A helper class that allows easy construction of custom
|
||||
:class:`~.orm.interfaces.PropComparator`
|
||||
classes for usage with hybrids."""
|
||||
|
||||
property = None
|
||||
|
||||
def __init__(self, expression):
|
||||
self.expression = expression
|
||||
|
||||
def __clause_element__(self):
|
||||
expr = self.expression
|
||||
while hasattr(expr, '__clause_element__'):
|
||||
expr = expr.__clause_element__()
|
||||
return expr
|
||||
|
||||
def adapted(self, adapter):
|
||||
# interesting....
|
||||
return self
|
||||
@@ -0,0 +1,407 @@
|
||||
"""Extensible class instrumentation.
|
||||
|
||||
The :mod:`sqlalchemy.ext.instrumentation` package provides for alternate
|
||||
systems of class instrumentation within the ORM. Class instrumentation
|
||||
refers to how the ORM places attributes on the class which maintain
|
||||
data and track changes to that data, as well as event hooks installed
|
||||
on the class.
|
||||
|
||||
.. note::
|
||||
The extension package is provided for the benefit of integration
|
||||
with other object management packages, which already perform
|
||||
their own instrumentation. It is not intended for general use.
|
||||
|
||||
For examples of how the instrumentation extension is used,
|
||||
see the example :ref:`examples_instrumentation`.
|
||||
|
||||
.. versionchanged:: 0.8
|
||||
The :mod:`sqlalchemy.orm.instrumentation` was split out so
|
||||
that all functionality having to do with non-standard
|
||||
instrumentation was moved out to :mod:`sqlalchemy.ext.instrumentation`.
|
||||
When imported, the module installs itself within
|
||||
:mod:`sqlalchemy.orm.instrumentation` so that it
|
||||
takes effect, including recognition of
|
||||
``__sa_instrumentation_manager__`` on mapped classes, as
|
||||
well :attr:`.instrumentation_finders`
|
||||
being used to determine class instrumentation resolution.
|
||||
|
||||
"""
|
||||
from ..orm import instrumentation as orm_instrumentation
|
||||
from ..orm.instrumentation import (
|
||||
ClassManager, InstrumentationFactory, _default_state_getter,
|
||||
_default_dict_getter, _default_manager_getter
|
||||
)
|
||||
from ..orm import attributes, collections
|
||||
from .. import util
|
||||
from ..orm import exc as orm_exc
|
||||
import weakref
|
||||
|
||||
INSTRUMENTATION_MANAGER = '__sa_instrumentation_manager__'
|
||||
"""Attribute, elects custom instrumentation when present on a mapped class.
|
||||
|
||||
Allows a class to specify a slightly or wildly different technique for
|
||||
tracking changes made to mapped attributes and collections.
|
||||
|
||||
Only one instrumentation implementation is allowed in a given object
|
||||
inheritance hierarchy.
|
||||
|
||||
The value of this attribute must be a callable and will be passed a class
|
||||
object. The callable must return one of:
|
||||
|
||||
- An instance of an InstrumentationManager or subclass
|
||||
- An object implementing all or some of InstrumentationManager (TODO)
|
||||
- A dictionary of callables, implementing all or some of the above (TODO)
|
||||
- An instance of a ClassManager or subclass
|
||||
|
||||
This attribute is consulted by SQLAlchemy instrumentation
|
||||
resolution, once the :mod:`sqlalchemy.ext.instrumentation` module
|
||||
has been imported. If custom finders are installed in the global
|
||||
instrumentation_finders list, they may or may not choose to honor this
|
||||
attribute.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def find_native_user_instrumentation_hook(cls):
|
||||
"""Find user-specified instrumentation management for a class."""
|
||||
return getattr(cls, INSTRUMENTATION_MANAGER, None)
|
||||
|
||||
instrumentation_finders = [find_native_user_instrumentation_hook]
|
||||
"""An extensible sequence of callables which return instrumentation
|
||||
implementations
|
||||
|
||||
When a class is registered, each callable will be passed a class object.
|
||||
If None is returned, the
|
||||
next finder in the sequence is consulted. Otherwise the return must be an
|
||||
instrumentation factory that follows the same guidelines as
|
||||
sqlalchemy.ext.instrumentation.INSTRUMENTATION_MANAGER.
|
||||
|
||||
By default, the only finder is find_native_user_instrumentation_hook, which
|
||||
searches for INSTRUMENTATION_MANAGER. If all finders return None, standard
|
||||
ClassManager instrumentation is used.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class ExtendedInstrumentationRegistry(InstrumentationFactory):
|
||||
"""Extends :class:`.InstrumentationFactory` with additional
|
||||
bookkeeping, to accommodate multiple types of
|
||||
class managers.
|
||||
|
||||
"""
|
||||
_manager_finders = weakref.WeakKeyDictionary()
|
||||
_state_finders = weakref.WeakKeyDictionary()
|
||||
_dict_finders = weakref.WeakKeyDictionary()
|
||||
_extended = False
|
||||
|
||||
def _locate_extended_factory(self, class_):
|
||||
for finder in instrumentation_finders:
|
||||
factory = finder(class_)
|
||||
if factory is not None:
|
||||
manager = self._extended_class_manager(class_, factory)
|
||||
return manager, factory
|
||||
else:
|
||||
return None, None
|
||||
|
||||
def _check_conflicts(self, class_, factory):
|
||||
existing_factories = self._collect_management_factories_for(class_).\
|
||||
difference([factory])
|
||||
if existing_factories:
|
||||
raise TypeError(
|
||||
"multiple instrumentation implementations specified "
|
||||
"in %s inheritance hierarchy: %r" % (
|
||||
class_.__name__, list(existing_factories)))
|
||||
|
||||
def _extended_class_manager(self, class_, factory):
|
||||
manager = factory(class_)
|
||||
if not isinstance(manager, ClassManager):
|
||||
manager = _ClassInstrumentationAdapter(class_, manager)
|
||||
|
||||
if factory != ClassManager and not self._extended:
|
||||
# somebody invoked a custom ClassManager.
|
||||
# reinstall global "getter" functions with the more
|
||||
# expensive ones.
|
||||
self._extended = True
|
||||
_install_instrumented_lookups()
|
||||
|
||||
self._manager_finders[class_] = manager.manager_getter()
|
||||
self._state_finders[class_] = manager.state_getter()
|
||||
self._dict_finders[class_] = manager.dict_getter()
|
||||
return manager
|
||||
|
||||
def _collect_management_factories_for(self, cls):
|
||||
"""Return a collection of factories in play or specified for a
|
||||
hierarchy.
|
||||
|
||||
Traverses the entire inheritance graph of a cls and returns a
|
||||
collection of instrumentation factories for those classes. Factories
|
||||
are extracted from active ClassManagers, if available, otherwise
|
||||
instrumentation_finders is consulted.
|
||||
|
||||
"""
|
||||
hierarchy = util.class_hierarchy(cls)
|
||||
factories = set()
|
||||
for member in hierarchy:
|
||||
manager = self.manager_of_class(member)
|
||||
if manager is not None:
|
||||
factories.add(manager.factory)
|
||||
else:
|
||||
for finder in instrumentation_finders:
|
||||
factory = finder(member)
|
||||
if factory is not None:
|
||||
break
|
||||
else:
|
||||
factory = None
|
||||
factories.add(factory)
|
||||
factories.discard(None)
|
||||
return factories
|
||||
|
||||
def unregister(self, class_):
|
||||
if class_ in self._manager_finders:
|
||||
del self._manager_finders[class_]
|
||||
del self._state_finders[class_]
|
||||
del self._dict_finders[class_]
|
||||
super(ExtendedInstrumentationRegistry, self).unregister(class_)
|
||||
|
||||
def manager_of_class(self, cls):
|
||||
if cls is None:
|
||||
return None
|
||||
return self._manager_finders.get(cls, _default_manager_getter)(cls)
|
||||
|
||||
def state_of(self, instance):
|
||||
if instance is None:
|
||||
raise AttributeError("None has no persistent state.")
|
||||
return self._state_finders.get(
|
||||
instance.__class__, _default_state_getter)(instance)
|
||||
|
||||
def dict_of(self, instance):
|
||||
if instance is None:
|
||||
raise AttributeError("None has no persistent state.")
|
||||
return self._dict_finders.get(
|
||||
instance.__class__, _default_dict_getter)(instance)
|
||||
|
||||
|
||||
orm_instrumentation._instrumentation_factory = \
|
||||
_instrumentation_factory = ExtendedInstrumentationRegistry()
|
||||
orm_instrumentation.instrumentation_finders = instrumentation_finders
|
||||
|
||||
|
||||
class InstrumentationManager(object):
|
||||
"""User-defined class instrumentation extension.
|
||||
|
||||
:class:`.InstrumentationManager` can be subclassed in order
|
||||
to change
|
||||
how class instrumentation proceeds. This class exists for
|
||||
the purposes of integration with other object management
|
||||
frameworks which would like to entirely modify the
|
||||
instrumentation methodology of the ORM, and is not intended
|
||||
for regular usage. For interception of class instrumentation
|
||||
events, see :class:`.InstrumentationEvents`.
|
||||
|
||||
The API for this class should be considered as semi-stable,
|
||||
and may change slightly with new releases.
|
||||
|
||||
.. versionchanged:: 0.8
|
||||
:class:`.InstrumentationManager` was moved from
|
||||
:mod:`sqlalchemy.orm.instrumentation` to
|
||||
:mod:`sqlalchemy.ext.instrumentation`.
|
||||
|
||||
"""
|
||||
|
||||
# r4361 added a mandatory (cls) constructor to this interface.
|
||||
# given that, perhaps class_ should be dropped from all of these
|
||||
# signatures.
|
||||
|
||||
def __init__(self, class_):
|
||||
pass
|
||||
|
||||
def manage(self, class_, manager):
|
||||
setattr(class_, '_default_class_manager', manager)
|
||||
|
||||
def dispose(self, class_, manager):
|
||||
delattr(class_, '_default_class_manager')
|
||||
|
||||
def manager_getter(self, class_):
|
||||
def get(cls):
|
||||
return cls._default_class_manager
|
||||
return get
|
||||
|
||||
def instrument_attribute(self, class_, key, inst):
|
||||
pass
|
||||
|
||||
def post_configure_attribute(self, class_, key, inst):
|
||||
pass
|
||||
|
||||
def install_descriptor(self, class_, key, inst):
|
||||
setattr(class_, key, inst)
|
||||
|
||||
def uninstall_descriptor(self, class_, key):
|
||||
delattr(class_, key)
|
||||
|
||||
def install_member(self, class_, key, implementation):
|
||||
setattr(class_, key, implementation)
|
||||
|
||||
def uninstall_member(self, class_, key):
|
||||
delattr(class_, key)
|
||||
|
||||
def instrument_collection_class(self, class_, key, collection_class):
|
||||
return collections.prepare_instrumentation(collection_class)
|
||||
|
||||
def get_instance_dict(self, class_, instance):
|
||||
return instance.__dict__
|
||||
|
||||
def initialize_instance_dict(self, class_, instance):
|
||||
pass
|
||||
|
||||
def install_state(self, class_, instance, state):
|
||||
setattr(instance, '_default_state', state)
|
||||
|
||||
def remove_state(self, class_, instance):
|
||||
delattr(instance, '_default_state')
|
||||
|
||||
def state_getter(self, class_):
|
||||
return lambda instance: getattr(instance, '_default_state')
|
||||
|
||||
def dict_getter(self, class_):
|
||||
return lambda inst: self.get_instance_dict(class_, inst)
|
||||
|
||||
|
||||
class _ClassInstrumentationAdapter(ClassManager):
|
||||
"""Adapts a user-defined InstrumentationManager to a ClassManager."""
|
||||
|
||||
def __init__(self, class_, override):
|
||||
self._adapted = override
|
||||
self._get_state = self._adapted.state_getter(class_)
|
||||
self._get_dict = self._adapted.dict_getter(class_)
|
||||
|
||||
ClassManager.__init__(self, class_)
|
||||
|
||||
def manage(self):
|
||||
self._adapted.manage(self.class_, self)
|
||||
|
||||
def dispose(self):
|
||||
self._adapted.dispose(self.class_)
|
||||
|
||||
def manager_getter(self):
|
||||
return self._adapted.manager_getter(self.class_)
|
||||
|
||||
def instrument_attribute(self, key, inst, propagated=False):
|
||||
ClassManager.instrument_attribute(self, key, inst, propagated)
|
||||
if not propagated:
|
||||
self._adapted.instrument_attribute(self.class_, key, inst)
|
||||
|
||||
def post_configure_attribute(self, key):
|
||||
super(_ClassInstrumentationAdapter, self).post_configure_attribute(key)
|
||||
self._adapted.post_configure_attribute(self.class_, key, self[key])
|
||||
|
||||
def install_descriptor(self, key, inst):
|
||||
self._adapted.install_descriptor(self.class_, key, inst)
|
||||
|
||||
def uninstall_descriptor(self, key):
|
||||
self._adapted.uninstall_descriptor(self.class_, key)
|
||||
|
||||
def install_member(self, key, implementation):
|
||||
self._adapted.install_member(self.class_, key, implementation)
|
||||
|
||||
def uninstall_member(self, key):
|
||||
self._adapted.uninstall_member(self.class_, key)
|
||||
|
||||
def instrument_collection_class(self, key, collection_class):
|
||||
return self._adapted.instrument_collection_class(
|
||||
self.class_, key, collection_class)
|
||||
|
||||
def initialize_collection(self, key, state, factory):
|
||||
delegate = getattr(self._adapted, 'initialize_collection', None)
|
||||
if delegate:
|
||||
return delegate(key, state, factory)
|
||||
else:
|
||||
return ClassManager.initialize_collection(self, key,
|
||||
state, factory)
|
||||
|
||||
def new_instance(self, state=None):
|
||||
instance = self.class_.__new__(self.class_)
|
||||
self.setup_instance(instance, state)
|
||||
return instance
|
||||
|
||||
def _new_state_if_none(self, instance):
|
||||
"""Install a default InstanceState if none is present.
|
||||
|
||||
A private convenience method used by the __init__ decorator.
|
||||
"""
|
||||
if self.has_state(instance):
|
||||
return False
|
||||
else:
|
||||
return self.setup_instance(instance)
|
||||
|
||||
def setup_instance(self, instance, state=None):
|
||||
self._adapted.initialize_instance_dict(self.class_, instance)
|
||||
|
||||
if state is None:
|
||||
state = self._state_constructor(instance, self)
|
||||
|
||||
# the given instance is assumed to have no state
|
||||
self._adapted.install_state(self.class_, instance, state)
|
||||
return state
|
||||
|
||||
def teardown_instance(self, instance):
|
||||
self._adapted.remove_state(self.class_, instance)
|
||||
|
||||
def has_state(self, instance):
|
||||
try:
|
||||
self._get_state(instance)
|
||||
except orm_exc.NO_STATE:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def state_getter(self):
|
||||
return self._get_state
|
||||
|
||||
def dict_getter(self):
|
||||
return self._get_dict
|
||||
|
||||
|
||||
def _install_instrumented_lookups():
|
||||
"""Replace global class/object management functions
|
||||
with ExtendedInstrumentationRegistry implementations, which
|
||||
allow multiple types of class managers to be present,
|
||||
at the cost of performance.
|
||||
|
||||
This function is called only by ExtendedInstrumentationRegistry
|
||||
and unit tests specific to this behavior.
|
||||
|
||||
The _reinstall_default_lookups() function can be called
|
||||
after this one to re-establish the default functions.
|
||||
|
||||
"""
|
||||
_install_lookups(
|
||||
dict(
|
||||
instance_state=_instrumentation_factory.state_of,
|
||||
instance_dict=_instrumentation_factory.dict_of,
|
||||
manager_of_class=_instrumentation_factory.manager_of_class
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _reinstall_default_lookups():
|
||||
"""Restore simplified lookups."""
|
||||
_install_lookups(
|
||||
dict(
|
||||
instance_state=_default_state_getter,
|
||||
instance_dict=_default_dict_getter,
|
||||
manager_of_class=_default_manager_getter
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _install_lookups(lookups):
|
||||
global instance_state, instance_dict, manager_of_class
|
||||
instance_state = lookups['instance_state']
|
||||
instance_dict = lookups['instance_dict']
|
||||
manager_of_class = lookups['manager_of_class']
|
||||
attributes.instance_state = \
|
||||
orm_instrumentation.instance_state = instance_state
|
||||
attributes.instance_dict = \
|
||||
orm_instrumentation.instance_dict = instance_dict
|
||||
attributes.manager_of_class = \
|
||||
orm_instrumentation.manager_of_class = manager_of_class
|
||||
@@ -0,0 +1,638 @@
|
||||
# ext/mutable.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Provide support for tracking of in-place changes to scalar values,
|
||||
which are propagated into ORM change events on owning parent objects.
|
||||
|
||||
The :mod:`sqlalchemy.ext.mutable` extension replaces SQLAlchemy's legacy
|
||||
approach to in-place mutations of scalar values, established by the
|
||||
:class:`.types.MutableType` class as well as the ``mutable=True`` type flag,
|
||||
with a system that allows change events to be propagated from the value to
|
||||
the owning parent, thereby removing the need for the ORM to maintain copies
|
||||
of values as well as the very expensive requirement of scanning through all
|
||||
"mutable" values on each flush call, looking for changes.
|
||||
|
||||
.. _mutable_scalars:
|
||||
|
||||
Establishing Mutability on Scalar Column Values
|
||||
===============================================
|
||||
|
||||
A typical example of a "mutable" structure is a Python dictionary.
|
||||
Following the example introduced in :ref:`types_toplevel`, we
|
||||
begin with a custom type that marshals Python dictionaries into
|
||||
JSON strings before being persisted::
|
||||
|
||||
from sqlalchemy.types import TypeDecorator, VARCHAR
|
||||
import json
|
||||
|
||||
class JSONEncodedDict(TypeDecorator):
|
||||
"Represents an immutable structure as a json-encoded string."
|
||||
|
||||
impl = VARCHAR
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
if value is not None:
|
||||
value = json.dumps(value)
|
||||
return value
|
||||
|
||||
def process_result_value(self, value, dialect):
|
||||
if value is not None:
|
||||
value = json.loads(value)
|
||||
return value
|
||||
|
||||
The usage of ``json`` is only for the purposes of example. The
|
||||
:mod:`sqlalchemy.ext.mutable` extension can be used
|
||||
with any type whose target Python type may be mutable, including
|
||||
:class:`.PickleType`, :class:`.postgresql.ARRAY`, etc.
|
||||
|
||||
When using the :mod:`sqlalchemy.ext.mutable` extension, the value itself
|
||||
tracks all parents which reference it. Below, we illustrate the a simple
|
||||
version of the :class:`.MutableDict` dictionary object, which applies
|
||||
the :class:`.Mutable` mixin to a plain Python dictionary::
|
||||
|
||||
import collections
|
||||
from sqlalchemy.ext.mutable import Mutable
|
||||
|
||||
class MutableDict(Mutable, dict):
|
||||
@classmethod
|
||||
def coerce(cls, key, value):
|
||||
"Convert plain dictionaries to MutableDict."
|
||||
|
||||
if not isinstance(value, MutableDict):
|
||||
if isinstance(value, dict):
|
||||
return MutableDict(value)
|
||||
|
||||
# this call will raise ValueError
|
||||
return Mutable.coerce(key, value)
|
||||
else:
|
||||
return value
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"Detect dictionary set events and emit change events."
|
||||
|
||||
dict.__setitem__(self, key, value)
|
||||
self.changed()
|
||||
|
||||
def __delitem__(self, key):
|
||||
"Detect dictionary del events and emit change events."
|
||||
|
||||
dict.__delitem__(self, key)
|
||||
self.changed()
|
||||
|
||||
The above dictionary class takes the approach of subclassing the Python
|
||||
built-in ``dict`` to produce a dict
|
||||
subclass which routes all mutation events through ``__setitem__``. There are
|
||||
variants on this approach, such as subclassing ``UserDict.UserDict`` or
|
||||
``collections.MutableMapping``; the part that's important to this example is
|
||||
that the :meth:`.Mutable.changed` method is called whenever an in-place
|
||||
change to the datastructure takes place.
|
||||
|
||||
We also redefine the :meth:`.Mutable.coerce` method which will be used to
|
||||
convert any values that are not instances of ``MutableDict``, such
|
||||
as the plain dictionaries returned by the ``json`` module, into the
|
||||
appropriate type. Defining this method is optional; we could just as well
|
||||
created our ``JSONEncodedDict`` such that it always returns an instance
|
||||
of ``MutableDict``, and additionally ensured that all calling code
|
||||
uses ``MutableDict`` explicitly. When :meth:`.Mutable.coerce` is not
|
||||
overridden, any values applied to a parent object which are not instances
|
||||
of the mutable type will raise a ``ValueError``.
|
||||
|
||||
Our new ``MutableDict`` type offers a class method
|
||||
:meth:`~.Mutable.as_mutable` which we can use within column metadata
|
||||
to associate with types. This method grabs the given type object or
|
||||
class and associates a listener that will detect all future mappings
|
||||
of this type, applying event listening instrumentation to the mapped
|
||||
attribute. Such as, with classical table metadata::
|
||||
|
||||
from sqlalchemy import Table, Column, Integer
|
||||
|
||||
my_data = Table('my_data', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('data', MutableDict.as_mutable(JSONEncodedDict))
|
||||
)
|
||||
|
||||
Above, :meth:`~.Mutable.as_mutable` returns an instance of ``JSONEncodedDict``
|
||||
(if the type object was not an instance already), which will intercept any
|
||||
attributes which are mapped against this type. Below we establish a simple
|
||||
mapping against the ``my_data`` table::
|
||||
|
||||
from sqlalchemy import mapper
|
||||
|
||||
class MyDataClass(object):
|
||||
pass
|
||||
|
||||
# associates mutation listeners with MyDataClass.data
|
||||
mapper(MyDataClass, my_data)
|
||||
|
||||
The ``MyDataClass.data`` member will now be notified of in place changes
|
||||
to its value.
|
||||
|
||||
There's no difference in usage when using declarative::
|
||||
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class MyDataClass(Base):
|
||||
__tablename__ = 'my_data'
|
||||
id = Column(Integer, primary_key=True)
|
||||
data = Column(MutableDict.as_mutable(JSONEncodedDict))
|
||||
|
||||
Any in-place changes to the ``MyDataClass.data`` member
|
||||
will flag the attribute as "dirty" on the parent object::
|
||||
|
||||
>>> from sqlalchemy.orm import Session
|
||||
|
||||
>>> sess = Session()
|
||||
>>> m1 = MyDataClass(data={'value1':'foo'})
|
||||
>>> sess.add(m1)
|
||||
>>> sess.commit()
|
||||
|
||||
>>> m1.data['value1'] = 'bar'
|
||||
>>> assert m1 in sess.dirty
|
||||
True
|
||||
|
||||
The ``MutableDict`` can be associated with all future instances
|
||||
of ``JSONEncodedDict`` in one step, using
|
||||
:meth:`~.Mutable.associate_with`. This is similar to
|
||||
:meth:`~.Mutable.as_mutable` except it will intercept all occurrences
|
||||
of ``MutableDict`` in all mappings unconditionally, without
|
||||
the need to declare it individually::
|
||||
|
||||
MutableDict.associate_with(JSONEncodedDict)
|
||||
|
||||
class MyDataClass(Base):
|
||||
__tablename__ = 'my_data'
|
||||
id = Column(Integer, primary_key=True)
|
||||
data = Column(JSONEncodedDict)
|
||||
|
||||
|
||||
Supporting Pickling
|
||||
--------------------
|
||||
|
||||
The key to the :mod:`sqlalchemy.ext.mutable` extension relies upon the
|
||||
placement of a ``weakref.WeakKeyDictionary`` upon the value object, which
|
||||
stores a mapping of parent mapped objects keyed to the attribute name under
|
||||
which they are associated with this value. ``WeakKeyDictionary`` objects are
|
||||
not picklable, due to the fact that they contain weakrefs and function
|
||||
callbacks. In our case, this is a good thing, since if this dictionary were
|
||||
picklable, it could lead to an excessively large pickle size for our value
|
||||
objects that are pickled by themselves outside of the context of the parent.
|
||||
The developer responsibility here is only to provide a ``__getstate__`` method
|
||||
that excludes the :meth:`~.MutableBase._parents` collection from the pickle
|
||||
stream::
|
||||
|
||||
class MyMutableType(Mutable):
|
||||
def __getstate__(self):
|
||||
d = self.__dict__.copy()
|
||||
d.pop('_parents', None)
|
||||
return d
|
||||
|
||||
With our dictionary example, we need to return the contents of the dict itself
|
||||
(and also restore them on __setstate__)::
|
||||
|
||||
class MutableDict(Mutable, dict):
|
||||
# ....
|
||||
|
||||
def __getstate__(self):
|
||||
return dict(self)
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.update(state)
|
||||
|
||||
In the case that our mutable value object is pickled as it is attached to one
|
||||
or more parent objects that are also part of the pickle, the :class:`.Mutable`
|
||||
mixin will re-establish the :attr:`.Mutable._parents` collection on each value
|
||||
object as the owning parents themselves are unpickled.
|
||||
|
||||
.. _mutable_composites:
|
||||
|
||||
Establishing Mutability on Composites
|
||||
=====================================
|
||||
|
||||
Composites are a special ORM feature which allow a single scalar attribute to
|
||||
be assigned an object value which represents information "composed" from one
|
||||
or more columns from the underlying mapped table. The usual example is that of
|
||||
a geometric "point", and is introduced in :ref:`mapper_composite`.
|
||||
|
||||
.. versionchanged:: 0.7
|
||||
The internals of :func:`.orm.composite` have been
|
||||
greatly simplified and in-place mutation detection is no longer enabled by
|
||||
default; instead, the user-defined value must detect changes on its own and
|
||||
propagate them to all owning parents. The :mod:`sqlalchemy.ext.mutable`
|
||||
extension provides the helper class :class:`.MutableComposite`, which is a
|
||||
slight variant on the :class:`.Mutable` class.
|
||||
|
||||
As is the case with :class:`.Mutable`, the user-defined composite class
|
||||
subclasses :class:`.MutableComposite` as a mixin, and detects and delivers
|
||||
change events to its parents via the :meth:`.MutableComposite.changed` method.
|
||||
In the case of a composite class, the detection is usually via the usage of
|
||||
Python descriptors (i.e. ``@property``), or alternatively via the special
|
||||
Python method ``__setattr__()``. Below we expand upon the ``Point`` class
|
||||
introduced in :ref:`mapper_composite` to subclass :class:`.MutableComposite`
|
||||
and to also route attribute set events via ``__setattr__`` to the
|
||||
:meth:`.MutableComposite.changed` method::
|
||||
|
||||
from sqlalchemy.ext.mutable import MutableComposite
|
||||
|
||||
class Point(MutableComposite):
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
"Intercept set events"
|
||||
|
||||
# set the attribute
|
||||
object.__setattr__(self, key, value)
|
||||
|
||||
# alert all parents to the change
|
||||
self.changed()
|
||||
|
||||
def __composite_values__(self):
|
||||
return self.x, self.y
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Point) and \\
|
||||
other.x == self.x and \\
|
||||
other.y == self.y
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
The :class:`.MutableComposite` class uses a Python metaclass to automatically
|
||||
establish listeners for any usage of :func:`.orm.composite` that specifies our
|
||||
``Point`` type. Below, when ``Point`` is mapped to the ``Vertex`` class,
|
||||
listeners are established which will route change events from ``Point``
|
||||
objects to each of the ``Vertex.start`` and ``Vertex.end`` attributes::
|
||||
|
||||
from sqlalchemy.orm import composite, mapper
|
||||
from sqlalchemy import Table, Column
|
||||
|
||||
vertices = Table('vertices', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('x1', Integer),
|
||||
Column('y1', Integer),
|
||||
Column('x2', Integer),
|
||||
Column('y2', Integer),
|
||||
)
|
||||
|
||||
class Vertex(object):
|
||||
pass
|
||||
|
||||
mapper(Vertex, vertices, properties={
|
||||
'start': composite(Point, vertices.c.x1, vertices.c.y1),
|
||||
'end': composite(Point, vertices.c.x2, vertices.c.y2)
|
||||
})
|
||||
|
||||
Any in-place changes to the ``Vertex.start`` or ``Vertex.end`` members
|
||||
will flag the attribute as "dirty" on the parent object::
|
||||
|
||||
>>> from sqlalchemy.orm import Session
|
||||
|
||||
>>> sess = Session()
|
||||
>>> v1 = Vertex(start=Point(3, 4), end=Point(12, 15))
|
||||
>>> sess.add(v1)
|
||||
>>> sess.commit()
|
||||
|
||||
>>> v1.end.x = 8
|
||||
>>> assert v1 in sess.dirty
|
||||
True
|
||||
|
||||
Coercing Mutable Composites
|
||||
---------------------------
|
||||
|
||||
The :meth:`.MutableBase.coerce` method is also supported on composite types.
|
||||
In the case of :class:`.MutableComposite`, the :meth:`.MutableBase.coerce`
|
||||
method is only called for attribute set operations, not load operations.
|
||||
Overriding the :meth:`.MutableBase.coerce` method is essentially equivalent
|
||||
to using a :func:`.validates` validation routine for all attributes which
|
||||
make use of the custom composite type::
|
||||
|
||||
class Point(MutableComposite):
|
||||
# other Point methods
|
||||
# ...
|
||||
|
||||
def coerce(cls, key, value):
|
||||
if isinstance(value, tuple):
|
||||
value = Point(*value)
|
||||
elif not isinstance(value, Point):
|
||||
raise ValueError("tuple or Point expected")
|
||||
return value
|
||||
|
||||
.. versionadded:: 0.7.10,0.8.0b2
|
||||
Support for the :meth:`.MutableBase.coerce` method in conjunction with
|
||||
objects of type :class:`.MutableComposite`.
|
||||
|
||||
Supporting Pickling
|
||||
--------------------
|
||||
|
||||
As is the case with :class:`.Mutable`, the :class:`.MutableComposite` helper
|
||||
class uses a ``weakref.WeakKeyDictionary`` available via the
|
||||
:meth:`.MutableBase._parents` attribute which isn't picklable. If we need to
|
||||
pickle instances of ``Point`` or its owning class ``Vertex``, we at least need
|
||||
to define a ``__getstate__`` that doesn't include the ``_parents`` dictionary.
|
||||
Below we define both a ``__getstate__`` and a ``__setstate__`` that package up
|
||||
the minimal form of our ``Point`` class::
|
||||
|
||||
class Point(MutableComposite):
|
||||
# ...
|
||||
|
||||
def __getstate__(self):
|
||||
return self.x, self.y
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.x, self.y = state
|
||||
|
||||
As with :class:`.Mutable`, the :class:`.MutableComposite` augments the
|
||||
pickling process of the parent's object-relational state so that the
|
||||
:meth:`.MutableBase._parents` collection is restored to all ``Point`` objects.
|
||||
|
||||
"""
|
||||
from ..orm.attributes import flag_modified
|
||||
from .. import event, types
|
||||
from ..orm import mapper, object_mapper, Mapper
|
||||
from ..util import memoized_property
|
||||
import weakref
|
||||
|
||||
|
||||
class MutableBase(object):
|
||||
"""Common base class to :class:`.Mutable`
|
||||
and :class:`.MutableComposite`.
|
||||
|
||||
"""
|
||||
|
||||
@memoized_property
|
||||
def _parents(self):
|
||||
"""Dictionary of parent object->attribute name on the parent.
|
||||
|
||||
This attribute is a so-called "memoized" property. It initializes
|
||||
itself with a new ``weakref.WeakKeyDictionary`` the first time
|
||||
it is accessed, returning the same object upon subsequent access.
|
||||
|
||||
"""
|
||||
|
||||
return weakref.WeakKeyDictionary()
|
||||
|
||||
@classmethod
|
||||
def coerce(cls, key, value):
|
||||
"""Given a value, coerce it into the target type.
|
||||
|
||||
Can be overridden by custom subclasses to coerce incoming
|
||||
data into a particular type.
|
||||
|
||||
By default, raises ``ValueError``.
|
||||
|
||||
This method is called in different scenarios depending on if
|
||||
the parent class is of type :class:`.Mutable` or of type
|
||||
:class:`.MutableComposite`. In the case of the former, it is called
|
||||
for both attribute-set operations as well as during ORM loading
|
||||
operations. For the latter, it is only called during attribute-set
|
||||
operations; the mechanics of the :func:`.composite` construct
|
||||
handle coercion during load operations.
|
||||
|
||||
|
||||
:param key: string name of the ORM-mapped attribute being set.
|
||||
:param value: the incoming value.
|
||||
:return: the method should return the coerced value, or raise
|
||||
``ValueError`` if the coercion cannot be completed.
|
||||
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
msg = "Attribute '%s' does not accept objects of type %s"
|
||||
raise ValueError(msg % (key, type(value)))
|
||||
|
||||
@classmethod
|
||||
def _listen_on_attribute(cls, attribute, coerce, parent_cls):
|
||||
"""Establish this type as a mutation listener for the given
|
||||
mapped descriptor.
|
||||
|
||||
"""
|
||||
key = attribute.key
|
||||
if parent_cls is not attribute.class_:
|
||||
return
|
||||
|
||||
# rely on "propagate" here
|
||||
parent_cls = attribute.class_
|
||||
|
||||
def load(state, *args):
|
||||
"""Listen for objects loaded or refreshed.
|
||||
|
||||
Wrap the target data member's value with
|
||||
``Mutable``.
|
||||
|
||||
"""
|
||||
val = state.dict.get(key, None)
|
||||
if val is not None:
|
||||
if coerce:
|
||||
val = cls.coerce(key, val)
|
||||
state.dict[key] = val
|
||||
val._parents[state.obj()] = key
|
||||
|
||||
def set(target, value, oldvalue, initiator):
|
||||
"""Listen for set/replace events on the target
|
||||
data member.
|
||||
|
||||
Establish a weak reference to the parent object
|
||||
on the incoming value, remove it for the one
|
||||
outgoing.
|
||||
|
||||
"""
|
||||
if not isinstance(value, cls):
|
||||
value = cls.coerce(key, value)
|
||||
if value is not None:
|
||||
value._parents[target.obj()] = key
|
||||
if isinstance(oldvalue, cls):
|
||||
oldvalue._parents.pop(target.obj(), None)
|
||||
return value
|
||||
|
||||
def pickle(state, state_dict):
|
||||
val = state.dict.get(key, None)
|
||||
if val is not None:
|
||||
if 'ext.mutable.values' not in state_dict:
|
||||
state_dict['ext.mutable.values'] = []
|
||||
state_dict['ext.mutable.values'].append(val)
|
||||
|
||||
def unpickle(state, state_dict):
|
||||
if 'ext.mutable.values' in state_dict:
|
||||
for val in state_dict['ext.mutable.values']:
|
||||
val._parents[state.obj()] = key
|
||||
|
||||
event.listen(parent_cls, 'load', load,
|
||||
raw=True, propagate=True)
|
||||
event.listen(parent_cls, 'refresh', load,
|
||||
raw=True, propagate=True)
|
||||
event.listen(attribute, 'set', set,
|
||||
raw=True, retval=True, propagate=True)
|
||||
event.listen(parent_cls, 'pickle', pickle,
|
||||
raw=True, propagate=True)
|
||||
event.listen(parent_cls, 'unpickle', unpickle,
|
||||
raw=True, propagate=True)
|
||||
|
||||
|
||||
class Mutable(MutableBase):
|
||||
"""Mixin that defines transparent propagation of change
|
||||
events to a parent object.
|
||||
|
||||
See the example in :ref:`mutable_scalars` for usage information.
|
||||
|
||||
"""
|
||||
|
||||
def changed(self):
|
||||
"""Subclasses should call this method whenever change events occur."""
|
||||
|
||||
for parent, key in self._parents.items():
|
||||
flag_modified(parent, key)
|
||||
|
||||
@classmethod
|
||||
def associate_with_attribute(cls, attribute):
|
||||
"""Establish this type as a mutation listener for the given
|
||||
mapped descriptor.
|
||||
|
||||
"""
|
||||
cls._listen_on_attribute(attribute, True, attribute.class_)
|
||||
|
||||
@classmethod
|
||||
def associate_with(cls, sqltype):
|
||||
"""Associate this wrapper with all future mapped columns
|
||||
of the given type.
|
||||
|
||||
This is a convenience method that calls
|
||||
``associate_with_attribute`` automatically.
|
||||
|
||||
.. warning::
|
||||
|
||||
The listeners established by this method are *global*
|
||||
to all mappers, and are *not* garbage collected. Only use
|
||||
:meth:`.associate_with` for types that are permanent to an
|
||||
application, not with ad-hoc types else this will cause unbounded
|
||||
growth in memory usage.
|
||||
|
||||
"""
|
||||
|
||||
def listen_for_type(mapper, class_):
|
||||
for prop in mapper.column_attrs:
|
||||
if isinstance(prop.columns[0].type, sqltype):
|
||||
cls.associate_with_attribute(getattr(class_, prop.key))
|
||||
|
||||
event.listen(mapper, 'mapper_configured', listen_for_type)
|
||||
|
||||
@classmethod
|
||||
def as_mutable(cls, sqltype):
|
||||
"""Associate a SQL type with this mutable Python type.
|
||||
|
||||
This establishes listeners that will detect ORM mappings against
|
||||
the given type, adding mutation event trackers to those mappings.
|
||||
|
||||
The type is returned, unconditionally as an instance, so that
|
||||
:meth:`.as_mutable` can be used inline::
|
||||
|
||||
Table('mytable', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('data', MyMutableType.as_mutable(PickleType))
|
||||
)
|
||||
|
||||
Note that the returned type is always an instance, even if a class
|
||||
is given, and that only columns which are declared specifically with
|
||||
that type instance receive additional instrumentation.
|
||||
|
||||
To associate a particular mutable type with all occurrences of a
|
||||
particular type, use the :meth:`.Mutable.associate_with` classmethod
|
||||
of the particular :meth:`.Mutable` subclass to establish a global
|
||||
association.
|
||||
|
||||
.. warning::
|
||||
|
||||
The listeners established by this method are *global*
|
||||
to all mappers, and are *not* garbage collected. Only use
|
||||
:meth:`.as_mutable` for types that are permanent to an application,
|
||||
not with ad-hoc types else this will cause unbounded growth
|
||||
in memory usage.
|
||||
|
||||
"""
|
||||
sqltype = types.to_instance(sqltype)
|
||||
|
||||
def listen_for_type(mapper, class_):
|
||||
for prop in mapper.column_attrs:
|
||||
if prop.columns[0].type is sqltype:
|
||||
cls.associate_with_attribute(getattr(class_, prop.key))
|
||||
|
||||
event.listen(mapper, 'mapper_configured', listen_for_type)
|
||||
|
||||
return sqltype
|
||||
|
||||
|
||||
|
||||
class MutableComposite(MutableBase):
|
||||
"""Mixin that defines transparent propagation of change
|
||||
events on a SQLAlchemy "composite" object to its
|
||||
owning parent or parents.
|
||||
|
||||
See the example in :ref:`mutable_composites` for usage information.
|
||||
|
||||
"""
|
||||
|
||||
def changed(self):
|
||||
"""Subclasses should call this method whenever change events occur."""
|
||||
|
||||
for parent, key in self._parents.items():
|
||||
|
||||
prop = object_mapper(parent).get_property(key)
|
||||
for value, attr_name in zip(
|
||||
self.__composite_values__(),
|
||||
prop._attribute_keys):
|
||||
setattr(parent, attr_name, value)
|
||||
|
||||
def _setup_composite_listener():
|
||||
import types
|
||||
def _listen_for_type(mapper, class_):
|
||||
for prop in mapper.iterate_properties:
|
||||
if (hasattr(prop, 'composite_class') and (type(prop.composite_class) in (types.ClassType, types.TypeType)) and
|
||||
issubclass(prop.composite_class, MutableComposite)):
|
||||
prop.composite_class._listen_on_attribute(
|
||||
getattr(class_, prop.key), False, class_)
|
||||
if not Mapper.dispatch.mapper_configured._contains(Mapper, _listen_for_type):
|
||||
event.listen(Mapper, 'mapper_configured', _listen_for_type)
|
||||
_setup_composite_listener()
|
||||
|
||||
|
||||
class MutableDict(Mutable, dict):
|
||||
"""A dictionary type that implements :class:`.Mutable`.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
"""
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Detect dictionary set events and emit change events."""
|
||||
dict.__setitem__(self, key, value)
|
||||
self.changed()
|
||||
|
||||
def __delitem__(self, key):
|
||||
"""Detect dictionary del events and emit change events."""
|
||||
dict.__delitem__(self, key)
|
||||
self.changed()
|
||||
|
||||
def clear(self):
|
||||
dict.clear(self)
|
||||
self.changed()
|
||||
|
||||
@classmethod
|
||||
def coerce(cls, key, value):
|
||||
"""Convert plain dictionary to MutableDict."""
|
||||
if not isinstance(value, MutableDict):
|
||||
if isinstance(value, dict):
|
||||
return MutableDict(value)
|
||||
return Mutable.coerce(key, value)
|
||||
else:
|
||||
return value
|
||||
|
||||
def __getstate__(self):
|
||||
return dict(self)
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.update(state)
|
||||
@@ -0,0 +1,367 @@
|
||||
# ext/orderinglist.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""A custom list that manages index/position information for contained
|
||||
elements.
|
||||
|
||||
:author: Jason Kirtland
|
||||
|
||||
``orderinglist`` is a helper for mutable ordered relationships. It will
|
||||
intercept list operations performed on a :func:`.relationship`-managed
|
||||
collection and
|
||||
automatically synchronize changes in list position onto a target scalar
|
||||
attribute.
|
||||
|
||||
Example: A ``slide`` table, where each row refers to zero or more entries
|
||||
in a related ``bullet`` table. The bullets within a slide are
|
||||
displayed in order based on the value of the ``position`` column in the
|
||||
``bullet`` table. As entries are reordered in memory, the value of the
|
||||
``position`` attribute should be updated to reflect the new sort order::
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class Slide(Base):
|
||||
__tablename__ = 'slide'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
|
||||
bullets = relationship("Bullet", order_by="Bullet.position")
|
||||
|
||||
class Bullet(Base):
|
||||
__tablename__ = 'bullet'
|
||||
id = Column(Integer, primary_key=True)
|
||||
slide_id = Column(Integer, ForeignKey('slide.id'))
|
||||
position = Column(Integer)
|
||||
text = Column(String)
|
||||
|
||||
The standard relationship mapping will produce a list-like attribute on each
|
||||
``Slide`` containing all related ``Bullet`` objects,
|
||||
but coping with changes in ordering is not handled automatically.
|
||||
When appending a ``Bullet`` into ``Slide.bullets``, the ``Bullet.position``
|
||||
attribute will remain unset until manually assigned. When the ``Bullet``
|
||||
is inserted into the middle of the list, the following ``Bullet`` objects
|
||||
will also need to be renumbered.
|
||||
|
||||
The :class:`.OrderingList` object automates this task, managing the
|
||||
``position`` attribute on all ``Bullet`` objects in the collection. It is
|
||||
constructed using the :func:`.ordering_list` factory::
|
||||
|
||||
from sqlalchemy.ext.orderinglist import ordering_list
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class Slide(Base):
|
||||
__tablename__ = 'slide'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
|
||||
bullets = relationship("Bullet", order_by="Bullet.position",
|
||||
collection_class=ordering_list('position'))
|
||||
|
||||
class Bullet(Base):
|
||||
__tablename__ = 'bullet'
|
||||
id = Column(Integer, primary_key=True)
|
||||
slide_id = Column(Integer, ForeignKey('slide.id'))
|
||||
position = Column(Integer)
|
||||
text = Column(String)
|
||||
|
||||
With the above mapping the ``Bullet.position`` attribute is managed::
|
||||
|
||||
s = Slide()
|
||||
s.bullets.append(Bullet())
|
||||
s.bullets.append(Bullet())
|
||||
s.bullets[1].position
|
||||
>>> 1
|
||||
s.bullets.insert(1, Bullet())
|
||||
s.bullets[2].position
|
||||
>>> 2
|
||||
|
||||
The :class:`.OrderingList` construct only works with **changes** to a collection,
|
||||
and not the initial load from the database, and requires that the list be
|
||||
sorted when loaded. Therefore, be sure to
|
||||
specify ``order_by`` on the :func:`.relationship` against the target ordering
|
||||
attribute, so that the ordering is correct when first loaded.
|
||||
|
||||
.. warning::
|
||||
|
||||
:class:`.OrderingList` only provides limited functionality when a primary
|
||||
key column or unique column is the target of the sort. Since changing the
|
||||
order of entries often means that two rows must trade values, this is not
|
||||
possible when the value is constrained by a primary key or unique
|
||||
constraint, since one of the rows would temporarily have to point to a
|
||||
third available value so that the other row could take its old
|
||||
value. :class:`.OrderingList` doesn't do any of this for you,
|
||||
nor does SQLAlchemy itself.
|
||||
|
||||
:func:`.ordering_list` takes the name of the related object's ordering attribute as
|
||||
an argument. By default, the zero-based integer index of the object's
|
||||
position in the :func:`.ordering_list` is synchronized with the ordering attribute:
|
||||
index 0 will get position 0, index 1 position 1, etc. To start numbering at 1
|
||||
or some other integer, provide ``count_from=1``.
|
||||
|
||||
|
||||
"""
|
||||
from ..orm.collections import collection
|
||||
from .. import util
|
||||
|
||||
__all__ = ['ordering_list']
|
||||
|
||||
|
||||
def ordering_list(attr, count_from=None, **kw):
|
||||
"""Prepares an :class:`OrderingList` factory for use in mapper definitions.
|
||||
|
||||
Returns an object suitable for use as an argument to a Mapper
|
||||
relationship's ``collection_class`` option. e.g.::
|
||||
|
||||
from sqlalchemy.ext.orderinglist import ordering_list
|
||||
|
||||
class Slide(Base):
|
||||
__tablename__ = 'slide'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
|
||||
bullets = relationship("Bullet", order_by="Bullet.position",
|
||||
collection_class=ordering_list('position'))
|
||||
|
||||
:param attr:
|
||||
Name of the mapped attribute to use for storage and retrieval of
|
||||
ordering information
|
||||
|
||||
:param count_from:
|
||||
Set up an integer-based ordering, starting at ``count_from``. For
|
||||
example, ``ordering_list('pos', count_from=1)`` would create a 1-based
|
||||
list in SQL, storing the value in the 'pos' column. Ignored if
|
||||
``ordering_func`` is supplied.
|
||||
|
||||
Additional arguments are passed to the :class:`.OrderingList` constructor.
|
||||
|
||||
"""
|
||||
|
||||
kw = _unsugar_count_from(count_from=count_from, **kw)
|
||||
return lambda: OrderingList(attr, **kw)
|
||||
|
||||
|
||||
# Ordering utility functions
|
||||
|
||||
|
||||
def count_from_0(index, collection):
|
||||
"""Numbering function: consecutive integers starting at 0."""
|
||||
|
||||
return index
|
||||
|
||||
|
||||
def count_from_1(index, collection):
|
||||
"""Numbering function: consecutive integers starting at 1."""
|
||||
|
||||
return index + 1
|
||||
|
||||
|
||||
def count_from_n_factory(start):
|
||||
"""Numbering function: consecutive integers starting at arbitrary start."""
|
||||
|
||||
def f(index, collection):
|
||||
return index + start
|
||||
try:
|
||||
f.__name__ = 'count_from_%i' % start
|
||||
except TypeError:
|
||||
pass
|
||||
return f
|
||||
|
||||
|
||||
def _unsugar_count_from(**kw):
|
||||
"""Builds counting functions from keyword arguments.
|
||||
|
||||
Keyword argument filter, prepares a simple ``ordering_func`` from a
|
||||
``count_from`` argument, otherwise passes ``ordering_func`` on unchanged.
|
||||
"""
|
||||
|
||||
count_from = kw.pop('count_from', None)
|
||||
if kw.get('ordering_func', None) is None and count_from is not None:
|
||||
if count_from == 0:
|
||||
kw['ordering_func'] = count_from_0
|
||||
elif count_from == 1:
|
||||
kw['ordering_func'] = count_from_1
|
||||
else:
|
||||
kw['ordering_func'] = count_from_n_factory(count_from)
|
||||
return kw
|
||||
|
||||
|
||||
class OrderingList(list):
|
||||
"""A custom list that manages position information for its children.
|
||||
|
||||
The :class:`.OrderingList` object is normally set up using the
|
||||
:func:`.ordering_list` factory function, used in conjunction with
|
||||
the :func:`.relationship` function.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, ordering_attr=None, ordering_func=None,
|
||||
reorder_on_append=False):
|
||||
"""A custom list that manages position information for its children.
|
||||
|
||||
``OrderingList`` is a ``collection_class`` list implementation that
|
||||
syncs position in a Python list with a position attribute on the
|
||||
mapped objects.
|
||||
|
||||
This implementation relies on the list starting in the proper order,
|
||||
so be **sure** to put an ``order_by`` on your relationship.
|
||||
|
||||
:param ordering_attr:
|
||||
Name of the attribute that stores the object's order in the
|
||||
relationship.
|
||||
|
||||
:param ordering_func: Optional. A function that maps the position in
|
||||
the Python list to a value to store in the
|
||||
``ordering_attr``. Values returned are usually (but need not be!)
|
||||
integers.
|
||||
|
||||
An ``ordering_func`` is called with two positional parameters: the
|
||||
index of the element in the list, and the list itself.
|
||||
|
||||
If omitted, Python list indexes are used for the attribute values.
|
||||
Two basic pre-built numbering functions are provided in this module:
|
||||
``count_from_0`` and ``count_from_1``. For more exotic examples
|
||||
like stepped numbering, alphabetical and Fibonacci numbering, see
|
||||
the unit tests.
|
||||
|
||||
:param reorder_on_append:
|
||||
Default False. When appending an object with an existing (non-None)
|
||||
ordering value, that value will be left untouched unless
|
||||
``reorder_on_append`` is true. This is an optimization to avoid a
|
||||
variety of dangerous unexpected database writes.
|
||||
|
||||
SQLAlchemy will add instances to the list via append() when your
|
||||
object loads. If for some reason the result set from the database
|
||||
skips a step in the ordering (say, row '1' is missing but you get
|
||||
'2', '3', and '4'), reorder_on_append=True would immediately
|
||||
renumber the items to '1', '2', '3'. If you have multiple sessions
|
||||
making changes, any of whom happen to load this collection even in
|
||||
passing, all of the sessions would try to "clean up" the numbering
|
||||
in their commits, possibly causing all but one to fail with a
|
||||
concurrent modification error.
|
||||
|
||||
Recommend leaving this with the default of False, and just call
|
||||
``reorder()`` if you're doing ``append()`` operations with
|
||||
previously ordered instances or when doing some housekeeping after
|
||||
manual sql operations.
|
||||
|
||||
"""
|
||||
self.ordering_attr = ordering_attr
|
||||
if ordering_func is None:
|
||||
ordering_func = count_from_0
|
||||
self.ordering_func = ordering_func
|
||||
self.reorder_on_append = reorder_on_append
|
||||
|
||||
# More complex serialization schemes (multi column, e.g.) are possible by
|
||||
# subclassing and reimplementing these two methods.
|
||||
def _get_order_value(self, entity):
|
||||
return getattr(entity, self.ordering_attr)
|
||||
|
||||
def _set_order_value(self, entity, value):
|
||||
setattr(entity, self.ordering_attr, value)
|
||||
|
||||
def reorder(self):
|
||||
"""Synchronize ordering for the entire collection.
|
||||
|
||||
Sweeps through the list and ensures that each object has accurate
|
||||
ordering information set.
|
||||
|
||||
"""
|
||||
for index, entity in enumerate(self):
|
||||
self._order_entity(index, entity, True)
|
||||
|
||||
# As of 0.5, _reorder is no longer semi-private
|
||||
_reorder = reorder
|
||||
|
||||
def _order_entity(self, index, entity, reorder=True):
|
||||
have = self._get_order_value(entity)
|
||||
|
||||
# Don't disturb existing ordering if reorder is False
|
||||
if have is not None and not reorder:
|
||||
return
|
||||
|
||||
should_be = self.ordering_func(index, self)
|
||||
if have != should_be:
|
||||
self._set_order_value(entity, should_be)
|
||||
|
||||
def append(self, entity):
|
||||
super(OrderingList, self).append(entity)
|
||||
self._order_entity(len(self) - 1, entity, self.reorder_on_append)
|
||||
|
||||
def _raw_append(self, entity):
|
||||
"""Append without any ordering behavior."""
|
||||
|
||||
super(OrderingList, self).append(entity)
|
||||
_raw_append = collection.adds(1)(_raw_append)
|
||||
|
||||
def insert(self, index, entity):
|
||||
super(OrderingList, self).insert(index, entity)
|
||||
self._reorder()
|
||||
|
||||
def remove(self, entity):
|
||||
super(OrderingList, self).remove(entity)
|
||||
self._reorder()
|
||||
|
||||
def pop(self, index=-1):
|
||||
entity = super(OrderingList, self).pop(index)
|
||||
self._reorder()
|
||||
return entity
|
||||
|
||||
def __setitem__(self, index, entity):
|
||||
if isinstance(index, slice):
|
||||
step = index.step or 1
|
||||
start = index.start or 0
|
||||
if start < 0:
|
||||
start += len(self)
|
||||
stop = index.stop or len(self)
|
||||
if stop < 0:
|
||||
stop += len(self)
|
||||
|
||||
for i in xrange(start, stop, step):
|
||||
self.__setitem__(i, entity[i])
|
||||
else:
|
||||
self._order_entity(index, entity, True)
|
||||
super(OrderingList, self).__setitem__(index, entity)
|
||||
|
||||
def __delitem__(self, index):
|
||||
super(OrderingList, self).__delitem__(index)
|
||||
self._reorder()
|
||||
|
||||
# Py2K
|
||||
def __setslice__(self, start, end, values):
|
||||
super(OrderingList, self).__setslice__(start, end, values)
|
||||
self._reorder()
|
||||
|
||||
def __delslice__(self, start, end):
|
||||
super(OrderingList, self).__delslice__(start, end)
|
||||
self._reorder()
|
||||
# end Py2K
|
||||
|
||||
def __reduce__(self):
|
||||
return _reconstitute, (self.__class__, self.__dict__, list(self))
|
||||
|
||||
for func_name, func in locals().items():
|
||||
if (util.callable(func) and func.func_name == func_name and
|
||||
not func.__doc__ and hasattr(list, func_name)):
|
||||
func.__doc__ = getattr(list, func_name).__doc__
|
||||
del func_name, func
|
||||
|
||||
|
||||
def _reconstitute(cls, dict_, items):
|
||||
""" Reconstitute an :class:`.OrderingList`.
|
||||
|
||||
This is the adjoint to :meth:`.OrderingList.__reduce__`. It is used for
|
||||
unpickling :class:`.OrderingList` objects.
|
||||
|
||||
"""
|
||||
obj = cls.__new__(cls)
|
||||
obj.__dict__.update(dict_)
|
||||
list.extend(obj, items)
|
||||
return obj
|
||||
@@ -0,0 +1,171 @@
|
||||
# ext/serializer.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Serializer/Deserializer objects for usage with SQLAlchemy query structures,
|
||||
allowing "contextual" deserialization.
|
||||
|
||||
Any SQLAlchemy query structure, either based on sqlalchemy.sql.*
|
||||
or sqlalchemy.orm.* can be used. The mappers, Tables, Columns, Session
|
||||
etc. which are referenced by the structure are not persisted in serialized
|
||||
form, but are instead re-associated with the query structure
|
||||
when it is deserialized.
|
||||
|
||||
Usage is nearly the same as that of the standard Python pickle module::
|
||||
|
||||
from sqlalchemy.ext.serializer import loads, dumps
|
||||
metadata = MetaData(bind=some_engine)
|
||||
Session = scoped_session(sessionmaker())
|
||||
|
||||
# ... define mappers
|
||||
|
||||
query = Session.query(MyClass).filter(MyClass.somedata=='foo').order_by(MyClass.sortkey)
|
||||
|
||||
# pickle the query
|
||||
serialized = dumps(query)
|
||||
|
||||
# unpickle. Pass in metadata + scoped_session
|
||||
query2 = loads(serialized, metadata, Session)
|
||||
|
||||
print query2.all()
|
||||
|
||||
Similar restrictions as when using raw pickle apply; mapped classes must be
|
||||
themselves be pickleable, meaning they are importable from a module-level
|
||||
namespace.
|
||||
|
||||
The serializer module is only appropriate for query structures. It is not
|
||||
needed for:
|
||||
|
||||
* instances of user-defined classes. These contain no references to engines,
|
||||
sessions or expression constructs in the typical case and can be serialized
|
||||
directly.
|
||||
|
||||
* Table metadata that is to be loaded entirely from the serialized structure
|
||||
(i.e. is not already declared in the application). Regular
|
||||
pickle.loads()/dumps() can be used to fully dump any ``MetaData`` object,
|
||||
typically one which was reflected from an existing database at some previous
|
||||
point in time. The serializer module is specifically for the opposite case,
|
||||
where the Table metadata is already present in memory.
|
||||
|
||||
"""
|
||||
|
||||
from ..orm import class_mapper
|
||||
from ..orm.session import Session
|
||||
from ..orm.mapper import Mapper
|
||||
from ..orm.interfaces import MapperProperty
|
||||
from ..orm.attributes import QueryableAttribute
|
||||
from .. import Table, Column
|
||||
from ..engine import Engine
|
||||
from ..util import pickle
|
||||
import re
|
||||
import base64
|
||||
# Py3K
|
||||
#from io import BytesIO as byte_buffer
|
||||
# Py2K
|
||||
from cStringIO import StringIO as byte_buffer
|
||||
# end Py2K
|
||||
|
||||
# Py3K
|
||||
#def b64encode(x):
|
||||
# return base64.b64encode(x).decode('ascii')
|
||||
#def b64decode(x):
|
||||
# return base64.b64decode(x.encode('ascii'))
|
||||
# Py2K
|
||||
b64encode = base64.b64encode
|
||||
b64decode = base64.b64decode
|
||||
# end Py2K
|
||||
|
||||
__all__ = ['Serializer', 'Deserializer', 'dumps', 'loads']
|
||||
|
||||
|
||||
def Serializer(*args, **kw):
|
||||
pickler = pickle.Pickler(*args, **kw)
|
||||
|
||||
def persistent_id(obj):
|
||||
#print "serializing:", repr(obj)
|
||||
if isinstance(obj, QueryableAttribute):
|
||||
cls = obj.impl.class_
|
||||
key = obj.impl.key
|
||||
id = "attribute:" + key + ":" + b64encode(pickle.dumps(cls))
|
||||
elif isinstance(obj, Mapper) and not obj.non_primary:
|
||||
id = "mapper:" + b64encode(pickle.dumps(obj.class_))
|
||||
elif isinstance(obj, MapperProperty) and not obj.parent.non_primary:
|
||||
id = "mapperprop:" + b64encode(pickle.dumps(obj.parent.class_)) + \
|
||||
":" + obj.key
|
||||
elif isinstance(obj, Table):
|
||||
id = "table:" + str(obj)
|
||||
elif isinstance(obj, Column) and isinstance(obj.table, Table):
|
||||
id = "column:" + str(obj.table) + ":" + obj.key
|
||||
elif isinstance(obj, Session):
|
||||
id = "session:"
|
||||
elif isinstance(obj, Engine):
|
||||
id = "engine:"
|
||||
else:
|
||||
return None
|
||||
return id
|
||||
|
||||
pickler.persistent_id = persistent_id
|
||||
return pickler
|
||||
|
||||
our_ids = re.compile(
|
||||
r'(mapperprop|mapper|table|column|session|attribute|engine):(.*)')
|
||||
|
||||
|
||||
def Deserializer(file, metadata=None, scoped_session=None, engine=None):
|
||||
unpickler = pickle.Unpickler(file)
|
||||
|
||||
def get_engine():
|
||||
if engine:
|
||||
return engine
|
||||
elif scoped_session and scoped_session().bind:
|
||||
return scoped_session().bind
|
||||
elif metadata and metadata.bind:
|
||||
return metadata.bind
|
||||
else:
|
||||
return None
|
||||
|
||||
def persistent_load(id):
|
||||
m = our_ids.match(str(id))
|
||||
if not m:
|
||||
return None
|
||||
else:
|
||||
type_, args = m.group(1, 2)
|
||||
if type_ == 'attribute':
|
||||
key, clsarg = args.split(":")
|
||||
cls = pickle.loads(b64decode(clsarg))
|
||||
return getattr(cls, key)
|
||||
elif type_ == "mapper":
|
||||
cls = pickle.loads(b64decode(args))
|
||||
return class_mapper(cls)
|
||||
elif type_ == "mapperprop":
|
||||
mapper, keyname = args.split(':')
|
||||
cls = pickle.loads(b64decode(mapper))
|
||||
return class_mapper(cls).attrs[keyname]
|
||||
elif type_ == "table":
|
||||
return metadata.tables[args]
|
||||
elif type_ == "column":
|
||||
table, colname = args.split(':')
|
||||
return metadata.tables[table].c[colname]
|
||||
elif type_ == "session":
|
||||
return scoped_session()
|
||||
elif type_ == "engine":
|
||||
return get_engine()
|
||||
else:
|
||||
raise Exception("Unknown token: %s" % type_)
|
||||
unpickler.persistent_load = persistent_load
|
||||
return unpickler
|
||||
|
||||
|
||||
def dumps(obj, protocol=0):
|
||||
buf = byte_buffer()
|
||||
pickler = Serializer(buf, protocol)
|
||||
pickler.dump(obj)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
def loads(data, metadata=None, scoped_session=None, engine=None):
|
||||
buf = byte_buffer(data)
|
||||
unpickler = Deserializer(buf, metadata, scoped_session, engine)
|
||||
return unpickler.load()
|
||||
@@ -0,0 +1,91 @@
|
||||
# sqlalchemy/inspect.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""The inspection module provides the :func:`.inspect` function,
|
||||
which delivers runtime information about a wide variety
|
||||
of SQLAlchemy objects, both within the Core as well as the
|
||||
ORM.
|
||||
|
||||
The :func:`.inspect` function is the entry point to SQLAlchemy's
|
||||
public API for viewing the configuration and construction
|
||||
of in-memory objects. Depending on the type of object
|
||||
passed to :func:`.inspect`, the return value will either be
|
||||
a related object which provides a known interface, or in many
|
||||
cases it will return the object itself.
|
||||
|
||||
The rationale for :func:`.inspect` is twofold. One is that
|
||||
it replaces the need to be aware of a large variety of "information
|
||||
getting" functions in SQLAlchemy, such as :meth:`.Inspector.from_engine`,
|
||||
:func:`.orm.attributes.instance_state`, :func:`.orm.class_mapper`,
|
||||
and others. The other is that the return value of :func:`.inspect`
|
||||
is guaranteed to obey a documented API, thus allowing third party
|
||||
tools which build on top of SQLAlchemy configurations to be constructed
|
||||
in a forwards-compatible way.
|
||||
|
||||
.. versionadded:: 0.8 The :func:`.inspect` system is introduced
|
||||
as of version 0.8.
|
||||
|
||||
"""
|
||||
|
||||
from . import util, exc
|
||||
_registrars = util.defaultdict(list)
|
||||
|
||||
|
||||
def inspect(subject, raiseerr=True):
|
||||
"""Produce an inspection object for the given target.
|
||||
|
||||
The returned value in some cases may be the
|
||||
same object as the one given, such as if a
|
||||
:class:`.orm.Mapper` object is passed. In other
|
||||
cases, it will be an instance of the registered
|
||||
inspection type for the given object, such as
|
||||
if a :class:`.engine.Engine` is passed, an
|
||||
:class:`.engine.Inspector` object is returned.
|
||||
|
||||
:param subject: the subject to be inspected.
|
||||
:param raiseerr: When ``True``, if the given subject
|
||||
does not
|
||||
correspond to a known SQLAlchemy inspected type,
|
||||
:class:`sqlalchemy.exc.NoInspectionAvailable`
|
||||
is raised. If ``False``, ``None`` is returned.
|
||||
|
||||
"""
|
||||
type_ = type(subject)
|
||||
for cls in type_.__mro__:
|
||||
if cls in _registrars:
|
||||
reg = _registrars[cls]
|
||||
if reg is True:
|
||||
return subject
|
||||
ret = reg(subject)
|
||||
if ret is not None:
|
||||
break
|
||||
else:
|
||||
reg = ret = None
|
||||
|
||||
if raiseerr and (
|
||||
reg is None or ret is None
|
||||
):
|
||||
raise exc.NoInspectionAvailable(
|
||||
"No inspection system is "
|
||||
"available for object of type %s" %
|
||||
type_)
|
||||
return ret
|
||||
|
||||
|
||||
def _inspects(*types):
|
||||
def decorate(fn_or_cls):
|
||||
for type_ in types:
|
||||
if type_ in _registrars:
|
||||
raise AssertionError(
|
||||
"Type %s is already "
|
||||
"registered" % type_)
|
||||
_registrars[type_] = fn_or_cls
|
||||
return fn_or_cls
|
||||
return decorate
|
||||
|
||||
|
||||
def _self_inspects(*types):
|
||||
_inspects(*types)(True)
|
||||
@@ -0,0 +1,310 @@
|
||||
# sqlalchemy/interfaces.py
|
||||
# Copyright (C) 2007-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2007 Jason Kirtland jek@discorporate.us
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Deprecated core event interfaces.
|
||||
|
||||
This module is **deprecated** and is superseded by the
|
||||
event system.
|
||||
|
||||
"""
|
||||
|
||||
from . import event, util
|
||||
|
||||
|
||||
class PoolListener(object):
|
||||
"""Hooks into the lifecycle of connections in a :class:`.Pool`.
|
||||
|
||||
.. note::
|
||||
|
||||
:class:`.PoolListener` is deprecated. Please
|
||||
refer to :class:`.PoolEvents`.
|
||||
|
||||
Usage::
|
||||
|
||||
class MyListener(PoolListener):
|
||||
def connect(self, dbapi_con, con_record):
|
||||
'''perform connect operations'''
|
||||
# etc.
|
||||
|
||||
# create a new pool with a listener
|
||||
p = QueuePool(..., listeners=[MyListener()])
|
||||
|
||||
# add a listener after the fact
|
||||
p.add_listener(MyListener())
|
||||
|
||||
# usage with create_engine()
|
||||
e = create_engine("url://", listeners=[MyListener()])
|
||||
|
||||
All of the standard connection :class:`~sqlalchemy.pool.Pool` types can
|
||||
accept event listeners for key connection lifecycle events:
|
||||
creation, pool check-out and check-in. There are no events fired
|
||||
when a connection closes.
|
||||
|
||||
For any given DB-API connection, there will be one ``connect``
|
||||
event, `n` number of ``checkout`` events, and either `n` or `n - 1`
|
||||
``checkin`` events. (If a ``Connection`` is detached from its
|
||||
pool via the ``detach()`` method, it won't be checked back in.)
|
||||
|
||||
These are low-level events for low-level objects: raw Python
|
||||
DB-API connections, without the conveniences of the SQLAlchemy
|
||||
``Connection`` wrapper, ``Dialect`` services or ``ClauseElement``
|
||||
execution. If you execute SQL through the connection, explicitly
|
||||
closing all cursors and other resources is recommended.
|
||||
|
||||
Events also receive a ``_ConnectionRecord``, a long-lived internal
|
||||
``Pool`` object that basically represents a "slot" in the
|
||||
connection pool. ``_ConnectionRecord`` objects have one public
|
||||
attribute of note: ``info``, a dictionary whose contents are
|
||||
scoped to the lifetime of the DB-API connection managed by the
|
||||
record. You can use this shared storage area however you like.
|
||||
|
||||
There is no need to subclass ``PoolListener`` to handle events.
|
||||
Any class that implements one or more of these methods can be used
|
||||
as a pool listener. The ``Pool`` will inspect the methods
|
||||
provided by a listener object and add the listener to one or more
|
||||
internal event queues based on its capabilities. In terms of
|
||||
efficiency and function call overhead, you're much better off only
|
||||
providing implementations for the hooks you'll be using.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _adapt_listener(cls, self, listener):
|
||||
"""Adapt a :class:`.PoolListener` to individual
|
||||
:class:`event.Dispatch` events.
|
||||
|
||||
"""
|
||||
|
||||
listener = util.as_interface(listener, methods=('connect',
|
||||
'first_connect', 'checkout', 'checkin'))
|
||||
if hasattr(listener, 'connect'):
|
||||
event.listen(self, 'connect', listener.connect)
|
||||
if hasattr(listener, 'first_connect'):
|
||||
event.listen(self, 'first_connect', listener.first_connect)
|
||||
if hasattr(listener, 'checkout'):
|
||||
event.listen(self, 'checkout', listener.checkout)
|
||||
if hasattr(listener, 'checkin'):
|
||||
event.listen(self, 'checkin', listener.checkin)
|
||||
|
||||
def connect(self, dbapi_con, con_record):
|
||||
"""Called once for each new DB-API connection or Pool's ``creator()``.
|
||||
|
||||
dbapi_con
|
||||
A newly connected raw DB-API connection (not a SQLAlchemy
|
||||
``Connection`` wrapper).
|
||||
|
||||
con_record
|
||||
The ``_ConnectionRecord`` that persistently manages the connection
|
||||
|
||||
"""
|
||||
|
||||
def first_connect(self, dbapi_con, con_record):
|
||||
"""Called exactly once for the first DB-API connection.
|
||||
|
||||
dbapi_con
|
||||
A newly connected raw DB-API connection (not a SQLAlchemy
|
||||
``Connection`` wrapper).
|
||||
|
||||
con_record
|
||||
The ``_ConnectionRecord`` that persistently manages the connection
|
||||
|
||||
"""
|
||||
|
||||
def checkout(self, dbapi_con, con_record, con_proxy):
|
||||
"""Called when a connection is retrieved from the Pool.
|
||||
|
||||
dbapi_con
|
||||
A raw DB-API connection
|
||||
|
||||
con_record
|
||||
The ``_ConnectionRecord`` that persistently manages the connection
|
||||
|
||||
con_proxy
|
||||
The ``_ConnectionFairy`` which manages the connection for the span of
|
||||
the current checkout.
|
||||
|
||||
If you raise an ``exc.DisconnectionError``, the current
|
||||
connection will be disposed and a fresh connection retrieved.
|
||||
Processing of all checkout listeners will abort and restart
|
||||
using the new connection.
|
||||
"""
|
||||
|
||||
def checkin(self, dbapi_con, con_record):
|
||||
"""Called when a connection returns to the pool.
|
||||
|
||||
Note that the connection may be closed, and may be None if the
|
||||
connection has been invalidated. ``checkin`` will not be called
|
||||
for detached connections. (They do not return to the pool.)
|
||||
|
||||
dbapi_con
|
||||
A raw DB-API connection
|
||||
|
||||
con_record
|
||||
The ``_ConnectionRecord`` that persistently manages the connection
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class ConnectionProxy(object):
|
||||
"""Allows interception of statement execution by Connections.
|
||||
|
||||
.. note::
|
||||
|
||||
:class:`.ConnectionProxy` is deprecated. Please
|
||||
refer to :class:`.ConnectionEvents`.
|
||||
|
||||
Either or both of the ``execute()`` and ``cursor_execute()``
|
||||
may be implemented to intercept compiled statement and
|
||||
cursor level executions, e.g.::
|
||||
|
||||
class MyProxy(ConnectionProxy):
|
||||
def execute(self, conn, execute, clauseelement,
|
||||
*multiparams, **params):
|
||||
print "compiled statement:", clauseelement
|
||||
return execute(clauseelement, *multiparams, **params)
|
||||
|
||||
def cursor_execute(self, execute, cursor, statement,
|
||||
parameters, context, executemany):
|
||||
print "raw statement:", statement
|
||||
return execute(cursor, statement, parameters, context)
|
||||
|
||||
The ``execute`` argument is a function that will fulfill the default
|
||||
execution behavior for the operation. The signature illustrated
|
||||
in the example should be used.
|
||||
|
||||
The proxy is installed into an :class:`~sqlalchemy.engine.Engine` via
|
||||
the ``proxy`` argument::
|
||||
|
||||
e = create_engine('someurl://', proxy=MyProxy())
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _adapt_listener(cls, self, listener):
|
||||
|
||||
def adapt_execute(conn, clauseelement, multiparams, params):
|
||||
|
||||
def execute_wrapper(clauseelement, *multiparams, **params):
|
||||
return clauseelement, multiparams, params
|
||||
|
||||
return listener.execute(conn, execute_wrapper,
|
||||
clauseelement, *multiparams,
|
||||
**params)
|
||||
|
||||
event.listen(self, 'before_execute', adapt_execute)
|
||||
|
||||
def adapt_cursor_execute(conn, cursor, statement,
|
||||
parameters, context, executemany):
|
||||
|
||||
def execute_wrapper(
|
||||
cursor,
|
||||
statement,
|
||||
parameters,
|
||||
context,
|
||||
):
|
||||
return statement, parameters
|
||||
|
||||
return listener.cursor_execute(
|
||||
execute_wrapper,
|
||||
cursor,
|
||||
statement,
|
||||
parameters,
|
||||
context,
|
||||
executemany,
|
||||
)
|
||||
|
||||
event.listen(self, 'before_cursor_execute', adapt_cursor_execute)
|
||||
|
||||
def do_nothing_callback(*arg, **kw):
|
||||
pass
|
||||
|
||||
def adapt_listener(fn):
|
||||
|
||||
def go(conn, *arg, **kw):
|
||||
fn(conn, do_nothing_callback, *arg, **kw)
|
||||
|
||||
return util.update_wrapper(go, fn)
|
||||
|
||||
event.listen(self, 'begin', adapt_listener(listener.begin))
|
||||
event.listen(self, 'rollback',
|
||||
adapt_listener(listener.rollback))
|
||||
event.listen(self, 'commit', adapt_listener(listener.commit))
|
||||
event.listen(self, 'savepoint',
|
||||
adapt_listener(listener.savepoint))
|
||||
event.listen(self, 'rollback_savepoint',
|
||||
adapt_listener(listener.rollback_savepoint))
|
||||
event.listen(self, 'release_savepoint',
|
||||
adapt_listener(listener.release_savepoint))
|
||||
event.listen(self, 'begin_twophase',
|
||||
adapt_listener(listener.begin_twophase))
|
||||
event.listen(self, 'prepare_twophase',
|
||||
adapt_listener(listener.prepare_twophase))
|
||||
event.listen(self, 'rollback_twophase',
|
||||
adapt_listener(listener.rollback_twophase))
|
||||
event.listen(self, 'commit_twophase',
|
||||
adapt_listener(listener.commit_twophase))
|
||||
|
||||
def execute(self, conn, execute, clauseelement, *multiparams, **params):
|
||||
"""Intercept high level execute() events."""
|
||||
|
||||
return execute(clauseelement, *multiparams, **params)
|
||||
|
||||
def cursor_execute(self, execute, cursor, statement, parameters,
|
||||
context, executemany):
|
||||
"""Intercept low-level cursor execute() events."""
|
||||
|
||||
return execute(cursor, statement, parameters, context)
|
||||
|
||||
def begin(self, conn, begin):
|
||||
"""Intercept begin() events."""
|
||||
|
||||
return begin()
|
||||
|
||||
def rollback(self, conn, rollback):
|
||||
"""Intercept rollback() events."""
|
||||
|
||||
return rollback()
|
||||
|
||||
def commit(self, conn, commit):
|
||||
"""Intercept commit() events."""
|
||||
|
||||
return commit()
|
||||
|
||||
def savepoint(self, conn, savepoint, name=None):
|
||||
"""Intercept savepoint() events."""
|
||||
|
||||
return savepoint(name=name)
|
||||
|
||||
def rollback_savepoint(self, conn, rollback_savepoint, name, context):
|
||||
"""Intercept rollback_savepoint() events."""
|
||||
|
||||
return rollback_savepoint(name, context)
|
||||
|
||||
def release_savepoint(self, conn, release_savepoint, name, context):
|
||||
"""Intercept release_savepoint() events."""
|
||||
|
||||
return release_savepoint(name, context)
|
||||
|
||||
def begin_twophase(self, conn, begin_twophase, xid):
|
||||
"""Intercept begin_twophase() events."""
|
||||
|
||||
return begin_twophase(xid)
|
||||
|
||||
def prepare_twophase(self, conn, prepare_twophase, xid):
|
||||
"""Intercept prepare_twophase() events."""
|
||||
|
||||
return prepare_twophase(xid)
|
||||
|
||||
def rollback_twophase(self, conn, rollback_twophase, xid, is_prepared):
|
||||
"""Intercept rollback_twophase() events."""
|
||||
|
||||
return rollback_twophase(xid, is_prepared)
|
||||
|
||||
def commit_twophase(self, conn, commit_twophase, xid, is_prepared):
|
||||
"""Intercept commit_twophase() events."""
|
||||
|
||||
return commit_twophase(xid, is_prepared)
|
||||
@@ -0,0 +1,218 @@
|
||||
# sqlalchemy/log.py
|
||||
# Copyright (C) 2006-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Includes alterations by Vinay Sajip vinay_sajip@yahoo.co.uk
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Logging control and utilities.
|
||||
|
||||
Control of logging for SA can be performed from the regular python logging
|
||||
module. The regular dotted module namespace is used, starting at
|
||||
'sqlalchemy'. For class-level logging, the class name is appended.
|
||||
|
||||
The "echo" keyword parameter, available on SQLA :class:`.Engine`
|
||||
and :class:`.Pool` objects, corresponds to a logger specific to that
|
||||
instance only.
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
# set initial level to WARN. This so that
|
||||
# log statements don't occur in the absense of explicit
|
||||
# logging being enabled for 'sqlalchemy'.
|
||||
rootlogger = logging.getLogger('sqlalchemy')
|
||||
if rootlogger.level == logging.NOTSET:
|
||||
rootlogger.setLevel(logging.WARN)
|
||||
|
||||
|
||||
def _add_default_handler(logger):
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
handler.setFormatter(logging.Formatter(
|
||||
'%(asctime)s %(levelname)s %(name)s %(message)s'))
|
||||
logger.addHandler(handler)
|
||||
|
||||
|
||||
_logged_classes = set()
|
||||
|
||||
|
||||
def class_logger(cls, enable=False):
|
||||
logger = logging.getLogger(cls.__module__ + "." + cls.__name__)
|
||||
if enable == 'debug':
|
||||
logger.setLevel(logging.DEBUG)
|
||||
elif enable == 'info':
|
||||
logger.setLevel(logging.INFO)
|
||||
cls._should_log_debug = lambda self: logger.isEnabledFor(logging.DEBUG)
|
||||
cls._should_log_info = lambda self: logger.isEnabledFor(logging.INFO)
|
||||
cls.logger = logger
|
||||
_logged_classes.add(cls)
|
||||
|
||||
|
||||
class Identified(object):
|
||||
logging_name = None
|
||||
|
||||
def _should_log_debug(self):
|
||||
return self.logger.isEnabledFor(logging.DEBUG)
|
||||
|
||||
def _should_log_info(self):
|
||||
return self.logger.isEnabledFor(logging.INFO)
|
||||
|
||||
|
||||
class InstanceLogger(object):
|
||||
"""A logger adapter (wrapper) for :class:`.Identified` subclasses.
|
||||
|
||||
This allows multiple instances (e.g. Engine or Pool instances)
|
||||
to share a logger, but have its verbosity controlled on a
|
||||
per-instance basis.
|
||||
|
||||
The basic functionality is to return a logging level
|
||||
which is based on an instance's echo setting.
|
||||
|
||||
Default implementation is:
|
||||
|
||||
'debug' -> logging.DEBUG
|
||||
True -> logging.INFO
|
||||
False -> Effective level of underlying logger
|
||||
(logging.WARNING by default)
|
||||
None -> same as False
|
||||
"""
|
||||
|
||||
# Map echo settings to logger levels
|
||||
_echo_map = {
|
||||
None: logging.NOTSET,
|
||||
False: logging.NOTSET,
|
||||
True: logging.INFO,
|
||||
'debug': logging.DEBUG,
|
||||
}
|
||||
|
||||
def __init__(self, echo, name):
|
||||
self.echo = echo
|
||||
self.logger = logging.getLogger(name)
|
||||
|
||||
# if echo flag is enabled and no handlers,
|
||||
# add a handler to the list
|
||||
if self._echo_map[echo] <= logging.INFO \
|
||||
and not self.logger.handlers:
|
||||
_add_default_handler(self.logger)
|
||||
|
||||
#
|
||||
# Boilerplate convenience methods
|
||||
#
|
||||
def debug(self, msg, *args, **kwargs):
|
||||
"""Delegate a debug call to the underlying logger."""
|
||||
|
||||
self.log(logging.DEBUG, msg, *args, **kwargs)
|
||||
|
||||
def info(self, msg, *args, **kwargs):
|
||||
"""Delegate an info call to the underlying logger."""
|
||||
|
||||
self.log(logging.INFO, msg, *args, **kwargs)
|
||||
|
||||
def warning(self, msg, *args, **kwargs):
|
||||
"""Delegate a warning call to the underlying logger."""
|
||||
|
||||
self.log(logging.WARNING, msg, *args, **kwargs)
|
||||
|
||||
warn = warning
|
||||
|
||||
def error(self, msg, *args, **kwargs):
|
||||
"""
|
||||
Delegate an error call to the underlying logger.
|
||||
"""
|
||||
self.log(logging.ERROR, msg, *args, **kwargs)
|
||||
|
||||
def exception(self, msg, *args, **kwargs):
|
||||
"""Delegate an exception call to the underlying logger."""
|
||||
|
||||
kwargs["exc_info"] = 1
|
||||
self.log(logging.ERROR, msg, *args, **kwargs)
|
||||
|
||||
def critical(self, msg, *args, **kwargs):
|
||||
"""Delegate a critical call to the underlying logger."""
|
||||
|
||||
self.log(logging.CRITICAL, msg, *args, **kwargs)
|
||||
|
||||
def log(self, level, msg, *args, **kwargs):
|
||||
"""Delegate a log call to the underlying logger.
|
||||
|
||||
The level here is determined by the echo
|
||||
flag as well as that of the underlying logger, and
|
||||
logger._log() is called directly.
|
||||
|
||||
"""
|
||||
|
||||
# inline the logic from isEnabledFor(),
|
||||
# getEffectiveLevel(), to avoid overhead.
|
||||
|
||||
if self.logger.manager.disable >= level:
|
||||
return
|
||||
|
||||
selected_level = self._echo_map[self.echo]
|
||||
if selected_level == logging.NOTSET:
|
||||
selected_level = self.logger.getEffectiveLevel()
|
||||
|
||||
if level >= selected_level:
|
||||
self.logger._log(level, msg, args, **kwargs)
|
||||
|
||||
def isEnabledFor(self, level):
|
||||
"""Is this logger enabled for level 'level'?"""
|
||||
|
||||
if self.logger.manager.disable >= level:
|
||||
return False
|
||||
return level >= self.getEffectiveLevel()
|
||||
|
||||
def getEffectiveLevel(self):
|
||||
"""What's the effective level for this logger?"""
|
||||
|
||||
level = self._echo_map[self.echo]
|
||||
if level == logging.NOTSET:
|
||||
level = self.logger.getEffectiveLevel()
|
||||
return level
|
||||
|
||||
|
||||
def instance_logger(instance, echoflag=None):
|
||||
"""create a logger for an instance that implements :class:`.Identified`."""
|
||||
|
||||
if instance.logging_name:
|
||||
name = "%s.%s.%s" % (instance.__class__.__module__,
|
||||
instance.__class__.__name__, instance.logging_name)
|
||||
else:
|
||||
name = "%s.%s" % (instance.__class__.__module__,
|
||||
instance.__class__.__name__)
|
||||
|
||||
instance._echo = echoflag
|
||||
|
||||
if echoflag in (False, None):
|
||||
# if no echo setting or False, return a Logger directly,
|
||||
# avoiding overhead of filtering
|
||||
logger = logging.getLogger(name)
|
||||
else:
|
||||
# if a specified echo flag, return an EchoLogger,
|
||||
# which checks the flag, overrides normal log
|
||||
# levels by calling logger._log()
|
||||
logger = InstanceLogger(echoflag, name)
|
||||
|
||||
instance.logger = logger
|
||||
|
||||
|
||||
class echo_property(object):
|
||||
__doc__ = """\
|
||||
When ``True``, enable log output for this element.
|
||||
|
||||
This has the effect of setting the Python logging level for the namespace
|
||||
of this element's class and object reference. A value of boolean ``True``
|
||||
indicates that the loglevel ``logging.INFO`` will be set for the logger,
|
||||
whereas the string value ``debug`` will set the loglevel to
|
||||
``logging.DEBUG``.
|
||||
"""
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
else:
|
||||
return instance._echo
|
||||
|
||||
def __set__(self, instance, value):
|
||||
instance_logger(instance, echoflag=value)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,588 @@
|
||||
# orm/deprecated_interfaces.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from .. import event, util
|
||||
from .interfaces import EXT_CONTINUE
|
||||
|
||||
|
||||
class MapperExtension(object):
|
||||
"""Base implementation for :class:`.Mapper` event hooks.
|
||||
|
||||
.. note::
|
||||
|
||||
:class:`.MapperExtension` is deprecated. Please
|
||||
refer to :func:`.event.listen` as well as
|
||||
:class:`.MapperEvents`.
|
||||
|
||||
New extension classes subclass :class:`.MapperExtension` and are specified
|
||||
using the ``extension`` mapper() argument, which is a single
|
||||
:class:`.MapperExtension` or a list of such::
|
||||
|
||||
from sqlalchemy.orm.interfaces import MapperExtension
|
||||
|
||||
class MyExtension(MapperExtension):
|
||||
def before_insert(self, mapper, connection, instance):
|
||||
print "instance %s before insert !" % instance
|
||||
|
||||
m = mapper(User, users_table, extension=MyExtension())
|
||||
|
||||
A single mapper can maintain a chain of ``MapperExtension``
|
||||
objects. When a particular mapping event occurs, the
|
||||
corresponding method on each ``MapperExtension`` is invoked
|
||||
serially, and each method has the ability to halt the chain
|
||||
from proceeding further::
|
||||
|
||||
m = mapper(User, users_table, extension=[ext1, ext2, ext3])
|
||||
|
||||
Each ``MapperExtension`` method returns the symbol
|
||||
EXT_CONTINUE by default. This symbol generally means "move
|
||||
to the next ``MapperExtension`` for processing". For methods
|
||||
that return objects like translated rows or new object
|
||||
instances, EXT_CONTINUE means the result of the method
|
||||
should be ignored. In some cases it's required for a
|
||||
default mapper activity to be performed, such as adding a
|
||||
new instance to a result list.
|
||||
|
||||
The symbol EXT_STOP has significance within a chain
|
||||
of ``MapperExtension`` objects that the chain will be stopped
|
||||
when this symbol is returned. Like EXT_CONTINUE, it also
|
||||
has additional significance in some cases that a default
|
||||
mapper activity will not be performed.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _adapt_instrument_class(cls, self, listener):
|
||||
cls._adapt_listener_methods(self, listener, ('instrument_class',))
|
||||
|
||||
@classmethod
|
||||
def _adapt_listener(cls, self, listener):
|
||||
cls._adapt_listener_methods(
|
||||
self, listener,
|
||||
(
|
||||
'init_instance',
|
||||
'init_failed',
|
||||
'translate_row',
|
||||
'create_instance',
|
||||
'append_result',
|
||||
'populate_instance',
|
||||
'reconstruct_instance',
|
||||
'before_insert',
|
||||
'after_insert',
|
||||
'before_update',
|
||||
'after_update',
|
||||
'before_delete',
|
||||
'after_delete'
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def _adapt_listener_methods(cls, self, listener, methods):
|
||||
|
||||
for meth in methods:
|
||||
me_meth = getattr(MapperExtension, meth)
|
||||
ls_meth = getattr(listener, meth)
|
||||
|
||||
if not util.methods_equivalent(me_meth, ls_meth):
|
||||
if meth == 'reconstruct_instance':
|
||||
def go(ls_meth):
|
||||
def reconstruct(instance, ctx):
|
||||
ls_meth(self, instance)
|
||||
return reconstruct
|
||||
event.listen(self.class_manager, 'load',
|
||||
go(ls_meth), raw=False, propagate=True)
|
||||
elif meth == 'init_instance':
|
||||
def go(ls_meth):
|
||||
def init_instance(instance, args, kwargs):
|
||||
ls_meth(self, self.class_,
|
||||
self.class_manager.original_init,
|
||||
instance, args, kwargs)
|
||||
return init_instance
|
||||
event.listen(self.class_manager, 'init',
|
||||
go(ls_meth), raw=False, propagate=True)
|
||||
elif meth == 'init_failed':
|
||||
def go(ls_meth):
|
||||
def init_failed(instance, args, kwargs):
|
||||
util.warn_exception(ls_meth, self, self.class_,
|
||||
self.class_manager.original_init,
|
||||
instance, args, kwargs)
|
||||
|
||||
return init_failed
|
||||
event.listen(self.class_manager, 'init_failure',
|
||||
go(ls_meth), raw=False, propagate=True)
|
||||
else:
|
||||
event.listen(self, "%s" % meth, ls_meth,
|
||||
raw=False, retval=True, propagate=True)
|
||||
|
||||
def instrument_class(self, mapper, class_):
|
||||
"""Receive a class when the mapper is first constructed, and has
|
||||
applied instrumentation to the mapped class.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
return EXT_CONTINUE
|
||||
|
||||
def init_instance(self, mapper, class_, oldinit, instance, args, kwargs):
|
||||
"""Receive an instance when it's constructor is called.
|
||||
|
||||
This method is only called during a userland construction of
|
||||
an object. It is not called when an object is loaded from the
|
||||
database.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
return EXT_CONTINUE
|
||||
|
||||
def init_failed(self, mapper, class_, oldinit, instance, args, kwargs):
|
||||
"""Receive an instance when it's constructor has been called,
|
||||
and raised an exception.
|
||||
|
||||
This method is only called during a userland construction of
|
||||
an object. It is not called when an object is loaded from the
|
||||
database.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
return EXT_CONTINUE
|
||||
|
||||
def translate_row(self, mapper, context, row):
|
||||
"""Perform pre-processing on the given result row and return a
|
||||
new row instance.
|
||||
|
||||
This is called when the mapper first receives a row, before
|
||||
the object identity or the instance itself has been derived
|
||||
from that row. The given row may or may not be a
|
||||
``RowProxy`` object - it will always be a dictionary-like
|
||||
object which contains mapped columns as keys. The
|
||||
returned object should also be a dictionary-like object
|
||||
which recognizes mapped columns as keys.
|
||||
|
||||
If the ultimate return value is EXT_CONTINUE, the row
|
||||
is not translated.
|
||||
|
||||
"""
|
||||
return EXT_CONTINUE
|
||||
|
||||
def create_instance(self, mapper, selectcontext, row, class_):
|
||||
"""Receive a row when a new object instance is about to be
|
||||
created from that row.
|
||||
|
||||
The method can choose to create the instance itself, or it can return
|
||||
EXT_CONTINUE to indicate normal object creation should take place.
|
||||
|
||||
mapper
|
||||
The mapper doing the operation
|
||||
|
||||
selectcontext
|
||||
The QueryContext generated from the Query.
|
||||
|
||||
row
|
||||
The result row from the database
|
||||
|
||||
class\_
|
||||
The class we are mapping.
|
||||
|
||||
return value
|
||||
A new object instance, or EXT_CONTINUE
|
||||
|
||||
"""
|
||||
return EXT_CONTINUE
|
||||
|
||||
def append_result(self, mapper, selectcontext, row, instance,
|
||||
result, **flags):
|
||||
"""Receive an object instance before that instance is appended
|
||||
to a result list.
|
||||
|
||||
If this method returns EXT_CONTINUE, result appending will proceed
|
||||
normally. if this method returns any other value or None,
|
||||
result appending will not proceed for this instance, giving
|
||||
this extension an opportunity to do the appending itself, if
|
||||
desired.
|
||||
|
||||
mapper
|
||||
The mapper doing the operation.
|
||||
|
||||
selectcontext
|
||||
The QueryContext generated from the Query.
|
||||
|
||||
row
|
||||
The result row from the database.
|
||||
|
||||
instance
|
||||
The object instance to be appended to the result.
|
||||
|
||||
result
|
||||
List to which results are being appended.
|
||||
|
||||
\**flags
|
||||
extra information about the row, same as criterion in
|
||||
``create_row_processor()`` method of
|
||||
:class:`~sqlalchemy.orm.interfaces.MapperProperty`
|
||||
"""
|
||||
|
||||
return EXT_CONTINUE
|
||||
|
||||
def populate_instance(self, mapper, selectcontext, row,
|
||||
instance, **flags):
|
||||
"""Receive an instance before that instance has
|
||||
its attributes populated.
|
||||
|
||||
This usually corresponds to a newly loaded instance but may
|
||||
also correspond to an already-loaded instance which has
|
||||
unloaded attributes to be populated. The method may be called
|
||||
many times for a single instance, as multiple result rows are
|
||||
used to populate eagerly loaded collections.
|
||||
|
||||
If this method returns EXT_CONTINUE, instance population will
|
||||
proceed normally. If any other value or None is returned,
|
||||
instance population will not proceed, giving this extension an
|
||||
opportunity to populate the instance itself, if desired.
|
||||
|
||||
.. deprecated:: 0.5
|
||||
Most usages of this hook are obsolete. For a
|
||||
generic "object has been newly created from a row" hook, use
|
||||
``reconstruct_instance()``, or the ``@orm.reconstructor``
|
||||
decorator.
|
||||
|
||||
"""
|
||||
return EXT_CONTINUE
|
||||
|
||||
def reconstruct_instance(self, mapper, instance):
|
||||
"""Receive an object instance after it has been created via
|
||||
``__new__``, and after initial attribute population has
|
||||
occurred.
|
||||
|
||||
This typically occurs when the instance is created based on
|
||||
incoming result rows, and is only called once for that
|
||||
instance's lifetime.
|
||||
|
||||
Note that during a result-row load, this method is called upon
|
||||
the first row received for this instance. Note that some
|
||||
attributes and collections may or may not be loaded or even
|
||||
initialized, depending on what's present in the result rows.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
return EXT_CONTINUE
|
||||
|
||||
def before_insert(self, mapper, connection, instance):
|
||||
"""Receive an object instance before that instance is inserted
|
||||
into its table.
|
||||
|
||||
This is a good place to set up primary key values and such
|
||||
that aren't handled otherwise.
|
||||
|
||||
Column-based attributes can be modified within this method
|
||||
which will result in the new value being inserted. However
|
||||
*no* changes to the overall flush plan can be made, and
|
||||
manipulation of the ``Session`` will not have the desired effect.
|
||||
To manipulate the ``Session`` within an extension, use
|
||||
``SessionExtension``.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
|
||||
return EXT_CONTINUE
|
||||
|
||||
def after_insert(self, mapper, connection, instance):
|
||||
"""Receive an object instance after that instance is inserted.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
|
||||
return EXT_CONTINUE
|
||||
|
||||
def before_update(self, mapper, connection, instance):
|
||||
"""Receive an object instance before that instance is updated.
|
||||
|
||||
Note that this method is called for all instances that are marked as
|
||||
"dirty", even those which have no net changes to their column-based
|
||||
attributes. An object is marked as dirty when any of its column-based
|
||||
attributes have a "set attribute" operation called or when any of its
|
||||
collections are modified. If, at update time, no column-based
|
||||
attributes have any net changes, no UPDATE statement will be issued.
|
||||
This means that an instance being sent to before_update is *not* a
|
||||
guarantee that an UPDATE statement will be issued (although you can
|
||||
affect the outcome here).
|
||||
|
||||
To detect if the column-based attributes on the object have net
|
||||
changes, and will therefore generate an UPDATE statement, use
|
||||
``object_session(instance).is_modified(instance,
|
||||
include_collections=False)``.
|
||||
|
||||
Column-based attributes can be modified within this method
|
||||
which will result in the new value being updated. However
|
||||
*no* changes to the overall flush plan can be made, and
|
||||
manipulation of the ``Session`` will not have the desired effect.
|
||||
To manipulate the ``Session`` within an extension, use
|
||||
``SessionExtension``.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
|
||||
return EXT_CONTINUE
|
||||
|
||||
def after_update(self, mapper, connection, instance):
|
||||
"""Receive an object instance after that instance is updated.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
|
||||
return EXT_CONTINUE
|
||||
|
||||
def before_delete(self, mapper, connection, instance):
|
||||
"""Receive an object instance before that instance is deleted.
|
||||
|
||||
Note that *no* changes to the overall flush plan can be made
|
||||
here; and manipulation of the ``Session`` will not have the
|
||||
desired effect. To manipulate the ``Session`` within an
|
||||
extension, use ``SessionExtension``.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
|
||||
return EXT_CONTINUE
|
||||
|
||||
def after_delete(self, mapper, connection, instance):
|
||||
"""Receive an object instance after that instance is deleted.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
|
||||
return EXT_CONTINUE
|
||||
|
||||
|
||||
class SessionExtension(object):
|
||||
|
||||
"""Base implementation for :class:`.Session` event hooks.
|
||||
|
||||
.. note::
|
||||
|
||||
:class:`.SessionExtension` is deprecated. Please
|
||||
refer to :func:`.event.listen` as well as
|
||||
:class:`.SessionEvents`.
|
||||
|
||||
Subclasses may be installed into a :class:`.Session` (or
|
||||
:class:`.sessionmaker`) using the ``extension`` keyword
|
||||
argument::
|
||||
|
||||
from sqlalchemy.orm.interfaces import SessionExtension
|
||||
|
||||
class MySessionExtension(SessionExtension):
|
||||
def before_commit(self, session):
|
||||
print "before commit!"
|
||||
|
||||
Session = sessionmaker(extension=MySessionExtension())
|
||||
|
||||
The same :class:`.SessionExtension` instance can be used
|
||||
with any number of sessions.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _adapt_listener(cls, self, listener):
|
||||
for meth in [
|
||||
'before_commit',
|
||||
'after_commit',
|
||||
'after_rollback',
|
||||
'before_flush',
|
||||
'after_flush',
|
||||
'after_flush_postexec',
|
||||
'after_begin',
|
||||
'after_attach',
|
||||
'after_bulk_update',
|
||||
'after_bulk_delete',
|
||||
]:
|
||||
me_meth = getattr(SessionExtension, meth)
|
||||
ls_meth = getattr(listener, meth)
|
||||
|
||||
if not util.methods_equivalent(me_meth, ls_meth):
|
||||
event.listen(self, meth, getattr(listener, meth))
|
||||
|
||||
def before_commit(self, session):
|
||||
"""Execute right before commit is called.
|
||||
|
||||
Note that this may not be per-flush if a longer running
|
||||
transaction is ongoing."""
|
||||
|
||||
def after_commit(self, session):
|
||||
"""Execute after a commit has occurred.
|
||||
|
||||
Note that this may not be per-flush if a longer running
|
||||
transaction is ongoing."""
|
||||
|
||||
def after_rollback(self, session):
|
||||
"""Execute after a rollback has occurred.
|
||||
|
||||
Note that this may not be per-flush if a longer running
|
||||
transaction is ongoing."""
|
||||
|
||||
def before_flush(self, session, flush_context, instances):
|
||||
"""Execute before flush process has started.
|
||||
|
||||
`instances` is an optional list of objects which were passed to
|
||||
the ``flush()`` method. """
|
||||
|
||||
def after_flush(self, session, flush_context):
|
||||
"""Execute after flush has completed, but before commit has been
|
||||
called.
|
||||
|
||||
Note that the session's state is still in pre-flush, i.e. 'new',
|
||||
'dirty', and 'deleted' lists still show pre-flush state as well
|
||||
as the history settings on instance attributes."""
|
||||
|
||||
def after_flush_postexec(self, session, flush_context):
|
||||
"""Execute after flush has completed, and after the post-exec
|
||||
state occurs.
|
||||
|
||||
This will be when the 'new', 'dirty', and 'deleted' lists are in
|
||||
their final state. An actual commit() may or may not have
|
||||
occurred, depending on whether or not the flush started its own
|
||||
transaction or participated in a larger transaction. """
|
||||
|
||||
def after_begin(self, session, transaction, connection):
|
||||
"""Execute after a transaction is begun on a connection
|
||||
|
||||
`transaction` is the SessionTransaction. This method is called
|
||||
after an engine level transaction is begun on a connection. """
|
||||
|
||||
def after_attach(self, session, instance):
|
||||
"""Execute after an instance is attached to a session.
|
||||
|
||||
This is called after an add, delete or merge. """
|
||||
|
||||
def after_bulk_update(self, session, query, query_context, result):
|
||||
"""Execute after a bulk update operation to the session.
|
||||
|
||||
This is called after a session.query(...).update()
|
||||
|
||||
`query` is the query object that this update operation was
|
||||
called on. `query_context` was the query context object.
|
||||
`result` is the result object returned from the bulk operation.
|
||||
"""
|
||||
|
||||
def after_bulk_delete(self, session, query, query_context, result):
|
||||
"""Execute after a bulk delete operation to the session.
|
||||
|
||||
This is called after a session.query(...).delete()
|
||||
|
||||
`query` is the query object that this delete operation was
|
||||
called on. `query_context` was the query context object.
|
||||
`result` is the result object returned from the bulk operation.
|
||||
"""
|
||||
|
||||
|
||||
class AttributeExtension(object):
|
||||
"""Base implementation for :class:`.AttributeImpl` event hooks, events
|
||||
that fire upon attribute mutations in user code.
|
||||
|
||||
.. note::
|
||||
|
||||
:class:`.AttributeExtension` is deprecated. Please
|
||||
refer to :func:`.event.listen` as well as
|
||||
:class:`.AttributeEvents`.
|
||||
|
||||
:class:`.AttributeExtension` is used to listen for set,
|
||||
remove, and append events on individual mapped attributes.
|
||||
It is established on an individual mapped attribute using
|
||||
the `extension` argument, available on
|
||||
:func:`.column_property`, :func:`.relationship`, and
|
||||
others::
|
||||
|
||||
from sqlalchemy.orm.interfaces import AttributeExtension
|
||||
from sqlalchemy.orm import mapper, relationship, column_property
|
||||
|
||||
class MyAttrExt(AttributeExtension):
|
||||
def append(self, state, value, initiator):
|
||||
print "append event !"
|
||||
return value
|
||||
|
||||
def set(self, state, value, oldvalue, initiator):
|
||||
print "set event !"
|
||||
return value
|
||||
|
||||
mapper(SomeClass, sometable, properties={
|
||||
'foo':column_property(sometable.c.foo, extension=MyAttrExt()),
|
||||
'bar':relationship(Bar, extension=MyAttrExt())
|
||||
})
|
||||
|
||||
Note that the :class:`.AttributeExtension` methods
|
||||
:meth:`~.AttributeExtension.append` and
|
||||
:meth:`~.AttributeExtension.set` need to return the
|
||||
``value`` parameter. The returned value is used as the
|
||||
effective value, and allows the extension to change what is
|
||||
ultimately persisted.
|
||||
|
||||
AttributeExtension is assembled within the descriptors associated
|
||||
with a mapped class.
|
||||
|
||||
"""
|
||||
|
||||
active_history = True
|
||||
"""indicates that the set() method would like to receive the 'old' value,
|
||||
even if it means firing lazy callables.
|
||||
|
||||
Note that ``active_history`` can also be set directly via
|
||||
:func:`.column_property` and :func:`.relationship`.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _adapt_listener(cls, self, listener):
|
||||
event.listen(self, 'append', listener.append,
|
||||
active_history=listener.active_history,
|
||||
raw=True, retval=True)
|
||||
event.listen(self, 'remove', listener.remove,
|
||||
active_history=listener.active_history,
|
||||
raw=True, retval=True)
|
||||
event.listen(self, 'set', listener.set,
|
||||
active_history=listener.active_history,
|
||||
raw=True, retval=True)
|
||||
|
||||
def append(self, state, value, initiator):
|
||||
"""Receive a collection append event.
|
||||
|
||||
The returned value will be used as the actual value to be
|
||||
appended.
|
||||
|
||||
"""
|
||||
return value
|
||||
|
||||
def remove(self, state, value, initiator):
|
||||
"""Receive a remove event.
|
||||
|
||||
No return value is defined.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def set(self, state, value, oldvalue, initiator):
|
||||
"""Receive a set event.
|
||||
|
||||
The returned value will be used as the actual value to be
|
||||
set.
|
||||
|
||||
"""
|
||||
return value
|
||||
@@ -0,0 +1,478 @@
|
||||
# orm/descriptor_props.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Descriptor properties are more "auxiliary" properties
|
||||
that exist as configurational elements, but don't participate
|
||||
as actively in the load/persist ORM loop.
|
||||
|
||||
"""
|
||||
|
||||
from .interfaces import MapperProperty, PropComparator
|
||||
from .util import _none_set
|
||||
from . import attributes, strategies
|
||||
from .. import util, sql, exc as sa_exc, event, schema
|
||||
from ..sql import expression
|
||||
properties = util.importlater('sqlalchemy.orm', 'properties')
|
||||
|
||||
|
||||
class DescriptorProperty(MapperProperty):
|
||||
""":class:`.MapperProperty` which proxies access to a
|
||||
user-defined descriptor."""
|
||||
|
||||
doc = None
|
||||
|
||||
def instrument_class(self, mapper):
|
||||
prop = self
|
||||
|
||||
class _ProxyImpl(object):
|
||||
accepts_scalar_loader = False
|
||||
expire_missing = True
|
||||
collection = False
|
||||
|
||||
def __init__(self, key):
|
||||
self.key = key
|
||||
|
||||
if hasattr(prop, 'get_history'):
|
||||
def get_history(self, state, dict_,
|
||||
passive=attributes.PASSIVE_OFF):
|
||||
return prop.get_history(state, dict_, passive)
|
||||
|
||||
if self.descriptor is None:
|
||||
desc = getattr(mapper.class_, self.key, None)
|
||||
if mapper._is_userland_descriptor(desc):
|
||||
self.descriptor = desc
|
||||
|
||||
if self.descriptor is None:
|
||||
def fset(obj, value):
|
||||
setattr(obj, self.name, value)
|
||||
|
||||
def fdel(obj):
|
||||
delattr(obj, self.name)
|
||||
|
||||
def fget(obj):
|
||||
return getattr(obj, self.name)
|
||||
|
||||
self.descriptor = property(
|
||||
fget=fget,
|
||||
fset=fset,
|
||||
fdel=fdel,
|
||||
)
|
||||
|
||||
proxy_attr = attributes.\
|
||||
create_proxied_attribute(self.descriptor)\
|
||||
(
|
||||
self.parent.class_,
|
||||
self.key,
|
||||
self.descriptor,
|
||||
lambda: self._comparator_factory(mapper),
|
||||
doc=self.doc,
|
||||
original_property=self
|
||||
)
|
||||
proxy_attr.impl = _ProxyImpl(self.key)
|
||||
mapper.class_manager.instrument_attribute(self.key, proxy_attr)
|
||||
|
||||
|
||||
class CompositeProperty(DescriptorProperty):
|
||||
"""Defines a "composite" mapped attribute, representing a collection
|
||||
of columns as one attribute.
|
||||
|
||||
:class:`.CompositeProperty` is constructed using the :func:`.composite`
|
||||
function.
|
||||
|
||||
See also:
|
||||
|
||||
:ref:`mapper_composite`
|
||||
|
||||
"""
|
||||
def __init__(self, class_, *attrs, **kwargs):
|
||||
self.attrs = attrs
|
||||
self.composite_class = class_
|
||||
self.active_history = kwargs.get('active_history', False)
|
||||
self.deferred = kwargs.get('deferred', False)
|
||||
self.group = kwargs.get('group', None)
|
||||
self.comparator_factory = kwargs.pop('comparator_factory',
|
||||
self.__class__.Comparator)
|
||||
if 'info' in kwargs:
|
||||
self.info = kwargs.pop('info')
|
||||
|
||||
util.set_creation_order(self)
|
||||
self._create_descriptor()
|
||||
|
||||
def instrument_class(self, mapper):
|
||||
super(CompositeProperty, self).instrument_class(mapper)
|
||||
self._setup_event_handlers()
|
||||
|
||||
def do_init(self):
|
||||
"""Initialization which occurs after the :class:`.CompositeProperty`
|
||||
has been associated with its parent mapper.
|
||||
|
||||
"""
|
||||
self._init_props()
|
||||
self._setup_arguments_on_columns()
|
||||
|
||||
def _create_descriptor(self):
|
||||
"""Create the Python descriptor that will serve as
|
||||
the access point on instances of the mapped class.
|
||||
|
||||
"""
|
||||
|
||||
def fget(instance):
|
||||
dict_ = attributes.instance_dict(instance)
|
||||
state = attributes.instance_state(instance)
|
||||
|
||||
if self.key not in dict_:
|
||||
# key not present. Iterate through related
|
||||
# attributes, retrieve their values. This
|
||||
# ensures they all load.
|
||||
values = [
|
||||
getattr(instance, key)
|
||||
for key in self._attribute_keys
|
||||
]
|
||||
|
||||
# current expected behavior here is that the composite is
|
||||
# created on access if the object is persistent or if
|
||||
# col attributes have non-None. This would be better
|
||||
# if the composite were created unconditionally,
|
||||
# but that would be a behavioral change.
|
||||
if self.key not in dict_ and (
|
||||
state.key is not None or
|
||||
not _none_set.issuperset(values)
|
||||
):
|
||||
dict_[self.key] = self.composite_class(*values)
|
||||
state.manager.dispatch.refresh(state, None, [self.key])
|
||||
|
||||
return dict_.get(self.key, None)
|
||||
|
||||
def fset(instance, value):
|
||||
dict_ = attributes.instance_dict(instance)
|
||||
state = attributes.instance_state(instance)
|
||||
attr = state.manager[self.key]
|
||||
previous = dict_.get(self.key, attributes.NO_VALUE)
|
||||
for fn in attr.dispatch.set:
|
||||
value = fn(state, value, previous, attr.impl)
|
||||
dict_[self.key] = value
|
||||
if value is None:
|
||||
for key in self._attribute_keys:
|
||||
setattr(instance, key, None)
|
||||
else:
|
||||
for key, value in zip(
|
||||
self._attribute_keys,
|
||||
value.__composite_values__()):
|
||||
setattr(instance, key, value)
|
||||
|
||||
def fdel(instance):
|
||||
state = attributes.instance_state(instance)
|
||||
dict_ = attributes.instance_dict(instance)
|
||||
previous = dict_.pop(self.key, attributes.NO_VALUE)
|
||||
attr = state.manager[self.key]
|
||||
attr.dispatch.remove(state, previous, attr.impl)
|
||||
for key in self._attribute_keys:
|
||||
setattr(instance, key, None)
|
||||
|
||||
self.descriptor = property(fget, fset, fdel)
|
||||
|
||||
@util.memoized_property
|
||||
def _comparable_elements(self):
|
||||
return [
|
||||
getattr(self.parent.class_, prop.key)
|
||||
for prop in self.props
|
||||
]
|
||||
|
||||
def _init_props(self):
|
||||
self.props = props = []
|
||||
for attr in self.attrs:
|
||||
if isinstance(attr, basestring):
|
||||
prop = self.parent.get_property(attr)
|
||||
elif isinstance(attr, schema.Column):
|
||||
prop = self.parent._columntoproperty[attr]
|
||||
elif isinstance(attr, attributes.InstrumentedAttribute):
|
||||
prop = attr.property
|
||||
props.append(prop)
|
||||
|
||||
@property
|
||||
def columns(self):
|
||||
return [a for a in self.attrs if isinstance(a, schema.Column)]
|
||||
|
||||
def _setup_arguments_on_columns(self):
|
||||
"""Propagate configuration arguments made on this composite
|
||||
to the target columns, for those that apply.
|
||||
|
||||
"""
|
||||
for prop in self.props:
|
||||
prop.active_history = self.active_history
|
||||
if self.deferred:
|
||||
prop.deferred = self.deferred
|
||||
prop.strategy_class = strategies.DeferredColumnLoader
|
||||
prop.group = self.group
|
||||
|
||||
def _setup_event_handlers(self):
|
||||
"""Establish events that populate/expire the composite attribute."""
|
||||
|
||||
def load_handler(state, *args):
|
||||
dict_ = state.dict
|
||||
|
||||
if self.key in dict_:
|
||||
return
|
||||
|
||||
# if column elements aren't loaded, skip.
|
||||
# __get__() will initiate a load for those
|
||||
# columns
|
||||
for k in self._attribute_keys:
|
||||
if k not in dict_:
|
||||
return
|
||||
|
||||
#assert self.key not in dict_
|
||||
dict_[self.key] = self.composite_class(
|
||||
*[state.dict[key] for key in
|
||||
self._attribute_keys]
|
||||
)
|
||||
|
||||
def expire_handler(state, keys):
|
||||
if keys is None or set(self._attribute_keys).intersection(keys):
|
||||
state.dict.pop(self.key, None)
|
||||
|
||||
def insert_update_handler(mapper, connection, state):
|
||||
"""After an insert or update, some columns may be expired due
|
||||
to server side defaults, or re-populated due to client side
|
||||
defaults. Pop out the composite value here so that it
|
||||
recreates.
|
||||
|
||||
"""
|
||||
|
||||
state.dict.pop(self.key, None)
|
||||
|
||||
event.listen(self.parent, 'after_insert',
|
||||
insert_update_handler, raw=True)
|
||||
event.listen(self.parent, 'after_update',
|
||||
insert_update_handler, raw=True)
|
||||
event.listen(self.parent, 'load',
|
||||
load_handler, raw=True, propagate=True)
|
||||
event.listen(self.parent, 'refresh',
|
||||
load_handler, raw=True, propagate=True)
|
||||
event.listen(self.parent, 'expire',
|
||||
expire_handler, raw=True, propagate=True)
|
||||
|
||||
# TODO: need a deserialize hook here
|
||||
|
||||
@util.memoized_property
|
||||
def _attribute_keys(self):
|
||||
return [
|
||||
prop.key for prop in self.props
|
||||
]
|
||||
|
||||
def get_history(self, state, dict_, passive=attributes.PASSIVE_OFF):
|
||||
"""Provided for userland code that uses attributes.get_history()."""
|
||||
|
||||
added = []
|
||||
deleted = []
|
||||
|
||||
has_history = False
|
||||
for prop in self.props:
|
||||
key = prop.key
|
||||
hist = state.manager[key].impl.get_history(state, dict_)
|
||||
if hist.has_changes():
|
||||
has_history = True
|
||||
|
||||
non_deleted = hist.non_deleted()
|
||||
if non_deleted:
|
||||
added.extend(non_deleted)
|
||||
else:
|
||||
added.append(None)
|
||||
if hist.deleted:
|
||||
deleted.extend(hist.deleted)
|
||||
else:
|
||||
deleted.append(None)
|
||||
|
||||
if has_history:
|
||||
return attributes.History(
|
||||
[self.composite_class(*added)],
|
||||
(),
|
||||
[self.composite_class(*deleted)]
|
||||
)
|
||||
else:
|
||||
return attributes.History(
|
||||
(), [self.composite_class(*added)], ()
|
||||
)
|
||||
|
||||
def _comparator_factory(self, mapper):
|
||||
return self.comparator_factory(self, mapper)
|
||||
|
||||
class Comparator(PropComparator):
|
||||
"""Produce boolean, comparison, and other operators for
|
||||
:class:`.CompositeProperty` attributes.
|
||||
|
||||
See the example in :ref:`composite_operations` for an overview
|
||||
of usage , as well as the documentation for :class:`.PropComparator`.
|
||||
|
||||
See also:
|
||||
|
||||
:class:`.PropComparator`
|
||||
|
||||
:class:`.ColumnOperators`
|
||||
|
||||
:ref:`types_operators`
|
||||
|
||||
:attr:`.TypeEngine.comparator_factory`
|
||||
|
||||
"""
|
||||
|
||||
def __clause_element__(self):
|
||||
return expression.ClauseList(group=False, *self._comparable_elements)
|
||||
|
||||
__hash__ = None
|
||||
|
||||
@util.memoized_property
|
||||
def _comparable_elements(self):
|
||||
if self.adapter:
|
||||
# we need to do a little fudging here because
|
||||
# the adapter function we're given only accepts
|
||||
# ColumnElements, but our prop._comparable_elements is returning
|
||||
# InstrumentedAttribute, because we support the use case
|
||||
# of composites that refer to relationships. The better
|
||||
# solution here is to open up how AliasedClass interacts
|
||||
# with PropComparators so more context is available.
|
||||
return [self.adapter(x.__clause_element__())
|
||||
for x in self.prop._comparable_elements]
|
||||
else:
|
||||
return self.prop._comparable_elements
|
||||
|
||||
def __eq__(self, other):
|
||||
if other is None:
|
||||
values = [None] * len(self.prop._comparable_elements)
|
||||
else:
|
||||
values = other.__composite_values__()
|
||||
comparisons = [
|
||||
a == b
|
||||
for a, b in zip(self.prop._comparable_elements, values)
|
||||
]
|
||||
if self.adapter:
|
||||
comparisons = [self.adapter(x) for x in comparisons]
|
||||
return sql.and_(*comparisons)
|
||||
|
||||
def __ne__(self, other):
|
||||
return sql.not_(self.__eq__(other))
|
||||
|
||||
def __str__(self):
|
||||
return str(self.parent.class_.__name__) + "." + self.key
|
||||
|
||||
|
||||
class ConcreteInheritedProperty(DescriptorProperty):
|
||||
"""A 'do nothing' :class:`.MapperProperty` that disables
|
||||
an attribute on a concrete subclass that is only present
|
||||
on the inherited mapper, not the concrete classes' mapper.
|
||||
|
||||
Cases where this occurs include:
|
||||
|
||||
* When the superclass mapper is mapped against a
|
||||
"polymorphic union", which includes all attributes from
|
||||
all subclasses.
|
||||
* When a relationship() is configured on an inherited mapper,
|
||||
but not on the subclass mapper. Concrete mappers require
|
||||
that relationship() is configured explicitly on each
|
||||
subclass.
|
||||
|
||||
"""
|
||||
|
||||
def _comparator_factory(self, mapper):
|
||||
comparator_callable = None
|
||||
|
||||
for m in self.parent.iterate_to_root():
|
||||
p = m._props[self.key]
|
||||
if not isinstance(p, ConcreteInheritedProperty):
|
||||
comparator_callable = p.comparator_factory
|
||||
break
|
||||
return comparator_callable
|
||||
|
||||
def __init__(self):
|
||||
def warn():
|
||||
raise AttributeError("Concrete %s does not implement "
|
||||
"attribute %r at the instance level. Add this "
|
||||
"property explicitly to %s." %
|
||||
(self.parent, self.key, self.parent))
|
||||
|
||||
class NoninheritedConcreteProp(object):
|
||||
def __set__(s, obj, value):
|
||||
warn()
|
||||
|
||||
def __delete__(s, obj):
|
||||
warn()
|
||||
|
||||
def __get__(s, obj, owner):
|
||||
if obj is None:
|
||||
return self.descriptor
|
||||
warn()
|
||||
self.descriptor = NoninheritedConcreteProp()
|
||||
|
||||
|
||||
class SynonymProperty(DescriptorProperty):
|
||||
|
||||
def __init__(self, name, map_column=None,
|
||||
descriptor=None, comparator_factory=None,
|
||||
doc=None):
|
||||
self.name = name
|
||||
self.map_column = map_column
|
||||
self.descriptor = descriptor
|
||||
self.comparator_factory = comparator_factory
|
||||
self.doc = doc or (descriptor and descriptor.__doc__) or None
|
||||
|
||||
util.set_creation_order(self)
|
||||
|
||||
# TODO: when initialized, check _proxied_property,
|
||||
# emit a warning if its not a column-based property
|
||||
|
||||
@util.memoized_property
|
||||
def _proxied_property(self):
|
||||
return getattr(self.parent.class_, self.name).property
|
||||
|
||||
def _comparator_factory(self, mapper):
|
||||
prop = self._proxied_property
|
||||
|
||||
if self.comparator_factory:
|
||||
comp = self.comparator_factory(prop, mapper)
|
||||
else:
|
||||
comp = prop.comparator_factory(prop, mapper)
|
||||
return comp
|
||||
|
||||
def set_parent(self, parent, init):
|
||||
if self.map_column:
|
||||
# implement the 'map_column' option.
|
||||
if self.key not in parent.mapped_table.c:
|
||||
raise sa_exc.ArgumentError(
|
||||
"Can't compile synonym '%s': no column on table "
|
||||
"'%s' named '%s'"
|
||||
% (self.name, parent.mapped_table.description, self.key))
|
||||
elif parent.mapped_table.c[self.key] in \
|
||||
parent._columntoproperty and \
|
||||
parent._columntoproperty[
|
||||
parent.mapped_table.c[self.key]
|
||||
].key == self.name:
|
||||
raise sa_exc.ArgumentError(
|
||||
"Can't call map_column=True for synonym %r=%r, "
|
||||
"a ColumnProperty already exists keyed to the name "
|
||||
"%r for column %r" %
|
||||
(self.key, self.name, self.name, self.key)
|
||||
)
|
||||
p = properties.ColumnProperty(parent.mapped_table.c[self.key])
|
||||
parent._configure_property(
|
||||
self.name, p,
|
||||
init=init,
|
||||
setparent=True)
|
||||
p._mapped_by_synonym = self.key
|
||||
|
||||
self.parent = parent
|
||||
|
||||
|
||||
class ComparableProperty(DescriptorProperty):
|
||||
"""Instruments a Python property for use in query expressions."""
|
||||
|
||||
def __init__(self, comparator_factory, descriptor=None, doc=None):
|
||||
self.descriptor = descriptor
|
||||
self.comparator_factory = comparator_factory
|
||||
self.doc = doc or (descriptor and descriptor.__doc__) or None
|
||||
util.set_creation_order(self)
|
||||
|
||||
def _comparator_factory(self, mapper):
|
||||
return self.comparator_factory(self, mapper)
|
||||
@@ -0,0 +1,362 @@
|
||||
# orm/dynamic.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Dynamic collection API.
|
||||
|
||||
Dynamic collections act like Query() objects for read operations and support
|
||||
basic add/delete mutation.
|
||||
|
||||
"""
|
||||
|
||||
from .. import log, util, exc
|
||||
from ..sql import operators
|
||||
from . import (
|
||||
attributes, object_session, util as orm_util, strategies,
|
||||
object_mapper, exc as orm_exc
|
||||
)
|
||||
from .query import Query
|
||||
|
||||
|
||||
class DynaLoader(strategies.AbstractRelationshipLoader):
|
||||
def init_class_attribute(self, mapper):
|
||||
self.is_class_level = True
|
||||
if not self.uselist:
|
||||
raise exc.InvalidRequestError(
|
||||
"On relationship %s, 'dynamic' loaders cannot be used with "
|
||||
"many-to-one/one-to-one relationships and/or "
|
||||
"uselist=False." % self.parent_property)
|
||||
strategies._register_attribute(self,
|
||||
mapper,
|
||||
useobject=True,
|
||||
uselist=True,
|
||||
impl_class=DynamicAttributeImpl,
|
||||
target_mapper=self.parent_property.mapper,
|
||||
order_by=self.parent_property.order_by,
|
||||
query_class=self.parent_property.query_class,
|
||||
backref=self.parent_property.back_populates,
|
||||
)
|
||||
|
||||
log.class_logger(DynaLoader)
|
||||
|
||||
|
||||
class DynamicAttributeImpl(attributes.AttributeImpl):
|
||||
uses_objects = True
|
||||
accepts_scalar_loader = False
|
||||
supports_population = False
|
||||
collection = False
|
||||
|
||||
def __init__(self, class_, key, typecallable,
|
||||
dispatch,
|
||||
target_mapper, order_by, query_class=None, **kw):
|
||||
super(DynamicAttributeImpl, self).\
|
||||
__init__(class_, key, typecallable, dispatch, **kw)
|
||||
self.target_mapper = target_mapper
|
||||
self.order_by = order_by
|
||||
if not query_class:
|
||||
self.query_class = AppenderQuery
|
||||
elif AppenderMixin in query_class.mro():
|
||||
self.query_class = query_class
|
||||
else:
|
||||
self.query_class = mixin_user_query(query_class)
|
||||
|
||||
def get(self, state, dict_, passive=attributes.PASSIVE_OFF):
|
||||
if not passive & attributes.SQL_OK:
|
||||
return self._get_collection_history(state,
|
||||
attributes.PASSIVE_NO_INITIALIZE).added_items
|
||||
else:
|
||||
return self.query_class(self, state)
|
||||
|
||||
def get_collection(self, state, dict_, user_data=None,
|
||||
passive=attributes.PASSIVE_NO_INITIALIZE):
|
||||
if not passive & attributes.SQL_OK:
|
||||
return self._get_collection_history(state,
|
||||
passive).added_items
|
||||
else:
|
||||
history = self._get_collection_history(state, passive)
|
||||
return history.added_plus_unchanged
|
||||
|
||||
def fire_append_event(self, state, dict_, value, initiator,
|
||||
collection_history=None):
|
||||
if collection_history is None:
|
||||
collection_history = self._modified_event(state, dict_)
|
||||
|
||||
collection_history.add_added(value)
|
||||
|
||||
for fn in self.dispatch.append:
|
||||
value = fn(state, value, initiator or self)
|
||||
|
||||
if self.trackparent and value is not None:
|
||||
self.sethasparent(attributes.instance_state(value), state, True)
|
||||
|
||||
def fire_remove_event(self, state, dict_, value, initiator,
|
||||
collection_history=None):
|
||||
if collection_history is None:
|
||||
collection_history = self._modified_event(state, dict_)
|
||||
|
||||
collection_history.add_removed(value)
|
||||
|
||||
if self.trackparent and value is not None:
|
||||
self.sethasparent(attributes.instance_state(value), state, False)
|
||||
|
||||
for fn in self.dispatch.remove:
|
||||
fn(state, value, initiator or self)
|
||||
|
||||
def _modified_event(self, state, dict_):
|
||||
|
||||
if self.key not in state.committed_state:
|
||||
state.committed_state[self.key] = CollectionHistory(self, state)
|
||||
|
||||
state._modified_event(dict_,
|
||||
self,
|
||||
attributes.NEVER_SET)
|
||||
|
||||
# this is a hack to allow the fixtures.ComparableEntity fixture
|
||||
# to work
|
||||
dict_[self.key] = True
|
||||
return state.committed_state[self.key]
|
||||
|
||||
def set(self, state, dict_, value, initiator,
|
||||
passive=attributes.PASSIVE_OFF,
|
||||
check_old=None, pop=False):
|
||||
if initiator and initiator.parent_token is self.parent_token:
|
||||
return
|
||||
|
||||
if pop and value is None:
|
||||
return
|
||||
self._set_iterable(state, dict_, value)
|
||||
|
||||
def _set_iterable(self, state, dict_, iterable, adapter=None):
|
||||
new_values = list(iterable)
|
||||
if state.has_identity:
|
||||
old_collection = util.IdentitySet(self.get(state, dict_))
|
||||
|
||||
collection_history = self._modified_event(state, dict_)
|
||||
if not state.has_identity:
|
||||
old_collection = collection_history.added_items
|
||||
else:
|
||||
old_collection = old_collection.union(
|
||||
collection_history.added_items)
|
||||
|
||||
idset = util.IdentitySet
|
||||
constants = old_collection.intersection(new_values)
|
||||
additions = idset(new_values).difference(constants)
|
||||
removals = old_collection.difference(constants)
|
||||
|
||||
for member in new_values:
|
||||
if member in additions:
|
||||
self.fire_append_event(state, dict_, member, None,
|
||||
collection_history=collection_history)
|
||||
|
||||
for member in removals:
|
||||
self.fire_remove_event(state, dict_, member, None,
|
||||
collection_history=collection_history)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_committed_value(self, state, dict_, value):
|
||||
raise NotImplementedError("Dynamic attributes don't support "
|
||||
"collection population.")
|
||||
|
||||
def get_history(self, state, dict_, passive=attributes.PASSIVE_OFF):
|
||||
c = self._get_collection_history(state, passive)
|
||||
return c.as_history()
|
||||
|
||||
def get_all_pending(self, state, dict_):
|
||||
c = self._get_collection_history(
|
||||
state, attributes.PASSIVE_NO_INITIALIZE)
|
||||
return [
|
||||
(attributes.instance_state(x), x)
|
||||
for x in
|
||||
c.all_items
|
||||
]
|
||||
|
||||
def _get_collection_history(self, state, passive=attributes.PASSIVE_OFF):
|
||||
if self.key in state.committed_state:
|
||||
c = state.committed_state[self.key]
|
||||
else:
|
||||
c = CollectionHistory(self, state)
|
||||
|
||||
if state.has_identity and (passive & attributes.INIT_OK):
|
||||
return CollectionHistory(self, state, apply_to=c)
|
||||
else:
|
||||
return c
|
||||
|
||||
def append(self, state, dict_, value, initiator,
|
||||
passive=attributes.PASSIVE_OFF):
|
||||
if initiator is not self:
|
||||
self.fire_append_event(state, dict_, value, initiator)
|
||||
|
||||
def remove(self, state, dict_, value, initiator,
|
||||
passive=attributes.PASSIVE_OFF):
|
||||
if initiator is not self:
|
||||
self.fire_remove_event(state, dict_, value, initiator)
|
||||
|
||||
def pop(self, state, dict_, value, initiator,
|
||||
passive=attributes.PASSIVE_OFF):
|
||||
self.remove(state, dict_, value, initiator, passive=passive)
|
||||
|
||||
|
||||
class AppenderMixin(object):
|
||||
query_class = None
|
||||
|
||||
def __init__(self, attr, state):
|
||||
super(AppenderMixin, self).__init__(attr.target_mapper, None)
|
||||
self.instance = instance = state.obj()
|
||||
self.attr = attr
|
||||
|
||||
mapper = object_mapper(instance)
|
||||
prop = mapper._props[self.attr.key]
|
||||
self._criterion = prop.compare(
|
||||
operators.eq,
|
||||
instance,
|
||||
value_is_parent=True,
|
||||
alias_secondary=False)
|
||||
|
||||
if self.attr.order_by:
|
||||
self._order_by = self.attr.order_by
|
||||
|
||||
def session(self):
|
||||
sess = object_session(self.instance)
|
||||
if sess is not None and self.autoflush and sess.autoflush \
|
||||
and self.instance in sess:
|
||||
sess.flush()
|
||||
if not orm_util.has_identity(self.instance):
|
||||
return None
|
||||
else:
|
||||
return sess
|
||||
session = property(session, lambda s, x: None)
|
||||
|
||||
def __iter__(self):
|
||||
sess = self.session
|
||||
if sess is None:
|
||||
return iter(self.attr._get_collection_history(
|
||||
attributes.instance_state(self.instance),
|
||||
attributes.PASSIVE_NO_INITIALIZE).added_items)
|
||||
else:
|
||||
return iter(self._clone(sess))
|
||||
|
||||
def __getitem__(self, index):
|
||||
sess = self.session
|
||||
if sess is None:
|
||||
return self.attr._get_collection_history(
|
||||
attributes.instance_state(self.instance),
|
||||
attributes.PASSIVE_NO_INITIALIZE).indexed(index)
|
||||
else:
|
||||
return self._clone(sess).__getitem__(index)
|
||||
|
||||
def count(self):
|
||||
sess = self.session
|
||||
if sess is None:
|
||||
return len(self.attr._get_collection_history(
|
||||
attributes.instance_state(self.instance),
|
||||
attributes.PASSIVE_NO_INITIALIZE).added_items)
|
||||
else:
|
||||
return self._clone(sess).count()
|
||||
|
||||
def _clone(self, sess=None):
|
||||
# note we're returning an entirely new Query class instance
|
||||
# here without any assignment capabilities; the class of this
|
||||
# query is determined by the session.
|
||||
instance = self.instance
|
||||
if sess is None:
|
||||
sess = object_session(instance)
|
||||
if sess is None:
|
||||
raise orm_exc.DetachedInstanceError(
|
||||
"Parent instance %s is not bound to a Session, and no "
|
||||
"contextual session is established; lazy load operation "
|
||||
"of attribute '%s' cannot proceed" % (
|
||||
orm_util.instance_str(instance), self.attr.key))
|
||||
|
||||
if self.query_class:
|
||||
query = self.query_class(self.attr.target_mapper, session=sess)
|
||||
else:
|
||||
query = sess.query(self.attr.target_mapper)
|
||||
|
||||
query._criterion = self._criterion
|
||||
query._order_by = self._order_by
|
||||
|
||||
return query
|
||||
|
||||
def extend(self, iterator):
|
||||
for item in iterator:
|
||||
self.attr.append(
|
||||
attributes.instance_state(self.instance),
|
||||
attributes.instance_dict(self.instance), item, None)
|
||||
|
||||
def append(self, item):
|
||||
self.attr.append(
|
||||
attributes.instance_state(self.instance),
|
||||
attributes.instance_dict(self.instance), item, None)
|
||||
|
||||
def remove(self, item):
|
||||
self.attr.remove(
|
||||
attributes.instance_state(self.instance),
|
||||
attributes.instance_dict(self.instance), item, None)
|
||||
|
||||
|
||||
class AppenderQuery(AppenderMixin, Query):
|
||||
"""A dynamic query that supports basic collection storage operations."""
|
||||
|
||||
|
||||
def mixin_user_query(cls):
|
||||
"""Return a new class with AppenderQuery functionality layered over."""
|
||||
name = 'Appender' + cls.__name__
|
||||
return type(name, (AppenderMixin, cls), {'query_class': cls})
|
||||
|
||||
|
||||
class CollectionHistory(object):
|
||||
"""Overrides AttributeHistory to receive append/remove events directly."""
|
||||
|
||||
def __init__(self, attr, state, apply_to=None):
|
||||
if apply_to:
|
||||
coll = AppenderQuery(attr, state).autoflush(False)
|
||||
self.unchanged_items = util.OrderedIdentitySet(coll)
|
||||
self.added_items = apply_to.added_items
|
||||
self.deleted_items = apply_to.deleted_items
|
||||
self._reconcile_collection = True
|
||||
else:
|
||||
self.deleted_items = util.OrderedIdentitySet()
|
||||
self.added_items = util.OrderedIdentitySet()
|
||||
self.unchanged_items = util.OrderedIdentitySet()
|
||||
self._reconcile_collection = False
|
||||
|
||||
@property
|
||||
def added_plus_unchanged(self):
|
||||
return list(self.added_items.union(self.unchanged_items))
|
||||
|
||||
@property
|
||||
def all_items(self):
|
||||
return list(self.added_items.union(
|
||||
self.unchanged_items).union(self.deleted_items))
|
||||
|
||||
def as_history(self):
|
||||
if self._reconcile_collection:
|
||||
added = self.added_items.difference(self.unchanged_items)
|
||||
deleted = self.deleted_items.intersection(self.unchanged_items)
|
||||
unchanged = self.unchanged_items.difference(deleted)
|
||||
else:
|
||||
added, unchanged, deleted = self.added_items,\
|
||||
self.unchanged_items,\
|
||||
self.deleted_items
|
||||
return attributes.History(
|
||||
list(added),
|
||||
list(unchanged),
|
||||
list(deleted),
|
||||
)
|
||||
|
||||
def indexed(self, index):
|
||||
return list(self.added_items)[index]
|
||||
|
||||
def add_added(self, value):
|
||||
self.added_items.add(value)
|
||||
|
||||
def add_removed(self, value):
|
||||
if value in self.added_items:
|
||||
self.added_items.remove(value)
|
||||
else:
|
||||
self.deleted_items.add(value)
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
# orm/evaluator.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import operator
|
||||
from ..sql import operators
|
||||
|
||||
|
||||
class UnevaluatableError(Exception):
|
||||
pass
|
||||
|
||||
_straight_ops = set(getattr(operators, op)
|
||||
for op in ('add', 'mul', 'sub',
|
||||
# Py2K
|
||||
'div',
|
||||
# end Py2K
|
||||
'mod', 'truediv',
|
||||
'lt', 'le', 'ne', 'gt', 'ge', 'eq'))
|
||||
|
||||
|
||||
_notimplemented_ops = set(getattr(operators, op)
|
||||
for op in ('like_op', 'notlike_op', 'ilike_op',
|
||||
'notilike_op', 'between_op', 'in_op',
|
||||
'notin_op', 'endswith_op', 'concat_op'))
|
||||
|
||||
|
||||
class EvaluatorCompiler(object):
|
||||
def process(self, clause):
|
||||
meth = getattr(self, "visit_%s" % clause.__visit_name__, None)
|
||||
if not meth:
|
||||
raise UnevaluatableError(
|
||||
"Cannot evaluate %s" % type(clause).__name__)
|
||||
return meth(clause)
|
||||
|
||||
def visit_grouping(self, clause):
|
||||
return self.process(clause.element)
|
||||
|
||||
def visit_null(self, clause):
|
||||
return lambda obj: None
|
||||
|
||||
def visit_false(self, clause):
|
||||
return lambda obj: False
|
||||
|
||||
def visit_true(self, clause):
|
||||
return lambda obj: True
|
||||
|
||||
def visit_column(self, clause):
|
||||
if 'parentmapper' in clause._annotations:
|
||||
key = clause._annotations['parentmapper'].\
|
||||
_columntoproperty[clause].key
|
||||
else:
|
||||
key = clause.key
|
||||
get_corresponding_attr = operator.attrgetter(key)
|
||||
return lambda obj: get_corresponding_attr(obj)
|
||||
|
||||
def visit_clauselist(self, clause):
|
||||
evaluators = map(self.process, clause.clauses)
|
||||
if clause.operator is operators.or_:
|
||||
def evaluate(obj):
|
||||
has_null = False
|
||||
for sub_evaluate in evaluators:
|
||||
value = sub_evaluate(obj)
|
||||
if value:
|
||||
return True
|
||||
has_null = has_null or value is None
|
||||
if has_null:
|
||||
return None
|
||||
return False
|
||||
elif clause.operator is operators.and_:
|
||||
def evaluate(obj):
|
||||
for sub_evaluate in evaluators:
|
||||
value = sub_evaluate(obj)
|
||||
if not value:
|
||||
if value is None:
|
||||
return None
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
raise UnevaluatableError(
|
||||
"Cannot evaluate clauselist with operator %s" %
|
||||
clause.operator)
|
||||
|
||||
return evaluate
|
||||
|
||||
def visit_binary(self, clause):
|
||||
eval_left, eval_right = map(self.process,
|
||||
[clause.left, clause.right])
|
||||
operator = clause.operator
|
||||
if operator is operators.is_:
|
||||
def evaluate(obj):
|
||||
return eval_left(obj) == eval_right(obj)
|
||||
elif operator is operators.isnot:
|
||||
def evaluate(obj):
|
||||
return eval_left(obj) != eval_right(obj)
|
||||
elif operator in _straight_ops:
|
||||
def evaluate(obj):
|
||||
left_val = eval_left(obj)
|
||||
right_val = eval_right(obj)
|
||||
if left_val is None or right_val is None:
|
||||
return None
|
||||
return operator(eval_left(obj), eval_right(obj))
|
||||
else:
|
||||
raise UnevaluatableError(
|
||||
"Cannot evaluate %s with operator %s" %
|
||||
(type(clause).__name__, clause.operator))
|
||||
return evaluate
|
||||
|
||||
def visit_unary(self, clause):
|
||||
eval_inner = self.process(clause.element)
|
||||
if clause.operator is operators.inv:
|
||||
def evaluate(obj):
|
||||
value = eval_inner(obj)
|
||||
if value is None:
|
||||
return None
|
||||
return not value
|
||||
return evaluate
|
||||
raise UnevaluatableError(
|
||||
"Cannot evaluate %s with operator %s" %
|
||||
(type(clause).__name__, clause.operator))
|
||||
|
||||
def visit_bindparam(self, clause):
|
||||
val = clause.value
|
||||
return lambda obj: val
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user