]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- subclassed Function off of new FunctionElement generic base
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 10 Nov 2009 00:43:53 +0000 (00:43 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 10 Nov 2009 00:43:53 +0000 (00:43 +0000)
- removed "key" accessor of Function, Grouping - this doesn't seem to be used for anything
- various formatting
- documented the four "Element" classes in the compiler extension as per [ticket:1590]

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

index acafa87478d30bdb7c0c8d41809f3f5bcf385514..018616bca88c51247d1d5ec20c8fef3d0b03235e 100644 (file)
@@ -146,6 +146,14 @@ Classes
    :members: where
    :show-inheritance:
 
+.. autoclass:: FunctionElement
+   :members:
+   :show-inheritance:
+
+.. autoclass:: Function
+   :members:
+   :show-inheritance:
+   
 .. autoclass:: FromClause
    :members:
    :show-inheritance:
index 05df8d2be6b7f7fa7c5149e54af12aeb3e458722..76896a3525afe648f0665c5539d9f4b59928c5a6 100644 (file)
@@ -16,10 +16,10 @@ subclasses and one or more callables defining its compilation::
     def compile_mycolumn(element, compiler, **kw):
         return "[%s]" % element.name
         
-Above, ``MyColumn`` extends :class:`~sqlalchemy.sql.expression.ColumnClause`, the
-base expression element for column objects.  The ``compiles`` decorator registers
-itself with the ``MyColumn`` class so that it is invoked when the object 
-is compiled to a string::
+Above, ``MyColumn`` extends :class:`~sqlalchemy.sql.expression.ColumnClause`,
+the base expression element for named column objects. The ``compiles``
+decorator registers itself with the ``MyColumn`` class so that it is invoked
+when the object is compiled to a string::
 
     from sqlalchemy import select
     
@@ -30,8 +30,8 @@ Produces::
 
     SELECT [x], [y]
 
-Compilers can also be made dialect-specific.  The appropriate compiler will be invoked
-for the dialect in use::
+Compilers can also be made dialect-specific. The appropriate compiler will be
+invoked for the dialect in use::
 
     from sqlalchemy.schema import DDLElement
 
@@ -51,11 +51,12 @@ for the dialect in use::
 
 The second ``visit_alter_table`` will be invoked when any ``postgresql`` dialect is used.
 
-The ``compiler`` argument is the :class:`~sqlalchemy.engine.base.Compiled` object
-in use.  This object can be inspected for any information about the in-progress 
-compilation, including ``compiler.dialect``, ``compiler.statement`` etc.
-The :class:`~sqlalchemy.sql.compiler.SQLCompiler` and :class:`~sqlalchemy.sql.compiler.DDLCompiler`
-both include a ``process()`` method which can be used for compilation of embedded attributes::
+The ``compiler`` argument is the :class:`~sqlalchemy.engine.base.Compiled`
+object in use. This object can be inspected for any information about the
+in-progress compilation, including ``compiler.dialect``,
+``compiler.statement`` etc. The :class:`~sqlalchemy.sql.compiler.SQLCompiler`
+and :class:`~sqlalchemy.sql.compiler.DDLCompiler` both include a ``process()``
+method which can be used for compilation of embedded attributes::
 
     class InsertFromSelect(ClauseElement):
         def __init__(self, table, select):
@@ -76,6 +77,36 @@ Produces::
 
     "INSERT INTO mytable (SELECT mytable.x, mytable.y, mytable.z FROM mytable WHERE mytable.x > :x_1)"
 
+Subclassing Guidelines
+======================
+
+A big part of using the compiler extension is subclassing SQLAlchemy expression constructs.  To make this easier, the expression and schema packages feature a set of "bases" intended for common tasks.  A synopsis is as follows:
+
+* :class:`~sqlalchemy.sql.expression.ClauseElement` - This is the root
+  expression class. Any SQL expression can be derived from this base, and is
+  probably the best choice for longer constructs such as specialized INSERT
+  statements.
+* :class:`~sqlalchemy.sql.expression.ColumnElement` - The root of all
+  "column-like" elements. Anything that you'd place in the "columns" clause of
+  a SELECT statement (as well as order by and group by) can derive from this -
+  the object will automatically have Python "comparison" behavior.
+* :class:`~sqlalchemy.sql.expression.FunctionElement` - This is a hybrid of a
+  ``ColumnElement`` and a "from clause" like object, and represents a SQL
+  function or stored procedure type of call. Since most databases support
+  statements along the line of "SELECT FROM <some function>"
+  ``FunctionElement`` adds in the ability to be used in the FROM clause of a
+  ``select()`` construct.
+* :class:`~sqlalchemy.schema.DDLElement` - The root of all DDL expressions,
+  like CREATE TABLE, ALTER TABLE, etc. Compilation of ``DDLElement``
+  subclasses is issued by a ``DDLCompiler`` instead of a ``SQLCompiler``.
+  ``DDLElement`` also features ``Table`` and ``MetaData`` event hooks via the
+  ``execute_at()`` method, allowing the construct to be invoked during CREATE
+  TABLE and DROP TABLE sequences.
+
 
 """
 
index 3dee2a0ba08d01ea96c7d9f1fe4dc11431854cab..2e5a1a6371e94fa34d24108ab401eb796bde7b25 100644 (file)
@@ -834,7 +834,8 @@ class Column(SchemaItem, expression.ColumnClause):
 class ForeignKey(SchemaItem):
     """Defines a column-level FOREIGN KEY constraint between two columns.
 
-    ``ForeignKey`` is specified as an argument to a :class:`Column` object, e.g.::
+    ``ForeignKey`` is specified as an argument to a :class:`Column` object,
+    e.g.::
     
         t = Table("remote_table", metadata, 
             Column("remote_id", ForeignKey("main_table.id"))
@@ -862,37 +863,37 @@ class ForeignKey(SchemaItem):
         :class:`Table` object's collection of constraints.
 
         :param column: A single target column for the key relationship. A
-        :class:`Column` object or a column name as a string:
-        ``tablename.columnkey`` or ``schema.tablename.columnkey``.
-        ``columnkey`` is the ``key`` which has been assigned to the column
-        (defaults to the column name itself), unless ``link_to_name`` is
-        ``True`` in which case the rendered name of the column is used.
+            :class:`Column` object or a column name as a string:
+            ``tablename.columnkey`` or ``schema.tablename.columnkey``.
+            ``columnkey`` is the ``key`` which has been assigned to the column
+            (defaults to the column name itself), unless ``link_to_name`` is
+            ``True`` in which case the rendered name of the column is used.
 
         :param name: Optional string. An in-database name for the key if
-        `constraint` is not provided.
+            `constraint` is not provided.
 
         :param onupdate: Optional string. If set, emit ON UPDATE <value> when
-        issuing DDL for this constraint. Typical values include CASCADE,
-        DELETE and RESTRICT.
+            issuing DDL for this constraint. Typical values include CASCADE,
+            DELETE and RESTRICT.
 
         :param ondelete: Optional string. If set, emit ON DELETE <value> when
-        issuing DDL for this constraint. Typical values include CASCADE,
-        DELETE and RESTRICT.
+            issuing DDL for this constraint. Typical values include CASCADE,
+            DELETE and RESTRICT.
 
         :param deferrable: Optional bool. If set, emit DEFERRABLE or NOT
-        DEFERRABLE when issuing DDL for this constraint.
+            DEFERRABLE when issuing DDL for this constraint.
 
         :param initially: Optional string. If set, emit INITIALLY <value> when
-        issuing DDL for this constraint.
+            issuing DDL for this constraint.
         
         :param link_to_name: if True, the string name given in ``column`` is
-        the rendered name of the referenced column, not its locally assigned
-        ``key``.
+            the rendered name of the referenced column, not its locally
+            assigned ``key``.
           
         :param use_alter: passed to the underlying
-        :class:`ForeignKeyConstraint` to indicate the constraint should be
-        generated/dropped externally from the CREATE TABLE/ DROP TABLE
-        statement. See that classes' constructor for details.
+            :class:`ForeignKeyConstraint` to indicate the constraint should be
+            generated/dropped externally from the CREATE TABLE/ DROP TABLE
+            statement. See that classes' constructor for details.
         
         """
 
@@ -1420,42 +1421,42 @@ class ForeignKeyConstraint(Constraint):
         """Construct a composite-capable FOREIGN KEY.
 
         :param columns: A sequence of local column names. The named columns
-        must be defined and present in the parent Table. The names should
-        match the ``key`` given to each column (defaults to the name) unless
-        ``link_to_name`` is True.
+          must be defined and present in the parent Table. The names should
+          match the ``key`` given to each column (defaults to the name) unless
+          ``link_to_name`` is True.
 
         :param refcolumns: A sequence of foreign column names or Column
-        objects. The columns must all be located within the same Table.
+          objects. The columns must all be located within the same Table.
 
         :param name: Optional, the in-database name of the key.
 
         :param onupdate: Optional string. If set, emit ON UPDATE <value> when
-        issuing DDL for this constraint. Typical values include CASCADE,
-        DELETE and RESTRICT.
+          issuing DDL for this constraint. Typical values include CASCADE,
+          DELETE and RESTRICT.
 
         :param ondelete: Optional string. If set, emit ON DELETE <value> when
-        issuing DDL for this constraint. Typical values include CASCADE,
-        DELETE and RESTRICT.
+          issuing DDL for this constraint. Typical values include CASCADE,
+          DELETE and RESTRICT.
 
         :param deferrable: Optional bool. If set, emit DEFERRABLE or NOT
-        DEFERRABLE when issuing DDL for this constraint.
+          DEFERRABLE when issuing DDL for this constraint.
 
         :param initially: Optional string. If set, emit INITIALLY <value> when
-        issuing DDL for this constraint.
+          issuing DDL for this constraint.
 
         :param link_to_name: if True, the string name given in ``column`` is
-        the rendered name of the referenced column, not its locally assigned
-        ``key``.
+          the rendered name of the referenced column, not its locally assigned
+          ``key``.
 
         :param use_alter: If True, do not emit the DDL for this constraint as
-        part of the CREATE TABLE definition. Instead, generate it via an ALTER
-        TABLE statement issued after the full collection of tables have been
-        created, and drop it via an ALTER TABLE statement before the full
-        collection of tables are dropped. This is shorthand for the usage of
-        :class:`AddConstraint` and :class:`DropConstraint` applied as
-        "after-create" and "before-drop" events on the MetaData object. This
-        is normally used to generate/drop constraints on objects that are
-        mutually dependent on each other.
+          part of the CREATE TABLE definition. Instead, generate it via an
+          ALTER TABLE statement issued after the full collection of tables
+          have been created, and drop it via an ALTER TABLE statement before
+          the full collection of tables are dropped. This is shorthand for the
+          usage of :class:`AddConstraint` and :class:`DropConstraint` applied
+          as "after-create" and "before-drop" events on the MetaData object.
+          This is normally used to generate/drop constraints on objects that
+          are mutually dependent on each other.
           
         """
         super(ForeignKeyConstraint, self).__init__(name, deferrable, initially)
index 4b49dc0bca429e4f03ad99a68c7352c6e05bf300..5e4978bfb05342fbe2d34f612a5cbd501fcfa634 100644 (file)
@@ -647,29 +647,25 @@ def alias(selectable, alias=None):
 def literal(value, type_=None):
     """Return a literal clause, bound to a bind parameter.
 
-    Literal clauses are created automatically when non-
-    ``ClauseElement`` objects (such as strings, ints, dates, etc.) are
-    used in a comparison operation with a
-    :class:`~sqlalchemy.sql.expression._CompareMixin` subclass, such as a ``Column``
-    object.  Use this function to force the generation of a literal
-    clause, which will be created as a
+    Literal clauses are created automatically when non- ``ClauseElement``
+    objects (such as strings, ints, dates, etc.) are used in a comparison
+    operation with a :class:`~sqlalchemy.sql.expression._CompareMixin`
+    subclass, such as a ``Column`` object. Use this function to force the
+    generation of a literal clause, which will be created as a
     :class:`~sqlalchemy.sql.expression._BindParamClause` with a bound value.
 
-    value
-      the value to be bound.  Can be any Python object supported by
-      the underlying DB-API, or is translatable via the given type
-      argument.
+    :param value: the value to be bound. Can be any Python object supported by
+        the underlying DB-API, or is translatable via the given type argument.
 
-    type\_
-      an optional :class:`~sqlalchemy.types.TypeEngine` which will provide
-      bind-parameter translation for this literal.
+    :param type\_: an optional :class:`~sqlalchemy.types.TypeEngine` which
+        will provide bind-parameter translation for this literal.
 
     """
     return _BindParamClause(None, value, type_=type_, unique=True)
 
 def label(name, obj):
-    """Return a :class:`~sqlalchemy.sql.expression._Label` object for the given
-    :class:`~sqlalchemy.sql.expression.ColumnElement`.
+    """Return a :class:`~sqlalchemy.sql.expression._Label` object for the
+    given :class:`~sqlalchemy.sql.expression.ColumnElement`.
 
     A label changes the name of an element in the columns clause of a
     ``SELECT`` statement, typically via the ``AS`` SQL keyword.
@@ -692,8 +688,8 @@ def column(text, type_=None):
 
     The object returned is an instance of
     :class:`~sqlalchemy.sql.expression.ColumnClause`, which represents the
-    "syntactical" portion of the schema-level :class:`~sqlalchemy.schema.Column`
-    object.
+    "syntactical" portion of the schema-level
+    :class:`~sqlalchemy.schema.Column` object.
 
     text
       the name of the column.  Quoting rules will be applied to the
@@ -767,8 +763,8 @@ def bindparam(key, value=None, type_=None, unique=False, required=False):
         return _BindParamClause(key, value, type_=type_, unique=unique, required=required)
 
 def outparam(key, type_=None):
-    """Create an 'OUT' parameter for usage in functions (stored procedures), for
-    databases which support them.
+    """Create an 'OUT' parameter for usage in functions (stored procedures),
+    for databases which support them.
 
     The ``outparam`` can be used like a regular function parameter.
     The "output" value will be available from the
@@ -2031,8 +2027,12 @@ class FromClause(Selectable):
         return self._foreign_keys
 
     columns = property(attrgetter('_columns'), doc=_columns.__doc__)
-    primary_key = property(attrgetter('_primary_key'), doc=_primary_key.__doc__)
-    foreign_keys = property(attrgetter('_foreign_keys'), doc=_foreign_keys.__doc__)
+    primary_key = property(
+                    attrgetter('_primary_key'), 
+                    doc=_primary_key.__doc__)
+    foreign_keys = property(
+                    attrgetter('_foreign_keys'), 
+                    doc=_foreign_keys.__doc__)
 
     # synonyms for 'columns'
     c = _select_iterable = property(attrgetter('columns'), doc=_columns.__doc__)
@@ -2378,23 +2378,18 @@ class _Case(ColumnElement):
     def _from_objects(self):
         return list(itertools.chain(*[x._from_objects for x in self.get_children()]))
 
-class Function(ColumnElement, FromClause):
-    """Describe a SQL function."""
-
-    __visit_name__ = 'function'
+class FunctionElement(ColumnElement, FromClause):
+    """Base for SQL function-oriented constructs."""
 
-    def __init__(self, name, *clauses, **kwargs):
-        self.packagenames = kwargs.get('packagenames', None) or []
-        self.name = name
+    def __init__(self, *clauses, **kwargs):
         self._bind = kwargs.get('bind', None)
         args = [_literal_as_binds(c, self.name) for c in clauses]
-        self.clause_expr = ClauseList(operator=operators.comma_op, group_contents=True, *args).self_group()
+        self.clause_expr = ClauseList(
+                                operator=operators.comma_op,
+                                 group_contents=True, *args).\
+                                 self_group()
         self.type = sqltypes.to_instance(kwargs.get('type_', None))
 
-    @property
-    def key(self):
-        return self.name
-
     @property
     def columns(self):
         return [self]
@@ -2402,7 +2397,7 @@ class Function(ColumnElement, FromClause):
     @util.memoized_property
     def clauses(self):
         return self.clause_expr.element
-        
+
     @property
     def _from_objects(self):
         return self.clauses._from_objects
@@ -2414,9 +2409,6 @@ class Function(ColumnElement, FromClause):
         self.clause_expr = clone(self.clause_expr)
         self._reset_exported()
         util.reset_memoized(self, 'clauses')
-        
-    def _bind_param(self, obj):
-        return _BindParamClause(self.name, obj, type_=self.type, unique=True)
 
     def select(self):
         return select([self])
@@ -2430,6 +2422,23 @@ class Function(ColumnElement, FromClause):
     def _compare_type(self, obj):
         return self.type
 
+    def _bind_param(self, obj):
+        return _BindParamClause(None, obj, type_=self.type, unique=True)
+
+    
+class Function(FunctionElement):
+    """Describe a named SQL function."""
+
+    __visit_name__ = 'function'
+
+    def __init__(self, name, *clauses, **kw):
+        self.packagenames = kw.pop('packagenames', None) or []
+        self.name = name
+        FunctionElement.__init__(self, *clauses, **kw)
+
+    def _bind_param(self, obj):
+        return _BindParamClause(self.name, obj, type_=self.type, unique=True)
+
 
 class _Cast(ColumnElement):
 
@@ -2836,10 +2845,6 @@ class _Grouping(ColumnElement):
         self.element = element
         self.type = getattr(element, 'type', None)
 
-    @property
-    def key(self):
-        return self.element.key
-
     @property
     def _label(self):
         return getattr(self.element, '_label', None) or self.anon_label
@@ -3178,11 +3183,11 @@ class _SelectBaseMixin(object):
         self._group_by_clause = ClauseList(*util.to_list(group_by) or [])
 
     def as_scalar(self):
-        """return a 'scalar' representation of this selectable, which can be used
-        as a column expression.
+        """return a 'scalar' representation of this selectable, which can be
+        used as a column expression.
 
-        Typically, a select statement which has only one column in its columns clause
-        is eligible to be used as a scalar expression.
+        Typically, a select statement which has only one column in its columns
+        clause is eligible to be used as a scalar expression.
 
         The returned object is an instance of 
         :class:`~sqlalchemy.sql.expression._ScalarSelect`.
@@ -3194,17 +3199,18 @@ class _SelectBaseMixin(object):
     def apply_labels(self):
         """return a new selectable with the 'use_labels' flag set to True.
 
-        This will result in column expressions being generated using labels against their
-        table name, such as "SELECT somecolumn AS tablename_somecolumn". This allows
-        selectables which contain multiple FROM clauses to produce a unique set of column
-        names regardless of name conflicts among the individual FROM clauses.
+        This will result in column expressions being generated using labels
+        against their table name, such as "SELECT somecolumn AS
+        tablename_somecolumn". This allows selectables which contain multiple
+        FROM clauses to produce a unique set of column names regardless of
+        name conflicts among the individual FROM clauses.
 
         """
         self.use_labels = True
 
     def label(self, name):
-        """return a 'scalar' representation of this selectable, embedded as a subquery
-        with a label.
+        """return a 'scalar' representation of this selectable, embedded as a
+        subquery with a label.
 
         See also ``as_scalar()``.
 
index 3ee94d0271b15027b8b6fda14fa8587de8602e48..d625ae0ca07d00439f8dd0651229eb6aaef7e30c 100644 (file)
@@ -1,6 +1,7 @@
 from sqlalchemy import *
 from sqlalchemy.types import TypeEngine
-from sqlalchemy.sql.expression import ClauseElement, ColumnClause
+from sqlalchemy.sql.expression import ClauseElement, ColumnClause,\
+                                    FunctionElement
 from sqlalchemy.schema import DDLElement
 from sqlalchemy.ext.compiler import compiles
 from sqlalchemy.sql import table, column
@@ -151,3 +152,30 @@ class UserDefinedTest(TestBase, AssertsCompiledSQL):
             "DROP THINGY",
         )
 
+    def test_functions(self):
+        from sqlalchemy.dialects.postgresql import base as postgresql
+        
+        class MyUtcFunction(FunctionElement):
+            pass
+            
+        @compiles(MyUtcFunction)
+        def visit_myfunc(element, compiler, **kw):
+            return "utcnow()"
+            
+        @compiles(MyUtcFunction, 'postgresql')
+        def visit_myfunc(element, compiler, **kw):
+            return "timezone('utc', current_timestamp)"
+            
+        self.assert_compile(
+            MyUtcFunction(),
+            "utcnow()",
+            use_default_dialect=True
+        )
+        self.assert_compile(
+            MyUtcFunction(),
+            "timezone('utc', current_timestamp)",
+            dialect=postgresql.dialect()
+        )
+            
+        
+        
\ No newline at end of file