【问题标题】:Implementing a custom collation in SQLite for WinRT在 SQLite 中为 WinRT 实现自定义排序规则
【发布时间】:2013-03-01 22:50:12
【问题描述】:

我正在尝试在 SQLite for Windows Runtime 中实现自定义排序规则。

create_collat​​ion 方法实现如下:

SQLITE_API int sqlite3_create_collation(
  sqlite3*, 
  const char *zName, 
  int eTextRep, 
  void *pArg,
  int(*xCompare)(void*,int,const void*,int,const void*)
);

到目前为止,我有以下 C# 签名:

[DllImport("sqlite3", EntryPoint = "sqlite3_create_collation", CallingConvention = CallingConvention.Cdecl)]
public static extern int CreateCollation(IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string name, int textRep, object state, Compare callback);

public delegate int Compare(object pCompareArg, int size1, IntPtr Key1, int size2, IntPtr Key2);

这是实现:

int i = CreateCollation(db, "unicode_nocase", SQLITE_UTF8, null, CompareMethod);

/* ... */

public static int CompareMethod(object o, int i1, IntPtr s1, int i2, IntPtr s2)
{
    return string.Compare(Marshal.PtrToStringUni(s1), Marshal.PtrToStringUni(s2));
}

应用程序编译没有错误。对 create_collat​​ion 的调用返回零 (SQLITE_OK),但如果我在语句中使用排序规则,则会返回以下错误消息:

no such collation sequence: unicode_nocase

来源参考:https://github.com/doo/SQLite3-WinRT/tree/master/SQLite3Component

有人可以帮帮我吗?

谢谢!

【问题讨论】:

    标签: c# sqlite interop windows-runtime


    【解决方案1】:

    在 Mono.Android.SQLite 内部看了一段时间,它也使用 SQLite 的 C 实现,我找到了解决方案:

    问题是对 sqlite3_create_collat​​ion 的调用有一个 void* 参数,我错误地将它定义为 C# 中的对象,它应该是 IntPtr。

    我在下面发布了当前的实现。我从 Mono 实现中对解决方案进行了部分逆向工程,它为每个要注册的排序规则调用 sqlite3_create_collat​​ion 两次——一次将参数 eTextRep 设置为 SQLITE_UTF16LE,第二次使用 SQLITE_UTF8。我只能想象这可能有助于 SQLite 核心为存储字符串值的不同格式找到快速实现。但是,这些在转换为 C# 字符串时需要不同的解码。

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate int CompareCallback(IntPtr pvUser, int len1, IntPtr pv1, int len2, IntPtr pv2);
    
        [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl)]
        private static extern int sqlite3_create_collation(IntPtr db, byte[] strName, int nType, IntPtr pvUser, CompareCallback func);
    
        private const int SQLITE_UTF8 = 1;
        private const int SQLITE_UTF16LE = 2;
        private const int SQLITE_UTF16BE = 3;
        private const int SQLITE_UTF16 = 4;    /* Use native byte order */
        private const int SQLITE_ANY = 5;    /* sqlite3_create_function only */
        private const int SQLITE_UTF16_ALIGNED = 8;    /* sqlite3_create_collation only */
    
        public void Register(IntPtr db)
        {
            if (db == IntPtr.Zero)
                throw new ArgumentNullException("db");
    
            //create null-terminated UTF8 byte array
            string name = Name;
            var nameLength = System.Text.Encoding.UTF8.GetByteCount(name);
            var nameBytes = new byte[nameLength + 1];
            System.Text.Encoding.UTF8.GetBytes(name, 0, name.Length, nameBytes, 0);
    
            //register UTF16 comparison
            int result = sqlite3_create_collation(db, nameBytes, SQLITE_UTF16LE, IntPtr.Zero, CompareUTF16);
            if (result != 0)
            {
                string msg = SQLite3.GetErrmsg(db);
                throw SQLiteException.New((SQLite3.Result)result, msg);
            }
    
            //register UTF8 comparison
            result = sqlite3_create_collation(db, nameBytes, SQLITE_UTF8, IntPtr.Zero, CompareUTF8);
            if (result != 0)
            {
                string msg = SQLite3.GetErrmsg(db);
                throw SQLiteException.New((SQLite3.Result)result, msg);
            }
        }
    
        private string GetUTF8String(IntPtr ptr, int len)
        {
            if (len == 0 || ptr == IntPtr.Zero)
                return string.Empty;
    
            if (len == -1)
            {
                do
                {
                    len++;
                }
                while (Marshal.ReadByte(ptr, len) != 0);
            }
    
            byte[] array = new byte[len];
            Marshal.Copy(ptr, array, 0, len);
    
            return Encoding.UTF8.GetString(array, 0, len);
        }
    
        private string GetUTF16String(IntPtr ptr, int len)
        {
            if (len == 0 || ptr == IntPtr.Zero)
                return string.Empty;
    
            if (len == -1)
            {
                return Marshal.PtrToStringUni(ptr);
            }
    
            return Marshal.PtrToStringUni(ptr, len / 2);
        }
    
        internal int CompareUTF8(IntPtr ptr, int len1, IntPtr ptr1, int len2, IntPtr ptr2)
        {
            return Compare(GetUTF8String(ptr1, len1), GetUTF8String(ptr2, len2));
        }
    
        internal int CompareUTF16(IntPtr ptr, int len1, IntPtr ptr1, int len2, IntPtr ptr2)
        {
            return Compare(GetUTF16String(ptr1, len1), GetUTF16String(ptr2, len2));
        }
    

    【讨论】:

    • 你应该可以在那里使用SQLITE_UTF8;这是 SQLite 字符串的原生格式。
    • 所以,只是为了确认我理解,这个自定义排序规则会比较慢,因为实现是 C#,是从 C 调用的?或者,例如,当使用 C# 自定义整理器执行表扫描时,是否可以期待合理的性能?
    • 实际上,我不认为性能损失会那么高(除了您选择比较值的任何 c# 实现)。但是,理想情况下,您应该为列创建索引,在这种情况下,在 SELECT 期间永远不会调用 c# 代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-31
    • 1970-01-01
    • 2011-12-29
    • 1970-01-01
    • 2018-10-26
    • 2015-01-17
    相关资源
    最近更新 更多