Bug 564268 - Make calendar code calling the local database more robust (r=philipp)
authorSimon Vaillancourt <simon.at.orcl@gmail.com>
Wed, 16 Jun 2010 14:50:48 -0400
changeset 5832 9a29b14426d9faa89544ae2a0c83739443c98dc8
parent 5831 f1aedaaa8da62f811737320ba7d643b0f2d1c102
child 5833 386df12e1a8063c283fd05dc71bd6af94466aa04
push idunknown
push userunknown
push dateunknown
reviewersphilipp
bugs564268
Bug 564268 - Make calendar code calling the local database more robust (r=philipp)
calendar/base/src/calCachedCalendar.js
calendar/providers/storage/calStorageCalendar.js
calendar/providers/storage/calStorageUpgrade.jsm
--- a/calendar/base/src/calCachedCalendar.js
+++ b/calendar/base/src/calCachedCalendar.js
@@ -250,20 +250,21 @@ calCachedCalendar.prototype = {
 
         if (this.offline) {
             return emptyQueue(Components.results.NS_OK);
         }
 
         if (this.supportsChangeLog) {
             LOG("[calCachedCalendar] Doing changelog based sync for calendar " + this.uri.spec);
             var opListener = {
+                thisCalendar : this,
                 onResult: function(op, result) {
                     if (!op || !op.isPending) {
                         var status = (op ? op.status : Components.results.NS_OK);
-                        ASSERT(Components.isSuccessCode(status), "replay action failed: " + (op ? op.id : "<unknown>"));
+                        ASSERT(Components.isSuccessCode(status), "replay action failed: " + (op ? op.id : "<unknown>")+", uri=" + this.thisCalendar.uri.spec + ", result="  +result + ", op=" + op);
                         LOG("[calCachedCalendar] replayChangesOn finished.");
                         emptyQueue(status);
                     }
                 }
             };
             this.mPendingSync = this.mUncachedCalendar.replayChangesOn(this.mCachedCalendar, opListener);
             return this.mPendingSync;
         }
@@ -483,9 +484,8 @@ calCachedCalendar.prototype = {
     defineForwards(calCachedCalendar.prototype, "mUncachedCalendar",
                    ["getProperty", "setProperty", "deleteProperty",
                     "isInvitation", "getInvitedAttendee", "canNotify"],
                    ["type"],
                    ["id", "name", "uri", "readOnly"]);
     defineForwards(calCachedCalendar.prototype, "mCachedCalendar",
                    ["getItem", "getItems", "startBatch", "endBatch"], [], []);
 })();
