From 69dcd805d2c6ed92adf81da443c5c06d23d3423a Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 17 Apr 2011 15:37:12 -0400 Subject: [PATCH] - Added explicit true()/false() constructs to expression lib - coercion rules will intercept "False"/"True" into these constructs. In 0.6, the constructs were typically converted straight to string, which was no longer accepted in 0.7. [ticket:2117] --- CHANGES | 6 +++ doc/build/core/expression_api.rst | 4 ++ lib/sqlalchemy/sql/compiler.py | 8 +++- lib/sqlalchemy/sql/expression.py | 54 +++++++++++++++++++++++-- test/sql/test_compiler.py | 65 ++++++++++++++++++++++++++++++- 5 files changed, 132 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 6db99a8ac2..9f2d1c31a6 100644 --- a/CHANGES +++ b/CHANGES @@ -104,6 +104,12 @@ CHANGES now function, tests added for before/after events on all constraint types. [ticket:2105] + - Added explicit true()/false() constructs to expression + lib - coercion rules will intercept "False"/"True" + into these constructs. In 0.6, the constructs were + typically converted straight to string, which was + no longer accepted in 0.7. [ticket:2117] + - engine - The C extension is now enabled by default on CPython 2.x with a fallback to pure python if it fails to diff --git a/doc/build/core/expression_api.rst b/doc/build/core/expression_api.rst index 48dc503944..b7004220b9 100644 --- a/doc/build/core/expression_api.rst +++ b/doc/build/core/expression_api.rst @@ -45,6 +45,8 @@ The expression package uses functions to construct SQL expressions. The return .. autofunction:: extract +.. autofunction:: false + .. autodata:: func .. autofunction:: insert @@ -81,6 +83,8 @@ The expression package uses functions to construct SQL expressions. The return .. autofunction:: text +.. autofunction:: true + .. autofunction:: tuple_ .. autofunction:: type_coerce diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index fe04da1a1b..82c1748ccd 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -426,9 +426,15 @@ class SQLCompiler(engine.Compiled): self.post_process_text(textclause.text)) ) - def visit_null(self, null, **kwargs): + def visit_null(self, expr, **kw): return 'NULL' + def visit_true(self, expr, **kw): + return 'true' + + def visit_false(self, expr, **kw): + return 'false' + def visit_clauselist(self, clauselist, **kwargs): sep = clauselist.operator if sep is None: diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index b7e543850d..c552f055f3 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -1046,12 +1046,25 @@ def over(func, partition_by=None, order_by=None): return _Over(func, partition_by=partition_by, order_by=order_by) def null(): - """Return a :class:`_Null` object, which compiles to ``NULL`` in a sql - statement. + """Return a :class:`_Null` object, which compiles to ``NULL``. """ return _Null() +def true(): + """Return a :class:`_True` object, which compiles to ``true``, or the + boolean equivalent for the target dialect. + + """ + return _True() + +def false(): + """Return a :class:`_False` object, which compiles to ``false``, or the + boolean equivalent for the target dialect. + + """ + return _False() + class _FunctionGenerator(object): """Generate :class:`.Function` objects based on getattr calls.""" @@ -1199,11 +1212,25 @@ def _literal_as_text(element): return element.__clause_element__() elif isinstance(element, basestring): return _TextClause(unicode(element)) + elif isinstance(element, (util.NoneType, bool)): + return _const_expr(element) else: raise exc.ArgumentError( "SQL expression object or string expected." ) +def _const_expr(element): + if element is None: + return null() + elif element is False: + return false() + elif element is True: + return true() + else: + raise exc.ArgumentError( + "Expected None, False, or True" + ) + def _clause_element_as_expr(element): if hasattr(element, '__clause_element__'): return element.__clause_element__() @@ -2840,10 +2867,31 @@ class _Null(ColumnElement): """ __visit_name__ = 'null' - def __init__(self): self.type = sqltypes.NULLTYPE +class _False(ColumnElement): + """Represent the ``false`` keyword in a SQL statement. + + Public constructor is the :func:`false()` function. + + """ + + __visit_name__ = 'false' + def __init__(self): + self.type = sqltypes.BOOLEANTYPE + +class _True(ColumnElement): + """Represent the ``true`` keyword in a SQL statement. + + Public constructor is the :func:`true()` function. + + """ + + __visit_name__ = 'true' + def __init__(self): + self.type = sqltypes.BOOLEANTYPE + class ClauseList(ClauseElement): """Describe a list of clauses, separated by an operator. diff --git a/test/sql/test_compiler.py b/test/sql/test_compiler.py index 678411fe2c..7e95f83efa 100644 --- a/test/sql/test_compiler.py +++ b/test/sql/test_compiler.py @@ -3,7 +3,7 @@ import datetime, re, operator, decimal from sqlalchemy import * from sqlalchemy import exc, sql, util from sqlalchemy.sql import table, column, label, compiler -from sqlalchemy.sql.expression import ClauseList +from sqlalchemy.sql.expression import ClauseList, _literal_as_text from sqlalchemy.engine import default from sqlalchemy.databases import * from test.lib import * @@ -2812,3 +2812,66 @@ class SchemaTest(fixtures.TestBase, AssertsCompiledSQL): "INSERT INTO remote_owner.remotetable (rem_id, datatype_id, value) VALUES " "(:rem_id, :datatype_id, :value)") + +class CoercionTest(fixtures.TestBase, AssertsCompiledSQL): + __dialect__ = 'default' + + def _fixture(self): + m = MetaData() + return Table('foo', m, + Column('id', Integer)) + + def test_null_constant(self): + t = self._fixture() + self.assert_compile(_literal_as_text(None), "NULL") + + def test_false_constant(self): + t = self._fixture() + self.assert_compile(_literal_as_text(False), "false") + + def test_true_constant(self): + t = self._fixture() + self.assert_compile(_literal_as_text(True), "true") + + def test_val_and_false(self): + t = self._fixture() + self.assert_compile(and_(t.c.id == 1, False), + "foo.id = :id_1 AND false") + + def test_val_and_true_coerced(self): + t = self._fixture() + self.assert_compile(and_(t.c.id == 1, True), + "foo.id = :id_1 AND true") + + def test_val_is_null_coerced(self): + t = self._fixture() + self.assert_compile(and_(t.c.id == None), + "foo.id IS NULL") + + def test_val_and_None(self): + # current convention is None in and_() or + # other clauselist is ignored. May want + # to revise this at some point. + t = self._fixture() + self.assert_compile(and_(t.c.id == 1, None), + "foo.id = :id_1") + + def test_None_and_val(self): + # current convention is None in and_() or + # other clauselist is ignored. May want + # to revise this at some point. + t = self._fixture() + self.assert_compile(and_(t.c.id == 1, None), + "foo.id = :id_1") + + def test_None_and_nothing(self): + # current convention is None in and_() + # returns None May want + # to revise this at some point. + assert and_(None) is None + + def test_val_and_null(self): + t = self._fixture() + self.assert_compile(and_(t.c.id == 1, null()), + "foo.id = :id_1 AND NULL") + -- 2.39.5