[CalendarServer-changes] [12210] twext/trunk/twext/enterprise/dal

source_changes at macosforge.org source_changes at macosforge.org
Wed Mar 12 11:18:01 PDT 2014


Revision: 12210
          http://trac.calendarserver.org//changeset/12210
Author:   cdaboo at apple.com
Date:     2014-01-02 09:02:37 -0800 (Thu, 02 Jan 2014)
Log Message:
-----------
Add "NOT" and comma-based cross join syntax to support CalDAV/CardDAV queries.

Modified Paths:
--------------
    twext/trunk/twext/enterprise/dal/syntax.py
    twext/trunk/twext/enterprise/dal/test/test_sqlsyntax.py

Modified: twext/trunk/twext/enterprise/dal/syntax.py
===================================================================
--- twext/trunk/twext/enterprise/dal/syntax.py	2014-01-02 16:53:18 UTC (rev 12209)
+++ twext/trunk/twext/enterprise/dal/syntax.py	2014-01-02 17:02:37 UTC (rev 12210)
@@ -407,16 +407,41 @@
         @param other: a constant parameter or sub-select
         @type other: L{Parameter} or L{Select}
         """
+        return self._commonIn('in', other)
+
+
+    def NotIn(self, other):
+        """
+        We support two forms of the SQL "NOT IN" syntax: one where a list of values
+        is supplied, the other where a sub-select is used to provide a set of
+        values.
+
+        @param other: a constant parameter or sub-select
+        @type other: L{Parameter} or L{Select}
+        """
+        return self._commonIn('not in', other)
+
+
+    def _commonIn(self, op, other):
+        """
+        We support two forms of the SQL "IN" and "NOT IN" syntax: one where a list
+        of values is supplied, the other where a sub-select is used to provide a set
+        of values.
+
+        @param other: a constant parameter or sub-select
+        @type other: L{Parameter} or L{Select}
+        """
+
         if isinstance(other, Parameter):
             if other.count is None:
                 raise DALError(
-                    "IN expression needs an explicit count of parameters"
+                    "{} expression needs an explicit count of parameters".format(op.upper())
                 )
-            return CompoundComparison(self, "in", Constant(other))
+            return CompoundComparison(self, op, Constant(other))
         else:
             # Can't be Select.__contains__ because __contains__ gets
             # __nonzero__ called on its result by the "in" syntax.
-            return CompoundComparison(self, "in", other)
+            return CompoundComparison(self, op, other)
 
 
     def StartsWith(self, other):
@@ -426,6 +451,13 @@
         )
 
 
+    def NotStartsWith(self, other):
+        return CompoundComparison(
+            self, "not like",
+            CompoundComparison(Constant(other), "||", Constant("%"))
+        )
+
+
     def EndsWith(self, other):
         return CompoundComparison(
             self, "like",
@@ -433,6 +465,13 @@
         )
 
 
+    def NotEndsWith(self, other):
+        return CompoundComparison(
+            self, "not like",
+            CompoundComparison(Constant("%"), "||", Constant(other))
+        )
+
+
     def Contains(self, other):
         return CompoundComparison(
             self, "like",
@@ -443,7 +482,17 @@
         )
 
 
+    def NotContains(self, other):
+        return CompoundComparison(
+            self, "not like",
+            CompoundComparison(
+                Constant("%"), "||",
+                CompoundComparison(Constant(other), "||", Constant("%"))
+            )
+        )
 
+
+
 class FunctionInvocation(ExpressionSyntax):
     def __init__(self, function, *args):
         self.function = function
@@ -647,7 +696,7 @@
         """
         Create a L{Join}, representing a join between two tables.
         """
-        if on is None:
+        if on is None and not type:
             type = "cross"
         return Join(self, type, otherTableSyntax, on)
 
@@ -792,13 +841,16 @@
     def subSQL(self, queryGenerator, allTables):
         stmt = SQLFragment()
         stmt.append(self.leftSide.subSQL(queryGenerator, allTables))
-        stmt.text += " "
-        if self.type:
-            stmt.text += self.type
+        if self.type == ",":
+            stmt.text += ", "
+        else:
             stmt.text += " "
-        stmt.text += "join "
+            if self.type:
+                stmt.text += self.type
+                stmt.text += " "
+            stmt.text += "join "
         stmt.append(self.rightSide.subSQL(queryGenerator, allTables))
-        if self.type != "cross":
+        if self.type not in ("cross", ","):
             stmt.text += " on "
             stmt.append(self.on.subSQL(queryGenerator, allTables))
         return stmt
@@ -984,6 +1036,26 @@
 
 
 
+class Not(Comparison):
+    """
+    A L{NotColumn} is a logical NOT of an expression.
+    """
+    def __init__(self, a):
+        # "op" and "b" are always None for this comparison type
+        super(Not, self).__init__(a, None, None)
+
+
+    def subSQL(self, queryGenerator, allTables):
+        sqls = SQLFragment()
+        sqls.text += "not "
+        result = self.a.subSQL(queryGenerator, allTables)
+        if isinstance(self.a, CompoundComparison) and self.a.op in ("or", "and"):
+            result = _inParens(result)
+        sqls.append(result)
+        return sqls
+
+
+
 class NullComparison(Comparison):
     """
     A L{NullComparison} is a comparison of a column or expression with None.

