[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