<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[12144] CalendarServer/branches/users/cdaboo/cross-pod-sharing</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/12144">12144</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2013-12-19 10:28:54 -0800 (Thu, 19 Dec 2013)</dd>
</dl>

<h3>Log Message</h3>
<pre>Get rid of sql_legacy, moving search directly onto store objects - will make cross-pod search api cleaner.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingcalendarservertoolsdbinspectpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/dbinspect.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingcalendarservertoolspurgepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/purge.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingcalendarservertoolstestcalverifyaccountsxml">CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/calverify/accounts.xml</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingcalendarservertoolstestpurgeaccountsxml">CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/purge/accounts.xml</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingcalendarservertoolstesttest_exportpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/test_export.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingcalendarservertoolstesttest_purgepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/test_purge.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtwextenterprisedalsyntaxpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/twext/enterprise/dal/syntax.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtwextenterprisedaltesttest_sqlsyntaxpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/twext/enterprise/dal/test/test_sqlsyntax.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavdirectoryopendirectorybackerpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/opendirectorybacker.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavdirectorytestaccountsxml">CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/test/accounts.xml</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavdirectorytestaugmentsxml">CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/test/augments.xml</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavdirectorytesttest_directorypy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/test/test_directory.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavmethodreport_addressbook_querypy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_addressbook_query.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavmethodreport_calendar_querypy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_calendar_query.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavmethodreport_commonpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_common.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavmethodreport_multiget_commonpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_multiget_common.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavstorebridgepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/storebridge.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavtesttest_calendarquerypy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/test/test_calendarquery.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavtesttest_sharingpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/test/test_sharing.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavtesttest_xmlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/test/test_xml.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoreindex_filepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/index_file.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoreschedulingfreebusypy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/scheduling/freebusy.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretesttest_index_filepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_index_file.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretesttest_sqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoreindex_filepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/index_file.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoretesttest_sql_sharingpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql_sharing.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li>CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/</li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequery__init__py">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/__init__.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequerybuilderpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/builder.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequeryfilterpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/filter.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequerygeneratorpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/generator.py</a></li>
<li>CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/</li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequerytest__init__py">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/__init__.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequerytesttest_filterpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/test_filter.py</a></li>
<li>CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/</li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequery__init__py">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/__init__.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequerybuilderpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/builder.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequeryfilterpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/filter.py</a></li>
<li>CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/</li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequerytest__init__py">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/__init__.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequerytesttest_filterpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/test_filter.py</a></li>
<li>CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/</li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequery__init__py">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/__init__.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequeryexpressionpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/expression.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequeryfilegeneratorpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/filegenerator.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequerygeneratorpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/generator.py</a></li>
<li>CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/</li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequerytest__init__py">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/__init__.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequerytesttest_expressionpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/test_expression.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequerytesttest_generatorpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/test_generator.py</a></li>
</ul>

