diff options
-rw-r--r-- | doc/build/core/expression_api.rst | 3 | ||||
-rw-r--r-- | doc/build/core/tutorial.rst | 14 | ||||
-rw-r--r-- | doc/build/core/types.rst | 62 | ||||
-rw-r--r-- | doc/build/orm/mapper_config.rst | 63 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/operators.py | 17 |
5 files changed, 107 insertions, 52 deletions
diff --git a/doc/build/core/expression_api.rst b/doc/build/core/expression_api.rst index 132b38b6a..66338efae 100644 --- a/doc/build/core/expression_api.rst +++ b/doc/build/core/expression_api.rst @@ -145,6 +145,9 @@ Classes :members: :show-inheritance: +.. autoclass:: sqlalchemy.sql.operators.custom_op + :members: + .. autoclass:: CTE :members: :show-inheritance: diff --git a/doc/build/core/tutorial.rst b/doc/build/core/tutorial.rst index 143f55df9..dad1aa68d 100644 --- a/doc/build/core/tutorial.rst +++ b/doc/build/core/tutorial.rst @@ -646,7 +646,7 @@ The above illustrates the SQL that's generated for an the ``||`` operator now compiles as MySQL's ``concat()`` function. If you have come across an operator which really isn't available, you can -always use the ``op()`` method; this generates whatever operator you need: +always use the :meth:`.ColumnOperators.op` method; this generates whatever operator you need: .. sourcecode:: pycon+sql @@ -659,6 +659,18 @@ This function can also be used to make bitwise operators explicit. For example:: is a bitwise AND of the value in `somecolumn`. +Operator Customization +----------------------- + +While :meth:`.ColumnOperators.op` is handy to get at a custom operator in a hurry, +the Core supports fundamental customization and extension of the operator system at +the type level. The behavior of existing operators can be modified on a per-type +basis, and new operations can be defined which become available for all column +expressions that are part of that particular type. See the section :ref:`types_operators` +for a description. + + + Conjunctions ============= diff --git a/doc/build/core/types.rst b/doc/build/core/types.rst index 8471ac29e..6518a5e9c 100644 --- a/doc/build/core/types.rst +++ b/doc/build/core/types.rst @@ -488,6 +488,68 @@ cursor directly:: def adapt(self, impltype): return MySpecialTime(self.special_argument) +.. _types_operators: + +Redefining and Creating New Operators +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +SQLAlchemy Core defines a fixed set of expression operators available to all column expressions. +Some of these operations have the effect of overloading Python's built in operators; +examples of such operators include +:meth:`.ColumnOperators.__eq__` (``table.c.somecolumn == 'foo'``), +:meth:`.ColumnOperators.neg` (``~table.c.flag``), +and :meth:`.ColumnOperators.__add__` (``table.c.x + table.c.y``). Other operators are exposed as +explicit methods on column expressions, such as +:meth:`.ColumnOperators.in_` (``table.c.value.in_(['x', 'y'])``) and :meth:`.ColumnOperators.like` +(``table.c.value.like('%ed%')``). + +The Core expression constructs in all cases consult the type of the expression in order to determine +the behavior of existing operators, as well as to locate additional operators that aren't part of +the built in set. The :class:`.TypeEngine` base class defines a root "comparison" implementation +:class:`.TypeEngine.Comparator`, and many specific types provide their own sub-implementations of this +class. User-defined :class:`.TypeEngine.Comparator` implementations can be built directly into a +simple subclass of a particular type in order to override or define new operations. Below, +we create a :class:`.Integer` subclass which overrides the :meth:`.ColumnOperators.__add__` operator:: + + from sqlalchemy import Integer + + class MyInt(Integer): + class comparator_factory(Integer.Comparator): + def __add__(self, other): + return self.op("goofy")(other) + +The above configuration creates a new class ``MyInt``, which +establishes the :attr:`.TypeEngine.comparator_factory` attribute as +referring to a new class, subclassing the ``Comparator`` class +associated with the :class:`.Integer` type. + +The implementation for :meth:`.ColumnOperators.__add__` is consulted +by an owning SQL expression, by instantiating the ``Comparator`` with +itself as as the ``expr`` attribute. The mechanics of the expression +system are such that operations continue recursively until an +expression object produces a new SQL expression construct. Above, we +could just as well have said ``self.expr.op("goofy")(other)`` instead +of ``self.op("goofy")(other)``. + +New methods added to a ``Comparator`` are exposed on an owning SQL expression +using a ``__getattr__`` scheme. For example, to add an implementation of the +Postgresql factorial operator:: + + from sqlalchemy import Integer + from sqlalchemy.sql import UnaryExpression + from sqlalchemy.sql import operators + + class MyInteger(Integer): + class comparator_factory(Integer.Comparator): + def factorial(self): + return UnaryExpression(self.expr, + modifier=operators.custom_op("!"), + type_=MyInteger) + + + + + Creating New Types ~~~~~~~~~~~~~~~~~~ diff --git a/doc/build/orm/mapper_config.rst b/doc/build/orm/mapper_config.rst index 55c5af979..62515d214 100644 --- a/doc/build/orm/mapper_config.rst +++ b/doc/build/orm/mapper_config.rst @@ -733,57 +733,18 @@ based attribute. .. _custom_comparators: -Custom Comparators ------------------- - -The expressions returned by comparison operations, such as -``User.name=='ed'``, can be customized, by implementing an object that -explicitly defines each comparison method needed. - -This is a relatively rare use case which generally applies only to -highly customized types. Usually, custom SQL behaviors can be -associated with a mapped class by composing together the classes' -existing mapped attributes with other expression components, -using the techniques described in :ref:`mapper_sql_expressions`. -Those approaches should be considered first before resorting to custom comparison objects. - -Each of :func:`.orm.column_property`, :func:`~.composite`, :func:`.relationship`, -and :func:`.comparable_property` accept an argument called -``comparator_factory``. A subclass of :class:`.PropComparator` can be provided -for this argument, which can then reimplement basic Python comparison methods -such as ``__eq__()``, ``__ne__()``, ``__lt__()``, and so on. - -It's best to subclass the :class:`.PropComparator` subclass provided by -each type of property. For example, to allow a column-mapped attribute to -do case-insensitive comparison:: - - from sqlalchemy.orm.properties import ColumnProperty - from sqlalchemy.sql import func, Column, Integer, String - - class MyComparator(ColumnProperty.Comparator): - def __eq__(self, other): - return func.lower(self.__clause_element__()) == func.lower(other) - - class EmailAddress(Base): - __tablename__ = 'address' - id = Column(Integer, primary_key=True) - email = column_property( - Column('email', String), - comparator_factory=MyComparator - ) - -Above, comparisons on the ``email`` column are wrapped in the SQL lower() -function to produce case-insensitive matching:: - - >>> str(EmailAddress.email == 'SomeAddress@foo.com') - lower(address.email) = lower(:lower_1) - -When building a :class:`.PropComparator`, the ``__clause_element__()`` method -should be used in order to acquire the underlying mapped column. This will -return a column that is appropriately wrapped in any kind of subquery -or aliasing that has been applied in the context of the generated SQL statement. - -.. autofunction:: comparable_property +Operator Customization +---------------------- + +The "operators" used by the SQLAlchemy ORM and Core expression language +are fully customizable. For example, the comparison expression +``User.name == 'ed'`` makes usage of an operator built into Python +itself called ``operator.eq`` - the actual SQL construct which SQLAlchemy +associates with such an operator can be modified. New +operations can be associated with column expressions as well. The operators +which take place for column expressions are most directly redefined at the +type level - see the +section :ref:`types_operators` for a description. .. _mapper_composite: diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py index 560f723f2..ba1117ef6 100644 --- a/lib/sqlalchemy/sql/operators.py +++ b/lib/sqlalchemy/sql/operators.py @@ -170,6 +170,23 @@ class Operators(object): class custom_op(object): + """Represent a 'custom' operator. + + :class:`.custom_op` is normally instantitated when the + :meth:`.ColumnOperators.op` method is used to create a + custom operator callable. The class can also be used directly + when programmatically constructing expressions. E.g. + to represent the "factorial" operation:: + + from sqlalchemy.sql import UnaryExpression + from sqlalchemy.sql import operators + from sqlalchemy import Numeric + + unary = UnaryExpression(table.c.somecolumn, + modifier=operators.custom_op("!"), + type_=Numeric) + + """ __name__ = 'custom_op' def __init__(self, opstring, precedence=0): |