]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Made sqlalchemy.sql.expressions.Executable part of public
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 12 Feb 2010 19:54:49 +0000 (19:54 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 12 Feb 2010 19:54:49 +0000 (19:54 +0000)
    API, used for any expression construct that can be sent to
    execute().  FunctionElement now inherits Executable so that
    it gains execution_options(), which are also propagated
    to the select() that's generated within execute().
    Executable in turn subclasses _Generative which marks
    any ClauseElement that supports the @_generative
    decorator - these may also become "public" for the benefit
    of the compiler extension at some point.

CHANGES
doc/build/reference/sqlalchemy/expressions.rst
lib/sqlalchemy/ext/compiler.py
lib/sqlalchemy/schema.py
lib/sqlalchemy/sql/expression.py
test/sql/test_functions.py

diff --git a/CHANGES b/CHANGES
index ebbd60b38f68efb0f57aa467688a84090faf4e09..7857c51363d8b81a5c07d4f9be9280e7784e5909 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -71,6 +71,16 @@ CHANGES
     types, before falling back to that of the known type
     on the other side of the expression.  Also part
     of [ticket:1683].
+
+  - Made sqlalchemy.sql.expressions.Executable part of public 
+    API, used for any expression construct that can be sent to
+    execute().  FunctionElement now inherits Executable so that
+    it gains execution_options(), which are also propagated
+    to the select() that's generated within execute().
+    Executable in turn subclasses _Generative which marks
+    any ClauseElement that supports the @_generative
+    decorator - these may also become "public" for the benefit
+    of the compiler extension at some point.
     
 - mysql
   - Fixed reflection bug whereby when COLLATE was present, 
index 1c3c208b46d2512aad5aa5cbe247ed7021ebd8f2..84210f04af857b0bafeb34d0c282c3c341c81c27 100644 (file)
@@ -152,6 +152,10 @@ Classes
    :members: where
    :show-inheritance:
 
+.. autoclass:: Executable
+   :members:
+   :show-inheritance:
+
 .. autoclass:: FunctionElement
    :members:
    :show-inheritance:
index 7861096f9ee3d7dc127c4a193d7bf1de9e04e5d4..3226b0efd8ae803b009d4b534be54c6a2720e4c8 100644 (file)
@@ -64,7 +64,9 @@ in-progress compilation, including ``compiler.dialect``,
 and :class:`~sqlalchemy.sql.compiler.DDLCompiler` both include a ``process()``
 method which can be used for compilation of embedded attributes::
 
-    class InsertFromSelect(ClauseElement):
+    from sqlalchemy.sql.expression import Executable, ClauseElement
+    
+    class InsertFromSelect(Executable, ClauseElement):
         def __init__(self, table, select):
             self.table = table
             self.select = select
@@ -154,6 +156,11 @@ A big part of using the compiler extension is subclassing SQLAlchemy expression
   ``execute_at()`` method, allowing the construct to be invoked during CREATE
   TABLE and DROP TABLE sequences.
 
+* :class:`~sqlalchemy.sql.expression.Executable` - This is a mixin which should be
+  used with any expression class that represents a "standalone" SQL statement that
+  can be passed directly to an ``execute()`` method.  It is already implicit 
+  within ``DDLElement`` and ``FunctionElement``.
+  
 """
 
 def compiles(class_, *specs):
index c345b0b21ede5e6116c1fe0172682f180a1f8bb3..d51ae3a458a7f5caab58188db50f05613551d8b9 100644 (file)
@@ -2030,10 +2030,10 @@ class SchemaVisitor(visitors.ClauseVisitor):
     __traverse_options__ = {'schema_visitor':True}
 
 
-class DDLElement(expression._Executable, expression.ClauseElement):
+class DDLElement(expression.Executable, expression.ClauseElement):
     """Base class for DDL expression constructs."""
     
-    _execution_options = expression._Executable.\
+    _execution_options = expression.Executable.\
                             _execution_options.union({'autocommit':True})
 
     target = None
index 1ae706999ec6479a259143452fc3ea78889f1873..0f55ac097d925aa6928a0d96b79f351f43ec1eb7 100644 (file)
@@ -2208,7 +2208,20 @@ class _TypeClause(ClauseElement):
     def __init__(self, type):
         self.type = type
 
-class _Executable(object):
+
+class _Generative(object):
+    """Allow a ClauseElement to generate itself via the
+    @_generative decorator.
+    
+    """
+    
+    def _generate(self):
+        s = self.__class__.__new__(self.__class__)
+        s.__dict__ = self.__dict__.copy()
+        return s
+
+
+class Executable(_Generative):
     """Mark a ClauseElement as supporting execution."""
 
     supports_execution = True
@@ -2240,8 +2253,10 @@ class _Executable(object):
         """
         self._execution_options = self._execution_options.union(kw)
 
+# legacy, some outside users may be calling this
+_Executable = Executable
     
-class _TextClause(_Executable, ClauseElement):
+class _TextClause(Executable, ClauseElement):
     """Represent a literal SQL text fragment.
 
     Public constructor is the :func:`text()` function.
@@ -2251,7 +2266,7 @@ class _TextClause(_Executable, ClauseElement):
     __visit_name__ = 'textclause'
 
     _bind_params_regex = re.compile(r'(?<![:\w\x5c]):(\w+)(?!:)', re.UNICODE)
-    _execution_options = _Executable._execution_options.union({'autocommit':PARSE_AUTOCOMMIT})
+    _execution_options = Executable._execution_options.union({'autocommit':PARSE_AUTOCOMMIT})
     
     @property
     def _select_iterable(self):
@@ -2293,11 +2308,6 @@ class _TextClause(_Executable, ClauseElement):
         else:
             return None
 
-    def _generate(self):
-        s = self.__class__.__new__(self.__class__)
-        s.__dict__ = self.__dict__.copy()
-        return s
-
     def _copy_internals(self, clone=_clone):
         self.bindparams = dict((b.key, clone(b))
                                for b in self.bindparams.values())
@@ -2483,7 +2493,7 @@ class _Case(ColumnElement):
     def _from_objects(self):
         return list(itertools.chain(*[x._from_objects for x in self.get_children()]))
 
-class FunctionElement(ColumnElement, FromClause):
+class FunctionElement(Executable, ColumnElement, FromClause):
     """Base for SQL function-oriented constructs."""
     
     def __init__(self, *clauses, **kwargs):
@@ -2514,13 +2524,16 @@ class FunctionElement(ColumnElement, FromClause):
         util.reset_memoized(self, 'clauses')
 
     def select(self):
-        return select([self])
+        s = select([self])
+        if self._execution_options:
+            s = s.execution_options(**self._execution_options)
+        return s
 
     def scalar(self):
-        return select([self]).execute().scalar()
+        return self.select().execute().scalar()
 
     def execute(self):
-        return select([self]).execute()
+        return self.select().execute()
 
     def _bind_param(self, obj):
         return _BindParamClause(None, obj, _fallback_type=self.type, unique=True)
@@ -3243,7 +3256,7 @@ class TableClause(_Immutable, FromClause):
     def _from_objects(self):
         return [self]
 
-class _SelectBaseMixin(_Executable):
+class _SelectBaseMixin(Executable):
     """Base class for :class:`Select` and ``CompoundSelects``."""
 
     def __init__(self,
@@ -3312,6 +3325,8 @@ class _SelectBaseMixin(_Executable):
         self._execution_options = self._execution_options.union({'autocommit':True})
 
     def _generate(self):
+        """Override the default _generate() method to also clear out exported collections."""
+        
         s = self.__class__.__new__(self.__class__)
         s.__dict__ = self.__dict__.copy()
         s._reset_exported()
@@ -3889,19 +3904,14 @@ class Select(_SelectBaseMixin, FromClause):
         self._bind = bind
     bind = property(bind, _set_bind)
 
-class _UpdateBase(_Executable, ClauseElement):
+class _UpdateBase(Executable, ClauseElement):
     """Form the base for ``INSERT``, ``UPDATE``, and ``DELETE`` statements."""
 
     __visit_name__ = 'update_base'
 
-    _execution_options = _Executable._execution_options.union({'autocommit':True})
+    _execution_options = Executable._execution_options.union({'autocommit':True})
     kwargs = util.frozendict()
     
-    def _generate(self):
-        s = self.__class__.__new__(self.__class__)
-        s.__dict__ = self.__dict__.copy()
-        return s
-
     def _process_colparams(self, parameters):
         if isinstance(parameters, (list, tuple)):
             pp = {}
@@ -4151,9 +4161,9 @@ class Delete(_UpdateBase):
         # TODO: coverage
         self._whereclause = clone(self._whereclause)
 
-class _IdentifiedClause(_Executable, ClauseElement):
+class _IdentifiedClause(Executable, ClauseElement):
     __visit_name__ = 'identified'
-    _execution_options = _Executable._execution_options.union({'autocommit':False})
+    _execution_options = Executable._execution_options.union({'autocommit':False})
     quote = None
 
     def __init__(self, ident):
index e6bcd2680dcb85a437cfbcb93f337838039d3729..960bb66526396361ff8022cbae67d7451414a877 100644 (file)
@@ -215,7 +215,21 @@ class ExecuteTest(TestBase):
         finally:
             conn.close()
         assert (x == y == z == q) is True
-    
+
+    def test_exec_options(self):
+        f = func.foo()
+        eq_(f._execution_options, {})
+        
+        f = f.execution_options(foo='bar')
+        eq_(f._execution_options, {'foo':'bar'})
+        s = f.select()
+        eq_(s._execution_options, {'foo':'bar'})
+        
+        ret = testing.db.execute(func.now().execution_options(foo='bar'))
+        eq_(ret.context.execution_options, {'foo':'bar'})
+        ret.close()
+        
+        
     @engines.close_first
     def test_update(self):
         """