<h3>Removed Paths</h3>
<ul>
<li>CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/query/</li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresql_legacypy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_legacy.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingcalendarservertoolsdbinspectpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/dbinspect.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/dbinspect.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/dbinspect.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -37,8 +37,8 @@
</span><span class="cx"> from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
</span><span class="cx"> from twistedcaldav.directory import calendaruserproxy
</span><span class="cx"> from twistedcaldav.directory.directory import DirectoryService
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><span class="cx"> from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
</span><ins>+from txdav.caldav.datastore.query.filter import Filter
</ins><span class="cx"> from txdav.common.datastore.sql_tables import schema, _BIND_MODE_OWN
</span><span class="cx"> from uuid import UUID
</span><span class="cx"> import os
</span><span class="lines">@@ -757,10 +757,10 @@
</span><span class="cx">                           name=&quot;VCALENDAR&quot;,
</span><span class="cx">                        )
</span><span class="cx">                   )
</span><del>-        filter = calendarqueryfilter.Filter(filter)
</del><ins>+        filter = Filter(filter)
</ins><span class="cx">         filter.settimezone(None)
</span><span class="cx"> 
</span><del>-        matches = yield calendar._index.indexedSearch(filter, useruid=uid, fbtype=False)
</del><ins>+        matches = yield calendar.search(filter, useruid=uid, fbtype=False)
</ins><span class="cx">         if matches is None:
</span><span class="cx">             returnValue(None)
</span><span class="cx">         for name, _ignore_uid, _ignore_type in matches:
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingcalendarservertoolspurgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/purge.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/purge.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/purge.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -30,10 +30,9 @@
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav import caldavxml
</span><del>-from twistedcaldav.caldavxml import TimeRange
</del><span class="cx"> from twistedcaldav.directory.directory import DirectoryRecord
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><span class="cx"> 
</span><ins>+from txdav.caldav.datastore.query.filter import Filter
</ins><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -817,13 +816,13 @@
</span><span class="cx">         query_filter = caldavxml.Filter(
</span><span class="cx">               caldavxml.ComponentFilter(
</span><span class="cx">                   caldavxml.ComponentFilter(
</span><del>-                      TimeRange(start=whenString,),
</del><ins>+                      caldavxml.TimeRange(start=whenString,),
</ins><span class="cx">                       name=(&quot;VEVENT&quot;,),
</span><span class="cx">                   ),
</span><span class="cx">                   name=&quot;VCALENDAR&quot;,
</span><span class="cx">                )
</span><span class="cx">           )
</span><del>-        query_filter = calendarqueryfilter.Filter(query_filter)
</del><ins>+        query_filter = Filter(query_filter)
</ins><span class="cx"> 
</span><span class="cx">         count = 0
</span><span class="cx">         txn = self.store.newTransaction()
</span><span class="lines">@@ -844,7 +843,7 @@
</span><span class="cx">                     childNames.append(childName)
</span><span class="cx">             else:
</span><span class="cx">                 # events matching filter
</span><del>-                for childName, _ignore_childUid, _ignore_childType in (yield calendar._index.indexedSearch(query_filter)):
</del><ins>+                for childName, _ignore_childUid, _ignore_childType in (yield calendar.search(query_filter)):
</ins><span class="cx">                     childNames.append(childName)
</span><span class="cx">             yield txn.commit()
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingcalendarservertoolstestcalverifyaccountsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/calverify/accounts.xml (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/calverify/accounts.xml        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/calverify/accounts.xml        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -47,4 +47,11 @@
</span><span class="cx">     &lt;name&gt;Example User4&lt;/name&gt;
</span><span class="cx">     &lt;email-address&gt;example4@example.com&lt;/email-address&gt;
</span><span class="cx">   &lt;/user&gt;
</span><ins>+  &lt;user&gt;
+    &lt;uid&gt;home1&lt;/uid&gt;
+    &lt;guid&gt;home1&lt;/guid&gt;
+    &lt;password&gt;home1&lt;/password&gt;
+    &lt;name&gt;Home 1&lt;/name&gt;
+    &lt;email-address&gt;home1@example.com&lt;/email-address&gt;
+  &lt;/user&gt;
</ins><span class="cx"> &lt;/accounts&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingcalendarservertoolstestpurgeaccountsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/purge/accounts.xml (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/purge/accounts.xml        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/purge/accounts.xml        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -26,4 +26,25 @@
</span><span class="cx">     &lt;name&gt;Example User&lt;/name&gt;
</span><span class="cx">     &lt;email-address&gt;example@example.com&lt;/email-address&gt;
</span><span class="cx">   &lt;/user&gt;
</span><ins>+  &lt;user&gt;
+    &lt;uid&gt;example2&lt;/uid&gt;
+    &lt;guid&gt;37DB0C90-4DB1-4932-BC69-3DAB66F374F5&lt;/guid&gt;
+    &lt;password&gt;example2&lt;/password&gt;
+    &lt;name&gt;Example User 2&lt;/name&gt;
+    &lt;email-address&gt;example2@example.com&lt;/email-address&gt;
+  &lt;/user&gt;
+  &lt;user&gt;
+    &lt;uid&gt;home1&lt;/uid&gt;
+    &lt;guid&gt;home1&lt;/guid&gt;
+    &lt;password&gt;home1&lt;/password&gt;
+    &lt;name&gt;Home 1&lt;/name&gt;
+    &lt;email-address&gt;home1@example.com&lt;/email-address&gt;
+  &lt;/user&gt;
+  &lt;user&gt;
+    &lt;uid&gt;home2&lt;/uid&gt;
+    &lt;guid&gt;home2&lt;/guid&gt;
+    &lt;password&gt;home2&lt;/password&gt;
+    &lt;name&gt;Home 2&lt;/name&gt;
+    &lt;email-address&gt;home2@example.com&lt;/email-address&gt;
+  &lt;/user&gt;
</ins><span class="cx"> &lt;/accounts&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingcalendarservertoolstesttest_exportpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/test_export.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/test_export.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/test_export.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -277,7 +277,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         yield populateCalendarsFrom(
</span><span class="cx">             {
</span><del>-                &quot;home1&quot;: {
</del><ins>+                &quot;user01&quot;: {
</ins><span class="cx">                     &quot;calendar1&quot;: {
</span><span class="cx">                         &quot;valentines-day.ics&quot;: (valentines, {})
</span><span class="cx">                     }
</span><span class="lines">@@ -291,7 +291,7 @@
</span><span class="cx"> 
</span><span class="cx">         io = StringIO()
</span><span class="cx">         yield exportToFile(
</span><del>-            [(yield self.txn().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+            [(yield self.txn().calendarHomeWithUID(&quot;user01&quot;))
</ins><span class="cx">               .calendarWithName(&quot;calendar1&quot;)], io
</span><span class="cx">         )
</span><span class="cx">         self.assertEquals(Component.fromString(io.getvalue()),
</span><span class="lines">@@ -306,7 +306,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         yield populateCalendarsFrom(
</span><span class="cx">             {
</span><del>-                &quot;home1&quot;: {
</del><ins>+                &quot;user01&quot;: {
</ins><span class="cx">                     &quot;calendar1&quot;: {
</span><span class="cx">                         &quot;valentines-day.ics&quot;: (valentines, {}),
</span><span class="cx">                         &quot;new-years-day.ics&quot;: (newYears, {})
</span><span class="lines">@@ -324,7 +324,7 @@
</span><span class="cx"> 
</span><span class="cx">         io = StringIO()
</span><span class="cx">         yield exportToFile(
</span><del>-            [(yield self.txn().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+            [(yield self.txn().calendarHomeWithUID(&quot;user01&quot;))
</ins><span class="cx">               .calendarWithName(&quot;calendar1&quot;)], io
</span><span class="cx">         )
</span><span class="cx">         self.assertEquals(Component.fromString(io.getvalue()),
</span><span class="lines">@@ -342,7 +342,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         yield populateCalendarsFrom(
</span><span class="cx">             {
</span><del>-                &quot;home1&quot;: {
</del><ins>+                &quot;user01&quot;: {
</ins><span class="cx">                     &quot;calendar1&quot;: {
</span><span class="cx">                         &quot;1.ics&quot;: (one, {}), # EST
</span><span class="cx">                         &quot;2.ics&quot;: (another, {}), # EST
</span><span class="lines">@@ -354,7 +354,7 @@
</span><span class="cx"> 
</span><span class="cx">         io = StringIO()
</span><span class="cx">         yield exportToFile(
</span><del>-            [(yield self.txn().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+            [(yield self.txn().calendarHomeWithUID(&quot;user01&quot;))
</ins><span class="cx">               .calendarWithName(&quot;calendar1&quot;)], io
</span><span class="cx">         )
</span><span class="cx">         result = Component.fromString(io.getvalue())
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingcalendarservertoolstesttest_purgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/test_purge.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/test_purge.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/calendarserver/tools/test/test_purge.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -806,16 +806,6 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def setUp(self):
</span><del>-        self.patch(config.DirectoryService.params, &quot;xmlFile&quot;,
-            os.path.join(
-                os.path.dirname(__file__), &quot;purge&quot;, &quot;accounts.xml&quot;
-            )
-        )
-        self.patch(config.ResourceService.params, &quot;xmlFile&quot;,
-            os.path.join(
-                os.path.dirname(__file__), &quot;purge&quot;, &quot;resources.xml&quot;
-            )
-        )
</del><span class="cx">         yield super(PurgePrincipalTests, self).setUp()
</span><span class="cx"> 
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><span class="lines">@@ -850,6 +840,20 @@
</span><span class="cx">         (yield txn.commit())
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def configure(self):
+        super(PurgePrincipalTests, self).configure()
+        self.patch(config.DirectoryService.params, &quot;xmlFile&quot;,
+            os.path.join(
+                os.path.dirname(__file__), &quot;purge&quot;, &quot;accounts.xml&quot;
+            )
+        )
+        self.patch(config.ResourceService.params, &quot;xmlFile&quot;,
+            os.path.join(
+                os.path.dirname(__file__), &quot;purge&quot;, &quot;resources.xml&quot;
+            )
+        )
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def populate(self):
</span><span class="cx">         yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtwextenterprisedalsyntaxpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twext/enterprise/dal/syntax.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twext/enterprise/dal/syntax.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twext/enterprise/dal/syntax.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -347,29 +347,63 @@
</span><span class="cx">         @param other: a constant parameter or sub-select
</span><span class="cx">         @type other: L{Parameter} or L{Select}
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+        return self._commonIn('in', other)
+
+
+    def NotIn(self, other):
+        &quot;&quot;&quot;
+        We support two forms of the SQL &quot;NOT IN&quot; 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}
+        &quot;&quot;&quot;
+        return self._commonIn('not in', other)
+
+
+    def _commonIn(self, op, other):
+        &quot;&quot;&quot;
+        We support two forms of the SQL &quot;NOT IN&quot; 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}
+        &quot;&quot;&quot;
</ins><span class="cx">         if isinstance(other, Parameter):
</span><span class="cx">             if other.count is None:
</span><del>-                raise DALError(&quot;IN expression needs an explicit count of parameters&quot;)
-            return CompoundComparison(self, 'in', Constant(other))
</del><ins>+                raise DALError(&quot;{} expression needs an explicit count of parameters&quot;.format(op.upper()))
+            return CompoundComparison(self, op, Constant(other))
</ins><span class="cx">         else:
</span><span class="cx">             # Can't be Select.__contains__ because __contains__ gets __nonzero__
</span><del>-            # called on its result by the 'in' syntax.
-            return CompoundComparison(self, 'in', other)
</del><ins>+            # called on its result by the 'not in' syntax.
+            return CompoundComparison(self, op, other)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def StartsWith(self, other):
</span><span class="cx">         return CompoundComparison(self, &quot;like&quot;, CompoundComparison(Constant(other), '||', Constant('%')))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def NotStartsWith(self, other):
+        return CompoundComparison(self, &quot;not like&quot;, CompoundComparison(Constant(other), '||', Constant('%')))
+
+
</ins><span class="cx">     def EndsWith(self, other):
</span><span class="cx">         return CompoundComparison(self, &quot;like&quot;, CompoundComparison(Constant('%'), '||', Constant(other)))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def NotEndsWith(self, other):
+        return CompoundComparison(self, &quot;not like&quot;, CompoundComparison(Constant('%'), '||', Constant(other)))
+
+
</ins><span class="cx">     def Contains(self, other):
</span><span class="cx">         return CompoundComparison(self, &quot;like&quot;, CompoundComparison(Constant('%'), '||', CompoundComparison(Constant(other), '||', Constant('%'))))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def NotContains(self, other):
+        return CompoundComparison(self, &quot;not like&quot;, CompoundComparison(Constant('%'), '||', CompoundComparison(Constant(other), '||', Constant('%'))))
</ins><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class FunctionInvocation(ExpressionSyntax):
</span><span class="cx">     def __init__(self, function, *args):
</span><span class="cx">         self.function = function
</span><span class="lines">@@ -555,7 +589,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a L{Join}, representing a join between two tables.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        if on is None:
</del><ins>+        if on is None and not type:
</ins><span class="cx">             type = 'cross'
</span><span class="cx">         return Join(self, type, otherTableSyntax, on)
</span><span class="cx"> 
</span><span class="lines">@@ -697,13 +731,16 @@
</span><span class="cx">     def subSQL(self, queryGenerator, allTables):
</span><span class="cx">         stmt = SQLFragment()
</span><span class="cx">         stmt.append(self.leftSide.subSQL(queryGenerator, allTables))
</span><del>-        stmt.text += ' '
-        if self.type:
-            stmt.text += self.type
</del><ins>+        if self.type == ',':
+            stmt.text += ', '
+        else:
</ins><span class="cx">             stmt.text += ' '
</span><del>-        stmt.text += 'join '
</del><ins>+            if self.type:
+                stmt.text += self.type
+                stmt.text += ' '
+            stmt.text += 'join '
</ins><span class="cx">         stmt.append(self.rightSide.subSQL(queryGenerator, allTables))
</span><del>-        if self.type != 'cross':
</del><ins>+        if self.type not in ('cross', ','):
</ins><span class="cx">             stmt.text += ' on '
</span><span class="cx">             stmt.append(self.on.subSQL(queryGenerator, allTables))
</span><span class="cx">         return stmt
</span><span class="lines">@@ -882,6 +919,26 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class Not(Comparison):
+    &quot;&quot;&quot;
+    A L{NotColumn} is a logical NOT of an expression.
+    &quot;&quot;&quot;
+    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 += &quot;not &quot;
+        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
+
+
+
</ins><span class="cx"> class NullComparison(Comparison):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     A L{NullComparison} is a comparison of a column or expression with None.
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtwextenterprisedaltesttest_sqlsyntaxpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twext/enterprise/dal/test/test_sqlsyntax.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twext/enterprise/dal/test/test_sqlsyntax.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twext/enterprise/dal/test/test_sqlsyntax.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -26,7 +26,7 @@
</span><span class="cx">     Savepoint, RollbackToSavepoint, ReleaseSavepoint, SavepointAction,
</span><span class="cx">     Union, Intersect, Except, SetExpression, DALError,
</span><span class="cx">     ResultAliasSyntax, Count, QueryGenerator, ALL_COLUMNS,
</span><del>-    DatabaseLock, DatabaseUnlock)
</del><ins>+    DatabaseLock, DatabaseUnlock, Not)
</ins><span class="cx"> from twext.enterprise.dal.syntax import FixedPlaceholder, NumericPlaceholder
</span><span class="cx"> from twext.enterprise.dal.syntax import Function
</span><span class="cx"> from twext.enterprise.dal.syntax import SchemaSyntax
</span><span class="lines">@@ -362,6 +362,17 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def test_commaJoin(self):
+        &quot;&quot;&quot;
+        A join with no clause specified will generate a cross join.  (This is an
+        explicit synonym for an implicit join: i.e. 'select * from FOO, BAR'.)
+        &quot;&quot;&quot;
+        self.assertEquals(
+            Select(From=self.schema.FOO.join(self.schema.BOZ, type=&quot;,&quot;)).toSQL(),
+            SQLFragment(&quot;select * from FOO, BOZ&quot;)
+        )
+
+
</ins><span class="cx">     def test_crossJoin(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         A join with no clause specified will generate a cross join.  (This is an
</span><span class="lines">@@ -963,6 +974,73 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def test_not(self):
+        &quot;&quot;&quot;
+        Test for the string starts with comparison.
+        (Note that this should be updated to use different techniques
+        as necessary in different databases.)
+        &quot;&quot;&quot;
+        self.assertEquals(
+            Select([
+                self.schema.TEXTUAL.MYTEXT],
+                From=self.schema.TEXTUAL,
+                Where=Not(self.schema.TEXTUAL.MYTEXT.StartsWith(&quot;test&quot;)),
+            ).toSQL(),
+            SQLFragment(
+                &quot;select MYTEXT from TEXTUAL where not MYTEXT like (? || ?)&quot;,
+                [&quot;test&quot;, &quot;%&quot;]
+            )
+        )
+
+        self.assertEquals(
+            Select([
+                self.schema.TEXTUAL.MYTEXT],
+                From=self.schema.TEXTUAL,
+                Where=Not(self.schema.TEXTUAL.MYTEXT == &quot;test&quot;),
+            ).toSQL(),
+            SQLFragment(
+                &quot;select MYTEXT from TEXTUAL where not MYTEXT = ?&quot;,
+                [&quot;test&quot;]
+            )
+        )
+
+        self.assertEquals(
+            Select([
+                self.schema.TEXTUAL.MYTEXT],
+                From=self.schema.TEXTUAL,
+                Where=Not((self.schema.TEXTUAL.MYTEXT == &quot;test1&quot;).And(self.schema.TEXTUAL.MYTEXT != &quot;test2&quot;)),
+            ).toSQL(),
+            SQLFragment(
+                &quot;select MYTEXT from TEXTUAL where not (MYTEXT = ? and MYTEXT != ?)&quot;,
+                [&quot;test1&quot;, &quot;test2&quot;]
+            )
+        )
+
+        self.assertEquals(
+            Select([
+                self.schema.TEXTUAL.MYTEXT],
+                From=self.schema.TEXTUAL,
+                Where=Not((self.schema.TEXTUAL.MYTEXT == &quot;test1&quot;)).And(self.schema.TEXTUAL.MYTEXT != &quot;test2&quot;),
+            ).toSQL(),
+            SQLFragment(
+                &quot;select MYTEXT from TEXTUAL where not MYTEXT = ? and MYTEXT != ?&quot;,
+                [&quot;test1&quot;, &quot;test2&quot;]
+            )
+        )
+
+        self.assertEquals(
+            Select([
+                self.schema.TEXTUAL.MYTEXT],
+                From=self.schema.TEXTUAL,
+                Where=Not(self.schema.TEXTUAL.MYTEXT.StartsWith(&quot;foo&quot;).And(self.schema.TEXTUAL.MYTEXT.NotEndsWith(&quot;bar&quot;))),
+            ).toSQL(),
+            SQLFragment(
+                &quot;select MYTEXT from TEXTUAL where not (MYTEXT like (? || ?) and MYTEXT not like (? || ?))&quot;,
+                [&quot;foo&quot;, &quot;%&quot;, &quot;%&quot;, &quot;bar&quot;]
+            )
+        )
+
+
</ins><span class="cx">     def test_insert(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         L{Insert.toSQL} generates an 'insert' statement with all the relevant
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavdirectoryopendirectorybackerpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/opendirectorybacker.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/opendirectorybacker.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/opendirectorybacker.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -23,46 +23,45 @@
</span><span class="cx">     &quot;OpenDirectoryBackingService&quot;, &quot;VCardRecord&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><del>-import traceback
-import hashlib
</del><ins>+from calendarserver.platform.darwin.od import opendirectory, dsattributes, dsquery
</ins><span class="cx"> 
</span><del>-import os
-import sys
-import time
-
-from os import listdir
-from os.path import join, abspath
-from tempfile import mkstemp, gettempdir
-from random import random
-
-from pycalendar.vcard.n import N
-from pycalendar.vcard.adr import Adr
</del><span class="cx"> from pycalendar.datetime import DateTime
</span><ins>+from pycalendar.vcard.adr import Adr
+from pycalendar.vcard.n import N
</ins><span class="cx"> 
</span><del>-from socket import getfqdn
</del><span class="cx"> 
</span><del>-from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, returnValue, deferredGenerator, succeed
</del><span class="cx"> from twext.python.filepath import CachingFilePath as FilePath
</span><del>-from txdav.xml import element as davxml
-from txdav.xml.base import twisted_dav_namespace, dav_namespace, parse_date, twisted_private_namespace
</del><span class="cx"> from twext.web2.dav.resource import DAVPropertyMixIn
</span><span class="cx"> from twext.web2.dav.util import joinURL
</span><span class="cx"> from twext.web2.http_headers import MimeType, generateContentType, ETag
</span><span class="cx"> 
</span><ins>+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, returnValue, deferredGenerator, succeed
</ins><span class="cx"> 
</span><span class="cx"> from twistedcaldav import customxml, carddavxml
</span><del>-from twistedcaldav.customxml import calendarserver_namespace
</del><span class="cx"> from twistedcaldav.config import config
</span><ins>+from twistedcaldav.customxml import calendarserver_namespace
</ins><span class="cx"> from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
</span><span class="cx"> from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
</span><del>-from twistedcaldav.query import addressbookqueryfilter
</del><span class="cx"> from twistedcaldav.vcard import Component, Property, vCardProductID
</span><span class="cx"> 
</span><ins>+from txdav.carddav.datastore.query.filter import IsNotDefined, ParameterFilter, \
+    TextMatch
+from txdav.xml import element as davxml
+from txdav.xml.base import twisted_dav_namespace, dav_namespace, parse_date, twisted_private_namespace
+
+from os import listdir
+from os.path import join, abspath
+from random import random
+from socket import getfqdn
+from tempfile import mkstemp, gettempdir
</ins><span class="cx"> from xmlrpclib import datetime
</span><ins>+import hashlib
+import os
+import sys
+import time
+import traceback
</ins><span class="cx"> 
</span><del>-from calendarserver.platform.darwin.od import opendirectory, dsattributes, dsquery
-
</del><span class="cx"> class OpenDirectoryBackingService(DirectoryService):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Open Directory implementation of L{IDirectoryService}.
</span><span class="lines">@@ -830,11 +829,11 @@
</span><span class="cx">                 if not constant and not allAttrStrings:
</span><span class="cx">                     return (False, [], [])
</span><span class="cx"> 
</span><del>-                if propFilter.qualifier and isinstance(propFilter.qualifier, addressbookqueryfilter.IsNotDefined):
</del><ins>+                if propFilter.qualifier and isinstance(propFilter.qualifier, IsNotDefined):
</ins><span class="cx">                     return definedExpression(False, filterAllOf, propFilter.filter_name, constant, queryAttributes, allAttrStrings)
</span><span class="cx"> 
</span><del>-                paramFilterElements = [paramFilterElement for paramFilterElement in propFilter.filters if isinstance(paramFilterElement, addressbookqueryfilter.ParameterFilter)]
-                textMatchElements = [textMatchElement for textMatchElement in propFilter.filters if isinstance(textMatchElement, addressbookqueryfilter.TextMatch)]
</del><ins>+                paramFilterElements = [paramFilterElement for paramFilterElement in propFilter.filters if isinstance(paramFilterElement, ParameterFilter)]
+                textMatchElements = [textMatchElement for textMatchElement in propFilter.filters if isinstance(textMatchElement, TextMatch)]
</ins><span class="cx">                 propFilterAllOf = propFilter.propfilter_test == &quot;allof&quot;
</span><span class="cx"> 
</span><span class="cx">                 # handle parameter filter elements
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavdirectorytestaccountsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/test/accounts.xml (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/test/accounts.xml        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/test/accounts.xml        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -139,7 +139,7 @@
</span><span class="cx">       &lt;member type=&quot;users&quot;&gt;delegateviagroup&lt;/member&gt;
</span><span class="cx">     &lt;/members&gt;
</span><span class="cx">   &lt;/group&gt;
</span><del>-  &lt;user repeat=&quot;2&quot;&gt;
</del><ins>+  &lt;user repeat=&quot;100&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;user%02d&lt;/uid&gt;
</span><span class="cx">     &lt;guid&gt;user%02d&lt;/guid&gt;
</span><span class="cx">     &lt;password&gt;%02duser&lt;/password&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavdirectorytestaugmentsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/test/augments.xml (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/test/augments.xml        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/test/augments.xml        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -57,7 +57,7 @@
</span><span class="cx">     &lt;enable-calendar&gt;false&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;false&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><del>-  &lt;record repeat=&quot;2&quot;&gt;
</del><ins>+  &lt;record repeat=&quot;100&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;user%02d&lt;/uid&gt;
</span><span class="cx">     &lt;enable&gt;true&lt;/enable&gt;
</span><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavdirectorytesttest_directorypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/test/test_directory.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/test/test_directory.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/directory/test/test_directory.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -999,8 +999,9 @@
</span><span class="cx">         Exercise the default recordsMatchingTokens implementation
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         records = list((yield self.directoryService.recordsMatchingTokens([&quot;Use&quot;, &quot;01&quot;])))
</span><del>-        self.assertEquals(len(records), 1)
-        self.assertEquals(records[0].shortNames[0], &quot;user01&quot;)
</del><ins>+        self.assertNotEquals(len(records), 0)
+        shorts = [record.shortNames[0] for record in records]
+        self.assertTrue(&quot;user01&quot; in shorts)
</ins><span class="cx"> 
</span><span class="cx">         records = list((yield self.directoryService.recordsMatchingTokens(['&quot;quotey&quot;'],
</span><span class="cx">             context=self.directoryService.searchContext_attendee)))
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavmethodreport_addressbook_querypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_addressbook_query.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_addressbook_query.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_addressbook_query.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -27,7 +27,6 @@
</span><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from twext.web2 import responsecode
</span><del>-from txdav.xml import element as davxml
</del><span class="cx"> from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
</span><span class="cx"> from twext.web2.dav.method.report import NumberOfMatchesWithinLimits
</span><span class="cx"> from twext.web2.dav.util import joinURL
</span><span class="lines">@@ -37,9 +36,11 @@
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.carddavxml import carddav_namespace, NResults
</span><span class="cx"> from twistedcaldav.method import report_common
</span><del>-from twistedcaldav.query import addressbookqueryfilter
</del><span class="cx"> 
</span><del>-from txdav.common.icommondatastore import ConcurrentModification
</del><ins>+from txdav.carddav.datastore.query.filter import Filter
+from txdav.common.icommondatastore import ConcurrentModification, \
+    IndexedSearchException
+from txdav.xml import element as davxml
</ins><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="lines">@@ -62,7 +63,7 @@
</span><span class="cx">     responses = []
</span><span class="cx"> 
</span><span class="cx">     xmlfilter = addressbook_query.filter
</span><del>-    filter = addressbookqueryfilter.Filter(xmlfilter)
</del><ins>+    filter = Filter(xmlfilter)
</ins><span class="cx">     query = addressbook_query.props
</span><span class="cx">     limit = addressbook_query.limit
</span><span class="cx"> 
</span><span class="lines">@@ -209,7 +210,7 @@
</span><span class="cx">                                                         carddavxml.TextMatch.fromString(resource_name[:-4]),
</span><span class="cx">                                                         name=&quot;UID&quot;, # attributes
</span><span class="cx">                                                         ), ])
</span><del>-                            vCardFilter = addressbookqueryfilter.Filter(vCardFilter)
</del><ins>+                            vCardFilter = Filter(vCardFilter)
</ins><span class="cx"> 
</span><span class="cx">                             directoryAddressBookLock, limited[0] = (yield  directory.cacheVCardsForAddressBookQuery(vCardFilter, query, max_number_of_results[0]))
</span><span class="cx"> 
</span><span class="lines">@@ -230,11 +231,13 @@
</span><span class="cx"> 
</span><span class="cx">                     # Check for disabled access
</span><span class="cx">                     if filteredaces is not None:
</span><del>-                        # See whether the filter is valid for an index only query
-                        index_query_ok = addrresource.index().searchValid(filter)
-
-                        # Get list of children that match the search and have read access
-                        names = [name for name, ignore_uid in (yield addrresource.index().search(filter))] #@UnusedVariable
</del><ins>+                        index_query_ok = True
+                        try:
+                            # Get list of children that match the search and have read access
+                            names = [name for name, ignore_uid in (yield addrresource.search(filter))] #@UnusedVariable
+                        except IndexedSearchException:
+                            names = yield addrresource.listChildren()
+                            index_query_ok = False
</ins><span class="cx">                         if not names:
</span><span class="cx">                             return
</span><span class="cx"> 
</span><span class="lines">@@ -277,7 +280,7 @@
</span><span class="cx">                                                     carddavxml.TextMatch.fromString(resource_name[:-4]),
</span><span class="cx">                                                     name=&quot;UID&quot;, # attributes
</span><span class="cx">                                                     ), ])
</span><del>-                        vCardFilter = addressbookqueryfilter.Filter(vCardFilter)
</del><ins>+                        vCardFilter = Filter(vCardFilter)
</ins><span class="cx"> 
</span><span class="cx">                         yield  maybeDeferred(queryDirectoryBackedAddressBook, parent, vCardFilter)
</span><span class="cx">                         handled = True
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavmethodreport_calendar_querypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_calendar_query.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_calendar_query.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_calendar_query.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -20,8 +20,7 @@
</span><span class="cx"> 
</span><span class="cx"> __all__ = [&quot;report_urn_ietf_params_xml_ns_caldav_calendar_query&quot;]
</span><span class="cx"> 
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue, \
-    maybeDeferred
</del><ins>+from twisted.internet.defer import inlineCallbacks, returnValue
</ins><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from twext.web2 import responsecode
</span><span class="lines">@@ -39,8 +38,8 @@
</span><span class="cx">     ConcurrentModification
</span><span class="cx"> from twistedcaldav.instance import TooManyInstancesError
</span><span class="cx"> from twistedcaldav.method import report_common
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><span class="cx"> 
</span><ins>+from txdav.caldav.datastore.query.filter import Filter
</ins><span class="cx"> from txdav.caldav.icalendarstore import TimeRangeLowerLimit, TimeRangeUpperLimit
</span><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> 
</span><span class="lines">@@ -66,7 +65,7 @@
</span><span class="cx">     responses = []
</span><span class="cx"> 
</span><span class="cx">     xmlfilter = calendar_query.filter
</span><del>-    filter = calendarqueryfilter.Filter(xmlfilter)
</del><ins>+    filter = Filter(xmlfilter)
</ins><span class="cx">     props = calendar_query.props
</span><span class="cx"> 
</span><span class="cx">     assert props is not None
</span><span class="lines">@@ -190,13 +189,11 @@
</span><span class="cx">             if filteredaces is not None:
</span><span class="cx">                 index_query_ok = True
</span><span class="cx">                 try:
</span><del>-                    # Get list of children that match the search and have read
-                    # access
-                    records = yield maybeDeferred(calresource.index().indexedSearch, filter)
</del><ins>+                    # Get list of children that match the search and have read access
+                    names = [name for name, ignore_uid, ignore_type in (yield calresource.search(filter))]
</ins><span class="cx">                 except IndexedSearchException:
</span><del>-                    records = yield maybeDeferred(calresource.index().bruteForceSearch)
</del><ins>+                    names = yield calresource.listChildren()
</ins><span class="cx">                     index_query_ok = False
</span><del>-                names = [name for name, ignore_uid, ignore_type in records]
</del><span class="cx"> 
</span><span class="cx">                 if not names:
</span><span class="cx">                     returnValue(True)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavmethodreport_commonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_common.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_common.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_common.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -36,17 +36,16 @@
</span><span class="cx"> except ImportError:
</span><span class="cx">     from md5 import new as md5
</span><span class="cx"> 
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
</del><ins>+from twisted.internet.defer import inlineCallbacks, returnValue
</ins><span class="cx"> from twisted.python.failure import Failure
</span><span class="cx"> from twext.web2 import responsecode
</span><span class="cx"> 
</span><del>-from txdav.xml import element
</del><span class="cx"> from twext.web2.dav.http import statusForFailure
</span><span class="cx"> from twext.web2.dav.method.propfind import propertyName
</span><span class="cx"> from twext.web2.dav.method.report import NumberOfMatchesWithinLimits
</span><span class="cx"> from twext.web2.dav.method.report import max_number_of_matches
</span><span class="cx"> from twext.web2.dav.resource import AccessDeniedError
</span><del>-from twext.web2.http import HTTPError
</del><ins>+from twext.web2.http import HTTPError, StatusResponse
</ins><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> 
</span><span class="lines">@@ -65,9 +64,9 @@
</span><span class="cx"> from twistedcaldav.instance import InstanceList
</span><span class="cx"> from twistedcaldav.memcacher import Memcacher
</span><span class="cx"> 
</span><del>-from twistedcaldav.query import calendarqueryfilter
-
</del><ins>+from txdav.caldav.datastore.query.filter import Filter
</ins><span class="cx"> from txdav.common.icommondatastore import IndexedSearchException
</span><ins>+from txdav.xml import element
</ins><span class="cx"> 
</span><span class="cx"> from pycalendar.duration import Duration
</span><span class="cx"> from pycalendar.datetime import DateTime
</span><span class="lines">@@ -583,17 +582,18 @@
</span><span class="cx">                           name=&quot;VCALENDAR&quot;,
</span><span class="cx">                        )
</span><span class="cx">                   )
</span><del>-        filter = calendarqueryfilter.Filter(filter)
</del><ins>+        filter = Filter(filter)
</ins><span class="cx">         tzinfo = filter.settimezone(tz)
</span><span class="cx"> 
</span><span class="cx">         try:
</span><del>-            resources = yield maybeDeferred(calresource.index().indexedSearch,
-                filter, useruid=useruid, fbtype=True
-            )
</del><ins>+            resources = yield calresource.search(filter, useruid=useruid, fbtype=True)
</ins><span class="cx">             if caching:
</span><span class="cx">                 yield FBCacheEntry.makeCacheEntry(calresource, useruid, cache_timerange, resources)
</span><span class="cx">         except IndexedSearchException:
</span><del>-            resources = yield maybeDeferred(calresource.index().bruteForceSearch)
</del><ins>+            raise HTTPError(StatusResponse(
+                responsecode.INTERNAL_SERVER_ERROR,
+                &quot;Failed freebusy query&quot;
+            ))
</ins><span class="cx"> 
</span><span class="cx">     else:
</span><span class="cx">         # Log extended item
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavmethodreport_multiget_commonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_multiget_common.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_multiget_common.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/method/report_multiget_common.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -24,8 +24,6 @@
</span><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from twext.web2 import responsecode
</span><del>-from txdav.xml import element as davxml
-from txdav.xml.base import dav_namespace
</del><span class="cx"> from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
</span><span class="cx"> from twext.web2.dav.resource import AccessDeniedError
</span><span class="cx"> from twext.web2.http import HTTPError, StatusResponse
</span><span class="lines">@@ -37,11 +35,14 @@
</span><span class="cx"> from twistedcaldav.carddavxml import carddav_namespace
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.method import report_common
</span><del>-from txdav.common.icommondatastore import ConcurrentModification
</del><span class="cx"> from twistedcaldav.method.report_common import COLLECTION_TYPE_CALENDAR, \
</span><span class="cx">     COLLECTION_TYPE_ADDRESSBOOK
</span><del>-from twistedcaldav.query import addressbookqueryfilter
</del><span class="cx"> 
</span><ins>+from txdav.carddav.datastore.query.filter import Filter
+from txdav.common.icommondatastore import ConcurrentModification
+from txdav.xml import element as davxml
+from txdav.xml.base import dav_namespace
+
</ins><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><span class="lines">@@ -265,7 +266,7 @@
</span><span class="cx">                     returnValue(None)
</span><span class="cx"> 
</span><span class="cx">                 addressBookFilter = carddavxml.Filter(*vCardFilters)
</span><del>-                addressBookFilter = addressbookqueryfilter.Filter(addressBookFilter)
</del><ins>+                addressBookFilter = Filter(addressBookFilter)
</ins><span class="cx">                 if self.directory.cacheQuery:
</span><span class="cx">                     # add vcards to directory address book and run &quot;normal case&quot; below
</span><span class="cx">                     limit = config.DirectoryAddressBook.MaxQueryResults
</span><span class="lines">@@ -333,11 +334,11 @@
</span><span class="cx">                     parent = (yield child.locateParent(request, resource_uri))
</span><span class="cx"> 
</span><span class="cx">                     if collection_type == COLLECTION_TYPE_CALENDAR:
</span><del>-                        if not parent.isCalendarCollection() or not (yield parent.index().resourceExists(name)):
</del><ins>+                        if not parent.isCalendarCollection() or not (yield parent.resourceExists(name)):
</ins><span class="cx">                             responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
</span><span class="cx">                             continue
</span><span class="cx">                     elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
</span><del>-                        if not parent.isAddressBookCollection() or not (yield parent.index().resourceExists(name)):
</del><ins>+                        if not parent.isAddressBookCollection() or not (yield parent.resourceExists(name)):
</ins><span class="cx">                             responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
</span><span class="cx">                             continue
</span><span class="cx"> 
</span><span class="lines">@@ -367,11 +368,11 @@
</span><span class="cx">                     parent = (yield self.locateParent(request, resource_uri))
</span><span class="cx"> 
</span><span class="cx">                     if collection_type == COLLECTION_TYPE_CALENDAR:
</span><del>-                        if not parent.isPseudoCalendarCollection() or not (yield parent.index().resourceExists(name)):
</del><ins>+                        if not parent.isPseudoCalendarCollection() or not (yield parent.resourceExists(name)):
</ins><span class="cx">                             responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
</span><span class="cx">                             continue
</span><span class="cx">                     elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
</span><del>-                        if not parent.isAddressBookCollection() or not (yield parent.index().resourceExists(name)):
</del><ins>+                        if not parent.isAddressBookCollection() or not (yield parent.resourceExists(name)):
</ins><span class="cx">                             responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
</span><span class="cx">                             continue
</span><span class="cx">                     child = self
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavstorebridgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/storebridge.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/storebridge.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/storebridge.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -286,13 +286,6 @@
</span><span class="cx">         return self._parentResource
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def index(self):
-        &quot;&quot;&quot;
-        Retrieve the new-style index wrapper.
-        &quot;&quot;&quot;
-        return self._newStoreObject.retrieveOldIndex()
-
-
</del><span class="cx">     def exists(self):
</span><span class="cx">         # FIXME: tests
</span><span class="cx">         return self._newStoreObject is not None
</span><span class="lines">@@ -347,6 +340,18 @@
</span><span class="cx">         return self._newStoreObject.countObjectResources()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def resourceExists(self, name):
+        &quot;&quot;&quot;
+        Indicate whether a resource with the specified name exists.
+
+        @return: C{True} if it exists
+        @rtype: C{bool}
+        &quot;&quot;&quot;
+        allNames = yield self._newStoreObject.listObjectResources()
+        returnValue(name in allNames)
+
+
</ins><span class="cx">     def name(self):
</span><span class="cx">         return self._name
</span><span class="cx"> 
</span><span class="lines">@@ -970,6 +975,10 @@
</span><span class="cx">                     )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def search(self, filter, **kwargs):
+        return self._newStoreObject.search(filter, **kwargs)
+
+
</ins><span class="cx">     def notifierID(self):
</span><span class="cx">         return &quot;%s/%s&quot; % self._newStoreObject.notifierID()
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavtesttest_calendarquerypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/test/test_calendarquery.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/test/test_calendarquery.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/test/test_calendarquery.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -27,7 +27,6 @@
</span><span class="cx"> from twistedcaldav import caldavxml
</span><span class="cx"> from twistedcaldav import ical
</span><span class="cx"> 
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.test.util import StoreTestCase, SimpleStoreRequest
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="lines">@@ -36,6 +35,7 @@
</span><span class="cx"> from twistedcaldav.ical import Component
</span><span class="cx"> from txdav.caldav.icalendarstore import ComponentUpdateState
</span><span class="cx"> from twistedcaldav.directory.directory import DirectoryService
</span><ins>+from txdav.caldav.datastore.query.filter import TimeRange
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><span class="lines">@@ -167,7 +167,7 @@
</span><span class="cx">                             cal = property.calendar()
</span><span class="cx">                             instances = cal.expandTimeRanges(query_timerange.end)
</span><span class="cx">                             vevents = [x for x in cal.subcomponents() if x.name() == &quot;VEVENT&quot;]
</span><del>-                            if not calendarqueryfilter.TimeRange(query_timerange).matchinstance(vevents[0], instances):
</del><ins>+                            if not TimeRange(query_timerange).matchinstance(vevents[0], instances):
</ins><span class="cx">                                 self.fail(&quot;REPORT property %r returned calendar %s outside of request time range %r&quot;
</span><span class="cx">                                           % (property, property.calendar, query_timerange))
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavtesttest_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/test/test_sharing.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/test/test_sharing.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/test/test_sharing.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -30,6 +30,7 @@
</span><span class="cx"> from twistedcaldav.test.test_cache import StubResponseCacheResource
</span><span class="cx"> from twistedcaldav.test.util import norequest, StoreTestCase, SimpleStoreRequest
</span><span class="cx"> 
</span><ins>+from txdav.caldav.datastore.test.util import buildDirectory
</ins><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_MODE_DIRECT
</span><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> from txdav.xml.parser import WebDAVDocument
</span><span class="lines">@@ -738,6 +739,8 @@
</span><span class="cx">         home is at /.  Return the name of the newly shared calendar in the
</span><span class="cx">         sharee's home.
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+
+        self._sqlCalendarStore._directoryService = buildDirectory(homes=(&quot;wiki-testing&quot;,))
</ins><span class="cx">         wcreate = self._sqlCalendarStore.newTransaction(&quot;create wiki&quot;)
</span><span class="cx">         yield wcreate.calendarHomeWithUID(&quot;wiki-testing&quot;, create=True)
</span><span class="cx">         yield wcreate.commit()
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtwistedcaldavtesttest_xmlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/test/test_xml.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/test/test_xml.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/test/test_xml.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -17,12 +17,15 @@
</span><span class="cx"> import os
</span><span class="cx"> 
</span><span class="cx"> from twisted.trial.unittest import SkipTest
</span><ins>+
</ins><span class="cx"> from twistedcaldav.ical import Component
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><span class="cx"> import twistedcaldav.test.util
</span><span class="cx"> from twistedcaldav.caldavxml import ComponentFilter, PropertyFilter, TextMatch, \
</span><span class="cx">     Filter, TimeRange
</span><span class="cx"> 
</span><ins>+from txdav.caldav.datastore.query.filter import Filter as storeFilter
+from txdav.caldav.datastore.query.filter import ComponentFilter as storeComponentFilter
+
</ins><span class="cx"> class XML (twistedcaldav.test.util.TestCase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     XML tests
</span><span class="lines">@@ -46,7 +49,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 no = &quot;&quot;
</span><span class="cx"> 
</span><del>-            if has != calendarqueryfilter.ComponentFilter(
</del><ins>+            if has != storeComponentFilter(
</ins><span class="cx">                 ComponentFilter(
</span><span class="cx">                     ComponentFilter(
</span><span class="cx">                         name=component_name
</span><span class="lines">@@ -70,7 +73,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 no = &quot;&quot;
</span><span class="cx"> 
</span><del>-            if has != calendarqueryfilter.ComponentFilter(
</del><ins>+            if has != storeComponentFilter(
</ins><span class="cx">                 ComponentFilter(
</span><span class="cx">                     ComponentFilter(
</span><span class="cx">                         PropertyFilter(
</span><span class="lines">@@ -106,7 +109,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 no = &quot;&quot;
</span><span class="cx"> 
</span><del>-            if has != calendarqueryfilter.ComponentFilter(
</del><ins>+            if has != storeComponentFilter(
</ins><span class="cx">                 ComponentFilter(
</span><span class="cx">                     ComponentFilter(
</span><span class="cx">                         PropertyFilter(
</span><span class="lines">@@ -148,7 +151,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 no = &quot;&quot;
</span><span class="cx"> 
</span><del>-            if has != calendarqueryfilter.Filter(
</del><ins>+            if has != storeFilter(
</ins><span class="cx">                 Filter(
</span><span class="cx">                     ComponentFilter(
</span><span class="cx">                         ComponentFilter(
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoreindex_filepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/index_file.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/index_file.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/index_file.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -15,6 +15,7 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> CalDAV Index.
</span><span class="cx"> 
</span><span class="lines">@@ -43,12 +44,14 @@
</span><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> 
</span><ins>+from txdav.caldav.datastore.query.builder import buildExpression
+from txdav.caldav.datastore.query.filter import Filter
+from txdav.common.datastore.query.filegenerator import sqllitegenerator
</ins><span class="cx"> from txdav.common.icommondatastore import SyncTokenValidException, \
</span><span class="cx">     ReservationError, IndexedSearchException
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.dateops import pyCalendarTodatetime
</span><span class="cx"> from twistedcaldav.ical import Component
</span><del>-from twistedcaldav.query import calendarquery, calendarqueryfilter
</del><span class="cx"> from twistedcaldav.sql import AbstractSQLDatabase
</span><span class="cx"> from twistedcaldav.sql import db_prefix
</span><span class="cx"> from twistedcaldav.instance import InvalidOverriddenInstanceError
</span><span class="lines">@@ -320,7 +323,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Make sure we have a proper Filter element and get the partial SQL
</span><span class="cx">         # statement to use.
</span><del>-        if isinstance(filter, calendarqueryfilter.Filter):
</del><ins>+        if isinstance(filter, Filter):
</ins><span class="cx">             if fbtype:
</span><span class="cx">                 # Lookup the useruid - try the empty (default) one if needed
</span><span class="cx">                 dbuseruid = self._db_value_for_sql(
</span><span class="lines">@@ -330,7 +333,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 dbuseruid = &quot;&quot;
</span><span class="cx"> 
</span><del>-            qualifiers = calendarquery.sqlcalendarquery(filter, None, dbuseruid, fbtype)
</del><ins>+            qualifiers = sqlcalendarquery(filter, None, dbuseruid, fbtype)
</ins><span class="cx">             if qualifiers is not None:
</span><span class="cx">                 # Determine how far we need to extend the current expansion of
</span><span class="cx">                 # events. If we have an open-ended time-range we will expand one
</span><span class="lines">@@ -437,6 +440,24 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+def sqlcalendarquery(filter, calendarid=None, userid=None, freebusy=False):
+    &quot;&quot;&quot;
+    Convert the supplied calendar-query into a partial SQL statement.
+
+    @param filter: the L{Filter} for the calendar-query to convert.
+    @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
+            and the C{list} is the list of argument substitutions to use with the SQL API execute method.
+            Or return C{None} if it is not possible to create an SQL query to fully match the calendar-query.
+    &quot;&quot;&quot;
+    try:
+        expression = buildExpression(filter, sqllitegenerator.FIELDS)
+        sql = sqllitegenerator(expression, calendarid, userid, freebusy)
+        return sql.generate()
+    except ValueError:
+        return None
+
+
+
</ins><span class="cx"> class CalendarIndex (AbstractCalendarIndex):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Calendar index - abstract class for indexer that indexes calendar objects in a collection.
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequery__init__py"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/__init__.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/__init__.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/__init__.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+##
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequerybuilderpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/builder.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/builder.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/builder.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,227 @@
</span><ins>+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.dateops import floatoffset, pyCalendarTodatetime
+
+from txdav.caldav.datastore.query.filter import ComponentFilter, PropertyFilter, TextMatch, TimeRange
+from txdav.common.datastore.query import expression
+
+
+&quot;&quot;&quot;
+SQL statement generator from query expressions.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;buildExpression&quot;,
+]
+
+
+
+# SQL Index column (field) names
+
+def buildExpression(filter, fields):
+    &quot;&quot;&quot;
+    Convert the supplied calendar-query into an expression tree.
+
+    @param filter: the L{Filter} for the calendar-query to convert.
+    @return: a L{baseExpression} for the expression tree.
+    &quot;&quot;&quot;
+
+    # Lets assume we have a valid filter from the outset.
+
+    # Top-level filter contains exactly one comp-filter element
+    assert filter.child is not None
+    vcalfilter = filter.child
+    assert isinstance(vcalfilter, ComponentFilter)
+    assert vcalfilter.filter_name == &quot;VCALENDAR&quot;
+
+    if len(vcalfilter.filters) &gt; 0:
+        # Determine logical expression grouping
+        logical = expression.andExpression if vcalfilter.filter_test == &quot;allof&quot; else expression.orExpression
+
+        # Only comp-filters are handled
+        for _ignore in [x for x in vcalfilter.filters if not isinstance(x, ComponentFilter)]:
+            raise ValueError
+
+        return compfilterListExpression(vcalfilter.filters, fields, logical)
+    else:
+        return expression.allExpression()
+
+
+
+def compfilterListExpression(compfilters, fields, logical):
+    &quot;&quot;&quot;
+    Create an expression for a list of comp-filter elements.
+
+    @param compfilters: the C{list} of L{ComponentFilter} elements.
+    @return: a L{baseExpression} for the expression tree.
+    &quot;&quot;&quot;
+
+    if len(compfilters) == 1:
+        return compfilterExpression(compfilters[0], fields)
+    else:
+        return logical([compfilterExpression(c, fields) for c in compfilters])
+
+
+
+def compfilterExpression(compfilter, fields):
+    &quot;&quot;&quot;
+    Create an expression for a single comp-filter element.
+
+    @param compfilter: the L{ComponentFilter} element.
+    @return: a L{baseExpression} for the expression tree.
+    &quot;&quot;&quot;
+
+    # Handle is-not-defined case
+    if not compfilter.defined:
+        # Test for TYPE != &lt;&lt;component-type name&gt;&gt;
+        return expression.isnotExpression(fields[&quot;TYPE&quot;], compfilter.filter_name, True)
+
+    # Determine logical expression grouping
+    logical = expression.andExpression if compfilter.filter_test == &quot;allof&quot; else expression.orExpression
+
+    expressions = []
+    if isinstance(compfilter.filter_name, str):
+        expressions.append(expression.isExpression(fields[&quot;TYPE&quot;], compfilter.filter_name, True))
+    else:
+        expressions.append(expression.inExpression(fields[&quot;TYPE&quot;], compfilter.filter_name, True))
+
+    # Handle time-range
+    if compfilter.qualifier and isinstance(compfilter.qualifier, TimeRange):
+        start, end, startfloat, endfloat = getTimerangeArguments(compfilter.qualifier)
+        expressions.append(expression.timerangeExpression(start, end, startfloat, endfloat))
+
+    # Handle properties - we can only do UID right now
+    props = []
+    for p in [x for x in compfilter.filters if isinstance(x, PropertyFilter)]:
+        props.append(propfilterExpression(p, fields))
+    if len(props) &gt; 1:
+        propsExpression = logical(props)
+    elif len(props) == 1:
+        propsExpression = props[0]
+    else:
+        propsExpression = None
+
+    # Handle embedded components - we do not right now as our Index does not handle them
+    comps = []
+    for _ignore in [x for x in compfilter.filters if isinstance(x, ComponentFilter)]:
+        raise ValueError
+    if len(comps) &gt; 1:
+        compsExpression = logical(comps)
+    elif len(comps) == 1:
+        compsExpression = comps[0]
+    else:
+        compsExpression = None
+
+    # Now build compound expression
+    if ((propsExpression is not None) and (compsExpression is not None)):
+        expressions.append(logical([propsExpression, compsExpression]))
+    elif propsExpression is not None:
+        expressions.append(propsExpression)
+    elif compsExpression is not None:
+        expressions.append(compsExpression)
+
+    # Now build return expression
+    return expression.andExpression(expressions)
+
+
+
+def propfilterExpression(propfilter, fields):
+    &quot;&quot;&quot;
+    Create an expression for a single prop-filter element.
+
+    @param propfilter: the L{PropertyFilter} element.
+    @return: a L{baseExpression} for the expression tree.
+    &quot;&quot;&quot;
+
+    # Only handle UID right now
+    if propfilter.filter_name != &quot;UID&quot;:
+        raise ValueError
+
+    # Handle is-not-defined case
+    if not propfilter.defined:
+        # Test for &lt;&lt;field&gt;&gt; != &quot;*&quot;
+        return expression.isExpression(fields[&quot;UID&quot;], &quot;&quot;, True)
+
+    # Determine logical expression grouping
+    logical = expression.andExpression if propfilter.filter_test == &quot;allof&quot; else expression.orExpression
+
+    # Handle time-range - we cannot do this with our Index right now
+    if propfilter.qualifier and isinstance(propfilter.qualifier, TimeRange):
+        raise ValueError
+
+    # Handle text-match
+    tm = None
+    if propfilter.qualifier and isinstance(propfilter.qualifier, TextMatch):
+        if propfilter.qualifier.match_type == &quot;equals&quot;:
+            tm = expression.isnotExpression if propfilter.qualifier.negate else expression.isExpression
+        elif propfilter.qualifier.match_type == &quot;contains&quot;:
+            tm = expression.notcontainsExpression if propfilter.qualifier.negate else expression.containsExpression
+        elif propfilter.qualifier.match_type == &quot;starts-with&quot;:
+            tm = expression.notstartswithExpression if propfilter.qualifier.negate else expression.startswithExpression
+        elif propfilter.qualifier.match_type == &quot;ends-with&quot;:
+            tm = expression.notendswithExpression if propfilter.qualifier.negate else expression.endswithExpression
+        tm = tm(fields[propfilter.filter_name], propfilter.qualifier.text, propfilter.qualifier.caseless)
+
+    # Handle embedded parameters - we do not right now as our Index does not handle them
+    params = []
+    for _ignore in propfilter.filters:
+        raise ValueError
+    if len(params) &gt; 1:
+        paramsExpression = logical(params)
+    elif len(params) == 1:
+        paramsExpression = params[0]
+    else:
+        paramsExpression = None
+
+    # Now build return expression
+    if (tm is not None) and (paramsExpression is not None):
+        return logical([tm, paramsExpression])
+    elif tm is not None:
+        return tm
+    elif paramsExpression is not None:
+        return paramsExpression
+    else:
+        return None
+
+
+
+def getTimerangeArguments(timerange):
+    &quot;&quot;&quot;
+    Get start/end and floating start/end (adjusted for timezone offset) values from the
+    supplied time-range test.
+
+    @param timerange: the L{TimeRange} used in the query.
+    @return: C{tuple} of C{str} for start, end, startfloat, endfloat
+    &quot;&quot;&quot;
+
+    # Start/end in UTC
+    start = timerange.start
+    end = timerange.end
+
+    # Get timezone
+    tzinfo = timerange.tzinfo
+
+    # Now force to floating UTC
+    startfloat = floatoffset(start, tzinfo) if start else None
+    endfloat = floatoffset(end, tzinfo) if end else None
+
+    return (
+        pyCalendarTodatetime(start) if start else None,
+        pyCalendarTodatetime(end) if end else None,
+        pyCalendarTodatetime(startfloat) if startfloat else None,
+        pyCalendarTodatetime(endfloat) if endfloat else None,
+    )
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequeryfilterpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/filter.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/filter.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/filter.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,754 @@
</span><ins>+##
+# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+##
+
+&quot;&quot;&quot;
+Object model of CALDAV:filter element used in an addressbook-query.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;Filter&quot;,
+]
+
+from twext.python.log import Logger
+
+from twistedcaldav.caldavxml import caldav_namespace, CalDAVTimeZoneElement
+from twistedcaldav.dateops import timeRangesOverlap
+from twistedcaldav.ical import Component, Property
+
+from pycalendar.datetime import DateTime
+from pycalendar.timezone import Timezone
+
+log = Logger()
+
+
+class FilterBase(object):
+    &quot;&quot;&quot;
+    Determines which matching components are returned.
+    &quot;&quot;&quot;
+
+    def __init__(self, xml_element):
+        self.xmlelement = xml_element
+
+
+    def match(self, item, access=None):
+        raise NotImplementedError
+
+
+    def valid(self, level=0):
+        raise NotImplementedError
+
+
+
+class Filter(FilterBase):
+    &quot;&quot;&quot;
+    Determines which matching components are returned.
+    &quot;&quot;&quot;
+
+    def __init__(self, xml_element):
+
+        super(Filter, self).__init__(xml_element)
+
+        # One comp-filter element must be present
+        if len(xml_element.children) != 1 or xml_element.children[0].qname() != (caldav_namespace, &quot;comp-filter&quot;):
+            raise ValueError(&quot;Invalid CALDAV:filter element: %s&quot; % (xml_element,))
+
+        self.child = ComponentFilter(xml_element.children[0])
+
+
+    def match(self, component, access=None):
+        &quot;&quot;&quot;
+        Returns True if the given calendar component matches this filter, False
+        otherwise.
+        &quot;&quot;&quot;
+
+        # We only care about certain access restrictions.
+        if access not in (Component.ACCESS_CONFIDENTIAL, Component.ACCESS_RESTRICTED):
+            access = None
+
+        # We need to prepare ourselves for a time-range query by pre-calculating
+        # the set of instances up to the latest time-range limit. That way we can
+        # avoid having to do some form of recurrence expansion for each query sub-part.
+        maxend, isStartTime = self.getmaxtimerange()
+        if maxend:
+            if isStartTime:
+                if component.isRecurringUnbounded():
+                    # Unbounded recurrence is always within a start-only time-range
+                    instances = None
+                else:
+                    # Expand the instances up to infinity
+                    instances = component.expandTimeRanges(DateTime(2100, 1, 1, 0, 0, 0, tzid=Timezone(utc=True)), ignoreInvalidInstances=True)
+            else:
+                instances = component.expandTimeRanges(maxend, ignoreInvalidInstances=True)
+        else:
+            instances = None
+        self.child.setInstances(instances)
+
+        # &lt;filter&gt; contains exactly one &lt;comp-filter&gt;
+        return self.child.match(component, access)
+
+
+    def valid(self):
+        &quot;&quot;&quot;
+        Indicate whether this filter element's structure is valid wrt iCalendar
+        data object model.
+
+        @return: True if valid, False otherwise
+        &quot;&quot;&quot;
+
+        # Must have one child element for VCALENDAR
+        return self.child.valid(0)
+
+
+    def settimezone(self, tzelement):
+        &quot;&quot;&quot;
+        Set the default timezone to use with this query.
+        @param calendar: a L{Component} for the VCALENDAR containing the one
+            VTIMEZONE that we want
+        @return: the L{Timezone} derived from the VTIMEZONE or utc.
+        &quot;&quot;&quot;
+
+        if tzelement is None:
+            tz = None
+        elif isinstance(tzelement, CalDAVTimeZoneElement):
+            tz = tzelement.gettimezone()
+        elif isinstance(tzelement, Component):
+            tz = tzelement.gettimezone()
+        if tz is None:
+            tz = Timezone(utc=True)
+        self.child.settzinfo(tz)
+        return tz
+
+
+    def getmaxtimerange(self):
+        &quot;&quot;&quot;
+        Get the date farthest into the future in any time-range elements
+        &quot;&quot;&quot;
+
+        return self.child.getmaxtimerange(None, False)
+
+
+    def getmintimerange(self):
+        &quot;&quot;&quot;
+        Get the date farthest into the past in any time-range elements. That is either
+        the start date, or if start is not present, the end date.
+        &quot;&quot;&quot;
+
+        return self.child.getmintimerange(None, False)
+
+
+
+class FilterChildBase(FilterBase):
+    &quot;&quot;&quot;
+    CalDAV filter element.
+    &quot;&quot;&quot;
+
+    def __init__(self, xml_element):
+
+        super(FilterChildBase, self).__init__(xml_element)
+
+        qualifier = None
+        filters = []
+
+        for child in xml_element.children:
+            qname = child.qname()
+
+            if qname in (
+                (caldav_namespace, &quot;is-not-defined&quot;),
+                (caldav_namespace, &quot;time-range&quot;),
+                (caldav_namespace, &quot;text-match&quot;),
+            ):
+                if qualifier is not None:
+                    raise ValueError(&quot;Only one of CalDAV:time-range, CalDAV:text-match allowed&quot;)
+
+                if qname == (caldav_namespace, &quot;is-not-defined&quot;):
+                    qualifier = IsNotDefined(child)
+                elif qname == (caldav_namespace, &quot;time-range&quot;):
+                    qualifier = TimeRange(child)
+                elif qname == (caldav_namespace, &quot;text-match&quot;):
+                    qualifier = TextMatch(child)
+
+            elif qname == (caldav_namespace, &quot;comp-filter&quot;):
+                filters.append(ComponentFilter(child))
+            elif qname == (caldav_namespace, &quot;prop-filter&quot;):
+                filters.append(PropertyFilter(child))
+            elif qname == (caldav_namespace, &quot;param-filter&quot;):
+                filters.append(ParameterFilter(child))
+            else:
+                raise ValueError(&quot;Unknown child element: %s&quot; % (qname,))
+
+        if qualifier and isinstance(qualifier, IsNotDefined) and (len(filters) != 0):
+            raise ValueError(&quot;No other tests allowed when CalDAV:is-not-defined is present&quot;)
+
+        self.qualifier = qualifier
+        self.filters = filters
+        self.filter_name = xml_element.attributes[&quot;name&quot;]
+        if isinstance(self.filter_name, unicode):
+            self.filter_name = self.filter_name.encode(&quot;utf-8&quot;)
+        self.defined = not self.qualifier or not isinstance(qualifier, IsNotDefined)
+
+        filter_test = xml_element.attributes.get(&quot;test&quot;, &quot;allof&quot;)
+        if filter_test not in (&quot;anyof&quot;, &quot;allof&quot;):
+            raise ValueError(&quot;Test must be only one of anyof, allof&quot;)
+        self.filter_test = filter_test
+
+
+    def match(self, item, access=None):
+        &quot;&quot;&quot;
+        Returns True if the given calendar item (either a component, property or parameter value)
+        matches this filter, False otherwise.
+        &quot;&quot;&quot;
+
+        # Always return True for the is-not-defined case as the result of this will
+        # be negated by the caller
+        if not self.defined:
+            return True
+
+        if self.qualifier and not self.qualifier.match(item, access):
+            return False
+
+        if len(self.filters) &gt; 0:
+            allof = self.filter_test == &quot;allof&quot;
+            for filter in self.filters:
+                if allof != filter._match(item, access):
+                    return not allof
+            return allof
+        else:
+            return True
+
+
+
+class ComponentFilter (FilterChildBase):
+    &quot;&quot;&quot;
+    Limits a search to only the chosen component types.
+    &quot;&quot;&quot;
+
+    def match(self, item, access):
+        &quot;&quot;&quot;
+        Returns True if the given calendar item (which is a component)
+        matches this filter, False otherwise.
+        This specialization uses the instance matching option of the time-range filter
+        to minimize instance expansion.
+        &quot;&quot;&quot;
+
+        # Always return True for the is-not-defined case as the result of this will
+        # be negated by the caller
+        if not self.defined:
+            return True
+
+        if self.qualifier and not self.qualifier.matchinstance(item, self.instances):
+            return False
+
+        if len(self.filters) &gt; 0:
+            allof = self.filter_test == &quot;allof&quot;
+            for filter in self.filters:
+                if allof != filter._match(item, access):
+                    return not allof
+            return allof
+        else:
+            return True
+
+
+    def _match(self, component, access):
+        # At least one subcomponent must match (or is-not-defined is set)
+        for subcomponent in component.subcomponents():
+            # If access restrictions are in force, restrict matching to specific components only.
+            # In particular do not match VALARM.
+            if access and subcomponent.name() not in (&quot;VEVENT&quot;, &quot;VTODO&quot;, &quot;VJOURNAL&quot;, &quot;VFREEBUSY&quot;, &quot;VTIMEZONE&quot;,):
+                continue
+
+            # Try to match the component name
+            if isinstance(self.filter_name, str):
+                if subcomponent.name() != self.filter_name:
+                    continue
+            else:
+                if subcomponent.name() not in self.filter_name:
+                    continue
+            if self.match(subcomponent, access):
+                break
+        else:
+            return not self.defined
+        return self.defined
+
+
+    def setInstances(self, instances):
+        &quot;&quot;&quot;
+        Give the list of instances to each comp-filter element.
+        @param instances: the list of instances.
+        &quot;&quot;&quot;
+        self.instances = instances
+        for compfilter in [x for x in self.filters if isinstance(x, ComponentFilter)]:
+            compfilter.setInstances(instances)
+
+
+    def valid(self, level):
+        &quot;&quot;&quot;
+        Indicate whether this filter element's structure is valid wrt iCalendar
+        data object model.
+
+        @param level: the nesting level of this filter element, 0 being the top comp-filter.
+        @return:      True if valid, False otherwise
+        &quot;&quot;&quot;
+
+        # Check for time-range
+        timerange = self.qualifier and isinstance(self.qualifier, TimeRange)
+
+        if level == 0:
+            # Must have VCALENDAR at the top
+            if (self.filter_name != &quot;VCALENDAR&quot;) or timerange:
+                log.info(&quot;Top-level comp-filter must be VCALENDAR, instead: %s&quot; % (self.filter_name,))
+                return False
+        elif level == 1:
+            # Disallow VCALENDAR, VALARM, STANDARD, DAYLIGHT, AVAILABLE at the top, everything else is OK
+            if self.filter_name in (&quot;VCALENDAR&quot;, &quot;VALARM&quot;, &quot;STANDARD&quot;, &quot;DAYLIGHT&quot;, &quot;AVAILABLE&quot;):
+                log.info(&quot;comp-filter wrong component type: %s&quot; % (self.filter_name,))
+                return False
+
+            # time-range only on VEVENT, VTODO, VJOURNAL, VFREEBUSY, VAVAILABILITY
+            if timerange and self.filter_name not in (&quot;VEVENT&quot;, &quot;VTODO&quot;, &quot;VJOURNAL&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;):
+                log.info(&quot;time-range cannot be used with component %s&quot; % (self.filter_name,))
+                return False
+        elif level == 2:
+            # Disallow VCALENDAR, VTIMEZONE, VEVENT, VTODO, VJOURNAL, VFREEBUSY, VAVAILABILITY at the top, everything else is OK
+            if (self.filter_name in (&quot;VCALENDAR&quot;, &quot;VTIMEZONE&quot;, &quot;VEVENT&quot;, &quot;VTODO&quot;, &quot;VJOURNAL&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)):
+                log.info(&quot;comp-filter wrong sub-component type: %s&quot; % (self.filter_name,))
+                return False
+
+            # time-range only on VALARM, AVAILABLE
+            if timerange and self.filter_name not in (&quot;VALARM&quot;, &quot;AVAILABLE&quot;,):
+                log.info(&quot;time-range cannot be used with sub-component %s&quot; % (self.filter_name,))
+                return False
+        else:
+            # Disallow all standard iCal components anywhere else
+            if (self.filter_name in (&quot;VCALENDAR&quot;, &quot;VTIMEZONE&quot;, &quot;VEVENT&quot;, &quot;VTODO&quot;, &quot;VJOURNAL&quot;, &quot;VFREEBUSY&quot;, &quot;VALARM&quot;, &quot;STANDARD&quot;, &quot;DAYLIGHT&quot;, &quot;AVAILABLE&quot;)) or timerange:
+                log.info(&quot;comp-filter wrong standard component type: %s&quot; % (self.filter_name,))
+                return False
+
+        # Test each property
+        for propfilter in [x for x in self.filters if isinstance(x, PropertyFilter)]:
+            if not propfilter.valid():
+                return False
+
+        # Test each component
+        for compfilter in [x for x in self.filters if isinstance(x, ComponentFilter)]:
+            if not compfilter.valid(level + 1):
+                return False
+
+        # Test the time-range
+        if timerange:
+            if not self.qualifier.valid():
+                return False
+
+        return True
+
+
+    def settzinfo(self, tzinfo):
+        &quot;&quot;&quot;
+        Set the default timezone to use with this query.
+        @param tzinfo: a L{Timezone} to use.
+        &quot;&quot;&quot;
+
+        # Give tzinfo to any TimeRange we have
+        if isinstance(self.qualifier, TimeRange):
+            self.qualifier.settzinfo(tzinfo)
+
+        # Pass down to sub components/properties
+        for x in self.filters:
+            x.settzinfo(tzinfo)
+
+
+    def getmaxtimerange(self, currentMaximum, currentIsStartTime):
+        &quot;&quot;&quot;
+        Get the date farthest into the future in any time-range elements
+
+        @param currentMaximum: current future value to compare with
+        @type currentMaximum: L{DateTime}
+        &quot;&quot;&quot;
+
+        # Give tzinfo to any TimeRange we have
+        isStartTime = False
+        if isinstance(self.qualifier, TimeRange):
+            isStartTime = self.qualifier.end is None
+            compareWith = self.qualifier.start if isStartTime else self.qualifier.end
+            if currentMaximum is None or currentMaximum &lt; compareWith:
+                currentMaximum = compareWith
+                currentIsStartTime = isStartTime
+
+        # Pass down to sub components/properties
+        for x in self.filters:
+            currentMaximum, currentIsStartTime = x.getmaxtimerange(currentMaximum, currentIsStartTime)
+
+        return currentMaximum, currentIsStartTime
+
+
+    def getmintimerange(self, currentMinimum, currentIsEndTime):
+        &quot;&quot;&quot;
+        Get the date farthest into the past in any time-range elements. That is either
+        the start date, or if start is not present, the end date.
+        &quot;&quot;&quot;
+
+        # Give tzinfo to any TimeRange we have
+        isEndTime = False
+        if isinstance(self.qualifier, TimeRange):
+            isEndTime = self.qualifier.start is None
+            compareWith = self.qualifier.end if isEndTime else self.qualifier.start
+            if currentMinimum is None or currentMinimum &gt; compareWith:
+                currentMinimum = compareWith
+                currentIsEndTime = isEndTime
+
+        # Pass down to sub components/properties
+        for x in self.filters:
+            currentMinimum, currentIsEndTime = x.getmintimerange(currentMinimum, currentIsEndTime)
+
+        return currentMinimum, currentIsEndTime
+
+
+
+class PropertyFilter (FilterChildBase):
+    &quot;&quot;&quot;
+    Limits a search to specific properties.
+    &quot;&quot;&quot;
+
+    def _match(self, component, access):
+        # When access restriction is in force, we need to only allow matches against the properties
+        # allowed by the access restriction level.
+        if access:
+            allowedProperties = Component.confidentialPropertiesMap.get(component.name(), None)
+            if allowedProperties and access == Component.ACCESS_RESTRICTED:
+                allowedProperties += Component.extraRestrictedProperties
+        else:
+            allowedProperties = None
+
+        # At least one property must match (or is-not-defined is set)
+        for property in component.properties():
+            # Apply access restrictions, if any.
+            if allowedProperties is not None and property.name().upper() not in allowedProperties:
+                continue
+            if property.name().upper() == self.filter_name.upper() and self.match(property, access):
+                break
+        else:
+            return not self.defined
+        return self.defined
+
+
+    def valid(self):
+        &quot;&quot;&quot;
+        Indicate whether this filter element's structure is valid wrt iCalendar
+        data object model.
+
+        @return:      True if valid, False otherwise
+        &quot;&quot;&quot;
+
+        # Check for time-range
+        timerange = self.qualifier and isinstance(self.qualifier, TimeRange)
+
+        # time-range only on COMPLETED, CREATED, DTSTAMP, LAST-MODIFIED
+        if timerange and self.filter_name.upper() not in (&quot;COMPLETED&quot;, &quot;CREATED&quot;, &quot;DTSTAMP&quot;, &quot;LAST-MODIFIED&quot;):
+            log.info(&quot;time-range cannot be used with property %s&quot; % (self.filter_name,))
+            return False
+
+        # Test the time-range
+        if timerange:
+            if not self.qualifier.valid():
+                return False
+
+        # No other tests
+        return True
+
+
+    def settzinfo(self, tzinfo):
+        &quot;&quot;&quot;
+        Set the default timezone to use with this query.
+        @param tzinfo: a L{Timezone} to use.
+        &quot;&quot;&quot;
+
+        # Give tzinfo to any TimeRange we have
+        if isinstance(self.qualifier, TimeRange):
+            self.qualifier.settzinfo(tzinfo)
+
+
+    def getmaxtimerange(self, currentMaximum, currentIsStartTime):
+        &quot;&quot;&quot;
+        Get the date farthest into the future in any time-range elements
+
+        @param currentMaximum: current future value to compare with
+        @type currentMaximum: L{DateTime}
+        &quot;&quot;&quot;
+
+        # Give tzinfo to any TimeRange we have
+        isStartTime = False
+        if isinstance(self.qualifier, TimeRange):
+            isStartTime = self.qualifier.end is None
+            compareWith = self.qualifier.start if isStartTime else self.qualifier.end
+            if currentMaximum is None or currentMaximum &lt; compareWith:
+                currentMaximum = compareWith
+                currentIsStartTime = isStartTime
+
+        return currentMaximum, currentIsStartTime
+
+
+    def getmintimerange(self, currentMinimum, currentIsEndTime):
+        &quot;&quot;&quot;
+        Get the date farthest into the past in any time-range elements. That is either
+        the start date, or if start is not present, the end date.
+        &quot;&quot;&quot;
+
+        # Give tzinfo to any TimeRange we have
+        isEndTime = False
+        if isinstance(self.qualifier, TimeRange):
+            isEndTime = self.qualifier.start is None
+            compareWith = self.qualifier.end if isEndTime else self.qualifier.start
+            if currentMinimum is None or currentMinimum &gt; compareWith:
+                currentMinimum = compareWith
+                currentIsEndTime = isEndTime
+
+        return currentMinimum, currentIsEndTime
+
+
+
+class ParameterFilter (FilterChildBase):
+    &quot;&quot;&quot;
+    Limits a search to specific parameters.
+    &quot;&quot;&quot;
+
+    def _match(self, property, access):
+
+        # At least one parameter must match (or is-not-defined is set)
+        result = not self.defined
+        for parameterName in property.parameterNames():
+            if parameterName.upper() == self.filter_name.upper() and self.match([property.parameterValue(parameterName)], access):
+                result = self.defined
+                break
+
+        return result
+
+
+
+class IsNotDefined (FilterBase):
+    &quot;&quot;&quot;
+    Specifies that the named iCalendar item does not exist.
+    &quot;&quot;&quot;
+
+    def match(self, component, access=None):
+        # Oddly, this needs always to return True so that it appears there is
+        # a match - but we then &quot;negate&quot; the result if is-not-defined is set.
+        # Actually this method should never be called as we special case the
+        # is-not-defined option.
+        return True
+
+
+
+class TextMatch (FilterBase):
+    &quot;&quot;&quot;
+    Specifies a substring match on a property or parameter value.
+    (CalDAV-access-09, section 9.6.4)
+    &quot;&quot;&quot;
+    def __init__(self, xml_element):
+
+        super(TextMatch, self).__init__(xml_element)
+
+        self.text = str(xml_element)
+        if &quot;caseless&quot; in xml_element.attributes:
+            caseless = xml_element.attributes[&quot;caseless&quot;]
+            if caseless == &quot;yes&quot;:
+                self.caseless = True
+            elif caseless == &quot;no&quot;:
+                self.caseless = False
+        else:
+            self.caseless = True
+
+        if &quot;negate-condition&quot; in xml_element.attributes:
+            negate = xml_element.attributes[&quot;negate-condition&quot;]
+            if negate == &quot;yes&quot;:
+                self.negate = True
+            elif negate == &quot;no&quot;:
+                self.negate = False
+        else:
+            self.negate = False
+
+        if &quot;match-type&quot; in xml_element.attributes:
+            self.match_type = xml_element.attributes[&quot;match-type&quot;]
+            if self.match_type not in (
+                &quot;equals&quot;,
+                &quot;contains&quot;,
+                &quot;starts-with&quot;,
+                &quot;ends-with&quot;,
+            ):
+                self.match_type = &quot;contains&quot;
+        else:
+            self.match_type = &quot;contains&quot;
+
+
+    def match(self, item, access):
+        &quot;&quot;&quot;
+        Match the text for the item.
+        If the item is a property, then match the property value,
+        otherwise it may be a list of parameter values - try to match anyone of those
+        &quot;&quot;&quot;
+        if item is None:
+            return False
+
+        if isinstance(item, Property):
+            values = [item.strvalue()]
+        else:
+            values = item
+
+        test = unicode(self.text, &quot;utf-8&quot;)
+        if self.caseless:
+            test = test.lower()
+
+        def _textCompare(s):
+            if self.caseless:
+                s = s.lower()
+
+            if self.match_type == &quot;equals&quot;:
+                return s == test
+            elif self.match_type == &quot;contains&quot;:
+                return s.find(test) != -1
+            elif self.match_type == &quot;starts-with&quot;:
+                return s.startswith(test)
+            elif self.match_type == &quot;ends-with&quot;:
+                return s.endswith(test)
+            else:
+                return False
+
+        for value in values:
+            # NB Its possible that we have a text list value which appears as a Python list,
+            # so we need to check for that and iterate over the list.
+            if isinstance(value, list):
+                for subvalue in value:
+                    if _textCompare(unicode(subvalue, &quot;utf-8&quot;)):
+                        return not self.negate
+            else:
+                if _textCompare(unicode(value, &quot;utf-8&quot;)):
+                    return not self.negate
+
+        return self.negate
+
+
+
+class TimeRange (FilterBase):
+    &quot;&quot;&quot;
+    Specifies a time for testing components against.
+    &quot;&quot;&quot;
+
+    def __init__(self, xml_element):
+
+        super(TimeRange, self).__init__(xml_element)
+
+        # One of start or end must be present
+        if &quot;start&quot; not in xml_element.attributes and &quot;end&quot; not in xml_element.attributes:
+            raise ValueError(&quot;One of 'start' or 'end' must be present in CALDAV:time-range&quot;)
+
+        self.start = DateTime.parseText(xml_element.attributes[&quot;start&quot;]) if &quot;start&quot; in xml_element.attributes else None
+        self.end = DateTime.parseText(xml_element.attributes[&quot;end&quot;]) if &quot;end&quot; in xml_element.attributes else None
+        self.tzinfo = None
+
+
+    def settzinfo(self, tzinfo):
+        &quot;&quot;&quot;
+        Set the default timezone to use with this query.
+        @param tzinfo: a L{Timezone} to use.
+        &quot;&quot;&quot;
+
+        # Give tzinfo to any TimeRange we have
+        self.tzinfo = tzinfo
+
+
+    def valid(self, level=0):
+        &quot;&quot;&quot;
+        Indicate whether the time-range is valid (must be date-time in UTC).
+
+        @return:      True if valid, False otherwise
+        &quot;&quot;&quot;
+
+        if self.start is not None and self.start.isDateOnly():
+            log.info(&quot;start attribute in &lt;time-range&gt; is not a date-time: %s&quot; % (self.start,))
+            return False
+        if self.end is not None and self.end.isDateOnly():
+            log.info(&quot;end attribute in &lt;time-range&gt; is not a date-time: %s&quot; % (self.end,))
+            return False
+        if self.start is not None and not self.start.utc():
+            log.info(&quot;start attribute in &lt;time-range&gt; is not UTC: %s&quot; % (self.start,))
+            return False
+        if self.end is not None and not self.end.utc():
+            log.info(&quot;end attribute in &lt;time-range&gt; is not UTC: %s&quot; % (self.end,))
+            return False
+
+        # No other tests
+        return True
+
+
+    def match(self, property, access=None):
+        &quot;&quot;&quot;
+        NB This is only called when doing a time-range match on a property.
+        &quot;&quot;&quot;
+        if property is None:
+            return False
+        else:
+            return property.containsTimeRange(self.start, self.end, self.tzinfo)
+
+
+    def matchinstance(self, component, instances):
+        &quot;&quot;&quot;
+        Test whether this time-range element causes a match to the specified component
+        using the specified set of instances to determine the expanded time ranges.
+        @param component: the L{Component} to test.
+        @param instances: the list of expanded instances.
+        @return: True if the time-range query matches, False otherwise.
+        &quot;&quot;&quot;
+        if component is None:
+            return False
+
+        assert instances is not None or self.end is None, &quot;Failure to expand instance for time-range filter: %r&quot; % (self,)
+
+        # Special case open-ended unbounded
+        if instances is None:
+            if component.getRecurrenceIDUTC() is None:
+                return True
+            else:
+                # See if the overridden component's start is past the start
+                start, _ignore_end = component.getEffectiveStartEnd()
+                if start is None:
+                    return True
+                else:
+                    return start &gt;= self.start
+
+        # Handle alarms as a special case
+        alarms = (component.name() == &quot;VALARM&quot;)
+        if alarms:
+            testcomponent = component._parent
+        else:
+            testcomponent = component
+
+        for key in instances:
+            instance = instances[key]
+
+            # First make sure components match
+            if not testcomponent.same(instance.component):
+                continue
+
+            if alarms:
+                # Get all the alarm triggers for this instance and test each one
+                triggers = instance.getAlarmTriggers()
+                for trigger in triggers:
+                    if timeRangesOverlap(trigger, None, self.start, self.end, self.tzinfo):
+                        return True
+            else:
+                # Regular instance overlap test
+                if timeRangesOverlap(instance.start, instance.end, self.start, self.end, self.tzinfo):
+                    return True
+
+        return False
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequerygeneratorpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/generator.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/generator.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/generator.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,207 @@
</span><ins>+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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 twext.enterprise.dal.syntax import Select
+
+from txdav.common.datastore.query import expression
+from txdav.common.datastore.query.generator import SQLQueryGenerator
+from txdav.common.datastore.sql_tables import schema
+
+&quot;&quot;&quot;
+SQL statement generator from query expressions.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;CalDAVSQLQueryGenerator&quot;,
+]
+
+class CalDAVSQLQueryGenerator(SQLQueryGenerator):
+
+    _timerange = schema.TIME_RANGE
+    _transparency = schema.TRANSPARENCY
+
+    def __init__(self, expr, collection, whereid, userid=None, freebusy=False):
+        &quot;&quot;&quot;
+
+        @param expr: the query expression object model
+        @type expr: L{expression}
+        @param collection: the resource targeted by the query
+        @type collection: L{CommonHomeChild}
+        @param userid: user for whom query is being done - query will be scoped to that user's privileges and their transparency
+        @type userid: C{str}
+        @param freebusy: whether or not a freebusy query is being done - if it is, additional time range and transparency information is returned
+        @type freebusy: C{bool}
+        &quot;&quot;&quot;
+        super(CalDAVSQLQueryGenerator, self).__init__(expr, collection, whereid)
+        self.userid = userid if userid else &quot;&quot;
+        self.freebusy = freebusy
+        self.usedtimerange = False
+
+
+    def generate(self):
+        &quot;&quot;&quot;
+        Generate the actual SQL statement from the passed in expression tree.
+
+        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
+            and the C{list} is the list of argument substitutions to use with the SQL API execute method.
+        &quot;&quot;&quot;
+
+        # Init state
+        self.arguments = {}
+        self.argcount = 0
+        obj = self.collection._objectSchema
+
+        columns = [obj.RESOURCE_NAME, obj.ICALENDAR_UID, obj.ICALENDAR_TYPE]
+        if self.freebusy:
+            columns.extend([
+                obj.ORGANIZER,
+                self._timerange.FLOATING,
+                self._timerange.START_DATE,
+                self._timerange.END_DATE,
+                self._timerange.FBTYPE,
+                self._timerange.TRANSPARENT,
+                self._transparency.TRANSPARENT,
+            ])
+
+        # For SQL data DB we need to restrict the query to just the targeted calendar resource-id if provided
+        if self.whereid:
+
+            test = expression.isExpression(obj.CALENDAR_RESOURCE_ID, self.whereid, 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:
+                    # timerange expression forces a join on calendarid
+                    pass
+                else:
+                    # AND the whole thing with calendarid
+                    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 use the id test
+            elif isinstance(self.expression, expression.allExpression):
+                self.expression = test
+
+            # Just AND the entire thing
+            else:
+                self.expression = test.andWith(self.expression)
+
+        # Generate ' where ...' partial statement
+        where = self.generateExpression(self.expression)
+
+        if self.usedtimerange:
+            where = where.And(self._timerange.CALENDAR_OBJECT_RESOURCE_ID == obj.RESOURCE_ID).And(self._timerange.CALENDAR_RESOURCE_ID == self.whereid)
+
+        # Set of tables depends on use of timespan and fb use
+        if self.usedtimerange:
+            if self.freebusy:
+                tables = obj.join(
+                    self._timerange.join(
+                        self._transparency,
+                        on=(self._timerange.INSTANCE_ID == self._transparency.TIME_RANGE_INSTANCE_ID).And(self._transparency.USER_ID == self.userid),
+                        type=&quot;left outer&quot;
+                    ),
+                    type=&quot;,&quot;
+                )
+            else:
+                tables = obj.join(self._timerange, type=&quot;,&quot;)
+        else:
+            tables = obj
+
+        select = Select(
+            columns,
+            From=tables,
+            Where=where,
+            Distinct=True,
+        )
+
+        return select, self.arguments, self.usedtimerange
+
+
+    def generateExpression(self, expr):
+        &quot;&quot;&quot;
+        Generate an expression and all it's subexpressions.
+
+        @param expr: the L{baseExpression} derived class to write out.
+        &quot;&quot;&quot;
+
+        # Generate based on each type of expression we might encounter
+        partial = None
+
+        # time-range
+        if isinstance(expr, expression.timerangeExpression):
+            if expr.start and expr.end:
+                partial = (
+                    (self._timerange.FLOATING == False).And(self._timerange.START_DATE &lt; expr.end).And(self._timerange.END_DATE &gt; expr.start)
+                ).Or(
+                    (self._timerange.FLOATING == True).And(self._timerange.START_DATE &lt; expr.endfloat).And(self._timerange.END_DATE &gt; expr.startfloat)
+                )
+            elif expr.start and expr.end is None:
+                partial = (
+                    (self._timerange.FLOATING == False).And(self._timerange.END_DATE &gt; expr.start)
+                ).Or(
+                    (self._timerange.FLOATING == True).And(self._timerange.END_DATE &gt; expr.startfloat)
+                )
+            elif not expr.start and expr.end:
+                partial = (
+                    (self._timerange.FLOATING == False).And(self._timerange.START_DATE &lt; expr.end)
+                ).Or(
+                    (self._timerange.FLOATING == True).And(self._timerange.START_DATE &lt; expr.endfloat)
+                )
+            self.usedtimerange = True
+
+        else:
+            partial = super(CalDAVSQLQueryGenerator, self).generateExpression(expr)
+
+        return partial
+
+
+    def addArgument(self, arg):
+        &quot;&quot;&quot;
+
+        @param arg: the C{str} of the argument to add
+        &quot;&quot;&quot;
+
+        # Append argument to the list and add the appropriate substitution string to the output stream.
+        self.argcount += 1
+        argname = &quot;arg{}&quot;.format(self.argcount)
+        self.arguments[argname] = arg
+        return argname
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequerytest__init__py"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/__init__.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/__init__.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/__init__.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+##
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequerytesttest_filterpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/test_filter.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/test_filter.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/test_filter.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,220 @@
</span><ins>+##
+# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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 pycalendar.timezone import Timezone
+
+from twext.enterprise.dal.syntax import SQLFragment, Parameter
+
+from twistedcaldav.test.util import TestCase
+from twistedcaldav import caldavxml
+from twistedcaldav.timezones import TimezoneCache
+
+from txdav.caldav.datastore.index_file import sqlcalendarquery
+from txdav.caldav.datastore.query.builder import buildExpression
+from txdav.caldav.datastore.query.filter import Filter
+from txdav.caldav.datastore.query.generator import CalDAVSQLQueryGenerator
+from txdav.common.datastore.sql_tables import schema
+
+from dateutil.tz import tzutc
+import datetime
+
+class TestQueryFilter(TestCase):
+
+    _objectSchema = schema.CALENDAR_OBJECT
+    _queryFields = {
+        &quot;UID&quot;: _objectSchema.UID,
+        &quot;TYPE&quot;: _objectSchema.ICALENDAR_TYPE,
+    }
+
+    def setUp(self):
+        super(TestQueryFilter, self).setUp()
+        TimezoneCache.create()
+
+
+    def test_query(self):
+        &quot;&quot;&quot;
+        Basic query test - no time range
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
+                )],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+
+        expression = buildExpression(filter, self._queryFields)
+        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
+        select, args, usedtimerange = sql.generate()
+
+        self.assertEqual(select.toSQL(), SQLFragment(
+            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_TYPE in (?, ?, ?)&quot;,
+            [1234, Parameter('arg1', 3)]
+        ))
+        self.assertEqual(args, {&quot;arg1&quot;: (&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)})
+        self.assertEqual(usedtimerange, False)
+
+
+    def test_query_timerange(self):
+        &quot;&quot;&quot;
+        Basic query test - with time range
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    *[caldavxml.TimeRange(**{&quot;start&quot;:&quot;20060605T160000Z&quot;, &quot;end&quot;:&quot;20060605T170000Z&quot;})],
+                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
+                )],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+
+        expression = buildExpression(filter, self._queryFields)
+        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
+        select, args, usedtimerange = sql.generate()
+
+        self.assertEqual(select.toSQL(), SQLFragment(
+            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and START_DATE &lt; ? and END_DATE &gt; ? or FLOATING = ? and START_DATE &lt; ? and END_DATE &gt; ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?&quot;,
+            [Parameter('arg1', 3), False, datetime.datetime(2006, 6, 5, 17, 0, tzinfo=tzutc()), datetime.datetime(2006, 6, 5, 16, 0, tzinfo=tzutc()), True, datetime.datetime(2006, 6, 5, 13, 0, tzinfo=tzutc()), datetime.datetime(2006, 6, 5, 12, 0, tzinfo=tzutc()), 1234]
+        ))
+        self.assertEqual(args, {&quot;arg1&quot;: (&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)})
+        self.assertEqual(usedtimerange, True)
+
+
+    def test_query_freebusy(self):
+        &quot;&quot;&quot;
+        Basic query test - with time range
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    *[caldavxml.TimeRange(**{&quot;start&quot;:&quot;20060605T160000Z&quot;, &quot;end&quot;:&quot;20060605T170000Z&quot;})],
+                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
+                )],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+
+        expression = buildExpression(filter, self._queryFields)
+        sql = CalDAVSQLQueryGenerator(expression, self, 1234, &quot;user01&quot;, True)
+        select, args, usedtimerange = sql.generate()
+
+        self.assertEqual(select.toSQL(), SQLFragment(
+            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE, ORGANIZER, FLOATING, START_DATE, END_DATE, FBTYPE, TIME_RANGE.TRANSPARENT, TRANSPARENCY.TRANSPARENT from CALENDAR_OBJECT, TIME_RANGE left outer join TRANSPARENCY on INSTANCE_ID = TIME_RANGE_INSTANCE_ID and USER_ID = ? where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and START_DATE &lt; ? and END_DATE &gt; ? or FLOATING = ? and START_DATE &lt; ? and END_DATE &gt; ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?&quot;,
+            ['user01', Parameter('arg1', 3), False, datetime.datetime(2006, 6, 5, 17, 0, tzinfo=tzutc()), datetime.datetime(2006, 6, 5, 16, 0, tzinfo=tzutc()), True, datetime.datetime(2006, 6, 5, 13, 0, tzinfo=tzutc()), datetime.datetime(2006, 6, 5, 12, 0, tzinfo=tzutc()), 1234]
+        ))
+        self.assertEqual(args, {&quot;arg1&quot;: (&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)})
+        self.assertEqual(usedtimerange, True)
+
+
+    def test_query_not_extended(self):
+        &quot;&quot;&quot;
+        Query test - two terms not anyof
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[
+                    caldavxml.ComponentFilter(
+                        **{&quot;name&quot;:(&quot;VEVENT&quot;)}
+                    ),
+                    caldavxml.ComponentFilter(
+                        **{&quot;name&quot;:(&quot;VTODO&quot;)}
+                    ),
+                ],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+
+        expression = buildExpression(filter, self._queryFields)
+        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
+        select, args, usedtimerange = sql.generate()
+
+        self.assertEqual(select.toSQL(), SQLFragment(
+            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_TYPE = ? and ICALENDAR_TYPE = ?&quot;,
+            [1234, &quot;VEVENT&quot;, &quot;VTODO&quot;]
+        ))
+        self.assertEqual(args, {})
+        self.assertEqual(usedtimerange, False)
+
+
+    def test_query_extended(self):
+        &quot;&quot;&quot;
+        Extended query test - two terms with anyof
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[
+                    caldavxml.ComponentFilter(
+                        *[caldavxml.TimeRange(**{&quot;start&quot;:&quot;20060605T160000Z&quot;, })],
+                        **{&quot;name&quot;:(&quot;VEVENT&quot;)}
+                    ),
+                    caldavxml.ComponentFilter(
+                        **{&quot;name&quot;:(&quot;VTODO&quot;)}
+                    ),
+                ],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;, &quot;test&quot;: &quot;anyof&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+
+        expression = buildExpression(filter, self._queryFields)
+        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
+        select, args, usedtimerange = sql.generate()
+
+        self.assertEqual(select.toSQL(), SQLFragment(
+            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where (ICALENDAR_TYPE = ? and (FLOATING = ? and END_DATE &gt; ? or FLOATING = ? and END_DATE &gt; ?) or ICALENDAR_TYPE = ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?&quot;,
+            ['VEVENT', False, datetime.datetime(2006, 6, 5, 16, 0, tzinfo=tzutc()), True, datetime.datetime(2006, 6, 5, 12, 0, tzinfo=tzutc()), 'VTODO', 1234]
+        ))
+        self.assertEqual(args, {})
+        self.assertEqual(usedtimerange, True)
+
+
+    def test_sqllite_query(self):
+        &quot;&quot;&quot;
+        Basic query test - single term.
+        Only UID can be queried via sql.
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
+                )],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+        sql, args = sqlcalendarquery(filter, 1234)
+
+        self.assertTrue(sql.find(&quot;RESOURCE&quot;) != -1)
+        self.assertTrue(sql.find(&quot;TIMESPAN&quot;) == -1)
+        self.assertTrue(sql.find(&quot;TRANSPARENCY&quot;) == -1)
+        self.assertTrue(&quot;VEVENT&quot; in args)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoreschedulingfreebusypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/scheduling/freebusy.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/scheduling/freebusy.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/scheduling/freebusy.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -32,10 +32,11 @@
</span><span class="cx"> from twistedcaldav.ical import Component, Property, iCalendarProductID
</span><span class="cx"> from twistedcaldav.instance import InstanceList
</span><span class="cx"> from twistedcaldav.memcacher import Memcacher
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><span class="cx"> 
</span><ins>+from txdav.caldav.datastore.query.filter import Filter
</ins><span class="cx"> from txdav.caldav.icalendarstore import QueryMaxResources
</span><del>-from txdav.common.icommondatastore import IndexedSearchException
</del><ins>+from txdav.common.icommondatastore import IndexedSearchException, \
+    InternalDataStoreError
</ins><span class="cx"> 
</span><span class="cx"> import uuid
</span><span class="cx"> 
</span><span class="lines">@@ -282,23 +283,23 @@
</span><span class="cx"> 
</span><span class="cx">         # Create fake filter element to match time-range
</span><span class="cx">         filter = caldavxml.Filter(
</span><del>-                      caldavxml.ComponentFilter(
-                          caldavxml.ComponentFilter(
-                              cache_timerange if caching else timerange,
-                              name=(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;),
-                          ),
-                          name=&quot;VCALENDAR&quot;,
-                       )
-                  )
-        filter = calendarqueryfilter.Filter(filter)
</del><ins>+            caldavxml.ComponentFilter(
+                caldavxml.ComponentFilter(
+                    cache_timerange if caching else timerange,
+                    name=(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;),
+                ),
+                name=&quot;VCALENDAR&quot;,
+            )
+        )
+        filter = Filter(filter)
</ins><span class="cx">         tzinfo = filter.settimezone(tz)
</span><span class="cx"> 
</span><span class="cx">         try:
</span><del>-            resources = yield calresource._index.indexedSearch(filter, useruid=attendee_uid, fbtype=True)
</del><ins>+            resources = yield calresource.search(filter, useruid=attendee_uid, fbtype=True)
</ins><span class="cx">             if caching:
</span><span class="cx">                 yield FBCacheEntry.makeCacheEntry(calresource, attendee_uid, cache_timerange, resources)
</span><span class="cx">         except IndexedSearchException:
</span><del>-            resources = yield calresource._index.bruteForceSearch()
</del><ins>+            raise InternalDataStoreError(&quot;Invalid indexedSearch query&quot;)
</ins><span class="cx"> 
</span><span class="cx">     else:
</span><span class="cx">         # Log extended item
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -57,6 +57,9 @@
</span><span class="cx"> from twistedcaldav.memcacher import Memcacher
</span><span class="cx"> 
</span><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><ins>+from txdav.caldav.datastore.query.builder import buildExpression
+from txdav.caldav.datastore.query.filter import Filter
+from txdav.caldav.datastore.query.generator import CalDAVSQLQueryGenerator
</ins><span class="cx"> from txdav.caldav.datastore.scheduling.icalsplitter import iCalSplitter
</span><span class="cx"> from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
</span><span class="cx"> from txdav.caldav.datastore.util import AttachmentRetrievalTransport, \
</span><span class="lines">@@ -72,12 +75,11 @@
</span><span class="cx">     AttendeeAllowedError, InvalidPerUserDataMerge, ComponentUpdateState, \
</span><span class="cx">     ValidOrganizerError, ShareeAllowedError, ComponentRemoveState, \
</span><span class="cx">     InvalidDefaultCalendar, \
</span><del>-    InvalidAttachmentOperation, DuplicatePrivateCommentsError
</del><ins>+    InvalidAttachmentOperation, DuplicatePrivateCommentsError, \
+    TimeRangeUpperLimit, TimeRangeLowerLimit
</ins><span class="cx"> from txdav.caldav.icalendarstore import QuotaExceeded
</span><span class="cx"> from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
</span><span class="cx">     CommonObjectResource, ECALENDARTYPE
</span><del>-from txdav.common.datastore.sql_legacy import PostgresLegacyIndexEmulator, \
-    PostgresLegacyInboxIndexEmulator
</del><span class="cx"> from txdav.common.datastore.sql_tables import _ATTACHMENTS_MODE_NONE, \
</span><span class="cx">     _ATTACHMENTS_MODE_WRITE, schema, _BIND_MODE_OWN, \
</span><span class="cx">     _ATTACHMENTS_MODE_READ, _TRANSP_OPAQUE, _TRANSP_TRANSPARENT
</span><span class="lines">@@ -87,7 +89,8 @@
</span><span class="cx">     InvalidObjectResourceError, ObjectResourceNameAlreadyExistsError, \
</span><span class="cx">     ObjectResourceNameNotAllowedError, TooManyObjectResourcesError, \
</span><span class="cx">     InvalidUIDError, UIDExistsError, UIDExistsElsewhereError, \
</span><del>-    InvalidResourceMove, InvalidComponentForStoreError
</del><ins>+    InvalidResourceMove, InvalidComponentForStoreError, \
+    NoSuchObjectResourceError
</ins><span class="cx"> from txdav.xml import element
</span><span class="cx"> 
</span><span class="cx"> from txdav.idav import ChangeCategory
</span><span class="lines">@@ -949,6 +952,12 @@
</span><span class="cx">     _objectSchema = schema.CALENDAR_OBJECT
</span><span class="cx">     _timeRangeSchema = schema.TIME_RANGE
</span><span class="cx"> 
</span><ins>+    # Mapping of iCalendar property name to DB column name
+    _queryFields = {
+        &quot;UID&quot;: _objectSchema.UID,
+        &quot;TYPE&quot;: _objectSchema.ICALENDAR_TYPE,
+    }
+
</ins><span class="cx">     _supportedComponents = None
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, *args, **kw):
</span><span class="lines">@@ -956,10 +965,6 @@
</span><span class="cx">         Initialize a calendar pointing at a record in a database.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         super(Calendar, self).__init__(*args, **kw)
</span><del>-        if self.isInbox():
-            self._index = PostgresLegacyInboxIndexEmulator(self)
-        else:
-            self._index = PostgresLegacyIndexEmulator(self)
</del><span class="cx">         self._transp = _TRANSP_OPAQUE
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1335,6 +1340,189 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def search(self, filter, useruid=None, fbtype=False):
+        &quot;&quot;&quot;
+        Finds resources matching the given qualifiers.
+        @param filter: the L{Filter} for the calendar-query to execute.
+        @return: an iterable of tuples for each resource matching the
+            given C{qualifiers}. The tuples are C{(name, uid)}, where
+            C{name} is the resource name, C{uid} is the resource UID.
+        &quot;&quot;&quot;
+
+        # Make sure we have a proper Filter element and get the partial SQL statement to use.
+        sql_stmt = self._sqlquery(filter, useruid, fbtype)
+
+        # No result means it is too complex for us
+        if sql_stmt is None:
+            raise IndexedSearchException()
+        sql_stmt, args, usedtimerange = sql_stmt
+
+        # Check for time-range re-expand
+        if usedtimerange is not None:
+
+            today = DateTime.getToday()
+
+            # Determine how far we need to extend the current expansion of
+            # events. If we have an open-ended time-range we will expand
+            # one year past the start. That should catch bounded
+            # recurrences - unbounded will have been indexed with an
+            # &quot;infinite&quot; value always included.
+            maxDate, isStartDate = filter.getmaxtimerange()
+            if maxDate:
+                maxDate = maxDate.duplicate()
+                maxDate.offsetDay(1)
+                maxDate.setDateOnly(True)
+                upperLimit = today + Duration(days=config.FreeBusyIndexExpandMaxDays)
+                if maxDate &gt; upperLimit:
+                    raise TimeRangeUpperLimit(upperLimit)
+                if isStartDate:
+                    maxDate += Duration(days=365)
+
+            # Determine if the start date is too early for the restricted range we
+            # are applying. If it is today or later we don't need to worry about truncation
+            # in the past.
+            minDate, _ignore_isEndDate = filter.getmintimerange()
+            if minDate &gt;= today:
+                minDate = None
+            if minDate is not None and config.FreeBusyIndexLowerLimitDays:
+                truncateLowerLimit = today - Duration(days=config.FreeBusyIndexLowerLimitDays)
+                if minDate &lt; truncateLowerLimit:
+                    raise TimeRangeLowerLimit(truncateLowerLimit)
+
+            if maxDate is not None or minDate is not None:
+                yield self.testAndUpdateIndex(minDate, maxDate)
+
+        rowiter = yield sql_stmt.on(self._txn, **args)
+
+        # Check result for missing resources
+        results = []
+        for row in rowiter:
+            if fbtype:
+                row = list(row)
+                row[4] = 'Y' if row[4] else 'N'
+                row[7] = indexfbtype_to_icalfbtype[row[7]]
+                if row[9] is not None:
+                    row[8] = row[9]
+                row[8] = 'T' if row[8] else 'F'
+                del row[9]
+            results.append(row)
+
+        returnValue(results)
+
+
+    def _sqlquery(self, filter, useruid, fbtype):
+        &quot;&quot;&quot;
+        Convert the supplied addressbook-query into a partial SQL statement.
+
+        @param filter: the L{Filter} for the addressbook-query to convert.
+        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
+                and the C{list} is the list of argument substitutions to use with the SQL API execute method.
+                Or return C{None} if it is not possible to create an SQL query to fully match the addressbook-query.
+        &quot;&quot;&quot;
+
+        if not isinstance(filter, Filter):
+            return None
+
+        try:
+            expression = buildExpression(filter, self._queryFields)
+            sql = CalDAVSQLQueryGenerator(expression, self, self.id(), useruid, fbtype)
+            return sql.generate()
+        except ValueError:
+            return None
+
+
+    @classproperty
+    def _notExpandedWithinQuery(cls): #@NoSelf
+        &quot;&quot;&quot;
+        Query to find resources that need to be re-expanded
+        &quot;&quot;&quot;
+        co = schema.CALENDAR_OBJECT
+        return Select(
+            [co.RESOURCE_NAME],
+            From=co,
+            Where=((co.RECURRANCE_MIN &gt; Parameter(&quot;minDate&quot;))
+                .Or(co.RECURRANCE_MAX &lt; Parameter(&quot;maxDate&quot;)))
+                .And(co.CALENDAR_RESOURCE_ID == Parameter(&quot;resourceID&quot;))
+        )
+
+
+    @inlineCallbacks
+    def notExpandedWithin(self, minDate, maxDate):
+        &quot;&quot;&quot;
+        Gives all resources which have not been expanded beyond a given date
+        in the database.  (Unused; see above L{postgresqlgenerator}.
+        &quot;&quot;&quot;
+        returnValue([row[0] for row in (
+            yield self._notExpandedWithinQuery.on(
+                self._txn,
+                minDate=pyCalendarTodatetime(normalizeForIndex(minDate)) if minDate is not None else None,
+                maxDate=pyCalendarTodatetime(normalizeForIndex(maxDate)),
+                resourceID=self._resourceID))]
+        )
+
+
+    @inlineCallbacks
+    def reExpandResource(self, name, expand_start, expand_end):
+        &quot;&quot;&quot;
+        Given a resource name, remove it from the database and re-add it
+        with a longer expansion.
+        &quot;&quot;&quot;
+        obj = yield self.calendarObjectWithName(name)
+
+        # Use a new transaction to do this update quickly without locking the row for too long. However, the original
+        # transaction may have the row locked, so use wait=False and if that fails, fall back to using the original txn.
+
+        newTxn = obj.transaction().store().newTransaction()
+        try:
+            yield obj.lock(wait=False, txn=newTxn)
+        except NoSuchObjectResourceError:
+            yield newTxn.commit()
+            returnValue(None)
+        except:
+            yield newTxn.abort()
+            newTxn = None
+
+        # Now do the re-expand using the appropriate transaction
+        try:
+            doExpand = False
+            if newTxn is None:
+                doExpand = True
+            else:
+                # We repeat this check because the resource may have been re-expanded by someone else
+                rmin, rmax = (yield obj.recurrenceMinMax(txn=newTxn))
+
+                # If the resource is not fully expanded, see if within the required range or not.
+                # Note that expand_start could be None if no lower limit is applied, but expand_end will
+                # never be None
+                if rmax is not None and rmax &lt; expand_end:
+                    doExpand = True
+                if rmin is not None and expand_start is not None and rmin &gt; expand_start:
+                    doExpand = True
+
+            if doExpand:
+                yield obj.updateDatabase(
+                    (yield obj.component()),
+                    expand_until=expand_end,
+                    reCreate=True,
+                    txn=newTxn,
+                )
+        finally:
+            if newTxn is not None:
+                yield newTxn.commit()
+
+
+    @inlineCallbacks
+    def testAndUpdateIndex(self, minDate, maxDate):
+        # Find out if the index is expanded far enough
+        names = yield self.notExpandedWithin(minDate, maxDate)
+
+        # Actually expand recurrence max
+        for name in names:
+            self.log.info(&quot;Search falls outside range of index for %s %s to %s&quot; % (name, minDate, maxDate))
+            yield self.reExpandResource(name, minDate, maxDate)
+
+
+    @inlineCallbacks
</ins><span class="cx">     def splitCollectionByComponentTypes(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         If the calendar contains iCalendar data with different component types, then split it into separate collections
</span><span class="lines">@@ -2179,7 +2367,10 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         if isinstance(component, str) or isinstance(component, unicode):
</span><del>-            component = self._componentClass.fromString(component)
</del><ins>+            try:
+                component = self._componentClass.fromString(component)
+            except InvalidICalendarDataError as e:
+                raise InvalidComponentForStoreError(str(e))
</ins><span class="cx">         return self._setComponentInternal(component, inserting, ComponentUpdateState.NORMAL, smart_merge)
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretesttest_index_filepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_index_file.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_index_file.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_index_file.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -19,6 +19,7 @@
</span><span class="cx"> from twisted.internet.task import deferLater
</span><span class="cx"> 
</span><span class="cx"> from txdav.caldav.datastore.index_file import Index, MemcachedUIDReserver
</span><ins>+from txdav.caldav.datastore.query.filter import Filter
</ins><span class="cx"> from txdav.common.icommondatastore import ReservationError, \
</span><span class="cx">     InternalDataStoreError
</span><span class="cx"> 
</span><span class="lines">@@ -26,7 +27,6 @@
</span><span class="cx"> from twistedcaldav.caldavxml import TimeRange
</span><span class="cx"> from twistedcaldav.ical import Component, InvalidICalendarDataError
</span><span class="cx"> from twistedcaldav.instance import InvalidOverriddenInstanceError
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><span class="cx"> from twistedcaldav.test.util import InMemoryMemcacheProtocol
</span><span class="cx"> import twistedcaldav.test.util
</span><span class="cx"> 
</span><span class="lines">@@ -480,7 +480,7 @@
</span><span class="cx">                       name=&quot;VCALENDAR&quot;,
</span><span class="cx">                    )
</span><span class="cx">               )
</span><del>-            filter = calendarqueryfilter.Filter(filter)
</del><ins>+            filter = Filter(filter)
</ins><span class="cx"> 
</span><span class="cx">             resources = yield self.db.indexedSearch(filter)
</span><span class="cx">             index_results = set()
</span><span class="lines">@@ -666,7 +666,7 @@
</span><span class="cx">                       name=&quot;VCALENDAR&quot;,
</span><span class="cx">                    )
</span><span class="cx">               )
</span><del>-            filter = calendarqueryfilter.Filter(filter)
</del><ins>+            filter = Filter(filter)
</ins><span class="cx"> 
</span><span class="cx">             resources = yield self.db.indexedSearch(filter, fbtype=True)
</span><span class="cx">             index_results = set()
</span><span class="lines">@@ -1073,7 +1073,7 @@
</span><span class="cx">                       name=&quot;VCALENDAR&quot;,
</span><span class="cx">                    )
</span><span class="cx">               )
</span><del>-            filter = calendarqueryfilter.Filter(filter)
</del><ins>+            filter = Filter(filter)
</ins><span class="cx"> 
</span><span class="cx">             for useruid, instances in peruserinstances:
</span><span class="cx">                 resources = yield self.db.indexedSearch(filter, useruid=useruid, fbtype=True)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -13,14 +13,6 @@
</span><span class="cx"> # See the License for the specific language governing permissions and
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><del>-from txdav.caldav.datastore.scheduling.processing import ImplicitProcessor
-from txdav.caldav.datastore.scheduling.cuaddress import RemoteCalendarUser, \
-    LocalCalendarUser
-from txdav.caldav.datastore.scheduling.caldav.scheduler import CalDAVScheduler
-from txdav.caldav.datastore.scheduling.scheduler import ScheduleResponseQueue
-from twext.web2 import responsecode
-from txdav.caldav.datastore.scheduling.itip import iTIPRequestStatus
-from twistedcaldav.instance import InvalidOverriddenInstanceError
</del><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> Tests for txdav.caldav.datastore.postgres, mostly based on
</span><span class="lines">@@ -33,6 +25,7 @@
</span><span class="cx"> from twext.enterprise.dal.syntax import Select, Parameter, Insert, Delete, \
</span><span class="cx">     Update
</span><span class="cx"> from twext.python.vcomponent import VComponent
</span><ins>+from twext.web2 import responsecode
</ins><span class="cx"> from twext.web2.http_headers import MimeType
</span><span class="cx"> from twext.web2.stream import MemoryStream
</span><span class="cx"> 
</span><span class="lines">@@ -47,9 +40,16 @@
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.dateops import datetimeMktime
</span><span class="cx"> from twistedcaldav.ical import Component, normalize_iCalStr, diff_iCalStrs
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><ins>+from twistedcaldav.instance import InvalidOverriddenInstanceError
</ins><span class="cx"> 
</span><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><ins>+from txdav.caldav.datastore.query.filter import Filter
+from txdav.caldav.datastore.scheduling.caldav.scheduler import CalDAVScheduler
+from txdav.caldav.datastore.scheduling.cuaddress import RemoteCalendarUser, \
+    LocalCalendarUser
+from txdav.caldav.datastore.scheduling.itip import iTIPRequestStatus
+from txdav.caldav.datastore.scheduling.processing import ImplicitProcessor
+from txdav.caldav.datastore.scheduling.scheduler import ScheduleResponseQueue
</ins><span class="cx"> from txdav.caldav.datastore.test.common import CommonTests as CalendarCommonTests, \
</span><span class="cx">     test_event_text
</span><span class="cx"> from txdav.caldav.datastore.test.test_file import setUpCalendarStore
</span><span class="lines">@@ -57,14 +57,13 @@
</span><span class="cx"> from txdav.caldav.datastore.util import _migrateCalendar, migrateHome
</span><span class="cx"> from txdav.caldav.icalendarstore import ComponentUpdateState, InvalidDefaultCalendar
</span><span class="cx"> from txdav.common.datastore.sql import ECALENDARTYPE, CommonObjectResource
</span><del>-from txdav.common.datastore.sql_legacy import PostgresLegacyIndexEmulator
</del><span class="cx"> from txdav.common.datastore.sql_tables import schema, _BIND_MODE_DIRECT, \
</span><span class="cx">     _BIND_STATUS_ACCEPTED
</span><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom, \
</span><span class="cx">     CommonCommonTests
</span><span class="cx"> from txdav.common.icommondatastore import NoSuchObjectResourceError
</span><del>-from txdav.xml.rfc2518 import GETContentLanguage, ResourceType
</del><span class="cx"> from txdav.idav import ChangeCategory
</span><ins>+from txdav.xml.rfc2518 import GETContentLanguage, ResourceType
</ins><span class="cx"> 
</span><span class="cx"> import datetime
</span><span class="cx"> 
</span><span class="lines">@@ -407,10 +406,10 @@
</span><span class="cx">                           name=&quot;VCALENDAR&quot;,
</span><span class="cx">                        )
</span><span class="cx">                   )
</span><del>-        filter = calendarqueryfilter.Filter(filter)
</del><ins>+        filter = Filter(filter)
</ins><span class="cx">         filter.settimezone(None)
</span><span class="cx"> 
</span><del>-        results = yield toCalendar._index.indexedSearch(filter, 'user01', True)
</del><ins>+        results = yield toCalendar.search(filter, 'user01', True)
</ins><span class="cx">         self.assertEquals(len(results), 1)
</span><span class="cx">         _ignore_name, uid, _ignore_type, _ignore_organizer, _ignore_float, _ignore_start, _ignore_end, _ignore_fbtype, transp = results[0]
</span><span class="cx">         self.assertEquals(uid, &quot;uid4&quot;)
</span><span class="lines">@@ -1369,7 +1368,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_notExpandedWithin(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Test PostgresLegacyIndexEmulator.notExpandedWithin to make sure it returns the correct
</del><ins>+        Test Calendar.notExpandedWithin to make sure it returns the correct
</ins><span class="cx">         result based on the ranges passed in.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="lines">@@ -1378,7 +1377,6 @@
</span><span class="cx">         # Create the index on a new calendar
</span><span class="cx">         home = yield self.homeUnderTest()
</span><span class="cx">         newcalendar = yield home.createCalendarWithName(&quot;index_testing&quot;)
</span><del>-        index = PostgresLegacyIndexEmulator(newcalendar)
</del><span class="cx"> 
</span><span class="cx">         # Create the calendar object to use for testing
</span><span class="cx">         nowYear = self.nowYear[&quot;now&quot;]
</span><span class="lines">@@ -1406,37 +1404,37 @@
</span><span class="cx">         # Fully within range
</span><span class="cx">         testMin = DateTime(nowYear, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><span class="cx">         testMax = DateTime(nowYear + 1, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><del>-        result = yield index.notExpandedWithin(testMin, testMax)
</del><ins>+        result = yield newcalendar.notExpandedWithin(testMin, testMax)
</ins><span class="cx">         self.assertEqual(result, [])
</span><span class="cx"> 
</span><span class="cx">         # Upper bound exceeded
</span><span class="cx">         testMin = DateTime(nowYear, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><span class="cx">         testMax = DateTime(nowYear + 5, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><del>-        result = yield index.notExpandedWithin(testMin, testMax)
</del><ins>+        result = yield newcalendar.notExpandedWithin(testMin, testMax)
</ins><span class="cx">         self.assertEqual(result, [&quot;indexing.ics&quot;])
</span><span class="cx"> 
</span><span class="cx">         # Lower bound exceeded
</span><span class="cx">         testMin = DateTime(nowYear - 5, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><span class="cx">         testMax = DateTime(nowYear + 1, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><del>-        result = yield index.notExpandedWithin(testMin, testMax)
</del><ins>+        result = yield newcalendar.notExpandedWithin(testMin, testMax)
</ins><span class="cx">         self.assertEqual(result, [&quot;indexing.ics&quot;])
</span><span class="cx"> 
</span><span class="cx">         # Lower and upper bounds exceeded
</span><span class="cx">         testMin = DateTime(nowYear - 5, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><span class="cx">         testMax = DateTime(nowYear + 5, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><del>-        result = yield index.notExpandedWithin(testMin, testMax)
</del><ins>+        result = yield newcalendar.notExpandedWithin(testMin, testMax)
</ins><span class="cx">         self.assertEqual(result, [&quot;indexing.ics&quot;])
</span><span class="cx"> 
</span><span class="cx">         # Lower none within range
</span><span class="cx">         testMin = None
</span><span class="cx">         testMax = DateTime(nowYear + 1, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><del>-        result = yield index.notExpandedWithin(testMin, testMax)
</del><ins>+        result = yield newcalendar.notExpandedWithin(testMin, testMax)
</ins><span class="cx">         self.assertEqual(result, [])
</span><span class="cx"> 
</span><span class="cx">         # Lower none and upper bounds exceeded
</span><span class="cx">         testMin = None
</span><span class="cx">         testMax = DateTime(nowYear + 5, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><del>-        result = yield index.notExpandedWithin(testMin, testMax)
</del><ins>+        result = yield newcalendar.notExpandedWithin(testMin, testMax)
</ins><span class="cx">         self.assertEqual(result, [&quot;indexing.ics&quot;])
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoreindex_filepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/index_file.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/index_file.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/index_file.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -37,10 +37,6 @@
</span><span class="cx"> 
</span><span class="cx"> from twisted.internet.defer import maybeDeferred
</span><span class="cx"> 
</span><del>-from twistedcaldav import carddavxml
-from txdav.common.icommondatastore import SyncTokenValidException, \
-    ReservationError
-from twistedcaldav.query import addressbookquery
</del><span class="cx"> from twistedcaldav.sql import AbstractSQLDatabase
</span><span class="cx"> from twistedcaldav.sql import db_prefix
</span><span class="cx"> from twistedcaldav.vcard import Component
</span><span class="lines">@@ -49,6 +45,12 @@
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.memcachepool import CachePoolUserMixIn
</span><span class="cx"> 
</span><ins>+from txdav.carddav.datastore.query.builder import buildExpression
+from txdav.carddav.datastore.query.filter import Filter
+from txdav.common.datastore.query.filegenerator import sqllitegenerator
+from txdav.common.icommondatastore import SyncTokenValidException, \
+    ReservationError
+
</ins><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> db_basename = db_prefix + &quot;sqlite&quot;
</span><span class="lines">@@ -218,6 +220,24 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+def sqladdressbookquery(filter, addressbookid=None):
+    &quot;&quot;&quot;
+    Convert the supplied addressbook-query into a partial SQL statement.
+
+    @param filter: the L{Filter} for the addressbook-query to convert.
+    @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
+            and the C{list} is the list of argument substitutions to use with the SQL API execute method.
+            Or return C{None} if it is not possible to create an SQL query to fully match the addressbook-query.
+    &quot;&quot;&quot;
+    try:
+        expression = buildExpression(filter, sqllitegenerator.FIELDS)
+        sql = sqllitegenerator(expression, addressbookid, None)
+        return sql.generate()
+    except ValueError:
+        return None
+
+
+
</ins><span class="cx"> class AddressBookIndex(AbstractSQLDatabase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     AddressBook collection index abstract base class that defines the apis for the index.
</span><span class="lines">@@ -445,8 +465,8 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def searchValid(self, filter):
</span><del>-        if isinstance(filter, carddavxml.Filter):
-            qualifiers = addressbookquery.sqladdressbookquery(filter)
</del><ins>+        if isinstance(filter, Filter):
+            qualifiers = sqladdressbookquery(filter)
</ins><span class="cx">         else:
</span><span class="cx">             qualifiers = None
</span><span class="cx"> 
</span><span class="lines">@@ -466,8 +486,8 @@
</span><span class="cx">         # start caching...
</span><span class="cx"> 
</span><span class="cx">         # Make sure we have a proper Filter element and get the partial SQL statement to use.
</span><del>-        if isinstance(filter, carddavxml.Filter):
-            qualifiers = addressbookquery.sqladdressbookquery(filter)
</del><ins>+        if isinstance(filter, Filter):
+            qualifiers = sqladdressbookquery(filter)
</ins><span class="cx">         else:
</span><span class="cx">             qualifiers = None
</span><span class="cx">         if qualifiers is not None:
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequery__init__py"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/__init__.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/__init__.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/__init__.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+##
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequerybuilderpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/builder.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/builder.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/builder.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,107 @@
</span><ins>+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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 txdav.common.datastore.query import expression
+from txdav.carddav.datastore.query.filter import TextMatch
+
+&quot;&quot;&quot;
+SQL statement generator from query expressions.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;buildExpression&quot;,
+]
+
+
+
+# SQL Index column (field) names
+
+def buildExpression(filter, fields):
+    &quot;&quot;&quot;
+    Convert the supplied addressbook-query into an expression tree.
+
+    @param filter: the L{Filter} for the addressbook-query to convert.
+    @return: a L{baseExpression} for the expression tree.
+    &quot;&quot;&quot;
+    # Lets assume we have a valid filter from the outset.
+
+    # Top-level filter contains zero or more prop-filter element
+    if len(filter.children) &gt; 0:
+        return propfilterListExpression(filter.children, fields)
+    else:
+        return expression.allExpression()
+
+
+
+def propfilterListExpression(propfilters, fields):
+    &quot;&quot;&quot;
+    Create an expression for a list of prop-filter elements.
+
+    @param propfilters: the C{list} of L{ComponentFilter} elements.
+    @return: a L{baseExpression} for the expression tree.
+    &quot;&quot;&quot;
+
+    if len(propfilters) == 1:
+        return propfilterExpression(propfilters[0], fields)
+    else:
+        return expression.orExpression([propfilterExpression(c, fields) for c in propfilters])
+
+
+
+def propfilterExpression(propfilter, fields):
+    &quot;&quot;&quot;
+    Create an expression for a single prop-filter element.
+
+    @param propfilter: the L{PropertyFilter} element.
+    @return: a L{baseExpression} for the expression tree.
+    &quot;&quot;&quot;
+
+    # Only handle UID right now
+    if propfilter.filter_name != &quot;UID&quot;:
+        raise ValueError
+
+    # Handle is-not-defined case
+    if not propfilter.defined:
+        # Test for &lt;&lt;field&gt;&gt; != &quot;*&quot;
+        return expression.isExpression(fields[&quot;UID&quot;], &quot;&quot;, True)
+
+    # Handle embedded parameters/text-match
+    params = []
+    for filter in propfilter.filters:
+        if isinstance(filter, TextMatch):
+            if filter.match_type == &quot;equals&quot;:
+                tm = expression.isnotExpression if filter.negate else expression.isExpression
+            elif filter.match_type == &quot;contains&quot;:
+                tm = expression.notcontainsExpression if filter.negate else expression.containsExpression
+            elif filter.match_type == &quot;starts-with&quot;:
+                tm = expression.notstartswithExpression if filter.negate else expression.startswithExpression
+            elif filter.match_type == &quot;ends-with&quot;:
+                tm = expression.notendswithExpression if filter.negate else expression.endswithExpression
+            params.append(tm(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) &gt; 1:
+        if propfilter.propfilter_test == &quot;anyof&quot;:
+            return expression.orExpression(params)
+        else:
+            return expression.andExpression(params)
+    elif len(params) == 1:
+        return params[0]
+    else:
+        return None
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequeryfilterpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/filter.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/filter.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/filter.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,314 @@
</span><ins>+##
+# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+##
+
+&quot;&quot;&quot;
+Object model of CARDDAV:filter element used in an addressbook-query.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;Filter&quot;,
+]
+
+from twext.python.log import Logger
+
+from twistedcaldav.carddavxml import carddav_namespace
+from twistedcaldav.vcard import Property
+
+log = Logger()
+
+class FilterBase(object):
+    &quot;&quot;&quot;
+    Determines which matching components are returned.
+    &quot;&quot;&quot;
+
+    def __init__(self, xml_element):
+        self.xmlelement = xml_element
+
+
+    def match(self, item, access=None):
+        raise NotImplementedError
+
+
+    def valid(self, level=0):
+        raise NotImplementedError
+
+
+
+class Filter(FilterBase):
+    &quot;&quot;&quot;
+    Determines which matching components are returned.
+    &quot;&quot;&quot;
+
+    def __init__(self, xml_element):
+
+        super(Filter, self).__init__(xml_element)
+
+        filter_test = xml_element.attributes.get(&quot;test&quot;, &quot;anyof&quot;)
+        if filter_test not in (&quot;anyof&quot;, &quot;allof&quot;):
+            raise ValueError(&quot;Test must be only one of anyof, allof&quot;)
+
+        self.filter_test = filter_test
+
+        self.children = [PropertyFilter(child) for child in xml_element.children]
+
+
+    def match(self, vcard):
+        &quot;&quot;&quot;
+        Returns True if the given address property matches this filter, False
+        otherwise. Empty element means always match.
+        &quot;&quot;&quot;
+
+        if len(self.children) &gt; 0:
+            allof = self.filter_test == &quot;allof&quot;
+            for propfilter in self.children:
+                if allof != propfilter._match(vcard):
+                    return not allof
+            return allof
+        else:
+            return True
+
+
+    def valid(self):
+        &quot;&quot;&quot;
+        Indicate whether this filter element's structure is valid wrt vCard
+        data object model.
+
+        @return: True if valid, False otherwise
+        &quot;&quot;&quot;
+
+        # Test each property
+        for propfilter in self.children:
+            if not propfilter.valid():
+                return False
+        else:
+            return True
+
+
+
+class FilterChildBase(FilterBase):
+    &quot;&quot;&quot;
+    CardDAV filter element.
+    &quot;&quot;&quot;
+
+    def __init__(self, xml_element):
+
+        super(FilterChildBase, self).__init__(xml_element)
+
+        qualifier = None
+        filters = []
+
+        for child in xml_element.children:
+            qname = child.qname()
+
+            if qname in (
+                (carddav_namespace, &quot;is-not-defined&quot;),
+            ):
+                if qualifier is not None:
+                    raise ValueError(&quot;Only one of CardDAV:is-not-defined allowed&quot;)
+                qualifier = IsNotDefined(child)
+
+            elif qname == (carddav_namespace, &quot;text-match&quot;):
+                filters.append(TextMatch(child))
+
+            elif qname == (carddav_namespace, &quot;param-filter&quot;):
+                filters.append(ParameterFilter(child))
+            else:
+                raise ValueError(&quot;Unknown child element: %s&quot; % (qname,))
+
+        if qualifier and isinstance(qualifier, IsNotDefined) and (len(filters) != 0):
+            raise ValueError(&quot;No other tests allowed when CardDAV:is-not-defined is present&quot;)
+
+        if xml_element.qname() == (carddav_namespace, &quot;prop-filter&quot;):
+            propfilter_test = xml_element.attributes.get(&quot;test&quot;, &quot;anyof&quot;)
+            if propfilter_test not in (&quot;anyof&quot;, &quot;allof&quot;):
+                raise ValueError(&quot;Test must be only one of anyof, allof&quot;)
+        else:
+            propfilter_test = &quot;anyof&quot;
+
+        self.propfilter_test = propfilter_test
+        self.qualifier = qualifier
+        self.filters = filters
+        self.filter_name = xml_element.attributes[&quot;name&quot;]
+        if isinstance(self.filter_name, unicode):
+            self.filter_name = self.filter_name.encode(&quot;utf-8&quot;)
+        self.defined = not self.qualifier or not isinstance(qualifier, IsNotDefined)
+
+
+    def match(self, item):
+        &quot;&quot;&quot;
+        Returns True if the given address book item (either a property or parameter value)
+        matches this filter, False otherwise.
+        &quot;&quot;&quot;
+
+        # Always return True for the is-not-defined case as the result of this will
+        # be negated by the caller
+        if not self.defined:
+            return True
+
+        if self.qualifier and not self.qualifier.match(item):
+            return False
+
+        if len(self.filters) &gt; 0:
+            allof = self.propfilter_test == &quot;allof&quot;
+            for filter in self.filters:
+                if allof != filter._match(item):
+                    return not allof
+            return allof
+        else:
+            return True
+
+
+
+class PropertyFilter (FilterChildBase):
+    &quot;&quot;&quot;
+    Limits a search to specific properties.
+    &quot;&quot;&quot;
+
+    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
+        else:
+            return not self.defined
+        return self.defined
+
+
+    def valid(self):
+        &quot;&quot;&quot;
+        Indicate whether this filter element's structure is valid wrt vCard
+        data object model.
+
+        @return:      True if valid, False otherwise
+        &quot;&quot;&quot;
+
+        # No tests
+        return True
+
+
+
+class ParameterFilter (FilterChildBase):
+    &quot;&quot;&quot;
+    Limits a search to specific parameters.
+    &quot;&quot;&quot;
+
+    def _match(self, property):
+
+        # At least one parameter must match (or is-not-defined is set)
+        result = not self.defined
+        for parameterName in property.parameterNames():
+            if parameterName.upper() == self.filter_name.upper() and self.match([property.parameterValues(parameterName)]):
+                result = self.defined
+                break
+
+        return result
+
+
+
+class IsNotDefined (FilterBase):
+    &quot;&quot;&quot;
+    Specifies that the named iCalendar item does not exist.
+    &quot;&quot;&quot;
+
+    def match(self, component, access=None):
+        # Oddly, this needs always to return True so that it appears there is
+        # a match - but we then &quot;negate&quot; the result if is-not-defined is set.
+        # Actually this method should never be called as we special case the
+        # is-not-defined option.
+        return True
+
+
+
+class TextMatch (FilterBase):
+    &quot;&quot;&quot;
+    Specifies a substring match on a property or parameter value.
+    &quot;&quot;&quot;
+    def __init__(self, xml_element):
+
+        super(TextMatch, self).__init__(xml_element)
+
+        self.text = str(xml_element)
+
+        if &quot;collation&quot; in xml_element.attributes:
+            self.collation = xml_element.attributes[&quot;collation&quot;]
+        else:
+            self.collation = &quot;i;unicode-casemap&quot;
+
+        if &quot;negate-condition&quot; in xml_element.attributes:
+            self.negate = xml_element.attributes[&quot;negate-condition&quot;]
+            if self.negate not in (&quot;yes&quot;, &quot;no&quot;):
+                self.negate = &quot;no&quot;
+            self.negate = {&quot;yes&quot;: True, &quot;no&quot;: False}[self.negate]
+        else:
+            self.negate = False
+
+        if &quot;match-type&quot; in xml_element.attributes:
+            self.match_type = xml_element.attributes[&quot;match-type&quot;]
+            if self.match_type not in (
+                &quot;equals&quot;,
+                &quot;contains&quot;,
+                &quot;starts-with&quot;,
+                &quot;ends-with&quot;,
+            ):
+                self.match_type = &quot;contains&quot;
+        else:
+            self.match_type = &quot;contains&quot;
+
+
+    def _match(self, item):
+        &quot;&quot;&quot;
+        Match the text for the item.
+        If the item is a property, then match the property value,
+        otherwise it may be a list of parameter values - try to match anyone of those
+        &quot;&quot;&quot;
+        if item is None:
+            return False
+
+        if isinstance(item, Property):
+            values = [item.strvalue()]
+        else:
+            values = item
+
+        test = unicode(self.text, &quot;utf-8&quot;).lower()
+
+
+        def _textCompare(s):
+            # Currently ignores the collation and does caseless matching
+            s = s.lower()
+
+            if self.match_type == &quot;equals&quot;:
+                return s == test
+            elif self.match_type == &quot;contains&quot;:
+                return s.find(test) != -1
+            elif self.match_type == &quot;starts-with&quot;:
+                return s.startswith(test)
+            elif self.match_type == &quot;ends-with&quot;:
+                return s.endswith(test)
+            else:
+                return False
+
+        for value in values:
+            # NB Its possible that we have a text list value which appears as a Python list,
+            # so we need to check for that and iterate over the list.
+            if isinstance(value, list):
+                for subvalue in value:
+                    if _textCompare(unicode(subvalue, &quot;utf-8&quot;)):
+                        return not self.negate
+            else:
+                if _textCompare(unicode(value, &quot;utf-8&quot;)):
+                    return not self.negate
+
+        return self.negate
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequerytest__init__py"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/__init__.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/__init__.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/__init__.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+##
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequerytesttest_filterpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/test_filter.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/test_filter.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/test_filter.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,74 @@
</span><ins>+##
+# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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 twext.enterprise.dal.syntax import SQLFragment
+
+from twisted.trial.unittest import TestCase
+
+from twistedcaldav import carddavxml
+
+from txdav.carddav.datastore.query.filter import Filter
+from txdav.common.datastore.sql_tables import schema
+from txdav.carddav.datastore.query.builder import buildExpression
+from txdav.common.datastore.query.generator import SQLQueryGenerator
+from txdav.carddav.datastore.index_file import sqladdressbookquery
+
+class TestQueryFilter(TestCase):
+
+    _objectSchema = schema.ADDRESSBOOK_OBJECT
+    _queryFields = {
+        &quot;UID&quot;: _objectSchema.UID
+    }
+
+    def test_query(self):
+        &quot;&quot;&quot;
+        Basic query test - single term.
+        Only UID can be queried via sql.
+        &quot;&quot;&quot;
+
+        filter = carddavxml.Filter(
+            *[carddavxml.PropertyFilter(
+                carddavxml.TextMatch.fromString(&quot;Example&quot;),
+                **{&quot;name&quot;:&quot;UID&quot;}
+            )]
+        )
+        filter = Filter(filter)
+
+        expression = buildExpression(filter, self._queryFields)
+        sql = SQLQueryGenerator(expression, self, 1234)
+        select, args = sql.generate()
+
+        self.assertEqual(select.toSQL(), SQLFragment(&quot;select distinct RESOURCE_NAME, VCARD_UID from ADDRESSBOOK_OBJECT where ADDRESSBOOK_HOME_RESOURCE_ID = ? and VCARD_UID like (? || (? || ?))&quot;, [1234, &quot;%&quot;, &quot;Example&quot;, &quot;%&quot;]))
+        self.assertEqual(args, {})
+
+
+    def test_sqllite_query(self):
+        &quot;&quot;&quot;
+        Basic query test - single term.
+        Only UID can be queried via sql.
+        &quot;&quot;&quot;
+
+        filter = carddavxml.Filter(
+            *[carddavxml.PropertyFilter(
+                carddavxml.TextMatch.fromString(&quot;Example&quot;),
+                **{&quot;name&quot;:&quot;UID&quot;}
+            )]
+        )
+        filter = Filter(filter)
+        sql, args = sqladdressbookquery(filter, 1234)
+
+        self.assertEqual(sql, &quot; from RESOURCE where RESOURCE.UID GLOB :1&quot;)
+        self.assertEqual(args, [&quot;*Example*&quot;])
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -15,6 +15,7 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> # #
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> SQL backend for CardDAV storage.
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="lines">@@ -46,12 +47,14 @@
</span><span class="cx"> 
</span><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><span class="cx"> from txdav.base.propertystore.sql import PropertyStore
</span><ins>+from txdav.carddav.datastore.query.builder import buildExpression
+from txdav.carddav.datastore.query.filter import Filter
</ins><span class="cx"> from txdav.carddav.iaddressbookstore import IAddressBookHome, IAddressBook, \
</span><span class="cx">     IAddressBookObject, GroupWithUnsharedAddressNotAllowedError, \
</span><span class="cx">     KindChangeNotAllowedError
</span><ins>+from txdav.common.datastore.query.generator import SQLQueryGenerator
</ins><span class="cx"> from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
</span><span class="cx">     CommonObjectResource, EADDRESSBOOKTYPE, SharingMixIn, SharingInvitation
</span><del>-from txdav.common.datastore.sql_legacy import PostgresLegacyABIndexEmulator
</del><span class="cx"> from txdav.common.datastore.sql_tables import _ABO_KIND_PERSON, \
</span><span class="cx">     _ABO_KIND_GROUP, _ABO_KIND_RESOURCE, _ABO_KIND_LOCATION, schema, \
</span><span class="cx">     _BIND_MODE_OWN, _BIND_MODE_WRITE, _BIND_STATUS_ACCEPTED, \
</span><span class="lines">@@ -60,7 +63,8 @@
</span><span class="cx"> from txdav.common.icommondatastore import InternalDataStoreError, \
</span><span class="cx">     InvalidUIDError, UIDExistsError, ObjectResourceTooBigError, \
</span><span class="cx">     InvalidObjectResourceError, InvalidComponentForStoreError, \
</span><del>-    AllRetriesFailed, ObjectResourceNameAlreadyExistsError
</del><ins>+    AllRetriesFailed, ObjectResourceNameAlreadyExistsError, \
+    IndexedSearchException
</ins><span class="cx"> from txdav.xml import element
</span><span class="cx"> 
</span><span class="cx"> from zope.interface.declarations import implements
</span><span class="lines">@@ -439,7 +443,12 @@
</span><span class="cx">     _revisionsSchema = schema.ADDRESSBOOK_OBJECT_REVISIONS
</span><span class="cx">     _objectSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="cx"> 
</span><ins>+    # Mapping of vCard property name to DB column name
+    _queryFields = {
+        &quot;UID&quot;: _objectSchema.UID,
+    }
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _getDBDataIndirect(cls, home, name, resourceID, externalID):
</span><span class="lines">@@ -515,7 +524,6 @@
</span><span class="cx">     def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, externalID=None):
</span><span class="cx">         ownerName = ownerHome.addressbook().name() if ownerHome else None
</span><span class="cx">         super(AddressBook, self).__init__(home, name, resourceID, mode, status, revision=revision, message=message, ownerHome=ownerHome, ownerName=ownerName, externalID=externalID)
</span><del>-        self._index = PostgresLegacyABIndexEmulator(self)
</del><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def __repr__(self):
</span><span class="lines">@@ -788,6 +796,50 @@
</span><span class="cx">             returnValue((yield super(AddressBook, self).bumpModified()))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def search(self, filter):
+        &quot;&quot;&quot;
+        Finds resources matching the given qualifiers.
+        @param filter: the L{Filter} for the addressbook-query to execute.
+        @return: an iterable of tuples for each resource matching the
+            given C{qualifiers}. The tuples are C{(name, uid)}, where
+            C{name} is the resource name, C{uid} is the resource UID.
+        &quot;&quot;&quot;
+
+        # Make sure we have a proper Filter element and get the partial SQL statement to use.
+        sql_stmt = self._sqlquery(filter)
+
+        # No result means it is too complex for us
+        if sql_stmt is None:
+            raise IndexedSearchException()
+
+        sql_stmt, args = sql_stmt
+        rowiter = yield sql_stmt.on(self._txn, **args)
+
+        returnValue(list(rowiter))
+
+
+    def _sqlquery(self, filter):
+        &quot;&quot;&quot;
+        Convert the supplied addressbook-query into a partial SQL statement.
+
+        @param filter: the L{Filter} for the addressbook-query to convert.
+        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
+                and the C{list} is the list of argument substitutions to use with the SQL API execute method.
+                Or return C{None} if it is not possible to create an SQL query to fully match the addressbook-query.
+        &quot;&quot;&quot;
+
+        if not isinstance(filter, Filter):
+            return None
+
+        try:
+            expression = buildExpression(filter, self._queryFields)
+            sql = SQLQueryGenerator(expression, self, self.id())
+            return sql.generate()
+        except ValueError:
+            return None
+
+
</ins><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def listObjects(cls, home):
</span><span class="lines">@@ -1989,6 +2041,10 @@
</span><span class="cx"> 
</span><span class="cx">         if isinstance(component, str) or isinstance(component, unicode):
</span><span class="cx">             component = self._componentClass.fromString(component)
</span><ins>+            try:
+                component = self._componentClass.fromString(component)
+            except InvalidVCardDataError as e:
+                raise InvalidComponentForStoreError(str(e))
</ins><span class="cx"> 
</span><span class="cx">         self._componentChanged = False
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoretesttest_sql_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql_sharing.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql_sharing.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql_sharing.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -1178,13 +1178,15 @@
</span><span class="cx"> 
</span><span class="cx">         otherHome = yield self.addressbookHomeUnderTest(name=&quot;user02&quot;)
</span><span class="cx">         for depth in (&quot;1&quot;, &quot;infinity&quot;,):
</span><del>-            changed, deleted = yield otherHome.resourceNamesSinceRevision(otherAB._bindRevision - 1, depth)
</del><ins>+            changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(otherAB._bindRevision - 1, depth)
</ins><span class="cx">             self.assertNotEqual(len(changed), 0)
</span><span class="cx">             self.assertEqual(len(deleted), 0)
</span><ins>+            self.assertEqual(len(invalid), 0)
</ins><span class="cx"> 
</span><del>-            changed, deleted = yield otherHome.resourceNamesSinceRevision(otherAB._bindRevision, depth)
</del><ins>+            changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(otherAB._bindRevision, depth)
</ins><span class="cx">             self.assertEqual(len(changed), 0)
</span><span class="cx">             self.assertEqual(len(deleted), 0)
</span><ins>+            self.assertEqual(len(invalid), 0)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequery__init__py"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/__init__.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/__init__.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/__init__.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+##
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequeryexpressionpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/expression.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/expression.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/expression.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,382 @@
</span><ins>+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+##
+
+&quot;&quot;&quot;
+Query Expression Elements. These are used to build a 'generic' query
+expression tree that can then be used by different query language
+generators to produce the actual query syntax required (SQL, xpath
+etc).
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;allExpression&quot;,
+    &quot;notExpression&quot;,
+    &quot;andExpression&quot;,
+    &quot;orExpression&quot;,
+    &quot;timerangeExpression&quot;,
+    &quot;textcompareExpression&quot;,
+    &quot;containsExpression&quot;,
+    &quot;notcontainsExpression&quot;,
+    &quot;isExpression&quot;,
+    &quot;isnotExpression&quot;,
+    &quot;startswithExpression&quot;,
+    &quot;notstartswithExpression&quot;,
+    &quot;endswithExpression&quot;,
+    &quot;notendswithExpression&quot;,
+    &quot;inExpression&quot;,
+    &quot;notinExpression&quot;,
+]
+
+class baseExpression(object):
+    &quot;&quot;&quot;
+    The base class for all types of expression.
+    &quot;&quot;&quot;
+
+    def __init__(self):
+        pass
+
+
+    def multi(self):
+        &quot;&quot;&quot;
+        Indicate whether this expression is composed of multiple sub-expressions.
+
+        @return: C{True} if this expressions contains multiple sub-expressions,
+            C{False} otherwise.
+        &quot;&quot;&quot;
+
+        return False
+
+
+    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):
+    &quot;&quot;&quot;
+    Match everything.
+    &quot;&quot;&quot;
+
+    def __init__(self):
+        pass
+
+
+
+class logicExpression(baseExpression):
+    &quot;&quot;&quot;
+    An expression representing a logical operation (boolean).
+    &quot;&quot;&quot;
+
+    def __init__(self, expressions):
+        self.expressions = expressions
+
+
+    def __str__(self):
+        &quot;&quot;&quot;
+        Generate a suitable text descriptor of this expression.
+
+        @return: a C{str} of the text for this expression.
+        &quot;&quot;&quot;
+
+        result = &quot;&quot;
+        for e in self.expressions:
+            if len(result) != 0:
+                result += &quot; &quot; + self.operator() + &quot; &quot;
+            result += str(e)
+        if len(result):
+            result = &quot;(&quot; + result + &quot;)&quot;
+        return result
+
+
+    def multi(self):
+        &quot;&quot;&quot;
+        Indicate whether this expression is composed of multiple expressions.
+
+        @return: C{True} if this expressions contains multiple sub-expressions,
+            C{False} otherwise.
+        &quot;&quot;&quot;
+
+        return True
+
+
+    def _collapsedExpression(self):
+        if self.multi() and len(self.expressions) == 1:
+            return self.expressions[0]._collapsedExpression()
+        else:
+            return self
+
+
+
+class notExpression(logicExpression):
+    &quot;&quot;&quot;
+    Logical NOT operation.
+    &quot;&quot;&quot;
+
+    def __init__(self, expression):
+        super(notExpression, self).__init__([expression])
+
+
+    def operator(self):
+        return &quot;NOT&quot;
+
+
+    def __str__(self):
+        result = self.operator() + &quot; &quot; + str(self.expressions[0])
+        return result
+
+
+    def multi(self):
+        &quot;&quot;&quot;
+        Indicate whether this expression is composed of multiple expressions.
+
+        @return: C{True} if this expressions contains multiple sub-expressions,
+            C{False} otherwise.
+        &quot;&quot;&quot;
+
+        return False
+
+
+
+class andExpression(logicExpression):
+    &quot;&quot;&quot;
+    Logical AND operation.
+    &quot;&quot;&quot;
+
+    def __init__(self, expressions):
+        super(andExpression, self).__init__(expressions)
+
+
+    def operator(self):
+        return &quot;AND&quot;
+
+
+    def andWith(self, other):
+        self.expressions = tuple(self.expressions) + (other._collapsedExpression(),)
+        return self
+
+
+
+class orExpression(logicExpression):
+    &quot;&quot;&quot;
+    Logical OR operation.
+    &quot;&quot;&quot;
+
+    def __init__(self, expressions):
+        super(orExpression, self).__init__(expressions)
+
+
+    def operator(self):
+        return &quot;OR&quot;
+
+
+    def orWith(self, other):
+        self.expressions = tuple(self.expressions) + (other._collapsedExpression(),)
+        return self
+
+
+
+class timerangeExpression(baseExpression):
+    &quot;&quot;&quot;
+    CalDAV time-range comparison expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, start, end, startfloat, endfloat):
+        self.start = start
+        self.end = end
+        self.startfloat = startfloat
+        self.endfloat = endfloat
+
+
+    def __str__(self):
+        return &quot;timerange(&quot; + str(self.start) + &quot;, &quot; + str(self.end) + &quot;)&quot;
+
+
+
+class textcompareExpression(baseExpression):
+    &quot;&quot;&quot;
+    Base class for text comparison expressions.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        self.field = field
+        self.text = text
+        self.caseless = caseless
+
+
+    def __str__(self):
+        return self.operator() + &quot;(&quot; + self.field + &quot;, &quot; + self.text + &quot;, &quot; + str(self.caseless) + &quot;)&quot;
+
+
+
+class containsExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text CONTAINS (sub-string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(containsExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;contains&quot;
+
+
+
+class notcontainsExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text NOT CONTAINS (sub-string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(notcontainsExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;does not contain&quot;
+
+
+
+class isExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text IS (exact string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(isExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;is&quot;
+
+
+
+class isnotExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text IS NOT (exact string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(isnotExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;is not&quot;
+
+
+
+class startswithExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text STARTSWITH (sub-string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(startswithExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;starts with&quot;
+
+
+
+class notstartswithExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text NOT STARTSWITH (sub-string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(notstartswithExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;does not start with&quot;
+
+
+
+class endswithExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text STARTSWITH (sub-string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(endswithExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;ends with&quot;
+
+
+
+class notendswithExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text NOT STARTSWITH (sub-string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(notendswithExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;does not end with&quot;
+
+
+
+class inExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text IN (exact string match to one of the supplied items) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text_list, caseless):
+        super(inExpression, self).__init__(field, text_list, caseless)
+
+
+    def operator(self):
+        return &quot;in&quot;
+
+
+    def __str__(self):
+        return self.operator() + &quot;(&quot; + self.field + &quot;, &quot; + str(self.text) + &quot;, &quot; + str(self.caseless) + &quot;)&quot;
+
+
+
+class notinExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text NOT IN (exact string match to none of the supplied items) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(notinExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;not in&quot;
+
+
+    def __str__(self):
+        return self.operator() + &quot;(&quot; + self.field + &quot;, &quot; + str(self.text) + &quot;, &quot; + str(self.caseless) + &quot;)&quot;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequeryfilegeneratorpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/filegenerator.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/filegenerator.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/filegenerator.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,322 @@
</span><ins>+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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 txdav.common.datastore.query import expression
+
+&quot;&quot;&quot;
+SQLLite statement generator from query expressions.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;sqllitegenerator&quot;,
+]
+
+import cStringIO as StringIO
+
+class sqllitegenerator(object):
+
+    FROM = &quot; from &quot;
+    WHERE = &quot; where &quot;
+    RESOURCEDB = &quot;RESOURCE&quot;
+    TIMESPANDB = &quot;TIMESPAN&quot;
+    TRANSPARENCYDB = &quot;TRANSPARENCY&quot;
+    PERUSERDB = &quot;PERUSER&quot;
+    NOTOP = &quot;NOT &quot;
+    ANDOP = &quot; AND &quot;
+    OROP = &quot; OR &quot;
+    CONTAINSOP = &quot; GLOB &quot;
+    NOTCONTAINSOP = &quot; NOT GLOB &quot;
+    ISOP = &quot; == &quot;
+    ISNOTOP = &quot; != &quot;
+    STARTSWITHOP = &quot; GLOB &quot;
+    NOTSTARTSWITHOP = &quot; NOT GLOB &quot;
+    ENDSWITHOP = &quot; GLOB &quot;
+    NOTENDSWITHOP = &quot; NOT GLOB &quot;
+    INOP = &quot; IN &quot;
+    NOTINOP = &quot; NOT IN &quot;
+
+    FIELDS = {
+        &quot;TYPE&quot;: &quot;RESOURCE.TYPE&quot;,
+        &quot;UID&quot;: &quot;RESOURCE.UID&quot;,
+    }
+
+    TIMESPANTEST = &quot;((TIMESPAN.FLOAT == 'N' AND TIMESPAN.START &lt; %s AND TIMESPAN.END &gt; %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.START &lt; %s AND TIMESPAN.END &gt; %s))&quot;
+    TIMESPANTEST_NOEND = &quot;((TIMESPAN.FLOAT == 'N' AND TIMESPAN.END &gt; %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.END &gt; %s))&quot;
+    TIMESPANTEST_NOSTART = &quot;((TIMESPAN.FLOAT == 'N' AND TIMESPAN.START &lt; %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.START &lt; %s))&quot;
+    TIMESPANTEST_TAIL_PIECE = &quot; AND TIMESPAN.RESOURCEID == RESOURCE.RESOURCEID&quot;
+    TIMESPANTEST_JOIN_ON_PIECE = &quot;TIMESPAN.INSTANCEID == TRANSPARENCY.INSTANCEID AND TRANSPARENCY.PERUSERID == %s&quot;
+
+    def __init__(self, expr, calendarid, userid, freebusy=False):
+        &quot;&quot;&quot;
+
+        @param expr: the query expression object model
+        @type expr: L{Filter}
+        @param calendarid: resource ID - not used for file-based per-calendar indexes
+        @type calendarid: C{int}
+        @param userid: user for whom query is being done - query will be scoped to that user's privileges and their transparency
+        @type userid: C{str}
+        @param freebusy: whether or not a freebusy query is being done - if it is, additional time range and transparency information is returned
+        @type freebusy: C{bool}
+        &quot;&quot;&quot;
+        self.expression = expr
+        self.calendarid = calendarid
+        self.userid = userid if userid else &quot;&quot;
+        self.freebusy = freebusy
+        self.usedtimespan = False
+
+
+    def generate(self):
+        &quot;&quot;&quot;
+        Generate the actual SQL 'where ...' expression from the passed in expression tree.
+
+        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
+            and the C{list} is the list of argument substitutions to use with the SQL API execute method.
+        &quot;&quot;&quot;
+
+        # Init state
+        self.sout = StringIO.StringIO()
+        self.arguments = []
+        self.substitutions = []
+        self.usedtimespan = False
+
+        # Generate ' where ...' partial statement
+        self.generateExpression(self.expression)
+
+        # Prefix with ' from ...' partial statement
+        select = self.FROM + self.RESOURCEDB
+        if self.usedtimespan:
+
+            # Free busy needs transparency join
+            if self.freebusy:
+                self.frontArgument(self.userid)
+                select += &quot;, %s LEFT OUTER JOIN %s ON (%s)&quot; % (
+                    self.TIMESPANDB,
+                    self.TRANSPARENCYDB,
+                    self.TIMESPANTEST_JOIN_ON_PIECE
+                )
+            else:
+                select += &quot;, %s&quot; % (
+                    self.TIMESPANDB,
+                )
+        select += self.WHERE
+        if self.usedtimespan:
+            select += &quot;(&quot;
+        select += self.sout.getvalue()
+        if self.usedtimespan:
+            if self.calendarid:
+                self.setArgument(self.calendarid)
+            select += &quot;)%s&quot; % (self.TIMESPANTEST_TAIL_PIECE,)
+
+        select = select % tuple(self.substitutions)
+
+        return select, self.arguments
+
+
+    def generateExpression(self, expr):
+        &quot;&quot;&quot;
+        Generate an expression and all it's subexpressions.
+
+        @param expr: the L{baseExpression} derived class to write out.
+        @return: C{True} if the TIMESPAN table is used, C{False} otherwise.
+        &quot;&quot;&quot;
+
+        # Generate based on each type of expression we might encounter
+
+        # ALL
+        if isinstance(expr, expression.allExpression):
+            # Wipe out the ' where ...' clause so everything is matched
+            self.sout.truncate(0)
+            self.arguments = []
+            self.substitutions = []
+            self.usedtimespan = False
+
+        # NOT
+        elif isinstance(expr, expression.notExpression):
+            self.sout.write(self.NOTOP)
+            self.generateSubExpression(expr.expressions[0])
+
+        # AND
+        elif isinstance(expr, expression.andExpression):
+            first = True
+            for e in expr.expressions:
+                if first:
+                    first = False
+                else:
+                    self.sout.write(self.ANDOP)
+                self.generateSubExpression(e)
+
+        # OR
+        elif isinstance(expr, expression.orExpression):
+            first = True
+            for e in expr.expressions:
+                if first:
+                    first = False
+                else:
+                    self.sout.write(self.OROP)
+                self.generateSubExpression(e)
+
+        # time-range
+        elif isinstance(expr, expression.timerangeExpression):
+            if expr.start and expr.end:
+                self.setArgument(expr.end)
+                self.setArgument(expr.start)
+                self.setArgument(expr.endfloat)
+                self.setArgument(expr.startfloat)
+                test = self.TIMESPANTEST
+            elif expr.start and expr.end is None:
+                self.setArgument(expr.start)
+                self.setArgument(expr.startfloat)
+                test = self.TIMESPANTEST_NOEND
+            elif not expr.start and expr.end:
+                self.setArgument(expr.end)
+                self.setArgument(expr.endfloat)
+                test = self.TIMESPANTEST_NOSTART
+
+            self.sout.write(test)
+            self.usedtimespan = True
+
+        # CONTAINS
+        elif isinstance(expr, expression.containsExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.CONTAINSOP)
+            self.addArgument(self.containsArgument(expr.text))
+
+        # NOT CONTAINS
+        elif isinstance(expr, expression.notcontainsExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.NOTCONTAINSOP)
+            self.addArgument(self.containsArgument(expr.text))
+
+        # IS
+        elif isinstance(expr, expression.isExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.ISOP)
+            self.addArgument(expr.text)
+
+        # IS NOT
+        elif isinstance(expr, expression.isnotExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.ISNOTOP)
+            self.addArgument(expr.text)
+
+        # STARTSWITH
+        elif isinstance(expr, expression.startswithExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.STARTSWITHOP)
+            self.addArgument(self.startswithArgument(expr.text))
+
+        # NOT STARTSWITH
+        elif isinstance(expr, expression.notstartswithExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.NOTSTARTSWITHOP)
+            self.addArgument(self.startswithArgument(expr.text))
+
+        # ENDSWITH
+        elif isinstance(expr, expression.endswithExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.ENDSWITHOP)
+            self.addArgument(self.endswithArgument(expr.text))
+
+        # NOT ENDSWITH
+        elif isinstance(expr, expression.notendswithExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.NOTENDSWITHOP)
+            self.addArgument(self.endswithArgument(expr.text))
+
+        # IN
+        elif isinstance(expr, expression.inExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.INOP)
+            self.sout.write(&quot;(&quot;)
+            for count, item in enumerate(expr.text):
+                if count != 0:
+                    self.sout.write(&quot;, &quot;)
+                self.addArgument(item)
+            self.sout.write(&quot;)&quot;)
+
+        # NOT IN
+        elif isinstance(expr, expression.notinExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.NOTINOP)
+            self.sout.write(&quot;(&quot;)
+            for count, item in enumerate(expr.text):
+                if count != 0:
+                    self.sout.write(&quot;, &quot;)
+                self.addArgument(item)
+            self.sout.write(&quot;)&quot;)
+
+
+    def generateSubExpression(self, expression):
+        &quot;&quot;&quot;
+        Generate an SQL expression possibly in parenthesis if its a compound expression.
+
+        @param expression: the L{baseExpression} to write out.
+        @return: C{True} if the TIMESPAN table is used, C{False} otherwise.
+        &quot;&quot;&quot;
+
+        if expression.multi():
+            self.sout.write(&quot;(&quot;)
+        self.generateExpression(expression)
+        if expression.multi():
+            self.sout.write(&quot;)&quot;)
+
+
+    def addArgument(self, arg):
+        &quot;&quot;&quot;
+
+        @param arg: the C{str} of the argument to add
+        &quot;&quot;&quot;
+
+        # Append argument to the list and add the appropriate substitution string to the output stream.
+        self.arguments.append(arg)
+        self.substitutions.append(&quot;:&quot; + str(len(self.arguments)))
+        self.sout.write(&quot;%s&quot;)
+
+
+    def setArgument(self, arg):
+        &quot;&quot;&quot;
+
+        @param arg: the C{str} of the argument to add
+        @return: C{str} for argument substitution text
+        &quot;&quot;&quot;
+
+        # Append argument to the list and add the appropriate substitution string to the output stream.
+        self.arguments.append(arg)
+        self.substitutions.append(&quot;:&quot; + str(len(self.arguments)))
+
+
+    def frontArgument(self, arg):
+        &quot;&quot;&quot;
+
+        @param arg: the C{str} of the argument to add
+        @return: C{str} for argument substitution text
+        &quot;&quot;&quot;
+
+        # Append argument to the list and add the appropriate substitution string to the output stream.
+        self.arguments.insert(0, arg)
+        self.substitutions.append(&quot;:&quot; + str(len(self.arguments)))
+
+
+    def containsArgument(self, arg):
+        return &quot;*%s*&quot; % (arg,)
+
+
+    def startswithArgument(self, arg):
+        return &quot;%s*&quot; % (arg,)
+
+
+    def endswithArgument(self, arg):
+        return &quot;*%s&quot; % (arg,)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequerygeneratorpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/generator.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/generator.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/generator.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,164 @@
</span><ins>+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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 twext.enterprise.dal.syntax import Select, Parameter, Not
+from txdav.common.datastore.query import expression
+
+&quot;&quot;&quot;
+SQL statement generator from query expressions.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;SQLQueryGenerator&quot;,
+]
+
+class SQLQueryGenerator(object):
+
+    def __init__(self, expr, collection, whereid):
+        &quot;&quot;&quot;
+
+        @param expr: the query expression object model
+        @type expr: L{expression}
+        @param collection: the resource targeted by the query
+        @type collection: L{CommonHomeChild}
+        &quot;&quot;&quot;
+        self.expression = expr
+        self.collection = collection
+        self.whereid = whereid
+
+
+    def generate(self):
+        &quot;&quot;&quot;
+        Generate the actual SQL statement from the passed in expression tree.
+
+        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
+            and the C{list} is the list of argument substitutions to use with the SQL API execute method.
+        &quot;&quot;&quot;
+
+        # Init state
+        self.arguments = {}
+        self.argcount = 0
+        obj = self.collection._objectSchema
+
+        columns = [obj.RESOURCE_NAME, obj.UID]
+
+        # For SQL data DB we need to restrict the query to just the targeted collection resource-id if provided
+        if self.whereid:
+            # AND the whole thing
+            test = expression.isExpression(obj.PARENT_RESOURCE_ID, self.whereid, True)
+            self.expression = test if isinstance(self.expression, expression.allExpression) else test.andWith(self.expression)
+
+        # Generate ' where ...' partial statement
+        where = self.generateExpression(self.expression)
+
+        select = Select(
+            columns,
+            From=obj,
+            Where=where,
+            Distinct=True,
+        )
+
+        return select, self.arguments
+
+
+    def generateExpression(self, expr):
+        &quot;&quot;&quot;
+        Generate an expression and all it's subexpressions.
+
+        @param expr: the L{baseExpression} derived class to write out.
+        &quot;&quot;&quot;
+
+        # Generate based on each type of expression we might encounter
+        partial = None
+
+        # ALL
+        if isinstance(expr, expression.allExpression):
+            # Everything is matched
+            partial = None
+            self.arguments = {}
+
+        # NOT
+        elif isinstance(expr, expression.notExpression):
+            partial = Not(self.generateExpression(expr.expressions[0]))
+
+        # AND
+        elif isinstance(expr, expression.andExpression):
+            for e in expr.expressions:
+                next = self.generateExpression(e)
+                partial = partial.And(next) if partial is not None else next
+
+        # OR
+        elif isinstance(expr, expression.orExpression):
+            for e in expr.expressions:
+                next = self.generateExpression(e)
+                partial = partial.Or(next) if partial is not None else next
+
+        # CONTAINS
+        elif isinstance(expr, expression.containsExpression):
+            partial = expr.field.Contains(expr.text)
+
+        # NOT CONTAINS
+        elif isinstance(expr, expression.notcontainsExpression):
+            partial = expr.field.NotContains(expr.text)
+
+        # IS
+        elif isinstance(expr, expression.isExpression):
+            partial = expr.field == expr.text
+
+        # IS NOT
+        elif isinstance(expr, expression.isnotExpression):
+            partial = expr.field != expr.text
+
+        # STARTSWITH
+        elif isinstance(expr, expression.startswithExpression):
+            partial = expr.field.StartsWith(expr.text)
+
+        # NOT STARTSWITH
+        elif isinstance(expr, expression.notstartswithExpression):
+            partial = expr.field.NotStartsWith(expr.text)
+
+        # ENDSWITH
+        elif isinstance(expr, expression.endswithExpression):
+            partial = expr.field.EndsWith(expr.text)
+
+        # NOT ENDSWITH
+        elif isinstance(expr, expression.notendswithExpression):
+            partial = expr.field.NotEndsWith(expr.text)
+
+        # IN
+        elif isinstance(expr, expression.inExpression):
+            argname = self.addArgument(expr.text)
+            partial = expr.field.In(Parameter(argname, len(expr.text)))
+
+        # NOT IN
+        elif isinstance(expr, expression.notinExpression):
+            argname = self.addArgument(expr.text)
+            partial = expr.field.NotIn(Parameter(argname, len(expr.text)))
+
+        return partial
+
+
+    def addArgument(self, arg):
+        &quot;&quot;&quot;
+
+        @param arg: the C{str} of the argument to add
+        &quot;&quot;&quot;
+
+        # Append argument to the list and add the appropriate substitution string to the output stream.
+        self.argcount += 1
+        argname = &quot;arg{}&quot;.format(self.argcount)
+        self.arguments[argname] = arg
+        return argname
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequerytest__init__py"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/__init__.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/__init__.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/__init__.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+##
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequerytesttest_expressionpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/test_expression.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/test_expression.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/test_expression.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,167 @@
</span><ins>+##
+# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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 txdav.common.datastore.query import expression
+from twisted.trial.unittest import TestCase
+
+class Tests(TestCase):
+
+    def test_andWith(self):
+
+        tests = (
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.andExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.andExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                    expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                )),
+                &quot;(is(A, 1, True) AND is(B, 2, True) AND is(C, 3, True))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.orExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.orExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                    expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                )),
+                &quot;(is(A, 1, True) AND (is(B, 2, True) OR is(C, 3, True)))&quot;
+            ),
+            (
+                expression.andExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                )),
+                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
+            ),
+            (
+                expression.andExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                &quot;(is(A, 1, True) AND is(B, 2, True) AND is(C, 3, True))&quot;
+            ),
+            (
+                expression.orExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                )),
+                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
+            ),
+            (
+                expression.orExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                &quot;((is(A, 1, True) OR is(B, 2, True)) AND is(C, 3, True))&quot;
+            ),
+        )
+
+        for expr1, expr2, result in tests:
+            self.assertEqual(str(expr1.andWith(expr2)), result, msg=&quot;Failed on %s&quot; % (result,))
+
+
+    def test_orWith(self):
+
+        tests = (
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.andExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.andExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                    expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                )),
+                &quot;(is(A, 1, True) OR (is(B, 2, True) AND is(C, 3, True)))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.orExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.orExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                    expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                )),
+                &quot;(is(A, 1, True) OR is(B, 2, True) OR is(C, 3, True))&quot;
+            ),
+            (
+                expression.andExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                )),
+                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
+            ),
+            (
+                expression.andExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                &quot;((is(A, 1, True) AND is(B, 2, True)) OR is(C, 3, True))&quot;
+            ),
+            (
+                expression.orExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                )),
+                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
+            ),
+            (
+                expression.orExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                &quot;(is(A, 1, True) OR is(B, 2, True) OR is(C, 3, True))&quot;
+            ),
+        )
+
+        for expr1, expr2, result in tests:
+            self.assertEqual(str(expr1.orWith(expr2)), result, msg=&quot;Failed on %s&quot; % (result,))
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequerytesttest_generatorpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/test_generator.py (0 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/test_generator.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/test_generator.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -0,0 +1,127 @@
</span><ins>+##
+# Copyright (c) 2012-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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 twext.enterprise.dal.syntax import SQLFragment, Parameter
+from txdav.common.datastore.query.generator import SQLQueryGenerator
+from txdav.common.datastore.query import expression
+
+&quot;&quot;&quot;
+Tests for L{txdav.common.datastore.sql}.
+&quot;&quot;&quot;
+
+from twisted.trial.unittest import TestCase
+
+from txdav.common.datastore.sql_tables import schema
+
+class SQLQueryGeneratorTests(TestCase):
+    &quot;&quot;&quot;
+    Tests for shared functionality in L{txdav.common.datastore.sql}.
+    &quot;&quot;&quot;
+
+    class FakeHomeChild(object):
+        _objectSchema = schema.CALENDAR_OBJECT
+
+        def id(self):
+            return 1234
+
+
+    def test_all_query(self):
+
+        expr = expression.allExpression()
+        resource = self.FakeHomeChild()
+        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
+        self.assertEqual(select.toSQL(), SQLFragment(&quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ?&quot;, [1234]))
+        self.assertEqual(args, {})
+
+
+    def test_uid_query(self):
+
+        resource = self.FakeHomeChild()
+        obj = resource._objectSchema
+        expr = expression.isExpression(obj.UID, 5678, False)
+        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
+        self.assertEqual(select.toSQL(), SQLFragment(&quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_UID = ?&quot;, [1234, 5678]))
+        self.assertEqual(args, {})
+
+
+    def test_or_query(self):
+
+        resource = self.FakeHomeChild()
+        obj = resource._objectSchema
+        expr = expression.orExpression((
+            expression.isExpression(obj.UID, 5678, False),
+            expression.isnotExpression(obj.RESOURCE_NAME, &quot;foobar.ics&quot;, False),
+        ))
+        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
+        self.assertEqual(
+            select.toSQL(),
+            SQLFragment(
+                &quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and (ICALENDAR_UID = ? or RESOURCE_NAME != ?)&quot;,
+                [1234, 5678, &quot;foobar.ics&quot;]
+            )
+        )
+        self.assertEqual(args, {})
+
+
+    def test_and_query(self):
+
+        resource = self.FakeHomeChild()
+        obj = resource._objectSchema
+        expr = expression.andExpression((
+            expression.isExpression(obj.UID, 5678, False),
+            expression.isnotExpression(obj.RESOURCE_NAME, &quot;foobar.ics&quot;, False),
+        ))
+        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
+        self.assertEqual(
+            select.toSQL(),
+            SQLFragment(
+                &quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_UID = ? and RESOURCE_NAME != ?&quot;,
+                [1234, 5678, &quot;foobar.ics&quot;]
+            )
+        )
+        self.assertEqual(args, {})
+
+
+    def test_not_query(self):
+
+        resource = self.FakeHomeChild()
+        obj = resource._objectSchema
+        expr = expression.notExpression(expression.isExpression(obj.UID, 5678, False))
+        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
+        self.assertEqual(
+            select.toSQL(),
+            SQLFragment(
+                &quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and not ICALENDAR_UID = ?&quot;,
+                [1234, 5678]
+            )
+        )
+        self.assertEqual(args, {})
+
+
+    def test_in_query(self):
+
+        resource = self.FakeHomeChild()
+        obj = resource._objectSchema
+        expr = expression.inExpression(obj.RESOURCE_NAME, [&quot;1.ics&quot;, &quot;2.ics&quot;, &quot;3.ics&quot;], False)
+        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
+        self.assertEqual(
+            select.toSQL(),
+            SQLFragment(
+                &quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and RESOURCE_NAME in (?, ?, ?)&quot;,
+                [1234, Parameter('arg1', 3)]
+            )
+        )
+        self.assertEqual(args, {&quot;arg1&quot;: [&quot;1.ics&quot;, &quot;2.ics&quot;, &quot;3.ics&quot;]})
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -67,7 +67,8 @@
</span><span class="cx">     _BIND_MODE_INDIRECT, _HOME_STATUS_NORMAL, _HOME_STATUS_EXTERNAL
</span><span class="cx"> from txdav.common.datastore.sql_tables import schema, splitSQLString
</span><span class="cx"> from txdav.common.icommondatastore import ConcurrentModification, \
</span><del>-    RecordNotAllowedError, ExternalShareFailed, ShareNotAllowed
</del><ins>+    RecordNotAllowedError, ExternalShareFailed, ShareNotAllowed, \
+    IndexedSearchException
</ins><span class="cx"> from txdav.common.icommondatastore import HomeChildNameNotAllowedError, \
</span><span class="cx">     HomeChildNameAlreadyExistsError, NoSuchHomeChildError, \
</span><span class="cx">     ObjectResourceNameNotAllowedError, ObjectResourceNameAlreadyExistsError, \
</span><span class="lines">@@ -3224,8 +3225,8 @@
</span><span class="cx">         ownerView = yield self.ownerView()
</span><span class="cx">         if self.direct():
</span><span class="cx">             yield ownerView.removeShare(self)
</span><del>-            if not ownerView.external():
-                yield self._removeExternalInvite(ownerView)
</del><ins>+            if ownerView.external():
+                yield self._removeExternalInvite()
</ins><span class="cx">         else:
</span><span class="cx">             yield self.declineShare()
</span><span class="cx"> 
</span><span class="lines">@@ -4129,9 +4130,7 @@
</span><span class="cx">         else:
</span><span class="cx">             self._notifiers = None
</span><span class="cx"> 
</span><del>-        self._index = None  # Derived classes need to set this
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def memoMe(self, key, memo): #@UnusedVariable
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Add this object to the memo dictionary in whatever fashion is appropriate.
</span><span class="lines">@@ -4356,10 +4355,6 @@
</span><span class="cx">         return self._txn.store().directoryService()
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def retrieveOldIndex(self):
-        return self._index
-
-
</del><span class="cx">     def __repr__(self):
</span><span class="cx">         return &quot;&lt;%s: %s&gt;&quot; % (self.__class__.__name__, self._resourceID)
</span><span class="cx"> 
</span><span class="lines">@@ -4803,6 +4798,21 @@
</span><span class="cx">         return super(CommonHomeChild, self).resourceNamesSinceRevision(revision)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def search(self, filter):
+        &quot;&quot;&quot;
+        Do a query of the contents of this collection.
+
+        @param filter: the query filter to use
+        @type filter: L{Filter}
+
+        @return: the names of the matching resources
+        @rtype: C{list}
+        &quot;&quot;&quot;
+
+        # This implementation raises - sub-classes override to do the actual query
+        raise IndexedSearchException()
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _loadPropertyStore(self, props=None):
</span><span class="cx">         if props is None:
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresql_legacypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_legacy.py (12143 => 12144)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_legacy.py        2013-12-19 06:18:02 UTC (rev 12143)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_legacy.py        2013-12-19 18:28:54 UTC (rev 12144)
</span><span class="lines">@@ -1,886 +0,0 @@
</span><del>-# -*- test-case-name: twistedcaldav.test.test_sharing,twistedcaldav.test.test_calendarquery -*-
-##
-# Copyright (c) 2010-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# 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 &quot;AS IS&quot; 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.
-##
-
-
-&quot;&quot;&quot;
-PostgreSQL data store.
-&quot;&quot;&quot;
-
-import StringIO
-
-
-from twisted.python import hashlib
-from twisted.internet.defer import succeed, inlineCallbacks, returnValue
-
-from twistedcaldav.config import config
-from twistedcaldav.dateops import normalizeForIndex, pyCalendarTodatetime
-from twistedcaldav.memcachepool import CachePoolUserMixIn
-from twistedcaldav.query import \
-    calendarqueryfilter, calendarquery, addressbookquery, expression, \
-    addressbookqueryfilter
-from twistedcaldav.query.sqlgenerator import sqlgenerator
-
-from txdav.caldav.icalendarstore import TimeRangeLowerLimit, TimeRangeUpperLimit
-from txdav.common.icommondatastore import IndexedSearchException, \
-    ReservationError, NoSuchObjectResourceError
-
-from txdav.common.datastore.sql_tables import schema
-from twext.enterprise.dal.syntax import Parameter, Select
-from twext.python.clsprop import classproperty
-from twext.python.log import Logger
-
-from pycalendar.datetime import DateTime
-from pycalendar.duration import Duration
-
-log = Logger()
-
-indexfbtype_to_icalfbtype = {
-    0: '?',
-    1: 'F',
-    2: 'B',
-    3: 'U',
-    4: 'T',
-}
-
-class MemcachedUIDReserver(CachePoolUserMixIn):
-    log = Logger()
-
-    def __init__(self, index, cachePool=None):
-        self.index = index
-        self._cachePool = cachePool
-
-
-    def _key(self, uid):
-        return 'reservation:%s' % (
-            hashlib.md5('%s:%s' % (uid,
-                                   self.index.resource._resourceID)).hexdigest())
-
-
-    def reserveUID(self, uid):
-        self.log.debug(&quot;Reserving UID %r @ %r&quot; % (
-                uid,
-                self.index.resource))
-
-        def _handleFalse(result):
-            if result is False:
-                raise ReservationError(
-                    &quot;UID %s already reserved for calendar collection %s.&quot;
-                    % (uid, self.index.resource._name)
-                    )
-
-        d = self.getCachePool().add(self._key(uid),
-                                    'reserved',
-                                    expireTime=config.UIDReservationTimeOut)
-        d.addCallback(_handleFalse)
-        return d
-
-
-    def unreserveUID(self, uid):
-        self.log.debug(&quot;Unreserving UID %r @ %r&quot; % (
-                uid,
-                self.index.resource))
-
-        def _handleFalse(result):
-            if result is False:
-                raise ReservationError(
-                    &quot;UID %s is not reserved for calendar collection %s.&quot;
-                    % (uid, self.index.resource._resourceID)
-                    )
-
-        d = self.getCachePool().delete(self._key(uid))
-        d.addCallback(_handleFalse)
-        return d
-
-
-    def isReservedUID(self, uid):
-        self.log.debug(&quot;Is reserved UID %r @ %r&quot; % (
-                uid,
-                self.index.resource))
-
-        def _checkValue((flags, value)):
-            if value is None:
-                return False
-            else:
-                return True
-
-        d = self.getCachePool().get(self._key(uid))
-        d.addCallback(_checkValue)
-        return d
-
-
-
-class DummyUIDReserver(object):
-    log = Logger()
-
-    def __init__(self, index):
-        self.index = index
-        self.reservations = set()
-
-
-    def _key(self, uid):
-        return 'reservation:%s' % (
-            hashlib.md5('%s:%s' % (uid,
-                                   self.index.resource._resourceID)).hexdigest())
-
-
-    def reserveUID(self, uid):
-        self.log.debug(&quot;Reserving UID %r @ %r&quot; % (
-                uid,
-                self.index.resource))
-
-        key = self._key(uid)
-        if key in self.reservations:
-            raise ReservationError(
-                &quot;UID %s already reserved for calendar collection %s.&quot;
-                % (uid, self.index.resource._name)
-                )
-        self.reservations.add(key)
-        return succeed(None)
-
-
-    def unreserveUID(self, uid):
-        self.log.debug(&quot;Unreserving UID %r @ %r&quot; % (
-                uid,
-                self.index.resource))
-
-        key = self._key(uid)
-        if key in self.reservations:
-            self.reservations.remove(key)
-        return succeed(None)
-
-
-    def isReservedUID(self, uid):
-        self.log.debug(&quot;Is reserved UID %r @ %r&quot; % (
-                uid,
-                self.index.resource))
-        key = self._key(uid)
-        return succeed(key in self.reservations)
-
-
-
-class RealSQLBehaviorMixin(object):
-    &quot;&quot;&quot;
-    Class attributes for 'real' SQL behavior; avoid idiosyncracies of SQLite,
-    use standard SQL constructions, and depend on the full schema in
-    sql_schema/current.sql rather than the partial one in twistedcaldav which depends
-    on the placement of the database in the filesystem for some information.
-    &quot;&quot;&quot;
-
-    ISOP = &quot; = &quot;
-    STARTSWITHOP = ENDSWITHOP = CONTAINSOP = &quot; LIKE &quot;
-    NOTSTARTSWITHOP = NOTENDSWITHOP = NOTCONTAINSOP = &quot; NOT LIKE &quot;
-
-    def containsArgument(self, arg):
-        return &quot;%%%s%%&quot; % (arg,)
-
-
-    def startswithArgument(self, arg):
-        return &quot;%s%%&quot; % (arg,)
-
-
-    def endswithArgument(self, arg):
-        return &quot;%%%s&quot; % (arg,)
-
-
-
-class CalDAVSQLBehaviorMixin(RealSQLBehaviorMixin):
-    &quot;&quot;&quot;
-    Query generator for CalDAV indexed searches.
-    &quot;&quot;&quot;
-
-    FIELDS = {
-        &quot;TYPE&quot;: &quot;CALENDAR_OBJECT.ICALENDAR_TYPE&quot;,
-        &quot;UID&quot;: &quot;CALENDAR_OBJECT.ICALENDAR_UID&quot;,
-    }
-    RESOURCEDB = &quot;CALENDAR_OBJECT&quot;
-    TIMESPANDB = &quot;TIME_RANGE&quot;
-
-    TIMESPANTEST = &quot;((TIME_RANGE.FLOATING = FALSE AND TIME_RANGE.START_DATE &lt; %s AND TIME_RANGE.END_DATE &gt; %s) OR (TIME_RANGE.FLOATING = TRUE AND TIME_RANGE.START_DATE &lt; %s AND TIME_RANGE.END_DATE &gt; %s))&quot;
-    TIMESPANTEST_NOEND = &quot;((TIME_RANGE.FLOATING = FALSE AND TIME_RANGE.END_DATE &gt; %s) OR (TIME_RANGE.FLOATING = TRUE AND TIME_RANGE.END_DATE &gt; %s))&quot;
-    TIMESPANTEST_NOSTART = &quot;((TIME_RANGE.FLOATING = FALSE AND TIME_RANGE.START_DATE &lt; %s) OR (TIME_RANGE.FLOATING = TRUE AND TIME_RANGE.START_DATE &lt; %s))&quot;
-    TIMESPANTEST_TAIL_PIECE = &quot; AND TIME_RANGE.CALENDAR_OBJECT_RESOURCE_ID = CALENDAR_OBJECT.RESOURCE_ID AND TIME_RANGE.CALENDAR_RESOURCE_ID = %s&quot;
-    TIMESPANTEST_JOIN_ON_PIECE = &quot;TIME_RANGE.INSTANCE_ID = TRANSPARENCY.TIME_RANGE_INSTANCE_ID AND TRANSPARENCY.USER_ID = %s&quot;
-
-    def generate(self):
-        &quot;&quot;&quot;
-        Generate the actual SQL 'where ...' expression from the passed in
-        expression tree.
-
-        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the
-            partial SQL statement, and the C{list} is the list of argument
-            substitutions to use with the SQL API execute method.
-        &quot;&quot;&quot;
-
-        # Init state
-        self.sout = StringIO.StringIO()
-        self.arguments = []
-        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(&quot;CALENDAR_OBJECT.CALENDAR_RESOURCE_ID&quot;, 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:
-                    # timerange expression forces a join on calendarid
-                    pass
-                else:
-                    # AND the whole thing with calendarid
-                    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.generateExpression(self.expression)
-
-        # Prefix with ' from ...' partial statement
-        select = self.FROM + self.RESOURCEDB
-        if self.usedtimespan:
-
-            # Free busy needs transparency join
-            if self.freebusy:
-                self.frontArgument(self.userid)
-                select += &quot;, %s LEFT OUTER JOIN %s ON (%s)&quot; % (
-                    self.TIMESPANDB,
-                    self.TRANSPARENCYDB,
-                    self.TIMESPANTEST_JOIN_ON_PIECE
-                )
-            else:
-                select += &quot;, %s&quot; % (
-                    self.TIMESPANDB,
-                )
-        select += self.WHERE
-        if self.usedtimespan:
-            select += &quot;(&quot;
-        select += self.sout.getvalue()
-        if self.usedtimespan:
-            if self.calendarid:
-                self.setArgument(self.calendarid)
-            select += &quot;)%s&quot; % (self.TIMESPANTEST_TAIL_PIECE,)
-
-        select = select % tuple(self.substitutions)
-
-        return select, self.arguments
-
-
-
-class FormatParamStyleMixin(object):
-    &quot;&quot;&quot;
-    Mixin for overriding methods on sqlgenerator that generate arguments
-    according to format/pyformat rules rather than the base class's 'numeric'
-    rules.
-    &quot;&quot;&quot;
-
-    def addArgument(self, arg):
-        self.arguments.append(arg)
-        self.substitutions.append(&quot;%s&quot;)
-        self.sout.write(&quot;%s&quot;)
-
-
-    def setArgument(self, arg):
-        self.arguments.append(arg)
-        self.substitutions.append(&quot;%s&quot;)
-
-
-    def frontArgument(self, arg):
-        self.arguments.insert(0, arg)
-        self.substitutions.insert(0, &quot;%s&quot;)
-
-
-
-class postgresqlgenerator(FormatParamStyleMixin, CalDAVSQLBehaviorMixin,
-                          sqlgenerator):
-    &quot;&quot;&quot;
-    Query generator for PostgreSQL indexed searches.
-    &quot;&quot;&quot;
-
-
-
-def fixbools(sqltext):
-    return sqltext.replace(&quot;TRUE&quot;, &quot;1&quot;).replace(&quot;FALSE&quot;, &quot;0&quot;)
-
-
-
-class oraclesqlgenerator(CalDAVSQLBehaviorMixin, sqlgenerator):
-    &quot;&quot;&quot;
-    Query generator for Oracle indexed searches.
-    &quot;&quot;&quot;
-    TIMESPANTEST = fixbools(CalDAVSQLBehaviorMixin.TIMESPANTEST)
-    TIMESPANTEST_NOEND = fixbools(CalDAVSQLBehaviorMixin.TIMESPANTEST_NOEND)
-    TIMESPANTEST_NOSTART = fixbools(CalDAVSQLBehaviorMixin.TIMESPANTEST_NOSTART)
-    TIMESPANTEST_TAIL_PIECE = fixbools(
-        CalDAVSQLBehaviorMixin.TIMESPANTEST_TAIL_PIECE)
-    TIMESPANTEST_JOIN_ON_PIECE = fixbools(
-        CalDAVSQLBehaviorMixin.TIMESPANTEST_JOIN_ON_PIECE)
-
-
-
-class LegacyIndexHelper(object):
-    log = Logger()
-
-    @inlineCallbacks
-    def isAllowedUID(self, uid, *names):
-        &quot;&quot;&quot;
-        Checks to see whether to allow an operation which would add the
-        specified UID to the index.  Specifically, the operation may not
-        violate the constraint that UIDs must be unique.
-        @param uid: the UID to check
-        @param names: the names of resources being replaced or deleted by the
-            operation; UIDs associated with these resources are not checked.
-        @return: True if the UID is not in the index and is not reserved,
-            False otherwise.
-        &quot;&quot;&quot;
-        rname = yield self.resourceNameForUID(uid)
-        returnValue(rname is None or rname in names)
-
-
-    def reserveUID(self, uid):
-        return self.reserver.reserveUID(uid)
-
-
-    def unreserveUID(self, uid):
-        return self.reserver.unreserveUID(uid)
-
-
-    def isReservedUID(self, uid):
-        return self.reserver.isReservedUID(uid)
-
-
-
-class PostgresLegacyIndexEmulator(LegacyIndexHelper):
-    &quot;&quot;&quot;
-    Emulator for L{twistedcaldv.index.Index} and
-    L{twistedcaldv.index.IndexSchedule}.
-    &quot;&quot;&quot;
-
-    def __init__(self, calendar):
-        self.resource = self.calendar = calendar
-        if (
-            hasattr(config, &quot;Memcached&quot;) and
-            config.Memcached.Pools.Default.ClientEnabled
-        ):
-            self.reserver = MemcachedUIDReserver(self)
-        else:
-            # This is only used with unit tests
-            self.reserver = DummyUIDReserver(self)
-
-    _objectSchema = schema.CALENDAR_OBJECT
-
-    @property
-    def _txn(self):
-        return self.calendar._txn
-
-
-    @inlineCallbacks
-    def isAllowedUID(self, uid, *names):
-        &quot;&quot;&quot;
-        Checks to see whether to allow an operation which would add the
-        specified UID to the index.  Specifically, the operation may not
-        violate the constraint that UIDs must be unique.
-        @param uid: the UID to check
-        @param names: the names of resources being replaced or deleted by the
-            operation; UIDs associated with these resources are not checked.
-        @return: True if the UID is not in the index and is not reserved,
-            False otherwise.
-        &quot;&quot;&quot;
-        rname = yield self.resourceNameForUID(uid)
-        returnValue(rname is None or rname in names)
-
-
-    @inlineCallbacks
-    def resourceUIDForName(self, name):
-        uid = yield self.calendar.resourceUIDForName(name)
-        returnValue(uid)
-
-
-    @inlineCallbacks
-    def resourceNameForUID(self, uid):
-        name = yield self.calendar.resourceNameForUID(uid)
-        returnValue(name)
-
-
-    @classproperty
-    def _notExpandedWithinQuery(cls): #@NoSelf
-        &quot;&quot;&quot;
-        DAL query to satisfy L{PostgresLegacyIndexEmulator.notExpandedBeyond}.
-        &quot;&quot;&quot;
-        co = schema.CALENDAR_OBJECT
-        return Select(
-            [co.RESOURCE_NAME],
-            From=co,
-            Where=((co.RECURRANCE_MIN &gt; Parameter(&quot;minDate&quot;))
-                .Or(co.RECURRANCE_MAX &lt; Parameter(&quot;maxDate&quot;)))
-                .And(co.CALENDAR_RESOURCE_ID == Parameter(&quot;resourceID&quot;))
-        )
-
-
-    @inlineCallbacks
-    def notExpandedWithin(self, minDate, maxDate):
-        &quot;&quot;&quot;
-        Gives all resources which have not been expanded beyond a given date
-        in the database.  (Unused; see above L{postgresqlgenerator}.
-        &quot;&quot;&quot;
-        returnValue([row[0] for row in (
-            yield self._notExpandedWithinQuery.on(
-                self._txn,
-                minDate=pyCalendarTodatetime(normalizeForIndex(minDate)) if minDate is not None else None,
-                maxDate=pyCalendarTodatetime(normalizeForIndex(maxDate)),
-                resourceID=self.calendar._resourceID))]
-        )
-
-
-    @inlineCallbacks
-    def reExpandResource(self, name, expand_start, expand_end):
-        &quot;&quot;&quot;
-        Given a resource name, remove it from the database and re-add it
-        with a longer expansion.
-        &quot;&quot;&quot;
-        obj = yield self.calendar.calendarObjectWithName(name)
-
-        # Use a new transaction to do this update quickly without locking the row for too long. However, the original
-        # transaction may have the row locked, so use wait=False and if that fails, fall back to using the original txn.
-
-        newTxn = obj.transaction().store().newTransaction()
-        try:
-            yield obj.lock(wait=False, txn=newTxn)
-        except NoSuchObjectResourceError:
-            yield newTxn.commit()
-            returnValue(None)
-        except:
-            yield newTxn.abort()
-            newTxn = None
-
-        # Now do the re-expand using the appropriate transaction
-        try:
-            doExpand = False
-            if newTxn is None:
-                doExpand = True
-            else:
-                # We repeat this check because the resource may have been re-expanded by someone else
-                rmin, rmax = (yield obj.recurrenceMinMax(txn=newTxn))
-
-                # If the resource is not fully expanded, see if within the required range or not.
-                # Note that expand_start could be None if no lower limit is applied, but expand_end will
-                # never be None
-                if rmax is not None and rmax &lt; expand_end:
-                    doExpand = True
-                if rmin is not None and expand_start is not None and rmin &gt; expand_start:
-                    doExpand = True
-
-            if doExpand:
-                yield obj.updateDatabase(
-                    (yield obj.component()),
-                    expand_until=expand_end,
-                    reCreate=True,
-                    txn=newTxn,
-                )
-        finally:
-            if newTxn is not None:
-                yield newTxn.commit()
-
-
-    @inlineCallbacks
-    def testAndUpdateIndex(self, minDate, maxDate):
-        # Find out if the index is expanded far enough
-        names = yield self.notExpandedWithin(minDate, maxDate)
-
-        # Actually expand recurrence max
-        for name in names:
-            self.log.info(&quot;Search falls outside range of index for %s %s to %s&quot; %
-                          (name, minDate, maxDate))
-            yield self.reExpandResource(name, minDate, maxDate)
-
-
-    @inlineCallbacks
-    def indexedSearch(self, filter, useruid='', fbtype=False):
-        &quot;&quot;&quot;
-        Finds resources matching the given qualifiers.
-
-        @param filter: the L{Filter} for the calendar-query to execute.
-
-        @return: a L{Deferred} which fires with an iterable of tuples for each
-            resource matching the given C{qualifiers}. The tuples are C{(name,
-            uid, type)}, where C{name} is the resource name, C{uid} is the
-            resource UID, and C{type} is the resource iCalendar component type.
-        &quot;&quot;&quot;
-        # Detect which style of parameter-generation we're using.  Naming is a
-        # little off here, because the reason we're using the numeric one is
-        # that it happens to be used by the oracle binding that we're using,
-        # whereas the postgres binding happens to use the 'pyformat' (e.g. %s)
-        # parameter style.
-        if self.calendar._txn.paramstyle == 'numeric':
-            generator = oraclesqlgenerator
-        else:
-            generator = postgresqlgenerator
-        # Make sure we have a proper Filter element and get the partial SQL
-        # statement to use.
-        if isinstance(filter, calendarqueryfilter.Filter):
-            qualifiers = calendarquery.sqlcalendarquery(
-                filter, self.calendar._resourceID, useruid, fbtype,
-                generator=generator
-            )
-            if qualifiers is not None:
-
-                today = DateTime.getToday()
-
-                # Determine how far we need to extend the current expansion of
-                # events. If we have an open-ended time-range we will expand
-                # one year past the start. That should catch bounded
-                # recurrences - unbounded will have been indexed with an
-                # &quot;infinite&quot; value always included.
-                maxDate, isStartDate = filter.getmaxtimerange()
-                if maxDate:
-                    maxDate = maxDate.duplicate()
-                    maxDate.offsetDay(1)
-                    maxDate.setDateOnly(True)
-                    upperLimit = today + Duration(days=config.FreeBusyIndexExpandMaxDays)
-                    if maxDate &gt; upperLimit:
-                        raise TimeRangeUpperLimit(upperLimit)
-                    if isStartDate:
-                        maxDate += Duration(days=365)
-
-                # Determine if the start date is too early for the restricted range we
-                # are applying. If it is today or later we don't need to worry about truncation
-                # in the past.
-                minDate, _ignore_isEndDate = filter.getmintimerange()
-                if minDate &gt;= today:
-                    minDate = None
-                if minDate is not None and config.FreeBusyIndexLowerLimitDays:
-                    truncateLowerLimit = today - Duration(days=config.FreeBusyIndexLowerLimitDays)
-                    if minDate &lt; truncateLowerLimit:
-                        raise TimeRangeLowerLimit(truncateLowerLimit)
-
-                if maxDate is not None or minDate is not None:
-                    yield self.testAndUpdateIndex(minDate, maxDate)
-
-            else:
-                # We cannot handle this filter in an indexed search
-                raise IndexedSearchException()
-        else:
-            qualifiers = None
-
-        # Perform the search
-        if qualifiers is None:
-            rowiter = yield self.bruteForceSearch()
-        else:
-            if fbtype:
-                # For a free-busy time-range query we return all instances
-                rowiter = yield self._txn.execSQL(
-                    &quot;&quot;&quot;
-                    select DISTINCT
-                        CALENDAR_OBJECT.RESOURCE_NAME,
-                        CALENDAR_OBJECT.ICALENDAR_UID,
-                        CALENDAR_OBJECT.ICALENDAR_TYPE,
-                        CALENDAR_OBJECT.ORGANIZER,
-                        TIME_RANGE.FLOATING, TIME_RANGE.START_DATE,
-                        TIME_RANGE.END_DATE, TIME_RANGE.FBTYPE,
-                        TIME_RANGE.TRANSPARENT, TRANSPARENCY.TRANSPARENT
-                    &quot;&quot;&quot; +
-                    qualifiers[0],
-                    qualifiers[1]
-                )
-            else:
-                rowiter = yield self._txn.execSQL(
-                    &quot;&quot;&quot;
-                    select
-                        DISTINCT CALENDAR_OBJECT.RESOURCE_NAME,
-                        CALENDAR_OBJECT.ICALENDAR_UID,
-                        CALENDAR_OBJECT.ICALENDAR_TYPE
-                    &quot;&quot;&quot; +
-                    qualifiers[0],
-                    qualifiers[1]
-                )
-
-        # Check result for missing resources
-
-        results = []
-        for row in rowiter:
-            if fbtype:
-                row = list(row)
-                row[4] = 'Y' if row[4] else 'N'
-                row[7] = indexfbtype_to_icalfbtype[row[7]]
-                if row[9] is not None:
-                    row[8] = row[9]
-                row[8] = 'T' if row[8] else 'F'
-                del row[9]
-            results.append(row)
-        returnValue(results)
-
-
-    @classproperty
-    def _bruteForceQuery(cls): #@NoSelf
-        &quot;&quot;&quot;
-        DAL query for all C{CALENDAR_OBJECT} rows in the calendar represented by
-        this index.
-        &quot;&quot;&quot;
-        obj = cls._objectSchema
-        return Select(
-            [obj.RESOURCE_NAME, obj.ICALENDAR_UID, obj.ICALENDAR_TYPE],
-            From=obj, Where=obj.PARENT_RESOURCE_ID == Parameter(&quot;resourceID&quot;)
-        )
-
-
-    def bruteForceSearch(self):
-        return self._bruteForceQuery.on(
-            self._txn, resourceID=self.resource._resourceID)
-
-
-    @inlineCallbacks
-    def resourcesExist(self, names):
-        returnValue(list(set(names).intersection(
-            set((yield self.calendar.listCalendarObjects())))))
-
-
-    @classproperty
-    def _resourceExistsQuery(cls): #@NoSelf
-        &quot;&quot;&quot;
-        DAL query to determine whether a calendar object exists in the
-        collection represented by this index.
-        &quot;&quot;&quot;
-        obj = cls._objectSchema
-        return Select(
-            [obj.RESOURCE_NAME], From=obj,
-            Where=(obj.RESOURCE_NAME == Parameter(&quot;name&quot;))
-            .And(obj.PARENT_RESOURCE_ID == Parameter(&quot;resourceID&quot;))
-        )
-
-
-    @inlineCallbacks
-    def resourceExists(self, name):
-        returnValue((bool(
-            (yield self._resourceExistsQuery.on(
-                self._txn, name=name, resourceID=self.resource._resourceID))
-        )))
-
-
-
-class PostgresLegacyInboxIndexEmulator(PostgresLegacyIndexEmulator):
-    &quot;&quot;&quot;
-    UIDs need not be unique in the 'inbox' calendar, so override those
-    behaviors intended to ensure that.
-    &quot;&quot;&quot;
-
-    def isAllowedUID(self):
-        return succeed(True)
-
-
-    def reserveUID(self, uid):
-        return succeed(None)
-
-
-    def unreserveUID(self, uid):
-        return succeed(None)
-
-
-    def isReservedUID(self, uid):
-        return succeed(False)
-
-
-
-# CARDDAV
-
-class CardDAVSQLBehaviorMixin(RealSQLBehaviorMixin):
-    &quot;&quot;&quot;
-    Query generator for CardDAV indexed searches.
-    &quot;&quot;&quot;
-
-    FIELDS = {
-        &quot;UID&quot;: &quot;ADDRESSBOOK_OBJECT.VCARD_UID&quot;,
-    }
-    RESOURCEDB = &quot;ADDRESSBOOK_OBJECT&quot;
-
-    def generate(self):
-        &quot;&quot;&quot;
-        Generate the actual SQL 'where ...' expression from the passed in
-        expression tree.
-
-        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the
-            partial SQL statement, and the C{list} is the list of argument
-            substitutions to use with the SQL API execute method.
-        &quot;&quot;&quot;
-
-        # Init state
-        self.sout = StringIO.StringIO()
-        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(&quot;ADDRESSBOOK_OBJECT.ADDRESSBOOK_HOME_RESOURCE_ID&quot;, str(self.calendarid), True)
-            self.expression = test.andWith(self.expression)
-
-        # Generate ' where ...' partial statement
-        self.sout.write(self.WHERE)
-        self.generateExpression(self.expression)
-
-        # Prefix with ' from ...' partial statement
-        select = self.FROM + self.RESOURCEDB
-        select += self.sout.getvalue()
-
-        select = select % tuple(self.substitutions)
-
-        return select, self.arguments
-
-
-
-class postgresqladbkgenerator(FormatParamStyleMixin, CardDAVSQLBehaviorMixin, sqlgenerator):
-    &quot;&quot;&quot;
-    Query generator for PostgreSQL indexed searches.
-    &quot;&quot;&quot;
-
-
-
-class oraclesqladbkgenerator(CardDAVSQLBehaviorMixin, sqlgenerator):
-    &quot;&quot;&quot;
-    Query generator for Oracle indexed searches.
-    &quot;&quot;&quot;
-
-
-
-class PostgresLegacyABIndexEmulator(LegacyIndexHelper):
-    &quot;&quot;&quot;
-    Emulator for L{twistedcaldv.index.Index} and
-    L{twistedcaldv.index.IndexSchedule}.
-    &quot;&quot;&quot;
-
-    _objectSchema = schema.ADDRESSBOOK_OBJECT
-
-    def __init__(self, addressbook):
-        self.resource = self.addressbook = addressbook
-        if (
-            hasattr(config, &quot;Memcached&quot;) and
-            config.Memcached.Pools.Default.ClientEnabled
-        ):
-            self.reserver = MemcachedUIDReserver(self)
-        else:
-            # This is only used with unit tests
-            self.reserver = DummyUIDReserver(self)
-
-
-    @property
-    def _txn(self):
-        return self.addressbook._txn
-
-
-    @inlineCallbacks
-    def resourceUIDForName(self, name):
-        obj = yield self.addressbook.addressbookObjectWithName(name)
-        if obj is None:
-            returnValue(None)
-        returnValue(obj.uid())
-
-
-    @inlineCallbacks
-    def resourceNameForUID(self, uid):
-        obj = yield self.addressbook.addressbookObjectWithUID(uid)
-        if obj is None:
-            returnValue(None)
-        returnValue(obj.name())
-
-
-    def searchValid(self, filter):
-        if isinstance(filter, addressbookqueryfilter.Filter):
-            qualifiers = addressbookquery.sqladdressbookquery(filter)
-        else:
-            qualifiers = None
-
-        return qualifiers is not None
-
-
-    @inlineCallbacks
-    def search(self, filter):
-        &quot;&quot;&quot;
-        Finds resources matching the given qualifiers.
-        @param filter: the L{Filter} for the addressbook-query to execute.
-        @return: an iterable of tuples for each resource matching the
-            given C{qualifiers}. The tuples are C{(name, uid, type)}, where
-            C{name} is the resource name, C{uid} is the resource UID, and
-            C{type} is the resource iCalendar component type.x
-        &quot;&quot;&quot;
-        if self.addressbook._txn.paramstyle == 'numeric':
-            generator = oraclesqladbkgenerator
-        else:
-            generator = postgresqladbkgenerator
-        # Make sure we have a proper Filter element and get the partial SQL statement to use.
-        if isinstance(filter, addressbookqueryfilter.Filter):
-            qualifiers = addressbookquery.sqladdressbookquery(
-                filter, self.addressbook._resourceID, generator=generator)
-        else:
-            qualifiers = None
-        if qualifiers is not None:
-            rowiter = yield self._txn.execSQL(
-                &quot;select DISTINCT ADDRESSBOOK_OBJECT.RESOURCE_NAME, ADDRESSBOOK_OBJECT.VCARD_UID&quot; +
-                qualifiers[0],
-                qualifiers[1]
-            )
-        else:
-            rowiter = yield Select(
-                [self._objectSchema.RESOURCE_NAME,
-                 self._objectSchema.VCARD_UID],
-                From=self._objectSchema,
-                Where=self._objectSchema.ADDRESSBOOK_HOME_RESOURCE_ID ==
-                self.addressbook._resourceID
-            ).on(self.addressbook._txn)
-
-        returnValue(list(rowiter))
-
-
-    def indexedSearch(self, filter, useruid='', fbtype=False):
-        &quot;&quot;&quot;
-        Always raise L{IndexedSearchException}, since these indexes are not
-        fully implemented yet.
-        &quot;&quot;&quot;
-        raise IndexedSearchException()
-
-
-    @inlineCallbacks
-    def resourcesExist(self, names):
-        returnValue(list(set(names).intersection(
-            set((yield self.addressbook.listAddressBookObjects())))))
</del></span></pre>
</div>
</div>

</body>
</html>