Bug 499990 - Locale-aware collation, r=sdwilsh, sr=vladimir
authorDrew Willcoxon <adw@mozilla.com>
Wed, 15 Jul 2009 10:49:05 -0700
changeset 30359 8a0a2a8321ace6980f18076dbe4cd6d06e1d1d99
parent 30358 004e7ca095fb43a1e3ee8c7481ef1e6d24267e45
child 30362 f20a0250d2981929638207c1147fed91dc0b236a
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssdwilsh, vladimir
bugs499990
milestone1.9.2a1pre
Bug 499990 - Locale-aware collation, r=sdwilsh, sr=vladimir
storage/src/Makefile.in
storage/src/SQLCollations.cpp
storage/src/SQLCollations.h
storage/src/mozStorageConnection.cpp
storage/src/mozStorageConnection.h
storage/src/mozStorageSQLFunctions.cpp
storage/src/mozStorageSQLFunctions.h
storage/src/mozStorageService.cpp
storage/src/mozStorageService.h
storage/test/unit/locale_collation.txt
storage/test/unit/test_locale_collation.js
--- a/storage/src/Makefile.in
+++ b/storage/src/Makefile.in
@@ -80,16 +80,17 @@ CPPSRCS = \
   mozStorageRow.cpp \
   mozStorageResultSet.cpp \
   mozStorageError.cpp \
   mozStorageAsyncStatementExecution.cpp \
   mozStorageStatementJSHelper.cpp \
   mozStoragePrivateHelpers.cpp \
   mozStorageBindingParamsArray.cpp \
   mozStorageBindingParams.cpp \
+  SQLCollations.cpp \
   $(NULL)
 
 LOCAL_INCLUDES = \
 	$(SQLITE_CFLAGS)
 
 # This is the default value.  If we ever change it when compiling sqlite, we
 # will need to change it here as well.
 DEFINES += -DSQLITE_MAX_LIKE_PATTERN_LENGTH=50000
