<!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" /><style type="text/css"><!--
#msg dl { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer { 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 #fc0 solid; padding: 6px; }
#msg ul, pre { overflow: auto; }
#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>
<title>[1195] CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav</title>
</head>
<body>

<div id="msg">
<dl>
<dt>Revision</dt> <dd><a href="http://trac.macosforge.org/projects/calendarserver/changeset/1195">1195</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2007-02-16 11:00:45 -0800 (Fri, 16 Feb 2007)</dd>
</dl>

<h3>Log Message</h3>
<pre>Add methods for &quot;batch&quot; queries on multiple properties and across multiple resources.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboosqlprops1126twistedcaldavsqlpropspy">CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/sqlprops.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboosqlprops1126twistedcaldavtesttest_sqlpropspy">CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/test/test_sqlprops.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserscdaboosqlprops1126twistedcaldavsqlpropspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/sqlprops.py (1194 => 1195)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/sqlprops.py        2007-02-15 19:33:00 UTC (rev 1194)
+++ CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/sqlprops.py        2007-02-16 19:00:45 UTC (rev 1195)
</span><span class="lines">@@ -44,15 +44,13 @@
</span><span class="cx"> 
</span><span class="cx"> class sqlPropertyStore (object):
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-
</del><ins>+    A dead property store that uses an SQLite database backend.
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">  
</span><span class="cx">     def _encode(clazz, name):
</span><del>-        return urllib.quote(&quot;{%s}%s&quot; % name, safe='{}:')
</del><ins>+        return &quot;{%s}%s&quot; % name
</ins><span class="cx"> 
</span><span class="cx">     def _decode(clazz, name):
</span><del>-        name = urllib.unquote(name)
-
</del><span class="cx">         index = name.find(&quot;}&quot;)
</span><span class="cx">     
</span><span class="cx">         if (index is -1 or not len(name) &gt; index or not name[0] == &quot;{&quot;):
</span><span class="lines">@@ -99,6 +97,58 @@
</span><span class="cx"> 
</span><span class="cx">         return doc.root_element
</span><span class="cx"> 
</span><ins>+    def getAll(self, qnames):
+        &quot;&quot;&quot;
+        Read properties from index.
+        
+        @param qnames: C{list} of C{tuple} of property namespace and name.
+        @return: a C{list} of property classes
+        &quot;&quot;&quot;
+        if not qnames:
+            return None
+
+        if not self.index:
+            raise HTTPError(StatusResponse(
+                responsecode.NOT_FOUND,
+                &quot;No such property: {%s}%s&quot; % qnames[0]
+            ))
+            
+        values = self.index.getAllPropertyValues(self.rname, map(self._encode, qnames))
+        
+        results = []
+        for value in values.itervalues():
+            doc = davxml.WebDAVDocument.fromString(value)
+            results.append(doc.root_element)
+        return results
+
+    def getAllResources(self, qnames):
+        &quot;&quot;&quot;
+        Read properties for all child resources from index.
+        
+        @param qnames: C{list} of C{tuple} of property namespace and name.
+        @return: a C{dict} with resource name as keys and C{list} of property classes as values
+        &quot;&quot;&quot;
+        if not qnames:
+            return None
+
+        if not self.index:
+            raise HTTPError(StatusResponse(
+                responsecode.NOT_FOUND,
+                &quot;No such property: {%s}%s&quot; % qnames[0]
+            ))
+            
+        values = self.index.getAllResourcePropertyValues(map(self._encode, qnames))
+        results = {}
+        
+        for key, value in values.iteritems():
+            pvalues = []
+            for pvalue in value.itervalues():
+                doc = davxml.WebDAVDocument.fromString(pvalue)
+                pvalues.append(doc.root_element)
+            results[key] = pvalues
+        
+        return results
+
</ins><span class="cx">     def set(self, property):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Write property into index.
</span><span class="lines">@@ -150,8 +200,7 @@
</span><span class="cx"> 
</span><span class="cx">         if self.index:
</span><span class="cx">             results = self.index.listProperties(self.rname)
</span><del>-            result = [self._decode(name) for name in results]
-            return result
</del><ins>+            return map(self._decode, results)
</ins><span class="cx">         else:
</span><span class="cx">             return []
</span><span class="cx"> 
</span><span class="lines">@@ -210,8 +259,57 @@
</span><span class="cx">             return members[0]
</span><span class="cx">         else:
</span><span class="cx">             raise ValueError(&quot;Multiple properties of the same name %s stored for resource %s&quot; % (pname, rname,))
</span><ins>+
+    def getAllPropertyValues(self, rname, pnames):
+        &quot;&quot;&quot;
+        Get specified property values from specific resource.
+    
+        @param rname: a C{str} containing the resource name.
+        @param pnames: a C{list} of C{str} containing the name of the properties to get.
+        @return: a C{dict} containing property name/value.
+        &quot;&quot;&quot;
</ins><span class="cx">         
</span><ins>+        # Remove what is there, then add it back.
+        log.msg(&quot;getAllPropertyValues: %s \&quot;%s\&quot;&quot; % (self.dbpath, pnames))
+        properties = {}
+        statement = &quot;select PROPERTYNAME, PROPERTYVALUE from PROPERTIES where RESOURCENAME = :1 and (&quot;
+        args = [rname]
+        for i, pname in enumerate(pnames):
+            if i != 0:
+                statement += &quot; or &quot;
+            statement += &quot;PROPERTYNAME=:%s&quot; % (i + 2,)
+            args.append(pname)
+        statement += &quot;)&quot;
</ins><span class="cx"> 
</span><ins>+        for row in self._db_execute(statement, *args):
+            properties[row[0]] = row[1]
+
+        return properties
+
+    def getAllResourcePropertyValues(self, pnames):
+        &quot;&quot;&quot;
+        Get specified property values from all resources.
+    
+        @param pnames: a C{list} of C{str} containing the name of the properties to get.
+        @return: a C{dict} containing C{str} keys (names of child resources) and C{dict} values of property name/value.
+        &quot;&quot;&quot;
+        
+        # Remove what is there, then add it back.
+        log.msg(&quot;getAllPropertyValues: %s \&quot;%s\&quot;&quot; % (self.dbpath, pnames))
+        members = {}
+        statement = &quot;select RESOURCENAME, PROPERTYNAME, PROPERTYVALUE from PROPERTIES where &quot;
+        args = []
+        for i, pname in enumerate(pnames):
+            if i != 0:
+                statement += &quot; or &quot;
+            statement += &quot;PROPERTYNAME=:%s&quot; % (i + 1,)
+            args.append(pname)
+
+        for row in self._db_execute(statement, *args):
+            members.setdefault(row[0], {})[row[1]] = row[2]
+
+        return members
+
</ins><span class="cx">     def removeProperty(self, rname, pname):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Remove a property.
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboosqlprops1126twistedcaldavtesttest_sqlpropspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/test/test_sqlprops.py (1194 => 1195)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/test/test_sqlprops.py        2007-02-15 19:33:00 UTC (rev 1194)
+++ CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/test/test_sqlprops.py        2007-02-16 19:00:45 UTC (rev 1195)
</span><span class="lines">@@ -15,6 +15,8 @@
</span><span class="cx"> #
</span><span class="cx"> # DRI: Wilfredo Sanchez, wsanchez@apple.com
</span><span class="cx"> ##
</span><ins>+import time
+from twistedcaldav.root import RootResource
</ins><span class="cx"> from twistedcaldav import customxml
</span><span class="cx"> from twistedcaldav import caldavxml
</span><span class="cx"> from twisted.web2.dav import davxml
</span><span class="lines">@@ -68,12 +70,49 @@
</span><span class="cx">                         msg=&quot;Could not find property %s.&quot; % prop)
</span><span class="cx">         self.assertTrue(index.get(prop.qname()) == prop,
</span><span class="cx">                         msg=&quot;Could not get property %s.&quot; % prop)
</span><del>-        
</del><ins>+    
+    def _testPropertyList(self, proplist):
+        self.assertTrue(len(proplist) == len(SQLProps.props),
+                        msg=&quot;Number of properties returned %s not equal to number queried %s.&quot; % (len(proplist), len(SQLProps.props),))
+        for prop in SQLProps.props:
+            for p in proplist:
+                if prop == p:
+                    proplist.remove(p)
+                    break
+        self.assertTrue(len(proplist) == 0,
+                        msg=&quot;Incorrect properties returned %s.&quot; % proplist)
+
+    def _testResourcePropertyList(self, num_resources, resourcedict):
+        self.assertTrue(len(resourcedict) == num_resources,
+                        msg=&quot;Number of resources returned %s not equal to number queried %s.&quot; % (len(resourcedict), num_resources,))
+        for i in xrange(num_resources):
+            fname = &quot;file%04s.ics&quot; % (i,)
+            self.assertTrue(resourcedict.has_key(fname),
+                            msg=&quot;Resource %s not returned in query results&quot; % (fname,))
+            self._testPropertyList(resourcedict[fname])
+
+    def _setupMultipleResources(self, number):
+        self.collection_name, self.collection_uri = self.mkdtemp(&quot;sql&quot;)
+        for i in xrange(number):
+            rsrc = CalDAVFile(os.path.join(self.collection_name, &quot;file%04s.ics&quot; % (i,)))
+            index = sqlPropertyStore(rsrc)
+            for prop in SQLProps.props:
+                index.set(prop)
+        return index
+
</ins><span class="cx">     def test_db_init_directory(self):
</span><span class="cx">         self.collection_name, self.collection_uri = self.mkdtemp(&quot;sql&quot;)
</span><span class="cx">         rsrc = CalDAVFile(self.collection_name)
</span><span class="cx">         index = sqlPropertyStore(rsrc)
</span><span class="cx">         db = index.index._db()
</span><ins>+        self.assertTrue(os.path.exists(os.path.join(os.path.dirname(self.collection_name), SQLPropertiesDatabase.dbFilename)),
+                        msg=&quot;Could not initialize index via collection resource.&quot;)
+
+    def test_db_init_root(self):
+        self.collection_name, self.collection_uri = self.mkdtemp(&quot;sql&quot;)
+        rsrc = RootResource(self.collection_name)
+        index = sqlPropertyStore(rsrc)
+        db = index.index._db()
</ins><span class="cx">         self.assertTrue(os.path.exists(os.path.join(self.collection_name, SQLPropertiesDatabase.dbFilename)),
</span><span class="cx">                         msg=&quot;Could not initialize index via collection resource.&quot;)
</span><span class="cx"> 
</span><span class="lines">@@ -121,3 +160,28 @@
</span><span class="cx">         for prop in SQLProps.props:
</span><span class="cx">             self.assertFalse(index.contains(prop.qname()),
</span><span class="cx">                             msg=&quot;Could not find property %s.&quot; % prop)
</span><ins>+
+    def test_getallproperties(self):
+        index = self._setUpIndex()
+        for prop in SQLProps.props:
+            self._setProperty(index, prop)
+        
+        result = index.getAll([p.qname() for p in SQLProps.props])
+        self._testPropertyList(result)
+
+    def test_getallresourceproperties(self):
+        num_resources = 10
+        index = self._setupMultipleResources(num_resources)
+        result = index.getAllResources([p.qname() for p in SQLProps.props])
+        self._testResourcePropertyList(num_resources, result)
+
+#    def test_timegetallresourceproperties(self):
+#        num_resources = 1000
+#        index = self._setupMultipleResources(num_resources)
+#        t1 = time.time()
+#        result = index.getAllResources([p.qname() for p in SQLProps.props])
+#        t2 = time.time()
+#        self.assertTrue(t1 == t2,
+#                        msg=&quot;Time for 1000 prop query = %s&quot; % (t2 - t1,))
+#
+#        self._testResourcePropertyList(num_resources, result)
</ins></span></pre>
</div>
</div>

</body>
</html>