[CalendarServer-changes] [6169] CalendarServer/branches/generic-sqlstore

source_changes at macosforge.org source_changes at macosforge.org
Tue Aug 24 09:07:46 PDT 2010


Revision: 6169
          http://trac.macosforge.org/projects/calendarserver/changeset/6169
Author:   cdaboo at apple.com
Date:     2010-08-24 09:07:45 -0700 (Tue, 24 Aug 2010)
Log Message:
-----------
Move postgres property store to a generic sql property store.

Modified Paths:
--------------
    CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/postgres.py
    CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py
    CalendarServer/branches/generic-sqlstore/txdav/propertystore/test/base.py

Added Paths:
-----------
    CalendarServer/branches/generic-sqlstore/txdav/propertystore/sql.py
    CalendarServer/branches/generic-sqlstore/txdav/propertystore/test/test_sql.py

Modified: CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/postgres.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/postgres.py	2010-08-23 18:36:43 UTC (rev 6168)
+++ CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/postgres.py	2010-08-24 16:07:45 UTC (rev 6169)
@@ -58,11 +58,10 @@
 
 from txcaldav.icalendarstore import (ICalendarHome, ICalendar, ICalendarObject, IAttachment)
 from txcarddav.iaddressbookstore import (IAddressBookHome, IAddressBook, IAddressBookObject)
-from txdav.propertystore.base import AbstractPropertyStore, PropertyName
-from txdav.propertystore.none import PropertyStore
+from txdav.propertystore.base import PropertyName
+from txdav.propertystore.sql import PropertyStore
 
 from twext.web2.http_headers import MimeType, generateContentType
-from twext.web2.dav.element.parser import WebDAVDocument
 
 from twext.python.log import Logger, LoggingMixIn
 from twext.python.vcomponent import VComponent
@@ -146,50 +145,6 @@
 }
 
 
-class PropertyStore(AbstractPropertyStore):
-
-    def __init__(self, defaultuser, txn, resourceID):
-        super(PropertyStore, self).__init__(defaultuser)
-        self._txn = txn
-        self._resourceID = resourceID
-
-
-    def _getitem_uid(self, key, uid):
-        rows = self._txn.execSQL(
-            "select VALUE from RESOURCE_PROPERTY where "
-            "RESOURCE_ID = %s and NAME = %s and VIEWER_UID = %s",
-            [self._resourceID, key.toString(), uid])
-        if not rows:
-            raise KeyError(key)
-        return WebDAVDocument.fromString(rows[0][0]).root_element
-
-
-    def _setitem_uid(self, key, value, uid):
-        self._delitem_uid(key, uid)
-        self._txn.execSQL(
-            "insert into RESOURCE_PROPERTY "
-            "(RESOURCE_ID, NAME, VALUE, VIEWER_UID) values (%s, %s, %s, %s)",
-            [self._resourceID, key.toString(), value.toxml(), uid])
-
-
-    def _delitem_uid(self, key, uid):
-        self._txn.execSQL(
-            "delete from RESOURCE_PROPERTY where VIEWER_UID = %s"
-            "and RESOURCE_ID = %s AND NAME = %s",
-            [uid, self._resourceID, key.toString()])
-
-
-    def _keys_uid(self, uid):
-        rows = self._txn.execSQL(
-            "select NAME from RESOURCE_PROPERTY where "
-            "VIEWER_UID = %s and RESOURCE_ID = %s",
-            [uid, self._resourceID]
-        )
-        for row in rows:
-            yield PropertyName.fromString(row[0])
-
-
-
 class PostgresCalendarObject(object):
     implements(ICalendarObject)
 

Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py	2010-08-23 18:36:43 UTC (rev 6168)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py	2010-08-24 16:07:45 UTC (rev 6169)
@@ -108,9 +108,11 @@
         return 'PG-TXN<%s>' % (self._label,)
 
 
-    def execSQL(self, sql, args=[]):
+    def execSQL(self, sql, args=[], raiseOnZeroRowCount=None):
         # print 'EXECUTE %s: %s' % (self._label, sql)
         self._cursor.execute(sql, args)