new file mode 100644
--- /dev/null
+++ b/storage/src/SQLCollations.cpp
@@ -0,0 +1,269 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Storage code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "SQLCollations.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Local Helper Functions
+
+namespace {
+
+/**
+ * Helper function for the UTF-8 locale collations.
+ *
+ * @param  aService
+ *         The Service that owns the nsICollation used by this collation.
+ * @param  aLen1
+ *         The number of bytes in aStr1.
+ * @param  aStr1
+ *         The string to be compared against aStr2 as provided by SQLite.  It
+ *         must be a non-null-terminated char* buffer.
+ * @param  aLen2
+ *         The number of bytes in aStr2.
+ * @param  aStr2
+ *         The string to be compared against aStr1 as provided by SQLite.  It
+ *         must be a non-null-terminated char* buffer.
+ * @param  aComparisonStrength
+ *         The sorting strength, one of the nsICollation constants.
+ * @return aStr1 - aStr2.  That is, if aStr1 < aStr2, returns a negative number.
+ *         If aStr1 > aStr2, returns a positive number.  If aStr1 == aStr2,
+ *         returns 0.
+ */
+int
+localeCollationHelper8(void *aService,
+                       int aLen1,
+                       const void *aStr1,
+                       int aLen2,
+                       const void *aStr2,
+                       PRInt32 aComparisonStrength)
+{
+  NS_ConvertUTF8toUTF16 str1(static_cast<const char *>(aStr1), aLen1);
+  NS_ConvertUTF8toUTF16 str2(static_cast<const char *>(aStr2), aLen2);
+  Service *serv = static_cast<Service *>(aService);
+  return serv->localeCompareStrings(str1, str2, aComparisonStrength);
+}
+
+/**
+ * Helper function for the UTF-16 locale collations.
+ *
+ * @param  aService
+ *         The Service that owns the nsICollation used by this collation.
+ * @param  aLen1
+ *         The number of bytes (not characters) in aStr1.
+ * @param  aStr1
+ *         The string to be compared against aStr2 as provided by SQLite.  It
+ *         must be a non-null-terminated PRUnichar* buffer.
+ * @param  aLen2
+ *         The number of bytes (not characters) in aStr2.
+ * @param  aStr2
+ *         The string to be compared against aStr1 as provided by SQLite.  It
+ *         must be a non-null-terminated PRUnichar* buffer.
+ * @param  aComparisonStrength
+ *         The sorting strength, one of the nsICollation constants.
+ * @return aStr1 - aStr2.  That is, if aStr1 < aStr2, returns a negative number.
+ *         If aStr1 > aStr2, returns a positive number.  If aStr1 == aStr2,
+ *         returns 0.
+ */
+int
+localeCollationHelper16(void *aService,
+                        int aLen1,
+                        const void *aStr1,
+                        int aLen2,
+                        const void *aStr2,
+                        PRInt32 aComparisonStrength)
+{
+  const PRUnichar *buf1 = static_cast<const PRUnichar *>(aStr1);
+  const PRUnichar *buf2 = static_cast<const PRUnichar *>(aStr2);
+
+  // The second argument to the nsDependentSubstring constructor is exclusive:
+  // It points to the PRUnichar immediately following the last one in the target
+  // substring.  Since aLen1 and aLen2 are in bytes, divide by sizeof(PRUnichar)
+  // so that the pointer arithmetic is correct.
+  nsDependentSubstring str1(buf1, buf1 + (aLen1 / sizeof(PRUnichar)));
+  nsDependentSubstring str2(buf2, buf2 + (aLen2 / sizeof(PRUnichar)));
+  Service *serv = static_cast<Service *>(aService);
+  return serv->localeCompareStrings(str1, str2, aComparisonStrength);
+}
+
+} // anonymous namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//// Exposed Functions
+
+int
+registerCollations(sqlite3 *aDB,
+                   Service *aService)
+{
+  struct Collations {
+    const char *zName;
+    int enc;
+    int(*xCompare)(void*, int, const void*, int, const void*);
+  } collations[] = {
+    {"locale",
+     SQLITE_UTF8,
+     localeCollation8},
+    {"locale_case_sensitive",
+     SQLITE_UTF8,
+     localeCollationCaseSensitive8},
+    {"locale_accent_sensitive",
+     SQLITE_UTF8,
+     localeCollationAccentSensitive8},
+    {"locale_case_accent_sensitive",
+     SQLITE_UTF8,
+     localeCollationCaseAccentSensitive8},
+    {"locale",
+     SQLITE_UTF16,
+     localeCollation16},
+    {"locale_case_sensitive",
+     SQLITE_UTF16,
+     localeCollationCaseSensitive16},
+    {"locale_accent_sensitive",
+     SQLITE_UTF16,
+     localeCollationAccentSensitive16},
+    {"locale_case_accent_sensitive",
+     SQLITE_UTF16,
+     localeCollationCaseAccentSensitive16},
+  };
+
+  int rv = SQLITE_OK;
+  for (size_t i = 0; SQLITE_OK == rv && i < NS_ARRAY_LENGTH(collations); ++i) {
+    struct Collations *p = &collations[i];
+    rv = ::sqlite3_create_collation(aDB, p->zName, p->enc, aService,
+                                    p->xCompare);
+  }
+
+  return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// SQL Collations
+
+int
+localeCollation8(void *aService,
+                 int aLen1,
+                 const void *aStr1,
+                 int aLen2,
+                 const void *aStr2)
+{
+  return localeCollationHelper8(aService, aLen1, aStr1, aLen2, aStr2,
+                                nsICollation::kCollationCaseInSensitive);
+}
+
+int
+localeCollationCaseSensitive8(void *aService,
+                              int aLen1,
+                              const void *aStr1,
+                              int aLen2,
+                              const void *aStr2)
+{
+  return localeCollationHelper8(aService, aLen1, aStr1, aLen2, aStr2,
+                                nsICollation::kCollationAccentInsenstive);
+}
+
+int
+localeCollationAccentSensitive8(void *aService,
+                                int aLen1,
+                                const void *aStr1,
+                                int aLen2,
+                                const void *aStr2)
+{
+  return localeCollationHelper8(aService, aLen1, aStr1, aLen2, aStr2,
+                                nsICollation::kCollationCaseInsensitiveAscii);
+}
+
+int
+localeCollationCaseAccentSensitive8(void *aService,
+                                    int aLen1,
+                                    const void *aStr1,
+                                    int aLen2,
+                                    const void *aStr2)
+{
+  return localeCollationHelper8(aService, aLen1, aStr1, aLen2, aStr2,
+                                nsICollation::kCollationCaseSensitive);
+}
+
+int
+localeCollation16(void *aService,
+                  int aLen1,
+                  const void *aStr1,
+                  int aLen2,
+                  const void *aStr2)
+{
+  return localeCollationHelper16(aService, aLen1, aStr1, aLen2, aStr2,
+                                 nsICollation::kCollationCaseInSensitive);
+}
+
+int
+localeCollationCaseSensitive16(void *aService,
+                               int aLen1,
+                               const void *aStr1,
+                               int aLen2,
+                               const void *aStr2)
+{
+  return localeCollationHelper16(aService, aLen1, aStr1, aLen2, aStr2,
+                                 nsICollation::kCollationAccentInsenstive);
+}
+
+int
+localeCollationAccentSensitive16(void *aService,
+                                 int aLen1,
+                                 const void *aStr1,
+                                 int aLen2,
+                                 const void *aStr2)
+{
+  return localeCollationHelper16(aService, aLen1, aStr1, aLen2, aStr2,
+                                 nsICollation::kCollationCaseInsensitiveAscii);
+}
+
+int
+localeCollationCaseAccentSensitive16(void *aService,
+                                     int aLen1,
+                                     const void *aStr1,
+                                     int aLen2,
+                                     const void *aStr2)
+{
+  return localeCollationHelper16(aService, aLen1, aStr1, aLen2, aStr2,
+                                 nsICollation::kCollationCaseSensitive);
+}
+
+} // namespace storage
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/storage/src/SQLCollations.h
@@ -0,0 +1,282 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Storage code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef _mozilla_storage_SQLCollations_h_
+#define _mozilla_storage_SQLCollations_h_
+
+#include "mozStorageService.h"
+#include "nscore.h"
+#include "nsString.h"
+
+#include "sqlite3.h"
+
+namespace mozilla {
+namespace storage {
+
+/**
+ * Registers the collating sequences declared here with the specified
+ * database and Service.
+ *
+ * @param  aDB
+ *         The database we'll be registering the collations with.
+ * @param  aService
+ *         The Service that owns the nsICollation used by our collations.
+ * @return the SQLite status code indicating success or failure.
+ */
+NS_HIDDEN_(int) registerCollations(sqlite3 *aDB, Service *aService);
+
+////////////////////////////////////////////////////////////////////////////////
+//// Predefined Functions
+
+/**
+ * Custom UTF-8 collating sequence that respects the application's locale.
+ * Comparison is case- and accent-insensitive.  This is called by SQLite.
+ *
+ * @param  aService
+ *         The Service that owns the nsICollation used by this collation.
+ * @param  aLen1
+ *         The number of bytes in aStr1.
+ * @param  aStr1
+ *         The string to be compared against aStr2.  It will be passed in by
+ *         SQLite as a non-null-terminated char* buffer.
+ * @param  aLen2
+ *         The number of bytes in aStr2.
+ * @param  aStr2
+ *         The string to be compared against aStr1.  It will be passed in by
+ *         SQLite as a non-null-terminated char* buffer.
+ * @return aStr1 - aStr2.  That is, if aStr1 < aStr2, returns a negative number.
+ *         If aStr1 > aStr2, returns a positive number.  If aStr1 == aStr2,
+ *         returns 0.
+ */
+NS_HIDDEN_(int) localeCollation8(void *aService,
+                                 int aLen1,
+                                 const void *aStr1,
+                                 int aLen2,
+                                 const void *aStr2);
+
+/**
+ * Custom UTF-8 collating sequence that respects the application's locale.
+ * Comparison is case-sensitive and accent-insensitive.  This is called by
+ * SQLite.
+ *
+ * @param  aService
+ *         The Service that owns the nsICollation used by this collation.
+ * @param  aLen1
+ *         The number of bytes in aStr1.
+ * @param  aStr1
+ *         The string to be compared against aStr2.  It will be passed in by
+ *         SQLite as a non-null-terminated char* buffer.
+ * @param  aLen2
+ *         The number of bytes in aStr2.
+ * @param  aStr2
+ *         The string to be compared against aStr1.  It will be passed in by
+ *         SQLite as a non-null-terminated char* buffer.
+ * @return aStr1 - aStr2.  That is, if aStr1 < aStr2, returns a negative number.
+ *         If aStr1 > aStr2, returns a positive number.  If aStr1 == aStr2,
+ *         returns 0.
+ */
+NS_HIDDEN_(int) localeCollationCaseSensitive8(void *aService,
+                                              int aLen1,
+                                              const void *aStr1,
+                                              int aLen2,
+                                              const void *aStr2);
+
+/**
+ * Custom UTF-8 collating sequence that respects the application's locale.
+ * Comparison is case-insensitive and accent-sensitive.  This is called by
+ * SQLite.
+ *
+ * @param  aService
+ *         The Service that owns the nsICollation used by this collation.
+ * @param  aLen1
+ *         The number of bytes in aStr1.
+ * @param  aStr1
+ *         The string to be compared against aStr2.  It will be passed in by
+ *         SQLite as a non-null-terminated char* buffer.
+ * @param  aLen2
+ *         The number of bytes in aStr2.
+ * @param  aStr2
+ *         The string to be compared against aStr1.  It will be passed in by
+ *         SQLite as a non-null-terminated char* buffer.
+ * @return aStr1 - aStr2.  That is, if aStr1 < aStr2, returns a negative number.
+ *         If aStr1 > aStr2, returns a positive number.  If aStr1 == aStr2,
+ *         returns 0.
+ */
+NS_HIDDEN_(int) localeCollationAccentSensitive8(void *aService,
+                                                int aLen1,
+                                                const void *aStr1,
+                                                int aLen2,
+                                                const void *aStr2);
+
+/**
+ * Custom UTF-8 collating sequence that respects the application's locale.
+ * Comparison is case- and accent-sensitive.  This is called by SQLite.
+ *
+ * @param  aService
+ *         The Service that owns the nsICollation used by this collation.
+ * @param  aLen1
+ *         The number of bytes in aStr1.
+ * @param  aStr1
+ *         The string to be compared against aStr2.  It will be passed in by
+ *         SQLite as a non-null-terminated char* buffer.
+ * @param  aLen2
+ *         The number of bytes in aStr2.
+ * @param  aStr2
+ *         The string to be compared against aStr1.  It will be passed in by
+ *         SQLite as a non-null-terminated char* buffer.
+ * @return aStr1 - aStr2.  That is, if aStr1 < aStr2, returns a negative number.
+ *         If aStr1 > aStr2, returns a positive number.  If aStr1 == aStr2,
+ *         returns 0.
+ */
+NS_HIDDEN_(int) localeCollationCaseAccentSensitive8(void *aService,
+                                                    int aLen1,
+                                                    const void *aStr1,
+                                                    int aLen2,
+                                                    const void *aStr2);
+
+/**
+ * Custom UTF-16 collating sequence that respects the application's locale.
+ * Comparison is case- and accent-insensitive.  This is called by SQLite.
+ *
+ * @param  aService
+ *         The Service that owns the nsICollation used by this collation.
+ * @param  aLen1
+ *         The number of bytes (not characters) in aStr1.
+ * @param  aStr1
+ *         The string to be compared against aStr2.  It will be passed in by
+ *         SQLite as a non-null-terminated PRUnichar* buffer.
+ * @param  aLen2
+ *         The number of bytes (not characters) in aStr2.
+ * @param  aStr2
+ *         The string to be compared against aStr1.  It will be passed in by
+ *         SQLite as a non-null-terminated PRUnichar* buffer.
+ * @return aStr1 - aStr2.  That is, if aStr1 < aStr2, returns a negative number.
+ *         If aStr1 > aStr2, returns a positive number.  If aStr1 == aStr2,
+ *         returns 0.
+ */
+NS_HIDDEN_(int) localeCollation16(void *aService,
+                                  int aLen1,
+                                  const void *aStr1,
+                                  int aLen2,
+                                  const void *aStr2);
+
+/**
+ * Custom UTF-16 collating sequence that respects the application's locale.
+ * Comparison is case-sensitive and accent-insensitive.  This is called by
+ * SQLite.
+ *
+ * @param  aService
+ *         The Service that owns the nsICollation used by this collation.
+ * @param  aLen1
+ *         The number of bytes (not characters) in aStr1.
+ * @param  aStr1
+ *         The string to be compared against aStr2.  It will be passed in by
+ *         SQLite as a non-null-terminated PRUnichar* buffer.
+ * @param  aLen2
+ *         The number of bytes (not characters) in aStr2.
+ * @param  aStr2
+ *         The string to be compared against aStr1.  It will be passed in by
+ *         SQLite as a non-null-terminated PRUnichar* buffer.
+ * @return aStr1 - aStr2.  That is, if aStr1 < aStr2, returns a negative number.
+ *         If aStr1 > aStr2, returns a positive number.  If aStr1 == aStr2,
+ *         returns 0.
+ */
+NS_HIDDEN_(int) localeCollationCaseSensitive16(void *aService,
+                                               int aLen1,
+                                               const void *aStr1,
+                                               int aLen2,
+                                               const void *aStr2);
+
+/**
+ * Custom UTF-16 collating sequence that respects the application's locale.
+ * Comparison is case-insensitive and accent-sensitive.  This is called by
+ * SQLite.
+ *
+ * @param  aService
+ *         The Service that owns the nsICollation used by this collation.
+ * @param  aLen1
+ *         The number of bytes (not characters) in aStr1.
+ * @param  aStr1
+ *         The string to be compared against aStr2.  It will be passed in by
+ *         SQLite as a non-null-terminated PRUnichar* buffer.
+ * @param  aLen2
+ *         The number of bytes (not characters) in aStr2.
+ * @param  aStr2
+ *         The string to be compared against aStr1.  It will be passed in by
+ *         SQLite as a non-null-terminated PRUnichar* buffer.
+ * @return aStr1 - aStr2.  That is, if aStr1 < aStr2, returns a negative number.
+ *         If aStr1 > aStr2, returns a positive number.  If aStr1 == aStr2,
+ *         returns 0.
+ */
+NS_HIDDEN_(int) localeCollationAccentSensitive16(void *aService,
+                                                 int aLen1,
+                                                 const void *aStr1,
+                                                 int aLen2,
+                                                 const void *aStr2);
+
+/**
+ * Custom UTF-16 collating sequence that respects the application's locale.
+ * Comparison is case- and accent-sensitive.  This is called by SQLite.
+ *
+ * @param  aService
+ *         The Service that owns the nsICollation used by this collation.
+ * @param  aLen1
+ *         The number of bytes (not characters) in aStr1.
+ * @param  aStr1
+ *         The string to be compared against aStr2.  It will be passed in by
+ *         SQLite as a non-null-terminated PRUnichar* buffer.
+ * @param  aLen2
+ *         The number of bytes (not characters) in aStr2.
+ * @param  aStr2
+ *         The string to be compared against aStr1.  It will be passed in by
+ *         SQLite as a non-null-terminated PRUnichar* buffer.
+ * @return aStr1 - aStr2.  That is, if aStr1 < aStr2, returns a negative number.
+ *         If aStr1 > aStr2, returns a positive number.  If aStr1 == aStr2,
+ *         returns 0.
+ */
+NS_HIDDEN_(int) localeCollationCaseAccentSensitive16(void *aService,
+                                                     int aLen1,
+                                                     const void *aStr1,
+                                                     int aLen2,
+                                                     const void *aStr2);
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // _mozilla_storage_SQLCollations_h_
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -20,16 +20,17 @@
  * Portions created by the Initial Developer are Copyright (C) 2004
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
  *   Brett Wilson <brettw@gmail.com>
  *   Shawn Wilsher <me@shawnwilsher.com>
  *   Lev Serebryakov <lev@serebryakov.spb.ru>
+ *   Drew Willcoxon <adw@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -58,16 +59,17 @@
 #include "mozStorageAsyncStatementExecution.h"
 #include "mozStorageSQLFunctions.h"
 #include "mozStorageConnection.h"
 #include "mozStorageService.h"
 #include "mozStorageStatement.h"
 #include "mozStorageArgValueArray.h"
 #include "mozStoragePrivateHelpers.h"
 #include "mozStorageStatementData.h"
+#include "SQLCollations.h"
 
 #include "prlog.h"
 #include "prprf.h"
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gStorageLog = nsnull;
 #endif
 
@@ -237,17 +239,17 @@ aggregateFunctionFinalHelper(sqlite3_con
 }
 
 
 } // anonymous namespace
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Connection
 
-Connection::Connection(mozIStorageService *aService)
+Connection::Connection(Service *aService)
 : sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex")
 , mDBConn(nsnull)
 , mAsyncExecutionMutex(nsAutoLock::NewLock("AsyncExecutionMutex"))
 , mAsyncExecutionThreadShuttingDown(PR_FALSE)
 , mTransactionMutex(nsAutoLock::NewLock("TransactionMutex"))
 , mTransactionInProgress(PR_FALSE)
 , mFunctionsMutex(nsAutoLock::NewLock("FunctionsMutex"))
 , mProgressHandlerMutex(nsAutoLock::NewLock("ProgressHandlerMutex"))
@@ -333,17 +335,27 @@ Connection::initialize(nsIFile *aDatabas
   nsCAutoString leafName(":memory");
   if (aDatabaseFile)
     (void)aDatabaseFile->GetNativeLeafName(leafName);
   PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Opening connection to '%s' (%p)",
                                       leafName.get(), this));
 #endif
 
   // Register our built-in SQL functions.