-
--- a/calendar/providers/storage/calStorageCalendar.js
+++ b/calendar/providers/storage/calStorageCalendar.js
@@ -72,20 +72,21 @@ calStorageCalendar.prototype = {
     //
     // private members
     //
     mDB: null,
     mItemCache: null,
     mRecItemCacheInited: false,
     mRecEventCache: null,
     mRecTodoCache: null,
+    mLastStatement: null,
 
     //
     // nsISupports interface
-    // 
+    //
     QueryInterface: function (aIID) {
         return doQueryInterface(this, calStorageCalendar.prototype, aIID,
                                 [Components.interfaces.calICalendarProvider,
                                  Components.interfaces.calISyncWriteCalendar]);
     },
 
     //
     // calICalendarProvider interface
@@ -101,42 +102,60 @@ calStorageCalendar.prototype = {
     createCalendar: function cSC_createCalendar() {
         throw NS_ERROR_NOT_IMPLEMENTED;
     },
 
     deleteCalendar: function cSC_deleteCalendar(cal, listener) {
         cal = cal.wrappedJSObject;
 
         for each (let stmt in this.mDeleteEventExtras) {
-            this.prepareStatement(stmt);
-            stmt.execute();
-            stmt.reset();
+            try {
+                this.prepareStatement(stmt);
+                stmt.execute();
+            } finally {
+                stmt.reset();
+            }
         }
 
         for each (let stmt in this.mDeleteTodoExtras) {
-            this.prepareStatement(stmt);
-            stmt.execute();
-            stmt.reset();
+            try {
+                this.prepareStatement(stmt);
+                stmt.execute();
+            } finally {
+                stmt.reset();
+            }
+        }
+
+        try {
+            this.prepareStatement(this.mDeleteAllEvents);
+            this.mDeleteAllEvents.execute();
+        } finally {
+            this.mDeleteAllEvents.reset();
         }
 
-        this.prepareStatement(this.mDeleteAllEvents);
-        this.mDeleteAllEvents.execute();
-        this.mDeleteAllEvents.reset();
-
-        this.prepareStatement(this.mDeleteAllTodos);
-        this.mDeleteAllTodos.execute();
-        this.mDeleteAllTodos.reset();
-
-        this.prepareStatement(this.mDeleteAllMetaData);
-        this.mDeleteAllMetaData.execute();
-        this.mDeleteAllMetaData.reset();
+        try {
+            this.prepareStatement(this.mDeleteAllTodos);
+            this.mDeleteAllTodos.execute();
+        } finally {
+            this.mDeleteAllTodos.reset();
+        }
 
         try {
-            listener.onDeleteCalendar(cal, Components.results.NS_OK, null);
+            this.prepareStatement(this.mDeleteAllMetaData);
+            this.mDeleteAllMetaData.execute();
+        } finally {
+            this.mDeleteAllMetaData.reset();
+        }
+
+        try {
+            if (listener) {
+                listener.onDeleteCalendar(cal, Components.results.NS_OK, null);
+            }
         } catch (ex) {
+            this.logError("error calling listener.onDeleteCalendar", ex);
         }
     },
 
     mRelaxedMode: undefined,
     get relaxedMode() {
         if (this.mRelaxedMode === undefined) {
             this.mRelaxedMode = this.getProperty("relaxedMode");
         }
@@ -223,17 +242,17 @@ calStorageCalendar.prototype = {
             if (this.mDB.tableExists("cal_events")) {
                 cal.LOG("Storage: Migrating storage.sdb -> local.sqlite");
                 upgradeDB(this.mDB); // upgrade schema before migating data
                 let attachStatement = createStatement(this.mDB, "ATTACH DATABASE :file_path AS local_sqlite");
                 try {
                     attachStatement.params.file_path = localDB.databaseFile.path;
                     attachStatement.execute();
                 } catch (exc) {
-                    cal.ERROR(exc + ", error: " + this.mDB.lastErrorString);
+                    this.logError("prepareInitDB attachStatement.execute exception", exc);
                     throw exc;
                 } finally {
                     attachStatement.reset();
                 }
                 try {
                     // hold lock on storage.sdb until we've migrated data from storage.sdb:
                     this.mDB.beginTransactionAs(Components.interfaces.mozIStorageConnection.TRANSACTION_EXCLUSIVE);
                     try {
@@ -246,17 +265,17 @@ calStorageCalendar.prototype = {
                                                               "; DROP TABLE IF EXISTS " +  table);
                                 }
                             }
                             this.mDB.commitTransaction();
                         } else { // migration done in the meantime
                             this.mDB.rollbackTransaction();
                         }
                     } catch (exc) {
-                        cal.ERROR(exc + ", error: " + this.mDB.lastErrorString);
+                        this.logError("prepareInitDB storage.sdb migration exception", exc);
                         this.mDB.rollbackTransaction();
                         throw exc;
                     }
                 } finally {
                     this.mDB.executeSimpleSQL("DETACH DATABASE local_sqlite");
                 }
             }
 
@@ -340,17 +359,17 @@ calStorageCalendar.prototype = {
                     // need to move all events with cal_id=0 to this id.
                     cal.LOG("Storage: Migrating stray cal_id=0 calendar to uuid");
                     migrateTables(this.mDB, this.id, 0);
                     this.setProperty("uri", "moz-storage-calendar://");
                     this.setProperty("old_calendar_id", 0);
                     this.mDB.commitTransaction();
                 }
             } catch (exc) {
-                cal.ERROR(exc + ", error: " + this.mDB.lastErrorString);
+                this.logError("prepareInitDB  moz-profile-calendar migration exception", exc);
                 this.mDB.rollbackTransaction();
                 throw exc;
             }
         } else if (this.uri.schemeIs("moz-storage-calendar")) {
             // New style uri, no need for migration here
             let localDB = cal.getCalendarDirectory();
             localDB.append("local.sqlite");
             localDB = dbService.openDatabase(localDB);
@@ -366,18 +385,19 @@ calStorageCalendar.prototype = {
     /**
      * Takes care of necessary preparations for most of our statements.
      *
      * @param aStmt         The statement to prepare.
      */
     prepareStatement: function cSC_prepareStatement(aStmt) {
         try {
             aStmt.params.cal_id = this.id;
+            this.mLastStatement = aStmt;
         } catch (e) {
-            cal.ERROR(e + "\n" + cal.STACK(10));
+            this.logError("prepareStatement exception", e);
         }
     },
 
     refresh: function cSC_refresh() {
         // no-op
     },
 
     // void addItem( in calIItemBase aItem, in calIOperationListener aListener );
@@ -435,16 +455,17 @@ calStorageCalendar.prototype = {
                                      aItem);
 
         // notify observers
         this.observers.notify("onAddItem", [aItem]);
     },
 
     // void modifyItem( in calIItemBase aNewItem, in calIItemBase aOldItem, in calIOperationListener aListener );
     modifyItem: function cSC_modifyItem(aNewItem, aOldItem, aListener) {
+
         if (this.readOnly) {
             this.notifyOperationComplete(aListener,
                                          Components.interfaces.calIErrors.CAL_IS_READONLY,
                                          Components.interfaces.calIOperationListener.MODIFY,
                                          null,
                                          "Calendar is readonly");
             return null;
         }
@@ -544,17 +565,17 @@ calStorageCalendar.prototype = {
         this.deleteItemById(aItem.id);
 
         this.notifyOperationComplete(aListener,
                                      Components.results.NS_OK,
                                      Components.interfaces.calIOperationListener.DELETE,
                                      aItem.id,
                                      aItem);
 
-        // notify observers 
+        // notify observers
         this.observers.notify("onDeleteItem", [aItem]);
     },
 
     // void getItem( in string id, in calIOperationListener aListener );
     getItem: function cSC_getItem(aId, aListener) {
         if (!aListener)
             return;
 
@@ -590,17 +611,17 @@ calStorageCalendar.prototype = {
 
         this.notifyOperationComplete(aListener,
                                      Components.results.NS_OK,
                                      Components.interfaces.calIOperationListener.GET,
                                      aId,
                                      null);
     },
 
-    // void getItems( in unsigned long aItemFilter, in unsigned long aCount, 
+    // void getItems( in unsigned long aItemFilter, in unsigned long aCount,
     //                in calIDateTime aRangeStart, in calIDateTime aRangeEnd,
     //                in calIOperationListener aListener );
     getItems: function cSC_getItems(aItemFilter, aCount,
                                     aRangeStart, aRangeEnd, aListener) {
         let this_ = this;
         cal.postPone(function() {
                 this_.getItems_(aItemFilter, aCount, aRangeStart, aRangeEnd, aListener);
             });
@@ -741,31 +762,30 @@ calStorageCalendar.prototype = {
 
         // First fetch all the events
         if (wantEvents) {
             var sp;             // stmt params
             var resultItems = [];
 
             // first get non-recurring events that happen to fall within the range
             //
-            this.prepareStatement(this.mSelectNonRecurringEventsByRange);
-            sp = this.mSelectNonRecurringEventsByRange.params;
-            sp.range_start = startTime;
-            sp.range_end = endTime;
-            sp.start_offset = aRangeStart ? aRangeStart.timezoneOffset * USECS_PER_SECOND : 0;
-            sp.end_offset = aRangeEnd ? aRangeEnd.timezoneOffset * USECS_PER_SECOND : 0;
+            try {
+                this.prepareStatement(this.mSelectNonRecurringEventsByRange);
+                sp = this.mSelectNonRecurringEventsByRange.params;
+                sp.range_start = startTime;
+                sp.range_end = endTime;
+                sp.start_offset = aRangeStart ? aRangeStart.timezoneOffset * USECS_PER_SECOND : 0;
+                sp.end_offset = aRangeEnd ? aRangeEnd.timezoneOffset * USECS_PER_SECOND : 0;
 
-            try {
                 while (this.mSelectNonRecurringEventsByRange.step()) {
                     let row = this.mSelectNonRecurringEventsByRange.row;
                     resultItems.push(this.getEventFromRow(row, {}));
                 }
             } catch (e) {
-                cal.ERROR("Error selecting non recurring events by range!\n" + e +
-                          "\nDB Error: " + this.mDB.lastErrorString);
+                this.logError("Error selecting non recurring events by range!\n", e);
             } finally {
                 this.mSelectNonRecurringEventsByRange.reset();
             }
 
             // process the non-recurring events:
             for each (var evitem in resultItems) {
                 count += handleResultItem(evitem, Components.interfaces.calIEvent);
                 if (checkCount()) {
@@ -783,31 +803,30 @@ calStorageCalendar.prototype = {
         }
 
         // if todos are wanted, do them next
         if (wantTodos) {
             var sp;             // stmt params
             var resultItems = [];
 
             // first get non-recurring todos that happen to fall within the range
-            this.prepareStatement(this.mSelectNonRecurringTodosByRange);
-            sp = this.mSelectNonRecurringTodosByRange.params;
-            sp.range_start = startTime;
-            sp.range_end = endTime;
-            sp.start_offset = aRangeStart ? aRangeStart.timezoneOffset * USECS_PER_SECOND : 0;
-            sp.end_offset = aRangeEnd ? aRangeEnd.timezoneOffset * USECS_PER_SECOND : 0;
+            try {
+                this.prepareStatement(this.mSelectNonRecurringTodosByRange);
+                sp = this.mSelectNonRecurringTodosByRange.params;
+                sp.range_start = startTime;
+                sp.range_end = endTime;
+                sp.start_offset = aRangeStart ? aRangeStart.timezoneOffset * USECS_PER_SECOND : 0;
+                sp.end_offset = aRangeEnd ? aRangeEnd.timezoneOffset * USECS_PER_SECOND : 0;
 
-            try {
                 while (this.mSelectNonRecurringTodosByRange.step()) {
                     let row = this.mSelectNonRecurringTodosByRange.row;
                     resultItems.push(this.getTodoFromRow(row, {}));
                 }
             } catch (e) {
-                cal.ERROR("Error selecting non recurring todos by range!\n" + e +
-                          "\nDB Error: " + this.mDB.lastErrorString);
+                this.logError("Error selecting non recurring todos by range", e);
             } finally {
                 this.mSelectNonRecurringTodosByRange.reset();
             }
 
             // process the non-recurring todos:
             for each (var todoitem in resultItems) {
                 count += handleResultItem(todoitem, Components.interfaces.calITodo, checkCompleted);
                 if (checkCount()) {
@@ -868,17 +887,17 @@ calStorageCalendar.prototype = {
             "LIMIT 1"
             );
 
         // The more readable version of the next where-clause is:
         //   WHERE  ((event_end > :range_start OR
         //           (event_end = :range_start AND
         //           event_start = :range_start))
         //          AND event_start < :range_end)
-        //         
+        //
         // but that doesn't work with floating start or end times. The logic
         // is the same though.
         // For readability, a few helpers:
         var floatingEventStart = "event_start_tz = 'floating' AND event_start"
         var nonFloatingEventStart = "event_start_tz != 'floating' AND event_start"
         var floatingEventEnd = "event_end_tz = 'floating' AND event_end"
         var nonFloatingEventEnd = "event_end_tz != 'floating' AND event_end"
         // The query needs to take both floating and non floating into account
@@ -896,17 +915,17 @@ calStorageCalendar.prototype = {
             "  (("+floatingEventStart+" < :range_end + :end_offset) OR " +
             "   ("+nonFloatingEventStart+" < :range_end)) " +
             " AND cal_id = :cal_id AND flags & 16 == 0 AND recurrence_id IS NULL"
             );
        /**
         * WHERE (due > rangeStart AND start < rangeEnd) OR
         *       (due = rangeStart AND start = rangeStart) OR
         *       (due IS NULL AND ((start >= rangeStart AND start < rangeEnd) OR
-        *                         (start IS NULL AND 
+        *                         (start IS NULL AND
         *                          (completed > rangeStart OR completed IS NULL))) OR
         *       (start IS NULL AND due >= rangeStart AND due < rangeEnd)
         */
 
         var floatingTodoEntry = "todo_entry_tz = 'floating' AND todo_entry";
         var nonFloatingTodoEntry = "todo_entry_tz != 'floating' AND todo_entry";
         var floatingTodoDue = "todo_due_tz = 'floating' AND todo_due";
         var nonFloatingTodoDue = "todo_due_tz != 'floating' AND todo_due";
@@ -1108,24 +1127,24 @@ calStorageCalendar.prototype = {
             this.mDB,
             "INSERT INTO cal_recurrence " +
             "  (cal_id, item_id, recur_index, recur_type, is_negative, dates, count, end_date, interval, second, minute, hour, day, monthday, yearday, weekno, month, setpos) " +
             "VALUES (:cal_id, :item_id, :recur_index, :recur_type, :is_negative, :dates, :count, :end_date, :interval, :second, :minute, :hour, :day, :monthday, :yearday, :weekno, :month, :setpos)"
             );
 
         this.mInsertAttachment = createStatement (
             this.mDB,
-            "INSERT INTO cal_attachments " + 
+            "INSERT INTO cal_attachments " +
             " (cal_id, item_id, data, format_type, encoding, recurrence_id, recurrence_id_tz) " +
             "VALUES (:cal_id, :item_id, :data, :format_type, :encoding, :recurrence_id, :recurrence_id_tz)"
             );
 
         this.mInsertRelation = createStatement (
             this.mDB,
-            "INSERT INTO cal_relations " + 
+            "INSERT INTO cal_relations " +
             " (cal_id, item_id, rel_type, rel_id, recurrence_id, recurrence_id_tz) " +
             "VALUES (:cal_id, :item_id, :rel_type, :rel_id, :recurrence_id, :recurrence_id_tz)"
             );
 
         this.mInsertMetaData = createStatement(
             this.mDB,
             "INSERT INTO cal_metadata"
             + " (cal_id, item_id, value)"
@@ -1275,42 +1294,40 @@ calStorageCalendar.prototype = {
 
     assureRecurringItemCaches: function cSC_assureRecurringItemCaches() {
         if (this.mRecItemCacheInited) {
             return;
         }
         // build up recurring event and todo cache, because we need that on every query:
         // for recurring items, we need to query database-wide.. yuck
 
-        this.prepareStatement(this.mSelectEventsWithRecurrence);
-        let sp = this.mSelectEventsWithRecurrence.params;
         try {
+            this.prepareStatement(this.mSelectEventsWithRecurrence);
+            let sp = this.mSelectEventsWithRecurrence.params;
             while (this.mSelectEventsWithRecurrence.step()) {
                 var row = this.mSelectEventsWithRecurrence.row;
                 var item = this.getEventFromRow(row, {});
                 this.mRecEventCache[item.id] = item;
             }
         } catch (e) {
-            cal.ERROR("Error selecting events with recurrence!\n" + e +
-                      "\nDB Error: " + this.mDB.lastErrorString);
+            this.logError("Error selecting events with recurrence!", e);
         } finally {
             this.mSelectEventsWithRecurrence.reset();
         }
 
-        this.prepareStatement(this.mSelectTodosWithRecurrence);
-        sp = this.mSelectTodosWithRecurrence.params;
         try {
+            this.prepareStatement(this.mSelectTodosWithRecurrence);
+            sp = this.mSelectTodosWithRecurrence.params;
             while (this.mSelectTodosWithRecurrence.step()) {
                 var row = this.mSelectTodosWithRecurrence.row;
                 var item = this.getTodoFromRow(row, {});
                 this.mRecTodoCache[item.id] = item;
             }
         } catch (e) {
-            cal.ERROR("Error selecting todos with recurrence!\n" + e +
-                      "\nDB Error: " + this.mDB.lastErrorString);
+            this.logError("Error selecting todos with recurrence!", e);
         } finally {
             this.mSelectTodosWithRecurrence.reset();
         }
 
         this.mRecItemCacheInited = true;
     },
 
     // xxx todo: consider removing flags parameter
@@ -1380,65 +1397,62 @@ calStorageCalendar.prototype = {
         return item;
     },
 
     // after we get the base item, we need to check if we need to pull in
     // any extra data from other tables.  We do that here.
 
     // We used to use mDBTwo for this, so this can be run while a
     // select is executing but this no longer seems to be required.
-    
+
     getAdditionalDataForItem: function cSC_getAdditionalDataForItem(item, flags) {
         // This is needed to keep the modification time intact.
         var savedLastModifiedTime = item.lastModifiedTime;
 
         if (flags & CAL_ITEM_FLAG.HAS_ATTENDEES) {
             var selectItem = null;
             if (item.recurrenceId == null)
                 selectItem = this.mSelectAttendeesForItem;
             else {
                 selectItem = this.mSelectAttendeesForItemWithRecurrenceId;
                 this.setDateParamHelper(selectItem.params, "recurrence_id", item.recurrenceId);
             }
 
-            this.prepareStatement(selectItem);
-            selectItem.params.item_id = item.id;
-
             try {
+                this.prepareStatement(selectItem);
+                selectItem.params.item_id = item.id;
                 while (selectItem.step()) {
                     var attendee = this.getAttendeeFromRow(selectItem.row);
                     if (attendee.isOrganizer) {
                         item.organizer = attendee;
                     } else {
                         item.addAttendee(attendee);
                     }
                 }
             } catch (e) {
-                cal.ERROR("Error getting attendees for item '" +
-                          item.title + "' (" + item.id + ")!\n" + e +
-                          "\nDB Error: " + this.mDB.lastErrorString);
+                this.logError("Error getting attendees for item '" +
+                              item.title + "' (" + item.id + ")!", e);
             } finally {
                 selectItem.reset();
             }
         }
 
         var row;
         if (flags & CAL_ITEM_FLAG.HAS_PROPERTIES) {
             var selectItem = null;
             if (item.recurrenceId == null)
                 selectItem = this.mSelectPropertiesForItem;
             else {
                 selectItem = this.mSelectPropertiesForItemWithRecurrenceId;
                 this.setDateParamHelper(selectItem.params, "recurrence_id", item.recurrenceId);
             }
-                
-            this.prepareStatement(selectItem);
-            selectItem.params.item_id = item.id;
-            
+
             try {
+                this.prepareStatement(selectItem);
+                selectItem.params.item_id = item.id;
                 while (selectItem.step()) {
                     row = selectItem.row;
                     var name = row.key;
                     switch (name) {
                         case "DURATION":
                             // for events DTEND/DUE is enforced by calEvent/calTodo, so suppress DURATION:
                             break;
                         case "CATEGORIES": {
@@ -1447,34 +1461,33 @@ calStorageCalendar.prototype = {
                             break;
                         }
                         default:
                             item.setProperty(name, row.value);
                             break;
                     }
                 }
             } catch (e) {
-                cal.ERROR("Error getting extra properties for item '" +
-                          item.title + "' (" + item.id + ")!\n" + e +
-                          "\nDB Error: " + this.mDB.lastErrorString);
+                this.logError("Error getting extra properties for item '" +
+                              item.title + "' (" + item.id + ")!", e);
             } finally {
                 selectItem.reset();
             }
         }
 
         var i;
         if (flags & CAL_ITEM_FLAG.HAS_RECURRENCE) {
             if (item.recurrenceId)
                 throw Components.results.NS_ERROR_UNEXPECTED;
 
             var rec = null;
 
-            this.prepareStatement(this.mSelectRecurrenceForItem);
-            this.mSelectRecurrenceForItem.params.item_id = item.id;
             try {
+                this.prepareStatement(this.mSelectRecurrenceForItem);
+                this.mSelectRecurrenceForItem.params.item_id = item.id;
                 while (this.mSelectRecurrenceForItem.step()) {
                     row = this.mSelectRecurrenceForItem.row;
 
                     var ritem = null;
 
                     if (row.recur_type == null ||
                         row.recur_type == "x-dateset")
                     {
@@ -1538,19 +1551,18 @@ calStorageCalendar.prototype = {
                     if (row.is_negative)
                         ritem.isNegative = true;
                     if (rec == null) {
                         rec = cal.createRecurrenceInfo(item);
                     }
                     rec.appendRecurrenceItem(ritem);
                 }
             } catch (e) {
-                cal.ERROR("Error getting recurrence for item '" +
-                          item.title + "' (" + item.id + ")!\n" + e +
-                          "\nDB Error: " + this.mDB.lastErrorString);
+                this.logError("Error getting recurrence for item '" +
+                              item.title + "' (" + item.id + ")!", e);
             } finally {
                 this.mSelectRecurrenceForItem.reset();
             }
 
             if (rec == null) {
                 dump ("XXXX Expected to find recurrence, but got no items!\n");
             }
             item.recurrenceInfo = rec;
@@ -1571,112 +1583,103 @@ calStorageCalendar.prototype = {
                 this.prepareStatement(this.mSelectEventExceptions);
                 try {
                     while (this.mSelectEventExceptions.step()) {
                         var row = this.mSelectEventExceptions.row;
                         var exc = this.getEventFromRow(row, {}, true /*isException*/);
                         rec.modifyException(exc, true);
                     }
                 } catch (e) {
-                    cal.ERROR("Error getting exceptions for event '" +
-                              item.title + "' (" + item.id + ")!\n" + e +
-                              "\nDB Error: " + this.mDB.lastErrorString);
+                    this.logError("Error getting exceptions for event '" +
+                                  item.title + "' (" + item.id + ")!", e);
                 } finally {
                     this.mSelectEventExceptions.reset();
                 }
             } else if (cal.isToDo(item)) {
                 this.mSelectTodoExceptions.params.id = item.id;
                 this.prepareStatement(this.mSelectTodoExceptions);
                 try {
                     while (this.mSelectTodoExceptions.step()) {
                         var row = this.mSelectTodoExceptions.row;
                         var exc = this.getTodoFromRow(row, {}, true /*isException*/);
                         rec.modifyException(exc, true);
                     }
                 } catch (e) {
-                    cal.ERROR("Error getting exceptions for task '" +
-                              item.title + "' (" + item.id + ")!\n" + e +
-                              "\nDB Error: " + this.mDB.lastErrorString);
+                    this.logError("Error getting exceptions for task '" +
+                                  item.title + "' (" + item.id + ")!", e);
                 } finally {
                     this.mSelectTodoExceptions.reset();
                 }
             } else {
                 throw Components.results.NS_ERROR_UNEXPECTED;
             }
         }
 
         if (flags & CAL_ITEM_FLAG.HAS_ATTACHMENTS) {
             let selectAttachment = this.mSelectAttachmentsForItem;
             if (item.recurrenceId != null) {
                 selectAttachment = this.mSelectAttachmentsForItemWithRecurrenceId;
                 this.setDateParamHelper(selectAttachment.params, "recurrence_id", item.recurrenceId);
             }
-
-            this.prepareStatement(selectAttachment);
-            selectAttachment.params.item_id = item.id;
-
             try {
+                this.prepareStatement(selectAttachment);
+                selectAttachment.params.item_id = item.id;
                 while (selectAttachment.step()) {
                     let row = selectAttachment.row;
                     let attachment = this.getAttachmentFromRow(row);
                     item.addAttachment(attachment);
                 }
             } catch (e) {
-                cal.ERROR("Error getting attachments for item '" +
-                          item.title + "' (" + item.id + ")!\n" + e +
-                          "\nDB Error: " + this.mDB.lastErrorString);
+                this.logError("Error getting attachments for item '" +
+                              item.title + "' (" + item.id + ")!", e);
             } finally {
                 selectAttachment.reset();
             }
         }
 
         if (flags & CAL_ITEM_FLAG.HAS_RELATIONS) {
             let selectRelation = this.mSelectRelationsForItem;
             if (item.recurrenceId != null) {
                 selectRelation = this.mSelectRelationsForItemWithRecurrenceId;
                 this.setDateParamHelper(selectRelation.params, "recurrence_id", item.recurrenceId);
             }
-
-            this.prepareStatement(selectRelation);
-            selectRelation.params.item_id = item.id;
             try {
+                this.prepareStatement(selectRelation);
+                selectRelation.params.item_id = item.id;
                 while (selectRelation.step()) {
                     let row = selectRelation.row;
                     let relation = this.getRelationFromRow(row);
                     item.addRelation(relation);
                 }
             } catch (e) {
-                cal.ERROR("Error getting relations for item '" +
-                          item.title + "' (" + item.id + ")!\n" + e +
-                          "\nDB Error: " + this.mDB.lastErrorString);
+                this.logError("Error getting relations for item '" +
+                              item.title + "' (" + item.id + ")!", e);
             } finally {
                 selectRelation.reset();
             }
         }
 
         if (flags & CAL_ITEM_FLAG.HAS_ALARMS) {
             let selectAlarm = this.mSelectAlarmsForItem;
             if (item.recurrenceId != null) {
                 selectAlarm = this.mSelectAlarmsForItemWithRecurrenceId;
                 this.setDateParamHelper(selectAlarm.params, "recurrence_id", item.recurrenceId);
             }
-
-            selectAlarm.params.item_id = item.id;
-            this.prepareStatement(selectAlarm);
-            try { 
+            try {
+                selectAlarm.params.item_id = item.id;
+                this.prepareStatement(selectAlarm);
                 while (selectAlarm.step()) {
                     let row = selectAlarm.row;
                     let alarm = cal.createAlarm();
                     alarm.icalString = row.icalString;
                     item.addAlarm(alarm);
                 }
             } catch (e) {
-                cal.ERROR("Error getting alarms for item '" +
-                          item.title + "' (" + item.id + ")!\n" + e +
-                          "\nDB Error: " + this.mDB.lastErrorString);
+                this.logError("Error getting alarms for item '" +
+                              item.title + "' (" + item.id + ")!", e);
             } finally {
                 selectAlarm.reset();
             }
         }
 
         // Restore the saved modification time
         item.setProperty("LAST-MODIFIED", savedLastModifiedTime);
     },
@@ -1707,22 +1710,22 @@ calStorageCalendar.prototype = {
             }
         }
 
         return a;
     },
 
     getAttachmentFromRow: function cSC_getAttachmentFromRow(row) {
         let a = cal.createAttachment();
-       
+
         // TODO we don't support binary data here, libical doesn't either.
         a.uri = makeURL(row.data);
         a.formatType = row.format_type;
         a.encoding = row.encoding;
-    
+
         return a;
     },
 
     getRelationFromRow: function cSC_getRelationFromRow(row) {
         let r = cal.createRelation();
         r.relType = row.rel_type;
         r.relId = row.rel_id;
         return r;
@@ -1738,41 +1741,39 @@ calStorageCalendar.prototype = {
         var item = this.mItemCache[aID];
         if (item) {
             return item;
         }
 
         // not cached; need to read from the db
         var flags = {};
 
-        // try events first
-        this.prepareStatement(this.mSelectEvent);
-        this.mSelectEvent.params.id = aID;
         try {
+            // try events first
+            this.prepareStatement(this.mSelectEvent);
+            this.mSelectEvent.params.id = aID;
             if (this.mSelectEvent.step()) {
                 item = this.getEventFromRow(this.mSelectEvent.row, flags);
             }
         } catch (e) {
-            cal.ERROR("Error selecting item by id " + aID + "!\n" + e +
-                      "\nDB Error: " + this.mDB.lastErrorString);
+            this.logError("Error selecting item by id " + aID + "!", e);
         } finally {
             this.mSelectEvent.reset();
         }
 
         // try todo if event fails
         if (!item) {
-            this.prepareStatement(this.mSelectTodo);
-            this.mSelectTodo.params.id = aID;
             try {
+                this.prepareStatement(this.mSelectTodo);
+                this.mSelectTodo.params.id = aID;
                 if (this.mSelectTodo.step()) {
                     item = this.getTodoFromRow(this.mSelectTodo.row, flags);
                 }
             } catch (e) {
-                cal.ERROR("Error selecting item by id " + aID + "!\n" + e +
-                          "\nDB Error: " + this.mDB.lastErrorString);
+                this.logError("Error selecting item by id " + aID + "!", e);
             } finally {
                 this.mSelectTodo.reset();
             }
         }
 
         return item;
     },
 
@@ -1833,62 +1834,68 @@ calStorageCalendar.prototype = {
             this.writeEvent(item, olditem, flags);
         else if (isToDo(item))
             this.writeTodo(item, olditem, flags);
         else
             throw Components.results.NS_ERROR_UNEXPECTED;
     },
 
     writeEvent: function cSC_writeEvent(item, olditem, flags) {
-        let ip = this.mInsertEvent.params;
-        this.prepareStatement(this.mInsertEvent);
-        this.setupItemBaseParams(item, olditem, ip);
+        try {
+            this.prepareStatement(this.mInsertEvent);
+            let ip = this.mInsertEvent.params;
+            this.setupItemBaseParams(item, olditem, ip);
 
-        this.setDateParamHelper(ip, "event_start", item.startDate);
-        this.setDateParamHelper(ip, "event_end", item.endDate);
-        let dtstamp = item.stampTime;
-        if (dtstamp) {
-            ip.event_stamp = dtstamp.nativeTime;
+            this.setDateParamHelper(ip, "event_start", item.startDate);
+            this.setDateParamHelper(ip, "event_end", item.endDate);
+            let dtstamp = item.stampTime;
+            if (dtstamp) {
+                ip.event_stamp = dtstamp.nativeTime;
+            }
+
+            if (item.startDate.isDate) {
+                flags |= CAL_ITEM_FLAG.EVENT_ALLDAY;
+            }
+
+            ip.flags = flags;
+
+            this.mInsertEvent.execute();
+        } finally {
+            this.mInsertEvent.reset();
         }
-
-        if (item.startDate.isDate) {
-            flags |= CAL_ITEM_FLAG.EVENT_ALLDAY;
-        }
-
-        ip.flags = flags;
-
-        this.mInsertEvent.execute();
-        this.mInsertEvent.reset();
     },
 
     writeTodo: function cSC_writeTodo(item, olditem, flags) {
-        let ip = this.mInsertTodo.params;
-        this.prepareStatement(this.mInsertTodo);
+        try {
+            this.prepareStatement(this.mInsertTodo);
+            let ip = this.mInsertTodo.params;
 
-        this.setupItemBaseParams(item, olditem, ip);
+            this.setupItemBaseParams(item, olditem, ip);
 
-        this.setDateParamHelper(ip, "todo_entry", item.entryDate);
-        this.setDateParamHelper(ip, "todo_due", item.dueDate);
-        let dtstamp = item.stampTime;
-        if (dtstamp) {
-            ip.todo_stamp = dtstamp.nativeTime;
+            this.setDateParamHelper(ip, "todo_entry", item.entryDate);
+            this.setDateParamHelper(ip, "todo_due", item.dueDate);
+            let dtstamp = item.stampTime;
+            if (dtstamp) {
+                ip.todo_stamp = dtstamp.nativeTime;
+            }
+            this.setDateParamHelper(ip, "todo_completed", item.getProperty("COMPLETED"));
+
+            ip.todo_complete = item.getProperty("PERCENT-COMPLETED");
+
+            let someDate = (item.entryDate || item.dueDate);
+            if (someDate && someDate.isDate) {
+                flags |= CAL_ITEM_FLAG.EVENT_ALLDAY;
+            }
+
+            ip.flags = flags;
+
+            this.mInsertTodo.execute();
+        } finally {
+            this.mInsertTodo.reset();
         }
-        this.setDateParamHelper(ip, "todo_completed", item.getProperty("COMPLETED"));
-
-        ip.todo_complete = item.getProperty("PERCENT-COMPLETED");
-
-        let someDate = (item.entryDate || item.dueDate);
-        if (someDate && someDate.isDate) {
-            flags |= CAL_ITEM_FLAG.EVENT_ALLDAY;
-        }
-
-        ip.flags = flags;
-
-        this.mInsertTodo.execute();
-        this.mInsertTodo.reset();
     },
 
     setupItemBaseParams: function cSC_setupItemBaseParams(item, olditem, ip) {
         ip.id = item.id;
 
         if (item.recurrenceId) {
             this.setDateParamHelper(ip, "recurrence_id", item.recurrenceId);
         }
@@ -1915,83 +1922,89 @@ calStorageCalendar.prototype = {
         if (item.organizer) {
             attendees = attendees.concat([]);
             attendees.push(item.organizer);
         }
         if (attendees.length > 0) {
             for each (var att in attendees) {
                 var ap = this.mInsertAttendee.params;
                 ap.item_id = item.id;
-                this.prepareStatement(this.mInsertAttendee);
-                this.setDateParamHelper(ap, "recurrence_id", item.recurrenceId);
-                ap.attendee_id = att.id;
-                ap.common_name = att.commonName;
-                switch (att.rsvp) {
-                    case "FALSE":
-                        ap.rsvp = 0;
-                        break;
-                    case "TRUE":
-                        ap.rsvp = 1;
-                        break;
-                    default:
-                        ap.rsvp = 2;
-                        break;
+                try {
+                    this.prepareStatement(this.mInsertAttendee);
+                    this.setDateParamHelper(ap, "recurrence_id", item.recurrenceId);
+                    ap.attendee_id = att.id;
+                    ap.common_name = att.commonName;
+                    switch (att.rsvp) {
+                        case "FALSE":
+                            ap.rsvp = 0;
+                            break;
+                        case "TRUE":
+                            ap.rsvp = 1;
+                            break;
+                        default:
+                            ap.rsvp = 2;
+                            break;
+                    }
+                    ap.role = att.role;
+                    ap.status = att.participationStatus;
+                    ap.type = att.userType;
+                    ap.is_organizer = att.isOrganizer;
+
+                    var props = "";
+                    var propEnum = att.propertyEnumerator;
+                    while (propEnum && propEnum.hasMoreElements()) {
+                        var prop = propEnum.getNext().QueryInterface(Components.interfaces.nsIProperty);
+                        if (props.length) {
+                            props += ",";
+                        }
+                        props += encodeURIComponent(prop.name);
+                        props += ":";
+                        props += encodeURIComponent(prop.value);
+                    }
+                    if (props.length) {
+                        ap.properties = props;
+                    }
+
+                    this.mInsertAttendee.execute();
+                } finally {
+                    this.mInsertAttendee.reset();
                 }
-                ap.role = att.role;
-                ap.status = att.participationStatus;
-                ap.type = att.userType;
-                ap.is_organizer = att.isOrganizer;
-
-                var props = "";
-                var propEnum = att.propertyEnumerator;
-                while (propEnum && propEnum.hasMoreElements()) {
-                    var prop = propEnum.getNext().QueryInterface(Components.interfaces.nsIProperty);
-                    if (props.length) {
-                        props += ",";
-                    }
-                    props += encodeURIComponent(prop.name);
-                    props += ":";
-                    props += encodeURIComponent(prop.value);
-                }
-                if (props.length) {
-                    ap.properties = props;
-                }
-
-                this.mInsertAttendee.execute();
-                this.mInsertAttendee.reset();
             }
 
             return CAL_ITEM_FLAG.HAS_ATTENDEES;
         }
 
         return 0;
     },
 
     writeProperty: function cSC_writeProperty(item, propName, propValue) {
-        var pp = this.mInsertProperty.params;
-        this.prepareStatement(this.mInsertProperty);
-        pp.key = propName;
-        if (calInstanceOf(propValue, Components.interfaces.calIDateTime)) {
-            pp.value = propValue.nativeTime;
-        } else {
-            try {
-                pp.value = propValue;
-            } catch (e) {
-                // The storage service throws an NS_ERROR_ILLEGAL_VALUE in
-                // case pval is something complex (i.e not a string or
-                // number). Swallow this error, leaving the value empty.
-                if (e.result != Components.results.NS_ERROR_ILLEGAL_VALUE) {
-                    throw e;
+        try {
+            this.prepareStatement(this.mInsertProperty);
+            var pp = this.mInsertProperty.params;
+            pp.key = propName;
+            if (calInstanceOf(propValue, Components.interfaces.calIDateTime)) {
+                pp.value = propValue.nativeTime;
+            } else {
+                try {
+                    pp.value = propValue;
+                } catch (e) {
+                    // The storage service throws an NS_ERROR_ILLEGAL_VALUE in
+                    // case pval is something complex (i.e not a string or
+                    // number). Swallow this error, leaving the value empty.
+                    if (e.result != Components.results.NS_ERROR_ILLEGAL_VALUE) {
+                        throw e;
+                    }
                 }
             }
+            pp.item_id = item.id;
+            this.setDateParamHelper(pp, "recurrence_id", item.recurrenceId);
+            this.mInsertProperty.execute();
+        } finally {
+            this.mInsertProperty.reset();
         }
-        pp.item_id = item.id;
-        this.setDateParamHelper(pp, "recurrence_id", item.recurrenceId);
-        this.mInsertProperty.execute();
-        this.mInsertProperty.reset();
     },
 
     writeProperties: function cSC_writeProperties(item, olditem) {
         var ret = 0;
         var propEnumerator = item.propertyEnumerator;
         while (propEnumerator.hasMoreElements()) {
             ret = CAL_ITEM_FLAG.HAS_PROPERTIES;
             var prop = propEnumerator.getNext().QueryInterface(Components.interfaces.nsIProperty);
@@ -2014,71 +2027,74 @@ calStorageCalendar.prototype = {
 
         var rec = item.recurrenceInfo;
         if (rec) {
             flags = CAL_ITEM_FLAG.HAS_RECURRENCE;
             var ritems = rec.getRecurrenceItems ({});
             for (i in ritems) {
                 var ritem = ritems[i];
                 var ap = this.mInsertRecurrence.params;
-                this.prepareStatement(this.mInsertRecurrence);
-                ap.item_id = item.id;
-                ap.recur_index = i;
-                ap.is_negative = ritem.isNegative;
-                if (calInstanceOf(ritem, Components.interfaces.calIRecurrenceDate)) {
-                    ap.recur_type = "x-date";
-                    ap.dates = dateToText(getInUtcOrKeepFloating(ritem.date));
+                try {
+                    this.prepareStatement(this.mInsertRecurrence);
+                    ap.item_id = item.id;
+                    ap.recur_index = i;
+                    ap.is_negative = ritem.isNegative;
+                    if (calInstanceOf(ritem, Components.interfaces.calIRecurrenceDate)) {
+                        ap.recur_type = "x-date";
+                        ap.dates = dateToText(getInUtcOrKeepFloating(ritem.date));
+
+                    } else if (calInstanceOf(ritem, Components.interfaces.calIRecurrenceDateSet)) {
+                        ap.recur_type = "x-dateset";
+
+                        var rdates = ritem.getDates({});
+                        var datestr = "";
+                        for (j in rdates) {
+                            if (j != 0)
+                                datestr += ",";
+
+                            datestr += dateToText(getInUtcOrKeepFloating(rdates[j]));
+                        }
+
+                        ap.dates = datestr;
+
+                    } else if (calInstanceOf(ritem, Components.interfaces.calIRecurrenceRule)) {
+                        ap.recur_type = ritem.type;
 
-                } else if (calInstanceOf(ritem, Components.interfaces.calIRecurrenceDateSet)) {
-                    ap.recur_type = "x-dateset";
+                        if (ritem.isByCount)
+                            ap.count = ritem.count;
+                        else
+                            ap.end_date = ritem.untilDate ? ritem.untilDate.nativeTime : null;
+
+                        ap.interval = ritem.interval;
 
-                    var rdates = ritem.getDates({});
-                    var datestr = "";
-                    for (j in rdates) {
-                        if (j != 0)
-                            datestr += ",";
-
-                        datestr += dateToText(getInUtcOrKeepFloating(rdates[j]));
+                        var rtypes = ["second",
+                                      "minute",
+                                      "hour",
+                                      "day",
+                                      "monthday",
+                                      "yearday",
+                                      "weekno",
+                                      "month",
+                                      "setpos"];
+                        for (var j = 0; j < rtypes.length; j++) {
+                            var comp = "BY" + rtypes[j].toUpperCase();
+                            var comps = ritem.getComponent(comp, {});
+                            if (comps && comps.length > 0) {
+                                var compstr = comps.join(",");
+                                ap[rtypes[j]] = compstr;
+                            }
+                        }
+                    } else {
+                        dump ("##### Don't know how to serialize recurrence item " + ritem + "!\n");
                     }
 
-                    ap.dates = datestr;
-
-                } else if (calInstanceOf(ritem, Components.interfaces.calIRecurrenceRule)) {
-                    ap.recur_type = ritem.type;
-
-                    if (ritem.isByCount)
-                        ap.count = ritem.count;
-                    else
-                        ap.end_date = ritem.untilDate ? ritem.untilDate.nativeTime : null;
-
-                    ap.interval = ritem.interval;
-
-                    var rtypes = ["second",
-                                  "minute",
-                                  "hour",
-                                  "day",
-                                  "monthday",
-                                  "yearday",
-                                  "weekno",
-                                  "month",
-                                  "setpos"];
-                    for (var j = 0; j < rtypes.length; j++) {
-                        var comp = "BY" + rtypes[j].toUpperCase();
-                        var comps = ritem.getComponent(comp, {});
-                        if (comps && comps.length > 0) {
-                            var compstr = comps.join(",");
-                            ap[rtypes[j]] = compstr;
-                        }
-                    }
-                } else {
-                    dump ("##### Don't know how to serialize recurrence item " + ritem + "!\n");
+                    this.mInsertRecurrence.execute();
+                } finally {
+                    this.mInsertRecurrence.reset();
                 }
-
-                this.mInsertRecurrence.execute();
-                this.mInsertRecurrence.reset();
             }
 
             var exceptions = rec.getExceptionIds ({});
             if (exceptions.length > 0) {
                 flags |= CAL_ITEM_FLAG.HAS_EXCEPTIONS;
 
                 // we need to serialize each exid as a separate
                 // event/todo; setupItemBase will handle
@@ -2097,68 +2113,72 @@ calStorageCalendar.prototype = {
         return flags;
     },
 
     writeAttachments: function cSC_writeAttachments(item, olditem) {
         let attachments = item.getAttachments({});
         if (attachments && attachments.length > 0) {
             for each (att in attachments) {
                 let ap = this.mInsertAttachment.params;
-                this.prepareStatement(this.mInsertAttachment);
-                this.setDateParamHelper(ap, "recurrence_id", item.recurrenceId);
-                ap.item_id = item.id;
-                ap.data = (att.uri ? att.uri.spec : "");
-                ap.format_type = att.formatType;
-                ap.encoding = att.encoding;
+                try {
+                    this.prepareStatement(this.mInsertAttachment);
+                    this.setDateParamHelper(ap, "recurrence_id", item.recurrenceId);
+                    ap.item_id = item.id;
+                    ap.data = (att.uri ? att.uri.spec : "");
+                    ap.format_type = att.formatType;
+                    ap.encoding = att.encoding;
 
-                this.mInsertAttachment.execute();
-                this.mInsertAttachment.reset();
+                    this.mInsertAttachment.execute();
+                } finally {
+                    this.mInsertAttachment.reset();
+                }
             }
             return CAL_ITEM_FLAG.HAS_ATTACHMENTS;
         }
         return 0;
     },
 
     writeRelations: function cSC_writeRelations(item, olditem) {
         let relations = item.getRelations({});
         if (relations && relations.length > 0) {
             for each (var rel in relations) {
                 let rp = this.mInsertRelation.params;
-                this.prepareStatement(this.mInsertRelation);
-                this.setDateParamHelper(rp, "recurrence_id", item.recurrenceId);
-                rp.item_id = item.id;
-                rp.rel_type = rel.relType;
-                rp.rel_id = rel.relId;
+                try {
+                    this.prepareStatement(this.mInsertRelation);
+                    this.setDateParamHelper(rp, "recurrence_id", item.recurrenceId);
+                    rp.item_id = item.id;
+                    rp.rel_type = rel.relType;
+                    rp.rel_id = rel.relId;
 
-                this.mInsertRelation.execute();
-                this.mInsertRelation.reset();
+                    this.mInsertRelation.execute();
+                } finally {
+                    this.mInsertRelation.reset();
+                }
             }
             return CAL_ITEM_FLAG.HAS_RELATIONS;
         }
         return 0;
-    },          
+    },
 
     writeAlarms: function cSC_writeAlarms(item, olditem) {
         let alarms = item.getAlarms({});
         if (alarms.length < 1) {
             return 0;
         }
 
         for each (let alarm in alarms) {
             let pp = this.mInsertAlarm.params;
-            this.prepareStatement(this.mInsertAlarm);
             try {
+                this.prepareStatement(this.mInsertAlarm);
                 this.setDateParamHelper(pp, "recurrence_id", item.recurrenceId);
                 pp.item_id = item.id;
                 pp.icalString = alarm.icalString;
                 this.mInsertAlarm.execute();
             } catch(e) {
-                cal.ERROR("Error writing alarm for item " + item.title + " (" + item.id + ")" +
-                          "\nDB Error: " + this.mDB.lastErrorString +
-                          "\nException: " + e);
+                this.logError("Error writing alarm for item " + item.title + " (" + item.id + ")", e);
             } finally {
                 this.mInsertAlarm.reset();
             }
         }
 
         return CAL_ITEM_FLAG.HAS_ALARMS;
     },
 
@@ -2212,31 +2232,31 @@ calStorageCalendar.prototype = {
      * when the transaction count reaches zero, the transaction is rolled back.
      *
      * @param err       (optional) If set, the transaction is set to fail when
      *                    the count reaches zero.
      */
     releaseTransaction: function cSC_releaseTransaction(err) {
         let calId = this.id;
         if (err) {
-            cal.ERROR("DB error: " + this.mDB.lastErrorString + "\nexc: " + err);
+            this.logError("releaseTransaction on error", err);
             gTransErr[calId] = err;
         }
 
         if (gTransCount[calId] > 0) {
             if (--gTransCount[calId] == 0) {
                 if (gTransErr[calId]) {
                     this.mDB.rollbackTransaction();
                     delete gTransErr[calId];
                 } else {
                     this.mDB.commitTransaction();
                 }
             }
         } else {
-            ASSERT(gTransCount[calId] > 0, "unexepcted batch count!");
+            ASSERT(gTransCount[calId] > 0, "unexpected batch count!");
         }
     },
 
     startBatch: function cSC_startBatch() {
         this.acquireTransaction();
         this.__proto__.__proto__.startBatch.apply(this, arguments);
     },
     endBatch: function cSC_endBatch() {
@@ -2244,71 +2264,118 @@ calStorageCalendar.prototype = {
         this.__proto__.__proto__.endBatch.apply(this, arguments);
     },
 
     //
     // calISyncWriteCalendar interface
     //
 
     setMetaData: function cSC_setMetaData(id, value) {
+
         this.mDeleteMetaData(id, this.id);
-        this.prepareStatement(this.mInsertMetaData);
-        var sp = this.mInsertMetaData.params;
-        sp.item_id = id;
-        try { 
+        try {
+            this.prepareStatement(this.mInsertMetaData);
+            var sp = this.mInsertMetaData.params;
+            sp.item_id = id;
             sp.value = value;
+            this.mInsertMetaData.execute();
         } catch (e) {
             // The storage service throws an NS_ERROR_ILLEGAL_VALUE in
             // case pval is something complex (i.e not a string or
             // number). Swallow this error, leaving the value empty.
             if (e.result != Components.results.NS_ERROR_ILLEGAL_VALUE) {
+                this.logError("Error setting metadata for id " + id + "!", e);
                 throw e;
             }
+        } finally {
+            this.mInsertMetaData.reset();
         }
-        this.mInsertMetaData.execute();
-        this.mInsertMetaData.reset();
     },
 
     deleteMetaData: function cSC_deleteMetaData(id) {
         this.mDeleteMetaData(id, this.id);
     },
 
     getMetaData: function cSC_getMetaData(id) {
         let query = this.mSelectMetaData;
-        this.prepareStatement(query);
-        query.params.item_id = id;
-        let value = null;
         try {
+            this.prepareStatement(query);
+            query.params.item_id = id;
+            let value = null;
+
             if (query.step()) {
                 value = query.row.value;
             }
         } catch (e) {
-            cal.ERROR("Error getting metadata for id " + id + "!\n" + e +
-                  "\nDB Error: " + this.mDB.lastErrorString);
+            this.logError("Error getting metadata for id " + id + "!", e);
         } finally {
             query.reset();
         }
         return value;
     },
 
     getAllMetaData: function cSC_getAllMetaData(out_count,
                                                  out_ids,
                                                  out_values) {
         let query = this.mSelectAllMetaData;
-        this.prepareStatement(query);
-        let ids = [];
-        let values = [];
         try {
+            this.prepareStatement(query);
+            let ids = [];
+            let values = [];
             while (query.step()) {
                 ids.push(query.row.item_id);
                 values.push(query.row.value);
             }
+            out_count.value = ids.length;
+            out_ids.value = ids;
+            out_values.value = values;
         } catch (e) {
-            cal.ERROR("Error getting all metadata!\n" + e +
-                      "\nDB Error: " + this.mDB.lastErrorString);
+            this.logError("Error getting all metadata!", e);
         } finally {
             query.reset();
         }
-        out_count.value = ids.length;
-        out_ids.value = ids;
-        out_values.value = values;
+    },
+    /**
+     * Internal logging function that should be called on any database error,
+     * it will log as much info as possible about the database context and
+     * last statement so the problem can be investigated more easilly.
+     *
+     * @param message           Error message to log.
+     * @param exception         Exception that caused the error.
+     */
+    logError: function cSC_logError(message,exception) {
+        let logMessage = "Message: " + message;
+        if (this.mDB) {
+            if (this.mDB.connectionReady) {
+              logMessage += "\nConnection Ready: " + this.mDB.connectionReady;
+            }
+            if (this.mDB.lastError) {
+              logMessage += "\nLast DB Error Number: " + this.mDB.lastError;
+            }
+            if (this.mDB.lastErrorString) {
+              logMessage += "\nLast DB Error Message: " + this.mDB.lastErrorString;
+            }
+            if (this.mDB.databaseFile) {
+              logMessage += "\nDatabase File: " + this.mDB.databaseFile.path;
+            }
+            if (this.mDB.lastInsertRowId) {
+              logMessage += "\nLast Insert Row Id: " + this.mDB.lastInsertRowId;
+            }
+            if (this.mDB.transactionInProgress) {
+              logMessage += "\nTransaction In Progress: " + this.mDB.transactionInProgress;
+            }
+        }
+
+        if (this.mLastStatement) {
+            logMessage += "\nLast DB Statement: " + this.mLastStatement;
+            if (this.mLastStatement.params) {
+                for (let param in this.mLastStatement.params) {
+                    logMessage += "\nLast Statement param [" + param + "]: " + this.mLastStatement.params[param];
+                }
+            }
+        }
+
+        if (exception) {
+            logMessage += "\nException: " + exception;
+        }
+        cal.ERROR(logMessage + "\n" + STACK(10));
     }
 };
--- a/calendar/providers/storage/calStorageUpgrade.jsm
+++ b/calendar/providers/storage/calStorageUpgrade.jsm
@@ -965,20 +965,24 @@ upgrade.v13 = function upgrade_v13(db, v
     try {
         alterTypes(tbl, "cal_metadata", ["item_id"], "TEXT", db);
 
         let calIds = {};
         if (db) {
             for each (let itemTable in ["events", "todos"]) {
                 let stmt = createStatement(db,
                                            "SELECT id, cal_id FROM cal_" + itemTable);
-                while (stmt.step()) {
-                    calIds[stmt.row.id] = stmt.row.cal_id;
+                try {
+                    while (stmt.step()) {
+                        calIds[stmt.row.id] = stmt.row.cal_id;
+                    }
                 }
-                stmt.reset();
+                finally {
+                    stmt.reset();
+                }
             }
         }
 
         for each (let tblid in ["attendees", "recurrence", "properties",
                                 "attachments"]) {
             addColumn(tbl, "cal_" + tblid, "cal_id", "INTEGER", db);
 
             for (let itemId in calIds) {
@@ -1169,32 +1173,37 @@ upgrade.v16 = function upgrade_v16(db, v
  */
 upgrade.v17 = function upgrade_v17(db, version) {
     let tbl = upgrade.v16(version < 16 && db, version);
     LOGdb(db, "Storage: Upgrading to v17");
     beginTransaction(db);
     try {
         for each (let tblName in ["alarms", "relations", "attachments"]) {
             let hasColumns = true;
+            let stmt;
             try {
                 // Stepping this statement will fail if the columns don't exist.
                 // We don't use the delegate here since it would show an error to
                 // the user, even through we expect the error. If the db is null,
                 // then swallowing the error is ok too since the cols will
                 // already be added in v16.
-                let stmt = db.createStatement("SELECT recurrence_id_tz," +
+                stmt = db.createStatement("SELECT recurrence_id_tz," +
                                               "       recurrence_id" +
                                               "  FROM cal_" + tblName +
                                               " LIMIT 1");
                 stmt.step();
-                stmt.finalize();
             } catch (e) {
                 // An error happened, which means the cols don't exist
                 hasColumns = false;
             }
+            finally {
+                if (stmt) {
+                    stmt.finalize();
+                }
+            }
 
             // Only add the columns if they are not there yet (i.e added in v16)
             // Since relations were broken all along, also make sure and add the
             // columns to the javascript object if there is no database.
             if (!hasColumns || !db) {
                 addColumn(tbl, "cal_" + tblName, "recurrence_id", "INTEGER", db);
                 addColumn(tbl, "cal_" + tblName, "recurrence_id_tz", "TEXT", db);
             }