<!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>[11860] CalendarServer/trunk</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/11860">11860</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2013-10-31 14:40:44 -0700 (Thu, 31 Oct 2013)</dd>
</dl>

<h3>Log Message</h3>
<pre>Allow for incremental upgrade of specific calendar homes.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunkcalendarservertapcaldavpy">CalendarServer/trunk/calendarserver/tap/caldav.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolsupgradepy">CalendarServer/trunk/calendarserver/tools/upgrade.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavstdconfigpy">CalendarServer/trunk/twistedcaldav/stdconfig.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresqlpy">CalendarServer/trunk/txdav/common/datastore/sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoreupgradesqlupgradescalendar_upgrade_from_3_to_4py">CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_3_to_4.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoreupgradesqlupgradescalendar_upgrade_from_4_to_5py">CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_4_to_5.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoreupgradesqlupgradestesttest_upgrade_from_3_to_4py">CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/test/test_upgrade_from_3_to_4.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoreupgradesqlupgradestesttest_upgrade_from_4_to_5py">CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/test/test_upgrade_from_4_to_5.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoreupgradesqlupgradesutilpy">CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/util.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunkcalendarservertapcaldavpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tap/caldav.py (11859 => 11860)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tap/caldav.py        2013-10-31 18:37:58 UTC (rev 11859)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py        2013-10-31 21:40:44 UTC (rev 11860)
</span><span class="lines">@@ -1429,7 +1429,9 @@
</span><span class="cx"> 
</span><span class="cx">                 # Conditionally stop after upgrade at this point
</span><span class="cx">                 pps.addStep(
</span><del>-                    QuitAfterUpgradeStep(config.StopAfterUpgradeTriggerFile)
</del><ins>+                    QuitAfterUpgradeStep(
+                        config.StopAfterUpgradeTriggerFile or config.UpgradeHomePrefix
+                    )
</ins><span class="cx">                 )
</span><span class="cx"> 
</span><span class="cx">                 pps.addStep(
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolsupgradepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/upgrade.py (11859 => 11860)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/upgrade.py        2013-10-31 18:37:58 UTC (rev 11859)
+++ CalendarServer/trunk/calendarserver/tools/upgrade.py        2013-10-31 21:40:44 UTC (rev 11860)
</span><span class="lines">@@ -82,6 +82,7 @@
</span><span class="cx"> 
</span><span class="cx">     optParameters = [
</span><span class="cx">         ['config', 'f', DEFAULT_CONFIG_FILE, &quot;Specify caldavd.plist configuration path.&quot;],
</span><ins>+        ['prefix', 'x', &quot;&quot;, &quot;Only upgrade homes with the specified GUID prefix - partial upgrade only.&quot;],
</ins><span class="cx">     ]
</span><span class="cx"> 
</span><span class="cx">     def __init__(self):
</span><span class="lines">@@ -197,9 +198,11 @@
</span><span class="cx">             data.MergeUpgrades = True
</span><span class="cx">         config.addPostUpdateHooks([setMerge])
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def makeService(store):
</span><span class="cx">         return UpgraderService(store, options, output, reactor, config)
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def onlyUpgradeEvents(eventDict):
</span><span class="cx">         text = formatEvent(eventDict)
</span><span class="cx">         output.write(logDateString() + &quot; &quot; + text + &quot;\n&quot;)
</span><span class="lines">@@ -209,14 +212,19 @@
</span><span class="cx">         log.publisher.levels.setLogLevelForNamespace(None, LogLevel.debug)
</span><span class="cx">         addObserver(onlyUpgradeEvents)
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def customServiceMaker():
</span><span class="cx">         customService = CalDAVServiceMaker()
</span><span class="cx">         customService.doPostImport = options[&quot;postprocess&quot;]
</span><span class="cx">         return customService
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def _patchConfig(config):
</span><span class="cx">         config.FailIfUpgradeNeeded = options[&quot;status&quot;]
</span><ins>+        if options[&quot;prefix&quot;]:
+            config.UpgradeHomePrefix = options[&quot;prefix&quot;]
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def _onShutdown():
</span><span class="cx">         if not UpgraderService.started:
</span><span class="cx">             print(&quot;Failed to start service.&quot;)
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavstdconfigpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py (11859 => 11860)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/stdconfig.py        2013-10-31 18:37:58 UTC (rev 11859)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py        2013-10-31 21:40:44 UTC (rev 11860)
</span><span class="lines">@@ -307,9 +307,15 @@
</span><span class="cx">     &quot;FailIfUpgradeNeeded&quot;  : True, # Set to True to prevent the server or utility tools
</span><span class="cx">                                    # tools from running if the database needs a schema
</span><span class="cx">                                    # upgrade.
</span><del>-    &quot;StopAfterUpgradeTriggerFile&quot; : &quot;stop_after_upgrade&quot;, # if this file exists
-        # in ConfigRoot, stop the service after finishing upgrade phase
</del><ins>+    &quot;StopAfterUpgradeTriggerFile&quot; : &quot;stop_after_upgrade&quot;,   # if this file exists in ConfigRoot, stop
+                                                            # the service after finishing upgrade phase
</ins><span class="cx"> 
</span><ins>+    &quot;UpgradeHomePrefix&quot;    : &quot;&quot;,    # When upgrading, only upgrade homes where the owner UID starts with
+                                    # with the specified prefix. The upgrade will only be partial and only
+                                    # apply to upgrade pieces that affect entire homes. The upgrade will
+                                    # need to be run again without this prefix set to complete the overall
+                                    # upgrade.
+
</ins><span class="cx">     #
</span><span class="cx">     # Types of service provided
</span><span class="cx">     #
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/sql.py (11859 => 11860)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql.py        2013-10-31 18:37:58 UTC (rev 11859)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py        2013-10-31 21:40:44 UTC (rev 11860)
</span><span class="lines">@@ -1444,6 +1444,7 @@
</span><span class="cx">         self._txn = transaction
</span><span class="cx">         self._ownerUID = ownerUID
</span><span class="cx">         self._resourceID = None
</span><ins>+        self._dataVersion = None
</ins><span class="cx">         self._childrenLoaded = False
</span><span class="cx">         self._children = {}
</span><span class="cx">         self._notifiers = None
</span><span class="lines">@@ -1689,6 +1690,23 @@
</span><span class="cx">             yield queryCacher.invalidateAfterCommit(self._txn, cacheKey)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @classproperty
+    def _dataVersionQuery(cls): #@NoSelf
+        ch = cls._homeSchema
+        return Select(
+            [ch.DATAVERSION], From=ch,
+            Where=ch.RESOURCE_ID == Parameter(&quot;resourceID&quot;)
+        )
+
+
+    @inlineCallbacks
+    def dataVersion(self):
+        if self._dataVersion is None:
+            self._dataVersion = (yield self._dataVersionQuery.on(
+                self._txn, resourceID=self._resourceID))[0][0]
+        returnValue(self._dataVersion)
+
+
</ins><span class="cx">     def name(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Implement L{IDataStoreObject.name} to return the uid.
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoreupgradesqlupgradescalendar_upgrade_from_3_to_4py"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_3_to_4.py (11859 => 11860)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_3_to_4.py        2013-10-31 18:37:58 UTC (rev 11859)
+++ CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_3_to_4.py        2013-10-31 21:40:44 UTC (rev 11860)
</span><span class="lines">@@ -15,21 +15,17 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><del>-from twext.enterprise.dal.syntax import Select, Delete, Parameter
-
</del><span class="cx"> from twisted.internet.defer import inlineCallbacks
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav import caldavxml, customxml
</span><span class="cx"> 
</span><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><span class="cx"> from txdav.caldav.icalendarstore import InvalidDefaultCalendar
</span><del>-from txdav.common.datastore.sql_tables import schema, _BIND_MODE_OWN
-from txdav.common.datastore.upgrade.sql.upgrades.util import rowsForProperty, updateCalendarDataVersion, \
-    updateAllCalendarHomeDataVersions, removeProperty, countProperty, cleanPropertyStore, \
-    logUpgradeStatus, logUpgradeError
-from txdav.xml.parser import WebDAVDocument
</del><ins>+from txdav.common.datastore.sql_tables import schema
+from txdav.common.datastore.upgrade.sql.upgrades.util import updateCalendarDataVersion, \
+    removeProperty, cleanPropertyStore, logUpgradeStatus, doToEachHomeNotAtVersion
</ins><span class="cx"> from txdav.xml import element
</span><del>-from twisted.python.failure import Failure
</del><ins>+from twistedcaldav.config import config
</ins><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> Data upgrade from database version 3 to 4
</span><span class="lines">@@ -43,212 +39,111 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Do the required upgrade steps.
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    yield moveDefaultCalendarProperties(sqlStore)
-    yield moveCalendarTranspProperties(sqlStore)
-    yield moveDefaultAlarmProperties(sqlStore)
-    yield removeResourceType(sqlStore)
</del><ins>+    yield updateCalendarHomes(sqlStore, config.UpgradeHomePrefix)
</ins><span class="cx"> 
</span><del>-    # Always bump the DB value
-    yield updateCalendarDataVersion(sqlStore, UPGRADE_TO_VERSION)
-    yield updateAllCalendarHomeDataVersions(sqlStore, UPGRADE_TO_VERSION)
</del><ins>+    # Don't do remaining upgrade if we are only process a subset of the homes
+    if not config.UpgradeHomePrefix:
+        yield removeResourceType(sqlStore)
</ins><span class="cx"> 
</span><ins>+        # Always bump the DB value
+        yield updateCalendarDataVersion(sqlStore, UPGRADE_TO_VERSION)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> @inlineCallbacks
</span><del>-def moveDefaultCalendarProperties(sqlStore):
</del><ins>+def updateCalendarHomes(sqlStore, prefix=None):
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    Need to move all the CalDAV:default-calendar and CS:default-tasks properties in the
-    RESOURCE_PROPERTY table to the new CALENDAR_HOME_METADATA table columns, extracting
-    the new value from the XML property.
</del><ins>+    For each calendar home, update the associated properties on the home or its owned calendars.
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    meta = schema.CALENDAR_HOME_METADATA
-    yield _processDefaultCalendarProperty(sqlStore, caldavxml.ScheduleDefaultCalendarURL, meta.DEFAULT_EVENTS)
-    yield _processDefaultCalendarProperty(sqlStore, customxml.ScheduleDefaultTasksURL, meta.DEFAULT_TASKS)
</del><ins>+    yield doToEachHomeNotAtVersion(sqlStore, schema.CALENDAR_HOME, UPGRADE_TO_VERSION, updateCalendarHome, &quot;Update Calendar Home&quot;, filterOwnerUID=prefix)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><del>-def _processDefaultCalendarProperty(sqlStore, propname, colname):
</del><ins>+def updateCalendarHome(txn, homeResourceID):
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    Move the specified property value to the matching CALENDAR_HOME_METADATA table column.
-
-    Since the number of calendar homes may well be large, we need to do this in batches.
</del><ins>+    For this calendar home, update the associated properties on the home or its owned calendars.
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    cb = schema.CALENDAR_BIND
-    rp = schema.RESOURCE_PROPERTY
</del><ins>+    home = yield txn.calendarHomeWithResourceID(homeResourceID)
+    yield moveDefaultCalendarProperties(home)
+    yield moveCalendarTranspProperties(home)
+    yield moveDefaultAlarmProperties(home)
+    yield cleanPropertyStore()
</ins><span class="cx"> 
</span><del>-    logUpgradeStatus(&quot;Starting Process {}&quot;.format(propname.qname()))
</del><span class="cx"> 
</span><del>-    sqlTxn = sqlStore.newTransaction()
-    total = (yield countProperty(sqlTxn, propname))
-    yield sqlTxn.commit()
-    count = 0
</del><span class="cx"> 
</span><del>-    try:
-        inbox_rid = None
-        while True:
-            sqlTxn = sqlStore.newTransaction()
-            rows = (yield rowsForProperty(sqlTxn, propname, batch=BATCH_SIZE))
-            if len(rows) == 0:
-                yield sqlTxn.commit()
-                break
</del><ins>+@inlineCallbacks
+def moveDefaultCalendarProperties(home):
+    &quot;&quot;&quot;
+    Need to move any the CalDAV:default-calendar and CS:default-tasks properties in the
+    RESOURCE_PROPERTY table to the new CALENDAR_HOME_METADATA table columns, extracting
+    the new value from the XML property.
+    &quot;&quot;&quot;
</ins><span class="cx"> 
</span><del>-            delete_ids = []
-            for inbox_rid, value in rows:
-                delete_ids.append(inbox_rid)
-                ids = yield Select(
-                    [cb.CALENDAR_HOME_RESOURCE_ID, ],
-                    From=cb,
-                    Where=cb.CALENDAR_RESOURCE_ID == inbox_rid,
-                ).on(sqlTxn)
-                if len(ids) &gt; 0:
</del><ins>+    yield _processDefaultCalendarProperty(home, caldavxml.ScheduleDefaultCalendarURL)
+    yield _processDefaultCalendarProperty(home, customxml.ScheduleDefaultTasksURL)
</ins><span class="cx"> 
</span><del>-                    calendarHome = (yield sqlTxn.calendarHomeWithResourceID(ids[0][0]))
-                    if calendarHome is not None:
</del><span class="cx"> 
</span><del>-                        prop = WebDAVDocument.fromString(value).root_element
-                        defaultCalendar = str(prop.children[0])
-                        parts = defaultCalendar.split(&quot;/&quot;)
-                        if len(parts) == 5:
</del><span class="cx"> 
</span><del>-                            calendarName = parts[-1]
-                            calendarHomeUID = parts[-2]
-                            expectedHome = (yield sqlTxn.calendarHomeWithUID(calendarHomeUID))
-                            if expectedHome is not None and expectedHome.id() == calendarHome.id():
</del><ins>+@inlineCallbacks
+def _processDefaultCalendarProperty(home, propname):
+    &quot;&quot;&quot;
+    Move the specified property value to the matching CALENDAR_HOME_METADATA table column.
+    &quot;&quot;&quot;
</ins><span class="cx"> 
</span><del>-                                calendar = (yield calendarHome.calendarWithName(calendarName))
-                                if calendar is not None:
-                                    try:
-                                        yield calendarHome.setDefaultCalendar(
-                                            calendar, tasks=(propname == customxml.ScheduleDefaultTasksURL)
-                                        )
-                                    except InvalidDefaultCalendar:
-                                        # Ignore these - the server will recover
-                                        pass
</del><ins>+    inbox = (yield home.calendarWithName(&quot;inbox&quot;))
+    prop = inbox.properties().get(PropertyName.fromElement(propname))
+    if prop is not None:
+        defaultCalendar = str(prop.children[0])
+        parts = defaultCalendar.split(&quot;/&quot;)
+        if len(parts) == 5:
</ins><span class="cx"> 
</span><del>-            # Always delete the rows so that batch processing works correctly
-            yield Delete(
-                From=rp,
-                Where=(rp.RESOURCE_ID.In(Parameter(&quot;ids&quot;, len(delete_ids)))).And
-                      (rp.NAME == PropertyName.fromElement(propname).toString()),
-            ).on(sqlTxn, ids=delete_ids)
</del><ins>+            calendarName = parts[-1]
+            calendarHomeUID = parts[-2]
+            if calendarHomeUID == home.uid():
</ins><span class="cx"> 
</span><del>-            yield sqlTxn.commit()
</del><ins>+                calendar = (yield home.calendarWithName(calendarName))
+                if calendar is not None:
+                    try:
+                        yield home.setDefaultCalendar(
+                            calendar, tasks=(propname == customxml.ScheduleDefaultTasksURL)
+                        )
+                    except InvalidDefaultCalendar:
+                        # Ignore these - the server will recover
+                        pass
</ins><span class="cx"> 
</span><del>-            count += len(rows)
-            logUpgradeStatus(
-                &quot;Process {}&quot;.format(propname.qname()),
-                count,
-                total
-            )
</del><ins>+        del inbox.properties()[PropertyName.fromElement(propname)]
</ins><span class="cx"> 
</span><del>-        yield cleanPropertyStore()
-        logUpgradeStatus(&quot;End Process {}&quot;.format(propname.qname()))
</del><span class="cx"> 
</span><del>-    except RuntimeError as e:
-        f = Failure()
-        logUpgradeError(
-            &quot;Process {}&quot;.format(propname.qname()),
-            &quot;Inbox: {}, error: {}&quot;.format(inbox_rid, e),
-        )
-        yield sqlTxn.abort()
-        f.raiseException()
</del><span class="cx"> 
</span><del>-
-
</del><span class="cx"> @inlineCallbacks
</span><del>-def moveCalendarTranspProperties(sqlStore):
</del><ins>+def moveCalendarTranspProperties(home):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Need to move all the CalDAV:schedule-calendar-transp properties in the
</span><span class="cx">     RESOURCE_PROPERTY table to the new CALENDAR_BIND table columns, extracting
</span><span class="cx">     the new value from the XML property.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    cb = schema.CALENDAR_BIND
-    rp = schema.RESOURCE_PROPERTY
</del><ins>+    # Iterate over each calendar (both owned and shared)
+    calendars = (yield home.loadChildren())
+    for calendar in calendars:
+        if calendar.isInbox():
+            continue
+        prop = calendar.properties().get(PropertyName.fromElement(caldavxml.ScheduleCalendarTransp))
+        if prop is not None:
+            yield calendar.setUsedForFreeBusy(prop == caldavxml.ScheduleCalendarTransp(caldavxml.Opaque()))
+            del calendar.properties()[PropertyName.fromElement(caldavxml.ScheduleCalendarTransp)]
+    inbox = (yield home.calendarWithName(&quot;inbox&quot;))
+    prop = inbox.properties().get(PropertyName.fromElement(caldavxml.CalendarFreeBusySet))
+    if prop is not None:
+        del inbox.properties()[PropertyName.fromElement(caldavxml.CalendarFreeBusySet)]
</ins><span class="cx"> 
</span><del>-    propname = caldavxml.ScheduleCalendarTransp
-    logUpgradeStatus(&quot;Starting Process {}&quot;.format(propname.qname()))
</del><span class="cx"> 
</span><del>-    sqlTxn = sqlStore.newTransaction()
-    total = (yield countProperty(sqlTxn, propname))
-    yield sqlTxn.commit()
-    count = 0
</del><span class="cx"> 
</span><del>-    try:
-        calendar_rid = None
-        calendars_for_id = {}
-        while True:
-            sqlTxn = sqlStore.newTransaction()
-            rows = (yield rowsForProperty(sqlTxn, propname, with_uid=True, batch=BATCH_SIZE))
-            if len(rows) == 0:
-                yield sqlTxn.commit()
-                break
-
-            delete_ids = []
-            for calendar_rid, value, viewer in rows:
-                delete_ids.append(calendar_rid)
-                if calendar_rid not in calendars_for_id:
-                    ids = yield Select(
-                        [cb.CALENDAR_HOME_RESOURCE_ID, cb.BIND_MODE, ],
-                        From=cb,
-                        Where=cb.CALENDAR_RESOURCE_ID == calendar_rid,
-                    ).on(sqlTxn)
-                    calendars_for_id[calendar_rid] = ids
-
-                if viewer:
-                    calendarHome = (yield sqlTxn.calendarHomeWithUID(viewer))
-                else:
-                    calendarHome = None
-                    for row in calendars_for_id[calendar_rid]:
-                        home_id, bind_mode = row
-                        if bind_mode == _BIND_MODE_OWN:
-                            calendarHome = (yield sqlTxn.calendarHomeWithResourceID(home_id))
-                            break
-
-                if calendarHome is not None:
-                    prop = WebDAVDocument.fromString(value).root_element
-                    calendar = (yield calendarHome.childWithID(calendar_rid))
-                    if calendar is not None:
-                        yield calendar.setUsedForFreeBusy(prop == caldavxml.ScheduleCalendarTransp(caldavxml.Opaque()))
-
-            # Always delete the rows so that batch processing works correctly
-            yield Delete(
-                From=rp,
-                Where=(rp.RESOURCE_ID.In(Parameter(&quot;ids&quot;, len(delete_ids)))).And
-                      (rp.NAME == PropertyName.fromElement(caldavxml.ScheduleCalendarTransp).toString()),
-            ).on(sqlTxn, ids=delete_ids)
-
-            yield sqlTxn.commit()
-
-            count += len(rows)
-            logUpgradeStatus(
-                &quot;Process {}&quot;.format(propname.qname()),
-                count,
-                total,
-            )
-
-        sqlTxn = sqlStore.newTransaction()
-        yield removeProperty(sqlTxn, PropertyName.fromElement(caldavxml.CalendarFreeBusySet))
-        yield sqlTxn.commit()
-        yield cleanPropertyStore()
-        logUpgradeStatus(&quot;End Process {}&quot;.format(propname.qname()))
-
-    except RuntimeError as e:
-        f = Failure()
-        logUpgradeError(
-            &quot;Process {}&quot;.format(propname.qname()),
-            &quot;Inbox: {}, error: {}&quot;.format(calendar_rid, e),
-        )
-        yield sqlTxn.abort()
-        f.raiseException()
-
-
-
</del><span class="cx"> @inlineCallbacks
</span><del>-def moveDefaultAlarmProperties(sqlStore):
</del><ins>+def moveDefaultAlarmProperties(home):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Need to move all the CalDAV:default-calendar and CS:default-tasks properties in the
</span><span class="cx">     RESOURCE_PROPERTY table to the new CALENDAR_HOME_METADATA table columns, extracting
</span><span class="lines">@@ -256,25 +151,25 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     yield _processDefaultAlarmProperty(
</span><del>-        sqlStore,
</del><ins>+        home,
</ins><span class="cx">         caldavxml.DefaultAlarmVEventDateTime,
</span><span class="cx">         True,
</span><span class="cx">         True,
</span><span class="cx">     )
</span><span class="cx">     yield _processDefaultAlarmProperty(
</span><del>-        sqlStore,
</del><ins>+        home,
</ins><span class="cx">         caldavxml.DefaultAlarmVEventDate,
</span><span class="cx">         True,
</span><span class="cx">         False,
</span><span class="cx">     )
</span><span class="cx">     yield _processDefaultAlarmProperty(
</span><del>-        sqlStore,
</del><ins>+        home,
</ins><span class="cx">         caldavxml.DefaultAlarmVToDoDateTime,
</span><span class="cx">         False,
</span><span class="cx">         True,
</span><span class="cx">     )
</span><span class="cx">     yield _processDefaultAlarmProperty(
</span><del>-        sqlStore,
</del><ins>+        home,
</ins><span class="cx">         caldavxml.DefaultAlarmVToDoDate,
</span><span class="cx">         False,
</span><span class="cx">         False,
</span><span class="lines">@@ -283,108 +178,33 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><del>-def _processDefaultAlarmProperty(sqlStore, propname, vevent, timed):
</del><ins>+def _processDefaultAlarmProperty(home, propname, vevent, timed):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Move the specified property value to the matching CALENDAR_HOME_METADATA or CALENDAR_BIND table column.
</span><span class="cx"> 
</span><span class="cx">     Since the number of properties may well be large, we need to do this in batches.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    hm = schema.CALENDAR_HOME_METADATA
-    cb = schema.CALENDAR_BIND
-    rp = schema.RESOURCE_PROPERTY
</del><ins>+    # Check the home first
+    prop = home.properties().get(PropertyName.fromElement(propname))
+    if prop is not None:
+        alarm = str(prop.children[0]) if prop.children else None
+        yield home.setDefaultAlarm(alarm, vevent, timed)
+        del home.properties()[PropertyName.fromElement(propname)]
</ins><span class="cx"> 
</span><del>-    logUpgradeStatus(&quot;Starting Process {} {}&quot;.format(propname.qname(), vevent))
</del><ins>+    # Now each child
+    calendars = (yield home.loadChildren())
+    for calendar in calendars:
+        if calendar.isInbox():
+            continue
+        prop = calendar.properties().get(PropertyName.fromElement(propname))
+        if prop is not None:
+            alarm = str(prop.children[0]) if prop.children else None
+            yield calendar.setDefaultAlarm(alarm, vevent, timed)
+            del calendar.properties()[PropertyName.fromElement(propname)]
</ins><span class="cx"> 
</span><del>-    sqlTxn = sqlStore.newTransaction()
-    total = (yield countProperty(sqlTxn, propname))
-    yield sqlTxn.commit()
-    count = 0
</del><span class="cx"> 
</span><del>-    try:
-        rid = None
-        calendars_for_id = {}
-        while True:
-            sqlTxn = sqlStore.newTransaction()
-            rows = (yield rowsForProperty(sqlTxn, propname, with_uid=True, batch=BATCH_SIZE))
-            if len(rows) == 0:
-                yield sqlTxn.commit()
-                break
</del><span class="cx"> 
</span><del>-            delete_ids = []
-            for rid, value, viewer in rows:
-                delete_ids.append(rid)
-
-                prop = WebDAVDocument.fromString(value).root_element
-                alarm = str(prop.children[0]) if prop.children else None
-
-                # First check if the rid is a home - this is the most common case
-                ids = yield Select(
-                    [hm.RESOURCE_ID, ],
-                    From=hm,
-                    Where=hm.RESOURCE_ID == rid,
-                ).on(sqlTxn)
-
-                if len(ids) &gt; 0:
-                    # Home object
-                    calendarHome = (yield sqlTxn.calendarHomeWithResourceID(ids[0][0]))
-                    if calendarHome is not None:
-                        yield calendarHome.setDefaultAlarm(alarm, vevent, timed)
-                else:
-                    # rid is a calendar - we need to find the per-user calendar for the resource viewer
-                    if rid not in calendars_for_id:
-                        ids = yield Select(
-                            [cb.CALENDAR_HOME_RESOURCE_ID, cb.BIND_MODE, ],
-                            From=cb,
-                            Where=cb.CALENDAR_RESOURCE_ID == rid,
-                        ).on(sqlTxn)
-                        calendars_for_id[rid] = ids
-
-                    if viewer:
-                        calendarHome = (yield sqlTxn.calendarHomeWithUID(viewer))
-                    else:
-                        calendarHome = None
-                        for row in calendars_for_id[rid]:
-                            home_id, bind_mode = row
-                            if bind_mode == _BIND_MODE_OWN:
-                                calendarHome = (yield sqlTxn.calendarHomeWithResourceID(home_id))
-                                break
-
-                    if calendarHome is not None:
-                        calendar = yield calendarHome.childWithID(rid)
-                        if calendar is not None:
-                            yield calendar.setDefaultAlarm(alarm, vevent, timed)
-
-            # Always delete the rows so that batch processing works correctly
-            yield Delete(
-                From=rp,
-                Where=(rp.RESOURCE_ID.In(Parameter(&quot;ids&quot;, len(delete_ids)))).And
-                      (rp.NAME == PropertyName.fromElement(propname).toString()),
-            ).on(sqlTxn, ids=delete_ids)
-
-            yield sqlTxn.commit()
-
-            count += len(rows)
-            logUpgradeStatus(
-                &quot;Process {} {}&quot;.format(propname.qname(), vevent),
-                count,
-                total,
-            )
-
-        yield cleanPropertyStore()
-        logUpgradeStatus(&quot;End Process {} {}&quot;.format(propname.qname(), vevent))
-
-    except RuntimeError as e:
-        f = Failure()
-        logUpgradeError(
-            &quot;Process {} {}&quot;.format(propname.qname(), vevent),
-            &quot;Rid: {}, error: {}&quot;.format(rid, e),
-        )
-        yield sqlTxn.abort()
-        f.raiseException()
-
-
-
</del><span class="cx"> @inlineCallbacks
</span><span class="cx"> def removeResourceType(sqlStore):
</span><span class="cx">     logUpgradeStatus(&quot;Starting Calendar Remove Resource Type&quot;)
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoreupgradesqlupgradescalendar_upgrade_from_4_to_5py"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_4_to_5.py (11859 => 11860)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_4_to_5.py        2013-10-31 18:37:58 UTC (rev 11859)
+++ CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_4_to_5.py        2013-10-31 21:40:44 UTC (rev 11860)
</span><span class="lines">@@ -15,22 +15,18 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><del>-from twext.enterprise.dal.syntax import Select, Delete, Parameter
</del><ins>+from twext.web2.dav.resource import TwistedQuotaUsedProperty, TwistedGETContentMD5
</ins><span class="cx"> 
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks
</span><del>-from twisted.python.failure import Failure
</del><span class="cx"> 
</span><span class="cx"> from twistedcaldav import caldavxml, customxml
</span><ins>+from twistedcaldav.config import config
</ins><span class="cx"> 
</span><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><del>-from txdav.common.datastore.sql_tables import schema, _BIND_MODE_OWN
-from txdav.common.datastore.upgrade.sql.upgrades.util import rowsForProperty, updateCalendarDataVersion, \
-    updateAllCalendarHomeDataVersions, removeProperty, cleanPropertyStore, \
-    logUpgradeStatus, countProperty, logUpgradeError
</del><ins>+from txdav.common.datastore.sql_tables import schema
+from txdav.common.datastore.upgrade.sql.upgrades.util import updateCalendarDataVersion, \
+    removeProperty, cleanPropertyStore, logUpgradeStatus, doToEachHomeNotAtVersion
</ins><span class="cx"> from txdav.xml import element
</span><del>-from txdav.xml.parser import WebDAVDocument
-from twext.web2.dav.resource import TwistedQuotaUsedProperty, \
-    TwistedGETContentMD5
</del><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> Data upgrade from database version 4 to 5
</span><span class="lines">@@ -44,178 +40,75 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Do the required upgrade steps.
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    yield moveCalendarTimezoneProperties(sqlStore)
-    yield moveCalendarAvailabilityProperties(sqlStore)
-    yield removeOtherProperties(sqlStore)
</del><ins>+    yield updateCalendarHomes(sqlStore, config.UpgradeHomePrefix)
</ins><span class="cx"> 
</span><del>-    # Always bump the DB value
-    yield updateCalendarDataVersion(sqlStore, UPGRADE_TO_VERSION)
-    yield updateAllCalendarHomeDataVersions(sqlStore, UPGRADE_TO_VERSION)
</del><ins>+    # Don't do remaining upgrade if we are only process a subset of the homes
+    if not config.UpgradeHomePrefix:
+        yield removeOtherProperties(sqlStore)
</ins><span class="cx"> 
</span><ins>+        # Always bump the DB value
+        yield updateCalendarDataVersion(sqlStore, UPGRADE_TO_VERSION)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> @inlineCallbacks
</span><del>-def moveCalendarTimezoneProperties(sqlStore):
</del><ins>+def updateCalendarHomes(sqlStore, prefix=None):
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    Need to move all the CalDAV:calendar-timezone properties in the
-    RESOURCE_PROPERTY table to the new CALENDAR_BIND table columns, extracting
-    the new value from the XML property.
</del><ins>+    For each calendar home, update the associated properties on the home or its owned calendars.
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    cb = schema.CALENDAR_BIND
-    rp = schema.RESOURCE_PROPERTY
</del><ins>+    yield doToEachHomeNotAtVersion(sqlStore, schema.CALENDAR_HOME, UPGRADE_TO_VERSION, updateCalendarHome, &quot;Update Calendar Home&quot;, filterOwnerUID=prefix)
</ins><span class="cx"> 
</span><del>-    propname = caldavxml.CalendarTimeZone
-    logUpgradeStatus(&quot;Starting Process {}&quot;.format(propname.qname()))
</del><span class="cx"> 
</span><del>-    sqlTxn = sqlStore.newTransaction()
-    total = (yield countProperty(sqlTxn, propname))
-    yield sqlTxn.commit()
-    count = 0
</del><span class="cx"> 
</span><del>-    try:
-        calendar_rid = None
-        calendars_for_id = {}
-        while True:
-            sqlTxn = sqlStore.newTransaction()
-            rows = (yield rowsForProperty(sqlTxn, propname, with_uid=True, batch=BATCH_SIZE))
-            if len(rows) == 0:
-                yield sqlTxn.commit()
-                break
</del><ins>+@inlineCallbacks
+def updateCalendarHome(txn, homeResourceID):
+    &quot;&quot;&quot;
+    For this calendar home, update the associated properties on the home or its owned calendars.
+    &quot;&quot;&quot;
</ins><span class="cx"> 
</span><del>-            delete_ids = []
-            for calendar_rid, value, viewer in rows:
-                delete_ids.append(calendar_rid)
-                if calendar_rid not in calendars_for_id:
-                    ids = yield Select(
-                        [cb.CALENDAR_HOME_RESOURCE_ID, cb.BIND_MODE, ],
-                        From=cb,
-                        Where=cb.CALENDAR_RESOURCE_ID == calendar_rid,
-                    ).on(sqlTxn)
-                    calendars_for_id[calendar_rid] = ids
</del><ins>+    home = yield txn.calendarHomeWithResourceID(homeResourceID)
+    yield moveCalendarTimezoneProperties(home)
+    yield moveCalendarAvailabilityProperties(home)
+    yield cleanPropertyStore()
</ins><span class="cx"> 
</span><del>-                if viewer:
-                    calendarHome = (yield sqlTxn.calendarHomeWithUID(viewer))
-                else:
-                    calendarHome = None
-                    for row in calendars_for_id[calendar_rid]:
-                        home_id, bind_mode = row
-                        if bind_mode == _BIND_MODE_OWN:
-                            calendarHome = (yield sqlTxn.calendarHomeWithResourceID(home_id))
-                            break
</del><span class="cx"> 
</span><del>-                if calendarHome is not None:
-                    prop = WebDAVDocument.fromString(value).root_element
-                    calendar = (yield calendarHome.childWithID(calendar_rid))
-                    if calendar is not None:
-                        yield calendar.setTimezone(prop.calendar())
</del><span class="cx"> 
</span><del>-            # Always delete the rows so that batch processing works correctly
-            yield Delete(
-                From=rp,
-                Where=(rp.RESOURCE_ID.In(Parameter(&quot;ids&quot;, len(delete_ids)))).And
-                      (rp.NAME == PropertyName.fromElement(caldavxml.CalendarTimeZone).toString()),
-            ).on(sqlTxn, ids=delete_ids)
</del><ins>+@inlineCallbacks
+def moveCalendarTimezoneProperties(home):
+    &quot;&quot;&quot;
+    Need to move all the CalDAV:calendar-timezone properties in the
+    RESOURCE_PROPERTY table to the new CALENDAR_BIND table columns, extracting
+    the new value from the XML property.
+    &quot;&quot;&quot;
</ins><span class="cx"> 
</span><del>-            yield sqlTxn.commit()
-            count += len(rows)
-            logUpgradeStatus(
-                &quot;Process {}&quot;.format(propname.qname()),
-                count,
-                total,
-            )
</del><ins>+    # Iterate over each calendar (both owned and shared)
+    calendars = (yield home.loadChildren())
+    for calendar in calendars:
+        if calendar.isInbox():
+            continue
+        prop = calendar.properties().get(PropertyName.fromElement(caldavxml.CalendarTimeZone))
+        if prop is not None:
+            yield calendar.setTimezone(prop.calendar())
+            del calendar.properties()[PropertyName.fromElement(caldavxml.CalendarTimeZone)]
</ins><span class="cx"> 
</span><del>-        yield cleanPropertyStore()
-        logUpgradeStatus(&quot;End Process {}&quot;.format(propname.qname()))
</del><span class="cx"> 
</span><del>-    except RuntimeError as e:
-        f = Failure()
-        logUpgradeError(
-            &quot;Process {}&quot;.format(propname.qname()),
-            &quot;Rid: {}, error: {}&quot;.format(calendar_rid, e),
-        )
-        yield sqlTxn.abort()
-        f.raiseException()
</del><span class="cx"> 
</span><del>-
-
</del><span class="cx"> @inlineCallbacks
</span><del>-def moveCalendarAvailabilityProperties(sqlStore):
</del><ins>+def moveCalendarAvailabilityProperties(home):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Need to move all the CS:calendar-availability properties in the
</span><span class="cx">     RESOURCE_PROPERTY table to the new CALENDAR_BIND table columns, extracting
</span><span class="cx">     the new value from the XML property.
</span><span class="cx">     &quot;&quot;&quot;
</span><ins>+    inbox = (yield home.calendarWithName(&quot;inbox&quot;))
+    prop = inbox.properties().get(PropertyName.fromElement(customxml.CalendarAvailability))
+    if prop is not None:
+        yield home.setAvailability(prop.calendar())
+        del inbox.properties()[customxml.CalendarAvailability]
</ins><span class="cx"> 
</span><del>-    cb = schema.CALENDAR_BIND
-    rp = schema.RESOURCE_PROPERTY
</del><span class="cx"> 
</span><del>-    propname = customxml.CalendarAvailability
-    logUpgradeStatus(&quot;Starting Process {}&quot;.format(propname.qname()))
</del><span class="cx"> 
</span><del>-    sqlTxn = sqlStore.newTransaction()
-    total = (yield countProperty(sqlTxn, propname))
-    yield sqlTxn.commit()
-    count = 0
-
-    try:
-        calendar_rid = None
-        while True:
-            sqlTxn = sqlStore.newTransaction()
-            rows = (yield rowsForProperty(sqlTxn, propname, batch=BATCH_SIZE))
-            if len(rows) == 0:
-                yield sqlTxn.commit()
-                break
-
-            # Map each calendar to a home id using a single query for efficiency
-            calendar_ids = [row[0] for row in rows]
-
-            home_map = yield Select(
-                [cb.CALENDAR_RESOURCE_ID, cb.CALENDAR_HOME_RESOURCE_ID, ],
-                From=cb,
-                Where=(cb.CALENDAR_RESOURCE_ID.In(Parameter(&quot;ids&quot;, len(calendar_ids)))).And(cb.BIND_MODE == _BIND_MODE_OWN),
-            ).on(sqlTxn, ids=calendar_ids)
-            calendar_to_home = dict(home_map)
-
-            # Move property to each home
-            for calendar_rid, value in rows:
-                if calendar_rid in calendar_to_home:
-                    calendarHome = (yield sqlTxn.calendarHomeWithResourceID(calendar_to_home[calendar_rid]))
-
-                    if calendarHome is not None:
-                        prop = WebDAVDocument.fromString(value).root_element
-                        yield calendarHome.setAvailability(prop.calendar())
-
-            # Always delete the rows so that batch processing works correctly
-            yield Delete(
-                From=rp,
-                Where=(rp.RESOURCE_ID.In(Parameter(&quot;ids&quot;, len(calendar_ids)))).And
-                      (rp.NAME == PropertyName.fromElement(customxml.CalendarAvailability).toString()),
-            ).on(sqlTxn, ids=calendar_ids)
-
-            yield sqlTxn.commit()
-
-            count += len(rows)
-            logUpgradeStatus(
-                &quot;Process {}&quot;.format(propname.qname()),
-                count,
-                total,
-            )
-
-        yield cleanPropertyStore()
-        logUpgradeStatus(&quot;End Process {}&quot;.format(propname.qname()))
-
-    except RuntimeError as e:
-        f = Failure()
-        logUpgradeError(
-            &quot;Process {}&quot;.format(propname.qname()),
-            &quot;Rid: {}, error: {}&quot;.format(calendar_rid, e),
-        )
-        yield sqlTxn.abort()
-        f.raiseException()
-
-
-
</del><span class="cx"> @inlineCallbacks
</span><span class="cx"> def removeOtherProperties(sqlStore):
</span><span class="cx">     &quot;&quot;&quot;
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoreupgradesqlupgradestesttest_upgrade_from_3_to_4py"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/test/test_upgrade_from_3_to_4.py (11859 => 11860)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/test/test_upgrade_from_3_to_4.py        2013-10-31 18:37:58 UTC (rev 11859)
+++ CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/test/test_upgrade_from_3_to_4.py        2013-10-31 21:40:44 UTC (rev 11860)
</span><span class="lines">@@ -13,23 +13,27 @@
</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><ins>+
+from twext.enterprise.dal.syntax import Update, Insert
+
+from twistedcaldav import caldavxml
</ins><span class="cx"> from twistedcaldav.caldavxml import ScheduleDefaultCalendarURL, \
</span><del>-    CalendarFreeBusySet, Opaque, ScheduleCalendarTransp
</del><ins>+    CalendarFreeBusySet, Opaque, ScheduleCalendarTransp, Transparent
+
</ins><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><span class="cx"> from txdav.caldav.datastore.test.util import CommonStoreTests
</span><ins>+from txdav.common.datastore.sql_tables import _BIND_MODE_WRITE, schema
+from txdav.common.datastore.upgrade.sql.upgrades.calendar_upgrade_from_3_to_4 import updateCalendarHomes, \
+    doUpgrade
+from txdav.xml import element
</ins><span class="cx"> from txdav.xml.element import HRef
</span><del>-from twext.enterprise.dal.syntax import Update, Insert
-from txdav.common.datastore.upgrade.sql.upgrades.calendar_upgrade_from_3_to_4 import moveDefaultCalendarProperties, \
-    moveCalendarTranspProperties, removeResourceType, moveDefaultAlarmProperties
-from txdav.xml import element
-from twistedcaldav import caldavxml
-from txdav.common.datastore.sql_tables import _BIND_MODE_WRITE, schema
</del><ins>+from twistedcaldav.config import config
</ins><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> Tests for L{txdav.common.datastore.upgrade.sql.upgrade}.
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-from twisted.internet.defer import inlineCallbacks
</del><ins>+from twisted.internet.defer import inlineCallbacks, returnValue
</ins><span class="cx"> 
</span><span class="cx"> class Upgrade_from_3_to_4(CommonStoreTests):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -37,7 +41,7 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def test_defaultCalendarUpgrade(self):
</del><ins>+    def _defaultCalendarUpgrade_setup(self):
</ins><span class="cx"> 
</span><span class="cx">         # Set dead property on inbox
</span><span class="cx">         for user in (&quot;user01&quot;, &quot;user02&quot;,):
</span><span class="lines">@@ -52,30 +56,56 @@
</span><span class="cx">                 Where=chm.RESOURCE_ID == home._resourceID,
</span><span class="cx">             ).on(self.transactionUnderTest())
</span><span class="cx"> 
</span><del>-        # Force data version to previous
-        ch = home._homeSchema
-        yield Update(
-            {ch.DATAVERSION: 3},
-            Where=ch.RESOURCE_ID == home._resourceID,
-        ).on(self.transactionUnderTest())
</del><ins>+            # Force data version to previous
+            ch = home._homeSchema
+            yield Update(
+                {ch.DATAVERSION: 3},
+                Where=ch.RESOURCE_ID == home._resourceID,
+            ).on(self.transactionUnderTest())
</ins><span class="cx"> 
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><del>-        # Trigger upgrade
-        yield moveDefaultCalendarProperties(self._sqlCalendarStore)
</del><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def _defaultCalendarUpgrade_check(self, changed_users, unchanged_users):
+
</ins><span class="cx">         # Test results
</span><del>-        for user in (&quot;user01&quot;, &quot;user02&quot;,):
</del><ins>+        for user in changed_users:
</ins><span class="cx">             home = (yield self.homeUnderTest(name=user))
</span><ins>+            version = (yield home.dataVersion())
+            self.assertEqual(version, 4)
</ins><span class="cx">             calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
</span><span class="cx">             self.assertTrue(home.isDefaultCalendar(calendar))
</span><span class="cx">             inbox = (yield self.calendarUnderTest(name=&quot;inbox&quot;, home=user))
</span><span class="cx">             self.assertTrue(PropertyName.fromElement(ScheduleDefaultCalendarURL) not in inbox.properties())
</span><span class="cx"> 
</span><ins>+        for user in unchanged_users:
+            home = (yield self.homeUnderTest(name=user))
+            version = (yield home.dataVersion())
+            self.assertEqual(version, 3)
+            calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
+            self.assertFalse(home.isDefaultCalendar(calendar))
+            inbox = (yield self.calendarUnderTest(name=&quot;inbox&quot;, home=user))
+            self.assertTrue(PropertyName.fromElement(ScheduleDefaultCalendarURL) in inbox.properties())
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def test_invalidDefaultCalendarUpgrade(self):
</del><ins>+    def test_defaultCalendarUpgrade(self):
+        yield self._defaultCalendarUpgrade_setup()
+        yield updateCalendarHomes(self._sqlCalendarStore)
+        yield self._defaultCalendarUpgrade_check((&quot;user01&quot;, &quot;user02&quot;,), ())
</ins><span class="cx"> 
</span><ins>+
+    @inlineCallbacks
+    def test_partialDefaultCalendarUpgrade(self):
+        yield self._defaultCalendarUpgrade_setup()
+        yield updateCalendarHomes(self._sqlCalendarStore, &quot;user01&quot;)
+        yield self._defaultCalendarUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;,))
+
+
+    @inlineCallbacks
+    def _invalidDefaultCalendarUpgrade_setup(self):
+
</ins><span class="cx">         # Set dead property on inbox
</span><span class="cx">         for user in (&quot;user01&quot;, &quot;user02&quot;,):
</span><span class="cx">             inbox = (yield self.calendarUnderTest(name=&quot;inbox&quot;, home=user))
</span><span class="lines">@@ -93,30 +123,56 @@
</span><span class="cx">             tasks = (yield home.createCalendarWithName(&quot;tasks_1&quot;))
</span><span class="cx">             yield tasks.setSupportedComponents(&quot;VTODO&quot;)
</span><span class="cx"> 
</span><del>-        # Force data version to previous
-        ch = home._homeSchema
-        yield Update(
-            {ch.DATAVERSION: 3},
-            Where=ch.RESOURCE_ID == home._resourceID,
-        ).on(self.transactionUnderTest())
</del><ins>+            # Force data version to previous
+            ch = home._homeSchema
+            yield Update(
+                {ch.DATAVERSION: 3},
+                Where=ch.RESOURCE_ID == home._resourceID,
+            ).on(self.transactionUnderTest())
</ins><span class="cx"> 
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><del>-        # Trigger upgrade
-        yield moveDefaultCalendarProperties(self._sqlCalendarStore)
</del><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def _invalidDefaultCalendarUpgrade_check(self, changed_users, unchanged_users):
+
</ins><span class="cx">         # Test results
</span><del>-        for user in (&quot;user01&quot;, &quot;user02&quot;,):
</del><ins>+        for user in changed_users:
</ins><span class="cx">             home = (yield self.homeUnderTest(name=user))
</span><ins>+            version = (yield home.dataVersion())
+            self.assertEqual(version, 4)
</ins><span class="cx">             calendar = (yield self.calendarUnderTest(name=&quot;tasks_1&quot;, home=user))
</span><span class="cx">             self.assertFalse(home.isDefaultCalendar(calendar))
</span><span class="cx">             inbox = (yield self.calendarUnderTest(name=&quot;inbox&quot;, home=user))
</span><span class="cx">             self.assertTrue(PropertyName.fromElement(ScheduleDefaultCalendarURL) not in inbox.properties())
</span><span class="cx"> 
</span><ins>+        for user in unchanged_users:
+            home = (yield self.homeUnderTest(name=user))
+            version = (yield home.dataVersion())
+            self.assertEqual(version, 3)
+            calendar = (yield self.calendarUnderTest(name=&quot;tasks_1&quot;, home=user))
+            self.assertFalse(home.isDefaultCalendar(calendar))
+            inbox = (yield self.calendarUnderTest(name=&quot;inbox&quot;, home=user))
+            self.assertTrue(PropertyName.fromElement(ScheduleDefaultCalendarURL) in inbox.properties())
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def test_calendarTranspUpgrade(self):
</del><ins>+    def test_invalidDefaultCalendarUpgrade(self):
+        yield self._invalidDefaultCalendarUpgrade_setup()
+        yield updateCalendarHomes(self._sqlCalendarStore)
+        yield self._invalidDefaultCalendarUpgrade_check((&quot;user01&quot;, &quot;user02&quot;,), ())
</ins><span class="cx"> 
</span><ins>+
+    @inlineCallbacks
+    def test_partialInvalidDefaultCalendarUpgrade(self):
+        yield self._invalidDefaultCalendarUpgrade_setup()
+        yield updateCalendarHomes(self._sqlCalendarStore, &quot;user01&quot;)
+        yield self._invalidDefaultCalendarUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;,))
+
+
+    @inlineCallbacks
+    def _calendarTranspUpgrade_setup(self):
+
</ins><span class="cx">         # Set dead property on inbox
</span><span class="cx">         for user in (&quot;user01&quot;, &quot;user02&quot;,):
</span><span class="cx">             inbox = (yield self.calendarUnderTest(name=&quot;inbox&quot;, home=user))
</span><span class="lines">@@ -125,7 +181,7 @@
</span><span class="cx">             # Force current to transparent
</span><span class="cx">             calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
</span><span class="cx">             yield calendar.setUsedForFreeBusy(False)
</span><del>-            calendar.properties()[PropertyName.fromElement(ScheduleCalendarTransp)] = ScheduleCalendarTransp(Opaque())
</del><ins>+            calendar.properties()[PropertyName.fromElement(ScheduleCalendarTransp)] = ScheduleCalendarTransp(Opaque() if user == &quot;user01&quot; else Transparent())
</ins><span class="cx"> 
</span><span class="cx">             # Force data version to previous
</span><span class="cx">             home = (yield self.homeUnderTest(name=user))
</span><span class="lines">@@ -159,21 +215,55 @@
</span><span class="cx">         ).on(txn)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><del>-        # Trigger upgrade
-        yield moveCalendarTranspProperties(self._sqlCalendarStore)
</del><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def _calendarTranspUpgrade_check(self, changed_users, unchanged_users):
+
</ins><span class="cx">         # Test results
</span><del>-        for user in (&quot;user01&quot;, &quot;user02&quot;,):
</del><ins>+        for user in changed_users:
</ins><span class="cx">             home = (yield self.homeUnderTest(name=user))
</span><ins>+            version = (yield home.dataVersion())
+            self.assertEqual(version, 4)
</ins><span class="cx">             calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
</span><del>-            self.assertTrue(calendar.isUsedForFreeBusy())
</del><ins>+            if user == &quot;user01&quot;:
+                self.assertTrue(calendar.isUsedForFreeBusy())
+            else:
+                self.assertFalse(calendar.isUsedForFreeBusy())
+            self.assertTrue(PropertyName.fromElement(caldavxml.ScheduleCalendarTransp) not in calendar.properties())
</ins><span class="cx">             inbox = (yield self.calendarUnderTest(name=&quot;inbox&quot;, home=user))
</span><span class="cx">             self.assertTrue(PropertyName.fromElement(CalendarFreeBusySet) not in inbox.properties())
</span><span class="cx"> 
</span><ins>+        for user in unchanged_users:
+            home = (yield self.homeUnderTest(name=user))
+            version = (yield home.dataVersion())
+            self.assertEqual(version, 3)
+            calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
+            if user == &quot;user01&quot;:
+                self.assertFalse(calendar.isUsedForFreeBusy())
+            else:
+                self.assertFalse(calendar.isUsedForFreeBusy())
+            self.assertTrue(PropertyName.fromElement(caldavxml.ScheduleCalendarTransp) in calendar.properties())
+            inbox = (yield self.calendarUnderTest(name=&quot;inbox&quot;, home=user))
+            self.assertTrue(PropertyName.fromElement(CalendarFreeBusySet) in inbox.properties())
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def test_defaultAlarmUpgrade(self):
</del><ins>+    def test_calendarTranspUpgrade(self):
+        yield self._calendarTranspUpgrade_setup()
+        yield updateCalendarHomes(self._sqlCalendarStore)
+        yield self._calendarTranspUpgrade_check((&quot;user01&quot;, &quot;user02&quot;,), ())
</ins><span class="cx"> 
</span><ins>+
+    @inlineCallbacks
+    def test_partialCalendarTranspUpgrade(self):
+        yield self._calendarTranspUpgrade_setup()
+        yield updateCalendarHomes(self._sqlCalendarStore, &quot;user01&quot;)
+        yield self._calendarTranspUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;,))
+
+
+    @inlineCallbacks
+    def _defaultAlarmUpgrade_setup(self):
+
</ins><span class="cx">         alarmhome1 = &quot;&quot;&quot;BEGIN:VALARM
</span><span class="cx"> ACTION:AUDIO
</span><span class="cx"> TRIGGER;RELATED=START:-PT1M
</span><span class="lines">@@ -277,13 +367,28 @@
</span><span class="cx">         shared = yield self.calendarUnderTest(name=shared_name, home=&quot;user02&quot;)
</span><span class="cx">         for _ignore_vevent, _ignore_timed, alarm, prop in detailsshared:
</span><span class="cx">             shared.properties()[PropertyName.fromElement(prop)] = prop(alarm)
</span><ins>+
+        for user in (&quot;user01&quot;, &quot;user02&quot;,):
+            # Force data version to previous
+            home = (yield self.homeUnderTest(name=user))
+            ch = home._homeSchema
+            yield Update(
+                {ch.DATAVERSION: 3},
+                Where=ch.RESOURCE_ID == home._resourceID,
+            ).on(self.transactionUnderTest())
+
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><del>-        # Trigger upgrade
-        yield moveDefaultAlarmProperties(self._sqlCalendarStore)
</del><ins>+        returnValue((detailshome, detailscalendar, detailsshared, shared_name,))
</ins><span class="cx"> 
</span><ins>+
+    @inlineCallbacks
+    def _defaultAlarmUpgrade_check(self, changed_users, unchanged_users, detailshome, detailscalendar, detailsshared, shared_name):
+
</ins><span class="cx">         # Check each type of collection
</span><span class="cx">         home = yield self.homeUnderTest(name=&quot;user01&quot;)
</span><ins>+        version = (yield home.dataVersion())
+        self.assertEqual(version, 4)
</ins><span class="cx">         for vevent, timed, alarm, prop in detailshome:
</span><span class="cx">             alarm_result = (yield home.getDefaultAlarm(vevent, timed))
</span><span class="cx">             self.assertEquals(alarm_result, alarm)
</span><span class="lines">@@ -293,18 +398,67 @@
</span><span class="cx">         for vevent, timed, alarm, prop in detailscalendar:
</span><span class="cx">             alarm_result = (yield calendar.getDefaultAlarm(vevent, timed))
</span><span class="cx">             self.assertEquals(alarm_result, alarm)
</span><del>-            self.assertTrue(PropertyName.fromElement(prop) not in home.properties())
</del><ins>+            self.assertTrue(PropertyName.fromElement(prop) not in calendar.properties())
</ins><span class="cx"> 
</span><del>-        shared = yield self.calendarUnderTest(name=shared_name, home=&quot;user02&quot;)
-        for vevent, timed, alarm, prop in detailsshared:
-            alarm_result = (yield shared.getDefaultAlarm(vevent, timed))
-            self.assertEquals(alarm_result, alarm)
-            self.assertTrue(PropertyName.fromElement(prop) not in home.properties())
</del><ins>+        if &quot;user02&quot; in changed_users:
+            home = (yield self.homeUnderTest(name=&quot;user02&quot;))
+            version = (yield home.dataVersion())
+            self.assertEqual(version, 4)
+            shared = yield self.calendarUnderTest(name=shared_name, home=&quot;user02&quot;)
+            for vevent, timed, alarm, prop in detailsshared:
+                alarm_result = (yield shared.getDefaultAlarm(vevent, timed))
+                self.assertEquals(alarm_result, alarm)
+                self.assertTrue(PropertyName.fromElement(prop) not in shared.properties())
+        else:
+            home = (yield self.homeUnderTest(name=&quot;user02&quot;))
+            version = (yield home.dataVersion())
+            self.assertEqual(version, 3)
+            shared = yield self.calendarUnderTest(name=shared_name, home=&quot;user02&quot;)
+            for vevent, timed, alarm, prop in detailsshared:
+                alarm_result = (yield shared.getDefaultAlarm(vevent, timed))
+                self.assertEquals(alarm_result, None)
+                self.assertTrue(PropertyName.fromElement(prop) in shared.properties())
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def test_resourceTypeUpgrade(self):
</del><ins>+    def test_defaultAlarmUpgrade(self):
+        detailshome, detailscalendar, detailsshared, shared_name = (yield self._defaultAlarmUpgrade_setup())
+        yield updateCalendarHomes(self._sqlCalendarStore)
+        yield self._defaultAlarmUpgrade_check((&quot;user01&quot;, &quot;user02&quot;,), (), detailshome, detailscalendar, detailsshared, shared_name)
</ins><span class="cx"> 
</span><ins>+
+    @inlineCallbacks
+    def test_partialDefaultAlarmUpgrade(self):
+        detailshome, detailscalendar, detailsshared, shared_name = (yield self._defaultAlarmUpgrade_setup())
+        yield updateCalendarHomes(self._sqlCalendarStore, &quot;user01&quot;)
+        yield self._defaultAlarmUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;,), detailshome, detailscalendar, detailsshared, shared_name)
+
+
+    @inlineCallbacks
+    def test_combinedUpgrade(self):
+        yield self._defaultCalendarUpgrade_setup()
+        yield self._calendarTranspUpgrade_setup()
+        detailshome, detailscalendar, detailsshared, shared_name = (yield self._defaultAlarmUpgrade_setup())
+        yield updateCalendarHomes(self._sqlCalendarStore)
+        yield self._defaultCalendarUpgrade_check((&quot;user01&quot;, &quot;user02&quot;,), ())
+        yield self._calendarTranspUpgrade_check((&quot;user01&quot;, &quot;user02&quot;,), ())
+        yield self._defaultAlarmUpgrade_check((&quot;user01&quot;, &quot;user02&quot;,), (), detailshome, detailscalendar, detailsshared, shared_name)
+
+
+    @inlineCallbacks
+    def test_partialCombinedUpgrade(self):
+        yield self._defaultCalendarUpgrade_setup()
+        yield self._calendarTranspUpgrade_setup()
+        detailshome, detailscalendar, detailsshared, shared_name = (yield self._defaultAlarmUpgrade_setup())
+        yield updateCalendarHomes(self._sqlCalendarStore, &quot;user01&quot;)
+        yield self._defaultCalendarUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;,))
+        yield self._calendarTranspUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;,))
+        yield self._defaultAlarmUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;,), detailshome, detailscalendar, detailsshared, shared_name)
+
+
+    @inlineCallbacks
+    def _resourceTypeUpgrade_setup(self):
+
</ins><span class="cx">         # Set dead property on calendar
</span><span class="cx">         for user in (&quot;user01&quot;, &quot;user02&quot;,):
</span><span class="cx">             calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
</span><span class="lines">@@ -314,12 +468,60 @@
</span><span class="cx">         for user in (&quot;user01&quot;, &quot;user02&quot;,):
</span><span class="cx">             calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
</span><span class="cx">             self.assertTrue(PropertyName.fromElement(element.ResourceType) in calendar.properties())
</span><ins>+
+        yield self.transactionUnderTest().updateCalendarserverValue(&quot;CALENDAR-DATAVERSION&quot;, &quot;3&quot;)
+
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><del>-        # Trigger upgrade
-        yield removeResourceType(self._sqlCalendarStore)
</del><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def _resourceTypeUpgrade_check(self, full=True):
+
</ins><span class="cx">         # Test results
</span><del>-        for user in (&quot;user01&quot;, &quot;user02&quot;,):
-            calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
-            self.assertTrue(PropertyName.fromElement(element.ResourceType) not in calendar.properties())
</del><ins>+        if full:
+            for user in (&quot;user01&quot;, &quot;user02&quot;,):
+                calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
+                self.assertTrue(PropertyName.fromElement(element.ResourceType) not in calendar.properties())
+            version = yield self.transactionUnderTest().calendarserverValue(&quot;CALENDAR-DATAVERSION&quot;)
+            self.assertEqual(int(version), 4)
+        else:
+            for user in (&quot;user01&quot;, &quot;user02&quot;,):
+                calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
+                self.assertTrue(PropertyName.fromElement(element.ResourceType) in calendar.properties())
+            version = yield self.transactionUnderTest().calendarserverValue(&quot;CALENDAR-DATAVERSION&quot;)
+            self.assertEqual(int(version), 3)
+
+
+    @inlineCallbacks
+    def test_resourceTypeUpgrade(self):
+        yield self._resourceTypeUpgrade_setup()
+        yield doUpgrade(self._sqlCalendarStore)
+        yield self._resourceTypeUpgrade_check()
+
+
+    @inlineCallbacks
+    def test_fullUpgrade(self):
+        self.patch(config, &quot;UpgradeHomePrefix&quot;, &quot;&quot;)
+        yield self._defaultCalendarUpgrade_setup()
+        yield self._calendarTranspUpgrade_setup()
+        detailshome, detailscalendar, detailsshared, shared_name = (yield self._defaultAlarmUpgrade_setup())
+        yield self._resourceTypeUpgrade_setup()
+        yield doUpgrade(self._sqlCalendarStore)
+        yield self._defaultCalendarUpgrade_check((&quot;user01&quot;, &quot;user02&quot;,), ())
+        yield self._calendarTranspUpgrade_check((&quot;user01&quot;, &quot;user02&quot;,), ())
+        yield self._defaultAlarmUpgrade_check((&quot;user01&quot;, &quot;user02&quot;,), (), detailshome, detailscalendar, detailsshared, shared_name)
+        yield self._resourceTypeUpgrade_check()
+
+
+    @inlineCallbacks
+    def test_partialFullUpgrade(self):
+        self.patch(config, &quot;UpgradeHomePrefix&quot;, &quot;user01&quot;)
+        yield self._defaultCalendarUpgrade_setup()
+        yield self._calendarTranspUpgrade_setup()
+        yield self._resourceTypeUpgrade_setup()
+        detailshome, detailscalendar, detailsshared, shared_name = (yield self._defaultAlarmUpgrade_setup())
+        yield doUpgrade(self._sqlCalendarStore)
+        yield self._defaultCalendarUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;,))
+        yield self._calendarTranspUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;,))
+        yield self._defaultAlarmUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;,), detailshome, detailscalendar, detailsshared, shared_name)
+        yield self._resourceTypeUpgrade_check(False)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoreupgradesqlupgradestesttest_upgrade_from_4_to_5py"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/test/test_upgrade_from_4_to_5.py (11859 => 11860)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/test/test_upgrade_from_4_to_5.py        2013-10-31 18:37:58 UTC (rev 11859)
+++ CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/test/test_upgrade_from_4_to_5.py        2013-10-31 21:40:44 UTC (rev 11860)
</span><span class="lines">@@ -13,21 +13,24 @@
</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 twistedcaldav import caldavxml, customxml
-from txdav.common.datastore.upgrade.sql.upgrades.calendar_upgrade_from_4_to_5 import moveCalendarTimezoneProperties, \
-    removeOtherProperties, moveCalendarAvailabilityProperties
-from txdav.common.datastore.sql_tables import _BIND_MODE_WRITE, schema
-from txdav.xml import element
</del><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> Tests for L{txdav.common.datastore.upgrade.sql.upgrade}.
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> from twext.enterprise.dal.syntax import Update, Insert
</span><del>-from twisted.internet.defer import inlineCallbacks
</del><ins>+
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+from twistedcaldav import caldavxml, customxml
+from twistedcaldav.config import config
</ins><span class="cx"> from twistedcaldav.ical import Component
</span><ins>+
</ins><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><span class="cx"> from txdav.caldav.datastore.test.util import CommonStoreTests
</span><ins>+from txdav.common.datastore.sql_tables import _BIND_MODE_WRITE, schema
+from txdav.common.datastore.upgrade.sql.upgrades.calendar_upgrade_from_4_to_5 import updateCalendarHomes, doUpgrade
+from txdav.xml import element
</ins><span class="cx"> 
</span><span class="cx"> class Upgrade_from_4_to_5(CommonStoreTests):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -35,7 +38,7 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def test_calendarTimezoneUpgrade(self):
</del><ins>+    def _calendarTimezoneUpgrade_setup(self):
</ins><span class="cx"> 
</span><span class="cx">         tz1 = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -137,19 +140,47 @@
</span><span class="cx">         ).on(txn)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><del>-        # Trigger upgrade
-        yield moveCalendarTimezoneProperties(self._sqlCalendarStore)
</del><ins>+        returnValue(user_details)
</ins><span class="cx"> 
</span><ins>+
+    @inlineCallbacks
+    def _calendarTimezoneUpgrade_check(self, changed_users, unchanged_users, user_details):
+
</ins><span class="cx">         # Test results
</span><span class="cx">         for user, calname, tz in user_details:
</span><del>-            calendar = (yield self.calendarUnderTest(name=calname, home=user))
-            self.assertEqual(calendar.getTimezone(), tz)
-            self.assertTrue(PropertyName.fromElement(caldavxml.CalendarTimeZone) not in calendar.properties())
</del><ins>+            if user in changed_users:
+                home = (yield self.homeUnderTest(name=user))
+                version = (yield home.dataVersion())
+                self.assertEqual(version, 5)
+                calendar = (yield self.calendarUnderTest(name=calname, home=user))
+                self.assertEqual(calendar.getTimezone(), tz)
+                self.assertTrue(PropertyName.fromElement(caldavxml.CalendarTimeZone) not in calendar.properties())
+            else:
+                home = (yield self.homeUnderTest(name=user))
+                version = (yield home.dataVersion())
+                self.assertEqual(version, 4)
+                calendar = (yield self.calendarUnderTest(name=calname, home=user))
+                self.assertEqual(calendar.getTimezone(), None)
+                self.assertTrue(PropertyName.fromElement(caldavxml.CalendarTimeZone) in calendar.properties())
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def test_calendarAvailabilityUpgrade(self):
</del><ins>+    def test_calendarTimezoneUpgrade(self):
+        user_details = yield self._calendarTimezoneUpgrade_setup()
+        yield updateCalendarHomes(self._sqlCalendarStore)
+        yield self._calendarTimezoneUpgrade_check((&quot;user01&quot;, &quot;user02&quot;, &quot;user03&quot;,), (), user_details)
</ins><span class="cx"> 
</span><ins>+
+    @inlineCallbacks
+    def test_partialCalendarTimezoneUpgrade(self):
+        user_details = yield self._calendarTimezoneUpgrade_setup()
+        yield updateCalendarHomes(self._sqlCalendarStore, &quot;user01&quot;)
+        yield self._calendarTimezoneUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;, &quot;user03&quot;,), user_details)
+
+
+    @inlineCallbacks
+    def _calendarAvailabilityUpgrade_setup(self):
+
</ins><span class="cx">         av1 = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="lines">@@ -220,20 +251,65 @@
</span><span class="cx">             self.assertEqual(PropertyName.fromElement(customxml.CalendarAvailability) in calendar.properties(), av is not None)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><del>-        # Trigger upgrade
-        yield moveCalendarAvailabilityProperties(self._sqlCalendarStore)
</del><ins>+        returnValue(user_details)
</ins><span class="cx"> 
</span><ins>+
+    @inlineCallbacks
+    def _calendarAvailabilityUpgrade_check(self, changed_users, unchanged_users, user_details):
+
</ins><span class="cx">         # Test results
</span><span class="cx">         for user, av in user_details:
</span><del>-            home = (yield self.homeUnderTest(name=user))
-            calendar = (yield self.calendarUnderTest(name=&quot;inbox&quot;, home=user))
-            self.assertEqual(home.getAvailability(), av)
-            self.assertTrue(PropertyName.fromElement(customxml.CalendarAvailability) not in calendar.properties())
</del><ins>+            if user in changed_users:
+                home = (yield self.homeUnderTest(name=user))
+                version = (yield home.dataVersion())
+                self.assertEqual(version, 5)
+                calendar = (yield self.calendarUnderTest(name=&quot;inbox&quot;, home=user))
+                self.assertEqual(home.getAvailability(), av)
+                self.assertTrue(PropertyName.fromElement(customxml.CalendarAvailability) not in calendar.properties())
+            else:
+                home = (yield self.homeUnderTest(name=user))
+                version = (yield home.dataVersion())
+                self.assertEqual(version, 4)
+                calendar = (yield self.calendarUnderTest(name=&quot;inbox&quot;, home=user))
+                self.assertEqual(home.getAvailability(), None)
+                self.assertTrue(PropertyName.fromElement(customxml.CalendarAvailability) in calendar.properties())
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def test_removeOtherPropertiesUpgrade(self):
</del><ins>+    def test_calendarAvailabilityUpgrade(self):
+        user_details = yield self._calendarAvailabilityUpgrade_setup()
+        yield updateCalendarHomes(self._sqlCalendarStore)
+        yield self._calendarAvailabilityUpgrade_check((&quot;user01&quot;, &quot;user02&quot;, &quot;user03&quot;,), (), user_details)
</ins><span class="cx"> 
</span><ins>+
+    @inlineCallbacks
+    def test_partialCalendarAvailabilityUpgrade(self):
+        user_details = yield self._calendarAvailabilityUpgrade_setup()
+        yield updateCalendarHomes(self._sqlCalendarStore, &quot;user01&quot;)
+        yield self._calendarAvailabilityUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;, &quot;user03&quot;,), user_details)
+
+
+    @inlineCallbacks
+    def test_combinedUpgrade(self):
+        user_details1 = yield self._calendarTimezoneUpgrade_setup()
+        user_details2 = yield self._calendarAvailabilityUpgrade_setup()
+        yield updateCalendarHomes(self._sqlCalendarStore)
+        yield self._calendarTimezoneUpgrade_check((&quot;user01&quot;, &quot;user02&quot;, &quot;user03&quot;,), (), user_details1)
+        yield self._calendarAvailabilityUpgrade_check((&quot;user01&quot;, &quot;user02&quot;, &quot;user03&quot;,), (), user_details2)
+
+
+    @inlineCallbacks
+    def test_partialCombinedUpgrade(self):
+        user_details1 = yield self._calendarTimezoneUpgrade_setup()
+        user_details2 = yield self._calendarAvailabilityUpgrade_setup()
+        yield updateCalendarHomes(self._sqlCalendarStore, &quot;user01&quot;)
+        yield self._calendarTimezoneUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;, &quot;user03&quot;,), user_details1)
+        yield self._calendarAvailabilityUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;, &quot;user03&quot;,), user_details2)
+
+
+    @inlineCallbacks
+    def _removeOtherPropertiesUpgrade_setup(self):
+
</ins><span class="cx">         # Set dead property on calendar
</span><span class="cx">         for user in (&quot;user01&quot;, &quot;user02&quot;,):
</span><span class="cx">             calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
</span><span class="lines">@@ -243,12 +319,55 @@
</span><span class="cx">         for user in (&quot;user01&quot;, &quot;user02&quot;,):
</span><span class="cx">             calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
</span><span class="cx">             self.assertTrue(PropertyName.fromElement(element.ResourceID) in calendar.properties())
</span><ins>+
+        yield self.transactionUnderTest().updateCalendarserverValue(&quot;CALENDAR-DATAVERSION&quot;, &quot;4&quot;)
+
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><del>-        # Trigger upgrade
-        yield removeOtherProperties(self._sqlCalendarStore)
</del><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def _removeOtherPropertiesUpgrade_check(self, full=True):
+
</ins><span class="cx">         # Test results
</span><span class="cx">         for user in (&quot;user01&quot;, &quot;user02&quot;,):
</span><del>-            calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
-            self.assertTrue(PropertyName.fromElement(element.ResourceID) not in calendar.properties())
</del><ins>+            if full:
+                calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
+                self.assertTrue(PropertyName.fromElement(element.ResourceID) not in calendar.properties())
+                version = yield self.transactionUnderTest().calendarserverValue(&quot;CALENDAR-DATAVERSION&quot;)
+                self.assertEqual(int(version), 5)
+            else:
+                calendar = (yield self.calendarUnderTest(name=&quot;calendar_1&quot;, home=user))
+                self.assertTrue(PropertyName.fromElement(element.ResourceID) in calendar.properties())
+                version = yield self.transactionUnderTest().calendarserverValue(&quot;CALENDAR-DATAVERSION&quot;)
+                self.assertEqual(int(version), 4)
+
+
+    @inlineCallbacks
+    def test_removeOtherPropertiesUpgrade(self):
+        yield self._removeOtherPropertiesUpgrade_setup()
+        yield doUpgrade(self._sqlCalendarStore)
+        yield self._removeOtherPropertiesUpgrade_check()
+
+
+    @inlineCallbacks
+    def test_fullUpgrade(self):
+        self.patch(config, &quot;UpgradeHomePrefix&quot;, &quot;&quot;)
+        user_details1 = yield self._calendarTimezoneUpgrade_setup()
+        user_details2 = yield self._calendarAvailabilityUpgrade_setup()
+        yield self._removeOtherPropertiesUpgrade_setup()
+        yield doUpgrade(self._sqlCalendarStore)
+        yield self._calendarTimezoneUpgrade_check((&quot;user01&quot;, &quot;user02&quot;, &quot;user03&quot;,), (), user_details1)
+        yield self._calendarAvailabilityUpgrade_check((&quot;user01&quot;, &quot;user02&quot;, &quot;user03&quot;,), (), user_details2)
+        yield self._removeOtherPropertiesUpgrade_check()
+
+
+    @inlineCallbacks
+    def test_partialFullUpgrade(self):
+        self.patch(config, &quot;UpgradeHomePrefix&quot;, &quot;user01&quot;)
+        user_details1 = yield self._calendarTimezoneUpgrade_setup()
+        user_details2 = yield self._calendarAvailabilityUpgrade_setup()
+        yield self._removeOtherPropertiesUpgrade_setup()
+        yield doUpgrade(self._sqlCalendarStore)
+        yield self._calendarTimezoneUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;, &quot;user03&quot;,), user_details1)
+        yield self._calendarAvailabilityUpgrade_check((&quot;user01&quot;,), (&quot;user02&quot;, &quot;user03&quot;,), user_details2)
+        yield self._removeOtherPropertiesUpgrade_check(False)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoreupgradesqlupgradesutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/util.py (11859 => 11860)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/util.py        2013-10-31 18:37:58 UTC (rev 11859)
+++ CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/utilpy        2013-10-31 21:40:44 UTC (rev 11860)
</span><span class="lines">@@ -129,17 +129,21 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><del>-def doToEachHomeNotAtVersion(store, homeSchema, version, doIt, logStr):
</del><ins>+def doToEachHomeNotAtVersion(store, homeSchema, version, doIt, logStr, filterOwnerUID=None):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Do something to each home whose version column indicates it is older
</span><del>-    than the specified version. Do this in batches as there may be a lot of work to do.
</del><ins>+    than the specified version. Do this in batches as there may be a lot of work to do. Also,
+    allow the GUID to be filtered to support a parallel mode of operation.
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     txn = store.newTransaction(&quot;updateDataVersion&quot;)
</span><ins>+    where = homeSchema.DATAVERSION &lt; version
+    if filterOwnerUID:
+        where = where.And(homeSchema.OWNER_UID.StartsWith(filterOwnerUID))
</ins><span class="cx">     total = (yield Select(
</span><span class="cx">         [Count(homeSchema.RESOURCE_ID), ],
</span><span class="cx">         From=homeSchema,
</span><del>-        Where=homeSchema.DATAVERSION &lt; version,
</del><ins>+        Where=where,
</ins><span class="cx">     ).on(txn))[0][0]
</span><span class="cx">     yield txn.commit()
</span><span class="cx">     count = 0
</span><span class="lines">@@ -154,7 +158,7 @@
</span><span class="cx">             rows = yield Select(
</span><span class="cx">                 [homeSchema.RESOURCE_ID, homeSchema.OWNER_UID, ],
</span><span class="cx">                 From=homeSchema,
</span><del>-                Where=homeSchema.DATAVERSION &lt; version,
</del><ins>+                Where=where,
</ins><span class="cx">                 OrderBy=homeSchema.OWNER_UID,
</span><span class="cx">                 Limit=1,
</span><span class="cx">             ).on(txn)
</span></span></pre>
</div>
</div>

</body>
</html>