-  if (registerFunctions(mDBConn) != SQLITE_OK) {
+  srv = registerFunctions(mDBConn);
+  if (srv != SQLITE_OK) {
+    ::sqlite3_close(mDBConn);
+    mDBConn = nsnull;
+    return convertResultCode(srv);
+  }
+
+  // Register our built-in SQL collating sequences.
+  srv = registerCollations(mDBConn, mStorageService);
+  if (srv != SQLITE_OK) {
+    ::sqlite3_close(mDBConn);
     mDBConn = nsnull;
     return convertResultCode(srv);
   }
 
   // Execute a dummy statement to force the db open, and to verify if it is
   // valid or not.
   sqlite3_stmt *stmt;
   srv = ::sqlite3_prepare_v2(mDBConn, "SELECT * FROM sqlite_master", -1, &stmt,
--- a/storage/src/mozStorageConnection.h
+++ b/storage/src/mozStorageConnection.h
@@ -36,44 +36,45 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _MOZSTORAGECONNECTION_H_
 #define _MOZSTORAGECONNECTION_H_
 
+#include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "mozilla/Mutex.h"
 
 #include "nsString.h"
 #include "nsInterfaceHashtable.h"
 #include "mozIStorageProgressHandler.h"
 #include "mozIStorageConnection.h"
+#include "mozStorageService.h"
 
 #include "nsIMutableArray.h"
 
 #include "sqlite3.h"
 
 struct PRLock;
 class nsIFile;
 class nsIEventTarget;
 class nsIThread;
-class mozIStorageService;
 
 namespace mozilla {
 namespace storage {
 
 class Connection : public mozIStorageConnection
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_MOZISTORAGECONNECTION
 
-  Connection(mozIStorageService* aService);
+  Connection(Service *aService);
 
   /**
    * Creates the connection to the database.
    *
    * @param aDatabaseFile
    *        The nsIFile of the location of the database to open, or create if it
    *        does not exist.  Passing in nsnull here creates an in-memory
    *        database.
@@ -161,17 +162,18 @@ private:
   PRBool mTransactionInProgress;
 
   PRLock *mFunctionsMutex;
   nsInterfaceHashtable<nsCStringHashKey, nsISupports> mFunctions;
 
   PRLock *mProgressHandlerMutex;
   nsCOMPtr<mozIStorageProgressHandler> mProgressHandler;
 
-  // This isn't accessed but is used to make sure that the connections do
-  // not outlive the service.
-  nsCOMPtr<mozIStorageService> mStorageService;
+  // This is here for two reasons: 1) It's used to make sure that the
+  // connections do not outlive the service.  2) Our custom collating functions
+  // call its localeCompareStrings() method.
+  nsRefPtr<Service> mStorageService;
 };
 
 } // namespace storage
 } // namespace mozilla
 
 #endif /* _MOZSTORAGECONNECTION_H_ */
--- a/storage/src/mozStorageSQLFunctions.cpp
+++ b/storage/src/mozStorageSQLFunctions.cpp
@@ -171,17 +171,17 @@ registerFunctions(sqlite3 *aDB)
 
     {"like",  2, SQLITE_UTF16, 0,        likeFunction},
     {"like",  2, SQLITE_UTF8,  0,        likeFunction},
     {"like",  3, SQLITE_UTF16, 0,        likeFunction},
     {"like",  3, SQLITE_UTF8,  0,        likeFunction},
   };
 
   int rv = SQLITE_OK;
-  for (unsigned i = 0; SQLITE_OK == rv && i < NS_ARRAY_LENGTH(functions); ++i) {
+  for (size_t i = 0; SQLITE_OK == rv && i < NS_ARRAY_LENGTH(functions); ++i) {
     struct Functions *p = &functions[i];
     rv = ::sqlite3_create_function(aDB, p->zName, p->nArg, p->enc, p->pContext,
                                    p->xFunc, NULL, NULL);
   }
 
   return rv;
 }
 
--- a/storage/src/mozStorageSQLFunctions.h
+++ b/storage/src/mozStorageSQLFunctions.h
@@ -33,17 +33,17 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _mozStorageSQLFunctions_h_
-#define _mozStorageUnicodeFunctions_h_
+#define _mozStorageSQLFunctions_h_
 
 #include "sqlite3.h"
 #include "nscore.h"
 
 namespace mozilla {
 namespace storage {
 
 /**
--- a/storage/src/mozStorageService.cpp
+++ b/storage/src/mozStorageService.cpp
@@ -19,16 +19,17 @@
  *  Oracle Corporation
  * Portions created by the Initial Developer are Copyright (C) 2004
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
  *   Brett Wilson <brettw@gmail.com>
  *   Shawn Wilsher <me@shawnwilsher.com>
+ *   Drew Willcoxon <adw@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -37,20 +38,22 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "mozStorageService.h"
 #include "mozStorageConnection.h"
 #include "prinit.h"
-#include "nsAutoLock.h"
 #include "nsAutoPtr.h"
+#include "nsCollationCID.h"
 #include "nsEmbedCID.h"
 #include "mozStoragePrivateHelpers.h"
+#include "nsILocale.h"
+#include "nsILocaleService.h"
 #include "nsIXPConnect.h"
 #include "nsIObserverService.h"
 
 #include "sqlite3.h"
 
 #include "nsIPromptService.h"
 
 namespace mozilla {
@@ -113,56 +116,57 @@ Service::getXPConnect()
   // not cache the service after this point.
   nsCOMPtr<nsIXPConnect> xpc(sXPConnect);
   if (!xpc)
     xpc = do_GetService(nsIXPConnect::GetCID());
   NS_ASSERTION(xpc, "Could not get XPConnect!");
   return xpc.forget();
 }
 
+Service::Service()
+: mMutex("Service::mMutex")
+{
+}
+
 Service::~Service()
 {
   // Shutdown the sqlite3 API.  Warn if shutdown did not turn out okay, but
   // there is nothing actionable we can do in that case.
   int rc = ::sqlite3_shutdown();
   if (rc != SQLITE_OK)
     NS_WARNING("sqlite3 did not shutdown cleanly.");
 
   gService = nsnull;
-  ::PR_DestroyLock(mLock);
 }
 
 void
 Service::shutdown()
 {
   NS_IF_RELEASE(sXPConnect);
 }
 
 nsresult
 Service::initialize()
 {
-  mLock = ::PR_NewLock();
-  NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY);
-
   // Disable memory allocation statistic collection, improving performance.
   // This must be done prior to a call to sqlite3_initialize to have any
   // effect.
   int rc = ::sqlite3_config(SQLITE_CONFIG_MEMSTATUS, 0);
   if (rc != SQLITE_OK)
     return convertResultCode(rc);
 
   // Explicitly initialize sqlite3.  Although this is implicitly called by
   // various sqlite3 functions (and the sqlite3_open calls in our case),
   // the documentation suggests calling this directly.  So we do.
   rc = ::sqlite3_initialize();
   if (rc != SQLITE_OK)
     return convertResultCode(rc);
 
   // This makes multiple connections to the same database share the same pager
-  // cache.  We do not need to lock here with mLock because this function is
+  // cache.  We do not need to lock here with mMutex because this function is
   // only ever called from Service::GetSingleton, which will only
   // call this function once, and will not return until this function returns.
   // (It does not matter where this is called relative to sqlite3_initialize.)
   rc = ::sqlite3_enable_shared_cache(1);
   if (rc != SQLITE_OK)
     return convertResultCode(rc);
 
   nsCOMPtr<nsIObserverService> os =
@@ -172,16 +176,79 @@ Service::initialize()
   nsresult rv = os->AddObserver(this, "xpcom-shutdown", PR_FALSE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // We cache XPConnect for our language helpers.
   (void)CallGetService(nsIXPConnect::GetCID(), &sXPConnect);
   return NS_OK;
 }
 
+int
+Service::localeCompareStrings(const nsAString &aStr1,
+                              const nsAString &aStr2,
+                              PRInt32 aComparisonStrength)
+{
+  // The implementation of nsICollation.CompareString() is platform-dependent.
+  // On Linux it's not thread-safe.  It may not be on Windows and OS X either,
+  // but it's more difficult to tell.  We therefore synchronize this method.
+  MutexAutoLock mutex(mMutex);
+
+  nsICollation *coll = getLocaleCollation();
+  if (!coll) {
+    NS_ERROR("Storage service has no collation");
+    return 0;
+  }
+
+  PRInt32 res;
+  nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res);
+  if (NS_FAILED(rv)) {
+    NS_ERROR("Collation compare string failed");
+    return 0;
+  }
+
+  return res;
+}
+
+nsICollation *
+Service::getLocaleCollation()
+{
+  mMutex.AssertCurrentThreadOwns();
+
+  if (mLocaleCollation)
+    return mLocaleCollation;
+
+  nsCOMPtr<nsILocaleService> svc(do_GetService(NS_LOCALESERVICE_CONTRACTID));
+  if (!svc) {
+    NS_WARNING("Could not get locale service");
+    return nsnull;
+  }
+
+  nsCOMPtr<nsILocale> appLocale;
+  nsresult rv = svc->GetApplicationLocale(getter_AddRefs(appLocale));
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Could not get application locale");
+    return nsnull;
+  }
+
+  nsCOMPtr<nsICollationFactory> collFact =
+    do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
+  if (!collFact) {
+    NS_WARNING("Could not create collation factory");
+    return nsnull;
+  }
+
+  rv = collFact->CreateCollation(appLocale, getter_AddRefs(mLocaleCollation));
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Could not create collation");
+    return nsnull;
+  }
+
+  return mLocaleCollation;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 //// mozIStorageService
 
 #ifndef NS_APP_STORAGE_50_FILE
 #define NS_APP_STORAGE_50_FILE "UStor"
 #endif
 
 NS_IMETHODIMP
@@ -223,17 +290,17 @@ Service::OpenSpecialDatabase(const char 
 NS_IMETHODIMP
 Service::OpenDatabase(nsIFile *aDatabaseFile,
                       mozIStorageConnection **_connection)
 {
   nsRefPtr<Connection> msc = new Connection(this);
   NS_ENSURE_TRUE(msc, NS_ERROR_OUT_OF_MEMORY);
 
   {
-    nsAutoLock lock(mLock);
+    MutexAutoLock mutex(mMutex);
     nsresult rv = msc->initialize(aDatabaseFile);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   NS_ADDREF(*_connection = msc);
   return NS_OK;
 }
 
@@ -247,17 +314,17 @@ Service::OpenUnsharedDatabase(nsIFile *a
   // Initialize the connection, temporarily turning off shared caches so the
   // new connection gets its own cache.  Database connections are assigned
   // caches when they are opened, and they retain those caches for their
   // lifetimes, unaffected by changes to the shared caches setting, so we can
   // disable shared caches temporarily while we initialize the new connection
   // without affecting the caches currently in use by other connections.
   nsresult rv;
   {
-    nsAutoLock lock(mLock);
+    MutexAutoLock mutex(mMutex);
     int rc = ::sqlite3_enable_shared_cache(0);
     if (rc != SQLITE_OK)
       return convertResultCode(rc);
 
     rv = msc->initialize(aDatabaseFile);
 
     rc = ::sqlite3_enable_shared_cache(1);
     if (rc != SQLITE_OK)
--- a/storage/src/mozStorageService.h
+++ b/storage/src/mozStorageService.h
@@ -19,16 +19,17 @@
  *  Oracle Corporation
  * Portions created by the Initial Developer are Copyright (C) 2004
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
  *   Brett Wilson <brettw@gmail.com>
  *   Shawn Wilsher <me@shawnwilsher.com>
+ *   Drew Willcoxon <adw@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -38,19 +39,20 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _MOZSTORAGESERVICE_H_
 #define _MOZSTORAGESERVICE_H_
 
 #include "nsCOMPtr.h"
+#include "nsICollation.h"
 #include "nsIFile.h"
 #include "nsIObserver.h"
-#include "prlock.h"
+#include "mozilla/Mutex.h"
 
 #include "mozIStorageService.h"
 
 class nsIXPConnect;
 
 namespace mozilla {
 namespace storage {
 
@@ -58,42 +60,79 @@ class Service : public mozIStorageServic
               , public nsIObserver
 {
 public:
   /**
    * Initializes the service.  This must be called before any other function!
    */
   nsresult initialize();
 
+  /**
+   * Compares two strings using the Service's locale-aware collation.
+   *
+   * @param  aStr1
+   *         The string to be compared against aStr2.
+   * @param  aStr2
+   *         The string to be compared against aStr1.
+   * @param  aComparisonStrength
+   *         The sorting strength, one of the nsICollation constants.
+   * @return aStr1 - aStr2.  That is, if aStr1 < aStr2, returns a negative
+   *         number.  If aStr1 > aStr2, returns a positive number.  If
+   *         aStr1 == aStr2, returns 0.
+   */
+  int localeCompareStrings(const nsAString &aStr1,
+                           const nsAString &aStr2,
+                           PRInt32 aComparisonStrength);
+
   static Service *getSingleton();
 
   NS_DECL_ISUPPORTS
   NS_DECL_MOZISTORAGESERVICE
   NS_DECL_NSIOBSERVER
 
   /**
    * Obtains an already AddRefed pointer to XPConnect.  This is used by
    * language helpers.
    */
   static already_AddRefed<nsIXPConnect> getXPConnect();
 
 private:
+  Service();
   virtual ~Service();
 
   /**
-   * Used for locking around calls when initializing connections so that we
-   * can ensure that the state of sqlite3_enable_shared_cache is sane.
+   * Used for 1) locking around calls when initializing connections so that we
+   * can ensure that the state of sqlite3_enable_shared_cache is sane and 2)
+   * synchronizing access to mLocaleCollation.
    */
-  PRLock *mLock;
+  Mutex mMutex;
 
   /**
    * Shuts down the storage service, freeing all of the acquired resources.
    */
   void shutdown();
 
+  /**
+   * Lazily creates and returns a collation created from the application's
+   * locale that all statements of all Connections of this Service may use.
+   * Since the collation's lifetime is that of the Service and no statement may
+   * execute outside the lifetime of the Service, this method returns a raw
+   * pointer.
+   */
+  nsICollation *getLocaleCollation();
+
+  /**
+   * Lazily created collation that all statements of all Connections of this
+   * Service may use.  The collation is created from the application's locale.
+   *
+   * @note Collation implementations are platform-dependent and in general not
+   *       thread-safe.  Access to this collation should be synchronized.
+   */
+  nsCOMPtr<nsICollation> mLocaleCollation;
+
   nsCOMPtr<nsIFile> mProfileStorageFile;
 
   static Service *gService;
 
   static nsIXPConnect *sXPConnect;
 };
 
 } // namespace storage
new file mode 100644
--- /dev/null
+++ b/storage/test/unit/locale_collation.txt
@@ -0,0 +1,174 @@
+ 
+!
+"
+#
+$
+%
+&
+'
+(
+)
+*
++
+,
+-
+.
+/
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+:
+;
+<
+=
+>
+?
+@
+A
+B
+C
+D
+E
+F
+G
+H
+I
+J
+K
+L
+M
+N
+O
+P
+Q
+R
+S
+T
+U
+V
+W
+X
+Y
+Z
+[
+\
+]
+^
+_
+`
+a
+b
+c
+d
+e
+f
+g
+h
+i
+j
+k
+l
+m
+n
+o
+p
+q
+r
+s
+t
+u
+v
+w
+x
+y
+z
+{
+|
+}
+~
+ludwig van beethoven
+Ludwig van Beethoven
+Ludwig van beethoven
+Jane
+jane
+JANE
+jAne
+jaNe
+janE
+JAne
+JaNe
+JanE
+JANe
+JaNE
+JAnE
+jANE
+Umberto Eco
+Umberto eco
+umberto eco
+umberto Eco
+UMBERTO ECO
+ace
+bash
+*ace
+!ace
+%ace
+~ace
+#ace
+cork
+denizen
+[denizen]
+(denizen)
+{denizen}
+/denizen/
+#denizen#
+$denizen$
+@denizen
+elf
+full
+gnome
+gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Japanese
+gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Javanese
+hint
+Internationalization
+Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization
+Zinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizatio
+n
+Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalization
+ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTioninternationalizationinternationalizationinternationalizationinternationalization
+ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTion
+jostle
+kin
+Laymen
+lumens
+Lumens
+motleycrew
+motley crew
+niven's creative talents
+nivens creative talents
+opie
+posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rockies
+posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rokkies
+posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the rockies
+quilt's
+quilts
+quilt
+Rondo
+street
+tamale oxidization and iodization in rapid progress
+tamale oxidization and iodization in rapid Progress
+until
+vera
+Wobble
+Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoneme
+Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoname
+yearn
+zodiac
+a
+
new file mode 100644
--- /dev/null
+++ b/storage/test/unit/test_locale_collation.js
@@ -0,0 +1,337 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Storage Test Code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Drew Willcoxon <adw@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * Bug 499990 - Locale-aware collation
+ *
+ * Tests our custom, locale-aware collating sequences.
+ */
+
+// The name of the file containing the strings we'll sort during this test.
+// The file's data is taken from intl/locale/tests/sort/us-ascii_base.txt and
+// and intl/locale/tests/sort/us-ascii_sort.txt.
+const DATA_BASENAME = "locale_collation.txt";
+
+// The test data from DATA_BASENAME is read into this array.
+var gStrings;
+
+// A collation created from the application's locale.  Used by localeCompare().
+var gLocaleCollation;
+
+// A connection to our in-memory UTF-16-encoded database.
+var gUtf16Conn;
+
+///////////////////////////////////////////////////////////////////////////////
+//// Helper Functions
+
+/**
+ * Since we create a UTF-16 database we have to clean it up, in addition to
+ * the normal cleanup of Storage tests.
+ */
+function cleanupLocaleTests()
+{
+  print("-- Cleaning up test_locale_collation.js suite.");
+  gUtf16Conn.close();
+  cleanup();
+}
+
+/**
+ * Creates a test database similar to the default one created in
+ * head_storage.js, except that this one uses UTF-16 encoding.
+ *
+ * @return A connection to the database.
+ */
+function createUtf16Database()
+{
+  print("Creating the in-memory UTF-16-encoded database.");
+  let conn = getService().openSpecialDatabase("memory");
+  conn.executeSimpleSQL("PRAGMA encoding = 'UTF-16'");
+
+  print("Make sure the encoding was set correctly and is now UTF-16.");
+  let stmt = conn.createStatement("PRAGMA encoding");
+  do_check_true(stmt.executeStep());
+  let enc = stmt.getString(0);
+  stmt.finalize();
+
+  // The value returned will actually be UTF-16le or UTF-16be.
+  do_check_true(enc === "UTF-16le" || enc === "UTF-16be");
+
+  return conn;
+}
+
+/**
+ * Compares aActual to aExpected, ensuring that the numbers and orderings of
+ * the two arrays' elements are the same.
+ *
+ * @param aActual
+ *        An array of strings retrieved from the database.
+ * @param aExpected
+ *        An array of strings to which aActual should be equivalent.
+ */
+function ensureResultsAreCorrect(aActual, aExpected)
+{
+  print("Actual results:   " + aActual);
+  print("Expected results: " + aExpected);
+
+  do_check_eq(aActual.length, aExpected.length);
+  for (let i = 0; i < aActual.length; i++)
+    do_check_eq(aActual[i], aExpected[i]);
+}
+
+/**
+ * Synchronously SELECTs all rows from the test table of the given database
+ * using the given collation.
+ *
+ * @param  aCollation
+ *         The name of one of our custom locale collations.  The rows are
+ *         ordered by this collation.
+ * @param  aConn
+ *         A connection to either the UTF-8 database or the UTF-16 database.
+ * @return The resulting strings in an array.
+ */
+function getResults(aCollation, aConn)
+{
+  let results = [];
+  let stmt = aConn.createStatement("SELECT t FROM test " +
+                                   "ORDER BY t COLLATE " + aCollation + " ASC");
+  while (stmt.executeStep())
+    results.push(stmt.row.t);
+  stmt.finalize();
+  return results;
+}
+
+/**
+ * Inserts strings into our test table of the given database in the order given.
+ *
+ * @param aStrings
+ *        An array of strings.
+ * @param aConn
+ *        A connection to either the UTF-8 database or the UTF-16 database.
+ */
+function initTableWithStrings(aStrings, aConn)
+{
+  print("Initializing test table.");
+
+  aConn.executeSimpleSQL("DROP TABLE IF EXISTS test");
+  aConn.createTable("test", "t TEXT");
+  let stmt = aConn.createStatement("INSERT INTO test (t) VALUES (:t)");
+  aStrings.forEach(function (str) {
+    stmt.params.t = str;
+    stmt.execute();
+    stmt.reset();
+  });
+  stmt.finalize();
+}
+
+/**
+ * Returns a sorting function suitable for passing to Array.prototype.sort().
+ * The returned function uses the application's locale to compare strings.
+ *
+ * @param  aCollation
+ *         The name of one of our custom locale collations.  The sorting
+ *         strength is computed from this value.
+ * @return A function to use as a sorting callback.
+ */
+function localeCompare(aCollation)
+{
+  var strength;
+
+  switch (aCollation) {
+  case "locale":
+    strength = Ci.nsICollation.kCollationCaseInSensitive;
+    break;
+  case "locale_case_sensitive":
+    strength = Ci.nsICollation.kCollationAccentInsenstive;
+    break;
+  case "locale_accent_sensitive":
+    strength = Ci.nsICollation.kCollationCaseInsensitiveAscii;
+    break;
+  case "locale_case_accent_sensitive":
+    strength = Ci.nsICollation.kCollationCaseSensitive;
+    break;
+  default:
+    do_throw("Error in test: unknown collation '" + aCollation + "'");
+    break;
+  }
+  return function (aStr1, aStr2)
+         gLocaleCollation.compareString(strength, aStr1, aStr2);
+}
+
+/**
+ * Reads in the test data from the file DATA_BASENAME and returns it as an array
+ * of strings.
+ *
+ * @return The test data as an array of strings.
+ */
+function readTestData()
+{
+  print("Reading in test data.");
+
+  let file = do_get_file(DATA_BASENAME);
+
+  let istream = Cc["@mozilla.org/network/file-input-stream;1"].
+                createInstance(Ci.nsIFileInputStream);
+  istream.init(file, -1, -1, 0);
+  istream.QueryInterface(Components.interfaces.nsILineInputStream);
+
+  let line = {};
+  let lines = [];
+  while (istream.readLine(line))
+    lines.push(line.value); 
+  istream.close();
+
+  return lines;
+}
+
+/**
+ * Gets the results from the given database using the given collation and
+ * ensures that they match gStrings sorted by the same collation.
+ *
+ * @param aCollation
+ *        The name of one of our custom locale collations.  The rows from the
+ *        database and the expected results are ordered by this collation.
+ * @param aConn
+ *        A connection to either the UTF-8 database or the UTF-16 database.
+ */
+function runTest(aCollation, aConn)
+{
+  ensureResultsAreCorrect(getResults(aCollation, aConn),
+                          gStrings.slice(0).sort(localeCompare(aCollation)));
+}
+
+/**
+ * Gets the results from the UTF-8 database using the given collation and
+ * ensures that they match gStrings sorted by the same collation.
+ *
+ * @param aCollation
+ *        The name of one of our custom locale collations.  The rows from the
+ *        database and the expected results are ordered by this collation.
+ */
+function runUtf8Test(aCollation)
+{
+  runTest(aCollation, getOpenedDatabase());
+}
+
+/**
+ * Gets the results from the UTF-16 database using the given collation and
+ * ensures that they match gStrings sorted by the same collation.
+ *
+ * @param aCollation
+ *        The name of one of our custom locale collations.  The rows from the
+ *        database and the expected results are ordered by this collation.
+ */
+function runUtf16Test(aCollation)
+{
+  runTest(aCollation, gUtf16Conn);
+}
+
+/**
+ * Sets up the test suite.
+ */
+function setup()
+{
+  print("-- Setting up the test_locale_collation.js suite.");
+
+  gStrings = readTestData();
+
+  initTableWithStrings(gStrings, getOpenedDatabase());
+
+  gUtf16Conn = createUtf16Database();
+  initTableWithStrings(gStrings, gUtf16Conn);
+
+  let localeSvc = Cc["@mozilla.org/intl/nslocaleservice;1"].
+                  getService(Ci.nsILocaleService);
+  let collFact = Cc["@mozilla.org/intl/collation-factory;1"].
+                 createInstance(Ci.nsICollationFactory);
+  gLocaleCollation = collFact.CreateCollation(localeSvc.getApplicationLocale());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//// Test Runs
+
+let gTests = [
+  {
+    desc: "Case and accent sensitive UTF-8",
+    run:   function () runUtf8Test("locale_case_accent_sensitive")
+  },
+
+  {
+    desc: "Case sensitive, accent insensitive UTF-8",
+    run:   function () runUtf8Test("locale_case_sensitive")
+  },
+
+  {
+    desc: "Case insensitive, accent sensitive UTF-8",
+    run:   function () runUtf8Test("locale_accent_sensitive")
+  },
+
+  {
+    desc: "Case and accent insensitive UTF-8",
+    run:   function () runUtf8Test("locale")
+  },
+
+  {
+    desc: "Case and accent sensitive UTF-16",
+    run:   function () runUtf16Test("locale_case_accent_sensitive")
+  },
+
+  {
+    desc: "Case sensitive, accent insensitive UTF-16",
+    run:   function () runUtf16Test("locale_case_sensitive")
+  },
+
+  {
+    desc: "Case insensitive, accent sensitive UTF-16",
+    run:   function () runUtf16Test("locale_accent_sensitive")
+  },
+
+  {
+    desc: "Case and accent insensitive UTF-16",
+    run:   function () runUtf16Test("locale")
+  },
+];
+
+function run_test()
+{
+  setup();
+  gTests.forEach(function (test) {
+    print("-- Running test: " + test.desc);
+    test.run();
+  });
+}