Bug 813978 - Part 1/5: use multi-key indexes. r=sicking, ferjm
authorVicamo Yang <vyang@mozilla.com>
Thu, 17 Jan 2013 16:05:10 +0800 (2013-01-17)
changeset 118246 cbb086e55a94146f64952b4d2598d09c06e46aa1
parent 118245 28756e5bb960b4149bfd2ef6067c37f397a02fa2
child 118247 0d822e3f2d5938c235a5474bd9d094cb19bdabe7
push id303
push userpweitershausen@mozilla.com
push dateSat, 19 Jan 2013 00:33:16 +0000 (2013-01-19)
reviewerssicking, ferjm
bugs813978
milestone18.0
Bug 813978 - Part 1/5: use multi-key indexes. r=sicking, ferjm
dom/sms/src/ril/SmsDatabaseService.js
--- a/dom/sms/src/ril/SmsDatabaseService.js
+++ b/dom/sms/src/ril/SmsDatabaseService.js
@@ -10,17 +10,17 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
 
 const RIL_SMSDATABASESERVICE_CONTRACTID = "@mozilla.org/sms/rilsmsdatabaseservice;1";
 const RIL_SMSDATABASESERVICE_CID = Components.ID("{a1fa610c-eb6c-4ac2-878f-b005d5e89249}");
 
 const DEBUG = false;
 const DB_NAME = "sms";
-const DB_VERSION = 6;
+const DB_VERSION = 7;
 const STORE_NAME = "sms";
 const MOST_RECENT_STORE_NAME = "most-recent";
 
 const DELIVERY_SENDING = "sending";
 const DELIVERY_RECEIVED = "received";
 
 const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable";
 const DELIVERY_STATUS_SUCCESS = "success";
@@ -189,16 +189,20 @@ SmsDatabaseService.prototype = {
           case 4:
             if (DEBUG) debug("Upgrade to version 5. Populate quick threads view.")
             self.upgradeSchema4(event.target.transaction);
             break;
           case 5:
             if (DEBUG) debug("Upgrade to version 6. Use PhonenumberJS.")
             self.upgradeSchema5(event.target.transaction);
             break;
+          case 6:
+            if (DEBUG) debug("Upgrade to version 7. Use multiple entry indexes.")
+            self.upgradeSchema6(event.target.transaction);
+            break;
           default:
             event.target.transaction.abort();
             callback("Old database version: " + event.oldVersion, null);
             break;
         }
         currentVersion++;
       }
     }