Modified: twext/trunk/twext/enterprise/dal/test/test_sqlsyntax.py
===================================================================
--- twext/trunk/twext/enterprise/dal/test/test_sqlsyntax.py	2014-01-02 16:53:18 UTC (rev 12209)
+++ twext/trunk/twext/enterprise/dal/test/test_sqlsyntax.py	2014-01-02 17:02:37 UTC (rev 12210)
@@ -33,8 +33,7 @@
     Savepoint, RollbackToSavepoint, ReleaseSavepoint, SavepointAction,
     Union, Intersect, Except, SetExpression, DALError,
     ResultAliasSyntax, Count, QueryGenerator, ALL_COLUMNS,
-    DatabaseLock, DatabaseUnlock
-)
+    DatabaseLock, DatabaseUnlock, Not)
 from twext.enterprise.dal.syntax import FixedPlaceholder, NumericPlaceholder
 from twext.enterprise.dal.syntax import Function
 from twext.enterprise.dal.syntax import SchemaSyntax
@@ -411,6 +410,17 @@
         )
 
 
+    def test_commaJoin(self):
+        """
+        A join with no clause specified will generate a cross join. This variant
+        uses a "," between table names rather than "cross join".
+        """
+        self.assertEquals(
+            Select(From=self.schema.FOO.join(self.schema.BOZ, type=",")).toSQL(),
+            SQLFragment("select * from FOO, BOZ")
+        )
+
+
     def test_crossJoin(self):
         """
         A join with no clause specified will generate a cross join.  (This is
@@ -965,13 +975,13 @@
                 From=self.schema.FOO,
                 Where=(
                     (
-                        self.schema.FOO.BAZ == Parameter("P1")
-                    ).Or(
-                        self.schema.FOO.BAR.In(Parameter("names", len(items)))
-                    ).And(
-                        self.schema.FOO.BAZ == Parameter("P2")
+                    self.schema.FOO.BAZ == Parameter("P1")
+                ).Or(
+                        self.schema.FOO.BAR.In(Parameter("names", len(items))
+                ).And(
+                    self.schema.FOO.BAZ == Parameter("P2")
                     )
-                )
+                ))
             ).toSQL().bind(P1="P1", P2="P2", names=items),
             SQLFragment(
                 "select * from FOO where BAZ = ? or BAR in (?, ?) and BAZ = ?",
@@ -1121,6 +1131,73 @@
         )
 
 
+    def test_not(self):
+        """
+        Test for the string starts with comparison.
+        (Note that this should be updated to use different techniques
+        as necessary in different databases.)
+        """
+        self.assertEquals(
+            Select([
+                self.schema.TEXTUAL.MYTEXT],
+                From=self.schema.TEXTUAL,
+                Where=Not(self.schema.TEXTUAL.MYTEXT.StartsWith("test")),
+            ).toSQL(),
+            SQLFragment(
+                "select MYTEXT from TEXTUAL where not MYTEXT like (? || ?)",
+                ["test", "%"]
+            )
+        )
+
+        self.assertEquals(
+            Select([
+                self.schema.TEXTUAL.MYTEXT],
+                From=self.schema.TEXTUAL,
+                Where=Not(self.schema.TEXTUAL.MYTEXT == "test"),
+            ).toSQL(),
+            SQLFragment(
+                "select MYTEXT from TEXTUAL where not MYTEXT = ?",
+                ["test"]
+            )
+        )
+
+        self.assertEquals(
+            Select([
+                self.schema.TEXTUAL.MYTEXT],
+                From=self.schema.TEXTUAL,
+                Where=Not((self.schema.TEXTUAL.MYTEXT == "test1").And(self.schema.TEXTUAL.MYTEXT != "test2")),
+            ).toSQL(),
+            SQLFragment(
+                "select MYTEXT from TEXTUAL where not (MYTEXT = ? and MYTEXT != ?)",
+                ["test1", "test2"]
+            )
+        )
+
+        self.assertEquals(
+            Select([
+                self.schema.TEXTUAL.MYTEXT],
+                From=self.schema.TEXTUAL,
+                Where=Not((self.schema.TEXTUAL.MYTEXT == "test1")).And(self.schema.TEXTUAL.MYTEXT != "test2"),
+            ).toSQL(),
+            SQLFragment(
+                "select MYTEXT from TEXTUAL where not MYTEXT = ? and MYTEXT != ?",
+                ["test1", "test2"]
+            )
+        )
+
+        self.assertEquals(
+            Select([
+                self.schema.TEXTUAL.MYTEXT],
+                From=self.schema.TEXTUAL,
+                Where=Not(self.schema.TEXTUAL.MYTEXT.StartsWith("foo").And(self.schema.TEXTUAL.MYTEXT.NotEndsWith("bar"))),
+            ).toSQL(),
+            SQLFragment(
+                "select MYTEXT from TEXTUAL where not (MYTEXT like (? || ?) and MYTEXT not like (? || ?))",
+                ["foo", "%", "%", "bar"]
+            )
+        )
+
+
     def test_insert(self):
         """
         L{Insert.toSQL} generates an C{insert} statement with all the relevant
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140312/258ae0ac/attachment.html>


More information about the calendarserver-changes mailing list