[CalendarServer-changes] [7515] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Fri May 20 13:36:24 PDT 2011
Revision: 7515
http://trac.macosforge.org/projects/calendarserver/changeset/7515
Author: cdaboo at apple.com
Date: 2011-05-20 13:36:24 -0700 (Fri, 20 May 2011)
Log Message:
-----------
Make sure SQL DB queries are scoped to calendar and addressbook collections as appropriate. Fix up some
incorrect addressbook query behavior.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/query/addressbookquery.py
CalendarServer/trunk/twistedcaldav/query/addressbookqueryfilter.py
CalendarServer/trunk/twistedcaldav/query/expression.py
CalendarServer/trunk/txdav/common/datastore/sql_legacy.py
Added Paths:
-----------
CalendarServer/trunk/twistedcaldav/query/test/test_expression.py
Modified: CalendarServer/trunk/twistedcaldav/query/addressbookquery.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/addressbookquery.py 2011-05-20 20:27:41 UTC (rev 7514)
+++ CalendarServer/trunk/twistedcaldav/query/addressbookquery.py 2011-05-20 20:36:24 UTC (rev 7515)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -26,8 +26,7 @@
"sqladdressbookquery",
]
-from twistedcaldav.query import expression, sqlgenerator
-from twistedcaldav import carddavxml
+from twistedcaldav.query import expression, sqlgenerator, addressbookqueryfilter
# SQL Index column (field) names
@@ -76,33 +75,27 @@
# Test for <<field>> != "*"
return expression.isExpression(fields["UID"], "", True)
- # Handle text-match
- tm = None
- if propfilter.qualifier and isinstance(propfilter.qualifier, carddavxml.TextMatch):
- if propfilter.qualifier.negate:
- tm = expression.notcontainsExpression(fields[propfilter.filter_name], str(propfilter.qualifier), propfilter.qualifier)
- else:
- tm = expression.containsExpression(fields[propfilter.filter_name], str(propfilter.qualifier), propfilter.qualifier)
-
- # Handle embedded parameters - we do not right now as our Index does not handle them
+ # Handle embedded parameters/text-match
params = []
- if len(propfilter.filters) > 0:
- raise ValueError
+ for filter in propfilter.filters:
+ if isinstance(filter, addressbookqueryfilter.TextMatch):
+ if filter.negate:
+ params.append(expression.notcontainsExpression(fields[propfilter.filter_name], str(filter.text), True))
+ else:
+ params.append(expression.containsExpression(fields[propfilter.filter_name], str(filter.text), True))
+ else:
+ # No embedded parameters - not right now as our Index does not handle them
+ raise ValueError
+
+ # Now build return expression
if len(params) > 1:
- paramsExpression = expression.orExpression[params]
+ if propfilter.propfilter_test == "anyof":
+ return expression.orExpression[params]
+ else:
+ return expression.andExpression[params]
elif len(params) == 1:
- paramsExpression = params[0]
+ return params[0]
else:
- paramsExpression = None
-
- # Now build return expression
- if (tm is not None) and (paramsExpression is not None):
- return expression.andExpression([tm, paramsExpression])
- elif tm is not None:
- return tm
- elif paramsExpression is not None:
- return paramsExpression
- else:
return None
def sqladdressbookquery(filter, addressbookid=None, generator=sqlgenerator.sqlgenerator):
@@ -116,7 +109,7 @@
"""
try:
expression = addressbookquery(filter, generator.FIELDS)
- sql = generator(expression, addressbookid)
+ sql = generator(expression, addressbookid, None)
return sql.generate()
except ValueError:
return None
Modified: CalendarServer/trunk/twistedcaldav/query/addressbookqueryfilter.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/addressbookqueryfilter.py 2011-05-20 20:27:41 UTC (rev 7514)
+++ CalendarServer/trunk/twistedcaldav/query/addressbookqueryfilter.py 2011-05-20 20:36:24 UTC (rev 7515)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2011 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -69,7 +69,7 @@
if len(self.children) > 0:
allof = self.filter_test == "allof"
for propfilter in self.children:
- if allof != propfilter.test(vcard):
+ if allof != propfilter._match(vcard):
return not allof
return allof
else:
@@ -153,7 +153,7 @@
if len(self.filters) > 0:
allof = self.propfilter_test == "allof"
for filter in self.filters:
- if allof != filter.test(item):
+ if allof != filter._match(item):
return not allof
return allof
else:
@@ -164,7 +164,7 @@
Limits a search to specific properties.
"""
- def test(self, vcard):
+ def _match(self, vcard):
# At least one property must match (or is-not-defined is set)
for property in vcard.properties():
if property.name().upper() == self.filter_name.upper() and self.match(property): break
@@ -188,7 +188,7 @@
Limits a search to specific parameters.
"""
- def test(self, property):
+ def _match(self, property):
# At least one parameter must match (or is-not-defined is set)
result = not self.defined
@@ -246,7 +246,7 @@
else:
self.match_type = "contains"
- def test(self, item):
+ def _match(self, item):
"""
Match the text for the item.
If the item is a property, then match the property value,
Modified: CalendarServer/trunk/twistedcaldav/query/expression.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/expression.py 2011-05-20 20:27:41 UTC (rev 7514)
+++ CalendarServer/trunk/twistedcaldav/query/expression.py 2011-05-20 20:36:24 UTC (rev 7515)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -55,7 +55,22 @@
return False
-class allExpression(object):
+ def _collapsedExpression(self):
+ return self
+
+ def andWith(self, other):
+ if isinstance(other, andExpression):
+ return andExpression((self._collapsedExpression(),) + tuple(other.expressions))
+ else:
+ return andExpression((self._collapsedExpression(), other._collapsedExpression(),))
+
+ def orWith(self, other):
+ if isinstance(other, orExpression):
+ return orExpression((self._collapsedExpression(),) + tuple(other.expressions))
+ else:
+ return orExpression((self._collapsedExpression(), other._collapsedExpression(),))
+
+class allExpression(baseExpression):
"""
Match everything.
"""
@@ -99,6 +114,12 @@
return True
+ def _collapsedExpression(self):
+ if self.multi() and len(self.expressions) == 1:
+ return self.expressions[0]._collapsedExpression()
+ else:
+ return self
+
class notExpression(logicExpression):
"""
Logical NOT operation.
@@ -135,6 +156,10 @@
def operator(self):
return "AND"
+ def andWith(self, other):
+ self.expressions = tuple(self.expressions) + (other._collapsedExpression(),)
+ return self
+
class orExpression(logicExpression):
"""
Logical OR operation.
@@ -146,6 +171,10 @@
def operator(self):
return "OR"
+ def orWith(self, other):
+ self.expressions = tuple(self.expressions) + (other._collapsedExpression(),)
+ return self
+
class timerangeExpression(baseExpression):
"""
CalDAV time-range comparison expression.
Added: CalendarServer/trunk/twistedcaldav/query/test/test_expression.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/query/test/test_expression.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/query/test/test_expression.py 2011-05-20 20:36:24 UTC (rev 7515)
@@ -0,0 +1,166 @@
+##
+# Copyright (c) 2011 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav.query import expression
+import twistedcaldav.test.util
+
+class Tests(twistedcaldav.test.util.TestCase):
+
+ def test_andWith(self):
+
+ tests = (
+ (
+ expression.isExpression("A", "1", True),
+ expression.isExpression("B", "2", True),
+ "(is(A, 1, True) AND is(B, 2, True))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.andExpression((
+ expression.isExpression("B", "2", True),
+ )),
+ "(is(A, 1, True) AND is(B, 2, True))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.andExpression((
+ expression.isExpression("B", "2", True),
+ expression.isExpression("C", "3", True),
+ )),
+ "(is(A, 1, True) AND is(B, 2, True) AND is(C, 3, True))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.orExpression((
+ expression.isExpression("B", "2", True),
+ )),
+ "(is(A, 1, True) AND is(B, 2, True))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.orExpression((
+ expression.isExpression("B", "2", True),
+ expression.isExpression("C", "3", True),
+ )),
+ "(is(A, 1, True) AND (is(B, 2, True) OR is(C, 3, True)))"
+ ),
+ (
+ expression.andExpression((
+ expression.isExpression("A", "1", True),
+ )),
+ expression.isExpression("B", "2", True),
+ "(is(A, 1, True) AND is(B, 2, True))"
+ ),
+ (
+ expression.andExpression((
+ expression.isExpression("A", "1", True),
+ expression.isExpression("B", "2", True),
+ )),
+ expression.isExpression("C", "3", True),
+ "(is(A, 1, True) AND is(B, 2, True) AND is(C, 3, True))"
+ ),
+ (
+ expression.orExpression((
+ expression.isExpression("A", "1", True),
+ )),
+ expression.isExpression("B", "2", True),
+ "(is(A, 1, True) AND is(B, 2, True))"
+ ),
+ (
+ expression.orExpression((
+ expression.isExpression("A", "1", True),
+ expression.isExpression("B", "2", True),
+ )),
+ expression.isExpression("C", "3", True),
+ "((is(A, 1, True) OR is(B, 2, True)) AND is(C, 3, True))"
+ ),
+ )
+
+ for expr1, expr2, result in tests:
+ self.assertEqual(str(expr1.andWith(expr2)), result, msg="Failed on %s" % (result,))
+
+ def test_orWith(self):
+
+ tests = (
+ (
+ expression.isExpression("A", "1", True),
+ expression.isExpression("B", "2", True),
+ "(is(A, 1, True) OR is(B, 2, True))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.andExpression((
+ expression.isExpression("B", "2", True),
+ )),
+ "(is(A, 1, True) OR is(B, 2, True))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.andExpression((
+ expression.isExpression("B", "2", True),
+ expression.isExpression("C", "3", True),
+ )),
+ "(is(A, 1, True) OR (is(B, 2, True) AND is(C, 3, True)))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.orExpression((
+ expression.isExpression("B", "2", True),
+ )),
+ "(is(A, 1, True) OR is(B, 2, True))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.orExpression((
+ expression.isExpression("B", "2", True),
+ expression.isExpression("C", "3", True),
+ )),
+ "(is(A, 1, True) OR is(B, 2, True) OR is(C, 3, True))"
+ ),
+ (
+ expression.andExpression((
+ expression.isExpression("A", "1", True),
+ )),
+ expression.isExpression("B", "2", True),
+ "(is(A, 1, True) OR is(B, 2, True))"
+ ),
+ (
+ expression.andExpression((
+ expression.isExpression("A", "1", True),
+ expression.isExpression("B", "2", True),
+ )),
+ expression.isExpression("C", "3", True),
+ "((is(A, 1, True) AND is(B, 2, True)) OR is(C, 3, True))"
+ ),
+ (
+ expression.orExpression((
+ expression.isExpression("A", "1", True),
+ )),
+ expression.isExpression("B", "2", True),
+ "(is(A, 1, True) OR is(B, 2, True))"
+ ),
+ (
+ expression.orExpression((
+ expression.isExpression("A", "1", True),
+ expression.isExpression("B", "2", True),
+ )),
+ expression.isExpression("C", "3", True),
+ "(is(A, 1, True) OR is(B, 2, True) OR is(C, 3, True))"
+ ),
+ )
+
+ for expr1, expr2, result in tests:
+ self.assertEqual(str(expr1.orWith(expr2)), result, msg="Failed on %s" % (result,))
Modified: CalendarServer/trunk/txdav/common/datastore/sql_legacy.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_legacy.py 2011-05-20 20:27:41 UTC (rev 7514)
+++ CalendarServer/trunk/txdav/common/datastore/sql_legacy.py 2011-05-20 20:36:24 UTC (rev 7515)
@@ -30,13 +30,13 @@
from twext.python.clsprop import classproperty
from twext.python.log import Logger, LoggingMixIn
-from twistedcaldav import carddavxml
from twistedcaldav.config import config
from twistedcaldav.dateops import normalizeForIndex, pyCalendarTodatetime
from twistedcaldav.memcachepool import CachePoolUserMixIn
from twistedcaldav.notifications import NotificationRecord
from twistedcaldav.query import (
- calendarqueryfilter, calendarquery, addressbookquery)
+ calendarqueryfilter, calendarquery, addressbookquery, expression,
+ addressbookqueryfilter)
from twistedcaldav.query.sqlgenerator import sqlgenerator
from twistedcaldav.sharing import Invite
@@ -891,6 +891,62 @@
self.substitutions = []
self.usedtimespan = False
+ # For SQL data DB we need to restrict the query to just the targeted calendar resource-id if provided
+ if self.calendarid:
+
+ test = expression.isExpression("CALENDAR_OBJECT.CALENDAR_RESOURCE_ID", str(self.calendarid), True)
+
+ # Since timerange expression already have the calendar resource-id test in them, do not
+ # add the additional term to those. When the additional term is added, add it as the first
+ # component in the AND expression to hopefully get the DB to use its index first
+
+ # Top-level timerange expression already has calendar resource-id restriction in it
+ if isinstance(self.expression, expression.timerangeExpression):
+ pass
+
+ # Top-level OR - check each component
+ elif isinstance(self.expression, expression.orExpression):
+
+ def _hasTopLevelTimerange(testexpr):
+ if isinstance(testexpr, expression.timerangeExpression):
+ return True
+ elif isinstance(testexpr, expression.andExpression):
+ return any([isinstance(expr, expression.timerangeExpression) for expr in testexpr.expressions])
+ else:
+ return False
+
+ hasTimerange = any([_hasTopLevelTimerange(expr) for expr in self.expression.expressions])
+
+ if hasTimerange:
+ # AND each of the non-timerange expressions
+ trexpressions = []
+ orexpressions = []
+ for expr in self.expression.expressions:
+ if _hasTopLevelTimerange(expr):
+ trexpressions.append(expr)
+ else:
+ orexpressions.append(expr)
+
+ if orexpressions:
+ self.expression.expressions = tuple(trexpressions) + (
+ test.andWith(expression.orExpression(orexpressions)),
+ )
+ else:
+ # AND the whole thing
+ self.expression = test.andWith(self.expression)
+
+
+ # Top-level AND - only add additional expression if timerange not present
+ elif isinstance(self.expression, expression.andExpression):
+ hasTimerange = any([isinstance(expr, expression.timerangeExpression) for expr in self.expression.expressions])
+ if not hasTimerange:
+ # AND the whole thing
+ self.expression = test.andWith(self.expression)
+
+ # Just AND the entire thing
+ else:
+ self.expression = test.andWith(self.expression)
+
# Generate ' where ...' partial statement
self.sout.write(self.WHERE)
self.generateExpression(self.expression)
@@ -1291,6 +1347,13 @@
self.arguments = []
self.substitutions = []
+ # For SQL data DB we need to restrict the query to just the targeted calendar resource-id if provided
+ if self.calendarid:
+
+ # AND the whole thing
+ test = expression.isExpression("ADDRESSBOOK_OBJECT.ADDRESSBOOK_RESOURCE_ID", str(self.calendarid), True)
+ self.expression = test.andWith(self.expression)
+
# Generate ' where ...' partial statement
self.sout.write(self.WHERE)
self.generateExpression(self.expression)
@@ -1356,7 +1419,7 @@
def searchValid(self, filter):
- if isinstance(filter, carddavxml.Filter):
+ if isinstance(filter, addressbookqueryfilter.Filter):
qualifiers = addressbookquery.sqladdressbookquery(filter)
else:
qualifiers = None
@@ -1379,7 +1442,7 @@
else:
generator = postgresqladbkgenerator
# Make sure we have a proper Filter element and get the partial SQL statement to use.
- if isinstance(filter, carddavxml.Filter):
+ if isinstance(filter, addressbookqueryfilter.Filter):
qualifiers = addressbookquery.sqladdressbookquery(
filter, self.addressbook._resourceID, generator=generator)
else:
@@ -1392,10 +1455,10 @@
)
else:
rowiter = yield Select(
- [schema.ADDRESSBOOK_OBJECT.RESOURCE_NAME,
- schema.ADDRESSBOOK_OBJECT.VCARD_UID],
- From=schema.ADDRESSBOOK_OBJECT,
- Where=schema.ADDRESSBOOK_OBJECT.ADDRESSBOOK_RESOURCE_ID ==
+ [self._objectSchema.RESOURCE_NAME,
+ self._objectSchema.VCARD_UID],
+ From=self._objectSchema,
+ Where=self._objectSchema.ADDRESSBOOK_RESOURCE_ID ==
self.addressbook._resourceID
).on(self.addressbook._txn)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110520/6526d98e/attachment-0001.html>
More information about the calendarserver-changes
mailing list