+        if raiseOnZeroRowCount is not None and self._cursor.rowcount == 0:
+            raise raiseOnZeroRowCount()
         if self._cursor.description:
             return self._cursor.fetchall()
         else:

Added: CalendarServer/branches/generic-sqlstore/txdav/propertystore/sql.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/propertystore/sql.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/propertystore/sql.py	2010-08-24 16:07:45 UTC (rev 6169)
@@ -0,0 +1,81 @@
+# -*- test-case-name: txcaldav.calendarstore.test.test_postgres -*-
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+PostgreSQL data store.
+"""
+
+__all__ = [
+    "PropertyStore",
+]
+
+from txdav.propertystore.base import AbstractPropertyStore, PropertyName,\
+    validKey
+
+from twext.web2.dav.davxml import WebDAVDocument
+
+class PropertyStore(AbstractPropertyStore):
+
+    def __init__(self, defaultuser, txn, resourceID):
+        super(PropertyStore, self).__init__(defaultuser)
+        self._txn = txn
+        self._resourceID = resourceID
+
+
+    def _getitem_uid(self, key, uid):
+        validKey(key)
+        rows = self._txn.execSQL(
+            "select VALUE from RESOURCE_PROPERTY where "
+            "RESOURCE_ID = %s and NAME = %s and VIEWER_UID = %s",
+            [self._resourceID, key.toString(), uid]
+        )
+        if not rows:
+            raise KeyError(key)
+        return WebDAVDocument.fromString(rows[0][0]).root_element
+
+
+    def _setitem_uid(self, key, value, uid):
+        validKey(key)
+        try:
+            self._delitem_uid(key, uid)
+        except KeyError:
+            pass
+        self._txn.execSQL(
+            "insert into RESOURCE_PROPERTY "
+            "(RESOURCE_ID, NAME, VALUE, VIEWER_UID) values (%s, %s, %s, %s)",
+            [self._resourceID, key.toString(), value.toxml(), uid]
+        )
+
+
+    def _delitem_uid(self, key, uid):
+        validKey(key)
+        self._txn.execSQL(
+            "delete from RESOURCE_PROPERTY where VIEWER_UID = %s"
+            "and RESOURCE_ID = %s AND NAME = %s",
+            [uid, self._resourceID, key.toString()],
+            raiseOnZeroRowCount=lambda:KeyError(key)
+        )
+            
+
+    def _keys_uid(self, uid):
+        rows = self._txn.execSQL(
+            "select NAME from RESOURCE_PROPERTY where "
+            "VIEWER_UID = %s and RESOURCE_ID = %s",
+            [uid, self._resourceID]
+        )
+        for row in rows:
+            yield PropertyName.fromString(row[0])

Modified: CalendarServer/branches/generic-sqlstore/txdav/propertystore/test/base.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/propertystore/test/base.py	2010-08-23 18:36:43 UTC (rev 6168)
+++ CalendarServer/branches/generic-sqlstore/txdav/propertystore/test/base.py	2010-08-24 16:07:45 UTC (rev 6169)
@@ -38,34 +38,75 @@
 class PropertyStoreTest(unittest.TestCase):
     # Subclass must define self.propertyStore in setUp().
 
+    def _preTest(self):
+        self.addCleanup(self._postTest)
+    def _postTest(self):
+        pass
+    def _changed(self, store):
+        store.flush()
+    def _abort(self, store):
+        store.abort()
+
     def test_interface(self):
         try:
+            self._preTest()
             verifyObject(IPropertyStore, self.propertyStore)
         except BrokenMethodImplementation, e:
             self.fail(e)
 
     def test_set_get_contains(self):
+        
+        self._preTest()
         store = self.propertyStore
 
         name = propertyName("test")
         value = propertyValue("Hello, World!")
 
+        # Test with commit after change
         store[name] = value
+        self._changed(store)
         self.assertEquals(store.get(name, None), value)
         self.failUnless(name in store)
 
+        # Test without commit after change
+        value = propertyValue("Hello, Universe!")
+        store[name] = value
+        self.assertEquals(store.get(name, None), value)
+        self.failUnless(name in store)
+
     def test_delete_get_contains(self):
+
+        self._preTest()
         store = self.propertyStore
 
+        # Test with commit after change
         name = propertyName("test")
         value = propertyValue("Hello, World!")
 
         store[name] = value
+        self._changed(store)
+
         del store[name]
+        self._changed(store)
+
         self.assertEquals(store.get(name, None), None)
         self.failIf(name in store)
 
+        # Test without commit after change
+        name = propertyName("test")
+        value = propertyValue("Hello, Universe!")
+
+        store[name] = value
+        self._changed(store)
+
+        del store[name]
+
+        self.assertEquals(store.get(name, None), None)
+        self.failIf(name in store)
+
     def test_peruser(self):
+
+        self._preTest()
         store1 = self.propertyStore1
         store2 = self.propertyStore2
 
@@ -74,34 +115,36 @@
         value2 = propertyValue("Hello, World2!")
 
         store1[name] = value1
-        store1.flush()
+        self._changed(store1)
         self.assertEquals(store1.get(name, None), value1)
         self.assertEquals(store2.get(name, None), None)
         self.failUnless(name in store1)
         self.failIf(name in store2)
         
         store2[name] = value2
-        store2.flush()
+        self._changed(store2)
         self.assertEquals(store1.get(name, None), value1)
         self.assertEquals(store2.get(name, None), value2)
         self.failUnless(name in store1)
         self.failUnless(name in store2)
         
         del store2[name]
-        store2.flush()
+        self._changed(store2)
         self.assertEquals(store1.get(name, None), value1)
         self.assertEquals(store2.get(name, None), None)
         self.failUnless(name in store1)
         self.failIf(name in store2)
         
         del store1[name]
-        store1.flush()
+        self._changed(store1)
         self.assertEquals(store1.get(name, None), None)
         self.assertEquals(store2.get(name, None), None)
         self.failIf(name in store1)
         self.failIf(name in store2)
         
     def test_peruser_shadow(self):
+
+        self._preTest()
         store1 = self.propertyStore1
         store2 = self.propertyStore2
 
@@ -114,35 +157,37 @@
         value2 = propertyValue("Hello, World2!")
 
         store1[name] = value1
-        store1.flush()
+        self._changed(store1)
         self.assertEquals(store1.get(name, None), value1)
         self.assertEquals(store2.get(name, None), value1)
         self.failUnless(name in store1)
         self.failUnless(name in store2)
         
         store2[name] = value2
-        store2.flush()
+        self._changed(store2)
         self.assertEquals(store1.get(name, None), value1)
         self.assertEquals(store2.get(name, None), value2)
         self.failUnless(name in store1)
         self.failUnless(name in store2)
         
         del store2[name]
-        store2.flush()
+        self._changed(store2)
         self.assertEquals(store1.get(name, None), value1)
         self.assertEquals(store2.get(name, None), value1)
         self.failUnless(name in store1)
         self.failUnless(name in store2)
         
         del store1[name]
-        store1.flush()
+        self._changed(store1)
         self.assertEquals(store1.get(name, None), None)
         self.assertEquals(store2.get(name, None), None)
         self.failIf(name in store1)
         self.failIf(name in store2)
-        
 
+
     def test_peruser_global(self):
+
+        self._preTest()
         store1 = self.propertyStore1
         store2 = self.propertyStore2
 
@@ -155,21 +200,21 @@
         value2 = propertyValue("Hello, World2!")
 
         store1[name] = value1
-        store1.flush()
+        self._changed(store1)
         self.assertEquals(store1.get(name, None), value1)
         self.assertEquals(store2.get(name, None), value1)
         self.failUnless(name in store1)
         self.failUnless(name in store2)
         
         store2[name] = value2
-        store2.flush()
+        self._changed(store2)
         self.assertEquals(store1.get(name, None), value2)
         self.assertEquals(store2.get(name, None), value2)
         self.failUnless(name in store1)
         self.failUnless(name in store2)
         
         del store2[name]
-        store2.flush()
+        self._changed(store2)
         self.assertEquals(store1.get(name, None), None)
         self.assertEquals(store2.get(name, None), None)
         self.failIf(name in store1)
@@ -177,6 +222,8 @@
         
 
     def test_iteration(self):
+
+        self._preTest()
         store = self.propertyStore
 
         value = propertyValue("Hello, World!")
@@ -190,12 +237,16 @@
         self.assertEquals(len(store), len(names))
 
     def test_delete_none(self):
+
+        self._preTest()
         def doDelete():
             del self.propertyStore[propertyName("xyzzy")]
 
         self.assertRaises(KeyError, doDelete)
 
     def test_keyInPropertyName(self):
+
+        self._preTest()
         store = self.propertyStore
 
         def doGet():
@@ -208,7 +259,7 @@
             del store["xyzzy"]
 
         def doContains():
-            "xyzzy" in store
+            return "xyzzy" in store
 
         self.assertRaises(TypeError, doGet)
         self.assertRaises(TypeError, doSet)
@@ -216,6 +267,8 @@
         self.assertRaises(TypeError, doContains)
 
     def test_flush(self):
+
+        self._preTest()
         store = self.propertyStore
 
         name = propertyName("test")
@@ -226,8 +279,8 @@
         #
         store[name] = value
 
-        store.flush()
-        store.abort()
+        self._changed(store)
+        self._abort(store)
 
         self.assertEquals(store.get(name, None), value)
         self.assertEquals(len(store), 1)
@@ -237,13 +290,15 @@
         #
         del store[name]
 
-        store.flush()
-        store.abort()
+        self._changed(store)
+        self._abort(store)
 
         self.assertEquals(store.get(name, None), None)
         self.assertEquals(len(store), 0)
 
     def test_abort(self):
+
+        self._preTest()
         store = self.propertyStore
 
         name = propertyName("test")
@@ -251,7 +306,7 @@
 
         store[name] = value
 
-        store.abort()
+        self._abort(store)
 
         self.assertEquals(store.get(name, None), None)
         self.assertEquals(len(store), 0)

Added: CalendarServer/branches/generic-sqlstore/txdav/propertystore/test/test_sql.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/propertystore/test/test_sql.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/propertystore/test/test_sql.py	2010-08-24 16:07:45 UTC (rev 6169)
@@ -0,0 +1,190 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Tests for txcaldav.calendarstore.postgres, mostly based on
+L{txcaldav.calendarstore.test.common}.
+"""
+
+
+from twext.python.filepath import CachingFilePath
+
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred, inlineCallbacks, succeed
+from twisted.internet.task import deferLater
+from twisted.python import log
+
+from txcaldav.calendarstore.postgres import v1_schema
+from txcaldav.calendarstore.test.common import StubNotifierFactory
+
+from txdav.common.datastore.sql import CommonDataStore
+from txdav.datastore.subpostgres import PostgresService
+from txdav.propertystore.base import PropertyName
+from txdav.propertystore.test import base
+
+try:
+    from txdav.propertystore.sql import PropertyStore
+except ImportError, e:
+    PropertyStore = None
+    importErrorMessage = str(e)
+
+
+
+class StoreBuilder(object):
+    """
+    Test-fixture-builder which can construct a PostgresStore.
+    """
+    sharedService = None
+    currentTestID = None
+
+    SHARED_DB_PATH = "../_test_postgres_db"
+
+    def buildStore(self, testCase, notifierFactory):
+        """
+        Do the necessary work to build a store for a particular test case.
+
+        @return: a L{Deferred} which fires with an L{IDataStore}.
+        """
+        currentTestID = testCase.id()
+        dbRoot = CachingFilePath(self.SHARED_DB_PATH)
+        if self.sharedService is None:
+            ready = Deferred()
+            def getReady(connectionFactory):
+                attachmentRoot = dbRoot.child("attachments")
+                try:
+                    attachmentRoot.createDirectory()
+                except OSError:
+                    pass
+                try:
+                    self.store = CommonDataStore(
+                        lambda label=None: connectionFactory(
+                            label or currentTestID
+                        ),
+                        notifierFactory,
+                        attachmentRoot
+                    )
+                except:
+                    ready.errback()
+                    raise
+                else:
+                    self.cleanDatabase(testCase)
+                    ready.callback(self.store)
+                return self.store
+            self.sharedService = PostgresService(
+                dbRoot, getReady, v1_schema, "caldav", resetSchema=True,
+                testMode=True
+            )
+            self.sharedService.startService()
+            def startStopping():
+                log.msg("Starting stopping.")
+                self.sharedService.unpauseMonitor()
+                return self.sharedService.stopService()
+            reactor.addSystemEventTrigger(#@UndefinedVariable
+                "before", "shutdown", startStopping)
+            result = ready
+        else:
+            self.store.notifierFactory = notifierFactory
+            self.cleanDatabase(testCase)
+            result = succeed(self.store)
+
+        def cleanUp():
+            # FIXME: clean up any leaked connections and report them with an
+            # immediate test failure.
+            def stopit():
+                self.sharedService.pauseMonitor()
+            return deferLater(reactor, 0.1, stopit)
+        testCase.addCleanup(cleanUp)
+        return result
+
+
+    def cleanDatabase(self, testCase):
+        cleanupConn = self.store.connectionFactory(
+            "%s schema-cleanup" % (testCase.id(),)
+        )
+        cursor = cleanupConn.cursor()
+        tables = ['INVITE',
+                  'RESOURCE_PROPERTY',
+                  'ATTACHMENT',
+                  'ADDRESSBOOK_OBJECT',
+                  'CALENDAR_OBJECT',
+                  'CALENDAR_BIND',
+                  'ADDRESSBOOK_BIND',
+                  'CALENDAR',
+                  'ADDRESSBOOK',
+                  'CALENDAR_HOME',
+                  'ADDRESSBOOK_HOME',
+                  'NOTIFICATION',
+                  'NOTIFICATION_HOME']
+        for table in tables:
+            try:
+                cursor.execute("delete from "+table)
+            except:
+                log.err()
+        cleanupConn.commit()
+        cleanupConn.close()
+
+
+
+theStoreBuilder = StoreBuilder()
+buildStore = theStoreBuilder.buildStore
+
+
+class PropertyStoreTest(base.PropertyStoreTest):
+
+    def _preTest(self):
+        self._txn = self.store.newTransaction()
+        self.propertyStore = self.propertyStore1 = PropertyStore(
+            "user01", self._txn, 1
+        )
+        self.propertyStore2 = PropertyStore("user01", self._txn, 1)
+        self.propertyStore2._setPerUserUID("user02")
+        
+        self.addCleanup(self._postTest)
+
+    def _postTest(self):
+        if hasattr(self, "_txn"):
+            self._txn.commit()
+            delattr(self, "_txn")
+        self.propertyStore = self.propertyStore1 = self.propertyStore2 = None
+
+    def _changed(self, store):
+        if hasattr(self, "_txn"):
+            self._txn.commit()
+            delattr(self, "_txn")
+        self._txn = self.store.newTransaction()
+        self.propertyStore1._txn = self._txn
+        self.propertyStore2._txn = self._txn
+
+    def _abort(self, store):
+        if hasattr(self, "_txn"):
+            self._txn.abort()
+            delattr(self, "_txn")
+
+        self._txn = self.store.newTransaction()
+        self.propertyStore1._txn = self._txn
+        self.propertyStore2._txn = self._txn
+
+    @inlineCallbacks
+    def setUp(self):
+        self.notifierFactory = StubNotifierFactory()
+        self.store = yield buildStore(self, self.notifierFactory)
+
+if PropertyStore is None:
+    PropertyStoreTest.skip = importErrorMessage
+
+
+def propertyName(name):
+    return PropertyName("http://calendarserver.org/ns/test/", name)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100824/cb44bf7b/attachment-0001.html>


More information about the calendarserver-changes mailing list