=== modified file 'src/Core/Banshee.Core/Banshee.Collection/AlbumInfo.cs'
--- src/Core/Banshee.Core/Banshee.Collection/AlbumInfo.cs	2009-02-11 01:24:04 +0000
+++ src/Core/Banshee.Core/Banshee.Collection/AlbumInfo.cs	2009-02-25 22:11:34 +0000
@@ -60,7 +60,7 @@
         
         public virtual string ArtistNameSort {
             get { return artist_name_sort; }
-            set { artist_name_sort = value; }
+            set { artist_name_sort = String.IsNullOrEmpty (value)? null : value; }
         }
         
         public virtual string Title {
@@ -70,7 +70,7 @@
         
         public virtual string TitleSort {
             get { return title_sort; }
-            set { title_sort = value; }
+            set { title_sort = String.IsNullOrEmpty (value)? null : value; }
         }
         
         public virtual bool IsCompilation {

=== modified file 'src/Core/Banshee.Core/Banshee.Collection/ArtistInfo.cs'
--- src/Core/Banshee.Core/Banshee.Collection/ArtistInfo.cs	2009-02-11 01:24:04 +0000
+++ src/Core/Banshee.Core/Banshee.Collection/ArtistInfo.cs	2009-02-25 22:12:10 +0000
@@ -26,6 +26,8 @@
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
+using System;
+
 namespace Banshee.Collection
 {
     public class ArtistInfo : CacheableItem
@@ -56,7 +58,7 @@
         
         public virtual string NameSort {
             get { return name_sort; }
-            set { name_sort = value; }
+            set { name_sort = String.IsNullOrEmpty (value)? null : value; }
         }
     }
 }

=== modified file 'src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs'
--- src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs	2009-02-11 01:24:04 +0000
+++ src/Core/Banshee.Core/Banshee.Collection/TrackInfo.cs	2009-02-25 22:11:52 +0000
@@ -211,7 +211,7 @@
         [Exportable (ExportName = "artistsort")]
         public virtual string ArtistNameSort {
             get { return artist_name_sort; }
-            set { artist_name_sort = value; }
+            set { artist_name_sort = String.IsNullOrEmpty (value)? null : value; }
         }
 
         [Exportable (ExportName = "album")]
@@ -223,7 +223,7 @@
         [Exportable (ExportName = "albumsort")]
         public virtual string AlbumTitleSort {
             get { return album_title_sort; }
-            set { album_title_sort = value; }
+            set { album_title_sort = String.IsNullOrEmpty (value)? null : value; }
         }
 
         [Exportable]
@@ -235,7 +235,7 @@
         [Exportable]
         public virtual string AlbumArtistSort {
             get { return album_artist_sort; }
-            set { album_artist_sort = value; }
+            set { album_artist_sort = String.IsNullOrEmpty (value)? null : value; }
         }
         
         [Exportable]
@@ -253,7 +253,7 @@
         [Exportable (ExportName = "namesort")]
         public virtual string TrackTitleSort {
             get { return track_title_sort; }
-            set { track_title_sort = value; }
+            set { track_title_sort = String.IsNullOrEmpty (value)? null : value; }
         }
         
         [Exportable]

=== modified file 'src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumInfo.cs'
--- src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumInfo.cs	2009-02-11 02:34:53 +0000
+++ src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumInfo.cs	2009-02-17 22:45:50 +0000
@@ -200,6 +200,16 @@
             set { base.TitleSort = value; }
         }
         
+        [DatabaseColumn(Select = false)]
+        internal byte[] TitleSortKey {
+            get { return Hyena.StringUtil.SortKey (TitleSort ?? Title); }
+        }
+
+        [DatabaseColumn(Select = false)]
+        internal string TitleLowered {
+            get { return Hyena.StringUtil.SearchKey (Title); }
+        }
+
         [DatabaseColumn]
         public override string ArtistName {
             get { return base.ArtistName; }
@@ -213,12 +223,12 @@
         }
 
         [DatabaseColumn(Select = false)]