@@ -265,19 +269,16 @@ SmsDatabaseService.prototype = {
    * Create the initial database schema.
    *
    * TODO need to worry about number normalization somewhere...
    * TODO full text search on body???
    */
   createSchema: function createSchema(db) {
     // This objectStore holds the main SMS data.
     let objectStore = db.createObjectStore(STORE_NAME, { keyPath: "id" });
-    objectStore.createIndex("delivery", "delivery", { unique: false });
-    objectStore.createIndex("sender", "sender", { unique: false });
-    objectStore.createIndex("receiver", "receiver", { unique: false });
     objectStore.createIndex("timestamp", "timestamp", { unique: false });
     if (DEBUG) debug("Created object stores and indexes");
   },
 
   /**
    * Upgrade to the corresponding database schema version.
    */
   upgradeSchema: function upgradeSchema(objectStore) {
@@ -361,16 +362,61 @@ SmsDatabaseService.prototype = {
       cursor.continue();
     }
   },
 
   upgradeSchema5: function upgradeSchema5(transaction) {
     // Don't perform any upgrade. See Bug 819560.
   },
 
+  upgradeSchema6: function upgradeSchema6(transaction) {
+    let objectStore = transaction.objectStore(STORE_NAME);
+
+    // Delete "delivery" index.
+    if (objectStore.indexNames.contains("delivery")) {
+      objectStore.deleteIndex("delivery");
+    }
+    // Delete "sender" index.
+    if (objectStore.indexNames.contains("sender")) {
+      objectStore.deleteIndex("sender");
+    }
+    // Delete "receiver" index.
+    if (objectStore.indexNames.contains("receiver")) {
+      objectStore.deleteIndex("receiver");
+    }
+    // Delete "read" index.
+    if (objectStore.indexNames.contains("read")) {
+      objectStore.deleteIndex("read");
+    }
+
+    // Create new "delivery", "number" and "read" indexes.
+    objectStore.createIndex("delivery", "deliveryIndex");
+    objectStore.createIndex("number", "numberIndex", { multiEntry: true });
+    objectStore.createIndex("read", "readIndex");
+
+    // Populate new "deliverIndex", "numberIndex" and "readIndex" attributes.
+    objectStore.openCursor().onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (!cursor) {
+        return;
+      }
+
+      let message = cursor.value;
+      let timestamp = message.timestamp;
+      message.deliveryIndex = [message.delivery, timestamp];
+      message.numberIndex = [
+        [message.sender, timestamp],
+        [message.receiver, timestamp]
+      ];
+      message.readIndex = [message.read, timestamp];
+      cursor.update(message);
+      cursor.continue();
+    }
+  },
+
   /**
    * Helper function to make the intersection of the partial result arrays
    * obtained within createMessageList.
    *
    * @param keys
    *        Object containing the partial result arrays.
    * @param fiter
    *        Object containing the filter search criteria used to retrieved the
@@ -521,24 +567,30 @@ SmsDatabaseService.prototype = {
     let sender = aSender;
     if (sender) {
       let parsedNumber = PhoneNumberUtils.parse(sender);
       sender = (parsedNumber && parsedNumber.internationalNumber)
                ? parsedNumber.internationalNumber
                : sender;
     }
 
-    let message = {delivery:       DELIVERY_RECEIVED,
-                   deliveryStatus: DELIVERY_STATUS_SUCCESS,
-                   sender:         sender,
-                   receiver:       receiver,
-                   body:           aBody,
-                   messageClass:   aMessageClass,
-                   timestamp:      aDate,
-                   read:           FILTER_READ_UNREAD};
+    let message = {
+      deliveryIndex:  [DELIVERY_RECEIVED, aDate],
+      numberIndex:    [[sender, aDate], [receiver, aDate]],
+      readIndex:      [FILTER_READ_UNREAD, aDate],
+
+      delivery:       DELIVERY_RECEIVED,
+      deliveryStatus: DELIVERY_STATUS_SUCCESS,
+      sender:         sender,
+      receiver:       receiver,
+      body:           aBody,
+      messageClass:   aMessageClass,
+      timestamp:      aDate,
+      read:           FILTER_READ_UNREAD
+    };
     return this.saveMessage(message);
   },
 
   saveSendingMessage: function saveSendingMessage(aReceiver, aBody, aDate) {
     let sender = this.mRIL.rilContext.icc ? this.mRIL.rilContext.icc.msisdn : null;
 
     // Workaround an xpconnect issue with undefined string objects.
     // See bug 808220
@@ -556,24 +608,30 @@ SmsDatabaseService.prototype = {
 
     if (sender) {
       let parsedNumber = PhoneNumberUtils.parse(sender.toString());
       sender = (parsedNumber && parsedNumber.internationalNumber)
                ? parsedNumber.internationalNumber
                : sender;
     }
 
-    let message = {delivery:       DELIVERY_SENDING,
-                   deliveryStatus: DELIVERY_STATUS_PENDING,
-                   sender:         sender,
-                   receiver:       receiver,
-                   body:           aBody,
-                   messageClass:   MESSAGE_CLASS_NORMAL,
-                   timestamp:      aDate,
-                   read:           FILTER_READ_READ};
+    let message = {
+      deliveryIndex:  [DELIVERY_SENDING, aDate],
+      numberIndex:    [[sender, aDate], [receiver, aDate]],
+      readIndex:      [FILTER_READ_READ, aDate],
+
+      delivery:       DELIVERY_SENDING,
+      deliveryStatus: DELIVERY_STATUS_PENDING,
+      sender:         sender,
+      receiver:       receiver,
+      body:           aBody,
+      messageClass:   MESSAGE_CLASS_NORMAL,
+      timestamp:      aDate,
+      read:           FILTER_READ_READ
+    };
     return this.saveMessage(message);
   },
 
   setMessageDelivery: function setMessageDelivery(messageId, delivery, deliveryStatus) {
     if (DEBUG) {
       debug("Setting message " + messageId + " delivery to " + delivery
             + ", and deliveryStatus to " + deliveryStatus);
     }
@@ -602,16 +660,17 @@ SmsDatabaseService.prototype = {
             && (message.deliveryStatus == deliveryStatus)) {
           if (DEBUG) {
             debug("The values of attribute delivery and deliveryStatus are the"
                   + " the same with given parameters.");
           }
           return;
         }
         message.delivery = delivery;
+        message.deliveryIndex = [delivery, message.timestamp];
         message.deliveryStatus = deliveryStatus;
         if (DEBUG) {
           debug("Message.delivery set to: " + delivery
                 + ", and Message.deliveryStatus set to: " + deliveryStatus);
         }
         store.put(message);
       };
     });
@@ -715,54 +774,39 @@ SmsDatabaseService.prototype = {
               // This must exist.
               let mostRecentEntry = event.target.result;
 
               if (!message.read) {
                 mostRecentEntry.unreadCount--;
               }
 
               if (mostRecentEntry.id == messageId) {
-                // This sucks, we have to find a new most-recent message.
-                message = null;
-
-                // Check most recent sender.
-                smsStore.index("sender").openCursor(number, "prev").onsuccess = function(event) {
-                  let cursor = event.target.result;
-                  if (cursor) {
-                    message = cursor.value;
-                  }
-                };
-
-                // Check most recent receiver.
-                smsStore.index("receiver").openCursor(number, "prev").onsuccess = function(event) {
+                // Check most recent sender/receiver.
+                let numberRange = IDBKeyRange.bound([number, 0], [number, ""]);
+                let numberRequest = smsStore.index("number")
+                                            .openCursor(numberRange, PREV);
+                numberRequest.onsuccess = function(event) {
                   let cursor = event.target.result;
-                  if (cursor) {
-                    if (!message || cursor.value.timeStamp > message.timestamp) {
-                      message = cursor.value;
-                    }
-                  }
-
-                  // If we found a new message then we need to update the data
-                  // in the most-recent store. Otherwise we can delete it.
-                  if (message) {
-                    mostRecentEntry.id = message.id;
-                    mostRecentEntry.timestamp = message.timestamp;
-                    mostRecentEntry.body = message.body;
-                    if (DEBUG) {
-                      debug("Updating mru entry: " +
-                            JSON.stringify(mostRecentEntry));
-                    }
-                    mruStore.put(mostRecentEntry);
-                  }
-                  else {
+                  if (!cursor) {
                     if (DEBUG) {
                       debug("Deleting mru entry for number '" + number + "'");
                     }
                     mruStore.delete(number);
+                    return;
                   }
+
+                  let nextMsg = cursor.value;
+                  mostRecentEntry.id = nextMsg.id;
+                  mostRecentEntry.timestamp = nextMsg.timestamp;
+                  mostRecentEntry.body = nextMsg.body;
+                  if (DEBUG) {
+                    debug("Updating mru entry: " +
+                          JSON.stringify(mostRecentEntry));
+                  }
+                  mruStore.put(mostRecentEntry);
                 };
               } else if (!message.read) {
                 // Shortcut, just update the unread count.
                 if (DEBUG) {
                   debug("Updating unread count for number '" + number + "': " +
                         (mostRecentEntry.unreadCount + 1) + " -> " +
                         mostRecentEntry.unreadCount);
                 }
@@ -847,48 +891,47 @@ SmsDatabaseService.prototype = {
       timeRequest.onsuccess = function onsuccess(event) {
         successCb(event.target.result, FILTER_TIMESTAMP);
       };
       timeRequest.onerror = errorCb;
 
       // Retrieve the keys from the 'delivery' index that matches the
       // value of filter.delivery.
       if (filter.delivery) {
-        let deliveryKeyRange = IDBKeyRange.only(filter.delivery);
+        let deliveryKeyRange = IDBKeyRange.bound([filter.delivery, 0],
+                                                 [filter.delivery, ""]);
         let deliveryRequest = store.index("delivery")
                                    .openKeyCursor(deliveryKeyRange);
         deliveryRequest.onsuccess = function onsuccess(event) {
           successCb(event.target.result, FILTER_DELIVERY);
         };
         deliveryRequest.onerror = errorCb;
       }
 
       // Retrieve the keys from the 'sender' and 'receiver' indexes that
       // match the values of filter.numbers
       if (filter.numbers) {
         for (let i = 0; i < filter.numbers.length; i++) {
-          let numberKeyRange = IDBKeyRange.only(filter.numbers[i]);
-          let senderRequest = store.index("sender")
+          let numberKeyRange = IDBKeyRange.bound([filter.numbers[i], 0],
+                                                 [filter.numbers[i], ""]);
+          let numberRequest = store.index("number")
                                    .openKeyCursor(numberKeyRange);
-          let receiverRequest = store.index("receiver")
-                                     .openKeyCursor(numberKeyRange);
-          senderRequest.onsuccess = receiverRequest.onsuccess =
-            function onsuccess(event){
-              successCb(event.target.result, FILTER_NUMBERS);
-            };
-          senderRequest.onerror = receiverRequest.onerror = errorCb;
+          numberRequest.onsuccess = function onsuccess(event) {
+            successCb(event.target.result, FILTER_NUMBERS);
+          };
+          numberRequest.onerror = errorCb;
         }
       }
 
       // Retrieve the keys from the 'read' index that matches the value of
       // filter.read
       if (filter.read != undefined) {
         let read = filter.read ? FILTER_READ_READ : FILTER_READ_UNREAD;
         if (DEBUG) debug("filter.read " + read);
-        let readKeyRange = IDBKeyRange.only(read);
+        let readKeyRange = IDBKeyRange.bound([read, 0], [read, ""]);
         let readRequest = store.index("read")
                                .openKeyCursor(readKeyRange);
         readRequest.onsuccess = function onsuccess(event) {
           successCb(event.target.result, FILTER_READ);
         };
         readRequest.onerror = errorCb;
       }
 
@@ -1003,16 +1046,17 @@ SmsDatabaseService.prototype = {
         // If the value to be set is the same as the current message `read`
         // value, we just notify successfully.
         if (message.read == value) {
           if (DEBUG) debug("The value of message.read is already " + value);
           aRequest.notifyMessageMarkedRead(message.read);
           return;
         }
         message.read = value ? FILTER_READ_READ : FILTER_READ_UNREAD;
+        message.readIndex = [message.read, message.timestamp];
         if (DEBUG) debug("Message.read set to: " + value);
         event.target.source.put(message).onsuccess = function onsuccess(event) {
           if (DEBUG) {
             debug("Update successfully completed. Message: " +
                   JSON.stringify(event.target.result));
           }
 
           // Now update the unread count.