Update SQLAlchemy
This commit is contained in:
1 parent
267ecfacab
commit
4aa6700ceb
121 files changed
+2552
-1259
No files matched your search
@@ -1,5 +1,5 @@
|
||||
# sqlalchemy/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -50,6 +50,8 @@ from sqlalchemy.sql import (
|
||||
)
|
||||
|
||||
from sqlalchemy.types import (
|
||||
BIGINT,
|
||||
BINARY,
|
||||
BLOB,
|
||||
BOOLEAN,
|
||||
BigInteger,
|
||||
@@ -87,6 +89,7 @@ from sqlalchemy.types import (
|
||||
TypeDecorator,
|
||||
Unicode,
|
||||
UnicodeText,
|
||||
VARBINARY,
|
||||
VARCHAR,
|
||||
)
|
||||
|
||||
@@ -117,7 +120,7 @@ from sqlalchemy.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.7.6'
|
||||
__version__ = '0.7.10'
|
||||
|
||||
del inspect, sys
|
||||
|
||||
|
||||
@@ -241,12 +241,13 @@ static PyObject *
|
||||
BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
|
||||
{
|
||||
PyObject *processors, *values;
|
||||
PyObject *processor, *value;
|
||||
PyObject *processor, *value, *processed_value;
|
||||
PyObject *row, *record, *result, *indexobject;
|
||||
PyObject *exc_module, *exception;
|
||||
PyObject *exc_module, *exception, *cstr_obj;
|
||||
char *cstr_key;
|
||||
long index;
|
||||
int key_fallback = 0;
|
||||
int tuple_check = 0;
|
||||
|
||||
if (PyInt_CheckExact(key)) {
|
||||
index = PyInt_AS_LONG(key);
|
||||
@@ -299,9 +300,16 @@ BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
|
||||
if (exception == NULL)
|
||||
return NULL;
|
||||
|
||||
cstr_key = PyString_AsString(key);
|
||||
if (cstr_key == NULL)
|
||||
// wow. this seems quite excessive.
|
||||
cstr_obj = PyObject_Str(key);
|
||||
if (cstr_obj == NULL)
|
||||
return NULL;
|
||||
cstr_key = PyString_AsString(cstr_obj);
|
||||
if (cstr_key == NULL) {
|
||||
Py_DECREF(cstr_obj);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(cstr_obj);
|
||||
|
||||
PyErr_Format(exception,
|
||||
"Ambiguous column name '%.200s' in result set! "
|
||||
@@ -319,17 +327,28 @@ BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
|
||||
return NULL;
|
||||
|
||||
row = self->row;
|
||||
if (PyTuple_CheckExact(row))
|
||||
if (PyTuple_CheckExact(row)) {
|
||||
value = PyTuple_GetItem(row, index);
|
||||
else
|
||||
tuple_check = 1;
|
||||
}
|
||||
else {
|
||||
value = PySequence_GetItem(row, index);
|
||||
tuple_check = 0;
|
||||
}
|
||||
|
||||
if (value == NULL)
|
||||
return NULL;
|
||||
|
||||
if (processor != Py_None) {
|
||||
return PyObject_CallFunctionObjArgs(processor, value, NULL);
|
||||
processed_value = PyObject_CallFunctionObjArgs(processor, value, NULL);
|
||||
if (!tuple_check) {
|
||||
Py_DECREF(value);
|
||||
}
|
||||
return processed_value;
|
||||
} else {
|
||||
Py_INCREF(value);
|
||||
if (tuple_check) {
|
||||
Py_INCREF(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# connectors/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# connectors/mxodbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -63,6 +63,7 @@ class MySQLDBConnector(Connector):
|
||||
|
||||
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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# connectors/pyodbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -37,6 +37,10 @@ class PyODBCConnector(Connector):
|
||||
# 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')
|
||||
@@ -119,8 +123,12 @@ class PyODBCConnector(Connector):
|
||||
# have not tried pyodbc + python3.1 yet.
|
||||
# Py2K
|
||||
self.supports_unicode_statements = not self.freetds and not self.easysoft
|
||||
self.supports_unicode_binds = (not self.freetds or
|
||||
self.freetds_driver_version >= '0.91') 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# connectors/zxJDBC.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# databases/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# dialects/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
"""
|
||||
Support for the Microsoft Access database.
|
||||
|
||||
This dialect is *not* ported to SQLAlchemy 0.6 or 0.7.
|
||||
.. note::
|
||||
|
||||
This dialect is *not* tested on SQLAlchemy 0.6 or 0.7.
|
||||
The Access dialect is **non-functional as of SQLAlchemy 0.6**,
|
||||
pending development efforts to bring it up-to-date.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
from sqlalchemy.dialects.drizzle import base, mysqldb
|
||||
|
||||
# default dialect
|
||||
base.dialect = mysqldb.dialect
|
||||
|
||||
from sqlalchemy.dialects.drizzle.base import \
|
||||
BIGINT, BINARY, BLOB, BOOLEAN, CHAR, DATE, DATETIME, \
|
||||
DECIMAL, DOUBLE, ENUM, \
|
||||
FLOAT, INTEGER, \
|
||||
NUMERIC, REAL, TEXT, TIME, TIMESTAMP, \
|
||||
VARBINARY, VARCHAR, dialect
|
||||
BIGINT, BINARY, BLOB, \
|
||||
BOOLEAN, CHAR, DATE, \
|
||||
DATETIME, DECIMAL, DOUBLE, \
|
||||
ENUM, FLOAT, INTEGER, \
|
||||
NUMERIC, REAL, TEXT, \
|
||||
TIME, TIMESTAMP, VARBINARY, \
|
||||
VARCHAR, dialect
|
||||
|
||||
__all__ = (
|
||||
'BIGINT', 'BINARY', 'BLOB', 'BOOLEAN', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'DOUBLE',
|
||||
'ENUM', 'FLOAT', 'INTEGER',
|
||||
'NUMERIC', 'SET', 'REAL', 'TEXT', 'TIME', 'TIMESTAMP',
|
||||
'VARBINARY', 'VARCHAR', 'dialect'
|
||||
'BIGINT', 'BINARY', 'BLOB',
|
||||
'BOOLEAN', 'CHAR', 'DATE',
|
||||
'DATETIME', 'DECIMAL', 'DOUBLE',
|
||||
'ENUM', 'FLOAT', 'INTEGER',
|
||||
'NUMERIC', 'REAL', 'TEXT',
|
||||
'TIME', 'TIMESTAMP', 'VARBINARY',
|
||||
'VARCHAR', 'dialect'
|
||||
)
|
||||
@@ -1,138 +1,36 @@
|
||||
# drizzle/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2010-2011 Monty Taylor <mordred@inaugust.com>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
|
||||
"""Support for the Drizzle database.
|
||||
|
||||
Supported Versions and Features
|
||||
-------------------------------
|
||||
Drizzle is a variant of MySQL. Unlike MySQL, Drizzle's default storage engine
|
||||
is InnoDB (transactions, foreign-keys) rather than MyISAM. For more
|
||||
`Notable Differences <http://docs.drizzle.org/mysql_differences.html>`_, visit
|
||||
the `Drizzle Documentation <http://docs.drizzle.org/index.html>`_.
|
||||
|
||||
SQLAlchemy supports the Drizzle database starting with 2010.08.
|
||||
with capabilities increasing with more modern servers.
|
||||
|
||||
Most available DBAPI drivers are supported; see below.
|
||||
|
||||
===================================== ===============
|
||||
Feature Minimum Version
|
||||
===================================== ===============
|
||||
sqlalchemy.orm 2010.08
|
||||
Table Reflection 2010.08
|
||||
DDL Generation 2010.08
|
||||
utf8/Full Unicode Connections 2010.08
|
||||
Transactions 2010.08
|
||||
Two-Phase Transactions 2010.08
|
||||
Nested Transactions 2010.08
|
||||
===================================== ===============
|
||||
|
||||
See the official Drizzle documentation for detailed information about features
|
||||
supported in any given server release.
|
||||
The SQLAlchemy Drizzle dialect leans heavily on the MySQL dialect, so much of
|
||||
the :doc:`SQLAlchemy MySQL <mysql>` documentation is also relevant.
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
See the API documentation on individual drivers for details on connecting.
|
||||
|
||||
Connection Timeouts
|
||||
-------------------
|
||||
|
||||
Drizzle features an automatic connection close behavior, for connections that
|
||||
have been idle for eight hours or more. To circumvent having this issue, use
|
||||
the ``pool_recycle`` option which controls the maximum age of any connection::
|
||||
|
||||
engine = create_engine('drizzle+mysqldb://...', pool_recycle=3600)
|
||||
|
||||
Storage Engines
|
||||
---------------
|
||||
|
||||
Drizzle defaults to the ``InnoDB`` storage engine, which is transactional.
|
||||
|
||||
Storage engines can be elected when creating tables in SQLAlchemy by supplying
|
||||
a ``drizzle_engine='whatever'`` to the ``Table`` constructor. Any Drizzle table
|
||||
creation option can be specified in this syntax::
|
||||
|
||||
Table('mytable', metadata,
|
||||
Column('data', String(32)),
|
||||
drizzle_engine='InnoDB',
|
||||
)
|
||||
|
||||
Keys
|
||||
----
|
||||
|
||||
Not all Drizzle storage engines support foreign keys. For ``BlitzDB`` and
|
||||
similar engines, the information loaded by table reflection will not include
|
||||
foreign keys. For these tables, you may supply a
|
||||
:class:`~sqlalchemy.ForeignKeyConstraint` at reflection time::
|
||||
|
||||
Table('mytable', metadata,
|
||||
ForeignKeyConstraint(['other_id'], ['othertable.other_id']),
|
||||
autoload=True
|
||||
)
|
||||
|
||||
When creating tables, SQLAlchemy will automatically set ``AUTO_INCREMENT`` on
|
||||
an integer primary key column::
|
||||
|
||||
>>> t = Table('mytable', metadata,
|
||||
... Column('mytable_id', Integer, primary_key=True)
|
||||
... )
|
||||
>>> t.create()
|
||||
CREATE TABLE mytable (
|
||||
id INTEGER NOT NULL AUTO_INCREMENT,
|
||||
PRIMARY KEY (id)
|
||||
)
|
||||
|
||||
You can disable this behavior by supplying ``autoincrement=False`` to the
|
||||
:class:`~sqlalchemy.Column`. This flag can also be used to enable
|
||||
auto-increment on a secondary column in a multi-column key for some storage
|
||||
engines::
|
||||
|
||||
Table('mytable', metadata,
|
||||
Column('gid', Integer, primary_key=True, autoincrement=False),
|
||||
Column('id', Integer, primary_key=True)
|
||||
)
|
||||
|
||||
Drizzle SQL Extensions
|
||||
----------------------
|
||||
|
||||
Many of the Drizzle SQL extensions are handled through SQLAlchemy's generic
|
||||
function and operator support::
|
||||
|
||||
table.select(table.c.password==func.md5('plaintext'))
|
||||
table.select(table.c.username.op('regexp')('^[a-d]'))
|
||||
|
||||
And of course any valid Drizzle statement can be executed as a string as well.
|
||||
|
||||
Some limited direct support for Drizzle extensions to SQL is currently
|
||||
available.
|
||||
|
||||
* SELECT pragma::
|
||||
|
||||
select(..., prefixes=['HIGH_PRIORITY', 'SQL_SMALL_RESULT'])
|
||||
|
||||
* UPDATE with LIMIT::
|
||||
|
||||
update(..., drizzle_limit=10)
|
||||
See the individual driver sections below for details on connecting.
|
||||
|
||||
"""
|
||||
|
||||
import datetime, inspect, re, sys
|
||||
|
||||
from sqlalchemy import schema as sa_schema
|
||||
from sqlalchemy import exc, log, sql, util
|
||||
from sqlalchemy.sql import operators as sql_operators
|
||||
from sqlalchemy.sql import functions as sql_functions
|
||||
from sqlalchemy.sql import compiler
|
||||
from array import array as _array
|
||||
|
||||
from sqlalchemy.engine import reflection
|
||||
from sqlalchemy.engine import base as engine_base, default
|
||||
from sqlalchemy import exc
|
||||
from sqlalchemy import log
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.engine import reflection
|
||||
from sqlalchemy.dialects.mysql import base as mysql_dialect
|
||||
|
||||
from sqlalchemy.types import DATE, DATETIME, BOOLEAN, TIME, \
|
||||
BLOB, BINARY, VARBINARY
|
||||
BLOB, BINARY, VARBINARY
|
||||
|
||||
|
||||
class _NumericType(object):
|
||||
"""Base for Drizzle numeric types."""
|
||||
@@ -140,6 +38,7 @@ class _NumericType(object):
|
||||
def __init__(self, **kw):
|
||||
super(_NumericType, self).__init__(**kw)
|
||||
|
||||
|
||||
class _FloatType(_NumericType, sqltypes.Float):
|
||||
def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
|
||||
if isinstance(self, (REAL, DOUBLE)) and \
|
||||
@@ -147,23 +46,22 @@ class _FloatType(_NumericType, sqltypes.Float):
|
||||
(precision is None and scale is not None) or
|
||||
(precision is not None and scale is None)
|
||||
):
|
||||
raise exc.ArgumentError(
|
||||
"You must specify both precision and scale or omit "
|
||||
"both altogether.")
|
||||
raise exc.ArgumentError(
|
||||
"You must specify both precision and scale or omit "
|
||||
"both altogether.")
|
||||
|
||||
super(_FloatType, self).__init__(precision=precision, asdecimal=asdecimal, **kw)
|
||||
super(_FloatType, self).__init__(precision=precision,
|
||||
asdecimal=asdecimal, **kw)
|
||||
self.scale = scale
|
||||
|
||||
|
||||
class _StringType(mysql_dialect._StringType):
|
||||
"""Base for Drizzle string types."""
|
||||
|
||||
def __init__(self, collation=None,
|
||||
binary=False,
|
||||
**kw):
|
||||
def __init__(self, collation=None, binary=False, **kw):
|
||||
kw['national'] = False
|
||||
super(_StringType, self).__init__(collation=collation,
|
||||
binary=binary,
|
||||
**kw)
|
||||
super(_StringType, self).__init__(collation=collation, binary=binary,
|
||||
**kw)
|
||||
|
||||
|
||||
class NUMERIC(_NumericType, sqltypes.NUMERIC):
|
||||
@@ -180,7 +78,9 @@ class NUMERIC(_NumericType, sqltypes.NUMERIC):
|
||||
:param scale: The number of digits after the decimal point.
|
||||
|
||||
"""
|
||||
super(NUMERIC, self).__init__(precision=precision, scale=scale, asdecimal=asdecimal, **kw)
|
||||
|
||||
super(NUMERIC, self).__init__(precision=precision, scale=scale,
|
||||
asdecimal=asdecimal, **kw)
|
||||
|
||||
|
||||
class DECIMAL(_NumericType, sqltypes.DECIMAL):
|
||||
@@ -215,9 +115,11 @@ class DOUBLE(_FloatType):
|
||||
:param scale: The number of digits after the decimal point.
|
||||
|
||||
"""
|
||||
|
||||
super(DOUBLE, self).__init__(precision=precision, scale=scale,
|
||||
asdecimal=asdecimal, **kw)
|
||||
|
||||
|
||||
class REAL(_FloatType, sqltypes.REAL):
|
||||
"""Drizzle REAL type."""
|
||||
|
||||
@@ -232,9 +134,11 @@ class REAL(_FloatType, sqltypes.REAL):
|
||||
:param scale: The number of digits after the decimal point.
|
||||
|
||||
"""
|
||||
|
||||
super(REAL, self).__init__(precision=precision, scale=scale,
|
||||
asdecimal=asdecimal, **kw)
|
||||
|
||||
|
||||
class FLOAT(_FloatType, sqltypes.FLOAT):
|
||||
"""Drizzle FLOAT type."""
|
||||
|
||||
@@ -249,42 +153,46 @@ class FLOAT(_FloatType, sqltypes.FLOAT):
|
||||
:param scale: The number of digits after the decimal point.
|
||||
|
||||
"""
|
||||
|
||||
super(FLOAT, self).__init__(precision=precision, scale=scale,
|
||||
asdecimal=asdecimal, **kw)
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
return None
|
||||
|
||||
|
||||
class INTEGER(sqltypes.INTEGER):
|
||||
"""Drizzle INTEGER type."""
|
||||
|
||||
__visit_name__ = 'INTEGER'
|
||||
|
||||
def __init__(self, **kw):
|
||||
"""Construct an INTEGER.
|
||||
"""Construct an INTEGER."""
|
||||
|
||||
"""
|
||||
super(INTEGER, self).__init__(**kw)
|
||||
|
||||
|
||||
class BIGINT(sqltypes.BIGINT):
|
||||
"""Drizzle BIGINTEGER type."""
|
||||
|
||||
__visit_name__ = 'BIGINT'
|
||||
|
||||
def __init__(self, **kw):
|
||||
"""Construct a BIGINTEGER.
|
||||
"""Construct a BIGINTEGER."""
|
||||
|
||||
"""
|
||||
super(BIGINT, self).__init__(**kw)
|
||||
|
||||
|
||||
class _DrizzleTime(mysql_dialect._MSTime):
|
||||
"""Drizzle TIME type."""
|
||||
|
||||
|
||||
class TIMESTAMP(sqltypes.TIMESTAMP):
|
||||
"""Drizzle TIMESTAMP type."""
|
||||
|
||||
__visit_name__ = 'TIMESTAMP'
|
||||
|
||||
|
||||
class TEXT(_StringType, sqltypes.TEXT):
|
||||
"""Drizzle TEXT type, for text up to 2^16 characters."""
|
||||
|
||||
@@ -306,8 +214,10 @@ class TEXT(_StringType, sqltypes.TEXT):
|
||||
only the collation of character data.
|
||||
|
||||
"""
|
||||
|
||||
super(TEXT, self).__init__(length=length, **kw)
|
||||
|
||||
|
||||
class VARCHAR(_StringType, sqltypes.VARCHAR):
|
||||
"""Drizzle VARCHAR type, for variable-length character data."""
|
||||
|
||||
@@ -325,8 +235,10 @@ class VARCHAR(_StringType, sqltypes.VARCHAR):
|
||||
only the collation of character data.
|
||||
|
||||
"""
|
||||
|
||||
super(VARCHAR, self).__init__(length=length, **kwargs)
|
||||
|
||||
|
||||
class CHAR(_StringType, sqltypes.CHAR):
|
||||
"""Drizzle CHAR type, for fixed-length character data."""
|
||||
|
||||
@@ -345,8 +257,10 @@ class CHAR(_StringType, sqltypes.CHAR):
|
||||
compatible with the national character set.
|
||||
|
||||
"""
|
||||
|
||||
super(CHAR, self).__init__(length=length, **kwargs)
|
||||
|
||||
|
||||
class ENUM(mysql_dialect.ENUM):
|
||||
"""Drizzle ENUM type."""
|
||||
|
||||
@@ -363,8 +277,9 @@ class ENUM(mysql_dialect.ENUM):
|
||||
|
||||
:param strict: Defaults to False: ensure that a given value is in this
|
||||
ENUM's range of permissible values when inserting or updating rows.
|
||||
Note that Drizzle will not raise a fatal error if you attempt to store
|
||||
an out of range value- an alternate value will be stored instead.
|
||||
Note that Drizzle will not raise a fatal error if you attempt to
|
||||
store an out of range value- an alternate value will be stored
|
||||
instead.
|
||||
(See Drizzle ENUM documentation.)
|
||||
|
||||
:param collation: Optional, a column-level collation for this string
|
||||
@@ -390,12 +305,15 @@ class ENUM(mysql_dialect.ENUM):
|
||||
literals for you. This is a transitional option.
|
||||
|
||||
"""
|
||||
|
||||
super(ENUM, self).__init__(*enums, **kw)
|
||||
|
||||
|
||||
class _DrizzleBoolean(sqltypes.Boolean):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.NUMERIC
|
||||
|
||||
|
||||
colspecs = {
|
||||
sqltypes.Numeric: NUMERIC,
|
||||
sqltypes.Float: FLOAT,
|
||||
@@ -404,6 +322,7 @@ colspecs = {
|
||||
sqltypes.Boolean: _DrizzleBoolean,
|
||||
}
|
||||
|
||||
|
||||
# All the types we have in Drizzle
|
||||
ischema_names = {
|
||||
'BIGINT': BIGINT,
|
||||
@@ -427,6 +346,7 @@ ischema_names = {
|
||||
'VARCHAR': VARCHAR,
|
||||
}
|
||||
|
||||
|
||||
class DrizzleCompiler(mysql_dialect.MySQLCompiler):
|
||||
|
||||
def visit_typeclause(self, typeclause):
|
||||
@@ -439,7 +359,7 @@ class DrizzleCompiler(mysql_dialect.MySQLCompiler):
|
||||
def visit_cast(self, cast, **kwargs):
|
||||
type_ = self.process(cast.typeclause)
|
||||
if type_ is None:
|
||||
return self.process(cast.clause)
|
||||
return self.process(cast.clause)
|
||||
|
||||
return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
|
||||
|
||||
@@ -447,6 +367,7 @@ class DrizzleCompiler(mysql_dialect.MySQLCompiler):
|
||||
class DrizzleDDLCompiler(mysql_dialect.MySQLDDLCompiler):
|
||||
pass
|
||||
|
||||
|
||||
class DrizzleTypeCompiler(mysql_dialect.MySQLTypeCompiler):
|
||||
def _extend_numeric(self, type_, spec):
|
||||
return spec
|
||||
@@ -492,11 +413,16 @@ class DrizzleTypeCompiler(mysql_dialect.MySQLTypeCompiler):
|
||||
class DrizzleExecutionContext(mysql_dialect.MySQLExecutionContext):
|
||||
pass
|
||||
|
||||
|
||||
class DrizzleIdentifierPreparer(mysql_dialect.MySQLIdentifierPreparer):
|
||||
pass
|
||||
|
||||
|
||||
class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
"""Details of the Drizzle dialect. Not used directly in application code."""
|
||||
"""Details of the Drizzle dialect.
|
||||
|
||||
Not used directly in application code.
|
||||
"""
|
||||
|
||||
name = 'drizzle'
|
||||
|
||||
@@ -505,7 +431,6 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
supports_native_boolean = True
|
||||
supports_views = False
|
||||
|
||||
|
||||
default_paramstyle = 'format'
|
||||
colspecs = colspecs
|
||||
|
||||
@@ -516,8 +441,8 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
preparer = DrizzleIdentifierPreparer
|
||||
|
||||
def on_connect(self):
|
||||
"""Force autocommit - Drizzle Bug#707842 doesn't set this
|
||||
properly"""
|
||||
"""Force autocommit - Drizzle Bug#707842 doesn't set this properly"""
|
||||
|
||||
def connect(conn):
|
||||
conn.autocommit(False)
|
||||
return connect
|
||||
@@ -535,6 +460,7 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
@reflection.cache
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
"""Return a Unicode SHOW TABLES from a given schema."""
|
||||
|
||||
if schema is not None:
|
||||
current_schema = schema
|
||||
else:
|
||||
@@ -554,8 +480,8 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
|
||||
Cached per-connection. This value can not change without a server
|
||||
restart.
|
||||
|
||||
"""
|
||||
|
||||
return 0
|
||||
|
||||
def _detect_collations(self, connection):
|
||||
@@ -566,7 +492,9 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
|
||||
collations = {}
|
||||
charset = self._connection_charset
|
||||
rs = connection.execute('SELECT CHARACTER_SET_NAME, COLLATION_NAME from data_dictionary.COLLATIONS')
|
||||
rs = connection.execute(
|
||||
'SELECT CHARACTER_SET_NAME, COLLATION_NAME FROM'
|
||||
' data_dictionary.COLLATIONS')
|
||||
for row in self._compat_fetchall(rs, charset):
|
||||
collations[row[0]] = row[1]
|
||||
return collations
|
||||
@@ -575,8 +503,7 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
"""Detect and adjust for the ANSI_QUOTES sql mode."""
|
||||
|
||||
self._server_ansiquotes = False
|
||||
|
||||
self._backslash_escapes = False
|
||||
|
||||
log.class_logger(DrizzleDialect)
|
||||
|
||||
log.class_logger(DrizzleDialect)
|
||||
@@ -1,11 +1,9 @@
|
||||
"""Support for the Drizzle database via the Drizzle-python adapter.
|
||||
"""Support for the Drizzle database via the mysql-python adapter.
|
||||
|
||||
Drizzle-Python is available at:
|
||||
MySQL-Python is available at:
|
||||
|
||||
http://sourceforge.net/projects/mysql-python
|
||||
|
||||
At least version 1.2.1 or 1.2.2 should be used.
|
||||
|
||||
Connecting
|
||||
-----------
|
||||
|
||||
@@ -13,37 +11,22 @@ Connect string format::
|
||||
|
||||
drizzle+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
|
||||
Unicode
|
||||
-------
|
||||
|
||||
Drizzle accommodates Python ``unicode`` objects directly and
|
||||
uses the ``utf8`` encoding in all cases.
|
||||
|
||||
Known Issues
|
||||
-------------
|
||||
|
||||
Drizzle-python at least as of version 1.2.2 has a serious memory leak related
|
||||
to unicode conversion, a feature which is disabled via ``use_unicode=0``.
|
||||
The recommended connection form with SQLAlchemy is::
|
||||
|
||||
engine = create_engine('mysql://scott:tiger@localhost/test?charset=utf8&use_unicode=0', pool_recycle=3600)
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.drizzle.base import (DrizzleDialect,
|
||||
DrizzleExecutionContext,
|
||||
DrizzleCompiler, DrizzleIdentifierPreparer)
|
||||
from sqlalchemy.dialects.drizzle.base import (
|
||||
DrizzleDialect,
|
||||
DrizzleExecutionContext,
|
||||
DrizzleCompiler,
|
||||
DrizzleIdentifierPreparer)
|
||||
from sqlalchemy.connectors.mysqldb import (
|
||||
MySQLDBExecutionContext,
|
||||
MySQLDBCompiler,
|
||||
MySQLDBIdentifierPreparer,
|
||||
MySQLDBConnector
|
||||
)
|
||||
MySQLDBExecutionContext,
|
||||
MySQLDBCompiler,
|
||||
MySQLDBIdentifierPreparer,
|
||||
MySQLDBConnector)
|
||||
|
||||
class DrizzleExecutionContext_mysqldb(
|
||||
MySQLDBExecutionContext,
|
||||
DrizzleExecutionContext):
|
||||
|
||||
class DrizzleExecutionContext_mysqldb(MySQLDBExecutionContext,
|
||||
DrizzleExecutionContext):
|
||||
pass
|
||||
|
||||
|
||||
@@ -51,11 +34,11 @@ class DrizzleCompiler_mysqldb(MySQLDBCompiler, DrizzleCompiler):
|
||||
pass
|
||||
|
||||
|
||||
class DrizzleIdentifierPreparer_mysqldb(
|
||||
MySQLDBIdentifierPreparer,
|
||||
DrizzleIdentifierPreparer):
|
||||
class DrizzleIdentifierPreparer_mysqldb(MySQLDBIdentifierPreparer,
|
||||
DrizzleIdentifierPreparer):
|
||||
pass
|
||||
|
||||
|
||||
class DrizzleDialect_mysqldb(MySQLDBConnector, DrizzleDialect):
|
||||
execution_ctx_cls = DrizzleExecutionContext_mysqldb
|
||||
statement_compiler = DrizzleCompiler_mysqldb
|
||||
@@ -63,6 +46,7 @@ class DrizzleDialect_mysqldb(MySQLDBConnector, DrizzleDialect):
|
||||
|
||||
def _detect_charset(self, connection):
|
||||
"""Sniff out the character set in use for connection results."""
|
||||
|
||||
return 'utf8'
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# firebird/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# firebird/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -198,7 +198,7 @@ class FBTypeCompiler(compiler.GenericTypeCompiler):
|
||||
|
||||
|
||||
class FBCompiler(sql.compiler.SQLCompiler):
|
||||
"""Firebird specific idiosincrasies"""
|
||||
"""Firebird specific idiosyncrasies"""
|
||||
|
||||
def visit_mod(self, binary, **kw):
|
||||
# Firebird lacks a builtin modulo operator, but there is
|
||||
@@ -293,7 +293,7 @@ class FBCompiler(sql.compiler.SQLCompiler):
|
||||
|
||||
|
||||
class FBDDLCompiler(sql.compiler.DDLCompiler):
|
||||
"""Firebird syntactic idiosincrasies"""
|
||||
"""Firebird syntactic idiosyncrasies"""
|
||||
|
||||
def visit_create_sequence(self, create):
|
||||
"""Generate a ``CREATE GENERATOR`` statement for the sequence."""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# firebird/kinterbasdb.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# informix/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# informix/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# coding: gbk
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -7,8 +7,11 @@
|
||||
|
||||
"""Support for the Informix database.
|
||||
|
||||
This dialect is mostly functional as of SQLAlchemy 0.6.5.
|
||||
.. note::
|
||||
|
||||
The Informix dialect functions on current SQLAlchemy versions
|
||||
but is not regularly tested, and may have many issues and
|
||||
caveats not currently handled.
|
||||
|
||||
"""
|
||||
|
||||
@@ -23,7 +26,7 @@ from sqlalchemy import types as sqltypes
|
||||
RESERVED_WORDS = set(
|
||||
["abs", "absolute", "access", "access_method", "acos", "active", "add",
|
||||
"address", "add_months", "admin", "after", "aggregate", "alignment",
|
||||
"all", "allocate", "all_rows", "altere", "and", "ansi", "any", "append",
|
||||
"all", "allocate", "all_rows", "alter", "and", "ansi", "any", "append",
|
||||
"array", "as", "asc", "ascii", "asin", "at", "atan", "atan2", "attach",
|
||||
"attributes", "audit", "authentication", "authid", "authorization",
|
||||
"authorized", "auto", "autofree", "auto_reprepare", "auto_stat_mode",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# informix/informixdb.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# maxdb/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
# maxdb/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the MaxDB database.
|
||||
|
||||
This dialect is *not* ported to SQLAlchemy 0.6 or 0.7.
|
||||
.. note::
|
||||
|
||||
This dialect is *not* tested on SQLAlchemy 0.6 or 0.7.
|
||||
The MaxDB dialect is **non-functional as of SQLAlchemy 0.6**,
|
||||
pending development efforts to bring it up-to-date.
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# maxdb/sapdb.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/adodbapi.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -107,10 +107,10 @@ Compatibility Levels
|
||||
--------------------
|
||||
MSSQL supports the notion of setting compatibility levels at the
|
||||
database level. This allows, for instance, to run a database that
|
||||
is compatibile with SQL2000 while running on a SQL2005 database
|
||||
is compatible with SQL2000 while running on a SQL2005 database
|
||||
server. ``server_version_info`` will always return the database
|
||||
server version information (in this case SQL2005) and not the
|
||||
compatibiility level information. Because of this, if running under
|
||||
compatibility level information. Because of this, if running under
|
||||
a backwards compatibility mode SQAlchemy may attempt to use T-SQL
|
||||
statements that are unable to be parsed by the database server.
|
||||
|
||||
@@ -161,11 +161,12 @@ http://msdn.microsoft.com/en-us/library/ms175095.aspx.
|
||||
Scalar Select Comparisons
|
||||
-------------------------
|
||||
|
||||
The MSSQL dialect contains a legacy behavior whereby comparing
|
||||
a scalar select to a value using the ``=`` or ``!=`` operator
|
||||
will resolve to IN or NOT IN, respectively. This behavior is
|
||||
deprecated and will be removed in 0.8 - the ``s.in_()``/``~s.in_()`` operators
|
||||
should be used when IN/NOT IN are desired.
|
||||
.. deprecated:: 0.8
|
||||
The MSSQL dialect contains a legacy behavior whereby comparing
|
||||
a scalar select to a value using the ``=`` or ``!=`` operator
|
||||
will resolve to IN or NOT IN, respectively. This behavior
|
||||
will be removed in 0.8 - the ``s.in_()``/``~s.in_()`` operators
|
||||
should be used when IN/NOT IN are desired.
|
||||
|
||||
For the time being, the existing behavior prevents a comparison
|
||||
between scalar select and another value that actually wants to use ``=``.
|
||||
@@ -689,18 +690,22 @@ class MSExecutionContext(default.DefaultExecutionContext):
|
||||
not self.executemany
|
||||
|
||||
if self._enable_identity_insert:
|
||||
self.cursor.execute("SET IDENTITY_INSERT %s ON" %
|
||||
self.dialect.identifier_preparer.format_table(tbl))
|
||||
self.root_connection._cursor_execute(self.cursor,
|
||||
"SET IDENTITY_INSERT %s ON" %
|
||||
self.dialect.identifier_preparer.format_table(tbl),
|
||||
())
|
||||
|
||||
def post_exec(self):
|
||||
"""Disable IDENTITY_INSERT if enabled."""
|
||||
|
||||
conn = self.root_connection
|
||||
if self._select_lastrowid:
|
||||
if self.dialect.use_scope_identity:
|
||||
self.cursor.execute(
|
||||
"SELECT scope_identity() AS lastrowid", ())
|
||||
conn._cursor_execute(self.cursor,
|
||||
"SELECT scope_identity() AS lastrowid", ())
|
||||
else:
|
||||
self.cursor.execute("SELECT @@identity AS lastrowid", ())
|
||||
conn._cursor_execute(self.cursor,
|
||||
"SELECT @@identity AS lastrowid", ())
|
||||
# fetchall() ensures the cursor is consumed without closing it
|
||||
row = self.cursor.fetchall()[0]
|
||||
self._lastrowid = int(row[0])
|
||||
@@ -710,10 +715,11 @@ class MSExecutionContext(default.DefaultExecutionContext):
|
||||
self._result_proxy = base.FullyBufferedResultProxy(self)
|
||||
|
||||
if self._enable_identity_insert:
|
||||
self.cursor.execute(
|
||||
conn._cursor_execute(self.cursor,
|
||||
"SET IDENTITY_INSERT %s OFF" %
|
||||
self.dialect.identifier_preparer.
|
||||
format_table(self.compiled.statement.table)
|
||||
format_table(self.compiled.statement.table),
|
||||
()
|
||||
)
|
||||
|
||||
def get_lastrowid(self):
|
||||
@@ -803,27 +809,28 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
so tries to wrap it in a subquery with ``row_number()`` criterion.
|
||||
|
||||
"""
|
||||
if not getattr(select, '_mssql_visit', None) and select._offset:
|
||||
if select._offset and not getattr(select, '_mssql_visit', None):
|
||||
# to use ROW_NUMBER(), an ORDER BY is required.
|
||||
orderby = self.process(select._order_by_clause)
|
||||
if not orderby:
|
||||
if not select._order_by_clause.clauses:
|
||||
raise exc.CompileError('MSSQL requires an order_by when '
|
||||
'using an offset.')
|
||||
|
||||
_offset = select._offset
|
||||
_limit = select._limit
|
||||
_order_by_clauses = select._order_by_clause.clauses
|
||||
select = select._generate()
|
||||
select._mssql_visit = True
|
||||
select = select.column(
|
||||
sql.literal_column("ROW_NUMBER() OVER (ORDER BY %s)" \
|
||||
% orderby).label("mssql_rn")
|
||||
sql.func.ROW_NUMBER().over(order_by=_order_by_clauses)
|
||||
.label("mssql_rn")
|
||||
).order_by(None).alias()
|
||||
|
||||
mssql_rn = sql.column('mssql_rn')
|
||||
limitselect = sql.select([c for c in select.c if
|
||||
c.key!='mssql_rn'])
|
||||
limitselect.append_whereclause(mssql_rn> _offset)
|
||||
c.key != 'mssql_rn'])
|
||||
limitselect.append_whereclause(mssql_rn > _offset)
|
||||
if _limit is not None:
|
||||
limitselect.append_whereclause(mssql_rn<=(_limit + _offset))
|
||||
limitselect.append_whereclause(mssql_rn <= (_limit + _offset))
|
||||
return self.process(limitselect, iswrapper=True, **kwargs)
|
||||
else:
|
||||
return compiler.SQLCompiler.visit_select(self, select, **kwargs)
|
||||
@@ -875,7 +882,8 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
|
||||
if result_map is not None:
|
||||
result_map[column.name.lower()] = \
|
||||
(column.name, (column, ),
|
||||
(column.name, (column, column.name,
|
||||
column.key),
|
||||
column.type)
|
||||
|
||||
return super(MSSQLCompiler, self).\
|
||||
@@ -980,6 +988,22 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
||||
else:
|
||||
return ""
|
||||
|
||||
def update_from_clause(self, update_stmt,
|
||||
from_table, extra_froms,
|
||||
from_hints,
|
||||
**kw):
|
||||
"""Render the UPDATE..FROM clause specific to MSSQL.
|
||||
|
||||
In MSSQL, if the UPDATE statement involves an alias of the table to
|
||||
be updated, then the table itself must be added to the FROM list as
|
||||
well. Otherwise, it is optional. Here, we add it regardless.
|
||||
|
||||
"""
|
||||
return "FROM " + ', '.join(
|
||||
t._compiler_dispatch(self, asfrom=True,
|
||||
fromhints=from_hints, **kw)
|
||||
for t in [from_table] + extra_froms)
|
||||
|
||||
class MSSQLStrictCompiler(MSSQLCompiler):
|
||||
"""A subclass of MSSQLCompiler which disables the usage of bind
|
||||
parameters where not allowed natively by MS-SQL.
|
||||
@@ -1431,6 +1455,7 @@ class MSDialect(default.DefaultDialect):
|
||||
# Primary key constraints
|
||||
s = sql.select([C.c.column_name, TC.c.constraint_type],
|
||||
sql.and_(TC.c.constraint_name == C.c.constraint_name,
|
||||
TC.c.table_schema == C.c.table_schema,
|
||||
C.c.table_name == tablename,
|
||||
C.c.table_schema == current_schema)
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/information_schema.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -15,8 +15,10 @@ class CoerceUnicode(TypeDecorator):
|
||||
impl = Unicode
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
# Py2K
|
||||
if isinstance(value, str):
|
||||
value = value.decode(dialect.encoding)
|
||||
# end Py2K
|
||||
return value
|
||||
|
||||
schemata = Table("SCHEMATA", ischema,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/mxodbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -47,7 +47,7 @@ This behavior can be controlled via
|
||||
:meth:`~sqlalchemy.sql.expression.Executable.execution_options` using the
|
||||
``native_odbc_execute`` flag with a value of ``True`` or ``False``, where a
|
||||
value of ``True`` will unconditionally use native bind parameters and a value
|
||||
of ``False`` will uncondtionally use string-escaped parameters.
|
||||
of ``False`` will unconditionally use string-escaped parameters.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/pymssql.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/pyodbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -80,6 +80,34 @@ the python shell. For example::
|
||||
>>> urllib.quote_plus('dsn=mydsn;Database=db')
|
||||
'dsn%3Dmydsn%3BDatabase%3Ddb'
|
||||
|
||||
Unicode Binds
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
The current state of PyODBC on a unix backend with FreeTDS and/or
|
||||
EasySoft is poor regarding unicode; different OS platforms and versions of UnixODBC
|
||||
versus IODBC versus FreeTDS/EasySoft versus PyODBC itself dramatically
|
||||
alter how strings are received. The PyODBC dialect attempts to use all the information
|
||||
it knows to determine whether or not a Python unicode literal can be
|
||||
passed directly to the PyODBC driver or not; while SQLAlchemy can encode
|
||||
these to bytestrings first, some users have reported that PyODBC mis-handles
|
||||
bytestrings for certain encodings and requires a Python unicode object,
|
||||
while the author has observed widespread cases where a Python unicode
|
||||
is completely misinterpreted by PyODBC, particularly when dealing with
|
||||
the information schema tables used in table reflection, and the value
|
||||
must first be encoded to a bytestring.
|
||||
|
||||
It is for this reason that whether or not unicode literals for bound
|
||||
parameters be sent to PyODBC can be controlled using the
|
||||
``supports_unicode_binds`` parameter to ``create_engine()``. When
|
||||
left at its default of ``None``, the PyODBC dialect will use its
|
||||
best guess as to whether or not the driver deals with unicode literals
|
||||
well. When ``False``, unicode literals will be encoded first, and when
|
||||
``True`` unicode literals will be passed straight through. This is an interim
|
||||
flag that hopefully should not be needed when the unicode situation stabilizes
|
||||
for unix + PyODBC.
|
||||
|
||||
.. versionadded:: 0.7.7
|
||||
``supports_unicode_binds`` parameter to ``create_engine()``\ .
|
||||
|
||||
"""
|
||||
|
||||
@@ -217,7 +245,8 @@ class MSDialect_pyodbc(PyODBCConnector, MSDialect):
|
||||
def __init__(self, description_encoding='latin-1', **params):
|
||||
super(MSDialect_pyodbc, self).__init__(**params)
|
||||
self.description_encoding = description_encoding
|
||||
self.use_scope_identity = self.dbapi and \
|
||||
self.use_scope_identity = self.use_scope_identity and \
|
||||
self.dbapi and \
|
||||
hasattr(self.dbapi.Cursor, 'nextset')
|
||||
self._need_decimal_fix = self.dbapi and \
|
||||
self._dbapi_version() < (2, 1, 8)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/zxjdbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# mysql/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.mysql import base, mysqldb, oursql, \
|
||||
pyodbc, zxjdbc, mysqlconnector, pymysql
|
||||
pyodbc, zxjdbc, mysqlconnector, pymysql,\
|
||||
gaerdbms
|
||||
|
||||
# default dialect
|
||||
base.dialect = mysqldb.dialect
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -9,29 +9,11 @@
|
||||
Supported Versions and Features
|
||||
-------------------------------
|
||||
|
||||
SQLAlchemy supports 6 major MySQL versions: 3.23, 4.0, 4.1, 5.0, 5.1 and 6.0,
|
||||
with capabilities increasing with more modern servers.
|
||||
|
||||
Versions 4.1 and higher support the basic SQL functionality that SQLAlchemy
|
||||
uses in the ORM and SQL expressions. These versions pass the applicable tests
|
||||
in the suite 100%. No heroic measures are taken to work around major missing
|
||||
SQL features- if your server version does not support sub-selects, for
|
||||
SQLAlchemy supports MySQL starting with version 4.1 through modern releases.
|
||||
However, no heroic measures are taken to work around major missing
|
||||
SQL features - if your server version does not support sub-selects, for
|
||||
example, they won't work in SQLAlchemy either.
|
||||
|
||||
Most available DBAPI drivers are supported; see below.
|
||||
|
||||
===================================== ===============
|
||||
Feature Minimum Version
|
||||
===================================== ===============
|
||||
sqlalchemy.orm 4.1.1
|
||||
Table Reflection 3.23.x
|
||||
DDL Generation 4.1.1
|
||||
utf8/Full Unicode Connections 4.1.1
|
||||
Transactions 3.23.15
|
||||
Two-Phase Transactions 5.0.3
|
||||
Nested Transactions 5.0.3
|
||||
===================================== ===============
|
||||
|
||||
See the official MySQL documentation for detailed information about features
|
||||
supported in any given server release.
|
||||
|
||||
@@ -49,13 +31,16 @@ been idle for eight hours or more. To circumvent having this issue, use the
|
||||
|
||||
engine = create_engine('mysql+mysqldb://...', pool_recycle=3600)
|
||||
|
||||
.. _mysql_storage_engines:
|
||||
|
||||
Storage Engines
|
||||
---------------
|
||||
|
||||
Most MySQL server installations have a default table type of ``MyISAM``, a
|
||||
non-transactional table type. During a transaction, non-transactional storage
|
||||
engines do not participate and continue to store table changes in autocommit
|
||||
mode. For fully atomic transactions, all participating tables must use a
|
||||
mode. For fully atomic transactions as well as support for foreign key
|
||||
constraints, all participating tables must use a
|
||||
transactional engine such as ``InnoDB``, ``Falcon``, ``SolidDB``, `PBXT`, etc.
|
||||
|
||||
Storage engines can be elected when creating tables in SQLAlchemy by supplying
|
||||
@@ -68,6 +53,10 @@ creation option can be specified in this syntax::
|
||||
mysql_charset='utf8'
|
||||
)
|
||||
|
||||
.. seealso::
|
||||
|
||||
`The InnoDB Storage Engine <http://dev.mysql.com/doc/refman/5.0/en/innodb-storage-engine.html>`_ - on the MySQL website.
|
||||
|
||||
Case Sensitivity and Table Reflection
|
||||
-------------------------------------
|
||||
|
||||
@@ -99,7 +88,7 @@ every new connection. Valid values for this parameter are
|
||||
isolation_level="READ UNCOMMITTED"
|
||||
)
|
||||
|
||||
(new in 0.7.6)
|
||||
.. versionadded:: 0.7.6
|
||||
|
||||
Keys
|
||||
----
|
||||
@@ -185,6 +174,24 @@ available.
|
||||
|
||||
update(..., mysql_limit=10)
|
||||
|
||||
rowcount Support
|
||||
----------------
|
||||
|
||||
SQLAlchemy standardizes the DBAPI ``cursor.rowcount`` attribute to be the
|
||||
usual definition of "number of rows matched by an UPDATE or DELETE" statement.
|
||||
This is in contradiction to the default setting on most MySQL DBAPI drivers,
|
||||
which is "number of rows actually modified/deleted". For this reason, the
|
||||
SQLAlchemy MySQL dialects always set the ``constants.CLIENT.FOUND_ROWS`` flag,
|
||||
or whatever is equivalent for the DBAPI in use, on connect, unless the flag value
|
||||
is overridden using DBAPI-specific options
|
||||
(such as ``client_flag`` for the MySQL-Python driver, ``found_rows`` for the
|
||||
OurSQL driver).
|
||||
|
||||
See also:
|
||||
|
||||
:attr:`.ResultProxy.rowcount`
|
||||
|
||||
|
||||
CAST Support
|
||||
------------
|
||||
|
||||
@@ -344,9 +351,9 @@ class _FloatType(_NumericType, sqltypes.Float):
|
||||
(precision is None and scale is not None) or
|
||||
(precision is not None and scale is None)
|
||||
):
|
||||
raise exc.ArgumentError(
|
||||
"You must specify both precision and scale or omit "
|
||||
"both altogether.")
|
||||
raise exc.ArgumentError(
|
||||
"You must specify both precision and scale or omit "
|
||||
"both altogether.")
|
||||
|
||||
super(_FloatType, self).__init__(precision=precision, asdecimal=asdecimal, **kw)
|
||||
self.scale = scale
|
||||
@@ -1273,11 +1280,11 @@ class MySQLCompiler(compiler.SQLCompiler):
|
||||
def visit_cast(self, cast, **kwargs):
|
||||
# No cast until 4, no decimals until 5.
|
||||
if not self.dialect._supports_cast:
|
||||
return self.process(cast.clause)
|
||||
return self.process(cast.clause.self_group())
|
||||
|
||||
type_ = self.process(cast.typeclause)
|
||||
if type_ is None:
|
||||
return self.process(cast.clause)
|
||||
return self.process(cast.clause.self_group())
|
||||
|
||||
return 'CAST(%s AS %s)' % (self.process(cast.clause), type_)
|
||||
|
||||
@@ -1395,8 +1402,12 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
||||
auto_inc_column is not list(table.primary_key)[0]:
|
||||
if constraint_string:
|
||||
constraint_string += ", \n\t"
|
||||
constraint_string += "KEY `idx_autoinc_%s`(`%s`)" % (auto_inc_column.name, \
|
||||
self.preparer.format_column(auto_inc_column))
|
||||
constraint_string += "KEY %s (%s)" % (
|
||||
self.preparer.quote(
|
||||
"idx_autoinc_%s" % auto_inc_column.name, None
|
||||
),
|
||||
self.preparer.format_column(auto_inc_column)
|
||||
)
|
||||
|
||||
return constraint_string
|
||||
|
||||
@@ -2017,7 +2028,6 @@ class MySQLDialect(default.DefaultDialect):
|
||||
|
||||
@reflection.cache
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
charset = self._connection_charset
|
||||
if self.server_version_info < (5, 0, 2):
|
||||
raise NotImplementedError
|
||||
if schema is None:
|
||||
@@ -2028,7 +2038,7 @@ class MySQLDialect(default.DefaultDialect):
|
||||
rp = connection.execute("SHOW FULL TABLES FROM %s" %
|
||||
self.identifier_preparer.quote_identifier(schema))
|
||||
return [row[0] for row in self._compat_fetchall(rp, charset=charset)\
|
||||
if row[1] == 'VIEW']
|
||||
if row[1] in ('VIEW', 'SYSTEM VIEW')]
|
||||
|
||||
@reflection.cache
|
||||
def get_table_options(self, connection, table_name, schema=None, **kw):
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# mysql/gaerdbms.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
"""Support for Google Cloud SQL on Google App Engine.
|
||||
|
||||
This dialect is based primarily on the :mod:`.mysql.mysqldb` dialect with minimal
|
||||
changes.
|
||||
|
||||
.. versionadded:: 0.7.8
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
Connect string format::
|
||||
|
||||
mysql+gaerdbms:///<dbname>
|
||||
|
||||
E.g.::
|
||||
|
||||
create_engine('mysql+gaerdbms:///mydb',
|
||||
connect_args={"instance":"instancename"})
|
||||
|
||||
Pooling
|
||||
-------
|
||||
|
||||
Google App Engine connections appear to be randomly recycled,
|
||||
so the dialect does not pool connections. The :class:`.NullPool`
|
||||
implementation is installed within the :class:`.Engine` by
|
||||
default.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.mysql.mysqldb import MySQLDialect_mysqldb
|
||||
from sqlalchemy.pool import NullPool
|
||||
import re
|
||||
|
||||
|
||||
class MySQLDialect_gaerdbms(MySQLDialect_mysqldb):
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
# from django:
|
||||
# http://code.google.com/p/googleappengine/source/
|
||||
# browse/trunk/python/google/storage/speckle/
|
||||
# python/django/backend/base.py#118
|
||||
# see also [ticket:2649]
|
||||
# see also http://stackoverflow.com/q/14224679/34549
|
||||
from google.appengine.api import apiproxy_stub_map
|
||||
|
||||
if apiproxy_stub_map.apiproxy.GetStub('rdbms'):
|
||||
from google.storage.speckle.python.api import rdbms_apiproxy
|
||||
return rdbms_apiproxy
|
||||
else:
|
||||
from google.storage.speckle.python.api import rdbms_googleapi
|
||||
return rdbms_googleapi
|
||||
|
||||
@classmethod
|
||||
def get_pool_class(cls, url):
|
||||
# Cloud SQL connections die at any moment
|
||||
return NullPool
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args()
|
||||
# 'dsn' and 'instance' are because we are skipping
|
||||
# the traditional google.api.rdbms wrapper
|
||||
|
||||
opts['dsn'] = ''
|
||||
opts['instance'] = url.query['instance']
|
||||
return [], opts
|
||||
|
||||
def _extract_error_code(self, exception):
|
||||
match = re.compile(r"^(\d+):").match(str(exception))
|
||||
# The rdbms api will wrap then re-raise some types of errors
|
||||
# making this regex return no matches.
|
||||
if match:
|
||||
code = match.group(1)
|
||||
else:
|
||||
code = None
|
||||
if code:
|
||||
return int(code)
|
||||
|
||||
dialect = MySQLDialect_gaerdbms
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/mysqlconnector.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -105,15 +105,11 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
dbapi_con = connection.connection
|
||||
|
||||
from mysql.connector.constants import ClientFlag
|
||||
dbapi_con.set_client_flag(ClientFlag.FOUND_ROWS)
|
||||
|
||||
version = dbapi_con.get_server_version()
|
||||
return tuple(version)
|
||||
|
||||
def _detect_charset(self, connection):
|
||||
return connection.connection.get_characterset_info()
|
||||
return connection.connection.charset
|
||||
|
||||
def _extract_error_code(self, exception):
|
||||
return exception.errno
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/mysqldb.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/oursql.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -208,6 +208,7 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
util.coerce_kw_type(opts, 'port', int)
|
||||
util.coerce_kw_type(opts, 'compress', bool)
|
||||
util.coerce_kw_type(opts, 'autoping', bool)
|
||||
util.coerce_kw_type(opts, 'raise_on_warnings', bool)
|
||||
|
||||
util.coerce_kw_type(opts, 'default_charset', bool)
|
||||
if opts.pop('default_charset', False):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/pymysql.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/pyodbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# mysql/zxjdbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# oracle/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -12,7 +12,7 @@ from sqlalchemy.dialects.oracle.base import \
|
||||
VARCHAR, NVARCHAR, CHAR, DATE, DATETIME, NUMBER,\
|
||||
BLOB, BFILE, CLOB, NCLOB, TIMESTAMP, RAW,\
|
||||
FLOAT, DOUBLE_PRECISION, LONG, dialect, INTERVAL,\
|
||||
VARCHAR2, NVARCHAR2
|
||||
VARCHAR2, NVARCHAR2, ROWID
|
||||
|
||||
|
||||
__all__ = (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# oracle/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -63,10 +63,12 @@ used on the SQLAlchemy side.
|
||||
Unicode
|
||||
-------
|
||||
|
||||
SQLAlchemy 0.6 uses the "native unicode" mode provided as of cx_oracle 5. cx_oracle 5.0.2
|
||||
or greater is recommended for support of NCLOB. If not using cx_oracle 5, the NLS_LANG
|
||||
environment variable needs to be set in order for the oracle client library to use
|
||||
proper encoding, such as "AMERICAN_AMERICA.UTF8".
|
||||
.. versionchanged:: 0.6
|
||||
SQLAlchemy uses the "native unicode" mode provided as of cx_oracle 5.
|
||||
cx_oracle 5.0.2 or greater is recommended for support of NCLOB.
|
||||
If not using cx_oracle 5, the NLS_LANG environment variable needs
|
||||
to be set in order for the oracle client library to use proper encoding,
|
||||
such as "AMERICAN_AMERICA.UTF8".
|
||||
|
||||
Also note that Oracle supports unicode data through the NVARCHAR and NCLOB data types.
|
||||
When using the SQLAlchemy Unicode and UnicodeText types, these DDL types will be used
|
||||
@@ -564,7 +566,7 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
if not self.dialect.use_binds_for_limits:
|
||||
max_row = sql.literal_column("%d" % max_row)
|
||||
limitselect.append_whereclause(
|
||||
sql.literal_column("ROWNUM")<=max_row)
|
||||
sql.literal_column("ROWNUM") <= max_row)
|
||||
|
||||
# If needed, add the ora_rn, and wrap again with offset.
|
||||
if select._offset is None:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# oracle/cx_oracle.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -71,8 +71,40 @@ To disable this processing, pass ``auto_convert_lobs=False`` to :func:`create_en
|
||||
Two Phase Transaction Support
|
||||
-----------------------------
|
||||
|
||||
Two Phase transactions are implemented using XA transactions. Success has been reported
|
||||
with this feature but it should be regarded as experimental.
|
||||
Two Phase transactions are implemented using XA transactions, and are known
|
||||
to work in a rudimental fashion with recent versions of cx_Oracle
|
||||
as of SQLAlchemy 0.8.0b2, 0.7.10. However, the mechanism is not yet
|
||||
considered to be robust and should still be regarded as experimental.
|
||||
|
||||
In particular, the cx_Oracle DBAPI as recently as 5.1.2 has a bug regarding
|
||||
two phase which prevents
|
||||
a particular DBAPI connection from being consistently usable in both
|
||||
prepared transactions as well as traditional DBAPI usage patterns; therefore
|
||||
once a particular connection is used via :meth:`.Connection.begin_prepared`,
|
||||
all subsequent usages of the underlying DBAPI connection must be within
|
||||
the context of prepared transactions.
|
||||
|
||||
The default behavior of :class:`.Engine` is to maintain a pool of DBAPI
|
||||
connections. Therefore, due to the above glitch, a DBAPI connection that has
|
||||
been used in a two-phase operation, and is then returned to the pool, will
|
||||
not be usable in a non-two-phase context. To avoid this situation,
|
||||
the application can make one of several choices:
|
||||
|
||||
* Disable connection pooling using :class:`.NullPool`
|
||||
|
||||
* Ensure that the particular :class:`.Engine` in use is only used
|
||||
for two-phase operations. A :class:`.Engine` bound to an ORM
|
||||
:class:`.Session` which includes ``twophase=True`` will consistently
|
||||
use the two-phase transaction style.
|
||||
|
||||
* For ad-hoc two-phase operations without disabling pooling, the DBAPI
|
||||
connection in use can be evicted from the connection pool using the
|
||||
:class:`.Connection.detach` method.
|
||||
|
||||
.. versionchanged:: 0.8.0b2,0.7.10
|
||||
Support for cx_oracle prepared transactions has been implemented
|
||||
and tested.
|
||||
|
||||
|
||||
Precision Numerics
|
||||
------------------
|
||||
@@ -98,7 +130,8 @@ to :func:`.create_engine`::
|
||||
engine = create_engine("oracle+cx_oracle://dsn",
|
||||
coerce_to_decimal=False)
|
||||
|
||||
The ``coerce_to_decimal`` flag is new in 0.7.6.
|
||||
.. 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;
|
||||
@@ -128,21 +161,23 @@ environment variable. Upon first connection, the dialect runs a
|
||||
test to determine the current "decimal" character, which can be
|
||||
a comma "," for european locales. From that point forward the
|
||||
outputtypehandler uses that character to represent a decimal
|
||||
point (this behavior is new in version 0.6.6). Note that
|
||||
cx_oracle 5.0.3 or greater is required when dealing with
|
||||
numerics with locale settings that don't use a period "." as the
|
||||
decimal character.
|
||||
point. Note that cx_oracle 5.0.3 or greater is required
|
||||
when dealing with numerics with locale settings that don't use
|
||||
a period "." as the decimal character.
|
||||
|
||||
.. versionchanged:: 0.6.6
|
||||
The outputtypehandler uses a comma "," character to represent
|
||||
a decimal point.
|
||||
|
||||
.. _OCI: http://www.oracle.com/technetwork/database/features/oci/index.html
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.oracle.base import OracleCompiler, OracleDialect, \
|
||||
RESERVED_WORDS, OracleExecutionContext
|
||||
OracleExecutionContext
|
||||
from sqlalchemy.dialects.oracle import base as oracle
|
||||
from sqlalchemy.engine import base
|
||||
from sqlalchemy import types as sqltypes, util, exc, processors
|
||||
from datetime import datetime
|
||||
import random
|
||||
import collections
|
||||
from sqlalchemy.util.compat import decimal
|
||||
@@ -248,6 +283,13 @@ class _OracleText(_LOBMixin, sqltypes.Text):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.CLOB
|
||||
|
||||
class _OracleLong(oracle.LONG):
|
||||
# a raw LONG is a text type, but does *not*
|
||||
# get the LobMixin with cx_oracle.
|
||||
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.LONG_STRING
|
||||
|
||||
class _OracleString(_NativeUnicodeMixin, sqltypes.String):
|
||||
pass
|
||||
|
||||
@@ -296,13 +338,13 @@ class _OracleRowid(oracle.ROWID):
|
||||
return dbapi.ROWID
|
||||
|
||||
class OracleCompiler_cx_oracle(OracleCompiler):
|
||||
def bindparam_string(self, name):
|
||||
def bindparam_string(self, name, **kw):
|
||||
if self.preparer._bindparam_requires_quotes(name):
|
||||
quoted_name = '"%s"' % name
|
||||
self._quoted_bind_names[name] = quoted_name
|
||||
return OracleCompiler.bindparam_string(self, quoted_name)
|
||||
return OracleCompiler.bindparam_string(self, quoted_name, **kw)
|
||||
else:
|
||||
return OracleCompiler.bindparam_string(self, name)
|
||||
return OracleCompiler.bindparam_string(self, name, **kw)
|
||||
|
||||
|
||||
class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
@@ -333,7 +375,7 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
# on String, including that outparams/RETURNING
|
||||
# breaks for varchars
|
||||
self.set_input_sizes(quoted_bind_names,
|
||||
exclude_types=self.dialect._cx_oracle_string_types
|
||||
exclude_types=self.dialect._cx_oracle_exclude_setinputsizes
|
||||
)
|
||||
|
||||
# if a single execute, check for outparams
|
||||
@@ -467,6 +509,11 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
sqltypes.String : _OracleString,
|
||||
sqltypes.UnicodeText : _OracleUnicodeText,
|
||||
sqltypes.CHAR : _OracleChar,
|
||||
|
||||
# a raw LONG is a text type, but does *not*
|
||||
# get the LobMixin with cx_oracle.
|
||||
oracle.LONG: _OracleLong,
|
||||
|
||||
sqltypes.Integer : _OracleInteger, # this is only needed for OUT parameters.
|
||||
# it would be nice if we could not use it otherwise.
|
||||
oracle.RAW: _OracleRaw,
|
||||
@@ -503,6 +550,7 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
getattr(self.dbapi, name, None) for name in names
|
||||
]).difference([None])
|
||||
|
||||
self._cx_oracle_exclude_setinputsizes = types("STRING", "UNICODE")
|
||||
self._cx_oracle_string_types = types("STRING", "UNICODE", "NCLOB", "CLOB")
|
||||
self._cx_oracle_unicode_types = types("UNICODE", "NCLOB")
|
||||
self._cx_oracle_binary_types = types("BFILE", "CLOB", "NCLOB", "BLOB")
|
||||
@@ -551,13 +599,13 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
# expect encoded strings or unicodes, etc.
|
||||
self.dbapi_type_map = {
|
||||
self.dbapi.CLOB: oracle.CLOB(),
|
||||
self.dbapi.NCLOB:oracle.NCLOB(),
|
||||
self.dbapi.NCLOB: oracle.NCLOB(),
|
||||
self.dbapi.BLOB: oracle.BLOB(),
|
||||
self.dbapi.BINARY: oracle.RAW(),
|
||||
}
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
import cx_Oracle
|
||||
cx_Oracle = __import__('cx_Oracle')
|
||||
return cx_Oracle
|
||||
|
||||
def initialize(self, connection):
|
||||
@@ -739,15 +787,23 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
connection.connection.begin(*xid)
|
||||
|
||||
def do_prepare_twophase(self, connection, xid):
|
||||
connection.connection.prepare()
|
||||
result = connection.connection.prepare()
|
||||
connection.info['cx_oracle_prepared'] = result
|
||||
|
||||
def do_rollback_twophase(self, connection, xid, is_prepared=True, recover=False):
|
||||
def do_rollback_twophase(self, connection, xid, is_prepared=True,
|
||||
recover=False):
|
||||
self.do_rollback(connection.connection)
|
||||
|
||||
def do_commit_twophase(self, connection, xid, is_prepared=True, recover=False):
|
||||
self.do_commit(connection.connection)
|
||||
def do_commit_twophase(self, connection, xid, is_prepared=True,
|
||||
recover=False):
|
||||
if not is_prepared:
|
||||
self.do_commit(connection.connection)
|
||||
else:
|
||||
oci_prepared = connection.info['cx_oracle_prepared']
|
||||
if oci_prepared:
|
||||
self.do_commit(connection.connection)
|
||||
|
||||
def do_recover_twophase(self, connection):
|
||||
pass
|
||||
connection.info.pop('cx_oracle_prepared', None)
|
||||
|
||||
dialect = OracleDialect_cx_oracle
|
||||
@@ -1,5 +1,5 @@
|
||||
# oracle/zxjdbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# dialects/postgres.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -82,9 +82,10 @@ to inspect the actual value using::
|
||||
|
||||
SHOW search_path;
|
||||
|
||||
Prior to version 0.7.3, cross-schema foreign keys when the schemas
|
||||
were also in the ``search_path`` could make an incorrect assumption
|
||||
if the schemas were explicitly stated on each :class:`.Table`.
|
||||
.. versionchanged:: 0.7.3
|
||||
Prior to this version, cross-schema foreign keys when the schemas
|
||||
were also in the ``search_path`` could make an incorrect assumption
|
||||
if the schemas were explicitly stated on each :class:`.Table`.
|
||||
|
||||
Background on PG's ``search_path`` is at:
|
||||
http://www.postgresql.org/docs/9.0/static/ddl-schemas.html#DDL-SCHEMAS-PATH
|
||||
@@ -137,7 +138,7 @@ Operator Classes
|
||||
PostgreSQL allows the specification of an *operator class* for each column of
|
||||
an index (see http://www.postgresql.org/docs/8.3/interactive/indexes-opclass.html).
|
||||
The :class:`.Index` construct allows these to be specified via the ``postgresql_ops``
|
||||
keyword argument (new as of SQLAlchemy 0.7.2)::
|
||||
keyword argument::
|
||||
|
||||
Index('my_index', my_table.c.id, my_table.c.data,
|
||||
postgresql_ops={
|
||||
@@ -145,6 +146,9 @@ keyword argument (new as of SQLAlchemy 0.7.2)::
|
||||
'id': 'int4_ops'
|
||||
})
|
||||
|
||||
.. versionadded:: 0.7.2
|
||||
``postgresql_ops`` keyword argument to :class:`.Index` construct.
|
||||
|
||||
Note that the keys in the ``postgresql_ops`` dictionary are the "key" name of
|
||||
the :class:`.Column`, i.e. the name used to access it from the ``.c`` collection
|
||||
of :class:`.Table`, which can be configured to be different than the actual
|
||||
@@ -348,21 +352,24 @@ class ARRAY(sqltypes.MutableType, sqltypes.Concatenable, sqltypes.TypeEngine):
|
||||
class should be considered mutable - this enables
|
||||
"mutable types" mode in the ORM. Be sure to read the
|
||||
notes for :class:`.MutableType` regarding ORM
|
||||
performance implications (default changed from ``True`` in
|
||||
0.7.0).
|
||||
performance implications.
|
||||
|
||||
.. note::
|
||||
.. versionchanged:: 0.7.0
|
||||
Default changed from ``True``\ .
|
||||
|
||||
This functionality is now superseded by the
|
||||
``sqlalchemy.ext.mutable`` extension described in
|
||||
:ref:`mutable_toplevel`.
|
||||
.. versionchanged:: 0.7
|
||||
This functionality is now superseded by the
|
||||
``sqlalchemy.ext.mutable`` extension described in
|
||||
:ref:`mutable_toplevel`.
|
||||
|
||||
:param as_tuple=False: Specify whether return results
|
||||
should be converted to tuples from lists. DBAPIs such
|
||||
as psycopg2 return lists by default. When tuples are
|
||||
returned, the results are hashable. This flag can only
|
||||
be set to ``True`` when ``mutable`` is set to
|
||||
``False``. (new in 0.6.5)
|
||||
``False``.
|
||||
|
||||
.. versionadded:: 0.6.5
|
||||
|
||||
"""
|
||||
if isinstance(item_type, ARRAY):
|
||||
@@ -489,7 +496,8 @@ class ENUM(sqltypes.Enum):
|
||||
the :meth:`~.postgresql.ENUM.create` and
|
||||
:meth:`~.postgresql.ENUM.drop` methods can
|
||||
be used to emit SQL to a target bind.
|
||||
(new in 0.7.4)
|
||||
|
||||
.. versionadded:: 0.7.4
|
||||
|
||||
"""
|
||||
self.create_type = kw.pop("create_type", True)
|
||||
@@ -675,6 +683,10 @@ class PGCompiler(compiler.SQLCompiler):
|
||||
def for_update_clause(self, select):
|
||||
if select.for_update == 'nowait':
|
||||
return " FOR UPDATE NOWAIT"
|
||||
elif select.for_update == 'read':
|
||||
return " FOR SHARE"
|
||||
elif select.for_update == 'read_nowait':
|
||||
return " FOR SHARE NOWAIT"
|
||||
else:
|
||||
return super(PGCompiler, self).for_update_clause(select)
|
||||
|
||||
@@ -1040,7 +1052,7 @@ class PGDialect(default.DefaultDialect):
|
||||
if is_prepared:
|
||||
if recover:
|
||||
#FIXME: ugly hack to get out of transaction
|
||||
# context when commiting recoverable transactions
|
||||
# context when committing recoverable transactions
|
||||
# Must find out a way how to make the dbapi not
|
||||
# open a transaction.
|
||||
connection.execute("ROLLBACK")
|
||||
@@ -1439,20 +1451,33 @@ class PGDialect(default.DefaultDialect):
|
||||
table_oid = self.get_table_oid(connection, table_name, schema,
|
||||
info_cache=kw.get('info_cache'))
|
||||
|
||||
PK_SQL = """
|
||||
SELECT a.attname
|
||||
if self.server_version_info < (8, 4):
|
||||
# unnest() and generate_subscripts() both introduced in
|
||||
# version 8.4
|
||||
PK_SQL = """
|
||||
SELECT a.attname
|
||||
FROM
|
||||
pg_class t
|
||||
join pg_index ix on t.oid = ix.indrelid
|
||||
join pg_attribute a
|
||||
on t.oid=a.attrelid and a.attnum=ANY(ix.indkey)
|
||||
WHERE
|
||||
t.oid = :table_oid and
|
||||
ix.indisprimary = 't'
|
||||
ORDER BY
|
||||
a.attnum
|
||||
"""
|
||||
t = sql.text(PK_SQL, typemap={'attname':sqltypes.Unicode})
|
||||
WHERE
|
||||
t.oid = :table_oid and ix.indisprimary = 't'
|
||||
ORDER BY a.attnum
|
||||
"""
|
||||
else:
|
||||
PK_SQL = """
|
||||
SELECT a.attname
|
||||
FROM pg_attribute a JOIN (
|
||||
SELECT unnest(ix.indkey) attnum,
|
||||
generate_subscripts(ix.indkey, 1) ord
|
||||
FROM pg_index ix
|
||||
WHERE ix.indrelid = :table_oid AND ix.indisprimary
|
||||
) k ON a.attnum=k.attnum
|
||||
WHERE a.attrelid = :table_oid
|
||||
ORDER BY k.ord
|
||||
"""
|
||||
t = sql.text(PK_SQL, typemap={'attname': sqltypes.Unicode})
|
||||
c = connection.execute(t, table_oid=table_oid)
|
||||
primary_keys = [r[0] for r in c.fetchall()]
|
||||
return primary_keys
|
||||
@@ -1610,7 +1635,6 @@ class PGDialect(default.DefaultDialect):
|
||||
e.enumlabel as "label"
|
||||
FROM pg_catalog.pg_type t
|
||||
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
||||
LEFT JOIN pg_catalog.pg_constraint r ON t.oid = r.contypid
|
||||
LEFT JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid
|
||||
WHERE t.typtype = 'e'
|
||||
ORDER BY "name", e.oid -- e.oid gives us label order
|
||||
@@ -1625,8 +1649,8 @@ class PGDialect(default.DefaultDialect):
|
||||
for enum in c.fetchall():
|
||||
if enum['visible']:
|
||||
# 'visible' just means whether or not the enum is in a
|
||||
# schema that's on the search path -- or not overriden by
|
||||
# a schema with higher presedence. If it's not visible,
|
||||
# schema that's on the search path -- or not overridden by
|
||||
# a schema with higher precedence. If it's not visible,
|
||||
# it will be prefixed with the schema-name when it's used.
|
||||
name = enum['name']
|
||||
else:
|
||||
@@ -1652,7 +1676,6 @@ class PGDialect(default.DefaultDialect):
|
||||
n.nspname as "schema"
|
||||
FROM pg_catalog.pg_type t
|
||||
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
||||
LEFT JOIN pg_catalog.pg_constraint r ON t.oid = r.contypid
|
||||
WHERE t.typtype = 'd'
|
||||
"""
|
||||
|
||||
@@ -1665,8 +1688,8 @@ class PGDialect(default.DefaultDialect):
|
||||
attype = re.search('([^\(]+)', domain['attype']).group(1)
|
||||
if domain['visible']:
|
||||
# 'visible' just means whether or not the domain is in a
|
||||
# schema that's on the search path -- or not overriden by
|
||||
# a schema with higher presedence. If it's not visible,
|
||||
# schema that's on the search path -- or not overridden by
|
||||
# a schema with higher precedence. If it's not visible,
|
||||
# it will be prefixed with the schema-name when it's used.
|
||||
name = domain['name']
|
||||
else:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/pg8000.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/psycopg2.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -96,8 +96,9 @@ on all new connections based on the value passed to
|
||||
engine = create_engine("postgresql://user:pass@host/dbname", client_encoding='utf8')
|
||||
|
||||
This overrides the encoding specified in the Postgresql client configuration.
|
||||
The psycopg2-specific ``client_encoding`` parameter to :func:`.create_engine` is new as of
|
||||
SQLAlchemy 0.7.3.
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
The psycopg2-specific ``client_encoding`` parameter to :func:`.create_engine`.
|
||||
|
||||
SQLAlchemy can also be instructed to skip the usage of the psycopg2
|
||||
``UNICODE`` extension and to instead utilize it's own unicode encode/decode
|
||||
@@ -371,7 +372,8 @@ class PGDialect_psycopg2(PGDialect):
|
||||
# these error messages from libpq: interfaces/libpq/fe-misc.c.
|
||||
# TODO: these are sent through gettext in libpq and we can't
|
||||
# check within other locales - consider using connection.closed
|
||||
return 'closed the connection' in str(e) or \
|
||||
return 'terminating connection' in str(e) or \
|
||||
'closed the connection' in str(e) or \
|
||||
'connection not open' in str(e) or \
|
||||
'could not receive data from server' in str(e)
|
||||
elif isinstance(e, self.dbapi.InterfaceError):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/pypostgresql.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -40,7 +40,7 @@ class PGDialect_pypostgresql(PGDialect):
|
||||
default_paramstyle = 'pyformat'
|
||||
|
||||
# requires trunk version to support sane rowcounts
|
||||
# TODO: use dbapi version information to set this flag appropariately
|
||||
# TODO: use dbapi version information to set this flag appropriately
|
||||
supports_sane_rowcount = True
|
||||
supports_sane_multi_rowcount = False
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# postgresql/zxjdbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sqlite/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sqlite/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -53,6 +53,74 @@ connection. Valid values for this parameter are ``SERIALIZABLE`` and
|
||||
See the section :ref:`pysqlite_serializable` for an important workaround
|
||||
when using serializable isolation with Pysqlite.
|
||||
|
||||
Database Locking Behavior / Concurrency
|
||||
---------------------------------------
|
||||
|
||||
Note that SQLite is not designed for a high level of concurrency. The database
|
||||
itself, being a file, is locked completely during write operations and within
|
||||
transactions, meaning exactly one connection has exclusive access to the database
|
||||
during this period - all other connections will be blocked during this time.
|
||||
|
||||
The Python DBAPI specification also calls for a connection model that is always
|
||||
in a transaction; there is no BEGIN method, only commit and rollback. This implies
|
||||
that a SQLite DBAPI driver would technically allow only serialized access to a
|
||||
particular database file at all times. The pysqlite driver attempts to ameliorate this by
|
||||
deferring the actual BEGIN statement until the first DML (INSERT, UPDATE, or
|
||||
DELETE) is received within a transaction. While this breaks serializable isolation,
|
||||
it at least delays the exclusive locking inherent in SQLite's design.
|
||||
|
||||
SQLAlchemy's default mode of usage with the ORM is known
|
||||
as "autocommit=False", which means the moment the :class:`.Session` begins to be
|
||||
used, a transaction is begun. As the :class:`.Session` is used, the autoflush
|
||||
feature, also on by default, will flush out pending changes to the database
|
||||
before each query. The effect of this is that a :class:`.Session` used in its
|
||||
default mode will often emit DML early on, long before the transaction is actually
|
||||
committed. This again will have the effect of serializing access to the SQLite
|
||||
database. If highly concurrent reads are desired against the SQLite database,
|
||||
it is advised that the autoflush feature be disabled, and potentially even
|
||||
that autocommit be re-enabled, which has the effect of each SQL statement and
|
||||
flush committing changes immediately.
|
||||
|
||||
For more information on SQLite's lack of concurrency by design, please
|
||||
see `Situations Where Another RDBMS May Work Better - High Concurrency <http://www.sqlite.org/whentouse.html>`_
|
||||
near the bottom of the page.
|
||||
|
||||
.. _sqlite_foreign_keys:
|
||||
|
||||
Foreign Key Support
|
||||
-------------------
|
||||
|
||||
SQLite supports FOREIGN KEY syntax when emitting CREATE statements for tables,
|
||||
however by default these constraints have no effect on the operation
|
||||
of the table.
|
||||
|
||||
Constraint checking on SQLite has three prerequisites:
|
||||
|
||||
* At least version 3.6.19 of SQLite must be in use
|
||||
* The SQLite libary must be compiled *without* the SQLITE_OMIT_FOREIGN_KEY
|
||||
or SQLITE_OMIT_TRIGGER symbols enabled.
|
||||
* The ``PRAGMA foreign_keys = ON`` statement must be emitted on all connections
|
||||
before use.
|
||||
|
||||
SQLAlchemy allows for the ``PRAGMA`` statement to be emitted automatically
|
||||
for new connections through the usage of events::
|
||||
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy import event
|
||||
|
||||
@event.listens_for(Engine, "connect")
|
||||
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||
cursor = dbapi_connection.cursor()
|
||||
cursor.execute("PRAGMA foreign_keys=ON")
|
||||
cursor.close()
|
||||
|
||||
.. seealso::
|
||||
|
||||
`SQLite Foreign Key Support <http://www.sqlite.org/foreignkeys.html>`_ -
|
||||
on the SQLite web site.
|
||||
|
||||
:ref:`event_toplevel` - SQLAlchemy event API.
|
||||
|
||||
"""
|
||||
|
||||
import datetime, re
|
||||
@@ -103,13 +171,13 @@ class DATETIME(_DateTimeMixin, sqltypes.DateTime):
|
||||
regexp=re.compile("(\d+)/(\d+)/(\d+) (\d+)-(\d+)-(\d+)(?:-(\d+))?")
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be appled to the
|
||||
:param storage_format: format string which will be applied to the
|
||||
tuple ``(value.year, value.month, value.day, value.hour,
|
||||
value.minute, value.second, value.microsecond)``, given a
|
||||
Python datetime.datetime() object.
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. The resulting match object is appled to
|
||||
incoming result rows. The resulting match object is applied to
|
||||
the Python datetime() constructor via ``*map(int,
|
||||
match_obj.groups(0))``.
|
||||
"""
|
||||
@@ -164,12 +232,12 @@ class DATE(_DateTimeMixin, sqltypes.Date):
|
||||
regexp=re.compile("(\d+)/(\d+)/(\d+)")
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be appled to the
|
||||
:param storage_format: format string which will be applied to the
|
||||
tuple ``(value.year, value.month, value.day)``,
|
||||
given a Python datetime.date() object.
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. The resulting match object is appled to
|
||||
incoming result rows. The resulting match object is applied to
|
||||
the Python date() constructor via ``*map(int,
|
||||
match_obj.groups(0))``.
|
||||
|
||||
@@ -221,12 +289,12 @@ class TIME(_DateTimeMixin, sqltypes.Time):
|
||||
regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?")
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be appled
|
||||
:param storage_format: format string which will be applied
|
||||
to the tuple ``(value.hour, value.minute, value.second,
|
||||
value.microsecond)``, given a Python datetime.time() object.
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. The resulting match object is appled to
|
||||
incoming result rows. The resulting match object is applied to
|
||||
the Python time() constructor via ``*map(int,
|
||||
match_obj.groups(0))``.
|
||||
|
||||
@@ -302,6 +370,9 @@ class SQLiteCompiler(compiler.SQLCompiler):
|
||||
def visit_now_func(self, fn, **kw):
|
||||
return "CURRENT_TIMESTAMP"
|
||||
|
||||
def visit_localtimestamp_func(self, func, **kw):
|
||||
return 'DATETIME(CURRENT_TIMESTAMP, "localtime")'
|
||||
|
||||
def visit_true(self, expr, **kw):
|
||||
return '1'
|
||||
|
||||
@@ -441,6 +512,22 @@ class SQLiteIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
result = self.quote_schema(index.table.schema, index.table.quote_schema) + "." + result
|
||||
return result
|
||||
|
||||
class SQLiteExecutionContext(default.DefaultExecutionContext):
|
||||
@util.memoized_property
|
||||
def _preserve_raw_colnames(self):
|
||||
return self.execution_options.get("sqlite_raw_colnames", False)
|
||||
|
||||
def _translate_colname(self, colname):
|
||||
# adjust for dotted column names. SQLite
|
||||
# in the case of UNION may store col names as
|
||||
# "tablename.colname"
|
||||
# in cursor.description
|
||||
if not self._preserve_raw_colnames and "." in colname:
|
||||
return colname.split(".")[1], colname
|
||||
else:
|
||||
return colname, None
|
||||
|
||||
|
||||
class SQLiteDialect(default.DefaultDialect):
|
||||
name = 'sqlite'
|
||||
supports_alter = False
|
||||
@@ -451,6 +538,7 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
supports_cast = True
|
||||
|
||||
default_paramstyle = 'qmark'
|
||||
execution_ctx_cls = SQLiteExecutionContext
|
||||
statement_compiler = SQLiteCompiler
|
||||
ddl_compiler = SQLiteDDLCompiler
|
||||
type_compiler = SQLiteTypeCompiler
|
||||
@@ -462,6 +550,8 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
supports_cast = True
|
||||
supports_default_values = True
|
||||
|
||||
_broken_fk_pragma_quotes = False
|
||||
|
||||
def __init__(self, isolation_level=None, native_datetime=False, **kwargs):
|
||||
default.DefaultDialect.__init__(self, **kwargs)
|
||||
self.isolation_level = isolation_level
|
||||
@@ -478,6 +568,12 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
self.supports_cast = \
|
||||
self.dbapi.sqlite_version_info >= (3, 2, 3)
|
||||
|
||||
# see http://www.sqlalchemy.org/trac/ticket/2568
|
||||
# as well as http://www.sqlite.org/src/info/600482d161
|
||||
self._broken_fk_pragma_quotes = \
|
||||
self.dbapi.sqlite_version_info < (3, 6, 14)
|
||||
|
||||
|
||||
_isolation_lookup = {
|
||||
'READ UNCOMMITTED':1,
|
||||
'SERIALIZABLE':0
|
||||
@@ -525,16 +621,6 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
else:
|
||||
return None
|
||||
|
||||
def _translate_colname(self, colname):
|
||||
# adjust for dotted column names. SQLite
|
||||
# in the case of UNION may store col names as
|
||||
# "tablename.colname"
|
||||
# in cursor.description
|
||||
if "." in colname:
|
||||
return colname.split(".")[1], colname
|
||||
else:
|
||||
return colname, None
|
||||
|
||||
@reflection.cache
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
if schema is not None:
|
||||
@@ -633,43 +719,50 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
c = _pragma_cursor(
|
||||
connection.execute("%stable_info(%s)" %
|
||||
(pragma, qtable)))
|
||||
found_table = False
|
||||
columns = []
|
||||
while True:
|
||||
row = c.fetchone()
|
||||
if row is None:
|
||||
break
|
||||
(name, type_, nullable, default, has_default, primary_key) = \
|
||||
(row[1], row[2].upper(), not row[3],
|
||||
row[4], row[4] is not None, row[5])
|
||||
name = re.sub(r'^\"|\"$', '', name)
|
||||
match = re.match(r'(\w+)(\(.*?\))?', type_)
|
||||
if match:
|
||||
coltype = match.group(1)
|
||||
args = match.group(2)
|
||||
else:
|
||||
coltype = "VARCHAR"
|
||||
args = ''
|
||||
try:
|
||||
coltype = self.ischema_names[coltype]
|
||||
if args is not None:
|
||||
args = re.findall(r'(\d+)', args)
|
||||
coltype = coltype(*[int(a) for a in args])
|
||||
except KeyError:
|
||||
util.warn("Did not recognize type '%s' of column '%s'" %
|
||||
(coltype, name))
|
||||
coltype = sqltypes.NullType()
|
||||
|
||||
columns.append({
|
||||
'name' : name,
|
||||
'type' : coltype,
|
||||
'nullable' : nullable,
|
||||
'default' : default,
|
||||
'autoincrement':default is None,
|
||||
'primary_key': primary_key
|
||||
})
|
||||
rows = c.fetchall()
|
||||
columns = []
|
||||
for row in rows:
|
||||
(name, type_, nullable, default, primary_key) = \
|
||||
(row[1], row[2].upper(), not row[3],
|
||||
row[4], row[5])
|
||||
|
||||
columns.append(self._get_column_info(name, type_, nullable,
|
||||
default, primary_key))
|
||||
return columns
|
||||
|
||||
def _get_column_info(self, name, type_, nullable,
|
||||
default, primary_key):
|
||||
|
||||
match = re.match(r'(\w+)(\(.*?\))?', type_)
|
||||
if match:
|
||||
coltype = match.group(1)
|
||||
args = match.group(2)
|
||||
else:
|
||||
coltype = "VARCHAR"
|
||||
args = ''
|
||||
try:
|
||||
coltype = self.ischema_names[coltype]
|
||||
if args is not None:
|
||||
args = re.findall(r'(\d+)', args)
|
||||
coltype = coltype(*[int(a) for a in args])
|
||||
except KeyError:
|
||||
util.warn("Did not recognize type '%s' of column '%s'" %
|
||||
(coltype, name))
|
||||
coltype = sqltypes.NullType()
|
||||
|
||||
if default is not None:
|
||||
default = unicode(default)
|
||||
|
||||
return {
|
||||
'name': name,
|
||||
'type': coltype,
|
||||
'nullable': nullable,
|
||||
'default': default,
|
||||
'autoincrement': default is None,
|
||||
'primary_key': primary_key
|
||||
}
|
||||
|
||||
@reflection.cache
|
||||
def get_primary_keys(self, connection, table_name, schema=None, **kw):
|
||||
cols = self.get_columns(connection, table_name, schema, **kw)
|
||||
@@ -687,7 +780,8 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
else:
|
||||
pragma = "PRAGMA "
|
||||
qtable = quote(table_name)
|
||||
c = _pragma_cursor(connection.execute("%sforeign_key_list(%s)" % (pragma, qtable)))
|
||||
statement = "%sforeign_key_list(%s)" % (pragma, qtable)
|
||||
c = _pragma_cursor(connection.execute(statement))
|
||||
fkeys = []
|
||||
fks = {}
|
||||
while True:
|
||||
@@ -695,34 +789,38 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
if row is None:
|
||||
break
|
||||
(numerical_id, rtbl, lcol, rcol) = (row[0], row[2], row[3], row[4])
|
||||
# sqlite won't return rcol if the table
|
||||
# was created with REFERENCES <tablename>, no col
|
||||
if rcol is None:
|
||||
rcol = lcol
|
||||
rtbl = re.sub(r'^\"|\"$', '', rtbl)
|
||||
lcol = re.sub(r'^\"|\"$', '', lcol)
|
||||
rcol = re.sub(r'^\"|\"$', '', rcol)
|
||||
try:
|
||||
fk = fks[numerical_id]
|
||||
except KeyError:
|
||||
fk = {
|
||||
'name' : None,
|
||||
'constrained_columns' : [],
|
||||
'referred_schema' : None,
|
||||
'referred_table' : rtbl,
|
||||
'referred_columns' : []
|
||||
}
|
||||
fkeys.append(fk)
|
||||
fks[numerical_id] = fk
|
||||
|
||||
# look up the table based on the given table's engine, not 'self',
|
||||
# since it could be a ProxyEngine
|
||||
if lcol not in fk['constrained_columns']:
|
||||
fk['constrained_columns'].append(lcol)
|
||||
if rcol not in fk['referred_columns']:
|
||||
fk['referred_columns'].append(rcol)
|
||||
self._parse_fk(fks, fkeys, numerical_id, rtbl, lcol, rcol)
|
||||
return fkeys
|
||||
|
||||
def _parse_fk(self, fks, fkeys, numerical_id, rtbl, lcol, rcol):
|
||||
# sqlite won't return rcol if the table
|
||||
# was created with REFERENCES <tablename>, no col
|
||||
if rcol is None:
|
||||
rcol = lcol
|
||||
|
||||
if self._broken_fk_pragma_quotes:
|
||||
rtbl = re.sub(r'^[\"\[`\']|[\"\]`\']$', '', rtbl)
|
||||
|
||||
try:
|
||||
fk = fks[numerical_id]
|
||||
except KeyError:
|
||||
fk = {
|
||||
'name': None,
|
||||
'constrained_columns': [],
|
||||
'referred_schema': None,
|
||||
'referred_table': rtbl,
|
||||
'referred_columns': []
|
||||
}
|
||||
fkeys.append(fk)
|
||||
fks[numerical_id] = fk
|
||||
|
||||
if lcol not in fk['constrained_columns']:
|
||||
fk['constrained_columns'].append(lcol)
|
||||
if rcol not in fk['referred_columns']:
|
||||
fk['referred_columns'].append(rcol)
|
||||
return fk
|
||||
|
||||
@reflection.cache
|
||||
def get_indexes(self, connection, table_name, schema=None, **kw):
|
||||
quote = self.identifier_preparer.quote_identifier
|
||||
@@ -762,4 +860,5 @@ def _pragma_cursor(cursor):
|
||||
|
||||
if cursor.closed:
|
||||
cursor.fetchone = lambda: None
|
||||
cursor.fetchall = lambda: []
|
||||
return cursor
|
||||
@@ -1,5 +1,5 @@
|
||||
# sqlite/pysqlite.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -97,13 +97,21 @@ Threading/Pooling Behavior
|
||||
---------------------------
|
||||
|
||||
Pysqlite's default behavior is to prohibit the usage of a single connection
|
||||
in more than one thread. This is controlled by the ``check_same_thread``
|
||||
Pysqlite flag. This default is intended to work with older versions
|
||||
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
|
||||
@@ -118,16 +126,11 @@ SQLAlchemy sets up pooling to work with Pysqlite's default behavior:
|
||||
prevents a connection from being used again in a different thread and works
|
||||
best with SQLite's coarse-grained file locking.
|
||||
|
||||
.. note::
|
||||
.. versionchanged:: 0.7
|
||||
Default selection of :class:`.NullPool` for SQLite file-based databases.
|
||||
Previous versions select :class:`.SingletonThreadPool` by
|
||||
default for all SQLite databases.
|
||||
|
||||
The default selection of :class:`.NullPool` for SQLite file-based databases
|
||||
is new in SQLAlchemy 0.7. Previous versions
|
||||
select :class:`.SingletonThreadPool` by
|
||||
default for all SQLite databases.
|
||||
|
||||
Modern versions of SQLite no longer have the threading restrictions, and assuming
|
||||
the sqlite3/pysqlite library was built with SQLite's default threading mode
|
||||
of "Serialized", even ``:memory:`` databases can be shared among threads.
|
||||
|
||||
Using a Memory Database in Multiple Threads
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sybase/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -10,8 +10,12 @@
|
||||
|
||||
"""Support for Sybase Adaptive Server Enterprise (ASE).
|
||||
|
||||
Note that this dialect is no longer specific to Sybase iAnywhere.
|
||||
ASE is the primary support platform.
|
||||
.. note::
|
||||
|
||||
The Sybase dialect functions on current SQLAlchemy versions
|
||||
but is not regularly tested, and may have many issues and
|
||||
caveats not currently handled. In particular, the table
|
||||
and database reflection features are not implemented.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sybase/mxodbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sybase/pyodbc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -52,7 +52,7 @@ class SybaseExecutionContext_pysybase(SybaseExecutionContext):
|
||||
|
||||
|
||||
class SybaseSQLCompiler_pysybase(SybaseSQLCompiler):
|
||||
def bindparam_string(self, name):
|
||||
def bindparam_string(self, name, **kw):
|
||||
return "@" + name
|
||||
|
||||
class SybaseDialect_pysybase(SybaseDialect):
|
||||
@@ -82,10 +82,10 @@ class SybaseDialect_pysybase(SybaseDialect):
|
||||
cursor.execute(statement, param)
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
vers = connection.scalar("select @@version_number")
|
||||
# i.e. 15500, 15000, 12500 == (15, 5, 0, 0), (15, 0, 0, 0),
|
||||
# (12, 5, 0, 0)
|
||||
return (vers / 1000, vers % 1000 / 100, vers % 100 / 10, vers % 10)
|
||||
vers = connection.scalar("select @@version_number")
|
||||
# i.e. 15500, 15000, 12500 == (15, 5, 0, 0), (15, 0, 0, 0),
|
||||
# (12, 5, 0, 0)
|
||||
return (vers / 1000, vers % 1000 / 100, vers % 100 / 10, vers % 10)
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, (self.dbapi.OperationalError,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# engine/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -310,15 +310,26 @@ def create_engine(*args, **kwargs):
|
||||
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`. (new as of 0.7.6)
|
||||
``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 is the ``threadlocal``
|
||||
strategy, which is described in :ref:`threadlocal_strategy`.
|
||||
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'``.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# engine/base.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -663,13 +663,10 @@ class ExecutionContext(object):
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_rowcount(self):
|
||||
"""Return the number of rows produced (by a SELECT query)
|
||||
or affected (by an INSERT/UPDATE/DELETE statement).
|
||||
"""Return the DBAPI ``cursor.rowcount`` value, or in some
|
||||
cases an interpreted value.
|
||||
|
||||
Note that this row count may not be properly implemented
|
||||
in some dialects; this is indicated by the
|
||||
``supports_sane_rowcount`` and ``supports_sane_multi_rowcount``
|
||||
dialect attributes.
|
||||
See :attr:`.ResultProxy.rowcount` for details on this.
|
||||
|
||||
"""
|
||||
|
||||
@@ -734,7 +731,7 @@ class Compiled(object):
|
||||
def construct_params(self, params=None):
|
||||
"""Return the bind params for this compiled object.
|
||||
|
||||
:param params: a dict of string/object pairs whos values will
|
||||
:param params: a dict of string/object pairs whose values will
|
||||
override bind values compiled in to the
|
||||
statement.
|
||||
"""
|
||||
@@ -978,10 +975,10 @@ class Connection(Connectable):
|
||||
:ref:`postgresql_toplevel` - see those dialect's documentation
|
||||
for further info.
|
||||
|
||||
Note that this option necessarily affects the underying
|
||||
Note that this option necessarily affects the underlying
|
||||
DBAPI connection for the lifespan of the originating
|
||||
:class:`.Connection`, and is not per-execution. This
|
||||
setting is not removed until the underying DBAPI connection
|
||||
setting is not removed until the underlying DBAPI connection
|
||||
is returned to the connection pool, i.e.
|
||||
the :meth:`.Connection.close` method is called.
|
||||
|
||||
@@ -995,7 +992,9 @@ class Connection(Connectable):
|
||||
containing percent signs (and possibly other characters)
|
||||
that is neutral regarding whether it's executed by the DBAPI
|
||||
or piped into a script that's later invoked by
|
||||
command line tools. New in 0.7.6.
|
||||
command line tools.
|
||||
|
||||
.. versionadded:: 0.7.6
|
||||
|
||||
:param stream_results: Available on: Connection, statement.
|
||||
Indicate to the dialect that results should be
|
||||
@@ -1074,7 +1073,7 @@ class Connection(Connectable):
|
||||
"""Returns self.
|
||||
|
||||
This ``Connectable`` interface method returns self, allowing
|
||||
Connections to be used interchangably with Engines in most
|
||||
Connections to be used interchangeably with Engines in most
|
||||
situations that require a bind.
|
||||
"""
|
||||
|
||||
@@ -1084,7 +1083,7 @@ class Connection(Connectable):
|
||||
"""Returns self.
|
||||
|
||||
This ``Connectable`` interface method returns self, allowing
|
||||
Connections to be used interchangably with Engines in most
|
||||
Connections to be used interchangeably with Engines in most
|
||||
situations that require a bind.
|
||||
"""
|
||||
|
||||
@@ -1123,7 +1122,7 @@ class Connection(Connectable):
|
||||
def detach(self):
|
||||
"""Detach the underlying DB-API connection from its connection pool.
|
||||
|
||||
This Connection instance will remain useable. When closed,
|
||||
This Connection instance will remain usable. When closed,
|
||||
the DB-API connection will be literally closed and not
|
||||
returned to its pool. The pool will typically lazily create a
|
||||
new connection to replace the detached connection.
|
||||
@@ -1471,7 +1470,8 @@ class Connection(Connectable):
|
||||
elif len(multiparams) == 1:
|
||||
zero = multiparams[0]
|
||||
if isinstance(zero, (list, tuple)):
|
||||
if not zero or hasattr(zero[0], '__iter__'):
|
||||
if not zero or hasattr(zero[0], '__iter__') and \
|
||||
not hasattr(zero[0], 'strip'):
|
||||
return zero
|
||||
else:
|
||||
return [zero]
|
||||
@@ -1480,7 +1480,8 @@ class Connection(Connectable):
|
||||
else:
|
||||
return [[zero]]
|
||||
else:
|
||||
if hasattr(multiparams[0], '__iter__'):
|
||||
if hasattr(multiparams[0], '__iter__') and \
|
||||
not hasattr(multiparams[0], 'strip'):
|
||||
return multiparams
|
||||
else:
|
||||
return [multiparams]
|
||||
@@ -1714,7 +1715,6 @@ class Connection(Connectable):
|
||||
# create a resultproxy, get rowcount/implicit RETURNING
|
||||
# rows, close cursor if no further results pending
|
||||
result = context.get_result_proxy()
|
||||
|
||||
if context.isinsert:
|
||||
if context._is_implicit_returning:
|
||||
context._fetch_implicit_returning(result)
|
||||
@@ -1806,10 +1806,19 @@ class Connection(Connectable):
|
||||
(statement is not None and context is None)
|
||||
|
||||
if should_wrap and context:
|
||||
if self._has_events:
|
||||
self.engine.dispatch.dbapi_error(self,
|
||||
cursor,
|
||||
statement,
|
||||
parameters,
|
||||
context,
|
||||
e)
|
||||
context.handle_dbapi_exception(e)
|
||||
|
||||
is_disconnect = isinstance(e, self.dialect.dbapi.Error) and \
|
||||
self.dialect.is_disconnect(e, self.__connection, cursor)
|
||||
|
||||
|
||||
if is_disconnect:
|
||||
self.invalidate(e)
|
||||
self.engine.dispose()
|
||||
@@ -2130,7 +2139,7 @@ class Engine(Connectable, log.Identified):
|
||||
:class:`~sqlalchemy.engine.base.Dialect` together to provide a source
|
||||
of database connectivity and behavior.
|
||||
|
||||
An :class:`.Engine` object is instantiated publically using the
|
||||
An :class:`.Engine` object is instantiated publicly using the
|
||||
:func:`~sqlalchemy.create_engine` function.
|
||||
|
||||
See also:
|
||||
@@ -2178,7 +2187,7 @@ class Engine(Connectable, log.Identified):
|
||||
The given keys/values in \**opt are added to the
|
||||
default execution options that will be used for
|
||||
all connections. The initial contents of this dictionary
|
||||
can be sent via the ``execution_options`` paramter
|
||||
can be sent via the ``execution_options`` parameter
|
||||
to :func:`.create_engine`.
|
||||
|
||||
See :meth:`.Connection.execution_options` for more
|
||||
@@ -2232,7 +2241,7 @@ class Engine(Connectable, log.Identified):
|
||||
|
||||
"""
|
||||
self.pool.dispose()
|
||||
self.pool = self.pool.recreate()
|
||||
self.pool = self.pool._replace()
|
||||
|
||||
@util.deprecated("0.7", "Use the create() method on the given schema "
|
||||
"object directly, i.e. :meth:`.Table.create`, "
|
||||
@@ -2334,7 +2343,7 @@ class Engine(Connectable, log.Identified):
|
||||
close the :class:`.Connection` when that :class:`.ResultProxy`
|
||||
has exhausted all result rows.
|
||||
|
||||
New in 0.7.6.
|
||||
.. versionadded:: 0.7.6
|
||||
|
||||
See also:
|
||||
|
||||
@@ -2346,7 +2355,11 @@ class Engine(Connectable, log.Identified):
|
||||
|
||||
"""
|
||||
conn = self.contextual_connect(close_with_result=close_with_result)
|
||||
trans = conn.begin()
|
||||
try:
|
||||
trans = conn.begin()
|
||||
except:
|
||||
conn.close()
|
||||
raise
|
||||
return Engine._trans_ctx(conn, trans, close_with_result)
|
||||
|
||||
def transaction(self, callable_, *args, **kwargs):
|
||||
@@ -2710,7 +2723,7 @@ class ResultMetaData(object):
|
||||
context = parent.context
|
||||
dialect = context.dialect
|
||||
typemap = dialect.dbapi_type_map
|
||||
translate_colname = dialect._translate_colname
|
||||
translate_colname = context._translate_colname
|
||||
|
||||
# high precedence key values.
|
||||
primary_keymap = {}
|
||||
@@ -2907,18 +2920,41 @@ class ResultProxy(object):
|
||||
def rowcount(self):
|
||||
"""Return the 'rowcount' for this result.
|
||||
|
||||
The 'rowcount' reports the number of rows affected
|
||||
by an UPDATE or DELETE statement. It has *no* other
|
||||
uses and is not intended to provide the number of rows
|
||||
present from a SELECT.
|
||||
The 'rowcount' reports the number of rows *matched*
|
||||
by the WHERE criterion of an UPDATE or DELETE statement.
|
||||
|
||||
Note that this row count may not be properly implemented in some
|
||||
dialects; this is indicated by
|
||||
:meth:`~sqlalchemy.engine.base.ResultProxy.supports_sane_rowcount()`
|
||||
and
|
||||
:meth:`~sqlalchemy.engine.base.ResultProxy.supports_sane_multi_rowcount()`.
|
||||
``rowcount()`` also may not work at this time for a statement that
|
||||
uses ``returning()``.
|
||||
.. 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:
|
||||
@@ -3098,12 +3134,20 @@ class ResultProxy(object):
|
||||
return self.context.prefetch_cols
|
||||
|
||||
def supports_sane_rowcount(self):
|
||||
"""Return ``supports_sane_rowcount`` from the dialect."""
|
||||
"""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."""
|
||||
"""Return ``supports_sane_multi_rowcount`` from the dialect.
|
||||
|
||||
See :attr:`.ResultProxy.rowcount` for background.
|
||||
|
||||
"""
|
||||
|
||||
return self.dialect.supports_sane_multi_rowcount
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# engine/default.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -96,10 +96,6 @@ class DefaultDialect(base.Dialect):
|
||||
# and denormalize_name() must be provided.
|
||||
requires_name_normalize = False
|
||||
|
||||
# a hook for SQLite's translation of
|
||||
# result column names
|
||||
_translate_colname = None
|
||||
|
||||
reflection_options = ()
|
||||
|
||||
def __init__(self, convert_unicode=False, assert_unicode=False,
|
||||
@@ -264,7 +260,7 @@ class DefaultDialect(base.Dialect):
|
||||
return insp.reflecttable(table, include_columns, exclude_columns)
|
||||
|
||||
def get_pk_constraint(self, conn, table_name, schema=None, **kw):
|
||||
"""Compatiblity method, adapts the result of get_primary_keys()
|
||||
"""Compatibility method, adapts the result of get_primary_keys()
|
||||
for those dialects which don't implement get_pk_constraint().
|
||||
|
||||
"""
|
||||
@@ -341,7 +337,7 @@ class DefaultDialect(base.Dialect):
|
||||
return False
|
||||
|
||||
def reset_isolation_level(self, dbapi_conn):
|
||||
# default_isolation_level is read from the first conenction
|
||||
# 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)
|
||||
@@ -355,9 +351,15 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
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."""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# engine/reflection.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -379,7 +379,7 @@ class Inspector(object):
|
||||
|
||||
coltype = col_d['type']
|
||||
col_kw = {
|
||||
'nullable':col_d['nullable'],
|
||||
'nullable': col_d['nullable'],
|
||||
}
|
||||
for k in ('autoincrement', 'quote', 'info', 'key'):
|
||||
if k in col_d:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# engine/strategies.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -24,7 +24,7 @@ strategies = {}
|
||||
|
||||
|
||||
class EngineStrategy(object):
|
||||
"""An adaptor that processes input arguements and produces an Engine.
|
||||
"""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.
|
||||
@@ -41,7 +41,7 @@ class EngineStrategy(object):
|
||||
|
||||
|
||||
class DefaultEngineStrategy(EngineStrategy):
|
||||
"""Base class for built-in stratgies."""
|
||||
"""Base class for built-in strategies."""
|
||||
|
||||
def create(self, name_or_url, **kwargs):
|
||||
# create url.URL object
|
||||
@@ -180,7 +180,7 @@ PlainEngineStrategy()
|
||||
|
||||
|
||||
class ThreadLocalEngineStrategy(DefaultEngineStrategy):
|
||||
"""Strategy for configuring an Engine with thredlocal behavior."""
|
||||
"""Strategy for configuring an Engine with threadlocal behavior."""
|
||||
|
||||
name = 'threadlocal'
|
||||
engine_cls = threadlocal.TLEngine
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# engine/threadlocal.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# engine/url.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
+111
-15
@@ -1,5 +1,5 @@
|
||||
# sqlalchemy/event.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -7,6 +7,7 @@
|
||||
"""Base event API."""
|
||||
|
||||
from sqlalchemy import util, exc
|
||||
import weakref
|
||||
|
||||
CANCEL = util.symbol('CANCEL')
|
||||
NO_RETVAL = util.symbol('NO_RETVAL')
|
||||
@@ -37,7 +38,7 @@ def listen(target, identifier, fn, *args, **kw):
|
||||
tgt.dispatch._listen(tgt, identifier, fn, *args, **kw)
|
||||
return
|
||||
raise exc.InvalidRequestError("No such event '%s' for target '%s'" %
|
||||
(identifier,target))
|
||||
(identifier, target))
|
||||
|
||||
def listens_for(target, identifier, *args, **kw):
|
||||
"""Decorate a function as a listener for the given target + identifier.
|
||||
@@ -69,7 +70,7 @@ def remove(target, identifier, fn):
|
||||
"""
|
||||
for evt_cls in _registrars[identifier]:
|
||||
for tgt in evt_cls._accept_with(target):
|
||||
tgt.dispatch._remove(identifier, tgt, fn, *args, **kw)
|
||||
tgt.dispatch._remove(identifier, tgt, fn)
|
||||
return
|
||||
|
||||
_registrars = util.defaultdict(list)
|
||||
@@ -120,7 +121,8 @@ class _Dispatch(object):
|
||||
object."""
|
||||
|
||||
for ls in _event_descriptors(other):
|
||||
getattr(self, ls.name)._update(ls, only_propagate=only_propagate)
|
||||
getattr(self, ls.name).\
|
||||
for_modify(self)._update(ls, only_propagate=only_propagate)
|
||||
|
||||
def _event_descriptors(target):
|
||||
return [getattr(target, k) for k in dir(target) if _is_event_name(k)]
|
||||
@@ -180,9 +182,11 @@ class Events(object):
|
||||
@classmethod
|
||||
def _listen(cls, target, identifier, fn, propagate=False, insert=False):
|
||||
if insert:
|
||||
getattr(target.dispatch, identifier).insert(fn, target, propagate)
|
||||
getattr(target.dispatch, identifier).\
|
||||
for_modify(target.dispatch).insert(fn, target, propagate)
|
||||
else:
|
||||
getattr(target.dispatch, identifier).append(fn, target, propagate)
|
||||
getattr(target.dispatch, identifier).\
|
||||
for_modify(target.dispatch).append(fn, target, propagate)
|
||||
|
||||
@classmethod
|
||||
def _remove(cls, target, identifier, fn):
|
||||
@@ -200,7 +204,12 @@ class _DispatchDescriptor(object):
|
||||
def __init__(self, fn):
|
||||
self.__name__ = fn.__name__
|
||||
self.__doc__ = fn.__doc__
|
||||
self._clslevel = util.defaultdict(list)
|
||||
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), \
|
||||
@@ -212,6 +221,8 @@ class _DispatchDescriptor(object):
|
||||
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):
|
||||
@@ -225,9 +236,13 @@ class _DispatchDescriptor(object):
|
||||
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:
|
||||
@@ -242,7 +257,8 @@ class _DispatchDescriptor(object):
|
||||
while stack:
|
||||
cls = stack.pop(0)
|
||||
stack.extend(cls.__subclasses__())
|
||||
self._clslevel[cls].remove(obj)
|
||||
if cls in self._clslevel:
|
||||
self._clslevel[cls].remove(obj)
|
||||
|
||||
def clear(self):
|
||||
"""Clear all class level listeners"""
|
||||
@@ -250,18 +266,91 @@ class _DispatchDescriptor(object):
|
||||
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
|
||||
obj.__dict__[self.__name__] = result = \
|
||||
_ListenerCollection(self, obj._parent_cls)
|
||||
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.
|
||||
|
||||
"""
|
||||
obj.__dict__[self.name] = result = _ListenerCollection(
|
||||
self.parent, obj._parent_cls)
|
||||
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 __getitem__(self, index):
|
||||
return (self.parent_listeners)[index]
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.parent_listeners)
|
||||
|
||||
|
||||
class _ListenerCollection(object):
|
||||
"""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.
|
||||
|
||||
"""
|
||||
|
||||
_exec_once = False
|
||||
@@ -274,6 +363,15 @@ class _ListenerCollection(object):
|
||||
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 exec_once(self, *args, **kw):
|
||||
"""Execute this event, but only if it has not been
|
||||
executed already for this collection."""
|
||||
@@ -294,11 +392,9 @@ class _ListenerCollection(object):
|
||||
# but this allows class-level listeners to be added
|
||||
# at any point.
|
||||
#
|
||||
# alternatively, _DispatchDescriptor could notify
|
||||
# all _ListenerCollection objects, but then we move
|
||||
# to a higher memory model, i.e.weakrefs to all _ListenerCollection
|
||||
# objects, the _DispatchDescriptor collection repeated
|
||||
# for all instances.
|
||||
# In the absense of instance-level listeners,
|
||||
# we stay with the _EmptyListener object when called
|
||||
# at the instance level.
|
||||
|
||||
def __len__(self):
|
||||
return len(self.parent_listeners + self.listeners)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sqlalchemy/events.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -401,6 +401,36 @@ class ConnectionEvents(event.Events):
|
||||
parameters, context, executemany):
|
||||
"""Intercept low-level cursor execute() events."""
|
||||
|
||||
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.
|
||||
|
||||
.. versionadded:: 0.7.7
|
||||
|
||||
"""
|
||||
|
||||
def begin(self, conn):
|
||||
"""Intercept begin() events."""
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sqlalchemy/exc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ext/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ext/associationproxy.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -141,7 +141,7 @@ class AssociationProxy(object):
|
||||
"""The 'remote' :class:`.MapperProperty` referenced by this
|
||||
:class:`.AssociationProxy`.
|
||||
|
||||
New in 0.7.3.
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
See also:
|
||||
|
||||
@@ -157,7 +157,7 @@ class AssociationProxy(object):
|
||||
"""The 'local' :class:`.MapperProperty` referenced by this
|
||||
:class:`.AssociationProxy`.
|
||||
|
||||
New in 0.7.3.
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
See also:
|
||||
|
||||
@@ -177,7 +177,7 @@ class AssociationProxy(object):
|
||||
|
||||
sess.query(Parent).join(*Parent.proxied.attr)
|
||||
|
||||
New in 0.7.3.
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
See also:
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ext/compiler.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ext/declarative.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -86,16 +86,6 @@ CREATE statements for all tables::
|
||||
engine = create_engine('sqlite://')
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
The usual techniques of associating :class:`.MetaData:` with :class:`.Engine`
|
||||
apply, such as assigning to the ``bind`` attribute::
|
||||
|
||||
Base.metadata.bind = create_engine('sqlite://')
|
||||
|
||||
To associate the engine with the :func:`declarative_base` at time
|
||||
of construction, the ``bind`` argument is accepted::
|
||||
|
||||
Base = declarative_base(bind=create_engine('sqlite://'))
|
||||
|
||||
:func:`declarative_base` can also receive a pre-existing
|
||||
:class:`.MetaData` object, which allows a
|
||||
declarative setup to be associated with an already
|
||||
@@ -400,8 +390,8 @@ table::
|
||||
|
||||
Note that above, the ``Engineer.id`` attribute, since it shares the
|
||||
same attribute name as the ``Person.id`` attribute, will in fact
|
||||
represent the ``people.id`` and ``engineers.id`` columns together, and
|
||||
will render inside a query as ``"people.id"``.
|
||||
represent the ``people.id`` and ``engineers.id`` columns together,
|
||||
with the "Engineer.id" column taking precedence if queried directly.
|
||||
To provide the ``Engineer`` class with an attribute that represents
|
||||
only the ``engineers.id`` column, give it a different attribute name::
|
||||
|
||||
@@ -412,6 +402,11 @@ only the ``engineers.id`` column, give it a different attribute name::
|
||||
primary_key=True)
|
||||
primary_language = Column(String(50))
|
||||
|
||||
|
||||
.. versionchanged:: 0.7 joined table inheritance favors the subclass
|
||||
column over that of the superclass, such as querying above
|
||||
for ``Engineer.id``. Prior to 0.7 this was the reverse.
|
||||
|
||||
Single Table Inheritance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -506,10 +501,12 @@ before the class is built::
|
||||
Using the Concrete Helpers
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
New helper classes released in 0.7.3 provides a simpler pattern for concrete inheritance.
|
||||
Helper classes provides a simpler pattern for concrete inheritance.
|
||||
With these objects, the ``__declare_last__`` helper is used to configure the "polymorphic"
|
||||
loader for the mapper after all subclasses have been declared.
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
An abstract base can be declared using the :class:`.AbstractConcreteBase` class::
|
||||
|
||||
from sqlalchemy.ext.declarative import AbstractConcreteBase
|
||||
@@ -671,8 +668,7 @@ keys, as a :class:`.ForeignKey` itself contains references to columns
|
||||
which can't be properly recreated at this level. For columns that
|
||||
have foreign keys, as well as for the variety of mapper-level constructs
|
||||
that require destination-explicit context, the
|
||||
:func:`~.declared_attr` decorator (renamed from ``sqlalchemy.util.classproperty`` in 0.6.5)
|
||||
is provided so that
|
||||
:func:`~.declared_attr` decorator is provided so that
|
||||
patterns common to many classes can be defined as callables::
|
||||
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
@@ -691,6 +687,9 @@ point at which the ``User`` class is constructed, and the declarative
|
||||
extension can use the resulting :class:`.Column` object as returned by
|
||||
the method without the need to copy it.
|
||||
|
||||
.. versionchanged:: > 0.6.5
|
||||
Rename 0.6.5 ``sqlalchemy.util.classproperty`` into :func:`~.declared_attr`.
|
||||
|
||||
Columns generated by :func:`~.declared_attr` can also be
|
||||
referenced by ``__mapper_args__`` to a limited degree, currently
|
||||
by ``polymorphic_on`` and ``version_id_col``, by specifying the
|
||||
@@ -713,7 +712,7 @@ Mixing in Relationships
|
||||
|
||||
Relationships created by :func:`~sqlalchemy.orm.relationship` are provided
|
||||
with declarative mixin classes exclusively using the
|
||||
:func:`.declared_attr` approach, eliminating any ambiguity
|
||||
:class:`.declared_attr` approach, eliminating any ambiguity
|
||||
which could arise when copying a relationship and its possibly column-bound
|
||||
contents. Below is an example which combines a foreign key column and a
|
||||
relationship so that two classes ``Foo`` and ``Bar`` can both be configured to
|
||||
@@ -744,7 +743,7 @@ reference a common target class via many-to-one::
|
||||
primaryjoin, order_by etc. expressions should use the string forms
|
||||
for these arguments, so that they are evaluated as late as possible.
|
||||
To reference the mixin class in these expressions, use the given ``cls``
|
||||
to get it's name::
|
||||
to get its name::
|
||||
|
||||
class RefTargetMixin(object):
|
||||
@declared_attr
|
||||
@@ -764,7 +763,7 @@ Like :func:`~sqlalchemy.orm.relationship`, all
|
||||
:class:`~sqlalchemy.orm.interfaces.MapperProperty` subclasses such as
|
||||
:func:`~sqlalchemy.orm.deferred`, :func:`~sqlalchemy.orm.column_property`,
|
||||
etc. ultimately involve references to columns, and therefore, when
|
||||
used with declarative mixins, have the :func:`.declared_attr`
|
||||
used with declarative mixins, have the :class:`.declared_attr`
|
||||
requirement so that no reliance on copying is needed::
|
||||
|
||||
class SomethingMixin(object):
|
||||
@@ -879,7 +878,7 @@ In the case of ``__table_args__`` or ``__mapper_args__``
|
||||
specified with declarative mixins, you may want to combine
|
||||
some parameters from several mixins with those you wish to
|
||||
define on the class iteself. The
|
||||
:func:`.declared_attr` decorator can be used
|
||||
:class:`.declared_attr` decorator can be used
|
||||
here to create user-defined collation routines that pull
|
||||
from multiple collections::
|
||||
|
||||
@@ -928,7 +927,7 @@ Special Directives
|
||||
``__declare_last__()``
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``__declare_last__()`` hook, introduced in 0.7.3, allows definition of
|
||||
The ``__declare_last__()`` hook allows definition of
|
||||
a class level function that is automatically called by the :meth:`.MapperEvents.after_configured`
|
||||
event, which occurs after mappings are assumed to be completed and the 'configure' step
|
||||
has finished::
|
||||
@@ -939,12 +938,14 @@ has finished::
|
||||
""
|
||||
# do something with mappings
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
.. _declarative_abstract:
|
||||
|
||||
``__abstract__``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``__abstract__`` is introduced in 0.7.3 and causes declarative to skip the production
|
||||
``__abstract__`` causes declarative to skip the production
|
||||
of a table or mapper for the class entirely. A class can be added within a hierarchy
|
||||
in the same way as mixin (see :ref:`declarative_mixins`), allowing subclasses to extend
|
||||
just from the special class::
|
||||
@@ -982,6 +983,8 @@ The tables themselves can then be created perhaps within distinct databases::
|
||||
DefaultBase.metadata.create_all(some_engine)
|
||||
OtherBase.metadata_create_all(some_other_engine)
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
Class Constructor
|
||||
=================
|
||||
|
||||
@@ -1322,11 +1325,9 @@ def _as_declarative(cls, classname, dict_):
|
||||
if k in inherited_mapper._props:
|
||||
p = inherited_mapper._props[k]
|
||||
if isinstance(p, ColumnProperty):
|
||||
# note here we place the superclass column
|
||||
# first. this corresponds to the
|
||||
# append() in mapper._configure_property().
|
||||
# change this ordering when we do [ticket:1892]
|
||||
our_stuff[k] = p.columns + [col]
|
||||
# note here we place the subclass column
|
||||
# first. See [ticket:1892] for background.
|
||||
our_stuff[k] = [col] + p.columns
|
||||
|
||||
|
||||
cls.__mapper__ = mapper_cls(cls,
|
||||
@@ -1336,11 +1337,9 @@ def _as_declarative(cls, classname, dict_):
|
||||
|
||||
class DeclarativeMeta(type):
|
||||
def __init__(cls, classname, bases, dict_):
|
||||
if '_decl_class_registry' in cls.__dict__:
|
||||
return type.__init__(cls, classname, bases, dict_)
|
||||
else:
|
||||
if '_decl_class_registry' not in cls.__dict__:
|
||||
_as_declarative(cls, classname, cls.__dict__)
|
||||
return type.__init__(cls, classname, bases, dict_)
|
||||
type.__init__(cls, classname, bases, dict_)
|
||||
|
||||
def __setattr__(cls, key, value):
|
||||
if '__mapper__' in cls.__dict__:
|
||||
@@ -1493,11 +1492,10 @@ class declared_attr(property):
|
||||
"""Mark a class-level method as representing the definition of
|
||||
a mapped property or special declarative member name.
|
||||
|
||||
.. note::
|
||||
|
||||
@declared_attr is available as
|
||||
``sqlalchemy.util.classproperty`` for SQLAlchemy versions
|
||||
0.6.2, 0.6.3, 0.6.4.
|
||||
.. versionchanged:: 0.6.{2,3,4}
|
||||
``@declared_attr`` is available as
|
||||
``sqlalchemy.util.classproperty`` for SQLAlchemy versions
|
||||
0.6.2, 0.6.3, 0.6.4.
|
||||
|
||||
@declared_attr turns the attribute into a scalar-like
|
||||
property that can be invoked from the uninstantiated class.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ext/horizontal_shard.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -10,7 +10,7 @@ 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 distrbution.
|
||||
the source distribution.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ext/hybrid.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -179,9 +179,16 @@ The ``length(self, value)`` method is now called upon set::
|
||||
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.
|
||||
Consider the following declarative mapping which relates a ``User`` to a ``SavingsAccount``::
|
||||
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
|
||||
@@ -222,30 +229,88 @@ Consider the following declarative mapping which relates a ``User`` to a ``Savin
|
||||
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
|
||||
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, we can't travel along relationships to column attributes
|
||||
directly since SQLAlchemy is explicit about joins. So here, it's expected that the ``User`` class will be
|
||||
used in an appropriate context such that an appropriate join to ``SavingsAccount`` will be present::
|
||||
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
|
||||
>>> 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
|
||||
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
|
||||
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 colunn 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ext/mutable.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -180,7 +180,7 @@ 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 responsiblity here is only to provide a ``__getstate__`` method
|
||||
The developer responsibility here is only to provide a ``__getstate__`` method
|
||||
that excludes the :meth:`~.MutableBase._parents` collection from the pickle
|
||||
stream::
|
||||
|
||||
@@ -217,12 +217,13 @@ 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`.
|
||||
|
||||
As of SQLAlchemy 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.
|
||||
.. 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
|
||||
@@ -300,6 +301,31 @@ will flag the attribute as "dirty" on the parent object::
|
||||
>>> 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
|
||||
--------------------
|
||||
|
||||
@@ -327,7 +353,7 @@ pickling process of the parent's object-relational state so that the
|
||||
"""
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
from sqlalchemy import event, types
|
||||
from sqlalchemy.orm import mapper, object_mapper
|
||||
from sqlalchemy.orm import mapper, object_mapper, Mapper
|
||||
from sqlalchemy.util import memoized_property
|
||||
import weakref
|
||||
|
||||
@@ -348,9 +374,27 @@ class MutableBase(object):
|
||||
|
||||
@classmethod
|
||||
def coerce(cls, key, value):
|
||||
"""Given a value, coerce it into this type.
|
||||
"""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.
|
||||
|
||||
By default raises ValueError.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
@@ -511,11 +555,6 @@ class Mutable(MutableBase):
|
||||
|
||||
return sqltype
|
||||
|
||||
class _MutableCompositeMeta(type):
|
||||
def __init__(cls, classname, bases, dict_):
|
||||
cls._setup_listeners()
|
||||
return type.__init__(cls, classname, bases, dict_)
|
||||
|
||||
class MutableComposite(MutableBase):
|
||||
"""Mixin that defines transparent propagation of change
|
||||
events on a SQLAlchemy "composite" object to its
|
||||
@@ -532,7 +571,6 @@ class MutableComposite(MutableBase):
|
||||
in memory usage.
|
||||
|
||||
"""
|
||||
__metaclass__ = _MutableCompositeMeta
|
||||
|
||||
def changed(self):
|
||||
"""Subclasses should call this method whenever change events occur."""
|
||||
@@ -545,19 +583,14 @@ class MutableComposite(MutableBase):
|
||||
prop._attribute_keys):
|
||||
setattr(parent, attr_name, value)
|
||||
|
||||
@classmethod
|
||||
def _setup_listeners(cls):
|
||||
"""Associate this wrapper with all future mapped composites
|
||||
of the given type.
|
||||
|
||||
This is a convenience method that calls ``associate_with_attribute`` automatically.
|
||||
|
||||
"""
|
||||
|
||||
def listen_for_type(mapper, class_):
|
||||
for prop in mapper.iterate_properties:
|
||||
if hasattr(prop, 'composite_class') and issubclass(prop.composite_class, cls):
|
||||
cls._listen_on_attribute(getattr(class_, prop.key), False, class_)
|
||||
|
||||
event.listen(mapper, 'mapper_configured', listen_for_type)
|
||||
|
||||
def _setup_composite_listener():
|
||||
def _listen_for_type(mapper, class_):
|
||||
for prop in mapper.iterate_properties:
|
||||
if (hasattr(prop, 'composite_class') 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()
|
||||
@@ -1,64 +1,77 @@
|
||||
# ext/orderinglist.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""A custom list that manages index/position information for its children.
|
||||
"""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 relationship collection and automatically
|
||||
synchronize changes in list position with an attribute on the related objects.
|
||||
(See :ref:`advdatamapping_entitycollections` for more information on the general pattern.)
|
||||
``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: Two tables that store slides in a presentation. Each slide
|
||||
has a number of bullet points, displayed in order by the 'position'
|
||||
column on the bullets table. These bullets can be inserted and re-ordered
|
||||
by your end users, and you need to update the 'position' column of all
|
||||
affected rows when changes are made.
|
||||
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::
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
slides_table = Table('Slides', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('name', String))
|
||||
Base = declarative_base()
|
||||
|
||||
bullets_table = Table('Bullets', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('slide_id', Integer, ForeignKey('Slides.id')),
|
||||
Column('position', Integer),
|
||||
Column('text', String))
|
||||
class Slide(Base):
|
||||
__tablename__ = 'slide'
|
||||
|
||||
class Slide(object):
|
||||
pass
|
||||
class Bullet(object):
|
||||
pass
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
|
||||
mapper(Slide, slides_table, properties={
|
||||
'bullets': relationship(Bullet, order_by=[bullets_table.c.position])
|
||||
})
|
||||
mapper(Bullet, bullets_table)
|
||||
bullets = relationship("Bullet", order_by="Bullet.position")
|
||||
|
||||
The standard relationship mapping will produce a list-like attribute on each Slide
|
||||
containing all related Bullets, but coping with changes in ordering is totally
|
||||
your responsibility. If you insert a Bullet into that list, there is no
|
||||
magic- it won't have a position attribute unless you assign it it one, and
|
||||
you'll need to manually renumber all the subsequent Bullets in the list to
|
||||
accommodate the insert.
|
||||
class Bullet(Base):
|
||||
__tablename__ = 'bullet'
|
||||
id = Column(Integer, primary_key=True)
|
||||
slide_id = Column(Integer, ForeignKey('slide.id'))
|
||||
position = Column(Integer)
|
||||
text = Column(String)
|
||||
|
||||
An ``orderinglist`` can automate this and manage the 'position' attribute on all
|
||||
related bullets for you.
|
||||
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.
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
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::
|
||||
|
||||
mapper(Slide, slides_table, properties={
|
||||
'bullets': relationship(Bullet,
|
||||
collection_class=ordering_list('position'),
|
||||
order_by=[bullets_table.c.position])
|
||||
})
|
||||
mapper(Bullet, bullets_table)
|
||||
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())
|
||||
@@ -69,73 +82,87 @@ related bullets for you.
|
||||
s.bullets[2].position
|
||||
>>> 2
|
||||
|
||||
Use the ``ordering_list`` function to set up the ``collection_class`` on relationships
|
||||
(as in the mapper example above). This implementation depends on the list
|
||||
starting in the proper order, so be SURE to put an order_by on your relationship.
|
||||
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::
|
||||
|
||||
``ordering_list`` 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. ``ordering_list`` doesn't do any of this for you,
|
||||
: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.
|
||||
|
||||
``ordering_list`` takes the name of the related object's ordering attribute as
|
||||
: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 ``ordering_list`` is synchronized with the ordering attribute:
|
||||
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``.
|
||||
|
||||
Ordering values are not limited to incrementing integers. Almost any scheme
|
||||
can implemented by supplying a custom ``ordering_func`` that maps a Python list
|
||||
index to any value you require.
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
from sqlalchemy.orm.collections import collection
|
||||
from sqlalchemy import util
|
||||
|
||||
__all__ = [ 'ordering_list' ]
|
||||
__all__ = ['ordering_list']
|
||||
|
||||
|
||||
def ordering_list(attr, count_from=None, **kw):
|
||||
"""Prepares an OrderingList factory for use in mapper definitions.
|
||||
"""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. Arguments are:
|
||||
Returns an object suitable for use as an argument to a Mapper
|
||||
relationship's ``collection_class`` option. e.g.::
|
||||
|
||||
attr
|
||||
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
|
||||
|
||||
count_from (optional)
|
||||
: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.
|
||||
|
||||
Passes along any keyword arguments to ``OrderingList`` constructor.
|
||||
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."""
|
||||
|
||||
@@ -147,8 +174,9 @@ def count_from_n_factory(start):
|
||||
pass
|
||||
return f
|
||||
|
||||
|
||||
def _unsugar_count_from(**kw):
|
||||
"""Builds counting functions from keywrod arguments.
|
||||
"""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.
|
||||
@@ -164,12 +192,13 @@ def _unsugar_count_from(**kw):
|
||||
kw['ordering_func'] = count_from_n_factory(count_from)
|
||||
return kw
|
||||
|
||||
|
||||
class OrderingList(list):
|
||||
"""A custom list that manages position information for its children.
|
||||
|
||||
See the module and __init__ documentation for more details. The
|
||||
``ordering_list`` factory function is used to configure ``OrderingList``
|
||||
collections in ``mapper`` relationship definitions.
|
||||
The :class:`.OrderingList` object is normally set up using the
|
||||
:func:`.ordering_list` factory function, used in conjunction with
|
||||
the :func:`.relationship` function.
|
||||
|
||||
"""
|
||||
|
||||
@@ -188,9 +217,10 @@ class OrderingList(list):
|
||||
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.
|
||||
: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.
|
||||
@@ -215,7 +245,7 @@ class OrderingList(list):
|
||||
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. Spooky action at a distance.
|
||||
concurrent modification error.
|
||||
|
||||
Recommend leaving this with the default of False, and just call
|
||||
``reorder()`` if you're doing ``append()`` operations with
|
||||
@@ -314,9 +344,24 @@ class OrderingList(list):
|
||||
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
|
||||
@@ -1,5 +1,5 @@
|
||||
# ext/serializer.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
# ext/sqlsoup.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
|
||||
.. note::
|
||||
|
||||
SQLSoup is now its own project. Documentation
|
||||
and project status are available at:
|
||||
|
||||
http://pypi.python.org/pypi/sqlsoup
|
||||
|
||||
http://readthedocs.org/docs/sqlsoup
|
||||
|
||||
SQLSoup will no longer be included with SQLAlchemy as of
|
||||
version 0.8.
|
||||
.. versionchanged:: 0.8
|
||||
SQLSoup is now its own project. Documentation
|
||||
and project status are available at:
|
||||
http://pypi.python.org/pypi/sqlsoup and
|
||||
http://readthedocs.org/docs/sqlsoup\ .
|
||||
SQLSoup will no longer be included with SQLAlchemy.
|
||||
|
||||
|
||||
Introduction
|
||||
@@ -256,8 +251,8 @@ accepts in normal mapper definition:
|
||||
Advanced Use
|
||||
============
|
||||
|
||||
Sessions, Transations and Application Integration
|
||||
-------------------------------------------------
|
||||
Sessions, Transactions and Application Integration
|
||||
---------------------------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -599,7 +594,7 @@ class SqlSoup(object):
|
||||
self.session.flush()
|
||||
|
||||
def rollback(self):
|
||||
"""Rollback the current transction.
|
||||
"""Rollback the current transaction.
|
||||
|
||||
See :meth:`.Session.rollback`.
|
||||
|
||||
@@ -642,7 +637,7 @@ class SqlSoup(object):
|
||||
This is the "master" method that can be used to create any
|
||||
configuration.
|
||||
|
||||
(new in 0.6.6)
|
||||
.. versionadded:: 0.6.6
|
||||
|
||||
:param attrname: String attribute name which will be
|
||||
established as an attribute on this :class:.`.SqlSoup`
|
||||
@@ -723,8 +718,9 @@ class SqlSoup(object):
|
||||
def map(self, selectable, base=None, **mapper_args):
|
||||
"""Map a selectable directly.
|
||||
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced (as of 0.6.6).
|
||||
.. versionchanged:: 0.6.6
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced.
|
||||
|
||||
:param selectable: an :func:`.expression.select` construct.
|
||||
:param base: a Python class which will be used as the
|
||||
@@ -749,8 +745,9 @@ class SqlSoup(object):
|
||||
"""Map a selectable directly, wrapping the
|
||||
selectable in a subquery with labels.
|
||||
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced (as of 0.6.6).
|
||||
.. versionchanged:: 0.6.6
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced.
|
||||
|
||||
:param selectable: an :func:`.expression.select` construct.
|
||||
:param base: a Python class which will be used as the
|
||||
@@ -773,8 +770,9 @@ class SqlSoup(object):
|
||||
base=None, **mapper_args):
|
||||
"""Create an :func:`.expression.join` and map to it.
|
||||
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced (as of 0.6.6).
|
||||
.. versionchanged:: 0.6.6
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced.
|
||||
|
||||
:param left: a mapped class or table object.
|
||||
:param right: a mapped class or table object.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sqlalchemy/interfaces.py
|
||||
# Copyright (C) 2007-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/__init__.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -180,10 +180,8 @@ def create_session(bind=None, **kwargs):
|
||||
def relationship(argument, secondary=None, **kwargs):
|
||||
"""Provide a relationship of a primary Mapper to a secondary Mapper.
|
||||
|
||||
.. note::
|
||||
|
||||
:func:`relationship` is historically known as
|
||||
:func:`relation` prior to version 0.6.
|
||||
.. versionchanged:: 0.6
|
||||
:func:`relationship` is historically known as :func:`relation`.
|
||||
|
||||
This corresponds to a parent-child or associative table relationship. The
|
||||
constructed class is an instance of :class:`.RelationshipProperty`.
|
||||
@@ -299,11 +297,13 @@ def relationship(argument, secondary=None, **kwargs):
|
||||
|
||||
* ``delete-orphan`` - if an item of the child's type is
|
||||
detached from its parent, mark it for deletion.
|
||||
As of version 0.7, this option does not prevent
|
||||
a new instance of the child object from being persisted
|
||||
without a parent to start with; to constrain against
|
||||
that case, ensure the child's foreign key column(s)
|
||||
is configured as NOT NULL
|
||||
|
||||
.. versionchanged:: 0.7
|
||||
This option does not prevent
|
||||
a new instance of the child object from being persisted
|
||||
without a parent to start with; to constrain against
|
||||
that case, ensure the child's foreign key column(s)
|
||||
is configured as NOT NULL
|
||||
|
||||
* ``refresh-expire`` - cascade the :meth:`.Session.expire`
|
||||
and :meth:`~sqlalchemy.orm.session.Session.refresh` operations
|
||||
@@ -390,12 +390,14 @@ def relationship(argument, secondary=None, **kwargs):
|
||||
:param innerjoin=False:
|
||||
when ``True``, joined eager loads will use an inner join to join
|
||||
against related tables instead of an outer join. The purpose
|
||||
of this option is strictly one of performance, as inner joins
|
||||
generally perform better than outer joins. This flag can
|
||||
be set to ``True`` when the relationship references an object
|
||||
via many-to-one using local foreign keys that are not nullable,
|
||||
or when the reference is one-to-one or a collection that is
|
||||
guaranteed to have one or at least one entry.
|
||||
of this option is generally one of performance, as inner joins
|
||||
generally perform better than outer joins. Another reason can be
|
||||
the use of ``with_lockmode``, which does not support outer joins.
|
||||
|
||||
This flag can be set to ``True`` when the relationship references an
|
||||
object via many-to-one using local foreign keys that are not nullable,
|
||||
or when the reference is one-to-one or a collection that is guaranteed
|
||||
to have one or at least one entry.
|
||||
|
||||
:param join_depth:
|
||||
when non-``None``, an integer value indicating how many levels
|
||||
@@ -417,7 +419,9 @@ def relationship(argument, secondary=None, **kwargs):
|
||||
|
||||
* ``immediate`` - items should be loaded as the parents are loaded,
|
||||
using a separate SELECT statement, or identity map fetch for
|
||||
simple many-to-one references. (new as of 0.6.5)
|
||||
simple many-to-one references.
|
||||
|
||||
.. versionadded:: 0.6.5
|
||||
|
||||
* ``joined`` - items should be loaded "eagerly" in the same query as
|
||||
that of the parent, using a JOIN or LEFT OUTER JOIN. Whether
|
||||
@@ -441,7 +445,7 @@ def relationship(argument, secondary=None, **kwargs):
|
||||
|
||||
* True - a synonym for 'select'
|
||||
|
||||
* False - a synonyn for 'joined'
|
||||
* False - a synonym for 'joined'
|
||||
|
||||
* None - a synonym for 'noload'
|
||||
|
||||
@@ -668,7 +672,9 @@ def column_property(*cols, **kw):
|
||||
flag is available for applications that make use of
|
||||
:func:`.attributes.get_history` or :meth:`.Session.is_modified`
|
||||
which also need to know
|
||||
the "previous" value of the attribute. (new in 0.6.6)
|
||||
the "previous" value of the attribute.
|
||||
|
||||
.. versionadded:: 0.6.6
|
||||
|
||||
:param comparator_factory: a class which extends
|
||||
:class:`.ColumnProperty.Comparator` which provides custom SQL clause
|
||||
@@ -700,8 +706,8 @@ def column_property(*cols, **kw):
|
||||
Note however that the :class:`.Session` with default expiration
|
||||
settings still expires
|
||||
all attributes after a :meth:`.Session.commit` call, however.
|
||||
New in 0.7.3.
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
:param extension:
|
||||
an
|
||||
@@ -719,7 +725,7 @@ def column_property(*cols, **kw):
|
||||
def composite(class_, *cols, **kwargs):
|
||||
"""Return a composite column-based property for use with a Mapper.
|
||||
|
||||
See the mapping documention section :ref:`mapper_composite` for a full
|
||||
See the mapping documentation section :ref:`mapper_composite` for a full
|
||||
usage example.
|
||||
|
||||
:param class\_:
|
||||
@@ -732,8 +738,10 @@ def composite(class_, *cols, **kwargs):
|
||||
When ``True``, indicates that the "previous" value for a
|
||||
scalar attribute should be loaded when replaced, if not
|
||||
already loaded. See the same flag on :func:`.column_property`.
|
||||
(This flag becomes meaningful specifically for
|
||||
:func:`.composite` in 0.7 - previously it was a placeholder).
|
||||
|
||||
.. versionchanged:: 0.7
|
||||
This flag specifically becomes meaningful
|
||||
- previously it was a placeholder.
|
||||
|
||||
:param group:
|
||||
A group name for this property when marked as deferred.
|
||||
@@ -973,7 +981,7 @@ def mapper(class_, local_table=None, *args, **params):
|
||||
"polymorphic_identity":"employee"
|
||||
}
|
||||
|
||||
As of SQLAlchemy 0.7.4, it may also be specified
|
||||
It may also be specified
|
||||
as a SQL expression, as in this example where we
|
||||
use the :func:`.case` construct to provide a conditional
|
||||
approach::
|
||||
@@ -992,7 +1000,7 @@ def mapper(class_, local_table=None, *args, **params):
|
||||
"polymorphic_identity":"employee"
|
||||
}
|
||||
|
||||
Also as of 0.7.4, it may also refer to any attribute
|
||||
It may also refer to any attribute
|
||||
configured with :func:`.column_property`, or to the
|
||||
string name of one::
|
||||
|
||||
@@ -1013,6 +1021,11 @@ def mapper(class_, local_table=None, *args, **params):
|
||||
"polymorphic_identity":"employee"
|
||||
}
|
||||
|
||||
.. versionchanged:: 0.7.4
|
||||
``polymorphic_on`` may be specified as a SQL expression,
|
||||
or refer to any attribute configured with
|
||||
:func:`.column_property`, or to the string name of one.
|
||||
|
||||
When setting ``polymorphic_on`` to reference an
|
||||
attribute or expression that's not present in the
|
||||
locally mapped :class:`.Table`, yet the value
|
||||
@@ -1119,11 +1132,10 @@ def synonym(name, map_column=False, descriptor=None,
|
||||
comparator_factory=None, doc=None):
|
||||
"""Denote an attribute name as a synonym to a mapped property.
|
||||
|
||||
.. note::
|
||||
|
||||
:func:`.synonym` is superseded as of 0.7 by
|
||||
the :mod:`~sqlalchemy.ext.hybrid` extension. See
|
||||
the documentation for hybrids at :ref:`hybrids_toplevel`.
|
||||
.. versionchanged:: 0.7
|
||||
:func:`.synonym` is superseded by the :mod:`~sqlalchemy.ext.hybrid`
|
||||
extension. See the documentation for hybrids
|
||||
at :ref:`hybrids_toplevel`.
|
||||
|
||||
Used with the ``properties`` dictionary sent to
|
||||
:func:`~sqlalchemy.orm.mapper`::
|
||||
@@ -1163,11 +1175,10 @@ def comparable_property(comparator_factory, descriptor=None):
|
||||
"""Provides a method of applying a :class:`.PropComparator`
|
||||
to any Python descriptor attribute.
|
||||
|
||||
.. note::
|
||||
|
||||
:func:`.comparable_property` is superseded as of 0.7 by
|
||||
the :mod:`~sqlalchemy.ext.hybrid` extension. See the example
|
||||
at :ref:`hybrid_custom_comparators`.
|
||||
.. versionchanged:: 0.7
|
||||
:func:`.comparable_property` is superseded by
|
||||
the :mod:`~sqlalchemy.ext.hybrid` extension. See the example
|
||||
at :ref:`hybrid_custom_comparators`.
|
||||
|
||||
Allows any Python descriptor to behave like a SQL-enabled
|
||||
attribute when used at the class level in queries, allowing
|
||||
@@ -1218,7 +1229,7 @@ def comparable_property(comparator_factory, descriptor=None):
|
||||
Optional when used in a ``properties={}`` declaration. The Python
|
||||
descriptor or property to layer comparison behavior on top of.
|
||||
|
||||
The like-named descriptor will be automatically retreived from the
|
||||
The like-named descriptor will be automatically retrieved from the
|
||||
mapped class if left blank in a ``properties`` declaration.
|
||||
|
||||
"""
|
||||
@@ -1267,18 +1278,17 @@ def joinedload(*keys, **kw):
|
||||
"""Return a ``MapperOption`` that will convert the property of the given
|
||||
name or series of mapped attributes into an joined eager load.
|
||||
|
||||
.. note::
|
||||
|
||||
This function is known as :func:`eagerload` in all versions
|
||||
of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
|
||||
series. :func:`eagerload` will remain available for the foreseeable
|
||||
future in order to enable cross-compatibility.
|
||||
.. versionchanged:: 0.6beta3
|
||||
This function is known as :func:`eagerload` in all versions
|
||||
of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
|
||||
series. :func:`eagerload` will remain available for the foreseeable
|
||||
future in order to enable cross-compatibility.
|
||||
|
||||
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
|
||||
|
||||
examples::
|
||||
|
||||
# joined-load the "orders" colleciton on "User"
|
||||
# joined-load the "orders" collection on "User"
|
||||
query(User).options(joinedload(User.orders))
|
||||
|
||||
# joined-load the "keywords" collection on each "Item",
|
||||
@@ -1327,12 +1337,11 @@ def joinedload_all(*keys, **kw):
|
||||
given dot-separated path or series of mapped attributes
|
||||
into an joined eager load.
|
||||
|
||||
.. note::
|
||||
|
||||
This function is known as :func:`eagerload_all` in all versions
|
||||
of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
|
||||
series. :func:`eagerload_all` will remain available for the
|
||||
foreseeable future in order to enable cross-compatibility.
|
||||
.. versionchanged:: 0.6beta3
|
||||
This function is known as :func:`eagerload_all` in all versions
|
||||
of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4
|
||||
series. :func:`eagerload_all` will remain available for the
|
||||
foreseeable future in order to enable cross-compatibility.
|
||||
|
||||
Used with :meth:`~sqlalchemy.orm.query.Query.options`.
|
||||
|
||||
@@ -1381,7 +1390,7 @@ def subqueryload(*keys):
|
||||
|
||||
examples::
|
||||
|
||||
# subquery-load the "orders" colleciton on "User"
|
||||
# subquery-load the "orders" collection on "User"
|
||||
query(User).options(subqueryload(User.orders))
|
||||
|
||||
# subquery-load the "keywords" collection on each "Item",
|
||||
@@ -1478,7 +1487,7 @@ def immediateload(*keys):
|
||||
|
||||
See also: :func:`lazyload`, :func:`eagerload`, :func:`subqueryload`
|
||||
|
||||
New as of verison 0.6.5.
|
||||
.. versionadded:: 0.6.5
|
||||
|
||||
"""
|
||||
return strategies.EagerLazyOption(keys, lazy='immediate')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/attributes.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -413,7 +413,7 @@ class AttributeImpl(object):
|
||||
Only applies to object-based attributes.
|
||||
|
||||
This is an inlining of existing functionality
|
||||
which roughly correponds to:
|
||||
which roughly corresponds to:
|
||||
|
||||
get_state_history(
|
||||
state,
|
||||
@@ -653,7 +653,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
|
||||
if current is not None:
|
||||
ret = [(instance_state(current), current)]
|
||||
else:
|
||||
ret = []
|
||||
ret = [(None, None)]
|
||||
|
||||
if self.key in state.committed_state:
|
||||
original = state.committed_state[self.key]
|
||||
@@ -686,7 +686,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
|
||||
old is not PASSIVE_NO_RESULT and \
|
||||
check_old is not old:
|
||||
if pop:
|
||||
return
|
||||
return
|
||||
else:
|
||||
raise ValueError(
|
||||
"Object %s not associated with %s on attribute '%s'" % (
|
||||
@@ -973,6 +973,14 @@ def backref_listeners(attribute, key, uselist):
|
||||
|
||||
# use easily recognizable names for stack traces
|
||||
|
||||
parent_token = attribute.impl.parent_token
|
||||
|
||||
def _acceptable_key_err(child_state, initiator):
|
||||
raise ValueError(
|
||||
"Object %s not associated with attribute of "
|
||||
"type %s" % (mapperutil.state_str(child_state),
|
||||
manager_of_class(initiator.class_)[initiator.key]))
|
||||
|
||||
def emit_backref_from_scalar_set_event(state, child, oldchild, initiator):
|
||||
if oldchild is child:
|
||||
return child
|
||||
@@ -991,35 +999,47 @@ def backref_listeners(attribute, key, uselist):
|
||||
if child is not None:
|
||||
child_state, child_dict = instance_state(child),\
|
||||
instance_dict(child)
|
||||
child_state.manager[key].impl.append(
|
||||
child_state,
|
||||
child_dict,
|
||||
state.obj(),
|
||||
initiator,
|
||||
passive=PASSIVE_NO_FETCH)
|
||||
child_impl = child_state.manager[key].impl
|
||||
if initiator.parent_token is not parent_token and \
|
||||
initiator.parent_token is not child_impl.parent_token:
|
||||
_acceptable_key_err(state, initiator)
|
||||
child_impl.append(
|
||||
child_state,
|
||||
child_dict,
|
||||
state.obj(),
|
||||
initiator,
|
||||
passive=PASSIVE_NO_FETCH)
|
||||
return child
|
||||
|
||||
def emit_backref_from_collection_append_event(state, child, initiator):
|
||||
child_state, child_dict = instance_state(child), \
|
||||
instance_dict(child)
|
||||
child_state.manager[key].impl.append(
|
||||
child_state,
|
||||
child_dict,
|
||||
state.obj(),
|
||||
initiator,
|
||||
passive=PASSIVE_NO_FETCH)
|
||||
child_impl = child_state.manager[key].impl
|
||||
if initiator.parent_token is not parent_token and \
|
||||
initiator.parent_token is not child_impl.parent_token:
|
||||
_acceptable_key_err(state, initiator)
|
||||
child_impl.append(
|
||||
child_state,
|
||||
child_dict,
|
||||
state.obj(),
|
||||
initiator,
|
||||
passive=PASSIVE_NO_FETCH)
|
||||
return child
|
||||
|
||||
def emit_backref_from_collection_remove_event(state, child, initiator):
|
||||
if child is not None:
|
||||
child_state, child_dict = instance_state(child),\
|
||||
instance_dict(child)
|
||||
child_state.manager[key].impl.pop(
|
||||
child_state,
|
||||
child_dict,
|
||||
state.obj(),
|
||||
initiator,
|
||||
passive=PASSIVE_NO_FETCH)
|
||||
child_impl = child_state.manager[key].impl
|
||||
# can't think of a path that would produce an initiator
|
||||
# mismatch here, as it would require an existing collection
|
||||
# mismatch.
|
||||
child_impl.pop(
|
||||
child_state,
|
||||
child_dict,
|
||||
state.obj(),
|
||||
initiator,
|
||||
passive=PASSIVE_NO_FETCH)
|
||||
|
||||
if uselist:
|
||||
event.listen(attribute, "append",
|
||||
@@ -1126,7 +1146,7 @@ class History(tuple):
|
||||
return cls((), (), ())
|
||||
else:
|
||||
return cls((), [current], ())
|
||||
# dont let ClauseElement expressions here trip things up
|
||||
# don't let ClauseElement expressions here trip things up
|
||||
elif attribute.is_equal(current, original) is True:
|
||||
return cls((), [current], ())
|
||||
else:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/collections.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -111,22 +111,57 @@ import weakref
|
||||
from sqlalchemy.sql import expression
|
||||
from sqlalchemy import schema, util, exc as sa_exc
|
||||
|
||||
|
||||
|
||||
__all__ = ['collection', 'collection_adapter',
|
||||
'mapped_collection', 'column_mapped_collection',
|
||||
'attribute_mapped_collection']
|
||||
|
||||
__instrumentation_mutex = util.threading.Lock()
|
||||
|
||||
|
||||
class _PlainColumnGetter(object):
|
||||
"""Plain column getter, stores collection of Column objects
|
||||
directly.
|
||||
|
||||
Serializes to a :class:`._SerializableColumnGetterV2`
|
||||
which has more expensive __call__() performance
|
||||
and some rare caveats.
|
||||
|
||||
"""
|
||||
def __init__(self, cols):
|
||||
self.cols = cols
|
||||
self.composite = len(cols) > 1
|
||||
|
||||
def __reduce__(self):
|
||||
return _SerializableColumnGetterV2._reduce_from_cols(self.cols)
|
||||
|
||||
def _cols(self, mapper):
|
||||
return self.cols
|
||||
|
||||
def __call__(self, value):
|
||||
state = instance_state(value)
|
||||
m = _state_mapper(state)
|
||||
|
||||
key = [
|
||||
m._get_state_attr_by_column(state, state.dict, col)
|
||||
for col in self._cols(m)
|
||||
]
|
||||
|
||||
if self.composite:
|
||||
return tuple(key)
|
||||
else:
|
||||
return key[0]
|
||||
|
||||
class _SerializableColumnGetter(object):
|
||||
"""Column-based getter used in version 0.7.6 only.
|
||||
|
||||
Remains here for pickle compatibility with 0.7.6.
|
||||
|
||||
"""
|
||||
def __init__(self, colkeys):
|
||||
self.colkeys = colkeys
|
||||
self.composite = len(colkeys) > 1
|
||||
|
||||
def __reduce__(self):
|
||||
return _SerializableColumnGetter, (self.colkeys,)
|
||||
|
||||
def __call__(self, value):
|
||||
state = instance_state(value)
|
||||
m = _state_mapper(state)
|
||||
@@ -139,6 +174,48 @@ class _SerializableColumnGetter(object):
|
||||
else:
|
||||
return key[0]
|
||||
|
||||
class _SerializableColumnGetterV2(_PlainColumnGetter):
|
||||
"""Updated serializable getter which deals with
|
||||
multi-table mapped classes.
|
||||
|
||||
Two extremely unusual cases are not supported.
|
||||
Mappings which have tables across multiple metadata
|
||||
objects, or which are mapped to non-Table selectables
|
||||
linked across inheriting mappers may fail to function
|
||||
here.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, colkeys):
|
||||
self.colkeys = colkeys
|
||||
self.composite = len(colkeys) > 1
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, (self.colkeys,)
|
||||
|
||||
@classmethod
|
||||
def _reduce_from_cols(cls, cols):
|
||||
def _table_key(c):
|
||||
if not isinstance(c.table, expression.TableClause):
|
||||
return None
|
||||
else:
|
||||
return c.table.key
|
||||
colkeys = [(c.key, _table_key(c)) for c in cols]
|
||||
return _SerializableColumnGetterV2, (colkeys,)
|
||||
|
||||
def _cols(self, mapper):
|
||||
cols = []
|
||||
metadata = getattr(mapper.local_table, 'metadata', None)
|
||||
for (ckey, tkey) in self.colkeys:
|
||||
if tkey is None or \
|
||||
metadata is None or \
|
||||
tkey not in metadata:
|
||||
cols.append(mapper.local_table.c[ckey])
|
||||
else:
|
||||
cols.append(metadata.tables[tkey].c[ckey])
|
||||
return cols
|
||||
|
||||
|
||||
def column_mapped_collection(mapping_spec):
|
||||
"""A dictionary-based collection type with column-based keying.
|
||||
|
||||
@@ -155,10 +232,10 @@ def column_mapped_collection(mapping_spec):
|
||||
from sqlalchemy.orm.util import _state_mapper
|
||||
from sqlalchemy.orm.attributes import instance_state
|
||||
|
||||
cols = [c.key for c in [
|
||||
expression._only_column_elements(q, "mapping_spec")
|
||||
for q in util.to_list(mapping_spec)]]
|
||||
keyfunc = _SerializableColumnGetter(cols)
|
||||
cols = [expression._only_column_elements(q, "mapping_spec")
|
||||
for q in util.to_list(mapping_spec)
|
||||
]
|
||||
keyfunc = _PlainColumnGetter(cols)
|
||||
return lambda: MappedCollection(keyfunc)
|
||||
|
||||
class _SerializableAttrGetter(object):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/dependency.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/deprecated_interfaces.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -247,10 +247,11 @@ class MapperExtension(object):
|
||||
instance population will not proceed, giving this extension an
|
||||
opportunity to populate the instance itself, if desired.
|
||||
|
||||
As of 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.
|
||||
.. 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/descriptor_props.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/dynamic.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -12,7 +12,6 @@ basic add/delete mutation.
|
||||
"""
|
||||
|
||||
from sqlalchemy import log, util
|
||||
from sqlalchemy import exc as sa_exc
|
||||
from sqlalchemy.orm import exc as orm_exc
|
||||
from sqlalchemy.sql import operators
|
||||
from sqlalchemy.orm import (
|
||||
@@ -20,12 +19,16 @@ from sqlalchemy.orm import (
|
||||
)
|
||||
from sqlalchemy.orm.query import Query
|
||||
from sqlalchemy.orm.util import has_identity
|
||||
from sqlalchemy.orm import attributes, collections
|
||||
from sqlalchemy.orm import collections
|
||||
|
||||
class DynaLoader(strategies.AbstractRelationshipLoader):
|
||||
def init_class_attribute(self, mapper):
|
||||
self.is_class_level = True
|
||||
|
||||
if not self.uselist:
|
||||
util.warn(
|
||||
"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,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/evaluator.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/events.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -388,8 +388,16 @@ class MapperEvents(event.Events):
|
||||
def mapper_configured(self, mapper, class_):
|
||||
"""Called when the mapper for the class is fully configured.
|
||||
|
||||
This event is the latest phase of mapper construction.
|
||||
The mapper should be in its final state.
|
||||
This event is the latest phase of mapper construction, and
|
||||
is invoked when the mapped classes are first used, so that relationships
|
||||
between mappers can be resolved. When the event is called,
|
||||
the mapper should be in its final state.
|
||||
|
||||
While the configuration event normally occurs automatically,
|
||||
it can be forced to occur ahead of time, in the case where the event
|
||||
is needed before any actual mapper usage, by using the
|
||||
:func:`.configure_mappers` function.
|
||||
|
||||
|
||||
:param mapper: the :class:`.Mapper` which is the target
|
||||
of this event.
|
||||
@@ -1007,7 +1015,7 @@ class SessionEvents(event.Events):
|
||||
for the given :class:`.Session` is available via the
|
||||
:attr:`.Session.transaction` attribute.
|
||||
|
||||
New in 0.7.3.
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/exc.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -32,7 +32,9 @@ class StaleDataError(sa.exc.SQLAlchemyError):
|
||||
the object was previously attached to a different parent
|
||||
identity which was garbage collected, and a decision
|
||||
cannot be made if the new parent was really the most
|
||||
recent "parent" (new in 0.7.4).
|
||||
recent "parent".
|
||||
|
||||
.. versionadded:: 0.7.4
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/identity.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/instrumentation.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/interfaces.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/mapper.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -580,6 +580,12 @@ class Mapper(object):
|
||||
self.inherits._inheriting_mappers.add(self)
|
||||
self.passive_updates = self.inherits.passive_updates
|
||||
self._all_tables = self.inherits._all_tables
|
||||
for key, prop in mapper._props.iteritems():
|
||||
if key not in self._props and \
|
||||
not self._should_exclude(key, key, local=False,
|
||||
column=None):
|
||||
self._adapt_inherited_property(key, prop, False)
|
||||
|
||||
|
||||
def _set_polymorphic_on(self, polymorphic_on):
|
||||
self.polymorphic_on = polymorphic_on
|
||||
@@ -678,9 +684,10 @@ class Mapper(object):
|
||||
self._reconstructor = method
|
||||
event.listen(manager, 'load', _event_on_load, raw=True)
|
||||
elif hasattr(method, '__sa_validators__'):
|
||||
include_removes = getattr(method, "__sa_include_removes__", False)
|
||||
for name in method.__sa_validators__:
|
||||
self.validators = self.validators.union(
|
||||
{name : method}
|
||||
{name : (method, include_removes)}
|
||||
)
|
||||
|
||||
manager.info[_INSTRUMENTOR] = self
|
||||
@@ -940,7 +947,6 @@ class Mapper(object):
|
||||
# with_polymorphic selectable, as when use polymorphic_union.
|
||||
# we'll make a separate ColumnProperty for it.
|
||||
instrument = True
|
||||
|
||||
key = getattr(col, 'key', None)
|
||||
if key:
|
||||
if self._should_exclude(col.key, col.key, False, col):
|
||||
@@ -2291,7 +2297,7 @@ def reconstructor(fn):
|
||||
fn.__sa_reconstructor__ = True
|
||||
return fn
|
||||
|
||||
def validates(*names):
|
||||
def validates(*names, **kw):
|
||||
"""Decorate a method as a 'validator' for one or more named properties.
|
||||
|
||||
Designates a method as a validator, a method which receives the
|
||||
@@ -2307,9 +2313,18 @@ def validates(*names):
|
||||
an assertion to avoid recursion overflows. This is a reentrant
|
||||
condition which is not supported.
|
||||
|
||||
:param \*names: list of attribute names to be validated.
|
||||
:param include_removes: if True, "remove" events will be
|
||||
sent as well - the validation function must accept an additional
|
||||
argument "is_remove" which will be a boolean.
|
||||
|
||||
.. versionadded:: 0.7.7
|
||||
|
||||
"""
|
||||
include_removes = kw.pop('include_removes', False)
|
||||
def wrap(fn):
|
||||
fn.__sa_validators__ = names
|
||||
fn.__sa_include_removes__ = include_removes
|
||||
return fn
|
||||
return wrap
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/persistence.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -466,7 +466,7 @@ def _emit_update_statements(base_mapper, uowtransaction,
|
||||
if needs_version_id:
|
||||
clause.clauses.append(mapper.version_id_col ==\
|
||||
sql.bindparam(mapper.version_id_col._label,
|
||||
type_=col.type))
|
||||
type_=mapper.version_id_col.type))
|
||||
|
||||
return table.update(clause)
|
||||
|
||||
@@ -748,13 +748,15 @@ def _connections_for_states(base_mapper, uowtransaction, states):
|
||||
connection_callable = \
|
||||
uowtransaction.session.connection_callable
|
||||
else:
|
||||
connection = uowtransaction.transaction.connection(
|
||||
base_mapper)
|
||||
connection = None
|
||||
connection_callable = None
|
||||
|
||||
for state in _sort_states(states):
|
||||
if connection_callable:
|
||||
connection = connection_callable(base_mapper, state.obj())
|
||||
elif not connection:
|
||||
connection = uowtransaction.transaction.connection(
|
||||
base_mapper)
|
||||
|
||||
mapper = _state_mapper(state)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/properties.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -136,7 +136,9 @@ class ColumnProperty(StrategizedProperty):
|
||||
|
||||
def merge(self, session, source_state, source_dict, dest_state,
|
||||
dest_dict, load, _recursive):
|
||||
if self.key in source_dict:
|
||||
if not self.instrument:
|
||||
return
|
||||
elif self.key in source_dict:
|
||||
value = source_dict[self.key]
|
||||
|
||||
if not load:
|
||||
@@ -144,9 +146,8 @@ class ColumnProperty(StrategizedProperty):
|
||||
else:
|
||||
impl = dest_state.get_impl(self.key)
|
||||
impl.set(dest_state, dest_dict, value, None)
|
||||
else:
|
||||
if dest_state.has_identity and self.key not in dest_dict:
|
||||
dest_state.expire_attributes(dest_dict, [self.key])
|
||||
elif dest_state.has_identity and self.key not in dest_dict:
|
||||
dest_state.expire_attributes(dest_dict, [self.key])
|
||||
|
||||
class Comparator(PropComparator):
|
||||
@util.memoized_instancemethod
|
||||
@@ -322,12 +323,6 @@ class RelationshipProperty(StrategizedProperty):
|
||||
else:
|
||||
return elem
|
||||
|
||||
def operate(self, op, *other, **kwargs):
|
||||
return op(self, *other, **kwargs)
|
||||
|
||||
def reverse_operate(self, op, other, **kwargs):
|
||||
return op(self, *other, **kwargs)
|
||||
|
||||
def of_type(self, cls):
|
||||
"""Produce a construct that represents a particular 'subtype' of
|
||||
attribute for the parent class.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/query.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -77,6 +77,7 @@ class Query(object):
|
||||
_group_by = False
|
||||
_having = None
|
||||
_distinct = False
|
||||
_prefixes = None
|
||||
_offset = None
|
||||
_limit = None
|
||||
_statement = None
|
||||
@@ -454,7 +455,7 @@ class Query(object):
|
||||
"""Return the full SELECT statement represented by this :class:`.Query`
|
||||
represented as a common table expression (CTE).
|
||||
|
||||
The :meth:`.Query.cte` method is new in 0.7.6.
|
||||
.. versionadded:: 0.7.6
|
||||
|
||||
Parameters and usage are the same as those of the
|
||||
:meth:`._SelectBase.cte` method; see that method for
|
||||
@@ -512,7 +513,7 @@ class Query(object):
|
||||
|
||||
Analogous to :meth:`sqlalchemy.sql._SelectBaseMixin.label`.
|
||||
|
||||
New in 0.6.5.
|
||||
.. versionadded:: 0.6.5
|
||||
|
||||
"""
|
||||
|
||||
@@ -525,7 +526,7 @@ class Query(object):
|
||||
|
||||
Analogous to :meth:`sqlalchemy.sql._SelectBaseMixin.as_scalar`.
|
||||
|
||||
New in 0.6.5.
|
||||
.. versionadded:: 0.6.5
|
||||
|
||||
"""
|
||||
|
||||
@@ -917,7 +918,8 @@ class Query(object):
|
||||
'_order_by', '_group_by',
|
||||
'_limit', '_offset',
|
||||
'_joinpath', '_joinpoint',
|
||||
'_distinct', '_having'
|
||||
'_distinct', '_having',
|
||||
'_prefixes',
|
||||
):
|
||||
self.__dict__.pop(attr, None)
|
||||
self._set_select_from(fromclause)
|
||||
@@ -979,7 +981,7 @@ class Query(object):
|
||||
q = q.join((subq, subq.c.email < Address.email)).\\
|
||||
limit(1)
|
||||
|
||||
New in 0.6.5.
|
||||
.. versionadded:: 0.6.5
|
||||
|
||||
"""
|
||||
self._set_entities(entities)
|
||||
@@ -1056,7 +1058,7 @@ class Query(object):
|
||||
This allows ad-hoc recipes to be created for :class:`.Query`
|
||||
objects. See the example at :ref:`hybrid_transformers`.
|
||||
|
||||
:meth:`~.Query.with_transformation` is new in SQLAlchemy 0.7.4.
|
||||
.. versionadded:: 0.7.4
|
||||
|
||||
"""
|
||||
return fn(self)
|
||||
@@ -1103,10 +1105,18 @@ class Query(object):
|
||||
``FOR UPDATE`` (standard SQL, supported by most dialects)
|
||||
|
||||
``'update_nowait'`` - passes ``for_update='nowait'``, which
|
||||
translates to ``FOR UPDATE NOWAIT`` (supported by Oracle)
|
||||
translates to ``FOR UPDATE NOWAIT`` (supported by Oracle,
|
||||
PostgreSQL 8.1 upwards)
|
||||
|
||||
``'read'`` - passes ``for_update='read'``, which translates to
|
||||
``LOCK IN SHARE MODE`` (supported by MySQL).
|
||||
``LOCK IN SHARE MODE`` (for MySQL), and ``FOR SHARE`` (for
|
||||
PostgreSQL)
|
||||
|
||||
``'read_nowait'`` - passes ``for_update='read_nowait'``, which
|
||||
translates to ``FOR SHARE NOWAIT`` (supported by PostgreSQL).
|
||||
|
||||
.. versionadded:: 0.7.7
|
||||
``FOR SHARE`` and ``FOR SHARE NOWAIT`` (PostgreSQL).
|
||||
"""
|
||||
|
||||
self._lockmode = mode
|
||||
@@ -1140,7 +1150,7 @@ class Query(object):
|
||||
|
||||
session.query(MyClass).filter(MyClass.name == 'some name')
|
||||
|
||||
Multiple criteria are joined together by AND (new in 0.7.5)::
|
||||
Multiple criteria are joined together by AND::
|
||||
|
||||
session.query(MyClass).\\
|
||||
filter(MyClass.name == 'some name', MyClass.id > 5)
|
||||
@@ -1149,6 +1159,9 @@ class Query(object):
|
||||
WHERE clause of a select. String expressions are coerced
|
||||
into SQL expression constructs via the :func:`.text` construct.
|
||||
|
||||
.. versionchanged:: 0.7.5
|
||||
Multiple criteria joined by AND.
|
||||
|
||||
See also:
|
||||
|
||||
:meth:`.Query.filter_by` - filter on keyword expressions.
|
||||
@@ -1233,7 +1246,7 @@ class Query(object):
|
||||
@_generative(_no_statement_condition, _no_limit_offset)
|
||||
def group_by(self, *criterion):
|
||||
"""apply one or more GROUP BY criterion to the query and return
|
||||
the newly resulting ``Query``"""
|
||||
the newly resulting :class:`.Query`"""
|
||||
|
||||
criterion = list(chain(*[_orm_columns(c) for c in criterion]))
|
||||
|
||||
@@ -1247,7 +1260,19 @@ class Query(object):
|
||||
@_generative(_no_statement_condition, _no_limit_offset)
|
||||
def having(self, criterion):
|
||||
"""apply a HAVING criterion to the query and return the
|
||||
newly resulting ``Query``."""
|
||||
newly resulting :class:`.Query`.
|
||||
|
||||
:meth:`having` is used in conjunction with :meth:`group_by`.
|
||||
|
||||
HAVING criterion makes it possible to use filters on aggregate
|
||||
functions like COUNT, SUM, AVG, MAX, and MIN, eg.::
|
||||
|
||||
q = session.query(User.id).\\
|
||||
join(User.addresses).\\
|
||||
group_by(User.id).\\
|
||||
having(func.count(Address.id) > 2)
|
||||
|
||||
"""
|
||||
|
||||
if isinstance(criterion, basestring):
|
||||
criterion = sql.text(criterion)
|
||||
@@ -1460,18 +1485,15 @@ class Query(object):
|
||||
|
||||
q = session.query(User).join(Address, User.id==Address.user_id)
|
||||
|
||||
.. note::
|
||||
|
||||
In SQLAlchemy 0.6 and earlier, the two argument form of
|
||||
:meth:`~.Query.join` requires the usage of a tuple::
|
||||
|
||||
query(User).join((Address, User.id==Address.user_id))
|
||||
|
||||
This calling form is accepted in 0.7 and further, though
|
||||
is not necessary unless multiple join conditions are passed to
|
||||
a single :meth:`~.Query.join` call, which itself is also not
|
||||
generally necessary as it is now equivalent to multiple
|
||||
calls (this wasn't always the case).
|
||||
.. versionchanged:: 0.7
|
||||
In SQLAlchemy 0.6 and earlier, the two argument form of
|
||||
:meth:`~.Query.join` requires the usage of a tuple:
|
||||
``query(User).join((Address, User.id==Address.user_id))``\ .
|
||||
This calling form is accepted in 0.7 and further, though
|
||||
is not necessary unless multiple join conditions are passed to
|
||||
a single :meth:`~.Query.join` call, which itself is also not
|
||||
generally necessary as it is now equivalent to multiple
|
||||
calls (this wasn't always the case).
|
||||
|
||||
**Advanced Join Targeting and Adaption**
|
||||
|
||||
@@ -1481,7 +1503,7 @@ class Query(object):
|
||||
and :func:`.select` constructs, with either the one or two-argument forms::
|
||||
|
||||
addresses_q = select([Address.user_id]).\\
|
||||
filter(Address.email_address.endswith("@bar.com")).\\
|
||||
where(Address.email_address.endswith("@bar.com")).\\
|
||||
alias()
|
||||
|
||||
q = session.query(User).\\
|
||||
@@ -2057,6 +2079,33 @@ class Query(object):
|
||||
else:
|
||||
self._distinct = criterion
|
||||
|
||||
@_generative()
|
||||
def prefix_with(self, *prefixes):
|
||||
"""Apply the prefixes to the query and return the newly resulting
|
||||
``Query``.
|
||||
|
||||
:param \*prefixes: optional prefixes, typically strings,
|
||||
not using any commas. In particular is useful for MySQL keywords.
|
||||
|
||||
e.g.::
|
||||
|
||||
query = sess.query(User.name).\\
|
||||
prefix_with('HIGH_PRIORITY').\\
|
||||
prefix_with('SQL_SMALL_RESULT', 'ALL')
|
||||
|
||||
Would render::
|
||||
|
||||
SELECT HIGH_PRIORITY SQL_SMALL_RESULT ALL users.name AS users_name
|
||||
FROM users
|
||||
|
||||
.. versionadded:: 0.7.7
|
||||
|
||||
"""
|
||||
if self._prefixes:
|
||||
self._prefixes += prefixes
|
||||
else:
|
||||
self._prefixes = prefixes
|
||||
|
||||
def all(self):
|
||||
"""Return the results represented by this ``Query`` as a list.
|
||||
|
||||
@@ -2125,9 +2174,11 @@ class Query(object):
|
||||
unique entity or entities - this is a successful result for one().
|
||||
|
||||
Calling ``one()`` results in an execution of the underlying query.
|
||||
As of 0.6, ``one()`` fully fetches all results instead of applying
|
||||
any kind of limit, so that the "unique"-ing of entities does not
|
||||
conceal multiple object identities.
|
||||
|
||||
.. versionchanged:: 0.6
|
||||
``one()`` fully fetches all results instead of applying
|
||||
any kind of limit, so that the "unique"-ing of entities does not
|
||||
conceal multiple object identities.
|
||||
|
||||
"""
|
||||
ret = list(self)
|
||||
@@ -2364,14 +2415,16 @@ class Query(object):
|
||||
mapped_entities = [i for i, e in enumerate(self._entities)
|
||||
if isinstance(e, _MapperEntity)]
|
||||
result = []
|
||||
keys = [ent._label_name for ent in self._entities]
|
||||
for row in iterator:
|
||||
newrow = list(row)
|
||||
for i in mapped_entities:
|
||||
newrow[i] = session._merge(
|
||||
attributes.instance_state(newrow[i]),
|
||||
attributes.instance_dict(newrow[i]),
|
||||
load=load, _recursive={})
|
||||
result.append(util.NamedTuple(newrow, row._labels))
|
||||
if newrow[i] is not None:
|
||||
newrow[i] = session._merge(
|
||||
attributes.instance_state(newrow[i]),
|
||||
attributes.instance_dict(newrow[i]),
|
||||
load=load, _recursive={})
|
||||
result.append(util.NamedTuple(newrow, keys))
|
||||
|
||||
return iter(result)
|
||||
finally:
|
||||
@@ -2468,6 +2521,7 @@ class Query(object):
|
||||
'limit':self._limit,
|
||||
'offset':self._offset,
|
||||
'distinct':self._distinct,
|
||||
'prefixes':self._prefixes,
|
||||
'group_by':self._group_by or None,
|
||||
'having':self._having
|
||||
}
|
||||
@@ -2488,8 +2542,8 @@ class Query(object):
|
||||
SELECT <rest of query follows...>
|
||||
) AS anon_1
|
||||
|
||||
Note the above scheme is newly refined in 0.7
|
||||
(as of 0.7b3).
|
||||
.. versionchanged:: 0.7
|
||||
The above scheme is newly refined as of 0.7b3.
|
||||
|
||||
For fine grained control over specific columns
|
||||
to count, to skip the usage of a subquery or
|
||||
@@ -2807,6 +2861,7 @@ class Query(object):
|
||||
if self._lockmode:
|
||||
try:
|
||||
for_update = {'read': 'read',
|
||||
'read_nowait': 'read_nowait',
|
||||
'update': True,
|
||||
'update_nowait': 'nowait',
|
||||
None: False}[self._lockmode]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/scoping.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
+272
-87
@@ -1,5 +1,5 @@
|
||||
# orm/session.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -103,22 +103,61 @@ def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False,
|
||||
|
||||
|
||||
class SessionTransaction(object):
|
||||
"""A Session-level transaction.
|
||||
"""A :class:`.Session`-level transaction.
|
||||
|
||||
This corresponds to one or more Core :class:`~.engine.base.Transaction`
|
||||
instances behind the scenes, with one :class:`~.engine.base.Transaction`
|
||||
per :class:`~.engine.base.Engine` in use.
|
||||
:class:`.SessionTransaction` is a mostly behind-the-scenes object
|
||||
not normally referenced directly by application code. It coordinates
|
||||
among multiple :class:`.Connection` objects, maintaining a database
|
||||
transaction for each one individually, committing or rolling them
|
||||
back all at once. It also provides optional two-phase commit behavior
|
||||
which can augment this coordination operation.
|
||||
|
||||
Direct usage of :class:`.SessionTransaction` is not typically
|
||||
necessary as of SQLAlchemy 0.4; use the :meth:`.Session.rollback` and
|
||||
:meth:`.Session.commit` methods on :class:`.Session` itself to
|
||||
control the transaction.
|
||||
The :attr:`.Session.transaction` attribute of :class:`.Session` refers to the
|
||||
current :class:`.SessionTransaction` object in use, if any.
|
||||
|
||||
The current instance of :class:`.SessionTransaction` for a given
|
||||
:class:`.Session` is available via the :attr:`.Session.transaction`
|
||||
attribute.
|
||||
|
||||
The :class:`.SessionTransaction` object is **not** thread-safe.
|
||||
A :class:`.SessionTransaction` is associated with a :class:`.Session`
|
||||
in its default mode of ``autocommit=False`` immediately, associated
|
||||
with no database connections. As the :class:`.Session` is called upon
|
||||
to emit SQL on behalf of various :class:`.Engine` or :class:`.Connection`
|
||||
objects, a corresponding :class:`.Connection` and associated :class:`.Transaction`
|
||||
is added to a collection within the :class:`.SessionTransaction` object,
|
||||
becoming one of the connection/transaction pairs maintained by the
|
||||
:class:`.SessionTransaction`.
|
||||
|
||||
The lifespan of the :class:`.SessionTransaction` ends when the
|
||||
:meth:`.Session.commit`, :meth:`.Session.rollback` or :meth:`.Session.close`
|
||||
methods are called. At this point, the :class:`.SessionTransaction` removes
|
||||
its association with its parent :class:`.Session`. A :class:`.Session`
|
||||
that is in ``autocommit=False`` mode will create a new
|
||||
:class:`.SessionTransaction` to replace it immediately, whereas a
|
||||
:class:`.Session` that's in ``autocommit=True``
|
||||
mode will remain without a :class:`.SessionTransaction` until the
|
||||
:meth:`.Session.begin` method is called.
|
||||
|
||||
Another detail of :class:`.SessionTransaction` behavior is that it is
|
||||
capable of "nesting". This means that the :meth:`.begin` method can
|
||||
be called while an existing :class:`.SessionTransaction` is already present,
|
||||
producing a new :class:`.SessionTransaction` that temporarily replaces
|
||||
the parent :class:`.SessionTransaction`. When a :class:`.SessionTransaction`
|
||||
is produced as nested, it assigns itself to the :attr:`.Session.transaction`
|
||||
attribute. When it is ended via :meth:`.Session.commit` or :meth:`.Session.rollback`,
|
||||
it restores its parent :class:`.SessionTransaction` back onto the
|
||||
:attr:`.Session.transaction` attribute. The
|
||||
behavior is effectively a stack, where :attr:`.Session.transaction` refers
|
||||
to the current head of the stack.
|
||||
|
||||
The purpose of this stack is to allow nesting of :meth:`.rollback` or
|
||||
:meth:`.commit` calls in context with various flavors of :meth:`.begin`.
|
||||
This nesting behavior applies to when :meth:`.Session.begin_nested`
|
||||
is used to emit a SAVEPOINT transaction, and is also used to produce
|
||||
a so-called "subtransaction" which allows a block of code to use a
|
||||
begin/rollback/commit sequence regardless of whether or not its enclosing
|
||||
code block has begun a transaction. The :meth:`.flush` method, whether called
|
||||
explicitly or via autoflush, is the primary consumer of the "subtransaction"
|
||||
feature, in that it wishes to guarantee that it works within in a transaction block
|
||||
regardless of whether or not the :class:`.Session` is in transactional mode
|
||||
when the method is called.
|
||||
|
||||
See also:
|
||||
|
||||
@@ -126,6 +165,10 @@ class SessionTransaction(object):
|
||||
|
||||
:meth:`.Session.commit`
|
||||
|
||||
:meth:`.Session.begin`
|
||||
|
||||
:meth:`.Session.begin_nested`
|
||||
|
||||
:attr:`.Session.is_active`
|
||||
|
||||
:meth:`.SessionEvents.after_commit`
|
||||
@@ -134,9 +177,6 @@ class SessionTransaction(object):
|
||||
|
||||
:meth:`.SessionEvents.after_soft_rollback`
|
||||
|
||||
.. index::
|
||||
single: thread safety; SessionTransaction
|
||||
|
||||
"""
|
||||
|
||||
_rollback_exception = None
|
||||
@@ -211,6 +251,7 @@ class SessionTransaction(object):
|
||||
if not self._is_transaction_boundary:
|
||||
self._new = self._parent._new
|
||||
self._deleted = self._parent._deleted
|
||||
self._key_switches = self._parent._key_switches
|
||||
return
|
||||
|
||||
if not self.session._flushing:
|
||||
@@ -218,6 +259,7 @@ class SessionTransaction(object):
|
||||
|
||||
self._new = weakref.WeakKeyDictionary()
|
||||
self._deleted = weakref.WeakKeyDictionary()
|
||||
self._key_switches = weakref.WeakKeyDictionary()
|
||||
|
||||
def _restore_snapshot(self):
|
||||
assert self._is_transaction_boundary
|
||||
@@ -227,11 +269,16 @@ class SessionTransaction(object):
|
||||
if s.key:
|
||||
del s.key
|
||||
|
||||
for s, (oldkey, newkey) in self._key_switches.items():
|
||||
self.session.identity_map.discard(s)
|
||||
s.key = oldkey
|
||||
self.session.identity_map.replace(s)
|
||||
|
||||
for s in set(self._deleted).union(self.session._deleted):
|
||||
if s.deleted:
|
||||
#assert s in self._deleted
|
||||
del s.deleted
|
||||
self.session._update_impl(s)
|
||||
self.session._update_impl(s, discard_existing=True)
|
||||
|
||||
assert not self.session._deleted
|
||||
|
||||
@@ -294,7 +341,15 @@ class SessionTransaction(object):
|
||||
subtransaction.commit()
|
||||
|
||||
if not self.session._flushing:
|
||||
self.session.flush()
|
||||
for _flush_guard in xrange(100):
|
||||
if self.session._is_clean():
|
||||
break
|
||||
self.session.flush()
|
||||
else:
|
||||
raise exc.FlushError(
|
||||
"Over 100 subsequent flushes have occurred within "
|
||||
"session.commit() - is an after_flush() hook "
|
||||
"creating new objects?")
|
||||
|
||||
if self._parent is None and self.session.twophase:
|
||||
try:
|
||||
@@ -735,49 +790,99 @@ class Session(object):
|
||||
return engine.contextual_connect(**kwargs)
|
||||
|
||||
def execute(self, clause, params=None, mapper=None, bind=None, **kw):
|
||||
"""Execute a clause within the current transaction.
|
||||
"""Execute a SQL expression construct or string statement within
|
||||
the current transaction.
|
||||
|
||||
Returns a :class:`.ResultProxy` representing
|
||||
results of the statement execution, in the same manner as that of an
|
||||
:class:`.Engine` or
|
||||
:class:`.Connection`.
|
||||
|
||||
E.g.::
|
||||
|
||||
result = session.execute(
|
||||
user_table.select().where(user_table.c.id == 5)
|
||||
)
|
||||
|
||||
:meth:`~.Session.execute` accepts any executable clause construct, such
|
||||
as :func:`~.sql.expression.select`,
|
||||
:func:`~.sql.expression.insert`,
|
||||
:func:`~.sql.expression.update`,
|
||||
:func:`~.sql.expression.delete`, and
|
||||
:func:`~.sql.expression.text`, and additionally accepts
|
||||
plain strings that represent SQL statements. If a plain string is
|
||||
passed, it is first converted to a
|
||||
:func:`~.sql.expression.text` construct, which here means
|
||||
that bind parameters should be specified using the format ``:param``.
|
||||
If raw DBAPI statement execution is desired, use :meth:`.Session.connection`
|
||||
to acquire a :class:`.Connection`, then call its :meth:`~.Connection.execute`
|
||||
method.
|
||||
:func:`~.sql.expression.text`. Plain SQL strings can be passed
|
||||
as well, which in the case of :meth:`.Session.execute` only
|
||||
will be interpreted the same as if it were passed via a :func:`~.expression.text`
|
||||
construct. That is, the following usage::
|
||||
|
||||
result = session.execute(
|
||||
"SELECT * FROM user WHERE id=:param",
|
||||
{"param":5}
|
||||
)
|
||||
|
||||
is equivalent to::
|
||||
|
||||
from sqlalchemy import text
|
||||
result = session.execute(
|
||||
text("SELECT * FROM user WHERE id=:param"),
|
||||
{"param":5}
|
||||
)
|
||||
|
||||
The second positional argument to :meth:`.Session.execute` is an
|
||||
optional parameter set. Similar to that of :meth:`.Connection.execute`, whether this
|
||||
is passed as a single dictionary, or a list of dictionaries, determines
|
||||
whether the DBAPI cursor's ``execute()`` or ``executemany()`` is used to execute the
|
||||
statement. An INSERT construct may be invoked for a single row::
|
||||
|
||||
result = session.execute(users.insert(), {"id": 7, "name": "somename"})
|
||||
|
||||
or for multiple rows::
|
||||
|
||||
result = session.execute(users.insert(), [
|
||||
{"id": 7, "name": "somename7"},
|
||||
{"id": 8, "name": "somename8"},
|
||||
{"id": 9, "name": "somename9"}
|
||||
])
|
||||
|
||||
The statement is executed within the current transactional context of
|
||||
this :class:`.Session`, using the same behavior as that of
|
||||
the :meth:`.Session.connection` method to determine the active
|
||||
:class:`.Connection`. The ``close_with_result`` flag is
|
||||
set to ``True`` so that an ``autocommit=True`` :class:`.Session`
|
||||
with no active transaction will produce a result that auto-closes
|
||||
the underlying :class:`.Connection`.
|
||||
this :class:`.Session`. The :class:`.Connection` which is used
|
||||
to execute the statement can also be acquired directly by
|
||||
calling the :meth:`.Session.connection` method. Both methods use
|
||||
a rule-based resolution scheme in order to determine the
|
||||
:class:`.Connection`, which in the average case is derived directly
|
||||
from the "bind" of the :class:`.Session` itself, and in other cases
|
||||
can be based on the :func:`.mapper`
|
||||
and :class:`.Table` objects passed to the method; see the documentation
|
||||
for :meth:`.Session.get_bind` for a full description of this scheme.
|
||||
|
||||
The :meth:`.Session.execute` method does *not* invoke autoflush.
|
||||
|
||||
The :class:`.ResultProxy` returned by the :meth:`.Session.execute`
|
||||
method is returned with the "close_with_result" flag set to true;
|
||||
the significance of this flag is that if this :class:`.Session` is
|
||||
autocommitting and does not have a transaction-dedicated :class:`.Connection`
|
||||
available, a temporary :class:`.Connection` is established for the
|
||||
statement execution, which is closed (meaning, returned to the connection
|
||||
pool) when the :class:`.ResultProxy` has consumed all available data.
|
||||
This applies *only* when the :class:`.Session` is configured with
|
||||
autocommit=True and no transaction has been started.
|
||||
|
||||
:param clause:
|
||||
A :class:`.ClauseElement` (i.e. :func:`~.sql.expression.select`,
|
||||
:func:`~.sql.expression.text`, etc.) or string SQL statement to be executed. The clause
|
||||
will also be used to locate a bind, if this :class:`.Session`
|
||||
is not bound to a single engine already, and the ``mapper``
|
||||
and ``bind`` arguments are not passed.
|
||||
An executable statement (i.e. an :class:`.Executable` expression
|
||||
such as :func:`.expression.select`) or string SQL statement
|
||||
to be executed.
|
||||
|
||||
:param params:
|
||||
Optional dictionary of bind names mapped to values.
|
||||
Optional dictionary, or list of dictionaries, containing
|
||||
bound parameter values. If a single dictionary, single-row
|
||||
execution occurs; if a list of dictionaries, an
|
||||
"executemany" will be invoked. The keys in each dictionary
|
||||
must correspond to parameter names present in the statement.
|
||||
|
||||
:param mapper:
|
||||
Optional :func:`.mapper` or mapped class, used to identify
|
||||
the appropriate bind. This argument takes precedence over
|
||||
``clause`` when locating a bind.
|
||||
``clause`` when locating a bind. See :meth:`.Session.get_bind`
|
||||
for more details.
|
||||
|
||||
:param bind:
|
||||
Optional :class:`.Engine` to be used as the bind. If
|
||||
@@ -787,9 +892,20 @@ class Session(object):
|
||||
a bind.
|
||||
|
||||
:param \**kw:
|
||||
Additional keyword arguments are sent to :meth:`get_bind()`,
|
||||
allowing additional arguments to be passed to custom
|
||||
implementations of :meth:`get_bind`.
|
||||
Additional keyword arguments are sent to :meth:`.Session.get_bind()`
|
||||
to allow extensibility of "bind" schemes.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`sqlexpression_toplevel` - Tutorial on using Core SQL
|
||||
constructs.
|
||||
|
||||
:ref:`connections_toplevel` - Further information on direct
|
||||
statement execution.
|
||||
|
||||
:meth:`.Connection.execute` - core level statement execution
|
||||
method, which is :meth:`.Session.execute` ultimately uses
|
||||
in order to execute the statement.
|
||||
|
||||
"""
|
||||
clause = expression._literal_as_text(clause)
|
||||
@@ -998,7 +1114,7 @@ class Session(object):
|
||||
of objects which involve existing database queries,
|
||||
where the uncompleted object should not yet be flushed.
|
||||
|
||||
New in 0.7.6.
|
||||
.. versionadded:: 0.7.6
|
||||
|
||||
"""
|
||||
autoflush = self.autoflush
|
||||
@@ -1214,6 +1330,11 @@ class Session(object):
|
||||
# state has already replaced this one in the identity
|
||||
# map (see test/orm/test_naturalpks.py ReversePKsTest)
|
||||
self.identity_map.discard(state)
|
||||
if state in self.transaction._key_switches:
|
||||
orig_key = self.transaction._key_switches[state][0]
|
||||
else:
|
||||
orig_key = state.key
|
||||
self.transaction._key_switches[state] = (orig_key, instance_key)
|
||||
state.key = instance_key
|
||||
|
||||
self.identity_map.replace(state)
|
||||
@@ -1303,20 +1424,47 @@ class Session(object):
|
||||
self._delete_impl(st_)
|
||||
|
||||
def merge(self, instance, load=True, **kw):
|
||||
"""Copy the state an instance onto the persistent instance with the
|
||||
same identifier.
|
||||
"""Copy the state of a given instance into a corresponding instance
|
||||
within this :class:`.Session`.
|
||||
|
||||
If there is no persistent instance currently associated with the
|
||||
session, it will be loaded. Return the persistent instance. If the
|
||||
given instance is unsaved, save a copy of and return it as a newly
|
||||
persistent instance. The given instance does not become associated
|
||||
with the session.
|
||||
:meth:`.Session.merge` examines the primary key attributes of the
|
||||
source instance, and attempts to reconcile it with an instance of the
|
||||
same primary key in the session. If not found locally, it attempts
|
||||
to load the object from the database based on primary key, and if
|
||||
none can be located, creates a new instance. The state of each attribute
|
||||
on the source instance is then copied to the target instance.
|
||||
The resulting target instance is then returned by the method; the
|
||||
original source instance is left unmodified, and un-associated with the
|
||||
:class:`.Session` if not already.
|
||||
|
||||
This operation cascades to associated instances if the association is
|
||||
mapped with ``cascade="merge"``.
|
||||
|
||||
See :ref:`unitofwork_merging` for a detailed discussion of merging.
|
||||
|
||||
:param instance: Instance to be merged.
|
||||
:param load: Boolean, when False, :meth:`.merge` switches into
|
||||
a "high performance" mode which causes it to forego emitting history
|
||||
events as well as all database access. This flag is used for
|
||||
cases such as transferring graphs of objects into a :class:`.Session`
|
||||
from a second level cache, or to transfer just-loaded objects
|
||||
into the :class:`.Session` owned by a worker thread or process
|
||||
without re-querying the database.
|
||||
|
||||
The ``load=False`` use case adds the caveat that the given
|
||||
object has to be in a "clean" state, that is, has no pending changes
|
||||
to be flushed - even if the incoming object is detached from any
|
||||
:class:`.Session`. This is so that when
|
||||
the merge operation populates local attributes and
|
||||
cascades to related objects and
|
||||
collections, the values can be "stamped" onto the
|
||||
target object as is, without generating any history or attribute
|
||||
events, and without the need to reconcile the incoming data with
|
||||
any existing related objects or collections that might not
|
||||
be loaded. The resulting objects from ``load=False`` are always
|
||||
produced as "clean", so it is only appropriate that the given objects
|
||||
should be "clean" as well, else this suggests a mis-use of the method.
|
||||
|
||||
"""
|
||||
if 'dont_load' in kw:
|
||||
load = not kw['dont_load']
|
||||
@@ -1465,7 +1613,7 @@ class Session(object):
|
||||
self._new[state] = state.obj()
|
||||
state.insert_order = len(self._new)
|
||||
|
||||
def _update_impl(self, state):
|
||||
def _update_impl(self, state, discard_existing=False):
|
||||
if (self.identity_map.contains_state(state) and
|
||||
state not in self._deleted):
|
||||
return
|
||||
@@ -1481,6 +1629,10 @@ class Session(object):
|
||||
"function to send this object back to the transient state." %
|
||||
mapperutil.state_str(state)
|
||||
)
|
||||
if discard_existing:
|
||||
existing = self.identity_map.get(state.key)
|
||||
if existing is not None:
|
||||
self.identity_map.discard(attributes.instance_state(existing))
|
||||
self._attach(state)
|
||||
self._deleted.pop(state, None)
|
||||
self.identity_map.add(state)
|
||||
@@ -1563,20 +1715,15 @@ class Session(object):
|
||||
will create a transaction on the fly that surrounds the entire set of
|
||||
operations int the flush.
|
||||
|
||||
objects
|
||||
Optional; a list or tuple collection. Restricts the flush operation
|
||||
to only these objects, rather than all pending changes.
|
||||
Deprecated - this flag prevents the session from properly maintaining
|
||||
accounting among inter-object relations and can cause invalid results.
|
||||
:param objects: Optional; restricts the flush operation to operate
|
||||
only on elements that are in the given collection.
|
||||
|
||||
This feature is for an extremely narrow set of use cases where
|
||||
particular objects may need to be operated upon before the
|
||||
full flush() occurs. It is not intended for general use.
|
||||
|
||||
"""
|
||||
|
||||
if objects:
|
||||
util.warn_deprecated(
|
||||
"The 'objects' argument to session.flush() is deprecated; "
|
||||
"Please do not add objects to the session which should not "
|
||||
"yet be persisted.")
|
||||
|
||||
if self._flushing:
|
||||
raise sa_exc.InvalidRequestError("Session is already flushing")
|
||||
|
||||
@@ -1695,17 +1842,16 @@ class Session(object):
|
||||
|
||||
return session.is_modified(someobject, passive=True)
|
||||
|
||||
.. note::
|
||||
|
||||
In SQLAlchemy 0.7 and earlier, the ``passive``
|
||||
flag should **always** be explicitly set to ``True``.
|
||||
The current default value of :data:`.attributes.PASSIVE_OFF`
|
||||
for this flag is incorrect, in that it loads unloaded
|
||||
collections and attributes which by definition
|
||||
have no modified state, and furthermore trips off
|
||||
autoflush which then causes all subsequent, possibly
|
||||
modified attributes to lose their modified state.
|
||||
The default value of the flag will be changed in 0.8.
|
||||
.. versionchanged:: 0.8
|
||||
In SQLAlchemy 0.7 and earlier, the ``passive``
|
||||
flag should **always** be explicitly set to ``True``.
|
||||
The current default value of :data:`.attributes.PASSIVE_OFF`
|
||||
for this flag is incorrect, in that it loads unloaded
|
||||
collections and attributes which by definition
|
||||
have no modified state, and furthermore trips off
|
||||
autoflush which then causes all subsequent, possibly
|
||||
modified attributes to lose their modified state.
|
||||
The default value of the flag will be changed in 0.8.
|
||||
|
||||
A few caveats to this method apply:
|
||||
|
||||
@@ -1750,10 +1896,11 @@ class Session(object):
|
||||
is a bug, as unloaded attributes by definition have
|
||||
no changes, and the load operation also triggers an
|
||||
autoflush which then cancels out subsequent changes.
|
||||
This flag should **always be set to
|
||||
True**. In 0.8 the flag will be deprecated and the default
|
||||
set to ``True``.
|
||||
This flag should **always be set to True**.
|
||||
|
||||
.. versionchanged:: 0.8
|
||||
The flag will be deprecated and the default
|
||||
set to ``True``.
|
||||
|
||||
"""
|
||||
try:
|
||||
@@ -1784,20 +1931,58 @@ class Session(object):
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
"""True if this :class:`.Session` has an active transaction.
|
||||
"""True if this :class:`.Session` is in "transaction mode" and
|
||||
is not in "partial rollback" state.
|
||||
|
||||
This indicates if the :class:`.Session` is capable of emitting
|
||||
SQL, as from the :meth:`.Session.execute`, :meth:`.Session.query`,
|
||||
or :meth:`.Session.flush` methods. If False, it indicates
|
||||
that the innermost transaction has been rolled back, but enclosing
|
||||
:class:`.SessionTransaction` objects remain in the transactional
|
||||
stack, which also must be rolled back.
|
||||
The :class:`.Session` in its default mode of ``autocommit=False``
|
||||
is essentially always in "transaction mode", in that a
|
||||
:class:`.SessionTransaction` is associated with it as soon as
|
||||
it is instantiated. This :class:`.SessionTransaction` is immediately
|
||||
replaced with a new one as soon as it is ended, due to a rollback,
|
||||
commit, or close operation.
|
||||
|
||||
This flag is generally only useful with a :class:`.Session`
|
||||
configured in its default mode of ``autocommit=False``.
|
||||
"Transaction mode" does *not* indicate whether
|
||||
or not actual database connection resources are in use; the
|
||||
:class:`.SessionTransaction` object coordinates among zero or more
|
||||
actual database transactions, and starts out with none, accumulating
|
||||
individual DBAPI connections as different data sources are used
|
||||
within its scope. The best way to track when a particular
|
||||
:class:`.Session` has actually begun to use DBAPI resources is to
|
||||
implement a listener using the :meth:`.SessionEvents.after_begin`
|
||||
method, which will deliver both the :class:`.Session` as well as the
|
||||
target :class:`.Connection` to a user-defined event listener.
|
||||
|
||||
The "partial rollback" state refers to when an "inner" transaction,
|
||||
typically used during a flush, encounters an error and emits
|
||||
a rollback of the DBAPI connection. At this point, the :class:`.Session`
|
||||
is in "partial rollback" and awaits for the user to call :meth:`.rollback`,
|
||||
in order to close out the transaction stack. It is in this "partial
|
||||
rollback" period that the :attr:`.is_active` flag returns False. After
|
||||
the call to :meth:`.rollback`, the :class:`.SessionTransaction` is replaced
|
||||
with a new one and :attr:`.is_active` returns ``True`` again.
|
||||
|
||||
When a :class:`.Session` is used in ``autocommit=True`` mode, the
|
||||
:class:`.SessionTransaction` is only instantiated within the scope
|
||||
of a flush call, or when :meth:`.Session.begin` is called. So
|
||||
:attr:`.is_active` will always be ``False`` outside of a flush or
|
||||
:meth:`.begin` block in this mode, and will be ``True`` within the
|
||||
:meth:`.begin` block as long as it doesn't enter "partial rollback"
|
||||
state.
|
||||
|
||||
From all the above, it follows that the only purpose to this flag is
|
||||
for application frameworks that wish to detect is a "rollback" is
|
||||
necessary within a generic error handling routine, for :class:`.Session`
|
||||
objects that would otherwise be in "partial rollback" mode. In
|
||||
a typical integration case, this is also not necessary as it is standard
|
||||
practice to emit :meth:`.Session.rollback` unconditionally within the
|
||||
outermost exception catch.
|
||||
|
||||
To track the transactional state of a :class:`.Session` fully,
|
||||
use event listeners, primarily the :meth:`.SessionEvents.after_begin`,
|
||||
:meth:`.SessionEvents.after_commit`, :meth:`.SessionEvents.after_rollback`
|
||||
and related events.
|
||||
|
||||
"""
|
||||
|
||||
return self.transaction and self.transaction.is_active
|
||||
|
||||
identity_map = None
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/shard.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# orm/state.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user