-        protected string TitleLowered {
-            get { return Hyena.StringUtil.SearchKey (Title); }
+        internal byte[] ArtistNameSortKey {
+            get { return Hyena.StringUtil.SortKey (ArtistNameSort ?? ArtistName); }
         }
-
+        
         [DatabaseColumn(Select = false)]
-        protected string ArtistNameLowered {
+        internal string ArtistNameLowered {
             get { return Hyena.StringUtil.SearchKey (ArtistName); }
         }
 

=== modified file 'src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs'
--- src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs	2008-09-03 14:28:58 +0000
+++ src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs	2009-02-17 22:45:50 +0000
@@ -51,7 +51,7 @@
                         (SELECT CoreTracks.AlbumID FROM CoreTracks, CoreCache{0}
                             WHERE CoreCache.ModelID = {1} AND
                                   CoreCache.ItemId = {2} {3})
-                    ORDER BY CoreAlbums.TitleLowered, CoreAlbums.ArtistNameLowered";
+                    ORDER BY CoreAlbums.TitleSortKey, CoreAlbums.ArtistNameSortKey";
         }
         
         public override string FilterColumn {

=== modified file 'src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistInfo.cs'
--- src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistInfo.cs	2009-02-11 02:34:53 +0000
+++ src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistInfo.cs	2009-02-17 22:45:50 +0000
@@ -134,7 +134,7 @@
         }
 
         [DatabaseColumn(Select = false)]
-        protected string NameLowered {
+        internal string NameLowered {
             get { return Hyena.StringUtil.SearchKey (Name); }
         }
 
@@ -144,6 +144,11 @@
             set { base.NameSort = value; }
         }
 
+        [DatabaseColumn(Select = false)]
+        internal byte[] NameSortKey {
+            get { return Hyena.StringUtil.SortKey (NameSort ?? Name); }
+        }
+        
         [DatabaseColumn("MusicBrainzID")]
         public override string MusicBrainzId {
             get { return base.MusicBrainzId; }

=== modified file 'src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs'
--- src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs	2009-02-11 01:24:04 +0000
+++ src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs	2009-02-17 22:45:50 +0000
@@ -50,7 +50,7 @@
                     (SELECT CoreTracks.ArtistID FROM CoreTracks, CoreCache{0}
                         WHERE CoreCache.ModelID = {1} AND
                               CoreCache.ItemID = {2} {3})
-                    ORDER BY NameLowered";
+                    ORDER BY NameSortKey";
         }
         
         public override string FilterColumn {

=== modified file 'src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs'
--- src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs	2009-02-11 02:34:53 +0000
+++ src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackInfo.cs	2009-02-25 22:11:05 +0000
@@ -450,14 +450,19 @@
             set { base.TrackTitle = value; }
         }
         
-        [DatabaseColumn]
+        [DatabaseColumn ("TitleSort")]
         public override string TrackTitleSort {
             get { return base.TrackTitleSort; }
             set { base.TrackTitleSort = value; }
         }
         
+        [DatabaseColumn("TitleSortKey", Select = false)]
+        internal byte[] TrackTitleSortKey {
+            get { return Hyena.StringUtil.SortKey (TrackTitleSort ?? TrackTitle); }
+        }
+        
         [DatabaseColumn(Select = false)]
-        protected string TitleLowered {
+        internal string TitleLowered {
             get { return Hyena.StringUtil.SearchKey (TrackTitle); }
         }
 

=== added directory 'src/Core/Banshee.Services/Banshee.Collection.Database/Tests'
=== added file 'src/Core/Banshee.Services/Banshee.Collection.Database/Tests/DatabaseAlbumInfoTests.cs'
--- src/Core/Banshee.Services/Banshee.Collection.Database/Tests/DatabaseAlbumInfoTests.cs	1970-01-01 00:00:00 +0000
+++ src/Core/Banshee.Services/Banshee.Collection.Database/Tests/DatabaseAlbumInfoTests.cs	2009-02-17 23:03:58 +0000
@@ -0,0 +1,121 @@
+//
+// DatabaseAlbumInfoTests.cs
+//
+// Author:
+//   John Millikin <jmillikin@gmail.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#if ENABLE_TESTS
+
+using NUnit.Framework;
+using Banshee.Collection.Database;
+
+namespace Banshee.Collection.Database.Tests
+{
+    [TestFixture]
+    public class DatabaseAlbumInfoTests
+    {
+        static DatabaseAlbumInfoTests () {
+            Banshee.Database.BansheeDatabaseSettings.CheckTables = false;
+        }
+
+        protected void AssertTitleSort (string title, string title_sort, byte[] expected)
+        {
+            DatabaseAlbumInfo info = new DatabaseAlbumInfo ();
+            info.Title = title;
+            info.TitleSort = title_sort;
+            Assert.AreEqual (expected, info.TitleSortKey);
+        }
+        
+        protected void AssertTitleLowered (string title, string expected)
+        {
+            DatabaseAlbumInfo info = new DatabaseAlbumInfo ();
+            info.Title = title;
+            Assert.AreEqual (expected, info.TitleLowered);
+        }
+        
+        protected void AssertArtistNameSort (string name, string name_sort, byte[] expected)
+        {
+            DatabaseAlbumInfo info = new DatabaseAlbumInfo ();
+            info.ArtistName = name;
+            info.ArtistNameSort = name_sort;
+            Assert.AreEqual (expected, info.ArtistNameSortKey);
+        }
+        
+        protected void AssertArtistNameLowered (string name, string expected)
+        {
+            DatabaseAlbumInfo info = new DatabaseAlbumInfo ();
+            info.ArtistName = name;
+            Assert.AreEqual (expected, info.ArtistNameLowered);
+        }
+        
+        [Test]
+        public void TestWithoutTitleSortKey ()
+        {
+            AssertTitleSort ("", null, new byte[] {1, 1, 1, 1, 0});
+            AssertTitleSort ("a", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertTitleSort ("A", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestTitleSortKey ()
+        {
+            AssertTitleSort ("Title", "", new byte[] {1, 1, 1, 1, 0});
+            AssertTitleSort ("Title", "a", new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertTitleSort ("Title", "A", new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestTitleLowered ()
+        {
+            AssertTitleLowered ("", "");
+            AssertTitleLowered ("A", "a");
+            AssertTitleLowered ("\u0104", "a");
+        }
+        
+        [Test]
+        public void TestWithoutArtistNameSortKey ()
+        {
+            AssertArtistNameSort ("", null, new byte[] {1, 1, 1, 1, 0});
+            AssertArtistNameSort ("a", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertArtistNameSort ("A", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestArtistNameSortKey ()
+        {
+            AssertArtistNameSort ("Title", "", new byte[] {1, 1, 1, 1, 0});
+            AssertArtistNameSort ("Title", "a", new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertArtistNameSort ("Title", "A", new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestArtistNameLowered ()
+        {
+            AssertArtistNameLowered ("", "");
+            AssertArtistNameLowered ("A", "a");
+            AssertArtistNameLowered ("\u0104", "a");
+        }
+    }
+}
+
+#endif

=== added file 'src/Core/Banshee.Services/Banshee.Collection.Database/Tests/DatabaseArtistInfoTests.cs'
--- src/Core/Banshee.Services/Banshee.Collection.Database/Tests/DatabaseArtistInfoTests.cs	1970-01-01 00:00:00 +0000
+++ src/Core/Banshee.Services/Banshee.Collection.Database/Tests/DatabaseArtistInfoTests.cs	2009-02-17 23:04:16 +0000
@@ -0,0 +1,83 @@
+//
+// DatabaseArtistInfoTests.cs
+//
+// Author:
+//   John Millikin <jmillikin@gmail.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#if ENABLE_TESTS
+
+using NUnit.Framework;
+using Banshee.Collection.Database;
+
+namespace Banshee.Collection.Database.Tests
+{
+    [TestFixture]
+    public class DatabaseArtistInfoTests
+    {
+        static DatabaseArtistInfoTests () {
+            Banshee.Database.BansheeDatabaseSettings.CheckTables = false;
+        }
+
+        protected void AssertNameSort (string name, string name_sort, byte[] expected)
+        {
+            DatabaseArtistInfo info = new DatabaseArtistInfo ();
+            info.Name = name;
+            info.NameSort = name_sort;
+            Assert.AreEqual (expected, info.NameSortKey);
+        }
+        
+        protected void AssertNameLowered (string name, string expected)
+        {
+            DatabaseArtistInfo info = new DatabaseArtistInfo ();
+            info.Name = name;
+            Assert.AreEqual (expected, info.NameLowered);
+        }
+        
+        [Test]
+        public void TestWithoutNameSortKey ()
+        {
+            AssertNameSort ("", null, new byte[] {1, 1, 1, 1, 0});
+            AssertNameSort ("a", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertNameSort ("A", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestNameSortKey ()
+        {
+            AssertNameSort ("Title", "", new byte[] {1, 1, 1, 1, 0});
+            AssertNameSort ("Title", "a", new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertNameSort ("Title", "A", new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestNameLowered ()
+        {
+            AssertNameLowered ("", "");
+            AssertNameLowered ("A", "a");
+            AssertNameLowered ("\u0104", "a");
+        }
+    }
+}
+
+#endif
+

=== added file 'src/Core/Banshee.Services/Banshee.Collection.Database/Tests/DatabaseTrackInfoTests.cs'
--- src/Core/Banshee.Services/Banshee.Collection.Database/Tests/DatabaseTrackInfoTests.cs	1970-01-01 00:00:00 +0000
+++ src/Core/Banshee.Services/Banshee.Collection.Database/Tests/DatabaseTrackInfoTests.cs	2009-02-17 23:05:18 +0000
@@ -0,0 +1,82 @@
+//
+// DatabaseTrackInfoTests.cs
+//
+// Author:
+//   John Millikin <jmillikin@gmail.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#if ENABLE_TESTS
+
+using NUnit.Framework;
+using Banshee.Collection.Database;
+
+namespace Banshee.Collection.Database.Tests
+{
+    [TestFixture]
+    public class DatabaseTrackInfoTests
+    {
+        static DatabaseTrackInfoTests () {
+            Banshee.Database.BansheeDatabaseSettings.CheckTables = false;
+        }
+
+        protected void AssertTitleSort (string title, string title_sort, byte[] expected)
+        {
+            DatabaseTrackInfo info = new DatabaseTrackInfo ();
+            info.TrackTitle = title;
+            info.TrackTitleSort = title_sort;
+            Assert.AreEqual (expected, info.TrackTitleSortKey);
+        }
+        
+        protected void AssertTitleLowered (string title, string expected)
+        {
+            DatabaseTrackInfo info = new DatabaseTrackInfo ();
+            info.TrackTitle = title;
+            Assert.AreEqual (expected, info.TitleLowered);
+        }
+        
+        [Test]
+        public void TestWithoutTitleSortKey ()
+        {
+            AssertTitleSort ("", null, new byte[] {1, 1, 1, 1, 0});
+            AssertTitleSort ("a", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertTitleSort ("A", null, new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestTitleSortKey ()
+        {
+            AssertTitleSort ("Title", "", new byte[] {1, 1, 1, 1, 0});
+            AssertTitleSort ("Title", "a", new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertTitleSort ("Title", "A", new byte[] {14, 2, 1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestTitleLowered ()
+        {
+            AssertTitleLowered ("", "");
+            AssertTitleLowered ("A", "a");
+            AssertTitleLowered ("\u0104", "a");
+        }
+    }
+}
+
+#endif

=== modified file 'src/Core/Banshee.Services/Banshee.Database/BansheeDbConnection.cs'
--- src/Core/Banshee.Services/Banshee.Database/BansheeDbConnection.cs	2009-01-26 04:24:37 +0000
+++ src/Core/Banshee.Services/Banshee.Database/BansheeDbConnection.cs	2009-02-17 22:45:50 +0000
@@ -71,6 +71,9 @@
             lock (this) {
                 migrator.Migrate ();
                 migrator = null;
+
+                // Update cached sorting keys
+                SortKeyUpdater.Update ();
             }
         }
 

=== modified file 'src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs'
--- src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs	2009-02-11 02:34:53 +0000
+++ src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs	2009-02-17 22:45:50 +0000
@@ -52,7 +52,7 @@
         // NOTE: Whenever there is a change in ANY of the database schema,
         //       this version MUST be incremented and a migration method
         //       MUST be supplied to match the new version number
-        protected const int CURRENT_VERSION = 24;
+        protected const int CURRENT_VERSION = 25;
         protected const int CURRENT_METADATA_VERSION = 5;
         
 #region Migration Driver
@@ -578,6 +578,20 @@
         }
 #endregion
 
+#region Version 25
+
+        [DatabaseVersion (25)]
+        private bool Migrate_25 ()
+        {
+            Execute ("ALTER TABLE CoreArtists ADD COLUMN NameSortKey BLOB");
+            Execute ("ALTER TABLE CoreAlbums ADD COLUMN ArtistNameSortKey BLOB");
+            Execute ("ALTER TABLE CoreAlbums ADD COLUMN TitleSortKey BLOB");
+            Execute ("ALTER TABLE CoreTracks ADD COLUMN TitleSortKey BLOB");
+            return true;
+        }
+        
+#endregion
+
 #pragma warning restore 0169
         
 #region Fresh database setup
@@ -643,6 +657,7 @@
                     Title               TEXT,
                     TitleLowered        TEXT,
                     TitleSort           TEXT,
+                    TitleSortKey        BLOB,
                     TrackNumber         INTEGER,
                     TrackCount          INTEGER,
                     Disc                INTEGER,
@@ -687,6 +702,7 @@
                     Title               TEXT,
                     TitleLowered        TEXT,
                     TitleSort           TEXT,
+                    TitleSortKey        BLOB,
 
                     ReleaseDate         INTEGER,
                     Duration            INTEGER,
@@ -696,6 +712,7 @@
                     ArtistName          TEXT,
                     ArtistNameLowered   TEXT,
                     ArtistNameSort      TEXT,
+                    ArtistNameSortKey   BLOB,
                     
                     Rating              INTEGER
                 )
@@ -711,6 +728,7 @@
                     Name                TEXT,
                     NameLowered         TEXT,
                     NameSort            TEXT,
+                    NameSortKey         BLOB,
                     Rating              INTEGER
                 )
             ");

=== modified file 'src/Core/Banshee.Services/Banshee.Database/BansheeModelProvider.cs'
--- src/Core/Banshee.Services/Banshee.Database/BansheeModelProvider.cs	2008-04-28 20:42:18 +0000
+++ src/Core/Banshee.Services/Banshee.Database/BansheeModelProvider.cs	2009-02-17 22:45:50 +0000
@@ -36,10 +36,16 @@
 
 namespace Banshee.Database
 {
+    // Used for disabling table-checks during testing
+    internal static class BansheeDatabaseSettings
+    {
+        internal static bool CheckTables = true;
+    }
+
     public class BansheeModelProvider<T> : SqliteModelProvider<T> where T : new ()
     {
         public BansheeModelProvider (BansheeDbConnection connection, string table_name)
-            : base (connection, table_name)
+            : base (connection, table_name, BansheeDatabaseSettings.CheckTables)
         {
         }
         

=== modified file 'src/Core/Banshee.Services/Banshee.Database/SortKeyUpdater.cs'
--- src/Core/Banshee.Services/Banshee.Database/SortKeyUpdater.cs	2009-02-17 20:00:07 +0000
+++ src/Core/Banshee.Services/Banshee.Database/SortKeyUpdater.cs	2009-02-17 22:45:50 +0000
@@ -0,0 +1,69 @@
+//
+// SortKeyUpdater.cs
+//
+// Author:
+//   John Millikin <jmillikin@gmail.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using Banshee.Configuration;
+using Banshee.ServiceStack;
+
+namespace Banshee.Database
+{
+    internal class SortKeyUpdater
+    {
+        public static void Update ()
+        {
+            string locale = CultureInfo.CurrentCulture.Name;
+            Hyena.Log.DebugFormat ("locale = {0}, previous = {1}", locale, PreviousLocale);
+            if (locale != PreviousLocale) {
+                ForceUpdate (locale);
+            }
+        }
+        
+        public static void ForceUpdate ()
+        {
+            ForceUpdate (CultureInfo.CurrentCulture.Name);
+        }
+        
+        protected static void ForceUpdate (string new_locale)
+        {
+            BansheeDbConnection db = ServiceManager.DbConnection;
+            db.Execute ("BEGIN");
+            db.Execute (@"UPDATE CoreArtists SET NameSortKey = HYENA_COLLATION_KEY(IFNULL(NameSort,Name))");
+            db.Execute (@"UPDATE CoreAlbums SET TitleSortKey = HYENA_COLLATION_KEY(IFNULL(TitleSort,Title)),
+                                                ArtistNameSortKey = HYENA_COLLATION_KEY(IFNULL(ArtistNameSort, ArtistName))");
+            db.Execute (@"UPDATE CoreTracks SET TitleSortKey = HYENA_COLLATION_KEY(IFNULL(TitleSort,Title))");
+            
+            DatabaseConfigurationClient.Client.Set<string> ("SortKeyLocale", new_locale);
+            db.Execute ("COMMIT");
+        }
+        
+        protected static string PreviousLocale {
+            get {
+                return DatabaseConfigurationClient.Client.Get<string> ("SortKeyLocale", "");
+            }
+        }
+    }
+}

=== modified file 'src/Core/Banshee.Services/Banshee.Query/BansheeQuery.cs'
--- src/Core/Banshee.Services/Banshee.Query/BansheeQuery.cs	2009-02-11 02:34:53 +0000
+++ src/Core/Banshee.Services/Banshee.Query/BansheeQuery.cs	2009-02-17 22:45:50 +0000
@@ -337,7 +337,7 @@
             PlayCountField.ShortLabel   = Catalog.GetString ("Plays");
         }
 
-        private const string default_sort = @"CoreAlbums.ArtistNameLowered ASC, CoreAlbums.TitleLowered ASC, CoreTracks.Disc ASC, CoreTracks.TrackNumber ASC";
+        private const string default_sort = @"CoreAlbums.ArtistNameSortKey ASC, CoreAlbums.TitleSortKey ASC, CoreTracks.Disc ASC, CoreTracks.TrackNumber ASC";
         public static string GetSort (string key)
         {
             return GetSort (key, false);
@@ -353,40 +353,40 @@
                 case "track":
                 case "grouping":
                     sort_query = String.Format (@"
-                        CoreAlbums.ArtistNameLowered ASC, 
-                        CoreAlbums.TitleLowered ASC, 
+                        CoreAlbums.ArtistNameSortKey ASC, 
+                        CoreAlbums.TitleSortKey ASC, 
                         CoreTracks.Disc ASC,
                         CoreTracks.TrackNumber {0}", ascDesc); 
                     break;
 
                 case "albumartist":
                     sort_query = String.Format (@"
-                        CoreAlbums.ArtistNameLowered {0}, 
-                        CoreAlbums.TitleLowered ASC, 
+                        CoreAlbums.ArtistNameSortKey {0}, 
+                        CoreAlbums.TitleSortKey ASC, 
                         CoreTracks.Disc ASC,
                         CoreTracks.TrackNumber ASC", ascDesc); 
                     break;
 
                 case "artist":
                     sort_query = String.Format (@"
-                        CoreArtists.NameLowered {0}, 
-                        CoreAlbums.TitleLowered ASC,
+                        CoreArtists.NameSortKey {0}, 
+                        CoreAlbums.TitleSortKey ASC,
                         CoreTracks.Disc ASC,
                         CoreTracks.TrackNumber ASC", ascDesc); 
                     break;
 
                 case "album":
                     sort_query = String.Format (@"
-                        CoreAlbums.TitleLowered {0},
+                        CoreAlbums.TitleSortKey {0},
                         CoreTracks.Disc ASC,
                         CoreTracks.TrackNumber ASC", ascDesc); 
                     break;
 
                 case "title":
                     sort_query = String.Format (@"
-                        CoreTracks.TitleLowered {0},
-                        CoreAlbums.ArtistNameLowered ASC, 
-                        CoreAlbums.TitleLowered ASC", ascDesc); 
+                        CoreTracks.TitleSortKey {0},
+                        CoreAlbums.ArtistNameSortKey ASC, 
+                        CoreAlbums.TitleSortKey ASC", ascDesc); 
                     break;
 
                 case "random":
@@ -395,8 +395,8 @@
 
                 case "disc":
                     sort_query = String.Format (@"
-                        CoreAlbums.ArtistNameLowered ASC, 
-                        CoreAlbums.TitleLowered ASC, 
+                        CoreAlbums.ArtistNameSortKey ASC, 
+                        CoreAlbums.TitleSortKey ASC, 
                         CoreTracks.Disc {0},
                         CoreTracks.TrackNumber ASC", ascDesc);
                     break;
@@ -406,18 +406,26 @@
                 case "lastplayed":
                 case "lastskipped":
                     column = String.Format ("{0}stamp", key);
-                    goto case "comment";
+                    goto case "year";
                 case "added":
                     column = "dateaddedstamp";
-                    goto case "comment";
+                    goto case "year";
+
+                case "conductor":
+                case "genre":
+                case "composer":
+                case "comment":
+                    sort_query = String.Format (
+                        "HYENA_COLLATION_KEY(CoreTracks.{0}) {1}, {2}",
+                        column ?? key, ascDesc, default_sort
+                    );
+                    break;
 
                 case "year":
                 case "bitrate":
                 case "bpm":
-                case "conductor":
                 case "trackcount":
                 case "disccount":
-                case "genre":
                 case "duration":
                 case "rating":
                 case "playcount":
@@ -428,9 +436,7 @@
                 case "dateaddedstamp":
                 case "uri":
                 case "mimetype":
-                case "composer":
                 case "licenseuri":
-                case "comment":
                     sort_query = String.Format (
                         "CoreTracks.{0} {1}, {2}",
                         column ?? key, ascDesc, default_sort

=== modified file 'src/Core/Banshee.Services/Makefile.am'
--- src/Core/Banshee.Services/Makefile.am	2009-02-02 18:28:57 +0000
+++ src/Core/Banshee.Services/Makefile.am	2009-02-17 23:04:49 +0000
@@ -22,6 +22,9 @@
 	Banshee.Collection.Database/IDatabaseTrackModelCache.cs \
 	Banshee.Collection.Database/IDatabaseTrackModelProvider.cs \
 	Banshee.Collection.Database/QueryFilterInfo.cs \
+	Banshee.Collection.Database/Tests/DatabaseAlbumInfoTests.cs \
+	Banshee.Collection.Database/Tests/DatabaseArtistInfoTests.cs \
+	Banshee.Collection.Database/Tests/DatabaseTrackInfoTests.cs \
 	Banshee.Collection.Indexer/CollectionIndexer.cs \
 	Banshee.Collection.Indexer/CollectionIndexerService.cs \
 	Banshee.Collection.Indexer/ICollectionIndexer.cs \
@@ -48,6 +51,7 @@
 	Banshee.Database/BansheeDbFormatMigrator.cs \
 	Banshee.Database/BansheeModelCache.cs \
 	Banshee.Database/BansheeModelProvider.cs \
+	Banshee.Database/SortKeyUpdater.cs \
 	Banshee.Equalizer/EqualizerManager.cs \
 	Banshee.Equalizer/EqualizerSetting.cs \
 	Banshee.Equalizer/EqualizerSettingEvent.cs \

=== added file 'src/Core/Banshee.ThickClient/Banshee.Gui.TrackEditor/SortingPage.cs'
--- src/Core/Banshee.ThickClient/Banshee.Gui.TrackEditor/SortingPage.cs	1970-01-01 00:00:00 +0000
+++ src/Core/Banshee.ThickClient/Banshee.Gui.TrackEditor/SortingPage.cs	2009-02-25 22:05:45 +0000
@@ -0,0 +1,76 @@
+//
+// SortingPage.cs
+//
+// Author:
+//   John Millikin <jmillikin@gmail.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using Mono.Unix;
+using Gtk;
+
+using Banshee.Collection;
+
+namespace Banshee.Gui.TrackEditor
+{
+    public class SortingPage : FieldPage, ITrackEditorPage
+    {        
+        public int Order {
+            get { return 30; }
+        }
+                                    
+        public string Title {
+            get { return Catalog.GetString ("Sorting"); }
+        }
+        
+        protected override void AddFields ()
+        {
+            AddField (this, new TextEntry (),
+                Catalog.GetString ("Set all sort track titles to this value"),
+                delegate { return Catalog.GetString ("Sort Track Title:"); },
+                delegate (EditorTrackInfo track, Widget widget) { ((TextEntry)widget).Text = track.TrackTitleSort; },
+                delegate (EditorTrackInfo track, Widget widget) {  track.TrackTitleSort = ((TextEntry)widget).Text; }
+            );
+            
+            AddField (this, new TextEntry (),
+                Catalog.GetString ("Set all sort track artists to this value"),
+                delegate { return Catalog.GetString ("Sort Track Artist:"); },
+                delegate (EditorTrackInfo track, Widget widget) { ((TextEntry)widget).Text = track.ArtistNameSort; },
+                delegate (EditorTrackInfo track, Widget widget) {  track.ArtistNameSort = ((TextEntry)widget).Text; }
+            );
+            
+            AddField (this, new TextEntry (),
+                Catalog.GetString ("Set all sort album artists to this value"),
+                delegate { return Catalog.GetString ("Sort Album Artist:"); },
+                delegate (EditorTrackInfo track, Widget widget) { ((TextEntry)widget).Text = track.AlbumArtistSort; },
+                delegate (EditorTrackInfo track, Widget widget) {  track.AlbumArtistSort = ((TextEntry)widget).Text; }
+            );
+            
+            AddField (this, new TextEntry (),
+                Catalog.GetString ("Set all sort album titles to this value"),
+                delegate { return Catalog.GetString ("Sort Album Title:"); },
+                delegate (EditorTrackInfo track, Widget widget) { ((TextEntry)widget).Text = track.AlbumTitleSort; },
+                delegate (EditorTrackInfo track, Widget widget) {  track.AlbumTitleSort = ((TextEntry)widget).Text; }
+            );
+        }
+    }
+}

=== modified file 'src/Core/Banshee.ThickClient/Banshee.ThickClient.addin.xml'
--- src/Core/Banshee.ThickClient/Banshee.ThickClient.addin.xml	2009-01-26 03:45:46 +0000
+++ src/Core/Banshee.ThickClient/Banshee.ThickClient.addin.xml	2009-02-25 22:00:46 +0000
@@ -23,6 +23,7 @@
   <Extension path="/Banshee/Gui/TrackEditor/NotebookPage">
     <TrackEditorPage class="Banshee.Gui.TrackEditor.BasicTrackDetailsPage"/>
     <TrackEditorPage class="Banshee.Gui.TrackEditor.ExtraTrackDetailsPage"/>
+    <TrackEditorPage class="Banshee.Gui.TrackEditor.SortingPage"/>
     <!--<TrackEditorPage class="Banshee.Gui.TrackEditor.LyricsPage"/>-->
     <TrackEditorPage class="Banshee.Gui.TrackEditor.StatisticsPage"/>
     <!--<TrackEditorPage class="Banshee.Gui.TrackEditor.HelpPage"/>-->

=== modified file 'src/Core/Banshee.ThickClient/Makefile.am'
--- src/Core/Banshee.ThickClient/Makefile.am	2009-01-27 20:27:45 +0000
+++ src/Core/Banshee.ThickClient/Makefile.am	2009-02-25 22:01:10 +0000
@@ -72,6 +72,7 @@
 	Banshee.Gui.TrackEditor/PageType.cs \
 	Banshee.Gui.TrackEditor/RangeEntry.cs \
 	Banshee.Gui.TrackEditor/RatingEntry.cs \
+	Banshee.Gui.TrackEditor/SortingPage.cs \
 	Banshee.Gui.TrackEditor/SpinButtonEntry.cs \
 	Banshee.Gui.TrackEditor/StatisticsPage.cs \
 	Banshee.Gui.TrackEditor/SyncButton.cs \

=== modified file 'src/Libraries/Hyena/Hyena.Data.Sqlite/HyenaSqliteCommand.cs'
--- src/Libraries/Hyena/Hyena.Data.Sqlite/HyenaSqliteCommand.cs	2009-01-28 01:29:07 +0000
+++ src/Libraries/Hyena/Hyena.Data.Sqlite/HyenaSqliteCommand.cs	2009-02-17 22:45:50 +0000
@@ -207,7 +207,7 @@
             return this;
         }
 
-        private static object SqlifyObject (object o)
+        public static object SqlifyObject (object o)
         {
             if (o is string) {
                 return String.Format ("'{0}'", (o as string).Replace ("'", "''"));
@@ -217,6 +217,9 @@
                 return ((bool)o) ? "1" : "0";
             } else if (o == null) {
                 return "NULL";
+            } else if (o is byte[]) {
+                string hex = BitConverter.ToString (o as byte[]).Replace ("-", "");
+                return String.Format ("X'{0}'", hex);
             } else if (o is Array) {
                 StringBuilder sb = new StringBuilder ();
                 bool first = true;

=== modified file 'src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteModelProvider.cs'
--- src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteModelProvider.cs	2008-09-12 00:35:43 +0000
+++ src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteModelProvider.cs	2009-02-17 22:45:50 +0000
@@ -43,6 +43,7 @@
         private DatabaseColumn key;
         private int key_select_column_index;
         private HyenaSqliteConnection connection;
+        private bool check_table = true;
         
         private HyenaSqliteCommand create_command;
         private HyenaSqliteCommand insert_command;
@@ -86,15 +87,20 @@
         public HyenaSqliteConnection Connection {
             get { return connection; }
         }
-        
+
         protected SqliteModelProvider (HyenaSqliteConnection connection)
         {
             this.connection = connection;
         }
+
+        public SqliteModelProvider (HyenaSqliteConnection connection, string table_name) : this (connection, table_name, true)
+        {
+        }
         
-        public SqliteModelProvider (HyenaSqliteConnection connection, string table_name) : this (connection)
+        public SqliteModelProvider (HyenaSqliteConnection connection, string table_name, bool checkTable) : this (connection)
         {
             this.table_name = table_name;
+            this.check_table = checkTable;
             Init ();
         }
 
@@ -126,8 +132,10 @@
             
             key_select_column_index = select_columns.IndexOf (key);
             
-            CheckVersion ();
-            CheckTable ();
+            if (check_table) {
+                CheckVersion ();
+                CheckTable ();
+            }
         }
         
         protected virtual void CheckVersion ()

=== modified file 'src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteUtils.cs'
--- src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteUtils.cs	2009-02-11 02:34:53 +0000
+++ src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteUtils.cs	2009-02-17 22:45:50 +0000
@@ -42,6 +42,8 @@
             } else if (type == typeof (int) || type == typeof (long) || type == typeof (bool)
                 || type == typeof (DateTime) || type == typeof (TimeSpan) || type.IsEnum) {
                 return "INTEGER";
+            } else if (type == typeof (byte[])) {
+                return "BLOB";
             } else {
                 throw new Exception (String.Format (
                     "The type {0} cannot be bound to a database column.", type.Name));
@@ -123,11 +125,21 @@
             return builder.ToString ();
         }
     }
+        
+    [SqliteFunction (Name = "HYENA_COLLATION_KEY", FuncType = FunctionType.Scalar, Arguments = 1)]
+    internal class CollationKeyFunction : SqliteFunction
+    {
+        public override object Invoke (object[] args)
+        {
+            return Hyena.StringUtil.SortKey (args[0] as string);
+        }
+    }
     
     [SqliteFunction (Name = "HYENA_SEARCH_KEY", FuncType = FunctionType.Scalar, Arguments = 1)]
     internal class SearchKeyFunction : SqliteFunction
     {
-        public override object Invoke (object[] args) {
+        public override object Invoke (object[] args)
+        {
             return Hyena.StringUtil.SearchKey (args[0] as string);
         }
     }

=== modified file 'src/Libraries/Hyena/Hyena.Data.Sqlite/Tests/SqliteCommandTests.cs'
--- src/Libraries/Hyena/Hyena.Data.Sqlite/Tests/SqliteCommandTests.cs	2008-05-19 20:34:45 +0000
+++ src/Libraries/Hyena/Hyena.Data.Sqlite/Tests/SqliteCommandTests.cs	2009-02-17 22:45:50 +0000
@@ -106,6 +106,62 @@
             return tf.GetValue (cmd, null) as string;
         }
     }
+    
+    [TestFixture]
+    public class ObjectToSqlTests
+    {
+        protected void AssertToSql (object o, object expected)
+        {
+            Assert.AreEqual (expected, HyenaSqliteCommand.SqlifyObject (o));
+        }
+        
+        [Test]
+        public void TestNull ()
+        {
+            AssertToSql (null, "NULL");
+        }
+        
+        [Test]
+        public void TestBool ()
+        {
+            AssertToSql (false, "0");
+            AssertToSql (true, "1");
+        }
+        
+        [Test]
+        public void TestString ()
+        {
+            AssertToSql ("", "''");
+            AssertToSql ("test", "'test'");
+            AssertToSql ("te'st", "'te''st'");
+        }
+        
+        [Test]
+        public void TestByteArray ()
+        {
+            // BLOB notation
+            AssertToSql (new byte[] {}, "X''");
+            AssertToSql (new byte[] {0x10, 0x20, 0x30}, "X'102030'");
+        }
+        
+        [Test]
+        public void TestOtherArray ()
+        {
+            AssertToSql (new object[] {}, "");
+            AssertToSql (new object[] {"a"}, "'a'");
+            AssertToSql (new object[] {"a", "b"}, "'a','b'");
+        }
+        
+        [Test]
+        public void TestDateTime ()
+        {
+            // Returned using local time, not UTC
+            AssertToSql (new DateTime (2000, 1, 2), 946792800);
+            
+            // Disregards milliseconds
+            AssertToSql (new DateTime (2000, 1, 2, 10, 9, 8, 7), 946829348);
+        }
+    }
 }
 
 #endif

=== added file 'src/Libraries/Hyena/Hyena.Data.Sqlite/Tests/SqliteUtilTests.cs'
--- src/Libraries/Hyena/Hyena.Data.Sqlite/Tests/SqliteUtilTests.cs	1970-01-01 00:00:00 +0000
+++ src/Libraries/Hyena/Hyena.Data.Sqlite/Tests/SqliteUtilTests.cs	2009-02-17 22:50:38 +0000
@@ -0,0 +1,86 @@
+//
+// SqliteUtilTests.cs
+//
+// Author:
+//   John Millikin <jmillikin@gmail.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#if ENABLE_TESTS
+
+using NUnit.Framework;
+using Hyena.Data.Sqlite;
+
+namespace Hyena.Data.Sqlite.Tests
+{
+    [TestFixture]
+    public class CollationKeyTests
+    {
+        protected void CollationKeyTest (object before, object after)
+        {
+            Assert.AreEqual (after, (new CollationKeyFunction ()).Invoke (new object[] {before}));
+        }
+        
+        [Test]
+        public void TestNull ()
+        {
+            CollationKeyTest (null, null);
+            CollationKeyTest (System.DBNull.Value, null);
+        }
+        
+        [Test]
+        public void TestKey ()
+        {
+            // See Hyena.StringUtil.Tests for full tests. This just checks that
+            // the collation function is actually being used.
+            CollationKeyTest ("", new byte[] {1, 1, 1, 1, 0});
+            CollationKeyTest ("\u0104", new byte[] {14, 2, 1, 27, 1, 1, 1, 0});
+        }
+    }
+    
+    [TestFixture]
+    public class SearchKeyTests
+    {
+        protected void SearchKeyTest (object before, object after)
+        {
+            Assert.AreEqual (after, (new SearchKeyFunction ()).Invoke (new object[] {before}));
+        }
+        
+        [Test]
+        public void TestNull ()
+        {
+            SearchKeyTest (null, null);
+            SearchKeyTest (System.DBNull.Value, null);
+        }
+        
+        [Test]
+        public void TestKey ()
+        {
+            // See Hyena.StringUtil.Tests for full tests. This just checks that
+            // the search key function is actually being used.
+            SearchKeyTest ("", "");
+            SearchKeyTest ("\u0104", "a");
+        }
+    }
+}
+
+#endif
+

=== modified file 'src/Libraries/Hyena/Hyena/StringUtil.cs'
--- src/Libraries/Hyena/Hyena/StringUtil.cs	2009-02-12 22:55:59 +0000
+++ src/Libraries/Hyena/Hyena/StringUtil.cs	2009-02-17 22:45:50 +0000
@@ -232,6 +232,13 @@
             return new Regex (regex_str, RegexOptions.Compiled);
         }
         
+        private static CompareInfo culture_compare_info = CultureInfo.CurrentCulture.CompareInfo;
+        public static byte[] SortKey (string orig)
+        {
+            if (orig == null) { return null; }
+            return culture_compare_info.GetSortKey (orig, CompareOptions.IgnoreCase).KeyData;
+        }
+        
         public static string EscapeFilename (string input)
         {
             if (input == null)

=== modified file 'src/Libraries/Hyena/Hyena/Tests/StringUtilTests.cs'
--- src/Libraries/Hyena/Hyena/Tests/StringUtilTests.cs	2009-02-12 22:55:59 +0000
+++ src/Libraries/Hyena/Hyena/Tests/StringUtilTests.cs	2009-02-17 22:47:19 +0000
@@ -279,6 +279,35 @@
             AssertProduces ("Greetings! -* 你好?", "Greetings! -_ 你好_");
         }
     }
+    
+    [TestFixture]
+    public class SortKeyTests
+    {
+        private void AssertSortKey (string before, object after)
+        {
+            Assert.AreEqual (after, StringUtil.SortKey (before));
+        }
+        
+        [Test]
+        public void TestNull ()
+        {
+            AssertSortKey (null, null);
+        }
+        
+        [Test]
+        public void TestEmpty ()
+        {
+            AssertSortKey ("", new byte[] {1, 1, 1, 1, 0});
+        }
+        
+        [Test]
+        public void TestSortKey ()
+        {
+            AssertSortKey ("a", new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertSortKey ("A", new byte[] {14, 2, 1, 1, 1, 1, 0});
+            AssertSortKey ("\u0104", new byte[] {14, 2, 1, 27, 1, 1, 1, 0,});
+        }
+    }
 }
 
 #endif

=== modified file 'src/Libraries/Hyena/Makefile.am'
--- src/Libraries/Hyena/Makefile.am	2009-02-11 02:34:53 +0000
+++ src/Libraries/Hyena/Makefile.am	2009-02-17 22:45:50 +0000
@@ -31,6 +31,7 @@
 	Hyena.Data.Sqlite/Tests/DbBoundType.cs \
 	Hyena.Data.Sqlite/Tests/SqliteCommandTests.cs \
 	Hyena.Data.Sqlite/Tests/SqliteModelProviderTests.cs \
+	Hyena.Data.Sqlite/Tests/SqliteUtilTests.cs \
 	Hyena.Data/ArrayModelCache.cs \
 	Hyena.Data/ColumnDescription.cs \
 	Hyena.Data/DictionaryModelCache.cs \

