diff --git a/Native.Csharp.Sdk/Cqp/CqApi.cs b/Native.Csharp.Sdk/Cqp/CqApi.cs index a63172b6..8829bffb 100644 --- a/Native.Csharp.Sdk/Cqp/CqApi.cs +++ b/Native.Csharp.Sdk/Cqp/CqApi.cs @@ -1,13 +1,13 @@ -using Native.Csharp.Sdk.Cqp.Core; -using Native.Csharp.Sdk.Cqp.Enum; -using Native.Csharp.Sdk.Cqp.Model; -using Native.Csharp.Sdk.Cqp.Other; -using Native.Csharp.Tool; -using System; +using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using Native.Csharp.Sdk.Cqp.Core; +using Native.Csharp.Sdk.Cqp.Enum; +using Native.Csharp.Sdk.Cqp.Model; +using Native.Csharp.Sdk.Cqp.Other; namespace Native.Csharp.Sdk.Cqp { @@ -16,6 +16,7 @@ public class CqApi #region --字段-- private int _authCode = 0; private string _appDirCache = null; + private Encoding _defaultEncoding = null; #endregion #region --属性-- @@ -27,12 +28,13 @@ public class CqApi #region --构造函数-- /// - /// 初始化一个 CqApi 类的新实例, 该实例将由酷Q授权 + /// 初始化一个 类的新实例, 该实例将由 Initialize () 函数授权 /// /// 插件验证码 public CqApi (int authCode) { this._authCode = authCode; + this._defaultEncoding = Encoding.GetEncoding ("GB18030"); } #endregion @@ -237,8 +239,9 @@ public string CqCode_Record (string filePath) /// 消息内容 public int SendGroupMessage (long groupId, string message) { - return CQP.CQ_sendGroupMsg (_authCode, groupId, NativeConvert.ToStringPtr (message, Encoding.GetEncoding ("GB18030"))); + return CQP.CQ_sendGroupMsg (_authCode, groupId, message.ToIntPtr (_defaultEncoding)); } + /// /// 发送私聊消息 /// @@ -247,8 +250,9 @@ public int SendGroupMessage (long groupId, string message) /// public int SendPrivateMessage (long qqId, string message) { - return CQP.CQ_sendPrivateMsg (_authCode, qqId, NativeConvert.ToStringPtr (message, Encoding.GetEncoding ("GB18030"))); + return CQP.CQ_sendPrivateMsg (_authCode, qqId, message.ToIntPtr (_defaultEncoding)); } + /// /// 发送讨论组消息 /// @@ -257,8 +261,9 @@ public int SendPrivateMessage (long qqId, string message) /// public int SendDiscussMessage (long discussId, string message) { - return CQP.CQ_sendDiscussMsg (_authCode, discussId, NativeConvert.ToStringPtr (message, Encoding.GetEncoding ("GB18030"))); + return CQP.CQ_sendDiscussMsg (_authCode, discussId, message.ToIntPtr (_defaultEncoding)); } + /// /// 发送赞 /// @@ -269,6 +274,7 @@ public int SendPraise (long qqId, int count = 1) { return CQP.CQ_sendLikeV2 (_authCode, qqId, (count <= 0 || count > 10) ? 1 : count); } + /// /// 接收消息中的语音(record),返回语音文件绝对路径 /// @@ -280,6 +286,7 @@ public string ReceiveRecord (string fileName, AudioOutFormat formatType) //return CQP.CQ_getRecord (_authCode, fileName, formatType.ToString ()); return CQP.CQ_getRecordV2 (_authCode, fileName, formatType.ToString ()); } + /// /// 接收消息中的图片(image),返回图片文件绝对路径 /// @@ -289,6 +296,7 @@ public string ReceiveImage (string fileName) { return CQP.CQ_getImage (_authCode, fileName); } + /// /// 撤回消息 /// @@ -309,14 +317,16 @@ public long GetLoginQQ () { return CQP.CQ_getLoginQQ (_authCode); } + /// /// 获取当前登录QQ的昵称 /// /// public string GetLoginNick () { - return NativeConvert.ToPtrString (CQP.CQ_getLoginNick (_authCode)); + return CQP.CQ_getLoginNick (_authCode).ToString (_defaultEncoding); } + /// /// 取应用目录 /// @@ -329,6 +339,7 @@ public string GetAppDirectory () } return _appDirCache; } + /// /// 获取Cookies 慎用,此接口需要严格授权 /// @@ -337,6 +348,7 @@ public string GetCookies () { return CQP.CQ_getCookies (_authCode); } + /// /// 即QQ网页用到的bkn/g_tk等 慎用,此接口需要严格授权 /// @@ -345,6 +357,7 @@ public int GetCsrfToken () { return CQP.CQ_getCsrfToken (_authCode); } + /// /// 获取QQ信息 /// @@ -360,14 +373,15 @@ public int GetQQInfo (long qqId, out QQ qqInfo, bool notCache = false) qqInfo = null; return -1000; } - UnPack unpack = new UnPack (Convert.FromBase64String (result)); + BinaryReader binary = new BinaryReader (new MemoryStream (Convert.FromBase64String (result))); qqInfo = new QQ (); - qqInfo.Id = unpack.GetInt64 (); - qqInfo.Nick = unpack.GetString (Encoding.GetEncoding ("GB18030")); - qqInfo.Sex = (Sex)unpack.GetInt32 (); - qqInfo.Age = unpack.GetInt32 (); + qqInfo.Id = binary.ReadInt64_Ex (); + qqInfo.Nick = binary.ReadString_Ex (_defaultEncoding); + qqInfo.Sex = (Sex)binary.ReadInt32_Ex (); + qqInfo.Age = binary.ReadInt32_Ex (); return 0; } + /// /// 获取群成员信息 /// @@ -385,27 +399,28 @@ public int GetMemberInfo (long groupId, long qqId, out GroupMember member, bool return -1000; } #region --其它_转换_文本到群成员信息-- + BinaryReader binary = new BinaryReader (new MemoryStream (Convert.FromBase64String (result))); member = new GroupMember (); - UnPack unpack = new UnPack (Convert.FromBase64String (result)); - member.GroupId = unpack.GetInt64 (); - member.QQId = unpack.GetInt64 (); - member.Nick = unpack.GetString (Encoding.GetEncoding ("GB18030")); - member.Card = unpack.GetString (Encoding.GetEncoding ("GB18030")); - member.Sex = (Sex)unpack.GetInt32 (); - member.Age = unpack.GetInt32 (); - member.Area = unpack.GetString (Encoding.GetEncoding ("GB18030")); - member.JoiningTime = NativeConvert.FotmatUnixTime (unpack.GetInt32 ().ToString ()); - member.LastDateTime = NativeConvert.FotmatUnixTime (unpack.GetInt32 ().ToString ()); - member.Level = unpack.GetString (Encoding.GetEncoding ("GB18030")); - member.PermitType = (PermitType)unpack.GetInt32 (); - member.BadRecord = unpack.GetInt32 () == 1; - member.SpecialTitle = unpack.GetString (Encoding.GetEncoding ("GB18030")); - member.SpecialTitleDurationTime = NativeConvert.FotmatUnixTime (unpack.GetInt32 ().ToString ()); - member.CanModifiedCard = unpack.GetInt32 () == 1; + member.GroupId = binary.ReadInt64_Ex (); + member.QQId = binary.ReadInt64_Ex (); + member.Nick = binary.ReadString_Ex (_defaultEncoding); + member.Card = binary.ReadString_Ex (_defaultEncoding); + member.Sex = (Sex)binary.ReadInt32_Ex (); + member.Age = binary.ReadInt32_Ex (); + member.Area = binary.ReadString_Ex (_defaultEncoding); + member.JoiningTime = binary.ReadInt32_Ex ().ToDateTime (); + member.LastDateTime = binary.ReadInt32_Ex ().ToDateTime (); + member.Level = binary.ReadString_Ex (_defaultEncoding); + member.PermitType = (PermitType)binary.ReadInt32_Ex (); + member.BadRecord = binary.ReadInt32_Ex () == 1; + member.SpecialTitle = binary.ReadString_Ex (_defaultEncoding); + member.SpecialTitleDurationTime = binary.ReadInt32_Ex ().ToDateTime (); + member.CanModifiedCard = binary.ReadInt32_Ex () == 1; #endregion return 0; } + /// /// 获取群成员列表 /// @@ -421,43 +436,44 @@ public int GetMemberList (long groupId, out List memberInfos) return -1000; } #region --其他_转换_文本到群成员列表信息a-- - UnPack unpack = new UnPack (Convert.FromBase64String (result)); + BinaryReader binary = new BinaryReader (new MemoryStream (Convert.FromBase64String (result))); memberInfos = new List (); - for (int i = 0, len = unpack.GetInt32 (); i < len; i++) + for (int i = 0, len = binary.ReadInt32_Ex (); i < len; i++) { - if (unpack.OverLength <= 0) + if (binary.Length () <= 0) { memberInfos = null; return -1000; } #region --其它_转换_ansihex到群成员信息-- - UnPack temp = new UnPack (unpack.GetToken ()); //解析群成员信息 + BinaryReader tempBinary = new BinaryReader (new MemoryStream (binary.ReadToken_Ex ())); //解析群成员信息 GroupMember member = new GroupMember (); - member.GroupId = temp.GetInt64 (); - member.QQId = temp.GetInt64 (); - member.Nick = temp.GetString (Encoding.GetEncoding ("GB18030")); - member.Card = temp.GetString (Encoding.GetEncoding ("GB18030")); - member.Sex = (Sex)temp.GetInt32 (); - member.Age = temp.GetInt32 (); - member.Area = temp.GetString (Encoding.GetEncoding ("GB18030")); - member.JoiningTime = NativeConvert.FotmatUnixTime (temp.GetInt32 ().ToString ()); - member.LastDateTime = NativeConvert.FotmatUnixTime (temp.GetInt32 ().ToString ()); - member.Level = temp.GetString (Encoding.GetEncoding ("GB18030")); - member.PermitType = (PermitType)temp.GetInt32 (); - member.BadRecord = temp.GetInt32 () == 1; - member.SpecialTitle = temp.GetString (Encoding.GetEncoding ("GB18030")); - member.SpecialTitleDurationTime = NativeConvert.FotmatUnixTime (temp.GetInt32 ().ToString ()); - member.CanModifiedCard = temp.GetInt32 () == 1; + member.GroupId = tempBinary.ReadInt64_Ex (); + member.QQId = tempBinary.ReadInt64_Ex (); + member.Nick = tempBinary.ReadString_Ex (_defaultEncoding); + member.Card = tempBinary.ReadString_Ex (_defaultEncoding); + member.Sex = (Sex)tempBinary.ReadInt32_Ex (); + member.Age = tempBinary.ReadInt32_Ex (); + member.Area = tempBinary.ReadString_Ex (_defaultEncoding); + member.JoiningTime = tempBinary.ReadInt32_Ex ().ToDateTime (); + member.LastDateTime = tempBinary.ReadInt32_Ex ().ToDateTime (); + member.Level = tempBinary.ReadString_Ex (_defaultEncoding); + member.PermitType = (PermitType)tempBinary.ReadInt32_Ex (); + member.BadRecord = tempBinary.ReadInt32_Ex () == 1; + member.SpecialTitle = tempBinary.ReadString_Ex (_defaultEncoding); + member.SpecialTitleDurationTime = binary.ReadInt32_Ex ().ToDateTime (); + member.CanModifiedCard = tempBinary.ReadInt32_Ex () == 1; #endregion memberInfos.Add (member); } #endregion return 0; } + /// /// 获取群列表 /// - /// + /// 返回的群列表 /// public int GetGroupList (out List groups) { @@ -469,25 +485,26 @@ public int GetGroupList (out List groups) } groups = new List (); #region --其他_转换_文本到群列表信息a-- - UnPack unpack = new UnPack (Convert.FromBase64String (result)); - for (int i = 0, len = unpack.GetInt32 (); i < len; i++) + BinaryReader binary = new BinaryReader (new MemoryStream (Convert.FromBase64String (result))); + for (int i = 0, len = binary.ReadInt32_Ex (); i < len; i++) { - if (unpack.OverLength <= 0) + if (binary.Length () <= 0) { groups = null; return -1000; } #region --其他_转换_ansihex到群信息-- - UnPack temp = new UnPack (unpack.GetToken ()); + BinaryReader tempBinary = new BinaryReader (new MemoryStream (binary.ReadToken_Ex ())); Group group = new Group (); - group.Id = temp.GetInt64 (); - group.Name = temp.GetString (Encoding.GetEncoding ("GB18030")); + group.Id = tempBinary.ReadInt64_Ex (); + group.Name = tempBinary.ReadString_Ex (_defaultEncoding); groups.Add (group); #endregion } #endregion return 0; } + /// /// 获取发送语音支持 /// @@ -496,6 +513,7 @@ public bool GetSendRecordSupport () { return CQP.CQ_canSendRecord (_authCode) > 0; } + /// /// 获取发送图片支持 /// @@ -516,8 +534,9 @@ public bool GetSendImageSupport () /// public int AddLoger (LogerLevel level, string type, string content) { - return CQP.CQ_addLog (_authCode, (int)level, type, NativeConvert.ToStringPtr (content, Encoding.GetEncoding ("GB18030"))); + return CQP.CQ_addLog (_authCode, (int)level, type, content.ToIntPtr (_defaultEncoding)); } + /// /// 添加致命错误提示 /// @@ -543,8 +562,9 @@ public int SetFriendAddRequest (string tag, ResponseType response, string append { appendMsg = string.Empty; } - return CQP.CQ_setFriendAddRequest (_authCode, tag, (int)response, NativeConvert.ToStringPtr (appendMsg, Encoding.GetEncoding ("GB18030"))); + return CQP.CQ_setFriendAddRequest (_authCode, tag, (int)response, appendMsg.ToIntPtr (_defaultEncoding)); } + /// /// 置群添加请求 /// @@ -559,7 +579,7 @@ public int SetGroupAddRequest (string tag, RequestType request, ResponseType res { appendMsg = string.Empty; } - return CQP.CQ_setGroupAddRequestV2 (_authCode, tag, (int)request, (int)response, NativeConvert.ToStringPtr (appendMsg, Encoding.GetEncoding ("GB18030"))); + return CQP.CQ_setGroupAddRequestV2 (_authCode, tag, (int)request, (int)response, appendMsg.ToIntPtr (_defaultEncoding)); } #endregion @@ -578,8 +598,9 @@ public int SetGroupAnonymousBanSpeak (long groupId, string anonymous, TimeSpan t time = TimeSpan.Zero; } - return CQP.CQ_setGroupAnonymousBan (_authCode, groupId, NativeConvert.ToStringPtr (anonymous, Encoding.GetEncoding ("GB18030")), (long)time.TotalSeconds); + return CQP.CQ_setGroupAnonymousBan (_authCode, groupId, anonymous.ToIntPtr (_defaultEncoding), (long)time.TotalSeconds); } + /// /// 置群员禁言 /// @@ -595,6 +616,7 @@ public int SetGroupBanSpeak (long groupId, long qqId, TimeSpan time) } return CQP.CQ_setGroupBan (_authCode, groupId, qqId, (long)time.TotalSeconds); } + /// /// 置全群禁言 /// @@ -605,6 +627,7 @@ public int SetGroupWholeBanSpeak (long groupId, bool isOpen) { return CQP.CQ_setGroupWholeBan (_authCode, groupId, isOpen); } + /// /// 置群成员名片 /// @@ -614,8 +637,9 @@ public int SetGroupWholeBanSpeak (long groupId, bool isOpen) /// public int SetGroupMemberNewCard (long groupId, long qqId, string newNick) { - return CQP.CQ_setGroupCard (_authCode, groupId, qqId, NativeConvert.ToStringPtr (newNick, Encoding.GetEncoding ("GB18030"))); + return CQP.CQ_setGroupCard (_authCode, groupId, qqId, newNick.ToIntPtr (_defaultEncoding)); } + /// /// 置群成员专属头衔 /// @@ -630,8 +654,9 @@ public int SetGroupSpecialTitle (long groupId, long qqId, string specialTitle, T { time = new TimeSpan (-10000000); //-1秒 } - return CQP.CQ_setGroupSpecialTitle (_authCode, groupId, qqId, NativeConvert.ToStringPtr (specialTitle, Encoding.GetEncoding ("GB18030")), (long)time.TotalSeconds); + return CQP.CQ_setGroupSpecialTitle (_authCode, groupId, qqId, specialTitle.ToIntPtr (_defaultEncoding), (long)time.TotalSeconds); } + /// /// 置群管理员 /// @@ -643,6 +668,7 @@ public int SetGroupManager (long groupId, long qqId, bool isCalcel) { return CQP.CQ_setGroupAdmin (_authCode, groupId, qqId, isCalcel); } + /// /// 置群匿名设置 /// @@ -653,6 +679,7 @@ public int SetAnonymousStatus (long groupId, bool isOpen) { return CQP.CQ_setGroupAnonymous (_authCode, groupId, isOpen); } + /// /// 置群退出 慎用,此接口需要严格授权 /// @@ -663,6 +690,7 @@ public int SetGroupExit (long groupId, bool dissolve = false) { return CQP.CQ_setGroupLeave (_authCode, groupId, dissolve); } + /// /// 置群员移除 /// @@ -674,6 +702,7 @@ public int SetGroupMemberRemove (long groupId, long qqId, bool notAccept = false { return CQP.CQ_setGroupKick (_authCode, groupId, qqId, notAccept); } + /// /// 置讨论组退出 /// @@ -695,6 +724,7 @@ public void SetAuthCode (int authCode) { _authCode = authCode; } + /// /// 获取App验证码 /// @@ -704,6 +734,7 @@ public int GetAuthCode () { return _authCode; } + /// /// 获取匿名信息 /// @@ -711,13 +742,14 @@ public int GetAuthCode () /// public GroupAnonymous GetAnonymous (string source) { - UnPack unPack = new UnPack (Convert.FromBase64String (source)); + BinaryReader binary = new BinaryReader (new MemoryStream (Convert.FromBase64String (source))); GroupAnonymous anonymous = new GroupAnonymous (); - anonymous.Id = unPack.GetInt64 (); - anonymous.CodeName = unPack.GetString (Encoding.GetEncoding ("GB18030")); - anonymous.Token = unPack.GetToken (); + anonymous.Id = binary.ReadInt64_Ex (); + anonymous.CodeName = binary.ReadString_Ex (); + anonymous.Token = binary.ReadToken_Ex (); return anonymous; } + /// /// 获取群文件 /// @@ -725,14 +757,15 @@ public GroupAnonymous GetAnonymous (string source) /// public GroupFile GetFile (string source) { - UnPack unPack = new UnPack (Convert.FromBase64String (source)); + BinaryReader binary = new BinaryReader (new MemoryStream (Convert.FromBase64String (source))); GroupFile file = new GroupFile (); - file.Id = unPack.GetString (Encoding.GetEncoding ("GB18030")); - file.Name = unPack.GetString (Encoding.GetEncoding ("GB18030")); - file.Size = unPack.GetInt64 (); - file.Busid = Convert.ToInt32 (unPack.GetInt64 ()); + file.Id = binary.ReadString_Ex (); // 参照官方SDK, 编码为 ASCII + file.Name = binary.ReadString_Ex (); // 参照官方SDK, 编码为 ASCII + file.Size = binary.ReadInt64_Ex (); + file.Busid = Convert.ToInt32 (binary.ReadInt64_Ex ()); return file; } + /// /// 编码悬浮窗数据置文本 /// @@ -740,11 +773,12 @@ public GroupFile GetFile (string source) /// public string FormatStringFloatWindow (FloatWindow floatWindow) { - Pack pack = new Pack (); - pack.SetLenString (floatWindow.Data); - pack.SetLenString (floatWindow.Unit); - pack.SetInt32 ((int)floatWindow.Color); - return Convert.ToBase64String (pack.GetAll ()); + BinaryWriter binary = new BinaryWriter (new MemoryStream ()); + binary.Write_Ex (floatWindow.Data); + binary.Write_Ex (floatWindow.Unit); + binary.Write_Ex ((int)floatWindow.Color); + + return Convert.ToBase64String (binary.ToArray ()); } #endregion } diff --git a/Native.Csharp.Sdk/Cqp/Other/BinaryReaderExpand.cs b/Native.Csharp.Sdk/Cqp/Other/BinaryReaderExpand.cs new file mode 100644 index 00000000..ca50d6e7 --- /dev/null +++ b/Native.Csharp.Sdk/Cqp/Other/BinaryReaderExpand.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +namespace Native.Csharp.Sdk.Cqp.Other +{ + /// + /// 类的扩展方法集 + /// + public static class BinaryReaderExpand + { + #region --公开方法-- + /// + /// 获取基础流的剩余长度 + /// + /// + /// + public static long Length (this BinaryReader binary) + { + return binary.BaseStream.Length - binary.BaseStream.Position; + } + + /// + /// 从字节数组中的指定点开始,从流中读取所有字节。 + /// + /// 基础 对象 + /// 读入 buffer 的字节数。 如果可用的字节没有请求的那么多,此数可能小于所请求的字节数;如果到达了流的末尾,此数可能为零。 + /// 流已关闭。 + /// 出现 I/O 错误。 + public static byte[] ReadAll_Ex (this BinaryReader binary) + { + return GetBinary (binary, binary.BaseStream.Length, false); + } + + /// + /// 从字节数组中的指定点开始,从流中读取指定字节长度。 + /// + /// 基础 对象 + /// 要读取的字节数。 + /// 读入 buffer 的字节数。 如果可用的字节没有请求的那么多,此数可能小于所请求的字节数;如果到达了流的末尾,此数可能为零。 + /// len 为负数。 + /// 已解码要读取的字符数超过了边界。 + /// 流已关闭。 + /// 出现 I/O 错误。 + public static byte[] ReadBin_Ex (this BinaryReader binary, long len) + { + return GetBinary (binary, len); + } + + /// + /// 从字节数组中的指定点开始,从流中读取 2 字节长度并反序为 值。 + /// + /// 基础 对象 + /// 读入 2 字节的结果值,如果可用的字节没有那么多,此数可能小于所请求的字节数;如果到达了流的末尾,此数可能为零。 + /// 已解码要读取的字符数超过了边界。 + /// 流已关闭。 + /// 出现 I/O 错误。 + public static short ReadInt16_Ex (this BinaryReader binary) + { + return BitConverter.ToInt16 (GetBinary (binary, 2, true), 0); + } + + /// + /// 从字节数组中的指定点开始,从流中读取 4 字节长度并反序为 值。 + /// + /// 基础 对象 + /// 读入 4 字节的结果值,如果可用的字节没有那么多,此数可能小于所请求的字节数;如果到达了流的末尾,此数可能为零。 + /// 已解码要读取的字符数超过了边界。 + /// 流已关闭。 + /// 出现 I/O 错误。 + public static int ReadInt32_Ex (this BinaryReader binary) + { + return BitConverter.ToInt32 (GetBinary (binary, 4, true), 0); + } + + /// + /// 从字节数组中的指定点开始,从流中读取 8 字节长度并反序为 值。 + /// + /// 基础 对象 + /// 读入 8 字节的结果值,如果可用的字节没有那么多,此数可能小于所请求的字节数;如果到达了流的末尾,此数可能为零。 + /// 已解码要读取的字符数超过了边界。 + /// 流已关闭。 + /// 出现 I/O 错误。 + public static long ReadInt64_Ex (this BinaryReader binary) + { + return BitConverter.ToInt64 (GetBinary (binary, 8, true), 0); + } + + /// + /// 从字节数组中的指定点开始,从流中读取指定字节长度。 + /// + /// 基础 对象 + /// 读入 buffer 的字节数。 如果可用的字节没有请求的那么多,此数可能小于所请求的字节数;如果到达了流的末尾,此数可能为零。 + /// len 为负数。 + /// 已解码要读取的字符数超过了边界。 + /// 流已关闭。 + /// 出现 I/O 错误。 + public static byte[] ReadToken_Ex (this BinaryReader binary) + { + short len = ReadInt16_Ex (binary); + return GetBinary (binary, len); + } + + /// + /// 从字节数组中的指定点开始,从流中读取指定编码的字符串。 + /// + /// 基础 对象 + /// + /// 读入 buffer 的字节数。 如果可用的字节没有请求的那么多,此数可能小于所请求的字节数;如果到达了流的末尾,此数可能为零。 + /// len 为负数。 + /// 已解码要读取的字符数超过了边界。 + /// 流已关闭。 + /// 出现 I/O 错误。 + public static string ReadString_Ex (this BinaryReader binary, Encoding encoding = null) + { + if (encoding == null) + { + encoding = Encoding.ASCII; + } + + return encoding.GetString (ReadToken_Ex (binary)); + } + #endregion + + #region --私有方法-- + private static byte[] GetBinary (BinaryReader binary, long len, bool isReverse = false) + { + byte[] buffer = new byte[len]; + binary.Read (buffer, 0, buffer.Length); + if (isReverse) + { + buffer = buffer.Reverse ().ToArray (); + } + return buffer; + } + #endregion + } +} diff --git a/Native.Csharp.Sdk/Cqp/Other/BinaryWriterExpand.cs b/Native.Csharp.Sdk/Cqp/Other/BinaryWriterExpand.cs new file mode 100644 index 00000000..bdc378eb --- /dev/null +++ b/Native.Csharp.Sdk/Cqp/Other/BinaryWriterExpand.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +namespace Native.Csharp.Sdk.Cqp.Other +{ + /// + /// 类的扩展方法集 + /// + public static class BinaryWriterExpand + { + #region --公开方法-- + /// + /// 将写入基础流的 数值 + /// + /// 基础 对象 + /// 要写入的值 + /// 出现 I/O 错误。 + /// 流已关闭。 + /// buffer 为 null。 + public static void Write_Ex (this BinaryWriter binary, short value) + { + SetBinary (binary, BitConverter.GetBytes (value), true); + } + + /// + /// 将写入基础流的 数值 + /// + /// 基础 对象 + /// 要写入的值 + /// 出现 I/O 错误。 + /// 流已关闭。 + /// buffer 为 null。 + public static void Write_Ex (this BinaryWriter binary, int value) + { + SetBinary (binary, BitConverter.GetBytes (value), true); + } + + /// + /// 将写入基础流的 数值 + /// + /// 基础 对象 + /// 要写入的值 + /// 出现 I/O 错误。 + /// 流已关闭。 + /// buffer 为 null。 + public static void Write_Ex (this BinaryWriter binary, long value) + { + SetBinary (binary, BitConverter.GetBytes (value), true); + } + + /// + /// 将写入基础流的 数值 + /// + /// 基础 对象 + /// 要写入的值 + /// 出现 I/O 错误。 + /// 流已关闭。 + /// buffer 为 null。 + public static void Write_Ex (this BinaryWriter binary, string value) + { + byte[] buffer = Encoding.Default.GetBytes (value); + Write_Ex (binary, (short)buffer.Length); + SetBinary (binary, buffer, false); + } + + /// + /// 将基础流转换为相同的字节数组 + /// + /// 基础 对象 + public static byte[] ToArray (this BinaryWriter binary) + { + long position = binary.BaseStream.Position; // 记录原指针位置 + + byte[] buffer = new byte[binary.BaseStream.Length]; + binary.BaseStream.Position = 0; // 设置读取位置为 0 + binary.BaseStream.Read (buffer, 0, buffer.Length); + + binary.BaseStream.Position = position; // 还原原指针位置 + return buffer; + } + #endregion + + #region --私有方法-- + private static void SetBinary (BinaryWriter binary, byte[] buffer, bool isReverse) + { + if (isReverse) + { + buffer = buffer.Reverse ().ToArray (); + } + binary.Write (buffer); + } + #endregion + } +} diff --git a/Native.Csharp.Sdk/Cqp/Other/OtherExpand.cs b/Native.Csharp.Sdk/Cqp/Other/OtherExpand.cs new file mode 100644 index 00000000..f51fe2fd --- /dev/null +++ b/Native.Csharp.Sdk/Cqp/Other/OtherExpand.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace Native.Csharp.Sdk.Cqp.Other +{ + /// + /// 其它类扩展方法集 + /// + public static class OtherExpand + { + #region --Kernel32-- + [DllImport ("kernel32.dll", EntryPoint = "lstrlenA", CharSet = CharSet.Ansi)] + internal extern static int LstrlenA (IntPtr ptr); + #endregion + + /// + /// 获取 Unix 时间戳的 表示形式 + /// + /// unix 时间戳 + /// + public static DateTime ToDateTime (this long unixTime) + { + DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime (new DateTime (1970, 1, 1)); + TimeSpan toNow = new TimeSpan (unixTime); + DateTime daTime = dtStart.Add (toNow); + return daTime; + } + + /// + /// 获取 Unix 时间戳的 表示形式 + /// + /// unix 时间戳 + /// + public static DateTime ToDateTime (this int unixTime) + { + DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime (new DateTime (1970, 1, 1)); + TimeSpan toNow = new TimeSpan (unixTime); + DateTime daTime = dtStart.Add (toNow); + return daTime; + } + + /// + /// 转换字符串的 实例对象 + /// + /// 将转换的字符串 + /// 目标编码格式 + /// + public static IntPtr ToIntPtr (this string source, Encoding encoding = null) + { + if (encoding == null) + { + encoding = Encoding.ASCII; + } + byte[] buffer = encoding.GetBytes (source); + GCHandle hobj = GCHandle.Alloc (buffer, GCHandleType.Pinned); + return hobj.AddrOfPinnedObject (); + } + + /// + /// 读取指针内所有的字节数组并编码为指定字符串 + /// + /// 字符串的 对象 + /// 目标编码格式 + /// + public static string ToString (this IntPtr strPtr, Encoding encoding = null) + { + if (encoding == null) + { + encoding = Encoding.Default; + } + + int len = LstrlenA (strPtr); //获取指针中数据的长度 + if (len == 0) + { + return string.Empty; + } + + byte[] buffer = new byte[len]; + Marshal.Copy (strPtr, buffer, 0, len); + return encoding.GetString (buffer); + } + } +} diff --git a/Native.Csharp.Sdk/Cqp/Other/Pack.cs b/Native.Csharp.Sdk/Cqp/Other/Pack.cs deleted file mode 100644 index 175ab0e6..00000000 --- a/Native.Csharp.Sdk/Cqp/Other/Pack.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Native.Csharp.Sdk.Cqp.Other -{ - /// - /// 装包类 - /// - public class Pack - { - #region --字段-- - private readonly List _bytes = null; - #endregion - - #region --属性-- - /// - /// 获取当前字节集长度 - /// - public int Length - { - get { return this._bytes.Count; } - } - #endregion - - #region --构造函数-- - public Pack() - { - _bytes = new List(); - } - #endregion - - #region --公开方法-- - /// - /// 取全部数据 - /// - /// - public byte[] GetAll() - { return this._bytes.ToArray(); } - /// - /// 置byte[] - /// - /// - public void SetBin(byte[] arr) - { - this._bytes.AddRange(arr); - } - /// - /// 置字节 - /// - /// - public void SetByte(byte value) - { - this._bytes.Add(value); - } - /// - /// 置类数据 - /// - /// - public void SetData(byte[] data) - { - this._bytes.Clear(); - this._bytes.AddRange(data); - - } - /// - /// 置Int16值 - /// - /// - public void SetInt16(short value) - { - this._bytes.AddRange(BitConverter.GetBytes(value)); - } - /// - /// 置Int32值 - /// - /// - public void SetInt32(int value) - { - this._bytes.AddRange(BitConverter.GetBytes(value)); - } - /// - /// 置Int64值 - /// - /// - public void SetInt64(long value) - { - this._bytes.AddRange(BitConverter.GetBytes(value)); - } - /// - /// 置字符串 - /// - /// 欲设置字符串 - /// 解码格式 - public void SetString(string str, Encoding decode = null) - { - if (decode == null) - { - decode = Encoding.Default; - } - this._bytes.AddRange(decode.GetBytes(str)); - } - /// - /// 置令牌字符串 - /// - /// - /// - public void SetLenString(string str, Encoding decode = null) - { - if (decode == null) - { - decode = Encoding.Default; - } - SetToken(decode.GetBytes(str)); - } - /// - /// 置令牌 - /// - /// - public void SetToken(byte[] buf) - { - SetInt16(Convert.ToInt16(buf.Length)); - SetBin(buf); - } - #endregion - } -} diff --git a/Native.Csharp.Sdk/Cqp/Other/UnPack.cs b/Native.Csharp.Sdk/Cqp/Other/UnPack.cs deleted file mode 100644 index 417702ea..00000000 --- a/Native.Csharp.Sdk/Cqp/Other/UnPack.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Native.Csharp.Sdk.Cqp.Other -{ - /// - /// 拆包类 - /// - public class UnPack - { - #region --字段-- - private readonly byte[] _bytes = null; - private int _index = 0; - #endregion - - #region --属性-- - /// - /// 获取当前解包数据的剩余长度 - /// - public int OverLength - { - get - { - return this._bytes.Length - this._index + 1; - } - } - #endregion - - #region --构造函数-- - /// - /// 初始化 Native.Sdk.Cqp.Tool.Unpack 实例对象 - /// - /// 预处理的 byte[] - public UnPack(byte[] data) - { - this._bytes = data; - } - #endregion - - #region --公开方法-- - /// - /// 获取剩余所有数据 - /// - /// - public byte[] GetAll() - { - return GetData(this._bytes.Length - this._index); - } - /// - /// 获取指定长度 byte[] - /// - /// 欲获取数据的长度 - /// - public byte[] GetBin(int len) - { - return this.GetData(len); - } - /// - /// 获取一个 byte - /// - /// - public byte GetByte() - { - return this.GetData(1)[0]; - } - /// - /// 获取一个 Int16 数据 - /// - /// - public short GetInt16() - { - return BitConverter.ToInt16(this.GetData(2, true), 0); - } - /// - /// 获取一个 Int32 数据 - /// - /// - public int GetInt32() - { - return BitConverter.ToInt32(this.GetData(4, true), 0); - } - /// - /// 获取一个 Int64 数据 - /// - /// - public long GetInt64() - { - return BitConverter.ToInt64(this.GetData(8, true), 0); - } - /// - /// 获取一个 String 数据 - /// - /// 编码格式, 默认: Default - /// - public string GetString(Encoding code = null) - { - if (code == null) - { - code = Encoding.Default; - } - short len = this.GetInt16(); - return code.GetString(this.GetData(len)); - } - /// - /// 获取令牌 - /// - /// - public byte[] GetToken() - { - short len = this.GetInt16(); - return GetBin(len); - } - #endregion - - #region --私有方法-- - /// - /// 获取指定位数的 byte[], 并把游标向后移动指定长度 - /// - /// 长度 - /// 是否反转, 默认: False - /// - private byte[] GetData(int len, bool isReverse = false) - { - byte[] temp = new byte[len]; - Buffer.BlockCopy(_bytes, _index, temp, 0, len); - _index += len; - return isReverse == true ? temp.Reverse().ToArray() : temp; - } - #endregion - } -} diff --git a/Native.Csharp.Sdk/Native.Csharp.Sdk.csproj b/Native.Csharp.Sdk/Native.Csharp.Sdk.csproj index 110c4502..26d51524 100644 --- a/Native.Csharp.Sdk/Native.Csharp.Sdk.csproj +++ b/Native.Csharp.Sdk/Native.Csharp.Sdk.csproj @@ -60,15 +60,10 @@ - - + + + - - - {9db94cbf-6843-4ea3-9241-769124416fe9} - Native.Csharp.Tool - - \ No newline at end of file diff --git a/Native.Csharp.Sdk/Properties/AssemblyInfo.cs b/Native.Csharp.Sdk/Properties/AssemblyInfo.cs index c046c9a5..dc42f9f7 100644 --- a/Native.Csharp.Sdk/Properties/AssemblyInfo.cs +++ b/Native.Csharp.Sdk/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 //通过使用 "*",如下所示: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion ("3.0.6.0525")] -[assembly: AssemblyFileVersion ("3.0.6.0525")] \ No newline at end of file +[assembly: AssemblyVersion ("3.0.7.0607")] +[assembly: AssemblyFileVersion ("3.0.7.0607")] \ No newline at end of file diff --git a/Native.Csharp.Tool/Core/Kernel32.cs b/Native.Csharp.Tool/Core/Kernel32.cs deleted file mode 100644 index 75967d49..00000000 --- a/Native.Csharp.Tool/Core/Kernel32.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; - -namespace Native.Csharp.Tool.Core -{ - internal static class Kernel32 - { - #region --常量-- - private const string DllName = "kernel32.dll"; - #endregion - - [DllImport (DllName)] - internal extern static int GetPrivateProfileIntA (string segName, string keyName, int iDefault, string fileName); - - [DllImport (DllName)] - internal extern static int GetPrivateProfileStringA (string segName, string keyName, string sDefault, StringBuilder buffer, int nSize, string fileName); - - [DllImport (DllName)] - internal extern static int GetPrivateProfileSectionA (string segName, StringBuilder buffer, int nSize, string fileName); - - [DllImport (DllName)] - internal extern static int GetPrivateProfileSectionNamesA (byte[] buffer, int iLen, string fileName); - - [DllImport (DllName)] - internal extern static int WritePrivateProfileSectionA (string segName, string sValue, string fileName); - - [DllImport (DllName)] - internal extern static int WritePrivateProfileStringA (string segName, string keyName, string sValue, string fileName); - - [DllImport (DllName, EntryPoint = "lstrlenA", CharSet = CharSet.Ansi)] - internal extern static int LstrlenA (IntPtr ptr); - } -} diff --git a/Native.Csharp.Tool/Http/HttpHelper.cs b/Native.Csharp.Tool/Http/HttpHelper.cs deleted file mode 100644 index 65e760ba..00000000 --- a/Native.Csharp.Tool/Http/HttpHelper.cs +++ /dev/null @@ -1,424 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net; -using System.Net.Security; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Web; - -namespace Native.Csharp.Tool.Http -{ - /** - * Http访问的操作类来自 Flexlive.CQP.Framework 框架. 若有侵权请联系我删除重写 - */ - /// - /// Http访问的操作类 - /// - public static class HttpHelper - { - /// - /// 向HTTP服务器发送Get请求 - /// - /// 请求地址 - /// 参照页 - /// User-Agent HTTP标头 - /// Accept HTTP标头 - /// 超时时间 - /// HTTP 标头 - /// Cookies - /// 文本编码 - /// 加密方式 - /// - [Obsolete("请使用 HttpWebClient")] - public static string Get(string url, string referer, string userAgent, string accept, int timeout, WebHeaderCollection header, CookieCollection cookies, Encoding encoding, DecompressionMethods decompression = DecompressionMethods.None) - { - string result = string.Empty; - try - { - HttpWebRequest httpWebRequest = null; - if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase)) - { - ServicePointManager.ServerCertificateValidationCallback = CheckValidationResult; - httpWebRequest = (HttpWebRequest)WebRequest.Create(new Uri(url)); - httpWebRequest.ProtocolVersion = HttpVersion.Version10; - } - else - { - httpWebRequest = (HttpWebRequest)WebRequest.Create(new Uri(url)); - } - httpWebRequest.UserAgent = userAgent; - httpWebRequest.Referer = referer; - httpWebRequest.Method = "GET"; - httpWebRequest.Timeout = timeout; - httpWebRequest.Accept = accept; - if (cookies != null) - { - httpWebRequest.CookieContainer = new CookieContainer(); - httpWebRequest.CookieContainer.Add(cookies); - } - if (header != null) - { - httpWebRequest.Headers = header; - } - httpWebRequest.AutomaticDecompression = decompression; - HttpWebResponse webResponse = (HttpWebResponse)httpWebRequest.GetResponse(); - result = GetResponseString(webResponse, encoding); - return result; - } - catch - { - return result; - } - } - - /// - /// 向HTTP服务器发送Get请求 - /// - /// 请求地址 - /// 参照页 - /// HTTP 标头 - /// Cookies - /// 文本编码 - /// 加密方式 - /// - [Obsolete("请使用 HttpWebClient")] - public static string Get(string url, string referer, WebHeaderCollection header, CookieCollection cookies, Encoding encoding, DecompressionMethods decompression = DecompressionMethods.None) - { - return Get(url, referer, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 30000, header, cookies, encoding, decompression); - } - - /// - /// 向HTTP服务器发送Get请求 - /// - /// 请求地址 - /// 参照页 - /// Cookies - /// 文本编码 - /// 加密方式 - /// - [Obsolete("请使用 HttpWebClient")] - public static string Get(string url, string referer, CookieCollection cookies, Encoding encoding, DecompressionMethods decompression = DecompressionMethods.None) - { - return Get(url, referer, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 30000, null, cookies, encoding, decompression); - } - - /// - /// 向HTTP服务器发送Get请求 - /// - /// 请求地址 - /// 参照页 - /// Cookies - /// 加密方式 - /// - [Obsolete("请使用 HttpWebClient")] - public static string Get(string url, string referer, CookieCollection cookies, DecompressionMethods decompression = DecompressionMethods.None) - { - return Get(url, referer, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 30000, null, cookies, Encoding.UTF8, decompression); - } - - /// - /// 向HTTP服务器发送Get请求 - /// - /// 请求地址 - /// 参照页 - /// 文本编码 - /// 加密方式 - /// - [Obsolete("请使用 HttpWebClient")] - public static string Get(string url, string referer, Encoding encoding, DecompressionMethods decompression = DecompressionMethods.None) - { - return Get(url, referer, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 30000, null, null, encoding, decompression); - } - - /// - /// 向HTTP服务器发送Get请求 - /// - /// 请求地址 - /// 参照页 - /// 加密方式 - /// - [Obsolete("请使用 HttpWebClient")] - public static string Get(string url, string referer, DecompressionMethods decompression = DecompressionMethods.None) - { - return Get(url, referer, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 30000, null, null, Encoding.UTF8, decompression); - } - - /// - /// 向HTTP服务器发送Get请求 - /// - /// 请求地址 - /// 加密方式 - /// - [Obsolete("请使用 HttpWebClient")] - public static string Get(string url, DecompressionMethods decompression = DecompressionMethods.None) - { - return Get(url, "", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 30000, null, null, Encoding.UTF8, decompression); - } - - /// - /// 向HTTP服务器发送Post请求 - /// - /// 请求地址 - /// 请求参数 - /// 参照页 - /// User-Agent HTTP标头 - /// Accept HTTP标头 - /// 超时时间 - /// HTTP 标头 - /// Cookies - /// 文本编码 - /// 加密方式 - /// - [Obsolete("请使用 HttpWebClient")] - public static string Post(string url, Dictionary parameters, string referer, string userAgent, string accept, int timeout, WebHeaderCollection header, CookieCollection cookies, Encoding encoding, DecompressionMethods decompression = DecompressionMethods.None) - { - string result = string.Empty; - try - { - HttpWebRequest httpWebRequest = null; - if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase)) - { - ServicePointManager.ServerCertificateValidationCallback = CheckValidationResult; - httpWebRequest = (HttpWebRequest)WebRequest.Create(new Uri(url)); - httpWebRequest.ProtocolVersion = HttpVersion.Version10; - } - else - { - httpWebRequest = (HttpWebRequest)WebRequest.Create(new Uri(url)); - } - httpWebRequest.UserAgent = userAgent; - httpWebRequest.Referer = referer; - httpWebRequest.Method = "POST"; - httpWebRequest.Timeout = timeout; - httpWebRequest.Accept = accept; - if (cookies != null) - { - httpWebRequest.CookieContainer = new CookieContainer(); - httpWebRequest.CookieContainer.Add(cookies); - } - if (header != null) - { - httpWebRequest.Headers = header; - } - httpWebRequest.AutomaticDecompression = decompression; - httpWebRequest.ContentType = "application/x-www-form-urlencoded"; - if (parameters != null && parameters.Count > 0) - { - StringBuilder stringBuilder = new StringBuilder(); - int num = 0; - foreach (string key in parameters.Keys) - { - stringBuilder.AppendFormat("{0}={1}", key, parameters[key]); - if (num != parameters.Keys.Count - 1) - { - stringBuilder.Append("&"); - } - num++; - } - httpWebRequest.ContentLength = stringBuilder.ToString().Length; - byte[] bytes = Encoding.ASCII.GetBytes(stringBuilder.ToString()); - using (Stream stream = httpWebRequest.GetRequestStream()) - { - stream.Write(bytes, 0, bytes.Length); - } - } - HttpWebResponse webResponse = (HttpWebResponse)httpWebRequest.GetResponse(); - result = GetResponseString(webResponse, encoding); - return result; - } - catch - { - return result; - } - } - - /// - /// 向HTTP服务器发送Post请求 - /// - /// 请求地址 - /// 请求参数 - /// 参照页 - /// HTTP 标头 - /// Cookies - /// 文本编码 - /// 加密方式 - /// - [Obsolete("请使用 HttpWebClient")] - public static string Post(string url, Dictionary parameters, string referer, WebHeaderCollection header, CookieCollection cookies, Encoding encoding, DecompressionMethods decompression = DecompressionMethods.None) - { - return Post(url, parameters, referer, "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 30000, header, cookies, encoding, decompression); - } - - /// - /// 向HTTP服务器发送Post请求 - /// - /// 请求地址 - /// 请求参数 - /// 参照页 - /// Cookies - /// 文本编码 - /// 加密方式 - /// - [Obsolete("请使用 HttpWebClient")] - public static string Post(string url, Dictionary parameters, string referer, CookieCollection cookies, Encoding encoding, DecompressionMethods decompression = DecompressionMethods.None) - { - return Post(url, parameters, referer, "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 30000, null, cookies, encoding, decompression); - } - - /// - /// 向HTTP服务器发送Post请求 - /// - /// 请求地址 - /// 请求参数 - /// 参照页 - /// Cookies - /// 加密方式 - /// - [Obsolete("请使用 HttpWebClient")] - public static string Post(string url, Dictionary parameters, string referer, CookieCollection cookies, DecompressionMethods decompression = DecompressionMethods.None) - { - return Post(url, parameters, referer, "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 30000, null, cookies, Encoding.UTF8, decompression); - } - - /// - /// 向HTTP服务器发送Post请求 - /// - /// 请求地址 - /// 请求参数 - /// 参照页 - /// 文本编码 - /// 加密方式 - /// - [Obsolete("请使用 HttpWebClient")] - public static string Post(string url, Dictionary parameters, string referer, Encoding encoding, DecompressionMethods decompression = DecompressionMethods.None) - { - return Post(url, parameters, referer, "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 30000, null, null, encoding, decompression); - } - - /// - /// 向HTTP服务器发送Post请求 - /// - /// 请求地址 - /// 请求参数 - /// 参照页 - /// 加密方式 - /// - [Obsolete("请使用 HttpWebClient")] - public static string Post(string url, Dictionary parameters, string referer, DecompressionMethods decompression = DecompressionMethods.None) - { - return Post(url, parameters, referer, "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 30000, null, null, Encoding.UTF8, decompression); - } - - /// - /// 向HTTP服务器发送Post请求 - /// - /// 请求地址 - /// 请求参数 - /// 加密方式 - /// - [Obsolete("请使用 HttpWebClient")] - public static string Post(string url, Dictionary parameters, DecompressionMethods decompression = DecompressionMethods.None) - { - return Post(url, parameters, "", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 30000, null, null, Encoding.UTF8, decompression); - } - - /// - /// 向HTTP服务器发送请求, 获取 byte[] - /// - /// Url地址 - /// - [Obsolete("请使用 HttpWebClient")] - public static byte[] GetData(string url) - { - byte[] result = null; - try - { - WebClient webClient = new WebClient(); - webClient.Headers.Add("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"); - Stream stream = webClient.OpenRead(url); - result = new byte[stream.Length]; - stream.Read(result, 0, result.Length); - stream.Close(); - webClient = null; - return result; - } - catch - { - return result; - } - } - - /// - /// Url编码数据 - /// - /// 要编码的数据 - /// 编码后的数据 - [Obsolete("请使用 HttpWebClient")] - public static string UrlEncode(string data) - { - return HttpUtility.UrlEncode(data); - } - - /// - /// Url解码 - /// - /// 要解码的数据 - /// 解码后的数据 - [Obsolete("请使用 HttpWebClient")] - public static string UrlDecode(string data) - { - return HttpUtility.UrlDecode(data); - } - - /// - /// 获取请求的数据 - /// - /// - /// - /// - private static string GetResponseString(HttpWebResponse webResponse, Encoding encoding) - { - if (webResponse.ContentEncoding.ToLower().Contains("gzip")) - { - using (GZipStream stream = new GZipStream(webResponse.GetResponseStream(), CompressionMode.Decompress)) - { - using (StreamReader streamReader = new StreamReader(stream, encoding)) - { - return streamReader.ReadToEnd(); - } - } - } - if (webResponse.ContentEncoding.ToLower().Contains("deflate")) - { - using (DeflateStream stream2 = new DeflateStream(webResponse.GetResponseStream(), CompressionMode.Decompress)) - { - using (StreamReader streamReader2 = new StreamReader(stream2, encoding)) - { - return streamReader2.ReadToEnd(); - } - } - } - using (Stream stream3 = webResponse.GetResponseStream()) - { - StreamReader streamReader3 = new StreamReader(stream3, encoding); - return streamReader3.ReadToEnd(); - } - } - - /// - /// 验证证书 - /// - /// - /// - /// - /// - /// - private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) - { - return true; - } - } -} diff --git a/Native.Csharp.Tool/Http/HttpTool.cs b/Native.Csharp.Tool/Http/HttpTool.cs new file mode 100644 index 00000000..74f63085 --- /dev/null +++ b/Native.Csharp.Tool/Http/HttpTool.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; + +namespace Native.Csharp.Tool.Http +{ + /// + /// Http 工具类 + /// + public static class HttpTool + { + /// + /// 使用默认编码对 URL 进行编码 + /// + /// 要编码的地址 + /// 编码后的地址 + public static string UrlEncode (string url) + { + return HttpUtility.UrlEncode (url); + } + + /// + /// 使用指定的编码 对 URL 进行编码 + /// + /// 要编码的地址 + /// 编码类型 + /// 编码后的地址 + public static string UrlEncode (string url, Encoding encoding) + { + return HttpUtility.UrlEncode (url, encoding); + } + + /// + /// 使用默认编码对 URL 进行解码 + /// + /// 要解码的地址 + /// 编码后的地址 + public static string UrlDecode (string url) + { + return HttpUtility.UrlDecode (url); + } + + /// + /// 使用指定的编码 对 URL 进行解码 + /// + /// 要解码的地址 + /// 编码类型 + /// 编码后的地址 + public static string UrlDecode (string url, Encoding encoding) + { + return HttpUtility.UrlDecode (url, encoding); + } + } +} diff --git a/Native.Csharp.Tool/Http/HttpWebClient.cs b/Native.Csharp.Tool/Http/HttpWebClient.cs index f3750f34..00d34ab6 100644 --- a/Native.Csharp.Tool/Http/HttpWebClient.cs +++ b/Native.Csharp.Tool/Http/HttpWebClient.cs @@ -9,740 +9,692 @@ namespace Native.Csharp.Tool.Http { - /// - /// 提供用于将数据发送到和接收来自通过 URI 确认的资源数据丰富的常用方法。 - /// - public class HttpWebClient : WebClient - { - #region --属性-- - /// - /// 获取或设置请求的方法 - /// - /// 未提供任何方法。 - 或 - 方法字符串包含无效字符。 - public string Method { get; set; } - /// - /// 获取或设置 User-Agent HTTP 标头的值 - /// - public string UserAgent { get; set; } - /// - /// 获取或设置 Referer HTTP 标头的值 - /// - public string Referer { get; set; } - /// - /// 获取或设置获取 Intelnet 资源过程的超时值 - /// - /// 指定的值是小于零,且不是 System.Threading.Timeout.Infinite。 - public int TimeOut { get; set; } - /// - /// 获取或设置 Accept HTTP 标头的值 - /// - public string Accept { get; set; } - /// - /// 获取或设置与此请求关联的 - /// - public CookieCollection CookieCollection { get; set; } - /// - /// 获取或设置 Content-Type HTTP 标头的值 - /// - public string ContentType { get; set; } - /// - /// 获取或设置一个值, 该值指示请求是否跟应跟随重定向响应 - /// - public bool AllowAutoRedirect { get; set; } - /// - /// 获取或设置请求将跟随的重定向的最大数目。 - /// - /// 值设置为 0 或更小。 - public int MaximumAutomaticRedirections { get; set; } - /// - /// 获取或设置一个值, 该值指示是否与 Internal 建立持续型的连接 - /// - public bool KeepAlive { get; set; } - /// - /// 获取或设置一个值, 该值指示是否获取 Internet 资源后自动合并关联的 - /// - public bool AutoCookieMerge { get; set; } - #endregion + /// + /// 提供用于将数据发送到和接收来自通过 URI 确认的资源数据丰富的常用方法。 + /// + public class HttpWebClient : WebClient + { + #region --属性-- + /// + /// 获取或设置请求的方法 + /// + /// 未提供任何方法。 - 或 - 方法字符串包含无效字符。 + public string Method { get; set; } + /// + /// 获取或设置 User-Agent HTTP 标头的值 + /// + public string UserAgent { get; set; } + /// + /// 获取或设置 Referer HTTP 标头的值 + /// + public string Referer { get; set; } + /// + /// 获取或设置获取 Intelnet 资源过程的超时值 + /// + /// 指定的值是小于零,且不是 System.Threading.Timeout.Infinite。 + public int TimeOut { get; set; } + /// + /// 获取或设置 Accept HTTP 标头的值 + /// + public string Accept { get; set; } + /// + /// 获取或设置与此请求关联的 + /// + public CookieCollection CookieCollection { get; set; } + /// + /// 获取或设置 Content-Type HTTP 标头的值 + /// + public string ContentType { get; set; } + /// + /// 获取或设置一个值, 该值指示请求是否跟应跟随重定向响应 + /// + public bool AllowAutoRedirect { get; set; } + /// + /// 获取或设置请求将跟随的重定向的最大数目。 + /// + /// 值设置为 0 或更小。 + public int MaximumAutomaticRedirections { get; set; } + /// + /// 获取或设置一个值, 该值指示是否与 Internal 建立持续型的连接 + /// + public bool KeepAlive { get; set; } + /// + /// 获取或设置一个值, 该值指示是否获取 Internet 资源后自动合并关联的 + /// + public bool AutoCookieMerge { get; set; } + #endregion - #region --构造函数-- - /// - /// 初始化 类的一个实例对象 - /// - public HttpWebClient () - { } - #endregion + #region --构造函数-- + /// + /// 初始化 类的一个实例对象 + /// + public HttpWebClient() + { } + #endregion - #region --公开方法-- + #region --公开方法-- - #region --Get-- - /// - /// 向服务器发送 HTTP GET 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 参考页链接 - /// 告知服务器, 访问时的来源地址 - /// - /// User-Agent HTTP 标头 - /// Accept HTTP 标头 - /// 超时时间 - /// 请求附带的 Cookies - /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie - /// - /// 请求附带的 Headers - /// 此参数支持自动更新 - /// - /// 代理 实例 - /// 文本编码 - /// 跟随重定向响应 - /// 指定自动 合并 - /// 返回从 Internal 读取的 数组 - public static byte[] Get (string url, string referer, string userAgent, string accept, int timeout, ref CookieCollection cookies, ref WebHeaderCollection headers, WebProxy proxy, Encoding encoding, bool allowAutoRedirect = true, bool autoCookieMerge = true) - { - HttpWebClient httpWebClient = new HttpWebClient (); - httpWebClient.CookieCollection = cookies; - httpWebClient.Headers = headers; - httpWebClient.Referer = referer; - httpWebClient.UserAgent = userAgent; - httpWebClient.Accept = accept; - httpWebClient.TimeOut = timeout; - httpWebClient.Encoding = encoding; - httpWebClient.Proxy = proxy; - httpWebClient.AllowAutoRedirect = allowAutoRedirect; - httpWebClient.AutoCookieMerge = autoCookieMerge; - byte[] result = httpWebClient.DownloadData (new Uri (url)); - headers = httpWebClient.ResponseHeaders; - cookies = httpWebClient.CookieCollection; - return result; - } - /// - /// 向服务器发送 HTTP GET 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 参考页链接 - /// 告知服务器, 访问时的来源地址 - /// - /// 请求附带的 Cookies - /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie - /// - /// 请求附带的 Headers - /// 此参数支持自动更新 - /// - /// 代理 实例 - /// 文本编码 - /// 跟随重定向响应 - /// 指定自动 合并 - /// 返回从 Internal 读取的 数组 - public static byte[] Get (string url, string referer, ref CookieCollection cookies, ref WebHeaderCollection headers, WebProxy proxy, Encoding encoding, bool allowAutoRedirect = true, bool autoCookieMerge = true) - { - return Get (url, referer, string.Empty, string.Empty, 0, ref cookies, ref headers, proxy, encoding, allowAutoRedirect, autoCookieMerge); - } - /// - /// 向服务器发送 HTTP GET 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 参考页链接 - /// 告知服务器, 访问时的来源地址 - /// - /// 请求附带的 Cookies - /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie - /// - /// 请求附带的 Headers - /// 此参数支持自动更新 - /// - /// 文本编码 - /// 跟随重定向响应 - /// 指定自动 合并 - /// 返回从 Internal 读取的 数组 - public static byte[] Get (string url, string referer, ref CookieCollection cookies, ref WebHeaderCollection headers, Encoding encoding, bool allowAutoRedirect = true, bool autoCookieMerge = true) - { - return Get (url, referer, ref cookies, ref headers, null, encoding, allowAutoRedirect, autoCookieMerge); - } - /// - /// 向服务器发送 HTTP GET 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 参考页链接 - /// 告知服务器, 访问时的来源地址 - /// - /// 请求附带的 Cookies - /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie - /// - /// 请求附带的 Headers - /// 此参数支持自动更新 - /// - /// 跟随重定向响应 - /// 指定自动 合并 - /// 返回从 Internal 读取的 数组 - public static byte[] Get (string url, string referer, ref CookieCollection cookies, ref WebHeaderCollection headers, bool allowAutoRedirect = true, bool autoCookieMerge = true) - { - return Get (url, referer, ref cookies, ref headers, null, Encoding.UTF8, allowAutoRedirect, autoCookieMerge); - } - /// - /// 向服务器发送 HTTP GET 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 参考页链接 - /// 告知服务器, 访问时的来源地址 - /// - /// 请求附带的 Cookies - /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie - /// - /// 请求附带的 Headers - /// 此参数支持自动更新 - /// - /// 跟随重定向响应 - /// 指定自动 合并 - /// 返回从 Internal 读取的 数组 - public static byte[] Get (string url, string referer, ref CookieCollection cookies, bool allowAutoRedirect = true, bool autoCookieMerge = true) - { - WebHeaderCollection headers = new WebHeaderCollection (); - return Get (url, referer, ref cookies, ref headers, null, Encoding.UTF8, allowAutoRedirect, autoCookieMerge); - } - /// - /// 向服务器发送 HTTP GET 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 参考页链接 - /// 告知服务器, 访问时的来源地址 - /// - /// 请求附带的 Headers - /// 此参数支持自动更新 - /// - /// 跟随重定向响应 - /// 返回从 Internal 读取的 数组 - public static byte[] Get (string url, string referer, ref WebHeaderCollection headers, bool allowAutoRedirect = true) - { - CookieCollection cookies = new CookieCollection (); - return Get (url, referer, ref cookies, ref headers, null, Encoding.UTF8, allowAutoRedirect, false); - } - /// - /// 向服务器发送 HTTP GET 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 参考页链接 - /// 告知服务器, 访问时的来源地址 - /// - /// 跟随重定向响应 - /// 返回从 Internal 读取的 数组 - public static byte[] Get (string url, string referer, bool allowAutoRedirect = true) - { - WebHeaderCollection headers = new WebHeaderCollection (); - return Get (url, referer, ref headers, allowAutoRedirect); - } - /// - /// 向服务器发送 HTTP GET 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 请求附带的 Cookies - /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie - /// - /// 请求附带的 Headers - /// 此参数支持自动更新 - /// - /// 跟随重定向响应 - /// 指定自动 合并 - /// 返回从 Internal 读取的 数组 - public static byte[] Get (string url, ref CookieCollection cookies, bool allowAutoRedirect = true, bool autoCookieMerge = true) - { - WebHeaderCollection headers = new WebHeaderCollection (); - return Get (url, string.Empty, ref cookies, ref headers, null, Encoding.UTF8, allowAutoRedirect, autoCookieMerge); - } - /// - /// 向服务器发送 HTTP GET 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 请求附带的 Headers - /// 此参数支持自动更新 - /// - /// 跟随重定向响应 - /// 返回从 Internal 读取的 数组 - public static byte[] Get (string url, ref WebHeaderCollection headers, bool allowAutoRedirect = true) - { - CookieCollection cookies = new CookieCollection (); - return Get (url, string.Empty, ref cookies, ref headers, null, Encoding.UTF8, allowAutoRedirect, false); - } - /// - /// 向服务器发送 HTTP GET 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 跟随重定向响应 - /// 返回从 Internal 读取的 数组 - public static byte[] Get (string url, bool allowAutoRedirect = true) - { - return Get (url, string.Empty, allowAutoRedirect); - } - #endregion + #region --Get-- + /// + /// 向服务器发送 HTTP GET 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 参考页链接 + /// 告知服务器, 访问时的来源地址 + /// + /// User-Agent HTTP 标头 + /// Accept HTTP 标头 + /// 超时时间 + /// 请求附带的 Cookies + /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie + /// + /// 请求附带的 Headers + /// 此参数支持自动更新 + /// + /// 代理 实例 + /// 文本编码 + /// 跟随重定向响应 + /// 指定自动 合并 + /// 返回从 Internal 读取的 数组 + public static byte[] Get(string url, string referer, string userAgent, string accept, int timeout, ref CookieCollection cookies, ref WebHeaderCollection headers, WebProxy proxy, Encoding encoding, bool allowAutoRedirect = true, bool autoCookieMerge = true) + { + HttpWebClient httpWebClient = new HttpWebClient(); + httpWebClient.CookieCollection = cookies; + httpWebClient.Headers = headers; + httpWebClient.Referer = referer; + httpWebClient.UserAgent = userAgent; + httpWebClient.Accept = accept; + httpWebClient.TimeOut = timeout; + httpWebClient.Encoding = encoding; + httpWebClient.Proxy = proxy; + httpWebClient.AllowAutoRedirect = allowAutoRedirect; + httpWebClient.AutoCookieMerge = autoCookieMerge; + byte[] result = httpWebClient.DownloadData(new Uri(url)); + headers = httpWebClient.ResponseHeaders; + cookies = httpWebClient.CookieCollection; + return result; + } + /// + /// 向服务器发送 HTTP GET 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 参考页链接 + /// 告知服务器, 访问时的来源地址 + /// + /// 请求附带的 Cookies + /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie + /// + /// 请求附带的 Headers + /// 此参数支持自动更新 + /// + /// 代理 实例 + /// 文本编码 + /// 跟随重定向响应 + /// 指定自动 合并 + /// 返回从 Internal 读取的 数组 + public static byte[] Get(string url, string referer, ref CookieCollection cookies, ref WebHeaderCollection headers, WebProxy proxy, Encoding encoding, bool allowAutoRedirect = true, bool autoCookieMerge = true) + { + return Get(url, referer, string.Empty, string.Empty, 0, ref cookies, ref headers, proxy, encoding, allowAutoRedirect, autoCookieMerge); + } + /// + /// 向服务器发送 HTTP GET 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 参考页链接 + /// 告知服务器, 访问时的来源地址 + /// + /// 请求附带的 Cookies + /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie + /// + /// 请求附带的 Headers + /// 此参数支持自动更新 + /// + /// 文本编码 + /// 跟随重定向响应 + /// 指定自动 合并 + /// 返回从 Internal 读取的 数组 + public static byte[] Get(string url, string referer, ref CookieCollection cookies, ref WebHeaderCollection headers, Encoding encoding, bool allowAutoRedirect = true, bool autoCookieMerge = true) + { + return Get(url, referer, ref cookies, ref headers, null, encoding, allowAutoRedirect, autoCookieMerge); + } + /// + /// 向服务器发送 HTTP GET 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 参考页链接 + /// 告知服务器, 访问时的来源地址 + /// + /// 请求附带的 Cookies + /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie + /// + /// 请求附带的 Headers + /// 此参数支持自动更新 + /// + /// 跟随重定向响应 + /// 指定自动 合并 + /// 返回从 Internal 读取的 数组 + public static byte[] Get(string url, string referer, ref CookieCollection cookies, ref WebHeaderCollection headers, bool allowAutoRedirect = true, bool autoCookieMerge = true) + { + return Get(url, referer, ref cookies, ref headers, null, Encoding.UTF8, allowAutoRedirect, autoCookieMerge); + } + /// + /// 向服务器发送 HTTP GET 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 参考页链接 + /// 告知服务器, 访问时的来源地址 + /// + /// 请求附带的 Cookies + /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie + /// + /// 跟随重定向响应 + /// 指定自动 合并 + /// 返回从 Internal 读取的 数组 + public static byte[] Get(string url, string referer, ref CookieCollection cookies, bool allowAutoRedirect = true, bool autoCookieMerge = true) + { + WebHeaderCollection headers = new WebHeaderCollection(); + return Get(url, referer, ref cookies, ref headers, null, Encoding.UTF8, allowAutoRedirect, autoCookieMerge); + } + /// + /// 向服务器发送 HTTP GET 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 参考页链接 + /// 告知服务器, 访问时的来源地址 + /// + /// 请求附带的 Headers + /// 此参数支持自动更新 + /// + /// 跟随重定向响应 + /// 返回从 Internal 读取的 数组 + public static byte[] Get(string url, string referer, ref WebHeaderCollection headers, bool allowAutoRedirect = true) + { + CookieCollection cookies = new CookieCollection(); + return Get(url, referer, ref cookies, ref headers, null, Encoding.UTF8, allowAutoRedirect, false); + } + /// + /// 向服务器发送 HTTP GET 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 参考页链接 + /// 告知服务器, 访问时的来源地址 + /// + /// 跟随重定向响应 + /// 返回从 Internal 读取的 数组 + public static byte[] Get(string url, string referer, bool allowAutoRedirect = true) + { + WebHeaderCollection headers = new WebHeaderCollection(); + return Get(url, referer, ref headers, allowAutoRedirect); + } + /// + /// 向服务器发送 HTTP GET 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 请求附带的 Cookies + /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie + /// + /// 跟随重定向响应 + /// 指定自动 合并 + /// 返回从 Internal 读取的 数组 + public static byte[] Get(string url, ref CookieCollection cookies, bool allowAutoRedirect = true, bool autoCookieMerge = true) + { + WebHeaderCollection headers = new WebHeaderCollection(); + return Get(url, string.Empty, ref cookies, ref headers, null, Encoding.UTF8, allowAutoRedirect, autoCookieMerge); + } + /// + /// 向服务器发送 HTTP GET 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 请求附带的 Headers + /// 此参数支持自动更新 + /// + /// 跟随重定向响应 + /// 返回从 Internal 读取的 数组 + public static byte[] Get(string url, ref WebHeaderCollection headers, bool allowAutoRedirect = true) + { + CookieCollection cookies = new CookieCollection(); + return Get(url, string.Empty, ref cookies, ref headers, null, Encoding.UTF8, allowAutoRedirect, false); + } + /// + /// 向服务器发送 HTTP GET 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 跟随重定向响应 + /// 返回从 Internal 读取的 数组 + public static byte[] Get(string url, bool allowAutoRedirect = true) + { + return Get(url, string.Empty, allowAutoRedirect); + } + #endregion - #region --Post-- - /// - /// 向服务器发送 HTTP POST 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 请求所需的上传数据 - /// Content-Type HTTP 标头 - /// 参考页链接 - /// 告知服务器, 访问时的来源地址 - /// - /// User-Agent HTTP 标头 - /// Accept HTTP 标头 - /// 超时时间 - /// 请求附带的 Cookies - /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie - /// - /// 请求附带的 Headers - /// 此参数支持自动更新 - /// - /// 代理 实例 - /// 文本编码 - /// 跟随重定向响应 - /// 指定自动 合并 - /// 返回从 Internal 读取的 数组 - public static byte[] Post (string url, byte[] data, string contentType, string referer, string userAgent, string accept, int timeout, ref CookieCollection cookies, ref WebHeaderCollection headers, WebProxy proxy, Encoding encoding, bool allowAutoRedirect = true, bool autoCookieMerge = true) - { - HttpWebClient httpWebClient = new HttpWebClient (); - httpWebClient.ContentType = contentType; - httpWebClient.Referer = referer; - httpWebClient.UserAgent = userAgent; - httpWebClient.Accept = accept; - httpWebClient.TimeOut = timeout; - httpWebClient.CookieCollection = cookies; - httpWebClient.Headers = headers; - httpWebClient.Proxy = proxy; - httpWebClient.AutoCookieMerge = autoCookieMerge; - httpWebClient.AllowAutoRedirect = allowAutoRedirect; - byte[] result = httpWebClient.UploadData (new Uri (url), data); - headers = httpWebClient.ResponseHeaders; - cookies = httpWebClient.CookieCollection; - return result; - } - /// - /// 向服务器发送 HTTP POST 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 请求所需的上传数据 - /// Content-Type HTTP 标头 - /// 参考页链接 - /// 告知服务器, 访问时的来源地址 - /// - /// 请求附带的 Cookies - /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie - /// - /// 请求附带的 Headers - /// 此参数支持自动更新 - /// - /// 代理 实例 - /// 文本编码 - /// 跟随重定向响应 - /// 指定自动 合并 - /// 返回从 Internal 读取的 数组 - public static byte[] Post (string url, byte[] data, string contentType, string referer, ref CookieCollection cookies, ref WebHeaderCollection headers, WebProxy proxy, Encoding encoding, bool allowAutoRedirect = true, bool autoCookieMerge = true) - { - return Post (url, data, contentType, referer, string.Empty, string.Empty, 0, ref cookies, ref headers, proxy, encoding, allowAutoRedirect, autoCookieMerge); - } - /// - /// 向服务器发送 HTTP POST 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 请求所需的上传数据 - /// Content-Type HTTP 标头 - /// 参考页链接 - /// 告知服务器, 访问时的来源地址 - /// - /// 请求附带的 Cookies - /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie - /// - /// 请求附带的 Headers - /// 此参数支持自动更新 - /// - /// 文本编码 - /// 跟随重定向响应 - /// 指定自动 合并 - /// 返回从 Internal 读取的 数组 - public static byte[] Post (string url, byte[] data, string contentType, string referer, ref CookieCollection cookies, ref WebHeaderCollection headers, Encoding encoding, bool allowAutoRedirect = true, bool autoCookieMerge = true) - { - return Post (url, data, contentType, referer, ref cookies, ref headers, null, encoding, allowAutoRedirect, autoCookieMerge); - } - /// - /// 向服务器发送 HTTP POST 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 请求所需的上传数据 - /// Content-Type HTTP 标头 - /// 参考页链接 - /// 告知服务器, 访问时的来源地址 - /// - /// 请求附带的 Cookies - /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie - /// - /// 请求附带的 Headers - /// 此参数支持自动更新 - /// - /// 跟随重定向响应 - /// 指定自动 合并 - /// 返回从 Internal 读取的 数组 - public static byte[] Post (string url, byte[] data, string contentType, string referer, ref CookieCollection cookies, ref WebHeaderCollection headers, bool allowAutoRedirect = true, bool autoCookieMerge = true) - { - return Post (url, data, contentType, referer, ref cookies, ref headers, Encoding.UTF8, allowAutoRedirect, autoCookieMerge); - } - /// - /// 向服务器发送 HTTP POST 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 请求所需的上传数据 - /// Content-Type HTTP 标头 - /// 参考页链接 - /// 告知服务器, 访问时的来源地址 - /// - /// 请求附带的 Cookies - /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie - /// - /// 跟随重定向响应 - /// 指定自动 合并 - /// 返回从 Internal 读取的 数组 - public static byte[] Post (string url, byte[] data, string contentType, string referer, ref CookieCollection cookies, bool allowAutoRedirect = true, bool autoCookieMerge = true) - { - WebHeaderCollection headers = new WebHeaderCollection (); - return Post (url, data, contentType, referer, ref cookies, ref headers, allowAutoRedirect, autoCookieMerge); - } - /// - /// 向服务器发送 HTTP POST 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 请求所需的上传数据 - /// Content-Type HTTP 标头 - /// 参考页链接 - /// 告知服务器, 访问时的来源地址 - /// - /// 请求附带的 Headers - /// 此参数支持自动更新 - /// - /// 跟随重定向响应 - /// 返回从 Internal 读取的 数组 - public static byte[] Post (string url, byte[] data, string contentType, string referer, ref WebHeaderCollection headers, bool allowAutoRedirect = true) - { - CookieCollection cookies = new CookieCollection (); - return Post (url, data, contentType, referer, ref cookies, ref headers, allowAutoRedirect, false); - } - /// - /// 向服务器发送 HTTP POST 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 请求所需的上传数据 - /// Content-Type HTTP 标头 - /// 请求附带的 Cookies - /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie - /// - /// 跟随重定向响应 - /// 指定自动 合并 - /// 返回从 Internal 读取的 数组 - public static byte[] Post (string url, byte[] data, string contentType, ref CookieCollection cookies, bool allowAutoRedirect = true, bool autoCookieMerge = true) - { - return Post (url, data, contentType, string.Empty, ref cookies, allowAutoRedirect, autoCookieMerge); - } - /// - /// 向服务器发送 HTTP POST 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 请求所需的上传数据 - /// Content-Type HTTP 标头 - /// 请求附带的 Headers - /// 此参数支持自动更新 - /// - /// 跟随重定向响应 - /// 返回从 Internal 读取的 数组 - public static byte[] Post (string url, byte[] data, string contentType, ref WebHeaderCollection headers, bool allowAutoRedirect = true) - { - return Post (url, data, contentType, string.Empty, ref headers, allowAutoRedirect); - } - /// - /// 向服务器发送 HTTP POST 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 请求所需的上传数据 - /// Content-Type HTTP 标头 - /// 参考页链接 - /// 告知服务器, 访问时的来源地址 - /// - /// 跟随重定向响应 - /// 返回从 Internal 读取的 数组 - public static byte[] Post (string url, byte[] data, string contentType, string referer, bool allowAutoRedirect = true) - { - WebHeaderCollection headers = new WebHeaderCollection (); - return Post (url, data, contentType, referer, ref headers, allowAutoRedirect); - } - /// - /// 向服务器发送 HTTP POST 请求 - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 请求所需的上传数据 - /// Content-Type HTTP 标头 - /// 跟随重定向响应 - /// 返回从 Internal 读取的 数组 - public static byte[] Post (string url, byte[] data, string contentType, bool allowAutoRedirect = true) - { - return Post (url, data, contentType, string.Empty, allowAutoRedirect); - } - /// - /// 向服务器发送 HTTP POST 请求 : application/x-www-form-urlencoded - /// - /// 完整的网页地址 - /// 必须包含 "http://" 或 "https://" - /// - /// 请求所需的上传数据 - /// Content-Type HTTP 标头 - /// 跟随重定向响应 - /// 返回从 Internal 读取的 数组 - public static byte[] Post (string url, byte[] data, bool allowAutoRedirect = true) - { - return Post (url, data, string.Empty, string.Empty, allowAutoRedirect); - } - #endregion + #region --Post-- + /// + /// 向服务器发送 HTTP POST 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 请求所需的上传数据 + /// Content-Type HTTP 标头 + /// 参考页链接 + /// 告知服务器, 访问时的来源地址 + /// + /// User-Agent HTTP 标头 + /// Accept HTTP 标头 + /// 超时时间 + /// 请求附带的 Cookies + /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie + /// + /// 请求附带的 Headers + /// 此参数支持自动更新 + /// + /// 代理 实例 + /// 文本编码 + /// 跟随重定向响应 + /// 指定自动 合并 + /// 返回从 Internal 读取的 数组 + public static byte[] Post(string url, byte[] data, string contentType, string referer, string userAgent, string accept, int timeout, ref CookieCollection cookies, ref WebHeaderCollection headers, WebProxy proxy, Encoding encoding, bool allowAutoRedirect = true, bool autoCookieMerge = true) + { + HttpWebClient httpWebClient = new HttpWebClient(); + httpWebClient.ContentType = contentType; + httpWebClient.Referer = referer; + httpWebClient.UserAgent = userAgent; + httpWebClient.Accept = accept; + httpWebClient.TimeOut = timeout; + httpWebClient.CookieCollection = cookies; + httpWebClient.Headers = headers; + httpWebClient.Proxy = proxy; + httpWebClient.AutoCookieMerge = autoCookieMerge; + httpWebClient.AllowAutoRedirect = allowAutoRedirect; + byte[] result = httpWebClient.UploadData(new Uri(url), data); + headers = httpWebClient.ResponseHeaders; + cookies = httpWebClient.CookieCollection; + return result; + } + /// + /// 向服务器发送 HTTP POST 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 请求所需的上传数据 + /// Content-Type HTTP 标头 + /// 参考页链接 + /// 告知服务器, 访问时的来源地址 + /// + /// 请求附带的 Cookies + /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie + /// + /// 请求附带的 Headers + /// 此参数支持自动更新 + /// + /// 代理 实例 + /// 文本编码 + /// 跟随重定向响应 + /// 指定自动 合并 + /// 返回从 Internal 读取的 数组 + public static byte[] Post(string url, byte[] data, string contentType, string referer, ref CookieCollection cookies, ref WebHeaderCollection headers, WebProxy proxy, Encoding encoding, bool allowAutoRedirect = true, bool autoCookieMerge = true) + { + return Post(url, data, contentType, referer, string.Empty, string.Empty, 0, ref cookies, ref headers, proxy, encoding, allowAutoRedirect, autoCookieMerge); + } + /// + /// 向服务器发送 HTTP POST 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 请求所需的上传数据 + /// Content-Type HTTP 标头 + /// 参考页链接 + /// 告知服务器, 访问时的来源地址 + /// + /// 请求附带的 Cookies + /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie + /// + /// 请求附带的 Headers + /// 此参数支持自动更新 + /// + /// 文本编码 + /// 跟随重定向响应 + /// 指定自动 合并 + /// 返回从 Internal 读取的 数组 + public static byte[] Post(string url, byte[] data, string contentType, string referer, ref CookieCollection cookies, ref WebHeaderCollection headers, Encoding encoding, bool allowAutoRedirect = true, bool autoCookieMerge = true) + { + return Post(url, data, contentType, referer, ref cookies, ref headers, null, encoding, allowAutoRedirect, autoCookieMerge); + } + /// + /// 向服务器发送 HTTP POST 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 请求所需的上传数据 + /// Content-Type HTTP 标头 + /// 参考页链接 + /// 告知服务器, 访问时的来源地址 + /// + /// 请求附带的 Cookies + /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie + /// + /// 请求附带的 Headers + /// 此参数支持自动更新 + /// + /// 跟随重定向响应 + /// 指定自动 合并 + /// 返回从 Internal 读取的 数组 + public static byte[] Post(string url, byte[] data, string contentType, string referer, ref CookieCollection cookies, ref WebHeaderCollection headers, bool allowAutoRedirect = true, bool autoCookieMerge = true) + { + return Post(url, data, contentType, referer, ref cookies, ref headers, Encoding.UTF8, allowAutoRedirect, autoCookieMerge); + } + /// + /// 向服务器发送 HTTP POST 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 请求所需的上传数据 + /// Content-Type HTTP 标头 + /// 参考页链接 + /// 告知服务器, 访问时的来源地址 + /// + /// 请求附带的 Cookies + /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie + /// + /// 跟随重定向响应 + /// 指定自动 合并 + /// 返回从 Internal 读取的 数组 + public static byte[] Post(string url, byte[] data, string contentType, string referer, ref CookieCollection cookies, bool allowAutoRedirect = true, bool autoCookieMerge = true) + { + WebHeaderCollection headers = new WebHeaderCollection(); + return Post(url, data, contentType, referer, ref cookies, ref headers, allowAutoRedirect, autoCookieMerge); + } + /// + /// 向服务器发送 HTTP POST 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 请求所需的上传数据 + /// Content-Type HTTP 标头 + /// 参考页链接 + /// 告知服务器, 访问时的来源地址 + /// + /// 请求附带的 Headers + /// 此参数支持自动更新 + /// + /// 跟随重定向响应 + /// 返回从 Internal 读取的 数组 + public static byte[] Post(string url, byte[] data, string contentType, string referer, ref WebHeaderCollection headers, bool allowAutoRedirect = true) + { + CookieCollection cookies = new CookieCollection(); + return Post(url, data, contentType, referer, ref cookies, ref headers, allowAutoRedirect, false); + } + /// + /// 向服务器发送 HTTP POST 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 请求所需的上传数据 + /// Content-Type HTTP 标头 + /// 请求附带的 Cookies + /// 此参数支持自动更新 , 若 参数为 True, 将合并新旧 Cookie + /// + /// 跟随重定向响应 + /// 指定自动 合并 + /// 返回从 Internal 读取的 数组 + public static byte[] Post(string url, byte[] data, string contentType, ref CookieCollection cookies, bool allowAutoRedirect = true, bool autoCookieMerge = true) + { + return Post(url, data, contentType, string.Empty, ref cookies, allowAutoRedirect, autoCookieMerge); + } + /// + /// 向服务器发送 HTTP POST 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 请求所需的上传数据 + /// Content-Type HTTP 标头 + /// 请求附带的 Headers + /// 此参数支持自动更新 + /// + /// 跟随重定向响应 + /// 返回从 Internal 读取的 数组 + public static byte[] Post(string url, byte[] data, string contentType, ref WebHeaderCollection headers, bool allowAutoRedirect = true) + { + return Post(url, data, contentType, string.Empty, ref headers, allowAutoRedirect); + } + /// + /// 向服务器发送 HTTP POST 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 请求所需的上传数据 + /// Content-Type HTTP 标头 + /// 参考页链接 + /// 告知服务器, 访问时的来源地址 + /// + /// 跟随重定向响应 + /// 返回从 Internal 读取的 数组 + public static byte[] Post(string url, byte[] data, string contentType, string referer, bool allowAutoRedirect = true) + { + WebHeaderCollection headers = new WebHeaderCollection(); + return Post(url, data, contentType, referer, ref headers, allowAutoRedirect); + } + /// + /// 向服务器发送 HTTP POST 请求 + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 请求所需的上传数据 + /// Content-Type HTTP 标头 + /// 跟随重定向响应 + /// 返回从 Internal 读取的 数组 + public static byte[] Post(string url, byte[] data, string contentType, bool allowAutoRedirect = true) + { + return Post(url, data, contentType, string.Empty, allowAutoRedirect); + } + /// + /// 向服务器发送 HTTP POST 请求 : application/x-www-form-urlencoded + /// + /// 完整的网页地址 + /// 必须包含 "http://" 或 "https://" + /// + /// 请求所需的上传数据 + /// 跟随重定向响应 + /// 返回从 Internal 读取的 数组 + public static byte[] Post(string url, byte[] data, bool allowAutoRedirect = true) + { + return Post(url, data, string.Empty, string.Empty, allowAutoRedirect); + } + #endregion - #region --Cookie-- - /// - /// 合并更新 - /// - /// 原始的Cookis - /// 欲合并Cookies - /// 返回处理过的 - public static CookieCollection UpdateCookie (CookieCollection oldCookies, CookieCollection newCookies) - { - if (oldCookies == null) - { - throw new ArgumentNullException ("oldCookies"); - } - if (newCookies == null) - { - throw new ArgumentNullException ("newCookies"); - } + #region --Cookie-- + /// + /// 合并更新 + /// + /// 原始的Cookis + /// 欲合并Cookies + /// 返回处理过的 + public static CookieCollection UpdateCookie(CookieCollection oldCookies, CookieCollection newCookies) + { + if (oldCookies == null) + { + throw new ArgumentNullException("oldCookies"); + } + if (newCookies == null) + { + throw new ArgumentNullException("newCookies"); + } - for (int i = 0; i < newCookies.Count; i++) - { - int index = CheckCookie (oldCookies, newCookies[i].Name); - if (index >= 0) - { - oldCookies[index].Value = newCookies[i].Value; - } - else - { - oldCookies.Add (newCookies[i]); - } - } - return oldCookies; - } - #endregion + for (int i = 0; i < newCookies.Count; i++) + { + int index = CheckCookie(oldCookies, newCookies[i].Name); + if (index >= 0) + { + oldCookies[index].Value = newCookies[i].Value; + } + else + { + oldCookies.Add(newCookies[i]); + } + } + return oldCookies; + } + #endregion - #region --URL-- - /// - /// 使用默认编码对 URL 进行编码 - /// - /// 要编码的地址 - /// 编码后的地址 - public static string UrlEncode (string url) - { - return HttpUtility.UrlEncode (url); - } - /// - /// 使用指定的编码 对 URL 进行编码 - /// - /// 要编码的地址 - /// 编码类型 - /// 编码后的地址 - public static string UrlEncode (string url, Encoding encoding) - { - return HttpUtility.UrlEncode (url, encoding); - } - /// - /// 使用默认编码对 URL 进行解码 - /// - /// 要解码的地址 - /// 编码后的地址 - public static string UrlDecode (string url) - { - return HttpUtility.UrlDecode (url); - } - /// - /// 使用指定的编码 对 URL 进行解码 - /// - /// 要解码的地址 - /// 编码类型 - /// 编码后的地址 - public static string UrlDecode (string url, Encoding encoding) - { - return HttpUtility.UrlDecode (url, encoding); - } - #endregion + #endregion - #endregion + #region --私有方法-- + /// + /// 验证HTTPS证书 + /// + /// + /// + /// + /// + /// + private bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + return true; + } + /// + /// 确认Cookie是否存在 + /// + /// Cookie对象 + /// cookie名称 + /// + private static int CheckCookie(CookieCollection cookie, string name) + { + for (int i = 0; i < cookie.Count; i++) + { + if (cookie[i].Name == name) + { + return i; + } + } + return -1; + } + #endregion - #region --私有方法-- - /// - /// 验证HTTPS证书 - /// - /// - /// - /// - /// - /// - private bool CheckValidationResult (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) - { - return true; - } - /// - /// 确认Cookie是否存在 - /// - /// Cookie对象 - /// cookie名称 - /// - private static int CheckCookie (CookieCollection cookie, string name) - { - for (int i = 0; i < cookie.Count; i++) - { - if (cookie[i].Name == name) - { - return i; - } - } - return -1; - } - #endregion + #region --重写方法-- + /// + /// 返回带有 Cookies 的 HttpWebRequest + /// + /// 一个 System.Uri,它标识要请求的资源 + /// + protected override WebRequest GetWebRequest(Uri address) + { + if (address.OriginalString.StartsWith("https", StringComparison.OrdinalIgnoreCase)) + { + ServicePointManager.ServerCertificateValidationCallback = CheckValidationResult; // 强行验证HTTPS通过 + ServicePointManager.SecurityProtocol = (SecurityProtocolType)(48 | 192 | 768 | 3072); // 通过验证的协议类型, 来源 .Net Framework 4.5 + } + HttpWebRequest httpWebRequest = (HttpWebRequest)base.GetWebRequest(address); + httpWebRequest.ProtocolVersion = HttpVersion.Version11; + httpWebRequest.KeepAlive = KeepAlive; // 默认: False, 不建立持续型连接 + if (CookieCollection != null) + { + httpWebRequest.CookieContainer = new CookieContainer(); + httpWebRequest.CookieContainer.Add(address, CookieCollection); + } + else + { + httpWebRequest.CookieContainer = new CookieContainer(); + } + if (!string.IsNullOrEmpty(this.UserAgent)) + { + httpWebRequest.UserAgent = UserAgent; + } + else + { + httpWebRequest.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36"; + } + if (TimeOut > 0) + { + httpWebRequest.Timeout = this.TimeOut; + } + if (!string.IsNullOrEmpty(this.Accept)) + { + httpWebRequest.Accept = this.Accept; + } + else + { + httpWebRequest.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"; + } + httpWebRequest.AllowAutoRedirect = this.AllowAutoRedirect; + if (this.AllowAutoRedirect) + { + if (this.MaximumAutomaticRedirections <= 0) + { + httpWebRequest.MaximumAutomaticRedirections = 5; + } + else + { + httpWebRequest.MaximumAutomaticRedirections = this.MaximumAutomaticRedirections; + } + } + if (!string.IsNullOrEmpty(this.Referer)) + { + httpWebRequest.Referer = this.Referer; + } + if (httpWebRequest.Method.ToUpper() != "GET") //GET不需要包体参数 + { + if (!string.IsNullOrEmpty(this.ContentType)) + { + httpWebRequest.ContentType = this.ContentType; + } + else + { + httpWebRequest.ContentType = "application/x-www-form-urlencoded"; + } + } - #region --重写方法-- - /// - /// 返回带有 Cookies 的 HttpWebRequest - /// - /// 一个 System.Uri,它标识要请求的资源 - /// - protected override WebRequest GetWebRequest (Uri address) - { - if (address.OriginalString.StartsWith ("https", StringComparison.OrdinalIgnoreCase)) - { - ServicePointManager.ServerCertificateValidationCallback = CheckValidationResult; // 强行验证HTTPS通过 - ServicePointManager.SecurityProtocol = (SecurityProtocolType)(48 | 192 | 768 | 3072); // 通过验证的协议类型, 来源 .Net Framework 4.5 - } - HttpWebRequest httpWebRequest = (HttpWebRequest)base.GetWebRequest (address); - httpWebRequest.ProtocolVersion = HttpVersion.Version11; - httpWebRequest.KeepAlive = KeepAlive; // 默认: False, 不建立持续型连接 - if (CookieCollection != null) - { - httpWebRequest.CookieContainer = new CookieContainer (); - httpWebRequest.CookieContainer.Add (address, CookieCollection); - } - else - { - httpWebRequest.CookieContainer = new CookieContainer (); - } - if (!string.IsNullOrEmpty (this.UserAgent)) - { - httpWebRequest.UserAgent = UserAgent; - } - else - { - httpWebRequest.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36"; - } - if (TimeOut > 0) - { - httpWebRequest.Timeout = this.TimeOut; - } - if (!string.IsNullOrEmpty (this.Accept)) - { - httpWebRequest.Accept = this.Accept; - } - else - { - httpWebRequest.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"; - } - httpWebRequest.AllowAutoRedirect = this.AllowAutoRedirect; - if (this.AllowAutoRedirect) - { - if (this.MaximumAutomaticRedirections <= 0) - { - httpWebRequest.MaximumAutomaticRedirections = 5; - } - else - { - httpWebRequest.MaximumAutomaticRedirections = this.MaximumAutomaticRedirections; - } - } - if (!string.IsNullOrEmpty (this.Referer)) - { - httpWebRequest.Referer = this.Referer; - } - if (httpWebRequest.Method.ToUpper () != "GET") //GET不需要包体参数 - { - if (!string.IsNullOrEmpty (this.ContentType)) - { - httpWebRequest.ContentType = this.ContentType; - } - else - { - httpWebRequest.ContentType = "application/x-www-form-urlencoded"; - } - } - - return httpWebRequest; - } - /// - /// 返回指定 System.Net.WebResponse 的 System.Net.WebRequest。 - /// - /// 一个 System.Net.WebRequest 用于获得响应。 - /// 一个 System.Net.WebResponse 包含指定的响应 System.Net.WebRequest。 - protected override WebResponse GetWebResponse (WebRequest request) - { - HttpWebResponse httpWebResponse = (HttpWebResponse)base.GetWebResponse (request); - this.Method = httpWebResponse.Method; - this.ContentType = httpWebResponse.ContentType; - this.Headers = httpWebResponse.Headers; - if (this.AutoCookieMerge) - { - UpdateCookie (this.CookieCollection, httpWebResponse.Cookies); - } - else - { - this.CookieCollection = httpWebResponse.Cookies; - } - return httpWebResponse; - } - #endregion - } + return httpWebRequest; + } + /// + /// 返回指定 System.Net.WebResponse 的 System.Net.WebRequest。 + /// + /// 一个 System.Net.WebRequest 用于获得响应。 + /// 一个 System.Net.WebResponse 包含指定的响应 System.Net.WebRequest。 + protected override WebResponse GetWebResponse(WebRequest request) + { + HttpWebResponse httpWebResponse = (HttpWebResponse)base.GetWebResponse(request); + this.Method = httpWebResponse.Method; + this.ContentType = httpWebResponse.ContentType; + this.Headers = httpWebResponse.Headers; + if (this.AutoCookieMerge) + { + UpdateCookie(this.CookieCollection, httpWebResponse.Cookies); + } + else + { + this.CookieCollection = httpWebResponse.Cookies; + } + return httpWebResponse; + } + #endregion + } } diff --git a/Native.Csharp.Tool/IniConfig/Linq/IniObject.cs b/Native.Csharp.Tool/IniConfig/Linq/IniObject.cs index dd6475b8..13b08b8c 100644 --- a/Native.Csharp.Tool/IniConfig/Linq/IniObject.cs +++ b/Native.Csharp.Tool/IniConfig/Linq/IniObject.cs @@ -98,9 +98,9 @@ public IniObject (int capacity) : base (capacity) { } /// - /// 初始化 类的新实例, 该实例从包含指定的 赋值的元素并为键类型使用默认的相等比较器 + /// 初始化 类的新实例, 该实例从包含指定的 赋值的元素并为键类型使用默认的相等比较器 /// - /// , 它的元素被复制到新 + /// , 它的元素被复制到新 public IniObject (IDictionary dictionary) : base (dictionary) { } @@ -140,7 +140,7 @@ public void Save () /// /// 将 Ini 配置项保存到指定的文件。 如果存在指定文件,则此方法会覆盖它。 /// - /// 要将文档保存到其中的文件的位置。 + /// 要将文档保存到其中的文件的位置。 public void Save (string filePath) { Save (new Uri (filePath)); diff --git a/Native.Csharp.Tool/IniConfig/Linq/IniValue.cs b/Native.Csharp.Tool/IniConfig/Linq/IniValue.cs index 4ac65e74..f0f451d1 100644 --- a/Native.Csharp.Tool/IniConfig/Linq/IniValue.cs +++ b/Native.Csharp.Tool/IniConfig/Linq/IniValue.cs @@ -195,7 +195,6 @@ public bool Equals (IniValue other) /// /// 指示当前对象是否等于另一个对象。 /// - /// 与此实例比较的另一个对象 /// 如果当前对象等于 obj 参数,则为 true;否则为 false。 public TypeCode GetTypeCode () { diff --git a/Native.Csharp.Tool/IniConfig/README.md b/Native.Csharp.Tool/IniConfig/README.md index 0f37b196..9313ae0d 100644 --- a/Native.Csharp.Tool/IniConfig/README.md +++ b/Native.Csharp.Tool/IniConfig/README.md @@ -69,7 +69,7 @@ value1.ToByte (); Convert.ToDateTime (value1); // 当然也可以使用 Convert // 快速拿取 Value -IniValue value2 = section2["节点"]["键1"]; +IniValue value2 = iObject["节点"]["键1"]; ``` >4. 修改 Ini 配置文件 @@ -81,4 +81,5 @@ IniObject iObject = IniObject.Load ("1.ini"); iObject["节点1"]["键1"] = new IniValue ("更新值"); // 因为无法重载 = 运算符, 所以没办法只能 new 对象 iObject["节点1"]["键1"] = new IniValue (10); iObject["节点1"]["键1"].Value = "更新值"; // 适用于字符串的时候 +iObject.Save (); ``` diff --git a/Native.Csharp.Tool/IniFile.cs b/Native.Csharp.Tool/IniFile.cs deleted file mode 100644 index 864b3618..00000000 --- a/Native.Csharp.Tool/IniFile.cs +++ /dev/null @@ -1,331 +0,0 @@ -using Native.Csharp.Tool.Core; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; - -namespace Native.Csharp.Tool -{ - /// - /// Ini配置文件 - /// - [Obsolete("请改用 IniConfig ")] - public class IniFile - { - #region --字段-- - private readonly string _fileName; - #endregion - - #region --属性-- - /// - /// 获取当前Ini文件的绝对路径 - /// - public string FileName - { - get { return _fileName; } - } - /// - /// 获取文件是否存在 - /// - public bool IsExists - { - get { return File.Exists(this.FileName); } - } - #endregion - - #region --构造函数-- - /// - /// 初始化 Native.Csharp.Sdk.Cqp.Tool.IniFile 实例对象 - /// - /// 文件路径 - public IniFile(string filePath) - { - this._fileName = filePath; - if (!IsExists) - { - File.Create(this.FileName); - } - } - #endregion - - #region --公开方法-- - - #region --Write-- - /// - /// 写入一个keyValuePair, 若 key 已存在, 则替换Value - /// - /// 该键所在的节名称 - /// 该键的名称 - /// 该键的值 - public void Write(string section, string key, object Value) - { - Kernel32.WritePrivateProfileStringA(section, key, Value.ToString(), this.FileName); - } - #endregion - - #region --Read-- - /// - /// 读取Ini文件指定节, 指定键中的值 - /// - /// 节 - /// 键 - /// 值, 当读取失败时, 返回该值 - /// - public string Read(string section, string key, string value = null) - { - StringBuilder buffer = new StringBuilder(65535); - Kernel32.GetPrivateProfileSectionA(section, buffer, buffer.Capacity, this.FileName); - string str = buffer.ToString(); - if (string.IsNullOrEmpty(str)) - { - return value; - } - return str; - } - /// - /// 读取Ini文件指定节, 指定键中的值 - /// - /// 节 - /// 键 - /// 值, 当读取失败时, 返回该值 - /// - public virtual int Read(string section, string key, int value = 0) - { - int result; - try - { - result = int.Parse(this.Read(section, key, string.Empty)); - } - catch - { - result = value; - } - return result; - } - /// - /// 读取Ini文件指定节, 指定键中的值 - /// - /// 节 - /// 键 - /// 值, 当读取失败时, 返回该值 - /// - public virtual long Read(string section, string key, long value = 0) - { - long result; - try - { - result = long.Parse(this.Read(section, key, string.Empty)); - } - catch - { - result = value; - } - return result; - } - /// - /// 读取Ini文件指定节, 指定键中的值 - /// - /// 节 - /// 键 - /// 值, 当读取失败时, 返回该值 - /// - public virtual byte Read(string section, string key, byte value = 0x00) - { - byte result; - try - { - result = byte.Parse(this.Read(section, key, string.Empty)); - } - catch - { - result = value; - } - return result; - } - /// - /// 读取Ini文件指定节, 指定键中的值 - /// - /// 节 - /// 键 - /// 值, 当读取失败时, 返回该值 - /// - public virtual float Read(string section, string key, float value = 0) - { - float result; - try - { - result = float.Parse(this.Read(section, key, string.Empty)); - } - catch - { - result = value; - } - return result; - } - /// - /// 读取Ini文件指定节, 指定键中的值 - /// - /// 节 - /// 键 - /// 值, 当读取失败时, 返回该值 - /// - public virtual double Read(string section, string key, double value = 0) - { - double result; - try - { - result = double.Parse(this.Read(section, key, string.Empty)); - } - catch - { - result = value; - } - return result; - } - /// - /// 读取Ini文件指定节, 指定键中的值 - /// - /// 节 - /// 键 - /// 值, 当读取失败时, 返回该值 - /// - public virtual bool Read(string section, string key, bool value = false) - { - bool result; - try - { - result = bool.Parse(this.Read(section, key, string.Empty)); - } - catch - { - result = value; - } - return result; - } - /// - /// 读取Ini文件指定节, 指定键中的值 - /// - /// 节 - /// 键 - /// 值, 当读取失败时, 返回该值 - /// - public virtual DateTime Read(string section, string key, DateTime value = new DateTime()) - { - DateTime result; - try - { - result = DateTime.Parse(this.Read(section, key, string.Empty)); - } - catch - { - result = value; - } - return result; - } - /// - /// 读取Ini文件指定节, 指定键中的值 - /// - /// 节 - /// 键 - /// 值, 当读取失败时, 返回该值 - /// - public virtual TimeSpan Read(string section, string key, TimeSpan value = new TimeSpan()) - { - TimeSpan result; - try - { - result = TimeSpan.Parse(this.Read(section, key, string.Empty)); - } - catch - { - result = value; - } - return result; - } - #endregion - - /// - /// 读取Ini文件的所有节集合 - /// - /// - public List ReadSections() - { - byte[] buffer = new byte[65535]; - int rel = Kernel32.GetPrivateProfileSectionNamesA(buffer, buffer.GetUpperBound(0), this.FileName); - int iCnt, iPos; - List arrayList = new List(); - string tmp; - if (rel > 0) - { - iCnt = 0; iPos = 0; - for (iCnt = 0; iCnt < rel; iCnt++) - { - if (buffer[iCnt] == 0x00) - { - tmp = ASCIIEncoding.Default.GetString(buffer, iPos, iCnt).Trim(); - iPos = iCnt + 1; - if (tmp != "") - arrayList.Add(tmp); - } - } - } - return arrayList; - } - /// - /// 判断指定的节是否存在 - /// - /// 节名称 - /// - public bool SectionExists(string section) - { - StringBuilder buffer = new StringBuilder(65535); - Kernel32.GetPrivateProfileSectionA(section, buffer, buffer.Capacity, this.FileName); - return buffer.ToString().Trim() == ""; - } - /// - /// 判断指定的节中指定的键是否存在 - /// - /// 节名称 - /// 键名称 - /// - public bool ValueExits(string section, string key) - { - return Read(section, key, string.Empty).Trim() == ""; - } - /// - /// 删除指定的节中的指定键 - /// - /// 该键所在的节的名称 - /// 该键的名称 - public void DeleteKey(string section, string key) - { - Write(section, key, null); - } - /// - /// 删除指定的节的所有内容 - /// - /// 要删除的节的名字 - public void DeleteSection(string section) - { - Kernel32.WritePrivateProfileSectionA(section, null, this.FileName); - } - /// - /// 添加一个节 - /// - /// 要添加的节名称 - public void AddSection(string section) - { - Kernel32.WritePrivateProfileSectionA(section, "", this.FileName); - } - /// - /// 清空Ini配置文件 - /// - public void Clear() - { - File.Delete(this.FileName); - File.Create(this.FileName); - } - #endregion - } -} diff --git a/Native.Csharp.Tool/Native.Csharp.Tool.csproj b/Native.Csharp.Tool/Native.Csharp.Tool.csproj index 56a97c98..642aa573 100644 --- a/Native.Csharp.Tool/Native.Csharp.Tool.csproj +++ b/Native.Csharp.Tool/Native.Csharp.Tool.csproj @@ -16,11 +16,14 @@ true bin\x86\Debug\ - DEBUG;TRACE + TRACE;DEBUG;NET_40;SQLITE_STANDARD;INTEROP_VIRTUAL_TABLE;INTEROP_SESSION_EXTENSION;TRACE_SHARED full x86 prompt MinimumRecommendedRules.ruleset + 618,1591;3001 + NU1605 + bin\x86\Debug\Native.Csharp.Tool.xml bin\x86\Release\ @@ -34,6 +37,7 @@ + @@ -42,23 +46,80 @@ - - + Component - + + + + + Component + + + + + + + + + + Component + + + Component + + + + + + + Component + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + \ No newline at end of file diff --git a/Native.Csharp.Tool/NativeConvert.cs b/Native.Csharp.Tool/NativeConvert.cs index aa09c9fe..6fb187e3 100644 --- a/Native.Csharp.Tool/NativeConvert.cs +++ b/Native.Csharp.Tool/NativeConvert.cs @@ -3,76 +3,84 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; -using Native.Csharp.Tool.Core; namespace Native.Csharp.Tool { /// - /// Native 用于数据转换的工具类 + /// 转换工具类 /// public static class NativeConvert { + #region --Kernel32-- + [DllImport ("kernel32.dll", EntryPoint = "lstrlenA", CharSet = CharSet.Ansi)] + internal extern static int LstrlenA (IntPtr ptr); + #endregion + /// - /// 获取10或13位时间戳的 System.DateTime 表示形式 + /// 获取 Unix 时间戳的 表示形式 /// - /// 10 or 13 位时间戳 + /// unix 时间戳 /// - public static DateTime FotmatUnixTime (string timeStamp) + public static DateTime ToDateTime (long unixTime) { DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime (new DateTime (1970, 1, 1)); - long lTime; - if (timeStamp.Length.Equals (10))//判断是10位 - { - lTime = long.Parse (timeStamp + "0000000"); - } - else - { - lTime = long.Parse (timeStamp + "0000");//13位 - } - TimeSpan toNow = new TimeSpan (lTime); + TimeSpan toNow = new TimeSpan (unixTime); DateTime daTime = dtStart.Add (toNow); return daTime; } /// - /// 获取指定 IntPtr 实例中的字符串 + /// 获取 Unix 时间戳的 表示形式 /// - /// 字符串的 IntPtr 对象 + /// unix 时间戳 + /// + public static DateTime ToDateTime (int unixTime) + { + DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime (new DateTime (1970, 1, 1)); + TimeSpan toNow = new TimeSpan (unixTime); + DateTime daTime = dtStart.Add (toNow); + return daTime; + } + + /// + /// 转换字符串的 实例对象 + /// + /// 将转换的字符串 /// 目标编码格式 /// - public static string ToPtrString (IntPtr strPtr, Encoding encoding = null) + public static IntPtr ToIntPtr (string source, Encoding encoding = null) { if (encoding == null) { - encoding = Encoding.Default; + encoding = Encoding.ASCII; } - - int len = Kernel32.LstrlenA (strPtr); //获取指针中数据的长度 - if (len == 0) - { - return string.Empty; - } - - byte[] buffer = new byte[len]; - Marshal.Copy (strPtr, buffer, 0, len); - return encoding.GetString (buffer); + byte[] buffer = encoding.GetBytes (source); + GCHandle hobj = GCHandle.Alloc (buffer, GCHandleType.Pinned); + return hobj.AddrOfPinnedObject (); } /// - /// 获取字符串的 IntPtr 实例对象 + /// 读取指针内所有的字节数组并编码为指定字符串 /// - /// 将转换的字符串 + /// 字符串的 对象 /// 目标编码格式 /// - public static IntPtr ToStringPtr (string value, Encoding encoding = null) + public static string ToString (IntPtr strPtr, Encoding encoding = null) { if (encoding == null) { encoding = Encoding.Default; } - byte[] buffer = encoding.GetBytes (value); - GCHandle hobj = GCHandle.Alloc (buffer, GCHandleType.Pinned); - return hobj.AddrOfPinnedObject (); + + int len = LstrlenA (strPtr); //获取指针中数据的长度 + if (len == 0) + { + return string.Empty; + } + + byte[] buffer = new byte[len]; + Marshal.Copy (strPtr, buffer, 0, len); + return encoding.GetString (buffer); } } } diff --git a/Native.Csharp.Tool/Properties/AssemblyInfo.cs b/Native.Csharp.Tool/Properties/AssemblyInfo.cs index 48046569..7d81e91e 100644 --- a/Native.Csharp.Tool/Properties/AssemblyInfo.cs +++ b/Native.Csharp.Tool/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 //通过使用 "*",如下所示: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion ("3.0.6.0525")] -[assembly: AssemblyFileVersion ("3.0.6.0525")] +[assembly: AssemblyVersion ("3.0.7.0607")] +[assembly: AssemblyFileVersion ("3.0.7.0607")] diff --git a/Native.Csharp.Tool/Properties/AssemblySourceIdAttribute.cs b/Native.Csharp.Tool/Properties/AssemblySourceIdAttribute.cs new file mode 100644 index 00000000..3666fe19 --- /dev/null +++ b/Native.Csharp.Tool/Properties/AssemblySourceIdAttribute.cs @@ -0,0 +1,42 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Joe Mistachkin (joe@mistachkin.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +using System; + +namespace System.Data.SQLite +{ + /// + /// Defines a source code identifier custom attribute for an assembly + /// manifest. + /// + [AttributeUsage(AttributeTargets.Assembly, Inherited = false)] + public sealed class AssemblySourceIdAttribute : Attribute + { + /// + /// Constructs an instance of this attribute class using the specified + /// source code identifier value. + /// + /// + /// The source code identifier value to use. + /// + public AssemblySourceIdAttribute(string value) + { + sourceId = value; + } + + /////////////////////////////////////////////////////////////////////// + + private string sourceId; + /// + /// Gets the source code identifier value. + /// + public string SourceId + { + get { return sourceId; } + } + } +} diff --git a/Native.Csharp.Tool/Properties/AssemblySourceTimeStampAttribute.cs b/Native.Csharp.Tool/Properties/AssemblySourceTimeStampAttribute.cs new file mode 100644 index 00000000..828aca66 --- /dev/null +++ b/Native.Csharp.Tool/Properties/AssemblySourceTimeStampAttribute.cs @@ -0,0 +1,42 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Joe Mistachkin (joe@mistachkin.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +using System; + +namespace System.Data.SQLite +{ + /// + /// Defines a source code time-stamp custom attribute for an assembly + /// manifest. + /// + [AttributeUsage(AttributeTargets.Assembly, Inherited = false)] + public sealed class AssemblySourceTimeStampAttribute : Attribute + { + /// + /// Constructs an instance of this attribute class using the specified + /// source code time-stamp value. + /// + /// + /// The source code time-stamp value to use. + /// + public AssemblySourceTimeStampAttribute(string value) + { + sourceTimeStamp = value; + } + + /////////////////////////////////////////////////////////////////////// + + private string sourceTimeStamp; + /// + /// Gets the source code time-stamp value. + /// + public string SourceTimeStamp + { + get { return sourceTimeStamp; } + } + } +} diff --git a/Native.Csharp.Tool/SQLite/Configurations/System.Data.SQLite.dll.config b/Native.Csharp.Tool/SQLite/Configurations/System.Data.SQLite.dll.config new file mode 100644 index 00000000..96afee22 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/Configurations/System.Data.SQLite.dll.config @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Native.Csharp.Tool/SQLite/Generated/SR.resources b/Native.Csharp.Tool/SQLite/Generated/SR.resources new file mode 100644 index 00000000..1c26a86d Binary files /dev/null and b/Native.Csharp.Tool/SQLite/Generated/SR.resources differ diff --git a/Native.Csharp.Tool/SQLite/ISQLiteNativeModule.cs b/Native.Csharp.Tool/SQLite/ISQLiteNativeModule.cs new file mode 100644 index 00000000..3f340334 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/ISQLiteNativeModule.cs @@ -0,0 +1,1642 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Joe Mistachkin (joe@mistachkin.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + #region ISQLiteNativeModule Interface + /// + /// This interface represents a virtual table implementation written in + /// native code. + /// + public interface ISQLiteNativeModule + { + /// + /// + /// int (*xCreate)(sqlite3 *db, void *pAux, + /// int argc, char *const*argv, + /// sqlite3_vtab **ppVTab, + /// char **pzErr); + /// + /// + /// The xCreate method is called to create a new instance of a virtual table + /// in response to a CREATE VIRTUAL TABLE statement. + /// If the xCreate method is the same pointer as the xConnect method, then the + /// virtual table is an eponymous virtual table. + /// If the xCreate method is omitted (if it is a NULL pointer) then the virtual + /// table is an eponymous-only virtual table. + /// + /// + /// The db parameter is a pointer to the SQLite database connection that + /// is executing the CREATE VIRTUAL TABLE statement. + /// The pAux argument is the copy of the client data pointer that was the + /// fourth argument to the sqlite3_create_module() or + /// sqlite3_create_module_v2() call that registered the + /// virtual table module. + /// The argv parameter is an array of argc pointers to null terminated strings. + /// The first string, argv[0], is the name of the module being invoked. The + /// module name is the name provided as the second argument to + /// sqlite3_create_module() and as the argument to the USING clause of the + /// CREATE VIRTUAL TABLE statement that is running. + /// The second, argv[1], is the name of the database in which the new virtual table is being created. The database name is "main" for the primary database, or + /// "temp" for TEMP database, or the name given at the end of the ATTACH + /// statement for attached databases. The third element of the array, argv[2], + /// is the name of the new virtual table, as specified following the TABLE + /// keyword in the CREATE VIRTUAL TABLE statement. + /// If present, the fourth and subsequent strings in the argv[] array report + /// the arguments to the module name in the CREATE VIRTUAL TABLE statement. + /// + /// + /// The job of this method is to construct the new virtual table object + /// (an sqlite3_vtab object) and return a pointer to it in *ppVTab. + /// + /// + /// As part of the task of creating a new sqlite3_vtab structure, this + /// method must invoke sqlite3_declare_vtab() to tell the SQLite + /// core about the columns and datatypes in the virtual table. + /// The sqlite3_declare_vtab() API has the following prototype: + /// + /// + /// int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable) + /// + /// + /// The first argument to sqlite3_declare_vtab() must be the same + /// database connection pointer as the first parameter to this method. + /// The second argument to sqlite3_declare_vtab() must a zero-terminated + /// UTF-8 string that contains a well-formed CREATE TABLE statement that + /// defines the columns in the virtual table and their data types. + /// The name of the table in this CREATE TABLE statement is ignored, + /// as are all constraints. Only the column names and datatypes matter. + /// The CREATE TABLE statement string need not to be + /// held in persistent memory. The string can be + /// deallocated and/or reused as soon as the sqlite3_declare_vtab() + /// routine returns. + /// + /// + /// The xCreate method need not initialize the pModule, nRef, and zErrMsg + /// fields of the sqlite3_vtab object. The SQLite core will take care of + /// that chore. + /// + /// + /// The xCreate should return SQLITE_OK if it is successful in + /// creating the new virtual table, or SQLITE_ERROR if it is not successful. + /// If not successful, the sqlite3_vtab structure must not be allocated. + /// An error message may optionally be returned in *pzErr if unsuccessful. + /// Space to hold the error message string must be allocated using + /// an SQLite memory allocation function like + /// sqlite3_malloc() or sqlite3_mprintf() as the SQLite core will + /// attempt to free the space using sqlite3_free() after the error has + /// been reported up to the application. + /// + /// + /// If the xCreate method is omitted (left as a NULL pointer) then the + /// virtual table is an eponymous-only virtual table. New instances of + /// the virtual table cannot be created using CREATE VIRTUAL TABLE and the + /// virtual table can only be used via its module name. + /// Note that SQLite versions prior to 3.9.0 (2015-10-14) do not understand + /// eponymous-only virtual tables and will segfault if an attempt is made + /// to CREATE VIRTUAL TABLE on an eponymous-only virtual table because + /// the xCreate method was not checked for null. + /// + /// + /// If the xCreate method is the exact same pointer as the xConnect method, + /// that indicates that the virtual table does not need to initialize backing + /// store. Such a virtual table can be used as an eponymous virtual table + /// or as a named virtual table using CREATE VIRTUAL TABLE or both. + /// + /// + /// If a column datatype contains the special keyword "HIDDEN" + /// (in any combination of upper and lower case letters) then that keyword + /// it is omitted from the column datatype name and the column is marked + /// as a hidden column internally. + /// A hidden column differs from a normal column in three respects: + /// + /// + /// ]]> + /// ]]> Hidden columns are not listed in the dataset returned by + /// "PRAGMA table_info", + /// ]]>]]> Hidden columns are not included in the expansion of a "*" + /// expression in the result set of a SELECT, and + /// ]]>]]> Hidden columns are not included in the implicit column-list + /// used by an INSERT statement that lacks an explicit column-list. + /// ]]>]]> + /// + /// + /// For example, if the following SQL is passed to sqlite3_declare_vtab(): + /// + /// + /// CREATE TABLE x(a HIDDEN VARCHAR(12), b INTEGER, c INTEGER Hidden); + /// + /// + /// Then the virtual table would be created with two hidden columns, + /// and with datatypes of "VARCHAR(12)" and "INTEGER". + /// + /// + /// An example use of hidden columns can be seen in the FTS3 virtual + /// table implementation, where every FTS virtual table + /// contains an FTS hidden column that is used to pass information from the + /// virtual table into FTS auxiliary functions and to the FTS MATCH operator. + /// + /// + /// A virtual table that contains hidden columns can be used like + /// a table-valued function in the FROM clause of a SELECT statement. + /// The arguments to the table-valued function become constraints on + /// the HIDDEN columns of the virtual table. + /// + /// + /// For example, the "generate_series" extension (located in the + /// ext/misc/series.c + /// file in the source tree) + /// implements an eponymous virtual table with the following schema: + /// + /// + /// CREATE TABLE generate_series( + /// value, + /// start HIDDEN, + /// stop HIDDEN, + /// step HIDDEN + /// ); + /// + /// + /// The sqlite3_module.xBestIndex method in the implementation of this + /// table checks for equality constraints against the HIDDEN columns, and uses + /// those as input parameters to determine the range of integer "value" outputs + /// to generate. Reasonable defaults are used for any unconstrained columns. + /// For example, to list all integers between 5 and 50: + /// + /// + /// SELECT value FROM generate_series(5,50); + /// + /// + /// The previous query is equivalent to the following: + /// + /// + /// SELECT value FROM generate_series WHERE start=5 AND stop=50; + /// + /// + /// Arguments on the virtual table name are matched to hidden columns + /// in order. The number of arguments can be less than the + /// number of hidden columns, in which case the latter hidden columns are + /// unconstrained. However, an error results if there are more arguments + /// than there are hidden columns in the virtual table. + /// + /// + /// Beginning with SQLite version 3.14.0 (2016-08-08), + /// the CREATE TABLE statement that + /// is passed into sqlite3_declare_vtab() may contain a WITHOUT ROWID clause. + /// This is useful for cases where the virtual table rows + /// cannot easily be mapped into unique integers. A CREATE TABLE + /// statement that includes WITHOUT ROWID must define one or more columns as + /// the PRIMARY KEY. Every column of the PRIMARY KEY must individually be + /// NOT NULL and all columns for each row must be collectively unique. + /// + /// + /// Note that SQLite does not enforce the PRIMARY KEY for a WITHOUT ROWID + /// virtual table. Enforcement is the responsibility of the underlying + /// virtual table implementation. But SQLite does assume that the PRIMARY KEY + /// constraint is valid - that the identified columns really are UNIQUE and + /// NOT NULL - and it uses that assumption to optimize queries against the + /// virtual table. + /// + /// + /// The rowid column is not accessible on a + /// WITHOUT ROWID virtual table (of course). + /// + /// + /// The xUpdate method was originally designed around having a + /// ROWID as a single value. The xUpdate method has been expanded to + /// accommodate an arbitrary PRIMARY KEY in place of the ROWID, but the + /// PRIMARY KEY must still be only one column. For this reason, SQLite + /// will reject any WITHOUT ROWID virtual table that has more than one + /// PRIMARY KEY column and a non-NULL xUpdate method. + /// + /// + /// + /// The native database connection handle. + /// + /// + /// The original native pointer value that was provided to the + /// sqlite3_create_module(), sqlite3_create_module_v2() or + /// sqlite3_create_disposable_module() functions. + /// + /// + /// The number of arguments from the CREATE VIRTUAL TABLE statement. + /// + /// + /// The array of string arguments from the CREATE VIRTUAL TABLE + /// statement. + /// + /// + /// Upon success, this parameter must be modified to point to the newly + /// created native sqlite3_vtab derived structure. + /// + /// + /// Upon failure, this parameter must be modified to point to the error + /// message, with the underlying memory having been obtained from the + /// sqlite3_malloc() function. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xCreate( + IntPtr pDb, + IntPtr pAux, + int argc, + IntPtr argv, + ref IntPtr pVtab, + ref IntPtr pError + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xConnect)(sqlite3*, void *pAux, + /// int argc, char *const*argv, + /// sqlite3_vtab **ppVTab, + /// char **pzErr); + /// + /// + /// The xConnect method is very similar to xCreate. + /// It has the same parameters and constructs a new sqlite3_vtab structure + /// just like xCreate. + /// And it must also call sqlite3_declare_vtab() like xCreate. + /// + /// + /// The difference is that xConnect is called to establish a new + /// connection to an existing virtual table whereas xCreate is called + /// to create a new virtual table from scratch. + /// + /// + /// The xCreate and xConnect methods are only different when the + /// virtual table has some kind of backing store that must be initialized + /// the first time the virtual table is created. The xCreate method creates + /// and initializes the backing store. The xConnect method just connects + /// to an existing backing store. When xCreate and xConnect are the same, + /// the table is an eponymous virtual table. + /// + /// + /// As an example, consider a virtual table implementation that + /// provides read-only access to existing comma-separated-value (CSV) + /// files on disk. There is no backing store that needs to be created + /// or initialized for such a virtual table (since the CSV files already + /// exist on disk) so the xCreate and xConnect methods will be identical + /// for that module. + /// + /// + /// Another example is a virtual table that implements a full-text index. + /// The xCreate method must create and initialize data structures to hold + /// the dictionary and posting lists for that index. The xConnect method, + /// on the other hand, only has to locate and use an existing dictionary + /// and posting lists that were created by a prior xCreate call. + /// + /// + /// The xConnect method must return SQLITE_OK if it is successful + /// in creating the new virtual table, or SQLITE_ERROR if it is not + /// successful. If not successful, the sqlite3_vtab structure must not be + /// allocated. An error message may optionally be returned in *pzErr if + /// unsuccessful. + /// Space to hold the error message string must be allocated using + /// an SQLite memory allocation function like + /// sqlite3_malloc() or sqlite3_mprintf() as the SQLite core will + /// attempt to free the space using sqlite3_free() after the error has + /// been reported up to the application. + /// + /// + /// The xConnect method is required for every virtual table implementation, + /// though the xCreate and xConnect pointers of the sqlite3_module object + /// may point to the same function if the virtual table does not need to + /// initialize backing store. + /// + /// + /// + /// The native database connection handle. + /// + /// + /// The original native pointer value that was provided to the + /// sqlite3_create_module(), sqlite3_create_module_v2() or + /// sqlite3_create_disposable_module() functions. + /// + /// + /// The number of arguments from the CREATE VIRTUAL TABLE statement. + /// + /// + /// The array of string arguments from the CREATE VIRTUAL TABLE + /// statement. + /// + /// + /// Upon success, this parameter must be modified to point to the newly + /// created native sqlite3_vtab derived structure. + /// + /// + /// Upon failure, this parameter must be modified to point to the error + /// message, with the underlying memory having been obtained from the + /// sqlite3_malloc() function. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xConnect( + IntPtr pDb, + IntPtr pAux, + int argc, + IntPtr argv, + ref IntPtr pVtab, + ref IntPtr pError + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// SQLite uses the xBestIndex method of a virtual table module to determine + /// the best way to access the virtual table. + /// The xBestIndex method has a prototype like this: + /// + /// + /// int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*); + /// + /// + /// The SQLite core communicates with the xBestIndex method by filling + /// in certain fields of the sqlite3_index_info structure and passing a + /// pointer to that structure into xBestIndex as the second parameter. + /// The xBestIndex method fills out other fields of this structure which + /// forms the reply. The sqlite3_index_info structure looks like this: + /// + /// + /// struct sqlite3_index_info { + /// /* Inputs */ + /// const int nConstraint; /* Number of entries in aConstraint */ + /// const struct sqlite3_index_constraint { + /// int iColumn; /* Column constrained. -1 for ROWID */ + /// unsigned char op; /* Constraint operator */ + /// unsigned char usable; /* True if this constraint is usable */ + /// int iTermOffset; /* Used internally - xBestIndex should ignore */ + /// } *const aConstraint; /* Table of WHERE clause constraints */ + /// const int nOrderBy; /* Number of terms in the ORDER BY clause */ + /// const struct sqlite3_index_orderby { + /// int iColumn; /* Column number */ + /// unsigned char desc; /* True for DESC. False for ASC. */ + /// } *const aOrderBy; /* The ORDER BY clause */ + /// /* Outputs */ + /// struct sqlite3_index_constraint_usage { + /// int argvIndex; /* if >0, constraint is part of argv to xFilter */ + /// unsigned char omit; /* Do not code a test for this constraint */ + /// } *const aConstraintUsage; + /// int idxNum; /* Number used to identify the index */ + /// char *idxStr; /* String, possibly obtained from sqlite3_malloc */ + /// int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */ + /// int orderByConsumed; /* True if output is already ordered */ + /// double estimatedCost; /* Estimated cost of using this index */ + /// ]]>/* Fields below are only available in SQLite 3.8.2 and later */]]> + /// sqlite3_int64 estimatedRows; /* Estimated number of rows returned */ + /// ]]>/* Fields below are only available in SQLite 3.9.0 and later */]]> + /// int idxFlags; /* Mask of SQLITE_INDEX_SCAN_* flags */ + /// ]]>/* Fields below are only available in SQLite 3.10.0 and later */]]> + /// sqlite3_uint64 colUsed; /* Input: Mask of columns used by statement */ + /// }; + /// + /// + /// Note the warnings on the "estimatedRows", "idxFlags", and colUsed fields. + /// These fields were added with SQLite versions 3.8.2, 3.9.0, and 3.10.0, respectively. + /// Any extension that reads or writes these fields must first check that the + /// version of the SQLite library in use is greater than or equal to appropriate + /// version - perhaps comparing the value returned from sqlite3_libversion_number() + /// against constants 3008002, 3009000, and/or 3010000. The result of attempting + /// to access these fields in an sqlite3_index_info structure created by an + /// older version of SQLite are undefined. + /// + /// + /// In addition, there are some defined constants: + /// + /// + /// #define SQLITE_INDEX_CONSTRAINT_EQ 2 + /// #define SQLITE_INDEX_CONSTRAINT_GT 4 + /// #define SQLITE_INDEX_CONSTRAINT_LE 8 + /// #define SQLITE_INDEX_CONSTRAINT_LT 16 + /// #define SQLITE_INDEX_CONSTRAINT_GE 32 + /// #define SQLITE_INDEX_CONSTRAINT_MATCH 64 + /// #define SQLITE_INDEX_CONSTRAINT_LIKE 65 /* 3.10.0 and later */ + /// #define SQLITE_INDEX_CONSTRAINT_GLOB 66 /* 3.10.0 and later */ + /// #define SQLITE_INDEX_CONSTRAINT_REGEXP 67 /* 3.10.0 and later */ + /// #define SQLITE_INDEX_CONSTRAINT_NE 68 /* 3.21.0 and later */ + /// #define SQLITE_INDEX_CONSTRAINT_ISNOT 69 /* 3.21.0 and later */ + /// #define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 /* 3.21.0 and later */ + /// #define SQLITE_INDEX_CONSTRAINT_ISNULL 71 /* 3.21.0 and later */ + /// #define SQLITE_INDEX_CONSTRAINT_IS 72 /* 3.21.0 and later */ + /// #define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 /* 3.25.0 and later */ + /// #define SQLITE_INDEX_SCAN_UNIQUE 1 /* Scan visits at most 1 row */ + /// + /// + /// The SQLite core calls the xBestIndex method when it is compiling a query + /// that involves a virtual table. In other words, SQLite calls this method + /// when it is running sqlite3_prepare() or the equivalent. + /// By calling this method, the + /// SQLite core is saying to the virtual table that it needs to access + /// some subset of the rows in the virtual table and it wants to know the + /// most efficient way to do that access. The xBestIndex method replies + /// with information that the SQLite core can then use to conduct an + /// efficient search of the virtual table. + /// + /// + /// While compiling a single SQL query, the SQLite core might call + /// xBestIndex multiple times with different settings in sqlite3_index_info. + /// The SQLite core will then select the combination that appears to + /// give the best performance. + /// + /// + /// Before calling this method, the SQLite core initializes an instance + /// of the sqlite3_index_info structure with information about the + /// query that it is currently trying to process. This information + /// derives mainly from the WHERE clause and ORDER BY or GROUP BY clauses + /// of the query, but also from any ON or USING clauses if the query is a + /// join. The information that the SQLite core provides to the xBestIndex + /// method is held in the part of the structure that is marked as "Inputs". + /// The "Outputs" section is initialized to zero. + /// + /// + /// The information in the sqlite3_index_info structure is ephemeral + /// and may be overwritten or deallocated as soon as the xBestIndex method + /// returns. If the xBestIndex method needs to remember any part of the + /// sqlite3_index_info structure, it should make a copy. Care must be + /// take to store the copy in a place where it will be deallocated, such + /// as in the idxStr field with needToFreeIdxStr set to 1. + /// + /// + /// Note that xBestIndex will always be called before xFilter, since + /// the idxNum and idxStr outputs from xBestIndex are required inputs to + /// xFilter. However, there is no guarantee that xFilter will be called + /// following a successful xBestIndex. + /// + /// + /// The xBestIndex method is required for every virtual table implementation. + /// + /// + /// The main thing that the SQLite core is trying to communicate to + /// the virtual table is the constraints that are available to limit + /// the number of rows that need to be searched. The aConstraint[] array + /// contains one entry for each constraint. There will be exactly + /// nConstraint entries in that array. + /// + /// + /// Each constraint will usually correspond to a term in the WHERE clause + /// or in a USING or ON clause that is of the form + /// + /// + /// column OP EXPR + /// + /// + /// Where "column" is a column in the virtual table, OP is an operator + /// like "=" or "<", and EXPR is an arbitrary expression. So, for example, + /// if the WHERE clause contained a term like this: + /// + /// + /// a = 5 + /// + /// + /// Then one of the constraints would be on the "a" column with + /// operator "=" and an expression of "5". Constraints need not have a + /// literal representation of the WHERE clause. The query optimizer might + /// make transformations to the + /// WHERE clause in order to extract as many constraints + /// as it can. So, for example, if the WHERE clause contained something + /// like this: + /// + /// + /// x BETWEEN 10 AND 100 AND 999>y + /// + /// + /// The query optimizer might translate this into three separate constraints: + /// + /// + /// x >= 10 + /// x <= 100 + /// y < 999 + /// + /// + /// For each such constraint, the aConstraint[].iColumn field indicates which + /// column appears on the left-hand side of the constraint. + /// The first column of the virtual table is column 0. + /// The rowid of the virtual table is column -1. + /// The aConstraint[].op field indicates which operator is used. + /// The SQLITE_INDEX_CONSTRAINT_* constants map integer constants + /// into operator values. + /// Columns occur in the order they were defined by the call to + /// sqlite3_declare_vtab() in the xCreate or xConnect method. + /// Hidden columns are counted when determining the column index. + /// + /// + /// If the xFindFunction() method for the virtual table is defined, and + /// if xFindFunction() sometimes returns SQLITE_INDEX_CONSTRAINT_FUNCTION or + /// larger, then the constraints might also be of the form: + /// + /// + /// FUNCTION( column, EXPR) + /// + /// + /// In this case the aConstraint[].op value is the same as the value + /// returned by xFindFunction() for FUNCTION. + /// + /// + /// The aConstraint[] array contains information about all constraints + /// that apply to the virtual table. But some of the constraints might + /// not be usable because of the way tables are ordered in a join. + /// The xBestIndex method must therefore only consider constraints + /// that have an aConstraint[].usable flag which is true. + /// + /// + /// In addition to WHERE clause constraints, the SQLite core also + /// tells the xBestIndex method about the ORDER BY clause. + /// (In an aggregate query, the SQLite core might put in GROUP BY clause + /// information in place of the ORDER BY clause information, but this fact + /// should not make any difference to the xBestIndex method.) + /// If all terms of the ORDER BY clause are columns in the virtual table, + /// then nOrderBy will be the number of terms in the ORDER BY clause + /// and the aOrderBy[] array will identify the column for each term + /// in the order by clause and whether or not that column is ASC or DESC. + /// + /// + /// In SQLite version 3.10.0 (2016-01-06) and later, + /// the colUsed field is available + /// to indicate which fields of the virtual table are actually used by the + /// statement being prepared. If the lowest bit of colUsed is set, that + /// means that the first column is used. The second lowest bit corresponds + /// to the second column. And so forth. If the most significant bit of + /// colUsed is set, that means that one or more columns other than the + /// first 63 columns are used. If column usage information is needed by the + /// xFilter method, then the required bits must be encoded into either + /// the idxNum or idxStr output fields. + /// + /// + /// Given all of the information above, the job of the xBestIndex + /// method it to figure out the best way to search the virtual table. + /// + /// + /// The xBestIndex method fills the idxNum and idxStr fields with + /// information that communicates an indexing strategy to the xFilter + /// method. The information in idxNum and idxStr is arbitrary as far + /// as the SQLite core is concerned. The SQLite core just copies the + /// information through to the xFilter method. Any desired meaning can + /// be assigned to idxNum and idxStr as long as xBestIndex and xFilter + /// agree on what that meaning is. + /// + /// + /// The idxStr value may be a string obtained from an SQLite + /// memory allocation function such as sqlite3_mprintf(). + /// If this is the case, then the needToFreeIdxStr flag must be set to + /// true so that the SQLite core will know to call sqlite3_free() on + /// that string when it has finished with it, and thus avoid a memory leak. + /// The idxStr value may also be a static constant string, in which case + /// the needToFreeIdxStr boolean should remain false. + /// + /// + /// If the virtual table will output rows in the order specified by + /// the ORDER BY clause, then the orderByConsumed flag may be set to + /// true. If the output is not automatically in the correct order + /// then orderByConsumed must be left in its default false setting. + /// This will indicate to the SQLite core that it will need to do a + /// separate sorting pass over the data after it comes out of the virtual table. + /// + /// + /// The estimatedCost field should be set to the estimated number + /// of disk access operations required to execute this query against + /// the virtual table. The SQLite core will often call xBestIndex + /// multiple times with different constraints, obtain multiple cost + /// estimates, then choose the query plan that gives the lowest estimate. + /// The SQLite core initializes estimatedCost to a very large value + /// prior to invoking xBestIndex, so if xBestIndex determines that the + /// current combination of parameters is undesirable, it can leave the + /// estimatedCost field unchanged to discourage its use. + /// + /// + /// If the current version of SQLite is 3.8.2 or greater, the estimatedRows + /// field may be set to an estimate of the number of rows returned by the + /// proposed query plan. If this value is not explicitly set, the default + /// estimate of 25 rows is used. + /// + /// + /// If the current version of SQLite is 3.9.0 or greater, the idxFlags field + /// may be set to SQLITE_INDEX_SCAN_UNIQUE to indicate that the virtual table + /// will return only zero or one rows given the input constraints. Additional + /// bits of the idxFlags field might be understood in later versions of SQLite. + /// + /// + /// The aConstraintUsage[] array contains one element for each of + /// the nConstraint constraints in the inputs section of the + /// sqlite3_index_info structure. + /// The aConstraintUsage[] array is used by xBestIndex to tell the + /// core how it is using the constraints. + /// + /// + /// The xBestIndex method may set aConstraintUsage[].argvIndex + /// entries to values greater than zero. + /// Exactly one entry should be set to 1, another to 2, another to 3, + /// and so forth up to as many or as few as the xBestIndex method wants. + /// The EXPR of the corresponding constraints will then be passed + /// in as the argv[] parameters to xFilter. + /// + /// + /// For example, if the aConstraint[3].argvIndex is set to 1, then + /// when xFilter is called, the argv[0] passed to xFilter will have + /// the EXPR value of the aConstraint[3] constraint. + /// + /// + /// By default, the SQLite core double checks all constraints on + /// each row of the virtual table that it receives. If such a check + /// is redundant, the xBestFilter method can suppress that double-check by + /// setting aConstraintUsage[].omit. + /// + /// + /// The xBestIndex method should return SQLITE_OK on success. If any + /// kind of fatal error occurs, an appropriate error code (ex: SQLITE_NOMEM) + /// should be returned instead. + /// + /// + /// If xBestIndex returns SQLITE_CONSTRAINT, that does not indicate an + /// error. Rather, SQLITE_CONSTRAINT indicates that the particular combination + /// of input parameters specified should not be used in the query plan. + /// The SQLITE_CONSTRAINT return is useful for table-valued functions that + /// have required parameters. If the aConstraint[].usable field is false + /// for one of the required parameter, then the xBestIndex method should + /// return SQLITE_CONSTRAINT. + /// + /// + /// The following example will better illustrate the use of SQLITE_CONSTRAINT + /// as a return value from xBestIndex: + /// + /// + /// SELECT * FROM realtab, tablevaluedfunc(realtab.x); + /// + /// + /// Assuming that the first hidden column of "tablevaluedfunc" is "param1", + /// the query above is semantically equivalent to this: + /// + /// + /// SELECT * FROM realtab, tablevaluedfunc + /// WHERE tablevaluedfunc.param1 = realtab.x; + /// + /// + /// The query planner must decide between many possible implementations + /// of this query, but two plans in particular are of note: + /// + /// ]]> + /// ]]>Scan all + /// rows of realtab and for each row, find rows in tablevaluedfunc where + /// param1 is equal to realtab.x + /// ]]>]]>Scan all rows of tablevalued func and for each row find rows + /// in realtab where x is equal to tablevaluedfunc.param1. + /// ]]>]]> + /// + /// The xBestIndex method will be invoked once for each of the potential + /// plans above. For plan 1, the aConstraint[].usable flag for for the + /// SQLITE_CONSTRAINT_EQ constraint on the param1 column will be true because + /// the right-hand side value for the "param1 = ?" constraint will be known, + /// since it is determined by the outer realtab loop. + /// But for plan 2, the aConstraint[].usable flag for "param1 = ?" will be false + /// because the right-hand side value is determined by an inner loop and is thus + /// an unknown quantity. Because param1 is a required input to the table-valued + /// functions, the xBestIndex method should return SQLITE_CONSTRAINT when presented + /// with plan 2, indicating that a required input is missing. This forces the + /// query planner to select plan 1. + /// + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// The native pointer to the sqlite3_index_info structure. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xBestIndex( + IntPtr pVtab, + IntPtr pIndex + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xDisconnect)(sqlite3_vtab *pVTab); + /// + /// + /// This method releases a connection to a virtual table. + /// Only the sqlite3_vtab object is destroyed. + /// The virtual table is not destroyed and any backing store + /// associated with the virtual table persists. + /// + /// This method undoes the work of xConnect. + /// + /// This method is a destructor for a connection to the virtual table. + /// Contrast this method with xDestroy. The xDestroy is a destructor + /// for the entire virtual table. + /// + /// + /// The xDisconnect method is required for every virtual table implementation, + /// though it is acceptable for the xDisconnect and xDestroy methods to be + /// the same function if that makes sense for the particular virtual table. + /// + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xDisconnect( + IntPtr pVtab + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xDestroy)(sqlite3_vtab *pVTab); + /// + /// + /// This method releases a connection to a virtual table, just like + /// the xDisconnect method, and it also destroys the underlying + /// table implementation. This method undoes the work of xCreate. + /// + /// + /// The xDisconnect method is called whenever a database connection + /// that uses a virtual table is closed. The xDestroy method is only + /// called when a DROP TABLE statement is executed against the virtual table. + /// + /// + /// The xDestroy method is required for every virtual table implementation, + /// though it is acceptable for the xDisconnect and xDestroy methods to be + /// the same function if that makes sense for the particular virtual table. + /// + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xDestroy( + IntPtr pVtab + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor); + /// + /// + /// The xOpen method creates a new cursor used for accessing (read and/or + /// writing) a virtual table. A successful invocation of this method + /// will allocate the memory for the sqlite3_vtab_cursor (or a subclass), + /// initialize the new object, and make *ppCursor point to the new object. + /// The successful call then returns SQLITE_OK. + /// + /// + /// For every successful call to this method, the SQLite core will + /// later invoke the xClose method to destroy + /// the allocated cursor. + /// + /// + /// The xOpen method need not initialize the pVtab field of the + /// sqlite3_vtab_cursor structure. The SQLite core will take care + /// of that chore automatically. + /// + /// + /// A virtual table implementation must be able to support an arbitrary + /// number of simultaneously open cursors. + /// + /// + /// When initially opened, the cursor is in an undefined state. + /// The SQLite core will invoke the xFilter method + /// on the cursor prior to any attempt to position or read from the cursor. + /// + /// + /// The xOpen method is required for every virtual table implementation. + /// + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// Upon success, this parameter must be modified to point to the newly + /// created native sqlite3_vtab_cursor derived structure. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xOpen( + IntPtr pVtab, + ref IntPtr pCursor + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xClose)(sqlite3_vtab_cursor*); + /// + /// + /// The xClose method closes a cursor previously opened by + /// xOpen. + /// The SQLite core will always call xClose once for each cursor opened + /// using xOpen. + /// + /// + /// This method must release all resources allocated by the + /// corresponding xOpen call. The routine will not be called again even if it + /// returns an error. The SQLite core will not use the + /// sqlite3_vtab_cursor again after it has been closed. + /// + /// + /// The xClose method is required for every virtual table implementation. + /// + /// + /// + /// The native pointer to the sqlite3_vtab_cursor derived structure. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xClose( + IntPtr pCursor + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr, + /// int argc, sqlite3_value **argv); + /// + /// + /// This method begins a search of a virtual table. + /// The first argument is a cursor opened by xOpen. + /// The next two arguments define a particular search index previously + /// chosen by xBestIndex. The specific meanings of idxNum and idxStr + /// are unimportant as long as xFilter and xBestIndex agree on what + /// that meaning is. + /// + /// + /// The xBestIndex function may have requested the values of + /// certain expressions using the aConstraintUsage[].argvIndex values + /// of the sqlite3_index_info structure. + /// Those values are passed to xFilter using the argc and argv parameters. + /// + /// + /// If the virtual table contains one or more rows that match the + /// search criteria, then the cursor must be left point at the first row. + /// Subsequent calls to xEof must return false (zero). + /// If there are no rows match, then the cursor must be left in a state + /// that will cause the xEof to return true (non-zero). + /// The SQLite engine will use + /// the xColumn and xRowid methods to access that row content. + /// The xNext method will be used to advance to the next row. + /// + /// + /// This method must return SQLITE_OK if successful, or an sqlite + /// error code if an error occurs. + /// + /// + /// The xFilter method is required for every virtual table implementation. + /// + /// + /// + /// The native pointer to the sqlite3_vtab_cursor derived structure. + /// + /// + /// Number used to help identify the selected index. + /// + /// + /// The native pointer to the UTF-8 encoded string containing the + /// string used to help identify the selected index. + /// + /// + /// The number of native pointers to sqlite3_value structures specified + /// in . + /// + /// + /// An array of native pointers to sqlite3_value structures containing + /// filtering criteria for the selected index. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xFilter( + IntPtr pCursor, + int idxNum, + IntPtr idxStr, + int argc, + IntPtr argv + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xNext)(sqlite3_vtab_cursor*); + /// + /// + /// The xNext method advances a virtual table cursor + /// to the next row of a result set initiated by xFilter. + /// If the cursor is already pointing at the last row when this + /// routine is called, then the cursor no longer points to valid + /// data and a subsequent call to the xEof method must return true (non-zero). + /// If the cursor is successfully advanced to another row of content, then + /// subsequent calls to xEof must return false (zero). + /// + /// + /// This method must return SQLITE_OK if successful, or an sqlite + /// error code if an error occurs. + /// + /// + /// The xNext method is required for every virtual table implementation. + /// + /// + /// + /// The native pointer to the sqlite3_vtab_cursor derived structure. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xNext( + IntPtr pCursor + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xEof)(sqlite3_vtab_cursor*); + /// + /// + /// The xEof method must return false (zero) if the specified cursor + /// currently points to a valid row of data, or true (non-zero) otherwise. + /// This method is called by the SQL engine immediately after each + /// xFilter and xNext invocation. + /// + /// + /// The xEof method is required for every virtual table implementation. + /// + /// + /// + /// The native pointer to the sqlite3_vtab_cursor derived structure. + /// + /// + /// Non-zero if no more rows are available; zero otherwise. + /// + int xEof( + IntPtr pCursor + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int N); + /// + /// + /// The SQLite core invokes this method in order to find the value for + /// the N-th column of the current row. N is zero-based so the first column + /// is numbered 0. + /// The xColumn method may return its result back to SQLite using one of the + /// following interface: + /// + /// + /// ]]> + /// ]]> sqlite3_result_blob() + /// ]]>]]> sqlite3_result_double() + /// ]]>]]> sqlite3_result_int() + /// ]]>]]> sqlite3_result_int64() + /// ]]>]]> sqlite3_result_null() + /// ]]>]]> sqlite3_result_text() + /// ]]>]]> sqlite3_result_text16() + /// ]]>]]> sqlite3_result_text16le() + /// ]]>]]> sqlite3_result_text16be() + /// ]]>]]> sqlite3_result_zeroblob() + /// ]]>]]> + /// + /// + /// If the xColumn method implementation calls none of the functions above, + /// then the value of the column defaults to an SQL NULL. + /// + /// + /// To raise an error, the xColumn method should use one of the result_text() + /// methods to set the error message text, then return an appropriate + /// error code. The xColumn method must return SQLITE_OK on success. + /// + /// + /// The xColumn method is required for every virtual table implementation. + /// + /// + /// + /// The native pointer to the sqlite3_vtab_cursor derived structure. + /// + /// + /// The native pointer to the sqlite3_context structure to be used + /// for returning the specified column value to the SQLite core + /// library. + /// + /// + /// The zero-based index corresponding to the column containing the + /// value to be returned. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xColumn( + IntPtr pCursor, + IntPtr pContext, + int index + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xRowid)(sqlite3_vtab_cursor *pCur, sqlite_int64 *pRowid); + /// + /// + /// A successful invocation of this method will cause *pRowid to be + /// filled with the rowid of row that the + /// virtual table cursor pCur is currently pointing at. + /// This method returns SQLITE_OK on success. + /// It returns an appropriate error code on failure. + /// + /// + /// The xRowid method is required for every virtual table implementation. + /// + /// + /// + /// The native pointer to the sqlite3_vtab_cursor derived structure. + /// + /// + /// Upon success, this parameter must be modified to contain the unique + /// integer row identifier for the current row for the specified cursor. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xRowId( + IntPtr pCursor, + ref long rowId + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xUpdate)( + /// sqlite3_vtab *pVTab, + /// int argc, + /// sqlite3_value **argv, + /// sqlite_int64 *pRowid + /// ); + /// + /// + /// All changes to a virtual table are made using the xUpdate method. + /// This one method can be used to insert, delete, or update. + /// + /// + /// The argc parameter specifies the number of entries in the argv array. + /// The value of argc will be 1 for a pure delete operation or N+2 for an insert + /// or replace or update where N is the number of columns in the table. + /// In the previous sentence, N includes any hidden columns. + /// + /// + /// Every argv entry will have a non-NULL value in C but may contain the + /// SQL value NULL. In other words, it is always true that + /// ]]>argv[i]!=0]]> for ]]>i]]> between 0 and ]]>argc-1]]>. + /// However, it might be the case that + /// ]]>sqlite3_value_type(argv[i])==SQLITE_NULL]]>. + /// + /// + /// The argv[0] parameter is the rowid of a row in the virtual table + /// to be deleted. If argv[0] is an SQL NULL, then no deletion occurs. + /// + /// + /// The argv[1] parameter is the rowid of a new row to be inserted + /// into the virtual table. If argv[1] is an SQL NULL, then the implementation + /// must choose a rowid for the newly inserted row. Subsequent argv[] + /// entries contain values of the columns of the virtual table, in the + /// order that the columns were declared. The number of columns will + /// match the table declaration that the xConnect or xCreate method made + /// using the sqlite3_declare_vtab() call. All hidden columns are included. + /// + /// + /// When doing an insert without a rowid (argc>1, argv[1] is an SQL NULL), + /// on a virtual table that uses ROWID (but not on a WITHOUT ROWID virtual table), + /// the implementation must set *pRowid to the rowid of the newly inserted row; + /// this will become the value returned by the sqlite3_last_insert_rowid() + /// function. Setting this value in all the other cases is a harmless no-op; + /// the SQLite engine ignores the *pRowid return value if argc==1 or + /// argv[1] is not an SQL NULL. + /// + /// + /// Each call to xUpdate will fall into one of cases shown below. + /// Not that references to ]]>argv[i]]]> mean the SQL value + /// held within the argv[i] object, not the argv[i] + /// object itself. + /// + /// + /// ]]> + /// ]]>]]>argc = 1 ]]> argv[0] ≠ NULL]]> + /// ]]>]]> + /// DELETE: The single row with rowid or PRIMARY KEY equal to argv[0] is deleted. + /// No insert occurs. + /// ]]>]]>]]>argc > 1 ]]> argv[0] = NULL]]> + /// ]]>]]> + /// INSERT: A new row is inserted with column values taken from + /// argv[2] and following. In a rowid virtual table, if argv[1] is an SQL NULL, + /// then a new unique rowid is generated automatically. The argv[1] will be NULL + /// for a WITHOUT ROWID virtual table, in which case the implementation should + /// take the PRIMARY KEY value from the appropriate column in argv[2] and following. + /// ]]>]]>]]>argc > 1 ]]> argv[0] ≠ NULL ]]> argv[0] = argv[1]]]> + /// ]]>]]> + /// UPDATE: + /// The row with rowid or PRIMARY KEY argv[0] is updated with new values + /// in argv[2] and following parameters. + /// ]]>]]>]]>argc > 1 ]]> argv[0] ≠ NULL ]]> argv[0] ≠ argv[1]]]> + /// ]]>]]> + /// UPDATE with rowid or PRIMARY KEY change: + /// The row with rowid or PRIMARY KEY argv[0] is updated with + /// the rowid or PRIMARY KEY in argv[1] + /// and new values in argv[2] and following parameters. This will occur + /// when an SQL statement updates a rowid, as in the statement: + /// + /// UPDATE table SET rowid=rowid+1 WHERE ...; + /// + /// ]]>]]> + /// + /// + /// The xUpdate method must return SQLITE_OK if and only if it is + /// successful. If a failure occurs, the xUpdate must return an appropriate + /// error code. On a failure, the pVTab->zErrMsg element may optionally + /// be replaced with error message text stored in memory allocated from SQLite + /// using functions such as sqlite3_mprintf() or sqlite3_malloc(). + /// + /// + /// If the xUpdate method violates some constraint of the virtual table + /// (including, but not limited to, attempting to store a value of the wrong + /// datatype, attempting to store a value that is too + /// large or too small, or attempting to change a read-only value) then the + /// xUpdate must fail with an appropriate error code. + /// + /// + /// If the xUpdate method is performing an UPDATE, then + /// sqlite3_value_nochange(X) can be used to discover which columns + /// of the virtual table were actually modified by the UPDATE + /// statement. The sqlite3_value_nochange(X) interface returns + /// true for columns that do not change. + /// On every UPDATE, SQLite will first invoke + /// xColumn separately for each unchanging column in the table to + /// obtain the value for that column. The xColumn method can + /// check to see if the column is unchanged at the SQL level + /// by invoking sqlite3_vtab_nochange(). If xColumn sees that + /// the column is not being modified, it should return without setting + /// a result using one of the sqlite3_result_xxxxx() + /// interfaces. Only in that case sqlite3_value_nochange() will be + /// true within the xUpdate method. If xColumn does + /// invoke one or more sqlite3_result_xxxxx() + /// interfaces, then SQLite understands that as a change in the value + /// of the column and the sqlite3_value_nochange() call for that + /// column within xUpdate will return false. + /// + /// + /// There might be one or more sqlite3_vtab_cursor objects open and in use + /// on the virtual table instance and perhaps even on the row of the virtual + /// table when the xUpdate method is invoked. The implementation of + /// xUpdate must be prepared for attempts to delete or modify rows of the table + /// out from other existing cursors. If the virtual table cannot accommodate + /// such changes, the xUpdate method must return an error code. + /// + /// + /// The xUpdate method is optional. + /// If the xUpdate pointer in the sqlite3_module for a virtual table + /// is a NULL pointer, then the virtual table is read-only. + /// + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// The number of new or modified column values contained in + /// . + /// + /// + /// The array of native pointers to sqlite3_value structures containing + /// the new or modified column values, if any. + /// + /// + /// Upon success, this parameter must be modified to contain the unique + /// integer row identifier for the row that was inserted, if any. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xUpdate( + IntPtr pVtab, + int argc, + IntPtr argv, + ref long rowId + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xBegin)(sqlite3_vtab *pVTab); + /// + /// + /// This method begins a transaction on a virtual table. + /// This is method is optional. The xBegin pointer of sqlite3_module + /// may be NULL. + /// + /// + /// This method is always followed by one call to either the + /// xCommit or xRollback method. Virtual table transactions do + /// not nest, so the xBegin method will not be invoked more than once + /// on a single virtual table + /// without an intervening call to either xCommit or xRollback. + /// Multiple calls to other methods can and likely will occur in between + /// the xBegin and the corresponding xCommit or xRollback. + /// + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xBegin( + IntPtr pVtab + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xSync)(sqlite3_vtab *pVTab); + /// + /// + /// This method signals the start of a two-phase commit on a virtual + /// table. + /// This is method is optional. The xSync pointer of sqlite3_module + /// may be NULL. + /// + /// + /// This method is only invoked after call to the xBegin method and + /// prior to an xCommit or xRollback. In order to implement two-phase + /// commit, the xSync method on all virtual tables is invoked prior to + /// invoking the xCommit method on any virtual table. If any of the + /// xSync methods fail, the entire transaction is rolled back. + /// + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xSync( + IntPtr pVtab + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xCommit)(sqlite3_vtab *pVTab); + /// + /// + /// This method causes a virtual table transaction to commit. + /// This is method is optional. The xCommit pointer of sqlite3_module + /// may be NULL. + /// + /// + /// A call to this method always follows a prior call to xBegin and + /// xSync. + /// + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xCommit( + IntPtr pVtab + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xRollback)(sqlite3_vtab *pVTab); + /// + /// + /// This method causes a virtual table transaction to rollback. + /// This is method is optional. The xRollback pointer of sqlite3_module + /// may be NULL. + /// + /// + /// A call to this method always follows a prior call to xBegin. + /// + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xRollback( + IntPtr pVtab + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xFindFunction)( + /// sqlite3_vtab *pVtab, + /// int nArg, + /// const char *zName, + /// void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), + /// void **ppArg + /// ); + /// + /// + /// This method is called during sqlite3_prepare() to give the virtual + /// table implementation an opportunity to overload functions. + /// This method may be set to NULL in which case no overloading occurs. + /// + /// + /// When a function uses a column from a virtual table as its first + /// argument, this method is called to see if the virtual table would + /// like to overload the function. The first three parameters are inputs: + /// the virtual table, the number of arguments to the function, and the + /// name of the function. If no overloading is desired, this method + /// returns 0. To overload the function, this method writes the new + /// function implementation into *pxFunc and writes user data into *ppArg + /// and returns either 1 or a number between + /// SQLITE_INDEX_CONSTRAINT_FUNCTION and 255. + /// + /// + /// Historically, the return value from xFindFunction() was either zero + /// or one. Zero means that the function is not overloaded and one means that + /// it is overload. The ability to return values of + /// SQLITE_INDEX_CONSTRAINT_FUNCTION or greater was added in + /// version 3.25.0 (2018-09-15). If xFindFunction returns + /// SQLITE_INDEX_CONSTRAINT_FUNCTION or greater, than means that the function + /// takes two arguments and the function + /// can be used as a boolean in the WHERE clause of a query and that + /// the virtual table is able to exploit that function to speed up the query + /// result. When xFindFunction returns SQLITE_INDEX_CONSTRAINT_FUNCTION or + /// larger, the value returned becomes the sqlite3_index_info.aConstraint.op + /// value for one of the constraints passed into xBestIndex() and the second + /// argument becomes the value corresponding to that constraint that is passed + /// to xFilter(). This enables the + /// xBestIndex()/xFilter implementations to use the function to speed + /// its search. + /// + /// + /// The technique of having xFindFunction() return values of + /// SQLITE_INDEX_CONSTRAINT_FUNCTION was initially used in the implementation + /// of the Geopoly module. The xFindFunction() method of that module returns + /// SQLITE_INDEX_CONSTRAINT_FUNCTION for the geopoly_overlap() SQL function + /// and it returns + /// SQLITE_INDEX_CONSTRAINT_FUNCTION+1 for the geopoly_within() SQL function. + /// This permits search optimizations for queries such as: + /// + /// + /// SELECT * FROM geopolytab WHERE geopoly_overlap(_shape, $query_polygon); + /// + /// + /// Note that infix functions (LIKE, GLOB, REGEXP, and MATCH) reverse + /// the order of their arguments. So "like(A,B)" is equivalent to "B like A". + /// For the form "B like A" the B term is considered the first argument + /// to the function. But for "like(A,B)" the A term is considered the + /// first argument. + /// + /// + /// The function pointer returned by this routine must be valid for + /// the lifetime of the sqlite3_vtab object given in the first parameter. + /// + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// The number of arguments to the function being sought. + /// + /// + /// The name of the function being sought. + /// + /// + /// Upon success, this parameter must be modified to contain the + /// delegate responsible for implementing the specified function. + /// + /// + /// Upon success, this parameter must be modified to contain the + /// native user-data pointer associated with + /// . + /// + /// + /// Non-zero if the specified function was found; zero otherwise. + /// + int xFindFunction( + IntPtr pVtab, + int nArg, + IntPtr zName, + ref SQLiteCallback callback, + ref IntPtr pClientData + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xRename)(sqlite3_vtab *pVtab, const char *zNew); + /// + /// + /// This method provides notification that the virtual table implementation + /// that the virtual table will be given a new name. + /// If this method returns SQLITE_OK then SQLite renames the table. + /// If this method returns an error code then the renaming is prevented. + /// + /// + /// The xRename method is optional. If omitted, then the virtual + /// table may not be renamed using the ALTER TABLE RENAME command. + /// + /// + /// The PRAGMA legacy_alter_table setting is enabled prior to invoking this + /// method, and the value for legacy_alter_table is restored after this + /// method finishes. This is necessary for the correct operation of virtual + /// tables that make use of shadow tables where the shadow tables must be + /// renamed to match the new virtual table name. If the legacy_alter_format is + /// off, then the xConnect method will be invoked for the virtual table every + /// time the xRename method tries to change the name of the shadow table. + /// + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// The native pointer to the UTF-8 encoded string containing the new + /// name for the virtual table. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xRename( + IntPtr pVtab, + IntPtr zNew + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xSavepoint)(sqlite3_vtab *pVtab, int); + /// int (*xRelease)(sqlite3_vtab *pVtab, int); + /// int (*xRollbackTo)(sqlite3_vtab *pVtab, int); + /// + /// + /// These methods provide the virtual table implementation an opportunity to + /// implement nested transactions. They are always optional and will only be + /// called in SQLite version 3.7.7 (2011-06-23) and later. + /// + /// + /// When xSavepoint(X,N) is invoked, that is a signal to the virtual table X + /// that it should save its current state as savepoint N. + /// A subsequent call + /// to xRollbackTo(X,R) means that the state of the virtual table should return + /// to what it was when xSavepoint(X,R) was last called. + /// The call + /// to xRollbackTo(X,R) will invalidate all savepoints with N>R; none of the + /// invalided savepoints will be rolled back or released without first + /// being reinitialized by a call to xSavepoint(). + /// A call to xRelease(X,M) invalidates all savepoints where N>=M. + /// + /// + /// None of the xSavepoint(), xRelease(), or xRollbackTo() methods will ever + /// be called except in between calls to xBegin() and + /// either xCommit() or xRollback(). + /// + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// This is an integer identifier under which the the current state of + /// the virtual table should be saved. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xSavepoint( + IntPtr pVtab, + int iSavepoint + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xSavepoint)(sqlite3_vtab *pVtab, int); + /// int (*xRelease)(sqlite3_vtab *pVtab, int); + /// int (*xRollbackTo)(sqlite3_vtab *pVtab, int); + /// + /// + /// These methods provide the virtual table implementation an opportunity to + /// implement nested transactions. They are always optional and will only be + /// called in SQLite version 3.7.7 (2011-06-23) and later. + /// + /// + /// When xSavepoint(X,N) is invoked, that is a signal to the virtual table X + /// that it should save its current state as savepoint N. + /// A subsequent call + /// to xRollbackTo(X,R) means that the state of the virtual table should return + /// to what it was when xSavepoint(X,R) was last called. + /// The call + /// to xRollbackTo(X,R) will invalidate all savepoints with N>R; none of the + /// invalided savepoints will be rolled back or released without first + /// being reinitialized by a call to xSavepoint(). + /// A call to xRelease(X,M) invalidates all savepoints where N>=M. + /// + /// + /// None of the xSavepoint(), xRelease(), or xRollbackTo() methods will ever + /// be called except in between calls to xBegin() and + /// either xCommit() or xRollback(). + /// + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// This is an integer used to indicate that any saved states with an + /// identifier greater than or equal to this should be deleted by the + /// virtual table. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xRelease( + IntPtr pVtab, + int iSavepoint + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// + /// int (*xSavepoint)(sqlite3_vtab *pVtab, int); + /// int (*xRelease)(sqlite3_vtab *pVtab, int); + /// int (*xRollbackTo)(sqlite3_vtab *pVtab, int); + /// + /// + /// These methods provide the virtual table implementation an opportunity to + /// implement nested transactions. They are always optional and will only be + /// called in SQLite version 3.7.7 (2011-06-23) and later. + /// + /// + /// When xSavepoint(X,N) is invoked, that is a signal to the virtual table X + /// that it should save its current state as savepoint N. + /// A subsequent call + /// to xRollbackTo(X,R) means that the state of the virtual table should return + /// to what it was when xSavepoint(X,R) was last called. + /// The call + /// to xRollbackTo(X,R) will invalidate all savepoints with N>R; none of the + /// invalided savepoints will be rolled back or released without first + /// being reinitialized by a call to xSavepoint(). + /// A call to xRelease(X,M) invalidates all savepoints where N>=M. + /// + /// + /// None of the xSavepoint(), xRelease(), or xRollbackTo() methods will ever + /// be called except in between calls to xBegin() and + /// either xCommit() or xRollback(). + /// + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// This is an integer identifier used to specify a specific saved + /// state for the virtual table for it to restore itself back to, which + /// should also have the effect of deleting all saved states with an + /// integer identifier greater than this one. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode xRollbackTo( + IntPtr pVtab, + int iSavepoint + ); + } + #endregion +} diff --git a/Native.Csharp.Tool/SQLite/LINQ/SQLiteConnection_Linq.cs b/Native.Csharp.Tool/SQLite/LINQ/SQLiteConnection_Linq.cs new file mode 100644 index 00000000..6f16dfc6 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/LINQ/SQLiteConnection_Linq.cs @@ -0,0 +1,22 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System.Data.Common; + + public sealed partial class SQLiteConnection + { + /// + /// Returns the instance. + /// + protected override DbProviderFactory DbProviderFactory + { + get { return SQLiteFactory.Instance; } + } + } +} \ No newline at end of file diff --git a/Native.Csharp.Tool/SQLite/LINQ/SQLiteFactory_Linq.cs b/Native.Csharp.Tool/SQLite/LINQ/SQLiteFactory_Linq.cs new file mode 100644 index 00000000..ac95d5cf --- /dev/null +++ b/Native.Csharp.Tool/SQLite/LINQ/SQLiteFactory_Linq.cs @@ -0,0 +1,110 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Globalization; + using System.Reflection; + using System.Security.Permissions; + + /// + /// SQLite implementation of . + /// + public sealed partial class SQLiteFactory : IServiceProvider + { + // + // TODO: This points to the legacy "System.Data.SQLite.Linq" assembly + // (i.e. the one that does not support Entity Framework 6). + // Currently, this class and its containing assembly (i.e. + // "System.Data.SQLite") know nothing about the Entity Framework + // 6 compatible assembly (i.e. "System.Data.SQLite.EF6"). This + // situation may need to change. + // + private static readonly string DefaultTypeName = + "System.Data.SQLite.Linq.SQLiteProviderServices, System.Data.SQLite.Linq, " + + "Version={0}, Culture=neutral, PublicKeyToken=db937bc2d44ff139"; + + private static readonly BindingFlags DefaultBindingFlags = + BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance; + + /////////////////////////////////////////////////////////////////////////// + + private static Type _dbProviderServicesType; + private static object _sqliteServices; + + static SQLiteFactory() + { +#if (SQLITE_STANDARD || USE_INTEROP_DLL || PLATFORM_COMPACTFRAMEWORK) && PRELOAD_NATIVE_LIBRARY + UnsafeNativeMethods.Initialize(); +#endif + + SQLiteLog.Initialize(typeof(SQLiteFactory).Name); + + string version = +#if NET_40 || NET_45 || NET_451 || NET_452 || NET_46 || NET_461 || NET_462 || NET_47 || NET_471 || NET_472 + "4.0.0.0"; +#else + "3.5.0.0"; +#endif + + _dbProviderServicesType = Type.GetType(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "System.Data.Common.DbProviderServices, System.Data.Entity, Version={0}, Culture=neutral, PublicKeyToken=b77a5c561934e089", version), false); + } + + /// + /// Will provide a object in .NET 3.5. + /// + /// The class or interface type to query for. + /// + object IServiceProvider.GetService(Type serviceType) + { + if (serviceType == typeof(ISQLiteSchemaExtensions) || + (_dbProviderServicesType != null && serviceType == _dbProviderServicesType)) + { + return GetSQLiteProviderServicesInstance(); + } + return null; + } + +#if !NET_STANDARD_20 + [ReflectionPermission(SecurityAction.Assert, MemberAccess = true)] +#endif + private object GetSQLiteProviderServicesInstance() + { + if (_sqliteServices == null) + { + string typeName = UnsafeNativeMethods.GetSettingValue( + "TypeName_SQLiteProviderServices", null); + + Version version = this.GetType().Assembly.GetName().Version; + + if (typeName != null) + { + typeName = HelperMethods.StringFormat( + CultureInfo.InvariantCulture, typeName, version); + } + else + { + typeName = HelperMethods.StringFormat( + CultureInfo.InvariantCulture, DefaultTypeName, version); + } + + Type type = Type.GetType(typeName, false); + + if (type != null) + { + FieldInfo field = type.GetField( + "Instance", DefaultBindingFlags); + + if (field != null) + _sqliteServices = field.GetValue(null); + } + } + return _sqliteServices; + } + } +} diff --git a/Native.Csharp.Tool/SQLite/Resources/DataTypes.xml b/Native.Csharp.Tool/SQLite/Resources/DataTypes.xml new file mode 100644 index 00000000..03cd3f3e --- /dev/null +++ b/Native.Csharp.Tool/SQLite/Resources/DataTypes.xml @@ -0,0 +1,806 @@ + + + + + + + smallint + 10 + 5 + System.Int16 + smallint + false + false + true + true + false + true + true + false + false + true + + + int + 11 + 10 + System.Int32 + int + false + false + true + true + false + true + true + false + false + true + + + real + 8 + 6 + System.Double + real + false + false + true + false + false + true + true + false + false + true + + + single + 15 + 7 + System.Single + single + false + false + true + false + false + true + true + false + false + true + + + float + 8 + 6 + System.Double + float + false + false + true + false + false + true + true + false + false + true + + + double + 8 + 6 + System.Double + double + false + false + true + false + false + true + true + false + false + false + + + money + 7 + 19 + System.Decimal + money + false + false + true + true + false + true + true + false + false + true + + + currency + 7 + 19 + System.Decimal + currency + false + false + true + true + false + true + true + false + false + false + + + decimal + 7 + 19 + System.Decimal + decimal + false + false + true + true + false + true + true + false + false + true + + + numeric + 7 + 19 + System.Decimal + numeric + false + false + true + true + false + true + true + false + false + false + + + bit + 3 + 1 + System.Boolean + bit + false + false + true + false + false + true + true + false + true + + + yesno + 3 + 1 + System.Boolean + yesno + false + false + true + false + false + true + true + false + false + + + logical + 3 + 1 + System.Boolean + logical + false + false + true + false + false + true + true + false + false + + + bool + 3 + 1 + System.Boolean + bool + false + false + true + false + false + true + true + false + false + + + boolean + 3 + 1 + System.Boolean + boolean + false + false + true + false + false + true + true + false + false + + + tinyint + 2 + 3 + System.Byte + tinyint + false + false + true + true + false + true + true + false + true + true + + + integer + 12 + 19 + System.Int64 + integer + true + false + true + true + false + true + true + false + false + true + + + counter + 12 + 19 + System.Int64 + counter + true + false + true + true + false + true + true + false + false + false + + + autoincrement + 12 + 19 + System.Int64 + autoincrement + true + false + true + true + false + true + true + false + false + false + + + identity + 12 + 19 + System.Int64 + identity + true + false + true + true + false + true + true + false + false + false + + + long + 12 + 19 + System.Int64 + long + true + false + true + true + false + true + true + false + false + false + + + bigint + 12 + 19 + System.Int64 + bigint + true + false + true + true + false + true + true + false + false + false + + + binary + 1 + 2147483647 + System.Byte[] + binary + false + false + false + false + false + true + false + false + X' + ' + true + + + varbinary + 1 + 2147483647 + System.Byte[] + varbinary + false + false + false + false + false + true + false + false + X' + ' + false + + + blob + 1 + 2147483647 + System.Byte[] + blob + false + false + false + false + false + true + false + false + X' + ' + false + + + image + 1 + 2147483647 + System.Byte[] + image + false + false + false + false + false + true + false + false + X' + ' + false + + + general + 1 + 2147483647 + System.Byte[] + general + false + false + false + false + false + true + false + false + X' + ' + false + + + oleobject + 1 + 2147483647 + System.Byte[] + oleobject + false + false + false + false + false + true + false + false + X' + ' + false + + + varchar + 16 + 2147483647 + max length + System.String + varchar({0}) + false + false + false + false + false + true + true + true + ' + ' + true + + + nvarchar + 16 + 2147483647 + max length + System.String + nvarchar({0}) + false + false + false + false + false + true + true + true + ' + ' + true + + + memo + 16 + 2147483647 + max length + System.String + memo({0}) + false + false + false + false + false + true + true + true + ' + ' + false + + + longtext + 16 + 2147483647 + max length + System.String + longtext({0}) + false + false + false + false + false + true + true + true + ' + ' + false + + + note + 16 + 2147483647 + max length + System.String + note({0}) + false + false + false + false + false + true + true + true + ' + ' + false + + + text + 16 + 2147483647 + max length + System.String + text({0}) + false + false + false + false + false + true + true + true + ' + ' + false + + + ntext + 16 + 2147483647 + max length + System.String + ntext({0}) + false + false + false + false + false + true + true + true + ' + ' + false + + + string + 16 + 2147483647 + max length + System.String + string({0}) + false + false + false + false + false + true + true + true + ' + ' + false + + + char + 16 + 2147483647 + max length + System.String + char({0}) + false + false + false + false + false + true + true + true + ' + ' + false + + + nchar + 16 + 2147483647 + max length + System.String + char({0}) + false + false + false + false + false + true + true + true + ' + ' + false + + + datetime + 6 + 23 + System.DateTime + datetime + false + false + true + false + false + true + true + true + ' + ' + true + + + smalldate + 6 + 23 + System.DateTime + smalldate + false + false + true + false + false + true + true + true + ' + ' + false + + + timestamp + 6 + 23 + System.DateTime + timestamp + false + false + true + false + false + true + true + true + ' + ' + false + + + date + 6 + 23 + System.DateTime + date + false + false + true + false + false + true + true + true + ' + ' + false + + + time + 6 + 23 + System.DateTime + time + false + false + true + false + false + true + true + true + ' + ' + false + + + uniqueidentifier + 9 + 16 + System.Guid + uniqueidentifier + false + false + true + false + false + true + true + false + ' + ' + true + + + guid + 9 + 16 + System.Guid + guid + false + false + true + false + false + true + true + false + ' + ' + false + + diff --git a/Native.Csharp.Tool/SQLite/Resources/MetaDataCollections.xml b/Native.Csharp.Tool/SQLite/Resources/MetaDataCollections.xml new file mode 100644 index 00000000..fa57aab6 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/Resources/MetaDataCollections.xml @@ -0,0 +1,78 @@ + + + + + + + MetaDataCollections + 0 + 0 + + + DataSourceInformation + 0 + 0 + + + DataTypes + 0 + 0 + + + ReservedWords + 0 + 0 + + + Catalogs + 1 + 1 + + + Columns + 4 + 4 + + + Indexes + 4 + 3 + + + IndexColumns + 5 + 4 + + + Tables + 4 + 3 + + + Views + 3 + 3 + + + ViewColumns + 4 + 4 + + + ForeignKeys + 4 + 3 + + + Triggers + 4 + 3 + + diff --git a/Native.Csharp.Tool/SQLite/Resources/SQLiteCommand.bmp b/Native.Csharp.Tool/SQLite/Resources/SQLiteCommand.bmp new file mode 100644 index 00000000..13dbda04 Binary files /dev/null and b/Native.Csharp.Tool/SQLite/Resources/SQLiteCommand.bmp differ diff --git a/Native.Csharp.Tool/SQLite/Resources/SQLiteConnection.bmp b/Native.Csharp.Tool/SQLite/Resources/SQLiteConnection.bmp new file mode 100644 index 00000000..f787a2db Binary files /dev/null and b/Native.Csharp.Tool/SQLite/Resources/SQLiteConnection.bmp differ diff --git a/Native.Csharp.Tool/SQLite/Resources/SQLiteDataAdapter.bmp b/Native.Csharp.Tool/SQLite/Resources/SQLiteDataAdapter.bmp new file mode 100644 index 00000000..27186a09 Binary files /dev/null and b/Native.Csharp.Tool/SQLite/Resources/SQLiteDataAdapter.bmp differ diff --git a/Native.Csharp.Tool/SQLite/Resources/SR.Designer.cs b/Native.Csharp.Tool/SQLite/Resources/SR.Designer.cs new file mode 100644 index 00000000..f33df398 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/Resources/SR.Designer.cs @@ -0,0 +1,121 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.1 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace System.Data.SQLite { + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] +#if !NET_COMPACT_20 + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] +#endif + internal sealed class SR { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal SR() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Data.SQLite.SR", typeof(SR).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" standalone="yes"?> + ///<DocumentElement> + /// <DataTypes> + /// <TypeName>smallint</TypeName> + /// <ProviderDbType>10</ProviderDbType> + /// <ColumnSize>5</ColumnSize> + /// <DataType>System.Int16</DataType> + /// <CreateFormat>smallint</CreateFormat> + /// <IsAutoIncrementable>false</IsAutoIncrementable> + /// <IsCaseSensitive>false</IsCaseSensitive> + /// <IsFixedLength>true</IsFixedLength> + /// <IsFixedPrecisionScale>true</IsFixedPrecisionScale> + /// <IsLong>false</IsLong> + /// <IsNullable>true</ [rest of string was truncated]";. + /// + internal static string DataTypes { + get { + return ResourceManager.GetString("DataTypes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ALL,ALTER,AND,AS,AUTOINCREMENT,BETWEEN,BY,CASE,CHECK,COLLATE,COMMIT,CONSTRAINT,CREATE,CROSS,DEFAULT,DEFERRABLE,DELETE,DISTINCT,DROP,ELSE,ESCAPE,EXCEPT,FOREIGN,FROM,FULL,GROUP,HAVING,IN,INDEX,INNER,INSERT,INTERSECT,INTO,IS,ISNULL,JOIN,LEFT,LIMIT,NATURAL,NOT,NOTNULL,NULL,ON,OR,ORDER,OUTER,PRIMARY,REFERENCES,RIGHT,ROLLBACK,SELECT,SET,TABLE,THEN,TO,TRANSACTION,UNION,UNIQUE,UPDATE,USING,VALUES,WHEN,WHERE. + /// + internal static string Keywords { + get { + return ResourceManager.GetString("Keywords", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8" ?> + ///<DocumentElement> + /// <MetaDataCollections> + /// <CollectionName>MetaDataCollections</CollectionName> + /// <NumberOfRestrictions>0</NumberOfRestrictions> + /// <NumberOfIdentifierParts>0</NumberOfIdentifierParts> + /// </MetaDataCollections> + /// <MetaDataCollections> + /// <CollectionName>DataSourceInformation</CollectionName> + /// <NumberOfRestrictions>0</NumberOfRestrictions> + /// <NumberOfIdentifierParts>0</NumberOfIdentifierParts> + /// </MetaDataCollections> + /// <MetaDataC [rest of string was truncated]";. + /// + internal static string MetaDataCollections { + get { + return ResourceManager.GetString("MetaDataCollections", resourceCulture); + } + } + } +} diff --git a/Native.Csharp.Tool/SQLite/Resources/SR.resx b/Native.Csharp.Tool/SQLite/Resources/SR.resx new file mode 100644 index 00000000..5c6589d1 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/Resources/SR.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + DataTypes.xml;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + ABORT,ACTION,ADD,AFTER,ALL,ALTER,ANALYZE,AND,AS,ASC,ATTACH,AUTOINCREMENT,BEFORE,BEGIN,BETWEEN,BY,CASCADE,CASE,CAST,CHECK,COLLATE,COLUMN,COMMIT,CONFLICT,CONSTRAINT,CREATE,CROSS,CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP,DATABASE,DEFAULT,DEFERRABLE,DEFERRED,DELETE,DESC,DETACH,DISTINCT,DO,DROP,EACH,ELSE,END,ESCAPE,EXCEPT,EXCLUSIVE,EXISTS,EXPLAIN,FAIL,FOR,FOREIGN,FROM,FULL,GLOB,GROUP,HAVING,IF,IGNORE,IMMEDIATE,IN,INDEX,INDEXED,INITIALLY,INNER,INSERT,INSTEAD,INTERSECT,INTO,IS,ISNULL,JOIN,KEY,LEFT,LIKE,LIMIT,MATCH,NATURAL,NO,NOT,NOTHING,NOTNULL,NULL,OF,OFFSET,ON,OR,ORDER,OUTER,PLAN,PRAGMA,PRIMARY,QUERY,RAISE,RECURSIVE,REFERENCES,REGEXP,REINDEX,RELEASE,RENAME,REPLACE,RESTRICT,RIGHT,ROLLBACK,ROW,SAVEPOINT,SELECT,SET,TABLE,TEMP,TEMPORARY,THEN,TO,TRANSACTION,TRIGGER,UNION,UNIQUE,UPDATE,USING,VACUUM,VALUES,VIEW,VIRTUAL,WHEN,WHERE,WITH,WITHOUT + + + MetaDataCollections.xml;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + \ No newline at end of file diff --git a/Native.Csharp.Tool/SQLite/SQLite3.cs b/Native.Csharp.Tool/SQLite/SQLite3.cs new file mode 100644 index 00000000..d7817c3b --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLite3.cs @@ -0,0 +1,4143 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Collections.Generic; + +#if !NET_COMPACT_20 && (TRACE_CONNECTION || TRACE_STATEMENT) + using System.Diagnostics; +#endif + + using System.Globalization; + using System.Runtime.InteropServices; + using System.Text; + using System.Threading; + + /// + /// This is the method signature for the SQLite core library logging callback + /// function for use with sqlite3_log() and the SQLITE_CONFIG_LOG. + /// + /// WARNING: This delegate is used more-or-less directly by native code, do + /// not modify its type signature. + /// + /// + /// The extra data associated with this message, if any. + /// + /// + /// The error code associated with this message. + /// + /// + /// The message string to be logged. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate void SQLiteLogCallback(IntPtr pUserData, int errorCode, IntPtr pMessage); + + /// + /// This class implements SQLiteBase completely, and is the guts of the code that interop's SQLite with .NET + /// + internal class SQLite3 : SQLiteBase + { + private static object syncRoot = new object(); + + /// + /// This field is used to refer to memory allocated for the + /// SQLITE_DBCONFIG_MAINDBNAME value used with the native + /// "sqlite3_db_config" API. If allocated, the associated + /// memeory will be freed when the underlying connection is + /// closed. + /// + private IntPtr dbName = IntPtr.Zero; + + // + // NOTE: This is the public key for the System.Data.SQLite assembly. If you change the + // SNK file, you will need to change this as well. + // + internal const string PublicKey = + "002400000480000094000000060200000024000052534131000400000100010005a288de5687c4e1" + + "b621ddff5d844727418956997f475eb829429e411aff3e93f97b70de698b972640925bdd44280df0" + + "a25a843266973704137cbb0e7441c1fe7cae4e2440ae91ab8cde3933febcb1ac48dd33b40e13c421" + + "d8215c18a4349a436dd499e3c385cc683015f886f6c10bd90115eb2bd61b67750839e3a19941dc9c"; + +#if !PLATFORM_COMPACTFRAMEWORK + internal const string DesignerVersion = "1.0.110.0"; +#endif + + /// + /// The opaque pointer returned to us by the sqlite provider + /// + protected internal SQLiteConnectionHandle _sql; + protected string _fileName; + protected SQLiteConnectionFlags _flags; + private bool _setLogCallback; + protected bool _usePool; + protected int _poolVersion; + private int _cancelCount; + +#if (NET_35 || NET_40 || NET_45 || NET_451 || NET_452 || NET_46 || NET_461 || NET_462 || NET_47 || NET_471 || NET_472) && !PLATFORM_COMPACTFRAMEWORK + private bool _buildingSchema; +#endif + + /// + /// The user-defined functions registered on this connection + /// + protected Dictionary _functions; + +#if INTEROP_VIRTUAL_TABLE + /// + /// This is the name of the native library file that contains the + /// "vtshim" extension [wrapper]. + /// + protected string _shimExtensionFileName = null; + + /// + /// This is the flag indicate whether the native library file that + /// contains the "vtshim" extension must be dynamically loaded by + /// this class prior to use. + /// + protected bool? _shimIsLoadNeeded = null; + + /// + /// This is the name of the native entry point for the "vtshim" + /// extension [wrapper]. + /// + protected string _shimExtensionProcName = "sqlite3_vtshim_init"; + + /// + /// The modules created using this connection. + /// + protected Dictionary _modules; +#endif + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Constructs the object used to interact with the SQLite core library + /// using the UTF-8 text encoding. + /// + /// + /// The DateTime format to be used when converting string values to a + /// DateTime and binding DateTime parameters. + /// + /// + /// The to be used when creating DateTime + /// values. + /// + /// + /// The format string to be used when parsing and formatting DateTime + /// values. + /// + /// + /// The native handle to be associated with the database connection. + /// + /// + /// The fully qualified file name associated with . + /// + /// + /// Non-zero if the newly created object instance will need to dispose + /// of when it is no longer needed. + /// + internal SQLite3( + SQLiteDateFormats fmt, + DateTimeKind kind, + string fmtString, + IntPtr db, + string fileName, + bool ownHandle + ) + : base(fmt, kind, fmtString) + { + if (db != IntPtr.Zero) + { + _sql = new SQLiteConnectionHandle(db, ownHandle); + _fileName = fileName; + + SQLiteConnection.OnChanged(null, new ConnectionEventArgs( + SQLiteConnectionEventType.NewCriticalHandle, null, + null, null, null, _sql, fileName, new object[] { + typeof(SQLite3), fmt, kind, fmtString, db, fileName, + ownHandle })); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLite3).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + +#if INTEROP_VIRTUAL_TABLE + DisposeModules(); +#endif + + Close(true); /* Disposing, cannot throw. */ + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + +#if DEBUG + public override string ToString() + { + return HelperMethods.StringFormat( + CultureInfo.InvariantCulture, "fileName = {0}, flags = {1}", + _fileName, _flags); + } +#endif + + /////////////////////////////////////////////////////////////////////////////////////////////// + +#if INTEROP_VIRTUAL_TABLE + /// + /// This method attempts to dispose of all the derived + /// object instances currently associated with the native database connection. + /// + private void DisposeModules() + { + // + // NOTE: If any modules were created, attempt to dispose of + // them now. This code is designed to avoid throwing + // exceptions unless the Dispose method of the module + // itself throws an exception. + // + if (_modules != null) + { + foreach (KeyValuePair pair in _modules) + { + SQLiteModule module = pair.Value; + + if (module == null) + continue; + + module.Dispose(); + } + + _modules.Clear(); + } + } +#endif + + /////////////////////////////////////////////////////////////////////////////////////////////// + + // It isn't necessary to cleanup any functions we've registered. If the connection + // goes to the pool and is resurrected later, re-registered functions will overwrite the + // previous functions. The SQLiteFunctionCookieHandle will take care of freeing unmanaged + // resources belonging to the previously-registered functions. + internal override void Close(bool disposing) + { + if (_sql != null) + { + if (!_sql.OwnHandle) + { + _sql = null; + return; + } + + bool unbindFunctions = HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UnbindFunctionsOnClose); + + retry: + + if (_usePool) + { + if (SQLiteBase.ResetConnection(_sql, _sql, !disposing) && + UnhookNativeCallbacks(true, !disposing)) + { + if (unbindFunctions) + { + if (SQLiteFunction.UnbindAllFunctions(this, _flags, false)) + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "UnbindFunctions (Pool) Success: {0}", + HandleToString())); +#endif + } + else + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "UnbindFunctions (Pool) Failure: {0}", + HandleToString())); +#endif + } + } + +#if INTEROP_VIRTUAL_TABLE + DisposeModules(); +#endif + + SQLiteConnectionPool.Add(_fileName, _sql, _poolVersion); + + SQLiteConnection.OnChanged(null, new ConnectionEventArgs( + SQLiteConnectionEventType.ClosedToPool, null, null, + null, null, _sql, _fileName, new object[] { + typeof(SQLite3), !disposing, _fileName, _poolVersion })); + +#if !NET_COMPACT_20 && TRACE_CONNECTION + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Close (Pool) Success: {0}", + HandleToString())); +#endif + } + else + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Close (Pool) Failure: {0}", + HandleToString())); +#endif + + // + // NOTE: This connection cannot be added to the pool; + // therefore, just use the normal disposal + // procedure on it. + // + _usePool = false; + goto retry; + } + } + else + { + /* IGNORED */ + UnhookNativeCallbacks(disposing, !disposing); + + if (unbindFunctions) + { + if (SQLiteFunction.UnbindAllFunctions(this, _flags, false)) + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "UnbindFunctions Success: {0}", + HandleToString())); +#endif + } + else + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "UnbindFunctions Failure: {0}", + HandleToString())); +#endif + } + } + + _sql.Dispose(); + + FreeDbName(!disposing); + } + _sql = null; + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + +#if !NET_COMPACT_20 && TRACE_CONNECTION + protected string HandleToString() + { + if (_sql == null) + return ""; + + return _sql.ToString(); + } +#endif + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Returns the number of times the method has been + /// called. + /// + private int GetCancelCount() + { + return Interlocked.CompareExchange(ref _cancelCount, 0, 0); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This method determines whether or not a + /// with a return code of should + /// be thrown after making a call into the SQLite core library. + /// + /// + /// Non-zero if a to be thrown. This method + /// will only return non-zero if the method was called + /// one or more times during a call into the SQLite core library (e.g. when + /// the sqlite3_prepare*() or sqlite3_step() APIs are used). + /// + private bool ShouldThrowForCancel() + { + return GetCancelCount() > 0; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Resets the value of the field. + /// + private int ResetCancelCount() + { + return Interlocked.CompareExchange(ref _cancelCount, 0, _cancelCount); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to interrupt the query currently executing on the associated + /// native database connection. + /// + internal override void Cancel() + { + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { + Interlocked.Increment(ref _cancelCount); + UnsafeNativeMethods.sqlite3_interrupt(_sql); + } + } + + /// + /// This function binds a user-defined function to the connection. + /// + /// + /// The object instance containing + /// the metadata for the function to be bound. + /// + /// + /// The object instance that implements the + /// function to be bound. + /// + /// + /// The flags associated with the parent connection object. + /// + internal override void BindFunction( + SQLiteFunctionAttribute functionAttribute, + SQLiteFunction function, + SQLiteConnectionFlags flags + ) + { + if (functionAttribute == null) + throw new ArgumentNullException("functionAttribute"); + + if (function == null) + throw new ArgumentNullException("function"); + + SQLiteFunction.BindFunction(this, functionAttribute, function, flags); + + if (_functions == null) + _functions = new Dictionary(); + + _functions[functionAttribute] = function; + } + + /// + /// This function binds a user-defined function to the connection. + /// + /// + /// The object instance containing + /// the metadata for the function to be unbound. + /// + /// + /// The flags associated with the parent connection object. + /// + /// Non-zero if the function was unbound and removed. + internal override bool UnbindFunction( + SQLiteFunctionAttribute functionAttribute, + SQLiteConnectionFlags flags + ) + { + if (functionAttribute == null) + throw new ArgumentNullException("functionAttribute"); + + if (_functions == null) + return false; + + SQLiteFunction function; + + if (_functions.TryGetValue(functionAttribute, out function)) + { + if (SQLiteFunction.UnbindFunction( + this, functionAttribute, function, flags) && + _functions.Remove(functionAttribute)) + { + return true; + } + } + + return false; + } + + internal override string Version + { + get + { + return SQLiteVersion; + } + } + + internal override int VersionNumber + { + get + { + return SQLiteVersionNumber; + } + } + + internal static string DefineConstants + { + get + { + StringBuilder result = new StringBuilder(); + IList list = SQLiteDefineConstants.OptionList; + + if (list != null) + { + foreach (string element in list) + { + if (element == null) + continue; + + if (result.Length > 0) + result.Append(' '); + + result.Append(element); + } + } + + return result.ToString(); + } + } + + internal static string SQLiteVersion + { + get + { + return UTF8ToString(UnsafeNativeMethods.sqlite3_libversion(), -1); + } + } + + internal static int SQLiteVersionNumber + { + get + { + return UnsafeNativeMethods.sqlite3_libversion_number(); + } + } + + internal static string SQLiteSourceId + { + get + { + return UTF8ToString(UnsafeNativeMethods.sqlite3_sourceid(), -1); + } + } + + internal static string SQLiteCompileOptions + { + get + { + StringBuilder result = new StringBuilder(); + int index = 0; + IntPtr zValue = UnsafeNativeMethods.sqlite3_compileoption_get(index++); + + while (zValue != IntPtr.Zero) + { + if (result.Length > 0) + result.Append(' '); + + result.Append(UTF8ToString(zValue, -1)); + zValue = UnsafeNativeMethods.sqlite3_compileoption_get(index++); + } + + return result.ToString(); + } + } + + internal static string InteropVersion + { + get + { +#if !SQLITE_STANDARD + return UTF8ToString(UnsafeNativeMethods.interop_libversion(), -1); +#else + return null; +#endif + } + } + + internal static string InteropSourceId + { + get + { +#if !SQLITE_STANDARD + return UTF8ToString(UnsafeNativeMethods.interop_sourceid(), -1); +#else + return null; +#endif + } + } + + internal static string InteropCompileOptions + { + get + { +#if !SQLITE_STANDARD + StringBuilder result = new StringBuilder(); + int index = 0; + IntPtr zValue = UnsafeNativeMethods.interop_compileoption_get(index++); + + while (zValue != IntPtr.Zero) + { + if (result.Length > 0) + result.Append(' '); + + result.Append(UTF8ToString(zValue, -1)); + zValue = UnsafeNativeMethods.interop_compileoption_get(index++); + } + + return result.ToString(); +#else + return null; +#endif + } + } + + internal override bool AutoCommit + { + get + { + return IsAutocommit(_sql, _sql); + } + } + + internal override bool IsReadOnly( + string name + ) + { + IntPtr pDbName = IntPtr.Zero; + + try + { + if (name != null) + pDbName = SQLiteString.Utf8IntPtrFromString(name); + + int result = UnsafeNativeMethods.sqlite3_db_readonly( + _sql, pDbName); + + if (result == -1) /* database not found */ + { + throw new SQLiteException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "database \"{0}\" not found", name)); + } + + return result == 0 ? false : true; + } + finally + { + if (pDbName != IntPtr.Zero) + { + SQLiteMemory.Free(pDbName); + pDbName = IntPtr.Zero; + } + } + } + + internal override long LastInsertRowId + { + get + { +#if !PLATFORM_COMPACTFRAMEWORK + return UnsafeNativeMethods.sqlite3_last_insert_rowid(_sql); +#elif !SQLITE_STANDARD + long rowId = 0; + UnsafeNativeMethods.sqlite3_last_insert_rowid_interop(_sql, ref rowId); + return rowId; +#else + throw new NotImplementedException(); +#endif + } + } + + internal override int Changes + { + get + { +#if !SQLITE_STANDARD + return UnsafeNativeMethods.sqlite3_changes_interop(_sql); +#else + return UnsafeNativeMethods.sqlite3_changes(_sql); +#endif + } + } + + internal override long MemoryUsed + { + get + { + return StaticMemoryUsed; + } + } + + internal static long StaticMemoryUsed + { + get + { +#if !PLATFORM_COMPACTFRAMEWORK + return UnsafeNativeMethods.sqlite3_memory_used(); +#elif !SQLITE_STANDARD + long bytes = 0; + UnsafeNativeMethods.sqlite3_memory_used_interop(ref bytes); + return bytes; +#else + throw new NotImplementedException(); +#endif + } + } + + internal override long MemoryHighwater + { + get + { + return StaticMemoryHighwater; + } + } + + internal static long StaticMemoryHighwater + { + get + { +#if !PLATFORM_COMPACTFRAMEWORK + return UnsafeNativeMethods.sqlite3_memory_highwater(0); +#elif !SQLITE_STANDARD + long bytes = 0; + UnsafeNativeMethods.sqlite3_memory_highwater_interop(0, ref bytes); + return bytes; +#else + throw new NotImplementedException(); +#endif + } + } + + /// + /// Returns non-zero if the underlying native connection handle is owned + /// by this instance. + /// + internal override bool OwnHandle + { + get + { + if (_sql == null) + throw new SQLiteException("no connection handle available"); + + return _sql.OwnHandle; + } + } + + /// + /// Returns the logical list of functions associated with this connection. + /// + internal override IDictionary Functions + { + get { return _functions; } + } + + internal override SQLiteErrorCode SetMemoryStatus(bool value) + { + return StaticSetMemoryStatus(value); + } + + internal static SQLiteErrorCode StaticSetMemoryStatus(bool value) + { + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_config_int( + SQLiteConfigOpsEnum.SQLITE_CONFIG_MEMSTATUS, value ? 1 : 0); + + return rc; + } + + /// + /// Attempts to free as much heap memory as possible for the database connection. + /// + /// A standard SQLite return code (i.e. zero for success and non-zero for failure). + internal override SQLiteErrorCode ReleaseMemory() + { + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_db_release_memory(_sql); + return rc; + } + + /// + /// Attempts to free N bytes of heap memory by deallocating non-essential memory + /// allocations held by the database library. Memory used to cache database pages + /// to improve performance is an example of non-essential memory. This is a no-op + /// returning zero if the SQLite core library was not compiled with the compile-time + /// option SQLITE_ENABLE_MEMORY_MANAGEMENT. Optionally, attempts to reset and/or + /// compact the Win32 native heap, if applicable. + /// + /// + /// The requested number of bytes to free. + /// + /// + /// Non-zero to attempt a heap reset. + /// + /// + /// Non-zero to attempt heap compaction. + /// + /// + /// The number of bytes actually freed. This value may be zero. + /// + /// + /// This value will be non-zero if the heap reset was successful. + /// + /// + /// The size of the largest committed free block in the heap, in bytes. + /// This value will be zero unless heap compaction is enabled. + /// + /// + /// A standard SQLite return code (i.e. zero for success and non-zero + /// for failure). + /// + internal static SQLiteErrorCode StaticReleaseMemory( + int nBytes, + bool reset, + bool compact, + ref int nFree, + ref bool resetOk, + ref uint nLargest + ) + { + SQLiteErrorCode rc = SQLiteErrorCode.Ok; + + int nFreeLocal = UnsafeNativeMethods.sqlite3_release_memory(nBytes); + uint nLargestLocal = 0; + bool resetOkLocal = false; + +#if !DEBUG && WINDOWS // NOTE: Should be "WIN32HEAP && !MEMDEBUG && WINDOWS" + if (HelperMethods.IsWindows()) + { + if ((rc == SQLiteErrorCode.Ok) && reset) + { + rc = UnsafeNativeMethods.sqlite3_win32_reset_heap(); + + if (rc == SQLiteErrorCode.Ok) + resetOkLocal = true; + } + + if ((rc == SQLiteErrorCode.Ok) && compact) + rc = UnsafeNativeMethods.sqlite3_win32_compact_heap(ref nLargestLocal); + } + else +#endif + if (reset || compact) + { + rc = SQLiteErrorCode.NotFound; + } + + nFree = nFreeLocal; + nLargest = nLargestLocal; + resetOk = resetOkLocal; + + return rc; + } + + /// + /// Shutdown the SQLite engine so that it can be restarted with different + /// configuration options. We depend on auto initialization to recover. + /// + /// Returns a standard SQLite result code. + internal override SQLiteErrorCode Shutdown() + { + return StaticShutdown(false); + } + + /// + /// Shutdown the SQLite engine so that it can be restarted with different + /// configuration options. We depend on auto initialization to recover. + /// + /// + /// Non-zero to reset the database and temporary directories to their + /// default values, which should be null for both. This parameter has no + /// effect on non-Windows operating systems. + /// + /// Returns a standard SQLite result code. + internal static SQLiteErrorCode StaticShutdown( + bool directories + ) + { + SQLiteErrorCode rc = SQLiteErrorCode.Ok; + + if (directories) + { +#if WINDOWS + if (HelperMethods.IsWindows()) + { + if (rc == SQLiteErrorCode.Ok) + rc = UnsafeNativeMethods.sqlite3_win32_set_directory(1, null); + + if (rc == SQLiteErrorCode.Ok) + rc = UnsafeNativeMethods.sqlite3_win32_set_directory(2, null); + } + else +#endif + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + Trace.WriteLine( + "Shutdown: Cannot reset directories on this platform."); +#endif + } + } + + if (rc == SQLiteErrorCode.Ok) + rc = UnsafeNativeMethods.sqlite3_shutdown(); + + return rc; + } + + /// + /// Determines if the associated native connection handle is open. + /// + /// + /// Non-zero if the associated native connection handle is open. + /// + internal override bool IsOpen() + { + return (_sql != null) && !_sql.IsInvalid && !_sql.IsClosed; + } + + /// + /// Returns the fully qualified path and file name for the currently open + /// database, if any. + /// + /// + /// The name of the attached database to query. + /// + /// + /// The fully qualified path and file name for the currently open database, + /// if any. + /// + internal override string GetFileName(string dbName) + { + if (_sql == null) + return null; + + return UTF8ToString(UnsafeNativeMethods.sqlite3_db_filename_bytes( + _sql, ToUTF8(dbName)), -1); + } + + /// + /// This method attempts to determine if a database connection opened + /// with the specified should be + /// allowed into the connection pool. + /// + /// + /// The that were specified when the + /// connection was opened. + /// + /// + /// Non-zero if the connection should (eventually) be allowed into the + /// connection pool; otherwise, zero. + /// + private static bool IsAllowedToUsePool( + SQLiteOpenFlagsEnum openFlags + ) + { + return openFlags == SQLiteOpenFlagsEnum.Default; + } + + internal override void Open(string strFilename, string vfsName, SQLiteConnectionFlags connectionFlags, SQLiteOpenFlagsEnum openFlags, int maxPoolSize, bool usePool) + { + // + // NOTE: If the database connection is currently open, attempt to + // close it now. This must be done because the file name or + // other parameters that may impact the underlying database + // connection may have changed. + // + if (_sql != null) Close(false); + + // + // NOTE: If the connection was not closed successfully, throw an + // exception now. + // + if (_sql != null) + throw new SQLiteException("connection handle is still active"); + + _usePool = usePool; + + // + // BUGFIX: Do not allow a connection into the pool if it was opened + // with flags that are incompatible with the default flags + // (e.g. read-only). + // + if (_usePool && !IsAllowedToUsePool(openFlags)) + _usePool = false; + + _fileName = strFilename; + _flags = connectionFlags; + + if (usePool) + { + _sql = SQLiteConnectionPool.Remove(strFilename, maxPoolSize, out _poolVersion); + + SQLiteConnection.OnChanged(null, new ConnectionEventArgs( + SQLiteConnectionEventType.OpenedFromPool, null, null, + null, null, _sql, strFilename, new object[] { + typeof(SQLite3), strFilename, vfsName, connectionFlags, + openFlags, maxPoolSize, usePool, _poolVersion })); + +#if !NET_COMPACT_20 && TRACE_CONNECTION + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Open (Pool): {0}", HandleToString())); +#endif + } + + if (_sql == null) + { + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { + IntPtr db = IntPtr.Zero; + SQLiteErrorCode n; + +#if !SQLITE_STANDARD + int extFuncs = HelperMethods.HasFlags(connectionFlags, SQLiteConnectionFlags.NoExtensionFunctions) ? 0 : 1; + + if (extFuncs != 0) + { + n = UnsafeNativeMethods.sqlite3_open_interop(ToUTF8(strFilename), ToUTF8(vfsName), openFlags, extFuncs, ref db); + } + else +#endif + { + n = UnsafeNativeMethods.sqlite3_open_v2(ToUTF8(strFilename), ref db, openFlags, ToUTF8(vfsName)); + } + +#if !NET_COMPACT_20 && TRACE_CONNECTION + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Open: {0}", db)); +#endif + + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, null); + _sql = new SQLiteConnectionHandle(db, true); + } + lock (_sql) { /* HACK: Force the SyncBlock to be "created" now. */ } + + SQLiteConnection.OnChanged(null, new ConnectionEventArgs( + SQLiteConnectionEventType.NewCriticalHandle, null, + null, null, null, _sql, strFilename, new object[] { + typeof(SQLite3), strFilename, vfsName, connectionFlags, + openFlags, maxPoolSize, usePool })); + } + + // Bind functions to this connection. If any previous functions of the same name + // were already bound, then the new bindings replace the old. + if (!HelperMethods.HasFlags(connectionFlags, SQLiteConnectionFlags.NoBindFunctions)) + { + if (_functions == null) + _functions = new Dictionary(); + + foreach (KeyValuePair pair + in SQLiteFunction.BindFunctions(this, connectionFlags)) + { + _functions[pair.Key] = pair.Value; + } + } + + SetTimeout(0); + GC.KeepAlive(_sql); + } + + internal override void ClearPool() + { + SQLiteConnectionPool.ClearPool(_fileName); + } + + internal override int CountPool() + { + Dictionary counts = null; + int openCount = 0; + int closeCount = 0; + int totalCount = 0; + + SQLiteConnectionPool.GetCounts(_fileName, + ref counts, ref openCount, ref closeCount, + ref totalCount); + + return totalCount; + } + + internal override void SetTimeout(int nTimeoutMS) + { + IntPtr db = _sql; + if (db == IntPtr.Zero) throw new SQLiteException("no connection handle available"); + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_busy_timeout(db, nTimeoutMS); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + } + + internal override bool Step(SQLiteStatement stmt) + { + SQLiteErrorCode n; + Random rnd = null; + uint starttick = (uint)Environment.TickCount; + uint timeout = (uint)(stmt._command._commandTimeout * 1000); + + ResetCancelCount(); + + while (true) + { + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { + n = UnsafeNativeMethods.sqlite3_step(stmt._sqlite_stmt); + } + + if (ShouldThrowForCancel()) + { + if ((n == SQLiteErrorCode.Ok) || + (n == SQLiteErrorCode.Row) || + (n == SQLiteErrorCode.Done)) + { + n = SQLiteErrorCode.Interrupt; + } + + throw new SQLiteException(n, null); + } + + if (n == SQLiteErrorCode.Interrupt) return false; + if (n == SQLiteErrorCode.Row) return true; + if (n == SQLiteErrorCode.Done) return false; + + if (n != SQLiteErrorCode.Ok) + { + SQLiteErrorCode r; + + // An error occurred, attempt to reset the statement. If the reset worked because the + // schema has changed, re-try the step again. If it errored our because the database + // is locked, then keep retrying until the command timeout occurs. + r = Reset(stmt); + + if (r == SQLiteErrorCode.Ok) + throw new SQLiteException(n, GetLastError()); + + else if ((r == SQLiteErrorCode.Locked || r == SQLiteErrorCode.Busy) && stmt._command != null) + { + // Keep trying + if (rnd == null) // First time we've encountered the lock + rnd = new Random(); + + // If we've exceeded the command's timeout, give up and throw an error + if ((uint)Environment.TickCount - starttick > timeout) + { + throw new SQLiteException(r, GetLastError()); + } + else + { + // Otherwise sleep for a random amount of time up to 150ms + System.Threading.Thread.Sleep(rnd.Next(1, 150)); + } + } + } + } + } + + /// + /// Has the sqlite3_errstr() core library API been checked for yet? + /// If so, is it present? + /// + private static bool? have_errstr = null; + + /// + /// Returns the error message for the specified SQLite return code using + /// the sqlite3_errstr() function, falling back to the internal lookup + /// table if necessary. + /// + /// WARNING: Do not remove this method, it is used via reflection. + /// + /// The SQLite return code. + /// The error message or null if it cannot be found. + internal static string GetErrorString(SQLiteErrorCode rc) + { + try + { + if (have_errstr == null) + { + int versionNumber = SQLiteVersionNumber; + have_errstr = (versionNumber >= 3007015); + } + + if ((bool)have_errstr) + { + IntPtr ptr = UnsafeNativeMethods.sqlite3_errstr(rc); + + if (ptr != IntPtr.Zero) + { +#if !PLATFORM_COMPACTFRAMEWORK + return Marshal.PtrToStringAnsi(ptr); +#else + return UTF8ToString(ptr, -1); +#endif + } + } + } + catch (EntryPointNotFoundException) + { + // do nothing. + } + + return FallbackGetErrorString(rc); + } + + /// + /// Has the sqlite3_stmt_readonly() core library API been checked for yet? + /// If so, is it present? + /// + private static bool? have_stmt_readonly = null; + + /// + /// Returns non-zero if the specified statement is read-only in nature. + /// + /// The statement to check. + /// True if the outer query is read-only. + internal override bool IsReadOnly( + SQLiteStatement stmt + ) + { + try + { + if (have_stmt_readonly == null) + { + int versionNumber = SQLiteVersionNumber; + have_stmt_readonly = (versionNumber >= 3007004); + } + + if ((bool)have_stmt_readonly) + { + return UnsafeNativeMethods.sqlite3_stmt_readonly( + stmt._sqlite_stmt) != 0; + } + } + catch (EntryPointNotFoundException) + { + // do nothing. + } + + return false; /* NOTE: Unknown, assume false. */ + } + + internal override SQLiteErrorCode Reset(SQLiteStatement stmt) + { + SQLiteErrorCode n; + +#if !SQLITE_STANDARD + n = UnsafeNativeMethods.sqlite3_reset_interop(stmt._sqlite_stmt); +#else + n = UnsafeNativeMethods.sqlite3_reset(stmt._sqlite_stmt); +#endif + + // If the schema changed, try and re-prepare it + if (n == SQLiteErrorCode.Schema) + { + // Recreate a dummy statement + string str = null; + using (SQLiteStatement tmp = Prepare(null, stmt._sqlStatement, null, (uint)(stmt._command._commandTimeout * 1000), ref str)) + { + // Finalize the existing statement + stmt._sqlite_stmt.Dispose(); + // Reassign a new statement pointer to the old statement and clear the temporary one + if (tmp != null) + { + stmt._sqlite_stmt = tmp._sqlite_stmt; + tmp._sqlite_stmt = null; + } + + // Reapply parameters + stmt.BindParameters(); + } + return SQLiteErrorCode.Unknown; // Reset was OK, with schema change + } + else if (n == SQLiteErrorCode.Locked || n == SQLiteErrorCode.Busy) + return n; + + if (n != SQLiteErrorCode.Ok) + throw new SQLiteException(n, GetLastError()); + + return n; // We reset OK, no schema changes + } + + internal override string GetLastError() + { + return GetLastError(null); + } + + internal override string GetLastError(string defValue) + { + string result = SQLiteBase.GetLastError(_sql, _sql); + if (String.IsNullOrEmpty(result)) result = defValue; + return result; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Query Diagnostics Support + /// + /// This field is used to keep track of whether or not the + /// "SQLite_ForceLogPrepare" environment variable has been queried. If so, + /// it will only be non-zero if the environment variable was present. + /// + private static bool? forceLogPrepare = null; + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Determines if all calls to prepare a SQL query will be logged, + /// regardless of the flags for the associated connection. + /// + /// + /// Non-zero to log all calls to prepare a SQL query. + /// + internal static bool ForceLogPrepare() + { + lock (syncRoot) + { + if (forceLogPrepare == null) + { + if (UnsafeNativeMethods.GetSettingValue( + "SQLite_ForceLogPrepare", null) != null) + { + forceLogPrepare = true; + } + else + { + forceLogPrepare = false; + } + } + + return (bool)forceLogPrepare; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + internal override SQLiteStatement Prepare(SQLiteConnection cnn, string strSql, SQLiteStatement previous, uint timeoutMS, ref string strRemain) + { + if (!String.IsNullOrEmpty(strSql)) strSql = strSql.Trim(); + if (!String.IsNullOrEmpty(strSql)) + { + // + // NOTE: SQLite does not support the concept of separate schemas + // in one database; therefore, remove the base schema name + // used to smooth integration with the base .NET Framework + // data classes. + // + string baseSchemaName = (cnn != null) ? cnn._baseSchemaName : null; + + if (!String.IsNullOrEmpty(baseSchemaName)) + { + strSql = strSql.Replace( + HelperMethods.StringFormat(CultureInfo.InvariantCulture, + "[{0}].", baseSchemaName), String.Empty); + + strSql = strSql.Replace( + HelperMethods.StringFormat(CultureInfo.InvariantCulture, + "{0}.", baseSchemaName), String.Empty); + } + } + + SQLiteConnectionFlags flags = + (cnn != null) ? cnn.Flags : SQLiteConnectionFlags.Default; + + if (ForceLogPrepare() || + HelperMethods.LogPrepare(flags)) + { + if ((strSql == null) || (strSql.Length == 0) || (strSql.Trim().Length == 0)) + SQLiteLog.LogMessage("Preparing {}..."); + else + SQLiteLog.LogMessage(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, "Preparing {{{0}}}...", strSql)); + } + + IntPtr stmt = IntPtr.Zero; + IntPtr ptr = IntPtr.Zero; + int len = 0; + SQLiteErrorCode n = SQLiteErrorCode.Schema; + int retries = 0; + int maximumRetries = (cnn != null) ? cnn._prepareRetries : SQLiteConnection.DefaultPrepareRetries; + byte[] b = ToUTF8(strSql); + string typedefs = null; + SQLiteStatement cmd = null; + Random rnd = null; + uint starttick = (uint)Environment.TickCount; + + ResetCancelCount(); + + GCHandle handle = GCHandle.Alloc(b, GCHandleType.Pinned); + IntPtr psql = handle.AddrOfPinnedObject(); + SQLiteStatementHandle statementHandle = null; + try + { + while ((n == SQLiteErrorCode.Schema || n == SQLiteErrorCode.Locked || n == SQLiteErrorCode.Busy) && retries < maximumRetries) + { + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { + stmt = IntPtr.Zero; + ptr = IntPtr.Zero; + +#if !SQLITE_STANDARD + len = 0; + n = UnsafeNativeMethods.sqlite3_prepare_interop(_sql, psql, b.Length - 1, ref stmt, ref ptr, ref len); +#else +#if USE_PREPARE_V2 + n = UnsafeNativeMethods.sqlite3_prepare_v2(_sql, psql, b.Length - 1, ref stmt, ref ptr); +#else + n = UnsafeNativeMethods.sqlite3_prepare(_sql, psql, b.Length - 1, ref stmt, ref ptr); +#endif + len = -1; +#endif + +#if !NET_COMPACT_20 && TRACE_STATEMENT + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Prepare ({0}): {1}", n, stmt)); +#endif + + if ((n == SQLiteErrorCode.Ok) && (stmt != IntPtr.Zero)) + { + if (statementHandle != null) statementHandle.Dispose(); + statementHandle = new SQLiteStatementHandle(_sql, stmt); + } + } + + if (statementHandle != null) + { + SQLiteConnection.OnChanged(null, new ConnectionEventArgs( + SQLiteConnectionEventType.NewCriticalHandle, null, null, + null, null, statementHandle, strSql, new object[] { + typeof(SQLite3), cnn, strSql, previous, timeoutMS })); + } + + if (ShouldThrowForCancel()) + { + if ((n == SQLiteErrorCode.Ok) || + (n == SQLiteErrorCode.Row) || + (n == SQLiteErrorCode.Done)) + { + n = SQLiteErrorCode.Interrupt; + } + + throw new SQLiteException(n, null); + } + + if (n == SQLiteErrorCode.Interrupt) + break; + else if (n == SQLiteErrorCode.Schema) + retries++; + else if (n == SQLiteErrorCode.Error) + { + if (String.Compare(GetLastError(), "near \"TYPES\": syntax error", StringComparison.OrdinalIgnoreCase) == 0) + { + int pos = strSql.IndexOf(';'); + if (pos == -1) pos = strSql.Length - 1; + + typedefs = strSql.Substring(0, pos + 1); + strSql = strSql.Substring(pos + 1); + + strRemain = String.Empty; + + while (cmd == null && strSql.Length > 0) + { + cmd = Prepare(cnn, strSql, previous, timeoutMS, ref strRemain); + strSql = strRemain; + } + + if (cmd != null) + cmd.SetTypes(typedefs); + + return cmd; + } +#if (NET_35 || NET_40 || NET_45 || NET_451 || NET_452 || NET_46 || NET_461 || NET_462 || NET_47 || NET_471 || NET_472) && !PLATFORM_COMPACTFRAMEWORK + else if (_buildingSchema == false && String.Compare(GetLastError(), 0, "no such table: TEMP.SCHEMA", 0, 26, StringComparison.OrdinalIgnoreCase) == 0) + { + strRemain = String.Empty; + _buildingSchema = true; + try + { + ISQLiteSchemaExtensions ext = ((IServiceProvider)SQLiteFactory.Instance).GetService(typeof(ISQLiteSchemaExtensions)) as ISQLiteSchemaExtensions; + + if (ext != null) + ext.BuildTempSchema(cnn); + + while (cmd == null && strSql.Length > 0) + { + cmd = Prepare(cnn, strSql, previous, timeoutMS, ref strRemain); + strSql = strRemain; + } + + return cmd; + } + finally + { + _buildingSchema = false; + } + } +#endif + } + else if (n == SQLiteErrorCode.Locked || n == SQLiteErrorCode.Busy) // Locked -- delay a small amount before retrying + { + // Keep trying + if (rnd == null) // First time we've encountered the lock + rnd = new Random(); + + // If we've exceeded the command's timeout, give up and throw an error + if ((uint)Environment.TickCount - starttick > timeoutMS) + { + throw new SQLiteException(n, GetLastError()); + } + else + { + // Otherwise sleep for a random amount of time up to 150ms + System.Threading.Thread.Sleep(rnd.Next(1, 150)); + } + } + } + + if (ShouldThrowForCancel()) + { + if ((n == SQLiteErrorCode.Ok) || + (n == SQLiteErrorCode.Row) || + (n == SQLiteErrorCode.Done)) + { + n = SQLiteErrorCode.Interrupt; + } + + throw new SQLiteException(n, null); + } + + if (n == SQLiteErrorCode.Interrupt) return null; + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + + strRemain = UTF8ToString(ptr, len); + + if (statementHandle != null) cmd = new SQLiteStatement(this, flags, statementHandle, strSql.Substring(0, strSql.Length - strRemain.Length), previous); + + return cmd; + } + finally + { + handle.Free(); + } + } + + protected static void LogBind(SQLiteStatementHandle handle, int index) + { + IntPtr handleIntPtr = handle; + + SQLiteLog.LogMessage(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Binding statement {0} paramter #{1} as NULL...", + handleIntPtr, index)); + } + + protected static void LogBind(SQLiteStatementHandle handle, int index, ValueType value) + { + IntPtr handleIntPtr = handle; + + SQLiteLog.LogMessage(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Binding statement {0} paramter #{1} as type {2} with value {{{3}}}...", + handleIntPtr, index, value.GetType(), value)); + } + + private static string FormatDateTime(DateTime value) + { + StringBuilder result = new StringBuilder(); + + result.Append(value.ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFK")); + result.Append(' '); + result.Append(value.Kind); + result.Append(' '); + result.Append(value.Ticks); + + return result.ToString(); + } + + protected static void LogBind(SQLiteStatementHandle handle, int index, DateTime value) + { + IntPtr handleIntPtr = handle; + + SQLiteLog.LogMessage(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Binding statement {0} paramter #{1} as type {2} with value {{{3}}}...", + handleIntPtr, index, typeof(DateTime), FormatDateTime(value))); + } + + protected static void LogBind(SQLiteStatementHandle handle, int index, string value) + { + IntPtr handleIntPtr = handle; + + SQLiteLog.LogMessage(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Binding statement {0} paramter #{1} as type {2} with value {{{3}}}...", + handleIntPtr, index, typeof(String), (value != null) ? value : "")); + } + + private static string ToHexadecimalString( + byte[] array + ) + { + if (array == null) + return null; + + StringBuilder result = new StringBuilder(array.Length * 2); + + int length = array.Length; + + for (int index = 0; index < length; index++) + result.Append(array[index].ToString("x2")); + + return result.ToString(); + } + + protected static void LogBind(SQLiteStatementHandle handle, int index, byte[] value) + { + IntPtr handleIntPtr = handle; + + SQLiteLog.LogMessage(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Binding statement {0} paramter #{1} as type {2} with value {{{3}}}...", + handleIntPtr, index, typeof(Byte[]), (value != null) ? ToHexadecimalString(value) : "")); + } + + internal override void Bind_Double(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, double value) + { + SQLiteStatementHandle handle = stmt._sqlite_stmt; + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index, value); + } + +#if !PLATFORM_COMPACTFRAMEWORK + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_double(handle, index, value); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); +#elif !SQLITE_STANDARD + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_double_interop(handle, index, ref value); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); +#else + throw new NotImplementedException(); +#endif + } + + internal override void Bind_Int32(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, int value) + { + SQLiteStatementHandle handle = stmt._sqlite_stmt; + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index, value); + } + + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_int(handle, index, value); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + } + + internal override void Bind_UInt32(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, uint value) + { + SQLiteStatementHandle handle = stmt._sqlite_stmt; + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index, value); + } + + SQLiteErrorCode n; + + if (HelperMethods.HasFlags(flags, SQLiteConnectionFlags.BindUInt32AsInt64)) + { + long value2 = value; + +#if !PLATFORM_COMPACTFRAMEWORK + n = UnsafeNativeMethods.sqlite3_bind_int64(handle, index, value2); +#elif !SQLITE_STANDARD + n = UnsafeNativeMethods.sqlite3_bind_int64_interop(handle, index, ref value2); +#else + throw new NotImplementedException(); +#endif + } + else + { + n = UnsafeNativeMethods.sqlite3_bind_uint(handle, index, value); + } + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + } + + internal override void Bind_Int64(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, long value) + { + SQLiteStatementHandle handle = stmt._sqlite_stmt; + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index, value); + } + +#if !PLATFORM_COMPACTFRAMEWORK + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_int64(handle, index, value); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); +#elif !SQLITE_STANDARD + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_int64_interop(handle, index, ref value); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); +#else + throw new NotImplementedException(); +#endif + } + + internal override void Bind_UInt64(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, ulong value) + { + SQLiteStatementHandle handle = stmt._sqlite_stmt; + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index, value); + } + +#if !PLATFORM_COMPACTFRAMEWORK + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_uint64(handle, index, value); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); +#elif !SQLITE_STANDARD + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_uint64_interop(handle, index, ref value); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); +#else + throw new NotImplementedException(); +#endif + } + + internal override void Bind_Boolean(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, bool value) + { + SQLiteStatementHandle handle = stmt._sqlite_stmt; + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index, value); + } + + int value2 = value ? 1 : 0; + + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_int(handle, index, value2); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + } + + internal override void Bind_Text(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, string value) + { + SQLiteStatementHandle handle = stmt._sqlite_stmt; + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index, value); + } + + byte[] b = ToUTF8(value); + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index, b); + } + + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_text(handle, index, b, b.Length - 1, (IntPtr)(-1)); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + } + + internal override void Bind_DateTime(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, DateTime dt) + { + SQLiteStatementHandle handle = stmt._sqlite_stmt; + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index, dt); + } + + if (HelperMethods.HasFlags(flags, SQLiteConnectionFlags.BindDateTimeWithKind)) + { + if ((_datetimeKind != DateTimeKind.Unspecified) && + (dt.Kind != DateTimeKind.Unspecified) && + (dt.Kind != _datetimeKind)) + { + if (_datetimeKind == DateTimeKind.Utc) + dt = dt.ToUniversalTime(); + else if (_datetimeKind == DateTimeKind.Local) + dt = dt.ToLocalTime(); + } + } + + switch (_datetimeFormat) + { + case SQLiteDateFormats.Ticks: + { + long value = dt.Ticks; + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index, value); + } + +#if !PLATFORM_COMPACTFRAMEWORK + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_int64(handle, index, value); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + break; +#elif !SQLITE_STANDARD + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_int64_interop(handle, index, ref value); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + break; +#else + throw new NotImplementedException(); +#endif + } + case SQLiteDateFormats.JulianDay: + { + double value = ToJulianDay(dt); + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index, value); + } + +#if !PLATFORM_COMPACTFRAMEWORK + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_double(handle, index, value); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + break; +#elif !SQLITE_STANDARD + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_double_interop(handle, index, ref value); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + break; +#else + throw new NotImplementedException(); +#endif + } + case SQLiteDateFormats.UnixEpoch: + { + long value = Convert.ToInt64(dt.Subtract(UnixEpoch).TotalSeconds); + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index, value); + } + +#if !PLATFORM_COMPACTFRAMEWORK + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_int64(handle, index, value); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + break; +#elif !SQLITE_STANDARD + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_int64_interop(handle, index, ref value); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + break; +#else + throw new NotImplementedException(); +#endif + } + default: + { + byte[] b = ToUTF8(dt); + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index, b); + } + + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_text(handle, index, b, b.Length - 1, (IntPtr)(-1)); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + break; + } + } + } + + internal override void Bind_Blob(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, byte[] blobData) + { + SQLiteStatementHandle handle = stmt._sqlite_stmt; + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index, blobData); + } + + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_blob(handle, index, blobData, blobData.Length, (IntPtr)(-1)); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + } + + internal override void Bind_Null(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index) + { + SQLiteStatementHandle handle = stmt._sqlite_stmt; + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + LogBind(handle, index); + } + + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_null(handle, index); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + } + + internal override int Bind_ParamCount(SQLiteStatement stmt, SQLiteConnectionFlags flags) + { + SQLiteStatementHandle handle = stmt._sqlite_stmt; + int value = UnsafeNativeMethods.sqlite3_bind_parameter_count(handle); + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + IntPtr handleIntPtr = handle; + + SQLiteLog.LogMessage(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Statement {0} paramter count is {1}.", + handleIntPtr, value)); + } + + return value; + } + + internal override string Bind_ParamName(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index) + { + SQLiteStatementHandle handle = stmt._sqlite_stmt; + string name; + +#if !SQLITE_STANDARD + int len = 0; + name = UTF8ToString(UnsafeNativeMethods.sqlite3_bind_parameter_name_interop(handle, index, ref len), len); +#else + name = UTF8ToString(UnsafeNativeMethods.sqlite3_bind_parameter_name(handle, index), -1); +#endif + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + IntPtr handleIntPtr = handle; + + SQLiteLog.LogMessage(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Statement {0} paramter #{1} name is {{{2}}}.", + handleIntPtr, index, name)); + } + + return name; + } + + internal override int Bind_ParamIndex(SQLiteStatement stmt, SQLiteConnectionFlags flags, string paramName) + { + SQLiteStatementHandle handle = stmt._sqlite_stmt; + int index = UnsafeNativeMethods.sqlite3_bind_parameter_index(handle, ToUTF8(paramName)); + + if (ForceLogPrepare() || HelperMethods.LogBind(flags)) + { + IntPtr handleIntPtr = handle; + + SQLiteLog.LogMessage(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Statement {0} paramter index of name {{{1}}} is #{2}.", + handleIntPtr, paramName, index)); + } + + return index; + } + + internal override int ColumnCount(SQLiteStatement stmt) + { + return UnsafeNativeMethods.sqlite3_column_count(stmt._sqlite_stmt); + } + + internal override string ColumnName(SQLiteStatement stmt, int index) + { +#if !SQLITE_STANDARD + int len = 0; + IntPtr p = UnsafeNativeMethods.sqlite3_column_name_interop(stmt._sqlite_stmt, index, ref len); +#else + IntPtr p = UnsafeNativeMethods.sqlite3_column_name(stmt._sqlite_stmt, index); +#endif + if (p == IntPtr.Zero) + throw new SQLiteException(SQLiteErrorCode.NoMem, GetLastError()); +#if !SQLITE_STANDARD + return UTF8ToString(p, len); +#else + return UTF8ToString(p, -1); +#endif + } + + internal override TypeAffinity ColumnAffinity(SQLiteStatement stmt, int index) + { + return UnsafeNativeMethods.sqlite3_column_type(stmt._sqlite_stmt, index); + } + + internal override string ColumnType(SQLiteStatement stmt, int index, ref TypeAffinity nAffinity) + { + int len; +#if !SQLITE_STANDARD + len = 0; + IntPtr p = UnsafeNativeMethods.sqlite3_column_decltype_interop(stmt._sqlite_stmt, index, ref len); +#else + len = -1; + IntPtr p = UnsafeNativeMethods.sqlite3_column_decltype(stmt._sqlite_stmt, index); +#endif + nAffinity = ColumnAffinity(stmt, index); + + if ((p != IntPtr.Zero) && ((len > 0) || (len == -1))) + { + string declType = UTF8ToString(p, len); + + if (!String.IsNullOrEmpty(declType)) + return declType; + } + + string[] ar = stmt.TypeDefinitions; + + if (ar != null) + { + if (index < ar.Length && ar[index] != null) + return ar[index]; + } + + return String.Empty; + } + + internal override int ColumnIndex(SQLiteStatement stmt, string columnName) + { + int x = ColumnCount(stmt); + + for (int n = 0; n < x; n++) + { + if (String.Compare(columnName, ColumnName(stmt, n), StringComparison.OrdinalIgnoreCase) == 0) + return n; + } + return -1; + } + + internal override string ColumnOriginalName(SQLiteStatement stmt, int index) + { +#if !SQLITE_STANDARD + int len = 0; + return UTF8ToString(UnsafeNativeMethods.sqlite3_column_origin_name_interop(stmt._sqlite_stmt, index, ref len), len); +#else + return UTF8ToString(UnsafeNativeMethods.sqlite3_column_origin_name(stmt._sqlite_stmt, index), -1); +#endif + } + + internal override string ColumnDatabaseName(SQLiteStatement stmt, int index) + { +#if !SQLITE_STANDARD + int len = 0; + return UTF8ToString(UnsafeNativeMethods.sqlite3_column_database_name_interop(stmt._sqlite_stmt, index, ref len), len); +#else + return UTF8ToString(UnsafeNativeMethods.sqlite3_column_database_name(stmt._sqlite_stmt, index), -1); +#endif + } + + internal override string ColumnTableName(SQLiteStatement stmt, int index) + { +#if !SQLITE_STANDARD + int len = 0; + return UTF8ToString(UnsafeNativeMethods.sqlite3_column_table_name_interop(stmt._sqlite_stmt, index, ref len), len); +#else + return UTF8ToString(UnsafeNativeMethods.sqlite3_column_table_name(stmt._sqlite_stmt, index), -1); +#endif + } + + internal override bool DoesTableExist( + string dataBase, + string table + ) + { + string dataType = null; /* NOT USED */ + string collateSequence = null; /* NOT USED */ + bool notNull = false; /* NOT USED */ + bool primaryKey = false; /* NOT USED */ + bool autoIncrement = false; /* NOT USED */ + + return ColumnMetaData( + dataBase, table, null, false, ref dataType, + ref collateSequence, ref notNull, ref primaryKey, + ref autoIncrement); + } + + internal override bool ColumnMetaData(string dataBase, string table, string column, bool canThrow, ref string dataType, ref string collateSequence, ref bool notNull, ref bool primaryKey, ref bool autoIncrement) + { + IntPtr dataTypePtr = IntPtr.Zero; + IntPtr collSeqPtr = IntPtr.Zero; + int nnotNull = 0; + int nprimaryKey = 0; + int nautoInc = 0; + SQLiteErrorCode n; + int dtLen; + int csLen; + +#if !SQLITE_STANDARD + dtLen = 0; + csLen = 0; + n = UnsafeNativeMethods.sqlite3_table_column_metadata_interop(_sql, ToUTF8(dataBase), ToUTF8(table), ToUTF8(column), ref dataTypePtr, ref collSeqPtr, ref nnotNull, ref nprimaryKey, ref nautoInc, ref dtLen, ref csLen); +#else + dtLen = -1; + csLen = -1; + + n = UnsafeNativeMethods.sqlite3_table_column_metadata(_sql, ToUTF8(dataBase), ToUTF8(table), ToUTF8(column), ref dataTypePtr, ref collSeqPtr, ref nnotNull, ref nprimaryKey, ref nautoInc); +#endif + if (canThrow && (n != SQLiteErrorCode.Ok)) throw new SQLiteException(n, GetLastError()); + + dataType = UTF8ToString(dataTypePtr, dtLen); + collateSequence = UTF8ToString(collSeqPtr, csLen); + + notNull = (nnotNull == 1); + primaryKey = (nprimaryKey == 1); + autoIncrement = (nautoInc == 1); + + return (n == SQLiteErrorCode.Ok); + } + + internal override object GetObject(SQLiteStatement stmt, int index) + { + switch (ColumnAffinity(stmt, index)) + { + case TypeAffinity.Int64: + { + return GetInt64(stmt, index); + } + case TypeAffinity.Double: + { + return GetDouble(stmt, index); + } + case TypeAffinity.Text: + { + return GetText(stmt, index); + } + case TypeAffinity.Blob: + { + long size = GetBytes(stmt, index, 0, null, 0, 0); + + if ((size > 0) && (size <= int.MaxValue)) + { + byte[] bytes = new byte[(int)size]; + + GetBytes(stmt, index, 0, bytes, 0, (int)size); + + return bytes; + } + break; + } + case TypeAffinity.Null: + { + return DBNull.Value; + } + } + + throw new NotImplementedException(); + } + + internal override double GetDouble(SQLiteStatement stmt, int index) + { +#if !PLATFORM_COMPACTFRAMEWORK + return UnsafeNativeMethods.sqlite3_column_double(stmt._sqlite_stmt, index); +#elif !SQLITE_STANDARD + double value = 0.0; + UnsafeNativeMethods.sqlite3_column_double_interop(stmt._sqlite_stmt, index, ref value); + return value; +#else + throw new NotImplementedException(); +#endif + } + + internal override bool GetBoolean(SQLiteStatement stmt, int index) + { + return ToBoolean(GetObject(stmt, index), CultureInfo.InvariantCulture, false); + } + + internal override sbyte GetSByte(SQLiteStatement stmt, int index) + { + return unchecked((sbyte)(GetInt32(stmt, index) & byte.MaxValue)); + } + + internal override byte GetByte(SQLiteStatement stmt, int index) + { + return unchecked((byte)(GetInt32(stmt, index) & byte.MaxValue)); + } + + internal override short GetInt16(SQLiteStatement stmt, int index) + { + return unchecked((short)(GetInt32(stmt, index) & ushort.MaxValue)); + } + + internal override ushort GetUInt16(SQLiteStatement stmt, int index) + { + return unchecked((ushort)(GetInt32(stmt, index) & ushort.MaxValue)); + } + + internal override int GetInt32(SQLiteStatement stmt, int index) + { + return UnsafeNativeMethods.sqlite3_column_int(stmt._sqlite_stmt, index); + } + + internal override uint GetUInt32(SQLiteStatement stmt, int index) + { + return unchecked((uint)GetInt32(stmt, index)); + } + + internal override long GetInt64(SQLiteStatement stmt, int index) + { +#if !PLATFORM_COMPACTFRAMEWORK + return UnsafeNativeMethods.sqlite3_column_int64(stmt._sqlite_stmt, index); +#elif !SQLITE_STANDARD + long value = 0; + UnsafeNativeMethods.sqlite3_column_int64_interop(stmt._sqlite_stmt, index, ref value); + return value; +#else + throw new NotImplementedException(); +#endif + } + + internal override ulong GetUInt64(SQLiteStatement stmt, int index) + { + return unchecked((ulong)GetInt64(stmt, index)); + } + + internal override string GetText(SQLiteStatement stmt, int index) + { +#if !SQLITE_STANDARD + int len = 0; + return UTF8ToString(UnsafeNativeMethods.sqlite3_column_text_interop(stmt._sqlite_stmt, index, ref len), len); +#else + return UTF8ToString(UnsafeNativeMethods.sqlite3_column_text(stmt._sqlite_stmt, index), + UnsafeNativeMethods.sqlite3_column_bytes(stmt._sqlite_stmt, index)); +#endif + } + + internal override DateTime GetDateTime(SQLiteStatement stmt, int index) + { + if (_datetimeFormat == SQLiteDateFormats.Ticks) + return TicksToDateTime(GetInt64(stmt, index), _datetimeKind); + else if (_datetimeFormat == SQLiteDateFormats.JulianDay) + return ToDateTime(GetDouble(stmt, index), _datetimeKind); + else if (_datetimeFormat == SQLiteDateFormats.UnixEpoch) + return UnixEpochToDateTime(GetInt64(stmt, index), _datetimeKind); + +#if !SQLITE_STANDARD + int len = 0; + return ToDateTime(UnsafeNativeMethods.sqlite3_column_text_interop(stmt._sqlite_stmt, index, ref len), len); +#else + return ToDateTime(UnsafeNativeMethods.sqlite3_column_text(stmt._sqlite_stmt, index), + UnsafeNativeMethods.sqlite3_column_bytes(stmt._sqlite_stmt, index)); +#endif + } + + internal override long GetBytes(SQLiteStatement stmt, int index, int nDataOffset, byte[] bDest, int nStart, int nLength) + { + int nlen = UnsafeNativeMethods.sqlite3_column_bytes(stmt._sqlite_stmt, index); + + // If no destination buffer, return the size needed. + if (bDest == null) return nlen; + + int nCopied = nLength; + + if (nCopied + nStart > bDest.Length) nCopied = bDest.Length - nStart; + if (nCopied + nDataOffset > nlen) nCopied = nlen - nDataOffset; + + if (nCopied > 0) + { + IntPtr ptr = UnsafeNativeMethods.sqlite3_column_blob(stmt._sqlite_stmt, index); + + Marshal.Copy((IntPtr)(ptr.ToInt64() + nDataOffset), bDest, nStart, nCopied); + } + else + { + nCopied = 0; + } + + return nCopied; + } + + internal override char GetChar(SQLiteStatement stmt, int index) + { + return Convert.ToChar(GetUInt16(stmt, index)); + } + + internal override long GetChars(SQLiteStatement stmt, int index, int nDataOffset, char[] bDest, int nStart, int nLength) + { + int nlen; + int nCopied = nLength; + + string str = GetText(stmt, index); + nlen = str.Length; + + if (bDest == null) return nlen; + + if (nCopied + nStart > bDest.Length) nCopied = bDest.Length - nStart; + if (nCopied + nDataOffset > nlen) nCopied = nlen - nDataOffset; + + if (nCopied > 0) + str.CopyTo(nDataOffset, bDest, nStart, nCopied); + else nCopied = 0; + + return nCopied; + } + + internal override bool IsNull(SQLiteStatement stmt, int index) + { + return (ColumnAffinity(stmt, index) == TypeAffinity.Null); + } + + internal override int AggregateCount(IntPtr context) + { + return UnsafeNativeMethods.sqlite3_aggregate_count(context); + } + + internal override SQLiteErrorCode CreateFunction(string strFunction, int nArgs, bool needCollSeq, SQLiteCallback func, SQLiteCallback funcstep, SQLiteFinalCallback funcfinal, bool canThrow) + { + SQLiteErrorCode n; + +#if !SQLITE_STANDARD + n = UnsafeNativeMethods.sqlite3_create_function_interop(_sql, ToUTF8(strFunction), nArgs, 4, IntPtr.Zero, func, funcstep, funcfinal, (needCollSeq == true) ? 1 : 0); + if (n == SQLiteErrorCode.Ok) n = UnsafeNativeMethods.sqlite3_create_function_interop(_sql, ToUTF8(strFunction), nArgs, 1, IntPtr.Zero, func, funcstep, funcfinal, (needCollSeq == true) ? 1 : 0); +#else + n = UnsafeNativeMethods.sqlite3_create_function(_sql, ToUTF8(strFunction), nArgs, 4, IntPtr.Zero, func, funcstep, funcfinal); + if (n == SQLiteErrorCode.Ok) n = UnsafeNativeMethods.sqlite3_create_function(_sql, ToUTF8(strFunction), nArgs, 1, IntPtr.Zero, func, funcstep, funcfinal); +#endif + if (canThrow && (n != SQLiteErrorCode.Ok)) throw new SQLiteException(n, GetLastError()); + return n; + } + + internal override SQLiteErrorCode CreateCollation(string strCollation, SQLiteCollation func, SQLiteCollation func16, bool canThrow) + { + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_create_collation(_sql, ToUTF8(strCollation), 2, IntPtr.Zero, func16); + if (n == SQLiteErrorCode.Ok) n = UnsafeNativeMethods.sqlite3_create_collation(_sql, ToUTF8(strCollation), 1, IntPtr.Zero, func); + if (canThrow && (n != SQLiteErrorCode.Ok)) throw new SQLiteException(n, GetLastError()); + return n; + } + + internal override int ContextCollateCompare(CollationEncodingEnum enc, IntPtr context, string s1, string s2) + { +#if !SQLITE_STANDARD + byte[] b1; + byte[] b2; + System.Text.Encoding converter = null; + + switch (enc) + { + case CollationEncodingEnum.UTF8: + converter = System.Text.Encoding.UTF8; + break; + case CollationEncodingEnum.UTF16LE: + converter = System.Text.Encoding.Unicode; + break; + case CollationEncodingEnum.UTF16BE: + converter = System.Text.Encoding.BigEndianUnicode; + break; + } + + b1 = converter.GetBytes(s1); + b2 = converter.GetBytes(s2); + + return UnsafeNativeMethods.sqlite3_context_collcompare_interop(context, b1, b1.Length, b2, b2.Length); +#else + throw new NotImplementedException(); +#endif + } + + internal override int ContextCollateCompare(CollationEncodingEnum enc, IntPtr context, char[] c1, char[] c2) + { +#if !SQLITE_STANDARD + byte[] b1; + byte[] b2; + System.Text.Encoding converter = null; + + switch (enc) + { + case CollationEncodingEnum.UTF8: + converter = System.Text.Encoding.UTF8; + break; + case CollationEncodingEnum.UTF16LE: + converter = System.Text.Encoding.Unicode; + break; + case CollationEncodingEnum.UTF16BE: + converter = System.Text.Encoding.BigEndianUnicode; + break; + } + + b1 = converter.GetBytes(c1); + b2 = converter.GetBytes(c2); + + return UnsafeNativeMethods.sqlite3_context_collcompare_interop(context, b1, b1.Length, b2, b2.Length); +#else + throw new NotImplementedException(); +#endif + } + + internal override CollationSequence GetCollationSequence(SQLiteFunction func, IntPtr context) + { +#if !SQLITE_STANDARD + CollationSequence seq = new CollationSequence(); + int len = 0; + int type = 0; + int enc = 0; + IntPtr p = UnsafeNativeMethods.sqlite3_context_collseq_interop(context, ref type, ref enc, ref len); + + if (p != null) seq.Name = UTF8ToString(p, len); + seq.Type = (CollationTypeEnum)type; + seq._func = func; + seq.Encoding = (CollationEncodingEnum)enc; + + return seq; +#else + throw new NotImplementedException(); +#endif + } + + internal override long GetParamValueBytes(IntPtr p, int nDataOffset, byte[] bDest, int nStart, int nLength) + { + int nlen = UnsafeNativeMethods.sqlite3_value_bytes(p); + + // If no destination buffer, return the size needed. + if (bDest == null) return nlen; + + int nCopied = nLength; + + if (nCopied + nStart > bDest.Length) nCopied = bDest.Length - nStart; + if (nCopied + nDataOffset > nlen) nCopied = nlen - nDataOffset; + + if (nCopied > 0) + { + IntPtr ptr = UnsafeNativeMethods.sqlite3_value_blob(p); + + Marshal.Copy((IntPtr)(ptr.ToInt64() + nDataOffset), bDest, nStart, nCopied); + } + else + { + nCopied = 0; + } + + return nCopied; + } + + internal override double GetParamValueDouble(IntPtr ptr) + { +#if !PLATFORM_COMPACTFRAMEWORK + return UnsafeNativeMethods.sqlite3_value_double(ptr); +#elif !SQLITE_STANDARD + double value = 0.0; + UnsafeNativeMethods.sqlite3_value_double_interop(ptr, ref value); + return value; +#else + throw new NotImplementedException(); +#endif + } + + internal override int GetParamValueInt32(IntPtr ptr) + { + return UnsafeNativeMethods.sqlite3_value_int(ptr); + } + + internal override long GetParamValueInt64(IntPtr ptr) + { +#if !PLATFORM_COMPACTFRAMEWORK + return UnsafeNativeMethods.sqlite3_value_int64(ptr); +#elif !SQLITE_STANDARD + Int64 value = 0; + UnsafeNativeMethods.sqlite3_value_int64_interop(ptr, ref value); + return value; +#else + throw new NotImplementedException(); +#endif + } + + internal override string GetParamValueText(IntPtr ptr) + { +#if !SQLITE_STANDARD + int len = 0; + return UTF8ToString(UnsafeNativeMethods.sqlite3_value_text_interop(ptr, ref len), len); +#else + return UTF8ToString(UnsafeNativeMethods.sqlite3_value_text(ptr), + UnsafeNativeMethods.sqlite3_value_bytes(ptr)); +#endif + } + + internal override TypeAffinity GetParamValueType(IntPtr ptr) + { + return UnsafeNativeMethods.sqlite3_value_type(ptr); + } + + internal override void ReturnBlob(IntPtr context, byte[] value) + { + UnsafeNativeMethods.sqlite3_result_blob(context, value, value.Length, (IntPtr)(-1)); + } + + internal override void ReturnDouble(IntPtr context, double value) + { +#if !PLATFORM_COMPACTFRAMEWORK + UnsafeNativeMethods.sqlite3_result_double(context, value); +#elif !SQLITE_STANDARD + UnsafeNativeMethods.sqlite3_result_double_interop(context, ref value); +#else + throw new NotImplementedException(); +#endif + } + + internal override void ReturnError(IntPtr context, string value) + { + UnsafeNativeMethods.sqlite3_result_error(context, ToUTF8(value), value.Length); + } + + internal override void ReturnInt32(IntPtr context, int value) + { + UnsafeNativeMethods.sqlite3_result_int(context, value); + } + + internal override void ReturnInt64(IntPtr context, long value) + { +#if !PLATFORM_COMPACTFRAMEWORK + UnsafeNativeMethods.sqlite3_result_int64(context, value); +#elif !SQLITE_STANDARD + UnsafeNativeMethods.sqlite3_result_int64_interop(context, ref value); +#else + throw new NotImplementedException(); +#endif + } + + internal override void ReturnNull(IntPtr context) + { + UnsafeNativeMethods.sqlite3_result_null(context); + } + + internal override void ReturnText(IntPtr context, string value) + { + byte[] b = ToUTF8(value); + UnsafeNativeMethods.sqlite3_result_text(context, ToUTF8(value), b.Length - 1, (IntPtr)(-1)); + } + +#if INTEROP_VIRTUAL_TABLE + /// + /// Determines the file name of the native library containing the native + /// "vtshim" extension -AND- whether it should be dynamically loaded by + /// this class. + /// + /// + /// This output parameter will be set to non-zero if the returned native + /// library file name should be dynamically loaded prior to attempting + /// the creation of native disposable extension modules. + /// + /// + /// The file name of the native library containing the native "vtshim" + /// extension -OR- null if it cannot be determined. + /// + private string GetShimExtensionFileName( + ref bool isLoadNeeded /* out */ + ) + { + if (_shimIsLoadNeeded != null) + isLoadNeeded = (bool)_shimIsLoadNeeded; + else +#if SQLITE_STANDARD || USE_INTEROP_DLL || PLATFORM_COMPACTFRAMEWORK + isLoadNeeded = HelperMethods.IsWindows(); /* COMPAT */ +#else + isLoadNeeded = false; /* mixed-mode assembly */ +#endif + + string fileName = _shimExtensionFileName; + + if (fileName != null) + return fileName; + +#if (SQLITE_STANDARD || USE_INTEROP_DLL || PLATFORM_COMPACTFRAMEWORK) && PRELOAD_NATIVE_LIBRARY + return UnsafeNativeMethods.GetNativeLibraryFileNameOnly(); /* COMPAT */ +#else + return null; +#endif + } + + /// + /// Calls the native SQLite core library in order to create a disposable + /// module containing the implementation of a virtual table. + /// + /// + /// The module object to be used when creating the native disposable module. + /// + /// + /// The flags for the associated object instance. + /// + internal override void CreateModule(SQLiteModule module, SQLiteConnectionFlags flags) + { + if (module == null) + throw new ArgumentNullException("module"); + + if (HelperMethods.NoLogModule(flags)) + { + module.LogErrors = HelperMethods.LogModuleError(flags); + module.LogExceptions = HelperMethods.LogModuleException(flags); + } + + if (_sql == null) + throw new SQLiteException("connection has an invalid handle"); + + bool isLoadNeeded = false; + string fileName = GetShimExtensionFileName(ref isLoadNeeded); + + if (isLoadNeeded) + { + if (fileName == null) + throw new SQLiteException("the file name for the \"vtshim\" extension is unknown"); + + if (_shimExtensionProcName == null) + throw new SQLiteException("the entry point for the \"vtshim\" extension is unknown"); + + SetLoadExtension(true); + LoadExtension(fileName, _shimExtensionProcName); + } + + if (module.CreateDisposableModule(_sql)) + { + if (_modules == null) + _modules = new Dictionary(); + + _modules.Add(module.Name, module); + + if (_usePool) + { + _usePool = false; + +#if !NET_COMPACT_20 && TRACE_CONNECTION + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "CreateModule (Pool) Disabled: {0}", + HandleToString())); +#endif + } + } + else + { + throw new SQLiteException(GetLastError()); + } + } + + /// + /// Calls the native SQLite core library in order to cleanup the resources + /// associated with a module containing the implementation of a virtual table. + /// + /// + /// The module object previously passed to the + /// method. + /// + /// + /// The flags for the associated object instance. + /// + internal override void DisposeModule(SQLiteModule module, SQLiteConnectionFlags flags) + { + if (module == null) + throw new ArgumentNullException("module"); + + module.Dispose(); + } +#endif + + internal override IntPtr AggregateContext(IntPtr context) + { + return UnsafeNativeMethods.sqlite3_aggregate_context(context, 1); + } + +#if INTEROP_VIRTUAL_TABLE + /// + /// Calls the native SQLite core library in order to declare a virtual table + /// in response to a call into the + /// or virtual table methods. + /// + /// + /// The virtual table module that is to be responsible for the virtual table + /// being declared. + /// + /// + /// The string containing the SQL statement describing the virtual table to + /// be declared. + /// + /// + /// Upon success, the contents of this parameter are undefined. Upon failure, + /// it should contain an appropriate error message. + /// + /// + /// A standard SQLite return code. + /// + internal override SQLiteErrorCode DeclareVirtualTable( + SQLiteModule module, + string strSql, + ref string error + ) + { + if (_sql == null) + { + error = "connection has an invalid handle"; + return SQLiteErrorCode.Error; + } + + IntPtr pSql = IntPtr.Zero; + + try + { + pSql = SQLiteString.Utf8IntPtrFromString(strSql); + + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_declare_vtab( + _sql, pSql); + + if ((n == SQLiteErrorCode.Ok) && (module != null)) + module.Declared = true; + + if (n != SQLiteErrorCode.Ok) error = GetLastError(); + + return n; + } + finally + { + if (pSql != IntPtr.Zero) + { + SQLiteMemory.Free(pSql); + pSql = IntPtr.Zero; + } + } + } + + /// + /// Calls the native SQLite core library in order to declare a virtual table + /// function in response to a call into the + /// or virtual table methods. + /// + /// + /// The virtual table module that is to be responsible for the virtual table + /// function being declared. + /// + /// + /// The number of arguments to the function being declared. + /// + /// + /// The name of the function being declared. + /// + /// + /// Upon success, the contents of this parameter are undefined. Upon failure, + /// it should contain an appropriate error message. + /// + /// + /// A standard SQLite return code. + /// + internal override SQLiteErrorCode DeclareVirtualFunction( + SQLiteModule module, + int argumentCount, + string name, + ref string error + ) + { + if (_sql == null) + { + error = "connection has an invalid handle"; + return SQLiteErrorCode.Error; + } + + IntPtr pName = IntPtr.Zero; + + try + { + pName = SQLiteString.Utf8IntPtrFromString(name); + + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_overload_function( + _sql, pName, argumentCount); + + if (n != SQLiteErrorCode.Ok) error = GetLastError(); + + return n; + } + finally + { + if (pName != IntPtr.Zero) + { + SQLiteMemory.Free(pName); + pName = IntPtr.Zero; + } + } + } +#endif + + /// + /// Builds an error message string fragment containing the + /// defined values of the + /// enumeration. + /// + /// + /// The built string fragment. + /// + private static string GetStatusDbOpsNames() + { + StringBuilder builder = new StringBuilder(); + +#if !PLATFORM_COMPACTFRAMEWORK + foreach (string name in Enum.GetNames( + typeof(SQLiteStatusOpsEnum))) + { + if (String.IsNullOrEmpty(name)) + continue; + + if (builder.Length > 0) + builder.Append(", "); + + builder.Append(name); + } +#else + // + // TODO: Update this list if the available values in the + // "SQLiteConfigDbOpsEnum" enumeration change. + // + builder.AppendFormat(CultureInfo.InvariantCulture, + "{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}", + SQLiteStatusOpsEnum.SQLITE_DBSTATUS_LOOKASIDE_USED, + SQLiteStatusOpsEnum.SQLITE_DBSTATUS_CACHE_USED, + SQLiteStatusOpsEnum.SQLITE_DBSTATUS_SCHEMA_USED, + SQLiteStatusOpsEnum.SQLITE_DBSTATUS_STMT_USED, + SQLiteStatusOpsEnum.SQLITE_DBSTATUS_LOOKASIDE_HIT, + SQLiteStatusOpsEnum.SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, + SQLiteStatusOpsEnum.SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, + SQLiteStatusOpsEnum.SQLITE_DBSTATUS_CACHE_HIT, + SQLiteStatusOpsEnum.SQLITE_DBSTATUS_CACHE_MISS, + SQLiteStatusOpsEnum.SQLITE_DBSTATUS_CACHE_WRITE, + SQLiteStatusOpsEnum.SQLITE_DBSTATUS_DEFERRED_FKS, + SQLiteStatusOpsEnum.SQLITE_DBSTATUS_CACHE_USED_SHARED); +#endif + + return builder.ToString(); + } + + /// + /// Builds an error message string fragment containing the + /// defined values of the + /// enumeration. + /// + /// + /// The built string fragment. + /// + private static string GetConfigDbOpsNames() + { + StringBuilder builder = new StringBuilder(); + +#if !PLATFORM_COMPACTFRAMEWORK + foreach (string name in Enum.GetNames( + typeof(SQLiteConfigDbOpsEnum))) + { + if (String.IsNullOrEmpty(name)) + continue; + + if (builder.Length > 0) + builder.Append(", "); + + builder.Append(name); + } +#else + // + // TODO: Update this list if the available values in the + // "SQLiteConfigDbOpsEnum" enumeration change. + // + builder.AppendFormat(CultureInfo.InvariantCulture, + "{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}", + SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_NONE, + SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_MAINDBNAME, + SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_LOOKASIDE, + SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_ENABLE_FKEY, + SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_ENABLE_TRIGGER, + SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, + SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, + SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, + SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_ENABLE_QPSG, + SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_TRIGGER_EQP); +#endif + + return builder.ToString(); + } + + /// + /// Returns the current and/or highwater values for the specified + /// database status parameter. + /// + /// + /// The database status parameter to query. + /// + /// + /// Non-zero to reset the highwater value to the current value. + /// + /// + /// If applicable, receives the current value. + /// + /// + /// If applicable, receives the highwater value. + /// + /// + /// A standard SQLite return code. + /// + internal override SQLiteErrorCode GetStatusParameter( + SQLiteStatusOpsEnum option, + bool reset, + ref int current, + ref int highwater + ) + { + if (!Enum.IsDefined(typeof(SQLiteStatusOpsEnum), option)) + { + throw new SQLiteException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "unrecognized status option, must be: {0}", + GetStatusDbOpsNames())); + } + + return UnsafeNativeMethods.sqlite3_db_status( + _sql, option, ref current, ref highwater, reset ? 1 : 0); + } + + /// + /// Change a configuration option value for the database. + /// connection. + /// + /// + /// The database configuration option to change. + /// + /// + /// The new value for the specified configuration option. + /// + /// + /// A standard SQLite return code. + /// + internal override SQLiteErrorCode SetConfigurationOption( + SQLiteConfigDbOpsEnum option, + object value + ) + { + if (!Enum.IsDefined(typeof(SQLiteConfigDbOpsEnum), option)) + { + throw new SQLiteException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "unrecognized configuration option, must be: {0}", + GetConfigDbOpsNames())); + } + + switch (option) + { + case SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_NONE: // nil + { + // + // NOTE: Do nothing, return success. + // + return SQLiteErrorCode.Ok; + } + case SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_MAINDBNAME: // char* + { + if (value == null) + throw new ArgumentNullException("value"); + + if (!(value is string)) + { + throw new SQLiteException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "configuration value type mismatch, must be of type {0}", + typeof(string))); + } + + SQLiteErrorCode rc = SQLiteErrorCode.Error; + IntPtr pDbName = IntPtr.Zero; + + try + { + pDbName = SQLiteString.Utf8IntPtrFromString( + (string)value); + + if (pDbName == IntPtr.Zero) + { + throw new SQLiteException( + SQLiteErrorCode.NoMem, + "cannot allocate database name"); + } + + rc = UnsafeNativeMethods.sqlite3_db_config_charptr( + _sql, option, pDbName); + + if (rc == SQLiteErrorCode.Ok) + { + FreeDbName(true); + + dbName = pDbName; + pDbName = IntPtr.Zero; + } + } + finally + { + if ((rc != SQLiteErrorCode.Ok) && + (pDbName != IntPtr.Zero)) + { + SQLiteMemory.Free(pDbName); + pDbName = IntPtr.Zero; + } + } + + return rc; + } + case SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_LOOKASIDE: // void* int int + { + object[] array = value as object[]; + + if (array == null) + { + throw new SQLiteException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "configuration value type mismatch, must be of type {0}", + typeof(object[]))); + } + + if (!(array[0] is IntPtr)) + { + throw new SQLiteException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "configuration element zero (0) type mismatch, must be of type {0}", + typeof(IntPtr))); + } + + if (!(array[1] is int)) + { + throw new SQLiteException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "configuration element one (1) type mismatch, must be of type {0}", + typeof(int))); + } + + if (!(array[2] is int)) + { + throw new SQLiteException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "configuration element two (2) type mismatch, must be of type {0}", + typeof(int))); + } + + return UnsafeNativeMethods.sqlite3_db_config_intptr_two_ints( + _sql, option, (IntPtr)array[0], (int)array[1], (int)array[2]); + } + case SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_ENABLE_FKEY: // int int* + case SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_ENABLE_TRIGGER: // int int* + case SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: // int int* + case SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: // int int* + case SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: // int int* + case SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_ENABLE_QPSG: // int int* + case SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_TRIGGER_EQP: // int int* + case SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_RESET_DATABASE: // int int* + { + if (!(value is bool)) + { + throw new SQLiteException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "configuration value type mismatch, must be of type {0}", + typeof(bool))); + } + + int result = 0; /* NOT USED */ + + return UnsafeNativeMethods.sqlite3_db_config_int_refint( + _sql, option, ((bool)value ? 1 : 0), ref result); + } + default: + { + throw new SQLiteException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "unsupported configuration option {0}", option)); + } + } + } + + /// + /// Enables or disables extension loading by SQLite. + /// + /// + /// True to enable loading of extensions, false to disable. + /// + internal override void SetLoadExtension(bool bOnOff) + { + SQLiteErrorCode n; + + if (SQLiteVersionNumber >= 3013000) + { + n = SetConfigurationOption( + SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, + bOnOff); + } + else + { + n = UnsafeNativeMethods.sqlite3_enable_load_extension( + _sql, (bOnOff ? -1 : 0)); + } + + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + } + + /// + /// Loads a SQLite extension library from the named file. + /// + /// + /// The name of the dynamic link library file containing the extension. + /// + /// + /// The name of the exported function used to initialize the extension. + /// If null, the default "sqlite3_extension_init" will be used. + /// + internal override void LoadExtension(string fileName, string procName) + { + if (fileName == null) + throw new ArgumentNullException("fileName"); + + IntPtr pError = IntPtr.Zero; + + try + { + byte[] utf8FileName = UTF8Encoding.UTF8.GetBytes(fileName + '\0'); + byte[] utf8ProcName = null; + + if (procName != null) + utf8ProcName = UTF8Encoding.UTF8.GetBytes(procName + '\0'); + + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_load_extension( + _sql, utf8FileName, utf8ProcName, ref pError); + + if (n != SQLiteErrorCode.Ok) + throw new SQLiteException(n, UTF8ToString(pError, -1)); + } + finally + { + if (pError != IntPtr.Zero) + { + UnsafeNativeMethods.sqlite3_free(pError); + pError = IntPtr.Zero; + } + } + } + + /// Enables or disabled extended result codes returned by SQLite + internal override void SetExtendedResultCodes(bool bOnOff) + { + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_extended_result_codes( + _sql, (bOnOff ? -1 : 0)); + + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + } + /// Gets the last SQLite error code + internal override SQLiteErrorCode ResultCode() + { + return UnsafeNativeMethods.sqlite3_errcode(_sql); + } + /// Gets the last SQLite extended error code + internal override SQLiteErrorCode ExtendedResultCode() + { + return UnsafeNativeMethods.sqlite3_extended_errcode(_sql); + } + + /// Add a log message via the SQLite sqlite3_log interface. + internal override void LogMessage(SQLiteErrorCode iErrCode, string zMessage) + { + StaticLogMessage(iErrCode, zMessage); + } + + /// Add a log message via the SQLite sqlite3_log interface. + internal static void StaticLogMessage(SQLiteErrorCode iErrCode, string zMessage) + { + UnsafeNativeMethods.sqlite3_log(iErrCode, ToUTF8(zMessage)); + } + +#if INTEROP_CODEC || INTEROP_INCLUDE_SEE + private static void ZeroPassword(byte[] passwordBytes) + { + if (passwordBytes == null) return; + + for (int index = 0; index < passwordBytes.Length; index++) + { + byte value = (byte)((index + 1) % byte.MaxValue); + + passwordBytes[index] = value; + passwordBytes[index] ^= value; + } + } + + internal override void SetPassword(byte[] passwordBytes) + { + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_key(_sql, passwordBytes, passwordBytes.Length); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.HidePassword)) + ZeroPassword(passwordBytes); + + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + + if (_usePool) + { + _usePool = false; + +#if !NET_COMPACT_20 && TRACE_CONNECTION + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "SetPassword (Pool) Disabled: {0}", + HandleToString())); +#endif + } + } + + internal override void ChangePassword(byte[] newPasswordBytes) + { + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_rekey(_sql, newPasswordBytes, (newPasswordBytes == null) ? 0 : newPasswordBytes.Length); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.HidePassword)) + ZeroPassword(newPasswordBytes); + + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + + if (_usePool) + { + _usePool = false; + +#if !NET_COMPACT_20 && TRACE_CONNECTION + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "ChangePassword (Pool) Disabled: {0}", + HandleToString())); +#endif + } + } +#endif + + internal override void SetProgressHook(int nOps, SQLiteProgressCallback func) + { + UnsafeNativeMethods.sqlite3_progress_handler(_sql, nOps, func, IntPtr.Zero); + } + + internal override void SetAuthorizerHook(SQLiteAuthorizerCallback func) + { + UnsafeNativeMethods.sqlite3_set_authorizer(_sql, func, IntPtr.Zero); + } + + internal override void SetUpdateHook(SQLiteUpdateCallback func) + { + UnsafeNativeMethods.sqlite3_update_hook(_sql, func, IntPtr.Zero); + } + + internal override void SetCommitHook(SQLiteCommitCallback func) + { + UnsafeNativeMethods.sqlite3_commit_hook(_sql, func, IntPtr.Zero); + } + + internal override void SetTraceCallback(SQLiteTraceCallback func) + { + UnsafeNativeMethods.sqlite3_trace(_sql, func, IntPtr.Zero); + } + + internal override void SetTraceCallback2(SQLiteTraceFlags mask, SQLiteTraceCallback2 func) + { + UnsafeNativeMethods.sqlite3_trace_v2(_sql, mask, func, IntPtr.Zero); + } + + internal override void SetRollbackHook(SQLiteRollbackCallback func) + { + UnsafeNativeMethods.sqlite3_rollback_hook(_sql, func, IntPtr.Zero); + } + + /// + /// Allows the setting of a logging callback invoked by SQLite when a + /// log event occurs. Only one callback may be set. If NULL is passed, + /// the logging callback is unregistered. + /// + /// The callback function to invoke. + /// Returns a result code + internal override SQLiteErrorCode SetLogCallback(SQLiteLogCallback func) + { + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_config_log( + SQLiteConfigOpsEnum.SQLITE_CONFIG_LOG, func, IntPtr.Zero); + + if (rc == SQLiteErrorCode.Ok) + _setLogCallback = (func != null); + + return rc; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Appends an error message and an appropriate line-ending to a + /// instance. This is useful because the .NET Compact Framework has a slightly different set + /// of supported methods for the class. + /// + /// + /// The instance to append to. + /// + /// + /// The message to append. It will be followed by an appropriate line-ending. + /// + private static void AppendError( + StringBuilder builder, + string message + ) + { + if (builder == null) + return; + +#if !PLATFORM_COMPACTFRAMEWORK + builder.AppendLine(message); +#else + builder.Append(message); + builder.Append("\r\n"); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This method attempts to cause the SQLite native library to invalidate + /// its function pointers that refer to this instance. This is necessary + /// to prevent calls from native code into delegates that may have been + /// garbage collected. Normally, these types of issues can only arise for + /// connections that are added to the pool; howver, it is good practice to + /// unconditionally invalidate function pointers that may refer to objects + /// being disposed. + /// + /// + /// Non-zero to also invalidate global function pointers (i.e. those that + /// are not directly associated with this connection on the native side). + /// + /// + /// Non-zero if this method is being executed within a context where it can + /// throw an exception in the event of failure; otherwise, zero. + /// + /// + /// Non-zero if this method was successful; otherwise, zero. + /// + private bool UnhookNativeCallbacks( + bool includeGlobal, + bool canThrow + ) + { + // + // NOTE: Initially, this method assumes success. Then, if any attempt + // to invalidate a function pointer fails, the overall result is + // set to failure. However, this will not prevent further + // attempts, if any, to invalidate subsequent function pointers. + // + bool result = true; + SQLiteErrorCode rc = SQLiteErrorCode.Ok; + StringBuilder builder = new StringBuilder(); + + /////////////////////////////////////////////////////////////////////////////////////////// + + #region Rollback Hook (Per-Connection) + try + { + SetRollbackHook(null); /* throw */ + } +#if !NET_COMPACT_20 && TRACE_CONNECTION + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Failed to unset rollback hook: {0}", + e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + + AppendError(builder, "failed to unset rollback hook"); + rc = SQLiteErrorCode.Error; + + result = false; + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////// + + #region Trace Callback (Per-Connection) + try + { + // + // NOTE: When using version 3.14 (or later) of the SQLite core + // library, use the newer sqlite3_trace_v2() API in order + // to unhook the trace callback, just in case the older + // API is not available (e.g. SQLITE_OMIT_DEPRECATED). + // + if (UnsafeNativeMethods.sqlite3_libversion_number() >= 3014000) + SetTraceCallback2(SQLiteTraceFlags.SQLITE_TRACE_NONE, null); /* throw */ + else + SetTraceCallback(null); /* throw */ + } +#if !NET_COMPACT_20 && TRACE_CONNECTION + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Failed to unset trace callback: {0}", + e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + + AppendError(builder, "failed to unset trace callback"); + rc = SQLiteErrorCode.Error; + + result = false; + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////// + + #region Commit Hook (Per-Connection) + try + { + SetCommitHook(null); /* throw */ + } +#if !NET_COMPACT_20 && TRACE_CONNECTION + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Failed to unset commit hook: {0}", + e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + + AppendError(builder, "failed to unset commit hook"); + rc = SQLiteErrorCode.Error; + + result = false; + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////// + + #region Update Hook (Per-Connection) + try + { + SetUpdateHook(null); /* throw */ + } +#if !NET_COMPACT_20 && TRACE_CONNECTION + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Failed to unset update hook: {0}", + e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + + AppendError(builder, "failed to unset update hook"); + rc = SQLiteErrorCode.Error; + + result = false; + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////// + + #region Authorizer Hook (Per-Connection) + try + { + SetAuthorizerHook(null); /* throw */ + } +#if !NET_COMPACT_20 && TRACE_CONNECTION + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Failed to unset authorizer hook: {0}", + e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + + AppendError(builder, "failed to unset authorizer hook"); + rc = SQLiteErrorCode.Error; + + result = false; + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////// + + #region Progress Hook (Per-Connection) + try + { + SetProgressHook(0, null); /* throw */ + } +#if !NET_COMPACT_20 && TRACE_CONNECTION + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Failed to unset progress hook: {0}", + e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + + AppendError(builder, "failed to unset progress hook"); + rc = SQLiteErrorCode.Error; + + result = false; + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////// + + #region Log Callback (Global) + // + // NOTE: We have to be careful here because the log callback + // is not per-connection on the native side. It should + // only be unset by this method if this instance was + // responsible for setting it. + // + if (includeGlobal && _setLogCallback) + { + try + { + SQLiteErrorCode rc2 = SetLogCallback(null); /* throw */ + + if (rc2 != SQLiteErrorCode.Ok) + { + AppendError(builder, "could not unset log callback"); + rc = rc2; + + result = false; + } + } +#if !NET_COMPACT_20 && TRACE_CONNECTION + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Failed to unset log callback: {0}", + e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + + AppendError(builder, "failed to unset log callback"); + rc = SQLiteErrorCode.Error; + + result = false; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////// + + if (!result && canThrow) + throw new SQLiteException(rc, builder.ToString()); + + return result; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This method attempts to free the cached database name used with the + /// method. + /// + /// + /// Non-zero if this method is being executed within a context where it can + /// throw an exception in the event of failure; otherwise, zero. + /// + /// + /// Non-zero if this method was successful; otherwise, zero. + /// + private bool FreeDbName( + bool canThrow + ) + { + try + { + if (dbName != IntPtr.Zero) + { + SQLiteMemory.Free(dbName); + dbName = IntPtr.Zero; + } + + return true; + } +#if !NET_COMPACT_20 && TRACE_CONNECTION + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Failed to free database name: {0}", + e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + + if (canThrow) + throw; + } + + return false; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Creates a new SQLite backup object based on the provided destination + /// database connection. The source database connection is the one + /// associated with this object. The source and destination database + /// connections cannot be the same. + /// + /// The destination database connection. + /// The destination database name. + /// The source database name. + /// The newly created backup object. + internal override SQLiteBackup InitializeBackup( + SQLiteConnection destCnn, + string destName, + string sourceName + ) + { + if (destCnn == null) + throw new ArgumentNullException("destCnn"); + + if (destName == null) + throw new ArgumentNullException("destName"); + + if (sourceName == null) + throw new ArgumentNullException("sourceName"); + + SQLite3 destSqlite3 = destCnn._sql as SQLite3; + + if (destSqlite3 == null) + throw new ArgumentException( + "Destination connection has no wrapper.", + "destCnn"); + + SQLiteConnectionHandle destHandle = destSqlite3._sql; + + if (destHandle == null) + throw new ArgumentException( + "Destination connection has an invalid handle.", + "destCnn"); + + SQLiteConnectionHandle sourceHandle = _sql; + + if (sourceHandle == null) + throw new InvalidOperationException( + "Source connection has an invalid handle."); + + byte[] zDestName = ToUTF8(destName); + byte[] zSourceName = ToUTF8(sourceName); + + SQLiteBackupHandle backupHandle = null; + + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { + IntPtr backup = UnsafeNativeMethods.sqlite3_backup_init( + destHandle, zDestName, sourceHandle, zSourceName); + + if (backup == IntPtr.Zero) + { + SQLiteErrorCode resultCode = ResultCode(); + + if (resultCode != SQLiteErrorCode.Ok) + throw new SQLiteException(resultCode, GetLastError()); + else + throw new SQLiteException("failed to initialize backup"); + } + + backupHandle = new SQLiteBackupHandle(destHandle, backup); + } + + SQLiteConnection.OnChanged(null, new ConnectionEventArgs( + SQLiteConnectionEventType.NewCriticalHandle, null, + null, null, null, backupHandle, null, new object[] { + typeof(SQLite3), destCnn, destName, sourceName })); + + return new SQLiteBackup( + this, backupHandle, destHandle, zDestName, sourceHandle, + zSourceName); + } + + /// + /// Copies up to N pages from the source database to the destination + /// database associated with the specified backup object. + /// + /// The backup object to use. + /// + /// The number of pages to copy, negative to copy all remaining pages. + /// + /// + /// Set to true if the operation needs to be retried due to database + /// locking issues; otherwise, set to false. + /// + /// + /// True if there are more pages to be copied, false otherwise. + /// + internal override bool StepBackup( + SQLiteBackup backup, + int nPage, + ref bool retry + ) + { + retry = false; + + if (backup == null) + throw new ArgumentNullException("backup"); + + SQLiteBackupHandle handle = backup._sqlite_backup; + + if (handle == null) + throw new InvalidOperationException( + "Backup object has an invalid handle."); + + IntPtr handlePtr = handle; + + if (handlePtr == IntPtr.Zero) + throw new InvalidOperationException( + "Backup object has an invalid handle pointer."); + + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_backup_step(handlePtr, nPage); + backup._stepResult = n; /* NOTE: Save for use by FinishBackup. */ + + if (n == SQLiteErrorCode.Ok) + { + return true; + } + else if (n == SQLiteErrorCode.Busy) + { + retry = true; + return true; + } + else if (n == SQLiteErrorCode.Locked) + { + retry = true; + return true; + } + else if (n == SQLiteErrorCode.Done) + { + return false; + } + else + { + throw new SQLiteException(n, GetLastError()); + } + } + + /// + /// Returns the number of pages remaining to be copied from the source + /// database to the destination database associated with the specified + /// backup object. + /// + /// The backup object to check. + /// The number of pages remaining to be copied. + internal override int RemainingBackup( + SQLiteBackup backup + ) + { + if (backup == null) + throw new ArgumentNullException("backup"); + + SQLiteBackupHandle handle = backup._sqlite_backup; + + if (handle == null) + throw new InvalidOperationException( + "Backup object has an invalid handle."); + + IntPtr handlePtr = handle; + + if (handlePtr == IntPtr.Zero) + throw new InvalidOperationException( + "Backup object has an invalid handle pointer."); + + return UnsafeNativeMethods.sqlite3_backup_remaining(handlePtr); + } + + /// + /// Returns the total number of pages in the source database associated + /// with the specified backup object. + /// + /// The backup object to check. + /// The total number of pages in the source database. + internal override int PageCountBackup( + SQLiteBackup backup + ) + { + if (backup == null) + throw new ArgumentNullException("backup"); + + SQLiteBackupHandle handle = backup._sqlite_backup; + + if (handle == null) + throw new InvalidOperationException( + "Backup object has an invalid handle."); + + IntPtr handlePtr = handle; + + if (handlePtr == IntPtr.Zero) + throw new InvalidOperationException( + "Backup object has an invalid handle pointer."); + + return UnsafeNativeMethods.sqlite3_backup_pagecount(handlePtr); + } + + /// + /// Destroys the backup object, rolling back any backup that may be in + /// progess. + /// + /// The backup object to destroy. + internal override void FinishBackup( + SQLiteBackup backup + ) + { + if (backup == null) + throw new ArgumentNullException("backup"); + + SQLiteBackupHandle handle = backup._sqlite_backup; + + if (handle == null) + throw new InvalidOperationException( + "Backup object has an invalid handle."); + + IntPtr handlePtr = handle; + + if (handlePtr == IntPtr.Zero) + throw new InvalidOperationException( + "Backup object has an invalid handle pointer."); + +#if !SQLITE_STANDARD + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_backup_finish_interop(handlePtr); +#else + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_backup_finish(handlePtr); +#endif + handle.SetHandleAsInvalid(); + +#if COUNT_HANDLE + if ((n == SQLiteErrorCode.Ok) || (n == backup._stepResult)) handle.WasReleasedOk(); +#endif + + if ((n != SQLiteErrorCode.Ok) && (n != backup._stepResult)) + throw new SQLiteException(n, GetLastError()); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Determines if the SQLite core library has been initialized for the + /// current process. + /// + /// + /// A boolean indicating whether or not the SQLite core library has been + /// initialized for the current process. + /// + internal override bool IsInitialized() + { + return StaticIsInitialized(); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Determines if the SQLite core library has been initialized for the + /// current process. + /// + /// + /// A boolean indicating whether or not the SQLite core library has been + /// initialized for the current process. + /// + internal static bool StaticIsInitialized() + { + // + // BUGFIX: Prevent races with other threads for this entire block, due + // to the try/finally semantics. See ticket [72905c9a77]. + // + lock (syncRoot) + { + // + // NOTE: Save the state of the logging class and then restore it + // after we are done to avoid logging too many false errors. + // + bool savedEnabled = SQLiteLog.Enabled; + SQLiteLog.Enabled = false; + + try + { + // + // NOTE: This method [ab]uses the fact that SQLite will always + // return SQLITE_ERROR for any unknown configuration option + // *unless* the SQLite library has already been initialized. + // In that case it will always return SQLITE_MISUSE. + // + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_config_none( + SQLiteConfigOpsEnum.SQLITE_CONFIG_NONE); + + return (rc == SQLiteErrorCode.Misuse); + } + finally + { + SQLiteLog.Enabled = savedEnabled; + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + +#if USE_INTEROP_DLL && INTEROP_LOG + internal static SQLiteErrorCode ConfigureLogForInterop( + string className + ) + { + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_config_log_interop(); + + if (rc == SQLiteErrorCode.Ok) + { + UnsafeNativeMethods.sqlite3_log(rc, SQLiteConvert.ToUTF8( + HelperMethods.StringFormat(CultureInfo.InvariantCulture, + "logging initialized via \"{0}\".", className))); + } + else if (rc == SQLiteErrorCode.Done) + { + rc = SQLiteErrorCode.Ok; + } + + return rc; + } +#endif + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Helper function to retrieve a column of data from an active statement. + /// + /// The statement being step()'d through + /// The flags associated with the connection. + /// The column index to retrieve + /// The type of data contained in the column. If Uninitialized, this function will retrieve the datatype information. + /// Returns the data in the column + internal override object GetValue(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, SQLiteType typ) + { + TypeAffinity aff = typ.Affinity; + if (aff == TypeAffinity.Null) return DBNull.Value; + Type t = null; + + if (typ.Type != DbType.Object) + { + t = SQLiteConvert.SQLiteTypeToType(typ); + aff = TypeToAffinity(t, flags); + } + + if (HelperMethods.HasFlags(flags, SQLiteConnectionFlags.GetAllAsText)) + return GetText(stmt, index); + + switch (aff) + { + case TypeAffinity.Blob: + if (typ.Type == DbType.Guid && typ.Affinity == TypeAffinity.Text) + return new Guid(GetText(stmt, index)); + + int n = (int)GetBytes(stmt, index, 0, null, 0, 0); + byte[] b = new byte[n]; + GetBytes(stmt, index, 0, b, 0, n); + + if (typ.Type == DbType.Guid && n == 16) + return new Guid(b); + + return b; + case TypeAffinity.DateTime: + return GetDateTime(stmt, index); + case TypeAffinity.Double: + if (t == null) return GetDouble(stmt, index); + return Convert.ChangeType(GetDouble(stmt, index), t, + HelperMethods.HasFlags(flags, SQLiteConnectionFlags.GetInvariantDouble) ? + CultureInfo.InvariantCulture : CultureInfo.CurrentCulture); + case TypeAffinity.Int64: + if (t == null) return GetInt64(stmt, index); + if (t == typeof(Boolean)) return GetBoolean(stmt, index); + if (t == typeof(SByte)) return GetSByte(stmt, index); + if (t == typeof(Byte)) return GetByte(stmt, index); + if (t == typeof(Int16)) return GetInt16(stmt, index); + if (t == typeof(UInt16)) return GetUInt16(stmt, index); + if (t == typeof(Int32)) return GetInt32(stmt, index); + if (t == typeof(UInt32)) return GetUInt32(stmt, index); + if (t == typeof(Int64)) return GetInt64(stmt, index); + if (t == typeof(UInt64)) return GetUInt64(stmt, index); + return Convert.ChangeType(GetInt64(stmt, index), t, + HelperMethods.HasFlags(flags, SQLiteConnectionFlags.GetInvariantInt64) ? + CultureInfo.InvariantCulture : CultureInfo.CurrentCulture); + default: + return GetText(stmt, index); + } + } + + internal override int GetCursorForTable(SQLiteStatement stmt, int db, int rootPage) + { +#if !SQLITE_STANDARD + return UnsafeNativeMethods.sqlite3_table_cursor_interop(stmt._sqlite_stmt, db, rootPage); +#else + return -1; +#endif + } + + internal override long GetRowIdForCursor(SQLiteStatement stmt, int cursor) + { +#if !SQLITE_STANDARD + long rowid = 0; + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_cursor_rowid_interop(stmt._sqlite_stmt, cursor, ref rowid); + if (rc == SQLiteErrorCode.Ok) return rowid; + + return 0; +#else + return 0; +#endif + } + + internal override void GetIndexColumnExtendedInfo(string database, string index, string column, ref int sortMode, ref int onError, ref string collationSequence) + { +#if !SQLITE_STANDARD + IntPtr coll = IntPtr.Zero; + int colllen = 0; + SQLiteErrorCode rc; + + rc = UnsafeNativeMethods.sqlite3_index_column_info_interop(_sql, ToUTF8(database), ToUTF8(index), ToUTF8(column), ref sortMode, ref onError, ref coll, ref colllen); + if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, null); + + collationSequence = UTF8ToString(coll, colllen); +#else + sortMode = 0; + onError = 2; + collationSequence = "BINARY"; +#endif + } + + internal override SQLiteErrorCode FileControl(string zDbName, int op, IntPtr pArg) + { + return UnsafeNativeMethods.sqlite3_file_control(_sql, (zDbName != null) ? ToUTF8(zDbName) : null, op, pArg); + } + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLite3_UTF16.cs b/Native.Csharp.Tool/SQLite/SQLite3_UTF16.cs new file mode 100644 index 00000000..67441366 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLite3_UTF16.cs @@ -0,0 +1,384 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Collections.Generic; + +#if !NET_COMPACT_20 && TRACE_CONNECTION + using System.Diagnostics; +#endif + + using System.Globalization; + using System.IO; + using System.Runtime.InteropServices; + + + /// + /// Alternate SQLite3 object, overriding many text behaviors to support UTF-16 (Unicode) + /// + internal sealed class SQLite3_UTF16 : SQLite3 + { + /// + /// Constructs the object used to interact with the SQLite core library + /// using the UTF-8 text encoding. + /// + /// + /// The DateTime format to be used when converting string values to a + /// DateTime and binding DateTime parameters. + /// + /// + /// The to be used when creating DateTime + /// values. + /// + /// + /// The format string to be used when parsing and formatting DateTime + /// values. + /// + /// + /// The native handle to be associated with the database connection. + /// + /// + /// The fully qualified file name associated with . + /// + /// + /// Non-zero if the newly created object instance will need to dispose + /// of when it is no longer needed. + /// + internal SQLite3_UTF16( + SQLiteDateFormats fmt, + DateTimeKind kind, + string fmtString, + IntPtr db, + string fileName, + bool ownHandle + ) + : base(fmt, kind, fmtString, db, fileName, ownHandle) + { + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLite3_UTF16).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Overrides SQLiteConvert.ToString() to marshal UTF-16 strings instead of UTF-8 + /// + /// A pointer to a UTF-16 string + /// The length (IN BYTES) of the string + /// A .NET string + public override string ToString(IntPtr b, int nbytelen) + { + CheckDisposed(); + return UTF16ToString(b, nbytelen); + } + + public static string UTF16ToString(IntPtr b, int nbytelen) + { + if (nbytelen == 0 || b == IntPtr.Zero) return String.Empty; + + if (nbytelen == -1) + return Marshal.PtrToStringUni(b); + else + return Marshal.PtrToStringUni(b, nbytelen / 2); + } + + internal override void Open(string strFilename, string vfsName, SQLiteConnectionFlags connectionFlags, SQLiteOpenFlagsEnum openFlags, int maxPoolSize, bool usePool) + { + // + // NOTE: If the database connection is currently open, attempt to + // close it now. This must be done because the file name or + // other parameters that may impact the underlying database + // connection may have changed. + // + if (_sql != null) Close(false); + + // + // NOTE: If the connection was not closed successfully, throw an + // exception now. + // + if (_sql != null) + throw new SQLiteException("connection handle is still active"); + + _usePool = usePool; + _fileName = strFilename; + _flags = connectionFlags; + + if (usePool) + { + _sql = SQLiteConnectionPool.Remove(strFilename, maxPoolSize, out _poolVersion); + + SQLiteConnection.OnChanged(null, new ConnectionEventArgs( + SQLiteConnectionEventType.OpenedFromPool, null, null, + null, null, _sql, strFilename, new object[] { + typeof(SQLite3_UTF16), strFilename, vfsName, + connectionFlags, openFlags, maxPoolSize, usePool, + _poolVersion })); + +#if !NET_COMPACT_20 && TRACE_CONNECTION + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Open16 (Pool): {0}", + HandleToString())); +#endif + } + + if (_sql == null) + { + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { + IntPtr db = IntPtr.Zero; + SQLiteErrorCode n; + + int extFuncs = HelperMethods.HasFlags(connectionFlags, SQLiteConnectionFlags.NoExtensionFunctions) ? 0 : 1; + +#if !SQLITE_STANDARD + if ((vfsName != null) || (extFuncs != 0)) + { + n = UnsafeNativeMethods.sqlite3_open16_interop(ToUTF8(strFilename), ToUTF8(vfsName), openFlags, extFuncs, ref db); + } + else +#endif + { + // + // NOTE: This flag check is designed to enforce the constraint that opening + // a database file that does not already exist requires specifying the + // "Create" flag, even when a native API is used that does not accept + // a flags parameter. + // + if (((openFlags & SQLiteOpenFlagsEnum.Create) != SQLiteOpenFlagsEnum.Create) && !File.Exists(strFilename)) + throw new SQLiteException(SQLiteErrorCode.CantOpen, strFilename); + + if (vfsName != null) + { + throw new SQLiteException(SQLiteErrorCode.CantOpen, HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "cannot open using UTF-16 and VFS \"{0}\": need interop assembly", vfsName)); + } + + n = UnsafeNativeMethods.sqlite3_open16(strFilename, ref db); + } + +#if !NET_COMPACT_20 && TRACE_CONNECTION + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Open16: {0}", db)); +#endif + + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, null); + _sql = new SQLiteConnectionHandle(db, true); + } + lock (_sql) { /* HACK: Force the SyncBlock to be "created" now. */ } + + SQLiteConnection.OnChanged(null, new ConnectionEventArgs( + SQLiteConnectionEventType.NewCriticalHandle, null, + null, null, null, _sql, strFilename, new object[] { + typeof(SQLite3_UTF16), strFilename, vfsName, + connectionFlags, openFlags, maxPoolSize, usePool })); + } + + // Bind functions to this connection. If any previous functions of the same name + // were already bound, then the new bindings replace the old. + if (!HelperMethods.HasFlags(connectionFlags, SQLiteConnectionFlags.NoBindFunctions)) + { + if (_functions == null) + _functions = new Dictionary(); + + foreach (KeyValuePair pair + in SQLiteFunction.BindFunctions(this, connectionFlags)) + { + _functions[pair.Key] = pair.Value; + } + } + + SetTimeout(0); + GC.KeepAlive(_sql); + } + + internal override void Bind_DateTime(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, DateTime dt) + { + switch (_datetimeFormat) + { + case SQLiteDateFormats.Ticks: + case SQLiteDateFormats.JulianDay: + case SQLiteDateFormats.UnixEpoch: + { + base.Bind_DateTime(stmt, flags, index, dt); + break; + } + default: + { +#if !PLATFORM_COMPACTFRAMEWORK + if (HelperMethods.LogBind(flags)) + { + SQLiteStatementHandle handle = + (stmt != null) ? stmt._sqlite_stmt : null; + + LogBind(handle, index, dt); + } +#endif + + Bind_Text(stmt, flags, index, ToString(dt)); + break; + } + } + } + + internal override void Bind_Text(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, string value) + { + SQLiteStatementHandle handle = stmt._sqlite_stmt; + +#if !PLATFORM_COMPACTFRAMEWORK + if (HelperMethods.LogBind(flags)) + { + LogBind(handle, index, value); + } +#endif + + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_bind_text16(handle, index, value, value.Length * 2, (IntPtr)(-1)); + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError()); + } + + internal override DateTime GetDateTime(SQLiteStatement stmt, int index) + { + if (_datetimeFormat == SQLiteDateFormats.Ticks) + return TicksToDateTime(GetInt64(stmt, index), _datetimeKind); + else if (_datetimeFormat == SQLiteDateFormats.JulianDay) + return ToDateTime(GetDouble(stmt, index), _datetimeKind); + else if (_datetimeFormat == SQLiteDateFormats.UnixEpoch) + return UnixEpochToDateTime(GetInt64(stmt, index), _datetimeKind); + + return ToDateTime(GetText(stmt, index)); + } + + internal override string ColumnName(SQLiteStatement stmt, int index) + { +#if !SQLITE_STANDARD + int len = 0; + IntPtr p = UnsafeNativeMethods.sqlite3_column_name16_interop(stmt._sqlite_stmt, index, ref len); +#else + IntPtr p = UnsafeNativeMethods.sqlite3_column_name16(stmt._sqlite_stmt, index); +#endif + if (p == IntPtr.Zero) + throw new SQLiteException(SQLiteErrorCode.NoMem, GetLastError()); +#if !SQLITE_STANDARD + return UTF16ToString(p, len); +#else + return UTF16ToString(p, -1); +#endif + } + + internal override string GetText(SQLiteStatement stmt, int index) + { +#if !SQLITE_STANDARD + int len = 0; + return UTF16ToString(UnsafeNativeMethods.sqlite3_column_text16_interop(stmt._sqlite_stmt, index, ref len), len); +#else + return UTF16ToString(UnsafeNativeMethods.sqlite3_column_text16(stmt._sqlite_stmt, index), + UnsafeNativeMethods.sqlite3_column_bytes16(stmt._sqlite_stmt, index)); +#endif + } + + internal override string ColumnOriginalName(SQLiteStatement stmt, int index) + { +#if !SQLITE_STANDARD + int len = 0; + return UTF16ToString(UnsafeNativeMethods.sqlite3_column_origin_name16_interop(stmt._sqlite_stmt, index, ref len), len); +#else + return UTF16ToString(UnsafeNativeMethods.sqlite3_column_origin_name16(stmt._sqlite_stmt, index), -1); +#endif + } + + internal override string ColumnDatabaseName(SQLiteStatement stmt, int index) + { +#if !SQLITE_STANDARD + int len = 0; + return UTF16ToString(UnsafeNativeMethods.sqlite3_column_database_name16_interop(stmt._sqlite_stmt, index, ref len), len); +#else + return UTF16ToString(UnsafeNativeMethods.sqlite3_column_database_name16(stmt._sqlite_stmt, index), -1); +#endif + } + + internal override string ColumnTableName(SQLiteStatement stmt, int index) + { +#if !SQLITE_STANDARD + int len = 0; + return UTF16ToString(UnsafeNativeMethods.sqlite3_column_table_name16_interop(stmt._sqlite_stmt, index, ref len), len); +#else + return UTF16ToString(UnsafeNativeMethods.sqlite3_column_table_name16(stmt._sqlite_stmt, index), -1); +#endif + } + + internal override string GetParamValueText(IntPtr ptr) + { +#if !SQLITE_STANDARD + int len = 0; + return UTF16ToString(UnsafeNativeMethods.sqlite3_value_text16_interop(ptr, ref len), len); +#else + return UTF16ToString(UnsafeNativeMethods.sqlite3_value_text16(ptr), + UnsafeNativeMethods.sqlite3_value_bytes16(ptr)); +#endif + } + + internal override void ReturnError(IntPtr context, string value) + { + UnsafeNativeMethods.sqlite3_result_error16(context, value, value.Length * 2); + } + + internal override void ReturnText(IntPtr context, string value) + { + UnsafeNativeMethods.sqlite3_result_text16(context, value, value.Length * 2, (IntPtr)(-1)); + } + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteBackup.cs b/Native.Csharp.Tool/SQLite/SQLiteBackup.cs new file mode 100644 index 00000000..ab6d13b5 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteBackup.cs @@ -0,0 +1,149 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Joe Mistachkin (joe@mistachkin.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + + /// + /// Represents a single SQL backup in SQLite. + /// + internal sealed class SQLiteBackup : IDisposable + { + /// + /// The underlying SQLite object this backup is bound to. + /// + internal SQLiteBase _sql; + + /// + /// The actual backup handle. + /// + internal SQLiteBackupHandle _sqlite_backup; + + /// + /// The destination database for the backup. + /// + internal IntPtr _destDb; + + /// + /// The destination database name for the backup. + /// + internal byte[] _zDestName; + + /// + /// The source database for the backup. + /// + internal IntPtr _sourceDb; + + /// + /// The source database name for the backup. + /// + internal byte[] _zSourceName; + + /// + /// The last result from the StepBackup method of the SQLite3 class. + /// This is used to determine if the call to the FinishBackup method of + /// the SQLite3 class should throw an exception when it receives a non-Ok + /// return code from the core SQLite library. + /// + internal SQLiteErrorCode _stepResult; + + /// + /// Initializes the backup. + /// + /// The base SQLite object. + /// The backup handle. + /// The destination database for the backup. + /// The destination database name for the backup. + /// The source database for the backup. + /// The source database name for the backup. + internal SQLiteBackup( + SQLiteBase sqlbase, + SQLiteBackupHandle backup, + IntPtr destDb, + byte[] zDestName, + IntPtr sourceDb, + byte[] zSourceName + ) + { + _sql = sqlbase; + _sqlite_backup = backup; + _destDb = destDb; + _zDestName = zDestName; + _sourceDb = sourceDb; + _zSourceName = zSourceName; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes and finalizes the backup. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteBackup).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (_sqlite_backup != null) + { + _sqlite_backup.Dispose(); + _sqlite_backup = null; + } + + _zSourceName = null; + _sourceDb = IntPtr.Zero; + _zDestName = null; + _destDb = IntPtr.Zero; + _sql = null; + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Destructor + ~SQLiteBackup() + { + Dispose(false); + } + #endregion + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteBase.cs b/Native.Csharp.Tool/SQLite/SQLiteBase.cs new file mode 100644 index 00000000..e10e64ba --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteBase.cs @@ -0,0 +1,1656 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Collections.Generic; + +#if !PLATFORM_COMPACTFRAMEWORK + using System.Runtime.InteropServices; +#endif + + /// + /// This internal class provides the foundation of SQLite support. It defines all the abstract members needed to implement + /// a SQLite data provider, and inherits from SQLiteConvert which allows for simple translations of string to and from SQLite. + /// + internal abstract class SQLiteBase : SQLiteConvert, IDisposable + { + #region Private Constants + /// + /// The error code used for logging exceptions caught in user-provided + /// code. + /// + internal const int COR_E_EXCEPTION = unchecked((int)0x80131500); + #endregion + + ///////////////////////////////////////////////////////////////////////// + + internal SQLiteBase(SQLiteDateFormats fmt, DateTimeKind kind, string fmtString) + : base(fmt, kind, fmtString) { } + + /// + /// Returns a string representing the active version of SQLite + /// + internal abstract string Version { get; } + /// + /// Returns an integer representing the active version of SQLite + /// + internal abstract int VersionNumber { get; } + /// + /// Returns non-zero if this connection to the database is read-only. + /// + internal abstract bool IsReadOnly(string name); + /// + /// Returns the rowid of the most recent successful INSERT into the database from this connection. + /// + internal abstract long LastInsertRowId { get; } + /// + /// Returns the number of changes the last executing insert/update caused. + /// + internal abstract int Changes { get; } + /// + /// Returns the amount of memory (in bytes) currently in use by the SQLite core library. This is not really a per-connection + /// value, it is global to the process. + /// + internal abstract long MemoryUsed { get; } + /// + /// Returns the maximum amount of memory (in bytes) used by the SQLite core library since the high-water mark was last reset. + /// This is not really a per-connection value, it is global to the process. + /// + internal abstract long MemoryHighwater { get; } + /// + /// Returns non-zero if the underlying native connection handle is owned by this instance. + /// + internal abstract bool OwnHandle { get; } + /// + /// Returns the logical list of functions associated with this connection. + /// + internal abstract IDictionary Functions { get; } + /// + /// Sets the status of the memory usage tracking subsystem in the SQLite core library. By default, this is enabled. + /// If this is disabled, memory usage tracking will not be performed. This is not really a per-connection value, it is + /// global to the process. + /// + /// Non-zero to enable memory usage tracking, zero otherwise. + /// A standard SQLite return code (i.e. zero for success and non-zero for failure). + internal abstract SQLiteErrorCode SetMemoryStatus(bool value); + /// + /// Attempts to free as much heap memory as possible for the database connection. + /// + /// A standard SQLite return code (i.e. zero for success and non-zero for failure). + internal abstract SQLiteErrorCode ReleaseMemory(); + /// + /// Shutdown the SQLite engine so that it can be restarted with different config options. + /// We depend on auto initialization to recover. + /// + internal abstract SQLiteErrorCode Shutdown(); + /// + /// Determines if the associated native connection handle is open. + /// + /// + /// Non-zero if a database connection is open. + /// + internal abstract bool IsOpen(); + /// + /// Returns the fully qualified path and file name for the currently open + /// database, if any. + /// + /// + /// The name of the attached database to query. + /// + /// + /// The fully qualified path and file name for the currently open database, + /// if any. + /// + internal abstract string GetFileName(string dbName); + /// + /// Opens a database. + /// + /// + /// Implementers should call SQLiteFunction.BindFunctions() and save the array after opening a connection + /// to bind all attributed user-defined functions and collating sequences to the new connection. + /// + /// The filename of the database to open. SQLite automatically creates it if it doesn't exist. + /// The name of the VFS to use -OR- null to use the default VFS. + /// The flags associated with the parent connection object + /// The open flags to use when creating the connection + /// The maximum size of the pool for the given filename + /// If true, the connection can be pulled from the connection pool + internal abstract void Open(string strFilename, string vfsName, SQLiteConnectionFlags connectionFlags, SQLiteOpenFlagsEnum openFlags, int maxPoolSize, bool usePool); + /// + /// Closes the currently-open database. + /// + /// + /// After the database has been closed implemeters should call SQLiteFunction.UnbindFunctions() to deallocate all interop allocated + /// memory associated with the user-defined functions and collating sequences tied to the closed connection. + /// + /// Non-zero if connection is being disposed, zero otherwise. + internal abstract void Close(bool disposing); + /// + /// Sets the busy timeout on the connection. SQLiteCommand will call this before executing any command. + /// + /// The number of milliseconds to wait before returning SQLITE_BUSY + internal abstract void SetTimeout(int nTimeoutMS); + /// + /// Returns the text of the last error issued by SQLite + /// + /// + internal abstract string GetLastError(); + + /// + /// Returns the text of the last error issued by SQLite -OR- the specified default error text if + /// none is available from the SQLite core library. + /// + /// + /// The error text to return in the event that one is not available from the SQLite core library. + /// + /// + /// The error text. + /// + internal abstract string GetLastError(string defValue); + + /// + /// When pooling is enabled, force this connection to be disposed rather than returned to the pool + /// + internal abstract void ClearPool(); + + /// + /// When pooling is enabled, returns the number of pool entries matching the current file name. + /// + /// The number of pool entries matching the current file name. + internal abstract int CountPool(); + + /// + /// Prepares a SQL statement for execution. + /// + /// The source connection preparing the command. Can be null for any caller except LINQ + /// The SQL command text to prepare + /// The previous statement in a multi-statement command, or null if no previous statement exists + /// The timeout to wait before aborting the prepare + /// The remainder of the statement that was not processed. Each call to prepare parses the + /// SQL up to to either the end of the text or to the first semi-colon delimiter. The remaining text is returned + /// here for a subsequent call to Prepare() until all the text has been processed. + /// Returns an initialized SQLiteStatement. + internal abstract SQLiteStatement Prepare(SQLiteConnection cnn, string strSql, SQLiteStatement previous, uint timeoutMS, ref string strRemain); + /// + /// Steps through a prepared statement. + /// + /// The SQLiteStatement to step through + /// True if a row was returned, False if not. + internal abstract bool Step(SQLiteStatement stmt); + /// + /// Returns non-zero if the specified statement is read-only in nature. + /// + /// The statement to check. + /// True if the outer query is read-only. + internal abstract bool IsReadOnly(SQLiteStatement stmt); + /// + /// Resets a prepared statement so it can be executed again. If the error returned is SQLITE_SCHEMA, + /// transparently attempt to rebuild the SQL statement and throw an error if that was not possible. + /// + /// The statement to reset + /// Returns -1 if the schema changed while resetting, 0 if the reset was sucessful or 6 (SQLITE_LOCKED) if the reset failed due to a lock + internal abstract SQLiteErrorCode Reset(SQLiteStatement stmt); + + /// + /// Attempts to interrupt the query currently executing on the associated + /// native database connection. + /// + internal abstract void Cancel(); + + /// + /// This function binds a user-defined function to the connection. + /// + /// + /// The object instance containing + /// the metadata for the function to be bound. + /// + /// + /// The object instance that implements the + /// function to be bound. + /// + /// + /// The flags associated with the parent connection object. + /// + internal abstract void BindFunction(SQLiteFunctionAttribute functionAttribute, SQLiteFunction function, SQLiteConnectionFlags flags); + + /// + /// This function unbinds a user-defined function from the connection. + /// + /// + /// The object instance containing + /// the metadata for the function to be unbound. + /// + /// + /// The flags associated with the parent connection object. + /// + /// Non-zero if the function was unbound. + internal abstract bool UnbindFunction(SQLiteFunctionAttribute functionAttribute, SQLiteConnectionFlags flags); + + internal abstract void Bind_Double(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, double value); + internal abstract void Bind_Int32(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, Int32 value); + internal abstract void Bind_UInt32(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, UInt32 value); + internal abstract void Bind_Int64(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, Int64 value); + internal abstract void Bind_UInt64(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, UInt64 value); + internal abstract void Bind_Boolean(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, bool value); + internal abstract void Bind_Text(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, string value); + internal abstract void Bind_Blob(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, byte[] blobData); + internal abstract void Bind_DateTime(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, DateTime dt); + internal abstract void Bind_Null(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index); + + internal abstract int Bind_ParamCount(SQLiteStatement stmt, SQLiteConnectionFlags flags); + internal abstract string Bind_ParamName(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index); + internal abstract int Bind_ParamIndex(SQLiteStatement stmt, SQLiteConnectionFlags flags, string paramName); + + internal abstract int ColumnCount(SQLiteStatement stmt); + internal abstract string ColumnName(SQLiteStatement stmt, int index); + internal abstract TypeAffinity ColumnAffinity(SQLiteStatement stmt, int index); + internal abstract string ColumnType(SQLiteStatement stmt, int index, ref TypeAffinity nAffinity); + internal abstract int ColumnIndex(SQLiteStatement stmt, string columnName); + internal abstract string ColumnOriginalName(SQLiteStatement stmt, int index); + internal abstract string ColumnDatabaseName(SQLiteStatement stmt, int index); + internal abstract string ColumnTableName(SQLiteStatement stmt, int index); + internal abstract bool DoesTableExist(string dataBase, string table); + internal abstract bool ColumnMetaData(string dataBase, string table, string column, bool canThrow, ref string dataType, ref string collateSequence, ref bool notNull, ref bool primaryKey, ref bool autoIncrement); + internal abstract void GetIndexColumnExtendedInfo(string database, string index, string column, ref int sortMode, ref int onError, ref string collationSequence); + + internal abstract object GetObject(SQLiteStatement stmt, int index); + internal abstract double GetDouble(SQLiteStatement stmt, int index); + internal abstract Boolean GetBoolean(SQLiteStatement stmt, int index); + internal abstract SByte GetSByte(SQLiteStatement stmt, int index); + internal abstract Byte GetByte(SQLiteStatement stmt, int index); + internal abstract Int16 GetInt16(SQLiteStatement stmt, int index); + internal abstract UInt16 GetUInt16(SQLiteStatement stmt, int index); + internal abstract Int32 GetInt32(SQLiteStatement stmt, int index); + internal abstract UInt32 GetUInt32(SQLiteStatement stmt, int index); + internal abstract Int64 GetInt64(SQLiteStatement stmt, int index); + internal abstract UInt64 GetUInt64(SQLiteStatement stmt, int index); + internal abstract string GetText(SQLiteStatement stmt, int index); + internal abstract long GetBytes(SQLiteStatement stmt, int index, int nDataoffset, byte[] bDest, int nStart, int nLength); + internal abstract char GetChar(SQLiteStatement stmt, int index); + internal abstract long GetChars(SQLiteStatement stmt, int index, int nDataoffset, char[] bDest, int nStart, int nLength); + internal abstract DateTime GetDateTime(SQLiteStatement stmt, int index); + internal abstract bool IsNull(SQLiteStatement stmt, int index); + + internal abstract SQLiteErrorCode CreateCollation(string strCollation, SQLiteCollation func, SQLiteCollation func16, bool @throw); + internal abstract SQLiteErrorCode CreateFunction(string strFunction, int nArgs, bool needCollSeq, SQLiteCallback func, SQLiteCallback funcstep, SQLiteFinalCallback funcfinal, bool @throw); + internal abstract CollationSequence GetCollationSequence(SQLiteFunction func, IntPtr context); + internal abstract int ContextCollateCompare(CollationEncodingEnum enc, IntPtr context, string s1, string s2); + internal abstract int ContextCollateCompare(CollationEncodingEnum enc, IntPtr context, char[] c1, char[] c2); + + internal abstract int AggregateCount(IntPtr context); + internal abstract IntPtr AggregateContext(IntPtr context); + + internal abstract long GetParamValueBytes(IntPtr ptr, int nDataOffset, byte[] bDest, int nStart, int nLength); + internal abstract double GetParamValueDouble(IntPtr ptr); + internal abstract int GetParamValueInt32(IntPtr ptr); + internal abstract Int64 GetParamValueInt64(IntPtr ptr); + internal abstract string GetParamValueText(IntPtr ptr); + internal abstract TypeAffinity GetParamValueType(IntPtr ptr); + + internal abstract void ReturnBlob(IntPtr context, byte[] value); + internal abstract void ReturnDouble(IntPtr context, double value); + internal abstract void ReturnError(IntPtr context, string value); + internal abstract void ReturnInt32(IntPtr context, Int32 value); + internal abstract void ReturnInt64(IntPtr context, Int64 value); + internal abstract void ReturnNull(IntPtr context); + internal abstract void ReturnText(IntPtr context, string value); + +#if INTEROP_VIRTUAL_TABLE + /// + /// Calls the native SQLite core library in order to create a disposable + /// module containing the implementation of a virtual table. + /// + /// + /// The module object to be used when creating the native disposable module. + /// + /// + /// The flags for the associated object instance. + /// + internal abstract void CreateModule(SQLiteModule module, SQLiteConnectionFlags flags); + + /// + /// Calls the native SQLite core library in order to cleanup the resources + /// associated with a module containing the implementation of a virtual table. + /// + /// + /// The module object previously passed to the + /// method. + /// + /// + /// The flags for the associated object instance. + /// + internal abstract void DisposeModule(SQLiteModule module, SQLiteConnectionFlags flags); + + /// + /// Calls the native SQLite core library in order to declare a virtual table + /// in response to a call into the + /// or virtual table methods. + /// + /// + /// The virtual table module that is to be responsible for the virtual table + /// being declared. + /// + /// + /// The string containing the SQL statement describing the virtual table to + /// be declared. + /// + /// + /// Upon success, the contents of this parameter are undefined. Upon failure, + /// it should contain an appropriate error message. + /// + /// + /// A standard SQLite return code. + /// + internal abstract SQLiteErrorCode DeclareVirtualTable(SQLiteModule module, string strSql, ref string error); + + /// + /// Calls the native SQLite core library in order to declare a virtual table + /// function in response to a call into the + /// or virtual table methods. + /// + /// + /// The virtual table module that is to be responsible for the virtual table + /// function being declared. + /// + /// + /// The number of arguments to the function being declared. + /// + /// + /// The name of the function being declared. + /// + /// + /// Upon success, the contents of this parameter are undefined. Upon failure, + /// it should contain an appropriate error message. + /// + /// + /// A standard SQLite return code. + /// + internal abstract SQLiteErrorCode DeclareVirtualFunction(SQLiteModule module, int argumentCount, string name, ref string error); +#endif + + /// + /// Returns the current and/or highwater values for the specified database status parameter. + /// + /// + /// The database status parameter to query. + /// + /// + /// Non-zero to reset the highwater value to the current value. + /// + /// + /// If applicable, receives the current value. + /// + /// + /// If applicable, receives the highwater value. + /// + /// + /// A standard SQLite return code. + /// + internal abstract SQLiteErrorCode GetStatusParameter(SQLiteStatusOpsEnum option, bool reset, ref int current, ref int highwater); + /// + /// Change a configuration option value for the database. + /// + /// + /// The database configuration option to change. + /// + /// + /// The new value for the specified configuration option. + /// + /// + /// A standard SQLite return code. + /// + internal abstract SQLiteErrorCode SetConfigurationOption(SQLiteConfigDbOpsEnum option, object value); + /// + /// Enables or disables extension loading by SQLite. + /// + /// + /// True to enable loading of extensions, false to disable. + /// + internal abstract void SetLoadExtension(bool bOnOff); + /// + /// Loads a SQLite extension library from the named file. + /// + /// + /// The name of the dynamic link library file containing the extension. + /// + /// + /// The name of the exported function used to initialize the extension. + /// If null, the default "sqlite3_extension_init" will be used. + /// + internal abstract void LoadExtension(string fileName, string procName); + /// + /// Enables or disabled extened result codes returned by SQLite + /// + /// true to enable extended result codes, false to disable. + /// + internal abstract void SetExtendedResultCodes(bool bOnOff); + /// + /// Returns the numeric result code for the most recent failed SQLite API call + /// associated with the database connection. + /// + /// Result code + internal abstract SQLiteErrorCode ResultCode(); + /// + /// Returns the extended numeric result code for the most recent failed SQLite API call + /// associated with the database connection. + /// + /// Extended result code + internal abstract SQLiteErrorCode ExtendedResultCode(); + + /// + /// Add a log message via the SQLite sqlite3_log interface. + /// + /// Error code to be logged with the message. + /// String to be logged. Unlike the SQLite sqlite3_log() + /// interface, this should be pre-formatted. Consider using the + /// String.Format() function. + /// + internal abstract void LogMessage(SQLiteErrorCode iErrCode, string zMessage); + +#if INTEROP_CODEC || INTEROP_INCLUDE_SEE + internal abstract void SetPassword(byte[] passwordBytes); + internal abstract void ChangePassword(byte[] newPasswordBytes); +#endif + + internal abstract void SetProgressHook(int nOps, SQLiteProgressCallback func); + internal abstract void SetAuthorizerHook(SQLiteAuthorizerCallback func); + internal abstract void SetUpdateHook(SQLiteUpdateCallback func); + internal abstract void SetCommitHook(SQLiteCommitCallback func); + internal abstract void SetTraceCallback(SQLiteTraceCallback func); + internal abstract void SetTraceCallback2(SQLiteTraceFlags mask, SQLiteTraceCallback2 func); + internal abstract void SetRollbackHook(SQLiteRollbackCallback func); + internal abstract SQLiteErrorCode SetLogCallback(SQLiteLogCallback func); + + /// + /// Checks if the SQLite core library has been initialized in the current process. + /// + /// + /// Non-zero if the SQLite core library has been initialized in the current process, + /// zero otherwise. + /// + internal abstract bool IsInitialized(); + + internal abstract int GetCursorForTable(SQLiteStatement stmt, int database, int rootPage); + internal abstract long GetRowIdForCursor(SQLiteStatement stmt, int cursor); + + internal abstract object GetValue(SQLiteStatement stmt, SQLiteConnectionFlags flags, int index, SQLiteType typ); + + /// + /// Returns non-zero if the given database connection is in autocommit mode. + /// Autocommit mode is on by default. Autocommit mode is disabled by a BEGIN + /// statement. Autocommit mode is re-enabled by a COMMIT or ROLLBACK. + /// + internal abstract bool AutoCommit + { + get; + } + + internal abstract SQLiteErrorCode FileControl(string zDbName, int op, IntPtr pArg); + + /// + /// Creates a new SQLite backup object based on the provided destination + /// database connection. The source database connection is the one + /// associated with this object. The source and destination database + /// connections cannot be the same. + /// + /// The destination database connection. + /// The destination database name. + /// The source database name. + /// The newly created backup object. + internal abstract SQLiteBackup InitializeBackup( + SQLiteConnection destCnn, string destName, + string sourceName); + + /// + /// Copies up to N pages from the source database to the destination + /// database associated with the specified backup object. + /// + /// The backup object to use. + /// + /// The number of pages to copy or negative to copy all remaining pages. + /// + /// + /// Set to true if the operation needs to be retried due to database + /// locking issues. + /// + /// + /// True if there are more pages to be copied, false otherwise. + /// + internal abstract bool StepBackup(SQLiteBackup backup, int nPage, ref bool retry); + + /// + /// Returns the number of pages remaining to be copied from the source + /// database to the destination database associated with the specified + /// backup object. + /// + /// The backup object to check. + /// The number of pages remaining to be copied. + internal abstract int RemainingBackup(SQLiteBackup backup); + + /// + /// Returns the total number of pages in the source database associated + /// with the specified backup object. + /// + /// The backup object to check. + /// The total number of pages in the source database. + internal abstract int PageCountBackup(SQLiteBackup backup); + + /// + /// Destroys the backup object, rolling back any backup that may be in + /// progess. + /// + /// The backup object to destroy. + internal abstract void FinishBackup(SQLiteBackup backup); + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteBase).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Destructor + ~SQLiteBase() + { + Dispose(false); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + // These statics are here for lack of a better place to put them. + // They exist here because they are called during the finalization of + // a SQLiteStatementHandle, SQLiteConnectionHandle, and SQLiteFunctionCookieHandle. + // Therefore these functions have to be static, and have to be low-level. + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private static string[] _errorMessages = { + /* SQLITE_OK */ "not an error", + /* SQLITE_ERROR */ "SQL logic error", + /* SQLITE_INTERNAL */ "internal logic error", + /* SQLITE_PERM */ "access permission denied", + /* SQLITE_ABORT */ "query aborted", + /* SQLITE_BUSY */ "database is locked", + /* SQLITE_LOCKED */ "database table is locked", + /* SQLITE_NOMEM */ "out of memory", + /* SQLITE_READONLY */ "attempt to write a readonly database", + /* SQLITE_INTERRUPT */ "interrupted", + /* SQLITE_IOERR */ "disk I/O error", + /* SQLITE_CORRUPT */ "database disk image is malformed", + /* SQLITE_NOTFOUND */ "unknown operation", + /* SQLITE_FULL */ "database or disk is full", + /* SQLITE_CANTOPEN */ "unable to open database file", + /* SQLITE_PROTOCOL */ "locking protocol", + /* SQLITE_EMPTY */ "table contains no data", + /* SQLITE_SCHEMA */ "database schema has changed", + /* SQLITE_TOOBIG */ "string or blob too big", + /* SQLITE_CONSTRAINT */ "constraint failed", + /* SQLITE_MISMATCH */ "datatype mismatch", + /* SQLITE_MISUSE */ "bad parameter or other API misuse", + /* SQLITE_NOLFS */ "large file support is disabled", + /* SQLITE_AUTH */ "authorization denied", + /* SQLITE_FORMAT */ "auxiliary database format error", + /* SQLITE_RANGE */ "column index out of range", + /* SQLITE_NOTADB */ "file is not a database", + /* SQLITE_NOTICE */ "notification message", + /* SQLITE_WARNING */ "warning message" + }; + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Returns the error message for the specified SQLite return code using + /// the internal static lookup table. + /// + /// The SQLite return code. + /// The error message or null if it cannot be found. + protected static string FallbackGetErrorString(SQLiteErrorCode rc) + { + switch (rc) + { + case SQLiteErrorCode.Abort_Rollback: + return "abort due to ROLLBACK"; + case SQLiteErrorCode.Row: + return "another row available"; + case SQLiteErrorCode.Done: + return "no more rows available"; + } + + if (_errorMessages == null) + return null; + + int index = (int)(rc & SQLiteErrorCode.NonExtendedMask); + + if ((index < 0) || (index >= _errorMessages.Length)) + index = (int)SQLiteErrorCode.Error; /* Make into generic error. */ + + return _errorMessages[index]; + } + + internal static string GetLastError(SQLiteConnectionHandle hdl, IntPtr db) + { + if ((hdl == null) || (db == IntPtr.Zero)) + return "null connection or database handle"; + + string result = null; + + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { +#if PLATFORM_COMPACTFRAMEWORK + lock (hdl.syncRoot) +#else + lock (hdl) +#endif + { + if (!hdl.IsInvalid && !hdl.IsClosed) + { +#if !SQLITE_STANDARD + int len = 0; + result = UTF8ToString(UnsafeNativeMethods.sqlite3_errmsg_interop(db, ref len), len); +#else + result = UTF8ToString(UnsafeNativeMethods.sqlite3_errmsg(db), -1); +#endif + } + else + { + result = "closed or invalid connection handle"; + } + } + } + GC.KeepAlive(hdl); + return result; + } + + internal static void FinishBackup(SQLiteConnectionHandle hdl, IntPtr backup) + { + if ((hdl == null) || (backup == IntPtr.Zero)) return; + + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { +#if PLATFORM_COMPACTFRAMEWORK + lock (hdl.syncRoot) +#else + lock (hdl) +#endif + { +#if !SQLITE_STANDARD + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_backup_finish_interop(backup); +#else + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_backup_finish(backup); +#endif + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, null); + } + } + } + + internal static void CloseBlob(SQLiteConnectionHandle hdl, IntPtr blob) + { + if ((hdl == null) || (blob == IntPtr.Zero)) return; + + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { +#if PLATFORM_COMPACTFRAMEWORK + lock (hdl.syncRoot) +#else + lock (hdl) +#endif + { +#if !SQLITE_STANDARD + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_blob_close_interop(blob); +#else + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_blob_close(blob); +#endif + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, null); + } + } + } + + internal static void FinalizeStatement(SQLiteConnectionHandle hdl, IntPtr stmt) + { + if ((hdl == null) || (stmt == IntPtr.Zero)) return; + + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { +#if PLATFORM_COMPACTFRAMEWORK + lock (hdl.syncRoot) +#else + lock (hdl) +#endif + { +#if !SQLITE_STANDARD + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_finalize_interop(stmt); +#else + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_finalize(stmt); +#endif + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, null); + } + } + } + + internal static void CloseConnection(SQLiteConnectionHandle hdl, IntPtr db) + { + if ((hdl == null) || (db == IntPtr.Zero)) return; + + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { +#if PLATFORM_COMPACTFRAMEWORK + lock (hdl.syncRoot) +#else + lock (hdl) +#endif + { +#if !SQLITE_STANDARD + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_close_interop(db); +#else + ResetConnection(hdl, db, false); + + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_close(db); +#endif + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError(hdl, db)); + } + } + } + +#if !INTEROP_LEGACY_CLOSE + internal static void CloseConnectionV2(SQLiteConnectionHandle hdl, IntPtr db) + { + if ((hdl == null) || (db == IntPtr.Zero)) return; + + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { +#if PLATFORM_COMPACTFRAMEWORK + lock (hdl.syncRoot) +#else + lock (hdl) +#endif + { +#if !SQLITE_STANDARD + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_close_interop(db); +#else + ResetConnection(hdl, db, false); + + SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_close_v2(db); +#endif + if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError(hdl, db)); + } + } + } +#endif + + internal static bool ResetConnection(SQLiteConnectionHandle hdl, IntPtr db, bool canThrow) + { + if ((hdl == null) || (db == IntPtr.Zero)) return false; + + bool result = false; + + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { +#if PLATFORM_COMPACTFRAMEWORK + lock (hdl.syncRoot) +#else + lock (hdl) +#endif + { + if (canThrow && hdl.IsInvalid) + throw new InvalidOperationException("The connection handle is invalid."); + + if (canThrow && hdl.IsClosed) + throw new InvalidOperationException("The connection handle is closed."); + + if (!hdl.IsInvalid && !hdl.IsClosed) + { + IntPtr stmt = IntPtr.Zero; + SQLiteErrorCode n; + + do + { + stmt = UnsafeNativeMethods.sqlite3_next_stmt(db, stmt); + if (stmt != IntPtr.Zero) + { +#if !SQLITE_STANDARD + n = UnsafeNativeMethods.sqlite3_reset_interop(stmt); +#else + n = UnsafeNativeMethods.sqlite3_reset(stmt); +#endif + } + } while (stmt != IntPtr.Zero); + + // + // NOTE: Is a transaction NOT pending on the connection? + // + if (IsAutocommit(hdl, db)) + { + result = true; + } + else + { + n = UnsafeNativeMethods.sqlite3_exec( + db, ToUTF8("ROLLBACK"), IntPtr.Zero, IntPtr.Zero, + ref stmt); + + if (n == SQLiteErrorCode.Ok) + { + result = true; + } + else if (canThrow) + { + throw new SQLiteException(n, GetLastError(hdl, db)); + } + } + } + } + } + GC.KeepAlive(hdl); + return result; + } + + internal static bool IsAutocommit(SQLiteConnectionHandle hdl, IntPtr db) + { + if ((hdl == null) || (db == IntPtr.Zero)) return false; + + bool result = false; + + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { +#if PLATFORM_COMPACTFRAMEWORK + lock (hdl.syncRoot) +#else + lock (hdl) +#endif + { + if (!hdl.IsInvalid && !hdl.IsClosed) + result = (UnsafeNativeMethods.sqlite3_get_autocommit(db) == 1); + } + } + GC.KeepAlive(hdl); /* NOTE: Unreachable code. */ + return result; + } + } + + /// + /// + /// + public interface ISQLiteSchemaExtensions + { + /// + /// Creates temporary tables on the connection so schema information can be queried. + /// + /// + /// The connection upon which to build the schema tables. + /// + void BuildTempSchema(SQLiteConnection connection); + } + + [Flags] + internal enum SQLiteOpenFlagsEnum + { + None = 0, + ReadOnly = 0x1, + ReadWrite = 0x2, + Create = 0x4, + Uri = 0x40, + Memory = 0x80, + Default = ReadWrite | Create, + } + + /// + /// The extra behavioral flags that can be applied to a connection. + /// + [Flags()] + public enum SQLiteConnectionFlags : long + { + /// + /// No extra flags. + /// + None = 0x0, + + /// + /// Enable logging of all SQL statements to be prepared. + /// + LogPrepare = 0x1, + + /// + /// Enable logging of all bound parameter types and raw values. + /// + LogPreBind = 0x2, + + /// + /// Enable logging of all bound parameter strongly typed values. + /// + LogBind = 0x4, + + /// + /// Enable logging of all exceptions caught from user-provided + /// managed code called from native code via delegates. + /// + LogCallbackException = 0x8, + + /// + /// Enable logging of backup API errors. + /// + LogBackup = 0x10, + + /// + /// Skip adding the extension functions provided by the native + /// interop assembly. + /// + NoExtensionFunctions = 0x20, + + /// + /// When binding parameter values with the + /// type, use the interop method that accepts an + /// value. + /// + BindUInt32AsInt64 = 0x40, + + /// + /// When binding parameter values, always bind them as though they were + /// plain text (i.e. no numeric, date/time, or other conversions should + /// be attempted). + /// + BindAllAsText = 0x80, + + /// + /// When returning column values, always return them as though they were + /// plain text (i.e. no numeric, date/time, or other conversions should + /// be attempted). + /// + GetAllAsText = 0x100, + + /// + /// Prevent this object instance from + /// loading extensions. + /// + NoLoadExtension = 0x200, + +#if INTEROP_VIRTUAL_TABLE + /// + /// Prevent this object instance from + /// creating virtual table modules. + /// + NoCreateModule = 0x400, +#endif + + /// + /// Skip binding any functions provided by other managed assemblies when + /// opening the connection. + /// + NoBindFunctions = 0x800, + +#if INTEROP_VIRTUAL_TABLE + /// + /// Skip setting the logging related properties of the + /// object instance that was passed to + /// the method. + /// + NoLogModule = 0x1000, + + /// + /// Enable logging of all virtual table module errors seen by the + /// method. + /// + LogModuleError = 0x2000, + + /// + /// Enable logging of certain virtual table module exceptions that cannot + /// be easily discovered via other means. + /// + LogModuleException = 0x4000, +#endif + + /// + /// Enable tracing of potentially important [non-fatal] error conditions + /// that cannot be easily reported through other means. + /// + TraceWarning = 0x8000, + + /// + /// When binding parameter values, always use the invariant culture when + /// converting their values from strings. + /// + ConvertInvariantText = 0x10000, + + /// + /// When binding parameter values, always use the invariant culture when + /// converting their values to strings. + /// + BindInvariantText = 0x20000, + + /// + /// Disable using the connection pool by default. If the "Pooling" + /// connection string property is specified, its value will override + /// this flag. The precise outcome of combining this flag with the + /// flag is unspecified; however, + /// one of the flags will be in effect. + /// + NoConnectionPool = 0x40000, + + /// + /// Enable using the connection pool by default. If the "Pooling" + /// connection string property is specified, its value will override + /// this flag. The precise outcome of combining this flag with the + /// flag is unspecified; however, + /// one of the flags will be in effect. + /// + UseConnectionPool = 0x80000, + + /// + /// Enable using per-connection mappings between type names and + /// values. Also see the + /// , + /// , and + /// methods. These + /// per-connection mappings, when present, override the corresponding + /// global mappings. + /// + UseConnectionTypes = 0x100000, + + /// + /// Disable using global mappings between type names and + /// values. This may be useful in some very narrow + /// cases; however, if there are no per-connection type mappings, the + /// fallback defaults will be used for both type names and their + /// associated values. Therefore, use of this flag + /// is not recommended. + /// + NoGlobalTypes = 0x200000, + + /// + /// When the property is used, it + /// should return non-zero if there were ever any rows in the associated + /// result sets. + /// + StickyHasRows = 0x400000, + + /// + /// Enable "strict" transaction enlistment semantics. Setting this flag + /// will cause an exception to be thrown if an attempt is made to enlist + /// in a transaction with an unavailable or unsupported isolation level. + /// In the future, more extensive checks may be enabled by this flag as + /// well. + /// + StrictEnlistment = 0x800000, + + /// + /// Enable mapping of unsupported transaction isolation levels to the + /// closest supported transaction isolation level. + /// + MapIsolationLevels = 0x1000000, + + /// + /// When returning column values, attempt to detect the affinity of + /// textual values by checking if they fully conform to those of the + /// , + /// , + /// , + /// or types. + /// + DetectTextAffinity = 0x2000000, + + /// + /// When returning column values, attempt to detect the type of + /// string values by checking if they fully conform to those of + /// the , + /// , + /// , + /// or types. + /// + DetectStringType = 0x4000000, + + /// + /// Skip querying runtime configuration settings for use by the + /// class, including the default + /// value and default database type name. + /// NOTE: If the + /// and/or + /// properties are not set explicitly nor set via their connection + /// string properties and repeated calls to determine these runtime + /// configuration settings are seen to be a problem, this flag + /// should be set. + /// + NoConvertSettings = 0x8000000, + + /// + /// When binding parameter values with the + /// type, take their into account as + /// well as that of the associated . + /// + BindDateTimeWithKind = 0x10000000, + + /// + /// If an exception is caught when raising the + /// event, the transaction + /// should be rolled back. If this is not specified, the transaction + /// will continue the commit process instead. + /// + RollbackOnException = 0x20000000, + + /// + /// If an exception is caught when raising the + /// event, the action should + /// should be denied. If this is not specified, the action will be + /// allowed instead. + /// + DenyOnException = 0x40000000, + + /// + /// If an exception is caught when raising the + /// event, the operation + /// should be interrupted. If this is not specified, the operation + /// will simply continue. + /// + InterruptOnException = 0x80000000, + + /// + /// Attempt to unbind all functions provided by other managed assemblies + /// when closing the connection. + /// + UnbindFunctionsOnClose = 0x100000000, + + /// + /// When returning column values as a , skip + /// verifying their affinity. + /// + NoVerifyTextAffinity = 0x200000000, + + /// + /// Enable using per-connection mappings between type names and + /// values. Also see the + /// , + /// , and + /// methods. + /// + UseConnectionBindValueCallbacks = 0x400000000, + + /// + /// Enable using per-connection mappings between type names and + /// values. Also see the + /// , + /// , and + /// methods. + /// + UseConnectionReadValueCallbacks = 0x800000000, + + /// + /// If the database type name has not been explicitly set for the + /// parameter specified, fallback to using the parameter name. + /// + UseParameterNameForTypeName = 0x1000000000, + + /// + /// If the database type name has not been explicitly set for the + /// parameter specified, fallback to using the database type name + /// associated with the value. + /// + UseParameterDbTypeForTypeName = 0x2000000000, + + /// + /// When returning column values, skip verifying their affinity. + /// + NoVerifyTypeAffinity = 0x4000000000, + + /// + /// Allow transactions to be nested. The outermost transaction still + /// controls whether or not any changes are ultimately committed or + /// rolled back. All non-outermost transactions are implemented using + /// the SAVEPOINT construct. + /// + AllowNestedTransactions = 0x8000000000, + + /// + /// When binding parameter values, always bind + /// values as though they were plain text (i.e. not , + /// which is the legacy behavior). + /// + BindDecimalAsText = 0x10000000000, + + /// + /// When returning column values, always return + /// values as though they were plain text (i.e. not , + /// which is the legacy behavior). + /// + GetDecimalAsText = 0x20000000000, + + /// + /// When binding parameter values, always use + /// the invariant culture when converting their values to strings. + /// + BindInvariantDecimal = 0x40000000000, + + /// + /// When returning column values, always use + /// the invariant culture when converting their values from strings. + /// + GetInvariantDecimal = 0x80000000000, + + /// + /// EXPERIMENTAL -- + /// Enable waiting for the enlistment to be reset prior to attempting + /// to create a new enlistment. This may be necessary due to the + /// semantics used by distributed transactions, which complete + /// asynchronously. + /// + WaitForEnlistmentReset = 0x100000000000, + + /// + /// When returning column values, always use + /// the invariant culture when converting their values from strings. + /// + GetInvariantInt64 = 0x200000000000, + + /// + /// When returning column values, always use + /// the invariant culture when converting their values from strings. + /// + GetInvariantDouble = 0x400000000000, + + /// + /// EXPERIMENTAL -- + /// Enable strict conformance to the ADO.NET standard, e.g. use of + /// thrown exceptions to indicate common error conditions. + /// + StrictConformance = 0x800000000000, + + /// + /// EXPERIMENTAL -- + /// When opening a connection, attempt to hide the password from the + /// connection string, etc. Given the memory architecture of the CLR, + /// (and P/Invoke) this is not 100% reliable and should not be relied + /// upon for security critical uses or applications. + /// + HidePassword = 0x1000000000000, + + /// + /// When binding parameter values or returning column values, always + /// treat them as though they were plain text (i.e. no numeric, + /// date/time, or other conversions should be attempted). + /// + BindAndGetAllAsText = BindAllAsText | GetAllAsText, + + /// + /// When binding parameter values, always use the invariant culture when + /// converting their values to strings or from strings. + /// + ConvertAndBindInvariantText = ConvertInvariantText | BindInvariantText, + + /// + /// When binding parameter values or returning column values, always + /// treat them as though they were plain text (i.e. no numeric, + /// date/time, or other conversions should be attempted) and always + /// use the invariant culture when converting their values to strings. + /// + BindAndGetAllAsInvariantText = BindAndGetAllAsText | BindInvariantText, + + /// + /// When binding parameter values or returning column values, always + /// treat them as though they were plain text (i.e. no numeric, + /// date/time, or other conversions should be attempted) and always + /// use the invariant culture when converting their values to strings + /// or from strings. + /// + ConvertAndBindAndGetAllAsInvariantText = BindAndGetAllAsText | + ConvertAndBindInvariantText, + + /// + /// Enables use of all per-connection value handling callbacks. + /// + UseConnectionAllValueCallbacks = UseConnectionBindValueCallbacks | + UseConnectionReadValueCallbacks, + + /// + /// Enables use of all applicable + /// properties as fallbacks for the database type name. + /// + UseParameterAnythingForTypeName = UseParameterNameForTypeName | + UseParameterDbTypeForTypeName, + + /// + /// Enable all logging. + /// +#if INTEROP_VIRTUAL_TABLE + LogAll = LogPrepare | LogPreBind | LogBind | + LogCallbackException | LogBackup | LogModuleError | + LogModuleException, +#else + LogAll = LogPrepare | LogPreBind | LogBind | + LogCallbackException | LogBackup, +#endif + + /// + /// The default logging related flags for new connections. + /// +#if INTEROP_VIRTUAL_TABLE + LogDefault = LogCallbackException | LogModuleException, +#else + LogDefault = LogCallbackException, +#endif + + /// + /// The default extra flags for new connections. + /// + Default = LogDefault | BindInvariantDecimal | GetInvariantDecimal, + + /// + /// The default extra flags for new connections with all logging enabled. + /// + DefaultAndLogAll = Default | LogAll + } + + /// + /// These are the supported status parameters for use with the native + /// SQLite library. + /// + internal enum SQLiteStatusOpsEnum + { + /// + /// This parameter returns the number of lookaside memory slots + /// currently checked out. + /// + SQLITE_DBSTATUS_LOOKASIDE_USED = 0, + + /// + /// This parameter returns the approximate number of bytes of + /// heap memory used by all pager caches associated with the + /// database connection. The highwater mark associated with + /// SQLITE_DBSTATUS_CACHE_USED is always 0. + /// + SQLITE_DBSTATUS_CACHE_USED = 1, + + /// + /// This parameter returns the approximate number of bytes of + /// heap memory used to store the schema for all databases + /// associated with the connection - main, temp, and any ATTACH-ed + /// databases. The full amount of memory used by the schemas is + /// reported, even if the schema memory is shared with other + /// database connections due to shared cache mode being enabled. + /// The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED + /// is always 0. + /// + SQLITE_DBSTATUS_SCHEMA_USED = 2, + + /// + /// This parameter returns the number malloc attempts that might + /// have been satisfied using lookaside memory but failed due to + /// all lookaside memory already being in use. Only the high-water + /// value is meaningful; the current value is always zero. + /// + SQLITE_DBSTATUS_STMT_USED = 3, + + /// + /// This parameter returns the number malloc attempts that were + /// satisfied using lookaside memory. Only the high-water value + /// is meaningful; the current value is always zero. + /// + SQLITE_DBSTATUS_LOOKASIDE_HIT = 4, + + /// + /// This parameter returns the number malloc attempts that might + /// have been satisfied using lookaside memory but failed due to + /// the amount of memory requested being larger than the lookaside + /// slot size. Only the high-water value is meaningful; the current + /// value is always zero. + /// + SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5, + + /// + /// This parameter returns the number malloc attempts that might + /// have been satisfied using lookaside memory but failed due to + /// the amount of memory requested being larger than the lookaside + /// slot size. Only the high-water value is meaningful; the current + /// value is always zero. + /// + SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6, + + /// + /// This parameter returns the number of pager cache hits that + /// have occurred. The highwater mark associated with + /// SQLITE_DBSTATUS_CACHE_HIT is always 0. + /// + SQLITE_DBSTATUS_CACHE_HIT = 7, + + /// + /// This parameter returns the number of pager cache misses that + /// have occurred. The highwater mark associated with + /// SQLITE_DBSTATUS_CACHE_MISS is always 0. + /// + SQLITE_DBSTATUS_CACHE_MISS = 8, + + /// + /// This parameter returns the number of dirty cache entries that + /// have been written to disk. Specifically, the number of pages + /// written to the wal file in wal mode databases, or the number + /// of pages written to the database file in rollback mode + /// databases. Any pages written as part of transaction rollback + /// or database recovery operations are not included. If an IO or + /// other error occurs while writing a page to disk, the effect + /// on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is + /// undefined. The highwater mark associated with + /// SQLITE_DBSTATUS_CACHE_WRITE is always 0. + /// + SQLITE_DBSTATUS_CACHE_WRITE = 9, + + /// + /// This parameter returns zero for the current value if and only + /// if all foreign key constraints (deferred or immediate) have + /// been resolved. The highwater mark is always 0. + /// + SQLITE_DBSTATUS_DEFERRED_FKS = 10, + + /// + /// This parameter is similar to DBSTATUS_CACHE_USED, except that + /// if a pager cache is shared between two or more connections the + /// bytes of heap memory used by that pager cache is divided evenly + /// between the attached connections. In other words, if none of + /// the pager caches associated with the database connection are + /// shared, this request returns the same value as DBSTATUS_CACHE_USED. + /// Or, if one or more or the pager caches are shared, the value + /// returned by this call will be smaller than that returned by + /// DBSTATUS_CACHE_USED. The highwater mark associated with + /// SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0. + /// + SQLITE_DBSTATUS_CACHE_USED_SHARED = 11 + } + + /// + /// These are the supported configuration verbs for use with the native + /// SQLite library. They are used with the + /// method. + /// + public enum SQLiteConfigDbOpsEnum + { + /// + /// This value represents an unknown (or invalid) option, do not use it. + /// + SQLITE_DBCONFIG_NONE = 0, // nil + + /// + /// This option is used to change the name of the "main" database + /// schema. The sole argument is a pointer to a constant UTF8 string + /// which will become the new schema name in place of "main". + /// + SQLITE_DBCONFIG_MAINDBNAME = 1000, // char* + + /// + /// This option is used to configure the lookaside memory allocator. + /// The value must be an array with three elements. The second element + /// must be an containing the size of each buffer + /// slot. The third element must be an containing + /// the number of slots. The first element must be an + /// that points to a native memory buffer of bytes equal to or greater + /// than the product of the second and third element values. + /// + SQLITE_DBCONFIG_LOOKASIDE = 1001, // void* int int + + /// + /// This option is used to enable or disable the enforcement of + /// foreign key constraints. + /// + SQLITE_DBCONFIG_ENABLE_FKEY = 1002, // int int* + + /// + /// This option is used to enable or disable triggers. + /// + SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003, // int int* + + /// + /// This option is used to enable or disable the two-argument version + /// of the fts3_tokenizer() function which is part of the FTS3 full-text + /// search engine extension. + /// + SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004, // int int* + + /// + /// This option is used to enable or disable the loading of extensions. + /// + SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005, // int int* + + /// + /// This option is used to enable or disable the automatic checkpointing + /// when a WAL database is closed. + /// + SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006, // int int* + + /// + /// This option is used to enable or disable the query planner stability + /// guarantee (QPSG). + /// + SQLITE_DBCONFIG_ENABLE_QPSG = 1007, // int int* + + /// + /// This option is used to enable or disable the extra EXPLAIN QUERY PLAN + /// output for trigger programs. + /// + SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // int int* + + /// + /// This option is used as part of the process to reset a database back + /// to an empty state. Because resetting a database is destructive and + /// irreversible, the process requires the use of this obscure flag and + /// multiple steps to help ensure that it does not happen by accident. + /// + SQLITE_DBCONFIG_RESET_DATABASE = 1009 // int int* + } + + // These are the options to the internal sqlite3_config call. + internal enum SQLiteConfigOpsEnum + { + SQLITE_CONFIG_NONE = 0, // nil + SQLITE_CONFIG_SINGLETHREAD = 1, // nil + SQLITE_CONFIG_MULTITHREAD = 2, // nil + SQLITE_CONFIG_SERIALIZED = 3, // nil + SQLITE_CONFIG_MALLOC = 4, // sqlite3_mem_methods* + SQLITE_CONFIG_GETMALLOC = 5, // sqlite3_mem_methods* + SQLITE_CONFIG_SCRATCH = 6, // void*, int sz, int N + SQLITE_CONFIG_PAGECACHE = 7, // void*, int sz, int N + SQLITE_CONFIG_HEAP = 8, // void*, int nByte, int min + SQLITE_CONFIG_MEMSTATUS = 9, // boolean + SQLITE_CONFIG_MUTEX = 10, // sqlite3_mutex_methods* + SQLITE_CONFIG_GETMUTEX = 11, // sqlite3_mutex_methods* + // previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused + SQLITE_CONFIG_LOOKASIDE = 13, // int int + SQLITE_CONFIG_PCACHE = 14, // sqlite3_pcache_methods* + SQLITE_CONFIG_GETPCACHE = 15, // sqlite3_pcache_methods* + SQLITE_CONFIG_LOG = 16, // xFunc, void* + SQLITE_CONFIG_URI = 17, // int + SQLITE_CONFIG_PCACHE2 = 18, // sqlite3_pcache_methods2* + SQLITE_CONFIG_GETPCACHE2 = 19, // sqlite3_pcache_methods2* + SQLITE_CONFIG_COVERING_INDEX_SCAN = 20, // int + SQLITE_CONFIG_SQLLOG = 21, // xSqllog, void* + SQLITE_CONFIG_MMAP_SIZE = 22, // sqlite3_int64, sqlite3_int64 + SQLITE_CONFIG_WIN32_HEAPSIZE = 23, // int nByte + SQLITE_CONFIG_PCACHE_HDRSZ = 24, // int *psz + SQLITE_CONFIG_PMASZ = 25 // unsigned int szPma + } + + /// + /// These constants are used with the sqlite3_trace_v2() API and the + /// callbacks registered by it. + /// + [Flags()] + internal enum SQLiteTraceFlags + { + SQLITE_TRACE_NONE = 0x0, // nil + SQLITE_TRACE_STMT = 0x1, // pStmt, zSql + SQLITE_TRACE_PROFILE = 0x2, // pStmt, piNsec64 + SQLITE_TRACE_ROW = 0x4, // pStmt + SQLITE_TRACE_CLOSE = 0x8 // pDb + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteBlob.cs b/Native.Csharp.Tool/SQLite/SQLiteBlob.cs new file mode 100644 index 00000000..3dca96a5 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteBlob.cs @@ -0,0 +1,410 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Joe Mistachkin (joe@mistachkin.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + + /// + /// Represents a single SQL blob in SQLite. + /// + public sealed class SQLiteBlob : IDisposable + { + #region Private Data + /// + /// The underlying SQLite object this blob is bound to. + /// + internal SQLiteBase _sql; + + /// + /// The actual blob handle. + /// + internal SQLiteBlobHandle _sqlite_blob; + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Initializes the blob. + /// + /// The base SQLite object. + /// The blob handle. + private SQLiteBlob( + SQLiteBase sqlbase, + SQLiteBlobHandle blob + ) + { + _sql = sqlbase; + _sqlite_blob = blob; + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Static "Factory" Methods + /// + /// Creates a object. This will not work + /// for tables that were created WITHOUT ROWID -OR- if the query + /// does not include the "rowid" column or one of its aliases -OR- + /// if the was not created with the + /// flag. + /// + /// + /// The instance with a result set + /// containing the desired blob column. + /// + /// + /// The index of the blob column. + /// + /// + /// Non-zero to open the blob object for read-only access. + /// + /// + /// The newly created instance -OR- null + /// if an error occurs. + /// + public static SQLiteBlob Create( + SQLiteDataReader dataReader, + int i, + bool readOnly + ) + { + if (dataReader == null) + throw new ArgumentNullException("dataReader"); + + long? rowId = dataReader.GetRowId(i); + + if (rowId == null) + throw new InvalidOperationException("No RowId is available"); + + return Create( + SQLiteDataReader.GetConnection(dataReader), + dataReader.GetDatabaseName(i), dataReader.GetTableName(i), + dataReader.GetName(i), (long)rowId, readOnly); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Creates a object. This will not work + /// for tables that were created WITHOUT ROWID. + /// + /// + /// The connection to use when opening the blob object. + /// + /// + /// The name of the database containing the blob object. + /// + /// + /// The name of the table containing the blob object. + /// + /// + /// The name of the column containing the blob object. + /// + /// + /// The integer identifier for the row associated with the desired + /// blob object. + /// + /// + /// Non-zero to open the blob object for read-only access. + /// + /// + /// The newly created instance -OR- null + /// if an error occurs. + /// + public static SQLiteBlob Create( + SQLiteConnection connection, + string databaseName, + string tableName, + string columnName, + long rowId, + bool readOnly + ) + { + if (connection == null) + throw new ArgumentNullException("connection"); + + SQLite3 sqlite3 = connection._sql as SQLite3; + + if (sqlite3 == null) + throw new InvalidOperationException("Connection has no wrapper"); + + SQLiteConnectionHandle handle = sqlite3._sql; + + if (handle == null) + throw new InvalidOperationException("Connection has an invalid handle."); + + SQLiteBlobHandle blob = null; + + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { + IntPtr ptrBlob = IntPtr.Zero; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_blob_open( + handle, SQLiteConvert.ToUTF8(databaseName), + SQLiteConvert.ToUTF8(tableName), SQLiteConvert.ToUTF8( + columnName), rowId, readOnly ? 0 : 1, ref ptrBlob); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, null); + + blob = new SQLiteBlobHandle(handle, ptrBlob); + } + + SQLiteConnection.OnChanged(connection, new ConnectionEventArgs( + SQLiteConnectionEventType.NewCriticalHandle, null, null, + null, null, blob, null, new object[] { typeof(SQLiteBlob), + databaseName, tableName, columnName, rowId, readOnly })); + + return new SQLiteBlob(sqlite3, blob); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// Throws an exception if the blob object does not appear to be open. + /// + private void CheckOpen() + { + if (_sqlite_blob == IntPtr.Zero) + throw new InvalidOperationException("Blob is not open"); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Throws an exception if an invalid read/write parameter is detected. + /// + /// + /// When reading, this array will be populated with the bytes read from + /// the underlying database blob. When writing, this array contains new + /// values for the specified portion of the underlying database blob. + /// + /// + /// The number of bytes to read or write. + /// + /// + /// The byte offset, relative to the start of the underlying database + /// blob, where the read or write operation will begin. + /// + private void VerifyParameters( + byte[] buffer, + int count, + int offset + ) + { + if (buffer == null) + throw new ArgumentNullException("buffer"); + + if (offset < 0) + throw new ArgumentException("Negative offset not allowed."); + + if (count < 0) + throw new ArgumentException("Negative count not allowed."); + + if (count > buffer.Length) + throw new ArgumentException("Buffer is too small."); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Public Methods + /// + /// Retargets this object to an underlying database blob for a + /// different row; the database, table, and column remain exactly + /// the same. If this operation fails for any reason, this blob + /// object is automatically disposed. + /// + /// + /// The integer identifier for the new row. + /// + public void Reopen( + long rowId + ) + { + CheckDisposed(); + CheckOpen(); + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_blob_reopen( + _sqlite_blob, rowId); + + if (rc != SQLiteErrorCode.Ok) + { + Dispose(); + throw new SQLiteException(rc, null); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Queries the total number of bytes for the underlying database blob. + /// + /// + /// The total number of bytes for the underlying database blob. + /// + public int GetCount() + { + CheckDisposed(); + CheckOpen(); + + return UnsafeNativeMethods.sqlite3_blob_bytes(_sqlite_blob); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Reads data from the underlying database blob. + /// + /// + /// This array will be populated with the bytes read from the + /// underlying database blob. + /// + /// + /// The number of bytes to read. + /// + /// + /// The byte offset, relative to the start of the underlying + /// database blob, where the read operation will begin. + /// + public void Read( + byte[] buffer, + int count, + int offset + ) + { + CheckDisposed(); + CheckOpen(); + VerifyParameters(buffer, count, offset); + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_blob_read( + _sqlite_blob, buffer, count, offset); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, null); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Writes data into the underlying database blob. + /// + /// + /// This array contains the new values for the specified portion of + /// the underlying database blob. + /// + /// + /// The number of bytes to write. + /// + /// + /// The byte offset, relative to the start of the underlying + /// database blob, where the write operation will begin. + /// + public void Write( + byte[] buffer, + int count, + int offset + ) + { + CheckDisposed(); + CheckOpen(); + VerifyParameters(buffer, count, offset); + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_blob_write( + _sqlite_blob, buffer, count, offset); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, null); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Closes the blob, freeing the associated resources. + /// + public void Close() + { + Dispose(); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes and finalizes the blob. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteBlob).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (_sqlite_blob != null) + { + _sqlite_blob.Dispose(); + _sqlite_blob = null; + } + + _sql = null; + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Destructor + /// + /// The destructor. + /// + ~SQLiteBlob() + { + Dispose(false); + } + #endregion + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteCommand.cs b/Native.Csharp.Tool/SQLite/SQLiteCommand.cs new file mode 100644 index 00000000..7c8d7c7a --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteCommand.cs @@ -0,0 +1,1160 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Data; + using System.Data.Common; + using System.Diagnostics; + using System.Collections.Generic; + using System.ComponentModel; + + /// + /// SQLite implementation of DbCommand. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Designer("SQLite.Designer.SQLiteCommandDesigner, SQLite.Designer, Version=" + SQLite3.DesignerVersion + ", Culture=neutral, PublicKeyToken=db937bc2d44ff139"), ToolboxItem(true)] +#endif + public sealed class SQLiteCommand : DbCommand, ICloneable + { + /// + /// The default connection string to be used when creating a temporary + /// connection to execute a command via the static + /// or + /// + /// methods. + /// + private static readonly string DefaultConnectionString = "Data Source=:memory:;"; + + /// + /// The command text this command is based on + /// + private string _commandText; + /// + /// The connection the command is associated with + /// + private SQLiteConnection _cnn; + /// + /// The version of the connection the command is associated with + /// + private int _version; + /// + /// Indicates whether or not a DataReader is active on the command. + /// + private WeakReference _activeReader; + /// + /// The timeout for the command, kludged because SQLite doesn't support per-command timeout values + /// + internal int _commandTimeout; + /// + /// Designer support + /// + private bool _designTimeVisible; + /// + /// Used by DbDataAdapter to determine updating behavior + /// + private UpdateRowSource _updateRowSource; + /// + /// The collection of parameters for the command + /// + private SQLiteParameterCollection _parameterCollection; + /// + /// The SQL command text, broken into individual SQL statements as they are executed + /// + internal List _statementList; + /// + /// Unprocessed SQL text that has not been executed + /// + internal string _remainingText; + /// + /// Transaction associated with this command + /// + private SQLiteTransaction _transaction; + + /// + /// Constructs a new SQLiteCommand + /// + /// + /// Default constructor + /// + public SQLiteCommand() :this(null, null) + { + } + + /// + /// Initializes the command with the given command text + /// + /// The SQL command text + public SQLiteCommand(string commandText) + : this(commandText, null, null) + { + } + + /// + /// Initializes the command with the given SQL command text and attach the command to the specified + /// connection. + /// + /// The SQL command text + /// The connection to associate with the command + public SQLiteCommand(string commandText, SQLiteConnection connection) + : this(commandText, connection, null) + { + } + + /// + /// Initializes the command and associates it with the specified connection. + /// + /// The connection to associate with the command + public SQLiteCommand(SQLiteConnection connection) + : this(null, connection, null) + { + } + + private SQLiteCommand(SQLiteCommand source) : this(source.CommandText, source.Connection, source.Transaction) + { + CommandTimeout = source.CommandTimeout; + DesignTimeVisible = source.DesignTimeVisible; + UpdatedRowSource = source.UpdatedRowSource; + + foreach (SQLiteParameter param in source._parameterCollection) + { + Parameters.Add(param.Clone()); + } + } + + /// + /// Initializes a command with the given SQL, connection and transaction + /// + /// The SQL command text + /// The connection to associate with the command + /// The transaction the command should be associated with + public SQLiteCommand(string commandText, SQLiteConnection connection, SQLiteTransaction transaction) + { + _commandTimeout = 30; + _parameterCollection = new SQLiteParameterCollection(this); + _designTimeVisible = true; + _updateRowSource = UpdateRowSource.None; + + if (commandText != null) + CommandText = commandText; + + if (connection != null) + { + DbConnection = connection; + _commandTimeout = connection.DefaultTimeout; + } + + if (transaction != null) + Transaction = transaction; + + SQLiteConnection.OnChanged(connection, new ConnectionEventArgs( + SQLiteConnectionEventType.NewCommand, null, transaction, this, + null, null, null, null)); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + [Conditional("CHECK_STATE")] + internal static void Check(SQLiteCommand command) + { + if (command == null) + throw new ArgumentNullException("command"); + + command.CheckDisposed(); + SQLiteConnection.Check(command._cnn); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteCommand).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of the command and clears all member variables + /// + /// Whether or not the class is being explicitly or implicitly disposed + protected override void Dispose(bool disposing) + { + SQLiteConnection.OnChanged(_cnn, new ConnectionEventArgs( + SQLiteConnectionEventType.DisposingCommand, null, _transaction, this, + null, null, null, new object[] { disposing, disposed })); + + bool skippedDispose = false; + + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + // If a reader is active on this command, don't destroy the command, instead let the reader do it + SQLiteDataReader reader = null; + if (_activeReader != null) + { + try + { + reader = _activeReader.Target as SQLiteDataReader; + } + catch (InvalidOperationException) + { + } + } + + if (reader != null) + { + reader._disposeCommand = true; + _activeReader = null; + skippedDispose = true; + return; + } + + Connection = null; + _parameterCollection.Clear(); + _commandText = null; + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + if (!skippedDispose) + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This method attempts to query the flags associated with the database + /// connection in use. If the database connection is disposed, the default + /// flags will be returned. + /// + /// + /// The command containing the databse connection to query the flags from. + /// + /// + /// The connection flags value. + /// + internal static SQLiteConnectionFlags GetFlags( + SQLiteCommand command + ) + { + try + { + if (command != null) + { + SQLiteConnection cnn = command._cnn; + + if (cnn != null) + return cnn.Flags; + } + } + catch (ObjectDisposedException) + { + // do nothing. + } + + return SQLiteConnectionFlags.Default; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private void DisposeStatements() + { + if (_statementList == null) return; + + int x = _statementList.Count; + + for (int n = 0; n < x; n++) + { + SQLiteStatement stmt = _statementList[n]; + if (stmt == null) continue; + stmt.Dispose(); + } + + _statementList = null; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private void ClearDataReader() + { + if (_activeReader != null) + { + SQLiteDataReader reader = null; + + try + { + reader = _activeReader.Target as SQLiteDataReader; + } + catch(InvalidOperationException) + { + // do nothing. + } + + if (reader != null) + reader.Close(); /* Dispose */ + + _activeReader = null; + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Clears and destroys all statements currently prepared + /// + internal void ClearCommands() + { + ClearDataReader(); + DisposeStatements(); + + _parameterCollection.Unbind(); + } + + /// + /// Builds an array of prepared statements for each complete SQL statement in the command text + /// + internal SQLiteStatement BuildNextCommand() + { + SQLiteStatement stmt = null; + + try + { + if ((_cnn != null) && (_cnn._sql != null)) + { + if (_statementList == null) + _remainingText = _commandText; + + stmt = _cnn._sql.Prepare(_cnn, _remainingText, (_statementList == null) ? null : _statementList[_statementList.Count - 1], (uint)(_commandTimeout * 1000), ref _remainingText); + + if (stmt != null) + { + stmt._command = this; + + if (_statementList == null) + _statementList = new List(); + + _statementList.Add(stmt); + + _parameterCollection.MapParameters(stmt); + stmt.BindParameters(); + } + } + return stmt; + } + catch (Exception) + { + if (stmt != null) + { + if ((_statementList != null) && _statementList.Contains(stmt)) + _statementList.Remove(stmt); + + stmt.Dispose(); + } + + // If we threw an error compiling the statement, we cannot continue on so set the remaining text to null. + _remainingText = null; + + throw; + } + } + + internal SQLiteStatement GetStatement(int index) + { + // Haven't built any statements yet + if (_statementList == null) return BuildNextCommand(); + + // If we're at the last built statement and want the next unbuilt statement, then build it + if (index == _statementList.Count) + { + if (String.IsNullOrEmpty(_remainingText) == false) return BuildNextCommand(); + else return null; // No more commands + } + + SQLiteStatement stmt = _statementList[index]; + stmt.BindParameters(); + + return stmt; + } + + /// + /// Not implemented + /// + public override void Cancel() + { + CheckDisposed(); + + if (_activeReader != null) + { + SQLiteDataReader reader = _activeReader.Target as SQLiteDataReader; + if (reader != null) + reader.Cancel(); + } + } + + /// + /// The SQL command text associated with the command + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DefaultValue(""), RefreshProperties(RefreshProperties.All), Editor("Microsoft.VSDesigner.Data.SQL.Design.SqlCommandTextEditor, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] +#endif + public override string CommandText + { + get + { + CheckDisposed(); + + return _commandText; + } + set + { + CheckDisposed(); + + if (_commandText == value) return; + + if (_activeReader != null && _activeReader.IsAlive) + { + throw new InvalidOperationException("Cannot set CommandText while a DataReader is active"); + } + + ClearCommands(); + _commandText = value; + + if (_cnn == null) return; + } + } + + /// + /// The amount of time to wait for the connection to become available before erroring out + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DefaultValue((int)30)] +#endif + public override int CommandTimeout + { + get + { + CheckDisposed(); + return _commandTimeout; + } + set + { + CheckDisposed(); + _commandTimeout = value; + } + } + + /// + /// The type of the command. SQLite only supports CommandType.Text + /// +#if !PLATFORM_COMPACTFRAMEWORK + [RefreshProperties(RefreshProperties.All), DefaultValue(CommandType.Text)] +#endif + public override CommandType CommandType + { + get + { + CheckDisposed(); + return CommandType.Text; + } + set + { + CheckDisposed(); + + if (value != CommandType.Text) + { + throw new NotSupportedException(); + } + } + } + + /// + /// Forwards to the local CreateParameter() function + /// + /// + protected override DbParameter CreateDbParameter() + { + return CreateParameter(); + } + + /// + /// Create a new parameter + /// + /// + public new SQLiteParameter CreateParameter() + { + CheckDisposed(); + return new SQLiteParameter(this); + } + + /// + /// The connection associated with this command + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DefaultValue((string)null), Editor("Microsoft.VSDesigner.Data.Design.DbConnectionEditor, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] +#endif + public new SQLiteConnection Connection + { + get { CheckDisposed(); return _cnn; } + set + { + CheckDisposed(); + + if (_activeReader != null && _activeReader.IsAlive) + throw new InvalidOperationException("Cannot set Connection while a DataReader is active"); + + if (_cnn != null) + { + ClearCommands(); + //_cnn.RemoveCommand(this); + } + + _cnn = value; + if (_cnn != null) + _version = _cnn._version; + + //if (_cnn != null) + // _cnn.AddCommand(this); + } + } + + /// + /// Forwards to the local Connection property + /// + protected override DbConnection DbConnection + { + get + { + return Connection; + } + set + { + Connection = (SQLiteConnection)value; + } + } + + /// + /// Returns the SQLiteParameterCollection for the given command + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] +#endif + public new SQLiteParameterCollection Parameters + { + get { CheckDisposed(); return _parameterCollection; } + } + + /// + /// Forwards to the local Parameters property + /// + protected override DbParameterCollection DbParameterCollection + { + get + { + return Parameters; + } + } + + /// + /// The transaction associated with this command. SQLite only supports one transaction per connection, so this property forwards to the + /// command's underlying connection. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] +#endif + public new SQLiteTransaction Transaction + { + get { CheckDisposed(); return _transaction; } + set + { + CheckDisposed(); + + if (_cnn != null) + { + if (_activeReader != null && _activeReader.IsAlive) + throw new InvalidOperationException("Cannot set Transaction while a DataReader is active"); + + if (value != null) + { + if (value._cnn != _cnn) + throw new ArgumentException("Transaction is not associated with the command's connection"); + } + _transaction = value; + } + else + { + if (value != null) Connection = value.Connection; + _transaction = value; + } + } + } + + /// + /// Forwards to the local Transaction property + /// + protected override DbTransaction DbTransaction + { + get + { + return Transaction; + } + set + { + Transaction = (SQLiteTransaction)value; + } + } + + /// + /// Verifies that all SQL queries associated with the current command text + /// can be successfully compiled. A will be + /// raised if any errors occur. + /// + public void VerifyOnly() + { + CheckDisposed(); + + SQLiteConnection connection = _cnn; + SQLiteConnection.Check(connection); /* throw */ + SQLiteBase sqlBase = connection._sql; + + if ((connection == null) || (sqlBase == null)) + throw new SQLiteException("invalid or unusable connection"); + + List statements = null; + SQLiteStatement currentStatement = null; + + try + { + string text = _commandText; + uint timeout = (uint)(_commandTimeout * 1000); + SQLiteStatement previousStatement = null; + + while ((text != null) && (text.Length > 0)) + { + currentStatement = sqlBase.Prepare( + connection, text, previousStatement, timeout, + ref text); /* throw */ + + previousStatement = currentStatement; + + if (currentStatement != null) + { + if (statements == null) + statements = new List(); + + statements.Add(currentStatement); + currentStatement = null; + } + + if (text == null) continue; + text = text.Trim(); + } + } + finally + { + if (currentStatement != null) + { + currentStatement.Dispose(); + currentStatement = null; + } + + if (statements != null) + { + foreach (SQLiteStatement statement in statements) + { + if (statement == null) + continue; + + statement.Dispose(); + } + + statements.Clear(); + statements = null; + } + } + } + + /// + /// This function ensures there are no active readers, that we have a valid connection, + /// that the connection is open, that all statements are prepared and all parameters are assigned + /// in preparation for allocating a data reader. + /// + private void InitializeForReader() + { + if (_activeReader != null && _activeReader.IsAlive) + throw new InvalidOperationException("DataReader already active on this command"); + + if (_cnn == null) + throw new InvalidOperationException("No connection associated with this command"); + + if (_cnn.State != ConnectionState.Open) + throw new InvalidOperationException("Database is not open"); + + // If the version of the connection has changed, clear out any previous commands before starting + if (_cnn._version != _version) + { + _version = _cnn._version; + ClearCommands(); + } + + // Map all parameters for statements already built + _parameterCollection.MapParameters(null); + + //// Set the default command timeout + //_cnn._sql.SetTimeout(_commandTimeout * 1000); + } + + /// + /// Creates a new SQLiteDataReader to execute/iterate the array of SQLite prepared statements + /// + /// The behavior the data reader should adopt + /// Returns a SQLiteDataReader object + protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) + { + return ExecuteReader(behavior); + } + + /// + /// This method creates a new connection, executes the query using the given + /// execution type, closes the connection, and returns the results. If the + /// connection string is null, a temporary in-memory database connection will + /// be used. + /// + /// + /// The text of the command to be executed. + /// + /// + /// The execution type for the command. This is used to determine which method + /// of the command object to call, which then determines the type of results + /// returned, if any. + /// + /// + /// The connection string to the database to be opened, used, and closed. If + /// this parameter is null, a temporary in-memory databse will be used. + /// + /// + /// The SQL parameter values to be used when building the command object to be + /// executed, if any. + /// + /// + /// The results of the query -OR- null if no results were produced from the + /// given execution type. + /// + public static object Execute( + string commandText, + SQLiteExecuteType executeType, + string connectionString, + params object[] args + ) + { + return Execute( + commandText, executeType, CommandBehavior.Default, + connectionString, args); + } + + /// + /// This method creates a new connection, executes the query using the given + /// execution type and command behavior, closes the connection unless a data + /// reader is created, and returns the results. If the connection string is + /// null, a temporary in-memory database connection will be used. + /// + /// + /// The text of the command to be executed. + /// + /// + /// The execution type for the command. This is used to determine which method + /// of the command object to call, which then determines the type of results + /// returned, if any. + /// + /// + /// The command behavior flags for the command. + /// + /// + /// The connection string to the database to be opened, used, and closed. If + /// this parameter is null, a temporary in-memory databse will be used. + /// + /// + /// The SQL parameter values to be used when building the command object to be + /// executed, if any. + /// + /// + /// The results of the query -OR- null if no results were produced from the + /// given execution type. + /// + public static object Execute( + string commandText, + SQLiteExecuteType executeType, + CommandBehavior commandBehavior, + string connectionString, + params object[] args + ) + { + SQLiteConnection connection = null; + + try + { + if (connectionString == null) + connectionString = DefaultConnectionString; + + using (connection = new SQLiteConnection(connectionString)) + { + connection.Open(); + + using (SQLiteCommand command = connection.CreateCommand()) + { + command.CommandText = commandText; + + if (args != null) + { + foreach (object arg in args) + { + SQLiteParameter parameter = arg as SQLiteParameter; + + if (parameter == null) + { + parameter = command.CreateParameter(); + parameter.DbType = DbType.Object; + parameter.Value = arg; + } + + command.Parameters.Add(parameter); + } + } + + switch (executeType) + { + case SQLiteExecuteType.None: + { + // + // NOTE: Do nothing. + // + break; + } + case SQLiteExecuteType.NonQuery: + { + return command.ExecuteNonQuery(commandBehavior); + } + case SQLiteExecuteType.Scalar: + { + return command.ExecuteScalar(commandBehavior); + } + case SQLiteExecuteType.Reader: + { + bool success = true; + + try + { + // + // NOTE: The CloseConnection flag is being added here. + // This should force the returned data reader to + // close the connection when it is disposed. In + // order to prevent the containing using block + // from disposing the connection prematurely, + // the innermost finally block sets the internal + // no-disposal flag to true. The outer finally + // block will reset the internal no-disposal flag + // to false so that the data reader will be able + // to (eventually) dispose of the connection. + // + return command.ExecuteReader( + commandBehavior | CommandBehavior.CloseConnection); + } + catch + { + success = false; + throw; + } + finally + { + // + // NOTE: If an exception was not thrown, that can only + // mean the data reader was successfully created + // and now owns the connection. Therefore, set + // the internal no-disposal flag (temporarily) + // in order to exit the containing using block + // without disposing it. + // + if (success) + connection._noDispose = true; + } + } + } + } + } + } + finally + { + // + // NOTE: Now that the using block has been exited, reset the + // internal disposal flag for the connection. This is + // always done if the connection was created because + // it will be harmless whether or not the data reader + // now owns it. + // + if (connection != null) + connection._noDispose = false; + } + + return null; + } + + /// + /// Overrides the default behavior to return a SQLiteDataReader specialization class + /// + /// The flags to be associated with the reader. + /// A SQLiteDataReader + public new SQLiteDataReader ExecuteReader(CommandBehavior behavior) + { + CheckDisposed(); + SQLiteConnection.Check(_cnn); + InitializeForReader(); + + SQLiteDataReader rd = new SQLiteDataReader(this, behavior); + _activeReader = new WeakReference(rd, false); + + return rd; + } + + /// + /// Overrides the default behavior of DbDataReader to return a specialized SQLiteDataReader class + /// + /// A SQLiteDataReader + public new SQLiteDataReader ExecuteReader() + { + CheckDisposed(); + SQLiteConnection.Check(_cnn); + return ExecuteReader(CommandBehavior.Default); + } + + /// + /// Called by the SQLiteDataReader when the data reader is closed. + /// + internal void ResetDataReader() + { + _activeReader = null; + } + + /// + /// Execute the command and return the number of rows inserted/updated affected by it. + /// + /// The number of rows inserted/updated affected by it. + public override int ExecuteNonQuery() + { + CheckDisposed(); + SQLiteConnection.Check(_cnn); + return ExecuteNonQuery(CommandBehavior.Default); + } + + /// + /// Execute the command and return the number of rows inserted/updated affected by it. + /// + /// The flags to be associated with the reader. + /// The number of rows inserted/updated affected by it. + public int ExecuteNonQuery( + CommandBehavior behavior + ) + { + CheckDisposed(); + SQLiteConnection.Check(_cnn); + + using (SQLiteDataReader reader = ExecuteReader(behavior | + CommandBehavior.SingleRow | CommandBehavior.SingleResult)) + { + while (reader.NextResult()) ; + return reader.RecordsAffected; + } + } + + /// + /// Execute the command and return the first column of the first row of the resultset + /// (if present), or null if no resultset was returned. + /// + /// The first column of the first row of the first resultset from the query. + public override object ExecuteScalar() + { + CheckDisposed(); + SQLiteConnection.Check(_cnn); + return ExecuteScalar(CommandBehavior.Default); + } + + /// + /// Execute the command and return the first column of the first row of the resultset + /// (if present), or null if no resultset was returned. + /// + /// The flags to be associated with the reader. + /// The first column of the first row of the first resultset from the query. + public object ExecuteScalar( + CommandBehavior behavior + ) + { + CheckDisposed(); + SQLiteConnection.Check(_cnn); + + using (SQLiteDataReader reader = ExecuteReader(behavior | + CommandBehavior.SingleRow | CommandBehavior.SingleResult)) + { + if (reader.Read() && (reader.FieldCount > 0)) + return reader[0]; + } + return null; + } + + /// + /// This method resets all the prepared statements held by this instance + /// back to their initial states, ready to be re-executed. + /// + public void Reset() + { + CheckDisposed(); + SQLiteConnection.Check(_cnn); + + Reset(true, false); + } + + /// + /// This method resets all the prepared statements held by this instance + /// back to their initial states, ready to be re-executed. + /// + /// + /// Non-zero if the parameter bindings should be cleared as well. + /// + /// + /// If this is zero, a may be thrown for + /// any unsuccessful return codes from the native library; otherwise, a + /// will only be thrown if the connection + /// or its state is invalid. + /// + public void Reset( + bool clearBindings, + bool ignoreErrors + ) + { + CheckDisposed(); + SQLiteConnection.Check(_cnn); + + if (clearBindings && (_parameterCollection != null)) + _parameterCollection.Unbind(); + + ClearDataReader(); + + if (_statementList == null) + return; + + SQLiteBase sqlBase = _cnn._sql; + SQLiteErrorCode rc; + + foreach (SQLiteStatement item in _statementList) + { + if (item == null) + continue; + + SQLiteStatementHandle stmt = item._sqlite_stmt; + + if (stmt == null) + continue; + + rc = sqlBase.Reset(item); + + if ((rc == SQLiteErrorCode.Ok) && clearBindings && + (SQLite3.SQLiteVersionNumber >= 3003007)) + { + rc = UnsafeNativeMethods.sqlite3_clear_bindings(stmt); + } + + if (!ignoreErrors && (rc != SQLiteErrorCode.Ok)) + throw new SQLiteException(rc, sqlBase.GetLastError()); + } + } + + /// + /// Does nothing. Commands are prepared as they are executed the first time, and kept in prepared state afterwards. + /// + public override void Prepare() + { + CheckDisposed(); + SQLiteConnection.Check(_cnn); + } + + /// + /// Sets the method the SQLiteCommandBuilder uses to determine how to update inserted or updated rows in a DataTable. + /// + [DefaultValue(UpdateRowSource.None)] + public override UpdateRowSource UpdatedRowSource + { + get + { + CheckDisposed(); + return _updateRowSource; + } + set + { + CheckDisposed(); + _updateRowSource = value; + } + } + + /// + /// Determines if the command is visible at design time. Defaults to True. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DesignOnly(true), Browsable(false), DefaultValue(true), EditorBrowsable(EditorBrowsableState.Never)] +#endif + public override bool DesignTimeVisible + { + get + { + CheckDisposed(); + return _designTimeVisible; + } + set + { + CheckDisposed(); + + _designTimeVisible = value; +#if !PLATFORM_COMPACTFRAMEWORK + TypeDescriptor.Refresh(this); +#endif + } + } + + /// + /// Clones a command, including all its parameters + /// + /// A new SQLiteCommand with the same commandtext, connection and parameters + public object Clone() + { + CheckDisposed(); + return new SQLiteCommand(this); + } + } +} \ No newline at end of file diff --git a/Native.Csharp.Tool/SQLite/SQLiteCommandBuilder.cs b/Native.Csharp.Tool/SQLite/SQLiteCommandBuilder.cs new file mode 100644 index 00000000..8dd35c09 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteCommandBuilder.cs @@ -0,0 +1,415 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Data; + using System.Data.Common; + using System.Globalization; + using System.ComponentModel; + + /// + /// SQLite implementation of DbCommandBuilder. + /// + public sealed class SQLiteCommandBuilder : DbCommandBuilder + { + /// + /// Default constructor + /// + public SQLiteCommandBuilder() : this(null) + { + } + + /// + /// Initializes the command builder and associates it with the specified data adapter. + /// + /// + public SQLiteCommandBuilder(SQLiteDataAdapter adp) + { + QuotePrefix = "["; + QuoteSuffix = "]"; + DataAdapter = adp; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteCommandBuilder).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Cleans up resources (native and managed) associated with the current instance. + /// + /// + /// Zero when being disposed via garbage collection; otherwise, non-zero. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Minimal amount of parameter processing. Primarily sets the DbType for the parameter equal to the provider type in the schema + /// + /// The parameter to use in applying custom behaviors to a row + /// The row to apply the parameter to + /// The type of statement + /// Whether the application of the parameter is part of a WHERE clause + protected override void ApplyParameterInfo(DbParameter parameter, DataRow row, StatementType statementType, bool whereClause) + { + SQLiteParameter param = (SQLiteParameter)parameter; + param.DbType = (DbType)row[SchemaTableColumn.ProviderType]; + } + + /// + /// Returns a valid named parameter + /// + /// The name of the parameter + /// Error + protected override string GetParameterName(string parameterName) + { + return HelperMethods.StringFormat(CultureInfo.InvariantCulture, "@{0}", parameterName); + } + + /// + /// Returns a named parameter for the given ordinal + /// + /// The i of the parameter + /// Error + protected override string GetParameterName(int parameterOrdinal) + { + return HelperMethods.StringFormat(CultureInfo.InvariantCulture, "@param{0}", parameterOrdinal); + } + + /// + /// Returns a placeholder character for the specified parameter i. + /// + /// The index of the parameter to provide a placeholder for + /// Returns a named parameter + protected override string GetParameterPlaceholder(int parameterOrdinal) + { + return GetParameterName(parameterOrdinal); + } + + /// + /// Sets the handler for receiving row updating events. Used by the DbCommandBuilder to autogenerate SQL + /// statements that may not have previously been generated. + /// + /// A data adapter to receive events on. + protected override void SetRowUpdatingHandler(DbDataAdapter adapter) + { + if (adapter == base.DataAdapter) + { + ((SQLiteDataAdapter)adapter).RowUpdating -= new EventHandler(RowUpdatingEventHandler); + } + else + { + ((SQLiteDataAdapter)adapter).RowUpdating += new EventHandler(RowUpdatingEventHandler); + } + } + + private void RowUpdatingEventHandler(object sender, RowUpdatingEventArgs e) + { + base.RowUpdatingHandler(e); + } + + /// + /// Gets/sets the DataAdapter for this CommandBuilder + /// + public new SQLiteDataAdapter DataAdapter + { + get { CheckDisposed(); return (SQLiteDataAdapter)base.DataAdapter; } + set { CheckDisposed(); base.DataAdapter = value; } + } + + /// + /// Returns the automatically-generated SQLite command to delete rows from the database + /// + /// + public new SQLiteCommand GetDeleteCommand() + { + CheckDisposed(); + return (SQLiteCommand)base.GetDeleteCommand(); + } + + /// + /// Returns the automatically-generated SQLite command to delete rows from the database + /// + /// + /// + public new SQLiteCommand GetDeleteCommand(bool useColumnsForParameterNames) + { + CheckDisposed(); + return (SQLiteCommand)base.GetDeleteCommand(useColumnsForParameterNames); + } + + /// + /// Returns the automatically-generated SQLite command to update rows in the database + /// + /// + public new SQLiteCommand GetUpdateCommand() + { + CheckDisposed(); + return (SQLiteCommand)base.GetUpdateCommand(); + } + + /// + /// Returns the automatically-generated SQLite command to update rows in the database + /// + /// + /// + public new SQLiteCommand GetUpdateCommand(bool useColumnsForParameterNames) + { + CheckDisposed(); + return (SQLiteCommand)base.GetUpdateCommand(useColumnsForParameterNames); + } + + /// + /// Returns the automatically-generated SQLite command to insert rows into the database + /// + /// + public new SQLiteCommand GetInsertCommand() + { + CheckDisposed(); + return (SQLiteCommand)base.GetInsertCommand(); + } + + /// + /// Returns the automatically-generated SQLite command to insert rows into the database + /// + /// + /// + public new SQLiteCommand GetInsertCommand(bool useColumnsForParameterNames) + { + CheckDisposed(); + return (SQLiteCommand)base.GetInsertCommand(useColumnsForParameterNames); + } + + /// + /// Overridden to hide its property from the designer + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false)] +#endif + public override CatalogLocation CatalogLocation + { + get + { + CheckDisposed(); + return base.CatalogLocation; + } + set + { + CheckDisposed(); + base.CatalogLocation = value; + } + } + + /// + /// Overridden to hide its property from the designer + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false)] +#endif + public override string CatalogSeparator + { + get + { + CheckDisposed(); + return base.CatalogSeparator; + } + set + { + CheckDisposed(); + base.CatalogSeparator = value; + } + } + + /// + /// Overridden to hide its property from the designer + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false)] +#endif + [DefaultValue("[")] + public override string QuotePrefix + { + get + { + CheckDisposed(); + return base.QuotePrefix; + } + set + { + CheckDisposed(); + base.QuotePrefix = value; + } + } + + /// + /// Overridden to hide its property from the designer + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false)] +#endif + public override string QuoteSuffix + { + get + { + CheckDisposed(); + return base.QuoteSuffix; + } + set + { + CheckDisposed(); + base.QuoteSuffix = value; + } + } + + /// + /// Places brackets around an identifier + /// + /// The identifier to quote + /// The bracketed identifier + public override string QuoteIdentifier(string unquotedIdentifier) + { + CheckDisposed(); + + if (String.IsNullOrEmpty(QuotePrefix) + || String.IsNullOrEmpty(QuoteSuffix) + || String.IsNullOrEmpty(unquotedIdentifier)) + return unquotedIdentifier; + + return QuotePrefix + unquotedIdentifier.Replace(QuoteSuffix, QuoteSuffix + QuoteSuffix) + QuoteSuffix; + } + + /// + /// Removes brackets around an identifier + /// + /// The quoted (bracketed) identifier + /// The undecorated identifier + public override string UnquoteIdentifier(string quotedIdentifier) + { + CheckDisposed(); + + if (String.IsNullOrEmpty(QuotePrefix) + || String.IsNullOrEmpty(QuoteSuffix) + || String.IsNullOrEmpty(quotedIdentifier)) + return quotedIdentifier; + + if (quotedIdentifier.StartsWith(QuotePrefix, StringComparison.OrdinalIgnoreCase) == false + || quotedIdentifier.EndsWith(QuoteSuffix, StringComparison.OrdinalIgnoreCase) == false) + return quotedIdentifier; + + return quotedIdentifier.Substring(QuotePrefix.Length, quotedIdentifier.Length - (QuotePrefix.Length + QuoteSuffix.Length)).Replace(QuoteSuffix + QuoteSuffix, QuoteSuffix); + } + + /// + /// Overridden to hide its property from the designer + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false)] +#endif + public override string SchemaSeparator + { + get + { + CheckDisposed(); + return base.SchemaSeparator; + } + set + { + CheckDisposed(); + base.SchemaSeparator = value; + } + } + + /// + /// Override helper, which can help the base command builder choose the right keys for the given query + /// + /// + /// + protected override DataTable GetSchemaTable(DbCommand sourceCommand) + { + using (IDataReader reader = sourceCommand.ExecuteReader(CommandBehavior.KeyInfo | CommandBehavior.SchemaOnly)) + { + DataTable schema = reader.GetSchemaTable(); + + // If the query contains a primary key, turn off the IsUnique property + // for all the non-key columns + if (HasSchemaPrimaryKey(schema)) + ResetIsUniqueSchemaColumn(schema); + + // if table has no primary key we use unique columns as a fall back + return schema; + } + } + + private bool HasSchemaPrimaryKey(DataTable schema) + { + DataColumn IsKeyColumn = schema.Columns[SchemaTableColumn.IsKey]; + + foreach (DataRow schemaRow in schema.Rows) + { + if ((bool)schemaRow[IsKeyColumn] == true) + return true; + } + + return false; + } + + private void ResetIsUniqueSchemaColumn(DataTable schema) + { + DataColumn IsUniqueColumn = schema.Columns[SchemaTableColumn.IsUnique]; + DataColumn IsKeyColumn = schema.Columns[SchemaTableColumn.IsKey]; + + foreach (DataRow schemaRow in schema.Rows) + { + if ((bool)schemaRow[IsKeyColumn] == false) + schemaRow[IsUniqueColumn] = false; + } + + schema.AcceptChanges(); + } + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteConnection.cs b/Native.Csharp.Tool/SQLite/SQLiteConnection.cs new file mode 100644 index 00000000..f0e44eb4 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteConnection.cs @@ -0,0 +1,7724 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Data; + using System.Data.Common; + using System.Diagnostics; + using System.Collections.Generic; + using System.Globalization; + using System.ComponentModel; + using System.Reflection; + using System.Runtime.InteropServices; + using System.IO; + using System.Text; + using System.Threading; + + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This class represents a single value to be returned + /// from the class via + /// its , + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , or + /// method. If the value of the + /// associated public field of this class is null upon returning from the + /// callback, the null value will only be used if the return type for the + /// method called is not a value type. + /// If the value to be returned from the + /// method is unsuitable (e.g. null with a value type), an exception will + /// be thrown. + /// + public sealed class SQLiteDataReaderValue + { + /// + /// The value to be returned from the + /// method -OR- null to + /// indicate an error. + /// + public SQLiteBlob BlobValue; + + /// + /// The value to be returned from the + /// method -OR- null to + /// indicate an error. + /// + public bool? BooleanValue; + + /// + /// The value to be returned from the + /// method -OR- null to + /// indicate an error. + /// + public byte? ByteValue; + + /// + /// The value to be returned from the + /// method. + /// + public byte[] BytesValue; + + /// + /// The value to be returned from the + /// method -OR- null to + /// indicate an error. + /// + public char? CharValue; + + /// + /// The value to be returned from the + /// method. + /// + public char[] CharsValue; + + /// + /// The value to be returned from the + /// method -OR- null to + /// indicate an error. + /// + public DateTime? DateTimeValue; + + /// + /// The value to be returned from the + /// method -OR- null to + /// indicate an error. + /// + public decimal? DecimalValue; + + /// + /// The value to be returned from the + /// method -OR- null to + /// indicate an error. + /// + public double? DoubleValue; + + /// + /// The value to be returned from the + /// method -OR- null to + /// indicate an error. + /// + public float? FloatValue; + + /// + /// The value to be returned from the + /// method -OR- null to + /// indicate an error. + /// + public Guid? GuidValue; + + /// + /// The value to be returned from the + /// method -OR- null to + /// indicate an error. + /// + public short? Int16Value; + + /// + /// The value to be returned from the + /// method -OR- null to + /// indicate an error. + /// + public int? Int32Value; + + /// + /// The value to be returned from the + /// method -OR- null to + /// indicate an error. + /// + public long? Int64Value; + + /// + /// The value to be returned from the + /// method. + /// + public string StringValue; + + /// + /// The value to be returned from the + /// method. + /// + public object Value; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This class represents the parameters that are provided + /// to the methods, with + /// the exception of the column index (provided separately). + /// + public abstract class SQLiteReadEventArgs : EventArgs + { + // nothing. + } + + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This class represents the parameters that are provided to + /// the method, with + /// the exception of the column index (provided separately). + /// + public class SQLiteReadBlobEventArgs : SQLiteReadEventArgs + { + #region Private Data + /// + /// Provides the underlying storage for the + /// property. + /// + private bool readOnly; + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs an instance of this class to pass into a user-defined + /// callback associated with the + /// method. + /// + /// + /// The value that was originally specified for the "readOnly" + /// parameter to the method. + /// + internal SQLiteReadBlobEventArgs( + bool readOnly + ) + { + this.readOnly = readOnly; + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Public Properties + /// + /// The value that was originally specified for the "readOnly" + /// parameter to the method. + /// + public bool ReadOnly + { + get { return readOnly; } + set { readOnly = value; } + } + #endregion + } + + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This class represents the parameters that are provided + /// to the and + /// methods, with + /// the exception of the column index (provided separately). + /// + public class SQLiteReadArrayEventArgs : SQLiteReadEventArgs + { + #region Private Data + /// + /// Provides the underlying storage for the + /// property. + /// + private long dataOffset; + + /// + /// Provides the underlying storage for the + /// property. + /// + private byte[] byteBuffer; + + /// + /// Provides the underlying storage for the + /// property. + /// + private char[] charBuffer; + + /// + /// Provides the underlying storage for the + /// property. + /// + private int bufferOffset; + + /// + /// Provides the underlying storage for the + /// property. + /// + private int length; + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs an instance of this class to pass into a user-defined + /// callback associated with the + /// method. + /// + /// + /// The value that was originally specified for the "dataOffset" + /// parameter to the or + /// methods. + /// + /// + /// The value that was originally specified for the "buffer" + /// parameter to the + /// method. + /// + /// + /// The value that was originally specified for the "bufferOffset" + /// parameter to the or + /// methods. + /// + /// + /// The value that was originally specified for the "length" + /// parameter to the or + /// methods. + /// + internal SQLiteReadArrayEventArgs( + long dataOffset, + byte[] byteBuffer, + int bufferOffset, + int length + ) + { + this.dataOffset = dataOffset; + this.byteBuffer = byteBuffer; + this.bufferOffset = bufferOffset; + this.length = length; + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Constructs an instance of this class to pass into a user-defined + /// callback associated with the + /// method. + /// + /// + /// The value that was originally specified for the "dataOffset" + /// parameter to the or + /// methods. + /// + /// + /// The value that was originally specified for the "buffer" + /// parameter to the + /// method. + /// + /// + /// The value that was originally specified for the "bufferOffset" + /// parameter to the or + /// methods. + /// + /// + /// The value that was originally specified for the "length" + /// parameter to the or + /// methods. + /// + internal SQLiteReadArrayEventArgs( + long dataOffset, + char[] charBuffer, + int bufferOffset, + int length + ) + { + this.dataOffset = dataOffset; + this.charBuffer = charBuffer; + this.bufferOffset = bufferOffset; + this.length = length; + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Public Properties + /// + /// The value that was originally specified for the "dataOffset" + /// parameter to the or + /// methods. + /// + public long DataOffset + { + get { return dataOffset; } + set { dataOffset = value; } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// The value that was originally specified for the "buffer" + /// parameter to the + /// method. + /// + public byte[] ByteBuffer + { + get { return byteBuffer; } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// The value that was originally specified for the "buffer" + /// parameter to the + /// method. + /// + public char[] CharBuffer + { + get { return charBuffer; } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// The value that was originally specified for the "bufferOffset" + /// parameter to the or + /// methods. + /// + public int BufferOffset + { + get { return bufferOffset; } + set { bufferOffset = value; } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// The value that was originally specified for the "length" + /// parameter to the or + /// methods. + /// + public int Length + { + get { return length; } + set { length = value; } + } + #endregion + } + + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This class represents the parameters and return values for the + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , + /// , and + /// methods. + /// + public class SQLiteReadValueEventArgs : SQLiteReadEventArgs + { + #region Private Data + /// + /// Provides the underlying storage for the + /// property. + /// + private string methodName; + + /// + /// Provides the underlying storage for the + /// property. + /// + private SQLiteReadEventArgs extraEventArgs; + + /// + /// Provides the underlying storage for the + /// property. + /// + private SQLiteDataReaderValue value; + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs a new instance of this class. Depending on the method + /// being called, the and/or + /// parameters may be null. + /// + /// + /// The name of the method that was + /// responsible for invoking this callback. + /// + /// + /// If the or + /// method is being called, + /// this object will contain the array related parameters for that + /// method. If the method is + /// being called, this object will contain the blob related parameters + /// for that method. + /// + /// + /// This may be used by the callback to set the return value for the + /// called method. + /// + internal SQLiteReadValueEventArgs( + string methodName, + SQLiteReadEventArgs extraEventArgs, + SQLiteDataReaderValue value + ) + { + this.methodName = methodName; + this.extraEventArgs = extraEventArgs; + this.value = value; + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Public Properties + /// + /// The name of the method that was + /// responsible for invoking this callback. + /// + public string MethodName + { + get { return methodName; } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// If the or + /// method is being called, + /// this object will contain the array related parameters for that + /// method. If the method is + /// being called, this object will contain the blob related parameters + /// for that method. + /// + public SQLiteReadEventArgs ExtraEventArgs + { + get { return extraEventArgs; } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// This may be used by the callback to set the return value for the + /// called method. + /// + public SQLiteDataReaderValue Value + { + get { return value; } + } + #endregion + } + + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This represents a method that will be called in response to a request to + /// bind a parameter to a command. If an exception is thrown, it will cause + /// the parameter binding operation to fail -AND- it will continue to unwind + /// the call stack. + /// + /// + /// The instance in use. + /// + /// + /// The instance in use. + /// + /// + /// The flags associated with the instance + /// in use. + /// + /// + /// The instance being bound to the command. + /// + /// + /// The database type name associated with this callback. + /// + /// + /// The ordinal of the parameter being bound to the command. + /// + /// + /// The data originally used when registering this callback. + /// + /// + /// Non-zero if the default handling for the parameter binding call should + /// be skipped (i.e. the parameter should not be bound at all). Great care + /// should be used when setting this to non-zero. + /// + public delegate void SQLiteBindValueCallback( + SQLiteConvert convert, + SQLiteCommand command, + SQLiteConnectionFlags flags, + SQLiteParameter parameter, + string typeName, + int index, + object userData, + out bool complete + ); + + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This represents a method that will be called in response to a request + /// to read a value from a data reader. If an exception is thrown, it will + /// cause the data reader operation to fail -AND- it will continue to unwind + /// the call stack. + /// + /// + /// The instance in use. + /// + /// + /// The instance in use. + /// + /// + /// The flags associated with the instance + /// in use. + /// + /// + /// The parameter and return type data for the column being read from the + /// data reader. + /// + /// + /// The database type name associated with this callback. + /// + /// + /// The zero based index of the column being read from the data reader. + /// + /// + /// The data originally used when registering this callback. + /// + /// + /// Non-zero if the default handling for the data reader call should be + /// skipped. If this is set to non-zero and the necessary return value + /// is unavailable or unsuitable, an exception will be thrown. + /// + public delegate void SQLiteReadValueCallback( + SQLiteConvert convert, + SQLiteDataReader dataReader, + SQLiteConnectionFlags flags, + SQLiteReadEventArgs eventArgs, + string typeName, + int index, + object userData, + out bool complete + ); + + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This class represents the custom data type handling callbacks + /// for a single type name. + /// + public sealed class SQLiteTypeCallbacks + { + #region Private Data + /// + /// Provides the underlying storage for the + /// property. + /// + private string typeName; + + /// + /// Provides the underlying storage for the + /// property. + /// + private SQLiteBindValueCallback bindValueCallback; + + /// + /// Provides the underlying storage for the + /// property. + /// + private SQLiteReadValueCallback readValueCallback; + + /// + /// Provides the underlying storage for the + /// property. + /// + private object bindValueUserData; + + /// + /// Provides the underlying storage for the + /// property. + /// + private object readValueUserData; + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs an instance of this class. + /// + /// + /// The custom paramater binding callback. This parameter may be null. + /// + /// + /// The custom data reader value callback. This parameter may be null. + /// + /// + /// The extra data to pass into the parameter binding callback. This + /// parameter may be null. + /// + /// + /// The extra data to pass into the data reader value callback. This + /// parameter may be null. + /// + private SQLiteTypeCallbacks( + SQLiteBindValueCallback bindValueCallback, + SQLiteReadValueCallback readValueCallback, + object bindValueUserData, + object readValueUserData + ) + { + this.bindValueCallback = bindValueCallback; + this.readValueCallback = readValueCallback; + this.bindValueUserData = bindValueUserData; + this.readValueUserData = readValueUserData; + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Static "Factory" Methods + /// + /// Creates an instance of the class. + /// + /// + /// The custom paramater binding callback. This parameter may be null. + /// + /// + /// The custom data reader value callback. This parameter may be null. + /// + /// + /// The extra data to pass into the parameter binding callback. This + /// parameter may be null. + /// + /// + /// The extra data to pass into the data reader value callback. This + /// parameter may be null. + /// + public static SQLiteTypeCallbacks Create( + SQLiteBindValueCallback bindValueCallback, + SQLiteReadValueCallback readValueCallback, + object bindValueUserData, + object readValueUserData + ) + { + return new SQLiteTypeCallbacks( + bindValueCallback, readValueCallback, bindValueUserData, + readValueUserData); + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Public Properties + /// + /// The database type name that the callbacks contained in this class + /// will apply to. This value may not be null. + /// + public string TypeName + { + get { return typeName; } + internal set { typeName = value; } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// The custom paramater binding callback. This value may be null. + /// + public SQLiteBindValueCallback BindValueCallback + { + get { return bindValueCallback; } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// The custom data reader value callback. This value may be null. + /// + public SQLiteReadValueCallback ReadValueCallback + { + get { return readValueCallback; } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// The extra data to pass into the parameter binding callback. This + /// value may be null. + /// + public object BindValueUserData + { + get { return bindValueUserData; } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// The extra data to pass into the data reader value callback. This + /// value may be null. + /// + public object ReadValueUserData + { + get { return readValueUserData; } + } + #endregion + } + + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This class represents the mappings between database type names + /// and their associated custom data type handling callbacks. + /// + internal sealed class SQLiteTypeCallbacksMap + : Dictionary + { + /// + /// Constructs an (empty) instance of this class. + /// + public SQLiteTypeCallbacksMap() + : base(new TypeNameStringComparer()) + { + // do nothing. + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Event data for connection event handlers. + /// + public class ConnectionEventArgs : EventArgs + { + /// + /// The type of event being raised. + /// + public readonly SQLiteConnectionEventType EventType; + + /// + /// The associated with this event, if any. + /// + public readonly StateChangeEventArgs EventArgs; + + /// + /// The transaction associated with this event, if any. + /// + public readonly IDbTransaction Transaction; + + /// + /// The command associated with this event, if any. + /// + public readonly IDbCommand Command; + + /// + /// The data reader associated with this event, if any. + /// + public readonly IDataReader DataReader; + + /// + /// The critical handle associated with this event, if any. + /// +#if !PLATFORM_COMPACTFRAMEWORK + public readonly CriticalHandle CriticalHandle; +#else + public readonly object CriticalHandle; +#endif + + /// + /// Command or message text associated with this event, if any. + /// + public readonly string Text; + + /// + /// Extra data associated with this event, if any. + /// + public readonly object Data; + + /// + /// Constructs the object. + /// + /// The type of event being raised. + /// The base associated + /// with this event, if any. + /// The transaction associated with this event, if any. + /// The command associated with this event, if any. + /// The data reader associated with this event, if any. + /// The critical handle associated with this event, if any. + /// The command or message text, if any. + /// The extra data, if any. + internal ConnectionEventArgs( + SQLiteConnectionEventType eventType, + StateChangeEventArgs eventArgs, + IDbTransaction transaction, + IDbCommand command, + IDataReader dataReader, +#if !PLATFORM_COMPACTFRAMEWORK + CriticalHandle criticalHandle, +#else + object criticalHandle, +#endif + string text, + object data + ) + { + EventType = eventType; + EventArgs = eventArgs; + Transaction = transaction; + Command = command; + DataReader = dataReader; + CriticalHandle = criticalHandle; + Text = text; + Data = data; + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Raised when an event pertaining to a connection occurs. + /// + /// The connection involved. + /// Extra information about the event. + public delegate void SQLiteConnectionEventHandler(object sender, ConnectionEventArgs e); + + ///////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// SQLite implentation of DbConnection. + /// + /// + /// The property can contain the following parameter(s), delimited with a semi-colon: + /// + /// + /// Parameter + /// Values + /// Required + /// Default + /// + /// + /// Data Source + /// + /// This may be a file name, the string ":memory:", or any supported URI (starting with SQLite 3.7.7). + /// Starting with release 1.0.86.0, in order to use more than one consecutive backslash (e.g. for a + /// UNC path), each of the adjoining backslash characters must be doubled (e.g. "\\Network\Share\test.db" + /// would become "\\\\Network\Share\test.db"). + /// + /// Y + /// + /// + /// + /// Uri + /// + /// If specified, this must be a file name that starts with "file://", "file:", or "/". Any leading + /// "file://" or "file:" prefix will be stripped off and the resulting file name will be used to open + /// the database. + /// + /// N + /// null + /// + /// + /// FullUri + /// + /// If specified, this must be a URI in a format recognized by the SQLite core library (starting with + /// SQLite 3.7.7). It will be passed verbatim to the SQLite core library. + /// + /// N + /// null + /// + /// + /// Version + /// 3 + /// N + /// 3 + /// + /// + /// UseUTF16Encoding + /// + /// True - The UTF-16 encoding should be used. + ///
+ /// False - The UTF-8 encoding should be used. + ///
+ /// N + /// False + ///
+ /// + /// DefaultDbType + /// + /// This is the default to use when one cannot be determined based on the + /// column metadata and the configured type mappings. + /// + /// N + /// null + /// + /// + /// DefaultTypeName + /// + /// This is the default type name to use when one cannot be determined based on the column metadata + /// and the configured type mappings. + /// + /// N + /// null + /// + /// + /// NoDefaultFlags + /// + /// True - Do not combine the specified (or existing) connection flags with the value of the + /// property. + ///
+ /// False - Combine the specified (or existing) connection flags with the value of the + /// property. + ///
+ /// N + /// False + ///
+ /// + /// NoSharedFlags + /// + /// True - Do not combine the specified (or existing) connection flags with the value of the + /// property. + ///
+ /// False - Combine the specified (or existing) connection flags with the value of the + /// property. + ///
+ /// N + /// False + ///
+ /// + /// VfsName + /// + /// The name of the VFS to use when opening the database connection. + /// If this is not specified, the default VFS will be used. + /// + /// N + /// null + /// + /// + /// ZipVfsVersion + /// + /// If non-null, this is the "version" of ZipVFS to use. This requires + /// the System.Data.SQLite interop assembly -AND- primary managed assembly + /// to be compiled with the INTEROP_INCLUDE_ZIPVFS option; otherwise, this + /// property does nothing. The valid values are "v2" and "v3". Using + /// anyother value will cause an exception to be thrown. Please see the + /// ZipVFS documentation for more information on how to use this parameter. + /// + /// N + /// null + /// + /// + /// DateTimeFormat + /// + /// Ticks - Use the value of DateTime.Ticks.
+ /// ISO8601 - Use the ISO-8601 format. Uses the "yyyy-MM-dd HH:mm:ss.FFFFFFFK" format for UTC + /// DateTime values and "yyyy-MM-dd HH:mm:ss.FFFFFFF" format for local DateTime values).
+ /// JulianDay - The interval of time in days and fractions of a day since January 1, 4713 BC.
+ /// UnixEpoch - The whole number of seconds since the Unix epoch (January 1, 1970).
+ /// InvariantCulture - Any culture-independent string value that the .NET Framework can interpret as a valid DateTime.
+ /// CurrentCulture - Any string value that the .NET Framework can interpret as a valid DateTime using the current culture.
+ /// N + /// ISO8601 + ///
+ /// + /// DateTimeKind + /// + /// Unspecified - Not specified as either UTC or local time. + ///
+ /// Utc - The time represented is UTC. + ///
+ /// Local - The time represented is local time. + ///
+ /// N + /// Unspecified + ///
+ /// + /// DateTimeFormatString + /// + /// The exact DateTime format string to use for all formatting and parsing of all DateTime + /// values for this connection. + /// + /// N + /// null + /// + /// + /// BaseSchemaName + /// + /// Some base data classes in the framework (e.g. those that build SQL queries dynamically) + /// assume that an ADO.NET provider cannot support an alternate catalog (i.e. database) without supporting + /// alternate schemas as well; however, SQLite does not fit into this model. Therefore, this value is used + /// as a placeholder and removed prior to preparing any SQL statements that may contain it. + /// + /// N + /// sqlite_default_schema + /// + /// + /// BinaryGUID + /// + /// True - Store GUID columns in binary form + ///
+ /// False - Store GUID columns as text + ///
+ /// N + /// True + ///
+ /// + /// Cache Size + /// + /// If the argument N is positive then the suggested cache size is set to N. + /// If the argument N is negative, then the number of cache pages is adjusted + /// to use approximately abs(N*4096) bytes of memory. Backwards compatibility + /// note: The behavior of cache_size with a negative N was different in SQLite + /// versions prior to 3.7.10. In version 3.7.9 and earlier, the number of + /// pages in the cache was set to the absolute value of N. + /// + /// N + /// -2000 + /// + /// + /// Synchronous + /// + /// Normal - Normal file flushing behavior + ///
+ /// Full - Full flushing after all writes + ///
+ /// Off - Underlying OS flushes I/O's + ///
+ /// N + /// Full + ///
+ /// + /// Page Size + /// {size in bytes} + /// N + /// 4096 + /// + /// + /// Password + /// + /// {password} - Using this parameter requires that the legacy CryptoAPI based + /// codec (or the SQLite Encryption Extension) be enabled at compile-time for + /// both the native interop assembly and the core managed assemblies; otherwise, + /// using this parameter may result in an exception being thrown when attempting + /// to open the connection. + /// + /// N + /// + /// + /// + /// HexPassword + /// + /// {hexPassword} - Must contain a sequence of zero or more hexadecimal encoded + /// byte values without a leading "0x" prefix. Using this parameter requires + /// that the legacy CryptoAPI based codec (or the SQLite Encryption Extension) + /// be enabled at compile-time for both the native interop assembly and the + /// core managed assemblies; otherwise, using this parameter may result in an + /// exception being thrown when attempting to open the connection. + /// + /// N + /// + /// + /// + /// Enlist + /// + /// Y - Automatically enlist in distributed transactions + ///
+ /// N - No automatic enlistment + ///
+ /// N + /// Y + ///
+ /// + /// Pooling + /// + /// True - Use connection pooling.
+ /// False - Do not use connection pooling.

+ /// WARNING: When using the default connection pool implementation, + /// setting this property to True should be avoided by applications that make + /// use of COM (either directly or indirectly) due to possible deadlocks that + /// can occur during the finalization of some COM objects. + ///
+ /// N + /// False + ///
+ /// + /// FailIfMissing + /// + /// True - Don't create the database if it does not exist, throw an error instead + ///
+ /// False - Automatically create the database if it does not exist + ///
+ /// N + /// False + ///
+ /// + /// Max Page Count + /// {size in pages} - Limits the maximum number of pages (limits the size) of the database + /// N + /// 0 + /// + /// + /// Legacy Format + /// + /// True - Use the more compatible legacy 3.x database format + ///
+ /// False - Use the newer 3.3x database format which compresses numbers more effectively + ///
+ /// N + /// False + ///
+ /// + /// Default Timeout + /// {time in seconds}
The default command timeout
+ /// N + /// 30 + ///
+ /// + /// BusyTimeout + /// {time in milliseconds}
Sets the busy timeout for the core library.
+ /// N + /// 0 + ///
+ /// + /// WaitTimeout + /// {time in milliseconds}
+ /// EXPERIMENTAL -- The wait timeout to use with + /// method. This is only used when + /// waiting for the enlistment to be reset prior to enlisting in a transaction, + /// and then only when the appropriate connection flag is set.
+ /// N + /// 30000 + ///
+ /// + /// Journal Mode + /// + /// Delete - Delete the journal file after a commit. + ///
+ /// Persist - Zero out and leave the journal file on disk after a + /// commit. + ///
+ /// Off - Disable the rollback journal entirely. This saves disk I/O + /// but at the expense of database safety and integrity. If the application + /// using SQLite crashes in the middle of a transaction when this journaling + /// mode is set, then the database file will very likely go corrupt. + ///
+ /// Truncate - Truncate the journal file to zero-length instead of + /// deleting it. + ///
+ /// Memory - Store the journal in volatile RAM. This saves disk I/O + /// but at the expense of database safety and integrity. If the application + /// using SQLite crashes in the middle of a transaction when this journaling + /// mode is set, then the database file will very likely go corrupt. + ///
+ /// Wal - Use a write-ahead log instead of a rollback journal. + ///
+ /// N + /// Delete + ///
+ /// + /// Read Only + /// + /// True - Open the database for read only access + ///
+ /// False - Open the database for normal read/write access + ///
+ /// N + /// False + ///
+ /// + /// Max Pool Size + /// The maximum number of connections for the given connection string that can be in the connection pool + /// N + /// 100 + /// + /// + /// Default IsolationLevel + /// The default transaciton isolation level + /// N + /// Serializable + /// + /// + /// Foreign Keys + /// Enable foreign key constraints + /// N + /// False + /// + /// + /// Flags + /// Extra behavioral flags for the connection. See the enumeration for possible values. + /// N + /// Default + /// + /// + /// SetDefaults + /// + /// True - Apply the default connection settings to the opened database.
+ /// False - Skip applying the default connection settings to the opened database. + ///
+ /// N + /// True + ///
+ /// + /// ToFullPath + /// + /// True - Attempt to expand the data source file name to a fully qualified path before opening. + ///
+ /// False - Skip attempting to expand the data source file name to a fully qualified path before opening. + ///
+ /// N + /// True + ///
+ /// + /// PrepareRetries + /// + /// The maximum number of retries when preparing SQL to be executed. This + /// normally only applies to preparation errors resulting from the database + /// schema being changed. + /// + /// N + /// 3 + /// + /// + /// ProgressOps + /// + /// The approximate number of virtual machine instructions between progress + /// events. In order for progress events to actually fire, the event handler + /// must be added to the event as well. + /// + /// N + /// 0 + /// + /// + /// Recursive Triggers + /// + /// True - Enable the recursive trigger capability. + /// False - Disable the recursive trigger capability. + /// + /// N + /// False + /// + ///
+ ///
+ public sealed partial class SQLiteConnection : DbConnection, ICloneable, IDisposable + { + #region Private Constants + /// + /// The "invalid value" for the enumeration used + /// by the property. This constant is shared + /// by this class and the SQLiteConnectionStringBuilder class. + /// + internal const DbType BadDbType = (DbType)(-1); + + /// + /// The default "stub" (i.e. placeholder) base schema name to use when + /// returning column schema information. Used as the initial value of + /// the BaseSchemaName property. This should start with "sqlite_*" + /// because those names are reserved for use by SQLite (i.e. they cannot + /// be confused with the names of user objects). + /// + internal const string DefaultBaseSchemaName = "sqlite_default_schema"; + + private const string MemoryFileName = ":memory:"; + + internal const IsolationLevel DeferredIsolationLevel = IsolationLevel.ReadCommitted; + internal const IsolationLevel ImmediateIsolationLevel = IsolationLevel.Serializable; + + private const SQLiteConnectionFlags FallbackDefaultFlags = SQLiteConnectionFlags.Default; + private const SQLiteSynchronousEnum DefaultSynchronous = SQLiteSynchronousEnum.Default; + private const SQLiteJournalModeEnum DefaultJournalMode = SQLiteJournalModeEnum.Default; + private const IsolationLevel DefaultIsolationLevel = IsolationLevel.Serializable; + internal const SQLiteDateFormats DefaultDateTimeFormat = SQLiteDateFormats.Default; + internal const DateTimeKind DefaultDateTimeKind = DateTimeKind.Unspecified; + internal const string DefaultDateTimeFormatString = null; + private const string DefaultDataSource = null; + private const string DefaultUri = null; + private const string DefaultFullUri = null; + private const string DefaultHexPassword = null; + private const string DefaultPassword = null; + private const int DefaultVersion = 3; + private const int DefaultPageSize = 4096; + private const int DefaultMaxPageCount = 0; + private const int DefaultCacheSize = -2000; + private const int DefaultMaxPoolSize = 100; + private const int DefaultConnectionTimeout = 30; + private const int DefaultBusyTimeout = 0; + private const int DefaultWaitTimeout = 30000; + private const bool DefaultNoDefaultFlags = false; + private const bool DefaultNoSharedFlags = false; + private const bool DefaultFailIfMissing = false; + private const bool DefaultReadOnly = false; + internal const bool DefaultBinaryGUID = true; + private const bool DefaultUseUTF16Encoding = false; + private const bool DefaultToFullPath = true; + private const bool DefaultPooling = false; // TODO: Maybe promote this to static property? + private const bool DefaultLegacyFormat = false; + private const bool DefaultForeignKeys = false; + private const bool DefaultRecursiveTriggers = false; + private const bool DefaultEnlist = true; + private const bool DefaultSetDefaults = true; + internal const int DefaultPrepareRetries = 3; + private const string DefaultVfsName = null; + private const int DefaultProgressOps = 0; + +#if INTEROP_INCLUDE_ZIPVFS + private const string ZipVfs_Automatic = "automatic"; + private const string ZipVfs_V2 = "v2"; + private const string ZipVfs_V3 = "v3"; + + private const string DefaultZipVfsVersion = null; +#endif + + private const int SQLITE_FCNTL_CHUNK_SIZE = 6; + private const int SQLITE_FCNTL_WIN32_AV_RETRY = 9; + + private const string _dataDirectory = "|DataDirectory|"; + + private static string _defaultCatalogName = "main"; + private static string _defaultMasterTableName = "sqlite_master"; + + private static string _temporaryCatalogName = "temp"; + private static string _temporaryMasterTableName = "sqlite_temp_master"; + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Private Static Data + /// + /// The managed assembly containing this type. + /// + private static readonly Assembly _assembly = typeof(SQLiteConnection).Assembly; + + /// + /// Object used to synchronize access to the static instance data + /// for this class. + /// + private static readonly object _syncRoot = new object(); + + /// + /// Static variable to store the connection event handlers to call. + /// + private static event SQLiteConnectionEventHandler _handlers; + + /// + /// The extra connection flags to be used for all opened connections. + /// + private static SQLiteConnectionFlags _sharedFlags; + + /// + /// The instance (for this thread) that + /// had the most recent call to . + /// +#if !PLATFORM_COMPACTFRAMEWORK + [ThreadStatic()] +#endif + private static SQLiteConnection _lastConnectionInOpen; + +#if SQLITE_STANDARD && !PLATFORM_COMPACTFRAMEWORK + /// + /// Used to hold the active library version number of SQLite. + /// + private static int _versionNumber; +#endif + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Private Data + /// + /// State of the current connection + /// + private ConnectionState _connectionState; + + /// + /// The connection string + /// + private string _connectionString; + +#if DEBUG + /// + /// This string will contain enough information to identify this connection, + /// e.g. the database file name, original thread, etc. It is not currently + /// exposed via the public interface as it is intended for use only when + /// debugging this library. + /// + private string _debugString; +#endif + + /// + /// Nesting level of the transactions open on the connection + /// + internal int _transactionLevel; + + /// + /// Transaction counter for the connection. Currently, this is only used + /// to build SAVEPOINT names. + /// + internal int _transactionSequence; + + /// + /// If this flag is non-zero, the method will have + /// no effect; however, the method will continue to + /// behave as normal. + /// + internal bool _noDispose; + + /// + /// If set, then the connection is currently being disposed. + /// + private bool _disposing; + + /// + /// The default isolation level for new transactions + /// + private IsolationLevel _defaultIsolation; + +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// This object is used with lock statements to synchronize access to the + /// field, below. + /// + internal readonly object _enlistmentSyncRoot = new object(); + + /// + /// Whether or not the connection is enlisted in a distrubuted transaction + /// + internal SQLiteEnlistment _enlistment; +#endif + + /// + /// The per-connection mappings between type names and + /// values. These mappings override the corresponding global mappings. + /// + internal SQLiteDbTypeMap _typeNames; + + /// + /// The per-connection mappings between type names and optional callbacks + /// for parameter binding and value reading. + /// + private SQLiteTypeCallbacksMap _typeCallbacks; + + /// + /// The base SQLite object to interop with + /// + internal SQLiteBase _sql; + /// + /// The database filename minus path and extension + /// + private string _dataSource; + +#if INTEROP_CODEC || INTEROP_INCLUDE_SEE + /// + /// Temporary password storage, emptied after the database has been opened + /// + private byte[] _password; +#endif + + /// + /// The "stub" (i.e. placeholder) base schema name to use when returning + /// column schema information. + /// + internal string _baseSchemaName; + + /// + /// The extra behavioral flags for this connection, if any. See the + /// enumeration for a list of + /// possible values. + /// + private SQLiteConnectionFlags _flags; + + /// + /// The cached values for all settings that have been fetched on behalf + /// of this connection. This cache may be cleared by calling the + /// method. + /// + private Dictionary _cachedSettings; + + /// + /// The default databse type for this connection. This value will only + /// be used if the + /// flag is set. + /// + private DbType? _defaultDbType; + + /// + /// The default databse type name for this connection. This value will only + /// be used if the + /// flag is set. + /// + private string _defaultTypeName; + + /// + /// The name of the VFS to be used when opening the database connection. + /// + private string _vfsName; + + /// + /// Default command timeout + /// + private int _defaultTimeout = DefaultConnectionTimeout; + + /// + /// The default busy timeout to use with the SQLite core library. This is + /// only used when opening a connection. + /// + private int _busyTimeout = DefaultBusyTimeout; + +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// The default wait timeout to use with + /// method. This is only used when waiting for the enlistment to be reset + /// prior to enlisting in a transaction, and then only when the appropriate + /// connection flag is set. + /// + private int _waitTimeout = DefaultWaitTimeout; +#endif + + /// + /// The maximum number of retries when preparing SQL to be executed. This + /// normally only applies to preparation errors resulting from the database + /// schema being changed. + /// + internal int _prepareRetries = DefaultPrepareRetries; + + /// + /// The approximate number of virtual machine instructions between progress + /// events. In order for progress events to actually fire, the event handler + /// must be added to the event as + /// well. This value will only be used when opening the database. + /// + private int _progressOps = DefaultProgressOps; + + /// + /// Non-zero if the built-in (i.e. framework provided) connection string + /// parser should be used when opening the connection. + /// + private bool _parseViaFramework; + + internal bool _binaryGuid; + + internal int _version; + + private event SQLiteProgressEventHandler _progressHandler; + private event SQLiteAuthorizerEventHandler _authorizerHandler; + private event SQLiteUpdateEventHandler _updateHandler; + private event SQLiteCommitHandler _commitHandler; + private event SQLiteTraceEventHandler _traceHandler; + private event EventHandler _rollbackHandler; + + private SQLiteProgressCallback _progressCallback; + private SQLiteAuthorizerCallback _authorizerCallback; + private SQLiteUpdateCallback _updateCallback; + private SQLiteCommitCallback _commitCallback; + private SQLiteTraceCallback _traceCallback; + private SQLiteRollbackCallback _rollbackCallback; + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private static string GetDefaultCatalogName() + { + return _defaultCatalogName; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private static bool IsDefaultCatalogName( + string catalogName + ) + { + return String.Compare(catalogName, GetDefaultCatalogName(), + StringComparison.OrdinalIgnoreCase) == 0; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private static string GetTemporaryCatalogName() + { + return _temporaryCatalogName; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private static bool IsTemporaryCatalogName( + string catalogName + ) + { + return String.Compare(catalogName, GetTemporaryCatalogName(), + StringComparison.OrdinalIgnoreCase) == 0; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private static string GetMasterTableName( + bool temporary + ) + { + return temporary ? _temporaryMasterTableName : _defaultMasterTableName; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This event is raised whenever the database is opened or closed. + /// + public override event StateChangeEventHandler StateChange; + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Constructs a new SQLiteConnection object + /// + /// + /// Default constructor + /// + public SQLiteConnection() + : this((string)null) + { + } + + /// + /// Initializes the connection with the specified connection string. + /// + /// The connection string to use. + public SQLiteConnection(string connectionString) + : this(connectionString, false) + { + // do nothing. + } + +#if INTEROP_VIRTUAL_TABLE + /// + /// Initializes the connection with a pre-existing native connection handle. + /// This constructor overload is intended to be used only by the private + /// method. + /// + /// + /// The native connection handle to use. + /// + /// + /// The file name corresponding to the native connection handle. + /// + /// + /// Non-zero if this instance owns the native connection handle and + /// should dispose of it when it is no longer needed. + /// + internal SQLiteConnection(IntPtr db, string fileName, bool ownHandle) + : this() + { + _sql = new SQLite3( + SQLiteDateFormats.Default, DateTimeKind.Unspecified, null, + db, fileName, ownHandle); + + _flags = SQLiteConnectionFlags.None; + + _connectionState = (db != IntPtr.Zero) ? + ConnectionState.Open : ConnectionState.Closed; + + _connectionString = null; /* unknown */ + +#if DEBUG + _debugString = HelperMethods.StringFormat( + CultureInfo.InvariantCulture, + "db = {0}, fileName = {1}, ownHandle = {2}", + db, fileName, ownHandle); +#endif + } +#endif + + /// + /// Initializes the connection with the specified connection string. + /// + /// + /// The connection string to use. + /// + /// + /// Non-zero to parse the connection string using the built-in (i.e. + /// framework provided) parser when opening the connection. + /// + public SQLiteConnection(string connectionString, bool parseViaFramework) + { + _noDispose = false; + +#if (SQLITE_STANDARD || USE_INTEROP_DLL || PLATFORM_COMPACTFRAMEWORK) && PRELOAD_NATIVE_LIBRARY + UnsafeNativeMethods.Initialize(); +#endif + + SQLiteLog.Initialize(typeof(SQLiteConnection).Name); + +#if !PLATFORM_COMPACTFRAMEWORK && !INTEROP_LEGACY_CLOSE && SQLITE_STANDARD + // + // NOTE: Check if the sqlite3_close_v2() native API should be available + // to use. This must be done dynamically because the delegate set + // here is used by the SQLiteConnectionHandle class, which is a + // CriticalHandle derived class (i.e. protected by a constrained + // execution region). Therefore, if the underlying native entry + // point is unavailable, an exception will be raised even if it is + // never actually called (i.e. because the runtime eagerly prepares + // all the methods in the call graph of the constrained execution + // region). + // + lock (_syncRoot) + { + if (_versionNumber == 0) + { + _versionNumber = SQLite3.SQLiteVersionNumber; + + if (_versionNumber >= 3007014) + SQLiteConnectionHandle.closeConnection = SQLiteBase.CloseConnectionV2; + } + } +#endif + + _cachedSettings = new Dictionary( + new TypeNameStringComparer()); + + _typeNames = new SQLiteDbTypeMap(); + _typeCallbacks = new SQLiteTypeCallbacksMap(); + _parseViaFramework = parseViaFramework; + _flags = SQLiteConnectionFlags.None; + _defaultDbType = null; + _defaultTypeName = null; + _vfsName = null; + _connectionState = ConnectionState.Closed; + _connectionString = null; + + if (connectionString != null) + ConnectionString = connectionString; + } + + /// + /// Clones the settings and connection string from an existing connection. If the existing connection is already open, this + /// function will open its own connection, enumerate any attached databases of the original connection, and automatically + /// attach to them. + /// + /// The connection to copy the settings from. + public SQLiteConnection(SQLiteConnection connection) + : this(connection.ConnectionString, connection.ParseViaFramework) + { +#if DEBUG + _debugString = connection._debugString; +#endif + + if (connection.State == ConnectionState.Open) + { + Open(); + + // Reattach all attached databases from the existing connection + using (DataTable tbl = connection.GetSchema("Catalogs")) + { + foreach (DataRow row in tbl.Rows) + { + string str = row[0].ToString(); + + if (!IsDefaultCatalogName(str) && !IsTemporaryCatalogName(str)) + { + using (SQLiteCommand cmd = CreateCommand()) + { + cmd.CommandText = HelperMethods.StringFormat(CultureInfo.InvariantCulture, "ATTACH DATABASE '{0}' AS [{1}]", row[1], row[0]); + cmd.ExecuteNonQuery(); + } + } + } + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to lookup the native handle associated with the connection. An exception will + /// be thrown if this cannot be accomplished. + /// + /// + /// The connection associated with the desired native handle. + /// + /// + /// The native handle associated with the connection or if it + /// cannot be determined. + /// + private static SQLiteConnectionHandle GetNativeHandle( + SQLiteConnection connection + ) + { + if (connection == null) + throw new ArgumentNullException("connection"); + + SQLite3 sqlite3 = connection._sql as SQLite3; + + if (sqlite3 == null) + throw new InvalidOperationException("Connection has no wrapper"); + + SQLiteConnectionHandle handle = sqlite3._sql; + + if (handle == null) + throw new InvalidOperationException("Connection has an invalid handle."); + + IntPtr handlePtr = handle; + + if (handlePtr == IntPtr.Zero) + { + throw new InvalidOperationException( + "Connection has an invalid handle pointer."); + } + + return handle; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Raises the event. + /// + /// + /// The connection associated with this event. If this parameter is not + /// null and the specified connection cannot raise events, then the + /// registered event handlers will not be invoked. + /// + /// + /// A that contains the event data. + /// + internal static void OnChanged( + SQLiteConnection connection, + ConnectionEventArgs e + ) + { +#if !PLATFORM_COMPACTFRAMEWORK + if ((connection != null) && !connection.CanRaiseEvents) + return; +#endif + + SQLiteConnectionEventHandler handlers; + + lock (_syncRoot) + { + if (_handlers != null) + handlers = _handlers.Clone() as SQLiteConnectionEventHandler; + else + handlers = null; + } + + if (handlers != null) handlers(connection, e); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This event is raised when events related to the lifecycle of a + /// SQLiteConnection object occur. + /// + public static event SQLiteConnectionEventHandler Changed + { + add + { + lock (_syncRoot) + { + // Remove any copies of this event handler from registered + // list. This essentially means that a handler will be + // called only once no matter how many times it is added. + _handlers -= value; + + // Add this to the list of event handlers. + _handlers += value; + } + } + remove + { + lock (_syncRoot) + { + _handlers -= value; + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This property is used to obtain or set the custom connection pool + /// implementation to use, if any. Setting this property to null will + /// cause the default connection pool implementation to be used. + /// + public static ISQLiteConnectionPool ConnectionPool + { + get { return SQLiteConnectionPool.GetConnectionPool(); } + set { SQLiteConnectionPool.SetConnectionPool(value); } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Creates and returns a new managed database connection handle. This + /// method is intended to be used by implementations of the + /// interface only. In theory, it + /// could be used by other classes; however, that usage is not supported. + /// + /// + /// This must be a native database connection handle returned by the + /// SQLite core library and it must remain valid and open during the + /// entire duration of the calling method. + /// + /// + /// The new managed database connection handle or null if it cannot be + /// created. + /// + public static object CreateHandle( + IntPtr nativeHandle + ) + { + SQLiteConnectionHandle result; + + try + { + // do nothing. + } + finally /* NOTE: Thread.Abort() protection. */ + { + result = (nativeHandle != IntPtr.Zero) ? + new SQLiteConnectionHandle(nativeHandle, true) : null; + } + + if (result != null) + { + SQLiteConnection.OnChanged(null, new ConnectionEventArgs( + SQLiteConnectionEventType.NewCriticalHandle, null, + null, null, null, result, null, new object[] { + typeof(SQLiteConnection), nativeHandle })); + } + + return result; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Backup API Members + /// + /// Backs up the database, using the specified database connection as the + /// destination. + /// + /// The destination database connection. + /// The destination database name. + /// The source database name. + /// + /// The number of pages to copy at a time -OR- a negative value to copy all + /// pages. When a negative value is used, the + /// may never be invoked. + /// + /// + /// The method to invoke between each step of the backup process. This + /// parameter may be null (i.e. no callbacks will be performed). If the + /// callback returns false -OR- throws an exception, the backup is canceled. + /// + /// + /// The number of milliseconds to sleep after encountering a locking error + /// during the backup process. A value less than zero means that no sleep + /// should be performed. + /// + public void BackupDatabase( + SQLiteConnection destination, + string destinationName, + string sourceName, + int pages, + SQLiteBackupCallback callback, + int retryMilliseconds + ) + { + CheckDisposed(); + + if (_connectionState != ConnectionState.Open) + throw new InvalidOperationException( + "Source database is not open."); + + if (destination == null) + throw new ArgumentNullException("destination"); + + if (destination._connectionState != ConnectionState.Open) + throw new ArgumentException( + "Destination database is not open.", "destination"); + + if (destinationName == null) + throw new ArgumentNullException("destinationName"); + + if (sourceName == null) + throw new ArgumentNullException("sourceName"); + + SQLiteBase sqliteBase = _sql; + + if (sqliteBase == null) + throw new InvalidOperationException( + "Connection object has an invalid handle."); + + SQLiteBackup backup = null; + + try + { + backup = sqliteBase.InitializeBackup( + destination, destinationName, sourceName); /* throw */ + + bool retry = false; + + while (sqliteBase.StepBackup(backup, pages, ref retry)) /* throw */ + { + // + // NOTE: If a callback was supplied by our caller, call it. + // If it returns false, halt the backup process. + // + if ((callback != null) && !callback(this, sourceName, + destination, destinationName, pages, + sqliteBase.RemainingBackup(backup), + sqliteBase.PageCountBackup(backup), retry)) + { + break; + } + + // + // NOTE: If we need to retry the previous operation, wait for + // the number of milliseconds specified by our caller + // unless the caller used a negative number, in that case + // skip sleeping at all because we do not want to block + // this thread forever. + // + if (retry && (retryMilliseconds >= 0)) + Thread.Sleep(retryMilliseconds); + + // + // NOTE: There is no point in calling the native API to copy + // zero pages as it does nothing; therefore, stop now. + // + if (pages == 0) + break; + } + } + catch (Exception e) + { + if (HelperMethods.LogBackup(_flags)) + { + SQLiteLog.LogMessage(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Caught exception while backing up database: {0}", e)); + } + + throw; + } + finally + { + if (backup != null) + sqliteBase.FinishBackup(backup); /* throw */ + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Per-Connection Settings + /// + /// Clears the per-connection cached settings. + /// + /// + /// The total number of per-connection settings cleared. + /// + public int ClearCachedSettings() + { + CheckDisposed(); + + int result = -1; /* NO SETTINGS */ + + if (_cachedSettings != null) + { + result = _cachedSettings.Count; + _cachedSettings.Clear(); + } + + return result; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Queries and returns the value of the specified setting, using the + /// cached setting names and values for this connection, when available. + /// + /// + /// The name of the setting. + /// + /// + /// The value to be returned if the setting has not been set explicitly + /// or cannot be determined. + /// + /// + /// The value of the cached setting is stored here if found; otherwise, + /// the value of is stored here. + /// + /// + /// Non-zero if the cached setting was found; otherwise, zero. + /// + internal bool TryGetCachedSetting( + string name, /* in */ + object @default, /* in */ + out object value /* out */ + ) + { + if ((name == null) || (_cachedSettings == null)) + { + value = @default; + return false; + } + + return _cachedSettings.TryGetValue(name, out value); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Adds or sets the cached setting specified by + /// to the value specified by . + /// + /// + /// The name of the cached setting to add or replace. + /// + /// + /// The new value of the cached setting. + /// + internal void SetCachedSetting( + string name, /* in */ + object value /* in */ + ) + { + if ((name == null) || (_cachedSettings == null)) + return; + + _cachedSettings[name] = value; + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Per-Connection Type Mappings + /// + /// Clears the per-connection type mappings. + /// + /// + /// The total number of per-connection type mappings cleared. + /// + public int ClearTypeMappings() + { + CheckDisposed(); + + int result = -1; /* NO MAPPINGS */ + + if (_typeNames != null) + result = _typeNames.Clear(); + + return result; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Returns the per-connection type mappings. + /// + /// + /// The per-connection type mappings -OR- null if they are unavailable. + /// + public Dictionary GetTypeMappings() + { + CheckDisposed(); + + Dictionary result = null; + + if (_typeNames != null) + { + result = new Dictionary(_typeNames.Count, _typeNames.Comparer); + + foreach (KeyValuePair pair in _typeNames) + { + SQLiteDbTypeMapping mapping = pair.Value; + + object typeName = null; /* System.String */ + object dataType = null; /* System.Data.DbType */ + object primary = null; /* System.Boolean */ + + if (mapping != null) + { + typeName = mapping.typeName; + dataType = mapping.dataType; + primary = mapping.primary; + } + + result.Add(pair.Key, new object[] { typeName, dataType, primary }); + } + } + + return result; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Adds a per-connection type mapping, possibly replacing one or more + /// that already exist. + /// + /// + /// The case-insensitive database type name (e.g. "MYDATE"). The value + /// of this parameter cannot be null. Using an empty string value (or + /// a string value consisting entirely of whitespace) for this parameter + /// is not recommended. + /// + /// + /// The value that should be associated with the + /// specified type name. + /// + /// + /// Non-zero if this mapping should be considered to be the primary one + /// for the specified . + /// + /// + /// A negative value if nothing was done. Zero if no per-connection type + /// mappings were replaced (i.e. it was a pure add operation). More than + /// zero if some per-connection type mappings were replaced. + /// + public int AddTypeMapping( + string typeName, + DbType dataType, + bool primary + ) + { + CheckDisposed(); + + if (typeName == null) + throw new ArgumentNullException("typeName"); + + int result = -1; /* NO MAPPINGS */ + + if (_typeNames != null) + { + result = 0; + + if (primary && _typeNames.ContainsKey(dataType)) + result += _typeNames.Remove(dataType) ? 1 : 0; + + if (_typeNames.ContainsKey(typeName)) + result += _typeNames.Remove(typeName) ? 1 : 0; + + _typeNames.Add(new SQLiteDbTypeMapping(typeName, dataType, primary)); + } + + return result; + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Per-Connection Type Callbacks + /// + /// Clears the per-connection type callbacks. + /// + /// + /// The total number of per-connection type callbacks cleared. + /// + public int ClearTypeCallbacks() + { + CheckDisposed(); + + int result = -1; /* NO CALLBACKS */ + + if (_typeCallbacks != null) + { + result = _typeCallbacks.Count; + _typeCallbacks.Clear(); + } + + return result; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to get the per-connection type callbacks for the specified + /// database type name. + /// + /// + /// The database type name. + /// + /// + /// Upon success, this parameter will contain the object holding the + /// callbacks for the database type name. Upon failure, this parameter + /// will be null. + /// + /// + /// Non-zero upon success; otherwise, zero. + /// + public bool TryGetTypeCallbacks( + string typeName, + out SQLiteTypeCallbacks callbacks + ) + { + CheckDisposed(); + + if (typeName == null) + throw new ArgumentNullException("typeName"); + + if (_typeCallbacks == null) + { + callbacks = null; + return false; + } + + return _typeCallbacks.TryGetValue(typeName, out callbacks); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Sets, resets, or clears the per-connection type callbacks for the + /// specified database type name. + /// + /// + /// The database type name. + /// + /// + /// The object holding the callbacks for the database type name. If + /// this parameter is null, any callbacks for the database type name + /// will be removed if they are present. + /// + /// + /// Non-zero if callbacks were set or removed; otherwise, zero. + /// + public bool SetTypeCallbacks( + string typeName, + SQLiteTypeCallbacks callbacks + ) + { + CheckDisposed(); + + if (typeName == null) + throw new ArgumentNullException("typeName"); + + if (_typeCallbacks == null) + return false; + + if (callbacks == null) + return _typeCallbacks.Remove(typeName); + + callbacks.TypeName = typeName; + _typeCallbacks[typeName] = callbacks; + + return true; + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to bind the specified object + /// instance to this connection. + /// + /// + /// The object instance containing + /// the metadata for the function to be bound. + /// + /// + /// The object instance that implements the + /// function to be bound. + /// + public void BindFunction( + SQLiteFunctionAttribute functionAttribute, + SQLiteFunction function + ) + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException( + "Database connection not valid for binding functions."); + + _sql.BindFunction(functionAttribute, function, _flags); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to bind the specified object + /// instance to this connection. + /// + /// + /// The object instance containing + /// the metadata for the function to be bound. + /// + /// + /// A object instance that helps implement the + /// function to be bound. For scalar functions, this corresponds to the + /// type. For aggregate functions, + /// this corresponds to the type. For + /// collation functions, this corresponds to the + /// type. + /// + /// + /// A object instance that helps implement the + /// function to be bound. For aggregate functions, this corresponds to the + /// type. For other callback types, it + /// is not used and must be null. + /// + public void BindFunction( + SQLiteFunctionAttribute functionAttribute, + Delegate callback1, + Delegate callback2 + ) + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException( + "Database connection not valid for binding functions."); + + _sql.BindFunction(functionAttribute, + new SQLiteDelegateFunction(callback1, callback2), _flags); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to unbind the specified object + /// instance to this connection. + /// + /// + /// The object instance containing + /// the metadata for the function to be unbound. + /// + /// Non-zero if the function was unbound. + public bool UnbindFunction( + SQLiteFunctionAttribute functionAttribute + ) + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException( + "Database connection not valid for unbinding functions."); + + return _sql.UnbindFunction(functionAttribute, _flags); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This method unbinds all registered (known) functions -OR- all previously + /// bound user-defined functions from this connection. + /// + /// + /// Non-zero to unbind all registered (known) functions -OR- zero to unbind + /// all functions currently bound to the connection. + /// + /// + /// Non-zero if all the specified user-defined functions were unbound. + /// + public bool UnbindAllFunctions( + bool registered + ) + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException( + "Database connection not valid for unbinding functions."); + + return SQLiteFunction.UnbindAllFunctions(_sql, _flags, registered); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + [Conditional("CHECK_STATE")] + internal static void Check(SQLiteConnection connection) + { + if (connection == null) + throw new ArgumentNullException("connection"); + + connection.CheckDisposed(); + + if (connection._connectionState != ConnectionState.Open) + throw new InvalidOperationException("The connection is not open."); + + SQLite3 sql = connection._sql as SQLite3; + + if (sql == null) + throw new InvalidOperationException("The connection handle wrapper is null."); + + SQLiteConnectionHandle handle = sql._sql; + + if (handle == null) + throw new InvalidOperationException("The connection handle is null."); + + if (handle.IsInvalid) + throw new InvalidOperationException("The connection handle is invalid."); + + if (handle.IsClosed) + throw new InvalidOperationException("The connection handle is closed."); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Parses a connection string into component parts using the custom + /// connection string parser. An exception may be thrown if the syntax + /// of the connection string is incorrect. + /// + /// + /// The connection string to parse. + /// + /// + /// Non-zero to parse the connection string using the algorithm provided + /// by the framework itself. This is not applicable when running on the + /// .NET Compact Framework. + /// + /// + /// Non-zero if names are allowed without values. + /// + /// + /// The list of key/value pairs corresponding to the parameters specified + /// within the connection string. + /// + internal static SortedList ParseConnectionString( + string connectionString, + bool parseViaFramework, + bool allowNameOnly + ) + { + return ParseConnectionString( + null, connectionString, parseViaFramework, allowNameOnly); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Parses a connection string into component parts using the custom + /// connection string parser. An exception may be thrown if the syntax + /// of the connection string is incorrect. + /// + /// + /// The connection that will be using the parsed connection string. + /// + /// + /// The connection string to parse. + /// + /// + /// Non-zero to parse the connection string using the algorithm provided + /// by the framework itself. This is not applicable when running on the + /// .NET Compact Framework. + /// + /// + /// Non-zero if names are allowed without values. + /// + /// + /// The list of key/value pairs corresponding to the parameters specified + /// within the connection string. + /// + private static SortedList ParseConnectionString( + SQLiteConnection connection, + string connectionString, + bool parseViaFramework, + bool allowNameOnly + ) + { + return parseViaFramework ? + ParseConnectionStringViaFramework(connection, connectionString, false) : + ParseConnectionString(connection, connectionString, allowNameOnly); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + +#if INTEROP_CODEC || INTEROP_INCLUDE_SEE + /// + /// Attempts to escape the specified connection string property name or + /// value in a way that is compatible with the connection string parser. + /// + /// + /// The connection string property name or value to escape. + /// + /// + /// Non-zero if the equals sign is permitted in the string. If this is + /// zero and the string contains an equals sign, an exception will be + /// thrown. + /// + /// + /// The original string, with all special characters escaped. If the + /// original string contains equals signs, they will not be escaped. + /// Instead, they will be preserved verbatim. + /// + private static string EscapeForConnectionString( + string value, + bool allowEquals + ) + { + if (String.IsNullOrEmpty(value)) + return value; + + if (value.IndexOfAny(SQLiteConvert.SpecialChars) == -1) + return value; + + int length = value.Length; + StringBuilder builder = new StringBuilder(length); + + for (int index = 0; index < length; index++) + { + char character = value[index]; + + switch (character) + { + case SQLiteConvert.QuoteChar: + case SQLiteConvert.AltQuoteChar: + case SQLiteConvert.PairChar: + case SQLiteConvert.EscapeChar: + { + builder.Append(SQLiteConvert.EscapeChar); + builder.Append(character); + break; + } + case SQLiteConvert.ValueChar: + { + if (allowEquals) + { + // + // HACK: The connection string parser allows + // connection string property values + // to contain equals signs; however, + // they cannot be escaped. + // + // builder.Append(SQLiteConvert.EscapeChar); + builder.Append(character); + } + else + { + throw new ArgumentException( + "equals sign character is not allowed here"); + } + break; + } + default: + { + builder.Append(character); + break; + } + } + } + + return builder.ToString(); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Builds a connection string from a list of key/value pairs. + /// + /// + /// The list of key/value pairs corresponding to the parameters to be + /// specified within the connection string. + /// + /// + /// The connection string. Depending on how the connection string was + /// originally parsed, the returned connection string value may not be + /// usable in a subsequent call to the method. + /// + private static string BuildConnectionString( + SortedList opts + ) + { + if (opts == null) return null; + StringBuilder builder = new StringBuilder(); + + foreach (KeyValuePair pair in opts) + { +#if NET_COMPACT_20 + builder.Append(HelperMethods.StringFormat( + CultureInfo.InvariantCulture, "{0}{1}{2}{3}", + EscapeForConnectionString(pair.Key, false), + SQLiteConvert.ValueChar, + EscapeForConnectionString(pair.Value, true), + SQLiteConvert.PairChar)); +#else + builder.AppendFormat("{0}{1}{2}{3}", + EscapeForConnectionString(pair.Key, false), + SQLiteConvert.ValueChar, + EscapeForConnectionString(pair.Value, true), + SQLiteConvert.PairChar); +#endif + } + + return builder.ToString(); + } +#endif + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private void SetupSQLiteBase(SortedList opts) + { + object enumValue; + + enumValue = TryParseEnum( + typeof(SQLiteDateFormats), FindKey(opts, "DateTimeFormat", + DefaultDateTimeFormat.ToString()), true); + + SQLiteDateFormats dateFormat = (enumValue is SQLiteDateFormats) ? + (SQLiteDateFormats)enumValue : DefaultDateTimeFormat; + + enumValue = TryParseEnum( + typeof(DateTimeKind), FindKey(opts, "DateTimeKind", + DefaultDateTimeKind.ToString()), true); + + DateTimeKind kind = (enumValue is DateTimeKind) ? + (DateTimeKind)enumValue : DefaultDateTimeKind; + + string dateTimeFormat = FindKey(opts, "DateTimeFormatString", + DefaultDateTimeFormatString); + + // + // NOTE: SQLite automatically sets the encoding of the database + // to UTF16 if called from sqlite3_open16(). + // + if (SQLiteConvert.ToBoolean(FindKey(opts, "UseUTF16Encoding", + DefaultUseUTF16Encoding.ToString()))) + { + _sql = new SQLite3_UTF16( + dateFormat, kind, dateTimeFormat, IntPtr.Zero, null, + false); + } + else + { + _sql = new SQLite3( + dateFormat, kind, dateTimeFormat, IntPtr.Zero, null, + false); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes and finalizes the connection, if applicable. + /// + public new void Dispose() + { + if (_noDispose) + return; + + base.Dispose(); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteConnection).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Cleans up resources (native and managed) associated with the current instance. + /// + /// + /// Zero when being disposed via garbage collection; otherwise, non-zero. + /// + protected override void Dispose(bool disposing) + { +#if !NET_COMPACT_20 && TRACE_WARNING + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.TraceWarning)) + { + if (_noDispose) + { + System.Diagnostics.Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "WARNING: Disposing of connection \"{0}\" with the no-dispose flag set.", + _connectionString)); + } + } +#endif + + _disposing = true; + + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + Close(); + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + +#if PLATFORM_COMPACTFRAMEWORK + /// + /// Obsolete + /// + public override int ConnectionTimeout + { + get + { + CheckDisposed(); + return DefaultConnectionTimeout; + } + } +#endif + + /// + /// Creates a clone of the connection. All attached databases and user-defined functions are cloned. If the existing connection is open, the cloned connection + /// will also be opened. + /// + /// + public object Clone() + { + CheckDisposed(); + return new SQLiteConnection(this); + } + + /// + /// Creates a database file. This just creates a zero-byte file which SQLite + /// will turn into a database when the file is opened properly. + /// + /// The file to create + static public void CreateFile(string databaseFileName) + { + FileStream fs = File.Create(databaseFileName); + fs.Close(); + } + + /// + /// Raises the state change event when the state of the connection changes + /// + /// The new connection state. If this is different + /// from the previous state, the event is + /// raised. + /// The event data created for the raised event, if + /// it was actually raised. + internal void OnStateChange( + ConnectionState newState, + ref StateChangeEventArgs eventArgs + ) + { + ConnectionState oldState = _connectionState; + + _connectionState = newState; + + if ((StateChange != null) && (newState != oldState)) + { + StateChangeEventArgs localEventArgs = + new StateChangeEventArgs(oldState, newState); + + StateChange(this, localEventArgs); + + eventArgs = localEventArgs; + } + } + + /// + /// Determines and returns the fallback default isolation level when one cannot be + /// obtained from an existing connection instance. + /// + /// + /// The fallback default isolation level for this connection instance -OR- + /// if it cannot be determined. + /// + private static IsolationLevel GetFallbackDefaultIsolationLevel() + { + return DefaultIsolationLevel; + } + + /// + /// Determines and returns the default isolation level for this connection instance. + /// + /// + /// The default isolation level for this connection instance -OR- + /// if it cannot be determined. + /// + internal IsolationLevel GetDefaultIsolationLevel() + { + return _defaultIsolation; + } + + /// + /// OBSOLETE. Creates a new SQLiteTransaction if one isn't already active on the connection. + /// + /// This parameter is ignored. + /// When TRUE, SQLite defers obtaining a write lock until a write operation is requested. + /// When FALSE, a writelock is obtained immediately. The default is TRUE, but in a multi-threaded multi-writer + /// environment, one may instead choose to lock the database immediately to avoid any possible writer deadlock. + /// Returns a SQLiteTransaction object. + [Obsolete("Use one of the standard BeginTransaction methods, this one will be removed soon")] + public SQLiteTransaction BeginTransaction(IsolationLevel isolationLevel, bool deferredLock) + { + CheckDisposed(); + return (SQLiteTransaction)BeginDbTransaction(deferredLock == false ? ImmediateIsolationLevel : DeferredIsolationLevel); + } + + /// + /// OBSOLETE. Creates a new SQLiteTransaction if one isn't already active on the connection. + /// + /// When TRUE, SQLite defers obtaining a write lock until a write operation is requested. + /// When FALSE, a writelock is obtained immediately. The default is false, but in a multi-threaded multi-writer + /// environment, one may instead choose to lock the database immediately to avoid any possible writer deadlock. + /// Returns a SQLiteTransaction object. + [Obsolete("Use one of the standard BeginTransaction methods, this one will be removed soon")] + public SQLiteTransaction BeginTransaction(bool deferredLock) + { + CheckDisposed(); + return (SQLiteTransaction)BeginDbTransaction(deferredLock == false ? ImmediateIsolationLevel : DeferredIsolationLevel); + } + + /// + /// Creates a new if one isn't already active on the connection. + /// + /// Supported isolation levels are Serializable, ReadCommitted and Unspecified. + /// + /// Unspecified will use the default isolation level specified in the connection string. If no isolation level is specified in the + /// connection string, Serializable is used. + /// Serializable transactions are the default. In this mode, the engine gets an immediate lock on the database, and no other threads + /// may begin a transaction. Other threads may read from the database, but not write. + /// With a ReadCommitted isolation level, locks are deferred and elevated as needed. It is possible for multiple threads to start + /// a transaction in ReadCommitted mode, but if a thread attempts to commit a transaction while another thread + /// has a ReadCommitted lock, it may timeout or cause a deadlock on both threads until both threads' CommandTimeout's are reached. + /// + /// Returns a SQLiteTransaction object. + public new SQLiteTransaction BeginTransaction(IsolationLevel isolationLevel) + { + CheckDisposed(); + return (SQLiteTransaction)BeginDbTransaction(isolationLevel); + } + + /// + /// Creates a new if one isn't already + /// active on the connection. + /// + /// Returns the new transaction object. + public new SQLiteTransaction BeginTransaction() + { + CheckDisposed(); + return (SQLiteTransaction)BeginDbTransaction(_defaultIsolation); + } + + /// + /// Forwards to the local function + /// + /// Supported isolation levels are Unspecified, Serializable, and ReadCommitted + /// + protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) + { + if (_connectionState != ConnectionState.Open) + throw new InvalidOperationException(); + + if (isolationLevel == IsolationLevel.Unspecified) isolationLevel = _defaultIsolation; + isolationLevel = GetEffectiveIsolationLevel(isolationLevel); + + if (isolationLevel != ImmediateIsolationLevel && isolationLevel != DeferredIsolationLevel) + throw new ArgumentException("isolationLevel"); + + SQLiteTransaction transaction; + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.AllowNestedTransactions)) + { + transaction = new SQLiteTransaction2( + this, isolationLevel != ImmediateIsolationLevel); + } + else + { + transaction = new SQLiteTransaction( + this, isolationLevel != ImmediateIsolationLevel); + } + + OnChanged(this, new ConnectionEventArgs( + SQLiteConnectionEventType.NewTransaction, null, transaction, + null, null, null, null, null)); + + return transaction; + } + + /// + /// This method is not implemented; however, the + /// event will still be raised. + /// + /// + public override void ChangeDatabase(string databaseName) + { + CheckDisposed(); + + OnChanged(this, new ConnectionEventArgs( + SQLiteConnectionEventType.ChangeDatabase, null, null, null, null, + null, databaseName, null)); + + throw new NotImplementedException(); // NOTE: For legacy compatibility. + } + + /// + /// When the database connection is closed, all commands linked to this connection are automatically reset. + /// + public override void Close() + { + CheckDisposed(); + + OnChanged(this, new ConnectionEventArgs( + SQLiteConnectionEventType.Closing, null, null, null, null, null, + null, null)); + + if (_sql != null) + { +#if !PLATFORM_COMPACTFRAMEWORK + lock (_enlistmentSyncRoot) /* TRANSACTIONAL */ + { + SQLiteEnlistment enlistment = _enlistment; + _enlistment = null; + + if (enlistment != null) + { + // If the connection is enlisted in a transaction scope and the scope is still active, + // we cannot truly shut down this connection until the scope has completed. Therefore make a + // hidden connection temporarily to hold open the connection until the scope has completed. + SQLiteConnection cnn = new SQLiteConnection(); + +#if DEBUG + cnn._debugString = HelperMethods.StringFormat( + CultureInfo.InvariantCulture, + "closeThreadId = {0}, {1}{2}{2}{3}", + HelperMethods.GetThreadId(), _sql, + Environment.NewLine, _debugString); +#endif + + cnn._sql = _sql; + cnn._transactionLevel = _transactionLevel; + cnn._transactionSequence = _transactionSequence; + cnn._enlistment = enlistment; + cnn._connectionState = _connectionState; + cnn._version = _version; + + SQLiteTransaction transaction = enlistment._transaction; + + if (transaction != null) + transaction._cnn = cnn; + + enlistment._disposeConnection = true; + + _sql = null; + } + } +#endif + if (_sql != null) + { + _sql.Close(_disposing); + _sql = null; + } + _transactionLevel = 0; + _transactionSequence = 0; + } + + StateChangeEventArgs eventArgs = null; + OnStateChange(ConnectionState.Closed, ref eventArgs); + + OnChanged(this, new ConnectionEventArgs( + SQLiteConnectionEventType.Closed, eventArgs, null, null, null, + null, null, null)); + } + + /// + /// Returns the number of pool entries for the file name associated with this connection. + /// + public int PoolCount + { + get + { + if (_sql == null) return 0; + return _sql.CountPool(); + } + } + + /// + /// Clears the connection pool associated with the connection. Any other active connections using the same database file + /// will be discarded instead of returned to the pool when they are closed. + /// + /// + public static void ClearPool(SQLiteConnection connection) + { + if (connection._sql == null) return; + connection._sql.ClearPool(); + } + + /// + /// Clears all connection pools. Any active connections will be discarded instead of sent to the pool when they are closed. + /// + public static void ClearAllPools() + { + SQLiteConnectionPool.ClearAllPools(); + } + + /// + /// The connection string containing the parameters for the connection + /// + /// + /// For the complete list of supported connection string properties, + /// please see . + /// +#if !PLATFORM_COMPACTFRAMEWORK + [RefreshProperties(RefreshProperties.All), DefaultValue("")] + [Editor("SQLite.Designer.SQLiteConnectionStringEditor, SQLite.Designer, Version=" + SQLite3.DesignerVersion + ", Culture=neutral, PublicKeyToken=db937bc2d44ff139", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] +#endif + public override string ConnectionString + { + get + { + CheckDisposed(); + return _connectionString; + } + set + { + CheckDisposed(); + + if (value == null) + throw new ArgumentNullException(); + + else if (_connectionState != ConnectionState.Closed) + throw new InvalidOperationException(); + + _connectionString = value; + } + } + + /// + /// Create a new and associate it with this connection. + /// + /// Returns a new command object already assigned to this connection. + public new SQLiteCommand CreateCommand() + { + CheckDisposed(); + return new SQLiteCommand(this); + } + + /// + /// Forwards to the local function. + /// + /// + protected override DbCommand CreateDbCommand() + { + return CreateCommand(); + } + +#if INTEROP_SESSION_EXTENSION + /// + /// Attempts to create a new object instance + /// using this connection and the specified database name. + /// + /// + /// The name of the database for the newly created session. + /// + /// + /// The newly created session -OR- null if it cannot be created. + /// + public ISQLiteSession CreateSession( + string databaseName + ) + { + CheckDisposed(); + + return new SQLiteSession(GetNativeHandle(this), _flags, databaseName); + } + + /// + /// Attempts to create a new object instance + /// using this connection and the specified raw data. + /// + /// + /// The raw data that contains a change set (or patch set). + /// + /// + /// The newly created change set -OR- null if it cannot be created. + /// + public ISQLiteChangeSet CreateChangeSet( + byte[] rawData + ) + { + CheckDisposed(); + + return new SQLiteMemoryChangeSet(rawData, GetNativeHandle(this), _flags); + } + + /// + /// Attempts to create a new object instance + /// using this connection and the specified raw data. + /// + /// + /// The raw data that contains a change set (or patch set). + /// + /// + /// The flags used to create the change set iterator. + /// + /// + /// The newly created change set -OR- null if it cannot be created. + /// + public ISQLiteChangeSet CreateChangeSet( + byte[] rawData, + SQLiteChangeSetStartFlags flags + ) + { + CheckDisposed(); + + return new SQLiteMemoryChangeSet(rawData, GetNativeHandle(this), _flags, flags); + } + + /// + /// Attempts to create a new object instance + /// using this connection and the specified stream. + /// + /// + /// The stream where the raw data that contains a change set (or patch set) + /// may be read. + /// + /// + /// The stream where the raw data that contains a change set (or patch set) + /// may be written. + /// + /// + /// The newly created change set -OR- null if it cannot be created. + /// + public ISQLiteChangeSet CreateChangeSet( + Stream inputStream, + Stream outputStream + ) + { + CheckDisposed(); + + return new SQLiteStreamChangeSet( + inputStream, outputStream, GetNativeHandle(this), _flags); + } + + /// + /// Attempts to create a new object instance + /// using this connection and the specified stream. + /// + /// + /// The stream where the raw data that contains a change set (or patch set) + /// may be read. + /// + /// + /// The stream where the raw data that contains a change set (or patch set) + /// may be written. + /// + /// + /// The flags used to create the change set iterator. + /// + /// + /// The newly created change set -OR- null if it cannot be created. + /// + public ISQLiteChangeSet CreateChangeSet( + Stream inputStream, + Stream outputStream, + SQLiteChangeSetStartFlags flags + ) + { + CheckDisposed(); + + return new SQLiteStreamChangeSet( + inputStream, outputStream, GetNativeHandle(this), _flags, flags); + } + + /// + /// Attempts to create a new object + /// instance using this connection. + /// + /// + /// The newly created change group -OR- null if it cannot be created. + /// + public ISQLiteChangeGroup CreateChangeGroup() + { + CheckDisposed(); + + return new SQLiteChangeGroup(_flags); + } +#endif + + /// + /// Returns the data source file name without extension or path. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] +#endif + public override string DataSource + { + get + { + CheckDisposed(); + return _dataSource; + } + } + + /// + /// Returns the fully qualified path and file name for the currently open + /// database, if any. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] +#endif + public string FileName + { + get + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException( + "Database connection not valid for getting file name."); + + return _sql.GetFileName(GetDefaultCatalogName()); + } + } + + /// + /// Returns the string "main". + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] +#endif + public override string Database + { + get + { + CheckDisposed(); + return GetDefaultCatalogName(); + } + } + + internal static string MapUriPath(string path) + { + if (path.StartsWith ("file://", StringComparison.OrdinalIgnoreCase)) + return path.Substring (7); + else if (path.StartsWith ("file:", StringComparison.OrdinalIgnoreCase)) + return path.Substring (5); + else if (path.StartsWith ("/", StringComparison.OrdinalIgnoreCase)) + return path; + else + throw new InvalidOperationException ("Invalid connection string: invalid URI"); + } + + /// + /// Determines if the legacy connection string parser should be used. + /// + /// + /// The connection that will be using the parsed connection string. + /// + /// + /// Non-zero if the legacy connection string parser should be used. + /// + private static bool ShouldUseLegacyConnectionStringParser( + SQLiteConnection connection + ) + { + string name = "No_SQLiteConnectionNewParser"; + object value; + + if ((connection != null) && + connection.TryGetCachedSetting(name, null, out value)) + { + return (value != null); + } + + if ((connection == null) && + TryGetLastCachedSetting(name, null, out value)) + { + return (value != null); + } + + value = UnsafeNativeMethods.GetSettingValue(name, null); + + if (connection != null) + connection.SetCachedSetting(name, value); + else + SetLastCachedSetting(name, value); + + return (value != null); + } + + /// + /// Parses a connection string into component parts using the custom + /// connection string parser. An exception may be thrown if the syntax + /// of the connection string is incorrect. + /// + /// + /// The connection string to parse. + /// + /// + /// Non-zero if names are allowed without values. + /// + /// + /// The list of key/value pairs corresponding to the parameters specified + /// within the connection string. + /// + private static SortedList ParseConnectionString( + string connectionString, + bool allowNameOnly + ) + { + return ParseConnectionString(null, connectionString, allowNameOnly); + } + + /// + /// Parses a connection string into component parts using the custom + /// connection string parser. An exception may be thrown if the syntax + /// of the connection string is incorrect. + /// + /// + /// The connection that will be using the parsed connection string. + /// + /// + /// The connection string to parse. + /// + /// + /// Non-zero if names are allowed without values. + /// + /// + /// The list of key/value pairs corresponding to the parameters specified + /// within the connection string. + /// + private static SortedList ParseConnectionString( + SQLiteConnection connection, + string connectionString, + bool allowNameOnly + ) + { + string s = connectionString; + int n; + SortedList ls = new SortedList(StringComparer.OrdinalIgnoreCase); + + // First split into semi-colon delimited values. + string error = null; + string[] arParts; + + if (ShouldUseLegacyConnectionStringParser(connection)) + arParts = SQLiteConvert.Split(s, SQLiteConvert.PairChar); + else + arParts = SQLiteConvert.NewSplit(s, SQLiteConvert.PairChar, true, ref error); + + if (arParts == null) + { + throw new ArgumentException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Invalid ConnectionString format, cannot parse: {0}", (error != null) ? + error : "could not split connection string into properties")); + } + + int x = (arParts != null) ? arParts.Length : 0; + // For each semi-colon piece, split into key and value pairs by the presence of the = sign + for (n = 0; n < x; n++) + { + if (arParts[n] == null) + continue; + + arParts[n] = arParts[n].Trim(); + + if (arParts[n].Length == 0) + continue; + + int indexOf = arParts[n].IndexOf(SQLiteConvert.ValueChar); + + if (indexOf != -1) + ls.Add(UnwrapString(arParts[n].Substring(0, indexOf).Trim()), UnwrapString(arParts[n].Substring(indexOf + 1).Trim())); + else if (allowNameOnly) + ls.Add(UnwrapString(arParts[n].Trim()), String.Empty); + else + throw new ArgumentException(HelperMethods.StringFormat(CultureInfo.CurrentCulture, "Invalid ConnectionString format for part \"{0}\", no equal sign found", arParts[n])); + } + return ls; + } + + /// + /// Parses a connection string using the built-in (i.e. framework provided) + /// connection string parser class and returns the key/value pairs. An + /// exception may be thrown if the connection string is invalid or cannot be + /// parsed. When compiled for the .NET Compact Framework, the custom + /// connection string parser is always used instead because the framework + /// provided one is unavailable there. + /// + /// + /// The connection that will be using the parsed connection string. + /// + /// + /// The connection string to parse. + /// + /// + /// Non-zero to throw an exception if any connection string values are not of + /// the type. This is not applicable when running on + /// the .NET Compact Framework. + /// + /// The list of key/value pairs. + private static SortedList ParseConnectionStringViaFramework( + SQLiteConnection connection, + string connectionString, + bool strict + ) + { +#if !PLATFORM_COMPACTFRAMEWORK + DbConnectionStringBuilder connectionStringBuilder + = new DbConnectionStringBuilder(); + + connectionStringBuilder.ConnectionString = connectionString; /* throw */ + + SortedList result = + new SortedList(StringComparer.OrdinalIgnoreCase); + + foreach (string keyName in connectionStringBuilder.Keys) + { + object value = connectionStringBuilder[keyName]; + string keyValue = null; + + if (value is string) + { + keyValue = (string)value; + } + else if (strict) + { + throw new ArgumentException( + "connection property value is not a string", + keyName); + } + else if (value != null) + { + keyValue = value.ToString(); + } + + result.Add(keyName, keyValue); + } + + return result; +#else + // + // NOTE: On the .NET Compact Framework, always use our custom connection + // string parser as the built-in (i.e. framework provided) one is + // unavailable. + // + return ParseConnectionString(connection, connectionString, false); +#endif + } + +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// Manual distributed transaction enlistment support + /// + /// The distributed transaction to enlist in + public override void EnlistTransaction(System.Transactions.Transaction transaction) + { + CheckDisposed(); + + bool waitForEnlistmentReset; + int waitTimeout; + + lock (_enlistmentSyncRoot) /* TRANSACTIONAL */ + { + waitForEnlistmentReset = HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.WaitForEnlistmentReset); + + waitTimeout = _waitTimeout; + } + + if (waitForEnlistmentReset) + /* IGNORED */ + WaitForEnlistmentReset(waitTimeout, null); + + lock (_enlistmentSyncRoot) /* TRANSACTIONAL */ + { + if (_enlistment != null && transaction == _enlistment._scope) + return; + else if (_enlistment != null) + throw new ArgumentException("Already enlisted in a transaction"); + + if (_transactionLevel > 0 && transaction != null) + throw new ArgumentException("Unable to enlist in transaction, a local transaction already exists"); + else if (transaction == null) + throw new ArgumentNullException("Unable to enlist in transaction, it is null"); + + bool strictEnlistment = HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.StrictEnlistment); + + _enlistment = new SQLiteEnlistment(this, transaction, + GetFallbackDefaultIsolationLevel(), strictEnlistment, + strictEnlistment); + + OnChanged(this, new ConnectionEventArgs( + SQLiteConnectionEventType.EnlistTransaction, null, null, null, null, + null, null, new object[] { _enlistment })); + } + } +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// EXPERIMENTAL -- + /// Waits for the enlistment associated with this connection to be reset. + /// This method always throws when + /// running on the .NET Compact Framework. + /// + /// + /// The approximate maximum number of milliseconds to wait before timing + /// out the wait operation. + /// + /// + /// The return value to use if the connection has been disposed; if this + /// value is null, will be raised + /// if the connection has been disposed. + /// + /// + /// Non-zero if the enlistment assciated with this connection was reset; + /// otherwise, zero. It should be noted that this method returning a + /// non-zero value does not necessarily guarantee that the connection + /// can enlist in a new transaction (i.e. due to potentical race with + /// other threads); therefore, callers should generally use try/catch + /// when calling the method. + /// +#else + /// + /// EXPERIMENTAL -- + /// Waits for the enlistment associated with this connection to be reset. + /// This method always throws when + /// running on the .NET Compact Framework. + /// + /// + /// The approximate maximum number of milliseconds to wait before timing + /// out the wait operation. + /// + /// + /// The return value to use if the connection has been disposed; if this + /// value is null, will be raised + /// if the connection has been disposed. + /// + /// + /// Non-zero if the enlistment assciated with this connection was reset; + /// otherwise, zero. It should be noted that this method returning a + /// non-zero value does not necessarily guarantee that the connection + /// can enlist in a new transaction (i.e. due to potentical race with + /// other threads); therefore, callers should generally use try/catch + /// when calling the EnlistTransaction method. + /// +#endif + public bool WaitForEnlistmentReset( + int timeoutMilliseconds, + bool? returnOnDisposed + ) + { + if (returnOnDisposed == null) + CheckDisposed(); + else if(disposed) + return (bool)returnOnDisposed; + +#if !PLATFORM_COMPACTFRAMEWORK + if (timeoutMilliseconds < 0) + throw new ArgumentException("timeout cannot be negative"); + + const int defaultMilliseconds = 100; + int sleepMilliseconds; + + if (timeoutMilliseconds == 0) + { + sleepMilliseconds = 0; + } + else + { + sleepMilliseconds = Math.Min( + timeoutMilliseconds / 10, defaultMilliseconds); + + if (sleepMilliseconds == 0) + sleepMilliseconds = defaultMilliseconds; + } + + DateTime start = DateTime.UtcNow; + + while (true) + { + // + // NOTE: Attempt to acquire the necessary lock without blocking. + // This method will treat a failure to obtain the lock the + // same as the enlistment not being reset yet. Both will + // advance toward the timeout. + // + bool locked = Monitor.TryEnter(_enlistmentSyncRoot); + + try + { + if (locked) + { + // + // NOTE: Is there still an enlistment? If not, we are + // done. There is a potential race condition in + // the caller if another thread is able to setup + // a new enlistment at any point prior to our + // caller fully dealing with the result of this + // method. However, that should generally never + // happen because this class is not intended to + // be used by multiple concurrent threads, with + // the notable exception of an active enlistment + // being asynchronously committed or rolled back + // by the .NET Framework. + // + if (_enlistment == null) + return true; + } + } + finally + { + if (locked) + { + Monitor.Exit(_enlistmentSyncRoot); + locked = false; + } + } + + // + // NOTE: A timeout value of zero is special. It means never + // sleep. + // + if (sleepMilliseconds == 0) + return false; + + // + // NOTE: How much time has elapsed since we first starting + // waiting? + // + DateTime now = DateTime.UtcNow; + TimeSpan elapsed = now.Subtract(start); + + // + // NOTE: Are we done wait? + // + double totalMilliseconds = elapsed.TotalMilliseconds; + + if ((totalMilliseconds < 0) || /* Time went backward? */ + (totalMilliseconds >= (double)timeoutMilliseconds)) + { + return false; + } + + // + // NOTE: Sleep for a bit and then try again. + // + Thread.Sleep(sleepMilliseconds); + } +#else + throw new NotImplementedException(); +#endif + } + + /// + /// Looks for a key in the array of key/values of the parameter string. If not found, return the specified default value + /// + /// The list to look in + /// The key to find + /// The default value to return if the key is not found + /// The value corresponding to the specified key, or the default value if not found. + static internal string FindKey(SortedList items, string key, string defValue) + { + string ret; + + if (String.IsNullOrEmpty(key)) return defValue; + if (items.TryGetValue(key, out ret)) return ret; + if (items.TryGetValue(key.Replace(" ", String.Empty), out ret)) return ret; + if (items.TryGetValue(key.Replace(" ", "_"), out ret)) return ret; + + return defValue; + } + + /// + /// Attempts to convert the string value to an enumerated value of the specified type. + /// + /// The enumerated type to convert the string value to. + /// The string value to be converted. + /// Non-zero to make the conversion case-insensitive. + /// The enumerated value upon success or null upon error. + internal static object TryParseEnum( + Type type, + string value, + bool ignoreCase + ) + { + if (!String.IsNullOrEmpty(value)) + { + try + { + return Enum.Parse(type, value, ignoreCase); + } + catch + { + // do nothing. + } + } + + return null; + } + + /// + /// Attempts to convert an input string into a byte value. + /// + /// + /// The string value to be converted. + /// + /// + /// The number styles to use for the conversion. + /// + /// + /// Upon sucess, this will contain the parsed byte value. + /// Upon failure, the value of this parameter is undefined. + /// + /// + /// Non-zero upon success; zero on failure. + /// + private static bool TryParseByte( + string value, + NumberStyles style, + out byte result + ) + { +#if !PLATFORM_COMPACTFRAMEWORK + return byte.TryParse(value, style, null, out result); +#else + try + { + result = byte.Parse(value, style); + return true; + } + catch + { + result = 0; + return false; + } +#endif + } + + /// + /// Change a configuration option value for the database. + /// + /// + /// The database configuration option to change. + /// + /// + /// The new value for the specified configuration option. + /// + public void SetConfigurationOption( + SQLiteConfigDbOpsEnum option, + object value + ) + { + CheckDisposed(); + + if (_sql == null) + { + throw new InvalidOperationException( + "Database connection not valid for changing a configuration option."); + } + + if ((option == SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION) && + HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.NoLoadExtension)) + { + throw new SQLiteException("Loading extensions is disabled for this database connection."); + } + + SQLiteErrorCode rc = _sql.SetConfigurationOption(option, value); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, null); + } + + /// + /// Enables or disabled extension loading. + /// + /// + /// True to enable loading of extensions, false to disable. + /// + public void EnableExtensions( + bool enable + ) + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Database connection not valid for {0} extensions.", + enable ? "enabling" : "disabling")); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.NoLoadExtension)) + throw new SQLiteException("Loading extensions is disabled for this database connection."); + + _sql.SetLoadExtension(enable); + } + + /// + /// Loads a SQLite extension library from the named dynamic link library file. + /// + /// + /// The name of the dynamic link library file containing the extension. + /// + public void LoadExtension( + string fileName + ) + { + CheckDisposed(); + + LoadExtension(fileName, null); + } + + /// + /// Loads a SQLite extension library from the named dynamic link library file. + /// + /// + /// The name of the dynamic link library file containing the extension. + /// + /// + /// The name of the exported function used to initialize the extension. + /// If null, the default "sqlite3_extension_init" will be used. + /// + public void LoadExtension( + string fileName, + string procName + ) + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException( + "Database connection not valid for loading extensions."); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.NoLoadExtension)) + throw new SQLiteException("Loading extensions is disabled for this database connection."); + + _sql.LoadExtension(fileName, procName); + } + +#if INTEROP_VIRTUAL_TABLE + /// + /// Creates a disposable module containing the implementation of a virtual + /// table. + /// + /// + /// The module object to be used when creating the disposable module. + /// + public void CreateModule( + SQLiteModule module + ) + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException( + "Database connection not valid for creating modules."); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.NoCreateModule)) + throw new SQLiteException("Creating modules is disabled for this database connection."); + + _sql.CreateModule(module, _flags); + } +#endif + + /// + /// Parses a string containing a sequence of zero or more hexadecimal + /// encoded byte values and returns the resulting byte array. The + /// "0x" prefix is not allowed on the input string. + /// + /// + /// The input string containing zero or more hexadecimal encoded byte + /// values. + /// + /// + /// A byte array containing the parsed byte values or null if an error + /// was encountered. + /// + internal static byte[] FromHexString( + string text + ) + { + string error = null; + + return FromHexString(text, ref error); + } + + /// + /// Creates and returns a string containing the hexadecimal encoded byte + /// values from the input array. + /// + /// + /// The input array of bytes. + /// + /// + /// The resulting string or null upon failure. + /// + internal static string ToHexString( + byte[] array + ) + { + if (array == null) + return null; + + StringBuilder result = new StringBuilder(); + + int length = array.Length; + + for (int index = 0; index < length; index++) +#if NET_COMPACT_20 + result.Append(HelperMethods.StringFormat( + CultureInfo.InvariantCulture, + "{0:x2}", array[index])); +#else + result.AppendFormat("{0:x2}", array[index]); +#endif + + return result.ToString(); + } + + /// + /// Parses a string containing a sequence of zero or more hexadecimal + /// encoded byte values and returns the resulting byte array. The + /// "0x" prefix is not allowed on the input string. + /// + /// + /// The input string containing zero or more hexadecimal encoded byte + /// values. + /// + /// + /// Upon failure, this will contain an appropriate error message. + /// + /// + /// A byte array containing the parsed byte values or null if an error + /// was encountered. + /// + private static byte[] FromHexString( + string text, + ref string error + ) + { + if (text == null) + { + error = "string is null"; + return null; + } + + if (text.Length % 2 != 0) + { + error = "string contains an odd number of characters"; + return null; + } + + byte[] result = new byte[text.Length / 2]; + + for (int index = 0; index < text.Length; index += 2) + { + string value = text.Substring(index, 2); + + if (!TryParseByte(value, + NumberStyles.HexNumber, out result[index / 2])) + { + error = HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "string contains \"{0}\", which cannot be converted to a byte value", + value); + + return null; + } + } + + return result; + } + + /// + /// This method figures out what the default connection pool setting should + /// be based on the connection flags. When present, the "Pooling" connection + /// string property value always overrides the value returned by this method. + /// + /// + /// Non-zero if the connection pool should be enabled by default; otherwise, + /// zero. + /// + private bool GetDefaultPooling() + { + bool result = DefaultPooling; + + if (result) /* NOTE: True branch not reached in the default build. */ + { + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.NoConnectionPool)) + result = false; + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionPool)) + result = true; + } + else + { + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionPool)) + result = true; + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.NoConnectionPool)) + result = false; + } + + return result; + } + + /// + /// Determines the transaction isolation level that should be used by + /// the caller, primarily based upon the one specified by the caller. + /// If mapping of transaction isolation levels is enabled, the returned + /// transaction isolation level may be significantly different than the + /// originally specified one. + /// + /// + /// The originally specified transaction isolation level. + /// + /// + /// The transaction isolation level that should be used. + /// + private IsolationLevel GetEffectiveIsolationLevel( + IsolationLevel isolationLevel + ) + { + if (!HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.MapIsolationLevels)) + { + return isolationLevel; + } + + switch (isolationLevel) + { + case IsolationLevel.Unspecified: + case IsolationLevel.Chaos: + case IsolationLevel.ReadUncommitted: + case IsolationLevel.ReadCommitted: + return DeferredIsolationLevel; + case IsolationLevel.RepeatableRead: + case IsolationLevel.Serializable: + case IsolationLevel.Snapshot: + return ImmediateIsolationLevel; + default: + return GetFallbackDefaultIsolationLevel(); + } + } + + /// + /// Opens the connection using the parameters found in the . + /// + public override void Open() + { + CheckDisposed(); + + _lastConnectionInOpen = this; /* THREAD-SAFE: per-thread datum. */ + + OnChanged(this, new ConnectionEventArgs( + SQLiteConnectionEventType.Opening, null, null, null, null, null, + null, null)); + + if (_connectionState != ConnectionState.Closed) + throw new InvalidOperationException(); + + Close(); + + SortedList opts = ParseConnectionString( + this, _connectionString, _parseViaFramework, false); + + object enumValue = TryParseEnum(typeof(SQLiteConnectionFlags), FindKey(opts, "Flags", null), true); + + // + // BUGFIX: Always preserve the pre-existing instance flags. This is OK + // because when the connection object is initially created, they + // are "None"; therefore, OR-ing the connection string property + // flags with the instance flags would produce exactly the same + // result. If the "Flags" connection string property is absent, + // OR-ing the the instance flags with the static DefaultFlags is + // done instead. This is OK for the same reason as before: when + // the connection object is initially created, they are "None" + // by default. If they are different now, they must have been + // manually set by the application. + // + bool noDefaultFlags = SQLiteConvert.ToBoolean(FindKey(opts, "NoDefaultFlags", DefaultNoDefaultFlags.ToString())); + + if (enumValue is SQLiteConnectionFlags) + _flags |= (SQLiteConnectionFlags)enumValue; + else if (!noDefaultFlags) + _flags |= DefaultFlags; + + bool noSharedFlags = SQLiteConvert.ToBoolean(FindKey(opts, "NoSharedFlags", DefaultNoSharedFlags.ToString())); + if (!noSharedFlags) { lock (_syncRoot) { _flags |= _sharedFlags; } } + +#if INTEROP_CODEC || INTEROP_INCLUDE_SEE + bool hidePassword = HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.HidePassword); +#endif + + SortedList eventArgOpts = opts; + string eventArgConnectionString = _connectionString; + +#if INTEROP_CODEC || INTEROP_INCLUDE_SEE + if (hidePassword) + { + eventArgOpts = new SortedList( + StringComparer.OrdinalIgnoreCase); + + foreach (KeyValuePair pair in opts) + { + if (String.Equals( + pair.Key, "Password", + StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (String.Equals( + pair.Key, "HexPassword", + StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + eventArgOpts.Add(pair.Key, pair.Value); + } + + eventArgConnectionString = BuildConnectionString( + eventArgOpts); + } +#endif + + OnChanged(this, new ConnectionEventArgs( + SQLiteConnectionEventType.ConnectionString, null, null, null, null, + null, eventArgConnectionString, new object[] { eventArgOpts })); + + enumValue = TryParseEnum(typeof(DbType), FindKey(opts, "DefaultDbType", null), true); + _defaultDbType = (enumValue is DbType) ? (DbType)enumValue : (DbType?)null; + + // + // NOTE: Nullable values types are not supported by the .NET Framework + // ADO.NET support components that work with the connection string + // builder; therefore, translate the "invalid value" used by the + // SQLiteConnectionStringBuilder.DefaultDbType property to null + // here. + // + if ((_defaultDbType != null) && ((DbType)_defaultDbType == BadDbType)) + _defaultDbType = null; + + _defaultTypeName = FindKey(opts, "DefaultTypeName", null); + _vfsName = FindKey(opts, "VfsName", DefaultVfsName); + +#if !NET_COMPACT_20 && TRACE_WARNING + bool uri = false; +#endif + bool fullUri = false; + string fileName; + + if (Convert.ToInt32(FindKey(opts, "Version", SQLiteConvert.ToString(DefaultVersion)), CultureInfo.InvariantCulture) != DefaultVersion) + throw new NotSupportedException(HelperMethods.StringFormat(CultureInfo.CurrentCulture, "Only SQLite Version {0} is supported at this time", DefaultVersion)); + +#if INTEROP_INCLUDE_ZIPVFS + bool useZipVfs = false; + string zipVfsVersion = FindKey(opts, "ZipVfsVersion", DefaultZipVfsVersion); + + if (zipVfsVersion != null) + { + if (String.Compare(zipVfsVersion, ZipVfs_Automatic) == 0) + { + useZipVfs = true; + } + else if (String.Compare(zipVfsVersion, ZipVfs_V2) == 0) + { + UnsafeNativeMethods.zipvfsInit_v2(); + useZipVfs = true; + } + else if (String.Compare(zipVfsVersion, ZipVfs_V3) == 0) + { + UnsafeNativeMethods.zipvfsInit_v3(0); + useZipVfs = true; + } + else + { + throw new NotSupportedException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, "Only ZipVFS versions {0}, {1}, and {2} are supported at this time", + ZipVfs_Automatic, ZipVfs_V2, ZipVfs_V3)); + } + } +#endif + + fileName = FindKey(opts, "Data Source", DefaultDataSource); + + if (String.IsNullOrEmpty(fileName)) + { + fileName = FindKey(opts, "Uri", DefaultUri); + if (String.IsNullOrEmpty(fileName)) + { + fileName = FindKey(opts, "FullUri", DefaultFullUri); + if (String.IsNullOrEmpty(fileName)) + throw new ArgumentException(HelperMethods.StringFormat(CultureInfo.CurrentCulture, "Data Source cannot be empty. Use {0} to open an in-memory database", MemoryFileName)); + else + fullUri = true; + } + else + { + fileName = MapUriPath(fileName); +#if !NET_COMPACT_20 && TRACE_WARNING + uri = true; +#endif + } + } + + bool isMemory = (String.Compare(fileName, MemoryFileName, StringComparison.OrdinalIgnoreCase) == 0); + +#if !NET_COMPACT_20 && TRACE_WARNING + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.TraceWarning)) + { + if (!uri && !fullUri && !isMemory && !String.IsNullOrEmpty(fileName) && + fileName.StartsWith("\\", StringComparison.OrdinalIgnoreCase) && + !fileName.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase)) + { + System.Diagnostics.Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "WARNING: Detected a possibly malformed UNC database file name \"{0}\" that " + + "may have originally started with two backslashes; however, four leading " + + "backslashes may be required, e.g.: \"Data Source=\\\\\\{0};\"", + fileName)); + } + } +#endif + + if (!fullUri) + { + if (isMemory) + fileName = MemoryFileName; + else + { +#if PLATFORM_COMPACTFRAMEWORK + if (fileName.StartsWith("./") || fileName.StartsWith(".\\")) + fileName = Path.GetDirectoryName(Assembly.GetCallingAssembly().GetName().CodeBase) + fileName.Substring(1); +#endif + bool toFullPath = SQLiteConvert.ToBoolean(FindKey(opts, "ToFullPath", DefaultToFullPath.ToString())); + fileName = ExpandFileName(fileName, toFullPath); + } + } + + try + { + bool usePooling = SQLiteConvert.ToBoolean(FindKey(opts, "Pooling", GetDefaultPooling().ToString())); + int maxPoolSize = Convert.ToInt32(FindKey(opts, "Max Pool Size", SQLiteConvert.ToString(DefaultMaxPoolSize)), CultureInfo.InvariantCulture); + + _defaultTimeout = Convert.ToInt32(FindKey(opts, "Default Timeout", SQLiteConvert.ToString(DefaultConnectionTimeout)), CultureInfo.InvariantCulture); + _busyTimeout = Convert.ToInt32(FindKey(opts, "BusyTimeout", SQLiteConvert.ToString(DefaultBusyTimeout)), CultureInfo.InvariantCulture); + +#if !PLATFORM_COMPACTFRAMEWORK + _waitTimeout = Convert.ToInt32(FindKey(opts, "WaitTimeout", SQLiteConvert.ToString(DefaultWaitTimeout)), CultureInfo.InvariantCulture); +#endif + + _prepareRetries = Convert.ToInt32(FindKey(opts, "PrepareRetries", SQLiteConvert.ToString(DefaultPrepareRetries)), CultureInfo.InvariantCulture); + _progressOps = Convert.ToInt32(FindKey(opts, "ProgressOps", SQLiteConvert.ToString(DefaultProgressOps)), CultureInfo.InvariantCulture); + + enumValue = TryParseEnum(typeof(IsolationLevel), FindKey(opts, "Default IsolationLevel", DefaultIsolationLevel.ToString()), true); + _defaultIsolation = (enumValue is IsolationLevel) ? (IsolationLevel)enumValue : DefaultIsolationLevel; + _defaultIsolation = GetEffectiveIsolationLevel(_defaultIsolation); + + if (_defaultIsolation != ImmediateIsolationLevel && _defaultIsolation != DeferredIsolationLevel) + throw new NotSupportedException("Invalid Default IsolationLevel specified"); + + _baseSchemaName = FindKey(opts, "BaseSchemaName", DefaultBaseSchemaName); + + if (_sql == null) + { + SetupSQLiteBase(opts); + } + + SQLiteOpenFlagsEnum flags = SQLiteOpenFlagsEnum.None; + + if (!SQLiteConvert.ToBoolean(FindKey(opts, "FailIfMissing", DefaultFailIfMissing.ToString()))) + flags |= SQLiteOpenFlagsEnum.Create; + + if (SQLiteConvert.ToBoolean(FindKey(opts, "Read Only", DefaultReadOnly.ToString()))) + { + flags |= SQLiteOpenFlagsEnum.ReadOnly; + // SQLite will return SQLITE_MISUSE on ReadOnly and Create + flags &= ~SQLiteOpenFlagsEnum.Create; + } + else + { + flags |= SQLiteOpenFlagsEnum.ReadWrite; + } + + if (fullUri) + flags |= SQLiteOpenFlagsEnum.Uri; + + _sql.Open(fileName, _vfsName, _flags, flags, maxPoolSize, usePooling); + + _binaryGuid = SQLiteConvert.ToBoolean(FindKey(opts, "BinaryGUID", DefaultBinaryGUID.ToString())); + +#if INTEROP_CODEC || INTEROP_INCLUDE_SEE + string hexPassword = FindKey(opts, "HexPassword", DefaultHexPassword); + + if (hexPassword != null) + { + string error = null; + byte[] hexPasswordBytes = FromHexString(hexPassword, ref error); + + if (hexPasswordBytes == null) + { + throw new FormatException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Cannot parse 'HexPassword' property value into byte values: {0}", + error)); + } + + _sql.SetPassword(hexPasswordBytes); + } + else + { + string password = FindKey(opts, "Password", DefaultPassword); + + if (password != null) + { + byte[] passwordBytes = UTF8Encoding.UTF8.GetBytes( + password); /* throw */ + + _sql.SetPassword(passwordBytes); + } + else if (_password != null) + { + _sql.SetPassword(_password); + } + } + + hexPassword = null; /* IMMUTABLE */ + _password = null; /* IMMUTABLE */ + + if (hidePassword) + { + if (opts.ContainsKey("HexPassword")) + opts["HexPassword"] = String.Empty; + + if (opts.ContainsKey("Password")) + opts["Password"] = String.Empty; + + _connectionString = BuildConnectionString(opts); + } +#else + if (FindKey(opts, "HexPassword", DefaultHexPassword) != null) + { + throw new SQLiteException(SQLiteErrorCode.Error, + "Cannot use \"HexPassword\" connection string property: " + + "library was not built with encryption support, please " + + "see \"https://www.sqlite.org/see\" for more information"); + } + + if (FindKey(opts, "Password", DefaultPassword) != null) + { + throw new SQLiteException(SQLiteErrorCode.Error, + "Cannot use \"Password\" connection string property: " + + "library was not built with encryption support, please " + + "see \"https://www.sqlite.org/see\" for more information"); + } +#endif + + if (!fullUri) + _dataSource = Path.GetFileNameWithoutExtension(fileName); + else + _dataSource = fileName; + + _version++; + + ConnectionState oldstate = _connectionState; + _connectionState = ConnectionState.Open; + + try + { + string strValue; + bool boolValue; + + strValue = FindKey(opts, "SetDefaults", DefaultSetDefaults.ToString()); + boolValue = SQLiteConvert.ToBoolean(strValue); + + if (boolValue) + { + using (SQLiteCommand cmd = CreateCommand()) + { + if (_busyTimeout != DefaultBusyTimeout) + { + cmd.CommandText = HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA busy_timeout={0}", _busyTimeout); + cmd.ExecuteNonQuery(); + } + + int intValue; + + if (!fullUri && !isMemory) + { + strValue = FindKey(opts, "Page Size", SQLiteConvert.ToString(DefaultPageSize)); + intValue = Convert.ToInt32(strValue, CultureInfo.InvariantCulture); + if (intValue != DefaultPageSize) + { + cmd.CommandText = HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA page_size={0}", intValue); + cmd.ExecuteNonQuery(); + } + } + + strValue = FindKey(opts, "Max Page Count", SQLiteConvert.ToString(DefaultMaxPageCount)); + intValue = Convert.ToInt32(strValue, CultureInfo.InvariantCulture); + if (intValue != DefaultMaxPageCount) + { + cmd.CommandText = HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA max_page_count={0}", intValue); + cmd.ExecuteNonQuery(); + } + + strValue = FindKey(opts, "Legacy Format", DefaultLegacyFormat.ToString()); + boolValue = SQLiteConvert.ToBoolean(strValue); + if (boolValue != DefaultLegacyFormat) + { + cmd.CommandText = HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA legacy_file_format={0}", boolValue ? "ON" : "OFF"); + cmd.ExecuteNonQuery(); + } + + strValue = FindKey(opts, "Synchronous", DefaultSynchronous.ToString()); + enumValue = TryParseEnum(typeof(SQLiteSynchronousEnum), strValue, true); + if (!(enumValue is SQLiteSynchronousEnum) || ((SQLiteSynchronousEnum)enumValue != DefaultSynchronous)) + { + cmd.CommandText = HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA synchronous={0}", strValue); + cmd.ExecuteNonQuery(); + } + + strValue = FindKey(opts, "Cache Size", SQLiteConvert.ToString(DefaultCacheSize)); + intValue = Convert.ToInt32(strValue, CultureInfo.InvariantCulture); + if (intValue != DefaultCacheSize) + { + cmd.CommandText = HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA cache_size={0}", intValue); + cmd.ExecuteNonQuery(); + } + + strValue = FindKey(opts, "Journal Mode", DefaultJournalMode.ToString()); + enumValue = TryParseEnum(typeof(SQLiteJournalModeEnum), strValue, true); + if (!(enumValue is SQLiteJournalModeEnum) || ((SQLiteJournalModeEnum)enumValue != DefaultJournalMode)) + { + string pragmaStr = "PRAGMA journal_mode={0}"; + +#if INTEROP_INCLUDE_ZIPVFS + if (useZipVfs) + pragmaStr = "PRAGMA zipvfs_journal_mode={0}"; +#endif + + cmd.CommandText = HelperMethods.StringFormat(CultureInfo.InvariantCulture, pragmaStr, strValue); + cmd.ExecuteNonQuery(); + } + + strValue = FindKey(opts, "Foreign Keys", DefaultForeignKeys.ToString()); + boolValue = SQLiteConvert.ToBoolean(strValue); + if (boolValue != DefaultForeignKeys) + { + cmd.CommandText = HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA foreign_keys={0}", boolValue ? "ON" : "OFF"); + cmd.ExecuteNonQuery(); + } + + strValue = FindKey(opts, "Recursive Triggers", DefaultRecursiveTriggers.ToString()); + boolValue = SQLiteConvert.ToBoolean(strValue); + if (boolValue != DefaultRecursiveTriggers) + { + cmd.CommandText = HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA recursive_triggers={0}", boolValue ? "ON" : "OFF"); + cmd.ExecuteNonQuery(); + } + } + } + + if (_progressHandler != null) + _sql.SetProgressHook(_progressOps, _progressCallback); + + if (_authorizerHandler != null) + _sql.SetAuthorizerHook(_authorizerCallback); + + if (_commitHandler != null) + _sql.SetCommitHook(_commitCallback); + + if (_updateHandler != null) + _sql.SetUpdateHook(_updateCallback); + + if (_rollbackHandler != null) + _sql.SetRollbackHook(_rollbackCallback); + +#if !PLATFORM_COMPACTFRAMEWORK + System.Transactions.Transaction transaction = Transactions.Transaction.Current; + + if (transaction != null && + SQLiteConvert.ToBoolean(FindKey(opts, "Enlist", DefaultEnlist.ToString()))) + { + EnlistTransaction(transaction); + } +#endif + + _connectionState = oldstate; + + StateChangeEventArgs eventArgs = null; + OnStateChange(ConnectionState.Open, ref eventArgs); + + OnChanged(this, new ConnectionEventArgs( + SQLiteConnectionEventType.Opened, eventArgs, null, null, null, + null, eventArgConnectionString, new object[] { eventArgOpts })); + +#if DEBUG + _debugString = HelperMethods.StringFormat( + CultureInfo.InvariantCulture, + "openThreadId = {0}, connectionString = {1}", + HelperMethods.GetThreadId(), + eventArgConnectionString); +#endif + } + catch + { + _connectionState = oldstate; + throw; + } + } + catch (SQLiteException) + { + Close(); + throw; + } + } + + /// + /// Opens the connection using the parameters found in the and then returns it. + /// + /// The current connection object. + public SQLiteConnection OpenAndReturn() + { + CheckDisposed(); Open(); return this; + } + + /// + /// Gets/sets the default command timeout for newly-created commands. This is especially useful for + /// commands used internally such as inside a SQLiteTransaction, where setting the timeout is not possible. + /// This can also be set in the ConnectionString with "Default Timeout" + /// + public int DefaultTimeout + { + get { CheckDisposed(); return _defaultTimeout; } + set { CheckDisposed(); _defaultTimeout = value; } + } + + /// + /// Gets/sets the default busy timeout to use with the SQLite core library. This is only used when + /// opening a connection. + /// + public int BusyTimeout + { + get { CheckDisposed(); return _busyTimeout; } + set { CheckDisposed(); _busyTimeout = value; } + } + +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// EXPERIMENTAL -- + /// The wait timeout to use with method. + /// This is only used when waiting for the enlistment to be reset prior to + /// enlisting in a transaction, and then only when the appropriate connection + /// flag is set. + /// + public int WaitTimeout + { + get { CheckDisposed(); return _waitTimeout; } + set { CheckDisposed(); _waitTimeout = value; } + } +#endif + + /// + /// The maximum number of retries when preparing SQL to be executed. This + /// normally only applies to preparation errors resulting from the database + /// schema being changed. + /// + public int PrepareRetries + { + get { CheckDisposed(); return _prepareRetries; } + set { CheckDisposed(); _prepareRetries = value; } + } + + /// + /// The approximate number of virtual machine instructions between progress + /// events. In order for progress events to actually fire, the event handler + /// must be added to the event as + /// well. This value will only be used when the underlying native progress + /// callback needs to be changed. + /// + public int ProgressOps + { + get { CheckDisposed(); return _progressOps; } + set { CheckDisposed(); _progressOps = value; } + } + + /// + /// Non-zero if the built-in (i.e. framework provided) connection string + /// parser should be used when opening the connection. + /// + public bool ParseViaFramework + { + get { CheckDisposed(); return _parseViaFramework; } + set { CheckDisposed(); _parseViaFramework = value; } + } + + /// + /// Gets/sets the extra behavioral flags for this connection. See the + /// enumeration for a list of + /// possible values. + /// + public SQLiteConnectionFlags Flags + { + get { CheckDisposed(); return _flags; } + set { CheckDisposed(); _flags = value; } + } + + /// + /// Gets/sets the default database type for this connection. This value + /// will only be used when not null. + /// + public DbType? DefaultDbType + { + get { CheckDisposed(); return _defaultDbType; } + set { CheckDisposed(); _defaultDbType = value; } + } + + /// + /// Gets/sets the default database type name for this connection. This + /// value will only be used when not null. + /// + public string DefaultTypeName + { + get { CheckDisposed(); return _defaultTypeName; } + set { CheckDisposed(); _defaultTypeName = value; } + } + + /// + /// Gets/sets the VFS name for this connection. This value will only be + /// used when opening the database. + /// + public string VfsName + { + get { CheckDisposed(); return _vfsName; } + set { CheckDisposed(); _vfsName = value; } + } + + /// + /// Returns non-zero if the underlying native connection handle is + /// owned by this instance. + /// + public bool OwnHandle + { + get + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException("Database connection not valid for checking handle."); + + return _sql.OwnHandle; + } + } + + /// + /// Returns the version of the underlying SQLite database engine + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] +#endif + public override string ServerVersion + { + get + { + CheckDisposed(); + return SQLiteVersion; + //if (_connectionState != ConnectionState.Open) + // throw new InvalidOperationException(); + + //return _sql.Version; + } + } + + /// + /// Returns the rowid of the most recent successful INSERT into the database from this connection. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] +#endif + public long LastInsertRowId + { + get + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException("Database connection not valid for getting last insert rowid."); + + return _sql.LastInsertRowId; + } + } + + /// + /// This method causes any pending database operation to abort and return at + /// its earliest opportunity. This routine is typically called in response + /// to a user action such as pressing "Cancel" or Ctrl-C where the user wants + /// a long query operation to halt immediately. It is safe to call this + /// routine from any thread. However, it is not safe to call this routine + /// with a database connection that is closed or might close before this method + /// returns. + /// + public void Cancel() + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException("Database connection not valid for query cancellation."); + + _sql.Cancel(); /* throw */ + } + + /// + /// Returns the number of rows changed by the last INSERT, UPDATE, or DELETE statement executed on + /// this connection. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] +#endif + public int Changes + { + get + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException("Database connection not valid for getting number of changes."); + + return _sql.Changes; + } + } + + /// + /// Checks if this connection to the specified database should be considered + /// read-only. An exception will be thrown if the database name specified + /// via cannot be found. + /// + /// + /// The name of a database associated with this connection -OR- null for the + /// main database. + /// + /// + /// Non-zero if this connection to the specified database should be considered + /// read-only. + /// + public bool IsReadOnly( + string name + ) + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException("Database connection not valid for checking read-only status."); + + return _sql.IsReadOnly(name); + } + + /// + /// Returns non-zero if the given database connection is in autocommit mode. + /// Autocommit mode is on by default. Autocommit mode is disabled by a BEGIN + /// statement. Autocommit mode is re-enabled by a COMMIT or ROLLBACK. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] +#endif + public bool AutoCommit + { + get + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException("Database connection not valid for getting autocommit mode."); + + return _sql.AutoCommit; + } + } + + /// + /// Returns the amount of memory (in bytes) currently in use by the SQLite core library. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] +#endif + public long MemoryUsed + { + get + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException("Database connection not valid for getting memory used."); + + return _sql.MemoryUsed; + } + } + + /// + /// Returns the maximum amount of memory (in bytes) used by the SQLite core library since the high-water mark was last reset. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] +#endif + public long MemoryHighwater + { + get + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException("Database connection not valid for getting maximum memory used."); + + return _sql.MemoryHighwater; + } + } + + /// + /// Returns various global memory statistics for the SQLite core library via + /// a dictionary of key/value pairs. Currently, only the "MemoryUsed" and + /// "MemoryHighwater" keys are returned and they have values that correspond + /// to the values that could be obtained via the + /// and connection properties. + /// + /// + /// This dictionary will be populated with the global memory statistics. It + /// will be created if necessary. + /// + public static void GetMemoryStatistics( + ref IDictionary statistics + ) + { + if (statistics == null) + statistics = new Dictionary(); + + statistics["MemoryUsed"] = SQLite3.StaticMemoryUsed; + statistics["MemoryHighwater"] = SQLite3.StaticMemoryHighwater; + } + + /// + /// Attempts to free as much heap memory as possible for this database connection. + /// + public void ReleaseMemory() + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException("Database connection not valid for releasing memory."); + + SQLiteErrorCode rc = _sql.ReleaseMemory(); + + if (rc != SQLiteErrorCode.Ok) + { + throw new SQLiteException(rc, + _sql.GetLastError("Could not release connection memory.")); + } + } + + /// + /// Attempts to free N bytes of heap memory by deallocating non-essential memory + /// allocations held by the database library. Memory used to cache database pages + /// to improve performance is an example of non-essential memory. This is a no-op + /// returning zero if the SQLite core library was not compiled with the compile-time + /// option SQLITE_ENABLE_MEMORY_MANAGEMENT. Optionally, attempts to reset and/or + /// compact the Win32 native heap, if applicable. + /// + /// + /// The requested number of bytes to free. + /// + /// + /// Non-zero to attempt a heap reset. + /// + /// + /// Non-zero to attempt heap compaction. + /// + /// + /// The number of bytes actually freed. This value may be zero. + /// + /// + /// This value will be non-zero if the heap reset was successful. + /// + /// + /// The size of the largest committed free block in the heap, in bytes. + /// This value will be zero unless heap compaction is enabled. + /// + /// + /// A standard SQLite return code (i.e. zero for success and non-zero + /// for failure). + /// + #pragma warning disable 3001 + public static SQLiteErrorCode ReleaseMemory( + int nBytes, + bool reset, + bool compact, + ref int nFree, + ref bool resetOk, + ref uint nLargest + ) + { + return SQLite3.StaticReleaseMemory( + nBytes, reset, compact, ref nFree, ref resetOk, ref nLargest); + } + #pragma warning restore 3001 + + /// + /// Sets the status of the memory usage tracking subsystem in the SQLite core library. By default, this is enabled. + /// If this is disabled, memory usage tracking will not be performed. This is not really a per-connection value, it is + /// global to the process. + /// + /// Non-zero to enable memory usage tracking, zero otherwise. + /// A standard SQLite return code (i.e. zero for success and non-zero for failure). + public static SQLiteErrorCode SetMemoryStatus(bool value) + { + return SQLite3.StaticSetMemoryStatus(value); + } + + /// + /// Returns a string containing the define constants (i.e. compile-time + /// options) used to compile the core managed assembly, delimited with + /// spaces. + /// + public static string DefineConstants + { + get { return SQLite3.DefineConstants; } + } + + /// + /// Returns the version of the underlying SQLite core library. + /// + public static string SQLiteVersion + { + get { return SQLite3.SQLiteVersion; } + } + + /// + /// This method returns the string whose value is the same as the + /// SQLITE_SOURCE_ID C preprocessor macro used when compiling the + /// SQLite core library. + /// + public static string SQLiteSourceId + { + get { return SQLite3.SQLiteSourceId; } + } + + /// + /// Returns a string containing the compile-time options used to + /// compile the SQLite core native library, delimited with spaces. + /// + public static string SQLiteCompileOptions + { + get { return SQLite3.SQLiteCompileOptions; } + } + + /// + /// This method returns the version of the interop SQLite assembly + /// used. If the SQLite interop assembly is not in use or the + /// necessary information cannot be obtained for any reason, a null + /// value may be returned. + /// + public static string InteropVersion + { + get { return SQLite3.InteropVersion; } + } + + /// + /// This method returns the string whose value contains the unique + /// identifier for the source checkout used to build the interop + /// assembly. If the SQLite interop assembly is not in use or the + /// necessary information cannot be obtained for any reason, a null + /// value may be returned. + /// + public static string InteropSourceId + { + get { return SQLite3.InteropSourceId; } + } + + /// + /// Returns a string containing the compile-time options used to + /// compile the SQLite interop assembly, delimited with spaces. + /// + public static string InteropCompileOptions + { + get { return SQLite3.InteropCompileOptions; } + } + + /// + /// This method returns the version of the managed components used + /// to interact with the SQLite core library. If the necessary + /// information cannot be obtained for any reason, a null value may + /// be returned. + /// + public static string ProviderVersion + { + get + { + return (_assembly != null) ? + _assembly.GetName().Version.ToString() : null; + } + } + + /// + /// This method returns the string whose value contains the unique + /// identifier for the source checkout used to build the managed + /// components currently executing. If the necessary information + /// cannot be obtained for any reason, a null value may be returned. + /// + public static string ProviderSourceId + { + get + { + if (_assembly == null) + return null; + + string sourceId = null; + + if (_assembly.IsDefined(typeof(AssemblySourceIdAttribute), false)) + { + AssemblySourceIdAttribute attribute = + (AssemblySourceIdAttribute)_assembly.GetCustomAttributes( + typeof(AssemblySourceIdAttribute), false)[0]; + + sourceId = attribute.SourceId; + } + + string sourceTimeStamp = null; + + if (_assembly.IsDefined(typeof(AssemblySourceTimeStampAttribute), false)) + { + AssemblySourceTimeStampAttribute attribute = + (AssemblySourceTimeStampAttribute)_assembly.GetCustomAttributes( + typeof(AssemblySourceTimeStampAttribute), false)[0]; + + sourceTimeStamp = attribute.SourceTimeStamp; + } + + if ((sourceId != null) || (sourceTimeStamp != null)) + { + if (sourceId == null) + sourceId = "0000000000000000000000000000000000000000"; + + if (sourceTimeStamp == null) + sourceTimeStamp = "0000-00-00 00:00:00 UTC"; + + return HelperMethods.StringFormat( + CultureInfo.InvariantCulture, + "{0} {1}", sourceId, sourceTimeStamp); + } + else + { + return null; + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Queries and returns the value of the specified setting, using the + /// cached setting names and values for the last connection that used + /// the method, when available. + /// + /// + /// The name of the setting. + /// + /// + /// The value to be returned if the setting has not been set explicitly + /// or cannot be determined. + /// + /// + /// The value of the cached setting is stored here if found; otherwise, + /// the value of is stored here. + /// + /// + /// Non-zero if the cached setting was found; otherwise, zero. + /// + private static bool TryGetLastCachedSetting( + string name, + object @default, + out object value + ) + { + if (_lastConnectionInOpen == null) + { + value = @default; + return false; + } + + return _lastConnectionInOpen.TryGetCachedSetting( + name, @default, out value); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Adds or sets the cached setting specified by + /// to the value specified by using the cached + /// setting names and values for the last connection that used the + /// method, when available. + /// + /// + /// The name of the cached setting to add or replace. + /// + /// + /// The new value of the cached setting. + /// + private static void SetLastCachedSetting( + string name, /* in */ + object value /* in */ + ) + { + if (_lastConnectionInOpen == null) + return; + + _lastConnectionInOpen.SetCachedSetting(name, value); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// The default connection flags to be used for all opened connections + /// when they are not present in the connection string. + /// + public static SQLiteConnectionFlags DefaultFlags + { + get + { + string name = "DefaultFlags_SQLiteConnection"; + object value; + + if (!TryGetLastCachedSetting(name, null, out value)) + { + value = UnsafeNativeMethods.GetSettingValue(name, null); + SetLastCachedSetting(name, value); + } + + if (value == null) + return FallbackDefaultFlags; + + object enumValue = TryParseEnum( + typeof(SQLiteConnectionFlags), value.ToString(), true); + + if (enumValue is SQLiteConnectionFlags) + return (SQLiteConnectionFlags)enumValue; + + return FallbackDefaultFlags; + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// The extra connection flags to be used for all opened connections. + /// + public static SQLiteConnectionFlags SharedFlags + { + get { lock (_syncRoot) { return _sharedFlags; } } + set { lock (_syncRoot) { _sharedFlags = value; } } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Returns the state of the connection. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] +#endif + public override ConnectionState State + { + get + { + CheckDisposed(); + return _connectionState; + } + } + + /// + /// Passes a shutdown request to the SQLite core library. Does not throw + /// an exception if the shutdown request fails. + /// + /// + /// A standard SQLite return code (i.e. zero for success and non-zero for + /// failure). + /// + public SQLiteErrorCode Shutdown() + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException("Database connection not valid for shutdown."); + + _sql.Close(false); /* NOTE: MUST be closed before shutdown. */ + SQLiteErrorCode rc = _sql.Shutdown(); + +#if !NET_COMPACT_20 && TRACE_CONNECTION + if (rc != SQLiteErrorCode.Ok) + System.Diagnostics.Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Shutdown (Instance) Failed: {0}", rc)); +#endif + + return rc; + } + + /// + /// Passes a shutdown request to the SQLite core library. Throws an + /// exception if the shutdown request fails and the no-throw parameter + /// is non-zero. + /// + /// + /// Non-zero to reset the database and temporary directories to their + /// default values, which should be null for both. + /// + /// + /// When non-zero, throw an exception if the shutdown request fails. + /// + public static void Shutdown( + bool directories, + bool noThrow + ) + { + SQLiteErrorCode rc = SQLite3.StaticShutdown(directories); + + if (rc != SQLiteErrorCode.Ok) + { +#if !NET_COMPACT_20 && TRACE_CONNECTION + System.Diagnostics.Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Shutdown (Static) Failed: {0}", rc)); +#endif + + if (!noThrow) + throw new SQLiteException(rc, null); + } + } + + /// Enables or disabled extended result codes returned by SQLite + public void SetExtendedResultCodes(bool bOnOff) + { + CheckDisposed(); + + if (_sql != null) _sql.SetExtendedResultCodes(bOnOff); + } + /// Enables or disabled extended result codes returned by SQLite + public SQLiteErrorCode ResultCode() + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException("Database connection not valid for getting result code."); + return _sql.ResultCode(); + } + /// Enables or disabled extended result codes returned by SQLite + public SQLiteErrorCode ExtendedResultCode() + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException("Database connection not valid for getting extended result code."); + return _sql.ExtendedResultCode(); + } + + /// Add a log message via the SQLite sqlite3_log interface. + public void LogMessage(SQLiteErrorCode iErrCode, string zMessage) + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException("Database connection not valid for logging message."); + + _sql.LogMessage(iErrCode, zMessage); + } + + /// Add a log message via the SQLite sqlite3_log interface. + public void LogMessage(int iErrCode, string zMessage) + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException("Database connection not valid for logging message."); + + _sql.LogMessage((SQLiteErrorCode)iErrCode, zMessage); + } + +#if INTEROP_CODEC || INTEROP_INCLUDE_SEE + /// + /// Change the password (or assign a password) to an open database. + /// + /// + /// No readers or writers may be active for this process. The database must already be open + /// and if it already was password protected, the existing password must already have been supplied. + /// + /// The new password to assign to the database + public void ChangePassword(string newPassword) + { + CheckDisposed(); + + if (!String.IsNullOrEmpty(newPassword)) + { + byte[] newPasswordBytes = UTF8Encoding.UTF8.GetBytes( + newPassword); /* throw */ + + ChangePassword(newPasswordBytes); + } + else + { + ChangePassword((byte[])null); + } + } + + /// + /// Change the password (or assign a password) to an open database. + /// + /// + /// No readers or writers may be active for this process. The database must already be open + /// and if it already was password protected, the existing password must already have been supplied. + /// + /// The new password to assign to the database + public void ChangePassword(byte[] newPassword) + { + CheckDisposed(); + + if (_connectionState != ConnectionState.Open) + throw new InvalidOperationException("Database must be opened before changing the password."); + + _sql.ChangePassword(newPassword); + } + + /// + /// Sets the password for a password-protected database. A password-protected database is + /// unusable for any operation until the password has been set. + /// + /// The password for the database + public void SetPassword(string databasePassword) + { + CheckDisposed(); + + if (!String.IsNullOrEmpty(databasePassword)) + { + byte[] databasePasswordBytes = UTF8Encoding.UTF8.GetBytes( + databasePassword); /* throw */ + + SetPassword(databasePasswordBytes); + } + else + { + SetPassword((byte[])null); + } + } + + /// + /// Sets the password for a password-protected database. A password-protected database is + /// unusable for any operation until the password has been set. + /// + /// The password for the database + public void SetPassword(byte[] databasePassword) + { + CheckDisposed(); + + if (_connectionState != ConnectionState.Closed) + throw new InvalidOperationException("Password can only be set before the database is opened."); + + if (databasePassword != null) + if (databasePassword.Length == 0) databasePassword = null; + + if ((databasePassword != null) && + HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.HidePassword)) + { + throw new InvalidOperationException( + "With 'HidePassword' enabled, passwords can only be set via the connection string."); + } + + _password = databasePassword; + } +#endif + + /// + /// Queries or modifies the number of retries or the retry interval (in milliseconds) for + /// certain I/O operations that may fail due to anti-virus software. + /// + /// The number of times to retry the I/O operation. A negative value + /// will cause the current count to be queried and replace that negative value. + /// The number of milliseconds to wait before retrying the I/O + /// operation. This number is multiplied by the number of retry attempts so far to come + /// up with the final number of milliseconds to wait. A negative value will cause the + /// current interval to be queried and replace that negative value. + /// Zero for success, non-zero for error. + public SQLiteErrorCode SetAvRetry(ref int count, ref int interval) + { + CheckDisposed(); + + if (_connectionState != ConnectionState.Open) + throw new InvalidOperationException( + "Database must be opened before changing the AV retry parameters."); + + SQLiteErrorCode rc; + IntPtr pArg = IntPtr.Zero; + + try + { + pArg = Marshal.AllocHGlobal(sizeof(int) * 2); + + Marshal.WriteInt32(pArg, 0, count); + Marshal.WriteInt32(pArg, sizeof(int), interval); + + rc = _sql.FileControl(null, SQLITE_FCNTL_WIN32_AV_RETRY, pArg); + + if (rc == SQLiteErrorCode.Ok) + { + count = Marshal.ReadInt32(pArg, 0); + interval = Marshal.ReadInt32(pArg, sizeof(int)); + } + } + finally + { + if (pArg != IntPtr.Zero) + Marshal.FreeHGlobal(pArg); + } + + return rc; + } + + /// + /// Sets the chunk size for the primary file associated with this database + /// connection. + /// + /// + /// The new chunk size for the main database, in bytes. + /// + /// + /// Zero for success, non-zero for error. + /// + public SQLiteErrorCode SetChunkSize(int size) + { + CheckDisposed(); + + if (_connectionState != ConnectionState.Open) + throw new InvalidOperationException( + "Database must be opened before changing the chunk size."); + + IntPtr pArg = IntPtr.Zero; + + try + { + pArg = Marshal.AllocHGlobal(sizeof(int) * 1); + + Marshal.WriteInt32(pArg, 0, size); + + return _sql.FileControl(null, SQLITE_FCNTL_CHUNK_SIZE, pArg); + } + finally + { + if (pArg != IntPtr.Zero) + Marshal.FreeHGlobal(pArg); + } + } + + /// + /// Removes one set of surrounding single -OR- double quotes from the string + /// value and returns the resulting string value. If the string is null, empty, + /// or contains quotes that are not balanced, nothing is done and the original + /// string value will be returned. + /// + /// The string value to process. + /// + /// The string value, modified to remove one set of surrounding single -OR- + /// double quotes, if applicable. + /// + private static string UnwrapString(string value) + { + if (String.IsNullOrEmpty(value)) + { + // + // NOTE: The string is null or empty, return it verbatim. + // + return value; + } + + int length = value.Length; + + if ((value[0] == SQLiteConvert.QuoteChar) && + (value[length - 1] == SQLiteConvert.QuoteChar)) + { + // + // NOTE: Remove the first and last character, which are + // both double quotes. + // + return value.Substring(1, length - 2); + } + + if ((value[0] == SQLiteConvert.AltQuoteChar) && + (value[length - 1] == SQLiteConvert.AltQuoteChar)) + { + // + // NOTE: Remove the first and last character, which are + // both single quotes. + // + return value.Substring(1, length - 2); + } + + // + // NOTE: No match, return the input string verbatim. + // + return value; + } + + /// + /// Determines the directory to be used when dealing with the "|DataDirectory|" + /// macro in a database file name. + /// + /// + /// The directory to use in place of the "|DataDirectory|" macro -OR- null if it + /// cannot be determined. + /// + private static string GetDataDirectory() + { +#if PLATFORM_COMPACTFRAMEWORK + string result = Path.GetDirectoryName( + Assembly.GetCallingAssembly().GetName().CodeBase); +#else + string result = AppDomain.CurrentDomain.GetData( + "DataDirectory") as string; + + if (String.IsNullOrEmpty(result)) + result = AppDomain.CurrentDomain.BaseDirectory; +#endif + + return result; + } + + /// + /// Expand the filename of the data source, resolving the |DataDirectory| + /// macro as appropriate. + /// + /// The database filename to expand + /// + /// Non-zero if the returned file name should be converted to a full path + /// (except when using the .NET Compact Framework). + /// + /// The expanded path and filename of the filename + private static string ExpandFileName(string sourceFile, bool toFullPath) + { + if (String.IsNullOrEmpty(sourceFile)) return sourceFile; + + if (sourceFile.StartsWith(_dataDirectory, StringComparison.OrdinalIgnoreCase)) + { + string dataDirectory = GetDataDirectory(); + + if (sourceFile.Length > _dataDirectory.Length) + { + if (sourceFile[_dataDirectory.Length] == Path.DirectorySeparatorChar || + sourceFile[_dataDirectory.Length] == Path.AltDirectorySeparatorChar) + sourceFile = sourceFile.Remove(_dataDirectory.Length, 1); + } + sourceFile = Path.Combine(dataDirectory, sourceFile.Substring(_dataDirectory.Length)); + } + +#if !PLATFORM_COMPACTFRAMEWORK + if (toFullPath) + sourceFile = Path.GetFullPath(sourceFile); +#endif + + return sourceFile; + } + + /// + /// The following commands are used to extract schema information out of the database. Valid schema types are: + /// + /// + /// MetaDataCollections + /// + /// + /// DataSourceInformation + /// + /// + /// Catalogs + /// + /// + /// Columns + /// + /// + /// ForeignKeys + /// + /// + /// Indexes + /// + /// + /// IndexColumns + /// + /// + /// Tables + /// + /// + /// Views + /// + /// + /// ViewColumns + /// + /// + /// + /// + /// Returns the MetaDataCollections schema + /// + /// A DataTable of the MetaDataCollections schema + public override DataTable GetSchema() + { + CheckDisposed(); + return GetSchema("MetaDataCollections", null); + } + + /// + /// Returns schema information of the specified collection + /// + /// The schema collection to retrieve + /// A DataTable of the specified collection + public override DataTable GetSchema(string collectionName) + { + CheckDisposed(); + return GetSchema(collectionName, new string[0]); + } + + /// + /// Retrieves schema information using the specified constraint(s) for the specified collection + /// + /// The collection to retrieve. + /// + /// The restrictions to impose. Typically, this may include: + /// + /// + /// restrictionValues element index + /// usage + /// + /// + /// 0 + /// The database (or catalog) name, if applicable. + /// + /// + /// 1 + /// The schema name. This is not used by this provider. + /// + /// + /// 2 + /// The table name, if applicable. + /// + /// + /// 3 + /// + /// Depends on . + /// When "IndexColumns", it is the index name; otherwise, it is the column name. + /// + /// + /// + /// 4 + /// + /// Depends on . + /// When "IndexColumns", it is the column name; otherwise, it is not used. + /// + /// + /// + /// + /// A DataTable of the specified collection + public override DataTable GetSchema(string collectionName, string[] restrictionValues) + { + CheckDisposed(); + + if (_connectionState != ConnectionState.Open) + throw new InvalidOperationException(); + + string[] parms = new string[5]; + + if (restrictionValues == null) restrictionValues = new string[0]; + restrictionValues.CopyTo(parms, 0); + + switch (collectionName.ToUpper(CultureInfo.InvariantCulture)) + { + case "METADATACOLLECTIONS": + return Schema_MetaDataCollections(); + case "DATASOURCEINFORMATION": + return Schema_DataSourceInformation(); + case "DATATYPES": + return Schema_DataTypes(); + case "COLUMNS": + case "TABLECOLUMNS": + return Schema_Columns(parms[0], parms[2], parms[3]); + case "INDEXES": + return Schema_Indexes(parms[0], parms[2], parms[3]); + case "TRIGGERS": + return Schema_Triggers(parms[0], parms[2], parms[3]); + case "INDEXCOLUMNS": + return Schema_IndexColumns(parms[0], parms[2], parms[3], parms[4]); + case "TABLES": + return Schema_Tables(parms[0], parms[2], parms[3]); + case "VIEWS": + return Schema_Views(parms[0], parms[2]); + case "VIEWCOLUMNS": + return Schema_ViewColumns(parms[0], parms[2], parms[3]); + case "FOREIGNKEYS": + return Schema_ForeignKeys(parms[0], parms[2], parms[3]); + case "CATALOGS": + return Schema_Catalogs(parms[0]); + case "RESERVEDWORDS": + return Schema_ReservedWords(); + } + throw new NotSupportedException(); + } + + private static DataTable Schema_ReservedWords() + { + DataTable tbl = new DataTable("ReservedWords"); + + tbl.Locale = CultureInfo.InvariantCulture; + tbl.Columns.Add("ReservedWord", typeof(string)); + tbl.Columns.Add("MaximumVersion", typeof(string)); + tbl.Columns.Add("MinimumVersion", typeof(string)); + + tbl.BeginLoadData(); + DataRow row; + foreach (string word in SR.Keywords.Split(new char[] { ',' })) + { + row = tbl.NewRow(); + row[0] = word; + tbl.Rows.Add(row); + } + + tbl.AcceptChanges(); + tbl.EndLoadData(); + + return tbl; + } + + /// + /// Builds a MetaDataCollections schema datatable + /// + /// DataTable + private static DataTable Schema_MetaDataCollections() + { + DataTable tbl = new DataTable("MetaDataCollections"); + + tbl.Locale = CultureInfo.InvariantCulture; + tbl.Columns.Add("CollectionName", typeof(string)); + tbl.Columns.Add("NumberOfRestrictions", typeof(int)); + tbl.Columns.Add("NumberOfIdentifierParts", typeof(int)); + + tbl.BeginLoadData(); + + StringReader reader = new StringReader(SR.MetaDataCollections); + tbl.ReadXml(reader); + reader.Close(); + + tbl.AcceptChanges(); + tbl.EndLoadData(); + + return tbl; + } + + /// + /// Builds a DataSourceInformation datatable + /// + /// DataTable + private DataTable Schema_DataSourceInformation() + { + DataTable tbl = new DataTable("DataSourceInformation"); + DataRow row; + + tbl.Locale = CultureInfo.InvariantCulture; + tbl.Columns.Add(DbMetaDataColumnNames.CompositeIdentifierSeparatorPattern, typeof(string)); + tbl.Columns.Add(DbMetaDataColumnNames.DataSourceProductName, typeof(string)); + tbl.Columns.Add(DbMetaDataColumnNames.DataSourceProductVersion, typeof(string)); + tbl.Columns.Add(DbMetaDataColumnNames.DataSourceProductVersionNormalized, typeof(string)); + tbl.Columns.Add(DbMetaDataColumnNames.GroupByBehavior, typeof(int)); + tbl.Columns.Add(DbMetaDataColumnNames.IdentifierPattern, typeof(string)); + tbl.Columns.Add(DbMetaDataColumnNames.IdentifierCase, typeof(int)); + tbl.Columns.Add(DbMetaDataColumnNames.OrderByColumnsInSelect, typeof(bool)); + tbl.Columns.Add(DbMetaDataColumnNames.ParameterMarkerFormat, typeof(string)); + tbl.Columns.Add(DbMetaDataColumnNames.ParameterMarkerPattern, typeof(string)); + tbl.Columns.Add(DbMetaDataColumnNames.ParameterNameMaxLength, typeof(int)); + tbl.Columns.Add(DbMetaDataColumnNames.ParameterNamePattern, typeof(string)); + tbl.Columns.Add(DbMetaDataColumnNames.QuotedIdentifierPattern, typeof(string)); + tbl.Columns.Add(DbMetaDataColumnNames.QuotedIdentifierCase, typeof(int)); + tbl.Columns.Add(DbMetaDataColumnNames.StatementSeparatorPattern, typeof(string)); + tbl.Columns.Add(DbMetaDataColumnNames.StringLiteralPattern, typeof(string)); + tbl.Columns.Add(DbMetaDataColumnNames.SupportedJoinOperators, typeof(int)); + + tbl.BeginLoadData(); + + row = tbl.NewRow(); + row.ItemArray = new object[] { + null, + "SQLite", + _sql.Version, + _sql.Version, + 3, + @"(^\[\p{Lo}\p{Lu}\p{Ll}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Nd}@$#_]*$)|(^\[[^\]\0]|\]\]+\]$)|(^\""[^\""\0]|\""\""+\""$)", + 1, + false, + "{0}", + @"@[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)", + 255, + @"^[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)", + @"(([^\[]|\]\])*)", + 1, + ";", + @"'(([^']|'')*)'", + 15 + }; + tbl.Rows.Add(row); + + tbl.AcceptChanges(); + tbl.EndLoadData(); + + return tbl; + } + + /// + /// Build a Columns schema + /// + /// The catalog (attached database) to query, can be null + /// The table to retrieve schema information for, can be null + /// The column to retrieve schema information for, can be null + /// DataTable + private DataTable Schema_Columns(string strCatalog, string strTable, string strColumn) + { + DataTable tbl = new DataTable("Columns"); + DataRow row; + + tbl.Locale = CultureInfo.InvariantCulture; + tbl.Columns.Add("TABLE_CATALOG", typeof(string)); + tbl.Columns.Add("TABLE_SCHEMA", typeof(string)); + tbl.Columns.Add("TABLE_NAME", typeof(string)); + tbl.Columns.Add("COLUMN_NAME", typeof(string)); + tbl.Columns.Add("COLUMN_GUID", typeof(Guid)); + tbl.Columns.Add("COLUMN_PROPID", typeof(long)); + tbl.Columns.Add("ORDINAL_POSITION", typeof(int)); + tbl.Columns.Add("COLUMN_HASDEFAULT", typeof(bool)); + tbl.Columns.Add("COLUMN_DEFAULT", typeof(string)); + tbl.Columns.Add("COLUMN_FLAGS", typeof(long)); + tbl.Columns.Add("IS_NULLABLE", typeof(bool)); + tbl.Columns.Add("DATA_TYPE", typeof(string)); + tbl.Columns.Add("TYPE_GUID", typeof(Guid)); + tbl.Columns.Add("CHARACTER_MAXIMUM_LENGTH", typeof(int)); + tbl.Columns.Add("CHARACTER_OCTET_LENGTH", typeof(int)); + tbl.Columns.Add("NUMERIC_PRECISION", typeof(int)); + tbl.Columns.Add("NUMERIC_SCALE", typeof(int)); + tbl.Columns.Add("DATETIME_PRECISION", typeof(long)); + tbl.Columns.Add("CHARACTER_SET_CATALOG", typeof(string)); + tbl.Columns.Add("CHARACTER_SET_SCHEMA", typeof(string)); + tbl.Columns.Add("CHARACTER_SET_NAME", typeof(string)); + tbl.Columns.Add("COLLATION_CATALOG", typeof(string)); + tbl.Columns.Add("COLLATION_SCHEMA", typeof(string)); + tbl.Columns.Add("COLLATION_NAME", typeof(string)); + tbl.Columns.Add("DOMAIN_CATALOG", typeof(string)); + tbl.Columns.Add("DOMAIN_NAME", typeof(string)); + tbl.Columns.Add("DESCRIPTION", typeof(string)); + tbl.Columns.Add("PRIMARY_KEY", typeof(bool)); + tbl.Columns.Add("EDM_TYPE", typeof(string)); + tbl.Columns.Add("AUTOINCREMENT", typeof(bool)); + tbl.Columns.Add("UNIQUE", typeof(bool)); + + tbl.BeginLoadData(); + + if (String.IsNullOrEmpty(strCatalog)) strCatalog = GetDefaultCatalogName(); + + string master = GetMasterTableName(IsTemporaryCatalogName(strCatalog)); + + using (SQLiteCommand cmdTables = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table' OR [type] LIKE 'view'", strCatalog, master), this)) + using (SQLiteDataReader rdTables = cmdTables.ExecuteReader()) + { + while (rdTables.Read()) + { + if (String.IsNullOrEmpty(strTable) || String.Compare(strTable, rdTables.GetString(2), StringComparison.OrdinalIgnoreCase) == 0) + { + try + { + using (SQLiteCommand cmd = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}]", strCatalog, rdTables.GetString(2)), this)) + using (SQLiteDataReader rd = (SQLiteDataReader)cmd.ExecuteReader(CommandBehavior.SchemaOnly)) + using (DataTable tblSchema = rd.GetSchemaTable(true, true)) + { + foreach (DataRow schemaRow in tblSchema.Rows) + { + if (String.Compare(schemaRow[SchemaTableColumn.ColumnName].ToString(), strColumn, StringComparison.OrdinalIgnoreCase) == 0 + || strColumn == null) + { + row = tbl.NewRow(); + + row["NUMERIC_PRECISION"] = schemaRow[SchemaTableColumn.NumericPrecision]; + row["NUMERIC_SCALE"] = schemaRow[SchemaTableColumn.NumericScale]; + row["TABLE_NAME"] = rdTables.GetString(2); + row["COLUMN_NAME"] = schemaRow[SchemaTableColumn.ColumnName]; + row["TABLE_CATALOG"] = strCatalog; + row["ORDINAL_POSITION"] = schemaRow[SchemaTableColumn.ColumnOrdinal]; + row["COLUMN_HASDEFAULT"] = (schemaRow[SchemaTableOptionalColumn.DefaultValue] != DBNull.Value); + row["COLUMN_DEFAULT"] = schemaRow[SchemaTableOptionalColumn.DefaultValue]; + row["IS_NULLABLE"] = schemaRow[SchemaTableColumn.AllowDBNull]; + row["DATA_TYPE"] = schemaRow["DataTypeName"].ToString().ToLower(CultureInfo.InvariantCulture); + row["EDM_TYPE"] = SQLiteConvert.DbTypeToTypeName(this, (DbType)schemaRow[SchemaTableColumn.ProviderType], _flags).ToString().ToLower(CultureInfo.InvariantCulture); + row["CHARACTER_MAXIMUM_LENGTH"] = schemaRow[SchemaTableColumn.ColumnSize]; + row["TABLE_SCHEMA"] = schemaRow[SchemaTableColumn.BaseSchemaName]; + row["PRIMARY_KEY"] = schemaRow[SchemaTableColumn.IsKey]; + row["AUTOINCREMENT"] = schemaRow[SchemaTableOptionalColumn.IsAutoIncrement]; + row["COLLATION_NAME"] = schemaRow["CollationType"]; + row["UNIQUE"] = schemaRow[SchemaTableColumn.IsUnique]; + tbl.Rows.Add(row); + } + } + } + } + catch(SQLiteException) + { + } + } + } + } + + tbl.AcceptChanges(); + tbl.EndLoadData(); + + return tbl; + } + + /// + /// Returns index information for the given database and catalog + /// + /// The catalog (attached database) to query, can be null + /// The name of the index to retrieve information for, can be null + /// The table to retrieve index information for, can be null + /// DataTable + private DataTable Schema_Indexes(string strCatalog, string strTable, string strIndex) + { + DataTable tbl = new DataTable("Indexes"); + DataRow row; + List primaryKeys = new List(); + bool maybeRowId; + + tbl.Locale = CultureInfo.InvariantCulture; + tbl.Columns.Add("TABLE_CATALOG", typeof(string)); + tbl.Columns.Add("TABLE_SCHEMA", typeof(string)); + tbl.Columns.Add("TABLE_NAME", typeof(string)); + tbl.Columns.Add("INDEX_CATALOG", typeof(string)); + tbl.Columns.Add("INDEX_SCHEMA", typeof(string)); + tbl.Columns.Add("INDEX_NAME", typeof(string)); + tbl.Columns.Add("PRIMARY_KEY", typeof(bool)); + tbl.Columns.Add("UNIQUE", typeof(bool)); + tbl.Columns.Add("CLUSTERED", typeof(bool)); + tbl.Columns.Add("TYPE", typeof(int)); + tbl.Columns.Add("FILL_FACTOR", typeof(int)); + tbl.Columns.Add("INITIAL_SIZE", typeof(int)); + tbl.Columns.Add("NULLS", typeof(int)); + tbl.Columns.Add("SORT_BOOKMARKS", typeof(bool)); + tbl.Columns.Add("AUTO_UPDATE", typeof(bool)); + tbl.Columns.Add("NULL_COLLATION", typeof(int)); + tbl.Columns.Add("ORDINAL_POSITION", typeof(int)); + tbl.Columns.Add("COLUMN_NAME", typeof(string)); + tbl.Columns.Add("COLUMN_GUID", typeof(Guid)); + tbl.Columns.Add("COLUMN_PROPID", typeof(long)); + tbl.Columns.Add("COLLATION", typeof(short)); + tbl.Columns.Add("CARDINALITY", typeof(Decimal)); + tbl.Columns.Add("PAGES", typeof(int)); + tbl.Columns.Add("FILTER_CONDITION", typeof(string)); + tbl.Columns.Add("INTEGRATED", typeof(bool)); + tbl.Columns.Add("INDEX_DEFINITION", typeof(string)); + + tbl.BeginLoadData(); + + if (String.IsNullOrEmpty(strCatalog)) strCatalog = GetDefaultCatalogName(); + + string master = GetMasterTableName(IsTemporaryCatalogName(strCatalog)); + + using (SQLiteCommand cmdTables = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table'", strCatalog, master), this)) + using (SQLiteDataReader rdTables = cmdTables.ExecuteReader()) + { + while (rdTables.Read()) + { + maybeRowId = false; + primaryKeys.Clear(); + if (String.IsNullOrEmpty(strTable) || String.Compare(rdTables.GetString(2), strTable, StringComparison.OrdinalIgnoreCase) == 0) + { + // First, look for any rowid indexes -- which sqlite defines are INTEGER PRIMARY KEY columns. + // Such indexes are not listed in the indexes list but count as indexes just the same. + try + { + using (SQLiteCommand cmdTable = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA [{0}].table_info([{1}])", strCatalog, rdTables.GetString(2)), this)) + using (SQLiteDataReader rdTable = cmdTable.ExecuteReader()) + { + while (rdTable.Read()) + { + if (rdTable.GetInt32(5) != 0) + { + primaryKeys.Add(rdTable.GetInt32(0)); + + // If the primary key is of type INTEGER, then its a rowid and we need to make a fake index entry for it. + if (String.Compare(rdTable.GetString(2), "INTEGER", StringComparison.OrdinalIgnoreCase) == 0) + maybeRowId = true; + } + } + } + } + catch (SQLiteException) + { + } + if (primaryKeys.Count == 1 && maybeRowId == true) + { + row = tbl.NewRow(); + + row["TABLE_CATALOG"] = strCatalog; + row["TABLE_NAME"] = rdTables.GetString(2); + row["INDEX_CATALOG"] = strCatalog; + row["PRIMARY_KEY"] = true; + row["INDEX_NAME"] = HelperMethods.StringFormat(CultureInfo.InvariantCulture, "{1}_PK_{0}", rdTables.GetString(2), master); + row["UNIQUE"] = true; + + if (String.Compare((string)row["INDEX_NAME"], strIndex, StringComparison.OrdinalIgnoreCase) == 0 + || strIndex == null) + { + tbl.Rows.Add(row); + } + + primaryKeys.Clear(); + } + + // Now fetch all the rest of the indexes. + try + { + using (SQLiteCommand cmd = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA [{0}].index_list([{1}])", strCatalog, rdTables.GetString(2)), this)) + using (SQLiteDataReader rd = (SQLiteDataReader)cmd.ExecuteReader()) + { + while (rd.Read()) + { + if (String.Compare(rd.GetString(1), strIndex, StringComparison.OrdinalIgnoreCase) == 0 + || strIndex == null) + { + row = tbl.NewRow(); + + row["TABLE_CATALOG"] = strCatalog; + row["TABLE_NAME"] = rdTables.GetString(2); + row["INDEX_CATALOG"] = strCatalog; + row["INDEX_NAME"] = rd.GetString(1); + row["UNIQUE"] = SQLiteConvert.ToBoolean(rd.GetValue(2), CultureInfo.InvariantCulture, false); + row["PRIMARY_KEY"] = false; + + // get the index definition + using (SQLiteCommand cmdIndexes = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{2}] WHERE [type] LIKE 'index' AND [name] LIKE '{1}'", strCatalog, rd.GetString(1).Replace("'", "''"), master), this)) + using (SQLiteDataReader rdIndexes = cmdIndexes.ExecuteReader()) + { + while (rdIndexes.Read()) + { + if (rdIndexes.IsDBNull(4) == false) + row["INDEX_DEFINITION"] = rdIndexes.GetString(4); + break; + } + } + + // Now for the really hard work. Figure out which index is the primary key index. + // The only way to figure it out is to check if the index was an autoindex and if we have a non-rowid + // primary key, and all the columns in the given index match the primary key columns + if (primaryKeys.Count > 0 && rd.GetString(1).StartsWith("sqlite_autoindex_" + rdTables.GetString(2), StringComparison.InvariantCultureIgnoreCase) == true) + { + using (SQLiteCommand cmdDetails = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA [{0}].index_info([{1}])", strCatalog, rd.GetString(1)), this)) + using (SQLiteDataReader rdDetails = cmdDetails.ExecuteReader()) + { + int nMatches = 0; + while (rdDetails.Read()) + { + if (primaryKeys.Contains(rdDetails.GetInt32(1)) == false) + { + nMatches = 0; + break; + } + nMatches++; + } + if (nMatches == primaryKeys.Count) + { + row["PRIMARY_KEY"] = true; + primaryKeys.Clear(); + } + } + } + + tbl.Rows.Add(row); + } + } + } + } + catch (SQLiteException) + { + } + } + } + } + + tbl.AcceptChanges(); + tbl.EndLoadData(); + + return tbl; + } + + private DataTable Schema_Triggers(string catalog, string table, string triggerName) + { + DataTable tbl = new DataTable("Triggers"); + DataRow row; + + tbl.Locale = CultureInfo.InvariantCulture; + tbl.Columns.Add("TABLE_CATALOG", typeof(string)); + tbl.Columns.Add("TABLE_SCHEMA", typeof(string)); + tbl.Columns.Add("TABLE_NAME", typeof(string)); + tbl.Columns.Add("TRIGGER_NAME", typeof(string)); + tbl.Columns.Add("TRIGGER_DEFINITION", typeof(string)); + + tbl.BeginLoadData(); + + if (String.IsNullOrEmpty(table)) table = null; + if (String.IsNullOrEmpty(catalog)) catalog = GetDefaultCatalogName(); + string master = GetMasterTableName(IsTemporaryCatalogName(catalog)); + + using (SQLiteCommand cmd = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "SELECT [type], [name], [tbl_name], [rootpage], [sql], [rowid] FROM [{0}].[{1}] WHERE [type] LIKE 'trigger'", catalog, master), this)) + using (SQLiteDataReader rd = (SQLiteDataReader)cmd.ExecuteReader()) + { + while (rd.Read()) + { + if (String.Compare(rd.GetString(1), triggerName, StringComparison.OrdinalIgnoreCase) == 0 + || triggerName == null) + { + if (table == null || String.Compare(table, rd.GetString(2), StringComparison.OrdinalIgnoreCase) == 0) + { + row = tbl.NewRow(); + + row["TABLE_CATALOG"] = catalog; + row["TABLE_NAME"] = rd.GetString(2); + row["TRIGGER_NAME"] = rd.GetString(1); + row["TRIGGER_DEFINITION"] = rd.GetString(4); + + tbl.Rows.Add(row); + } + } + } + } + tbl.AcceptChanges(); + tbl.EndLoadData(); + + return tbl; + } + + /// + /// Retrieves table schema information for the database and catalog + /// + /// The catalog (attached database) to retrieve tables on + /// The table to retrieve, can be null + /// The table type, can be null + /// DataTable + private DataTable Schema_Tables(string strCatalog, string strTable, string strType) + { + DataTable tbl = new DataTable("Tables"); + DataRow row; + string strItem; + + tbl.Locale = CultureInfo.InvariantCulture; + tbl.Columns.Add("TABLE_CATALOG", typeof(string)); + tbl.Columns.Add("TABLE_SCHEMA", typeof(string)); + tbl.Columns.Add("TABLE_NAME", typeof(string)); + tbl.Columns.Add("TABLE_TYPE", typeof(string)); + tbl.Columns.Add("TABLE_ID", typeof(long)); + tbl.Columns.Add("TABLE_ROOTPAGE", typeof(int)); + tbl.Columns.Add("TABLE_DEFINITION", typeof(string)); + tbl.BeginLoadData(); + + if (String.IsNullOrEmpty(strCatalog)) strCatalog = GetDefaultCatalogName(); + + string master = GetMasterTableName(IsTemporaryCatalogName(strCatalog)); + + using (SQLiteCommand cmd = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "SELECT [type], [name], [tbl_name], [rootpage], [sql], [rowid] FROM [{0}].[{1}] WHERE [type] LIKE 'table'", strCatalog, master), this)) + using (SQLiteDataReader rd = (SQLiteDataReader)cmd.ExecuteReader()) + { + while (rd.Read()) + { + strItem = rd.GetString(0); + if (String.Compare(rd.GetString(2), 0, "SQLITE_", 0, 7, StringComparison.OrdinalIgnoreCase) == 0) + strItem = "SYSTEM_TABLE"; + + if (String.Compare(strType, strItem, StringComparison.OrdinalIgnoreCase) == 0 + || strType == null) + { + if (String.Compare(rd.GetString(2), strTable, StringComparison.OrdinalIgnoreCase) == 0 + || strTable == null) + { + row = tbl.NewRow(); + + row["TABLE_CATALOG"] = strCatalog; + row["TABLE_NAME"] = rd.GetString(2); + row["TABLE_TYPE"] = strItem; + row["TABLE_ID"] = rd.GetInt64(5); + row["TABLE_ROOTPAGE"] = rd.GetInt32(3); + row["TABLE_DEFINITION"] = rd.GetString(4); + + tbl.Rows.Add(row); + } + } + } + } + + tbl.AcceptChanges(); + tbl.EndLoadData(); + + return tbl; + } + + /// + /// Retrieves view schema information for the database + /// + /// The catalog (attached database) to retrieve views on + /// The view name, can be null + /// DataTable + private DataTable Schema_Views(string strCatalog, string strView) + { + DataTable tbl = new DataTable("Views"); + DataRow row; + string strItem; + int nPos; + + tbl.Locale = CultureInfo.InvariantCulture; + tbl.Columns.Add("TABLE_CATALOG", typeof(string)); + tbl.Columns.Add("TABLE_SCHEMA", typeof(string)); + tbl.Columns.Add("TABLE_NAME", typeof(string)); + tbl.Columns.Add("VIEW_DEFINITION", typeof(string)); + tbl.Columns.Add("CHECK_OPTION", typeof(bool)); + tbl.Columns.Add("IS_UPDATABLE", typeof(bool)); + tbl.Columns.Add("DESCRIPTION", typeof(string)); + tbl.Columns.Add("DATE_CREATED", typeof(DateTime)); + tbl.Columns.Add("DATE_MODIFIED", typeof(DateTime)); + + tbl.BeginLoadData(); + + if (String.IsNullOrEmpty(strCatalog)) strCatalog = GetDefaultCatalogName(); + + string master = GetMasterTableName(IsTemporaryCatalogName(strCatalog)); + + using (SQLiteCommand cmd = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'view'", strCatalog, master), this)) + using (SQLiteDataReader rd = (SQLiteDataReader)cmd.ExecuteReader()) + { + while (rd.Read()) + { + if (String.Compare(rd.GetString(1), strView, StringComparison.OrdinalIgnoreCase) == 0 + || String.IsNullOrEmpty(strView)) + { + strItem = rd.GetString(4).Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' '); + nPos = CultureInfo.InvariantCulture.CompareInfo.IndexOf(strItem, " AS ", CompareOptions.IgnoreCase); + if (nPos > -1) + { + strItem = strItem.Substring(nPos + 4).Trim(); + row = tbl.NewRow(); + + row["TABLE_CATALOG"] = strCatalog; + row["TABLE_NAME"] = rd.GetString(2); + row["IS_UPDATABLE"] = false; + row["VIEW_DEFINITION"] = strItem; + + tbl.Rows.Add(row); + } + } + } + } + + tbl.AcceptChanges(); + tbl.EndLoadData(); + + return tbl; + } + + /// + /// Retrieves catalog (attached databases) schema information for the database + /// + /// The catalog to retrieve, can be null + /// DataTable + private DataTable Schema_Catalogs(string strCatalog) + { + DataTable tbl = new DataTable("Catalogs"); + DataRow row; + + tbl.Locale = CultureInfo.InvariantCulture; + tbl.Columns.Add("CATALOG_NAME", typeof(string)); + tbl.Columns.Add("DESCRIPTION", typeof(string)); + tbl.Columns.Add("ID", typeof(long)); + + tbl.BeginLoadData(); + + using (SQLiteCommand cmd = new SQLiteCommand("PRAGMA database_list", this)) + using (SQLiteDataReader rd = (SQLiteDataReader)cmd.ExecuteReader()) + { + while (rd.Read()) + { + if (String.Compare(rd.GetString(1), strCatalog, StringComparison.OrdinalIgnoreCase) == 0 + || strCatalog == null) + { + row = tbl.NewRow(); + + row["CATALOG_NAME"] = rd.GetString(1); + row["DESCRIPTION"] = rd.GetString(2); + row["ID"] = rd.GetInt64(0); + + tbl.Rows.Add(row); + } + } + } + + tbl.AcceptChanges(); + tbl.EndLoadData(); + + return tbl; + } + + private DataTable Schema_DataTypes() + { + DataTable tbl = new DataTable("DataTypes"); + + tbl.Locale = CultureInfo.InvariantCulture; + tbl.Columns.Add("TypeName", typeof(String)); + tbl.Columns.Add("ProviderDbType", typeof(int)); + tbl.Columns.Add("ColumnSize", typeof(long)); + tbl.Columns.Add("CreateFormat", typeof(String)); + tbl.Columns.Add("CreateParameters", typeof(String)); + tbl.Columns.Add("DataType", typeof(String)); + tbl.Columns.Add("IsAutoIncrementable", typeof(bool)); + tbl.Columns.Add("IsBestMatch", typeof(bool)); + tbl.Columns.Add("IsCaseSensitive", typeof(bool)); + tbl.Columns.Add("IsFixedLength", typeof(bool)); + tbl.Columns.Add("IsFixedPrecisionScale", typeof(bool)); + tbl.Columns.Add("IsLong", typeof(bool)); + tbl.Columns.Add("IsNullable", typeof(bool)); + tbl.Columns.Add("IsSearchable", typeof(bool)); + tbl.Columns.Add("IsSearchableWithLike", typeof(bool)); + tbl.Columns.Add("IsLiteralSupported", typeof(bool)); + tbl.Columns.Add("LiteralPrefix", typeof(String)); + tbl.Columns.Add("LiteralSuffix", typeof(String)); + tbl.Columns.Add("IsUnsigned", typeof(bool)); + tbl.Columns.Add("MaximumScale", typeof(short)); + tbl.Columns.Add("MinimumScale", typeof(short)); + tbl.Columns.Add("IsConcurrencyType", typeof(bool)); + + tbl.BeginLoadData(); + + StringReader reader = new StringReader(SR.DataTypes); + tbl.ReadXml(reader); + reader.Close(); + + tbl.AcceptChanges(); + tbl.EndLoadData(); + + return tbl; + } + + /// + /// Returns the base column information for indexes in a database + /// + /// The catalog to retrieve indexes for (can be null) + /// The table to restrict index information by (can be null) + /// The index to restrict index information by (can be null) + /// The source column to restrict index information by (can be null) + /// A DataTable containing the results + private DataTable Schema_IndexColumns(string strCatalog, string strTable, string strIndex, string strColumn) + { + DataTable tbl = new DataTable("IndexColumns"); + DataRow row; + List> primaryKeys = new List>(); + bool maybeRowId; + + tbl.Locale = CultureInfo.InvariantCulture; + tbl.Columns.Add("CONSTRAINT_CATALOG", typeof(string)); + tbl.Columns.Add("CONSTRAINT_SCHEMA", typeof(string)); + tbl.Columns.Add("CONSTRAINT_NAME", typeof(string)); + tbl.Columns.Add("TABLE_CATALOG", typeof(string)); + tbl.Columns.Add("TABLE_SCHEMA", typeof(string)); + tbl.Columns.Add("TABLE_NAME", typeof(string)); + tbl.Columns.Add("COLUMN_NAME", typeof(string)); + tbl.Columns.Add("ORDINAL_POSITION", typeof(int)); + tbl.Columns.Add("INDEX_NAME", typeof(string)); + tbl.Columns.Add("COLLATION_NAME", typeof(string)); + tbl.Columns.Add("SORT_MODE", typeof(string)); + tbl.Columns.Add("CONFLICT_OPTION", typeof(int)); + + if (String.IsNullOrEmpty(strCatalog)) strCatalog = GetDefaultCatalogName(); + + string master = GetMasterTableName(IsTemporaryCatalogName(strCatalog)); + + tbl.BeginLoadData(); + + using (SQLiteCommand cmdTables = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table'", strCatalog, master), this)) + using (SQLiteDataReader rdTables = cmdTables.ExecuteReader()) + { + while (rdTables.Read()) + { + maybeRowId = false; + primaryKeys.Clear(); + if (String.IsNullOrEmpty(strTable) || String.Compare(rdTables.GetString(2), strTable, StringComparison.OrdinalIgnoreCase) == 0) + { + try + { + using (SQLiteCommand cmdTable = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA [{0}].table_info([{1}])", strCatalog, rdTables.GetString(2)), this)) + using (SQLiteDataReader rdTable = cmdTable.ExecuteReader()) + { + while (rdTable.Read()) + { + if (rdTable.GetInt32(5) == 1) // is a primary key + { + primaryKeys.Add(new KeyValuePair(rdTable.GetInt32(0), rdTable.GetString(1))); + // Is an integer -- could be a rowid if no other primary keys exist in the table + if (String.Compare(rdTable.GetString(2), "INTEGER", StringComparison.OrdinalIgnoreCase) == 0) + maybeRowId = true; + } + } + } + } + catch (SQLiteException) + { + } + // This is a rowid row + if (primaryKeys.Count == 1 && maybeRowId == true) + { + row = tbl.NewRow(); + row["CONSTRAINT_CATALOG"] = strCatalog; + row["CONSTRAINT_NAME"] = HelperMethods.StringFormat(CultureInfo.InvariantCulture, "{1}_PK_{0}", rdTables.GetString(2), master); + row["TABLE_CATALOG"] = strCatalog; + row["TABLE_NAME"] = rdTables.GetString(2); + row["COLUMN_NAME"] = primaryKeys[0].Value; + row["INDEX_NAME"] = row["CONSTRAINT_NAME"]; + row["ORDINAL_POSITION"] = 0; // primaryKeys[0].Key; + row["COLLATION_NAME"] = "BINARY"; + row["SORT_MODE"] = "ASC"; + row["CONFLICT_OPTION"] = 2; + + if (String.IsNullOrEmpty(strIndex) || String.Compare(strIndex, (string)row["INDEX_NAME"], StringComparison.OrdinalIgnoreCase) == 0) + tbl.Rows.Add(row); + } + + using (SQLiteCommand cmdIndexes = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{2}] WHERE [type] LIKE 'index' AND [tbl_name] LIKE '{1}'", strCatalog, rdTables.GetString(2).Replace("'", "''"), master), this)) + using (SQLiteDataReader rdIndexes = cmdIndexes.ExecuteReader()) + { + while (rdIndexes.Read()) + { + int ordinal = 0; + if (String.IsNullOrEmpty(strIndex) || String.Compare(strIndex, rdIndexes.GetString(1), StringComparison.OrdinalIgnoreCase) == 0) + { + try + { + using (SQLiteCommand cmdIndex = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA [{0}].index_info([{1}])", strCatalog, rdIndexes.GetString(1)), this)) + using (SQLiteDataReader rdIndex = cmdIndex.ExecuteReader()) + { + while (rdIndex.Read()) + { + string columnName = rdIndex.IsDBNull(2) ? null : rdIndex.GetString(2); + + row = tbl.NewRow(); + row["CONSTRAINT_CATALOG"] = strCatalog; + row["CONSTRAINT_NAME"] = rdIndexes.GetString(1); + row["TABLE_CATALOG"] = strCatalog; + row["TABLE_NAME"] = rdIndexes.GetString(2); + row["COLUMN_NAME"] = columnName; + row["INDEX_NAME"] = rdIndexes.GetString(1); + row["ORDINAL_POSITION"] = ordinal; // rdIndex.GetInt32(1); + + string collationSequence = null; + int sortMode = 0; + int onError = 0; + + if (columnName != null) + _sql.GetIndexColumnExtendedInfo(strCatalog, rdIndexes.GetString(1), columnName, ref sortMode, ref onError, ref collationSequence); + + if (String.IsNullOrEmpty(collationSequence) == false) + row["COLLATION_NAME"] = collationSequence; + + row["SORT_MODE"] = (sortMode == 0) ? "ASC" : "DESC"; + row["CONFLICT_OPTION"] = onError; + + ordinal++; + + if ((strColumn == null) || String.Compare(strColumn, columnName, StringComparison.OrdinalIgnoreCase) == 0) + tbl.Rows.Add(row); + } + } + } + catch (SQLiteException) + { + } + } + } + } + } + } + } + + tbl.EndLoadData(); + tbl.AcceptChanges(); + + return tbl; + } + + /// + /// Returns detailed column information for a specified view + /// + /// The catalog to retrieve columns for (can be null) + /// The view to restrict column information by (can be null) + /// The source column to restrict column information by (can be null) + /// A DataTable containing the results + private DataTable Schema_ViewColumns(string strCatalog, string strView, string strColumn) + { + DataTable tbl = new DataTable("ViewColumns"); + DataRow row; + string strSql; + int n; + DataRow schemaRow; + DataRow viewRow; + + tbl.Locale = CultureInfo.InvariantCulture; + tbl.Columns.Add("VIEW_CATALOG", typeof(string)); + tbl.Columns.Add("VIEW_SCHEMA", typeof(string)); + tbl.Columns.Add("VIEW_NAME", typeof(string)); + tbl.Columns.Add("VIEW_COLUMN_NAME", typeof(String)); + tbl.Columns.Add("TABLE_CATALOG", typeof(string)); + tbl.Columns.Add("TABLE_SCHEMA", typeof(string)); + tbl.Columns.Add("TABLE_NAME", typeof(string)); + tbl.Columns.Add("COLUMN_NAME", typeof(string)); + tbl.Columns.Add("ORDINAL_POSITION", typeof(int)); + tbl.Columns.Add("COLUMN_HASDEFAULT", typeof(bool)); + tbl.Columns.Add("COLUMN_DEFAULT", typeof(string)); + tbl.Columns.Add("COLUMN_FLAGS", typeof(long)); + tbl.Columns.Add("IS_NULLABLE", typeof(bool)); + tbl.Columns.Add("DATA_TYPE", typeof(string)); + tbl.Columns.Add("CHARACTER_MAXIMUM_LENGTH", typeof(int)); + tbl.Columns.Add("NUMERIC_PRECISION", typeof(int)); + tbl.Columns.Add("NUMERIC_SCALE", typeof(int)); + tbl.Columns.Add("DATETIME_PRECISION", typeof(long)); + tbl.Columns.Add("CHARACTER_SET_CATALOG", typeof(string)); + tbl.Columns.Add("CHARACTER_SET_SCHEMA", typeof(string)); + tbl.Columns.Add("CHARACTER_SET_NAME", typeof(string)); + tbl.Columns.Add("COLLATION_CATALOG", typeof(string)); + tbl.Columns.Add("COLLATION_SCHEMA", typeof(string)); + tbl.Columns.Add("COLLATION_NAME", typeof(string)); + tbl.Columns.Add("PRIMARY_KEY", typeof(bool)); + tbl.Columns.Add("EDM_TYPE", typeof(string)); + tbl.Columns.Add("AUTOINCREMENT", typeof(bool)); + tbl.Columns.Add("UNIQUE", typeof(bool)); + + if (String.IsNullOrEmpty(strCatalog)) strCatalog = GetDefaultCatalogName(); + + string master = GetMasterTableName(IsTemporaryCatalogName(strCatalog)); + + tbl.BeginLoadData(); + + using (SQLiteCommand cmdViews = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'view'", strCatalog, master), this)) + using (SQLiteDataReader rdViews = cmdViews.ExecuteReader()) + { + while (rdViews.Read()) + { + if (String.IsNullOrEmpty(strView) || String.Compare(strView, rdViews.GetString(2), StringComparison.OrdinalIgnoreCase) == 0) + { + using (SQLiteCommand cmdViewSelect = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}]", strCatalog, rdViews.GetString(2)), this)) + { + strSql = rdViews.GetString(4).Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' '); + n = CultureInfo.InvariantCulture.CompareInfo.IndexOf(strSql, " AS ", CompareOptions.IgnoreCase); + if (n < 0) + continue; + + strSql = strSql.Substring(n + 4); + + using (SQLiteCommand cmd = new SQLiteCommand(strSql, this)) + using (SQLiteDataReader rdViewSelect = cmdViewSelect.ExecuteReader(CommandBehavior.SchemaOnly)) + using (SQLiteDataReader rd = (SQLiteDataReader)cmd.ExecuteReader(CommandBehavior.SchemaOnly)) + using (DataTable tblSchemaView = rdViewSelect.GetSchemaTable(false, false)) + using (DataTable tblSchema = rd.GetSchemaTable(false, false)) + { + for (n = 0; n < tblSchema.Rows.Count; n++) + { + viewRow = tblSchemaView.Rows[n]; + schemaRow = tblSchema.Rows[n]; + + if (String.Compare(viewRow[SchemaTableColumn.ColumnName].ToString(), strColumn, StringComparison.OrdinalIgnoreCase) == 0 + || strColumn == null) + { + row = tbl.NewRow(); + + row["VIEW_CATALOG"] = strCatalog; + row["VIEW_NAME"] = rdViews.GetString(2); + row["TABLE_CATALOG"] = strCatalog; + row["TABLE_SCHEMA"] = schemaRow[SchemaTableColumn.BaseSchemaName]; + row["TABLE_NAME"] = schemaRow[SchemaTableColumn.BaseTableName]; + row["COLUMN_NAME"] = schemaRow[SchemaTableColumn.BaseColumnName]; + row["VIEW_COLUMN_NAME"] = viewRow[SchemaTableColumn.ColumnName]; + row["COLUMN_HASDEFAULT"] = (viewRow[SchemaTableOptionalColumn.DefaultValue] != DBNull.Value); + row["COLUMN_DEFAULT"] = viewRow[SchemaTableOptionalColumn.DefaultValue]; + row["ORDINAL_POSITION"] = viewRow[SchemaTableColumn.ColumnOrdinal]; + row["IS_NULLABLE"] = viewRow[SchemaTableColumn.AllowDBNull]; + row["DATA_TYPE"] = viewRow["DataTypeName"]; // SQLiteConvert.DbTypeToType((DbType)viewRow[SchemaTableColumn.ProviderType]).ToString(); + row["EDM_TYPE"] = SQLiteConvert.DbTypeToTypeName(this, (DbType)viewRow[SchemaTableColumn.ProviderType], _flags).ToString().ToLower(CultureInfo.InvariantCulture); + row["CHARACTER_MAXIMUM_LENGTH"] = viewRow[SchemaTableColumn.ColumnSize]; + row["TABLE_SCHEMA"] = viewRow[SchemaTableColumn.BaseSchemaName]; + row["PRIMARY_KEY"] = viewRow[SchemaTableColumn.IsKey]; + row["AUTOINCREMENT"] = viewRow[SchemaTableOptionalColumn.IsAutoIncrement]; + row["COLLATION_NAME"] = viewRow["CollationType"]; + row["UNIQUE"] = viewRow[SchemaTableColumn.IsUnique]; + tbl.Rows.Add(row); + } + } + } + } + } + } + } + + tbl.EndLoadData(); + tbl.AcceptChanges(); + + return tbl; + } + + /// + /// Retrieves foreign key information from the specified set of filters + /// + /// An optional catalog to restrict results on + /// An optional table to restrict results on + /// An optional foreign key name to restrict results on + /// A DataTable with the results of the query + private DataTable Schema_ForeignKeys(string strCatalog, string strTable, string strKeyName) + { + DataTable tbl = new DataTable("ForeignKeys"); + DataRow row; + + tbl.Locale = CultureInfo.InvariantCulture; + tbl.Columns.Add("CONSTRAINT_CATALOG", typeof(string)); + tbl.Columns.Add("CONSTRAINT_SCHEMA", typeof(string)); + tbl.Columns.Add("CONSTRAINT_NAME", typeof(string)); + tbl.Columns.Add("TABLE_CATALOG", typeof(string)); + tbl.Columns.Add("TABLE_SCHEMA", typeof(string)); + tbl.Columns.Add("TABLE_NAME", typeof(string)); + tbl.Columns.Add("CONSTRAINT_TYPE", typeof(string)); + tbl.Columns.Add("IS_DEFERRABLE", typeof(bool)); + tbl.Columns.Add("INITIALLY_DEFERRED", typeof(bool)); + tbl.Columns.Add("FKEY_ID", typeof(int)); + tbl.Columns.Add("FKEY_FROM_COLUMN", typeof(string)); + tbl.Columns.Add("FKEY_FROM_ORDINAL_POSITION", typeof(int)); + tbl.Columns.Add("FKEY_TO_CATALOG", typeof(string)); + tbl.Columns.Add("FKEY_TO_SCHEMA", typeof(string)); + tbl.Columns.Add("FKEY_TO_TABLE", typeof(string)); + tbl.Columns.Add("FKEY_TO_COLUMN", typeof(string)); + tbl.Columns.Add("FKEY_ON_UPDATE", typeof(string)); + tbl.Columns.Add("FKEY_ON_DELETE", typeof(string)); + tbl.Columns.Add("FKEY_MATCH", typeof(string)); + + if (String.IsNullOrEmpty(strCatalog)) strCatalog = GetDefaultCatalogName(); + + string master = GetMasterTableName(IsTemporaryCatalogName(strCatalog)); + + tbl.BeginLoadData(); + + using (SQLiteCommand cmdTables = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table'", strCatalog, master), this)) + using (SQLiteDataReader rdTables = cmdTables.ExecuteReader()) + { + while (rdTables.Read()) + { + if (String.IsNullOrEmpty(strTable) || String.Compare(strTable, rdTables.GetString(2), StringComparison.OrdinalIgnoreCase) == 0) + { + try + { + using (SQLiteCommandBuilder builder = new SQLiteCommandBuilder()) + using (SQLiteCommand cmdKey = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA [{0}].foreign_key_list([{1}])", strCatalog, rdTables.GetString(2)), this)) + using (SQLiteDataReader rdKey = cmdKey.ExecuteReader()) + { + while (rdKey.Read()) + { + row = tbl.NewRow(); + row["CONSTRAINT_CATALOG"] = strCatalog; + row["CONSTRAINT_NAME"] = HelperMethods.StringFormat(CultureInfo.InvariantCulture, "FK_{0}_{1}_{2}", rdTables[2], rdKey.GetInt32(0), rdKey.GetInt32(1)); + row["TABLE_CATALOG"] = strCatalog; + row["TABLE_NAME"] = builder.UnquoteIdentifier(rdTables.GetString(2)); + row["CONSTRAINT_TYPE"] = "FOREIGN KEY"; + row["IS_DEFERRABLE"] = false; + row["INITIALLY_DEFERRED"] = false; + row["FKEY_ID"] = rdKey[0]; + row["FKEY_FROM_COLUMN"] = builder.UnquoteIdentifier(rdKey[3].ToString()); + row["FKEY_TO_CATALOG"] = strCatalog; + row["FKEY_TO_TABLE"] = builder.UnquoteIdentifier(rdKey[2].ToString()); + row["FKEY_TO_COLUMN"] = builder.UnquoteIdentifier(rdKey[4].ToString()); + row["FKEY_FROM_ORDINAL_POSITION"] = rdKey[1]; + row["FKEY_ON_UPDATE"] = (rdKey.FieldCount > 5) ? rdKey[5] : String.Empty; + row["FKEY_ON_DELETE"] = (rdKey.FieldCount > 6) ? rdKey[6] : String.Empty; + row["FKEY_MATCH"] = (rdKey.FieldCount > 7) ? rdKey[7] : String.Empty; + + if (String.IsNullOrEmpty(strKeyName) || String.Compare(strKeyName, row["CONSTRAINT_NAME"].ToString(), StringComparison.OrdinalIgnoreCase) == 0) + tbl.Rows.Add(row); + } + } + } + catch (SQLiteException) + { + } + } + } + } + + tbl.EndLoadData(); + tbl.AcceptChanges(); + + return tbl; + } + + /// + /// This event is raised periodically during long running queries. Changing + /// the value of the property will + /// determine if the operation in progress will continue or be interrupted. + /// For the entire duration of the event, the associated connection and + /// statement objects must not be modified, either directly or indirectly, by + /// the called code. + /// + public event SQLiteProgressEventHandler Progress + { + add + { + CheckDisposed(); + + if (_progressHandler == null) + { + _progressCallback = new SQLiteProgressCallback(ProgressCallback); + if (_sql != null) _sql.SetProgressHook(_progressOps, _progressCallback); + } + _progressHandler += value; + } + remove + { + CheckDisposed(); + + _progressHandler -= value; + if (_progressHandler == null) + { + if (_sql != null) _sql.SetProgressHook(0, null); + _progressCallback = null; + } + } + } + + /// + /// This event is raised whenever SQLite encounters an action covered by the + /// authorizer during query preparation. Changing the value of the + /// property will determine if + /// the specific action will be allowed, ignored, or denied. For the entire + /// duration of the event, the associated connection and statement objects + /// must not be modified, either directly or indirectly, by the called code. + /// + public event SQLiteAuthorizerEventHandler Authorize + { + add + { + CheckDisposed(); + + if (_authorizerHandler == null) + { + _authorizerCallback = new SQLiteAuthorizerCallback(AuthorizerCallback); + if (_sql != null) _sql.SetAuthorizerHook(_authorizerCallback); + } + _authorizerHandler += value; + } + remove + { + CheckDisposed(); + + _authorizerHandler -= value; + if (_authorizerHandler == null) + { + if (_sql != null) _sql.SetAuthorizerHook(null); + _authorizerCallback = null; + } + } + } + + /// + /// This event is raised whenever SQLite makes an update/delete/insert into the database on + /// this connection. It only applies to the given connection. + /// + public event SQLiteUpdateEventHandler Update + { + add + { + CheckDisposed(); + + if (_updateHandler == null) + { + _updateCallback = new SQLiteUpdateCallback(UpdateCallback); + if (_sql != null) _sql.SetUpdateHook(_updateCallback); + } + _updateHandler += value; + } + remove + { + CheckDisposed(); + + _updateHandler -= value; + if (_updateHandler == null) + { + if (_sql != null) _sql.SetUpdateHook(null); + _updateCallback = null; + } + } + } + + private SQLiteProgressReturnCode ProgressCallback( + IntPtr pUserData /* NOT USED: Always IntPtr.Zero. */ + ) + { + try + { + ProgressEventArgs eventArgs = new ProgressEventArgs( + pUserData, SQLiteProgressReturnCode.Continue); + + if (_progressHandler != null) + _progressHandler(this, eventArgs); + + return eventArgs.ReturnCode; + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + try + { + if (HelperMethods.LogCallbackExceptions(_flags)) + { + SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat(CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "Progress", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } + + // + // NOTE: Should throwing an exception interrupt the operation? + // + if (HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.InterruptOnException)) + { + return SQLiteProgressReturnCode.Interrupt; + } + else + { + return SQLiteProgressReturnCode.Continue; + } + } + + private SQLiteAuthorizerReturnCode AuthorizerCallback( + IntPtr pUserData, /* NOT USED: Always IntPtr.Zero. */ + SQLiteAuthorizerActionCode actionCode, + IntPtr pArgument1, + IntPtr pArgument2, + IntPtr pDatabase, + IntPtr pAuthContext) + { + try + { + AuthorizerEventArgs eventArgs = new AuthorizerEventArgs(pUserData, actionCode, + SQLiteBase.UTF8ToString(pArgument1, -1), SQLiteBase.UTF8ToString(pArgument2, -1), + SQLiteBase.UTF8ToString(pDatabase, -1), SQLiteBase.UTF8ToString(pAuthContext, -1), + SQLiteAuthorizerReturnCode.Ok); + + if (_authorizerHandler != null) + _authorizerHandler(this, eventArgs); + + return eventArgs.ReturnCode; + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + try + { + if (HelperMethods.LogCallbackExceptions(_flags)) + { + SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat(CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "Authorize", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } + + // + // NOTE: Should throwing an exception deny the action? + // + if (HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.DenyOnException)) + { + return SQLiteAuthorizerReturnCode.Deny; + } + else + { + return SQLiteAuthorizerReturnCode.Ok; + } + } + + private void UpdateCallback( + IntPtr puser, /* NOT USED */ + int type, + IntPtr database, + IntPtr table, + Int64 rowid + ) + { + try + { + _updateHandler(this, new UpdateEventArgs( + SQLiteBase.UTF8ToString(database, -1), + SQLiteBase.UTF8ToString(table, -1), + (UpdateEventType)type, + rowid)); + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + try + { + if (HelperMethods.LogCallbackExceptions(_flags)) + { + SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat(CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "Update", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } + } + + /// + /// This event is raised whenever SQLite is committing a transaction. + /// Return non-zero to trigger a rollback. + /// + public event SQLiteCommitHandler Commit + { + add + { + CheckDisposed(); + + if (_commitHandler == null) + { + _commitCallback = new SQLiteCommitCallback(CommitCallback); + if (_sql != null) _sql.SetCommitHook(_commitCallback); + } + _commitHandler += value; + } + remove + { + CheckDisposed(); + + _commitHandler -= value; + if (_commitHandler == null) + { + if (_sql != null) _sql.SetCommitHook(null); + _commitCallback = null; + } + } + } + + /// + /// This event is raised whenever SQLite statement first begins executing on + /// this connection. It only applies to the given connection. + /// + public event SQLiteTraceEventHandler Trace + { + add + { + CheckDisposed(); + + if (_traceHandler == null) + { + _traceCallback = new SQLiteTraceCallback(TraceCallback); + if (_sql != null) _sql.SetTraceCallback(_traceCallback); + } + _traceHandler += value; + } + remove + { + CheckDisposed(); + + _traceHandler -= value; + if (_traceHandler == null) + { + if (_sql != null) _sql.SetTraceCallback(null); + _traceCallback = null; + } + } + } + + private void TraceCallback( + IntPtr puser, /* NOT USED */ + IntPtr statement + ) + { + try + { + if (_traceHandler != null) + _traceHandler(this, new TraceEventArgs( + SQLiteBase.UTF8ToString(statement, -1))); + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + try + { + if (HelperMethods.LogCallbackExceptions(_flags)) + { + SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat(CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "Trace", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } + } + + /// + /// This event is raised whenever SQLite is rolling back a transaction. + /// + public event EventHandler RollBack + { + add + { + CheckDisposed(); + + if (_rollbackHandler == null) + { + _rollbackCallback = new SQLiteRollbackCallback(RollbackCallback); + if (_sql != null) _sql.SetRollbackHook(_rollbackCallback); + } + _rollbackHandler += value; + } + remove + { + CheckDisposed(); + + _rollbackHandler -= value; + if (_rollbackHandler == null) + { + if (_sql != null) _sql.SetRollbackHook(null); + _rollbackCallback = null; + } + } + } + + private int CommitCallback( + IntPtr parg /* NOT USED */ + ) + { + try + { + CommitEventArgs e = new CommitEventArgs(); + + if (_commitHandler != null) + _commitHandler(this, e); + + return (e.AbortTransaction == true) ? 1 : 0; + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + try + { + if (HelperMethods.LogCallbackExceptions(_flags)) + { + SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat(CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "Commit", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } + + // + // NOTE: Should throwing an exception rollback the transaction? + // + if (HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.RollbackOnException)) + { + return 1; // rollback + } + else + { + return 0; // commit + } + } + + private void RollbackCallback( + IntPtr parg /* NOT USED */ + ) + { + try + { + if (_rollbackHandler != null) + _rollbackHandler(this, EventArgs.Empty); + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + try + { + if (HelperMethods.LogCallbackExceptions(_flags)) + { + SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat(CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "Rollback", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } + } + } + + /// + /// The I/O file cache flushing behavior for the connection + /// + public enum SynchronizationModes + { + /// + /// Normal file flushing at critical sections of the code + /// + Normal = 0, + /// + /// Full file flushing after every write operation + /// + Full = 1, + /// + /// Use the default operating system's file flushing, SQLite does not explicitly flush the file buffers after writing + /// + Off = 2, + } + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate SQLiteProgressReturnCode SQLiteProgressCallback(IntPtr pUserData); + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate SQLiteAuthorizerReturnCode SQLiteAuthorizerCallback( + IntPtr pUserData, + SQLiteAuthorizerActionCode actionCode, + IntPtr pArgument1, + IntPtr pArgument2, + IntPtr pDatabase, + IntPtr pAuthContext + ); + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate void SQLiteUpdateCallback(IntPtr puser, int type, IntPtr database, IntPtr table, Int64 rowid); + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate int SQLiteCommitCallback(IntPtr puser); + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate void SQLiteTraceCallback(IntPtr puser, IntPtr statement); + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate void SQLiteTraceCallback2(SQLiteTraceFlags type, IntPtr puser, IntPtr pCtx1, IntPtr pCtx2); + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate void SQLiteRollbackCallback(IntPtr puser); + + /// + /// Raised each time the number of virtual machine instructions is + /// approximately equal to the value of the + /// property. + /// + /// The connection performing the operation. + /// A that contains the + /// event data. + public delegate void SQLiteProgressEventHandler(object sender, ProgressEventArgs e); + + /// + /// Raised when authorization is required to perform an action contained + /// within a SQL query. + /// + /// The connection performing the action. + /// A that contains the + /// event data. + public delegate void SQLiteAuthorizerEventHandler(object sender, AuthorizerEventArgs e); + + /// + /// Raised when a transaction is about to be committed. To roll back a transaction, set the + /// rollbackTrans boolean value to true. + /// + /// The connection committing the transaction + /// Event arguments on the transaction + public delegate void SQLiteCommitHandler(object sender, CommitEventArgs e); + + /// + /// Raised when data is inserted, updated and deleted on a given connection + /// + /// The connection committing the transaction + /// The event parameters which triggered the event + public delegate void SQLiteUpdateEventHandler(object sender, UpdateEventArgs e); + + /// + /// Raised when a statement first begins executing on a given connection + /// + /// The connection executing the statement + /// Event arguments of the trace + public delegate void SQLiteTraceEventHandler(object sender, TraceEventArgs e); + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Backup API Members + /// + /// Raised between each backup step. + /// + /// + /// The source database connection. + /// + /// + /// The source database name. + /// + /// + /// The destination database connection. + /// + /// + /// The destination database name. + /// + /// + /// The number of pages copied with each step. + /// + /// + /// The number of pages remaining to be copied. + /// + /// + /// The total number of pages in the source database. + /// + /// + /// Set to true if the operation needs to be retried due to database + /// locking issues; otherwise, set to false. + /// + /// + /// True to continue with the backup process or false to halt the backup + /// process, rolling back any changes that have been made so far. + /// + public delegate bool SQLiteBackupCallback( + SQLiteConnection source, + string sourceName, + SQLiteConnection destination, + string destinationName, + int pages, + int remainingPages, + int totalPages, + bool retry + ); + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// The event data associated with progress reporting events. + /// + public class ProgressEventArgs : EventArgs + { + /// + /// The user-defined native data associated with this event. Currently, + /// this will always contain the value of . + /// + public readonly IntPtr UserData; + + /// + /// The return code for the current call into the progress callback. + /// + public SQLiteProgressReturnCode ReturnCode; + + /// + /// Constructs an instance of this class with default property values. + /// + private ProgressEventArgs() + { + this.UserData = IntPtr.Zero; + this.ReturnCode = SQLiteProgressReturnCode.Continue; + } + + /// + /// Constructs an instance of this class with specific property values. + /// + /// + /// The user-defined native data associated with this event. + /// + /// + /// The progress return code. + /// + internal ProgressEventArgs( + IntPtr pUserData, + SQLiteProgressReturnCode returnCode + ) + : this() + { + this.UserData = pUserData; + this.ReturnCode = returnCode; + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// The data associated with a call into the authorizer. + /// + public class AuthorizerEventArgs : EventArgs + { + /// + /// The user-defined native data associated with this event. Currently, + /// this will always contain the value of . + /// + public readonly IntPtr UserData; + + /// + /// The action code responsible for the current call into the authorizer. + /// + public readonly SQLiteAuthorizerActionCode ActionCode; + + /// + /// The first string argument for the current call into the authorizer. + /// The exact value will vary based on the action code, see the + /// enumeration for possible + /// values. + /// + public readonly string Argument1; + + /// + /// The second string argument for the current call into the authorizer. + /// The exact value will vary based on the action code, see the + /// enumeration for possible + /// values. + /// + public readonly string Argument2; + + /// + /// The database name for the current call into the authorizer, if + /// applicable. + /// + public readonly string Database; + + /// + /// The name of the inner-most trigger or view that is responsible for + /// the access attempt or a null value if this access attempt is directly + /// from top-level SQL code. + /// + public readonly string Context; + + /// + /// The return code for the current call into the authorizer. + /// + public SQLiteAuthorizerReturnCode ReturnCode; + + /////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Constructs an instance of this class with default property values. + /// + private AuthorizerEventArgs() + { + this.UserData = IntPtr.Zero; + this.ActionCode = SQLiteAuthorizerActionCode.None; + this.Argument1 = null; + this.Argument2 = null; + this.Database = null; + this.Context = null; + this.ReturnCode = SQLiteAuthorizerReturnCode.Ok; + } + + /////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Constructs an instance of this class with specific property values. + /// + /// + /// The user-defined native data associated with this event. + /// + /// + /// The authorizer action code. + /// + /// + /// The first authorizer argument. + /// + /// + /// The second authorizer argument. + /// + /// + /// The database name, if applicable. + /// + /// + /// The name of the inner-most trigger or view that is responsible for + /// the access attempt or a null value if this access attempt is directly + /// from top-level SQL code. + /// + /// + /// The authorizer return code. + /// + internal AuthorizerEventArgs( + IntPtr pUserData, + SQLiteAuthorizerActionCode actionCode, + string argument1, + string argument2, + string database, + string context, + SQLiteAuthorizerReturnCode returnCode + ) + : this() + { + this.UserData = pUserData; + this.ActionCode = actionCode; + this.Argument1 = argument1; + this.Argument2 = argument2; + this.Database = database; + this.Context = context; + this.ReturnCode = returnCode; + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Whenever an update event is triggered on a connection, this enum will indicate + /// exactly what type of operation is being performed. + /// + public enum UpdateEventType + { + /// + /// A row is being deleted from the given database and table + /// + Delete = 9, + /// + /// A row is being inserted into the table. + /// + Insert = 18, + /// + /// A row is being updated in the table. + /// + Update = 23, + } + + /// + /// Passed during an Update callback, these event arguments detail the type of update operation being performed + /// on the given connection. + /// + public class UpdateEventArgs : EventArgs + { + /// + /// The name of the database being updated (usually "main" but can be any attached or temporary database) + /// + public readonly string Database; + + /// + /// The name of the table being updated + /// + public readonly string Table; + + /// + /// The type of update being performed (insert/update/delete) + /// + public readonly UpdateEventType Event; + + /// + /// The RowId affected by this update. + /// + public readonly Int64 RowId; + + internal UpdateEventArgs(string database, string table, UpdateEventType eventType, Int64 rowid) + { + Database = database; + Table = table; + Event = eventType; + RowId = rowid; + } + } + + /// + /// Event arguments raised when a transaction is being committed + /// + public class CommitEventArgs : EventArgs + { + internal CommitEventArgs() + { + } + + /// + /// Set to true to abort the transaction and trigger a rollback + /// + public bool AbortTransaction; + } + + /// + /// Passed during an Trace callback, these event arguments contain the UTF-8 rendering of the SQL statement text + /// + public class TraceEventArgs : EventArgs + { + /// + /// SQL statement text as the statement first begins executing + /// + public readonly string Statement; + + internal TraceEventArgs(string statement) + { + Statement = statement; + } + } + +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteConnectionPool.cs b/Native.Csharp.Tool/SQLite/SQLiteConnectionPool.cs new file mode 100644 index 00000000..1e140f8e --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteConnectionPool.cs @@ -0,0 +1,1028 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Collections.Generic; + +#if !PLATFORM_COMPACTFRAMEWORK && DEBUG + using System.Text; +#endif + + using System.Threading; + + /////////////////////////////////////////////////////////////////////////// + + #region Null Connection Pool Class +#if !PLATFORM_COMPACTFRAMEWORK && DEBUG + /// + /// This class implements a connection pool where all methods of the + /// interface are NOPs. This class + /// is used for testing purposes only. + /// + internal sealed class NullConnectionPool : ISQLiteConnectionPool + { + #region Private Data + /// + /// This field keeps track of all method calls made into the + /// interface methods of this + /// class. + /// + private StringBuilder log; + + /////////////////////////////////////////////////////////////////////// + + /// + /// Non-zero to dispose of database connection handles received via the + /// method. + /// + private bool dispose; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs a connection pool object where all methods of the + /// interface are NOPs. This + /// class is used for testing purposes only. + /// + private NullConnectionPool() + { + log = new StringBuilder(); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs a connection pool object where all methods of the + /// interface are NOPs. This + /// class is used for testing purposes only. + /// + /// + /// Non-zero to dispose of database connection handles received via the + /// method. + /// + public NullConnectionPool( + bool dispose + ) + : this() + { + this.dispose = dispose; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteConnectionPool Members + /// + /// Counts the number of pool entries matching the specified file name. + /// + /// + /// The file name to match or null to match all files. + /// + /// + /// The pool entry counts for each matching file. + /// + /// + /// The total number of connections successfully opened from any pool. + /// + /// + /// The total number of connections successfully closed from any pool. + /// + /// + /// The total number of pool entries for all matching files. + /// + public void GetCounts( + string fileName, + ref Dictionary counts, + ref int openCount, + ref int closeCount, + ref int totalCount + ) + { + if (log != null) + { + log.AppendFormat( + "GetCounts(\"{0}\", {1}, {2}, {3}, {4}){5}", fileName, + counts, openCount, closeCount, totalCount, + Environment.NewLine); + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of all pooled connections associated with the specified + /// database file name. + /// + /// + /// The database file name. + /// + public void ClearPool( + string fileName + ) + { + if (log != null) + { + log.AppendFormat( + "ClearPool(\"{0}\"){1}", fileName, Environment.NewLine); + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of all pooled connections. + /// + public void ClearAllPools() + { + if (log != null) + { + log.AppendFormat( + "ClearAllPools(){0}", Environment.NewLine); + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Adds a connection to the pool of those associated with the + /// specified database file name. + /// + /// + /// The database file name. + /// + /// + /// The database connection handle. + /// + /// + /// The connection pool version at the point the database connection + /// handle was received from the connection pool. This is also the + /// connection pool version that the database connection handle was + /// created under. + /// + public void Add( + string fileName, + object handle, + int version + ) + { + if (log != null) + { + log.AppendFormat( + "Add(\"{0}\", {1}, {2}){3}", fileName, handle, version, + Environment.NewLine); + } + + // + // NOTE: If configured to do so, dispose of the received connection + // handle now. + // + if (dispose) + { + IDisposable disposable = handle as IDisposable; + + if (disposable != null) + disposable.Dispose(); + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Removes a connection from the pool of those associated with the + /// specified database file name with the intent of using it to + /// interact with the database. + /// + /// + /// The database file name. + /// + /// + /// The new maximum size of the connection pool for the specified + /// database file name. + /// + /// + /// The connection pool version associated with the returned database + /// connection handle, if any. + /// + /// + /// The database connection handle associated with the specified + /// database file name or null if it cannot be obtained. + /// + public object Remove( + string fileName, + int maxPoolSize, + out int version + ) + { + version = 0; + + if (log != null) + { + log.AppendFormat( + "Remove(\"{0}\", {1}, {2}){3}", fileName, maxPoolSize, + version, Environment.NewLine); + } + + return null; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region System.Object Overrides + /// + /// Overrides the default method + /// to provide a log of all methods called on the + /// interface. + /// + /// + /// A string containing a log of all method calls into the + /// interface, along with their + /// parameters, delimited by . + /// + public override string ToString() + { + return (log != null) ? log.ToString() : String.Empty; + } + #endregion + } +#endif + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region Public Connection Pool Interface + /// + /// This interface represents a custom connection pool implementation + /// usable by System.Data.SQLite. + /// + public interface ISQLiteConnectionPool + { + /// + /// Counts the number of pool entries matching the specified file name. + /// + /// + /// The file name to match or null to match all files. + /// + /// + /// The pool entry counts for each matching file. + /// + /// + /// The total number of connections successfully opened from any pool. + /// + /// + /// The total number of connections successfully closed from any pool. + /// + /// + /// The total number of pool entries for all matching files. + /// + void GetCounts(string fileName, ref Dictionary counts, + ref int openCount, ref int closeCount, ref int totalCount); + + /// + /// Disposes of all pooled connections associated with the specified + /// database file name. + /// + /// + /// The database file name. + /// + void ClearPool(string fileName); + + /// + /// Disposes of all pooled connections. + /// + void ClearAllPools(); + + /// + /// Adds a connection to the pool of those associated with the + /// specified database file name. + /// + /// + /// The database file name. + /// + /// + /// The database connection handle. + /// + /// + /// The connection pool version at the point the database connection + /// handle was received from the connection pool. This is also the + /// connection pool version that the database connection handle was + /// created under. + /// + void Add(string fileName, object handle, int version); + + /// + /// Removes a connection from the pool of those associated with the + /// specified database file name with the intent of using it to + /// interact with the database. + /// + /// + /// The database file name. + /// + /// + /// The new maximum size of the connection pool for the specified + /// database file name. + /// + /// + /// The connection pool version associated with the returned database + /// connection handle, if any. + /// + /// + /// The database connection handle associated with the specified + /// database file name or null if it cannot be obtained. + /// + object Remove(string fileName, int maxPoolSize, out int version); + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region Connection Pool Subsystem & Default Implementation + /// + /// This default method implementations in this class should not be used by + /// applications that make use of COM (either directly or indirectly) due + /// to possible deadlocks that can occur during finalization of some COM + /// objects. + /// + internal static class SQLiteConnectionPool + { + #region Private Pool Class + /// + /// Keeps track of connections made on a specified file. The PoolVersion + /// dictates whether old objects get returned to the pool or discarded + /// when no longer in use. + /// + private sealed class PoolQueue + { + #region Private Data + /// + /// The queue of weak references to the actual database connection + /// handles. + /// + internal readonly Queue Queue = + new Queue(); + + /////////////////////////////////////////////////////////////////// + + /// + /// This pool version associated with the database connection + /// handles in this pool queue. + /// + internal int PoolVersion; + + /////////////////////////////////////////////////////////////////// + + /// + /// The maximum size of this pool queue. + /// + internal int MaxPoolSize; + #endregion + + /////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs a connection pool queue using the specified version + /// and maximum size. Normally, all the database connection + /// handles in this pool are associated with a single database file + /// name. + /// + /// + /// The initial pool version for this connection pool queue. + /// + /// + /// The initial maximum size for this connection pool queue. + /// + internal PoolQueue( + int version, + int maxSize + ) + { + PoolVersion = version; + MaxPoolSize = maxSize; + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Static Data + /// + /// This field is used to synchronize access to the private static data + /// in this class. + /// + private static readonly object _syncRoot = new object(); + + /////////////////////////////////////////////////////////////////////// + + /// + /// When this field is non-null, it will be used to provide the + /// implementation of all the connection pool methods; otherwise, + /// the default method implementations will be used. + /// + private static ISQLiteConnectionPool _connectionPool = null; + + /////////////////////////////////////////////////////////////////////// + + /// + /// The dictionary of connection pools, based on the normalized file + /// name of the SQLite database. + /// + private static SortedList _queueList = + new SortedList(StringComparer.OrdinalIgnoreCase); + + /////////////////////////////////////////////////////////////////////// + + /// + /// The default version number new pools will get. + /// + private static int _poolVersion = 1; + + /////////////////////////////////////////////////////////////////////// + + /// + /// The number of connections successfully opened from any pool. + /// This value is incremented by the Remove method. + /// + private static int _poolOpened = 0; + + /////////////////////////////////////////////////////////////////////// + + /// + /// The number of connections successfully closed from any pool. + /// This value is incremented by the Add method. + /// + private static int _poolClosed = 0; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteConnectionPool Members (Static, Non-Formal) + /// + /// Counts the number of pool entries matching the specified file name. + /// + /// + /// The file name to match or null to match all files. + /// + /// + /// The pool entry counts for each matching file. + /// + /// + /// The total number of connections successfully opened from any pool. + /// + /// + /// The total number of connections successfully closed from any pool. + /// + /// + /// The total number of pool entries for all matching files. + /// + internal static void GetCounts( + string fileName, + ref Dictionary counts, + ref int openCount, + ref int closeCount, + ref int totalCount + ) + { + ISQLiteConnectionPool connectionPool = GetConnectionPool(); + + if (connectionPool != null) + { + connectionPool.GetCounts( + fileName, ref counts, ref openCount, ref closeCount, + ref totalCount); + } + else + { + lock (_syncRoot) + { + openCount = _poolOpened; + closeCount = _poolClosed; + + if (counts == null) + { + counts = new Dictionary( + StringComparer.OrdinalIgnoreCase); + } + + if (fileName != null) + { + PoolQueue queue; + + if (_queueList.TryGetValue(fileName, out queue)) + { + Queue poolQueue = queue.Queue; + int count = (poolQueue != null) ? poolQueue.Count : 0; + + counts.Add(fileName, count); + totalCount += count; + } + } + else + { + foreach (KeyValuePair pair in _queueList) + { + if (pair.Value == null) + continue; + + Queue poolQueue = pair.Value.Queue; + int count = (poolQueue != null) ? poolQueue.Count : 0; + + counts.Add(pair.Key, count); + totalCount += count; + } + } + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of all pooled connections associated with the specified + /// database file name. + /// + /// + /// The database file name. + /// + internal static void ClearPool(string fileName) + { + ISQLiteConnectionPool connectionPool = GetConnectionPool(); + + if (connectionPool != null) + { + connectionPool.ClearPool(fileName); + } + else + { + lock (_syncRoot) + { + PoolQueue queue; + + if (_queueList.TryGetValue(fileName, out queue)) + { + queue.PoolVersion++; + + Queue poolQueue = queue.Queue; + if (poolQueue == null) return; + + while (poolQueue.Count > 0) + { + WeakReference connection = poolQueue.Dequeue(); + + if (connection == null) continue; + + SQLiteConnectionHandle handle = + connection.Target as SQLiteConnectionHandle; + + if (handle != null) + handle.Dispose(); + + GC.KeepAlive(handle); + } + } + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of all pooled connections. + /// + internal static void ClearAllPools() + { + ISQLiteConnectionPool connectionPool = GetConnectionPool(); + + if (connectionPool != null) + { + connectionPool.ClearAllPools(); + } + else + { + lock (_syncRoot) + { + foreach (KeyValuePair pair in _queueList) + { + if (pair.Value == null) + continue; + + Queue poolQueue = pair.Value.Queue; + + while (poolQueue.Count > 0) + { + WeakReference connection = poolQueue.Dequeue(); + + if (connection == null) continue; + + SQLiteConnectionHandle handle = + connection.Target as SQLiteConnectionHandle; + + if (handle != null) + handle.Dispose(); + + GC.KeepAlive(handle); + } + + // + // NOTE: Keep track of the highest revision so we can + // go one higher when we are finished. + // + if (_poolVersion <= pair.Value.PoolVersion) + _poolVersion = pair.Value.PoolVersion + 1; + } + + // + // NOTE: All pools are cleared and we have a new highest + // version number to force all old version active + // items to get discarded instead of going back to + // the queue when they are closed. We can get away + // with this because we have pumped up the pool + // version out of range of all active connections, + // so they will all get discarded when they try to + // put themselves back into their pools. + // + _queueList.Clear(); + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Adds a connection to the pool of those associated with the + /// specified database file name. + /// + /// + /// The database file name. + /// + /// + /// The database connection handle. + /// + /// + /// The connection pool version at the point the database connection + /// handle was received from the connection pool. This is also the + /// connection pool version that the database connection handle was + /// created under. + /// + internal static void Add( + string fileName, + SQLiteConnectionHandle handle, + int version + ) + { + ISQLiteConnectionPool connectionPool = GetConnectionPool(); + + if (connectionPool != null) + { + connectionPool.Add(fileName, handle, version); + } + else + { + lock (_syncRoot) + { + // + // NOTE: If the queue does not exist in the pool, then it + // must have been cleared sometime after the + // connection was created. + // + PoolQueue queue; + + if (_queueList.TryGetValue(fileName, out queue) && + (version == queue.PoolVersion)) + { + ResizePool(queue, true); + + Queue poolQueue = queue.Queue; + if (poolQueue == null) return; + + poolQueue.Enqueue(new WeakReference(handle, false)); + Interlocked.Increment(ref _poolClosed); + } + else + { + handle.Close(); + } + + GC.KeepAlive(handle); + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Removes a connection from the pool of those associated with the + /// specified database file name with the intent of using it to + /// interact with the database. + /// + /// + /// The database file name. + /// + /// + /// The new maximum size of the connection pool for the specified + /// database file name. + /// + /// + /// The connection pool version associated with the returned database + /// connection handle, if any. + /// + /// + /// The database connection handle associated with the specified + /// database file name or null if it cannot be obtained. + /// + internal static SQLiteConnectionHandle Remove( + string fileName, + int maxPoolSize, + out int version + ) + { + ISQLiteConnectionPool connectionPool = GetConnectionPool(); + + if (connectionPool != null) + { + return connectionPool.Remove(fileName, maxPoolSize, + out version) as SQLiteConnectionHandle; + } + else + { + int localVersion; + Queue poolQueue; + + // + // NOTE: This lock cannot be held while checking the queue for + // available connections because other methods of this + // class are called from the GC finalizer thread and we + // use the WaitForPendingFinalizers method (below). + // Holding this lock while calling that method would + // therefore result in a deadlock. Instead, this lock + // is held only while a temporary copy of the queue is + // created, and if necessary, when committing changes + // back to that original queue prior to returning from + // this method. + // + lock (_syncRoot) + { + PoolQueue queue; + + // + // NOTE: Default to the highest pool version. + // + version = _poolVersion; + + // + // NOTE: If we didn't find a pool for this file, create one + // even though it will be empty. We have to do this + // here because otherwise calling ClearPool() on the + // file will not work for active connections that have + // never seen the pool yet. + // + if (!_queueList.TryGetValue(fileName, out queue)) + { + queue = new PoolQueue(_poolVersion, maxPoolSize); + _queueList.Add(fileName, queue); + + return null; + } + + // + // NOTE: We found a pool for this file, so use its version + // number. + // + version = localVersion = queue.PoolVersion; + queue.MaxPoolSize = maxPoolSize; + + // + // NOTE: Now, resize the pool to the new maximum size, if + // necessary. + // + ResizePool(queue, false); + + // + // NOTE: Try and get a pooled connection from the queue. + // + poolQueue = queue.Queue; + if (poolQueue == null) return null; + + // + // NOTE: Temporarily tranfer the queue for this file into + // a local variable. The queue for this file will + // be modified and then committed back to the real + // pool list (below) prior to returning from this + // method. + // + _queueList.Remove(fileName); + poolQueue = new Queue(poolQueue); + } + + try + { + while (poolQueue.Count > 0) + { + WeakReference connection = poolQueue.Dequeue(); + + if (connection == null) continue; + + SQLiteConnectionHandle handle = + connection.Target as SQLiteConnectionHandle; + + if (handle == null) continue; + + // + // BUGFIX: For ticket [996d13cd87], step #1. After + // this point, make sure that the finalizer for + // the connection handle just obtained from the + // queue cannot START running (i.e. it may + // still be pending but it will no longer start + // after this point). + // + GC.SuppressFinalize(handle); + + try + { + // + // BUGFIX: For ticket [996d13cd87], step #2. Now, + // we must wait for all pending finalizers + // which have STARTED running and have not + // yet COMPLETED. This must be done just + // in case the finalizer for the connection + // handle just obtained from the queue has + // STARTED running at some point before + // SuppressFinalize was called on it. + // + // After this point, checking properties of + // the connection handle (e.g. IsClosed) + // should work reliably without having to + // worry that they will (due to the + // finalizer) change out from under us. + // + GC.WaitForPendingFinalizers(); + + // + // BUGFIX: For ticket [996d13cd87], step #3. Next, + // verify that the connection handle is + // actually valid and [still?] not closed + // prior to actually returning it to our + // caller. + // + if (!handle.IsInvalid && !handle.IsClosed) + { + Interlocked.Increment(ref _poolOpened); + return handle; + } + } + finally + { + // + // BUGFIX: For ticket [996d13cd87], step #4. Next, + // we must re-register the connection + // handle for finalization now that we have + // a strong reference to it (i.e. the + // finalizer will not run at least until + // the connection is subsequently closed). + // + GC.ReRegisterForFinalize(handle); + } + + GC.KeepAlive(handle); + } + } + finally + { + // + // BUGFIX: For ticket [996d13cd87], step #5. Finally, + // commit any changes to the pool/queue for this + // database file. + // + lock (_syncRoot) + { + // + // NOTE: We must check [again] if a pool exists for + // this file because one may have been added + // while the search for an available connection + // was in progress (above). + // + PoolQueue queue; + Queue newPoolQueue; + bool addPool; + + if (_queueList.TryGetValue(fileName, out queue)) + { + addPool = false; + } + else + { + addPool = true; + queue = new PoolQueue(localVersion, maxPoolSize); + } + + newPoolQueue = queue.Queue; + + while (poolQueue.Count > 0) + newPoolQueue.Enqueue(poolQueue.Dequeue()); + + ResizePool(queue, false); + + if (addPool) + _queueList.Add(fileName, queue); + } + } + + return null; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Helper Methods + /// + /// This method is used to obtain a reference to the custom connection + /// pool implementation currently in use, if any. + /// + /// + /// The custom connection pool implementation or null if the default + /// connection pool implementation should be used. + /// + internal static ISQLiteConnectionPool GetConnectionPool() + { + lock (_syncRoot) + { + return _connectionPool; + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is used to set the reference to the custom connection + /// pool implementation to use, if any. + /// + /// + /// The custom connection pool implementation to use or null if the + /// default connection pool implementation should be used. + /// + internal static void SetConnectionPool( + ISQLiteConnectionPool connectionPool + ) + { + lock (_syncRoot) + { + _connectionPool = connectionPool; + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// We do not have to thread-lock anything in this function, because it + /// is only called by other functions above which already take the lock. + /// + /// + /// The pool queue to resize. + /// + /// + /// If a function intends to add to the pool, this is true, which + /// forces the resize to take one more than it needs from the pool. + /// + private static void ResizePool( + PoolQueue queue, + bool add + ) + { + int target = queue.MaxPoolSize; + + if (add && target > 0) target--; + + Queue poolQueue = queue.Queue; + if (poolQueue == null) return; + + while (poolQueue.Count > target) + { + WeakReference connection = poolQueue.Dequeue(); + + if (connection == null) continue; + + SQLiteConnectionHandle handle = + connection.Target as SQLiteConnectionHandle; + + if (handle != null) + handle.Dispose(); + + GC.KeepAlive(handle); + } + } + #endregion + } + #endregion +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteConnectionStringBuilder.cs b/Native.Csharp.Tool/SQLite/SQLiteConnectionStringBuilder.cs new file mode 100644 index 00000000..ea0e7f18 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteConnectionStringBuilder.cs @@ -0,0 +1,991 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Data.Common; + using System.ComponentModel; + using System.Collections; + using System.Globalization; + using System.Reflection; + +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// SQLite implementation of DbConnectionStringBuilder. + /// + [DefaultProperty("DataSource")] + [DefaultMember("Item")] + public sealed class SQLiteConnectionStringBuilder : DbConnectionStringBuilder + { + /// + /// Properties of this class + /// + private Hashtable _properties; + + /// + /// Constructs a new instance of the class + /// + /// + /// Default constructor + /// + public SQLiteConnectionStringBuilder() + { + Initialize(null); + } + + /// + /// Constructs a new instance of the class using the specified connection string. + /// + /// The connection string to parse + public SQLiteConnectionStringBuilder(string connectionString) + { + Initialize(connectionString); + } + + /// + /// Private initializer, which assigns the connection string and resets the builder + /// + /// The connection string to assign + private void Initialize(string cnnString) + { + _properties = new Hashtable(StringComparer.OrdinalIgnoreCase); + try + { + base.GetProperties(_properties); + } + catch(NotImplementedException) + { + FallbackGetProperties(_properties); + } + + if (String.IsNullOrEmpty(cnnString) == false) + ConnectionString = cnnString; + } + + /// + /// Gets/Sets the default version of the SQLite engine to instantiate. Currently the only valid value is 3, indicating version 3 of the sqlite library. + /// + [Browsable(true)] + [DefaultValue(3)] + public int Version + { + get + { + object value; + TryGetValue("version", out value); + return Convert.ToInt32(value, CultureInfo.CurrentCulture); + } + set + { + if (value != 3) + throw new NotSupportedException(); + + this["version"] = value; + } + } + + /// + /// Gets/Sets the synchronization mode (file flushing) of the connection string. Default is "Normal". + /// + [DisplayName("Synchronous")] + [Browsable(true)] + [DefaultValue(SynchronizationModes.Normal)] + public SynchronizationModes SyncMode + { + get + { + object value; + TryGetValue("synchronous", out value); + if (value is string) + return (SynchronizationModes)TypeDescriptor.GetConverter(typeof(SynchronizationModes)).ConvertFrom(value); + else return (SynchronizationModes)value; + } + set + { + this["synchronous"] = value; + } + } + + /// + /// Gets/Sets the encoding for the connection string. The default is "False" which indicates UTF-8 encoding. + /// + [DisplayName("Use UTF-16 Encoding")] + [Browsable(true)] + [DefaultValue(false)] + public bool UseUTF16Encoding + { + get + { + object value; + TryGetValue("useutf16encoding", out value); + return SQLiteConvert.ToBoolean(value); + } + set + { + this["useutf16encoding"] = value; + } + } + + /// + /// Gets/Sets whether or not to use connection pooling. The default is "False" + /// + [Browsable(true)] + [DefaultValue(false)] + public bool Pooling + { + get + { + object value; + TryGetValue("pooling", out value); + return SQLiteConvert.ToBoolean(value); + } + set + { + this["pooling"] = value; + } + } + + /// + /// Gets/Sets whethor not to store GUID's in binary format. The default is True + /// which saves space in the database. + /// + [DisplayName("Binary GUID")] + [Browsable(true)] + [DefaultValue(true)] + public bool BinaryGUID + { + get + { + object value; + TryGetValue("binaryguid", out value); + return SQLiteConvert.ToBoolean(value); + } + set + { + this["binaryguid"] = value; + } + } + + /// + /// Gets/Sets the filename to open on the connection string. + /// + [DisplayName("Data Source")] + [Browsable(true)] + [DefaultValue("")] + public string DataSource + { + get + { + object value; + TryGetValue("data source", out value); + return (value != null) ? value.ToString() : null; + } + set + { + this["data source"] = value; + } + } + + /// + /// An alternate to the data source property + /// + [DisplayName("URI")] + [Browsable(true)] + [DefaultValue(null)] + public string Uri + { + get + { + object value; + TryGetValue("uri", out value); + return (value != null) ? value.ToString() : null; + } + set + { + this["uri"] = value; + } + } + + /// + /// An alternate to the data source property that uses the SQLite URI syntax. + /// + [DisplayName("Full URI")] + [Browsable(true)] + [DefaultValue(null)] + public string FullUri + { + get + { + object value; + TryGetValue("fulluri", out value); + return (value != null) ? value.ToString() : null; + } + set + { + this["fulluri"] = value; + } + } + + /// + /// Gets/sets the default command timeout for newly-created commands. This is especially useful for + /// commands used internally such as inside a SQLiteTransaction, where setting the timeout is not possible. + /// + [DisplayName("Default Timeout")] + [Browsable(true)] + [DefaultValue(30)] + public int DefaultTimeout + { + get + { + object value; + TryGetValue("default timeout", out value); + return Convert.ToInt32(value, CultureInfo.CurrentCulture); + } + set + { + this["default timeout"] = value; + } + } + + /// + /// Gets/sets the busy timeout to use with the SQLite core library. + /// + [DisplayName("Busy Timeout")] + [Browsable(true)] + [DefaultValue(0)] + public int BusyTimeout + { + get + { + object value; + TryGetValue("busytimeout", out value); + return Convert.ToInt32(value, CultureInfo.CurrentCulture); + } + set + { + this["busytimeout"] = value; + } + } + + /// + /// EXPERIMENTAL -- + /// The wait timeout to use with + /// method. + /// This is only used when waiting for the enlistment to be reset + /// prior to enlisting in a transaction, and then only when the + /// appropriate connection flag is set. + /// + [DisplayName("Wait Timeout")] + [Browsable(true)] + [DefaultValue(30000)] + public int WaitTimeout + { + get + { + object value; + TryGetValue("waittimeout", out value); + return Convert.ToInt32(value, CultureInfo.CurrentCulture); + } + set + { + this["waittimeout"] = value; + } + } + + /// + /// Gets/sets the maximum number of retries when preparing SQL to be executed. + /// This normally only applies to preparation errors resulting from the database + /// schema being changed. + /// + [DisplayName("Prepare Retries")] + [Browsable(true)] + [DefaultValue(3)] + public int PrepareRetries + { + get + { + object value; + TryGetValue("prepareretries", out value); + return Convert.ToInt32(value, CultureInfo.CurrentCulture); + } + set + { + this["prepareretries"] = value; + } + } + + /// + /// Gets/sets the approximate number of virtual machine instructions between + /// progress events. In order for progress events to actually fire, the event + /// handler must be added to the event + /// as well. + /// + [DisplayName("Progress Ops")] + [Browsable(true)] + [DefaultValue(0)] + public int ProgressOps + { + get + { + object value; + TryGetValue("progressops", out value); + return Convert.ToInt32(value, CultureInfo.CurrentCulture); + } + set + { + this["progressops"] = value; + } + } + + /// + /// Determines whether or not the connection will automatically participate + /// in the current distributed transaction (if one exists) + /// + [Browsable(true)] + [DefaultValue(true)] + public bool Enlist + { + get + { + object value; + TryGetValue("enlist", out value); + return SQLiteConvert.ToBoolean(value); + } + set + { + this["enlist"] = value; + } + } + + /// + /// If set to true, will throw an exception if the database specified in the connection + /// string does not exist. If false, the database will be created automatically. + /// + [DisplayName("Fail If Missing")] + [Browsable(true)] + [DefaultValue(false)] + public bool FailIfMissing + { + get + { + object value; + TryGetValue("failifmissing", out value); + return SQLiteConvert.ToBoolean(value); + } + set + { + this["failifmissing"] = value; + } + } + + /// + /// If enabled, uses the legacy 3.xx format for maximum compatibility, but results in larger + /// database sizes. + /// + [DisplayName("Legacy Format")] + [Browsable(true)] + [DefaultValue(false)] + public bool LegacyFormat + { + get + { + object value; + TryGetValue("legacy format", out value); + return SQLiteConvert.ToBoolean(value); + } + set + { + this["legacy format"] = value; + } + } + + /// + /// When enabled, the database will be opened for read-only access and writing will be disabled. + /// + [DisplayName("Read Only")] + [Browsable(true)] + [DefaultValue(false)] + public bool ReadOnly + { + get + { + object value; + TryGetValue("read only", out value); + return SQLiteConvert.ToBoolean(value); + } + set + { + this["read only"] = value; + } + } + + /// + /// Gets/sets the database encryption password + /// + [Browsable(true)] + [PasswordPropertyText(true)] + [DefaultValue("")] + public string Password + { + get + { + object value; + TryGetValue("password", out value); + return (value != null) ? value.ToString() : null; + } + set + { + this["password"] = value; + } + } + + /// + /// Gets/sets the database encryption hexadecimal password + /// + [DisplayName("Hexadecimal Password")] + [Browsable(true)] + [PasswordPropertyText(true)] + [DefaultValue(null)] + public byte[] HexPassword + { + get + { + object value; + + if (TryGetValue("hexpassword", out value)) + { + if (value is string) + return SQLiteConnection.FromHexString((string)value); + else if (value != null) + return (byte[])value; + } + + return null; + } + set + { + this["hexpassword"] = SQLiteConnection.ToHexString(value); + } + } + + /// + /// Gets/Sets the page size for the connection. + /// + [DisplayName("Page Size")] + [Browsable(true)] + [DefaultValue(4096)] + public int PageSize + { + get + { + object value; + TryGetValue("page size", out value); + return Convert.ToInt32(value, CultureInfo.CurrentCulture); + } + set + { + this["page size"] = value; + } + } + + /// + /// Gets/Sets the maximum number of pages the database may hold + /// + [DisplayName("Maximum Page Count")] + [Browsable(true)] + [DefaultValue(0)] + public int MaxPageCount + { + get + { + object value; + TryGetValue("max page count", out value); + return Convert.ToInt32(value, CultureInfo.CurrentCulture); + } + set + { + this["max page count"] = value; + } + } + + /// + /// Gets/Sets the cache size for the connection. + /// + [DisplayName("Cache Size")] + [Browsable(true)] + [DefaultValue(-2000)] + public int CacheSize + { + get + { + object value; + TryGetValue("cache size", out value); + return Convert.ToInt32(value, CultureInfo.CurrentCulture); + } + set + { + this["cache size"] = value; + } + } + + /// + /// Gets/Sets the DateTime format for the connection. + /// + [DisplayName("DateTime Format")] + [Browsable(true)] + [DefaultValue(SQLiteDateFormats.Default)] + public SQLiteDateFormats DateTimeFormat + { + get + { + object value; + + if (TryGetValue("datetimeformat", out value)) + { + if (value is SQLiteDateFormats) + return (SQLiteDateFormats)value; + else if (value != null) + return (SQLiteDateFormats)TypeDescriptor.GetConverter( + typeof(SQLiteDateFormats)).ConvertFrom(value); + } + + return SQLiteDateFormats.Default; + } + set + { + this["datetimeformat"] = value; + } + } + + /// + /// Gets/Sets the DateTime kind for the connection. + /// + [DisplayName("DateTime Kind")] + [Browsable(true)] + [DefaultValue(DateTimeKind.Unspecified)] + public DateTimeKind DateTimeKind + { + get + { + object value; + + if (TryGetValue("datetimekind", out value)) + { + if (value is DateTimeKind) + return (DateTimeKind)value; + else if (value != null) + return (DateTimeKind)TypeDescriptor.GetConverter( + typeof(DateTimeKind)).ConvertFrom(value); + } + + return DateTimeKind.Unspecified; + } + set + { + this["datetimekind"] = value; + } + } + + /// + /// Gets/sets the DateTime format string used for formatting + /// and parsing purposes. + /// + [DisplayName("DateTime Format String")] + [Browsable(true)] + [DefaultValue(null)] + public string DateTimeFormatString + { + get + { + object value; + + if (TryGetValue("datetimeformatstring", out value)) + { + if (value is string) + return (string)value; + else if (value != null) + return value.ToString(); + } + + return null; + } + set + { + this["datetimeformatstring"] = value; + } + } + + /// + /// Gets/Sets the placeholder base schema name used for + /// .NET Framework compatibility purposes. + /// + [DisplayName("Base Schema Name")] + [Browsable(true)] + [DefaultValue(SQLiteConnection.DefaultBaseSchemaName)] + public string BaseSchemaName + { + get + { + object value; + + if (TryGetValue("baseschemaname", out value)) + { + if (value is string) + return (string)value; + else if (value != null) + return value.ToString(); + } + + return null; + } + set + { + this["baseschemaname"] = value; + } + } + + /// + /// Determines how SQLite handles the transaction journal file. + /// + [Browsable(true)] + [DefaultValue(SQLiteJournalModeEnum.Default)] + [DisplayName("Journal Mode")] + public SQLiteJournalModeEnum JournalMode + { + get + { + object value; + TryGetValue("journal mode", out value); + if (value is string) + return (SQLiteJournalModeEnum)TypeDescriptor.GetConverter(typeof(SQLiteJournalModeEnum)).ConvertFrom(value); + else + return (SQLiteJournalModeEnum)value; + } + set + { + this["journal mode"] = value; + } + } + + /// + /// Sets the default isolation level for transactions on the connection. + /// + [Browsable(true)] + [DefaultValue(IsolationLevel.Serializable)] + [DisplayName("Default Isolation Level")] + public IsolationLevel DefaultIsolationLevel + { + get + { + object value; + TryGetValue("default isolationlevel", out value); + if (value is string) + return (IsolationLevel)TypeDescriptor.GetConverter(typeof(IsolationLevel)).ConvertFrom(value); + else + return (IsolationLevel)value; + } + set + { + this["default isolationlevel"] = value; + } + } + + /// + /// Gets/sets the default database type for the connection. + /// + [DisplayName("Default Database Type")] + [Browsable(true)] + [DefaultValue(SQLiteConnection.BadDbType)] + public DbType DefaultDbType + { + get + { + object value; + + if (TryGetValue("defaultdbtype", out value)) + { + if (value is string) + return (DbType)TypeDescriptor.GetConverter( + typeof(DbType)).ConvertFrom(value); + else if (value != null) + return (DbType)value; + } + + return SQLiteConnection.BadDbType; + } + set + { + this["defaultdbtype"] = value; + } + } + + /// + /// Gets/sets the default type name for the connection. + /// + [DisplayName("Default Type Name")] + [Browsable(true)] + [DefaultValue(null)] + public string DefaultTypeName + { + get + { + object value; + TryGetValue("defaulttypename", out value); + return (value != null) ? value.ToString() : null; + } + set + { + this["defaulttypename"] = value; + } + } + + /// + /// Gets/sets the VFS name for the connection. + /// + [DisplayName("VFS Name")] + [Browsable(true)] + [DefaultValue(null)] + public string VfsName + { + get + { + object value; + TryGetValue("vfsname", out value); + return (value != null) ? value.ToString() : null; + } + set + { + this["vfsname"] = value; + } + } + + /// + /// If enabled, use foreign key constraints + /// + [DisplayName("Foreign Keys")] + [Browsable(true)] + [DefaultValue(false)] + public bool ForeignKeys + { + get + { + object value; + TryGetValue("foreign keys", out value); + return SQLiteConvert.ToBoolean(value); + } + set + { + this["foreign keys"] = value; + } + } + + /// + /// Enable or disable the recursive trigger capability. + /// + [DisplayName("Recursive Triggers")] + [Browsable(true)] + [DefaultValue(false)] + public bool RecursiveTriggers + { + get + { + object value; + TryGetValue("recursive triggers", out value); + return SQLiteConvert.ToBoolean(value); + } + set + { + this["recursive triggers"] = value; + } + } + + /// + /// If non-null, this is the version of ZipVFS to use. This requires the + /// System.Data.SQLite interop assembly -AND- primary managed assembly to + /// be compiled with the INTEROP_INCLUDE_ZIPVFS option; otherwise, this + /// property does nothing. + /// + [DisplayName("ZipVFS Version")] + [Browsable(true)] + [DefaultValue(null)] + public string ZipVfsVersion + { + get + { + object value; + TryGetValue("zipvfsversion", out value); + return (value != null) ? value.ToString() : null; + } + set + { + this["zipvfsversion"] = value; + } + } + + /// + /// Gets/Sets the extra behavioral flags. + /// + [Browsable(true)] + [DefaultValue(SQLiteConnectionFlags.Default)] + public SQLiteConnectionFlags Flags + { + get + { + object value; + + if (TryGetValue("flags", out value)) + { + if (value is SQLiteConnectionFlags) + return (SQLiteConnectionFlags)value; + else if (value != null) + return (SQLiteConnectionFlags)TypeDescriptor.GetConverter( + typeof(SQLiteConnectionFlags)).ConvertFrom(value); + } + + return SQLiteConnectionFlags.Default; + } + set + { + this["flags"] = value; + } + } + + /// + /// If enabled, apply the default connection settings to opened databases. + /// + [DisplayName("Set Defaults")] + [Browsable(true)] + [DefaultValue(true)] + public bool SetDefaults + { + get + { + object value; + TryGetValue("setdefaults", out value); + return SQLiteConvert.ToBoolean(value); + } + set + { + this["setdefaults"] = value; + } + } + + /// + /// If enabled, attempt to resolve the provided data source file name to a + /// full path before opening. + /// + [DisplayName("To Full Path")] + [Browsable(true)] + [DefaultValue(true)] + public bool ToFullPath + { + get + { + object value; + TryGetValue("tofullpath", out value); + return SQLiteConvert.ToBoolean(value); + } + set + { + this["tofullpath"] = value; + } + } + + /// + /// If enabled, skip using the configured default connection flags. + /// + [DisplayName("No Default Flags")] + [Browsable(true)] + [DefaultValue(false)] + public bool NoDefaultFlags + { + get + { + object value; + TryGetValue("nodefaultflags", out value); + return SQLiteConvert.ToBoolean(value); + } + set + { + this["nodefaultflags"] = value; + } + } + + /// + /// If enabled, skip using the configured shared connection flags. + /// + [DisplayName("No Shared Flags")] + [Browsable(true)] + [DefaultValue(false)] + public bool NoSharedFlags + { + get + { + object value; + TryGetValue("nosharedflags", out value); + return SQLiteConvert.ToBoolean(value); + } + set + { + this["nosharedflags"] = value; + } + } + + /// + /// Helper function for retrieving values from the connectionstring + /// + /// The keyword to retrieve settings for + /// The resulting parameter value + /// Returns true if the value was found and returned + public override bool TryGetValue(string keyword, out object value) + { + bool b = base.TryGetValue(keyword, out value); + + if (!_properties.ContainsKey(keyword)) return b; + + PropertyDescriptor pd = _properties[keyword] as PropertyDescriptor; + + if (pd == null) return b; + + // Attempt to coerce the value into something more solid + if (b) + { + if (pd.PropertyType == typeof(Boolean)) + value = SQLiteConvert.ToBoolean(value); + else if (pd.PropertyType != typeof(byte[])) + value = TypeDescriptor.GetConverter(pd.PropertyType).ConvertFrom(value); + } + else + { + DefaultValueAttribute att = pd.Attributes[typeof(DefaultValueAttribute)] as DefaultValueAttribute; + if (att != null) + { + value = att.Value; + b = true; + } + } + return b; + } + + /// + /// Fallback method for MONO, which doesn't implement DbConnectionStringBuilder.GetProperties() + /// + /// The hashtable to fill with property descriptors + private void FallbackGetProperties(Hashtable propertyList) + { + foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(this, true)) + { + if (descriptor.Name != "ConnectionString" && propertyList.ContainsKey(descriptor.DisplayName) == false) + { + propertyList.Add(descriptor.DisplayName, descriptor); + } + } + } + } +#endif +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteConvert.cs b/Native.Csharp.Tool/SQLite/SQLiteConvert.cs new file mode 100644 index 00000000..a1aadebd --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteConvert.cs @@ -0,0 +1,3026 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + +#if !NET_COMPACT_20 && TRACE_WARNING + using System.Diagnostics; +#endif + + using System.Runtime.InteropServices; + using System.Collections.Generic; + using System.Globalization; + using System.Text; + + /// + /// This base class provides datatype conversion services for the SQLite provider. + /// + public abstract class SQLiteConvert + { + /// + /// This character is used to escape other characters, including itself, in + /// connection string property names and values. + /// + internal const char EscapeChar = '\\'; + + /// + /// This character can be used to wrap connection string property names and + /// values. Normally, it is optional; however, when used, it must be the + /// first -AND- last character of that connection string property name -OR- + /// value. + /// + internal const char QuoteChar = '"'; + + /// + /// This character can be used to wrap connection string property names and + /// values. Normally, it is optional; however, when used, it must be the + /// first -AND- last character of that connection string property name -OR- + /// value. + /// + internal const char AltQuoteChar = '\''; + + /// + /// The character is used to separate the name and value for a connection + /// string property. This character cannot be present in any connection + /// string property name. This character can be present in a connection + /// string property value; however, this should be avoided unless deemed + /// absolutely necessary. + /// + internal const char ValueChar = '='; + + /// + /// This character is used to separate connection string properties. When + /// the "No_SQLiteConnectionNewParser" setting is enabled, this character + /// may not appear in connection string property names -OR- values. + /// + internal const char PairChar = ';'; + + /// + /// These are the characters that are special to the connection string + /// parser. + /// + internal static readonly char[] SpecialChars = { + QuoteChar, AltQuoteChar, PairChar, ValueChar, EscapeChar + }; + + /// + /// The fallback default database type when one cannot be obtained from an + /// existing connection instance. + /// + private const DbType FallbackDefaultDbType = DbType.Object; + + /// + /// The fallback default database type name when one cannot be obtained from + /// an existing connection instance. + /// + private static readonly string FallbackDefaultTypeName = String.Empty; + + /// + /// The value for the Unix epoch (e.g. January 1, 1970 at midnight, in UTC). + /// + protected static readonly DateTime UnixEpoch = + new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + #pragma warning disable 414 + /// + /// The value of the OLE Automation epoch represented as a Julian day. This + /// field cannot be removed as the test suite relies upon it. + /// + private static readonly double OleAutomationEpochAsJulianDay = 2415018.5; + #pragma warning restore 414 + + /// + /// The format string for DateTime values when using the InvariantCulture or CurrentCulture formats. + /// + private const string FullFormat = "yyyy-MM-ddTHH:mm:ss.fffffffK"; + + /// + /// This is the minimum Julian Day value supported by this library + /// (148731163200000). + /// + private static readonly long MinimumJd = computeJD(DateTime.MinValue); + + /// + /// This is the maximum Julian Day value supported by this library + /// (464269060799000). + /// + private static readonly long MaximumJd = computeJD(DateTime.MaxValue); + + /// + /// An array of ISO-8601 DateTime formats that we support parsing. + /// + private static string[] _datetimeFormats = new string[] { + "THHmmssK", + "THHmmK", + "HH:mm:ss.FFFFFFFK", + "HH:mm:ssK", + "HH:mmK", + "yyyy-MM-dd HH:mm:ss.FFFFFFFK", /* NOTE: UTC default (5). */ + "yyyy-MM-dd HH:mm:ssK", + "yyyy-MM-dd HH:mmK", + "yyyy-MM-ddTHH:mm:ss.FFFFFFFK", + "yyyy-MM-ddTHH:mmK", + "yyyy-MM-ddTHH:mm:ssK", + "yyyyMMddHHmmssK", + "yyyyMMddHHmmK", + "yyyyMMddTHHmmssFFFFFFFK", + "THHmmss", + "THHmm", + "HH:mm:ss.FFFFFFF", + "HH:mm:ss", + "HH:mm", + "yyyy-MM-dd HH:mm:ss.FFFFFFF", /* NOTE: Non-UTC default (19). */ + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm", + "yyyy-MM-ddTHH:mm:ss.FFFFFFF", + "yyyy-MM-ddTHH:mm", + "yyyy-MM-ddTHH:mm:ss", + "yyyyMMddHHmmss", + "yyyyMMddHHmm", + "yyyyMMddTHHmmssFFFFFFF", + "yyyy-MM-dd", + "yyyyMMdd", + "yy-MM-dd" + }; + + /// + /// The internal default format for UTC DateTime values when converting + /// to a string. + /// + private static readonly string _datetimeFormatUtc = _datetimeFormats[5]; + + /// + /// The internal default format for local DateTime values when converting + /// to a string. + /// + private static readonly string _datetimeFormatLocal = _datetimeFormats[19]; + + /// + /// An UTF-8 Encoding instance, so we can convert strings to and from UTF-8 + /// + private static Encoding _utf8 = new UTF8Encoding(); + /// + /// The default DateTime format for this instance. + /// + internal SQLiteDateFormats _datetimeFormat; + /// + /// The default DateTimeKind for this instance. + /// + internal DateTimeKind _datetimeKind; + /// + /// The default DateTime format string for this instance. + /// + internal string _datetimeFormatString = null; + /// + /// Initializes the conversion class + /// + /// The default date/time format to use for this instance + /// The DateTimeKind to use. + /// The DateTime format string to use. + internal SQLiteConvert( + SQLiteDateFormats fmt, + DateTimeKind kind, + string fmtString + ) + { + _datetimeFormat = fmt; + _datetimeKind = kind; + _datetimeFormatString = fmtString; + } + + #region UTF-8 Conversion Functions + /// + /// Converts a string to a UTF-8 encoded byte array sized to include a null-terminating character. + /// + /// The string to convert to UTF-8 + /// A byte array containing the converted string plus an extra 0 terminating byte at the end of the array. + public static byte[] ToUTF8(string sourceText) + { + if (sourceText == null) return null; + Byte[] byteArray; + int nlen = _utf8.GetByteCount(sourceText) + 1; + + byteArray = new byte[nlen]; + nlen = _utf8.GetBytes(sourceText, 0, sourceText.Length, byteArray, 0); + byteArray[nlen] = 0; + + return byteArray; + } + + /// + /// Convert a DateTime to a UTF-8 encoded, zero-terminated byte array. + /// + /// + /// This function is a convenience function, which first calls ToString() on the DateTime, and then calls ToUTF8() with the + /// string result. + /// + /// The DateTime to convert. + /// The UTF-8 encoded string, including a 0 terminating byte at the end of the array. + public byte[] ToUTF8(DateTime dateTimeValue) + { + return ToUTF8(ToString(dateTimeValue)); + } + + /// + /// Converts a UTF-8 encoded IntPtr of the specified length into a .NET string + /// + /// The pointer to the memory where the UTF-8 string is encoded + /// The number of bytes to decode + /// A string containing the translated character(s) + public virtual string ToString(IntPtr nativestring, int nativestringlen) + { + return UTF8ToString(nativestring, nativestringlen); + } + + /// + /// Converts a UTF-8 encoded IntPtr of the specified length into a .NET string + /// + /// The pointer to the memory where the UTF-8 string is encoded + /// The number of bytes to decode + /// A string containing the translated character(s) + public static string UTF8ToString(IntPtr nativestring, int nativestringlen) + { + if (nativestring == IntPtr.Zero || nativestringlen == 0) return String.Empty; + if (nativestringlen < 0) + { + nativestringlen = 0; + + while (Marshal.ReadByte(nativestring, nativestringlen) != 0) + nativestringlen++; + + if (nativestringlen == 0) return String.Empty; + } + + byte[] byteArray = new byte[nativestringlen]; + + Marshal.Copy(nativestring, byteArray, 0, nativestringlen); + + return _utf8.GetString(byteArray, 0, nativestringlen); + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region DateTime Conversion Functions + #region New Julian Day Conversion Methods + /// + /// Checks if the specified is within the + /// supported range for a Julian Day value. + /// + /// + /// The Julian Day value to check. + /// + /// + /// Non-zero if the specified Julian Day value is in the supported + /// range; otherwise, zero. + /// + private static bool isValidJd( + long jd + ) + { + return ((jd >= MinimumJd) && (jd <= MaximumJd)); + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Converts a Julian Day value from a to an + /// . + /// + /// + /// The Julian Day value to convert. + /// + /// + /// The resulting Julian Day value. + /// + private static long DoubleToJd( + double julianDay + ) + { + return (long)Math.Round(julianDay * 86400000.0); + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Converts a Julian Day value from an to a + /// . + /// + /// + /// The Julian Day value to convert. + /// + /// + /// The resulting Julian Day value. + /// + private static double JdToDouble( + long jd + ) + { + return (double)(jd / 86400000.0); + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Converts a Julian Day value to a . + /// This method was translated from the "computeYMD" function in the + /// "date.c" file belonging to the SQLite core library. + /// + /// + /// The Julian Day value to convert. + /// + /// + /// The value to return in the event that the + /// Julian Day is out of the supported range. If this value is null, + /// an exception will be thrown instead. + /// + /// + /// A value that contains the year, month, and + /// day values that are closest to the specified Julian Day value. + /// + private static DateTime computeYMD( + long jd, + DateTime? badValue + ) + { + if (!isValidJd(jd)) + { + if (badValue == null) + { + throw new ArgumentException( + "Not a supported Julian Day value."); + } + + return (DateTime)badValue; + } + + int Z, A, B, C, D, E, X1; + + Z = (int)((jd + 43200000) / 86400000); + A = (int)((Z - 1867216.25) / 36524.25); + A = Z + 1 + A - (A / 4); + B = A + 1524; + C = (int)((B - 122.1) / 365.25); + D = (36525 * C) / 100; + E = (int)((B - D) / 30.6001); + X1 = (int)(30.6001 * E); + + int day, month, year; + + day = B - D - X1; + month = E < 14 ? E - 1 : E - 13; + year = month > 2 ? C - 4716 : C - 4715; + + try + { + return new DateTime(year, month, day); + } + catch + { + if (badValue == null) + throw; + + return (DateTime)badValue; + } + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Converts a Julian Day value to a . + /// This method was translated from the "computeHMS" function in the + /// "date.c" file belonging to the SQLite core library. + /// + /// + /// The Julian Day value to convert. + /// + /// + /// The value to return in the event that the + /// Julian Day value is out of the supported range. If this value is + /// null, an exception will be thrown instead. + /// + /// + /// A value that contains the hour, minute, and + /// second, and millisecond values that are closest to the specified + /// Julian Day value. + /// + private static DateTime computeHMS( + long jd, + DateTime? badValue + ) + { + if (!isValidJd(jd)) + { + if (badValue == null) + { + throw new ArgumentException( + "Not a supported Julian Day value."); + } + + return (DateTime)badValue; + } + + int si; + + si = (int)((jd + 43200000) % 86400000); + + decimal sd; + + sd = si / 1000.0M; + si = (int)sd; + + int millisecond = (int)((sd - si) * 1000.0M); + + sd -= si; + + int hour; + + hour = si / 3600; + si -= hour * 3600; + + int minute; + + minute = si / 60; + sd += si - minute * 60; + + int second = (int)sd; + + try + { + DateTime minValue = DateTime.MinValue; + + return new DateTime( + minValue.Year, minValue.Month, minValue.Day, + hour, minute, second, millisecond); + } + catch + { + if (badValue == null) + throw; + + return (DateTime)badValue; + } + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Converts a to a Julian Day value. + /// This method was translated from the "computeJD" function in + /// the "date.c" file belonging to the SQLite core library. + /// Since the range of Julian Day values supported by this method + /// includes all possible (valid) values of a + /// value, it should be extremely difficult for this method to + /// raise an exception or return an undefined result. + /// + /// + /// The value to convert. This value + /// will be within the range of + /// (00:00:00.0000000, January 1, 0001) to + /// (23:59:59.9999999, December + /// 31, 9999). + /// + /// + /// The nearest Julian Day value corresponding to the specified + /// value. + /// + private static long computeJD( + DateTime dateTime + ) + { + int Y, M, D; + + Y = dateTime.Year; + M = dateTime.Month; + D = dateTime.Day; + + if (M <= 2) + { + Y--; + M += 12; + } + + int A, B, X1, X2; + + A = Y / 100; + B = 2 - A + (A / 4); + X1 = 36525 * (Y + 4716) / 100; + X2 = 306001 * (M + 1) / 10000; + + long jd; + + jd = (long)((X1 + X2 + D + B - 1524.5) * 86400000); + + jd += (dateTime.Hour * 3600000) + (dateTime.Minute * 60000) + + (dateTime.Second * 1000) + dateTime.Millisecond; + + return jd; + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Converts a string into a DateTime, using the DateTimeFormat, DateTimeKind, + /// and DateTimeFormatString specified for the connection when it was opened. + /// + /// + /// Acceptable ISO8601 DateTime formats are: + /// + /// THHmmssK + /// THHmmK + /// HH:mm:ss.FFFFFFFK + /// HH:mm:ssK + /// HH:mmK + /// yyyy-MM-dd HH:mm:ss.FFFFFFFK + /// yyyy-MM-dd HH:mm:ssK + /// yyyy-MM-dd HH:mmK + /// yyyy-MM-ddTHH:mm:ss.FFFFFFFK + /// yyyy-MM-ddTHH:mmK + /// yyyy-MM-ddTHH:mm:ssK + /// yyyyMMddHHmmssK + /// yyyyMMddHHmmK + /// yyyyMMddTHHmmssFFFFFFFK + /// THHmmss + /// THHmm + /// HH:mm:ss.FFFFFFF + /// HH:mm:ss + /// HH:mm + /// yyyy-MM-dd HH:mm:ss.FFFFFFF + /// yyyy-MM-dd HH:mm:ss + /// yyyy-MM-dd HH:mm + /// yyyy-MM-ddTHH:mm:ss.FFFFFFF + /// yyyy-MM-ddTHH:mm + /// yyyy-MM-ddTHH:mm:ss + /// yyyyMMddHHmmss + /// yyyyMMddHHmm + /// yyyyMMddTHHmmssFFFFFFF + /// yyyy-MM-dd + /// yyyyMMdd + /// yy-MM-dd + /// + /// If the string cannot be matched to one of the above formats -OR- + /// the DateTimeFormatString if one was provided, an exception will + /// be thrown. + /// + /// The string containing either a long integer number of 100-nanosecond units since + /// System.DateTime.MinValue, a Julian day double, an integer number of seconds since the Unix epoch, a + /// culture-independent formatted date and time string, a formatted date and time string in the current + /// culture, or an ISO8601-format string. + /// A DateTime value + public DateTime ToDateTime(string dateText) + { + return ToDateTime(dateText, _datetimeFormat, _datetimeKind, _datetimeFormatString); + } + + /// + /// Converts a string into a DateTime, using the specified DateTimeFormat, + /// DateTimeKind and DateTimeFormatString. + /// + /// + /// Acceptable ISO8601 DateTime formats are: + /// + /// THHmmssK + /// THHmmK + /// HH:mm:ss.FFFFFFFK + /// HH:mm:ssK + /// HH:mmK + /// yyyy-MM-dd HH:mm:ss.FFFFFFFK + /// yyyy-MM-dd HH:mm:ssK + /// yyyy-MM-dd HH:mmK + /// yyyy-MM-ddTHH:mm:ss.FFFFFFFK + /// yyyy-MM-ddTHH:mmK + /// yyyy-MM-ddTHH:mm:ssK + /// yyyyMMddHHmmssK + /// yyyyMMddHHmmK + /// yyyyMMddTHHmmssFFFFFFFK + /// THHmmss + /// THHmm + /// HH:mm:ss.FFFFFFF + /// HH:mm:ss + /// HH:mm + /// yyyy-MM-dd HH:mm:ss.FFFFFFF + /// yyyy-MM-dd HH:mm:ss + /// yyyy-MM-dd HH:mm + /// yyyy-MM-ddTHH:mm:ss.FFFFFFF + /// yyyy-MM-ddTHH:mm + /// yyyy-MM-ddTHH:mm:ss + /// yyyyMMddHHmmss + /// yyyyMMddHHmm + /// yyyyMMddTHHmmssFFFFFFF + /// yyyy-MM-dd + /// yyyyMMdd + /// yy-MM-dd + /// + /// If the string cannot be matched to one of the above formats -OR- + /// the DateTimeFormatString if one was provided, an exception will + /// be thrown. + /// + /// The string containing either a long integer number of 100-nanosecond units since + /// System.DateTime.MinValue, a Julian day double, an integer number of seconds since the Unix epoch, a + /// culture-independent formatted date and time string, a formatted date and time string in the current + /// culture, or an ISO8601-format string. + /// The SQLiteDateFormats to use. + /// The DateTimeKind to use. + /// The DateTime format string to use. + /// A DateTime value + public static DateTime ToDateTime( + string dateText, + SQLiteDateFormats format, + DateTimeKind kind, + string formatString + ) + { + switch (format) + { + case SQLiteDateFormats.Ticks: + { + return TicksToDateTime(Convert.ToInt64( + dateText, CultureInfo.InvariantCulture), kind); + } + case SQLiteDateFormats.JulianDay: + { + return ToDateTime(Convert.ToDouble( + dateText, CultureInfo.InvariantCulture), kind); + } + case SQLiteDateFormats.UnixEpoch: + { + return UnixEpochToDateTime(Convert.ToInt64( + dateText, CultureInfo.InvariantCulture), kind); + } + case SQLiteDateFormats.InvariantCulture: + { + if (formatString != null) + return DateTime.SpecifyKind(DateTime.ParseExact( + dateText, formatString, + DateTimeFormatInfo.InvariantInfo, + kind == DateTimeKind.Utc ? + DateTimeStyles.AdjustToUniversal : + DateTimeStyles.None), + kind); + else + return DateTime.SpecifyKind(DateTime.Parse( + dateText, DateTimeFormatInfo.InvariantInfo, + kind == DateTimeKind.Utc ? + DateTimeStyles.AdjustToUniversal : + DateTimeStyles.None), + kind); + } + case SQLiteDateFormats.CurrentCulture: + { + if (formatString != null) + return DateTime.SpecifyKind(DateTime.ParseExact( + dateText, formatString, + DateTimeFormatInfo.CurrentInfo, + kind == DateTimeKind.Utc ? + DateTimeStyles.AdjustToUniversal : + DateTimeStyles.None), + kind); + else + return DateTime.SpecifyKind(DateTime.Parse( + dateText, DateTimeFormatInfo.CurrentInfo, + kind == DateTimeKind.Utc ? + DateTimeStyles.AdjustToUniversal : + DateTimeStyles.None), + kind); + } + default: /* ISO-8601 */ + { + if (formatString != null) + return DateTime.SpecifyKind(DateTime.ParseExact( + dateText, formatString, + DateTimeFormatInfo.InvariantInfo, + kind == DateTimeKind.Utc ? + DateTimeStyles.AdjustToUniversal : + DateTimeStyles.None), + kind); + else + return DateTime.SpecifyKind(DateTime.ParseExact( + dateText, _datetimeFormats, + DateTimeFormatInfo.InvariantInfo, + kind == DateTimeKind.Utc ? + DateTimeStyles.AdjustToUniversal : + DateTimeStyles.None), + kind); + } + } + } + + /// + /// Converts a julianday value into a DateTime + /// + /// The value to convert + /// A .NET DateTime + public DateTime ToDateTime(double julianDay) + { + return ToDateTime(julianDay, _datetimeKind); + } + + /// + /// Converts a julianday value into a DateTime + /// + /// The value to convert + /// The DateTimeKind to use. + /// A .NET DateTime + public static DateTime ToDateTime( + double julianDay, + DateTimeKind kind + ) + { + long jd = DoubleToJd(julianDay); + DateTime dateTimeYMD = computeYMD(jd, null); + DateTime dateTimeHMS = computeHMS(jd, null); + + return new DateTime( + dateTimeYMD.Year, dateTimeYMD.Month, dateTimeYMD.Day, + dateTimeHMS.Hour, dateTimeHMS.Minute, dateTimeHMS.Second, + dateTimeHMS.Millisecond, kind); + } + + /// + /// Converts the specified number of seconds from the Unix epoch into a + /// value. + /// + /// + /// The number of whole seconds since the Unix epoch. + /// + /// + /// Either Utc or Local time. + /// + /// + /// The new value. + /// + internal static DateTime UnixEpochToDateTime(long seconds, DateTimeKind kind) + { + return DateTime.SpecifyKind(UnixEpoch.AddSeconds(seconds), kind); + } + + /// + /// Converts the specified number of ticks since the epoch into a + /// value. + /// + /// + /// The number of whole ticks since the epoch. + /// + /// + /// Either Utc or Local time. + /// + /// + /// The new value. + /// + internal static DateTime TicksToDateTime(long ticks, DateTimeKind kind) + { + return new DateTime(ticks, kind); + } + + /// + /// Converts a DateTime struct to a JulianDay double + /// + /// The DateTime to convert + /// The JulianDay value the Datetime represents + public static double ToJulianDay(DateTime value) + { + return JdToDouble(computeJD(value)); + } + + /// + /// Converts a DateTime struct to the whole number of seconds since the + /// Unix epoch. + /// + /// The DateTime to convert + /// The whole number of seconds since the Unix epoch + public static long ToUnixEpoch(DateTime value) + { + return (value.Subtract(UnixEpoch).Ticks / TimeSpan.TicksPerSecond); + } + + /// + /// Returns the DateTime format string to use for the specified DateTimeKind. + /// If is not null, it will be returned verbatim. + /// + /// The DateTimeKind to use. + /// The DateTime format string to use. + /// + /// The DateTime format string to use for the specified DateTimeKind. + /// + private static string GetDateTimeKindFormat( + DateTimeKind kind, + string formatString + ) + { + if (formatString != null) return formatString; + return (kind == DateTimeKind.Utc) ? _datetimeFormatUtc : _datetimeFormatLocal; + } + + /// + /// Converts a string into a DateTime, using the DateTimeFormat, DateTimeKind, + /// and DateTimeFormatString specified for the connection when it was opened. + /// + /// The DateTime value to convert + /// Either a string containing the long integer number of 100-nanosecond units since System.DateTime.MinValue, a + /// Julian day double, an integer number of seconds since the Unix epoch, a culture-independent formatted date and time + /// string, a formatted date and time string in the current culture, or an ISO8601-format date/time string. + public string ToString(DateTime dateValue) + { + return ToString(dateValue, _datetimeFormat, _datetimeKind, _datetimeFormatString); + } + + /// + /// Converts a string into a DateTime, using the DateTimeFormat, DateTimeKind, + /// and DateTimeFormatString specified for the connection when it was opened. + /// + /// The DateTime value to convert + /// The SQLiteDateFormats to use. + /// The DateTimeKind to use. + /// The DateTime format string to use. + /// Either a string containing the long integer number of 100-nanosecond units since System.DateTime.MinValue, a + /// Julian day double, an integer number of seconds since the Unix epoch, a culture-independent formatted date and time + /// string, a formatted date and time string in the current culture, or an ISO8601-format date/time string. + public static string ToString( + DateTime dateValue, + SQLiteDateFormats format, + DateTimeKind kind, + string formatString + ) + { + switch (format) + { + case SQLiteDateFormats.Ticks: + return dateValue.Ticks.ToString(CultureInfo.InvariantCulture); + case SQLiteDateFormats.JulianDay: + return ToJulianDay(dateValue).ToString(CultureInfo.InvariantCulture); + case SQLiteDateFormats.UnixEpoch: + return ((long)(dateValue.Subtract(UnixEpoch).Ticks / TimeSpan.TicksPerSecond)).ToString(); + case SQLiteDateFormats.InvariantCulture: + return dateValue.ToString((formatString != null) ? + formatString : FullFormat, CultureInfo.InvariantCulture); + case SQLiteDateFormats.CurrentCulture: + return dateValue.ToString((formatString != null) ? + formatString : FullFormat, CultureInfo.CurrentCulture); + default: + return (dateValue.Kind == DateTimeKind.Unspecified) ? + DateTime.SpecifyKind(dateValue, kind).ToString( + GetDateTimeKindFormat(kind, formatString), + CultureInfo.InvariantCulture) : dateValue.ToString( + GetDateTimeKindFormat(dateValue.Kind, formatString), + CultureInfo.InvariantCulture); + } + } + + /// + /// Internal function to convert a UTF-8 encoded IntPtr of the specified length to a DateTime. + /// + /// + /// This is a convenience function, which first calls ToString() on the IntPtr to convert it to a string, then calls + /// ToDateTime() on the string to return a DateTime. + /// + /// A pointer to the UTF-8 encoded string + /// The length in bytes of the string + /// The parsed DateTime value + internal DateTime ToDateTime(IntPtr ptr, int len) + { + return ToDateTime(ToString(ptr, len)); + } + #endregion + + /// + /// Smart method of splitting a string. Skips quoted elements, removes the quotes. + /// + /// + /// This split function works somewhat like the String.Split() function in that it breaks apart a string into + /// pieces and returns the pieces as an array. The primary differences are: + /// + /// Only one character can be provided as a separator character + /// Quoted text inside the string is skipped over when searching for the separator, and the quotes are removed. + /// + /// Thus, if splitting the following string looking for a comma:
+ /// One,Two, "Three, Four", Five
+ ///
+ /// The resulting array would contain
+ /// [0] One
+ /// [1] Two
+ /// [2] Three, Four
+ /// [3] Five
+ ///
+ /// Note that the leading and trailing spaces were removed from each item during the split. + ///
+ /// Source string to split apart + /// Separator character + /// A string array of the split up elements + public static string[] Split(string source, char separator) + { + char[] toks = new char[2] { QuoteChar, separator }; + char[] quot = new char[1] { QuoteChar }; + int n = 0; + List ls = new List(); + string s; + + while (source.Length > 0) + { + n = source.IndexOfAny(toks, n); + if (n == -1) break; + if (source[n] == toks[0]) + { + //source = source.Remove(n, 1); + n = source.IndexOfAny(quot, n + 1); + if (n == -1) + { + //source = "\"" + source; + break; + } + n++; + //source = source.Remove(n, 1); + } + else + { + s = source.Substring(0, n).Trim(); + if (s.Length > 1 && s[0] == quot[0] && s[s.Length - 1] == s[0]) + s = s.Substring(1, s.Length - 2); + + source = source.Substring(n + 1).Trim(); + if (s.Length > 0) ls.Add(s); + n = 0; + } + } + if (source.Length > 0) + { + s = source.Trim(); + if (s.Length > 1 && s[0] == quot[0] && s[s.Length - 1] == s[0]) + s = s.Substring(1, s.Length - 2); + ls.Add(s); + } + + string[] ar = new string[ls.Count]; + ls.CopyTo(ar, 0); + + return ar; + } + + /// + /// Splits the specified string into multiple strings based on a separator + /// and returns the result as an array of strings. + /// + /// + /// The string to split into pieces based on the separator character. If + /// this string is null, null will always be returned. If this string is + /// empty, an array of zero strings will always be returned. + /// + /// + /// The character used to divide the original string into sub-strings. + /// This character cannot be a backslash or a double-quote; otherwise, no + /// work will be performed and null will be returned. + /// + /// + /// If this parameter is non-zero, all double-quote characters will be + /// retained in the returned list of strings; otherwise, they will be + /// dropped. + /// + /// + /// Upon failure, this parameter will be modified to contain an appropriate + /// error message. + /// + /// + /// The new array of strings or null if the input string is null -OR- the + /// separator character is a backslash or a double-quote -OR- the string + /// contains an unbalanced backslash or double-quote character. + /// + internal static string[] NewSplit( + string value, + char separator, + bool keepQuote, + ref string error + ) + { + // + // NOTE: It is illegal for the separator character to be either a + // backslash or a double-quote because both of those characters + // are used for escaping other characters (e.g. the separator + // character). + // + if ((separator == EscapeChar) || (separator == QuoteChar)) + { + error = "separator character cannot be the escape or quote characters"; + return null; + } + + if (value == null) + { + error = "string value to split cannot be null"; + return null; + } + + int length = value.Length; + + if (length == 0) + return new string[0]; + + List list = new List(); + StringBuilder element = new StringBuilder(); + int index = 0; + bool escape = false; + bool quote = false; + + while (index < length) + { + char character = value[index++]; + + if (escape) + { + // + // HACK: Only consider the escape character to be an actual + // "escape" if it is followed by a reserved character; + // otherwise, emit the original escape character and + // the current character in an effort to help preserve + // the original string content. + // + if ((character != EscapeChar) && + (character != QuoteChar) && + (character != separator)) + { + element.Append(EscapeChar); + } + + element.Append(character); + escape = false; + } + else if (character == EscapeChar) + { + escape = true; + } + else if (character == QuoteChar) + { + if (keepQuote) + element.Append(character); + + quote = !quote; + } + else if (character == separator) + { + if (quote) + { + element.Append(character); + } + else + { + list.Add(element.ToString()); + element.Length = 0; + } + } + else + { + element.Append(character); + } + } + + // + // NOTE: An unbalanced escape or quote character in the string is + // considered to be a fatal error; therefore, return null. + // + if (escape || quote) + { + error = "unbalanced escape or quote character found"; + return null; + } + + if (element.Length > 0) + list.Add(element.ToString()); + + return list.ToArray(); + } + + /// + /// Queries and returns the string representation for an object, using the + /// specified (or current) format provider. + /// + /// + /// The object instance to return the string representation for. + /// + /// + /// The format provider to use -OR- null if the current format provider for + /// the thread should be used instead. + /// + /// + /// The string representation for the object instance -OR- null if the + /// object instance is also null. + /// + public static string ToStringWithProvider( + object obj, + IFormatProvider provider + ) + { + if (obj == null) + return null; /* null --> null */ + + if (obj is string) + return (string)obj; /* identity */ + + IConvertible convertible = obj as IConvertible; + + if (convertible != null) + return convertible.ToString(provider); + + return obj.ToString(); /* not IConvertible */ + } + + /// + /// Attempts to convert an arbitrary object to the Boolean data type. + /// Null object values are converted to false. Throws an exception + /// upon failure. + /// + /// + /// The object value to convert. + /// + /// + /// The format provider to use. + /// + /// + /// If non-zero, a string value will be converted using the + /// + /// method; otherwise, the + /// method will be used. + /// + /// + /// The converted boolean value. + /// + internal static bool ToBoolean( + object obj, + IFormatProvider provider, + bool viaFramework + ) + { + if (obj == null) + return false; + + TypeCode typeCode = Type.GetTypeCode(obj.GetType()); + + switch (typeCode) + { + case TypeCode.Empty: + case TypeCode.DBNull: + return false; + case TypeCode.Boolean: + return (bool)obj; + case TypeCode.Char: + return ((char)obj) != (char)0 ? true : false; + case TypeCode.SByte: + return ((sbyte)obj) != (sbyte)0 ? true : false; + case TypeCode.Byte: + return ((byte)obj) != (byte)0 ? true : false; + case TypeCode.Int16: + return ((short)obj) != (short)0 ? true : false; + case TypeCode.UInt16: + return ((ushort)obj) != (ushort)0 ? true : false; + case TypeCode.Int32: + return ((int)obj) != (int)0 ? true : false; + case TypeCode.UInt32: + return ((uint)obj) != (uint)0 ? true : false; + case TypeCode.Int64: + return ((long)obj) != (long)0 ? true : false; + case TypeCode.UInt64: + return ((ulong)obj) != (ulong)0 ? true : false; + case TypeCode.Single: + return ((float)obj) != (float)0.0 ? true : false; + case TypeCode.Double: + return ((double)obj) != (double)0.0 ? true : false; + case TypeCode.Decimal: + return ((decimal)obj) != Decimal.Zero ? true : false; + case TypeCode.String: + return viaFramework ? + Convert.ToBoolean(obj, provider) : + ToBoolean(ToStringWithProvider(obj, provider)); + default: + throw new SQLiteException(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Cannot convert type {0} to boolean", + typeCode)); + } + } + + /// + /// Convert a value to true or false. + /// + /// A string or number representing true or false + /// + public static bool ToBoolean(object source) + { + if (source is bool) return (bool)source; + + return ToBoolean(ToStringWithProvider( + source, CultureInfo.InvariantCulture)); + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Converts an integer to a string that can be round-tripped using the + /// invariant culture. + /// + /// + /// The integer value to return the string representation for. + /// + /// + /// The string representation of the specified integer value, using the + /// invariant culture. + /// + internal static string ToString(int value) + { + return value.ToString(CultureInfo.InvariantCulture); + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to convert a into a . + /// + /// + /// The to convert, cannot be null. + /// + /// + /// The converted value. + /// + /// + /// The supported strings are "yes", "no", "y", "n", "on", "off", "0", "1", + /// as well as any prefix of the strings + /// and . All strings are treated in a + /// case-insensitive manner. + /// + public static bool ToBoolean(string source) + { + if (source == null) throw new ArgumentNullException("source"); + if (String.Compare(source, 0, bool.TrueString, 0, source.Length, StringComparison.OrdinalIgnoreCase) == 0) return true; + else if (String.Compare(source, 0, bool.FalseString, 0, source.Length, StringComparison.OrdinalIgnoreCase) == 0) return false; + + switch (source.ToLower(CultureInfo.InvariantCulture)) + { + case "y": + case "yes": + case "on": + case "1": + return true; + case "n": + case "no": + case "off": + case "0": + return false; + } + + throw new ArgumentException("source"); + } + + #region Type Conversions + /// + /// Converts a SQLiteType to a .NET Type object + /// + /// The SQLiteType to convert + /// Returns a .NET Type object + internal static Type SQLiteTypeToType(SQLiteType t) + { + if (t.Type == DbType.Object) + return _affinitytotype[(int)t.Affinity]; + else + return SQLiteConvert.DbTypeToType(t.Type); + } + + private static Type[] _affinitytotype = { + typeof(object), // Uninitialized (0) + typeof(Int64), // Int64 (1) + typeof(Double), // Double (2) + typeof(string), // Text (3) + typeof(byte[]), // Blob (4) + typeof(object), // Null (5) + null, // Undefined (6) + null, // Undefined (7) + null, // Undefined (8) + null, // Undefined (9) + typeof(DateTime), // DateTime (10) + typeof(object) // None (11) + }; + + /// + /// For a given intrinsic type, return a DbType + /// + /// The native type to convert + /// The corresponding (closest match) DbType + internal static DbType TypeToDbType(Type typ) + { + TypeCode tc = Type.GetTypeCode(typ); + if (tc == TypeCode.Object) + { + if (typ == typeof(byte[])) return DbType.Binary; + if (typ == typeof(Guid)) return DbType.Guid; + return DbType.String; + } + return _typetodbtype[(int)tc]; + } + + private static DbType[] _typetodbtype = { + DbType.Object, // Empty (0) + DbType.Binary, // Object (1) + DbType.Object, // DBNull (2) + DbType.Boolean, // Boolean (3) + DbType.SByte, // Char (4) + DbType.SByte, // SByte (5) + DbType.Byte, // Byte (6) + DbType.Int16, // Int16 (7) + DbType.UInt16, // UInt16 (8) + DbType.Int32, // Int32 (9) + DbType.UInt32, // UInt32 (10) + DbType.Int64, // Int64 (11) + DbType.UInt64, // UInt64 (12) + DbType.Single, // Single (13) + DbType.Double, // Double (14) + DbType.Decimal, // Decimal (15) + DbType.DateTime, // DateTime (16) + DbType.Object, // ?? (17) + DbType.String // String (18) + }; + + /// + /// Returns the ColumnSize for the given DbType + /// + /// The DbType to get the size of + /// + internal static int DbTypeToColumnSize(DbType typ) + { + return _dbtypetocolumnsize[(int)typ]; + } + + private static int[] _dbtypetocolumnsize = { + int.MaxValue, // AnsiString (0) + int.MaxValue, // Binary (1) + 1, // Byte (2) + 1, // Boolean (3) + 8, // Currency (4) + 8, // Date (5) + 8, // DateTime (6) + 8, // Decimal (7) + 8, // Double (8) + 16, // Guid (9) + 2, // Int16 (10) + 4, // Int32 (11) + 8, // Int64 (12) + int.MaxValue, // Object (13) + 1, // SByte (14) + 4, // Single (15) + int.MaxValue, // String (16) + 8, // Time (17) + 2, // UInt16 (18) + 4, // UInt32 (19) + 8, // UInt64 (20) + 8, // VarNumeric (21) + int.MaxValue, // AnsiStringFixedLength (22) + int.MaxValue, // StringFixedLength (23) + int.MaxValue, // ?? (24) + int.MaxValue, // Xml (25) + 8, // DateTime2 (26) + 10 // DateTimeOffset (27) + }; + + internal static object DbTypeToNumericPrecision(DbType typ) + { + return _dbtypetonumericprecision[(int)typ]; + } + + private static object[] _dbtypetonumericprecision = { + DBNull.Value, // AnsiString (0) + DBNull.Value, // Binary (1) + 3, // Byte (2) + DBNull.Value, // Boolean (3) + 19, // Currency (4) + DBNull.Value, // Date (5) + DBNull.Value, // DateTime (6) + 53, // Decimal (7) + 53, // Double (8) + DBNull.Value, // Guid (9) + 5, // Int16 (10) + 10, // Int32 (11) + 19, // Int64 (12) + DBNull.Value, // Object (13) + 3, // SByte (14) + 24, // Single (15) + DBNull.Value, // String (16) + DBNull.Value, // Time (17) + 5, // UInt16 (18) + 10, // UInt32 (19) + 19, // UInt64 (20) + 53, // VarNumeric (21) + DBNull.Value, // AnsiStringFixedLength (22) + DBNull.Value, // StringFixedLength (23) + DBNull.Value, // ?? (24) + DBNull.Value, // Xml (25) + DBNull.Value, // DateTime2 (26) + DBNull.Value // DateTimeOffset (27) + }; + + internal static object DbTypeToNumericScale(DbType typ) + { + return _dbtypetonumericscale[(int)typ]; + } + + private static object[] _dbtypetonumericscale = { + DBNull.Value, // AnsiString (0) + DBNull.Value, // Binary (1) + 0, // Byte (2) + DBNull.Value, // Boolean (3) + 4, // Currency (4) + DBNull.Value, // Date (5) + DBNull.Value, // DateTime (6) + DBNull.Value, // Decimal (7) + DBNull.Value, // Double (8) + DBNull.Value, // Guid (9) + 0, // Int16 (10) + 0, // Int32 (11) + 0, // Int64 (12) + DBNull.Value, // Object (13) + 0, // SByte (14) + DBNull.Value, // Single (15) + DBNull.Value, // String (16) + DBNull.Value, // Time (17) + 0, // UInt16 (18) + 0, // UInt32 (19) + 0, // UInt64 (20) + 0, // VarNumeric (21) + DBNull.Value, // AnsiStringFixedLength (22) + DBNull.Value, // StringFixedLength (23) + DBNull.Value, // ?? (24) + DBNull.Value, // Xml (25) + DBNull.Value, // DateTime2 (26) + DBNull.Value // DateTimeOffset (27) + }; + + /// + /// Determines the default database type name to be used when a + /// per-connection value is not available. + /// + /// + /// The connection context for type mappings, if any. + /// + /// + /// The default database type name to use. + /// + private static string GetDefaultTypeName( + SQLiteConnection connection + ) + { + SQLiteConnectionFlags flags = (connection != null) ? + connection.Flags : SQLiteConnectionFlags.None; + + if (HelperMethods.HasFlags( + flags, SQLiteConnectionFlags.NoConvertSettings)) + { + return FallbackDefaultTypeName; + } + + string name = "Use_SQLiteConvert_DefaultTypeName"; + object value = null; + string @default = null; + + if ((connection == null) || + !connection.TryGetCachedSetting(name, @default, out value)) + { + try + { + value = UnsafeNativeMethods.GetSettingValue(name, @default); + + if (value == null) + value = FallbackDefaultTypeName; + } + finally + { + if (connection != null) + connection.SetCachedSetting(name, value); + } + } + + return SettingValueToString(value); + } + +#if !NET_COMPACT_20 && TRACE_WARNING + /// + /// If applicable, issues a trace log message warning about falling back to + /// the default database type name. + /// + /// + /// The database value type. + /// + /// + /// The flags associated with the parent connection object. + /// + /// + /// The textual name of the database type. + /// + private static void DefaultTypeNameWarning( + DbType dbType, + SQLiteConnectionFlags flags, + string typeName + ) + { + if (HelperMethods.HasFlags(flags, SQLiteConnectionFlags.TraceWarning)) + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "WARNING: Type mapping failed, returning default name \"{0}\" for type {1}.", + typeName, dbType)); + } + } + + /// + /// If applicable, issues a trace log message warning about falling back to + /// the default database value type. + /// + /// + /// The textual name of the database type. + /// + /// + /// The flags associated with the parent connection object. + /// + /// + /// The database value type. + /// + private static void DefaultDbTypeWarning( + string typeName, + SQLiteConnectionFlags flags, + DbType? dbType + ) + { + if (!String.IsNullOrEmpty(typeName) && + HelperMethods.HasFlags(flags, SQLiteConnectionFlags.TraceWarning)) + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "WARNING: Type mapping failed, returning default type {0} for name \"{1}\".", + dbType, typeName)); + } + } +#endif + + /// + /// For a given database value type, return the "closest-match" textual database type name. + /// + /// The connection context for custom type mappings, if any. + /// The database value type. + /// The flags associated with the parent connection object. + /// The type name or an empty string if it cannot be determined. + internal static string DbTypeToTypeName( + SQLiteConnection connection, + DbType dbType, + SQLiteConnectionFlags flags + ) + { + string defaultTypeName = null; + + if (connection != null) + { + flags |= connection.Flags; + + if (HelperMethods.HasFlags(flags, SQLiteConnectionFlags.UseConnectionTypes)) + { + SQLiteDbTypeMap connectionTypeNames = connection._typeNames; + + if (connectionTypeNames != null) + { + SQLiteDbTypeMapping value; + + if (connectionTypeNames.TryGetValue(dbType, out value)) + return value.typeName; + } + } + + // + // NOTE: Use the default database type name for the connection. + // + defaultTypeName = connection.DefaultTypeName; + } + + if (HelperMethods.HasFlags(flags, SQLiteConnectionFlags.NoGlobalTypes)) + { + if (defaultTypeName != null) + return defaultTypeName; + + defaultTypeName = GetDefaultTypeName(connection); + +#if !NET_COMPACT_20 && TRACE_WARNING + DefaultTypeNameWarning(dbType, flags, defaultTypeName); +#endif + + return defaultTypeName; + } + + { + SQLiteDbTypeMapping value; + + if ((_typeNames != null) && + _typeNames.TryGetValue(dbType, out value)) + { + return value.typeName; + } + } + + if (defaultTypeName != null) + return defaultTypeName; + + defaultTypeName = GetDefaultTypeName(connection); + +#if !NET_COMPACT_20 && TRACE_WARNING + DefaultTypeNameWarning(dbType, flags, defaultTypeName); +#endif + + return defaultTypeName; + } + + /// + /// Convert a DbType to a Type + /// + /// The DbType to convert from + /// The closest-match .NET type + internal static Type DbTypeToType(DbType typ) + { + return _dbtypeToType[(int)typ]; + } + + private static Type[] _dbtypeToType = { + typeof(string), // AnsiString (0) + typeof(byte[]), // Binary (1) + typeof(byte), // Byte (2) + typeof(bool), // Boolean (3) + typeof(decimal), // Currency (4) + typeof(DateTime), // Date (5) + typeof(DateTime), // DateTime (6) + typeof(decimal), // Decimal (7) + typeof(double), // Double (8) + typeof(Guid), // Guid (9) + typeof(Int16), // Int16 (10) + typeof(Int32), // Int32 (11) + typeof(Int64), // Int64 (12) + typeof(object), // Object (13) + typeof(sbyte), // SByte (14) + typeof(float), // Single (15) + typeof(string), // String (16) + typeof(DateTime), // Time (17) + typeof(UInt16), // UInt16 (18) + typeof(UInt32), // UInt32 (19) + typeof(UInt64), // UInt64 (20) + typeof(double), // VarNumeric (21) + typeof(string), // AnsiStringFixedLength (22) + typeof(string), // StringFixedLength (23) + typeof(string), // ?? (24) + typeof(string), // Xml (25) + typeof(DateTime), // DateTime2 (26) +#if !PLATFORM_COMPACTFRAMEWORK && (NET_35 || NET_40 || NET_45 || NET_451 || NET_452 || NET_46 || NET_461 || NET_462 || NET_47 || NET_471 || NET_472) + // + // NOTE: This type is only available on the + // .NET Framework 2.0 SP1 and later. + // + typeof(DateTimeOffset) // DateTimeOffset (27) +#else + typeof(DateTime) // DateTimeOffset (27) +#endif + }; + + /// + /// For a given type, return the closest-match SQLite TypeAffinity, which only understands a very limited subset of types. + /// + /// The type to evaluate + /// The flags associated with the connection. + /// The SQLite type affinity for that type. + internal static TypeAffinity TypeToAffinity( + Type typ, + SQLiteConnectionFlags flags + ) + { + TypeCode tc = Type.GetTypeCode(typ); + if (tc == TypeCode.Object) + { + if (typ == typeof(byte[]) || typ == typeof(Guid)) + return TypeAffinity.Blob; + else + return TypeAffinity.Text; + } + if ((tc == TypeCode.Decimal) && + HelperMethods.HasFlags(flags, SQLiteConnectionFlags.GetDecimalAsText)) + { + return TypeAffinity.Text; + } + return _typecodeAffinities[(int)tc]; + } + + private static TypeAffinity[] _typecodeAffinities = { + TypeAffinity.Null, // Empty (0) + TypeAffinity.Blob, // Object (1) + TypeAffinity.Null, // DBNull (2) + TypeAffinity.Int64, // Boolean (3) + TypeAffinity.Int64, // Char (4) + TypeAffinity.Int64, // SByte (5) + TypeAffinity.Int64, // Byte (6) + TypeAffinity.Int64, // Int16 (7) + TypeAffinity.Int64, // UInt16 (8) + TypeAffinity.Int64, // Int32 (9) + TypeAffinity.Int64, // UInt32 (10) + TypeAffinity.Int64, // Int64 (11) + TypeAffinity.Int64, // UInt64 (12) + TypeAffinity.Double, // Single (13) + TypeAffinity.Double, // Double (14) + TypeAffinity.Double, // Decimal (15) + TypeAffinity.DateTime, // DateTime (16) + TypeAffinity.Null, // ?? (17) + TypeAffinity.Text // String (18) + }; + + /// + /// Builds and returns a map containing the database column types + /// recognized by this provider. + /// + /// + /// A map containing the database column types recognized by this + /// provider. + /// + private static SQLiteDbTypeMap GetSQLiteDbTypeMap() + { + return new SQLiteDbTypeMap(new SQLiteDbTypeMapping[] { + new SQLiteDbTypeMapping("BIGINT", DbType.Int64, false), + new SQLiteDbTypeMapping("BIGUINT", DbType.UInt64, false), + new SQLiteDbTypeMapping("BINARY", DbType.Binary, false), + new SQLiteDbTypeMapping("BIT", DbType.Boolean, true), + new SQLiteDbTypeMapping("BLOB", DbType.Binary, true), + new SQLiteDbTypeMapping("BOOL", DbType.Boolean, false), + new SQLiteDbTypeMapping("BOOLEAN", DbType.Boolean, false), + new SQLiteDbTypeMapping("CHAR", DbType.AnsiStringFixedLength, true), + new SQLiteDbTypeMapping("CLOB", DbType.String, false), + new SQLiteDbTypeMapping("COUNTER", DbType.Int64, false), + new SQLiteDbTypeMapping("CURRENCY", DbType.Decimal, false), + new SQLiteDbTypeMapping("DATE", DbType.DateTime, false), + new SQLiteDbTypeMapping("DATETIME", DbType.DateTime, true), + new SQLiteDbTypeMapping("DECIMAL", DbType.Decimal, true), + new SQLiteDbTypeMapping("DECIMALTEXT", DbType.Decimal, false), + new SQLiteDbTypeMapping("DOUBLE", DbType.Double, false), + new SQLiteDbTypeMapping("FLOAT", DbType.Double, false), + new SQLiteDbTypeMapping("GENERAL", DbType.Binary, false), + new SQLiteDbTypeMapping("GUID", DbType.Guid, false), + new SQLiteDbTypeMapping("IDENTITY", DbType.Int64, false), + new SQLiteDbTypeMapping("IMAGE", DbType.Binary, false), + new SQLiteDbTypeMapping("INT", DbType.Int32, true), + new SQLiteDbTypeMapping("INT8", DbType.SByte, false), + new SQLiteDbTypeMapping("INT16", DbType.Int16, false), + new SQLiteDbTypeMapping("INT32", DbType.Int32, false), + new SQLiteDbTypeMapping("INT64", DbType.Int64, false), + new SQLiteDbTypeMapping("INTEGER", DbType.Int64, true), + new SQLiteDbTypeMapping("INTEGER8", DbType.SByte, false), + new SQLiteDbTypeMapping("INTEGER16", DbType.Int16, false), + new SQLiteDbTypeMapping("INTEGER32", DbType.Int32, false), + new SQLiteDbTypeMapping("INTEGER64", DbType.Int64, false), + new SQLiteDbTypeMapping("LOGICAL", DbType.Boolean, false), + new SQLiteDbTypeMapping("LONG", DbType.Int64, false), + new SQLiteDbTypeMapping("LONGCHAR", DbType.String, false), + new SQLiteDbTypeMapping("LONGTEXT", DbType.String, false), + new SQLiteDbTypeMapping("LONGVARCHAR", DbType.String, false), + new SQLiteDbTypeMapping("MEMO", DbType.String, false), + new SQLiteDbTypeMapping("MONEY", DbType.Decimal, false), + new SQLiteDbTypeMapping("NCHAR", DbType.StringFixedLength, true), + new SQLiteDbTypeMapping("NOTE", DbType.String, false), + new SQLiteDbTypeMapping("NTEXT", DbType.String, false), + new SQLiteDbTypeMapping("NUMBER", DbType.Decimal, false), + new SQLiteDbTypeMapping("NUMERIC", DbType.Decimal, false), + new SQLiteDbTypeMapping("NUMERICTEXT", DbType.Decimal, false), + new SQLiteDbTypeMapping("NVARCHAR", DbType.String, true), + new SQLiteDbTypeMapping("OLEOBJECT", DbType.Binary, false), + new SQLiteDbTypeMapping("RAW", DbType.Binary, false), + new SQLiteDbTypeMapping("REAL", DbType.Double, true), + new SQLiteDbTypeMapping("SINGLE", DbType.Single, true), + new SQLiteDbTypeMapping("SMALLDATE", DbType.DateTime, false), + new SQLiteDbTypeMapping("SMALLINT", DbType.Int16, true), + new SQLiteDbTypeMapping("SMALLUINT", DbType.UInt16, true), + new SQLiteDbTypeMapping("STRING", DbType.String, false), + new SQLiteDbTypeMapping("TEXT", DbType.String, false), + new SQLiteDbTypeMapping("TIME", DbType.DateTime, false), + new SQLiteDbTypeMapping("TIMESTAMP", DbType.DateTime, false), + new SQLiteDbTypeMapping("TINYINT", DbType.Byte, true), + new SQLiteDbTypeMapping("TINYSINT", DbType.SByte, true), + new SQLiteDbTypeMapping("UINT", DbType.UInt32, true), + new SQLiteDbTypeMapping("UINT8", DbType.Byte, false), + new SQLiteDbTypeMapping("UINT16", DbType.UInt16, false), + new SQLiteDbTypeMapping("UINT32", DbType.UInt32, false), + new SQLiteDbTypeMapping("UINT64", DbType.UInt64, false), + new SQLiteDbTypeMapping("ULONG", DbType.UInt64, false), + new SQLiteDbTypeMapping("UNIQUEIDENTIFIER", DbType.Guid, true), + new SQLiteDbTypeMapping("UNSIGNEDINTEGER", DbType.UInt64, true), + new SQLiteDbTypeMapping("UNSIGNEDINTEGER8", DbType.Byte, false), + new SQLiteDbTypeMapping("UNSIGNEDINTEGER16", DbType.UInt16, false), + new SQLiteDbTypeMapping("UNSIGNEDINTEGER32", DbType.UInt32, false), + new SQLiteDbTypeMapping("UNSIGNEDINTEGER64", DbType.UInt64, false), + new SQLiteDbTypeMapping("VARBINARY", DbType.Binary, false), + new SQLiteDbTypeMapping("VARCHAR", DbType.AnsiString, true), + new SQLiteDbTypeMapping("VARCHAR2", DbType.AnsiString, false), + new SQLiteDbTypeMapping("YESNO", DbType.Boolean, false) + }); + } + + /// + /// Determines if a database type is considered to be a string. + /// + /// + /// The database type to check. + /// + /// + /// Non-zero if the database type is considered to be a string, zero + /// otherwise. + /// + internal static bool IsStringDbType( + DbType type + ) + { + switch (type) + { + case DbType.AnsiString: + case DbType.String: + case DbType.AnsiStringFixedLength: + case DbType.StringFixedLength: + return true; + default: + return false; + } + } + + /// + /// Determines and returns the runtime configuration setting string that + /// should be used in place of the specified object value. + /// + /// + /// The object value to convert to a string. + /// + /// + /// Either the string to use in place of the object value -OR- null if it + /// cannot be determined. + /// + private static string SettingValueToString( + object value + ) + { + if (value is string) + return (string)value; + + if (value != null) + return value.ToString(); + + return null; + } + + /// + /// Determines the default value to be used when a + /// per-connection value is not available. + /// + /// + /// The connection context for type mappings, if any. + /// + /// + /// The default value to use. + /// + private static DbType GetDefaultDbType( + SQLiteConnection connection + ) + { + SQLiteConnectionFlags flags = (connection != null) ? + connection.Flags : SQLiteConnectionFlags.None; + + if (HelperMethods.HasFlags( + flags, SQLiteConnectionFlags.NoConvertSettings)) + { + return FallbackDefaultDbType; + } + + bool found = false; + string name = "Use_SQLiteConvert_DefaultDbType"; + object value = null; + string @default = null; + + if ((connection == null) || + !connection.TryGetCachedSetting(name, @default, out value)) + { + value = UnsafeNativeMethods.GetSettingValue(name, @default); + + if (value == null) + value = FallbackDefaultDbType; + } + else + { + found = true; + } + + try + { + if (!(value is DbType)) + { + value = SQLiteConnection.TryParseEnum( + typeof(DbType), SettingValueToString(value), true); + + if (!(value is DbType)) + value = FallbackDefaultDbType; + } + + return (DbType)value; + } + finally + { + if (!found && (connection != null)) + connection.SetCachedSetting(name, value); + } + } + + /// + /// Converts the object value, which is assumed to have originated + /// from a , to a string value. + /// + /// + /// The value to be converted to a string. + /// + /// + /// A null value will be returned if the original value is null -OR- + /// the original value is . Otherwise, + /// the original value will be converted to a string, using its + /// (possibly overridden) method and + /// then returned. + /// + public static string GetStringOrNull( + object value + ) + { + if (value == null) + return null; + + if (value is string) + return (string)value; + + if (value == DBNull.Value) + return null; + + return value.ToString(); + } + + /// + /// Determines if the specified textual value appears to be a + /// value. + /// + /// + /// The textual value to inspect. + /// + /// + /// Non-zero if the text looks like a value, + /// zero otherwise. + /// + internal static bool LooksLikeNull( + string text + ) + { + return (text == null); + } + + /// + /// Determines if the specified textual value appears to be an + /// value. + /// + /// + /// The textual value to inspect. + /// + /// + /// Non-zero if the text looks like an value, + /// zero otherwise. + /// + internal static bool LooksLikeInt64( + string text + ) + { + long longValue; + +#if !PLATFORM_COMPACTFRAMEWORK + if (!long.TryParse( + text, NumberStyles.Integer, CultureInfo.InvariantCulture, + out longValue)) + { + return false; + } +#else + try + { + longValue = long.Parse( + text, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch + { + return false; + } +#endif + + return String.Equals( + longValue.ToString(CultureInfo.InvariantCulture), text, + StringComparison.Ordinal); + } + + /// + /// Determines if the specified textual value appears to be a + /// value. + /// + /// + /// The textual value to inspect. + /// + /// + /// Non-zero if the text looks like a value, + /// zero otherwise. + /// + internal static bool LooksLikeDouble( + string text + ) + { + double doubleValue; + +#if !PLATFORM_COMPACTFRAMEWORK + if (!double.TryParse( + text, NumberStyles.Float | NumberStyles.AllowThousands, + CultureInfo.InvariantCulture, out doubleValue)) + { + return false; + } +#else + try + { + doubleValue = double.Parse(text, CultureInfo.InvariantCulture); + } + catch + { + return false; + } +#endif + + return String.Equals( + doubleValue.ToString(CultureInfo.InvariantCulture), text, + StringComparison.Ordinal); + } + + /// + /// Determines if the specified textual value appears to be a + /// value. + /// + /// + /// The object instance configured with + /// the chosen format. + /// + /// + /// The textual value to inspect. + /// + /// + /// Non-zero if the text looks like a in the + /// configured format, zero otherwise. + /// + internal static bool LooksLikeDateTime( + SQLiteConvert convert, + string text + ) + { + if (convert == null) + return false; + + try + { + DateTime dateTimeValue = convert.ToDateTime(text); + + if (String.Equals( + convert.ToString(dateTimeValue), + text, StringComparison.Ordinal)) + { + return true; + } + } + catch + { + // do nothing. + } + + return false; + } + + /// + /// For a given textual database type name, return the "closest-match" database type. + /// This method is called during query result processing; therefore, its performance + /// is critical. + /// + /// The connection context for custom type mappings, if any. + /// The textual name of the database type to match. + /// The flags associated with the parent connection object. + /// The .NET DBType the text evaluates to. + internal static DbType TypeNameToDbType( + SQLiteConnection connection, + string typeName, + SQLiteConnectionFlags flags + ) + { + DbType? defaultDbType = null; + + if (connection != null) + { + flags |= connection.Flags; + + if (HelperMethods.HasFlags(flags, SQLiteConnectionFlags.UseConnectionTypes)) + { + SQLiteDbTypeMap connectionTypeNames = connection._typeNames; + + if (connectionTypeNames != null) + { + if (typeName != null) + { + SQLiteDbTypeMapping value; + + if (connectionTypeNames.TryGetValue(typeName, out value)) + { + return value.dataType; + } + else + { + int index = typeName.IndexOf('('); + + if ((index > 0) && + connectionTypeNames.TryGetValue(typeName.Substring(0, index).TrimEnd(), out value)) + { + return value.dataType; + } + } + } + } + } + + // + // NOTE: Use the default database type for the connection. + // + defaultDbType = connection.DefaultDbType; + } + + if (HelperMethods.HasFlags(flags, SQLiteConnectionFlags.NoGlobalTypes)) + { + if (defaultDbType != null) + return (DbType)defaultDbType; + + defaultDbType = GetDefaultDbType(connection); + +#if !NET_COMPACT_20 && TRACE_WARNING + DefaultDbTypeWarning(typeName, flags, defaultDbType); +#endif + + return (DbType)defaultDbType; + } + + { + if ((_typeNames != null) && (typeName != null)) + { + SQLiteDbTypeMapping value; + + if (_typeNames.TryGetValue(typeName, out value)) + { + return value.dataType; + } + else + { + int index = typeName.IndexOf('('); + + if ((index > 0) && + _typeNames.TryGetValue(typeName.Substring(0, index).TrimEnd(), out value)) + { + return value.dataType; + } + } + } + } + + if (defaultDbType != null) + return (DbType)defaultDbType; + + defaultDbType = GetDefaultDbType(connection); + +#if !NET_COMPACT_20 && TRACE_WARNING + DefaultDbTypeWarning(typeName, flags, defaultDbType); +#endif + + return (DbType)defaultDbType; + } + #endregion + + private static readonly SQLiteDbTypeMap _typeNames = GetSQLiteDbTypeMap(); + } + + /// + /// SQLite has very limited types, and is inherently text-based. The first 5 types below represent the sum of all types SQLite + /// understands. The DateTime extension to the spec is for internal use only. + /// + public enum TypeAffinity + { + /// + /// Not used + /// + Uninitialized = 0, + /// + /// All integers in SQLite default to Int64 + /// + Int64 = 1, + /// + /// All floating point numbers in SQLite default to double + /// + Double = 2, + /// + /// The default data type of SQLite is text + /// + Text = 3, + /// + /// Typically blob types are only seen when returned from a function + /// + Blob = 4, + /// + /// Null types can be returned from functions + /// + Null = 5, + /// + /// Used internally by this provider + /// + DateTime = 10, + /// + /// Used internally by this provider + /// + None = 11, + } + + /// + /// These are the event types associated with the + /// + /// delegate (and its corresponding event) and the + /// class. + /// + public enum SQLiteConnectionEventType + { + /// + /// Not used. + /// + Invalid = -1, + + /// + /// Not used. + /// + Unknown = 0, + + /// + /// The connection is being opened. + /// + Opening = 1, + + /// + /// The connection string has been parsed. + /// + ConnectionString = 2, + + /// + /// The connection was opened. + /// + Opened = 3, + + /// + /// The method was called on the + /// connection. + /// + ChangeDatabase = 4, + + /// + /// A transaction was created using the connection. + /// + NewTransaction = 5, + + /// + /// The connection was enlisted into a transaction. + /// + EnlistTransaction = 6, + + /// + /// A command was created using the connection. + /// + NewCommand = 7, + + /// + /// A data reader was created using the connection. + /// + NewDataReader = 8, + + /// + /// An instance of a derived class has + /// been created to wrap a native resource. + /// + NewCriticalHandle = 9, + + /// + /// The connection is being closed. + /// + Closing = 10, + + /// + /// The connection was closed. + /// + Closed = 11, + + /// + /// A command is being disposed. + /// + DisposingCommand = 12, + + /// + /// A data reader is being disposed. + /// + DisposingDataReader = 13, + + /// + /// A data reader is being closed. + /// + ClosingDataReader = 14, + + /// + /// A native resource was opened (i.e. obtained) from the pool. + /// + OpenedFromPool = 15, + + /// + /// A native resource was closed (i.e. released) to the pool. + /// + ClosedToPool = 16 + } + + /// + /// This implementation of SQLite for ADO.NET can process date/time fields in + /// databases in one of six formats. + /// + /// + /// ISO8601 format is more compatible, readable, fully-processable, but less + /// accurate as it does not provide time down to fractions of a second. + /// JulianDay is the numeric format the SQLite uses internally and is arguably + /// the most compatible with 3rd party tools. It is not readable as text + /// without post-processing. Ticks less compatible with 3rd party tools that + /// query the database, and renders the DateTime field unreadable as text + /// without post-processing. UnixEpoch is more compatible with Unix systems. + /// InvariantCulture allows the configured format for the invariant culture + /// format to be used and is human readable. CurrentCulture allows the + /// configured format for the current culture to be used and is also human + /// readable. + /// + /// The preferred order of choosing a DateTime format is JulianDay, ISO8601, + /// and then Ticks. Ticks is mainly present for legacy code support. + /// + public enum SQLiteDateFormats + { + /// + /// Use the value of DateTime.Ticks. This value is not recommended and is not well supported with LINQ. + /// + Ticks = 0, + /// + /// Use the ISO-8601 format. Uses the "yyyy-MM-dd HH:mm:ss.FFFFFFFK" format for UTC DateTime values and + /// "yyyy-MM-dd HH:mm:ss.FFFFFFF" format for local DateTime values). + /// + ISO8601 = 1, + /// + /// The interval of time in days and fractions of a day since January 1, 4713 BC. + /// + JulianDay = 2, + /// + /// The whole number of seconds since the Unix epoch (January 1, 1970). + /// + UnixEpoch = 3, + /// + /// Any culture-independent string value that the .NET Framework can interpret as a valid DateTime. + /// + InvariantCulture = 4, + /// + /// Any string value that the .NET Framework can interpret as a valid DateTime using the current culture. + /// + CurrentCulture = 5, + /// + /// The default format for this provider. + /// + Default = ISO8601 + } + + /// + /// This enum determines how SQLite treats its journal file. + /// + /// + /// By default SQLite will create and delete the journal file when needed during a transaction. + /// However, for some computers running certain filesystem monitoring tools, the rapid + /// creation and deletion of the journal file can cause those programs to fail, or to interfere with SQLite. + /// + /// If a program or virus scanner is interfering with SQLite's journal file, you may receive errors like "unable to open database file" + /// when starting a transaction. If this is happening, you may want to change the default journal mode to Persist. + /// + public enum SQLiteJournalModeEnum + { + /// + /// The default mode, this causes SQLite to use the existing journaling mode for the database. + /// + Default = -1, + /// + /// SQLite will create and destroy the journal file as-needed. + /// + Delete = 0, + /// + /// When this is set, SQLite will keep the journal file even after a transaction has completed. It's contents will be erased, + /// and the journal re-used as often as needed. If it is deleted, it will be recreated the next time it is needed. + /// + Persist = 1, + /// + /// This option disables the rollback journal entirely. Interrupted transactions or a program crash can cause database + /// corruption in this mode! + /// + Off = 2, + /// + /// SQLite will truncate the journal file to zero-length instead of deleting it. + /// + Truncate = 3, + /// + /// SQLite will store the journal in volatile RAM. This saves disk I/O but at the expense of database safety and integrity. + /// If the application using SQLite crashes in the middle of a transaction when the MEMORY journaling mode is set, then the + /// database file will very likely go corrupt. + /// + Memory = 4, + /// + /// SQLite uses a write-ahead log instead of a rollback journal to implement transactions. The WAL journaling mode is persistent; + /// after being set it stays in effect across multiple database connections and after closing and reopening the database. A database + /// in WAL journaling mode can only be accessed by SQLite version 3.7.0 or later. + /// + Wal = 5 + } + + /// + /// Possible values for the "synchronous" database setting. This setting determines + /// how often the database engine calls the xSync method of the VFS. + /// + internal enum SQLiteSynchronousEnum + { + /// + /// Use the default "synchronous" database setting. Currently, this should be + /// the same as using the FULL mode. + /// + Default = -1, + + /// + /// The database engine continues without syncing as soon as it has handed + /// data off to the operating system. If the application running SQLite + /// crashes, the data will be safe, but the database might become corrupted + /// if the operating system crashes or the computer loses power before that + /// data has been written to the disk surface. + /// + Off = 0, + + /// + /// The database engine will still sync at the most critical moments, but + /// less often than in FULL mode. There is a very small (though non-zero) + /// chance that a power failure at just the wrong time could corrupt the + /// database in NORMAL mode. + /// + Normal = 1, + + /// + /// The database engine will use the xSync method of the VFS to ensure that + /// all content is safely written to the disk surface prior to continuing. + /// This ensures that an operating system crash or power failure will not + /// corrupt the database. FULL synchronous is very safe, but it is also + /// slower. + /// + Full = 2 + } + + /// + /// The requested command execution type. This controls which method of the + /// object will be called. + /// + public enum SQLiteExecuteType + { + /// + /// Do nothing. No method will be called. + /// + None = 0, + + /// + /// The command is not expected to return a result -OR- the result is not + /// needed. The or + /// method + /// will be called. + /// + NonQuery = 1, + + /// + /// The command is expected to return a scalar result -OR- the result should + /// be limited to a scalar result. The + /// or method will + /// be called. + /// + Scalar = 2, + + /// + /// The command is expected to return result. + /// The or + /// method will + /// be called. + /// + Reader = 3, + + /// + /// Use the default command execution type. Using this value is the same + /// as using the value. + /// + Default = NonQuery /* TODO: Good default? */ + } + + /// + /// The action code responsible for the current call into the authorizer. + /// + public enum SQLiteAuthorizerActionCode + { + /// + /// No action is being performed. This value should not be used from + /// external code. + /// + None = -1, + + /// + /// No longer used. + /// + Copy = 0, + + /// + /// An index will be created. The action-specific arguments are the + /// index name and the table name. + /// + /// + CreateIndex = 1, + + /// + /// A table will be created. The action-specific arguments are the + /// table name and a null value. + /// + CreateTable = 2, + + /// + /// A temporary index will be created. The action-specific arguments + /// are the index name and the table name. + /// + CreateTempIndex = 3, + + /// + /// A temporary table will be created. The action-specific arguments + /// are the table name and a null value. + /// + CreateTempTable = 4, + + /// + /// A temporary trigger will be created. The action-specific arguments + /// are the trigger name and the table name. + /// + CreateTempTrigger = 5, + + /// + /// A temporary view will be created. The action-specific arguments are + /// the view name and a null value. + /// + CreateTempView = 6, + + /// + /// A trigger will be created. The action-specific arguments are the + /// trigger name and the table name. + /// + CreateTrigger = 7, + + /// + /// A view will be created. The action-specific arguments are the view + /// name and a null value. + /// + CreateView = 8, + + /// + /// A DELETE statement will be executed. The action-specific arguments + /// are the table name and a null value. + /// + Delete = 9, + + /// + /// An index will be dropped. The action-specific arguments are the + /// index name and the table name. + /// + DropIndex = 10, + + /// + /// A table will be dropped. The action-specific arguments are the tables + /// name and a null value. + /// + DropTable = 11, + + /// + /// A temporary index will be dropped. The action-specific arguments are + /// the index name and the table name. + /// + DropTempIndex = 12, + + /// + /// A temporary table will be dropped. The action-specific arguments are + /// the table name and a null value. + /// + DropTempTable = 13, + + /// + /// A temporary trigger will be dropped. The action-specific arguments + /// are the trigger name and the table name. + /// + DropTempTrigger = 14, + + /// + /// A temporary view will be dropped. The action-specific arguments are + /// the view name and a null value. + /// + DropTempView = 15, + + /// + /// A trigger will be dropped. The action-specific arguments are the + /// trigger name and the table name. + /// + DropTrigger = 16, + + /// + /// A view will be dropped. The action-specific arguments are the view + /// name and a null value. + /// + DropView = 17, + + /// + /// An INSERT statement will be executed. The action-specific arguments + /// are the table name and a null value. + /// + Insert = 18, + + /// + /// A PRAGMA statement will be executed. The action-specific arguments + /// are the name of the PRAGMA and the new value or a null value. + /// + Pragma = 19, + + /// + /// A table column will be read. The action-specific arguments are the + /// table name and the column name. + /// + Read = 20, + + /// + /// A SELECT statement will be executed. The action-specific arguments + /// are both null values. + /// + Select = 21, + + /// + /// A transaction will be started, committed, or rolled back. The + /// action-specific arguments are the name of the operation (BEGIN, + /// COMMIT, or ROLLBACK) and a null value. + /// + Transaction = 22, + + /// + /// An UPDATE statement will be executed. The action-specific arguments + /// are the table name and the column name. + /// + Update = 23, + + /// + /// A database will be attached to the connection. The action-specific + /// arguments are the database file name and a null value. + /// + Attach = 24, + + /// + /// A database will be detached from the connection. The action-specific + /// arguments are the database name and a null value. + /// + Detach = 25, + + /// + /// The schema of a table will be altered. The action-specific arguments + /// are the database name and the table name. + /// + AlterTable = 26, + + /// + /// An index will be deleted and then recreated. The action-specific + /// arguments are the index name and a null value. + /// + Reindex = 27, + + /// + /// A table will be analyzed to gathers statistics about it. The + /// action-specific arguments are the table name and a null value. + /// + Analyze = 28, + + /// + /// A virtual table will be created. The action-specific arguments are + /// the table name and the module name. + /// + CreateVtable = 29, + + /// + /// A virtual table will be dropped. The action-specific arguments are + /// the table name and the module name. + /// + DropVtable = 30, + + /// + /// A SQL function will be called. The action-specific arguments are a + /// null value and the function name. + /// + Function = 31, + + /// + /// A savepoint will be created, released, or rolled back. The + /// action-specific arguments are the name of the operation (BEGIN, + /// RELEASE, or ROLLBACK) and the savepoint name. + /// + Savepoint = 32, + + /// + /// A recursive query will be executed. The action-specific arguments + /// are two null values. + /// + Recursive = 33 + } + + /// + /// The possible return codes for the progress callback. + /// + public enum SQLiteProgressReturnCode /* int */ + { + /// + /// The operation should continue. + /// + Continue = 0, + + /// + /// The operation should be interrupted. + /// + Interrupt = 1 + } + + /// + /// The return code for the current call into the authorizer. + /// + public enum SQLiteAuthorizerReturnCode + { + /// + /// The action will be allowed. + /// + Ok = 0, + + /// + /// The overall action will be disallowed and an error message will be + /// returned from the query preparation method. + /// + Deny = 1, + + /// + /// The specific action will be disallowed; however, the overall action + /// will continue. The exact effects of this return code vary depending + /// on the specific action, please refer to the SQLite core library + /// documentation for futher details. + /// + Ignore = 2 + } + + /// + /// Class used internally to determine the datatype of a column in a resultset + /// + internal sealed class SQLiteType + { + /// + /// The DbType of the column, or DbType.Object if it cannot be determined + /// + internal DbType Type; + /// + /// The affinity of a column, used for expressions or when Type is DbType.Object + /// + internal TypeAffinity Affinity; + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Constructs a default instance of this type. + /// + public SQLiteType() + { + // do nothing. + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Constructs an instance of this type with the specified field values. + /// + /// + /// The type affinity to use for the new instance. + /// + /// + /// The database type to use for the new instance. + /// + public SQLiteType( + TypeAffinity affinity, + DbType type + ) + : this() + { + this.Affinity = affinity; + this.Type = type; + } + } + + ///////////////////////////////////////////////////////////////////////////// + + internal sealed class SQLiteDbTypeMap + : Dictionary + { + #region Private Data + private Dictionary reverse; + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Public Constructors + public SQLiteDbTypeMap() + : base(new TypeNameStringComparer()) + { + reverse = new Dictionary(); + } + + ///////////////////////////////////////////////////////////////////////// + + public SQLiteDbTypeMap( + IEnumerable collection + ) + : this() + { + Add(collection); + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region System.Collections.Generic.Dictionary "Overrides" + public new int Clear() + { + int result = 0; + + if (reverse != null) + { + result += reverse.Count; + reverse.Clear(); + } + + result += base.Count; + base.Clear(); + + return result; + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region SQLiteDbTypeMapping Helper Methods + public void Add( + IEnumerable collection + ) + { + if (collection == null) + throw new ArgumentNullException("collection"); + + foreach (SQLiteDbTypeMapping item in collection) + Add(item); + } + + ///////////////////////////////////////////////////////////////////////// + + public void Add(SQLiteDbTypeMapping item) + { + if (item == null) + throw new ArgumentNullException("item"); + + if (item.typeName == null) + throw new ArgumentException("item type name cannot be null"); + + base.Add(item.typeName, item); + + if (item.primary) + reverse.Add(item.dataType, item); + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region DbType Helper Methods + public bool ContainsKey(DbType key) + { + if (reverse == null) + return false; + + return reverse.ContainsKey(key); + } + + ///////////////////////////////////////////////////////////////////////// + + public bool TryGetValue(DbType key, out SQLiteDbTypeMapping value) + { + if (reverse == null) + { + value = null; + return false; + } + + return reverse.TryGetValue(key, out value); + } + + ///////////////////////////////////////////////////////////////////////// + + public bool Remove(DbType key) + { + if (reverse == null) + return false; + + return reverse.Remove(key); + } + #endregion + } + + ///////////////////////////////////////////////////////////////////////////// + + internal sealed class SQLiteDbTypeMapping + { + internal SQLiteDbTypeMapping( + string newTypeName, + DbType newDataType, + bool newPrimary + ) + { + typeName = newTypeName; + dataType = newDataType; + primary = newPrimary; + } + + internal string typeName; + internal DbType dataType; + internal bool primary; + } + + internal sealed class TypeNameStringComparer : IEqualityComparer, IComparer + { + #region IEqualityComparer Members + public bool Equals( + string left, + string right + ) + { + return String.Equals(left, right, StringComparison.OrdinalIgnoreCase); + } + + /////////////////////////////////////////////////////////////////////////// + + public int GetHashCode( + string value + ) + { + // + // NOTE: The only thing that we must guarantee here, according + // to the MSDN documentation for IEqualityComparer, is + // that for two given strings, if Equals return true then + // the two strings must hash to the same value. + // + if (value != null) + return StringComparer.OrdinalIgnoreCase.GetHashCode(value); + else + throw new ArgumentNullException("value"); + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region IComparer Members + public int Compare( + string x, + string y + ) + { + if ((x == null) && (y == null)) + return 0; + else if (x == null) + return -1; + else if (y == null) + return 1; + else + return x.CompareTo(y); + } + #endregion + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteDataAdapter.cs b/Native.Csharp.Tool/SQLite/SQLiteDataAdapter.cs new file mode 100644 index 00000000..1801c04a --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteDataAdapter.cs @@ -0,0 +1,328 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Data.Common; + using System.ComponentModel; + + /// + /// SQLite implementation of DbDataAdapter. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DefaultEvent("RowUpdated")] + [ToolboxItem("SQLite.Designer.SQLiteDataAdapterToolboxItem, SQLite.Designer, Version=" + SQLite3.DesignerVersion + ", Culture=neutral, PublicKeyToken=db937bc2d44ff139")] + [Designer("Microsoft.VSDesigner.Data.VS.SqlDataAdapterDesigner, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] +#endif + public sealed class SQLiteDataAdapter : DbDataAdapter + { + private bool disposeSelect = true; + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private static object _updatingEventPH = new object(); + private static object _updatedEventPH = new object(); + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// This class is just a shell around the DbDataAdapter. Nothing from + /// DbDataAdapter is overridden here, just a few constructors are defined. + /// + /// + /// Default constructor. + /// + public SQLiteDataAdapter() + { + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Constructs a data adapter using the specified select command. + /// + /// + /// The select command to associate with the adapter. + /// + public SQLiteDataAdapter(SQLiteCommand cmd) + { + SelectCommand = cmd; + disposeSelect = false; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Constructs a data adapter with the supplied select command text and + /// associated with the specified connection. + /// + /// + /// The select command text to associate with the data adapter. + /// + /// + /// The connection to associate with the select command. + /// + public SQLiteDataAdapter(string commandText, SQLiteConnection connection) + { + SelectCommand = new SQLiteCommand(commandText, connection); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Constructs a data adapter with the specified select command text, + /// and using the specified database connection string. + /// + /// + /// The select command text to use to construct a select command. + /// + /// + /// A connection string suitable for passing to a new SQLiteConnection, + /// which is associated with the select command. + /// + public SQLiteDataAdapter( + string commandText, + string connectionString + ) + : this(commandText, connectionString, false) + { + // do nothing. + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Constructs a data adapter with the specified select command text, + /// and using the specified database connection string. + /// + /// + /// The select command text to use to construct a select command. + /// + /// + /// A connection string suitable for passing to a new SQLiteConnection, + /// which is associated with the select command. + /// + /// + /// Non-zero to parse the connection string using the built-in (i.e. + /// framework provided) parser when opening the connection. + /// + public SQLiteDataAdapter( + string commandText, + string connectionString, + bool parseViaFramework + ) + { + SQLiteConnection cnn = new SQLiteConnection( + connectionString, parseViaFramework); + + SelectCommand = new SQLiteCommand(commandText, cnn); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteDataAdapter).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Cleans up resources (native and managed) associated with the current instance. + /// + /// + /// Zero when being disposed via garbage collection; otherwise, non-zero. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (disposeSelect && (SelectCommand != null)) + { + SelectCommand.Dispose(); + SelectCommand = null; + } + + if (InsertCommand != null) + { + InsertCommand.Dispose(); + InsertCommand = null; + } + + if (UpdateCommand != null) + { + UpdateCommand.Dispose(); + UpdateCommand = null; + } + + if (DeleteCommand != null) + { + DeleteCommand.Dispose(); + DeleteCommand = null; + } + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Row updating event handler + /// + public event EventHandler RowUpdating + { + add + { + CheckDisposed(); + +#if !PLATFORM_COMPACTFRAMEWORK + EventHandler previous = (EventHandler)base.Events[_updatingEventPH]; + if ((previous != null) && (value.Target is DbCommandBuilder)) + { + EventHandler handler = (EventHandler)FindBuilder(previous); + if (handler != null) + { + base.Events.RemoveHandler(_updatingEventPH, handler); + } + } +#endif + base.Events.AddHandler(_updatingEventPH, value); + } + remove { CheckDisposed(); base.Events.RemoveHandler(_updatingEventPH, value); } + } + +#if !PLATFORM_COMPACTFRAMEWORK + internal static Delegate FindBuilder(MulticastDelegate mcd) + { + if (mcd != null) + { + Delegate[] invocationList = mcd.GetInvocationList(); + for (int i = 0; i < invocationList.Length; i++) + { + if (invocationList[i].Target is DbCommandBuilder) + { + return invocationList[i]; + } + } + } + return null; + } +#endif + + /// + /// Row updated event handler + /// + public event EventHandler RowUpdated + { + add { CheckDisposed(); base.Events.AddHandler(_updatedEventPH, value); } + remove { CheckDisposed(); base.Events.RemoveHandler(_updatedEventPH, value); } + } + + /// + /// Raised by the underlying DbDataAdapter when a row is being updated + /// + /// The event's specifics + protected override void OnRowUpdating(RowUpdatingEventArgs value) + { + EventHandler handler = base.Events[_updatingEventPH] as EventHandler; + + if (handler != null) + handler(this, value); + } + + /// + /// Raised by DbDataAdapter after a row is updated + /// + /// The event's specifics + protected override void OnRowUpdated(RowUpdatedEventArgs value) + { + EventHandler handler = base.Events[_updatedEventPH] as EventHandler; + + if (handler != null) + handler(this, value); + } + + /// + /// Gets/sets the select command for this DataAdapter + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DefaultValue((string)null), Editor("Microsoft.VSDesigner.Data.Design.DBCommandEditor, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] +#endif + public new SQLiteCommand SelectCommand + { + get { CheckDisposed(); return (SQLiteCommand)base.SelectCommand; } + set { CheckDisposed(); base.SelectCommand = value; } + } + + /// + /// Gets/sets the insert command for this DataAdapter + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DefaultValue((string)null), Editor("Microsoft.VSDesigner.Data.Design.DBCommandEditor, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] +#endif + public new SQLiteCommand InsertCommand + { + get { CheckDisposed(); return (SQLiteCommand)base.InsertCommand; } + set { CheckDisposed(); base.InsertCommand = value; } + } + + /// + /// Gets/sets the update command for this DataAdapter + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DefaultValue((string)null), Editor("Microsoft.VSDesigner.Data.Design.DBCommandEditor, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] +#endif + public new SQLiteCommand UpdateCommand + { + get { CheckDisposed(); return (SQLiteCommand)base.UpdateCommand; } + set { CheckDisposed(); base.UpdateCommand = value; } + } + + /// + /// Gets/sets the delete command for this DataAdapter + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DefaultValue((string)null), Editor("Microsoft.VSDesigner.Data.Design.DBCommandEditor, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] +#endif + public new SQLiteCommand DeleteCommand + { + get { CheckDisposed(); return (SQLiteCommand)base.DeleteCommand; } + set { CheckDisposed(); base.DeleteCommand = value; } + } + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteDataReader.cs b/Native.Csharp.Tool/SQLite/SQLiteDataReader.cs new file mode 100644 index 00000000..68941997 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteDataReader.cs @@ -0,0 +1,2165 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Data; + using System.Data.Common; + using System.Globalization; + + /// + /// SQLite implementation of DbDataReader. + /// + public sealed class SQLiteDataReader : DbDataReader + { + /// + /// Underlying command this reader is attached to + /// + private SQLiteCommand _command; + /// + /// The flags pertaining to the associated connection (via the command). + /// + private SQLiteConnectionFlags _flags; + /// + /// Index of the current statement in the command being processed + /// + private int _activeStatementIndex; + /// + /// Current statement being Read() + /// + private SQLiteStatement _activeStatement; + /// + /// State of the current statement being processed. + /// -1 = First Step() executed, so the first Read() will be ignored + /// 0 = Actively reading + /// 1 = Finished reading + /// 2 = Non-row-returning statement, no records + /// + private int _readingState; + /// + /// Number of records affected by the insert/update statements executed on the command + /// + private int _rowsAffected; + /// + /// Count of fields (columns) in the row-returning statement currently being processed + /// + private int _fieldCount; + /// + /// The number of calls to Step() that have returned true (i.e. the number of rows that + /// have been read in the current result set). + /// + private int _stepCount; + /// + /// Maps the field (column) names to their corresponding indexes within the results. + /// + private Dictionary _fieldIndexes; + /// + /// Datatypes of active fields (columns) in the current statement, used for type-restricting data + /// + private SQLiteType[] _fieldTypeArray; + + /// + /// The behavior of the datareader + /// + private CommandBehavior _commandBehavior; + + /// + /// If set, then dispose of the command object when the reader is finished + /// + internal bool _disposeCommand; + + /// + /// If set, then raise an exception when the object is accessed after being disposed. + /// + internal bool _throwOnDisposed; + + /// + /// An array of rowid's for the active statement if CommandBehavior.KeyInfo is specified + /// + private SQLiteKeyReader _keyInfo; + + /// + /// Matches the version of the connection. + /// + internal int _version; + + /// + /// The "stub" (i.e. placeholder) base schema name to use when returning + /// column schema information. Matches the base schema name used by the + /// associated connection. + /// + private string _baseSchemaName; + + /// + /// Internal constructor, initializes the datareader and sets up to begin executing statements + /// + /// The SQLiteCommand this data reader is for + /// The expected behavior of the data reader + internal SQLiteDataReader(SQLiteCommand cmd, CommandBehavior behave) + { + _throwOnDisposed = true; + _command = cmd; + _version = _command.Connection._version; + _baseSchemaName = _command.Connection._baseSchemaName; + + _commandBehavior = behave; + _activeStatementIndex = -1; + _rowsAffected = -1; + + RefreshFlags(); + + SQLiteConnection.OnChanged(GetConnection(this), + new ConnectionEventArgs(SQLiteConnectionEventType.NewDataReader, + null, null, _command, this, null, null, new object[] { behave })); + + if (_command != null) + NextResult(); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed && _throwOnDisposed) + throw new ObjectDisposedException(typeof(SQLiteDataReader).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Dispose of all resources used by this datareader. + /// + /// + protected override void Dispose(bool disposing) + { + SQLiteConnection.OnChanged(GetConnection(this), + new ConnectionEventArgs(SQLiteConnectionEventType.DisposingDataReader, + null, null, _command, this, null, null, new object[] { disposing, + disposed, _commandBehavior, _readingState, _rowsAffected, _stepCount, + _fieldCount, _disposeCommand, _throwOnDisposed })); + + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + // + // NOTE: Fix for ticket [e1b2e0f769], do NOT throw exceptions + // while we are being disposed. + // + _throwOnDisposed = false; + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + internal void Cancel() + { + _version = 0; + } + + /// + /// Closes the datareader, potentially closing the connection as well if CommandBehavior.CloseConnection was specified. + /// + public override void Close() + { + CheckDisposed(); + + SQLiteConnection.OnChanged(GetConnection(this), + new ConnectionEventArgs(SQLiteConnectionEventType.ClosingDataReader, + null, null, _command, this, null, null, new object[] { _commandBehavior, + _readingState, _rowsAffected, _stepCount, _fieldCount, _disposeCommand, + _throwOnDisposed })); + + try + { + if (_command != null) + { + try + { + try + { + // Make sure we've not been canceled + if (_version != 0) + { + try + { + while (NextResult()) + { + } + } + catch(SQLiteException) + { + } + } + _command.ResetDataReader(); + } + finally + { + // If the datareader's behavior includes closing the connection, then do so here. + if ((_commandBehavior & CommandBehavior.CloseConnection) != 0 && _command.Connection != null) + _command.Connection.Close(); + } + } + finally + { + if (_disposeCommand) + _command.Dispose(); + } + } + + _command = null; + _activeStatement = null; + _fieldIndexes = null; + _fieldTypeArray = null; + } + finally + { + if (_keyInfo != null) + { + _keyInfo.Dispose(); + _keyInfo = null; + } + } + } + + /// + /// Throw an error if the datareader is closed + /// + private void CheckClosed() + { + if (!_throwOnDisposed) + return; + + if (_command == null) + throw new InvalidOperationException("DataReader has been closed"); + + if (_version == 0) + throw new SQLiteException("Execution was aborted by the user"); + + SQLiteConnection connection = _command.Connection; + + if (connection._version != _version || connection.State != ConnectionState.Open) + throw new InvalidOperationException("Connection was closed, statement was terminated"); + } + + /// + /// Throw an error if a row is not loaded + /// + private void CheckValidRow() + { + if (_readingState != 0) + throw new InvalidOperationException("No current row"); + } + + /// + /// Enumerator support + /// + /// Returns a DbEnumerator object. + public override Collections.IEnumerator GetEnumerator() + { + CheckDisposed(); + return new DbEnumerator(this, ((_commandBehavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection)); + } + + /// + /// Not implemented. Returns 0 + /// + public override int Depth + { + get + { + CheckDisposed(); + CheckClosed(); + return 0; + } + } + + /// + /// Returns the number of columns in the current resultset + /// + public override int FieldCount + { + get + { + CheckDisposed(); + CheckClosed(); + + if (_keyInfo == null) + return _fieldCount; + + return _fieldCount + _keyInfo.Count; + } + } + + /// + /// Forces the connection flags cached by this data reader to be refreshed + /// from the underlying connection. + /// + public void RefreshFlags() + { + CheckDisposed(); + + _flags = SQLiteCommand.GetFlags(_command); + } + + /// + /// Returns the number of rows seen so far in the current result set. + /// + public int StepCount + { + get + { + CheckDisposed(); + CheckClosed(); + + return _stepCount; + } + } + + private int PrivateVisibleFieldCount + { + get { return _fieldCount; } + } + + /// + /// Returns the number of visible fields in the current resultset + /// + public override int VisibleFieldCount + { + get + { + CheckDisposed(); + CheckClosed(); + return PrivateVisibleFieldCount; + } + } + + /// + /// This method is used to make sure the result set is open and a row is currently available. + /// + private void VerifyForGet() + { + CheckClosed(); + CheckValidRow(); + } + + /// + /// SQLite is inherently un-typed. All datatypes in SQLite are natively strings. The definition of the columns of a table + /// and the affinity of returned types are all we have to go on to type-restrict data in the reader. + /// + /// This function attempts to verify that the type of data being requested of a column matches the datatype of the column. In + /// the case of columns that are not backed into a table definition, we attempt to match up the affinity of a column (int, double, string or blob) + /// to a set of known types that closely match that affinity. It's not an exact science, but its the best we can do. + /// + /// + /// This function throws an InvalidTypeCast() exception if the requested type doesn't match the column's definition or affinity. + /// + /// The index of the column to type-check + /// The type we want to get out of the column + private TypeAffinity VerifyType(int i, DbType typ) + { + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.NoVerifyTypeAffinity)) + return TypeAffinity.None; + + TypeAffinity affinity = GetSQLiteType(_flags, i).Affinity; + + switch (affinity) + { + case TypeAffinity.Int64: + if (typ == DbType.Int64) return affinity; + if (typ == DbType.Int32) return affinity; + if (typ == DbType.Int16) return affinity; + if (typ == DbType.Byte) return affinity; + if (typ == DbType.SByte) return affinity; + if (typ == DbType.Boolean) return affinity; + if (typ == DbType.DateTime) return affinity; + if (typ == DbType.Double) return affinity; + if (typ == DbType.Single) return affinity; + if (typ == DbType.Decimal) return affinity; + break; + case TypeAffinity.Double: + if (typ == DbType.Double) return affinity; + if (typ == DbType.Single) return affinity; + if (typ == DbType.Decimal) return affinity; + if (typ == DbType.DateTime) return affinity; + break; + case TypeAffinity.Text: + if (typ == DbType.String) return affinity; + if (typ == DbType.Guid) return affinity; + if (typ == DbType.DateTime) return affinity; + if (typ == DbType.Decimal) return affinity; + break; + case TypeAffinity.Blob: + if (typ == DbType.Guid) return affinity; + if (typ == DbType.Binary) return affinity; + if (typ == DbType.String) return affinity; + break; + } + + throw new InvalidCastException(); + } + + /// + /// Invokes the data reader value callback configured for the database + /// type name associated with the specified column. If no data reader + /// value callback is available for the database type name, do nothing. + /// + /// + /// The index of the column being read. + /// + /// + /// The extra event data to pass into the callback. + /// + /// + /// Non-zero if the default handling for the data reader call should be + /// skipped. If this is set to non-zero and the necessary return value + /// is unavailable or unsuitable, an exception will be thrown. + /// + private void InvokeReadValueCallback( + int index, + SQLiteReadEventArgs eventArgs, + out bool complete + ) + { + complete = false; + SQLiteConnectionFlags oldFlags = _flags; + _flags &= ~SQLiteConnectionFlags.UseConnectionReadValueCallbacks; + + try + { + string typeName = GetDataTypeName(index); + + if (typeName == null) + return; + + SQLiteConnection connection = GetConnection(this); + + if (connection == null) + return; + + SQLiteTypeCallbacks callbacks; + + if (!connection.TryGetTypeCallbacks(typeName, out callbacks) || + (callbacks == null)) + { + return; + } + + SQLiteReadValueCallback callback = callbacks.ReadValueCallback; + + if (callback == null) + return; + + object userData = callbacks.ReadValueUserData; + + callback( + _activeStatement._sql, this, oldFlags, eventArgs, typeName, + index, userData, out complete); /* throw */ + } + finally + { + _flags |= SQLiteConnectionFlags.UseConnectionReadValueCallbacks; + } + } + + /// + /// Attempts to query the integer identifier for the current row. This + /// will not work for tables that were created WITHOUT ROWID -OR- if the + /// query does not include the "rowid" column or one of its aliases -OR- + /// if the was not created with the + /// flag. + /// + /// + /// The index of the BLOB column. + /// + /// + /// The integer identifier for the current row -OR- null if it could not + /// be determined. + /// + internal long? GetRowId( + int i + ) + { + // CheckDisposed(); + VerifyForGet(); + + if (_keyInfo == null) + return null; + + string databaseName = GetDatabaseName(i); + string tableName = GetTableName(i); + int iRowId = _keyInfo.GetRowIdIndex(databaseName, tableName); + + if (iRowId != -1) + return GetInt64(iRowId); + + return _keyInfo.GetRowId(databaseName, tableName); + } + + /// + /// Retrieves the column as a object. + /// This will not work for tables that were created WITHOUT ROWID + /// -OR- if the query does not include the "rowid" column or one + /// of its aliases -OR- if the was + /// not created with the + /// flag. + /// + /// The index of the column. + /// + /// Non-zero to open the blob object for read-only access. + /// + /// A new object. + public SQLiteBlob GetBlob(int i, bool readOnly) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetBlob", new SQLiteReadBlobEventArgs(readOnly), value), + out complete); + + if (complete) + return (SQLiteBlob)value.BlobValue; + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetBlob(i - PrivateVisibleFieldCount, readOnly); + + return SQLiteBlob.Create(this, i, readOnly); + } + + /// + /// Retrieves the column as a boolean value + /// + /// The index of the column. + /// bool + public override bool GetBoolean(int i) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetBoolean", null, value), out complete); + + if (complete) + { + if (value.BooleanValue == null) + throw new SQLiteException("missing boolean return value"); + + return (bool)value.BooleanValue; + } + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetBoolean(i - PrivateVisibleFieldCount); + + VerifyType(i, DbType.Boolean); + return Convert.ToBoolean(GetValue(i), CultureInfo.CurrentCulture); + } + + /// + /// Retrieves the column as a single byte value + /// + /// The index of the column. + /// byte + public override byte GetByte(int i) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetByte", null, value), out complete); + + if (complete) + { + if (value.ByteValue == null) + throw new SQLiteException("missing byte return value"); + + return (byte)value.ByteValue; + } + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetByte(i - PrivateVisibleFieldCount); + + VerifyType(i, DbType.Byte); + return _activeStatement._sql.GetByte(_activeStatement, i); + } + + /// + /// Retrieves a column as an array of bytes (blob) + /// + /// The index of the column. + /// The zero-based index of where to begin reading the data + /// The buffer to write the bytes into + /// The zero-based index of where to begin writing into the array + /// The number of bytes to retrieve + /// The actual number of bytes written into the array + /// + /// To determine the number of bytes in the column, pass a null value for the buffer. The total length will be returned. + /// + public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteReadArrayEventArgs eventArgs = new SQLiteReadArrayEventArgs( + fieldOffset, buffer, bufferoffset, length); + + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetBytes", eventArgs, value), out complete); + + if (complete) + { + byte[] bytes = value.BytesValue; + + if (bytes != null) + { +#if !PLATFORM_COMPACTFRAMEWORK + Array.Copy(bytes, /* throw */ + eventArgs.DataOffset, eventArgs.ByteBuffer, + eventArgs.BufferOffset, eventArgs.Length); +#else + Array.Copy(bytes, /* throw */ + (int)eventArgs.DataOffset, eventArgs.ByteBuffer, + eventArgs.BufferOffset, eventArgs.Length); +#endif + + return eventArgs.Length; + } + else + { + return -1; + } + } + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetBytes(i - PrivateVisibleFieldCount, fieldOffset, buffer, bufferoffset, length); + + VerifyType(i, DbType.Binary); + return _activeStatement._sql.GetBytes(_activeStatement, i, (int)fieldOffset, buffer, bufferoffset, length); + } + + /// + /// Returns the column as a single character + /// + /// The index of the column. + /// char + public override char GetChar(int i) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetChar", null, value), out complete); + + if (complete) + { + if (value.CharValue == null) + throw new SQLiteException("missing character return value"); + + return (char)value.CharValue; + } + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetChar(i - PrivateVisibleFieldCount); + + VerifyType(i, DbType.SByte); + return _activeStatement._sql.GetChar(_activeStatement, i); + } + + /// + /// Retrieves a column as an array of chars (blob) + /// + /// The index of the column. + /// The zero-based index of where to begin reading the data + /// The buffer to write the characters into + /// The zero-based index of where to begin writing into the array + /// The number of bytes to retrieve + /// The actual number of characters written into the array + /// + /// To determine the number of characters in the column, pass a null value for the buffer. The total length will be returned. + /// + public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteReadArrayEventArgs eventArgs = new SQLiteReadArrayEventArgs( + fieldoffset, buffer, bufferoffset, length); + + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetChars", eventArgs, value), out complete); + + if (complete) + { + char[] chars = value.CharsValue; + + if (chars != null) + { +#if !PLATFORM_COMPACTFRAMEWORK + Array.Copy(chars, /* throw */ + eventArgs.DataOffset, eventArgs.CharBuffer, + eventArgs.BufferOffset, eventArgs.Length); +#else + Array.Copy(chars, /* throw */ + (int)eventArgs.DataOffset, eventArgs.CharBuffer, + eventArgs.BufferOffset, eventArgs.Length); +#endif + + return eventArgs.Length; + } + else + { + return -1; + } + } + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetChars(i - PrivateVisibleFieldCount, fieldoffset, buffer, bufferoffset, length); + + if (!HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.NoVerifyTextAffinity)) + VerifyType(i, DbType.String); + + return _activeStatement._sql.GetChars(_activeStatement, i, (int)fieldoffset, buffer, bufferoffset, length); + } + + /// + /// Retrieves the name of the back-end datatype of the column + /// + /// The index of the column. + /// string + public override string GetDataTypeName(int i) + { + CheckDisposed(); + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetDataTypeName(i - PrivateVisibleFieldCount); + + TypeAffinity affin = TypeAffinity.Uninitialized; + return _activeStatement._sql.ColumnType(_activeStatement, i, ref affin); + } + + /// + /// Retrieve the column as a date/time value + /// + /// The index of the column. + /// DateTime + public override DateTime GetDateTime(int i) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetDateTime", null, value), out complete); + + if (complete) + { + if (value.DateTimeValue == null) + throw new SQLiteException("missing date/time return value"); + + return (DateTime)value.DateTimeValue; + } + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetDateTime(i - PrivateVisibleFieldCount); + + VerifyType(i, DbType.DateTime); + return _activeStatement._sql.GetDateTime(_activeStatement, i); + } + + /// + /// Retrieve the column as a decimal value + /// + /// The index of the column. + /// decimal + public override decimal GetDecimal(int i) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetDecimal", null, value), out complete); + + if (complete) + { + if (value.DecimalValue == null) + throw new SQLiteException("missing decimal return value"); + + return (decimal)value.DecimalValue; + } + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetDecimal(i - PrivateVisibleFieldCount); + + VerifyType(i, DbType.Decimal); + + CultureInfo cultureInfo = CultureInfo.CurrentCulture; + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.GetInvariantDecimal)) + cultureInfo = CultureInfo.InvariantCulture; + + return Decimal.Parse(_activeStatement._sql.GetText(_activeStatement, i), NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingSign, cultureInfo); + } + + /// + /// Returns the column as a double + /// + /// The index of the column. + /// double + public override double GetDouble(int i) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetDouble", null, value), out complete); + + if (complete) + { + if (value.DoubleValue == null) + throw new SQLiteException("missing double return value"); + + return (double)value.DoubleValue; + } + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetDouble(i - PrivateVisibleFieldCount); + + VerifyType(i, DbType.Double); + return _activeStatement._sql.GetDouble(_activeStatement, i); + } + + /// + /// Determines and returns the of the + /// specified column. + /// + /// + /// The index of the column. + /// + /// + /// The associated with the specified + /// column, if any. + /// + public TypeAffinity GetFieldAffinity(int i) + { + CheckDisposed(); + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetFieldAffinity(i - PrivateVisibleFieldCount); + + return GetSQLiteType(_flags, i).Affinity; + } + + /// + /// Returns the .NET type of a given column + /// + /// The index of the column. + /// Type + public override Type GetFieldType(int i) + { + CheckDisposed(); + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetFieldType(i - PrivateVisibleFieldCount); + + return SQLiteConvert.SQLiteTypeToType(GetSQLiteType(_flags, i)); + } + + /// + /// Returns a column as a float value + /// + /// The index of the column. + /// float + public override float GetFloat(int i) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetFloat", null, value), out complete); + + if (complete) + { + if (value.FloatValue == null) + throw new SQLiteException("missing float return value"); + + return (float)value.FloatValue; + } + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetFloat(i - PrivateVisibleFieldCount); + + VerifyType(i, DbType.Single); + return Convert.ToSingle(_activeStatement._sql.GetDouble(_activeStatement, i)); + } + + /// + /// Returns the column as a Guid + /// + /// The index of the column. + /// Guid + public override Guid GetGuid(int i) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetGuid", null, value), out complete); + + if (complete) + { + if (value.GuidValue == null) + throw new SQLiteException("missing guid return value"); + + return (Guid)value.GuidValue; + } + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetGuid(i - PrivateVisibleFieldCount); + + TypeAffinity affinity = VerifyType(i, DbType.Guid); + if (affinity == TypeAffinity.Blob) + { + byte[] buffer = new byte[16]; + _activeStatement._sql.GetBytes(_activeStatement, i, 0, buffer, 0, 16); + return new Guid(buffer); + } + else + return new Guid(_activeStatement._sql.GetText(_activeStatement, i)); + } + + /// + /// Returns the column as a short + /// + /// The index of the column. + /// Int16 + public override Int16 GetInt16(int i) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetInt16", null, value), out complete); + + if (complete) + { + if (value.Int16Value == null) + throw new SQLiteException("missing int16 return value"); + + return (Int16)value.Int16Value; + } + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetInt16(i - PrivateVisibleFieldCount); + + VerifyType(i, DbType.Int16); + return _activeStatement._sql.GetInt16(_activeStatement, i); + } + + /// + /// Retrieves the column as an int + /// + /// The index of the column. + /// Int32 + public override Int32 GetInt32(int i) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetInt32", null, value), out complete); + + if (complete) + { + if (value.Int32Value == null) + throw new SQLiteException("missing int32 return value"); + + return (Int32)value.Int32Value; + } + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetInt32(i - PrivateVisibleFieldCount); + + VerifyType(i, DbType.Int32); + return _activeStatement._sql.GetInt32(_activeStatement, i); + } + + /// + /// Retrieves the column as a long + /// + /// The index of the column. + /// Int64 + public override Int64 GetInt64(int i) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetInt64", null, value), out complete); + + if (complete) + { + if (value.Int64Value == null) + throw new SQLiteException("missing int64 return value"); + + return (Int64)value.Int64Value; + } + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetInt64(i - PrivateVisibleFieldCount); + + VerifyType(i, DbType.Int64); + return _activeStatement._sql.GetInt64(_activeStatement, i); + } + + /// + /// Retrieves the name of the column + /// + /// The index of the column. + /// string + public override string GetName(int i) + { + CheckDisposed(); + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetName(i - PrivateVisibleFieldCount); + + return _activeStatement._sql.ColumnName(_activeStatement, i); + } + + /// + /// Returns the name of the database associated with the specified column. + /// + /// The index of the column. + /// string + public string GetDatabaseName(int i) + { + CheckDisposed(); + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetDatabaseName(i - PrivateVisibleFieldCount); + + return _activeStatement._sql.ColumnDatabaseName(_activeStatement, i); + } + + /// + /// Returns the name of the table associated with the specified column. + /// + /// The index of the column. + /// string + public string GetTableName(int i) + { + CheckDisposed(); + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetTableName(i - PrivateVisibleFieldCount); + + return _activeStatement._sql.ColumnTableName(_activeStatement, i); + } + + /// + /// Returns the original name of the specified column. + /// + /// The index of the column. + /// string + public string GetOriginalName(int i) + { + CheckDisposed(); + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetName(i - PrivateVisibleFieldCount); + + return _activeStatement._sql.ColumnOriginalName(_activeStatement, i); + } + + /// + /// Retrieves the i of a column, given its name + /// + /// The name of the column to retrieve + /// The int i of the column + public override int GetOrdinal(string name) + { + CheckDisposed(); + + if (_throwOnDisposed) SQLiteCommand.Check(_command); + + // + // NOTE: First, check if the column name cache has been initialized yet. + // If not, do it now. + // + if (_fieldIndexes == null) + { + _fieldIndexes = new Dictionary( + StringComparer.OrdinalIgnoreCase); + } + + // + // NOTE: Next, see if the index for the requested column name has been + // cached already. If so, return the cached value. Otherwise, + // lookup the value and then cache the result for future use. + // + int r; + + if (!_fieldIndexes.TryGetValue(name, out r)) + { + r = _activeStatement._sql.ColumnIndex(_activeStatement, name); + + if (r == -1 && _keyInfo != null) + { + r = _keyInfo.GetOrdinal(name); + if (r > -1) r += PrivateVisibleFieldCount; + } + + _fieldIndexes.Add(name, r); + } + + if (r == -1 && HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.StrictConformance)) + { + throw new IndexOutOfRangeException(); + } + + return r; + } + + /// + /// Schema information in SQLite is difficult to map into .NET conventions, so a lot of work must be done + /// to gather the necessary information so it can be represented in an ADO.NET manner. + /// + /// Returns a DataTable containing the schema information for the active SELECT statement being processed. + public override DataTable GetSchemaTable() + { + CheckDisposed(); + return GetSchemaTable(true, false); + } + + /////////////////////////////////////////////////////////////////////////// + + #region ColumnParent Class + private sealed class ColumnParent : IEqualityComparer + { + #region Public Fields + public string DatabaseName; + public string TableName; + public string ColumnName; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + public ColumnParent() + { + // do nothing. + } + + /////////////////////////////////////////////////////////////////////// + + public ColumnParent( + string databaseName, + string tableName, + string columnName + ) + : this() + { + this.DatabaseName = databaseName; + this.TableName = tableName; + this.ColumnName = columnName; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IEqualityComparer Members + public bool Equals(ColumnParent x, ColumnParent y) + { + if ((x == null) && (y == null)) + { + return true; + } + else if ((x == null) || (y == null)) + { + return false; + } + else + { + if (!String.Equals(x.DatabaseName, y.DatabaseName, + StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (!String.Equals(x.TableName, y.TableName, + StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (!String.Equals(x.ColumnName, y.ColumnName, + StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return true; + } + } + + /////////////////////////////////////////////////////////////////////// + + public int GetHashCode(ColumnParent obj) + { + int result = 0; + + if ((obj != null) && (obj.DatabaseName != null)) + result ^= obj.DatabaseName.GetHashCode(); + + if ((obj != null) && (obj.TableName != null)) + result ^= obj.TableName.GetHashCode(); + + if ((obj != null) && (obj.ColumnName != null)) + result ^= obj.ColumnName.GetHashCode(); + + return result; + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + private static void GetStatementColumnParents( + SQLiteBase sql, + SQLiteStatement stmt, + int fieldCount, + ref Dictionary> parentToColumns, + ref Dictionary columnToParent + ) + { + if (parentToColumns == null) + parentToColumns = new Dictionary>( + new ColumnParent()); + + if (columnToParent == null) + columnToParent = new Dictionary(); + + for (int n = 0; n < fieldCount; n++) + { + string databaseName = sql.ColumnDatabaseName(stmt, n); + string tableName = sql.ColumnTableName(stmt, n); + string columnName = sql.ColumnOriginalName(stmt, n); + + ColumnParent key = new ColumnParent(databaseName, tableName, null); + ColumnParent value = new ColumnParent(databaseName, tableName, columnName); + + List indexList; + + if (!parentToColumns.TryGetValue(key, out indexList)) + parentToColumns.Add(key, new List(new int[] { n })); + else if (indexList != null) + indexList.Add(n); + else + parentToColumns[key] = new List(new int[] { n }); + + columnToParent.Add(n, value); + } + } + + /////////////////////////////////////////////////////////////////////////// + + private static int CountParents( + Dictionary> parentToColumns + ) + { + int result = 0; + + if (parentToColumns != null) + { + foreach (ColumnParent key in parentToColumns.Keys) + { + if (key == null) + continue; + + string tableName = key.TableName; + + if (String.IsNullOrEmpty(tableName)) + continue; + + result++; + } + } + + return result; + } + + /////////////////////////////////////////////////////////////////////////// + + internal DataTable GetSchemaTable(bool wantUniqueInfo, bool wantDefaultValue) + { + CheckClosed(); + if (_throwOnDisposed) SQLiteCommand.Check(_command); + + // + // BUGFIX: We need to quickly scan all the fields in the current + // "result set" to see how many distinct tables are actually + // involved. This information is necessary so that some + // intelligent decisions can be made when constructing the + // metadata below. For example, we need to be very careful + // about flagging a particular column as "unique" just + // because it was in its original underlying database table + // if there are now multiple tables involved in the + // "result set". See ticket [7e3fa93744] for more detailed + // information. + // + Dictionary> parentToColumns = null; + Dictionary columnToParent = null; + SQLiteBase sql = _command.Connection._sql; + + GetStatementColumnParents( + sql, _activeStatement, _fieldCount, + ref parentToColumns, ref columnToParent); + + DataTable tbl = new DataTable("SchemaTable"); + DataTable tblIndexes = null; + DataTable tblIndexColumns; + DataRow row; + string temp; + string strCatalog = String.Empty; + string strTable = String.Empty; + string strColumn = String.Empty; + + tbl.Locale = CultureInfo.InvariantCulture; + tbl.Columns.Add(SchemaTableColumn.ColumnName, typeof(String)); + tbl.Columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int)); + tbl.Columns.Add(SchemaTableColumn.ColumnSize, typeof(int)); + tbl.Columns.Add(SchemaTableColumn.NumericPrecision, typeof(int)); + tbl.Columns.Add(SchemaTableColumn.NumericScale, typeof(int)); + tbl.Columns.Add(SchemaTableColumn.IsUnique, typeof(Boolean)); + tbl.Columns.Add(SchemaTableColumn.IsKey, typeof(Boolean)); + tbl.Columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(string)); + tbl.Columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(String)); + tbl.Columns.Add(SchemaTableColumn.BaseColumnName, typeof(String)); + tbl.Columns.Add(SchemaTableColumn.BaseSchemaName, typeof(String)); + tbl.Columns.Add(SchemaTableColumn.BaseTableName, typeof(String)); + tbl.Columns.Add(SchemaTableColumn.DataType, typeof(Type)); + tbl.Columns.Add(SchemaTableColumn.AllowDBNull, typeof(Boolean)); + tbl.Columns.Add(SchemaTableColumn.ProviderType, typeof(int)); + tbl.Columns.Add(SchemaTableColumn.IsAliased, typeof(Boolean)); + tbl.Columns.Add(SchemaTableColumn.IsExpression, typeof(Boolean)); + tbl.Columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(Boolean)); + tbl.Columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(Boolean)); + tbl.Columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(Boolean)); + tbl.Columns.Add(SchemaTableColumn.IsLong, typeof(Boolean)); + tbl.Columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(Boolean)); + tbl.Columns.Add(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(Type)); + tbl.Columns.Add(SchemaTableOptionalColumn.DefaultValue, typeof(object)); + tbl.Columns.Add("DataTypeName", typeof(string)); + tbl.Columns.Add("CollationType", typeof(string)); + tbl.BeginLoadData(); + + for (int n = 0; n < _fieldCount; n++) + { + SQLiteType sqlType = GetSQLiteType(_flags, n); + + row = tbl.NewRow(); + + DbType typ = sqlType.Type; + + // Default settings for the column + row[SchemaTableColumn.ColumnName] = GetName(n); + row[SchemaTableColumn.ColumnOrdinal] = n; + row[SchemaTableColumn.ColumnSize] = SQLiteConvert.DbTypeToColumnSize(typ); + row[SchemaTableColumn.NumericPrecision] = SQLiteConvert.DbTypeToNumericPrecision(typ); + row[SchemaTableColumn.NumericScale] = SQLiteConvert.DbTypeToNumericScale(typ); + row[SchemaTableColumn.ProviderType] = sqlType.Type; + row[SchemaTableColumn.IsLong] = false; + row[SchemaTableColumn.AllowDBNull] = true; + row[SchemaTableOptionalColumn.IsReadOnly] = false; + row[SchemaTableOptionalColumn.IsRowVersion] = false; + row[SchemaTableColumn.IsUnique] = false; + row[SchemaTableColumn.IsKey] = false; + row[SchemaTableOptionalColumn.IsAutoIncrement] = false; + row[SchemaTableColumn.DataType] = GetFieldType(n); + row[SchemaTableOptionalColumn.IsHidden] = false; + row[SchemaTableColumn.BaseSchemaName] = _baseSchemaName; + + strColumn = columnToParent[n].ColumnName; + if (String.IsNullOrEmpty(strColumn) == false) row[SchemaTableColumn.BaseColumnName] = strColumn; + + row[SchemaTableColumn.IsExpression] = String.IsNullOrEmpty(strColumn); + row[SchemaTableColumn.IsAliased] = (String.Compare(GetName(n), strColumn, StringComparison.OrdinalIgnoreCase) != 0); + + temp = columnToParent[n].TableName; + if (String.IsNullOrEmpty(temp) == false) row[SchemaTableColumn.BaseTableName] = temp; + + temp = columnToParent[n].DatabaseName; + if (String.IsNullOrEmpty(temp) == false) row[SchemaTableOptionalColumn.BaseCatalogName] = temp; + + string dataType = null; + // If we have a table-bound column, extract the extra information from it + if (String.IsNullOrEmpty(strColumn) == false) + { + string baseCatalogName = String.Empty; + + if (row[SchemaTableOptionalColumn.BaseCatalogName] != DBNull.Value) + baseCatalogName = (string)row[SchemaTableOptionalColumn.BaseCatalogName]; + + string baseTableName = String.Empty; + + if (row[SchemaTableColumn.BaseTableName] != DBNull.Value) + baseTableName = (string)row[SchemaTableColumn.BaseTableName]; + + if (sql.DoesTableExist(baseCatalogName, baseTableName)) + { + string baseColumnName = String.Empty; + + if (row[SchemaTableColumn.BaseColumnName] != DBNull.Value) + baseColumnName = (string)row[SchemaTableColumn.BaseColumnName]; + + string collSeq = null; + bool bNotNull = false; + bool bPrimaryKey = false; + bool bAutoIncrement = false; + string[] arSize; + + // Get the column meta data + _command.Connection._sql.ColumnMetaData( + baseCatalogName, + baseTableName, + strColumn, + true, + ref dataType, ref collSeq, ref bNotNull, ref bPrimaryKey, ref bAutoIncrement); + + if (bNotNull || bPrimaryKey) row[SchemaTableColumn.AllowDBNull] = false; + bool allowDbNull = (bool)row[SchemaTableColumn.AllowDBNull]; + + row[SchemaTableColumn.IsKey] = bPrimaryKey && CountParents(parentToColumns) <= 1; + row[SchemaTableOptionalColumn.IsAutoIncrement] = bAutoIncrement; + row["CollationType"] = collSeq; + + // For types like varchar(50) and such, extract the size + arSize = dataType.Split('('); + if (arSize.Length > 1) + { + dataType = arSize[0]; + arSize = arSize[1].Split(')'); + if (arSize.Length > 1) + { + arSize = arSize[0].Split(',', '.'); + if (sqlType.Type == DbType.Binary || SQLiteConvert.IsStringDbType(sqlType.Type)) + { + row[SchemaTableColumn.ColumnSize] = Convert.ToInt32(arSize[0], CultureInfo.InvariantCulture); + } + else + { + row[SchemaTableColumn.NumericPrecision] = Convert.ToInt32(arSize[0], CultureInfo.InvariantCulture); + if (arSize.Length > 1) + row[SchemaTableColumn.NumericScale] = Convert.ToInt32(arSize[1], CultureInfo.InvariantCulture); + } + } + } + + if (wantDefaultValue) + { + // Determine the default value for the column, which sucks because we have to query the schema for each column + using (SQLiteCommand cmdTable = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA [{0}].TABLE_INFO([{1}])", + baseCatalogName, + baseTableName + ), _command.Connection)) + using (DbDataReader rdTable = cmdTable.ExecuteReader()) + { + // Find the matching column + while (rdTable.Read()) + { + if (String.Compare(baseColumnName, rdTable.GetString(1), StringComparison.OrdinalIgnoreCase) == 0) + { + if (rdTable.IsDBNull(4) == false) + row[SchemaTableOptionalColumn.DefaultValue] = rdTable[4]; + + break; + } + } + } + } + + // Determine IsUnique properly, which is a pain in the butt! + if (wantUniqueInfo) + { + if (baseCatalogName != strCatalog || baseTableName != strTable) + { + strCatalog = baseCatalogName; + strTable = baseTableName; + + tblIndexes = _command.Connection.GetSchema("Indexes", new string[] { + baseCatalogName, + null, + baseTableName, + null + }); + } + + foreach (DataRow rowIndexes in tblIndexes.Rows) + { + tblIndexColumns = _command.Connection.GetSchema("IndexColumns", new string[] { + baseCatalogName, + null, + baseTableName, + (string)rowIndexes["INDEX_NAME"], + null + }); + foreach (DataRow rowColumnIndex in tblIndexColumns.Rows) + { + if (String.Compare(SQLiteConvert.GetStringOrNull(rowColumnIndex["COLUMN_NAME"]), strColumn, StringComparison.OrdinalIgnoreCase) == 0) + { + // + // BUGFIX: Make sure that we only flag this column as "unique" + // if we are not processing of some kind of multi-table + // construct (i.e. a join) because in that case we must + // allow duplicate values (refer to ticket [7e3fa93744]). + // + if (parentToColumns.Count == 1 && tblIndexColumns.Rows.Count == 1 && allowDbNull == false) + row[SchemaTableColumn.IsUnique] = rowIndexes["UNIQUE"]; + + // If its an integer primary key and the only primary key in the table, then its a rowid alias and is autoincrement + // NOTE: Currently commented out because this is not always the desired behavior. For example, a 1:1 relationship with + // another table, where the other table is autoincrement, but this one is not, and uses the rowid from the other. + // It is safer to only set Autoincrement on tables where we're SURE the user specified AUTOINCREMENT, even if its a rowid column. + + //if (tblIndexColumns.Rows.Count == 1 && (bool)rowIndexes["PRIMARY_KEY"] == true && String.IsNullOrEmpty(dataType) == false && + // String.Compare(dataType, "integer", StringComparison.OrdinalIgnoreCase) == 0) + //{ + // // row[SchemaTableOptionalColumn.IsAutoIncrement] = true; + //} + + break; + } + } + } + } + } + + if (String.IsNullOrEmpty(dataType)) + { + TypeAffinity affin = TypeAffinity.Uninitialized; + dataType = _activeStatement._sql.ColumnType(_activeStatement, n, ref affin); + } + + if (String.IsNullOrEmpty(dataType) == false) + row["DataTypeName"] = dataType; + } + + tbl.Rows.Add(row); + } + + if (_keyInfo != null) + _keyInfo.AppendSchemaTable(tbl); + + tbl.AcceptChanges(); + tbl.EndLoadData(); + + return tbl; + } + + /// + /// Retrieves the column as a string + /// + /// The index of the column. + /// string + public override string GetString(int i) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetString", null, value), out complete); + + if (complete) + return value.StringValue; + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetString(i - PrivateVisibleFieldCount); + + if (!HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.NoVerifyTextAffinity)) + VerifyType(i, DbType.String); + + return _activeStatement._sql.GetText(_activeStatement, i); + } + + /// + /// Retrieves the column as an object corresponding to the underlying datatype of the column + /// + /// The index of the column. + /// object + public override object GetValue(int i) + { + CheckDisposed(); + VerifyForGet(); + + if (HelperMethods.HasFlags(_flags, SQLiteConnectionFlags.UseConnectionReadValueCallbacks)) + { + SQLiteDataReaderValue value = new SQLiteDataReaderValue(); + bool complete; + + InvokeReadValueCallback(i, new SQLiteReadValueEventArgs( + "GetValue", null, value), out complete); + + if (complete) + return value.Value; + } + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.GetValue(i - PrivateVisibleFieldCount); + + SQLiteType typ = GetSQLiteType(_flags, i); + + if (HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.DetectTextAffinity) && + ((typ == null) || (typ.Affinity == TypeAffinity.Text))) + { + typ = GetSQLiteType( + typ, _activeStatement._sql.GetText(_activeStatement, i)); + } + else if (HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.DetectStringType) && + ((typ == null) || SQLiteConvert.IsStringDbType(typ.Type))) + { + typ = GetSQLiteType( + typ, _activeStatement._sql.GetText(_activeStatement, i)); + } + + return _activeStatement._sql.GetValue(_activeStatement, _flags, i, typ); + } + + /// + /// Retreives the values of multiple columns, up to the size of the supplied array + /// + /// The array to fill with values from the columns in the current resultset + /// The number of columns retrieved + public override int GetValues(object[] values) + { + CheckDisposed(); + + int nMax = FieldCount; + if (values.Length < nMax) nMax = values.Length; + + for (int n = 0; n < nMax; n++) + { + values[n] = GetValue(n); + } + + return nMax; + } + + /// + /// Returns a collection containing all the column names and values for the + /// current row of data in the current resultset, if any. If there is no + /// current row or no current resultset, an exception may be thrown. + /// + /// + /// The collection containing the column name and value information for the + /// current row of data in the current resultset or null if this information + /// cannot be obtained. + /// + public NameValueCollection GetValues() + { + CheckDisposed(); + + if ((_activeStatement == null) || (_activeStatement._sql == null)) + throw new InvalidOperationException(); + + int nMax = PrivateVisibleFieldCount; + NameValueCollection result = new NameValueCollection(nMax); + + for (int n = 0; n < nMax; n++) + { + string name = _activeStatement._sql.ColumnName(_activeStatement, n); + string value = _activeStatement._sql.GetText(_activeStatement, n); + + result.Add(name, value); + } + + return result; + } + + /// + /// Returns True if the resultset has rows that can be fetched + /// + public override bool HasRows + { + get + { + CheckDisposed(); + CheckClosed(); + + // + // NOTE: If the "sticky" flag has been set, use the new behavior, + // which returns non-zero if there were ever any rows in + // the associated result sets. Generally, this flag is only + // useful when it is necessary to retain compatibility with + // other ADO.NET providers that use these same semantics for + // the HasRows property. + // + if (HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.StickyHasRows)) + { + return ((_readingState != 1) || (_stepCount > 0)); + } + + // + // NOTE: This is the default behavior. It returns non-zero only if + // more rows are available (i.e. a call to the Read method is + // expected to succeed). Prior to the introduction of the + // "sticky" flag, this is how this property has always worked. + // + return (_readingState != 1); + } + } + + /// + /// Returns True if the data reader is closed + /// + public override bool IsClosed + { + get { CheckDisposed(); return (_command == null); } + } + + /// + /// Returns True if the specified column is null + /// + /// The index of the column. + /// True or False + public override bool IsDBNull(int i) + { + CheckDisposed(); + VerifyForGet(); + + if (i >= PrivateVisibleFieldCount && _keyInfo != null) + return _keyInfo.IsDBNull(i - PrivateVisibleFieldCount); + + return _activeStatement._sql.IsNull(_activeStatement, i); + } + + /// + /// Moves to the next resultset in multiple row-returning SQL command. + /// + /// True if the command was successful and a new resultset is available, False otherwise. + public override bool NextResult() + { + CheckDisposed(); + CheckClosed(); + if (_throwOnDisposed) SQLiteCommand.Check(_command); + + SQLiteStatement stmt = null; + int fieldCount; + bool schemaOnly = ((_commandBehavior & CommandBehavior.SchemaOnly) != 0); + + while (true) + { + if (stmt == null && _activeStatement != null && _activeStatement._sql != null && _activeStatement._sql.IsOpen()) + { + // Reset the previously-executed statement + if (!schemaOnly) _activeStatement._sql.Reset(_activeStatement); + + // If we're only supposed to return a single rowset, step through all remaining statements once until + // they are all done and return false to indicate no more resultsets exist. + if ((_commandBehavior & CommandBehavior.SingleResult) != 0) + { + for (; ; ) + { + stmt = _command.GetStatement(_activeStatementIndex + 1); + if (stmt == null) break; + _activeStatementIndex++; + + if (!schemaOnly && stmt._sql.Step(stmt)) _stepCount++; + if (stmt._sql.ColumnCount(stmt) == 0) + { + int changes = 0; + bool readOnly = false; + if (stmt.TryGetChanges(ref changes, ref readOnly)) + { + if (!readOnly) + { + if (_rowsAffected == -1) _rowsAffected = 0; + _rowsAffected += changes; + } + } + else + { + return false; + } + } + if (!schemaOnly) stmt._sql.Reset(stmt); // Gotta reset after every step to release any locks and such! + } + return false; + } + } + + // Get the next statement to execute + stmt = _command.GetStatement(_activeStatementIndex + 1); + + // If we've reached the end of the statements, return false, no more resultsets + if (stmt == null) + return false; + + // If we were on a current resultset, set the state to "done reading" for it + if (_readingState < 1) + _readingState = 1; + + _activeStatementIndex++; + + fieldCount = stmt._sql.ColumnCount(stmt); + + // If the statement is not a select statement or we're not retrieving schema only, then perform the initial step + if (!schemaOnly || (fieldCount == 0)) + { + if (!schemaOnly && stmt._sql.Step(stmt)) + { + _stepCount++; + _readingState = -1; + } + else if (fieldCount == 0) // No rows returned, if fieldCount is zero, skip to the next statement + { + int changes = 0; + bool readOnly = false; + if (stmt.TryGetChanges(ref changes, ref readOnly)) + { + if (!readOnly) + { + if (_rowsAffected == -1) _rowsAffected = 0; + _rowsAffected += changes; + } + } + else + { + return false; + } + if (!schemaOnly) stmt._sql.Reset(stmt); + continue; // Skip this command and move to the next, it was not a row-returning resultset + } + else // No rows, fieldCount is non-zero so stop here + { + _readingState = 1; // This command returned columns but no rows, so return true, but HasRows = false and Read() returns false + } + } + + // Ahh, we found a row-returning resultset eligible to be returned! + _activeStatement = stmt; + _fieldCount = fieldCount; + _fieldIndexes = new Dictionary(StringComparer.OrdinalIgnoreCase); + _fieldTypeArray = new SQLiteType[PrivateVisibleFieldCount]; + + if ((_commandBehavior & CommandBehavior.KeyInfo) != 0) + LoadKeyInfo(); + + return true; + } + } + + /// + /// This method attempts to query the database connection associated with + /// the data reader in use. If the underlying command or connection is + /// unavailable, a null value will be returned. + /// + /// + /// The connection object -OR- null if it is unavailable. + /// + internal static SQLiteConnection GetConnection( + SQLiteDataReader dataReader + ) + { + try + { + if (dataReader != null) + { + SQLiteCommand command = dataReader._command; + + if (command != null) + { + SQLiteConnection connection = command.Connection; + + if (connection != null) + return connection; + } + } + } + catch (ObjectDisposedException) + { + // do nothing. + } + + return null; + } + + /// + /// Retrieves the SQLiteType for a given column and row value. + /// + /// + /// The original SQLiteType structure, based only on the column. + /// + /// + /// The textual value of the column for a given row. + /// + /// + /// The SQLiteType structure. + /// + private SQLiteType GetSQLiteType( + SQLiteType oldType, /* PASS-THROUGH */ + string text + ) + { + if (SQLiteConvert.LooksLikeNull(text)) + return new SQLiteType(TypeAffinity.Null, DbType.Object); + + if (SQLiteConvert.LooksLikeInt64(text)) + return new SQLiteType(TypeAffinity.Int64, DbType.Int64); + + if (SQLiteConvert.LooksLikeDouble(text)) + return new SQLiteType(TypeAffinity.Double, DbType.Double); + + if ((_activeStatement != null) && + SQLiteConvert.LooksLikeDateTime(_activeStatement._sql, text)) + { + return new SQLiteType(TypeAffinity.DateTime, DbType.DateTime); + } + + return oldType; + } + + /// + /// Retrieves the SQLiteType for a given column, and caches it to avoid repetetive interop calls. + /// + /// The flags associated with the parent connection object. + /// The index of the column. + /// A SQLiteType structure + private SQLiteType GetSQLiteType(SQLiteConnectionFlags flags, int i) + { + SQLiteType typ = _fieldTypeArray[i]; + + if (typ == null) + { + // Initialize this column's field type instance + typ = _fieldTypeArray[i] = new SQLiteType(); + } + + // If not initialized, then fetch the declared column datatype and attempt to convert it + // to a known DbType. + if (typ.Affinity == TypeAffinity.Uninitialized) + { + typ.Type = SQLiteConvert.TypeNameToDbType( + GetConnection(this), _activeStatement._sql.ColumnType( + _activeStatement, i, ref typ.Affinity), flags); + } + else + { + typ.Affinity = _activeStatement._sql.ColumnAffinity( + _activeStatement, i); + } + + return typ; + } + + /// + /// Reads the next row from the resultset + /// + /// True if a new row was successfully loaded and is ready for processing + public override bool Read() + { + CheckDisposed(); + CheckClosed(); + if (_throwOnDisposed) SQLiteCommand.Check(_command); + + if ((_commandBehavior & CommandBehavior.SchemaOnly) != 0) + return false; + + if (_readingState == -1) // First step was already done at the NextResult() level, so don't step again, just return true. + { + _readingState = 0; + return true; + } + else if (_readingState == 0) // Actively reading rows + { + // Don't read a new row if the command behavior dictates SingleRow. We've already read the first row. + if ((_commandBehavior & CommandBehavior.SingleRow) == 0) + { + if (_activeStatement._sql.Step(_activeStatement) == true) + { + _stepCount++; + + if (_keyInfo != null) + _keyInfo.Reset(); + + return true; + } + } + + _readingState = 1; // Finished reading rows + } + + return false; + } + + /// + /// Returns the number of rows affected by the statement being executed. + /// The value returned may not be accurate for DDL statements. Also, it + /// will be -1 for any statement that does not modify the database (e.g. + /// SELECT). If an otherwise read-only statement modifies the database + /// indirectly (e.g. via a virtual table or user-defined function), the + /// value returned is undefined. + /// + public override int RecordsAffected + { + get { CheckDisposed(); return _rowsAffected; } + } + + /// + /// Indexer to retrieve data from a column given its name + /// + /// The name of the column to retrieve data for + /// The value contained in the column + public override object this[string name] + { + get { CheckDisposed(); return GetValue(GetOrdinal(name)); } + } + + /// + /// Indexer to retrieve data from a column given its i + /// + /// The index of the column. + /// The value contained in the column + public override object this[int i] + { + get { CheckDisposed(); return GetValue(i); } + } + + private void LoadKeyInfo() + { + if (_keyInfo != null) + { + _keyInfo.Dispose(); + _keyInfo = null; + } + + _keyInfo = new SQLiteKeyReader(_command.Connection, this, _activeStatement); + } + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteDefineConstants.cs b/Native.Csharp.Tool/SQLite/SQLiteDefineConstants.cs new file mode 100644 index 00000000..033ff43a --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteDefineConstants.cs @@ -0,0 +1,230 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +using System.Collections.Generic; + +namespace System.Data.SQLite +{ + internal static class SQLiteDefineConstants + { + public static readonly IList OptionList = new List(new string[] { +#if CHECK_STATE + "CHECK_STATE", +#endif + +#if COUNT_HANDLE + "COUNT_HANDLE", +#endif + +#if DEBUG + "DEBUG", +#endif + +#if INTEROP_CODEC + "INTEROP_CODEC", +#endif + +#if INTEROP_DEBUG + "INTEROP_DEBUG", +#endif + +#if INTEROP_EXTENSION_FUNCTIONS + "INTEROP_EXTENSION_FUNCTIONS", +#endif + +#if INTEROP_FTS5_EXTENSION + "INTEROP_FTS5_EXTENSION", +#endif + +#if INTEROP_INCLUDE_CEROD + "INTEROP_INCLUDE_CEROD", +#endif + +#if INTEROP_INCLUDE_EXTRA + "INTEROP_INCLUDE_EXTRA", +#endif + +#if INTEROP_INCLUDE_SEE + "INTEROP_INCLUDE_SEE", +#endif + +#if INTEROP_INCLUDE_ZIPVFS + "INTEROP_INCLUDE_ZIPVFS", +#endif + +#if INTEROP_JSON1_EXTENSION + "INTEROP_JSON1_EXTENSION", +#endif + +#if INTEROP_LEGACY_CLOSE + "INTEROP_LEGACY_CLOSE", +#endif + +#if INTEROP_LOG + "INTEROP_LOG", +#endif + +#if INTEROP_PERCENTILE_EXTENSION + "INTEROP_PERCENTILE_EXTENSION", +#endif + +#if INTEROP_REGEXP_EXTENSION + "INTEROP_REGEXP_EXTENSION", +#endif + +#if INTEROP_SESSION_EXTENSION + "INTEROP_SESSION_EXTENSION", +#endif + +#if INTEROP_SHA1_EXTENSION + "INTEROP_SHA1_EXTENSION", +#endif + +#if INTEROP_TEST_EXTENSION + "INTEROP_TEST_EXTENSION", +#endif + +#if INTEROP_TOTYPE_EXTENSION + "INTEROP_TOTYPE_EXTENSION", +#endif + +#if INTEROP_VIRTUAL_TABLE + "INTEROP_VIRTUAL_TABLE", +#endif + +#if NET_20 + "NET_20", +#endif + +#if NET_35 + "NET_35", +#endif + +#if NET_40 + "NET_40", +#endif + +#if NET_45 + "NET_45", +#endif + +#if NET_451 + "NET_451", +#endif + +#if NET_452 + "NET_452", +#endif + +#if NET_46 + "NET_46", +#endif + +#if NET_461 + "NET_461", +#endif + +#if NET_462 + "NET_462", +#endif + +#if NET_47 + "NET_47", +#endif + +#if NET_471 + "NET_471", +#endif + +#if NET_472 + "NET_472", +#endif + +#if NET_COMPACT_20 + "NET_COMPACT_20", +#endif + +#if NET_STANDARD_20 + "NET_STANDARD_20", +#endif + +#if PLATFORM_COMPACTFRAMEWORK + "PLATFORM_COMPACTFRAMEWORK", +#endif + +#if PRELOAD_NATIVE_LIBRARY + "PRELOAD_NATIVE_LIBRARY", +#endif + +#if RETARGETABLE + "RETARGETABLE", +#endif + +#if SQLITE_STANDARD + "SQLITE_STANDARD", +#endif + +#if THROW_ON_DISPOSED + "THROW_ON_DISPOSED", +#endif + +#if TRACE + "TRACE", +#endif + +#if TRACE_CONNECTION + "TRACE_CONNECTION", +#endif + +#if TRACE_DETECTION + "TRACE_DETECTION", +#endif + +#if TRACE_HANDLE + "TRACE_HANDLE", +#endif + +#if TRACE_PRELOAD + "TRACE_PRELOAD", +#endif + +#if TRACE_SHARED + "TRACE_SHARED", +#endif + +#if TRACE_STATEMENT + "TRACE_STATEMENT", +#endif + +#if TRACE_WARNING + "TRACE_WARNING", +#endif + +#if TRACK_MEMORY_BYTES + "TRACK_MEMORY_BYTES", +#endif + +#if USE_ENTITY_FRAMEWORK_6 + "USE_ENTITY_FRAMEWORK_6", +#endif + +#if USE_INTEROP_DLL + "USE_INTEROP_DLL", +#endif + +#if USE_PREPARE_V2 + "USE_PREPARE_V2", +#endif + +#if WINDOWS + "WINDOWS", +#endif + + null + }); + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteEnlistment.cs b/Native.Csharp.Tool/SQLite/SQLiteEnlistment.cs new file mode 100644 index 00000000..61c7ffab --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteEnlistment.cs @@ -0,0 +1,297 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +#if !PLATFORM_COMPACTFRAMEWORK +namespace System.Data.SQLite +{ + using System.Globalization; + using System.Transactions; + + internal sealed class SQLiteEnlistment : IDisposable, IEnlistmentNotification + { + internal SQLiteTransaction _transaction; + internal Transaction _scope; + internal bool _disposeConnection; + + internal SQLiteEnlistment( + SQLiteConnection cnn, + Transaction scope, + System.Data.IsolationLevel defaultIsolationLevel, + bool throwOnUnavailable, + bool throwOnUnsupported + ) + { + _transaction = cnn.BeginTransaction(GetSystemDataIsolationLevel( + cnn, scope, defaultIsolationLevel, throwOnUnavailable, + throwOnUnsupported)); + + _scope = scope; + + _scope.EnlistVolatile(this, EnlistmentOptions.None); + } + + /////////////////////////////////////////////////////////////////////////// + + #region Private Methods + private System.Data.IsolationLevel GetSystemDataIsolationLevel( + SQLiteConnection connection, + Transaction transaction, + System.Data.IsolationLevel defaultIsolationLevel, + bool throwOnUnavailable, + bool throwOnUnsupported + ) + { + if (transaction == null) + { + // + // NOTE: If neither the transaction nor connection isolation + // level is available, throw an exception if instructed + // by the caller. + // + if (connection != null) + return connection.GetDefaultIsolationLevel(); + + if (throwOnUnavailable) + { + throw new InvalidOperationException( + "isolation level is unavailable"); + } + + return defaultIsolationLevel; + } + + IsolationLevel isolationLevel = transaction.IsolationLevel; + + // + // TODO: Are these isolation level mappings actually correct? + // + switch (isolationLevel) + { + case IsolationLevel.Unspecified: + return System.Data.IsolationLevel.Unspecified; + case IsolationLevel.Chaos: + return System.Data.IsolationLevel.Chaos; + case IsolationLevel.ReadUncommitted: + return System.Data.IsolationLevel.ReadUncommitted; + case IsolationLevel.ReadCommitted: + return System.Data.IsolationLevel.ReadCommitted; + case IsolationLevel.RepeatableRead: + return System.Data.IsolationLevel.RepeatableRead; + case IsolationLevel.Serializable: + return System.Data.IsolationLevel.Serializable; + case IsolationLevel.Snapshot: + return System.Data.IsolationLevel.Snapshot; + } + + // + // NOTE: When in "strict" mode, throw an exception if the isolation + // level is not recognized; otherwise, fallback to the default + // isolation level specified by the caller. + // + if (throwOnUnsupported) + { + throw new InvalidOperationException( + HelperMethods.StringFormat(CultureInfo.CurrentCulture, + "unsupported isolation level {0}", isolationLevel)); + } + + return defaultIsolationLevel; + } + + /////////////////////////////////////////////////////////////////////////// + + private void Cleanup(SQLiteConnection cnn) + { + if (_disposeConnection && (cnn != null)) + cnn.Dispose(); + + _transaction = null; + _scope = null; + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteEnlistment).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////// + + private /* protected virtual */ void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (_transaction != null) + { + _transaction.Dispose(); + _transaction = null; + } + + if (_scope != null) + { + // _scope.Dispose(); // NOTE: Not "owned" by us. + _scope = null; + } + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region Destructor + ~SQLiteEnlistment() + { + Dispose(false); + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region IEnlistmentNotification Members + public void Commit(Enlistment enlistment) + { + CheckDisposed(); + + SQLiteConnection cnn = null; + + try + { + while (true) + { + cnn = _transaction.Connection; + + if (cnn == null) + break; + + lock (cnn._enlistmentSyncRoot) /* TRANSACTIONAL */ + { + // + // NOTE: This check is necessary to detect the case where + // the SQLiteConnection.Close() method changes the + // connection associated with our transaction (i.e. + // to avoid a race (condition) between grabbing the + // Connection property and locking its enlistment). + // + if (!Object.ReferenceEquals(cnn, _transaction.Connection)) + continue; + + cnn._enlistment = null; + + _transaction.IsValid(true); /* throw */ + cnn._transactionLevel = 1; + _transaction.Commit(); + + break; + } + } + + enlistment.Done(); + } + finally + { + Cleanup(cnn); + } + } + + /////////////////////////////////////////////////////////////////////////// + + public void InDoubt(Enlistment enlistment) + { + CheckDisposed(); + enlistment.Done(); + } + + /////////////////////////////////////////////////////////////////////////// + + public void Prepare(PreparingEnlistment preparingEnlistment) + { + CheckDisposed(); + + if (_transaction.IsValid(false) == false) + preparingEnlistment.ForceRollback(); + else + preparingEnlistment.Prepared(); + } + + /////////////////////////////////////////////////////////////////////////// + + public void Rollback(Enlistment enlistment) + { + CheckDisposed(); + + SQLiteConnection cnn = null; + + try + { + while (true) + { + cnn = _transaction.Connection; + + if (cnn == null) + break; + + lock (cnn._enlistmentSyncRoot) /* TRANSACTIONAL */ + { + // + // NOTE: This check is necessary to detect the case where + // the SQLiteConnection.Close() method changes the + // connection associated with our transaction (i.e. + // to avoid a race (condition) between grabbing the + // Connection property and locking its enlistment). + // + if (!Object.ReferenceEquals(cnn, _transaction.Connection)) + continue; + + cnn._enlistment = null; + + _transaction.Rollback(); + + break; + } + } + + enlistment.Done(); + } + finally + { + Cleanup(cnn); + } + } + #endregion + } +} +#endif // !PLATFORM_COMPACT_FRAMEWORK diff --git a/Native.Csharp.Tool/SQLite/SQLiteException.cs b/Native.Csharp.Tool/SQLite/SQLiteException.cs new file mode 100644 index 00000000..df51ad2d --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteException.cs @@ -0,0 +1,853 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Data.Common; + using System.Globalization; + +#if !PLATFORM_COMPACTFRAMEWORK + using System.Reflection; + using System.Runtime.Serialization; + using System.Security.Permissions; +#endif + + /// + /// SQLite exception class. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Serializable()] + public sealed class SQLiteException : DbException, ISerializable +#else + public sealed class SQLiteException : Exception +#endif + { + #region Private Constants + /// + /// This value was copied from the "WinError.h" file included with the + /// Platform SDK for Windows 10. + /// + private const int FACILITY_SQLITE = 1967; + #endregion + + /////////////////////////////////////////////////////////////////////////// + + private SQLiteErrorCode _errorCode; + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// Private constructor for use with serialization. + /// + /// + /// Holds the serialized object data about the exception being thrown. + /// + /// + /// Contains contextual information about the source or destination. + /// + private SQLiteException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + _errorCode = (SQLiteErrorCode)info.GetInt32("errorCode"); + + Initialize(); + } +#endif + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Public constructor for generating a SQLite exception given the error + /// code and message. + /// + /// + /// The SQLite return code to report. + /// + /// + /// Message text to go along with the return code message text. + /// + public SQLiteException(SQLiteErrorCode errorCode, string message) + : base(GetStockErrorMessage(errorCode, message)) + { + _errorCode = errorCode; + + Initialize(); + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Public constructor that uses the base class constructor for the error + /// message. + /// + /// Error message text. + public SQLiteException(string message) + : this(SQLiteErrorCode.Unknown, message) + { + // do nothing. + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Public constructor that uses the default base class constructor. + /// + public SQLiteException() + : base() + { + Initialize(); + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Public constructor that uses the base class constructor for the error + /// message and inner exception. + /// + /// Error message text. + /// The original (inner) exception. + public SQLiteException(string message, Exception innerException) + : base(message, innerException) + { + Initialize(); + } + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// Adds extra information to the serialized object data specific to this + /// class type. This is only used for serialization. + /// + /// + /// Holds the serialized object data about the exception being thrown. + /// + /// + /// Contains contextual information about the source or destination. + /// + [SecurityPermission( + SecurityAction.LinkDemand, + Flags = SecurityPermissionFlag.SerializationFormatter)] + public override void GetObjectData( + SerializationInfo info, + StreamingContext context) + { + if (info != null) + info.AddValue("errorCode", _errorCode); + + base.GetObjectData(info, context); + } +#endif + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Gets the associated SQLite result code for this exception as a + /// . This property returns the same + /// underlying value as the property. + /// + public SQLiteErrorCode ResultCode + { + get { return _errorCode; } + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Gets the associated SQLite return code for this exception as an + /// . For desktop versions of the .NET Framework, + /// this property overrides the property of the same name within the + /// + /// class. This property returns the same underlying value as the + /// property. + /// +#if !PLATFORM_COMPACTFRAMEWORK + public override int ErrorCode +#else + public int ErrorCode +#endif + { + get { return (int)_errorCode; } + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// This method performs extra initialization tasks. It may be called by + /// any of the constructors of this class. It must not throw exceptions. + /// + private void Initialize() + { + if (HResult == unchecked((int)0x80004005)) /* E_FAIL */ + { + int? localHResult = GetHResultForErrorCode(ResultCode); + + if (localHResult != null) + HResult = (int)localHResult; + } + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Maps a Win32 error code to an HRESULT. + /// + /// + /// The specified Win32 error code. It must be within the range of zero + /// (0) to 0xFFFF (65535). + /// + /// + /// Non-zero if the HRESULT should indicate success; otherwise, zero. + /// + /// + /// The integer value of the HRESULT. + /// + private static int MakeHResult( + int errorCode, + bool success + ) + { + return (errorCode & 0xFFFF) | FACILITY_SQLITE | + (success ? 0 : unchecked((int)0x80000000)); + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to map the specified onto an + /// existing HRESULT -OR- a Win32 error code wrapped in an HRESULT. The + /// mappings may not have perfectly matching semantics; however, they do + /// have the benefit of being unique within the context of this exception + /// type. + /// + /// + /// The to map. + /// + /// + /// The integer HRESULT value -OR- null if there is no known mapping. + /// + private static int? GetHResultForErrorCode( + SQLiteErrorCode errorCode + ) + { + switch (errorCode & SQLiteErrorCode.NonExtendedMask) + { + case SQLiteErrorCode.Ok: + { + return 0; /* S_OK */ + } + case SQLiteErrorCode.Error: + { + return MakeHResult(0x001F, false); /* ERROR_GEN_FAILURE */ + } + case SQLiteErrorCode.Internal: + { + return unchecked((int)0x8000FFFF); /* E_UNEXPECTED */ + } + case SQLiteErrorCode.Perm: + { + return MakeHResult(0x0005, false); /* ERROR_ACCESS_DENIED */ + } + case SQLiteErrorCode.Abort: + { + return unchecked((int)0x80004004); /* E_ABORT */ + } + case SQLiteErrorCode.Busy: + { + return MakeHResult(0x00AA, false); /* ERROR_BUSY */ + } + case SQLiteErrorCode.Locked: + { + return MakeHResult(0x00D4, false); /* ERROR_LOCKED */ + } + case SQLiteErrorCode.NoMem: + { + return MakeHResult(0x000E, false); /* ERROR_OUTOFMEMORY */ + } + case SQLiteErrorCode.ReadOnly: + { + return MakeHResult(0x1779, false); /* ERROR_FILE_READ_ONLY */ + } + case SQLiteErrorCode.Interrupt: + { + return MakeHResult(0x04C7, false); /* ERROR_CANCELLED */ + } + case SQLiteErrorCode.IoErr: + { + return MakeHResult(0x045D, false); /* ERROR_IO_DEVICE */ + } + case SQLiteErrorCode.Corrupt: + { + return MakeHResult(0x054E, false); /* ERROR_INTERNAL_DB_CORRUPTION */ + } + case SQLiteErrorCode.NotFound: + { + return MakeHResult(0x0032, false); /* ERROR_NOT_SUPPORTED */ + } + case SQLiteErrorCode.Full: + { + return MakeHResult(0x0070, false); /* ERROR_DISK_FULL */ + } + case SQLiteErrorCode.CantOpen: + { + return MakeHResult(0x03F3, false); /* ERROR_CANTOPEN */ + } + case SQLiteErrorCode.Protocol: + { + return MakeHResult(0x05B4, false); /* ERROR_TIMEOUT */ + } + case SQLiteErrorCode.Empty: + { + return MakeHResult(0x10D2, false); /* ERROR_EMPTY */ + } + case SQLiteErrorCode.Schema: + { + return MakeHResult(0x078B, false); /* ERROR_CONTEXT_EXPIRED */ + } + case SQLiteErrorCode.TooBig: + { + return unchecked((int)0x800288C5); /* TYPE_E_SIZETOOBIG */ + } + case SQLiteErrorCode.Constraint: + { + return MakeHResult(0x202F, false); /* ERROR_DS_CONSTRAINT_VIOLATION */ + } + case SQLiteErrorCode.Mismatch: + { + return MakeHResult(0x065D, false); /* ERROR_DATATYPE_MISMATCH */ + } + case SQLiteErrorCode.Misuse: + { + return MakeHResult(0x0649, false); /* ERROR_INVALID_HANDLE_STATE */ + } + case SQLiteErrorCode.NoLfs: + { + return MakeHResult(0x0646, false); /* ERROR_UNKNOWN_FEATURE */ + } + case SQLiteErrorCode.Auth: + { + return MakeHResult(0x078F, false); /* ERROR_AUTHENTICATION_FIREWALL_FAILED */ + } + case SQLiteErrorCode.Format: + { + return MakeHResult(0x000B, false); /* ERROR_BAD_FORMAT */ + } + case SQLiteErrorCode.Range: + { + return unchecked((int)0x80028CA1); /* TYPE_E_OUTOFBOUNDS */ + } + case SQLiteErrorCode.NotADb: + { + return MakeHResult(0x0570, false); /* ERROR_FILE_CORRUPT */ + } + case SQLiteErrorCode.Notice: + case SQLiteErrorCode.Warning: + case SQLiteErrorCode.Row: + case SQLiteErrorCode.Done: + { + // + // NOTE: These result codes are not errors, per se; + // therefore, mask off all HRESULT bits that + // are not part of the "code" portion (e.g. + // the severity, facility, etc). This will + // have the effect of creating an HRESULT + // that indicates success, while (hopefully) + // preserving the specified result code. At + // the time this method was written (2018-02), + // no SQLite result codes were outside of the + // supported range for HRESULT codes (e.g. + // 0x0000 to 0xFFFF, inclusive), which made + // the following masking operation a harmless + // NOOP. + // + return MakeHResult((int)errorCode, true); + } + } + + return null; + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Returns the error message for the specified SQLite return code. + /// + /// The SQLite return code. + /// The error message or null if it cannot be found. + private static string GetErrorString( + SQLiteErrorCode errorCode + ) + { +#if !PLATFORM_COMPACTFRAMEWORK + // + // HACK: This must be done via reflection in order to prevent + // the RuntimeHelpers.PrepareDelegate method from over- + // eagerly attempting to locate the new (and optional) + // sqlite3_errstr() function in the SQLite core library + // because it happens to be in the static call graph for + // the AppDomain.DomainUnload event handler registered + // by the SQLiteLog class. + // + BindingFlags flags = BindingFlags.Static | + BindingFlags.NonPublic | BindingFlags.InvokeMethod; + + return typeof(SQLite3).InvokeMember("GetErrorString", + flags, null, null, new object[] { errorCode }) as string; +#else + return SQLite3.GetErrorString(errorCode); +#endif + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Returns the composite error message based on the SQLite return code + /// and the optional detailed error message. + /// + /// The SQLite return code. + /// Optional detailed error message. + /// Error message text for the return code. + private static string GetStockErrorMessage( + SQLiteErrorCode errorCode, + string message + ) + { + return HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "{0}{1}{2}", + GetErrorString(errorCode), +#if !NET_COMPACT_20 + Environment.NewLine, message).Trim(); +#else + "\r\n", message).Trim(); +#endif + } + + /////////////////////////////////////////////////////////////////////////// + + #region System.Object Overrides + public override string ToString() + { + return HelperMethods.StringFormat( + CultureInfo.CurrentCulture, "code = {0} ({1}), message = {2}", + _errorCode, (int)_errorCode, base.ToString()); + } + #endregion + } + + /// + /// SQLite error codes. Actually, this enumeration represents a return code, + /// which may also indicate success in one of several ways (e.g. SQLITE_OK, + /// SQLITE_ROW, and SQLITE_DONE). Therefore, the name of this enumeration is + /// something of a misnomer. + /// + public enum SQLiteErrorCode + { + /// + /// The error code is unknown. This error code + /// is only used by the managed wrapper itself. + /// + Unknown = -1, + /// + /// Successful result + /// + Ok /* 0 */, + /// + /// SQL error or missing database + /// + Error /* 1 */, + /// + /// Internal logic error in SQLite + /// + Internal /* 2 */, + /// + /// Access permission denied + /// + Perm /* 3 */, + /// + /// Callback routine requested an abort + /// + Abort /* 4 */, + /// + /// The database file is locked + /// + Busy /* 5 */, + /// + /// A table in the database is locked + /// + Locked /* 6 */, + /// + /// A malloc() failed + /// + NoMem /* 7 */, + /// + /// Attempt to write a readonly database + /// + ReadOnly /* 8 */, + /// + /// Operation terminated by sqlite3_interrupt() + /// + Interrupt /* 9 */, + /// + /// Some kind of disk I/O error occurred + /// + IoErr /* 10 */, + /// + /// The database disk image is malformed + /// + Corrupt /* 11 */, + /// + /// Unknown opcode in sqlite3_file_control() + /// + NotFound /* 12 */, + /// + /// Insertion failed because database is full + /// + Full /* 13 */, + /// + /// Unable to open the database file + /// + CantOpen /* 14 */, + /// + /// Database lock protocol error + /// + Protocol /* 15 */, + /// + /// Database is empty + /// + Empty /* 16 */, + /// + /// The database schema changed + /// + Schema /* 17 */, + /// + /// String or BLOB exceeds size limit + /// + TooBig /* 18 */, + /// + /// Abort due to constraint violation + /// + Constraint /* 19 */, + /// + /// Data type mismatch + /// + Mismatch /* 20 */, + /// + /// Library used incorrectly + /// + Misuse /* 21 */, + /// + /// Uses OS features not supported on host + /// + NoLfs /* 22 */, + /// + /// Authorization denied + /// + Auth /* 23 */, + /// + /// Auxiliary database format error + /// + Format /* 24 */, + /// + /// 2nd parameter to sqlite3_bind out of range + /// + Range /* 25 */, + /// + /// File opened that is not a database file + /// + NotADb /* 26 */, + /// + /// Notifications from sqlite3_log() + /// + Notice /* 27 */, + /// + /// Warnings from sqlite3_log() + /// + Warning /* 28 */, + /// + /// sqlite3_step() has another row ready + /// + Row = 100, + /// + /// sqlite3_step() has finished executing + /// + Done, /* 101 */ + /// + /// Used to mask off extended result codes + /// + NonExtendedMask = 0xFF, + + ///////////////////////////////////////////////////////////////////////// + // BEGIN EXTENDED RESULT CODES + ///////////////////////////////////////////////////////////////////////// + + /// + /// A collation sequence was referenced by a schema and it cannot be + /// found. + /// + Error_Missing_CollSeq = (Error | (1 << 8)), + /// + /// An internal operation failed and it may succeed if retried. + /// + Error_Retry = (Error | (2 << 8)), + /// + /// A file read operation failed. + /// + IoErr_Read = (IoErr | (1 << 8)), + /// + /// A file read operation returned less data than requested. + /// + IoErr_Short_Read = (IoErr | (2 << 8)), + /// + /// A file write operation failed. + /// + IoErr_Write = (IoErr | (3 << 8)), + /// + /// A file synchronization operation failed. + /// + IoErr_Fsync = (IoErr | (4 << 8)), + /// + /// A directory synchronization operation failed. + /// + IoErr_Dir_Fsync = (IoErr | (5 << 8)), + /// + /// A file truncate operation failed. + /// + IoErr_Truncate = (IoErr | (6 << 8)), + /// + /// A file metadata operation failed. + /// + IoErr_Fstat = (IoErr | (7 << 8)), + /// + /// A file unlock operation failed. + /// + IoErr_Unlock = (IoErr | (8 << 8)), + /// + /// A file lock operation failed. + /// + IoErr_RdLock = (IoErr | (9 << 8)), + /// + /// A file delete operation failed. + /// + IoErr_Delete = (IoErr | (10 << 8)), + /// + /// Not currently used. + /// + IoErr_Blocked = (IoErr | (11 << 8)), + /// + /// Out-of-memory during a file operation. + /// + IoErr_NoMem = (IoErr | (12 << 8)), + /// + /// A file existence/status operation failed. + /// + IoErr_Access = (IoErr | (13 << 8)), + /// + /// A check for a reserved lock failed. + /// + IoErr_CheckReservedLock = (IoErr | (14 << 8)), + /// + /// A file lock operation failed. + /// + IoErr_Lock = (IoErr | (15 << 8)), + /// + /// A file close operation failed. + /// + IoErr_Close = (IoErr | (16 << 8)), + /// + /// A directory close operation failed. + /// + IoErr_Dir_Close = (IoErr | (17 << 8)), + /// + /// A shared memory open operation failed. + /// + IoErr_ShmOpen = (IoErr | (18 << 8)), + /// + /// A shared memory size operation failed. + /// + IoErr_ShmSize = (IoErr | (19 << 8)), + /// + /// A shared memory lock operation failed. + /// + IoErr_ShmLock = (IoErr | (20 << 8)), + /// + /// A shared memory map operation failed. + /// + IoErr_ShmMap = (IoErr | (21 << 8)), + /// + /// A file seek operation failed. + /// + IoErr_Seek = (IoErr | (22 << 8)), + /// + /// A file delete operation failed because it does not exist. + /// + IoErr_Delete_NoEnt = (IoErr | (23 << 8)), + /// + /// A file memory mapping operation failed. + /// + IoErr_Mmap = (IoErr | (24 << 8)), + /// + /// The temporary directory path could not be obtained. + /// + IoErr_GetTempPath = (IoErr | (25 << 8)), + /// + /// A path string conversion operation failed. + /// + IoErr_ConvPath = (IoErr | (26 << 8)), + /// + /// Reserved. + /// + IoErr_VNode = (IoErr | (27 << 8)), + /// + /// An attempt to authenticate failed. + /// + IoErr_Auth = (IoErr | (28 << 8)), + /// + /// An attempt to begin a file system transaction failed. + /// + IoErr_Begin_Atomic = (IoErr | (29 << 8)), + /// + /// An attempt to commit a file system transaction failed. + /// + IoErr_Commit_Atomic = (IoErr | (30 << 8)), + /// + /// An attempt to rollback a file system transaction failed. + /// + IoErr_Rollback_Atomic = (IoErr | (31 << 8)), + /// + /// A database table is locked in shared-cache mode. + /// + Locked_SharedCache = (Locked | (1 << 8)), + /// + /// A virtual table in the database is locked. + /// + Locked_Vtab = (Locked | (2 << 8)), + /// + /// A database file is locked due to a recovery operation. + /// + Busy_Recovery = (Busy | (1 << 8)), + /// + /// A database file is locked due to snapshot semantics. + /// + Busy_Snapshot = (Busy | (2 << 8)), + /// + /// A database file cannot be opened because no temporary directory is available. + /// + CantOpen_NoTempDir = (CantOpen | (1 << 8)), + /// + /// A database file cannot be opened because its path represents a directory. + /// + CantOpen_IsDir = (CantOpen | (2 << 8)), + /// + /// A database file cannot be opened because its full path could not be obtained. + /// + CantOpen_FullPath = (CantOpen | (3 << 8)), + /// + /// A database file cannot be opened because a path string conversion operation failed. + /// + CantOpen_ConvPath = (CantOpen | (4 << 8)), + /// + /// A virtual table is malformed. + /// + Corrupt_Vtab = (Corrupt | (1 << 8)), + /// + /// A required sequence table is missing or corrupt. + /// + Corrupt_Sequence = (Corrupt | (2 << 8)), + /// + /// A database file is read-only due to a recovery operation. + /// + ReadOnly_Recovery = (ReadOnly | (1 << 8)), + /// + /// A database file is read-only because a lock could not be obtained. + /// + ReadOnly_CantLock = (ReadOnly | (2 << 8)), + /// + /// A database file is read-only because it needs rollback processing. + /// + ReadOnly_Rollback = (ReadOnly | (3 << 8)), + /// + /// A database file is read-only because it was moved while open. + /// + ReadOnly_DbMoved = (ReadOnly | (4 << 8)), + /// + /// The shared-memory file is read-only and it should be read-write. + /// + ReadOnly_CantInit = (ReadOnly | (5 << 8)), + /// + /// Unable to create journal file because the directory is read-only. + /// + ReadOnly_Directory = (ReadOnly | (6 << 8)), + /// + /// An operation is being aborted due to rollback processing. + /// + Abort_Rollback = (Abort | (2 << 8)), + /// + /// A CHECK constraint failed. + /// + Constraint_Check = (Constraint | (1 << 8)), + /// + /// A commit hook produced a unsuccessful return code. + /// + Constraint_CommitHook = (Constraint | (2 << 8)), + /// + /// A FOREIGN KEY constraint failed. + /// + Constraint_ForeignKey = (Constraint | (3 << 8)), + /// + /// Not currently used. + /// + Constraint_Function = (Constraint | (4 << 8)), + /// + /// A NOT NULL constraint failed. + /// + Constraint_NotNull = (Constraint | (5 << 8)), + /// + /// A PRIMARY KEY constraint failed. + /// + Constraint_PrimaryKey = (Constraint | (6 << 8)), + /// + /// The RAISE function was used by a trigger-program. + /// + Constraint_Trigger = (Constraint | (7 << 8)), + /// + /// A UNIQUE constraint failed. + /// + Constraint_Unique = (Constraint | (8 << 8)), + /// + /// Not currently used. + /// + Constraint_Vtab = (Constraint | (9 << 8)), + /// + /// A ROWID constraint failed. + /// + Constraint_RowId = (Constraint | (10 << 8)), + /// + /// Frames were recovered from the WAL log file. + /// + Notice_Recover_Wal = (Notice | (1 << 8)), + /// + /// Pages were recovered from the journal file. + /// + Notice_Recover_Rollback = (Notice | (2 << 8)), + /// + /// An automatic index was created to process a query. + /// + Warning_AutoIndex = (Warning | (1 << 8)), + /// + /// User authentication failed. + /// + Auth_User = (Auth | (1 << 8)), + /// + /// Success. Prevents the extension from unloading until the process + /// terminates. + /// + Ok_Load_Permanently = (Ok | (1 << 8)) + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteFactory.cs b/Native.Csharp.Tool/SQLite/SQLiteFactory.cs new file mode 100644 index 00000000..db2d7487 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteFactory.cs @@ -0,0 +1,169 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Data.Common; + +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// SQLite implementation of . + /// + public sealed partial class SQLiteFactory : DbProviderFactory, IDisposable + { + /// + /// Constructs a new instance. + /// + public SQLiteFactory() + { + // + // NOTE: Do nothing here now. All the logging setup related code has + // been moved to the new SQLiteLog static class. + // + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Cleans up resources (native and managed) associated with the current instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteFactory).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private void Dispose(bool disposing) + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Destructor + /// + /// Cleans up resources associated with the current instance. + /// + ~SQLiteFactory() + { + Dispose(false); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// This event is raised whenever SQLite raises a logging event. + /// Note that this should be set as one of the first things in the + /// application. This event is provided for backward compatibility only. + /// New code should use the class instead. + /// + public event SQLiteLogEventHandler Log + { + add { CheckDisposed(); SQLiteLog.Log += value; } + remove { CheckDisposed(); SQLiteLog.Log -= value; } + } + + /// + /// Static instance member which returns an instanced class. + /// + public static readonly SQLiteFactory Instance = new SQLiteFactory(); + + /// + /// Creates and returns a new object. + /// + /// The new object. + public override DbCommand CreateCommand() + { + CheckDisposed(); + return new SQLiteCommand(); + } + + /// + /// Creates and returns a new object. + /// + /// The new object. + public override DbCommandBuilder CreateCommandBuilder() + { + CheckDisposed(); + return new SQLiteCommandBuilder(); + } + + /// + /// Creates and returns a new object. + /// + /// The new object. + public override DbConnection CreateConnection() + { + CheckDisposed(); + return new SQLiteConnection(); + } + + /// + /// Creates and returns a new object. + /// + /// The new object. + public override DbConnectionStringBuilder CreateConnectionStringBuilder() + { + CheckDisposed(); + return new SQLiteConnectionStringBuilder(); + } + + /// + /// Creates and returns a new object. + /// + /// The new object. + public override DbDataAdapter CreateDataAdapter() + { + CheckDisposed(); + return new SQLiteDataAdapter(); + } + + /// + /// Creates and returns a new object. + /// + /// The new object. + public override DbParameter CreateParameter() + { + CheckDisposed(); + return new SQLiteParameter(); + } + } +#endif +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteFunction.cs b/Native.Csharp.Tool/SQLite/SQLiteFunction.cs new file mode 100644 index 00000000..ab4f5b90 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteFunction.cs @@ -0,0 +1,1948 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Collections.Generic; + using System.Runtime.InteropServices; + using System.Globalization; + + /// + /// This abstract class is designed to handle user-defined functions easily. An instance of the derived class is made for each + /// connection to the database. + /// + /// + /// Although there is one instance of a class derived from SQLiteFunction per database connection, the derived class has no access + /// to the underlying connection. This is necessary to deter implementers from thinking it would be a good idea to make database + /// calls during processing. + /// + /// It is important to distinguish between a per-connection instance, and a per-SQL statement context. One instance of this class + /// services all SQL statements being stepped through on that connection, and there can be many. One should never store per-statement + /// information in member variables of user-defined function classes. + /// + /// For aggregate functions, always create and store your per-statement data in the contextData object on the 1st step. This data will + /// be automatically freed for you (and Dispose() called if the item supports IDisposable) when the statement completes. + /// + public abstract class SQLiteFunction : IDisposable + { + private class AggregateData + { + internal int _count = 1; + internal object _data; + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// The base connection this function is attached to + /// + internal SQLiteBase _base; + + /// + /// Internal array used to keep track of aggregate function context data + /// + private Dictionary _contextDataList; + + /// + /// The connection flags associated with this object (this should be the + /// same value as the flags associated with the parent connection object). + /// + private SQLiteConnectionFlags _flags; + + /// + /// Holds a reference to the callback function for user functions + /// + private SQLiteCallback _InvokeFunc; + /// + /// Holds a reference to the callbakc function for stepping in an aggregate function + /// + private SQLiteCallback _StepFunc; + /// + /// Holds a reference to the callback function for finalizing an aggregate function + /// + private SQLiteFinalCallback _FinalFunc; + /// + /// Holds a reference to the callback function for collating sequences + /// + private SQLiteCollation _CompareFunc; + + private SQLiteCollation _CompareFunc16; + + /// + /// Current context of the current callback. Only valid during a callback + /// + internal IntPtr _context; + + /// + /// This static dictionary contains all the registered (known) user-defined + /// functions declared using the proper attributes. The contained dictionary + /// values are always null and are not currently used. + /// + private static IDictionary _registeredFunctions; + + /// + /// Internal constructor, initializes the function's internal variables. + /// + protected SQLiteFunction() + { + _contextDataList = new Dictionary(); + } + + /// + /// Constructs an instance of this class using the specified data-type + /// conversion parameters. + /// + /// + /// The DateTime format to be used when converting string values to a + /// DateTime and binding DateTime parameters. + /// + /// + /// The to be used when creating DateTime + /// values. + /// + /// + /// The format string to be used when parsing and formatting DateTime + /// values. + /// + /// + /// Non-zero to create a UTF-16 data-type conversion context; otherwise, + /// a UTF-8 data-type conversion context will be created. + /// + protected SQLiteFunction( + SQLiteDateFormats format, + DateTimeKind kind, + string formatString, + bool utf16 + ) + : this() + { + if (utf16) + _base = new SQLite3_UTF16(format, kind, formatString, IntPtr.Zero, null, false); + else + _base = new SQLite3(format, kind, formatString, IntPtr.Zero, null, false); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes of any active contextData variables that were not automatically cleaned up. Sometimes this can happen if + /// someone closes the connection while a DataReader is open. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteFunction).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Placeholder for a user-defined disposal routine + /// + /// True if the object is being disposed explicitly + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + IDisposable disp; + + foreach (KeyValuePair kv in _contextDataList) + { + disp = kv.Value._data as IDisposable; + if (disp != null) + disp.Dispose(); + } + _contextDataList.Clear(); + _contextDataList = null; + + _flags = SQLiteConnectionFlags.None; + + _InvokeFunc = null; + _StepFunc = null; + _FinalFunc = null; + _CompareFunc = null; + _base = null; + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Destructor + /// + /// Cleans up resources associated with the current instance. + /// + ~SQLiteFunction() + { + Dispose(false); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Returns a reference to the underlying connection's SQLiteConvert class, which can be used to convert + /// strings and DateTime's into the current connection's encoding schema. + /// + public SQLiteConvert SQLiteConvert + { + get + { + CheckDisposed(); + return _base; + } + } + + /// + /// Scalar functions override this method to do their magic. + /// + /// + /// Parameters passed to functions have only an affinity for a certain data type, there is no underlying schema available + /// to force them into a certain type. Therefore the only types you will ever see as parameters are + /// DBNull.Value, Int64, Double, String or byte[] array. + /// + /// The arguments for the command to process + /// You may return most simple types as a return value, null or DBNull.Value to return null, DateTime, or + /// you may return an Exception-derived class if you wish to return an error to SQLite. Do not actually throw the error, + /// just return it! + public virtual object Invoke(object[] args) + { + CheckDisposed(); + return null; + } + + /// + /// Aggregate functions override this method to do their magic. + /// + /// + /// Typically you'll be updating whatever you've placed in the contextData field and returning as quickly as possible. + /// + /// The arguments for the command to process + /// The 1-based step number. This is incrememted each time the step method is called. + /// A placeholder for implementers to store contextual data pertaining to the current context. + public virtual void Step(object[] args, int stepNumber, ref object contextData) + { + CheckDisposed(); + } + + /// + /// Aggregate functions override this method to finish their aggregate processing. + /// + /// + /// If you implemented your aggregate function properly, + /// you've been recording and keeping track of your data in the contextData object provided, and now at this stage you should have + /// all the information you need in there to figure out what to return. + /// NOTE: It is possible to arrive here without receiving a previous call to Step(), in which case the contextData will + /// be null. This can happen when no rows were returned. You can either return null, or 0 or some other custom return value + /// if that is the case. + /// + /// Your own assigned contextData, provided for you so you can return your final results. + /// You may return most simple types as a return value, null or DBNull.Value to return null, DateTime, or + /// you may return an Exception-derived class if you wish to return an error to SQLite. Do not actually throw the error, + /// just return it! + /// + public virtual object Final(object contextData) + { + CheckDisposed(); + return null; + } + + /// + /// User-defined collating sequences override this method to provide a custom string sorting algorithm. + /// + /// The first string to compare. + /// The second strnig to compare. + /// 1 if param1 is greater than param2, 0 if they are equal, or -1 if param1 is less than param2. + public virtual int Compare(string param1, string param2) + { + CheckDisposed(); + return 0; + } + + /// + /// Converts an IntPtr array of context arguments to an object array containing the resolved parameters the pointers point to. + /// + /// + /// Parameters passed to functions have only an affinity for a certain data type, there is no underlying schema available + /// to force them into a certain type. Therefore the only types you will ever see as parameters are + /// DBNull.Value, Int64, Double, String or byte[] array. + /// + /// The number of arguments + /// A pointer to the array of arguments + /// An object array of the arguments once they've been converted to .NET values + internal object[] ConvertParams(int nArgs, IntPtr argsptr) + { + object[] parms = new object[nArgs]; +#if !PLATFORM_COMPACTFRAMEWORK + IntPtr[] argint = new IntPtr[nArgs]; +#else + int[] argint = new int[nArgs]; +#endif + Marshal.Copy(argsptr, argint, 0, nArgs); + + for (int n = 0; n < nArgs; n++) + { + switch (_base.GetParamValueType((IntPtr)argint[n])) + { + case TypeAffinity.Null: + parms[n] = DBNull.Value; + break; + case TypeAffinity.Int64: + parms[n] = _base.GetParamValueInt64((IntPtr)argint[n]); + break; + case TypeAffinity.Double: + parms[n] = _base.GetParamValueDouble((IntPtr)argint[n]); + break; + case TypeAffinity.Text: + parms[n] = _base.GetParamValueText((IntPtr)argint[n]); + break; + case TypeAffinity.Blob: + { + int x; + byte[] blob; + + x = (int)_base.GetParamValueBytes((IntPtr)argint[n], 0, null, 0, 0); + blob = new byte[x]; + _base.GetParamValueBytes((IntPtr)argint[n], 0, blob, 0, x); + parms[n] = blob; + } + break; + case TypeAffinity.DateTime: // Never happens here but what the heck, maybe it will one day. + parms[n] = _base.ToDateTime(_base.GetParamValueText((IntPtr)argint[n])); + break; + } + } + return parms; + } + + /// + /// Takes the return value from Invoke() and Final() and figures out how to return it to SQLite's context. + /// + /// The context the return value applies to + /// The parameter to return to SQLite + private void SetReturnValue(IntPtr context, object returnValue) + { + if (returnValue == null || returnValue == DBNull.Value) + { + _base.ReturnNull(context); + return; + } + + Type t = returnValue.GetType(); + if (t == typeof(DateTime)) + { + _base.ReturnText(context, _base.ToString((DateTime)returnValue)); + return; + } + else + { + Exception r = returnValue as Exception; + + if (r != null) + { + _base.ReturnError(context, r.Message); + return; + } + } + + switch (SQLiteConvert.TypeToAffinity(t, _flags)) + { + case TypeAffinity.Null: + _base.ReturnNull(context); + return; + case TypeAffinity.Int64: + _base.ReturnInt64(context, Convert.ToInt64(returnValue, CultureInfo.CurrentCulture)); + return; + case TypeAffinity.Double: + _base.ReturnDouble(context, Convert.ToDouble(returnValue, CultureInfo.CurrentCulture)); + return; + case TypeAffinity.Text: + _base.ReturnText(context, returnValue.ToString()); + return; + case TypeAffinity.Blob: + _base.ReturnBlob(context, (byte[])returnValue); + return; + } + } + + /// + /// Internal scalar callback function, which wraps the raw context pointer and calls the virtual Invoke() method. + /// WARNING: Must not throw exceptions. + /// + /// A raw context pointer + /// Number of arguments passed in + /// A pointer to the array of arguments + internal void ScalarCallback(IntPtr context, int nArgs, IntPtr argsptr) + { + try + { + _context = context; + SetReturnValue(context, + Invoke(ConvertParams(nArgs, argsptr))); /* throw */ + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + try + { + if (HelperMethods.LogCallbackExceptions(_flags)) + { + SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat(CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "Invoke", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } + } + + /// + /// Internal collating sequence function, which wraps up the raw string pointers and executes the Compare() virtual function. + /// WARNING: Must not throw exceptions. + /// + /// Not used + /// Length of the string pv1 + /// Pointer to the first string to compare + /// Length of the string pv2 + /// Pointer to the second string to compare + /// Returns -1 if the first string is less than the second. 0 if they are equal, or 1 if the first string is greater + /// than the second. Returns 0 if an exception is caught. + internal int CompareCallback(IntPtr ptr, int len1, IntPtr ptr1, int len2, IntPtr ptr2) + { + try + { + return Compare(SQLiteConvert.UTF8ToString(ptr1, len1), + SQLiteConvert.UTF8ToString(ptr2, len2)); /* throw */ + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + try + { + if (HelperMethods.LogCallbackExceptions(_flags)) + { + SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat(CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "Compare", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } + + // + // NOTE: This must be done to prevent the core SQLite library from + // using our (invalid) result. + // + if ((_base != null) && _base.IsOpen()) + _base.Cancel(); + + return 0; + } + + /// + /// Internal collating sequence function, which wraps up the raw string pointers and executes the Compare() virtual function. + /// WARNING: Must not throw exceptions. + /// + /// Not used + /// Length of the string pv1 + /// Pointer to the first string to compare + /// Length of the string pv2 + /// Pointer to the second string to compare + /// Returns -1 if the first string is less than the second. 0 if they are equal, or 1 if the first string is greater + /// than the second. Returns 0 if an exception is caught. + internal int CompareCallback16(IntPtr ptr, int len1, IntPtr ptr1, int len2, IntPtr ptr2) + { + try + { + return Compare(SQLite3_UTF16.UTF16ToString(ptr1, len1), + SQLite3_UTF16.UTF16ToString(ptr2, len2)); /* throw */ + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + try + { + if (HelperMethods.LogCallbackExceptions(_flags)) + { + SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat(CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "Compare (UTF16)", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } + + // + // NOTE: This must be done to prevent the core SQLite library from + // using our (invalid) result. + // + if ((_base != null) && _base.IsOpen()) + _base.Cancel(); + + return 0; + } + + /// + /// The internal aggregate Step function callback, which wraps the raw context pointer and calls the virtual Step() method. + /// WARNING: Must not throw exceptions. + /// + /// + /// This function takes care of doing the lookups and getting the important information put together to call the Step() function. + /// That includes pulling out the user's contextData and updating it after the call is made. We use a sorted list for this so + /// binary searches can be done to find the data. + /// + /// A raw context pointer + /// Number of arguments passed in + /// A pointer to the array of arguments + internal void StepCallback(IntPtr context, int nArgs, IntPtr argsptr) + { + try + { + AggregateData data = null; + + if (_base != null) + { + IntPtr nAux = _base.AggregateContext(context); + + if ((_contextDataList != null) && + !_contextDataList.TryGetValue(nAux, out data)) + { + data = new AggregateData(); + _contextDataList[nAux] = data; + } + } + + if (data == null) + data = new AggregateData(); + + try + { + _context = context; + Step(ConvertParams(nArgs, argsptr), + data._count, ref data._data); /* throw */ + } + finally + { + data._count++; + } + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + try + { + if (HelperMethods.LogCallbackExceptions(_flags)) + { + SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat(CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "Step", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } + } + + /// + /// An internal aggregate Final function callback, which wraps the context pointer and calls the virtual Final() method. + /// WARNING: Must not throw exceptions. + /// + /// A raw context pointer + internal void FinalCallback(IntPtr context) + { + try + { + object obj = null; + + if (_base != null) + { + IntPtr n = _base.AggregateContext(context); + AggregateData aggData; + + if ((_contextDataList != null) && + _contextDataList.TryGetValue(n, out aggData)) + { + obj = aggData._data; + _contextDataList.Remove(n); + } + } + + try + { + _context = context; + SetReturnValue(context, Final(obj)); /* throw */ + } + finally + { + IDisposable disp = obj as IDisposable; + if (disp != null) disp.Dispose(); /* throw */ + } + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + try + { + if (HelperMethods.LogCallbackExceptions(_flags)) + { + SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat(CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "Final", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } + } + + /// + /// Using reflection, enumerate all assemblies in the current appdomain looking for classes that + /// have a SQLiteFunctionAttribute attribute, and registering them accordingly. + /// +#if !PLATFORM_COMPACTFRAMEWORK && !NET_STANDARD_20 + [Security.Permissions.FileIOPermission(Security.Permissions.SecurityAction.Assert, AllFiles = Security.Permissions.FileIOPermissionAccess.PathDiscovery)] +#endif + static SQLiteFunction() + { + _registeredFunctions = new Dictionary(); + try + { +#if !PLATFORM_COMPACTFRAMEWORK + // + // NOTE: If the "No_SQLiteFunctions" environment variable is set, + // skip all our special code and simply return. + // + if (UnsafeNativeMethods.GetSettingValue("No_SQLiteFunctions", null) != null) + return; + + SQLiteFunctionAttribute at; + System.Reflection.Assembly[] arAssemblies = System.AppDomain.CurrentDomain.GetAssemblies(); + int w = arAssemblies.Length; + System.Reflection.AssemblyName sqlite = System.Reflection.Assembly.GetExecutingAssembly().GetName(); + + for (int n = 0; n < w; n++) + { + Type[] arTypes; + bool found = false; + System.Reflection.AssemblyName[] references; + try + { + // Inspect only assemblies that reference SQLite + references = arAssemblies[n].GetReferencedAssemblies(); + int t = references.Length; + for (int z = 0; z < t; z++) + { + if (references[z].Name == sqlite.Name) + { + found = true; + break; + } + } + + if (found == false) + continue; + + arTypes = arAssemblies[n].GetTypes(); + } + catch (Reflection.ReflectionTypeLoadException e) + { + arTypes = e.Types; + } + + int v = arTypes.Length; + for (int x = 0; x < v; x++) + { + if (arTypes[x] == null) continue; + + object[] arAtt = arTypes[x].GetCustomAttributes(typeof(SQLiteFunctionAttribute), false); + int u = arAtt.Length; + for (int y = 0; y < u; y++) + { + at = arAtt[y] as SQLiteFunctionAttribute; + if (at != null) + { + at.InstanceType = arTypes[x]; + ReplaceFunction(at, null); + } + } + } + } +#endif + } + catch // SQLite provider can continue without being able to find built-in functions + { + } + } + + /// + /// Manual method of registering a function. The type must still have the SQLiteFunctionAttributes in order to work + /// properly, but this is a workaround for the Compact Framework where enumerating assemblies is not currently supported. + /// + /// The type of the function to register + public static void RegisterFunction(Type typ) + { + object[] arAtt = typ.GetCustomAttributes( + typeof(SQLiteFunctionAttribute), false); + + for (int y = 0; y < arAtt.Length; y++) + { + SQLiteFunctionAttribute at = arAtt[y] as SQLiteFunctionAttribute; + + if (at == null) + continue; + + RegisterFunction( + at.Name, at.Arguments, at.FuncType, typ, + at.Callback1, at.Callback2); + } + } + + /// + /// Alternative method of registering a function. This method + /// does not require the specified type to be annotated with + /// . + /// + /// + /// The name of the function to register. + /// + /// + /// The number of arguments accepted by the function. + /// + /// + /// The type of SQLite function being resitered (e.g. scalar, + /// aggregate, or collating sequence). + /// + /// + /// The that actually implements the function. + /// This will only be used if the + /// and parameters are null. + /// + /// + /// The to be used for all calls into the + /// , + /// , + /// and virtual methods. + /// + /// + /// The to be used for all calls into the + /// virtual method. This + /// parameter is only necessary for aggregate functions. + /// + public static void RegisterFunction( + string name, + int argumentCount, + FunctionType functionType, + Type instanceType, + Delegate callback1, + Delegate callback2 + ) + { + SQLiteFunctionAttribute at = new SQLiteFunctionAttribute( + name, argumentCount, functionType); + + at.InstanceType = instanceType; + at.Callback1 = callback1; + at.Callback2 = callback2; + + ReplaceFunction(at, null); + } + + /// + /// Replaces a registered function, disposing of the associated (old) + /// value if necessary. + /// + /// + /// The attribute that describes the function to replace. + /// + /// + /// The new value to use. + /// + /// + /// Non-zero if an existing registered function was replaced; otherwise, + /// zero. + /// + private static bool ReplaceFunction( + SQLiteFunctionAttribute at, + object newValue + ) + { + object oldValue; + + if (_registeredFunctions.TryGetValue(at, out oldValue)) + { + IDisposable disposable = oldValue as IDisposable; + + if (disposable != null) + { + disposable.Dispose(); + disposable = null; + } + + _registeredFunctions[at] = newValue; + return true; + } + else + { + _registeredFunctions.Add(at, newValue); + return false; + } + } + + /// + /// Creates a instance based on the specified + /// . + /// + /// + /// The containing the metadata about + /// the function to create. + /// + /// + /// The created function -OR- null if the function could not be created. + /// + /// + /// Non-zero if the function was created; otherwise, zero. + /// + private static bool CreateFunction( + SQLiteFunctionAttribute functionAttribute, + out SQLiteFunction function + ) + { + if (functionAttribute == null) + { + function = null; + return false; + } + else if ((functionAttribute.Callback1 != null) || + (functionAttribute.Callback2 != null)) + { + function = new SQLiteDelegateFunction( + functionAttribute.Callback1, + functionAttribute.Callback2); + + return true; + } + else if (functionAttribute.InstanceType != null) + { + function = (SQLiteFunction)Activator.CreateInstance( + functionAttribute.InstanceType); + + return true; + } + else + { + function = null; + return false; + } + } + + /// + /// Called by the SQLiteBase derived classes, this method binds all registered (known) user-defined functions to a connection. + /// It is done this way so that all user-defined functions will access the database using the same encoding scheme + /// as the connection (UTF-8 or UTF-16). + /// + /// + /// The wrapper functions that interop with SQLite will create a unique cookie value, which internally is a pointer to + /// all the wrapped callback functions. The interop function uses it to map CDecl callbacks to StdCall callbacks. + /// + /// The base object on which the functions are to bind. + /// The flags associated with the parent connection object. + /// Returns a logical list of functions which the connection should retain until it is closed. + internal static IDictionary BindFunctions( + SQLiteBase sqlbase, + SQLiteConnectionFlags flags + ) + { + IDictionary lFunctions = + new Dictionary(); + + foreach (KeyValuePair pair + in _registeredFunctions) + { + SQLiteFunctionAttribute pr = pair.Key; + + if (pr == null) + continue; + + SQLiteFunction f; + + if (CreateFunction(pr, out f)) + { + BindFunction(sqlbase, pr, f, flags); + lFunctions[pr] = f; + } + else + { + lFunctions[pr] = null; + } + } + + return lFunctions; + } + + /// + /// Called by the SQLiteBase derived classes, this method unbinds all registered (known) + /// functions -OR- all previously bound user-defined functions from a connection. + /// + /// The base object from which the functions are to be unbound. + /// The flags associated with the parent connection object. + /// + /// Non-zero to unbind all registered (known) functions -OR- zero to unbind all functions + /// currently bound to the connection. + /// + /// Non-zero if all the specified user-defined functions were unbound. + internal static bool UnbindAllFunctions( + SQLiteBase sqlbase, + SQLiteConnectionFlags flags, + bool registered + ) + { + if (sqlbase == null) + return false; + + IDictionary lFunctions = + sqlbase.Functions; + + if (lFunctions == null) + return false; + + bool result = true; + + if (registered) + { + foreach (KeyValuePair pair + in _registeredFunctions) + { + SQLiteFunctionAttribute pr = pair.Key; + + if (pr == null) + continue; + + SQLiteFunction f; + + if (!lFunctions.TryGetValue(pr, out f) || + (f == null) || + !UnbindFunction(sqlbase, pr, f, flags)) + { + result = false; + } + } + } + else + { + // + // NOTE: Need to use a copy of the function dictionary in this method + // because the dictionary is modified within the UnbindFunction + // method, which is called inside the loop. + // + lFunctions = new Dictionary( + lFunctions); + + foreach (KeyValuePair pair + in lFunctions) + { + SQLiteFunctionAttribute pr = pair.Key; + + if (pr == null) + continue; + + SQLiteFunction f = pair.Value; + + if ((f != null) && + UnbindFunction(sqlbase, pr, f, flags)) + { + /* IGNORED */ + sqlbase.Functions.Remove(pr); + } + else + { + result = false; + } + } + } + + return result; + } + + /// + /// This function binds a user-defined function to a connection. + /// + /// + /// The object instance associated with the + /// that the function should be bound to. + /// + /// + /// The object instance containing + /// the metadata for the function to be bound. + /// + /// + /// The object instance that implements the + /// function to be bound. + /// + /// + /// The flags associated with the parent connection object. + /// + internal static void BindFunction( + SQLiteBase sqliteBase, + SQLiteFunctionAttribute functionAttribute, + SQLiteFunction function, + SQLiteConnectionFlags flags + ) + { + if (sqliteBase == null) + throw new ArgumentNullException("sqliteBase"); + + if (functionAttribute == null) + throw new ArgumentNullException("functionAttribute"); + + if (function == null) + throw new ArgumentNullException("function"); + + FunctionType functionType = functionAttribute.FuncType; + + function._base = sqliteBase; + function._flags = flags; + + function._InvokeFunc = (functionType == FunctionType.Scalar) ? + new SQLiteCallback(function.ScalarCallback) : null; + + function._StepFunc = (functionType == FunctionType.Aggregate) ? + new SQLiteCallback(function.StepCallback) : null; + + function._FinalFunc = (functionType == FunctionType.Aggregate) ? + new SQLiteFinalCallback(function.FinalCallback) : null; + + function._CompareFunc = (functionType == FunctionType.Collation) ? + new SQLiteCollation(function.CompareCallback) : null; + + function._CompareFunc16 = (functionType == FunctionType.Collation) ? + new SQLiteCollation(function.CompareCallback16) : null; + + string name = functionAttribute.Name; + + if (functionType != FunctionType.Collation) + { + bool needCollSeq = (function is SQLiteFunctionEx); + + sqliteBase.CreateFunction( + name, functionAttribute.Arguments, needCollSeq, + function._InvokeFunc, function._StepFunc, + function._FinalFunc, true); + } + else + { + sqliteBase.CreateCollation( + name, function._CompareFunc, function._CompareFunc16, + true); + } + } + + /// + /// This function unbinds a user-defined functions from a connection. + /// + /// + /// The object instance associated with the + /// that the function should be bound to. + /// + /// + /// The object instance containing + /// the metadata for the function to be bound. + /// + /// + /// The object instance that implements the + /// function to be bound. + /// + /// + /// The flags associated with the parent connection object. + /// + /// Non-zero if the function was unbound. + internal static bool UnbindFunction( + SQLiteBase sqliteBase, + SQLiteFunctionAttribute functionAttribute, + SQLiteFunction function, + SQLiteConnectionFlags flags /* NOT USED */ + ) + { + if (sqliteBase == null) + throw new ArgumentNullException("sqliteBase"); + + if (functionAttribute == null) + throw new ArgumentNullException("functionAttribute"); + + if (function == null) + throw new ArgumentNullException("function"); + + FunctionType functionType = functionAttribute.FuncType; + string name = functionAttribute.Name; + + if (functionType != FunctionType.Collation) + { + bool needCollSeq = (function is SQLiteFunctionEx); + + return sqliteBase.CreateFunction( + name, functionAttribute.Arguments, needCollSeq, + null, null, null, false) == SQLiteErrorCode.Ok; + } + else + { + return sqliteBase.CreateCollation( + name, null, null, false) == SQLiteErrorCode.Ok; + } + } + } + + ///////////////////////////////////////////////////////////////////////////// + + /// + /// This type is used with the + /// method. + /// + /// + /// This is always the string literal "Invoke". + /// + /// + /// The arguments for the scalar function. + /// + /// + /// The result of the scalar function. + /// + public delegate object SQLiteInvokeDelegate( + string param0, + object[] args + ); + + ///////////////////////////////////////////////////////////////////////////// + + /// + /// This type is used with the + /// method. + /// + /// + /// This is always the string literal "Step". + /// + /// + /// The arguments for the aggregate function. + /// + /// + /// The step number (one based). This is incrememted each time the + /// method is called. + /// + /// + /// A placeholder for implementers to store contextual data pertaining + /// to the current context. + /// + public delegate void SQLiteStepDelegate( + string param0, + object[] args, + int stepNumber, + ref object contextData + ); + + ///////////////////////////////////////////////////////////////////////////// + + /// + /// This type is used with the + /// method. + /// + /// + /// This is always the string literal "Final". + /// + /// + /// A placeholder for implementers to store contextual data pertaining + /// to the current context. + /// + /// + /// The result of the aggregate function. + /// + public delegate object SQLiteFinalDelegate( + string param0, + object contextData + ); + + ///////////////////////////////////////////////////////////////////////////// + + /// + /// This type is used with the + /// method. + /// + /// + /// This is always the string literal "Compare". + /// + /// + /// The first string to compare. + /// + /// + /// The second strnig to compare. + /// + /// + /// A positive integer if the parameter is + /// greater than the parameter, a negative + /// integer if the parameter is less than + /// the parameter, or zero if they are + /// equal. + /// + public delegate int SQLiteCompareDelegate( + string param0, + string param1, + string param2 + ); + + ///////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// This class implements a SQLite function using a . + /// All the virtual methods of the class are + /// implemented using calls to the , + /// , , + /// and strongly typed delegate types + /// or via the method. + /// The arguments are presented in the same order they appear in + /// the associated methods with one exception: + /// the first argument is the name of the virtual method being implemented. + /// +#else + /// + /// This class implements a SQLite function using a . + /// All the virtual methods of the class are + /// implemented using calls to the , + /// , , + /// and strongly typed delegate types. + /// The arguments are presented in the same order they appear in + /// the associated methods with one exception: + /// the first argument is the name of the virtual method being implemented. + /// +#endif + public class SQLiteDelegateFunction : SQLiteFunction + { + #region Private Constants + /// + /// This error message is used by the overridden virtual methods when + /// a required property (e.g. + /// or ) has not been + /// set. + /// + private const string NoCallbackError = "No \"{0}\" callback is set."; + + ///////////////////////////////////////////////////////////////////////// + + /// + /// This error message is used by the overridden + /// method when the result does not have a type of . + /// + private const string ResultInt32Error = "\"{0}\" result must be Int32."; + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs an empty instance of this class. + /// + public SQLiteDelegateFunction() + : this(null, null) + { + // do nothing. + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Constructs an instance of this class using the specified + /// as the + /// implementation. + /// + /// + /// The to be used for all calls into the + /// , , and + /// virtual methods needed by the + /// base class. + /// + /// + /// The to be used for all calls into the + /// virtual methods needed by the + /// base class. + /// + public SQLiteDelegateFunction( + Delegate callback1, + Delegate callback2 + ) + { + this.callback1 = callback1; + this.callback2 = callback2; + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Protected Methods + /// + /// Returns the list of arguments for the method, + /// as an of . The first + /// argument is always the literal string "Invoke". + /// + /// + /// The original arguments received by the method. + /// + /// + /// Non-zero if the returned arguments are going to be used with the + /// type; otherwise, zero. + /// + /// + /// The arguments to pass to the configured . + /// + protected virtual object[] GetInvokeArgs( + object[] args, + bool earlyBound + ) + { + object[] newArgs = new object[] { "Invoke", args }; + + if (!earlyBound) + newArgs = new object[] { newArgs }; // WRAP + + return newArgs; + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Returns the list of arguments for the method, + /// as an of . The first + /// argument is always the literal string "Step". + /// + /// + /// The original arguments received by the method. + /// + /// + /// The step number (one based). This is incrememted each time the + /// method is called. + /// + /// + /// A placeholder for implementers to store contextual data pertaining + /// to the current context. + /// + /// + /// Non-zero if the returned arguments are going to be used with the + /// type; otherwise, zero. + /// + /// + /// The arguments to pass to the configured . + /// + protected virtual object[] GetStepArgs( + object[] args, + int stepNumber, + object contextData, + bool earlyBound + ) + { + object[] newArgs = new object[] { + "Step", args, stepNumber, contextData + }; + + if (!earlyBound) + newArgs = new object[] { newArgs }; // WRAP + + return newArgs; + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Updates the output arguments for the method, + /// using an of . The first + /// argument is always the literal string "Step". Currently, only the + /// parameter is updated. + /// + /// + /// The original arguments received by the method. + /// + /// + /// A placeholder for implementers to store contextual data pertaining + /// to the current context. + /// + /// + /// Non-zero if the returned arguments are going to be used with the + /// type; otherwise, zero. + /// + /// + /// The arguments to pass to the configured . + /// + protected virtual void UpdateStepArgs( + object[] args, + ref object contextData, + bool earlyBound + ) + { + object[] newArgs; + + if (earlyBound) + newArgs = args; + else + newArgs = args[0] as object[]; + + if (newArgs == null) + return; + + contextData = newArgs[newArgs.Length - 1]; + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Returns the list of arguments for the method, + /// as an of . The first + /// argument is always the literal string "Final". + /// + /// + /// A placeholder for implementers to store contextual data pertaining + /// to the current context. + /// + /// + /// Non-zero if the returned arguments are going to be used with the + /// type; otherwise, zero. + /// + /// + /// The arguments to pass to the configured . + /// + protected virtual object[] GetFinalArgs( + object contextData, + bool earlyBound + ) + { + object[] newArgs = new object[] { "Final", contextData }; + + if (!earlyBound) + newArgs = new object[] { newArgs }; // WRAP + + return newArgs; + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Returns the list of arguments for the method, + /// as an of . The first + /// argument is always the literal string "Compare". + /// + /// + /// The first string to compare. + /// + /// + /// The second strnig to compare. + /// + /// + /// Non-zero if the returned arguments are going to be used with the + /// type; otherwise, zero. + /// + /// + /// The arguments to pass to the configured . + /// + protected virtual object[] GetCompareArgs( + string param1, + string param2, + bool earlyBound + ) + { + object[] newArgs = new object[] { "Compare", param1, param2 }; + + if (!earlyBound) + newArgs = new object[] { newArgs }; // WRAP + + return newArgs; + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Public Properties + private Delegate callback1; + /// + /// The to be used for all calls into the + /// , , and + /// virtual methods needed by the + /// base class. + /// + public virtual Delegate Callback1 + { + get { return callback1; } + set { callback1 = value; } + } + + ///////////////////////////////////////////////////////////////////////// + + private Delegate callback2; + /// + /// The to be used for all calls into the + /// virtual methods needed by the + /// base class. + /// + public virtual Delegate Callback2 + { + get { return callback2; } + set { callback2 = value; } + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region System.Data.SQLite.SQLiteFunction Overrides + /// + /// This virtual method is the implementation for scalar functions. + /// See the method for more + /// details. + /// + /// + /// The arguments for the scalar function. + /// + /// + /// The result of the scalar function. + /// + public override object Invoke( + object[] args /* in */ + ) + { + if (callback1 == null) + { + throw new InvalidOperationException( + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + NoCallbackError, "Invoke")); + } + + SQLiteInvokeDelegate invokeDelegate = + callback1 as SQLiteInvokeDelegate; + + if (invokeDelegate != null) + { + return invokeDelegate.Invoke("Invoke", args); /* throw */ + } + else + { +#if !PLATFORM_COMPACTFRAMEWORK + return callback1.DynamicInvoke( + GetInvokeArgs(args, false)); /* throw */ +#else + throw new NotImplementedException(); +#endif + } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// This virtual method is part of the implementation for aggregate + /// functions. See the method + /// for more details. + /// + /// + /// The arguments for the aggregate function. + /// + /// + /// The step number (one based). This is incrememted each time the + /// method is called. + /// + /// + /// A placeholder for implementers to store contextual data pertaining + /// to the current context. + /// + public override void Step( + object[] args, /* in */ + int stepNumber, /* in */ + ref object contextData /* in, out */ + ) + { + if (callback1 == null) + { + throw new InvalidOperationException( + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + NoCallbackError, "Step")); + } + + SQLiteStepDelegate stepDelegate = callback1 as SQLiteStepDelegate; + + if (stepDelegate != null) + { + stepDelegate.Invoke( + "Step", args, stepNumber, ref contextData); /* throw */ + } + else + { +#if !PLATFORM_COMPACTFRAMEWORK + object[] newArgs = GetStepArgs( + args, stepNumber, contextData, false); + + /* IGNORED */ + callback1.DynamicInvoke(newArgs); /* throw */ + + UpdateStepArgs(newArgs, ref contextData, false); +#else + throw new NotImplementedException(); +#endif + } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// This virtual method is part of the implementation for aggregate + /// functions. See the method + /// for more details. + /// + /// + /// A placeholder for implementers to store contextual data pertaining + /// to the current context. + /// + /// + /// The result of the aggregate function. + /// + public override object Final( + object contextData /* in */ + ) + { + if (callback2 == null) + { + throw new InvalidOperationException( + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + NoCallbackError, "Final")); + } + + SQLiteFinalDelegate finalDelegate = callback2 as SQLiteFinalDelegate; + + if (finalDelegate != null) + { + return finalDelegate.Invoke("Final", contextData); /* throw */ + } + else + { +#if !PLATFORM_COMPACTFRAMEWORK + return callback1.DynamicInvoke(GetFinalArgs( + contextData, false)); /* throw */ +#else + throw new NotImplementedException(); +#endif + } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// This virtual method is part of the implementation for collating + /// sequences. See the method + /// for more details. + /// + /// + /// The first string to compare. + /// + /// + /// The second strnig to compare. + /// + /// + /// A positive integer if the parameter is + /// greater than the parameter, a negative + /// integer if the parameter is less than + /// the parameter, or zero if they are + /// equal. + /// + public override int Compare( + string param1, /* in */ + string param2 /* in */ + ) + { + if (callback1 == null) + { + throw new InvalidOperationException( + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + NoCallbackError, "Compare")); + } + + SQLiteCompareDelegate compareDelegate = + callback1 as SQLiteCompareDelegate; + + if (compareDelegate != null) + { + return compareDelegate.Invoke( + "Compare", param1, param2); /* throw */ + } + else + { +#if !PLATFORM_COMPACTFRAMEWORK + object result = callback1.DynamicInvoke(GetCompareArgs( + param1, param2, false)); /* throw */ + + if (result is int) + return (int)result; + + throw new InvalidOperationException( + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + ResultInt32Error, "Compare")); +#else + throw new NotImplementedException(); +#endif + } + } + #endregion + } + + ///////////////////////////////////////////////////////////////////////////// + + /// + /// Extends SQLiteFunction and allows an inherited class to obtain the collating sequence associated with a function call. + /// + /// + /// User-defined functions can call the GetCollationSequence() method in this class and use it to compare strings and char arrays. + /// + public class SQLiteFunctionEx : SQLiteFunction + { + /// + /// Obtains the collating sequence in effect for the given function. + /// + /// + protected CollationSequence GetCollationSequence() + { + return _base.GetCollationSequence(this, _context); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteFunctionEx).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Cleans up resources (native and managed) associated with the current instance. + /// + /// + /// Zero when being disposed via garbage collection; otherwise, non-zero. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } + + /// + /// The type of user-defined function to declare + /// + public enum FunctionType + { + /// + /// Scalar functions are designed to be called and return a result immediately. Examples include ABS(), Upper(), Lower(), etc. + /// + Scalar = 0, + /// + /// Aggregate functions are designed to accumulate data until the end of a call and then return a result gleaned from the accumulated data. + /// Examples include SUM(), COUNT(), AVG(), etc. + /// + Aggregate = 1, + /// + /// Collating sequences are used to sort textual data in a custom manner, and appear in an ORDER BY clause. Typically text in an ORDER BY is + /// sorted using a straight case-insensitive comparison function. Custom collating sequences can be used to alter the behavior of text sorting + /// in a user-defined manner. + /// + Collation = 2, + } + + /// + /// An internal callback delegate declaration. + /// + /// Raw native context pointer for the user function. + /// Total number of arguments to the user function. + /// Raw native pointer to the array of raw native argument pointers. +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate void SQLiteCallback(IntPtr context, int argc, IntPtr argv); + /// + /// An internal final callback delegate declaration. + /// + /// Raw context pointer for the user function +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate void SQLiteFinalCallback(IntPtr context); + /// + /// Internal callback delegate for implementing collating sequences + /// + /// Not used + /// Length of the string pv1 + /// Pointer to the first string to compare + /// Length of the string pv2 + /// Pointer to the second string to compare + /// Returns -1 if the first string is less than the second. 0 if they are equal, or 1 if the first string is greater + /// than the second. +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate int SQLiteCollation(IntPtr puser, int len1, IntPtr pv1, int len2, IntPtr pv2); + + /// + /// The type of collating sequence + /// + public enum CollationTypeEnum + { + /// + /// The built-in BINARY collating sequence + /// + Binary = 1, + /// + /// The built-in NOCASE collating sequence + /// + NoCase = 2, + /// + /// The built-in REVERSE collating sequence + /// + Reverse = 3, + /// + /// A custom user-defined collating sequence + /// + Custom = 0, + } + + /// + /// The encoding type the collation sequence uses + /// + public enum CollationEncodingEnum + { + /// + /// The collation sequence is UTF8 + /// + UTF8 = 1, + /// + /// The collation sequence is UTF16 little-endian + /// + UTF16LE = 2, + /// + /// The collation sequence is UTF16 big-endian + /// + UTF16BE = 3, + } + + /// + /// A struct describing the collating sequence a function is executing in + /// + public struct CollationSequence + { + /// + /// The name of the collating sequence + /// + public string Name; + /// + /// The type of collating sequence + /// + public CollationTypeEnum Type; + + /// + /// The text encoding of the collation sequence + /// + public CollationEncodingEnum Encoding; + + /// + /// Context of the function that requested the collating sequence + /// + internal SQLiteFunction _func; + + /// + /// Calls the base collating sequence to compare two strings + /// + /// The first string to compare + /// The second string to compare + /// -1 if s1 is less than s2, 0 if s1 is equal to s2, and 1 if s1 is greater than s2 + public int Compare(string s1, string s2) + { + return _func._base.ContextCollateCompare(Encoding, _func._context, s1, s2); + } + + /// + /// Calls the base collating sequence to compare two character arrays + /// + /// The first array to compare + /// The second array to compare + /// -1 if c1 is less than c2, 0 if c1 is equal to c2, and 1 if c1 is greater than c2 + public int Compare(char[] c1, char[] c2) + { + return _func._base.ContextCollateCompare(Encoding, _func._context, c1, c2); + } + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteFunctionAttribute.cs b/Native.Csharp.Tool/SQLite/SQLiteFunctionAttribute.cs new file mode 100644 index 00000000..f506bf99 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteFunctionAttribute.cs @@ -0,0 +1,125 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + + /// + /// A simple custom attribute to enable us to easily find user-defined functions in + /// the loaded assemblies and initialize them in SQLite as connections are made. + /// + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] + public sealed class SQLiteFunctionAttribute : Attribute + { + private string _name; + private int _argumentCount; + private FunctionType _functionType; + private Type _instanceType; + private Delegate _callback1; + private Delegate _callback2; + + /// + /// Default constructor, initializes the internal variables for the function. + /// + public SQLiteFunctionAttribute() + : this(null, -1, FunctionType.Scalar) + { + // do nothing. + } + + /// + /// Constructs an instance of this class. This sets the initial + /// , , and + /// properties to null. + /// + /// + /// The name of the function, as seen by the SQLite core library. + /// + /// + /// The number of arguments that the function will accept. + /// + /// + /// The type of function being declared. This will either be Scalar, + /// Aggregate, or Collation. + /// + public SQLiteFunctionAttribute( + string name, + int argumentCount, + FunctionType functionType + ) + { + _name = name; + _argumentCount = argumentCount; + _functionType = functionType; + _instanceType = null; + _callback1 = null; + _callback2 = null; + } + + /// + /// The function's name as it will be used in SQLite command text. + /// + public string Name + { + get { return _name; } + set { _name = value; } + } + + /// + /// The number of arguments this function expects. -1 if the number of arguments is variable. + /// + public int Arguments + { + get { return _argumentCount; } + set { _argumentCount = value; } + } + + /// + /// The type of function this implementation will be. + /// + public FunctionType FuncType + { + get { return _functionType; } + set { _functionType = value; } + } + + /// + /// The object instance that describes the class + /// containing the implementation for the associated function. The value of + /// this property will not be used if either the or + /// property values are set to non-null. + /// + internal Type InstanceType + { + get { return _instanceType; } + set { _instanceType = value; } + } + + /// + /// The that refers to the implementation for the + /// associated function. If this property value is set to non-null, it will + /// be used instead of the property value. + /// + internal Delegate Callback1 + { + get { return _callback1; } + set { _callback1 = value; } + } + + /// + /// The that refers to the implementation for the + /// associated function. If this property value is set to non-null, it will + /// be used instead of the property value. + /// + internal Delegate Callback2 + { + get { return _callback2; } + set { _callback2 = value; } + } + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteKeyReader.cs b/Native.Csharp.Tool/SQLite/SQLiteKeyReader.cs new file mode 100644 index 00000000..38fb126e --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteKeyReader.cs @@ -0,0 +1,764 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Data; + using System.Data.Common; + using System.Collections.Generic; + using System.Globalization; + + /// + /// This class provides key info for a given SQLite statement. + /// + /// Providing key information for a given statement is non-trivial :( + /// + /// + internal sealed class SQLiteKeyReader : IDisposable + { + private KeyInfo[] _keyInfo; + private SQLiteStatement _stmt; + private bool _isValid; + private RowIdInfo[] _rowIdInfo; + + /// + /// Used to support CommandBehavior.KeyInfo + /// + private struct KeyInfo + { + internal string databaseName; + internal string tableName; + internal string columnName; + internal int database; + internal int rootPage; + internal int cursor; + internal KeyQuery query; + internal int column; + } + + /// + /// Used to keep track of the per-table RowId column metadata. + /// + private struct RowIdInfo + { + internal string databaseName; + internal string tableName; + internal int column; + } + + /// + /// A single sub-query for a given table/database. + /// + private sealed class KeyQuery : IDisposable + { + private SQLiteCommand _command; + internal SQLiteDataReader _reader; + + internal KeyQuery(SQLiteConnection cnn, string database, string table, params string[] columns) + { + using (SQLiteCommandBuilder builder = new SQLiteCommandBuilder()) + { + _command = cnn.CreateCommand(); + for (int n = 0; n < columns.Length; n++) + { + columns[n] = builder.QuoteIdentifier(columns[n]); + } + } + _command.CommandText = HelperMethods.StringFormat(CultureInfo.InvariantCulture, "SELECT {0} FROM [{1}].[{2}] WHERE ROWID = ?", String.Join(",", columns), database, table); + _command.Parameters.AddWithValue(null, (long)0); + } + + internal bool IsValid + { + set + { + if (value != false) throw new ArgumentException(); + if (_reader != null) + { + _reader.Dispose(); + _reader = null; + } + } + } + + internal void Sync(long rowid) + { + IsValid = false; + _command.Parameters[0].Value = rowid; + _reader = _command.ExecuteReader(); + _reader.Read(); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(KeyQuery).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + IsValid = false; + + if (_command != null) _command.Dispose(); + _command = null; + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Destructor + ~KeyQuery() + { + Dispose(false); + } + #endregion + } + + /// + /// This function does all the nasty work at determining what keys need to be returned for + /// a given statement. + /// + /// + /// + /// + internal SQLiteKeyReader(SQLiteConnection cnn, SQLiteDataReader reader, SQLiteStatement stmt) + { + Dictionary catalogs = new Dictionary(); + Dictionary> tables = new Dictionary>(); + List list; + List keys = new List(); + List rowIds = new List(); + + // Record the statement so we can use it later for sync'ing + _stmt = stmt; + + // Fetch all the attached databases on this connection + using (DataTable tbl = cnn.GetSchema("Catalogs")) + { + foreach (DataRow row in tbl.Rows) + { + catalogs.Add((string)row["CATALOG_NAME"], Convert.ToInt32(row["ID"], CultureInfo.InvariantCulture)); + } + } + + // Fetch all the unique tables and catalogs used by the current statement + using (DataTable schema = reader.GetSchemaTable(false, false)) + { + foreach (DataRow row in schema.Rows) + { + // Check if column is backed to a table + if (row[SchemaTableOptionalColumn.BaseCatalogName] == DBNull.Value) + continue; + + // Record the unique table so we can look up its keys + string catalog = (string)row[SchemaTableOptionalColumn.BaseCatalogName]; + string table = (string)row[SchemaTableColumn.BaseTableName]; + + if (tables.ContainsKey(catalog) == false) + { + list = new List(); + tables.Add(catalog, list); + } + else + list = tables[catalog]; + + if (list.Contains(table) == false) + list.Add(table); + } + + // For each catalog and each table, query the indexes for the table. + // Find a primary key index if there is one. If not, find a unique index instead + foreach (KeyValuePair> pair in tables) + { + for (int i = 0; i < pair.Value.Count; i++) + { + string table = pair.Value[i]; + DataRow preferredRow = null; + using (DataTable tbl = cnn.GetSchema("Indexes", new string[] { pair.Key, null, table })) + { + // Loop twice. The first time looking for a primary key index, + // the second time looking for a unique index + for (int n = 0; n < 2 && preferredRow == null; n++) + { + foreach (DataRow row in tbl.Rows) + { + if (n == 0 && (bool)row["PRIMARY_KEY"] == true) + { + preferredRow = row; + break; + } + else if (n == 1 && (bool)row["UNIQUE"] == true) + { + preferredRow = row; + break; + } + } + } + if (preferredRow == null) // Unable to find any suitable index for this table so remove it + { + pair.Value.RemoveAt(i); + i--; + } + else // We found a usable index, so fetch the necessary table details + { + using (DataTable tblTables = cnn.GetSchema("Tables", new string[] { pair.Key, null, table })) + { + // Find the root page of the table in the current statement and get the cursor that's iterating it + int database = catalogs[pair.Key]; + int rootPage = Convert.ToInt32(tblTables.Rows[0]["TABLE_ROOTPAGE"], CultureInfo.InvariantCulture); + int cursor = stmt._sql.GetCursorForTable(stmt, database, rootPage); + + // Now enumerate the members of the index we're going to use + using (DataTable indexColumns = cnn.GetSchema("IndexColumns", new string[] { pair.Key, null, table, (string)preferredRow["INDEX_NAME"] })) + { + // + // NOTE: If this is actually a RowId (or alias), record that now. There should + // be exactly one index column in that case. + // + bool isRowId = (string)preferredRow["INDEX_NAME"] == "sqlite_master_PK_" + table; + KeyQuery query = null; + + List cols = new List(); + for (int x = 0; x < indexColumns.Rows.Count; x++) + { + string columnName = SQLiteConvert.GetStringOrNull( + indexColumns.Rows[x]["COLUMN_NAME"]); + + bool addKey = true; + // If the column in the index already appears in the query, skip it + foreach (DataRow row in schema.Rows) + { + if (row.IsNull(SchemaTableColumn.BaseColumnName)) + continue; + + if ((string)row[SchemaTableColumn.BaseColumnName] == columnName && + (string)row[SchemaTableColumn.BaseTableName] == table && + (string)row[SchemaTableOptionalColumn.BaseCatalogName] == pair.Key) + { + if (isRowId) + { + RowIdInfo rowId = new RowIdInfo(); + + rowId.databaseName = pair.Key; + rowId.tableName = table; + rowId.column = (int)row[SchemaTableColumn.ColumnOrdinal]; + + rowIds.Add(rowId); + } + indexColumns.Rows.RemoveAt(x); + x--; + addKey = false; + break; + } + } + if (addKey == true) + cols.Add(columnName); + } + + // If the index is not a rowid alias, record all the columns + // needed to make up the unique index and construct a SQL query for it + if (!isRowId) + { + // Whatever remains of the columns we need that make up the index that are not + // already in the query need to be queried separately, so construct a subquery + if (cols.Count > 0) + { + string[] querycols = new string[cols.Count]; + cols.CopyTo(querycols); + query = new KeyQuery(cnn, pair.Key, table, querycols); + } + } + + // Create a KeyInfo struct for each column of the index + for (int x = 0; x < indexColumns.Rows.Count; x++) + { + string columnName = SQLiteConvert.GetStringOrNull(indexColumns.Rows[x]["COLUMN_NAME"]); + KeyInfo key = new KeyInfo(); + + key.rootPage = rootPage; + key.cursor = cursor; + key.database = database; + key.databaseName = pair.Key; + key.tableName = table; + key.columnName = columnName; + key.query = query; + key.column = x; + + keys.Add(key); + } + } + } + } + } + } + } + } + + // Now we have all the additional columns we have to return in order to support + // CommandBehavior.KeyInfo + _keyInfo = new KeyInfo[keys.Count]; + keys.CopyTo(_keyInfo); + + _rowIdInfo = new RowIdInfo[rowIds.Count]; + rowIds.CopyTo(_rowIdInfo); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + internal int GetRowIdIndex( + string databaseName, + string tableName + ) + { + if ((_rowIdInfo != null) && + (databaseName != null) && + (tableName != null)) + { + for (int i = 0; i < _rowIdInfo.Length; i++) + { + if (_rowIdInfo[i].databaseName == databaseName && + _rowIdInfo[i].tableName == tableName) + { + return _rowIdInfo[i].column; + } + } + } + + return -1; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + internal long? GetRowId( + string databaseName, + string tableName + ) + { + if ((_keyInfo != null) && + (databaseName != null) && + (tableName != null)) + { + for (int i = 0; i < _keyInfo.Length; i++) + { + if (_keyInfo[i].databaseName == databaseName && + _keyInfo[i].tableName == tableName) + { + long rowid = _stmt._sql.GetRowIdForCursor(_stmt, _keyInfo[i].cursor); + + if (rowid != 0) + return rowid; + } + } + } + + return null; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteKeyReader).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + _stmt = null; + + if (_keyInfo != null) + { + for (int n = 0; n < _keyInfo.Length; n++) + { + if (_keyInfo[n].query != null) + _keyInfo[n].query.Dispose(); + } + + _keyInfo = null; + } + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Destructor + ~SQLiteKeyReader() + { + Dispose(false); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// How many additional columns of keyinfo we're holding + /// + internal int Count + { + get { return (_keyInfo == null) ? 0 : _keyInfo.Length; } + } + + private void Sync(int i) + { + Sync(); + if (_keyInfo[i].cursor == -1) + throw new InvalidCastException(); + } + + /// + /// Make sure all the subqueries are open and ready and sync'd with the current rowid + /// of the table they're supporting + /// + private void Sync() + { + if (_isValid == true) return; + + KeyQuery last = null; + + for (int n = 0; n < _keyInfo.Length; n++) + { + if (_keyInfo[n].query == null || _keyInfo[n].query != last) + { + last = _keyInfo[n].query; + + if (last != null) + { + last.Sync(_stmt._sql.GetRowIdForCursor(_stmt, _keyInfo[n].cursor)); + } + } + } + _isValid = true; + } + + /// + /// Release any readers on any subqueries + /// + internal void Reset() + { + _isValid = false; + if (_keyInfo == null) return; + + for (int n = 0; n < _keyInfo.Length; n++) + { + if (_keyInfo[n].query != null) + _keyInfo[n].query.IsValid = false; + } + } + + internal string GetDataTypeName(int i) + { + Sync(); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetDataTypeName(_keyInfo[i].column); + else return "integer"; + } + + internal TypeAffinity GetFieldAffinity(int i) + { + Sync(); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetFieldAffinity(_keyInfo[i].column); + else return TypeAffinity.Uninitialized; + } + + internal Type GetFieldType(int i) + { + Sync(); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetFieldType(_keyInfo[i].column); + else return typeof(Int64); + } + + internal string GetDatabaseName(int i) + { + return _keyInfo[i].databaseName; + } + + internal string GetTableName(int i) + { + return _keyInfo[i].tableName; + } + + internal string GetName(int i) + { + return _keyInfo[i].columnName; + } + + internal int GetOrdinal(string name) + { + for (int n = 0; n < _keyInfo.Length; n++) + { + if (String.Compare(name, _keyInfo[n].columnName, StringComparison.OrdinalIgnoreCase) == 0) return n; + } + return -1; + } + + internal SQLiteBlob GetBlob(int i, bool readOnly) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetBlob(_keyInfo[i].column, readOnly); + else throw new InvalidCastException(); + } + + internal bool GetBoolean(int i) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetBoolean(_keyInfo[i].column); + else throw new InvalidCastException(); + } + + internal byte GetByte(int i) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetByte(_keyInfo[i].column); + else throw new InvalidCastException(); + } + + internal long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetBytes(_keyInfo[i].column, fieldOffset, buffer, bufferoffset, length); + else throw new InvalidCastException(); + } + + internal char GetChar(int i) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetChar(_keyInfo[i].column); + else throw new InvalidCastException(); + } + + internal long GetChars(int i, long fieldOffset, char[] buffer, int bufferoffset, int length) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetChars(_keyInfo[i].column, fieldOffset, buffer, bufferoffset, length); + else throw new InvalidCastException(); + } + + internal DateTime GetDateTime(int i) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetDateTime(_keyInfo[i].column); + else throw new InvalidCastException(); + } + + internal decimal GetDecimal(int i) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetDecimal(_keyInfo[i].column); + else throw new InvalidCastException(); + } + + internal double GetDouble(int i) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetDouble(_keyInfo[i].column); + else throw new InvalidCastException(); + } + + internal float GetFloat(int i) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetFloat(_keyInfo[i].column); + else throw new InvalidCastException(); + } + + internal Guid GetGuid(int i) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetGuid(_keyInfo[i].column); + else throw new InvalidCastException(); + } + + internal Int16 GetInt16(int i) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetInt16(_keyInfo[i].column); + else + { + long rowid = _stmt._sql.GetRowIdForCursor(_stmt, _keyInfo[i].cursor); + if (rowid == 0) throw new InvalidCastException(); + return Convert.ToInt16(rowid); + } + } + + internal Int32 GetInt32(int i) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetInt32(_keyInfo[i].column); + else + { + long rowid = _stmt._sql.GetRowIdForCursor(_stmt, _keyInfo[i].cursor); + if (rowid == 0) throw new InvalidCastException(); + return Convert.ToInt32(rowid); + } + } + + internal Int64 GetInt64(int i) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetInt64(_keyInfo[i].column); + else + { + long rowid = _stmt._sql.GetRowIdForCursor(_stmt, _keyInfo[i].cursor); + if (rowid == 0) throw new InvalidCastException(); + return rowid; + } + } + + internal string GetString(int i) + { + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetString(_keyInfo[i].column); + else throw new InvalidCastException(); + } + + internal object GetValue(int i) + { + if (_keyInfo[i].cursor == -1) return DBNull.Value; + + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.GetValue(_keyInfo[i].column); + + if (IsDBNull(i) == true) + return DBNull.Value; + else return GetInt64(i); + } + + internal bool IsDBNull(int i) + { + if (_keyInfo[i].cursor == -1) return true; + + Sync(i); + if (_keyInfo[i].query != null) return _keyInfo[i].query._reader.IsDBNull(_keyInfo[i].column); + else return _stmt._sql.GetRowIdForCursor(_stmt, _keyInfo[i].cursor) == 0; + } + + /// + /// Append all the columns we've added to the original query to the schema + /// + /// + internal void AppendSchemaTable(DataTable tbl) + { + KeyQuery last = null; + + for (int n = 0; n < _keyInfo.Length; n++) + { + if (_keyInfo[n].query == null || _keyInfo[n].query != last) + { + last = _keyInfo[n].query; + + if (last == null) // ROWID aliases are treated special + { + DataRow row = tbl.NewRow(); + row[SchemaTableColumn.ColumnName] = _keyInfo[n].columnName; + row[SchemaTableColumn.ColumnOrdinal] = tbl.Rows.Count; + row[SchemaTableColumn.ColumnSize] = 8; + row[SchemaTableColumn.NumericPrecision] = 255; + row[SchemaTableColumn.NumericScale] = 255; + row[SchemaTableColumn.ProviderType] = DbType.Int64; + row[SchemaTableColumn.IsLong] = false; + row[SchemaTableColumn.AllowDBNull] = false; + row[SchemaTableOptionalColumn.IsReadOnly] = false; + row[SchemaTableOptionalColumn.IsRowVersion] = false; + row[SchemaTableColumn.IsUnique] = false; + row[SchemaTableColumn.IsKey] = true; + row[SchemaTableColumn.DataType] = typeof(Int64); + row[SchemaTableOptionalColumn.IsHidden] = true; + row[SchemaTableColumn.BaseColumnName] = _keyInfo[n].columnName; + row[SchemaTableColumn.IsExpression] = false; + row[SchemaTableColumn.IsAliased] = false; + row[SchemaTableColumn.BaseTableName] = _keyInfo[n].tableName; + row[SchemaTableOptionalColumn.BaseCatalogName] = _keyInfo[n].databaseName; + row[SchemaTableOptionalColumn.IsAutoIncrement] = true; + row["DataTypeName"] = "integer"; + + tbl.Rows.Add(row); + } + else + { + last.Sync(0); + using (DataTable tblSub = last._reader.GetSchemaTable()) + { + foreach (DataRow row in tblSub.Rows) + { + object[] o = row.ItemArray; + DataRow newrow = tbl.Rows.Add(o); + newrow[SchemaTableOptionalColumn.IsHidden] = true; + newrow[SchemaTableColumn.ColumnOrdinal] = tbl.Rows.Count - 1; + } + } + } + } + } + } + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteLog.cs b/Native.Csharp.Tool/SQLite/SQLiteLog.cs new file mode 100644 index 00000000..6cca81ec --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteLog.cs @@ -0,0 +1,652 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Data.Common; + using System.Diagnostics; + using System.Globalization; + using System.Threading; + + /// + /// Event data for logging event handlers. + /// + public class LogEventArgs : EventArgs + { + /// + /// The error code. The type of this object value should be + /// or . + /// + public readonly object ErrorCode; + + /// + /// SQL statement text as the statement first begins executing + /// + public readonly string Message; + + /// + /// Extra data associated with this event, if any. + /// + public readonly object Data; + + /// + /// Constructs the object. + /// + /// Should be null. + /// + /// The error code. The type of this object value should be + /// or . + /// + /// The error message, if any. + /// The extra data, if any. + internal LogEventArgs( + IntPtr pUserData, + object errorCode, + string message, + object data + ) + { + ErrorCode = errorCode; + Message = message; + Data = data; + } + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Raised when a log event occurs. + /// + /// The current connection + /// Event arguments of the trace + public delegate void SQLiteLogEventHandler(object sender, LogEventArgs e); + + /////////////////////////////////////////////////////////////////////////// + + /// + /// Manages the SQLite custom logging functionality and the associated + /// callback for the whole process. + /// + public static class SQLiteLog + { + /// + /// Object used to synchronize access to the static instance data + /// for this class. + /// + private static object syncRoot = new object(); + + /////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// Member variable to store the AppDomain.DomainUnload event handler. + /// + private static EventHandler _domainUnload; +#endif + + /////////////////////////////////////////////////////////////////////// + + /// + /// Member variable to store the application log handler to call. + /// + private static event SQLiteLogEventHandler _handlers; + + /////////////////////////////////////////////////////////////////////// + + /// + /// The default log event handler. + /// + private static SQLiteLogEventHandler _defaultHandler; + + /////////////////////////////////////////////////////////////////////// + +#if !USE_INTEROP_DLL || !INTEROP_LOG + /// + /// The log callback passed to native SQLite engine. This must live + /// as long as the SQLite library has a pointer to it. + /// + private static SQLiteLogCallback _callback; + + /////////////////////////////////////////////////////////////////////// + + /// + /// The base SQLite object to interop with. + /// + private static SQLiteBase _sql; +#endif + + /////////////////////////////////////////////////////////////////////// + + /// + /// This will be non-zero if an attempt was already made to initialize + /// the (managed) logging subsystem. + /// + private static int _attemptedInitialize; + + /////////////////////////////////////////////////////////////////////// + + /// + /// This will be non-zero if logging is currently enabled. + /// + private static bool _enabled; + + /////////////////////////////////////////////////////////////////////// + + /// + /// Initializes the SQLite logging facilities. + /// + public static void Initialize() + { + Initialize(null); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Initializes the SQLite logging facilities. + /// + /// + /// The name of the managed class that called this method. This + /// parameter may be null. + /// + internal static void Initialize( + string className + ) + { + // + // NOTE: First, check if the managed logging subsystem is always + // supposed to at least attempt to initialize itself. In + // order to do this, several fairly complex steps must be + // taken, including calling a P/Invoke (interop) method; + // therefore, by default, attempt to perform these steps + // once. + // + if (UnsafeNativeMethods.GetSettingValue( + "Initialize_SQLiteLog", null) == null) + { + if (Interlocked.Increment(ref _attemptedInitialize) > 1) + { + Interlocked.Decrement(ref _attemptedInitialize); + return; + } + } + + /////////////////////////////////////////////////////////////////// + + // + // BUFXIX: We cannot initialize the logging interface if the SQLite + // core library has already been initialized anywhere in + // the process (see ticket [2ce0870fad]). + // + if (SQLite3.StaticIsInitialized()) + return; + + /////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + // + // BUGFIX: To avoid nasty situations where multiple AppDomains are + // attempting to initialize and/or shutdown what is really + // a shared native resource (i.e. the SQLite core library + // is loaded per-process and has only one logging callback, + // not one per-AppDomain, which it knows nothing about), + // prevent all non-default AppDomains from registering a + // log handler unless the "Force_SQLiteLog" environment + // variable is used to manually override this safety check. + // + if (!AppDomain.CurrentDomain.IsDefaultAppDomain() && + UnsafeNativeMethods.GetSettingValue("Force_SQLiteLog", null) == null) + { + return; + } +#endif + + /////////////////////////////////////////////////////////////////// + + lock (syncRoot) + { +#if !PLATFORM_COMPACTFRAMEWORK + // + // NOTE: Add an event handler for the DomainUnload event so + // that we can unhook our logging managed function + // pointer from the native SQLite code prior to it + // being invalidated. + // + // BUGFIX: Make sure this event handler is only added one + // time (per-AppDomain). + // + if (_domainUnload == null) + { + _domainUnload = new EventHandler(DomainUnload); + AppDomain.CurrentDomain.DomainUnload += _domainUnload; + } +#endif + + /////////////////////////////////////////////////////////////// + +#if USE_INTEROP_DLL && INTEROP_LOG + // + // NOTE: Attempt to setup interop assembly log callback. + // This may fail, e.g. if the SQLite core library + // has somehow been initialized. An exception will + // be raised in that case. + // + SQLiteErrorCode rc = SQLite3.ConfigureLogForInterop( + className); + + if (rc != SQLiteErrorCode.Ok) + { + throw new SQLiteException(rc, + "Failed to configure interop assembly logging."); + } +#else + // + // NOTE: Create an instance of the SQLite wrapper class. + // + if (_sql == null) + { + _sql = new SQLite3( + SQLiteDateFormats.Default, DateTimeKind.Unspecified, + null, IntPtr.Zero, null, false); + } + + // + // NOTE: Create a single "global" (i.e. per-process) callback + // to register with SQLite. This callback will pass the + // event on to any registered handler. We only want to + // do this once. + // + if (_callback == null) + { + _callback = new SQLiteLogCallback(LogCallback); + + SQLiteErrorCode rc = _sql.SetLogCallback(_callback); + + if (rc != SQLiteErrorCode.Ok) + { + throw new SQLiteException(rc, + "Failed to configure managed assembly logging."); + } + } +#endif + + /////////////////////////////////////////////////////////////// + + // + // NOTE: Logging is enabled by default unless the configuration + // setting "Disable_SQLiteLog" is present. + // + if (UnsafeNativeMethods.GetSettingValue( + "Disable_SQLiteLog", null) == null) + { + _enabled = true; + } + + /////////////////////////////////////////////////////////////// + + // + // NOTE: For now, always setup the default log event handler. + // + AddDefaultHandler(); + } + } + + /////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// Handles the AppDomain being unloaded. + /// + /// Should be null. + /// The data associated with this event. + private static void DomainUnload( + object sender, + EventArgs e + ) + { + lock (syncRoot) + { + // + // NOTE: Remove the default log event handler. + // + RemoveDefaultHandler(); + + // + // NOTE: Disable logging. If necessary, it can be re-enabled + // later by the Initialize method. + // + _enabled = false; + +#if !USE_INTEROP_DLL || !INTEROP_LOG + // + // BUGBUG: This will cause serious problems if other AppDomains + // have any open SQLite connections; however, there is + // currently no way around this limitation. + // + if (_sql != null) + { + SQLiteErrorCode rc = _sql.Shutdown(); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, + "Failed to shutdown interface."); + + rc = _sql.SetLogCallback(null); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, + "Failed to shutdown logging."); + } + + // + // BUGFIX: Make sure to reset the callback for next time. This + // must be done after it has been succesfully removed + // as logging callback by the SQLite core library as we + // cannot allow native code to refer to a delegate that + // has been garbage collected. + // + if (_callback != null) + { + _callback = null; + } +#endif + + // + // NOTE: Remove the event handler for the DomainUnload event + // that we added earlier. + // + if (_domainUnload != null) + { + AppDomain.CurrentDomain.DomainUnload -= _domainUnload; + _domainUnload = null; + } + } + } +#endif + + /////////////////////////////////////////////////////////////////////// + + /// + /// This event is raised whenever SQLite raises a logging event. + /// Note that this should be set as one of the first things in the + /// application. + /// + public static event SQLiteLogEventHandler Log + { + add + { + lock (syncRoot) + { + // Remove any copies of this event handler from registered + // list. This essentially means that a handler will be + // called only once no matter how many times it is added. + _handlers -= value; + + // Add this to the list of event handlers. + _handlers += value; + } + } + remove + { + lock (syncRoot) + { + _handlers -= value; + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// If this property is true, logging is enabled; otherwise, logging is + /// disabled. When logging is disabled, no logging events will fire. + /// + public static bool Enabled + { + get { lock (syncRoot) { return _enabled; } } + set { lock (syncRoot) { _enabled = value; } } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Log a message to all the registered log event handlers without going + /// through the SQLite library. + /// + /// The message to be logged. + public static void LogMessage( + string message + ) + { + LogMessage(null, message); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Log a message to all the registered log event handlers without going + /// through the SQLite library. + /// + /// The SQLite error code. + /// The message to be logged. + public static void LogMessage( + SQLiteErrorCode errorCode, + string message + ) + { + LogMessage((object)errorCode, message); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Log a message to all the registered log event handlers without going + /// through the SQLite library. + /// + /// The integer error code. + /// The message to be logged. + public static void LogMessage( + int errorCode, + string message + ) + { + LogMessage((object)errorCode, message); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Log a message to all the registered log event handlers without going + /// through the SQLite library. + /// + /// + /// The error code. The type of this object value should be + /// System.Int32 or SQLiteErrorCode. + /// + /// The message to be logged. + private static void LogMessage( + object errorCode, + string message + ) + { + bool enabled; + SQLiteLogEventHandler handlers; + + lock (syncRoot) + { + enabled = _enabled; + + if (_handlers != null) + handlers = _handlers.Clone() as SQLiteLogEventHandler; + else + handlers = null; + } + + if (enabled && (handlers != null)) + handlers(null, new LogEventArgs( + IntPtr.Zero, errorCode, message, null)); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Creates and initializes the default log event handler. + /// + private static void InitializeDefaultHandler() + { + lock (syncRoot) + { + if (_defaultHandler == null) + _defaultHandler = new SQLiteLogEventHandler(LogEventHandler); + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Adds the default log event handler to the list of handlers. + /// + public static void AddDefaultHandler() + { + InitializeDefaultHandler(); + Log += _defaultHandler; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Removes the default log event handler from the list of handlers. + /// + public static void RemoveDefaultHandler() + { + InitializeDefaultHandler(); + Log -= _defaultHandler; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Internal proxy function that calls any registered application log + /// event handlers. + /// + /// WARNING: This method is used more-or-less directly by native code, + /// do not modify its type signature. + /// + /// + /// The extra data associated with this message, if any. + /// + /// + /// The error code associated with this message. + /// + /// + /// The message string to be logged. + /// + private static void LogCallback( + IntPtr pUserData, + int errorCode, + IntPtr pMessage + ) + { + bool enabled; + SQLiteLogEventHandler handlers; + + lock (syncRoot) + { + enabled = _enabled; + + if (_handlers != null) + handlers = _handlers.Clone() as SQLiteLogEventHandler; + else + handlers = null; + } + + if (enabled && (handlers != null)) + handlers(null, new LogEventArgs(pUserData, errorCode, + SQLiteBase.UTF8ToString(pMessage, -1), null)); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Default logger. Currently, uses the Trace class (i.e. sends events + /// to the current trace listeners, if any). + /// + /// Should be null. + /// The data associated with this event. + private static void LogEventHandler( + object sender, + LogEventArgs e + ) + { +#if !NET_COMPACT_20 + if (e == null) + return; + + string message = e.Message; + + if (message == null) + { + message = ""; + } + else + { + message = message.Trim(); + + if (message.Length == 0) + message = ""; + } + + object errorCode = e.ErrorCode; + string type = "error"; + + if ((errorCode is SQLiteErrorCode) || (errorCode is int)) + { + SQLiteErrorCode rc = (SQLiteErrorCode)(int)errorCode; + + rc &= SQLiteErrorCode.NonExtendedMask; + + if (rc == SQLiteErrorCode.Ok) + { + type = "message"; + } + else if (rc == SQLiteErrorCode.Notice) + { + type = "notice"; + } + else if (rc == SQLiteErrorCode.Warning) + { + type = "warning"; + } + else if ((rc == SQLiteErrorCode.Row) || + (rc == SQLiteErrorCode.Done)) + { + type = "data"; + } + } + else if (errorCode == null) + { + type = "trace"; + } + + if ((errorCode != null) && + !Object.ReferenceEquals(errorCode, String.Empty)) + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, "SQLite {0} ({1}): {2}", + type, errorCode, message)); + } + else + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, "SQLite {0}: {1}", + type, message)); + } +#endif + } + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteMetaDataCollectionNames.cs b/Native.Csharp.Tool/SQLite/SQLiteMetaDataCollectionNames.cs new file mode 100644 index 00000000..e1c21341 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteMetaDataCollectionNames.cs @@ -0,0 +1,52 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + /// + /// MetaDataCollections specific to SQLite + /// + public static class SQLiteMetaDataCollectionNames + { + /// + /// Returns a list of databases attached to the connection + /// + public static readonly string Catalogs = "Catalogs"; + /// + /// Returns column information for the specified table + /// + public static readonly string Columns = "Columns"; + /// + /// Returns index information for the optionally-specified table + /// + public static readonly string Indexes = "Indexes"; + /// + /// Returns base columns for the given index + /// + public static readonly string IndexColumns = "IndexColumns"; + /// + /// Returns the tables in the given catalog + /// + public static readonly string Tables = "Tables"; + /// + /// Returns user-defined views in the given catalog + /// + public static readonly string Views = "Views"; + /// + /// Returns underlying column information on the given view + /// + public static readonly string ViewColumns = "ViewColumns"; + /// + /// Returns foreign key information for the given catalog + /// + public static readonly string ForeignKeys = "ForeignKeys"; + /// + /// Returns the triggers on the database + /// + public static readonly string Triggers = "Triggers"; + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteModule.cs b/Native.Csharp.Tool/SQLite/SQLiteModule.cs new file mode 100644 index 00000000..94489670 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteModule.cs @@ -0,0 +1,8765 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Joe Mistachkin (joe@mistachkin.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +using System.Collections.Generic; +using System.Globalization; + +#if !PLATFORM_COMPACTFRAMEWORK +using System.Runtime.CompilerServices; +#endif + +using System.Runtime.InteropServices; +using System.Text; + +namespace System.Data.SQLite +{ + #region SQLiteContext Helper Class + /// + /// This class represents a context from the SQLite core library that can + /// be passed to the sqlite3_result_*() and associated functions. + /// + public sealed class SQLiteContext : ISQLiteNativeHandle + { + #region Private Data + /// + /// The native context handle. + /// + private IntPtr pContext; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs an instance of this class using the specified native + /// context handle. + /// + /// + /// The native context handle to use. + /// + internal SQLiteContext(IntPtr pContext) + { + this.pContext = pContext; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteNativeHandle Members + /// + /// Returns the underlying SQLite native handle associated with this + /// object instance. + /// + public IntPtr NativeHandle + { + get { return pContext; } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Methods + /// + /// Sets the context result to NULL. + /// + public void SetNull() + { + if (pContext == IntPtr.Zero) + throw new InvalidOperationException(); + + UnsafeNativeMethods.sqlite3_result_null(pContext); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the context result to the specified + /// value. + /// + /// + /// The value to use. + /// + public void SetDouble(double value) + { + if (pContext == IntPtr.Zero) + throw new InvalidOperationException(); + +#if !PLATFORM_COMPACTFRAMEWORK + UnsafeNativeMethods.sqlite3_result_double(pContext, value); +#elif !SQLITE_STANDARD + UnsafeNativeMethods.sqlite3_result_double_interop(pContext, ref value); +#else + throw new NotImplementedException(); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the context result to the specified + /// value. + /// + /// + /// The value to use. + /// + public void SetInt(int value) + { + if (pContext == IntPtr.Zero) + throw new InvalidOperationException(); + + UnsafeNativeMethods.sqlite3_result_int(pContext, value); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the context result to the specified + /// value. + /// + /// + /// The value to use. + /// + public void SetInt64(long value) + { + if (pContext == IntPtr.Zero) + throw new InvalidOperationException(); + +#if !PLATFORM_COMPACTFRAMEWORK + UnsafeNativeMethods.sqlite3_result_int64(pContext, value); +#elif !SQLITE_STANDARD + UnsafeNativeMethods.sqlite3_result_int64_interop(pContext, ref value); +#else + throw new NotImplementedException(); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the context result to the specified + /// value. + /// + /// + /// The value to use. This value will be + /// converted to the UTF-8 encoding prior to being used. + /// + public void SetString(string value) + { + if (pContext == IntPtr.Zero) + throw new InvalidOperationException(); + + byte[] bytes = SQLiteString.GetUtf8BytesFromString(value); + + if (bytes == null) + throw new ArgumentNullException("value"); + + UnsafeNativeMethods.sqlite3_result_text( + pContext, bytes, bytes.Length, (IntPtr)(-1)); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the context result to the specified + /// value containing an error message. + /// + /// + /// The value containing the error message text. + /// This value will be converted to the UTF-8 encoding prior to being + /// used. + /// + public void SetError(string value) + { + if (pContext == IntPtr.Zero) + throw new InvalidOperationException(); + + byte[] bytes = SQLiteString.GetUtf8BytesFromString(value); + + if (bytes == null) + throw new ArgumentNullException("value"); + + UnsafeNativeMethods.sqlite3_result_error( + pContext, bytes, bytes.Length); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the context result to the specified + /// value. + /// + /// + /// The value to use. + /// + public void SetErrorCode(SQLiteErrorCode value) + { + if (pContext == IntPtr.Zero) + throw new InvalidOperationException(); + + UnsafeNativeMethods.sqlite3_result_error_code(pContext, value); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the context result to contain the error code SQLITE_TOOBIG. + /// + public void SetErrorTooBig() + { + if (pContext == IntPtr.Zero) + throw new InvalidOperationException(); + + UnsafeNativeMethods.sqlite3_result_error_toobig(pContext); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the context result to contain the error code SQLITE_NOMEM. + /// + public void SetErrorNoMemory() + { + if (pContext == IntPtr.Zero) + throw new InvalidOperationException(); + + UnsafeNativeMethods.sqlite3_result_error_nomem(pContext); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the context result to the specified array + /// value. + /// + /// + /// The array value to use. + /// + public void SetBlob(byte[] value) + { + if (pContext == IntPtr.Zero) + throw new InvalidOperationException(); + + if (value == null) + throw new ArgumentNullException("value"); + + UnsafeNativeMethods.sqlite3_result_blob( + pContext, value, value.Length, (IntPtr)(-1)); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the context result to a BLOB of zeros of the specified size. + /// + /// + /// The number of zero bytes to use for the BLOB context result. + /// + public void SetZeroBlob(int value) + { + if (pContext == IntPtr.Zero) + throw new InvalidOperationException(); + + UnsafeNativeMethods.sqlite3_result_zeroblob(pContext, value); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the context result to the specified . + /// + /// + /// The to use. + /// + public void SetValue(SQLiteValue value) + { + if (pContext == IntPtr.Zero) + throw new InvalidOperationException(); + + if (value == null) + throw new ArgumentNullException("value"); + + UnsafeNativeMethods.sqlite3_result_value( + pContext, value.NativeHandle); + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteValue Helper Class + /// + /// This class represents a value from the SQLite core library that can be + /// passed to the sqlite3_value_*() and associated functions. + /// + public sealed class SQLiteValue : ISQLiteNativeHandle + { + #region Private Data + /// + /// The native value handle. + /// + private IntPtr pValue; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs an instance of this class using the specified native + /// value handle. + /// + /// + /// The native value handle to use. + /// + private SQLiteValue(IntPtr pValue) + { + this.pValue = pValue; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// Invalidates the native value handle, thereby preventing further + /// access to it from this object instance. + /// + private void PreventNativeAccess() + { + pValue = IntPtr.Zero; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Internal Marshal Helper Methods + /// + /// Converts a native pointer to a native sqlite3_value structure into + /// a managed object instance. + /// + /// + /// The native pointer to a native sqlite3_value structure to convert. + /// + /// + /// The managed object instance or null upon + /// failure. + /// + internal static SQLiteValue FromIntPtr( + IntPtr pValue + ) + { + if (pValue == IntPtr.Zero) return null; + return new SQLiteValue(pValue); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Converts a logical array of native pointers to native sqlite3_value + /// structures into a managed array of + /// object instances. + /// + /// + /// The number of elements in the logical array of native sqlite3_value + /// structures. + /// + /// + /// The native pointer to the logical array of native sqlite3_value + /// structures to convert. + /// + /// + /// The managed array of object instances or + /// null upon failure. + /// + internal static SQLiteValue[] ArrayFromSizeAndIntPtr( + int argc, + IntPtr argv + ) + { + if (argc < 0) + return null; + + if (argv == IntPtr.Zero) + return null; + + SQLiteValue[] result = new SQLiteValue[argc]; + + for (int index = 0, offset = 0; + index < result.Length; + index++, offset += IntPtr.Size) + { + IntPtr pArg = SQLiteMarshal.ReadIntPtr(argv, offset); + + result[index] = (pArg != IntPtr.Zero) ? + new SQLiteValue(pArg) : null; + } + + return result; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteNativeHandle Members + /// + /// Returns the underlying SQLite native handle associated with this + /// object instance. + /// + public IntPtr NativeHandle + { + get { return pValue; } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Properties + private bool persisted; + /// + /// Returns non-zero if the native SQLite value has been successfully + /// persisted as a managed value within this object instance (i.e. the + /// property may then be read successfully). + /// + public bool Persisted + { + get { return persisted; } + } + + /////////////////////////////////////////////////////////////////////// + + private object value; + /// + /// If the managed value for this object instance is available (i.e. it + /// has been previously persisted via the ) method, + /// that value is returned; otherwise, an exception is thrown. The + /// returned value may be null. + /// + public object Value + { + get + { + if (!persisted) + { + throw new InvalidOperationException( + "value was not persisted"); + } + + return value; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Methods + /// + /// Gets and returns the type affinity associated with this value. + /// + /// + /// The type affinity associated with this value. + /// + public TypeAffinity GetTypeAffinity() + { + if (pValue == IntPtr.Zero) return TypeAffinity.None; + return UnsafeNativeMethods.sqlite3_value_type(pValue); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Gets and returns the number of bytes associated with this value, if + /// it refers to a UTF-8 encoded string. + /// + /// + /// The number of bytes associated with this value. The returned value + /// may be zero. + /// + public int GetBytes() + { + if (pValue == IntPtr.Zero) return 0; + return UnsafeNativeMethods.sqlite3_value_bytes(pValue); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Gets and returns the associated with this + /// value. + /// + /// + /// The associated with this value. + /// + public int GetInt() + { + if (pValue == IntPtr.Zero) return default(int); + return UnsafeNativeMethods.sqlite3_value_int(pValue); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Gets and returns the associated with + /// this value. + /// + /// + /// The associated with this value. + /// + public long GetInt64() + { + if (pValue == IntPtr.Zero) return default(long); + +#if !PLATFORM_COMPACTFRAMEWORK + return UnsafeNativeMethods.sqlite3_value_int64(pValue); +#elif !SQLITE_STANDARD + long value = 0; + UnsafeNativeMethods.sqlite3_value_int64_interop(pValue, ref value); + return value; +#else + throw new NotImplementedException(); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Gets and returns the associated with this + /// value. + /// + /// + /// The associated with this value. + /// + public double GetDouble() + { + if (pValue == IntPtr.Zero) return default(double); + +#if !PLATFORM_COMPACTFRAMEWORK + return UnsafeNativeMethods.sqlite3_value_double(pValue); +#elif !SQLITE_STANDARD + double value = 0.0; + UnsafeNativeMethods.sqlite3_value_double_interop(pValue, ref value); + return value; +#else + throw new NotImplementedException(); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Gets and returns the associated with this + /// value. + /// + /// + /// The associated with this value. The value is + /// converted from the UTF-8 encoding prior to being returned. + /// + public string GetString() + { + if (pValue == IntPtr.Zero) return null; + + int length; + IntPtr pString; + +#if SQLITE_STANDARD + length = UnsafeNativeMethods.sqlite3_value_bytes(pValue); + pString = UnsafeNativeMethods.sqlite3_value_text(pValue); +#else + length = 0; + + pString = UnsafeNativeMethods.sqlite3_value_text_interop( + pValue, ref length); +#endif + + return SQLiteString.StringFromUtf8IntPtr(pString, length); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Gets and returns the array associated with this + /// value. + /// + /// + /// The array associated with this value. + /// + public byte[] GetBlob() + { + if (pValue == IntPtr.Zero) return null; + + return SQLiteBytes.FromIntPtr( + UnsafeNativeMethods.sqlite3_value_blob(pValue), GetBytes()); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Gets and returns an instance associated with + /// this value. + /// + /// + /// The associated with this value. If the type + /// affinity of the object is unknown or cannot be determined, a null + /// value will be returned. + /// + public object GetObject() + { + switch (GetTypeAffinity()) + { + case TypeAffinity.Uninitialized: + { + return null; + } + case TypeAffinity.Int64: + { + return GetInt64(); + } + case TypeAffinity.Double: + { + return GetDouble(); + } + case TypeAffinity.Text: + { + return GetString(); + } + case TypeAffinity.Blob: + { + return GetBytes(); + } + case TypeAffinity.Null: + { + return DBNull.Value; + } + default: + { + return null; + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Uses the native value handle to obtain and store the managed value + /// for this object instance, thus saving it for later use. The type + /// of the managed value is determined by the type affinity of the + /// native value. If the type affinity is not recognized by this + /// method, no work is done and false is returned. + /// + /// + /// Non-zero if the native value was persisted successfully. + /// + public bool Persist() + { + switch (GetTypeAffinity()) + { + case TypeAffinity.Uninitialized: + { + value = null; + PreventNativeAccess(); + return (persisted = true); + } + case TypeAffinity.Int64: + { + value = GetInt64(); + PreventNativeAccess(); + return (persisted = true); + } + case TypeAffinity.Double: + { + value = GetDouble(); + PreventNativeAccess(); + return (persisted = true); + } + case TypeAffinity.Text: + { + value = GetString(); + PreventNativeAccess(); + return (persisted = true); + } + case TypeAffinity.Blob: + { + value = GetBytes(); + PreventNativeAccess(); + return (persisted = true); + } + case TypeAffinity.Null: + { + value = DBNull.Value; + PreventNativeAccess(); + return (persisted = true); + } + default: + { + return false; + } + } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteIndexConstraintOp Enumeration + /// + /// These are the allowed values for the operators that are part of a + /// constraint term in the WHERE clause of a query that uses a virtual + /// table. + /// + public enum SQLiteIndexConstraintOp : byte + { + /// + /// This value represents the equality operator. + /// + EqualTo = 2, + + /// + /// This value represents the greater than operator. + /// + GreaterThan = 4, + + /// + /// This value represents the less than or equal to operator. + /// + LessThanOrEqualTo = 8, + + /// + /// This value represents the less than operator. + /// + LessThan = 16, + + /// + /// This value represents the greater than or equal to operator. + /// + GreaterThanOrEqualTo = 32, + + /// + /// This value represents the MATCH operator. + /// + Match = 64, + + /// + /// This value represents the LIKE operator. + /// + Like = 65, + + /// + /// This value represents the GLOB operator. + /// + Glob = 66, + + /// + /// This value represents the REGEXP operator. + /// + Regexp = 67, + + /// + /// This value represents the inequality operator. + /// + NotEqualTo = 68, + + /// + /// This value represents the IS NOT operator. + /// + IsNot = 69, + + /// + /// This value represents the IS NOT NULL operator. + /// + IsNotNull = 70, + + /// + /// This value represents the IS NULL operator. + /// + IsNull = 71, + + /// + /// This value represents the IS operator. + /// + Is = 72 + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteIndexFlags Enumeration + /// + /// These are the allowed values for the index flags from the + /// method. + /// + [Flags()] + public enum SQLiteIndexFlags + { + /// + /// No special handling. This is the default. + /// + None = 0x0, + + /// + /// This value indicates that the scan of the index will visit at + /// most one row. + /// + ScanUnique = 0x1 + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteIndexConstraint Helper Class + /// + /// This class represents the native sqlite3_index_constraint structure + /// from the SQLite core library. + /// + public sealed class SQLiteIndexConstraint + { + #region Internal Constructors + /// + /// Constructs an instance of this class using the specified native + /// sqlite3_index_constraint structure. + /// + /// + /// The native sqlite3_index_constraint structure to use. + /// + internal SQLiteIndexConstraint( + UnsafeNativeMethods.sqlite3_index_constraint constraint + ) + : this(constraint.iColumn, constraint.op, constraint.usable, + constraint.iTermOffset) + { + // do nothing. + } + #endregion + + ////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs an instance of this class using the specified field + /// values. + /// + /// + /// Column on left-hand side of constraint. + /// + /// + /// Constraint operator (). + /// + /// + /// True if this constraint is usable. + /// + /// + /// Used internally - + /// should ignore. + /// + private SQLiteIndexConstraint( + int iColumn, + SQLiteIndexConstraintOp op, + byte usable, + int iTermOffset + ) + { + this.iColumn = iColumn; + this.op = op; + this.usable = usable; + this.iTermOffset = iTermOffset; + } + #endregion + + ////////////////////////////////////////////////////////////////////// + + #region Public Fields + /// + /// Column on left-hand side of constraint. + /// + public int iColumn; + + ////////////////////////////////////////////////////////////////////// + + /// + /// Constraint operator (). + /// + public SQLiteIndexConstraintOp op; + + ////////////////////////////////////////////////////////////////////// + + /// + /// True if this constraint is usable. + /// + public byte usable; + + ////////////////////////////////////////////////////////////////////// + + /// + /// Used internally - + /// should ignore. + /// + public int iTermOffset; + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteIndexOrderBy Helper Class + /// + /// This class represents the native sqlite3_index_orderby structure from + /// the SQLite core library. + /// + public sealed class SQLiteIndexOrderBy + { + #region Internal Constructors + /// + /// Constructs an instance of this class using the specified native + /// sqlite3_index_orderby structure. + /// + /// + /// The native sqlite3_index_orderby structure to use. + /// + internal SQLiteIndexOrderBy( + UnsafeNativeMethods.sqlite3_index_orderby orderBy + ) + : this(orderBy.iColumn, orderBy.desc) + { + // do nothing. + } + #endregion + + ////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs an instance of this class using the specified field + /// values. + /// + /// + /// Column number. + /// + /// + /// True for DESC. False for ASC. + /// + private SQLiteIndexOrderBy( + int iColumn, + byte desc + ) + { + this.iColumn = iColumn; + this.desc = desc; + } + #endregion + + ////////////////////////////////////////////////////////////////////// + + #region Public Fields + /// + /// Column number. + /// + public int iColumn; + + ////////////////////////////////////////////////////////////////////// + + /// + /// True for DESC. False for ASC. + /// + public byte desc; + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteIndexConstraintUsage Helper Class + /// + /// This class represents the native sqlite3_index_constraint_usage + /// structure from the SQLite core library. + /// + public sealed class SQLiteIndexConstraintUsage + { + #region Internal Constructors + /// + /// Constructs a default instance of this class. + /// + internal SQLiteIndexConstraintUsage() + { + // do nothing. + } + + ////////////////////////////////////////////////////////////////////// + + /// + /// Constructs an instance of this class using the specified native + /// sqlite3_index_constraint_usage structure. + /// + /// + /// The native sqlite3_index_constraint_usage structure to use. + /// + internal SQLiteIndexConstraintUsage( + UnsafeNativeMethods.sqlite3_index_constraint_usage constraintUsage + ) + : this(constraintUsage.argvIndex, constraintUsage.omit) + { + // do nothing. + } + #endregion + + ////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs an instance of this class using the specified field + /// values. + /// + /// + /// If greater than 0, constraint is part of argv to xFilter. + /// + /// + /// Do not code a test for this constraint. + /// + private SQLiteIndexConstraintUsage( + int argvIndex, + byte omit + ) + { + this.argvIndex = argvIndex; + this.omit = omit; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Fields + /// + /// If greater than 0, constraint is part of argv to xFilter. + /// + public int argvIndex; + + /////////////////////////////////////////////////////////////////////// + + /// + /// Do not code a test for this constraint. + /// + public byte omit; + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteIndexInputs Helper Class + /// + /// This class represents the various inputs provided by the SQLite core + /// library to the method. + /// + public sealed class SQLiteIndexInputs + { + #region Internal Constructors + /// + /// Constructs an instance of this class. + /// + /// + /// The number of instances to + /// pre-allocate space for. + /// + /// + /// The number of instances to + /// pre-allocate space for. + /// + internal SQLiteIndexInputs(int nConstraint, int nOrderBy) + { + constraints = new SQLiteIndexConstraint[nConstraint]; + orderBys = new SQLiteIndexOrderBy[nOrderBy]; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Properties + private SQLiteIndexConstraint[] constraints; + /// + /// An array of object instances, + /// each containing information supplied by the SQLite core library. + /// + public SQLiteIndexConstraint[] Constraints + { + get { return constraints; } + } + + /////////////////////////////////////////////////////////////////////// + + private SQLiteIndexOrderBy[] orderBys; + /// + /// An array of object instances, + /// each containing information supplied by the SQLite core library. + /// + public SQLiteIndexOrderBy[] OrderBys + { + get { return orderBys; } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteIndexOutputs Helper Class + /// + /// This class represents the various outputs provided to the SQLite core + /// library by the method. + /// + public sealed class SQLiteIndexOutputs + { + #region Internal Constructors + /// + /// Constructs an instance of this class. + /// + /// + /// The number of instances + /// to pre-allocate space for. + /// + internal SQLiteIndexOutputs(int nConstraint) + { + constraintUsages = new SQLiteIndexConstraintUsage[nConstraint]; + + // + // BUGFIX: Create the [empty] constraint usages now so they can be + // used by the xBestIndex callback. + // + for (int iConstraint = 0; iConstraint < nConstraint; iConstraint++) + constraintUsages[iConstraint] = new SQLiteIndexConstraintUsage(); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + /// + /// Determines if the native estimatedRows field can be used, based on + /// the available version of the SQLite core library. + /// + /// + /// Non-zero if the property is supported + /// by the SQLite core library. + /// + public bool CanUseEstimatedRows() + { + if (UnsafeNativeMethods.sqlite3_libversion_number() >= 3008002) + return true; + + return false; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Determines if the native flags field can be used, based on the + /// available version of the SQLite core library. + /// + /// + /// Non-zero if the property is supported by + /// the SQLite core library. + /// + public bool CanUseIndexFlags() + { + if (UnsafeNativeMethods.sqlite3_libversion_number() >= 3009000) + return true; + + return false; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Determines if the native flags field can be used, based on the + /// available version of the SQLite core library. + /// + /// + /// Non-zero if the property is supported by + /// the SQLite core library. + /// + public bool CanUseColumnsUsed() + { + if (UnsafeNativeMethods.sqlite3_libversion_number() >= 3010000) + return true; + + return false; + } + + /////////////////////////////////////////////////////////////////////// + + #region Public Properties + private SQLiteIndexConstraintUsage[] constraintUsages; + /// + /// An array of object + /// instances, each containing information to be supplied to the SQLite + /// core library. + /// + public SQLiteIndexConstraintUsage[] ConstraintUsages + { + get { return constraintUsages; } + } + + /////////////////////////////////////////////////////////////////////// + + private int indexNumber; + /// + /// Number used to help identify the selected index. This value will + /// later be provided to the + /// method. + /// + public int IndexNumber + { + get { return indexNumber; } + set { indexNumber = value; } + } + + /////////////////////////////////////////////////////////////////////// + + private string indexString; + /// + /// String used to help identify the selected index. This value will + /// later be provided to the + /// method. + /// + public string IndexString + { + get { return indexString; } + set { indexString = value; } + } + + /////////////////////////////////////////////////////////////////////// + + private int needToFreeIndexString; + /// + /// Non-zero if the index string must be freed by the SQLite core + /// library. + /// + public int NeedToFreeIndexString + { + get { return needToFreeIndexString; } + set { needToFreeIndexString = value; } + } + + /////////////////////////////////////////////////////////////////////// + + private int orderByConsumed; + /// + /// True if output is already ordered. + /// + public int OrderByConsumed + { + get { return orderByConsumed; } + set { orderByConsumed = value; } + } + + /////////////////////////////////////////////////////////////////////// + + private double? estimatedCost; + /// + /// Estimated cost of using this index. Using a null value here + /// indicates that a default estimated cost value should be used. + /// + public double? EstimatedCost + { + get { return estimatedCost; } + set { estimatedCost = value; } + } + + /////////////////////////////////////////////////////////////////////// + + private long? estimatedRows; + /// + /// Estimated number of rows returned. Using a null value here + /// indicates that a default estimated rows value should be used. + /// This property has no effect if the SQLite core library is not at + /// least version 3.8.2. + /// + public long? EstimatedRows + { + get { return estimatedRows; } + set { estimatedRows = value; } + } + + /////////////////////////////////////////////////////////////////////// + + private SQLiteIndexFlags? indexFlags; + /// + /// The flags that should be used with this index. Using a null value + /// here indicates that a default flags value should be used. This + /// property has no effect if the SQLite core library is not at least + /// version 3.9.0. + /// + public SQLiteIndexFlags? IndexFlags + { + get { return indexFlags; } + set { indexFlags = value; } + } + + /////////////////////////////////////////////////////////////////////// + + private long? columnsUsed; + /// + /// + /// Indicates which columns of the virtual table may be required by the + /// current scan. Virtual table columns are numbered from zero in the + /// order in which they appear within the CREATE TABLE statement passed + /// to sqlite3_declare_vtab(). For the first 63 columns (columns 0-62), + /// the corresponding bit is set within the bit mask if the column may + /// be required by SQLite. If the table has at least 64 columns and + /// any column to the right of the first 63 is required, then bit 63 of + /// colUsed is also set. In other words, column iCol may be required + /// if the expression + /// + /// + /// (colUsed & ((sqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) + /// + /// + /// evaluates to non-zero. Using a null value here indicates that a + /// default flags value should be used. This property has no effect if + /// the SQLite core library is not at least version 3.10.0. + /// + /// + public long? ColumnsUsed + { + get { return columnsUsed; } + set { columnsUsed = value; } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteIndex Helper Class + /// + /// This class represents the various inputs and outputs used with the + /// method. + /// + public sealed class SQLiteIndex + { + #region Internal Constructors + /// + /// Constructs an instance of this class. + /// + /// + /// The number of (and + /// ) instances to + /// pre-allocate space for. + /// + /// + /// The number of instances to + /// pre-allocate space for. + /// + internal SQLiteIndex( + int nConstraint, + int nOrderBy + ) + { + inputs = new SQLiteIndexInputs(nConstraint, nOrderBy); + outputs = new SQLiteIndexOutputs(nConstraint); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Marshal Helper Methods (For Test Use Only) + /// + /// Attempts to determine the structure sizes needed to create and + /// populate a native + /// + /// structure. + /// + /// + /// The size of the native + /// + /// structure is stored here. + /// + /// + /// The size of the native + /// + /// structure is stored here. + /// + /// + /// The size of the native + /// + /// structure is stored here. + /// + /// + /// The size of the native + /// + /// structure is stored here. + /// + private static void SizeOfNative( + out int sizeOfInfoType, + out int sizeOfConstraintType, + out int sizeOfOrderByType, + out int sizeOfConstraintUsageType + ) + { + sizeOfInfoType = Marshal.SizeOf(typeof( + UnsafeNativeMethods.sqlite3_index_info)); + + sizeOfConstraintType = Marshal.SizeOf(typeof( + UnsafeNativeMethods.sqlite3_index_constraint)); + + sizeOfOrderByType = Marshal.SizeOf(typeof( + UnsafeNativeMethods.sqlite3_index_orderby)); + + sizeOfConstraintUsageType = Marshal.SizeOf(typeof( + UnsafeNativeMethods.sqlite3_index_constraint_usage)); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to allocate and initialize a native + /// + /// structure. + /// + /// + /// The number of instances to + /// pre-allocate space for. + /// + /// + /// The number of instances to + /// pre-allocate space for. + /// + /// + /// The newly allocated native + /// structure + /// -OR- if it could not be fully allocated. + /// + private static IntPtr AllocateAndInitializeNative( + int nConstraint, + int nOrderBy + ) + { + IntPtr pIndex = IntPtr.Zero; + IntPtr pInfo = IntPtr.Zero; + IntPtr pConstraint = IntPtr.Zero; + IntPtr pOrderBy = IntPtr.Zero; + IntPtr pConstraintUsage = IntPtr.Zero; + + try + { + int sizeOfInfoType; + int sizeOfOrderByType; + int sizeOfConstraintType; + int sizeOfConstraintUsageType; + + SizeOfNative(out sizeOfInfoType, out sizeOfConstraintType, + out sizeOfOrderByType, out sizeOfConstraintUsageType); + + if ((sizeOfInfoType > 0) && + (sizeOfConstraintType > 0) && + (sizeOfOrderByType > 0) && + (sizeOfConstraintUsageType > 0)) + { + pInfo = SQLiteMemory.Allocate(sizeOfInfoType); + + pConstraint = SQLiteMemory.Allocate( + sizeOfConstraintType * nConstraint); + + pOrderBy = SQLiteMemory.Allocate( + sizeOfOrderByType * nOrderBy); + + pConstraintUsage = SQLiteMemory.Allocate( + sizeOfConstraintUsageType * nConstraint); + + if ((pInfo != IntPtr.Zero) && + (pConstraint != IntPtr.Zero) && + (pOrderBy != IntPtr.Zero) && + (pConstraintUsage != IntPtr.Zero)) + { + int offset = 0; + + SQLiteMarshal.WriteInt32( + pInfo, offset, nConstraint); + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), IntPtr.Size); + + SQLiteMarshal.WriteIntPtr( + pInfo, offset, pConstraint); + + offset = SQLiteMarshal.NextOffsetOf( + offset, IntPtr.Size, sizeof(int)); + + SQLiteMarshal.WriteInt32( + pInfo, offset, nOrderBy); + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), IntPtr.Size); + + SQLiteMarshal.WriteIntPtr( + pInfo, offset, pOrderBy); + + offset = SQLiteMarshal.NextOffsetOf( + offset, IntPtr.Size, IntPtr.Size); + + SQLiteMarshal.WriteIntPtr( + pInfo, offset, pConstraintUsage); + + pIndex = pInfo; /* NOTE: Success. */ + } + } + } + finally + { + if (pIndex == IntPtr.Zero) /* NOTE: Failure? */ + { + if (pConstraintUsage != IntPtr.Zero) + { + SQLiteMemory.Free(pConstraintUsage); + pConstraintUsage = IntPtr.Zero; + } + + if (pOrderBy != IntPtr.Zero) + { + SQLiteMemory.Free(pOrderBy); + pOrderBy = IntPtr.Zero; + } + + if (pConstraint != IntPtr.Zero) + { + SQLiteMemory.Free(pConstraint); + pConstraint = IntPtr.Zero; + } + + if (pInfo != IntPtr.Zero) + { + SQLiteMemory.Free(pInfo); + pInfo = IntPtr.Zero; + } + } + } + + return pIndex; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Frees all the memory associated with a native + /// + /// structure. + /// + /// + /// The native pointer to the native sqlite3_index_info structure to + /// free. + /// + private static void FreeNative( + IntPtr pIndex + ) + { + if (pIndex == IntPtr.Zero) + return; + + int offset = 0; + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), IntPtr.Size); + + IntPtr pConstraint = SQLiteMarshal.ReadIntPtr( + pIndex, offset); + + int constraintOffset = offset; + + offset = SQLiteMarshal.NextOffsetOf( + offset, IntPtr.Size, sizeof(int)); + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), IntPtr.Size); + + IntPtr pOrderBy = SQLiteMarshal.ReadIntPtr( + pIndex, offset); + + int orderByOffset = offset; + + offset = SQLiteMarshal.NextOffsetOf( + offset, IntPtr.Size, IntPtr.Size); + + IntPtr pConstraintUsage = SQLiteMarshal.ReadIntPtr( + pIndex, offset); + + int constraintUsageOffset = offset; + + if (pConstraintUsage != IntPtr.Zero) + { + SQLiteMemory.Free(pConstraintUsage); + pConstraintUsage = IntPtr.Zero; + + SQLiteMarshal.WriteIntPtr( + pIndex, constraintUsageOffset, pConstraintUsage); + } + + if (pOrderBy != IntPtr.Zero) + { + SQLiteMemory.Free(pOrderBy); + pOrderBy = IntPtr.Zero; + + SQLiteMarshal.WriteIntPtr( + pIndex, orderByOffset, pOrderBy); + } + + if (pConstraint != IntPtr.Zero) + { + SQLiteMemory.Free(pConstraint); + pConstraint = IntPtr.Zero; + + SQLiteMarshal.WriteIntPtr( + pIndex, constraintOffset, pConstraint); + } + + if (pIndex != IntPtr.Zero) + { + SQLiteMemory.Free(pIndex); + pIndex = IntPtr.Zero; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Internal Marshal Helper Methods + /// + /// Converts a native pointer to a native sqlite3_index_info structure + /// into a new object instance. + /// + /// + /// The native pointer to the native sqlite3_index_info structure to + /// convert. + /// + /// + /// Non-zero to include fields from the outputs portion of the native + /// structure; otherwise, the "output" fields will not be read. + /// + /// + /// Upon success, this parameter will be modified to contain the newly + /// created object instance. + /// + internal static void FromIntPtr( + IntPtr pIndex, + bool includeOutput, + ref SQLiteIndex index + ) + { + if (pIndex == IntPtr.Zero) + return; + + int offset = 0; + + int nConstraint = SQLiteMarshal.ReadInt32( + pIndex, offset); + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), IntPtr.Size); + + IntPtr pConstraint = SQLiteMarshal.ReadIntPtr( + pIndex, offset); + + offset = SQLiteMarshal.NextOffsetOf( + offset, IntPtr.Size, sizeof(int)); + + int nOrderBy = SQLiteMarshal.ReadInt32( + pIndex, offset); + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), IntPtr.Size); + + IntPtr pOrderBy = SQLiteMarshal.ReadIntPtr( + pIndex, offset); + + IntPtr pConstraintUsage = IntPtr.Zero; + + if (includeOutput) + { + offset = SQLiteMarshal.NextOffsetOf( + offset, IntPtr.Size, IntPtr.Size); + + pConstraintUsage = SQLiteMarshal.ReadIntPtr( + pIndex, offset); + } + + index = new SQLiteIndex(nConstraint, nOrderBy); + SQLiteIndexInputs inputs = index.Inputs; + + if (inputs == null) + return; + + SQLiteIndexConstraint[] constraints = inputs.Constraints; + + if (constraints == null) + return; + + SQLiteIndexOrderBy[] orderBys = inputs.OrderBys; + + if (orderBys == null) + return; + + Type constraintType = typeof( + UnsafeNativeMethods.sqlite3_index_constraint); + + int sizeOfConstraintType = Marshal.SizeOf( + constraintType); + + for (int iConstraint = 0; iConstraint < nConstraint; iConstraint++) + { + IntPtr pOffset = SQLiteMarshal.IntPtrForOffset( + pConstraint, iConstraint * sizeOfConstraintType); + + UnsafeNativeMethods.sqlite3_index_constraint constraint = + (UnsafeNativeMethods.sqlite3_index_constraint) + Marshal.PtrToStructure(pOffset, constraintType); + + constraints[iConstraint] = new SQLiteIndexConstraint( + constraint); + } + + Type orderByType = typeof( + UnsafeNativeMethods.sqlite3_index_orderby); + + int sizeOfOrderByType = Marshal.SizeOf(orderByType); + + for (int iOrderBy = 0; iOrderBy < nOrderBy; iOrderBy++) + { + IntPtr pOffset = SQLiteMarshal.IntPtrForOffset( + pOrderBy, iOrderBy * sizeOfOrderByType); + + UnsafeNativeMethods.sqlite3_index_orderby orderBy = + (UnsafeNativeMethods.sqlite3_index_orderby) + Marshal.PtrToStructure(pOffset, orderByType); + + orderBys[iOrderBy] = new SQLiteIndexOrderBy(orderBy); + } + + if (includeOutput) + { + SQLiteIndexOutputs outputs = index.Outputs; + + if (outputs == null) + return; + + SQLiteIndexConstraintUsage[] constraintUsages = + outputs.ConstraintUsages; + + if (constraintUsages == null) + return; + + Type constraintUsageType = typeof( + UnsafeNativeMethods.sqlite3_index_constraint_usage); + + int sizeOfConstraintUsageType = Marshal.SizeOf( + constraintUsageType); + + for (int iConstraint = 0; iConstraint < nConstraint; iConstraint++) + { + IntPtr pOffset = SQLiteMarshal.IntPtrForOffset( + pConstraintUsage, iConstraint * sizeOfConstraintUsageType); + + UnsafeNativeMethods.sqlite3_index_constraint_usage constraintUsage = + (UnsafeNativeMethods.sqlite3_index_constraint_usage) + Marshal.PtrToStructure(pOffset, constraintUsageType); + + constraintUsages[iConstraint] = new SQLiteIndexConstraintUsage( + constraintUsage); + } + + offset = SQLiteMarshal.NextOffsetOf( + offset, IntPtr.Size, sizeof(int)); + + outputs.IndexNumber = SQLiteMarshal.ReadInt32( + pIndex, offset); + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), IntPtr.Size); + + outputs.IndexString = SQLiteString.StringFromUtf8IntPtr( + SQLiteMarshal.ReadIntPtr(pIndex, offset)); + + offset = SQLiteMarshal.NextOffsetOf( + offset, IntPtr.Size, sizeof(int)); + + outputs.NeedToFreeIndexString = SQLiteMarshal.ReadInt32( + pIndex, offset); + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), sizeof(int)); + + outputs.OrderByConsumed = SQLiteMarshal.ReadInt32( + pIndex, offset); + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), sizeof(double)); + + outputs.EstimatedCost = SQLiteMarshal.ReadDouble( + pIndex, offset); + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(double), sizeof(long)); + + if (outputs.CanUseEstimatedRows()) + { + outputs.EstimatedRows = SQLiteMarshal.ReadInt64( + pIndex, offset); + } + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(long), sizeof(int)); + + if (outputs.CanUseIndexFlags()) + { + outputs.IndexFlags = (SQLiteIndexFlags) + SQLiteMarshal.ReadInt32(pIndex, offset); + } + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), sizeof(long)); + + if (outputs.CanUseColumnsUsed()) + { + outputs.ColumnsUsed = SQLiteMarshal.ReadInt64( + pIndex, offset); + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Populates the outputs of a pre-allocated native sqlite3_index_info + /// structure using an existing object + /// instance. + /// + /// + /// The existing object instance containing + /// the output data to use. + /// + /// + /// The native pointer to the pre-allocated native sqlite3_index_info + /// structure. + /// + /// + /// Non-zero to include fields from the inputs portion of the native + /// structure; otherwise, the "input" fields will not be written. + /// + internal static void ToIntPtr( + SQLiteIndex index, + IntPtr pIndex, + bool includeInput + ) + { + if (index == null) + return; + + SQLiteIndexOutputs outputs = index.Outputs; + + if (outputs == null) + return; + + SQLiteIndexConstraintUsage[] constraintUsages = + outputs.ConstraintUsages; + + if (constraintUsages == null) + return; + + SQLiteIndexInputs inputs = null; + SQLiteIndexConstraint[] constraints = null; + SQLiteIndexOrderBy[] orderBys = null; + + if (includeInput) + { + inputs = index.Inputs; + + if (inputs == null) + return; + + constraints = inputs.Constraints; + + if (constraints == null) + return; + + orderBys = inputs.OrderBys; + + if (orderBys == null) + return; + } + + if (pIndex == IntPtr.Zero) + return; + + int offset = 0; + + int nConstraint = SQLiteMarshal.ReadInt32(pIndex, offset); + + if (includeInput && (nConstraint != constraints.Length)) + return; + + if (nConstraint != constraintUsages.Length) + return; + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), IntPtr.Size); + + if (includeInput) + { + IntPtr pConstraint = SQLiteMarshal.ReadIntPtr( + pIndex, offset); + + int sizeOfConstraintType = Marshal.SizeOf(typeof( + UnsafeNativeMethods.sqlite3_index_constraint)); + + for (int iConstraint = 0; iConstraint < nConstraint; iConstraint++) + { + UnsafeNativeMethods.sqlite3_index_constraint constraint = + new UnsafeNativeMethods.sqlite3_index_constraint( + constraints[iConstraint]); + + Marshal.StructureToPtr( + constraint, SQLiteMarshal.IntPtrForOffset( + pConstraint, iConstraint * sizeOfConstraintType), + false); + } + } + + offset = SQLiteMarshal.NextOffsetOf( + offset, IntPtr.Size, sizeof(int)); + + int nOrderBy = includeInput ? + SQLiteMarshal.ReadInt32(pIndex, offset) : 0; + + if (includeInput && (nOrderBy != orderBys.Length)) + return; + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), IntPtr.Size); + + if (includeInput) + { + IntPtr pOrderBy = SQLiteMarshal.ReadIntPtr(pIndex, offset); + + int sizeOfOrderByType = Marshal.SizeOf(typeof( + UnsafeNativeMethods.sqlite3_index_orderby)); + + for (int iOrderBy = 0; iOrderBy < nOrderBy; iOrderBy++) + { + UnsafeNativeMethods.sqlite3_index_orderby orderBy = + new UnsafeNativeMethods.sqlite3_index_orderby( + orderBys[iOrderBy]); + + Marshal.StructureToPtr( + orderBy, SQLiteMarshal.IntPtrForOffset( + pOrderBy, iOrderBy * sizeOfOrderByType), + false); + } + } + + offset = SQLiteMarshal.NextOffsetOf( + offset, IntPtr.Size, IntPtr.Size); + + IntPtr pConstraintUsage = SQLiteMarshal.ReadIntPtr( + pIndex, offset); + + int sizeOfConstraintUsageType = Marshal.SizeOf(typeof( + UnsafeNativeMethods.sqlite3_index_constraint_usage)); + + for (int iConstraint = 0; iConstraint < nConstraint; iConstraint++) + { + UnsafeNativeMethods.sqlite3_index_constraint_usage constraintUsage = + new UnsafeNativeMethods.sqlite3_index_constraint_usage( + constraintUsages[iConstraint]); + + Marshal.StructureToPtr( + constraintUsage, SQLiteMarshal.IntPtrForOffset( + pConstraintUsage, iConstraint * sizeOfConstraintUsageType), + false); + } + + offset = SQLiteMarshal.NextOffsetOf( + offset, IntPtr.Size, sizeof(int)); + + SQLiteMarshal.WriteInt32(pIndex, offset, + outputs.IndexNumber); + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), IntPtr.Size); + + SQLiteMarshal.WriteIntPtr(pIndex, offset, + SQLiteString.Utf8IntPtrFromString( + outputs.IndexString, false)); /* OK: FREED BY CORE*/ + + offset = SQLiteMarshal.NextOffsetOf( + offset, IntPtr.Size, sizeof(int)); + + // + // NOTE: We just allocated the IndexString field; therefore, we + // need to set make sure the NeedToFreeIndexString field + // is non-zero; however, we are not picky about the exact + // value. + // + int needToFreeIndexString = outputs.NeedToFreeIndexString != 0 ? + outputs.NeedToFreeIndexString : 1; + + SQLiteMarshal.WriteInt32(pIndex, offset, + needToFreeIndexString); + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), sizeof(int)); + + SQLiteMarshal.WriteInt32(pIndex, offset, + outputs.OrderByConsumed); + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), sizeof(double)); + + if (outputs.EstimatedCost.HasValue) + { + SQLiteMarshal.WriteDouble(pIndex, offset, + outputs.EstimatedCost.GetValueOrDefault()); + } + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(double), sizeof(long)); + + if (outputs.CanUseEstimatedRows() && + outputs.EstimatedRows.HasValue) + { + SQLiteMarshal.WriteInt64(pIndex, offset, + outputs.EstimatedRows.GetValueOrDefault()); + } + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(long), sizeof(int)); + + if (outputs.CanUseIndexFlags() && + outputs.IndexFlags.HasValue) + { + SQLiteMarshal.WriteInt32(pIndex, offset, + (int)outputs.IndexFlags.GetValueOrDefault()); + } + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), sizeof(long)); + + if (outputs.CanUseColumnsUsed() && + outputs.ColumnsUsed.HasValue) + { + SQLiteMarshal.WriteInt64(pIndex, offset, + outputs.ColumnsUsed.GetValueOrDefault()); + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Properties + private SQLiteIndexInputs inputs; + /// + /// The object instance containing + /// the inputs to the + /// method. + /// + public SQLiteIndexInputs Inputs + { + get { return inputs; } + } + + /////////////////////////////////////////////////////////////////////// + + private SQLiteIndexOutputs outputs; + /// + /// The object instance containing + /// the outputs from the + /// method. + /// + public SQLiteIndexOutputs Outputs + { + get { return outputs; } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteVirtualTable Base Class + /// + /// This class represents a managed virtual table implementation. It is + /// not sealed and should be used as the base class for any user-defined + /// virtual table classes implemented in managed code. + /// + public class SQLiteVirtualTable : + ISQLiteNativeHandle, IDisposable /* NOT SEALED */ + { + #region Private Constants + /// + /// The index within the array of strings provided to the + /// and + /// methods containing the + /// name of the module implementing this virtual table. + /// + private const int ModuleNameIndex = 0; + + /////////////////////////////////////////////////////////////////////// + + /// + /// The index within the array of strings provided to the + /// and + /// methods containing the + /// name of the database containing this virtual table. + /// + private const int DatabaseNameIndex = 1; + + /////////////////////////////////////////////////////////////////////// + + /// + /// The index within the array of strings provided to the + /// and + /// methods containing the + /// name of the virtual table. + /// + private const int TableNameIndex = 2; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs an instance of this class. + /// + /// + /// The original array of strings provided to the + /// and + /// methods. + /// + public SQLiteVirtualTable( + string[] arguments + ) + { + this.arguments = arguments; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Properties + private string[] arguments; + /// + /// The original array of strings provided to the + /// and + /// methods. + /// + public virtual string[] Arguments + { + get { CheckDisposed(); return arguments; } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// The name of the module implementing this virtual table. + /// + public virtual string ModuleName + { + get + { + CheckDisposed(); + + string[] arguments = Arguments; + + if ((arguments != null) && + (arguments.Length > ModuleNameIndex)) + { + return arguments[ModuleNameIndex]; + } + else + { + return null; + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// The name of the database containing this virtual table. + /// + public virtual string DatabaseName + { + get + { + CheckDisposed(); + + string[] arguments = Arguments; + + if ((arguments != null) && + (arguments.Length > DatabaseNameIndex)) + { + return arguments[DatabaseNameIndex]; + } + else + { + return null; + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// The name of the virtual table. + /// + public virtual string TableName + { + get + { + CheckDisposed(); + + string[] arguments = Arguments; + + if ((arguments != null) && + (arguments.Length > TableNameIndex)) + { + return arguments[TableNameIndex]; + } + else + { + return null; + } + } + } + + /////////////////////////////////////////////////////////////////////// + + private SQLiteIndex index; + /// + /// The object instance containing all the + /// data for the inputs and outputs relating to the most recent index + /// selection. + /// + public virtual SQLiteIndex Index + { + get { CheckDisposed(); return index; } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Methods + /// + /// This method should normally be used by the + /// method in order to + /// perform index selection based on the constraints provided by the + /// SQLite core library. + /// + /// + /// The object instance containing all the + /// data for the inputs and outputs relating to index selection. + /// + /// + /// Non-zero upon success. + /// + public virtual bool BestIndex( + SQLiteIndex index + ) + { + CheckDisposed(); + + this.index = index; + + return true; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to record the renaming of the virtual table associated + /// with this object instance. + /// + /// + /// The new name for the virtual table. + /// + /// + /// Non-zero upon success. + /// + public virtual bool Rename( + string name + ) + { + CheckDisposed(); + + if ((arguments != null) && + (arguments.Length > TableNameIndex)) + { + arguments[TableNameIndex] = name; + return true; + } + + return false; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteNativeHandle Members + private IntPtr nativeHandle; + /// + /// Returns the underlying SQLite native handle associated with this + /// object instance. + /// + public virtual IntPtr NativeHandle + { + get { CheckDisposed(); return nativeHandle; } + internal set { nativeHandle = value; } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes of this object instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + /// + /// Throws an if this object + /// instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteVirtualTable).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of this object instance. + /// + /// + /// Non-zero if this method is being called from the + /// method. Zero if this method is being called + /// from the finalizer. + /// + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Destructor + /// + /// Finalizes this object instance. + /// + ~SQLiteVirtualTable() + { + Dispose(false); + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteVirtualTableCursor Base Class + /// + /// This class represents a managed virtual table cursor implementation. + /// It is not sealed and should be used as the base class for any + /// user-defined virtual table cursor classes implemented in managed code. + /// + public class SQLiteVirtualTableCursor : + ISQLiteNativeHandle, IDisposable /* NOT SEALED */ + { + #region Protected Constants + /// + /// This value represents an invalid integer row sequence number. + /// + protected static readonly int InvalidRowIndex = 0; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Data + /// + /// The field holds the integer row sequence number for the current row + /// pointed to by this cursor object instance. + /// + private int rowIndex; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs an instance of this class. + /// + /// + /// The object instance associated + /// with this object instance. + /// + public SQLiteVirtualTableCursor( + SQLiteVirtualTable table + ) + : this() + { + this.table = table; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs an instance of this class. + /// + private SQLiteVirtualTableCursor() + { + rowIndex = InvalidRowIndex; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Properties + private SQLiteVirtualTable table; + /// + /// The object instance associated + /// with this object instance. + /// + public virtual SQLiteVirtualTable Table + { + get { CheckDisposed(); return table; } + } + + /////////////////////////////////////////////////////////////////////// + + private int indexNumber; + /// + /// Number used to help identify the selected index. This value will + /// be set via the method. + /// + public virtual int IndexNumber + { + get { CheckDisposed(); return indexNumber; } + } + + /////////////////////////////////////////////////////////////////////// + + private string indexString; + /// + /// String used to help identify the selected index. This value will + /// be set via the method. + /// + public virtual string IndexString + { + get { CheckDisposed(); return indexString; } + } + + /////////////////////////////////////////////////////////////////////// + + private SQLiteValue[] values; + /// + /// The values used to filter the rows returned via this cursor object + /// instance. This value will be set via the + /// method. + /// + public virtual SQLiteValue[] Values + { + get { CheckDisposed(); return values; } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Protected Methods + /// + /// Attempts to persist the specified object + /// instances in order to make them available after the + /// method returns. + /// + /// + /// The array of object instances to be + /// persisted. + /// + /// + /// The number of object instances that were + /// successfully persisted. + /// + protected virtual int TryPersistValues( + SQLiteValue[] values + ) + { + int result = 0; + + if (values != null) + { + foreach (SQLiteValue value in values) + { + if (value == null) + continue; + + if (value.Persist()) + result++; + } + } + + return result; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Methods + /// + /// This method should normally be used by the + /// method in order to + /// perform filtering of the result rows and/or to record the filtering + /// criteria provided by the SQLite core library. + /// + /// + /// Number used to help identify the selected index. + /// + /// + /// String used to help identify the selected index. + /// + /// + /// The values corresponding to each column in the selected index. + /// + public virtual void Filter( + int indexNumber, + string indexString, + SQLiteValue[] values + ) + { + CheckDisposed(); + + if ((values != null) && + (TryPersistValues(values) != values.Length)) + { + throw new SQLiteException( + "failed to persist one or more values"); + } + + this.indexNumber = indexNumber; + this.indexString = indexString; + this.values = values; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Determines the integer row sequence number for the current row. + /// + /// + /// The integer row sequence number for the current row -OR- zero if + /// it cannot be determined. + /// + public virtual int GetRowIndex() + { + return rowIndex; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Adjusts the integer row sequence number so that it refers to the + /// next row. + /// + public virtual void NextRowIndex() + { + rowIndex++; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteNativeHandle Members + private IntPtr nativeHandle; + /// + /// Returns the underlying SQLite native handle associated with this + /// object instance. + /// + public virtual IntPtr NativeHandle + { + get { CheckDisposed(); return nativeHandle; } + internal set { nativeHandle = value; } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes of this object instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + /// + /// Throws an if this object + /// instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteVirtualTableCursor).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of this object instance. + /// + /// + /// Non-zero if this method is being called from the + /// method. Zero if this method is being called + /// from the finalizer. + /// + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Destructor + /// + /// Finalizes this object instance. + /// + ~SQLiteVirtualTableCursor() + { + Dispose(false); + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region ISQLiteNativeHandle Interface + /// + /// This interface represents a native handle provided by the SQLite core + /// library. + /// + public interface ISQLiteNativeHandle + { + /// + /// The native handle value. + /// + IntPtr NativeHandle { get; } + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region ISQLiteManagedModule Interface + /// + /// This interface represents a virtual table implementation written in + /// managed code. + /// + public interface ISQLiteManagedModule + { + /// + /// Returns non-zero if the schema for the virtual table has been + /// declared. + /// + bool Declared { get; } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Returns the name of the module as it was registered with the SQLite + /// core library. + /// + string Name { get; } + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated with + /// the virtual table. + /// + /// + /// The native user-data pointer associated with this module, as it was + /// provided to the SQLite core library when the native module instance + /// was created. + /// + /// + /// The module name, database name, virtual table name, and all other + /// arguments passed to the CREATE VIRTUAL TABLE statement. + /// + /// + /// Upon success, this parameter must be modified to contain the + /// object instance associated with + /// the virtual table. + /// + /// + /// Upon failure, this parameter must be modified to contain an error + /// message. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Create( + SQLiteConnection connection, /* in */ + IntPtr pClientData, /* in */ + string[] arguments, /* in */ + ref SQLiteVirtualTable table, /* out */ + ref string error /* out */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated with + /// the virtual table. + /// + /// + /// The native user-data pointer associated with this module, as it was + /// provided to the SQLite core library when the native module instance + /// was created. + /// + /// + /// The module name, database name, virtual table name, and all other + /// arguments passed to the CREATE VIRTUAL TABLE statement. + /// + /// + /// Upon success, this parameter must be modified to contain the + /// object instance associated with + /// the virtual table. + /// + /// + /// Upon failure, this parameter must be modified to contain an error + /// message. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Connect( + SQLiteConnection connection, /* in */ + IntPtr pClientData, /* in */ + string[] arguments, /* in */ + ref SQLiteVirtualTable table, /* out */ + ref string error /* out */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// The object instance containing all the + /// data for the inputs and outputs relating to index selection. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode BestIndex( + SQLiteVirtualTable table, /* in */ + SQLiteIndex index /* in, out */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Disconnect( + SQLiteVirtualTable table /* in */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Destroy( + SQLiteVirtualTable table /* in */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// Upon success, this parameter must be modified to contain the + /// object instance associated + /// with the newly opened virtual table cursor. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Open( + SQLiteVirtualTable table, /* in */ + ref SQLiteVirtualTableCursor cursor /* out */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance + /// associated with the previously opened virtual table cursor to be + /// used. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Close( + SQLiteVirtualTableCursor cursor /* in */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance + /// associated with the previously opened virtual table cursor to be + /// used. + /// + /// + /// Number used to help identify the selected index. + /// + /// + /// String used to help identify the selected index. + /// + /// + /// The values corresponding to each column in the selected index. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Filter( + SQLiteVirtualTableCursor cursor, /* in */ + int indexNumber, /* in */ + string indexString, /* in */ + SQLiteValue[] values /* in */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance + /// associated with the previously opened virtual table cursor to be + /// used. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Next( + SQLiteVirtualTableCursor cursor /* in */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance + /// associated with the previously opened virtual table cursor to be + /// used. + /// + /// + /// Non-zero if no more rows are available; zero otherwise. + /// + bool Eof( + SQLiteVirtualTableCursor cursor /* in */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance + /// associated with the previously opened virtual table cursor to be + /// used. + /// + /// + /// The object instance to be used for + /// returning the specified column value to the SQLite core library. + /// + /// + /// The zero-based index corresponding to the column containing the + /// value to be returned. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Column( + SQLiteVirtualTableCursor cursor, /* in */ + SQLiteContext context, /* in */ + int index /* in */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance + /// associated with the previously opened virtual table cursor to be + /// used. + /// + /// + /// Upon success, this parameter must be modified to contain the unique + /// integer row identifier for the current row for the specified cursor. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode RowId( + SQLiteVirtualTableCursor cursor, /* in */ + ref long rowId /* out */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// The array of object instances containing + /// the new or modified column values, if any. + /// + /// + /// Upon success, this parameter must be modified to contain the unique + /// integer row identifier for the row that was inserted, if any. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Update( + SQLiteVirtualTable table, /* in */ + SQLiteValue[] values, /* in */ + ref long rowId /* out */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Begin( + SQLiteVirtualTable table /* in */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Sync( + SQLiteVirtualTable table /* in */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Commit( + SQLiteVirtualTable table /* in */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Rollback( + SQLiteVirtualTable table /* in */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// The number of arguments to the function being sought. + /// + /// + /// The name of the function being sought. + /// + /// + /// Upon success, this parameter must be modified to contain the + /// object instance responsible for + /// implementing the specified function. + /// + /// + /// Upon success, this parameter must be modified to contain the + /// native user-data pointer associated with + /// . + /// + /// + /// Non-zero if the specified function was found; zero otherwise. + /// + bool FindFunction( + SQLiteVirtualTable table, /* in */ + int argumentCount, /* in */ + string name, /* in */ + ref SQLiteFunction function, /* out */ + ref IntPtr pClientData /* out */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// The new name for the virtual table. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Rename( + SQLiteVirtualTable table, /* in */ + string newName /* in */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// This is an integer identifier under which the the current state of + /// the virtual table should be saved. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Savepoint( + SQLiteVirtualTable table, /* in */ + int savepoint /* in */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// This is an integer used to indicate that any saved states with an + /// identifier greater than or equal to this should be deleted by the + /// virtual table. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode Release( + SQLiteVirtualTable table, /* in */ + int savepoint /* in */ + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// This is an integer identifier used to specify a specific saved + /// state for the virtual table for it to restore itself back to, which + /// should also have the effect of deleting all saved states with an + /// integer identifier greater than this one. + /// + /// + /// A standard SQLite return code. + /// + SQLiteErrorCode RollbackTo( + SQLiteVirtualTable table, /* in */ + int savepoint /* in */ + ); + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteMemory Static Class + /// + /// This class contains static methods that are used to allocate, + /// manipulate, and free native memory provided by the SQLite core library. + /// + internal static class SQLiteMemory + { + #region Private Data +#if TRACK_MEMORY_BYTES + /// + /// This object instance is used to synchronize access to the other + /// static fields of this class. + /// + private static object syncRoot = new object(); + + /////////////////////////////////////////////////////////////////////// + + /// + /// The total number of outstanding memory bytes allocated by this + /// class using the SQLite core library. + /// + private static ulong bytesAllocated; + + /////////////////////////////////////////////////////////////////////// + + /// + /// The maximum number of outstanding memory bytes ever allocated by + /// this class using the SQLite core library. + /// + private static ulong maximumBytesAllocated; +#endif + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Memory Tracking Helper Methods +#if TRACK_MEMORY_BYTES + /// + /// Attempts to determine the size of the specified memory block. If + /// the method can be used, the returned value + /// may be larger than . A message may + /// be sent to the logging subsystem if an error is encountered. + /// + /// + /// The native pointer to the memory block previously obtained from + /// the , , + /// , or + /// methods or directly from the + /// SQLite core library. + /// + /// + /// The size of the specified memory block -OR- zero if the 32-bit + /// signed value reported from the native API was less than zero. + /// + private static ulong GetBlockSize( + IntPtr pMemory + ) + { + ulong ulongSize = 0; + + if (CanUseSize64()) + { + ulongSize = Size64(pMemory); + } + else + { + int intSize = Size(pMemory); + + if (intSize > 0) + { + ulongSize = (ulong)intSize; + } +#if DEBUG + else if (intSize < 0) + { + SQLiteLog.LogMessage(SQLiteErrorCode.Warning, + HelperMethods.StringFormat(CultureInfo.CurrentCulture, + "pointer {0} size {1} appears to be negative: {2}", + pMemory, intSize, +#if !PLATFORM_COMPACTFRAMEWORK + Environment.StackTrace +#else + null +#endif + )); + } +#endif + } + + return ulongSize; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Adjusts the total number of (tracked) bytes that are currently + /// considered to be allocated by this class. The total number is + /// increased by the total size of the memory block pointed to by + /// . If the new total number exceeds + /// the previously seen maximum, the maximum will be reset. + /// + /// + /// A native pointer to newly allocated memory. + /// + private static void MemoryWasAllocated( + IntPtr pMemory + ) + { + if (pMemory != IntPtr.Zero) + { + ulong blockSize = GetBlockSize(pMemory); + + if (blockSize > 0) + { + lock (syncRoot) + { + bytesAllocated += blockSize; + + if (bytesAllocated > maximumBytesAllocated) + maximumBytesAllocated = bytesAllocated; + } + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Adjusts the total number of (tracked) bytes that are currently + /// considered to be allocated by this class. The total number is + /// decreased by the total size of the memory block pointed to by + /// . + /// + /// + /// A native pointer to allocated memory that is going to be freed. + /// + private static void MemoryIsBeingFreed( + IntPtr pMemory + ) + { + if (pMemory != IntPtr.Zero) + { + ulong blockSize = GetBlockSize(pMemory); + + if (blockSize > 0) + { + lock (syncRoot) + { + bytesAllocated -= blockSize; + } + } + } + } +#endif + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Memory Version Helper Methods + /// + /// Determines if the native sqlite3_msize() API can be used, based on + /// the available version of the SQLite core library. + /// + /// + /// Non-zero if the native sqlite3_msize() API is supported by the + /// SQLite core library. + /// + private static bool CanUseSize64() + { +#if !PLATFORM_COMPACTFRAMEWORK || !SQLITE_STANDARD + if (UnsafeNativeMethods.sqlite3_libversion_number() >= 3008007) + return true; +#endif + + return false; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Memory Allocation Helper Methods + /// + /// Allocates at least the specified number of bytes of native memory + /// via the SQLite core library sqlite3_malloc() function and returns + /// the resulting native pointer. If the TRACK_MEMORY_BYTES option + /// was enabled at compile-time, adjusts the number of bytes currently + /// allocated by this class. + /// + /// + /// The number of bytes to allocate. + /// + /// + /// The native pointer that points to a block of memory of at least the + /// specified size -OR- if the memory could + /// not be allocated. + /// + public static IntPtr Allocate(int size) + { + IntPtr pMemory = UnsafeNativeMethods.sqlite3_malloc(size); + +#if TRACK_MEMORY_BYTES + MemoryWasAllocated(pMemory); +#endif + + return pMemory; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Allocates at least the specified number of bytes of native memory + /// via the SQLite core library sqlite3_malloc64() function and returns + /// the resulting native pointer. If the TRACK_MEMORY_BYTES option + /// was enabled at compile-time, adjusts the number of bytes currently + /// allocated by this class. + /// + /// + /// The number of bytes to allocate. + /// + /// + /// The native pointer that points to a block of memory of at least the + /// specified size -OR- if the memory could + /// not be allocated. + /// + public static IntPtr Allocate64(ulong size) + { + IntPtr pMemory = UnsafeNativeMethods.sqlite3_malloc64(size); + +#if TRACK_MEMORY_BYTES + MemoryWasAllocated(pMemory); +#endif + + return pMemory; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Allocates at least the specified number of bytes of native memory + /// via the SQLite core library sqlite3_malloc() function and returns + /// the resulting native pointer without adjusting the number of + /// allocated bytes currently tracked by this class. This is useful + /// when dealing with blocks of memory that will be freed directly by + /// the SQLite core library. + /// + /// + /// The number of bytes to allocate. + /// + /// + /// The native pointer that points to a block of memory of at least the + /// specified size -OR- if the memory could + /// not be allocated. + /// + public static IntPtr AllocateUntracked(int size) + { + return UnsafeNativeMethods.sqlite3_malloc(size); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Allocates at least the specified number of bytes of native memory + /// via the SQLite core library sqlite3_malloc64() function and returns + /// the resulting native pointer without adjusting the number of + /// allocated bytes currently tracked by this class. This is useful + /// when dealing with blocks of memory that will be freed directly by + /// the SQLite core library. + /// + /// + /// The number of bytes to allocate. + /// + /// + /// The native pointer that points to a block of memory of at least the + /// specified size -OR- if the memory could + /// not be allocated. + /// + public static IntPtr Allocate64Untracked(ulong size) + { + return UnsafeNativeMethods.sqlite3_malloc64(size); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Gets and returns the actual size of the specified memory block + /// that was previously obtained from the , + /// , , or + /// methods or directly from the + /// SQLite core library. + /// + /// + /// The native pointer to the memory block previously obtained from + /// the , , + /// , or + /// methods or directly from the + /// SQLite core library. + /// + /// + /// The actual size, in bytes, of the memory block specified via the + /// native pointer. + /// + public static int Size(IntPtr pMemory) + { +#if DEBUG + SQLiteMarshal.CheckAlignment("Size", pMemory, 0, IntPtr.Size); +#endif + +#if !SQLITE_STANDARD + return UnsafeNativeMethods.sqlite3_malloc_size_interop(pMemory); +#elif TRACK_MEMORY_BYTES + // + // HACK: Ok, we cannot determine the size of the memory block; + // therefore, just track number of allocations instead. + // + return (pMemory != IntPtr.Zero) ? 1 : 0; +#else + return 0; +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Gets and returns the actual size of the specified memory block + /// that was previously obtained from the , + /// , , or + /// methods or directly from the + /// SQLite core library. + /// + /// + /// The native pointer to the memory block previously obtained from + /// the , , + /// , or + /// methods or directly from the + /// SQLite core library. + /// + /// + /// The actual size, in bytes, of the memory block specified via the + /// native pointer. + /// + public static ulong Size64(IntPtr pMemory) + { +#if DEBUG + SQLiteMarshal.CheckAlignment("Size64", pMemory, 0, IntPtr.Size); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + return UnsafeNativeMethods.sqlite3_msize(pMemory); +#elif !SQLITE_STANDARD + ulong size = 0; + UnsafeNativeMethods.sqlite3_msize_interop(pMemory, ref size); + return size; +#else + throw new NotImplementedException(); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Frees a memory block previously obtained from the + /// or methods. If + /// the TRACK_MEMORY_BYTES option was enabled at compile-time, adjusts + /// the number of bytes currently allocated by this class. + /// + /// + /// The native pointer to the memory block previously obtained from the + /// or methods. + /// + public static void Free(IntPtr pMemory) + { +#if DEBUG + SQLiteMarshal.CheckAlignment("Free", pMemory, 0, IntPtr.Size); +#endif + +#if TRACK_MEMORY_BYTES + MemoryIsBeingFreed(pMemory); +#endif + + UnsafeNativeMethods.sqlite3_free(pMemory); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Frees a memory block previously obtained from the SQLite core + /// library without adjusting the number of allocated bytes currently + /// tracked by this class. This is useful when dealing with blocks of + /// memory that were not allocated using this class. + /// + /// + /// The native pointer to the memory block previously obtained from the + /// SQLite core library. + /// + public static void FreeUntracked(IntPtr pMemory) + { +#if DEBUG + SQLiteMarshal.CheckAlignment( + "FreeUntracked", pMemory, 0, IntPtr.Size); +#endif + + UnsafeNativeMethods.sqlite3_free(pMemory); + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteString Static Class + /// + /// This class contains static methods that are used to deal with native + /// UTF-8 string pointers to be used with the SQLite core library. + /// + internal static class SQLiteString + { + #region Private Constants + /// + /// This is the maximum possible length for the native UTF-8 encoded + /// strings used with the SQLite core library. + /// + private static int ThirtyBits = 0x3fffffff; + + /////////////////////////////////////////////////////////////////////// + + /// + /// This is the object instance used to handle + /// conversions from/to UTF-8. + /// + private static readonly Encoding Utf8Encoding = Encoding.UTF8; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region UTF-8 Encoding Helper Methods + /// + /// Converts the specified managed string into the UTF-8 encoding and + /// returns the array of bytes containing its representation in that + /// encoding. + /// + /// + /// The managed string to convert. + /// + /// + /// The array of bytes containing the representation of the managed + /// string in the UTF-8 encoding or null upon failure. + /// + public static byte[] GetUtf8BytesFromString( + string value + ) + { + if (value == null) + return null; + + return Utf8Encoding.GetBytes(value); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Converts the specified array of bytes representing a string in the + /// UTF-8 encoding and returns a managed string. + /// + /// + /// The array of bytes to convert. + /// + /// + /// The managed string or null upon failure. + /// + public static string GetStringFromUtf8Bytes( + byte[] bytes + ) + { + if (bytes == null) + return null; + +#if !PLATFORM_COMPACTFRAMEWORK + return Utf8Encoding.GetString(bytes); +#else + return Utf8Encoding.GetString(bytes, 0, bytes.Length); +#endif + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region UTF-8 String Helper Methods + /// + /// Probes a native pointer to a string in the UTF-8 encoding for its + /// terminating NUL character, within the specified length limit. + /// + /// + /// The native NUL-terminated string pointer. + /// + /// + /// The maximum length of the native string, in bytes. + /// + /// + /// The length of the native string, in bytes -OR- zero if the length + /// could not be determined. + /// + public static int ProbeForUtf8ByteLength( + IntPtr pValue, + int limit + ) + { + int length = 0; + + if ((pValue != IntPtr.Zero) && (limit > 0)) + { + do + { + if (Marshal.ReadByte(pValue, length) == 0) + break; + + if (length >= limit) + break; + + length++; + } while (true); + } + + return length; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Converts the specified native NUL-terminated UTF-8 string pointer + /// into a managed string. + /// + /// + /// The native NUL-terminated UTF-8 string pointer. + /// + /// + /// The managed string or null upon failure. + /// + public static string StringFromUtf8IntPtr( + IntPtr pValue + ) + { + return StringFromUtf8IntPtr(pValue, + ProbeForUtf8ByteLength(pValue, ThirtyBits)); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Converts the specified native UTF-8 string pointer of the specified + /// length into a managed string. + /// + /// + /// The native UTF-8 string pointer. + /// + /// + /// The length of the native string, in bytes. + /// + /// + /// The managed string or null upon failure. + /// + public static string StringFromUtf8IntPtr( + IntPtr pValue, + int length + ) + { + if (pValue == IntPtr.Zero) + return null; + + if (length > 0) + { + byte[] bytes = new byte[length]; + + Marshal.Copy(pValue, bytes, 0, length); + + return GetStringFromUtf8Bytes(bytes); + } + + return String.Empty; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Converts the specified managed string into a native NUL-terminated + /// UTF-8 string pointer using memory obtained from the SQLite core + /// library. + /// + /// + /// The managed string to convert. + /// + /// + /// The native NUL-terminated UTF-8 string pointer or + /// upon failure. + /// + public static IntPtr Utf8IntPtrFromString( + string value + ) + { + return Utf8IntPtrFromString(value, true); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Converts the specified managed string into a native NUL-terminated + /// UTF-8 string pointer using memory obtained from the SQLite core + /// library. + /// + /// + /// The managed string to convert. + /// + /// + /// Non-zero to obtain memory from the SQLite core library without + /// adjusting the number of allocated bytes currently being tracked + /// by the class. + /// + /// + /// The native NUL-terminated UTF-8 string pointer or + /// upon failure. + /// + public static IntPtr Utf8IntPtrFromString( + string value, + bool tracked + ) + { + int length = 0; + + return Utf8IntPtrFromString(value, tracked, ref length); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Converts the specified managed string into a native NUL-terminated + /// UTF-8 string pointer using memory obtained from the SQLite core + /// library. + /// + /// + /// The managed string to convert. + /// + /// + /// The length of the native string, in bytes. + /// + /// + /// The native NUL-terminated UTF-8 string pointer or + /// upon failure. + /// + public static IntPtr Utf8IntPtrFromString( + string value, + ref int length + ) + { + return Utf8IntPtrFromString(value, true, ref length); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Converts the specified managed string into a native NUL-terminated + /// UTF-8 string pointer using memory obtained from the SQLite core + /// library. + /// + /// + /// The managed string to convert. + /// + /// + /// Non-zero to obtain memory from the SQLite core library without + /// adjusting the number of allocated bytes currently being tracked + /// by the class. + /// + /// + /// The length of the native string, in bytes. + /// + /// + /// The native NUL-terminated UTF-8 string pointer or + /// upon failure. + /// + public static IntPtr Utf8IntPtrFromString( + string value, + bool tracked, + ref int length + ) + { + if (value == null) + return IntPtr.Zero; + + IntPtr result = IntPtr.Zero; + byte[] bytes = GetUtf8BytesFromString(value); + + if (bytes == null) + return IntPtr.Zero; + + length = bytes.Length; + + if (tracked) + result = SQLiteMemory.Allocate(length + 1); + else + result = SQLiteMemory.AllocateUntracked(length + 1); + + if (result == IntPtr.Zero) + return IntPtr.Zero; + + Marshal.Copy(bytes, 0, result, length); + Marshal.WriteByte(result, length, 0); + + return result; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region UTF-8 String Array Helper Methods + /// + /// Converts a logical array of native NUL-terminated UTF-8 string + /// pointers into an array of managed strings. + /// + /// + /// The number of elements in the logical array of native + /// NUL-terminated UTF-8 string pointers. + /// + /// + /// The native pointer to the logical array of native NUL-terminated + /// UTF-8 string pointers to convert. + /// + /// + /// The array of managed strings or null upon failure. + /// + public static string[] StringArrayFromUtf8SizeAndIntPtr( + int argc, + IntPtr argv + ) + { + if (argc < 0) + return null; + + if (argv == IntPtr.Zero) + return null; + + string[] result = new string[argc]; + + for (int index = 0, offset = 0; + index < result.Length; + index++, offset += IntPtr.Size) + { + IntPtr pArg = SQLiteMarshal.ReadIntPtr(argv, offset); + + result[index] = (pArg != IntPtr.Zero) ? + StringFromUtf8IntPtr(pArg) : null; + } + + return result; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Converts an array of managed strings into an array of native + /// NUL-terminated UTF-8 string pointers. + /// + /// + /// The array of managed strings to convert. + /// + /// + /// Non-zero to obtain memory from the SQLite core library without + /// adjusting the number of allocated bytes currently being tracked + /// by the class. + /// + /// + /// The array of native NUL-terminated UTF-8 string pointers or null + /// upon failure. + /// + public static IntPtr[] Utf8IntPtrArrayFromStringArray( + string[] values, + bool tracked + ) + { + if (values == null) + return null; + + IntPtr[] result = new IntPtr[values.Length]; + + for (int index = 0; index < result.Length; index++) + result[index] = Utf8IntPtrFromString(values[index], tracked); + + return result; + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteBytes Static Class + /// + /// This class contains static methods that are used to deal with native + /// pointers to memory blocks that logically contain arrays of bytes to be + /// used with the SQLite core library. + /// + internal static class SQLiteBytes + { + #region Byte Array Helper Methods + /// + /// Converts a native pointer to a logical array of bytes of the + /// specified length into a managed byte array. + /// + /// + /// The native pointer to the logical array of bytes to convert. + /// + /// + /// The length, in bytes, of the logical array of bytes to convert. + /// + /// + /// The managed byte array or null upon failure. + /// + public static byte[] FromIntPtr( + IntPtr pValue, + int length + ) + { + if (pValue == IntPtr.Zero) + return null; + + if (length == 0) + return new byte[0]; + + byte[] result = new byte[length]; + + Marshal.Copy(pValue, result, 0, length); + + return result; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Converts a managed byte array into a native pointer to a logical + /// array of bytes. + /// + /// + /// The managed byte array to convert. + /// + /// + /// The native pointer to a logical byte array or null upon failure. + /// + public static IntPtr ToIntPtr( + byte[] value + ) + { + int length = 0; + + return ToIntPtr(value, ref length); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Converts a managed byte array into a native pointer to a logical + /// array of bytes. + /// + /// + /// The managed byte array to convert. + /// + /// + /// The length, in bytes, of the converted logical array of bytes. + /// + /// + /// The native pointer to a logical byte array or null upon failure. + /// + public static IntPtr ToIntPtr( + byte[] value, + ref int length + ) + { + if (value == null) + return IntPtr.Zero; + + length = value.Length; + + if (length == 0) + return IntPtr.Zero; + + IntPtr result = SQLiteMemory.Allocate(length); + + if (result == IntPtr.Zero) + return IntPtr.Zero; + + Marshal.Copy(value, 0, result, length); + + return result; + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteMarshal Static Class + /// + /// This class contains static methods that are used to perform several + /// low-level data marshalling tasks between native and managed code. + /// + internal static class SQLiteMarshal + { + #region IntPtr Helper Methods + /// + /// Returns a new object instance based on the + /// specified object instance and an integer + /// offset. + /// + /// + /// The object instance representing the base + /// memory location. + /// + /// + /// The integer offset from the base memory location that the new + /// object instance should point to. + /// + /// + /// The new object instance. + /// + public static IntPtr IntPtrForOffset( + IntPtr pointer, + int offset + ) + { + return new IntPtr(pointer.ToInt64() + offset); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Rounds up an integer size to the next multiple of the alignment. + /// + /// + /// The size, in bytes, to be rounded up. + /// + /// + /// The required alignment for the return value. + /// + /// + /// The size, in bytes, rounded up to the next multiple of the + /// alignment. This value may end up being the same as the original + /// size. + /// + public static int RoundUp( + int size, + int alignment + ) + { + int alignmentMinusOne = alignment - 1; + return ((size + alignmentMinusOne) & ~alignmentMinusOne); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Determines the offset, in bytes, of the next structure member. + /// + /// + /// The offset, in bytes, of the current structure member. + /// + /// + /// The size, in bytes, of the current structure member. + /// + /// + /// The alignment, in bytes, of the next structure member. + /// + /// + /// The offset, in bytes, of the next structure member. + /// + public static int NextOffsetOf( + int offset, + int size, + int alignment + ) + { + return RoundUp(offset + size, alignment); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Marshal Read Helper Methods + /// + /// Reads a value from the specified memory + /// location. + /// + /// + /// The object instance representing the base + /// memory location. + /// + /// + /// The integer offset from the base memory location where the + /// value to be read is located. + /// + /// + /// The value at the specified memory location. + /// + public static int ReadInt32( + IntPtr pointer, + int offset + ) + { +#if DEBUG + CheckAlignment("ReadInt32", pointer, offset, sizeof(int)); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + return Marshal.ReadInt32(pointer, offset); +#else + return Marshal.ReadInt32(IntPtrForOffset(pointer, offset)); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Reads a value from the specified memory + /// location. + /// + /// + /// The object instance representing the base + /// memory location. + /// + /// + /// The integer offset from the base memory location where the + /// value to be read is located. + /// + /// + /// The value at the specified memory location. + /// + public static long ReadInt64( + IntPtr pointer, + int offset + ) + { +#if DEBUG + CheckAlignment("ReadInt64", pointer, offset, sizeof(long)); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + return Marshal.ReadInt64(pointer, offset); +#else + return Marshal.ReadInt64(IntPtrForOffset(pointer, offset)); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Reads a value from the specified memory + /// location. + /// + /// + /// The object instance representing the base + /// memory location. + /// + /// + /// The integer offset from the base memory location where the + /// to be read is located. + /// + /// + /// The value at the specified memory location. + /// + public static double ReadDouble( + IntPtr pointer, + int offset + ) + { +#if DEBUG + CheckAlignment("ReadDouble", pointer, offset, sizeof(double)); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + return BitConverter.Int64BitsToDouble(Marshal.ReadInt64( + pointer, offset)); +#else + return BitConverter.ToDouble(BitConverter.GetBytes( + Marshal.ReadInt64(IntPtrForOffset(pointer, offset))), 0); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Reads an value from the specified memory + /// location. + /// + /// + /// The object instance representing the base + /// memory location. + /// + /// + /// The integer offset from the base memory location where the + /// value to be read is located. + /// + /// + /// The value at the specified memory location. + /// + public static IntPtr ReadIntPtr( + IntPtr pointer, + int offset + ) + { +#if DEBUG + CheckAlignment("ReadIntPtr", pointer, offset, IntPtr.Size); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + return Marshal.ReadIntPtr(pointer, offset); +#else + return Marshal.ReadIntPtr(IntPtrForOffset(pointer, offset)); +#endif + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Marshal Write Helper Methods + /// + /// Writes an value to the specified memory + /// location. + /// + /// + /// The object instance representing the base + /// memory location. + /// + /// + /// The integer offset from the base memory location where the + /// value to be written is located. + /// + /// + /// The value to write. + /// + public static void WriteInt32( + IntPtr pointer, + int offset, + int value + ) + { +#if DEBUG + CheckAlignment("WriteInt32", pointer, offset, sizeof(int)); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + Marshal.WriteInt32(pointer, offset, value); +#else + Marshal.WriteInt32(IntPtrForOffset(pointer, offset), value); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Writes an value to the specified memory + /// location. + /// + /// + /// The object instance representing the base + /// memory location. + /// + /// + /// The integer offset from the base memory location where the + /// value to be written is located. + /// + /// + /// The value to write. + /// + public static void WriteInt64( + IntPtr pointer, + int offset, + long value + ) + { +#if DEBUG + CheckAlignment("WriteInt64", pointer, offset, sizeof(long)); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + Marshal.WriteInt64(pointer, offset, value); +#else + Marshal.WriteInt64(IntPtrForOffset(pointer, offset), value); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Writes a value to the specified memory + /// location. + /// + /// + /// The object instance representing the base + /// memory location. + /// + /// + /// The integer offset from the base memory location where the + /// value to be written is located. + /// + /// + /// The value to write. + /// + public static void WriteDouble( + IntPtr pointer, + int offset, + double value + ) + { +#if DEBUG + CheckAlignment("WriteDouble", pointer, offset, sizeof(double)); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + Marshal.WriteInt64(pointer, offset, + BitConverter.DoubleToInt64Bits(value)); +#else + Marshal.WriteInt64(IntPtrForOffset(pointer, offset), + BitConverter.ToInt64(BitConverter.GetBytes(value), 0)); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Writes a value to the specified memory + /// location. + /// + /// + /// The object instance representing the base + /// memory location. + /// + /// + /// The integer offset from the base memory location where the + /// value to be written is located. + /// + /// + /// The value to write. + /// + public static void WriteIntPtr( + IntPtr pointer, + int offset, + IntPtr value + ) + { +#if DEBUG + CheckAlignment( + "WriteIntPtr(pointer)", pointer, offset, IntPtr.Size); + + CheckAlignment("WriteIntPtr(value)", value, 0, IntPtr.Size); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + Marshal.WriteIntPtr(pointer, offset, value); +#else + Marshal.WriteIntPtr(IntPtrForOffset(pointer, offset), value); +#endif + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Object Helper Methods + /// + /// Generates a hash code value for the object. + /// + /// + /// The object instance used to calculate the hash code. + /// + /// + /// Non-zero if different object instances with the same value should + /// generate different hash codes, where applicable. This parameter + /// has no effect on the .NET Compact Framework. + /// + /// + /// The hash code value -OR- zero if the object is null. + /// + public static int GetHashCode( + object value, + bool identity + ) + { +#if !PLATFORM_COMPACTFRAMEWORK + if (identity) + return RuntimeHelpers.GetHashCode(value); +#endif + + if (value == null) return 0; + return value.GetHashCode(); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Methods +#if DEBUG + /// + /// Attempts to verify that the specified native pointer is properly + /// aligned for the size of the data value. If that is not the case, + /// a message will be sent to the logging subsystem. + /// + /// + /// The type of operation being performed by the caller. This value + /// may be used within diagnostic messages. + /// + /// + /// The object instance representing the base + /// memory location. + /// + /// + /// The integer offset from the base memory location where the data + /// value to be read or written. + /// + /// + /// The size, in bytes, of the data value. + /// + internal static void CheckAlignment( + string type, + IntPtr pointer, + int offset, + int size + ) + { + IntPtr savedPointer = pointer; + + if (offset != 0) + pointer = new IntPtr(pointer.ToInt64() + offset); + + if ((pointer.ToInt64() % size) != 0) + { + SQLiteLog.LogMessage(SQLiteErrorCode.Warning, + HelperMethods.StringFormat(CultureInfo.CurrentCulture, + "{0}: pointer {1} and offset {2} not aligned to {3}: {4}", + type, savedPointer, offset, size, +#if !PLATFORM_COMPACTFRAMEWORK + Environment.StackTrace +#else + null +#endif + )); + } + } +#endif + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteModule Base Class + /// + /// This class represents a managed virtual table module implementation. + /// It is not sealed and must be used as the base class for any + /// user-defined virtual table module classes implemented in managed code. + /// + public abstract class SQLiteModule : + ISQLiteManagedModule, /*ISQLiteNativeModule,*/ + IDisposable /* NOT SEALED */ + { + #region SQLiteNativeModule Private Class + /// + /// This class implements the + /// interface by forwarding those method calls to the + /// object instance it contains. If the + /// contained object instance is null, all + /// the methods simply generate an + /// error. + /// + private sealed class SQLiteNativeModule : + ISQLiteNativeModule, IDisposable + { + #region Private Constants + /// + /// This is the value that is always used for the "logErrors" + /// parameter to the various static error handling methods provided + /// by the class. + /// + private const bool DefaultLogErrors = true; + + /////////////////////////////////////////////////////////////////// + + /// + /// This is the value that is always used for the "logExceptions" + /// parameter to the various static error handling methods provided + /// by the class. + /// + private const bool DefaultLogExceptions = true; + + /////////////////////////////////////////////////////////////////// + + /// + /// This is the error message text used when the contained + /// object instance is not available + /// for any reason. + /// + private const string ModuleNotAvailableErrorMessage = + "native module implementation not available"; + #endregion + + /////////////////////////////////////////////////////////////////// + + #region Private Data + /// + /// The object instance used to provide + /// an implementation of the + /// interface. + /// + private SQLiteModule module; + #endregion + + /////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs an instance of this class. + /// + /// + /// The object instance used to provide + /// an implementation of the + /// interface. + /// + public SQLiteNativeModule( + SQLiteModule module + ) + { + this.module = module; + } + #endregion + + /////////////////////////////////////////////////////////////////// + + #region Private Static Methods + /// + /// Sets the table error message to one that indicates the native + /// module implementation is not available. + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// The value of . + /// + private static SQLiteErrorCode ModuleNotAvailableTableError( + IntPtr pVtab + ) + { + SetTableError(null, pVtab, DefaultLogErrors, + DefaultLogExceptions, ModuleNotAvailableErrorMessage); + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////// + + /// + /// Sets the table error message to one that indicates the native + /// module implementation is not available. + /// + /// + /// The native pointer to the sqlite3_vtab_cursor derived + /// structure. + /// + /// + /// The value of . + /// + private static SQLiteErrorCode ModuleNotAvailableCursorError( + IntPtr pCursor + ) + { + SetCursorError(null, pCursor, DefaultLogErrors, + DefaultLogExceptions, ModuleNotAvailableErrorMessage); + + return SQLiteErrorCode.Error; + } + #endregion + + /////////////////////////////////////////////////////////////////// + + #region ISQLiteNativeModule Members + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xCreate( + IntPtr pDb, + IntPtr pAux, + int argc, + IntPtr argv, + ref IntPtr pVtab, + ref IntPtr pError + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + { + pError = SQLiteString.Utf8IntPtrFromString( + ModuleNotAvailableErrorMessage); + + return SQLiteErrorCode.Error; + } + + return module.xCreate( + pDb, pAux, argc, argv, ref pVtab, ref pError); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xConnect( + IntPtr pDb, + IntPtr pAux, + int argc, + IntPtr argv, + ref IntPtr pVtab, + ref IntPtr pError + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + { + pError = SQLiteString.Utf8IntPtrFromString( + ModuleNotAvailableErrorMessage); + + return SQLiteErrorCode.Error; + } + + return module.xConnect( + pDb, pAux, argc, argv, ref pVtab, ref pError); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xBestIndex( + IntPtr pVtab, + IntPtr pIndex + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableTableError(pVtab); + + return module.xBestIndex(pVtab, pIndex); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xDisconnect( + IntPtr pVtab + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableTableError(pVtab); + + return module.xDisconnect(pVtab); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xDestroy( + IntPtr pVtab + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableTableError(pVtab); + + return module.xDestroy(pVtab); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xOpen( + IntPtr pVtab, + ref IntPtr pCursor + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableTableError(pVtab); + + return module.xOpen(pVtab, ref pCursor); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xClose( + IntPtr pCursor + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableCursorError(pCursor); + + return module.xClose(pCursor); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xFilter( + IntPtr pCursor, + int idxNum, + IntPtr idxStr, + int argc, + IntPtr argv + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableCursorError(pCursor); + + return module.xFilter(pCursor, idxNum, idxStr, argc, argv); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xNext( + IntPtr pCursor + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableCursorError(pCursor); + + return module.xNext(pCursor); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public int xEof( + IntPtr pCursor + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + { + ModuleNotAvailableCursorError(pCursor); + return 1; + } + + return module.xEof(pCursor); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xColumn( + IntPtr pCursor, + IntPtr pContext, + int index + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableCursorError(pCursor); + + return module.xColumn(pCursor, pContext, index); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xRowId( + IntPtr pCursor, + ref long rowId + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableCursorError(pCursor); + + return module.xRowId(pCursor, ref rowId); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xUpdate( + IntPtr pVtab, + int argc, + IntPtr argv, + ref long rowId + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableTableError(pVtab); + + return module.xUpdate(pVtab, argc, argv, ref rowId); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xBegin( + IntPtr pVtab + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableTableError(pVtab); + + return module.xBegin(pVtab); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xSync( + IntPtr pVtab + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableTableError(pVtab); + + return module.xSync(pVtab); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xCommit( + IntPtr pVtab + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableTableError(pVtab); + + return module.xCommit(pVtab); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xRollback( + IntPtr pVtab + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableTableError(pVtab); + + return module.xRollback(pVtab); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public int xFindFunction( + IntPtr pVtab, + int nArg, + IntPtr zName, + ref SQLiteCallback callback, + ref IntPtr pClientData + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + { + ModuleNotAvailableTableError(pVtab); + return 0; + } + + return module.xFindFunction( + pVtab, nArg, zName, ref callback, ref pClientData); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xRename( + IntPtr pVtab, + IntPtr zNew + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableTableError(pVtab); + + return module.xRename(pVtab, zNew); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xSavepoint( + IntPtr pVtab, + int iSavepoint + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableTableError(pVtab); + + return module.xSavepoint(pVtab, iSavepoint); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xRelease( + IntPtr pVtab, + int iSavepoint + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableTableError(pVtab); + + return module.xRelease(pVtab, iSavepoint); + } + + /////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public SQLiteErrorCode xRollbackTo( + IntPtr pVtab, + int iSavepoint + ) + { + // + // NOTE: Called by native code. + // + // CheckDisposed(); /* EXEMPT */ + + if (module == null) + return ModuleNotAvailableTableError(pVtab); + + return module.xRollbackTo(pVtab, iSavepoint); + } + #endregion + + /////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes of this object instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + /// + /// Throws an if this object + /// instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteNativeModule).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////// + + /// + /// Disposes of this object instance. + /// + /// + /// Non-zero if this method is being called from the + /// method. Zero if this method is being + /// called from the finalizer. + /// + private /* protected virtual */ void Dispose(bool disposing) + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + // + // NOTE: The module is not owned by us; therefore, do not + // dispose it. + // + if (module != null) + module = null; + + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////// + + #region Destructor + /// + /// Finalizes this object instance. + /// + ~SQLiteNativeModule() + { + Dispose(false); + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Constants + /// + /// The default version of the native sqlite3_module structure in use. + /// + private static readonly int DefaultModuleVersion = 2; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Data + /// + /// This field is used to store the native sqlite3_module structure + /// associated with this object instance. + /// + private UnsafeNativeMethods.sqlite3_module nativeModule; + + /////////////////////////////////////////////////////////////////////// + + /// + /// This field is used to store the destructor delegate to be passed to + /// the SQLite core library via the sqlite3_create_disposable_module() + /// function. + /// + private UnsafeNativeMethods.xDestroyModule destroyModule; + + /////////////////////////////////////////////////////////////////////// + + /// + /// This field is used to store a pointer to the native sqlite3_module + /// structure returned by the sqlite3_create_disposable_module + /// function. + /// + private IntPtr disposableModule; + + /////////////////////////////////////////////////////////////////////// + +#if PLATFORM_COMPACTFRAMEWORK + /// + /// This field is used to hold the block of native memory that contains + /// the native sqlite3_module structure associated with this object + /// instance when running on the .NET Compact Framework. + /// + private IntPtr pNativeModule; +#endif + + /////////////////////////////////////////////////////////////////////// + + /// + /// This field is used to store the virtual table instances associated + /// with this module. The native pointer to the sqlite3_vtab derived + /// structure is used to key into this collection. + /// + private Dictionary tables; + + /////////////////////////////////////////////////////////////////////// + + /// + /// This field is used to store the virtual table cursor instances + /// associated with this module. The native pointer to the + /// sqlite3_vtab_cursor derived structure is used to key into this + /// collection. + /// + private Dictionary cursors; + + /////////////////////////////////////////////////////////////////////// + + /// + /// This field is used to store the virtual table function instances + /// associated with this module. The case-insensitive function name + /// and the number of arguments (with -1 meaning "any") are used to + /// construct the string that is used to key into this collection. + /// + private Dictionary functions; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs an instance of this class. + /// + /// + /// The name of the module. This parameter cannot be null. + /// + public SQLiteModule(string name) + { + if (name == null) + throw new ArgumentNullException("name"); + + this.name = name; + this.tables = new Dictionary(); + this.cursors = new Dictionary(); + this.functions = new Dictionary(); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Internal Methods + /// + /// Calls the native SQLite core library in order to create a new + /// disposable module containing the implementation of a virtual table. + /// + /// + /// The native database connection pointer to use. + /// + /// + /// Non-zero upon success. + /// + internal bool CreateDisposableModule( + IntPtr pDb + ) + { + if (disposableModule != IntPtr.Zero) + return true; + + IntPtr pName = IntPtr.Zero; + + try + { + pName = SQLiteString.Utf8IntPtrFromString(name); + + UnsafeNativeMethods.sqlite3_module nativeModule = + AllocateNativeModule(); + + destroyModule = new UnsafeNativeMethods.xDestroyModule( + xDestroyModule); + +#if !PLATFORM_COMPACTFRAMEWORK + disposableModule = + UnsafeNativeMethods.sqlite3_create_disposable_module( + pDb, pName, ref nativeModule, IntPtr.Zero, destroyModule); + + return (disposableModule != IntPtr.Zero); +#elif !SQLITE_STANDARD + disposableModule = + UnsafeNativeMethods.sqlite3_create_disposable_module_interop( + pDb, pName, AllocateNativeModuleInterop(), + nativeModule.iVersion, nativeModule.xCreate, + nativeModule.xConnect, nativeModule.xBestIndex, + nativeModule.xDisconnect, nativeModule.xDestroy, + nativeModule.xOpen, nativeModule.xClose, + nativeModule.xFilter, nativeModule.xNext, + nativeModule.xEof, nativeModule.xColumn, + nativeModule.xRowId, nativeModule.xUpdate, + nativeModule.xBegin, nativeModule.xSync, + nativeModule.xCommit, nativeModule.xRollback, + nativeModule.xFindFunction, nativeModule.xRename, + nativeModule.xSavepoint, nativeModule.xRelease, + nativeModule.xRollbackTo, IntPtr.Zero, destroyModule); + + return (disposableModule != IntPtr.Zero); +#else + throw new NotImplementedException(); +#endif + } + finally + { + if (pName != IntPtr.Zero) + { + SQLiteMemory.Free(pName); + pName = IntPtr.Zero; + } + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// This method is called by the SQLite core library when the native + /// module associated with this object instance is being destroyed due + /// to its parent connection being closed. It may also be called by + /// the "vtshim" module if/when the sqlite3_dispose_module() function + /// is called. + /// + /// + /// The native user-data pointer associated with this module, as it was + /// provided to the SQLite core library when the native module instance + /// was created. + /// + private void xDestroyModule( + IntPtr pClientData /* NOT USED */ + ) + { + // + // NOTE: At this point, just make sure that this native module + // handle is not reused, nor passed into the native + // sqlite3_dispose_module() function later (i.e. if/when + // the Dispose() method of this object instance is called). + // + disposableModule = IntPtr.Zero; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Creates and returns the native sqlite_module structure using the + /// configured (or default) + /// interface implementation. + /// + /// + /// The native sqlite_module structure using the configured (or + /// default) interface + /// implementation. + /// + private UnsafeNativeMethods.sqlite3_module AllocateNativeModule() + { + return AllocateNativeModule(GetNativeModuleImpl()); + } + + /////////////////////////////////////////////////////////////////////// + +#if PLATFORM_COMPACTFRAMEWORK + /// + /// Creates and returns a memory block obtained from the SQLite core + /// library used to store the native sqlite3_module structure for this + /// object instance when running on the .NET Compact Framework. + /// + /// + /// The native pointer to the native sqlite3_module structure. + /// + private IntPtr AllocateNativeModuleInterop() + { + if (pNativeModule == IntPtr.Zero) + { + // + // HACK: No easy way to determine the size of the native + // sqlite_module structure when running on the .NET + // Compact Framework; therefore, just base the size + // on what we know: + // + // There is one integer member. + // There are 22 function pointer members. + // + pNativeModule = SQLiteMemory.Allocate(23 * IntPtr.Size); + + if (pNativeModule == IntPtr.Zero) + throw new OutOfMemoryException("sqlite3_module"); + } + + return pNativeModule; + } +#endif + + /////////////////////////////////////////////////////////////////////// + + /// + /// Creates and returns the native sqlite_module structure using the + /// specified interface + /// implementation. + /// + /// + /// The interface implementation to + /// use. + /// + /// + /// The native sqlite_module structure using the specified + /// interface implementation. + /// + private UnsafeNativeMethods.sqlite3_module AllocateNativeModule( + ISQLiteNativeModule module + ) + { + nativeModule = new UnsafeNativeMethods.sqlite3_module(); + nativeModule.iVersion = DefaultModuleVersion; + + if (module != null) + { + nativeModule.xCreate = new UnsafeNativeMethods.xCreate( + module.xCreate); + + nativeModule.xConnect = new UnsafeNativeMethods.xConnect( + module.xConnect); + + nativeModule.xBestIndex = new UnsafeNativeMethods.xBestIndex( + module.xBestIndex); + + nativeModule.xDisconnect = new UnsafeNativeMethods.xDisconnect( + module.xDisconnect); + + nativeModule.xDestroy = new UnsafeNativeMethods.xDestroy( + module.xDestroy); + + nativeModule.xOpen = new UnsafeNativeMethods.xOpen( + module.xOpen); + + nativeModule.xClose = new UnsafeNativeMethods.xClose( + module.xClose); + + nativeModule.xFilter = new UnsafeNativeMethods.xFilter( + module.xFilter); + + nativeModule.xNext = new UnsafeNativeMethods.xNext( + module.xNext); + + nativeModule.xEof = new UnsafeNativeMethods.xEof(module.xEof); + + nativeModule.xColumn = new UnsafeNativeMethods.xColumn( + module.xColumn); + + nativeModule.xRowId = new UnsafeNativeMethods.xRowId( + module.xRowId); + + nativeModule.xUpdate = new UnsafeNativeMethods.xUpdate( + module.xUpdate); + + nativeModule.xBegin = new UnsafeNativeMethods.xBegin( + module.xBegin); + + nativeModule.xSync = new UnsafeNativeMethods.xSync( + module.xSync); + + nativeModule.xCommit = new UnsafeNativeMethods.xCommit( + module.xCommit); + + nativeModule.xRollback = new UnsafeNativeMethods.xRollback( + module.xRollback); + + nativeModule.xFindFunction = new UnsafeNativeMethods.xFindFunction( + module.xFindFunction); + + nativeModule.xRename = new UnsafeNativeMethods.xRename( + module.xRename); + + nativeModule.xSavepoint = new UnsafeNativeMethods.xSavepoint( + module.xSavepoint); + + nativeModule.xRelease = new UnsafeNativeMethods.xRelease( + module.xRelease); + + nativeModule.xRollbackTo = new UnsafeNativeMethods.xRollbackTo( + module.xRollbackTo); + } + else + { + nativeModule.xCreate = new UnsafeNativeMethods.xCreate( + xCreate); + + nativeModule.xConnect = new UnsafeNativeMethods.xConnect( + xConnect); + + nativeModule.xBestIndex = new UnsafeNativeMethods.xBestIndex( + xBestIndex); + + nativeModule.xDisconnect = new UnsafeNativeMethods.xDisconnect( + xDisconnect); + + nativeModule.xDestroy = new UnsafeNativeMethods.xDestroy( + xDestroy); + + nativeModule.xOpen = new UnsafeNativeMethods.xOpen(xOpen); + nativeModule.xClose = new UnsafeNativeMethods.xClose(xClose); + + nativeModule.xFilter = new UnsafeNativeMethods.xFilter( + xFilter); + + nativeModule.xNext = new UnsafeNativeMethods.xNext(xNext); + nativeModule.xEof = new UnsafeNativeMethods.xEof(xEof); + + nativeModule.xColumn = new UnsafeNativeMethods.xColumn( + xColumn); + + nativeModule.xRowId = new UnsafeNativeMethods.xRowId(xRowId); + + nativeModule.xUpdate = new UnsafeNativeMethods.xUpdate( + xUpdate); + + nativeModule.xBegin = new UnsafeNativeMethods.xBegin(xBegin); + nativeModule.xSync = new UnsafeNativeMethods.xSync(xSync); + + nativeModule.xCommit = new UnsafeNativeMethods.xCommit( + xCommit); + + nativeModule.xRollback = new UnsafeNativeMethods.xRollback( + xRollback); + + nativeModule.xFindFunction = new UnsafeNativeMethods.xFindFunction( + xFindFunction); + + nativeModule.xRename = new UnsafeNativeMethods.xRename( + xRename); + + nativeModule.xSavepoint = new UnsafeNativeMethods.xSavepoint( + xSavepoint); + + nativeModule.xRelease = new UnsafeNativeMethods.xRelease( + xRelease); + + nativeModule.xRollbackTo = new UnsafeNativeMethods.xRollbackTo( + xRollbackTo); + } + + return nativeModule; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Creates a copy of the specified + /// object instance, + /// using default implementations for the contained delegates when + /// necessary. + /// + /// + /// The object + /// instance to copy. + /// + /// + /// The new object + /// instance. + /// + private UnsafeNativeMethods.sqlite3_module CopyNativeModule( + UnsafeNativeMethods.sqlite3_module module + ) + { + UnsafeNativeMethods.sqlite3_module newModule = + new UnsafeNativeMethods.sqlite3_module(); + + newModule.iVersion = module.iVersion; + + newModule.xCreate = new UnsafeNativeMethods.xCreate( + (module.xCreate != null) ? module.xCreate : xCreate); + + newModule.xConnect = new UnsafeNativeMethods.xConnect( + (module.xConnect != null) ? module.xConnect : xConnect); + + newModule.xBestIndex = new UnsafeNativeMethods.xBestIndex( + (module.xBestIndex != null) ? module.xBestIndex : xBestIndex); + + newModule.xDisconnect = new UnsafeNativeMethods.xDisconnect( + (module.xDisconnect != null) ? module.xDisconnect : + xDisconnect); + + newModule.xDestroy = new UnsafeNativeMethods.xDestroy( + (module.xDestroy != null) ? module.xDestroy : xDestroy); + + newModule.xOpen = new UnsafeNativeMethods.xOpen( + (module.xOpen != null) ? module.xOpen : xOpen); + + newModule.xClose = new UnsafeNativeMethods.xClose( + (module.xClose != null) ? module.xClose : xClose); + + newModule.xFilter = new UnsafeNativeMethods.xFilter( + (module.xFilter != null) ? module.xFilter : xFilter); + + newModule.xNext = new UnsafeNativeMethods.xNext( + (module.xNext != null) ? module.xNext : xNext); + + newModule.xEof = new UnsafeNativeMethods.xEof( + (module.xEof != null) ? module.xEof : xEof); + + newModule.xColumn = new UnsafeNativeMethods.xColumn( + (module.xColumn != null) ? module.xColumn : xColumn); + + newModule.xRowId = new UnsafeNativeMethods.xRowId( + (module.xRowId != null) ? module.xRowId : xRowId); + + newModule.xUpdate = new UnsafeNativeMethods.xUpdate( + (module.xUpdate != null) ? module.xUpdate : xUpdate); + + newModule.xBegin = new UnsafeNativeMethods.xBegin( + (module.xBegin != null) ? module.xBegin : xBegin); + + newModule.xSync = new UnsafeNativeMethods.xSync( + (module.xSync != null) ? module.xSync : xSync); + + newModule.xCommit = new UnsafeNativeMethods.xCommit( + (module.xCommit != null) ? module.xCommit : xCommit); + + newModule.xRollback = new UnsafeNativeMethods.xRollback( + (module.xRollback != null) ? module.xRollback : xRollback); + + newModule.xFindFunction = new UnsafeNativeMethods.xFindFunction( + (module.xFindFunction != null) ? module.xFindFunction : + xFindFunction); + + newModule.xRename = new UnsafeNativeMethods.xRename( + (module.xRename != null) ? module.xRename : xRename); + + newModule.xSavepoint = new UnsafeNativeMethods.xSavepoint( + (module.xSavepoint != null) ? module.xSavepoint : xSavepoint); + + newModule.xRelease = new UnsafeNativeMethods.xRelease( + (module.xRelease != null) ? module.xRelease : xRelease); + + newModule.xRollbackTo = new UnsafeNativeMethods.xRollbackTo( + (module.xRollbackTo != null) ? module.xRollbackTo : + xRollbackTo); + + return newModule; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Calls one of the virtual table initialization methods. + /// + /// + /// Non-zero to call the + /// method; otherwise, the + /// method will be called. + /// + /// + /// The native database connection handle. + /// + /// + /// The original native pointer value that was provided to the + /// sqlite3_create_module(), sqlite3_create_module_v2() or + /// sqlite3_create_disposable_module() functions. + /// + /// + /// The number of arguments from the CREATE VIRTUAL TABLE statement. + /// + /// + /// The array of string arguments from the CREATE VIRTUAL TABLE + /// statement. + /// + /// + /// Upon success, this parameter must be modified to point to the newly + /// created native sqlite3_vtab derived structure. + /// + /// + /// Upon failure, this parameter must be modified to point to the error + /// message, with the underlying memory having been obtained from the + /// sqlite3_malloc() function. + /// + /// + /// A standard SQLite return code. + /// + private SQLiteErrorCode CreateOrConnect( + bool create, + IntPtr pDb, + IntPtr pAux, + int argc, + IntPtr argv, + ref IntPtr pVtab, + ref IntPtr pError + ) + { + try + { + string fileName = SQLiteString.StringFromUtf8IntPtr( + UnsafeNativeMethods.sqlite3_db_filename(pDb, IntPtr.Zero)); + + using (SQLiteConnection connection = new SQLiteConnection( + pDb, fileName, false)) + { + SQLiteVirtualTable table = null; + string error = null; + + if ((create && Create(connection, pAux, + SQLiteString.StringArrayFromUtf8SizeAndIntPtr(argc, + argv), ref table, ref error) == SQLiteErrorCode.Ok) || + (!create && Connect(connection, pAux, + SQLiteString.StringArrayFromUtf8SizeAndIntPtr(argc, + argv), ref table, ref error) == SQLiteErrorCode.Ok)) + { + if (table != null) + { + pVtab = TableToIntPtr(table); + return SQLiteErrorCode.Ok; + } + else + { + pError = SQLiteString.Utf8IntPtrFromString( + "no table was created"); + } + } + else + { + pError = SQLiteString.Utf8IntPtrFromString(error); + } + } + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + pError = SQLiteString.Utf8IntPtrFromString(e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Calls one of the virtual table finalization methods. + /// + /// + /// Non-zero to call the + /// method; otherwise, the + /// method will be + /// called. + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// A standard SQLite return code. + /// + private SQLiteErrorCode DestroyOrDisconnect( + bool destroy, + IntPtr pVtab + ) + { + try + { + SQLiteVirtualTable table = TableFromIntPtr(pVtab); + + if (table != null) + { + if ((destroy && (Destroy(table) == SQLiteErrorCode.Ok)) || + (!destroy && (Disconnect(table) == SQLiteErrorCode.Ok))) + { + if (tables != null) + tables.Remove(pVtab); + + return SQLiteErrorCode.Ok; + } + } + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + // + // NOTE: At this point, there is no way to report the error + // condition back to the caller; therefore, use the + // logging facility instead. + // + try + { + if (LogExceptionsNoThrow) + { + /* throw */ + SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + destroy ? "xDestroy" : "xDisconnect", e)); + } + } + catch + { + // do nothing. + } + } + finally + { + FreeTable(pVtab); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + #region Static Error Handling Helper Methods + /// + /// Arranges for the specified error message to be placed into the + /// zErrMsg field of a sqlite3_vtab derived structure, freeing the + /// existing error message, if any. + /// + /// + /// The object instance to be used. + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// Non-zero if this error message should also be logged using the + /// class. + /// + /// + /// Non-zero if caught exceptions should be logged using the + /// class. + /// + /// + /// The error message. + /// + /// + /// Non-zero upon success. + /// + private static bool SetTableError( + SQLiteModule module, + IntPtr pVtab, + bool logErrors, + bool logExceptions, + string error + ) + { + try + { + if (logErrors && (error != null)) + { + SQLiteLog.LogMessage(SQLiteErrorCode.Error, + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Virtual table error: {0}", error)); /* throw */ + } + } + catch + { + // do nothing. + } + + bool success = false; + IntPtr pNewError = IntPtr.Zero; + + try + { + if (pVtab == IntPtr.Zero) + return false; + + int offset = 0; + + offset = SQLiteMarshal.NextOffsetOf( + offset, IntPtr.Size, sizeof(int)); + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), IntPtr.Size); + + IntPtr pOldError = SQLiteMarshal.ReadIntPtr(pVtab, offset); + + if (pOldError != IntPtr.Zero) + { + SQLiteMemory.Free(pOldError); pOldError = IntPtr.Zero; + SQLiteMarshal.WriteIntPtr(pVtab, offset, pOldError); + } + + if (error == null) + return true; + + pNewError = SQLiteString.Utf8IntPtrFromString(error); + SQLiteMarshal.WriteIntPtr(pVtab, offset, pNewError); + success = true; + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + try + { + if (logExceptions) + { + SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "SetTableError", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } + finally + { + if (!success && (pNewError != IntPtr.Zero)) + { + SQLiteMemory.Free(pNewError); + pNewError = IntPtr.Zero; + } + } + + return success; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Arranges for the specified error message to be placed into the + /// zErrMsg field of a sqlite3_vtab derived structure, freeing the + /// existing error message, if any. + /// + /// + /// The object instance to be used. + /// + /// + /// The object instance used to + /// lookup the native pointer to the sqlite3_vtab derived structure. + /// + /// + /// Non-zero if this error message should also be logged using the + /// class. + /// + /// + /// Non-zero if caught exceptions should be logged using the + /// class. + /// + /// + /// The error message. + /// + /// + /// Non-zero upon success. + /// + private static bool SetTableError( + SQLiteModule module, + SQLiteVirtualTable table, + bool logErrors, + bool logExceptions, + string error + ) + { + if (table == null) + return false; + + IntPtr pVtab = table.NativeHandle; + + if (pVtab == IntPtr.Zero) + return false; + + return SetTableError( + module, pVtab, logErrors, logExceptions, error); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Arranges for the specified error message to be placed into the + /// zErrMsg field of a sqlite3_vtab derived structure, freeing the + /// existing error message, if any. + /// + /// + /// The object instance to be used. + /// + /// + /// The native pointer to the sqlite3_vtab_cursor derived structure + /// used to get the native pointer to the sqlite3_vtab derived + /// structure. + /// + /// + /// Non-zero if this error message should also be logged using the + /// class. + /// + /// + /// Non-zero if caught exceptions should be logged using the + /// class. + /// + /// + /// The error message. + /// + /// + /// Non-zero upon success. + /// + private static bool SetCursorError( + SQLiteModule module, + IntPtr pCursor, + bool logErrors, + bool logExceptions, + string error + ) + { + if (pCursor == IntPtr.Zero) + return false; + + IntPtr pVtab = TableFromCursor(module, pCursor); + + if (pVtab == IntPtr.Zero) + return false; + + return SetTableError( + module, pVtab, logErrors, logExceptions, error); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Arranges for the specified error message to be placed into the + /// zErrMsg field of a sqlite3_vtab derived structure, freeing the + /// existing error message, if any. + /// + /// + /// The object instance to be used. + /// + /// + /// The object instance used to + /// lookup the native pointer to the sqlite3_vtab derived structure. + /// + /// + /// Non-zero if this error message should also be logged using the + /// class. + /// + /// + /// Non-zero if caught exceptions should be logged using the + /// class. + /// + /// + /// The error message. + /// + /// + /// Non-zero upon success. + /// + private static bool SetCursorError( + SQLiteModule module, + SQLiteVirtualTableCursor cursor, + bool logErrors, + bool logExceptions, + string error + ) + { + if (cursor == null) + return false; + + IntPtr pCursor = cursor.NativeHandle; + + if (pCursor == IntPtr.Zero) + return false; + + return SetCursorError( + module, pCursor, logErrors, logExceptions, error); + } + #endregion + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Protected Members + #region Module Helper Methods + /// + /// Gets and returns the interface + /// implementation to be used when creating the native sqlite3_module + /// structure. Derived classes may override this method to supply an + /// alternate implementation for the + /// interface. + /// + /// + /// The interface implementation to + /// be used when populating the native sqlite3_module structure. If + /// the returned value is null, the private methods provided by the + /// class and relating to the + /// interface will be used to + /// create the necessary delegates. + /// + protected virtual ISQLiteNativeModule GetNativeModuleImpl() + { + return null; /* NOTE: Use the built-in default delegates. */ + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Creates and returns the + /// interface implementation corresponding to the current + /// object instance. + /// + /// + /// The interface implementation + /// corresponding to the current object + /// instance. + /// + protected virtual ISQLiteNativeModule CreateNativeModuleImpl() + { + return new SQLiteNativeModule(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Native Table Helper Methods + /// + /// Allocates a native sqlite3_vtab derived structure and returns a + /// native pointer to it. + /// + /// + /// A native pointer to a native sqlite3_vtab derived structure. + /// + protected virtual IntPtr AllocateTable() + { + int size = Marshal.SizeOf(typeof( + UnsafeNativeMethods.sqlite3_vtab)); + + return SQLiteMemory.Allocate(size); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Zeros out the fields of a native sqlite3_vtab derived structure. + /// + /// + /// The native pointer to the native sqlite3_vtab derived structure to + /// zero. + /// + protected virtual void ZeroTable( + IntPtr pVtab + ) + { + if (pVtab == IntPtr.Zero) + return; + + int offset = 0; + + SQLiteMarshal.WriteIntPtr(pVtab, offset, IntPtr.Zero); + + offset = SQLiteMarshal.NextOffsetOf( + offset, IntPtr.Size, sizeof(int)); + + SQLiteMarshal.WriteInt32(pVtab, offset, 0); + + offset = SQLiteMarshal.NextOffsetOf( + offset, sizeof(int), IntPtr.Size); + + SQLiteMarshal.WriteIntPtr(pVtab, offset, IntPtr.Zero); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Frees a native sqlite3_vtab structure using the provided native + /// pointer to it. + /// + /// + /// A native pointer to a native sqlite3_vtab derived structure. + /// + protected virtual void FreeTable( + IntPtr pVtab + ) + { + SetTableError(pVtab, null); + SQLiteMemory.Free(pVtab); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Native Cursor Helper Methods + /// + /// Allocates a native sqlite3_vtab_cursor derived structure and + /// returns a native pointer to it. + /// + /// + /// A native pointer to a native sqlite3_vtab_cursor derived structure. + /// + protected virtual IntPtr AllocateCursor() + { + int size = Marshal.SizeOf(typeof( + UnsafeNativeMethods.sqlite3_vtab_cursor)); + + return SQLiteMemory.Allocate(size); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Frees a native sqlite3_vtab_cursor structure using the provided + /// native pointer to it. + /// + /// + /// A native pointer to a native sqlite3_vtab_cursor derived structure. + /// + protected virtual void FreeCursor( + IntPtr pCursor + ) + { + SQLiteMemory.Free(pCursor); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Static Table Lookup Methods + /// + /// Reads and returns the native pointer to the sqlite3_vtab derived + /// structure based on the native pointer to the sqlite3_vtab_cursor + /// derived structure. + /// + /// + /// The object instance to be used. + /// + /// + /// The native pointer to the sqlite3_vtab_cursor derived structure + /// from which to read the native pointer to the sqlite3_vtab derived + /// structure. + /// + /// + /// The native pointer to the sqlite3_vtab derived structure -OR- + /// if it cannot be determined. + /// + private static IntPtr TableFromCursor( + SQLiteModule module, + IntPtr pCursor + ) + { + if (pCursor == IntPtr.Zero) + return IntPtr.Zero; + + return Marshal.ReadIntPtr(pCursor); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Table Lookup Methods + /// + /// Reads and returns the native pointer to the sqlite3_vtab derived + /// structure based on the native pointer to the sqlite3_vtab_cursor + /// derived structure. + /// + /// + /// The native pointer to the sqlite3_vtab_cursor derived structure + /// from which to read the native pointer to the sqlite3_vtab derived + /// structure. + /// + /// + /// The native pointer to the sqlite3_vtab derived structure -OR- + /// if it cannot be determined. + /// + protected virtual IntPtr TableFromCursor( + IntPtr pCursor + ) + { + return TableFromCursor(this, pCursor); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Looks up and returns the object + /// instance based on the native pointer to the sqlite3_vtab derived + /// structure. + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// The object instance or null if + /// the corresponding one cannot be found. + /// + protected virtual SQLiteVirtualTable TableFromIntPtr( + IntPtr pVtab + ) + { + if (pVtab == IntPtr.Zero) + { + SetTableError(pVtab, "invalid native table"); + return null; + } + + SQLiteVirtualTable table; + + if ((tables != null) && + tables.TryGetValue(pVtab, out table)) + { + return table; + } + + SetTableError(pVtab, HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "managed table for {0} not found", pVtab)); + + return null; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Allocates and returns a native pointer to a sqlite3_vtab derived + /// structure and creates an association between it and the specified + /// object instance. + /// + /// + /// The object instance to be used + /// when creating the association. + /// + /// + /// The native pointer to a sqlite3_vtab derived structure or + /// if the method fails for any reason. + /// + protected virtual IntPtr TableToIntPtr( + SQLiteVirtualTable table + ) + { + if ((table == null) || (tables == null)) + return IntPtr.Zero; + + IntPtr pVtab = IntPtr.Zero; + bool success = false; + + try + { + pVtab = AllocateTable(); + + if (pVtab != IntPtr.Zero) + { + ZeroTable(pVtab); + table.NativeHandle = pVtab; + tables.Add(pVtab, table); + success = true; + } + } + finally + { + if (!success && (pVtab != IntPtr.Zero)) + { + FreeTable(pVtab); + pVtab = IntPtr.Zero; + } + } + + return pVtab; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Cursor Lookup Methods + /// + /// Looks up and returns the + /// object instance based on the native pointer to the + /// sqlite3_vtab_cursor derived structure. + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// The native pointer to the sqlite3_vtab_cursor derived structure. + /// + /// + /// The object instance or null + /// if the corresponding one cannot be found. + /// + protected virtual SQLiteVirtualTableCursor CursorFromIntPtr( + IntPtr pVtab, + IntPtr pCursor + ) + { + if (pCursor == IntPtr.Zero) + { + SetTableError(pVtab, "invalid native cursor"); + return null; + } + + SQLiteVirtualTableCursor cursor; + + if ((cursors != null) && + cursors.TryGetValue(pCursor, out cursor)) + { + return cursor; + } + + SetTableError(pVtab, HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "managed cursor for {0} not found", pCursor)); + + return null; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Allocates and returns a native pointer to a sqlite3_vtab_cursor + /// derived structure and creates an association between it and the + /// specified object instance. + /// + /// + /// The object instance to be + /// used when creating the association. + /// + /// + /// The native pointer to a sqlite3_vtab_cursor derived structure or + /// if the method fails for any reason. + /// + protected virtual IntPtr CursorToIntPtr( + SQLiteVirtualTableCursor cursor + ) + { + if ((cursor == null) || (cursors == null)) + return IntPtr.Zero; + + IntPtr pCursor = IntPtr.Zero; + bool success = false; + + try + { + pCursor = AllocateCursor(); + + if (pCursor != IntPtr.Zero) + { + cursor.NativeHandle = pCursor; + cursors.Add(pCursor, cursor); + success = true; + } + } + finally + { + if (!success && (pCursor != IntPtr.Zero)) + { + FreeCursor(pCursor); + pCursor = IntPtr.Zero; + } + } + + return pCursor; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Function Lookup Methods + /// + /// Deterimines the key that should be used to identify and store the + /// object instance for the virtual table + /// (i.e. to be returned via the + /// method). + /// + /// + /// The number of arguments to the virtual table function. + /// + /// + /// The name of the virtual table function. + /// + /// + /// The object instance associated with + /// this virtual table function. + /// + /// + /// The string that should be used to identify and store the virtual + /// table function instance. This method cannot return null. If null + /// is returned from this method, the behavior is undefined. + /// + protected virtual string GetFunctionKey( + int argumentCount, + string name, + SQLiteFunction function + ) + { + return HelperMethods.StringFormat( + CultureInfo.InvariantCulture, + "{0}:{1}", argumentCount, name); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Table Declaration Helper Methods + /// + /// Attempts to declare the schema for the virtual table using the + /// specified database connection. + /// + /// + /// The object instance to use when + /// declaring the schema of the virtual table. This parameter may not + /// be null. + /// + /// + /// The string containing the CREATE TABLE statement that completely + /// describes the schema for the virtual table. This parameter may not + /// be null. + /// + /// + /// Upon failure, this parameter must be modified to contain an error + /// message. + /// + /// + /// A standard SQLite return code. + /// + protected virtual SQLiteErrorCode DeclareTable( + SQLiteConnection connection, + string sql, + ref string error + ) + { + if (connection == null) + { + error = "invalid connection"; + return SQLiteErrorCode.Error; + } + + SQLiteBase sqliteBase = connection._sql; + + if (sqliteBase == null) + { + error = "connection has invalid handle"; + return SQLiteErrorCode.Error; + } + + if (sql == null) + { + error = "invalid SQL statement"; + return SQLiteErrorCode.Error; + } + + return sqliteBase.DeclareVirtualTable(this, sql, ref error); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Function Declaration Helper Methods + /// + /// Calls the native SQLite core library in order to declare a virtual + /// table function in response to a call into the + /// + /// or virtual table + /// methods. + /// + /// + /// The object instance to use when + /// declaring the schema of the virtual table. + /// + /// + /// The number of arguments to the function being declared. + /// + /// + /// The name of the function being declared. + /// + /// + /// Upon success, the contents of this parameter are undefined. Upon + /// failure, it should contain an appropriate error message. + /// + /// + /// A standard SQLite return code. + /// + protected virtual SQLiteErrorCode DeclareFunction( + SQLiteConnection connection, + int argumentCount, + string name, + ref string error + ) + { + if (connection == null) + { + error = "invalid connection"; + return SQLiteErrorCode.Error; + } + + SQLiteBase sqliteBase = connection._sql; + + if (sqliteBase == null) + { + error = "connection has invalid handle"; + return SQLiteErrorCode.Error; + } + + return sqliteBase.DeclareVirtualFunction( + this, argumentCount, name, ref error); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Error Handling Properties + private bool logErrors; + /// + /// Returns or sets a boolean value indicating whether virtual table + /// errors should be logged using the class. + /// + protected virtual bool LogErrorsNoThrow + { + get { return logErrors; } + set { logErrors = value; } + } + + /////////////////////////////////////////////////////////////////////// + + private bool logExceptions; + /// + /// Returns or sets a boolean value indicating whether exceptions + /// caught in the + /// method, + /// the method, + /// the method, + /// the method, + /// and the method should be logged using the + /// class. + /// + protected virtual bool LogExceptionsNoThrow + { + get { return logExceptions; } + set { logExceptions = value; } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Error Handling Helper Methods + /// + /// Arranges for the specified error message to be placed into the + /// zErrMsg field of a sqlite3_vtab derived structure, freeing the + /// existing error message, if any. + /// + /// + /// The native pointer to the sqlite3_vtab derived structure. + /// + /// + /// The error message. + /// + /// + /// Non-zero upon success. + /// + protected virtual bool SetTableError( + IntPtr pVtab, + string error + ) + { + return SetTableError( + this, pVtab, LogErrorsNoThrow, LogExceptionsNoThrow, error); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Arranges for the specified error message to be placed into the + /// zErrMsg field of a sqlite3_vtab derived structure, freeing the + /// existing error message, if any. + /// + /// + /// The object instance used to + /// lookup the native pointer to the sqlite3_vtab derived structure. + /// + /// + /// The error message. + /// + /// + /// Non-zero upon success. + /// + protected virtual bool SetTableError( + SQLiteVirtualTable table, + string error + ) + { + return SetTableError( + this, table, LogErrorsNoThrow, LogExceptionsNoThrow, error); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Arranges for the specified error message to be placed into the + /// zErrMsg field of a sqlite3_vtab derived structure, freeing the + /// existing error message, if any. + /// + /// + /// The object instance used to + /// lookup the native pointer to the sqlite3_vtab derived structure. + /// + /// + /// The error message. + /// + /// + /// Non-zero upon success. + /// + protected virtual bool SetCursorError( + SQLiteVirtualTableCursor cursor, + string error + ) + { + return SetCursorError( + this, cursor, LogErrorsNoThrow, LogExceptionsNoThrow, error); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Index Handling Helper Methods + /// + /// Modifies the specified object instance + /// to contain the specified estimated cost. + /// + /// + /// The object instance to modify. + /// + /// + /// The estimated cost value to use. Using a null value means that the + /// default value provided by the SQLite core library should be used. + /// + /// + /// Non-zero upon success. + /// + protected virtual bool SetEstimatedCost( + SQLiteIndex index, + double? estimatedCost + ) + { + if ((index == null) || (index.Outputs == null)) + return false; + + index.Outputs.EstimatedCost = estimatedCost; + return true; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Modifies the specified object instance + /// to contain the default estimated cost. + /// + /// + /// The object instance to modify. + /// + /// + /// Non-zero upon success. + /// + protected virtual bool SetEstimatedCost( + SQLiteIndex index + ) + { + return SetEstimatedCost(index, null); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Modifies the specified object instance + /// to contain the specified estimated rows. + /// + /// + /// The object instance to modify. + /// + /// + /// The estimated rows value to use. Using a null value means that the + /// default value provided by the SQLite core library should be used. + /// + /// + /// Non-zero upon success. + /// + protected virtual bool SetEstimatedRows( + SQLiteIndex index, + long? estimatedRows + ) + { + if ((index == null) || (index.Outputs == null)) + return false; + + index.Outputs.EstimatedRows = estimatedRows; + return true; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Modifies the specified object instance + /// to contain the default estimated rows. + /// + /// + /// The object instance to modify. + /// + /// + /// Non-zero upon success. + /// + protected virtual bool SetEstimatedRows( + SQLiteIndex index + ) + { + return SetEstimatedRows(index, null); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Modifies the specified object instance + /// to contain the specified flags. + /// + /// + /// The object instance to modify. + /// + /// + /// The index flags value to use. Using a null value means that the + /// default value provided by the SQLite core library should be used. + /// + /// + /// Non-zero upon success. + /// + protected virtual bool SetIndexFlags( + SQLiteIndex index, + SQLiteIndexFlags? indexFlags + ) + { + if ((index == null) || (index.Outputs == null)) + return false; + + index.Outputs.IndexFlags = indexFlags; + return true; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Modifies the specified object instance + /// to contain the default index flags. + /// + /// + /// The object instance to modify. + /// + /// + /// Non-zero upon success. + /// + protected virtual bool SetIndexFlags( + SQLiteIndex index + ) + { + return SetIndexFlags(index, null); + } + #endregion + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Properties + /// + /// Returns or sets a boolean value indicating whether virtual table + /// errors should be logged using the class. + /// + public virtual bool LogErrors + { + get { CheckDisposed(); return LogErrorsNoThrow; } + set { CheckDisposed(); LogErrorsNoThrow = value; } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Returns or sets a boolean value indicating whether exceptions + /// caught in the + /// method, + /// method, and the + /// method should be logged using the + /// class. + /// + public virtual bool LogExceptions + { + get { CheckDisposed(); return LogExceptionsNoThrow; } + set { CheckDisposed(); LogExceptionsNoThrow = value; } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteNativeModule Members + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xCreate( + IntPtr pDb, + IntPtr pAux, + int argc, + IntPtr argv, + ref IntPtr pVtab, + ref IntPtr pError + ) + { + return CreateOrConnect( + true, pDb, pAux, argc, argv, ref pVtab, ref pError); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xConnect( + IntPtr pDb, + IntPtr pAux, + int argc, + IntPtr argv, + ref IntPtr pVtab, + ref IntPtr pError + ) + { + return CreateOrConnect( + false, pDb, pAux, argc, argv, ref pVtab, ref pError); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xBestIndex( + IntPtr pVtab, + IntPtr pIndex + ) + { + try + { + SQLiteVirtualTable table = TableFromIntPtr(pVtab); + + if (table != null) + { + SQLiteIndex index = null; + + SQLiteIndex.FromIntPtr(pIndex, true, ref index); + + if (BestIndex(table, index) == SQLiteErrorCode.Ok) + { + SQLiteIndex.ToIntPtr(index, pIndex, true); + return SQLiteErrorCode.Ok; + } + } + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xDisconnect( + IntPtr pVtab + ) + { + return DestroyOrDisconnect(false, pVtab); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xDestroy( + IntPtr pVtab + ) + { + return DestroyOrDisconnect(true, pVtab); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xOpen( + IntPtr pVtab, + ref IntPtr pCursor + ) + { + try + { + SQLiteVirtualTable table = TableFromIntPtr(pVtab); + + if (table != null) + { + SQLiteVirtualTableCursor cursor = null; + + if (Open(table, ref cursor) == SQLiteErrorCode.Ok) + { + if (cursor != null) + { + pCursor = CursorToIntPtr(cursor); + + if (pCursor != IntPtr.Zero) + { + return SQLiteErrorCode.Ok; + } + else + { + SetTableError(pVtab, + "no native cursor was created"); + } + } + else + { + SetTableError(pVtab, + "no managed cursor was created"); + } + } + } + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xClose( + IntPtr pCursor + ) + { + IntPtr pVtab = IntPtr.Zero; + + try + { + pVtab = TableFromCursor(pCursor); + + SQLiteVirtualTableCursor cursor = CursorFromIntPtr( + pVtab, pCursor); + + if (cursor != null) + { + if (Close(cursor) == SQLiteErrorCode.Ok) + { + if (cursors != null) + cursors.Remove(pCursor); + + return SQLiteErrorCode.Ok; + } + } + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + finally + { + FreeCursor(pCursor); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xFilter( + IntPtr pCursor, + int idxNum, + IntPtr idxStr, + int argc, + IntPtr argv + ) + { + IntPtr pVtab = IntPtr.Zero; + + try + { + pVtab = TableFromCursor(pCursor); + + SQLiteVirtualTableCursor cursor = CursorFromIntPtr( + pVtab, pCursor); + + if (cursor != null) + { + if (Filter(cursor, idxNum, + SQLiteString.StringFromUtf8IntPtr(idxStr), + SQLiteValue.ArrayFromSizeAndIntPtr(argc, + argv)) == SQLiteErrorCode.Ok) + { + return SQLiteErrorCode.Ok; + } + } + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xNext( + IntPtr pCursor + ) + { + IntPtr pVtab = IntPtr.Zero; + + try + { + pVtab = TableFromCursor(pCursor); + + SQLiteVirtualTableCursor cursor = CursorFromIntPtr( + pVtab, pCursor); + + if (cursor != null) + { + if (Next(cursor) == SQLiteErrorCode.Ok) + return SQLiteErrorCode.Ok; + } + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private int xEof( + IntPtr pCursor + ) + { + IntPtr pVtab = IntPtr.Zero; + + try + { + pVtab = TableFromCursor(pCursor); + + SQLiteVirtualTableCursor cursor = CursorFromIntPtr( + pVtab, pCursor); + + if (cursor != null) + return Eof(cursor) ? 1 : 0; + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return 1; /* NOTE: On any error, return "no more rows". */ + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xColumn( + IntPtr pCursor, + IntPtr pContext, + int index + ) + { + IntPtr pVtab = IntPtr.Zero; + + try + { + pVtab = TableFromCursor(pCursor); + + SQLiteVirtualTableCursor cursor = CursorFromIntPtr( + pVtab, pCursor); + + if (cursor != null) + { + SQLiteContext context = new SQLiteContext(pContext); + + return Column(cursor, context, index); + } + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xRowId( + IntPtr pCursor, + ref long rowId + ) + { + IntPtr pVtab = IntPtr.Zero; + + try + { + pVtab = TableFromCursor(pCursor); + + SQLiteVirtualTableCursor cursor = CursorFromIntPtr( + pVtab, pCursor); + + if (cursor != null) + return RowId(cursor, ref rowId); + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xUpdate( + IntPtr pVtab, + int argc, + IntPtr argv, + ref long rowId + ) + { + try + { + SQLiteVirtualTable table = TableFromIntPtr(pVtab); + + if (table != null) + { + return Update(table, + SQLiteValue.ArrayFromSizeAndIntPtr(argc, argv), + ref rowId); + } + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xBegin( + IntPtr pVtab + ) + { + try + { + SQLiteVirtualTable table = TableFromIntPtr(pVtab); + + if (table != null) + return Begin(table); + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xSync( + IntPtr pVtab + ) + { + try + { + SQLiteVirtualTable table = TableFromIntPtr(pVtab); + + if (table != null) + return Sync(table); + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xCommit( + IntPtr pVtab + ) + { + try + { + SQLiteVirtualTable table = TableFromIntPtr(pVtab); + + if (table != null) + return Commit(table); + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xRollback( + IntPtr pVtab + ) + { + try + { + SQLiteVirtualTable table = TableFromIntPtr(pVtab); + + if (table != null) + return Rollback(table); + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private int xFindFunction( + IntPtr pVtab, + int nArg, + IntPtr zName, + ref SQLiteCallback callback, + ref IntPtr pClientData + ) + { + try + { + SQLiteVirtualTable table = TableFromIntPtr(pVtab); + + if (table != null) + { + string name = SQLiteString.StringFromUtf8IntPtr(zName); + SQLiteFunction function = null; + + if (FindFunction( + table, nArg, name, ref function, ref pClientData)) + { + if (function != null) + { + string key = GetFunctionKey(nArg, name, function); + + functions[key] = function; + callback = function.ScalarCallback; + + return 1; + } + else + { + SetTableError(pVtab, "no function was created"); + } + } + } + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return 0; /* NOTE: On any error, return "no such function". */ + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xRename( + IntPtr pVtab, + IntPtr zNew + ) + { + try + { + SQLiteVirtualTable table = TableFromIntPtr(pVtab); + + if (table != null) + { + return Rename(table, + SQLiteString.StringFromUtf8IntPtr(zNew)); + } + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xSavepoint( + IntPtr pVtab, + int iSavepoint + ) + { + try + { + SQLiteVirtualTable table = TableFromIntPtr(pVtab); + + if (table != null) + return Savepoint(table, iSavepoint); + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xRelease( + IntPtr pVtab, + int iSavepoint + ) + { + try + { + SQLiteVirtualTable table = TableFromIntPtr(pVtab); + + if (table != null) + return Release(table, iSavepoint); + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + private SQLiteErrorCode xRollbackTo( + IntPtr pVtab, + int iSavepoint + ) + { + try + { + SQLiteVirtualTable table = TableFromIntPtr(pVtab); + + if (table != null) + return RollbackTo(table, iSavepoint); + } + catch (Exception e) /* NOTE: Must catch ALL. */ + { + SetTableError(pVtab, e.ToString()); + } + + return SQLiteErrorCode.Error; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteManagedModule Members + private bool declared; + /// + /// Returns non-zero if the schema for the virtual table has been + /// declared. + /// + public virtual bool Declared + { + get { CheckDisposed(); return declared; } + internal set { declared = value; } + } + + /////////////////////////////////////////////////////////////////////// + + private string name; + /// + /// Returns the name of the module as it was registered with the SQLite + /// core library. + /// + public virtual string Name + { + get { CheckDisposed(); return name; } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated with + /// the virtual table. + /// + /// + /// The native user-data pointer associated with this module, as it was + /// provided to the SQLite core library when the native module instance + /// was created. + /// + /// + /// The module name, database name, virtual table name, and all other + /// arguments passed to the CREATE VIRTUAL TABLE statement. + /// + /// + /// Upon success, this parameter must be modified to contain the + /// object instance associated with + /// the virtual table. + /// + /// + /// Upon failure, this parameter must be modified to contain an error + /// message. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Create( + SQLiteConnection connection, + IntPtr pClientData, + string[] arguments, + ref SQLiteVirtualTable table, + ref string error + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated with + /// the virtual table. + /// + /// + /// The native user-data pointer associated with this module, as it was + /// provided to the SQLite core library when the native module instance + /// was created. + /// + /// + /// The module name, database name, virtual table name, and all other + /// arguments passed to the CREATE VIRTUAL TABLE statement. + /// + /// + /// Upon success, this parameter must be modified to contain the + /// object instance associated with + /// the virtual table. + /// + /// + /// Upon failure, this parameter must be modified to contain an error + /// message. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Connect( + SQLiteConnection connection, + IntPtr pClientData, + string[] arguments, + ref SQLiteVirtualTable table, + ref string error + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// The object instance containing all the + /// data for the inputs and outputs relating to index selection. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode BestIndex( + SQLiteVirtualTable table, + SQLiteIndex index + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Disconnect( + SQLiteVirtualTable table + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Destroy( + SQLiteVirtualTable table + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// Upon success, this parameter must be modified to contain the + /// object instance associated + /// with the newly opened virtual table cursor. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Open( + SQLiteVirtualTable table, + ref SQLiteVirtualTableCursor cursor + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance + /// associated with the previously opened virtual table cursor to be + /// used. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Close( + SQLiteVirtualTableCursor cursor + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance + /// associated with the previously opened virtual table cursor to be + /// used. + /// + /// + /// Number used to help identify the selected index. + /// + /// + /// String used to help identify the selected index. + /// + /// + /// The values corresponding to each column in the selected index. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Filter( + SQLiteVirtualTableCursor cursor, + int indexNumber, + string indexString, + SQLiteValue[] values + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance + /// associated with the previously opened virtual table cursor to be + /// used. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Next( + SQLiteVirtualTableCursor cursor + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance + /// associated with the previously opened virtual table cursor to be + /// used. + /// + /// + /// Non-zero if no more rows are available; zero otherwise. + /// + public abstract bool Eof( + SQLiteVirtualTableCursor cursor + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance + /// associated with the previously opened virtual table cursor to be + /// used. + /// + /// + /// The object instance to be used for + /// returning the specified column value to the SQLite core library. + /// + /// + /// The zero-based index corresponding to the column containing the + /// value to be returned. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Column( + SQLiteVirtualTableCursor cursor, + SQLiteContext context, + int index + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance + /// associated with the previously opened virtual table cursor to be + /// used. + /// + /// + /// Upon success, this parameter must be modified to contain the unique + /// integer row identifier for the current row for the specified cursor. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode RowId( + SQLiteVirtualTableCursor cursor, + ref long rowId + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// The array of object instances containing + /// the new or modified column values, if any. + /// + /// + /// Upon success, this parameter must be modified to contain the unique + /// integer row identifier for the row that was inserted, if any. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Update( + SQLiteVirtualTable table, + SQLiteValue[] values, + ref long rowId + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Begin( + SQLiteVirtualTable table + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Sync( + SQLiteVirtualTable table + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Commit( + SQLiteVirtualTable table + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Rollback( + SQLiteVirtualTable table + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// The number of arguments to the function being sought. + /// + /// + /// The name of the function being sought. + /// + /// + /// Upon success, this parameter must be modified to contain the + /// object instance responsible for + /// implementing the specified function. + /// + /// + /// Upon success, this parameter must be modified to contain the + /// native user-data pointer associated with + /// . + /// + /// + /// Non-zero if the specified function was found; zero otherwise. + /// + public abstract bool FindFunction( + SQLiteVirtualTable table, + int argumentCount, + string name, + ref SQLiteFunction function, + ref IntPtr pClientData + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// The new name for the virtual table. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Rename( + SQLiteVirtualTable table, + string newName + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// This is an integer identifier under which the the current state of + /// the virtual table should be saved. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Savepoint( + SQLiteVirtualTable table, + int savepoint + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// This is an integer used to indicate that any saved states with an + /// identifier greater than or equal to this should be deleted by the + /// virtual table. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode Release( + SQLiteVirtualTable table, + int savepoint + ); + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is called in response to the + /// method. + /// + /// + /// The object instance associated + /// with this virtual table. + /// + /// + /// This is an integer identifier used to specify a specific saved + /// state for the virtual table for it to restore itself back to, which + /// should also have the effect of deleting all saved states with an + /// integer identifier greater than this one. + /// + /// + /// A standard SQLite return code. + /// + public abstract SQLiteErrorCode RollbackTo( + SQLiteVirtualTable table, + int savepoint + ); + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes of this object instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + /// + /// Throws an if this object + /// instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteModule).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of this object instance. + /// + /// + /// Non-zero if this method is being called from the + /// method. Zero if this method is being + /// called from the finalizer. + /// + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (functions != null) + functions.Clear(); + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + try + { + if (disposableModule != IntPtr.Zero) + { + UnsafeNativeMethods.sqlite3_dispose_module( + disposableModule); + + disposableModule = IntPtr.Zero; + } + } + catch (Exception e) + { + try + { + if (LogExceptionsNoThrow) + { + SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "Dispose", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } +#if PLATFORM_COMPACTFRAMEWORK + finally + { + if (pNativeModule != IntPtr.Zero) + { + SQLiteMemory.Free(pNativeModule); + pNativeModule = IntPtr.Zero; + } + } +#endif + + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Destructor + /// + /// Finalizes this object instance. + /// + ~SQLiteModule() + { + Dispose(false); + } + #endregion + } + #endregion +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteModuleCommon.cs b/Native.Csharp.Tool/SQLite/SQLiteModuleCommon.cs new file mode 100644 index 00000000..897c1721 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteModuleCommon.cs @@ -0,0 +1,286 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Joe Mistachkin (joe@mistachkin.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +using System.Globalization; + +#region Non-Generic Classes +namespace System.Data.SQLite +{ + #region SQLiteModuleCommon Class + /// + /// This class contains some virtual methods that may be useful for other + /// virtual table classes. It specifically does NOT implement any of the + /// interface methods. + /// + public class SQLiteModuleCommon : SQLiteModuleNoop /* NOT SEALED */ + { + #region Private Constants + /// + /// The CREATE TABLE statement used to declare the schema for the + /// virtual table. + /// + private static readonly string declareSql = + HelperMethods.StringFormat( + CultureInfo.InvariantCulture, "CREATE TABLE {0}(x);", + typeof(SQLiteModuleCommon).Name); + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Data + /// + /// Non-zero if different object instances with the same value should + /// generate different row identifiers, where applicable. This has no + /// effect on the .NET Compact Framework. + /// + private bool objectIdentity; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs an instance of this class. + /// + /// + /// The name of the module. This parameter cannot be null. + /// + public SQLiteModuleCommon( + string name + ) + : this(name, false) + { + // do nothing. + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Constructs an instance of this class. + /// + /// + /// The name of the module. This parameter cannot be null. + /// + /// + /// Non-zero if different object instances with the same value should + /// generate different row identifiers, where applicable. This + /// parameter has no effect on the .NET Compact Framework. + /// + public SQLiteModuleCommon( + string name, + bool objectIdentity + ) + : base(name) + { + this.objectIdentity = objectIdentity; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Protected Methods + /// + /// Determines the SQL statement used to declare the virtual table. + /// This method should be overridden in derived classes if they require + /// a custom virtual table schema. + /// + /// + /// The SQL statement used to declare the virtual table -OR- null if it + /// cannot be determined. + /// + protected virtual string GetSqlForDeclareTable() + { + return declareSql; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the table error message to one that indicates the virtual + /// table cursor is of the wrong type. + /// + /// + /// The object instance. + /// + /// + /// The that the virtual table cursor should be. + /// + /// + /// The value of . + /// + protected virtual SQLiteErrorCode CursorTypeMismatchError( + SQLiteVirtualTableCursor cursor, + Type type + ) + { + if (type != null) + { + SetCursorError(cursor, HelperMethods.StringFormat( + CultureInfo.CurrentCulture, "not a \"{0}\" cursor", + type)); + } + else + { + SetCursorError(cursor, "cursor type mismatch"); + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Determines the string to return as the column value for the object + /// instance value. + /// + /// + /// The object instance + /// associated with the previously opened virtual table cursor to be + /// used. + /// + /// + /// The object instance to return a string representation for. + /// + /// + /// The string representation of the specified object instance or null + /// upon failure. + /// + protected virtual string GetStringFromObject( + SQLiteVirtualTableCursor cursor, + object value + ) + { + if (value == null) + return null; + + if (value is string) + return (string)value; + + return value.ToString(); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Constructs an unique row identifier from two + /// values. The first value + /// must contain the row sequence number for the current row and the + /// second value must contain the hash code of the key column value + /// for the current row. + /// + /// + /// The integer row sequence number for the current row. + /// + /// + /// The hash code of the key column value for the current row. + /// + /// + /// The unique row identifier or zero upon failure. + /// + protected virtual long MakeRowId( + int rowIndex, + int hashCode + ) + { + long result = rowIndex; + + result <<= 32; /* typeof(int) bits */ + result |= (long)(uint)hashCode; + + return result; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Determines the unique row identifier for the current row. + /// + /// + /// The object instance + /// associated with the previously opened virtual table cursor to be + /// used. + /// + /// + /// The object instance to return a unique row identifier for. + /// + /// + /// The unique row identifier or zero upon failure. + /// + protected virtual long GetRowIdFromObject( + SQLiteVirtualTableCursor cursor, + object value + ) + { + int rowIndex = (cursor != null) ? cursor.GetRowIndex() : 0; + int hashCode = SQLiteMarshal.GetHashCode(value, objectIdentity); + + return MakeRowId(rowIndex, hashCode); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + /// + /// Throws an if this object + /// instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteModuleCommon).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of this object instance. + /// + /// + /// Non-zero if this method is being called from the + /// method. Zero if this method is + /// being called from the finalizer. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } + #endregion +} +#endregion diff --git a/Native.Csharp.Tool/SQLite/SQLiteModuleEnumerable.cs b/Native.Csharp.Tool/SQLite/SQLiteModuleEnumerable.cs new file mode 100644 index 00000000..dc10bcb9 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteModuleEnumerable.cs @@ -0,0 +1,1270 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Joe Mistachkin (joe@mistachkin.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +using System.Collections; +using System.Collections.Generic; +using System.Globalization; + +#region Non-Generic Classes +namespace System.Data.SQLite +{ + #region SQLiteVirtualTableCursorEnumerator Class + /// + /// This class represents a virtual table cursor to be used with the + /// class. It is not sealed and may + /// be used as the base class for any user-defined virtual table cursor + /// class that wraps an object instance. + /// + public class SQLiteVirtualTableCursorEnumerator : + SQLiteVirtualTableCursor, IEnumerator /* NOT SEALED */ + { + #region Private Data + /// + /// The instance provided when this cursor + /// was created. + /// + private IEnumerator enumerator; + + /////////////////////////////////////////////////////////////////////// + + /// + /// This value will be non-zero if false has been returned from the + /// method. + /// + private bool endOfEnumerator; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs an instance of this class. + /// + /// + /// The object instance associated + /// with this object instance. + /// + /// + /// The instance to expose as a virtual + /// table cursor. + /// + public SQLiteVirtualTableCursorEnumerator( + SQLiteVirtualTable table, + IEnumerator enumerator + ) + : base(table) + { + this.enumerator = enumerator; + this.endOfEnumerator = true; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Members + /// + /// Advances to the next row of the virtual table cursor using the + /// method of the + /// object instance. + /// + /// + /// Non-zero if the current row is valid; zero otherwise. If zero is + /// returned, no further rows are available. + /// + public virtual bool MoveNext() + { + CheckDisposed(); + CheckClosed(); + + if (enumerator == null) + return false; + + endOfEnumerator = !enumerator.MoveNext(); + + if (!endOfEnumerator) + NextRowIndex(); + + return !endOfEnumerator; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Returns the value for the current row of the virtual table cursor + /// using the property of the + /// object instance. + /// + public virtual object Current + { + get + { + CheckDisposed(); + CheckClosed(); + + if (enumerator == null) + return null; + + return enumerator.Current; + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Resets the virtual table cursor position, also invalidating the + /// current row, using the method of + /// the object instance. + /// + public virtual void Reset() + { + CheckDisposed(); + CheckClosed(); + + if (enumerator == null) + return; + + enumerator.Reset(); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Returns non-zero if the end of the virtual table cursor has been + /// seen (i.e. no more rows are available, including the current one). + /// + public virtual bool EndOfEnumerator + { + get { CheckDisposed(); CheckClosed(); return endOfEnumerator; } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Returns non-zero if the virtual table cursor is open. + /// + public virtual bool IsOpen + { + get { CheckDisposed(); return (enumerator != null); } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Closes the virtual table cursor. This method must not throw any + /// exceptions. + /// + public virtual void Close() + { + // CheckDisposed(); + // CheckClosed(); + + if (enumerator != null) + enumerator = null; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Throws an if the virtual + /// table cursor has been closed. + /// + public virtual void CheckClosed() + { + CheckDisposed(); + + if (!IsOpen) + { + throw new InvalidOperationException( + "virtual table cursor is closed"); + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + /// + /// Throws an if this object + /// instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteVirtualTableCursorEnumerator).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of this object instance. + /// + /// + /// Non-zero if this method is being called from the + /// method. Zero if this method is + /// being called from the finalizer. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + Close(); + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteModuleEnumerable Class + /// + /// This class implements a virtual table module that exposes an + /// object instance as a read-only virtual + /// table. It is not sealed and may be used as the base class for any + /// user-defined virtual table class that wraps an + /// object instance. The following short + /// example shows it being used to treat an array of strings as a table + /// data source: + /// + /// public static class Sample + /// { + /// public static void Main() + /// { + /// using (SQLiteConnection connection = new SQLiteConnection( + /// "Data Source=:memory:;")) + /// { + /// connection.Open(); + /// + /// connection.CreateModule(new SQLiteModuleEnumerable( + /// "sampleModule", new string[] { "one", "two", "three" })); + /// + /// using (SQLiteCommand command = connection.CreateCommand()) + /// { + /// command.CommandText = + /// "CREATE VIRTUAL TABLE t1 USING sampleModule;"; + /// + /// command.ExecuteNonQuery(); + /// } + /// + /// using (SQLiteCommand command = connection.CreateCommand()) + /// { + /// command.CommandText = "SELECT * FROM t1;"; + /// + /// using (SQLiteDataReader dataReader = command.ExecuteReader()) + /// { + /// while (dataReader.Read()) + /// Console.WriteLine(dataReader[0].ToString()); + /// } + /// } + /// + /// connection.Close(); + /// } + /// } + /// } + /// + /// + public class SQLiteModuleEnumerable : SQLiteModuleCommon /* NOT SEALED */ + { + #region Private Data + /// + /// The instance containing the backing data + /// for the virtual table. + /// + private IEnumerable enumerable; + + /////////////////////////////////////////////////////////////////////// + + /// + /// Non-zero if different object instances with the same value should + /// generate different row identifiers, where applicable. This has no + /// effect on the .NET Compact Framework. + /// + private bool objectIdentity; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs an instance of this class. + /// + /// + /// The name of the module. This parameter cannot be null. + /// + /// + /// The instance to expose as a virtual + /// table. This parameter cannot be null. + /// + public SQLiteModuleEnumerable( + string name, + IEnumerable enumerable + ) + : this(name, enumerable, false) + { + // do nothing. + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Constructs an instance of this class. + /// + /// + /// The name of the module. This parameter cannot be null. + /// + /// + /// The instance to expose as a virtual + /// table. This parameter cannot be null. + /// + /// + /// Non-zero if different object instances with the same value should + /// generate different row identifiers, where applicable. This + /// parameter has no effect on the .NET Compact Framework. + /// + public SQLiteModuleEnumerable( + string name, + IEnumerable enumerable, + bool objectIdentity + ) + : base(name) + { + if (enumerable == null) + throw new ArgumentNullException("enumerable"); + + this.enumerable = enumerable; + this.objectIdentity = objectIdentity; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Protected Methods + /// + /// Sets the table error message to one that indicates the virtual + /// table cursor has no current row. + /// + /// + /// The object instance. + /// + /// + /// The value of . + /// + protected virtual SQLiteErrorCode CursorEndOfEnumeratorError( + SQLiteVirtualTableCursor cursor + ) + { + SetCursorError(cursor, "already hit end of enumerator"); + return SQLiteErrorCode.Error; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteManagedModule Members + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Create( + SQLiteConnection connection, + IntPtr pClientData, + string[] arguments, + ref SQLiteVirtualTable table, + ref string error + ) + { + CheckDisposed(); + + if (DeclareTable( + connection, GetSqlForDeclareTable(), + ref error) == SQLiteErrorCode.Ok) + { + table = new SQLiteVirtualTable(arguments); + return SQLiteErrorCode.Ok; + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Connect( + SQLiteConnection connection, + IntPtr pClientData, + string[] arguments, + ref SQLiteVirtualTable table, + ref string error + ) + { + CheckDisposed(); + + if (DeclareTable( + connection, GetSqlForDeclareTable(), + ref error) == SQLiteErrorCode.Ok) + { + table = new SQLiteVirtualTable(arguments); + return SQLiteErrorCode.Ok; + } + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode BestIndex( + SQLiteVirtualTable table, + SQLiteIndex index + ) + { + CheckDisposed(); + + if (!table.BestIndex(index)) + { + SetTableError(table, HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "failed to select best index for virtual table \"{0}\"", + table.TableName)); + + return SQLiteErrorCode.Error; + } + + return SQLiteErrorCode.Ok; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Disconnect( + SQLiteVirtualTable table + ) + { + CheckDisposed(); + + table.Dispose(); + return SQLiteErrorCode.Ok; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Destroy( + SQLiteVirtualTable table + ) + { + CheckDisposed(); + + table.Dispose(); + return SQLiteErrorCode.Ok; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Open( + SQLiteVirtualTable table, + ref SQLiteVirtualTableCursor cursor + ) + { + CheckDisposed(); + + cursor = new SQLiteVirtualTableCursorEnumerator( + table, enumerable.GetEnumerator()); + + return SQLiteErrorCode.Ok; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Close( + SQLiteVirtualTableCursor cursor + ) + { + CheckDisposed(); + + SQLiteVirtualTableCursorEnumerator enumeratorCursor = + cursor as SQLiteVirtualTableCursorEnumerator; + + if (enumeratorCursor == null) + { + return CursorTypeMismatchError(cursor, + typeof(SQLiteVirtualTableCursorEnumerator)); + } + + enumeratorCursor.Close(); + return SQLiteErrorCode.Ok; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Filter( + SQLiteVirtualTableCursor cursor, + int indexNumber, + string indexString, + SQLiteValue[] values + ) + { + CheckDisposed(); + + SQLiteVirtualTableCursorEnumerator enumeratorCursor = + cursor as SQLiteVirtualTableCursorEnumerator; + + if (enumeratorCursor == null) + { + return CursorTypeMismatchError(cursor, + typeof(SQLiteVirtualTableCursorEnumerator)); + } + + enumeratorCursor.Filter(indexNumber, indexString, values); + enumeratorCursor.Reset(); /* NO RESULT */ + enumeratorCursor.MoveNext(); /* IGNORED */ + + return SQLiteErrorCode.Ok; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Next( + SQLiteVirtualTableCursor cursor + ) + { + CheckDisposed(); + + SQLiteVirtualTableCursorEnumerator enumeratorCursor = + cursor as SQLiteVirtualTableCursorEnumerator; + + if (enumeratorCursor == null) + { + return CursorTypeMismatchError(cursor, + typeof(SQLiteVirtualTableCursorEnumerator)); + } + + if (enumeratorCursor.EndOfEnumerator) + return CursorEndOfEnumeratorError(cursor); + + enumeratorCursor.MoveNext(); /* IGNORED */ + return SQLiteErrorCode.Ok; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override bool Eof( + SQLiteVirtualTableCursor cursor + ) + { + CheckDisposed(); + + SQLiteVirtualTableCursorEnumerator enumeratorCursor = + cursor as SQLiteVirtualTableCursorEnumerator; + + if (enumeratorCursor == null) + { + return ResultCodeToEofResult(CursorTypeMismatchError( + cursor, typeof(SQLiteVirtualTableCursorEnumerator))); + } + + return enumeratorCursor.EndOfEnumerator; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Column( + SQLiteVirtualTableCursor cursor, + SQLiteContext context, + int index + ) + { + CheckDisposed(); + + SQLiteVirtualTableCursorEnumerator enumeratorCursor = + cursor as SQLiteVirtualTableCursorEnumerator; + + if (enumeratorCursor == null) + { + return CursorTypeMismatchError(cursor, + typeof(SQLiteVirtualTableCursorEnumerator)); + } + + if (enumeratorCursor.EndOfEnumerator) + return CursorEndOfEnumeratorError(cursor); + + object current = enumeratorCursor.Current; + + if (current != null) + context.SetString(GetStringFromObject(cursor, current)); + else + context.SetNull(); + + return SQLiteErrorCode.Ok; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode RowId( + SQLiteVirtualTableCursor cursor, + ref long rowId + ) + { + CheckDisposed(); + + SQLiteVirtualTableCursorEnumerator enumeratorCursor = + cursor as SQLiteVirtualTableCursorEnumerator; + + if (enumeratorCursor == null) + { + return CursorTypeMismatchError(cursor, + typeof(SQLiteVirtualTableCursorEnumerator)); + } + + if (enumeratorCursor.EndOfEnumerator) + return CursorEndOfEnumeratorError(cursor); + + object current = enumeratorCursor.Current; + + rowId = GetRowIdFromObject(cursor, current); + return SQLiteErrorCode.Ok; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Update( + SQLiteVirtualTable table, + SQLiteValue[] values, + ref long rowId + ) + { + CheckDisposed(); + + SetTableError(table, HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "virtual table \"{0}\" is read-only", table.TableName)); + + return SQLiteErrorCode.Error; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Rename( + SQLiteVirtualTable table, + string newName + ) + { + CheckDisposed(); + + if (!table.Rename(newName)) + { + SetTableError(table, HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "failed to rename virtual table from \"{0}\" to \"{1}\"", + table.TableName, newName)); + + return SQLiteErrorCode.Error; + } + + return SQLiteErrorCode.Ok; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + /// + /// Throws an if this object + /// instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteModuleEnumerable).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of this object instance. + /// + /// + /// Non-zero if this method is being called from the + /// method. Zero if this method is + /// being called from the finalizer. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } + #endregion +} +#endregion + +/////////////////////////////////////////////////////////////////////////////// + +#region Generic Classes +namespace System.Data.SQLite.Generic +{ + #region SQLiteVirtualTableCursorEnumerator Class + /// + /// This class represents a virtual table cursor to be used with the + /// class. It is not sealed and may + /// be used as the base class for any user-defined virtual table cursor + /// class that wraps an object instance. + /// + public class SQLiteVirtualTableCursorEnumerator : + SQLiteVirtualTableCursorEnumerator, IEnumerator /* NOT SEALED */ + { + #region Private Data + /// + /// The instance provided when this + /// cursor was created. + /// + private IEnumerator enumerator; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs an instance of this class. + /// + /// + /// The object instance associated + /// with this object instance. + /// + /// + /// The instance to expose as a virtual + /// table cursor. + /// + public SQLiteVirtualTableCursorEnumerator( + SQLiteVirtualTable table, + IEnumerator enumerator + ) + : base(table, enumerator as IEnumerator) + { + this.enumerator = enumerator; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Members + /// + /// Returns the value for the current row of the virtual table cursor + /// using the property of the + /// object instance. + /// + T IEnumerator.Current + { + get + { + CheckDisposed(); + CheckClosed(); + + if (enumerator == null) + return default(T); + + return enumerator.Current; + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Closes the virtual table cursor. This method must not throw any + /// exceptions. + /// + public override void Close() + { + // CheckDisposed(); + // CheckClosed(); + + if (enumerator != null) + enumerator = null; + + base.Close(); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + /// + /// Throws an if this object + /// instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteVirtualTableCursorEnumerator).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of this object instance. + /// + /// + /// Non-zero if this method is being called from the + /// method. Zero if this method is + /// being called from the finalizer. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + Close(); + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteModuleEnumerable Class + /// + /// This class implements a virtual table module that exposes an + /// object instance as a read-only virtual + /// table. It is not sealed and may be used as the base class for any + /// user-defined virtual table class that wraps an + /// object instance. + /// + public class SQLiteModuleEnumerable : + SQLiteModuleEnumerable /* NOT SEALED */ + { + #region Private Data + /// + /// The instance containing the backing + /// data for the virtual table. + /// + private IEnumerable enumerable; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs an instance of this class. + /// + /// + /// The name of the module. This parameter cannot be null. + /// + /// + /// The instance to expose as a virtual + /// table. This parameter cannot be null. + /// + public SQLiteModuleEnumerable( + string name, + IEnumerable enumerable + ) + : base(name, enumerable as IEnumerable) + { + this.enumerable = enumerable; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteManagedModule Members + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Open( + SQLiteVirtualTable table, + ref SQLiteVirtualTableCursor cursor + ) + { + CheckDisposed(); + + cursor = new SQLiteVirtualTableCursorEnumerator( + table, enumerable.GetEnumerator()); + + return SQLiteErrorCode.Ok; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Column( + SQLiteVirtualTableCursor cursor, + SQLiteContext context, + int index + ) + { + CheckDisposed(); + + SQLiteVirtualTableCursorEnumerator enumeratorCursor = + cursor as SQLiteVirtualTableCursorEnumerator; + + if (enumeratorCursor == null) + { + return CursorTypeMismatchError(cursor, + typeof(SQLiteVirtualTableCursorEnumerator)); + } + + if (enumeratorCursor.EndOfEnumerator) + return CursorEndOfEnumeratorError(cursor); + + T current = ((IEnumerator)enumeratorCursor).Current; + + if (current != null) + context.SetString(GetStringFromObject(cursor, current)); + else + context.SetNull(); + + return SQLiteErrorCode.Ok; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + /// + /// Throws an if this object + /// instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteModuleEnumerable).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of this object instance. + /// + /// + /// Non-zero if this method is being called from the + /// method. Zero if this method is + /// being called from the finalizer. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } + #endregion +} +#endregion diff --git a/Native.Csharp.Tool/SQLite/SQLiteModuleNoop.cs b/Native.Csharp.Tool/SQLite/SQLiteModuleNoop.cs new file mode 100644 index 00000000..32a967e3 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteModuleNoop.cs @@ -0,0 +1,786 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Joe Mistachkin (joe@mistachkin.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +using System.Collections.Generic; + +namespace System.Data.SQLite +{ + /// + /// This class implements a virtual table module that does nothing by + /// providing "empty" implementations for all of the + /// interface methods. The result + /// codes returned by these "empty" method implementations may be + /// controlled on a per-method basis by using and/or overriding the + /// , + /// , + /// , + /// , and + /// methods from within derived classes. + /// + public class SQLiteModuleNoop : SQLiteModule /* NOT SEALED */ + { + #region Private Data + /// + /// This field is used to store the + /// values to return, on a per-method basis, for all methods that are + /// part of the interface. + /// + private Dictionary resultCodes; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs an instance of this class. + /// + /// + /// The name of the module. This parameter cannot be null. + /// + public SQLiteModuleNoop( + string name + ) + : base(name) + { + resultCodes = new Dictionary(); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Protected Methods + /// + /// Determines the default value to be + /// returned by methods of the + /// interface that lack an overridden implementation in all classes + /// derived from the class. + /// + /// + /// The value that should be returned + /// by all interface methods unless + /// a more specific result code has been set for that interface method. + /// + protected virtual SQLiteErrorCode GetDefaultResultCode() + { + return SQLiteErrorCode.Ok; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Converts a value into a boolean + /// return value for use with the + /// method. + /// + /// + /// The value to convert. + /// + /// + /// The value. + /// + protected virtual bool ResultCodeToEofResult( + SQLiteErrorCode resultCode + ) + { + return (resultCode == SQLiteErrorCode.Ok) ? false : true; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Converts a value into a boolean + /// return value for use with the + /// method. + /// + /// + /// The value to convert. + /// + /// + /// The value. + /// + protected virtual bool ResultCodeToFindFunctionResult( + SQLiteErrorCode resultCode + ) + { + return (resultCode == SQLiteErrorCode.Ok) ? true : false; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Determines the value that should be + /// returned by the specified + /// interface method if it lack an overridden implementation. If no + /// specific value is available (or set) + /// for the specified method, the value + /// returned by the method will be + /// returned instead. + /// + /// + /// The name of the method. Currently, this method must be part of + /// the interface. + /// + /// + /// The value that should be returned + /// by the interface method. + /// + protected virtual SQLiteErrorCode GetMethodResultCode( + string methodName + ) + { + if ((methodName == null) || (resultCodes == null)) + return GetDefaultResultCode(); + + SQLiteErrorCode resultCode; + + if ((resultCodes != null) && + resultCodes.TryGetValue(methodName, out resultCode)) + { + return resultCode; + } + + return GetDefaultResultCode(); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the value that should be + /// returned by the specified + /// interface method if it lack an overridden implementation. + /// + /// + /// The name of the method. Currently, this method must be part of + /// the interface. + /// + /// + /// The value that should be returned + /// by the interface method. + /// + /// + /// Non-zero upon success. + /// + protected virtual bool SetMethodResultCode( + string methodName, + SQLiteErrorCode resultCode + ) + { + if ((methodName == null) || (resultCodes == null)) + return false; + + resultCodes[methodName] = resultCode; + return true; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteManagedModule Members + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Create( + SQLiteConnection connection, + IntPtr pClientData, + string[] arguments, + ref SQLiteVirtualTable table, + ref string error + ) + { + CheckDisposed(); + + return GetMethodResultCode("Create"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Connect( + SQLiteConnection connection, + IntPtr pClientData, + string[] arguments, + ref SQLiteVirtualTable table, + ref string error + ) + { + CheckDisposed(); + + return GetMethodResultCode("Connect"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode BestIndex( + SQLiteVirtualTable table, + SQLiteIndex index + ) + { + CheckDisposed(); + + return GetMethodResultCode("BestIndex"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Disconnect( + SQLiteVirtualTable table + ) + { + CheckDisposed(); + + return GetMethodResultCode("Disconnect"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Destroy( + SQLiteVirtualTable table + ) + { + CheckDisposed(); + + return GetMethodResultCode("Destroy"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Open( + SQLiteVirtualTable table, + ref SQLiteVirtualTableCursor cursor + ) + { + CheckDisposed(); + + return GetMethodResultCode("Open"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Close( + SQLiteVirtualTableCursor cursor + ) + { + CheckDisposed(); + + return GetMethodResultCode("Close"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Filter( + SQLiteVirtualTableCursor cursor, + int indexNumber, + string indexString, + SQLiteValue[] values + ) + { + CheckDisposed(); + + return GetMethodResultCode("Filter"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Next( + SQLiteVirtualTableCursor cursor + ) + { + CheckDisposed(); + + return GetMethodResultCode("Next"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override bool Eof( + SQLiteVirtualTableCursor cursor + ) + { + CheckDisposed(); + + return ResultCodeToEofResult(GetMethodResultCode("Eof")); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Column( + SQLiteVirtualTableCursor cursor, + SQLiteContext context, + int index + ) + { + CheckDisposed(); + + return GetMethodResultCode("Column"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode RowId( + SQLiteVirtualTableCursor cursor, + ref long rowId + ) + { + CheckDisposed(); + + return GetMethodResultCode("RowId"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Update( + SQLiteVirtualTable table, + SQLiteValue[] values, + ref long rowId + ) + { + CheckDisposed(); + + return GetMethodResultCode("Update"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Begin( + SQLiteVirtualTable table + ) + { + CheckDisposed(); + + return GetMethodResultCode("Begin"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Sync( + SQLiteVirtualTable table + ) + { + CheckDisposed(); + + return GetMethodResultCode("Sync"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Commit( + SQLiteVirtualTable table + ) + { + CheckDisposed(); + + return GetMethodResultCode("Commit"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Rollback( + SQLiteVirtualTable table + ) + { + CheckDisposed(); + + return GetMethodResultCode("Rollback"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override bool FindFunction( + SQLiteVirtualTable table, + int argumentCount, + string name, + ref SQLiteFunction function, + ref IntPtr pClientData + ) + { + CheckDisposed(); + + return ResultCodeToFindFunctionResult(GetMethodResultCode( + "FindFunction")); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Rename( + SQLiteVirtualTable table, + string newName + ) + { + CheckDisposed(); + + return GetMethodResultCode("Rename"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Savepoint( + SQLiteVirtualTable table, + int savepoint + ) + { + CheckDisposed(); + + return GetMethodResultCode("Savepoint"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode Release( + SQLiteVirtualTable table, + int savepoint + ) + { + CheckDisposed(); + + return GetMethodResultCode("Release"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + /// + /// See the method. + /// + public override SQLiteErrorCode RollbackTo( + SQLiteVirtualTable table, + int savepoint + ) + { + CheckDisposed(); + + return GetMethodResultCode("RollbackTo"); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + /// + /// Throws an if this object + /// instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteModuleNoop).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of this object instance. + /// + /// + /// Non-zero if this method is being called from the + /// method. Zero if this method is + /// being called from the finalizer. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteParameter.cs b/Native.Csharp.Tool/SQLite/SQLiteParameter.cs new file mode 100644 index 00000000..40ca6517 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteParameter.cs @@ -0,0 +1,511 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Data; + using System.Data.Common; + using System.ComponentModel; + + /// + /// SQLite implementation of DbParameter. + /// + public sealed class SQLiteParameter : DbParameter, ICloneable + { + /// + /// This value represents an "unknown" . + /// + private const DbType UnknownDbType = (DbType)(-1); + + /// + /// The command associated with this parameter. + /// + private IDbCommand _command; + /// + /// The data type of the parameter + /// + internal DbType _dbType; + /// + /// The version information for mapping the parameter + /// + private DataRowVersion _rowVersion; + /// + /// The value of the data in the parameter + /// + private Object _objValue; + /// + /// The source column for the parameter + /// + private string _sourceColumn; + /// + /// The column name + /// + private string _parameterName; + /// + /// The data size, unused by SQLite + /// + private int _dataSize; + + private bool _nullable; + private bool _nullMapping; + + /// + /// The database type name associated with this parameter, if any. + /// + private string _typeName; + + /// + /// Constructor used when creating for use with a specific command. + /// + /// + /// The command associated with this parameter. + /// + internal SQLiteParameter( + IDbCommand command + ) + : this() + { + _command = command; + } + + /// + /// Default constructor + /// + public SQLiteParameter() + : this(null, UnknownDbType, 0, null, DataRowVersion.Current) + { + } + + /// + /// Constructs a named parameter given the specified parameter name + /// + /// The parameter name + public SQLiteParameter(string parameterName) + : this(parameterName, UnknownDbType, 0, null, DataRowVersion.Current) + { + } + + /// + /// Constructs a named parameter given the specified parameter name and initial value + /// + /// The parameter name + /// The initial value of the parameter + public SQLiteParameter(string parameterName, object value) + : this(parameterName, UnknownDbType, 0, null, DataRowVersion.Current) + { + Value = value; + } + + /// + /// Constructs a named parameter of the specified type + /// + /// The parameter name + /// The datatype of the parameter + public SQLiteParameter(string parameterName, DbType dbType) + : this(parameterName, dbType, 0, null, DataRowVersion.Current) + { + } + + /// + /// Constructs a named parameter of the specified type and source column reference + /// + /// The parameter name + /// The data type + /// The source column + public SQLiteParameter(string parameterName, DbType dbType, string sourceColumn) + : this(parameterName, dbType, 0, sourceColumn, DataRowVersion.Current) + { + } + + /// + /// Constructs a named parameter of the specified type, source column and row version + /// + /// The parameter name + /// The data type + /// The source column + /// The row version information + public SQLiteParameter(string parameterName, DbType dbType, string sourceColumn, DataRowVersion rowVersion) + : this(parameterName, dbType, 0, sourceColumn, rowVersion) + { + } + + /// + /// Constructs an unnamed parameter of the specified data type + /// + /// The datatype of the parameter + public SQLiteParameter(DbType dbType) + : this(null, dbType, 0, null, DataRowVersion.Current) + { + } + + /// + /// Constructs an unnamed parameter of the specified data type and sets the initial value + /// + /// The datatype of the parameter + /// The initial value of the parameter + public SQLiteParameter(DbType dbType, object value) + : this(null, dbType, 0, null, DataRowVersion.Current) + { + Value = value; + } + + /// + /// Constructs an unnamed parameter of the specified data type and source column + /// + /// The datatype of the parameter + /// The source column + public SQLiteParameter(DbType dbType, string sourceColumn) + : this(null, dbType, 0, sourceColumn, DataRowVersion.Current) + { + } + + /// + /// Constructs an unnamed parameter of the specified data type, source column and row version + /// + /// The data type + /// The source column + /// The row version information + public SQLiteParameter(DbType dbType, string sourceColumn, DataRowVersion rowVersion) + : this(null, dbType, 0, sourceColumn, rowVersion) + { + } + + /// + /// Constructs a named parameter of the specified type and size + /// + /// The parameter name + /// The data type + /// The size of the parameter + public SQLiteParameter(string parameterName, DbType parameterType, int parameterSize) + : this(parameterName, parameterType, parameterSize, null, DataRowVersion.Current) + { + } + + /// + /// Constructs a named parameter of the specified type, size and source column + /// + /// The name of the parameter + /// The data type + /// The size of the parameter + /// The source column + public SQLiteParameter(string parameterName, DbType parameterType, int parameterSize, string sourceColumn) + : this(parameterName, parameterType, parameterSize, sourceColumn, DataRowVersion.Current) + { + } + + /// + /// Constructs a named parameter of the specified type, size, source column and row version + /// + /// The name of the parameter + /// The data type + /// The size of the parameter + /// The source column + /// The row version information + public SQLiteParameter(string parameterName, DbType parameterType, int parameterSize, string sourceColumn, DataRowVersion rowVersion) + { + _parameterName = parameterName; + _dbType = parameterType; + _sourceColumn = sourceColumn; + _rowVersion = rowVersion; + _dataSize = parameterSize; + _nullable = true; + } + + private SQLiteParameter(SQLiteParameter source) + : this(source.ParameterName, source._dbType, 0, source.Direction, source.IsNullable, 0, 0, source.SourceColumn, source.SourceVersion, source.Value) + { + _nullMapping = source._nullMapping; + } + + /// + /// Constructs a named parameter of the specified type, size, source column and row version + /// + /// The name of the parameter + /// The data type + /// The size of the parameter + /// Only input parameters are supported in SQLite + /// Ignored + /// Ignored + /// Ignored + /// The source column + /// The row version information + /// The initial value to assign the parameter +#if !PLATFORM_COMPACTFRAMEWORK + [EditorBrowsable(EditorBrowsableState.Advanced)] +#endif + public SQLiteParameter(string parameterName, DbType parameterType, int parameterSize, ParameterDirection direction, bool isNullable, byte precision, byte scale, string sourceColumn, DataRowVersion rowVersion, object value) + : this(parameterName, parameterType, parameterSize, sourceColumn, rowVersion) + { + Direction = direction; + IsNullable = isNullable; + Value = value; + } + + /// + /// Constructs a named parameter, yet another flavor + /// + /// The name of the parameter + /// The data type + /// The size of the parameter + /// Only input parameters are supported in SQLite + /// Ignored + /// Ignored + /// The source column + /// The row version information + /// Whether or not this parameter is for comparing NULL's + /// The intial value to assign the parameter +#if !PLATFORM_COMPACTFRAMEWORK + [EditorBrowsable(EditorBrowsableState.Advanced)] +#endif + public SQLiteParameter(string parameterName, DbType parameterType, int parameterSize, ParameterDirection direction, byte precision, byte scale, string sourceColumn, DataRowVersion rowVersion, bool sourceColumnNullMapping, object value) + : this(parameterName, parameterType, parameterSize, sourceColumn, rowVersion) + { + Direction = direction; + SourceColumnNullMapping = sourceColumnNullMapping; + Value = value; + } + + /// + /// Constructs an unnamed parameter of the specified type and size + /// + /// The data type + /// The size of the parameter + public SQLiteParameter(DbType parameterType, int parameterSize) + : this(null, parameterType, parameterSize, null, DataRowVersion.Current) + { + } + + /// + /// Constructs an unnamed parameter of the specified type, size, and source column + /// + /// The data type + /// The size of the parameter + /// The source column + public SQLiteParameter(DbType parameterType, int parameterSize, string sourceColumn) + : this(null, parameterType, parameterSize, sourceColumn, DataRowVersion.Current) + { + } + + /// + /// Constructs an unnamed parameter of the specified type, size, source column and row version + /// + /// The data type + /// The size of the parameter + /// The source column + /// The row version information + public SQLiteParameter(DbType parameterType, int parameterSize, string sourceColumn, DataRowVersion rowVersion) + : this(null, parameterType, parameterSize, sourceColumn, rowVersion) + { + } + + /// + /// The command associated with this parameter. + /// + public IDbCommand Command + { + get + { + return _command; + } + set + { + _command = value; + } + } + + /// + /// Whether or not the parameter can contain a null value + /// + public override bool IsNullable + { + get + { + return _nullable; + } + set + { + _nullable = value; + } + } + + /// + /// Returns the datatype of the parameter + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DbProviderSpecificTypeProperty(true)] + [RefreshProperties(RefreshProperties.All)] +#endif + public override DbType DbType + { + get + { + if (_dbType == UnknownDbType) + { + if (_objValue != null && _objValue != DBNull.Value) + { + return SQLiteConvert.TypeToDbType(_objValue.GetType()); + } + return DbType.String; // Unassigned default value is String + } + return _dbType; + } + set + { + _dbType = value; + } + } + + /// + /// Supports only input parameters + /// + public override ParameterDirection Direction + { + get + { + return ParameterDirection.Input; + } + set + { + if (value != ParameterDirection.Input) + throw new NotSupportedException(); + } + } + + /// + /// Returns the parameter name + /// + public override string ParameterName + { + get + { + return _parameterName; + } + set + { + _parameterName = value; + } + } + + /// + /// Resets the DbType of the parameter so it can be inferred from the value + /// + public override void ResetDbType() + { + _dbType = UnknownDbType; + } + + /// + /// Returns the size of the parameter + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DefaultValue((int)0)] +#endif + public override int Size + { + get + { + return _dataSize; + } + set + { + _dataSize = value; + } + } + + /// + /// Gets/sets the source column + /// + public override string SourceColumn + { + get + { + return _sourceColumn; + } + set + { + _sourceColumn = value; + } + } + + /// + /// Used by DbCommandBuilder to determine the mapping for nullable fields + /// + public override bool SourceColumnNullMapping + { + get + { + return _nullMapping; + } + set + { + _nullMapping = value; + } + } + + /// + /// Gets and sets the row version + /// + public override DataRowVersion SourceVersion + { + get + { + return _rowVersion; + } + set + { + _rowVersion = value; + } + } + + /// + /// Gets and sets the parameter value. If no datatype was specified, the datatype will assume the type from the value given. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [TypeConverter(typeof(StringConverter)), RefreshProperties(RefreshProperties.All)] +#endif + public override object Value + { + get + { + return _objValue; + } + set + { + _objValue = value; + if (_dbType == UnknownDbType && _objValue != null && _objValue != DBNull.Value) // If the DbType has never been assigned, try to glean one from the value's datatype + _dbType = SQLiteConvert.TypeToDbType(_objValue.GetType()); + } + } + + /// + /// The database type name associated with this parameter, if any. + /// + public string TypeName + { + get + { + return _typeName; + } + set + { + _typeName = value; + } + } + + /// + /// Clones a parameter + /// + /// A new, unassociated SQLiteParameter + public object Clone() + { + SQLiteParameter newparam = new SQLiteParameter(this); + + return newparam; + } + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteParameterCollection.cs b/Native.Csharp.Tool/SQLite/SQLiteParameterCollection.cs new file mode 100644 index 00000000..2945cd47 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteParameterCollection.cs @@ -0,0 +1,477 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Data; + using System.Data.Common; + using System.Collections.Generic; + using System.Globalization; + +#if !PLATFORM_COMPACTFRAMEWORK + using System.ComponentModel; +#endif + + using System.Reflection; + + /// + /// SQLite implementation of DbParameterCollection. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Editor("Microsoft.VSDesigner.Data.Design.DBParametersEditor, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), ListBindable(false)] +#endif + public sealed class SQLiteParameterCollection : DbParameterCollection + { + /// + /// The underlying command to which this collection belongs + /// + private SQLiteCommand _command; + /// + /// The internal array of parameters in this collection + /// + private List _parameterList; + /// + /// Determines whether or not all parameters have been bound to their statement(s) + /// + private bool _unboundFlag; + + /// + /// Initializes the collection + /// + /// The command to which the collection belongs + internal SQLiteParameterCollection(SQLiteCommand cmd) + { + _command = cmd; + _parameterList = new List(); + _unboundFlag = true; + } + + /// + /// Returns false + /// + public override bool IsSynchronized + { + get { return false; } + } + + /// + /// Returns false + /// + public override bool IsFixedSize + { + get { return false; } + } + + /// + /// Returns false + /// + public override bool IsReadOnly + { + get { return false; } + } + + /// + /// Returns null + /// + public override object SyncRoot + { + get { return null; } + } + + /// + /// Retrieves an enumerator for the collection + /// + /// An enumerator for the underlying array + public override System.Collections.IEnumerator GetEnumerator() + { + return _parameterList.GetEnumerator(); + } + + /// + /// Adds a parameter to the collection + /// + /// The parameter name + /// The data type + /// The size of the value + /// The source column + /// A SQLiteParameter object + public SQLiteParameter Add(string parameterName, DbType parameterType, int parameterSize, string sourceColumn) + { + SQLiteParameter param = new SQLiteParameter(parameterName, parameterType, parameterSize, sourceColumn); + Add(param); + + return param; + } + + /// + /// Adds a parameter to the collection + /// + /// The parameter name + /// The data type + /// The size of the value + /// A SQLiteParameter object + public SQLiteParameter Add(string parameterName, DbType parameterType, int parameterSize) + { + SQLiteParameter param = new SQLiteParameter(parameterName, parameterType, parameterSize); + Add(param); + + return param; + } + + /// + /// Adds a parameter to the collection + /// + /// The parameter name + /// The data type + /// A SQLiteParameter object + public SQLiteParameter Add(string parameterName, DbType parameterType) + { + SQLiteParameter param = new SQLiteParameter(parameterName, parameterType); + Add(param); + + return param; + } + + /// + /// Adds a parameter to the collection + /// + /// The parameter to add + /// A zero-based index of where the parameter is located in the array + public int Add(SQLiteParameter parameter) + { + int n = -1; + + if (String.IsNullOrEmpty(parameter.ParameterName) == false) + { + n = IndexOf(parameter.ParameterName); + } + + if (n == -1) + { + n = _parameterList.Count; + _parameterList.Add((SQLiteParameter)parameter); + } + + SetParameter(n, parameter); + + return n; + } + + /// + /// Adds a parameter to the collection + /// + /// The parameter to add + /// A zero-based index of where the parameter is located in the array +#if !PLATFORM_COMPACTFRAMEWORK + [EditorBrowsable(EditorBrowsableState.Never)] +#endif + public override int Add(object value) + { + return Add((SQLiteParameter)value); + } + + /// + /// Adds a named/unnamed parameter and its value to the parameter collection. + /// + /// Name of the parameter, or null to indicate an unnamed parameter + /// The initial value of the parameter + /// Returns the SQLiteParameter object created during the call. + public SQLiteParameter AddWithValue(string parameterName, object value) + { + SQLiteParameter param = new SQLiteParameter(parameterName, value); + Add(param); + + return param; + } + + /// + /// Adds an array of parameters to the collection + /// + /// The array of parameters to add + public void AddRange(SQLiteParameter[] values) + { + int x = values.Length; + for (int n = 0; n < x; n++) + Add(values[n]); + } + + /// + /// Adds an array of parameters to the collection + /// + /// The array of parameters to add + public override void AddRange(Array values) + { + int x = values.Length; + for (int n = 0; n < x; n++) + Add((SQLiteParameter)(values.GetValue(n))); + } + + /// + /// Clears the array and resets the collection + /// + public override void Clear() + { + _unboundFlag = true; + _parameterList.Clear(); + } + + /// + /// Determines if the named parameter exists in the collection + /// + /// The name of the parameter to check + /// True if the parameter is in the collection + public override bool Contains(string parameterName) + { + return (IndexOf(parameterName) != -1); + } + + /// + /// Determines if the parameter exists in the collection + /// + /// The SQLiteParameter to check + /// True if the parameter is in the collection + public override bool Contains(object value) + { + return _parameterList.Contains((SQLiteParameter)value); + } + + /// + /// Not implemented + /// + /// + /// + public override void CopyTo(Array array, int index) + { + throw new NotImplementedException(); + } + + /// + /// Returns a count of parameters in the collection + /// + public override int Count + { + get { return _parameterList.Count; } + } + + /// + /// Overloaded to specialize the return value of the default indexer + /// + /// Name of the parameter to get/set + /// The specified named SQLite parameter + public new SQLiteParameter this[string parameterName] + { + get + { + return (SQLiteParameter)GetParameter(parameterName); + } + set + { + SetParameter(parameterName, value); + } + } + + /// + /// Overloaded to specialize the return value of the default indexer + /// + /// The index of the parameter to get/set + /// The specified SQLite parameter + public new SQLiteParameter this[int index] + { + get + { + return (SQLiteParameter)GetParameter(index); + } + set + { + SetParameter(index, value); + } + } + /// + /// Retrieve a parameter by name from the collection + /// + /// The name of the parameter to fetch + /// A DbParameter object + protected override DbParameter GetParameter(string parameterName) + { + return GetParameter(IndexOf(parameterName)); + } + + /// + /// Retrieves a parameter by its index in the collection + /// + /// The index of the parameter to retrieve + /// A DbParameter object + protected override DbParameter GetParameter(int index) + { + return _parameterList[index]; + } + + /// + /// Returns the index of a parameter given its name + /// + /// The name of the parameter to find + /// -1 if not found, otherwise a zero-based index of the parameter + public override int IndexOf(string parameterName) + { + int x = _parameterList.Count; + for (int n = 0; n < x; n++) + { + if (String.Compare(parameterName, _parameterList[n].ParameterName, StringComparison.OrdinalIgnoreCase) == 0) + return n; + } + return -1; + } + + /// + /// Returns the index of a parameter + /// + /// The parameter to find + /// -1 if not found, otherwise a zero-based index of the parameter + public override int IndexOf(object value) + { + return _parameterList.IndexOf((SQLiteParameter)value); + } + + /// + /// Inserts a parameter into the array at the specified location + /// + /// The zero-based index to insert the parameter at + /// The parameter to insert + public override void Insert(int index, object value) + { + _unboundFlag = true; + _parameterList.Insert(index, (SQLiteParameter)value); + } + + /// + /// Removes a parameter from the collection + /// + /// The parameter to remove + public override void Remove(object value) + { + _unboundFlag = true; + _parameterList.Remove((SQLiteParameter)value); + } + + /// + /// Removes a parameter from the collection given its name + /// + /// The name of the parameter to remove + public override void RemoveAt(string parameterName) + { + RemoveAt(IndexOf(parameterName)); + } + + /// + /// Removes a parameter from the collection given its index + /// + /// The zero-based parameter index to remove + public override void RemoveAt(int index) + { + _unboundFlag = true; + _parameterList.RemoveAt(index); + } + + /// + /// Re-assign the named parameter to a new parameter object + /// + /// The name of the parameter to replace + /// The new parameter + protected override void SetParameter(string parameterName, DbParameter value) + { + SetParameter(IndexOf(parameterName), value); + } + + /// + /// Re-assign a parameter at the specified index + /// + /// The zero-based index of the parameter to replace + /// The new parameter + protected override void SetParameter(int index, DbParameter value) + { + _unboundFlag = true; + _parameterList[index] = (SQLiteParameter)value; + } + + /// + /// Un-binds all parameters from their statements + /// + internal void Unbind() + { + _unboundFlag = true; + } + + /// + /// This function attempts to map all parameters in the collection to all statements in a Command. + /// Since named parameters may span multiple statements, this function makes sure all statements are bound + /// to the same named parameter. Unnamed parameters are bound in sequence. + /// + internal void MapParameters(SQLiteStatement activeStatement) + { + if (_unboundFlag == false || _parameterList.Count == 0 || _command._statementList == null) return; + + int nUnnamed = 0; + string s; + int n; + int y = -1; + SQLiteStatement stmt; + + foreach(SQLiteParameter p in _parameterList) + { + y ++; + s = p.ParameterName; + if (s == null) + { + s = HelperMethods.StringFormat(CultureInfo.InvariantCulture, ";{0}", nUnnamed); + nUnnamed++; + } + + int x; + bool isMapped = false; + + if (activeStatement == null) + x = _command._statementList.Count; + else + x = 1; + + stmt = activeStatement; + for (n = 0; n < x; n++) + { + isMapped = false; + if (stmt == null) stmt = _command._statementList[n]; + if (stmt._paramNames != null) + { + if (stmt.MapParameter(s, p) == true) + isMapped = true; + } + stmt = null; + } + + // If the parameter has a name, but the SQL statement uses unnamed references, this can happen -- attempt to map + // the parameter by its index in the collection + if (isMapped == false) + { + s = HelperMethods.StringFormat(CultureInfo.InvariantCulture, ";{0}", y); + + stmt = activeStatement; + for (n = 0; n < x; n++) + { + if (stmt == null) stmt = _command._statementList[n]; + if (stmt._paramNames != null) + { + if (stmt.MapParameter(s, p) == true) + isMapped = true; + } + stmt = null; + } + } + } + if (activeStatement == null) _unboundFlag = false; + } + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLitePatchLevel.cs b/Native.Csharp.Tool/SQLite/SQLitePatchLevel.cs new file mode 100644 index 00000000..1e13a589 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLitePatchLevel.cs @@ -0,0 +1,16 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Joe Mistachkin (joe@mistachkin.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +using System.Data.SQLite; + +/////////////////////////////////////////////////////////////////////////////// + +[assembly: AssemblySourceId(null)] + +/////////////////////////////////////////////////////////////////////////////// + +[assembly: AssemblySourceTimeStamp(null)] diff --git a/Native.Csharp.Tool/SQLite/SQLiteSession.cs b/Native.Csharp.Tool/SQLite/SQLiteSession.cs new file mode 100644 index 00000000..a02dd2c3 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteSession.cs @@ -0,0 +1,5568 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Joe Mistachkin (joe@mistachkin.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +using System.Collections; +using System.Collections.Generic; + +#if DEBUG +using System.Diagnostics; +#endif + +using System.IO; +using System.Globalization; +using System.Runtime.InteropServices; + +namespace System.Data.SQLite +{ + #region Session Extension Enumerations + /// + /// This enumerated type represents a type of conflict seen when apply + /// changes from a change set or patch set. + /// + public enum SQLiteChangeSetConflictType + { + /// + /// This value is seen when processing a DELETE or UPDATE change if a + /// row with the required PRIMARY KEY fields is present in the + /// database, but one or more other (non primary-key) fields modified + /// by the update do not contain the expected "before" values. + /// + Data = 1, + + /// + /// This value is seen when processing a DELETE or UPDATE change if a + /// row with the required PRIMARY KEY fields is not present in the + /// database. There is no conflicting row in this case. + /// + /// The results of invoking the + /// + /// method are undefined. + /// + NotFound = 2, + + /// + /// This value is seen when processing an INSERT change if the + /// operation would result in duplicate primary key values. + /// The conflicting row in this case is the database row with the + /// matching primary key. + /// + Conflict = 3, + + /// + /// If a non-foreign key constraint violation occurs while applying a + /// change (i.e. a UNIQUE, CHECK or NOT NULL constraint), the conflict + /// callback will see this value. + /// + /// There is no conflicting row in this case. The results of invoking + /// the + /// method are undefined. + /// + Constraint = 4, + + /// + /// If foreign key handling is enabled, and applying a changes leaves + /// the database in a state containing foreign key violations, this + /// value will be seen exactly once before the changes are committed. + /// If the conflict handler + /// , the changes, + /// including those that caused the foreign key constraint violation, + /// are committed. Or, if it returns + /// , the changes are + /// rolled back. + /// + /// No current or conflicting row information is provided. The only + /// method it is possible to call on the supplied + /// object is + /// . + /// + ForeignKey = 5 + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// This enumerated type represents the result of a user-defined conflict + /// resolution callback. + /// + public enum SQLiteChangeSetConflictResult + { + /// + /// If a conflict callback returns this value no special action is + /// taken. The change that caused the conflict is not applied. The + /// application of changes continues with the next change. + /// + Omit = 0, + + /// + /// This value may only be returned from a conflict callback if the + /// type of conflict was + /// or . If this is + /// not the case, any changes applied so far are rolled back and the + /// call to + /// + /// will raise a with an error code of + /// . + /// + /// If this value is returned for a + /// conflict, then the + /// conflicting row is either updated or deleted, depending on the type + /// of change. + /// + /// If this value is returned for a + /// conflict, then + /// the conflicting row is removed from the database and a second + /// attempt to apply the change is made. If this second attempt fails, + /// the original row is restored to the database before continuing. + /// + Replace = 1, + + /// + /// If this value is returned, any changes applied so far are rolled + /// back and the call to + /// + /// will raise a with an error code of + /// . + /// + Abort = 2 + } + + /////////////////////////////////////////////////////////////////////////// + + /// + /// This enumerated type represents possible flags that may be passed + /// to the appropriate overloads of various change set creation methods. + /// + public enum SQLiteChangeSetStartFlags + { + /// + /// No special handling. + /// + None = 0x0, + + /// + /// Invert the change set while iterating through it. + /// This is equivalent to inverting a change set using + /// before + /// applying it. It is an error to specify this flag + /// with a patch set. + /// + Invert = 0x2 + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region Session Extension Delegates + /// + /// This callback is invoked when a determination must be made about + /// whether changes to a specific table should be tracked -OR- applied. + /// It will not be called for tables that are already attached to a + /// . + /// + /// + /// The optional application-defined context data that was originally + /// passed to the or + /// + /// methods. This value may be null. + /// + /// + /// The name of the table. + /// + /// + /// Non-zero if changes to the table should be considered; otherwise, + /// zero. Throwing an exception from this callback will result in + /// undefined behavior. + /// + public delegate bool SessionTableFilterCallback( + object clientData, + string name + ); + + /////////////////////////////////////////////////////////////////////////// + + /// + /// This callback is invoked when there is a conflict while apply changes + /// to a database. + /// + /// + /// The optional application-defined context data that was originally + /// passed to the + /// + /// method. This value may be null. + /// + /// + /// The type of this conflict. + /// + /// + /// The object associated with + /// this conflict. This value may not be null; however, only properties + /// that are applicable to the conflict type will be available. Further + /// information on this is available within the descriptions of the + /// available values. + /// + /// + /// A value that indicates the + /// action to be taken in order to resolve the conflict. Throwing an + /// exception from this callback will result in undefined behavior. + /// + public delegate SQLiteChangeSetConflictResult SessionConflictCallback( + object clientData, + SQLiteChangeSetConflictType type, + ISQLiteChangeSetMetadataItem item + ); + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region ISQLiteChangeSet Interface + /// + /// This interface contains methods used to manipulate a set of changes for + /// a database. + /// + public interface ISQLiteChangeSet : + IEnumerable, IDisposable + { + /// + /// This method "inverts" the set of changes within this instance. + /// Applying an inverted set of changes to a database reverses the + /// effects of applying the uninverted changes. Specifically: + /// ]]>]]> + /// Each DELETE change is changed to an INSERT, and + /// ]]>]]> + /// Each INSERT change is changed to a DELETE, and + /// ]]>]]> + /// For each UPDATE change, the old.* and new.* values are exchanged. + /// ]]>]]> + /// This method does not change the order in which changes appear + /// within the set of changes. It merely reverses the sense of each + /// individual change. + /// + /// + /// The new instance that represents + /// the resulting set of changes -OR- null if it is not available. + /// + ISQLiteChangeSet Invert(); + + /// + /// This method combines the specified set of changes with the ones + /// contained in this instance. + /// + /// + /// The changes to be combined with those in this instance. + /// + /// + /// The new instance that represents + /// the resulting set of changes -OR- null if it is not available. + /// + ISQLiteChangeSet CombineWith(ISQLiteChangeSet changeSet); + + /// + /// Attempts to apply the set of changes in this instance to the + /// associated database. + /// + /// + /// The delegate that will need + /// to handle any conflicting changes that may arise. + /// + /// + /// The optional application-defined context data. This value may be + /// null. + /// + void Apply( + SessionConflictCallback conflictCallback, + object clientData + ); + + /// + /// Attempts to apply the set of changes in this instance to the + /// associated database. + /// + /// + /// The delegate that will need + /// to handle any conflicting changes that may arise. + /// + /// + /// The optional delegate + /// that can be used to filter the list of tables impacted by the set + /// of changes. + /// + /// + /// The optional application-defined context data. This value may be + /// null. + /// + void Apply( + SessionConflictCallback conflictCallback, + SessionTableFilterCallback tableFilterCallback, + object clientData + ); + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region ISQLiteChangeGroup Interface + /// + /// This interface contains methods used to manipulate multiple sets of + /// changes for a database. + /// + public interface ISQLiteChangeGroup : IDisposable + { + /// + /// Attempts to add a change set (or patch set) to this change group + /// instance. The underlying data must be contained entirely within + /// the byte array. + /// + /// + /// The raw byte data for the specified change set (or patch set). + /// + void AddChangeSet(byte[] rawData); + + /// + /// Attempts to add a change set (or patch set) to this change group + /// instance. The underlying data will be read from the specified + /// . + /// + /// + /// The instance containing the raw change set + /// (or patch set) data to read. + /// + void AddChangeSet(Stream stream); + + /// + /// Attempts to create and return, via , the + /// combined set of changes represented by this change group instance. + /// + /// + /// Upon success, this will contain the raw byte data for all the + /// changes in this change group instance. + /// + void CreateChangeSet(ref byte[] rawData); + + /// + /// Attempts to create and write, via , the + /// combined set of changes represented by this change group instance. + /// + /// + /// Upon success, the raw byte data for all the changes in this change + /// group instance will be written to this . + /// + void CreateChangeSet(Stream stream); + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region ISQLiteChangeSetMetadataItem Interface + /// + /// This interface contains properties and methods used to fetch metadata + /// about one change within a set of changes for a database. + /// + public interface ISQLiteChangeSetMetadataItem : IDisposable + { + /// + /// The name of the table the change was made to. + /// + string TableName { get; } + + /// + /// The number of columns impacted by this change. This value can be + /// used to determine the highest valid column index that may be used + /// with the , , + /// and methods of this interface. It + /// will be this value minus one. + /// + int NumberOfColumns { get; } + + /// + /// This will contain the value + /// , + /// , or + /// , corresponding to + /// the overall type of change this item represents. + /// + SQLiteAuthorizerActionCode OperationCode { get; } + + /// + /// Non-zero if this change is considered to be indirect (i.e. as + /// though they were made via a trigger or foreign key action). + /// + bool Indirect { get; } + + /// + /// This array contains a for each column in + /// the table associated with this change. The element will be zero + /// if the column is not part of the primary key; otherwise, it will + /// be non-zero. + /// + bool[] PrimaryKeyColumns { get; } + + /// + /// This method may only be called from within a + /// delegate when the conflict + /// type is . It + /// returns the total number of known foreign key violations in the + /// destination database. + /// + int NumberOfForeignKeyConflicts { get; } + + /// + /// Queries and returns the original value of a given column for this + /// change. This method may only be called when the + /// has a value of + /// or + /// . + /// + /// + /// The index for the column. This value must be between zero and one + /// less than the total number of columns for this table. + /// + /// + /// The original value of a given column for this change. + /// + SQLiteValue GetOldValue(int columnIndex); + + /// + /// Queries and returns the updated value of a given column for this + /// change. This method may only be called when the + /// has a value of + /// or + /// . + /// + /// + /// The index for the column. This value must be between zero and one + /// less than the total number of columns for this table. + /// + /// + /// The updated value of a given column for this change. + /// + SQLiteValue GetNewValue(int columnIndex); + + /// + /// Queries and returns the conflicting value of a given column for + /// this change. This method may only be called from within a + /// delegate when the conflict + /// type is or + /// . + /// + /// + /// The index for the column. This value must be between zero and one + /// less than the total number of columns for this table. + /// + /// + /// The conflicting value of a given column for this change. + /// + SQLiteValue GetConflictValue(int columnIndex); + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region ISQLiteSession Interface + /// + /// This interface contains methods to query and manipulate the state of a + /// change tracking session for a database. + /// + public interface ISQLiteSession : IDisposable + { + /// + /// Determines if this session is currently tracking changes to its + /// associated database. + /// + /// + /// Non-zero if changes to the associated database are being trakced; + /// otherwise, zero. + /// + bool IsEnabled(); + + /// + /// Enables tracking of changes to the associated database. + /// + void SetToEnabled(); + + /// + /// Disables tracking of changes to the associated database. + /// + void SetToDisabled(); + + /// + /// Determines if this session is currently set to mark changes as + /// indirect (i.e. as though they were made via a trigger or foreign + /// key action). + /// + /// + /// Non-zero if changes to the associated database are being marked as + /// indirect; otherwise, zero. + /// + bool IsIndirect(); + + /// + /// Sets the indirect flag for this session. Subsequent changes will + /// be marked as indirect until this flag is changed again. + /// + void SetToIndirect(); + + /// + /// Clears the indirect flag for this session. Subsequent changes will + /// be marked as direct until this flag is changed again. + /// + void SetToDirect(); + + /// + /// Determines if there are any tracked changes currently within the + /// data for this session. + /// + /// + /// Non-zero if there are no changes within the data for this session; + /// otherwise, zero. + /// + bool IsEmpty(); + + /// + /// Upon success, causes changes to the specified table(s) to start + /// being tracked. Any tables impacted by calls to this method will + /// not cause the callback + /// to be invoked. + /// + /// + /// The name of the table to be tracked -OR- null to track all + /// applicable tables within this database. + /// + void AttachTable(string name); + + /// + /// This method is used to set the table filter for this instance. + /// + /// + /// The table filter callback -OR- null to clear any existing table + /// filter callback. + /// + /// + /// The optional application-defined context data. This value may be + /// null. + /// + void SetTableFilter( + SessionTableFilterCallback callback, + object clientData + ); + + /// + /// Attempts to create and return, via , the + /// combined set of changes represented by this session instance. + /// + /// + /// Upon success, this will contain the raw byte data for all the + /// changes in this session instance. + /// + void CreateChangeSet(ref byte[] rawData); + + /// + /// Attempts to create and write, via , the + /// combined set of changes represented by this session instance. + /// + /// + /// Upon success, the raw byte data for all the changes in this session + /// instance will be written to this . + /// + void CreateChangeSet(Stream stream); + + /// + /// Attempts to create and return, via , the + /// combined set of changes represented by this session instance as a + /// patch set. + /// + /// + /// Upon success, this will contain the raw byte data for all the + /// changes in this session instance. + /// + void CreatePatchSet(ref byte[] rawData); + + /// + /// Attempts to create and write, via , the + /// combined set of changes represented by this session instance as a + /// patch set. + /// + /// + /// Upon success, the raw byte data for all the changes in this session + /// instance will be written to this . + /// + void CreatePatchSet(Stream stream); + + /// + /// This method loads the differences between two tables [with the same + /// name, set of columns, and primary key definition] into this session + /// instance. + /// + /// + /// The name of the database containing the table with the original + /// data (i.e. it will need updating in order to be identical to the + /// one within the database associated with this session instance). + /// + /// + /// The name of the table. + /// + void LoadDifferencesFromTable( + string fromDatabaseName, + string tableName + ); + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteSessionHelpers Class + /// + /// This class contains some static helper methods for use within this + /// subsystem. + /// + internal static class SQLiteSessionHelpers + { + #region Public Methods + /// + /// This method checks the byte array specified by the caller to make + /// sure it will be usable. + /// + /// + /// A byte array provided by the caller into one of the public methods + /// for the classes that belong to this subsystem. This value cannot + /// be null or represent an empty array; otherwise, an appropriate + /// exception will be thrown. + /// + public static void CheckRawData( + byte[] rawData + ) + { + if (rawData == null) + throw new ArgumentNullException("rawData"); + + if (rawData.Length == 0) + { + throw new ArgumentException( + "empty change set data", "rawData"); + } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteConnectionLock Class + /// + /// This class is used to hold the native connection handle associated with + /// a open until this subsystem is totally + /// done with it. This class is for internal use by this subsystem only. + /// + internal abstract class SQLiteConnectionLock : IDisposable + { + #region Private Constants + /// + /// The SQL statement used when creating the native statement handle. + /// There are no special requirements for this other than counting as + /// an "open statement handle". + /// + private const string LockNopSql = "SELECT 1;"; + + /////////////////////////////////////////////////////////////////////// + + /// + /// The format of the error message used when reporting, during object + /// disposal, that the statement handle is still open (i.e. because + /// this situation is considered a fairly serious programming error). + /// + private const string StatementMessageFormat = + "Connection lock object was {0} with statement {1}"; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Data + /// + /// The wrapped native connection handle associated with this lock. + /// + private SQLiteConnectionHandle handle; + + /// + /// The flags associated with the connection represented by the + /// value. + /// + private SQLiteConnectionFlags flags; + + /////////////////////////////////////////////////////////////////////// + + /// + /// The native statement handle for this lock. The garbage collector + /// cannot cause this statement to be finalized; therefore, it will + /// serve to hold the associated native connection open until it is + /// freed manually using the method. + /// + private IntPtr statement; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs a new instance of this class using the specified wrapped + /// native connection handle and associated flags. + /// + /// + /// The wrapped native connection handle to be associated with this + /// lock. + /// + /// + /// The flags associated with the connection represented by the + /// value. + /// + /// + /// Non-zero if the method should be called prior + /// to returning from this constructor. + /// + public SQLiteConnectionLock( + SQLiteConnectionHandle handle, + SQLiteConnectionFlags flags, + bool autoLock + ) + { + this.handle = handle; + this.flags = flags; + + if (autoLock) + Lock(); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Protected Methods + /// + /// Queries and returns the wrapped native connection handle for this + /// instance. + /// + /// + /// The wrapped native connection handle for this instance -OR- null + /// if it is unavailable. + /// + protected SQLiteConnectionHandle GetHandle() + { + return handle; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Queries and returns the flags associated with the connection for + /// this instance. + /// + /// + /// The value. There is no return + /// value reserved to indicate an error. + /// + protected SQLiteConnectionFlags GetFlags() + { + return flags; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Queries and returns the native connection handle for this instance. + /// + /// + /// The native connection handle for this instance. If this value is + /// unavailable or invalid an exception will be thrown. + /// + protected IntPtr GetIntPtr() + { + if (handle == null) + { + throw new InvalidOperationException( + "Connection lock object has an invalid handle."); + } + + IntPtr handlePtr = handle; + + if (handlePtr == IntPtr.Zero) + { + throw new InvalidOperationException( + "Connection lock object has an invalid handle pointer."); + } + + return handlePtr; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Methods + /// + /// This method attempts to "lock" the associated native connection + /// handle by preparing a SQL statement that will not be finalized + /// until the method is called (i.e. and which + /// cannot be done by the garbage collector). If the statement is + /// already prepared, nothing is done. If the statement cannot be + /// prepared for any reason, an exception will be thrown. + /// + public void Lock() + { + CheckDisposed(); + + if (statement != IntPtr.Zero) + return; + + IntPtr pSql = IntPtr.Zero; + + try + { + int nSql = 0; + + pSql = SQLiteString.Utf8IntPtrFromString(LockNopSql, ref nSql); + + IntPtr pRemain = IntPtr.Zero; + +#if !SQLITE_STANDARD + int nRemain = 0; + string functionName = "sqlite3_prepare_interop"; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_prepare_interop( + GetIntPtr(), pSql, nSql, ref statement, ref pRemain, + ref nRemain); +#else +#if USE_PREPARE_V2 + string functionName = "sqlite3_prepare_v2"; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_prepare_v2( + GetIntPtr(), pSql, nSql, ref statement, ref pRemain); +#else + string functionName = "sqlite3_prepare"; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_prepare( + GetIntPtr(), pSql, nSql, ref statement, ref pRemain); +#endif +#endif + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, functionName); + } + finally + { + if (pSql != IntPtr.Zero) + { + SQLiteMemory.Free(pSql); + pSql = IntPtr.Zero; + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method attempts to "unlock" the associated native connection + /// handle by finalizing the previously prepared statement. If the + /// statement is already finalized, nothing is done. If the statement + /// cannot be finalized for any reason, an exception will be thrown. + /// + public void Unlock() + { + CheckDisposed(); + + if (statement == IntPtr.Zero) + return; + +#if !SQLITE_STANDARD + string functionName = "sqlite3_finalize_interop"; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_finalize_interop( + statement); +#else + string functionName = "sqlite3_finalize"; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_finalize( + statement); +#endif + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, functionName); + + statement = IntPtr.Zero; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes of this object instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteConnectionLock).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + protected virtual void Dispose(bool disposing) + { + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + if (statement != IntPtr.Zero) + { + // + // NOTE: This should never happen. This object was + // disposed (or finalized) without the Unlock + // method being called first. + // + try + { + if (HelperMethods.LogPrepare(GetFlags())) + { + /* throw */ + SQLiteLog.LogMessage( + SQLiteErrorCode.Misuse, + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + StatementMessageFormat, disposing ? + "disposed" : "finalized", + statement)); + } + } + catch + { + // do nothing. + } + +#if DEBUG + Debugger.Break(); +#endif + } + } + } + finally + { + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Destructor + /// + /// Finalizes this object instance. + /// + ~SQLiteConnectionLock() + { + Dispose(false); + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteChangeSetIterator Class + /// + /// This class manages the native change set iterator. It is used as the + /// base class for the and + /// classes. It knows how to + /// advance the native iterator handle as well as finalize it. + /// + internal class SQLiteChangeSetIterator : IDisposable + { + #region Private Data + /// + /// The native change set (a.k.a. iterator) handle. + /// + private IntPtr iterator; + + /////////////////////////////////////////////////////////////////////// + + /// + /// Non-zero if this instance owns the native iterator handle in the + /// field. In that case, this instance will + /// finalize the native iterator handle upon being disposed or + /// finalized. + /// + private bool ownHandle; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Protected Constructors + /// + /// Constructs a new instance of this class using the specified native + /// iterator handle. + /// + /// + /// The native iterator handle to use. + /// + /// + /// Non-zero if this instance is to take ownership of the native + /// iterator handle specified by . + /// + protected SQLiteChangeSetIterator( + IntPtr iterator, + bool ownHandle + ) + { + this.iterator = iterator; + this.ownHandle = ownHandle; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// Throws an exception if the native iterator handle is invalid. + /// + internal void CheckHandle() + { + if (iterator == IntPtr.Zero) + throw new InvalidOperationException("iterator is not open"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Used to query the native iterator handle. This method is only used + /// by the class. + /// + /// + /// The native iterator handle -OR- if it + /// is not available. + /// + internal IntPtr GetIntPtr() + { + return iterator; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Methods + /// + /// Attempts to advance the native iterator handle to its next item. + /// + /// + /// Non-zero if the native iterator handle was advanced and contains + /// more data; otherwise, zero. If the underlying native API returns + /// an unexpected value then an exception will be thrown. + /// + public bool Next() + { + CheckDisposed(); + CheckHandle(); + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_next( + iterator); + + switch (rc) + { + case SQLiteErrorCode.Ok: + { + throw new SQLiteException(SQLiteErrorCode.Ok, + "sqlite3changeset_next: unexpected result Ok"); + } + case SQLiteErrorCode.Row: + { + return true; + } + case SQLiteErrorCode.Done: + { + return false; + } + default: + { + throw new SQLiteException(rc, "sqlite3changeset_next"); + } + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Static "Factory" Methods + /// + /// Attempts to create an instance of this class that is associated + /// with the specified native iterator handle. Ownership of the + /// native iterator handle is NOT transferred to the new instance of + /// this class. + /// + /// + /// The native iterator handle to use. + /// + /// + /// The new instance of this class. No return value is reserved to + /// indicate an error; however, if the native iterator handle is not + /// valid, any subsequent attempt to make use of it via the returned + /// instance of this class may throw exceptions. + /// + public static SQLiteChangeSetIterator Attach( + IntPtr iterator + ) + { + return new SQLiteChangeSetIterator(iterator, false); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes of this object instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteChangeSetIterator).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + protected virtual void Dispose(bool disposing) + { + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + if (iterator != IntPtr.Zero) + { + if (ownHandle) + { + UnsafeNativeMethods.sqlite3changeset_finalize( + iterator); + } + + iterator = IntPtr.Zero; + } + } + } + finally + { + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Destructor + /// + /// Finalizes this object instance. + /// + ~SQLiteChangeSetIterator() + { + Dispose(false); + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteMemoryChangeSetIterator Class + /// + /// This class manages the native change set iterator for a set of changes + /// contained entirely in memory. + /// + internal sealed class SQLiteMemoryChangeSetIterator : + SQLiteChangeSetIterator + { + #region Private Data + /// + /// The native memory buffer allocated to contain the set of changes + /// associated with this instance. This will always be freed when this + /// instance is disposed or finalized. + /// + private IntPtr pData; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs an instance of this class using the specified native + /// memory buffer and native iterator handle. + /// + /// + /// The native memory buffer to use. + /// + /// + /// The native iterator handle to use. + /// + /// + /// Non-zero if this instance is to take ownership of the native + /// iterator handle specified by . + /// + private SQLiteMemoryChangeSetIterator( + IntPtr pData, + IntPtr iterator, + bool ownHandle + ) + : base(iterator, ownHandle) + { + this.pData = pData; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Static "Factory" Methods + /// + /// Attempts to create an instance of this class using the specified + /// raw byte data. + /// + /// + /// The raw byte data containing the set of changes for this native + /// iterator. + /// + /// + /// The new instance of this class -OR- null if it cannot be created. + /// + public static SQLiteMemoryChangeSetIterator Create( + byte[] rawData + ) + { + SQLiteSessionHelpers.CheckRawData(rawData); + + SQLiteMemoryChangeSetIterator result = null; + IntPtr pData = IntPtr.Zero; + IntPtr iterator = IntPtr.Zero; + + try + { + int nData = 0; + + pData = SQLiteBytes.ToIntPtr(rawData, ref nData); + + if (pData == IntPtr.Zero) + throw new SQLiteException(SQLiteErrorCode.NoMem, null); + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_start( + ref iterator, nData, pData); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changeset_start"); + + result = new SQLiteMemoryChangeSetIterator( + pData, iterator, true); + } + finally + { + if (result == null) + { + if (iterator != IntPtr.Zero) + { + UnsafeNativeMethods.sqlite3changeset_finalize( + iterator); + + iterator = IntPtr.Zero; + } + + if (pData != IntPtr.Zero) + { + SQLiteMemory.Free(pData); + pData = IntPtr.Zero; + } + } + } + + return result; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to create an instance of this class using the specified + /// raw byte data. + /// + /// + /// The raw byte data containing the set of changes for this native + /// iterator. + /// + /// + /// The flags used to create the change set iterator. + /// + /// + /// The new instance of this class -OR- null if it cannot be created. + /// + public static SQLiteMemoryChangeSetIterator Create( + byte[] rawData, + SQLiteChangeSetStartFlags flags + ) + { + SQLiteSessionHelpers.CheckRawData(rawData); + + SQLiteMemoryChangeSetIterator result = null; + IntPtr pData = IntPtr.Zero; + IntPtr iterator = IntPtr.Zero; + + try + { + int nData = 0; + + pData = SQLiteBytes.ToIntPtr(rawData, ref nData); + + if (pData == IntPtr.Zero) + throw new SQLiteException(SQLiteErrorCode.NoMem, null); + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_start_v2( + ref iterator, nData, pData, flags); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changeset_start_v2"); + + result = new SQLiteMemoryChangeSetIterator( + pData, iterator, true); + } + finally + { + if (result == null) + { + if (iterator != IntPtr.Zero) + { + UnsafeNativeMethods.sqlite3changeset_finalize( + iterator); + + iterator = IntPtr.Zero; + } + + if (pData != IntPtr.Zero) + { + SQLiteMemory.Free(pData); + pData = IntPtr.Zero; + } + } + } + + return result; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteMemoryChangeSetIterator).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + protected override void Dispose(bool disposing) + { + // + // NOTE: Must dispose of the base class first (leaky abstraction) + // because it contains the iterator handle, which must be + // closed *prior* to freeing the underlying memory. + // + base.Dispose(disposing); + + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + if (pData != IntPtr.Zero) + { + SQLiteMemory.Free(pData); + pData = IntPtr.Zero; + } + } + } + finally + { + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteStreamChangeSetIterator Class + /// + /// This class manages the native change set iterator for a set of changes + /// backed by a instance. + /// + internal sealed class SQLiteStreamChangeSetIterator : + SQLiteChangeSetIterator + { + #region Private Data + /// + /// The instance that is managing + /// the underlying used as the backing store for + /// the set of changes associated with this native change set iterator. + /// + private SQLiteStreamAdapter streamAdapter; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs an instance of this class using the specified native + /// iterator handle and . + /// + /// + /// The instance to use. + /// + /// + /// The native iterator handle to use. + /// + /// + /// Non-zero if this instance is to take ownership of the native + /// iterator handle specified by . + /// + private SQLiteStreamChangeSetIterator( + SQLiteStreamAdapter streamAdapter, + IntPtr iterator, + bool ownHandle + ) + : base(iterator, ownHandle) + { + this.streamAdapter = streamAdapter; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Static "Factory" Methods + /// + /// Attempts to create an instance of this class using the specified + /// . + /// + /// + /// The where the raw byte data for the set of + /// changes may be read. + /// + /// + /// The flags associated with the parent connection. + /// + /// + /// The new instance of this class -OR- null if it cannot be created. + /// + public static SQLiteStreamChangeSetIterator Create( + Stream stream, + SQLiteConnectionFlags connectionFlags + ) + { + if (stream == null) + throw new ArgumentNullException("stream"); + + SQLiteStreamAdapter streamAdapter = null; + SQLiteStreamChangeSetIterator result = null; + IntPtr iterator = IntPtr.Zero; + + try + { + streamAdapter = new SQLiteStreamAdapter(stream, connectionFlags); + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_start_strm( + ref iterator, streamAdapter.GetInputDelegate(), IntPtr.Zero); + + if (rc != SQLiteErrorCode.Ok) + { + throw new SQLiteException( + rc, "sqlite3changeset_start_strm"); + } + + result = new SQLiteStreamChangeSetIterator( + streamAdapter, iterator, true); + } + finally + { + if (result == null) + { + if (iterator != IntPtr.Zero) + { + UnsafeNativeMethods.sqlite3changeset_finalize( + iterator); + + iterator = IntPtr.Zero; + } + + if (streamAdapter != null) + { + streamAdapter.Dispose(); + streamAdapter = null; + } + } + } + + return result; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to create an instance of this class using the specified + /// . + /// + /// + /// The where the raw byte data for the set of + /// changes may be read. + /// + /// + /// The flags associated with the parent connection. + /// + /// + /// The flags used to create the change set iterator. + /// + /// + /// The new instance of this class -OR- null if it cannot be created. + /// + public static SQLiteStreamChangeSetIterator Create( + Stream stream, + SQLiteConnectionFlags connectionFlags, + SQLiteChangeSetStartFlags startFlags + ) + { + if (stream == null) + throw new ArgumentNullException("stream"); + + SQLiteStreamAdapter streamAdapter = null; + SQLiteStreamChangeSetIterator result = null; + IntPtr iterator = IntPtr.Zero; + + try + { + streamAdapter = new SQLiteStreamAdapter(stream, connectionFlags); + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_start_v2_strm( + ref iterator, streamAdapter.GetInputDelegate(), IntPtr.Zero, + startFlags); + + if (rc != SQLiteErrorCode.Ok) + { + throw new SQLiteException( + rc, "sqlite3changeset_start_v2_strm"); + } + + result = new SQLiteStreamChangeSetIterator( + streamAdapter, iterator, true); + } + finally + { + if (result == null) + { + if (iterator != IntPtr.Zero) + { + UnsafeNativeMethods.sqlite3changeset_finalize( + iterator); + + iterator = IntPtr.Zero; + } + + if (streamAdapter != null) + { + streamAdapter.Dispose(); + streamAdapter = null; + } + } + } + + return result; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteStreamChangeSetIterator).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + //if (disposing) + //{ + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + //} + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteStreamAdapter Class + /// + /// This class is used to act as a bridge between a + /// instance and the delegates used with the native streaming API. + /// + internal sealed class SQLiteStreamAdapter : IDisposable + { + #region Private Data + /// + /// The managed stream instance used to in order to service the native + /// delegates for both input and output. + /// + private Stream stream; + + /// + /// The flags associated with the connection. + /// + private SQLiteConnectionFlags flags; + + /////////////////////////////////////////////////////////////////////// + + /// + /// The delegate used to provide input to the native streaming API. + /// It will be null -OR- point to the method. + /// + private UnsafeNativeMethods.xSessionInput xInput; + + /// + /// The delegate used to provide output to the native streaming API. + /// It will be null -OR- point to the method. + /// + private UnsafeNativeMethods.xSessionOutput xOutput; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs a new instance of this class using the specified managed + /// stream and connection flags. + /// + /// + /// The managed stream instance to be used in order to service the + /// native delegates for both input and output. + /// + /// + /// The flags associated with the parent connection. + /// + public SQLiteStreamAdapter( + Stream stream, + SQLiteConnectionFlags flags + ) + { + this.stream = stream; + this.flags = flags; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// Queries and returns the flags associated with the connection for + /// this instance. + /// + /// + /// The value. There is no return + /// value reserved to indicate an error. + /// + private SQLiteConnectionFlags GetFlags() + { + return flags; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Methods + /// + /// Returns a delegate that wraps the method, + /// creating it first if necessary. + /// + /// + /// A delegate that refers to the method. + /// + public UnsafeNativeMethods.xSessionInput GetInputDelegate() + { + CheckDisposed(); + + if (xInput == null) + xInput = new UnsafeNativeMethods.xSessionInput(Input); + + return xInput; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Returns a delegate that wraps the method, + /// creating it first if necessary. + /// + /// + /// A delegate that refers to the method. + /// + public UnsafeNativeMethods.xSessionOutput GetOutputDelegate() + { + CheckDisposed(); + + if (xOutput == null) + xOutput = new UnsafeNativeMethods.xSessionOutput(Output); + + return xOutput; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Native Callback Methods + /// + /// This method attempts to read bytes from + /// the managed stream, writing them to the + /// buffer. + /// + /// + /// Optional extra context information. Currently, this will always + /// have a value of . + /// + /// + /// A preallocated native buffer to receive the requested input bytes. + /// It must be at least bytes in size. + /// + /// + /// Upon entry, the number of bytes to read. Upon exit, the number of + /// bytes actually read. This value may be zero upon exit. + /// + /// + /// The value upon success -OR- an + /// appropriate error code upon failure. + /// + private SQLiteErrorCode Input( + IntPtr context, + IntPtr pData, + ref int nData + ) + { + try + { + Stream localStream = stream; + + if (localStream == null) + return SQLiteErrorCode.Misuse; + + if (nData > 0) + { + byte[] bytes = new byte[nData]; + int nRead = localStream.Read(bytes, 0, nData); + + if ((nRead > 0) && (pData != IntPtr.Zero)) + Marshal.Copy(bytes, 0, pData, nRead); + + nData = nRead; + } + + return SQLiteErrorCode.Ok; + } + catch (Exception e) + { + try + { + if (HelperMethods.LogCallbackExceptions(GetFlags())) + { + SQLiteLog.LogMessage( + SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "xSessionInput", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } + + return SQLiteErrorCode.IoErr_Read; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method attempts to write bytes to + /// the managed stream, reading them from the + /// buffer. + /// + /// + /// Optional extra context information. Currently, this will always + /// have a value of . + /// + /// + /// A preallocated native buffer containing the requested output + /// bytes. It must be at least bytes in + /// size. + /// + /// + /// The number of bytes to write. + /// + /// + /// The value upon success -OR- an + /// appropriate error code upon failure. + /// + private SQLiteErrorCode Output( + IntPtr context, + IntPtr pData, + int nData + ) + { + try + { + Stream localStream = stream; + + if (localStream == null) + return SQLiteErrorCode.Misuse; + + if (nData > 0) + { + byte[] bytes = new byte[nData]; + + if (pData != IntPtr.Zero) + Marshal.Copy(pData, bytes, 0, nData); + + localStream.Write(bytes, 0, nData); + } + + localStream.Flush(); + + return SQLiteErrorCode.Ok; + } + catch (Exception e) + { + try + { + if (HelperMethods.LogCallbackExceptions(GetFlags())) + { + SQLiteLog.LogMessage( + SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "xSessionOutput", e)); /* throw */ + } + } + catch + { + // do nothing. + } + } + + return SQLiteErrorCode.IoErr_Write; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes of this object instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteStreamAdapter).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + private /* protected virtual */ void Dispose(bool disposing) + { + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (xInput != null) + xInput = null; + + if (xOutput != null) + xOutput = null; + + if (stream != null) + stream = null; /* NOT OWNED */ + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Destructor + /// + /// Finalizes this object instance. + /// + ~SQLiteStreamAdapter() + { + Dispose(false); + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteSessionStreamManager Class + /// + /// This class manages a collection of + /// instances. When used, it takes responsibility for creating, returning, + /// and disposing of its instances. + /// + internal sealed class SQLiteSessionStreamManager : IDisposable + { + #region Private Data + /// + /// The managed collection of + /// instances, keyed by their associated + /// instance. + /// + private Dictionary streamAdapters; + + /// + /// The flags associated with the connection. + /// + private SQLiteConnectionFlags flags; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs a new instance of this class using the specified + /// connection flags. + /// + /// + /// The flags associated with the parent connection. + /// + public SQLiteSessionStreamManager( + SQLiteConnectionFlags flags + ) + { + this.flags = flags; + + InitializeStreamAdapters(); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// Makes sure the collection of + /// is created. + /// + private void InitializeStreamAdapters() + { + if (streamAdapters != null) + return; + + streamAdapters = new Dictionary(); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Makes sure the collection of + /// is disposed. + /// + private void DisposeStreamAdapters() + { + if (streamAdapters == null) + return; + + foreach (KeyValuePair pair + in streamAdapters) + { + SQLiteStreamAdapter streamAdapter = pair.Value; + + if (streamAdapter == null) + continue; + + streamAdapter.Dispose(); + } + + streamAdapters.Clear(); + streamAdapters = null; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Methods + /// + /// Attempts to return a instance + /// suitable for the specified . + /// + /// + /// The instance. If this value is null, a null + /// value will be returned. + /// + /// + /// A instance. Typically, these + /// are always freshly created; however, this method is designed to + /// return the existing instance + /// associated with the specified stream, should one exist. + /// + public SQLiteStreamAdapter GetAdapter( + Stream stream + ) + { + CheckDisposed(); + + if (stream == null) + return null; + + SQLiteStreamAdapter streamAdapter; + + if (streamAdapters.TryGetValue(stream, out streamAdapter)) + return streamAdapter; + + streamAdapter = new SQLiteStreamAdapter(stream, flags); + streamAdapters.Add(stream, streamAdapter); + + return streamAdapter; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes of this object instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteSessionStreamManager).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + private /* protected virtual */ void Dispose(bool disposing) + { + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + DisposeStreamAdapters(); + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Destructor + /// + /// Finalizes this object instance. + /// + ~SQLiteSessionStreamManager() + { + Dispose(false); + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteChangeGroup Class + /// + /// This class represents a group of change sets (or patch sets). + /// + internal sealed class SQLiteChangeGroup : ISQLiteChangeGroup + { + #region Private Data + /// + /// The instance associated + /// with this change group. + /// + private SQLiteSessionStreamManager streamManager; + + /// + /// The flags associated with the connection. + /// + private SQLiteConnectionFlags flags; + + /////////////////////////////////////////////////////////////////////// + + /// + /// The native handle for this change group. This will be deleted when + /// this instance is disposed or finalized. + /// + private IntPtr changeGroup; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs a new instance of this class using the specified + /// connection flags. + /// + /// + /// The flags associated with the parent connection. + /// + public SQLiteChangeGroup( + SQLiteConnectionFlags flags + ) + { + this.flags = flags; + + InitializeHandle(); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// Throws an exception if the native change group handle is invalid. + /// + private void CheckHandle() + { + if (changeGroup == IntPtr.Zero) + throw new InvalidOperationException("change group not open"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Makes sure the native change group handle is valid, creating it if + /// necessary. + /// + private void InitializeHandle() + { + if (changeGroup != IntPtr.Zero) + return; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changegroup_new( + ref changeGroup); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changegroup_new"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Makes sure the instance + /// is available, creating it if necessary. + /// + private void InitializeStreamManager() + { + if (streamManager != null) + return; + + streamManager = new SQLiteSessionStreamManager(flags); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to return a instance + /// suitable for the specified . + /// + /// + /// The instance. If this value is null, a null + /// value will be returned. + /// + /// + /// A instance. Typically, these + /// are always freshly created; however, this method is designed to + /// return the existing instance + /// associated with the specified stream, should one exist. + /// + private SQLiteStreamAdapter GetStreamAdapter( + Stream stream + ) + { + InitializeStreamManager(); + + return streamManager.GetAdapter(stream); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteChangeGroup Members + /// + /// Attempts to add a change set (or patch set) to this change group + /// instance. The underlying data must be contained entirely within + /// the byte array. + /// + /// + /// The raw byte data for the specified change set (or patch set). + /// + public void AddChangeSet( + byte[] rawData + ) + { + CheckDisposed(); + CheckHandle(); + + SQLiteSessionHelpers.CheckRawData(rawData); + + IntPtr pData = IntPtr.Zero; + + try + { + int nData = 0; + + pData = SQLiteBytes.ToIntPtr(rawData, ref nData); + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changegroup_add( + changeGroup, nData, pData); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changegroup_add"); + } + finally + { + if (pData != IntPtr.Zero) + { + SQLiteMemory.Free(pData); + pData = IntPtr.Zero; + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to add a change set (or patch set) to this change group + /// instance. The underlying data will be read from the specified + /// . + /// + /// + /// The instance containing the raw change set + /// (or patch set) data to read. + /// + public void AddChangeSet( + Stream stream + ) + { + CheckDisposed(); + CheckHandle(); + + if (stream == null) + throw new ArgumentNullException("stream"); + + SQLiteStreamAdapter streamAdapter = GetStreamAdapter(stream); + + if (streamAdapter == null) + { + throw new SQLiteException( + "could not get or create adapter for input stream"); + } + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changegroup_add_strm( + changeGroup, streamAdapter.GetInputDelegate(), IntPtr.Zero); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changegroup_add_strm"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to create and return, via , the + /// combined set of changes represented by this change group instance. + /// + /// + /// Upon success, this will contain the raw byte data for all the + /// changes in this change group instance. + /// + public void CreateChangeSet( + ref byte[] rawData + ) + { + CheckDisposed(); + CheckHandle(); + + IntPtr pData = IntPtr.Zero; + + try + { + int nData = 0; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changegroup_output( + changeGroup, ref nData, ref pData); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changegroup_output"); + + rawData = SQLiteBytes.FromIntPtr(pData, nData); + } + finally + { + if (pData != IntPtr.Zero) + { + SQLiteMemory.FreeUntracked(pData); + pData = IntPtr.Zero; + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to create and write, via , the + /// combined set of changes represented by this change group instance. + /// + /// + /// Upon success, the raw byte data for all the changes in this change + /// group instance will be written to this . + /// + public void CreateChangeSet( + Stream stream + ) + { + CheckDisposed(); + CheckHandle(); + + if (stream == null) + throw new ArgumentNullException("stream"); + + SQLiteStreamAdapter streamAdapter = GetStreamAdapter(stream); + + if (streamAdapter == null) + { + throw new SQLiteException( + "could not get or create adapter for output stream"); + } + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changegroup_output_strm( + changeGroup, streamAdapter.GetOutputDelegate(), IntPtr.Zero); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changegroup_output_strm"); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes of this object instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteChangeGroup).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + private /* protected virtual */ void Dispose(bool disposing) + { + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (streamManager != null) + { + streamManager.Dispose(); + streamManager = null; + } + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + if (changeGroup != IntPtr.Zero) + { + UnsafeNativeMethods.sqlite3changegroup_delete( + changeGroup); + + changeGroup = IntPtr.Zero; + } + } + } + finally + { + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Destructor + /// + /// Finalizes this object instance. + /// + ~SQLiteChangeGroup() + { + Dispose(false); + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteSession Class + /// + /// This class represents the change tracking session associated with a + /// database. + /// + internal sealed class SQLiteSession : SQLiteConnectionLock, ISQLiteSession + { + #region Private Data + /// + /// The instance associated + /// with this session. + /// + private SQLiteSessionStreamManager streamManager; + + /// + /// The name of the database (e.g. "main") for this session. + /// + private string databaseName; + + /////////////////////////////////////////////////////////////////////// + + /// + /// The native handle for this session. This will be deleted when + /// this instance is disposed or finalized. + /// + private IntPtr session; + + /////////////////////////////////////////////////////////////////////// + + /// + /// The delegate used to provide table filtering to the native API. + /// It will be null -OR- point to the method. + /// + private UnsafeNativeMethods.xSessionFilter xFilter; + + /// + /// The managed callback used to filter tables for this session. Set + /// via the method. + /// + private SessionTableFilterCallback tableFilterCallback; + + /// + /// The optional application-defined context data that was passed to + /// the method. This value may be null. + /// + private object tableFilterClientData; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs a new instance of this class using the specified wrapped + /// native connection handle and associated flags. + /// + /// + /// The wrapped native connection handle to be associated with this + /// session. + /// + /// + /// The flags associated with the connection represented by the + /// value. + /// + /// + /// The name of the database (e.g. "main") for this session. + /// + public SQLiteSession( + SQLiteConnectionHandle handle, + SQLiteConnectionFlags flags, + string databaseName + ) + : base(handle, flags, true) + { + this.databaseName = databaseName; + + InitializeHandle(); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// Throws an exception if the native session handle is invalid. + /// + private void CheckHandle() + { + if (session == IntPtr.Zero) + throw new InvalidOperationException("session is not open"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Makes sure the native session handle is valid, creating it if + /// necessary. + /// + private void InitializeHandle() + { + if (session != IntPtr.Zero) + return; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_create( + GetIntPtr(), SQLiteString.GetUtf8BytesFromString(databaseName), + ref session); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3session_create"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method sets up the internal table filtering associated state + /// of this instance. + /// + /// + /// The table filter callback -OR- null to clear any existing table + /// filter callback. + /// + /// + /// The optional application-defined context data. This value may be + /// null. + /// + /// + /// The native + /// delegate -OR- null to clear any existing table filter. + /// + private UnsafeNativeMethods.xSessionFilter ApplyTableFilter( + SessionTableFilterCallback callback, /* in: NULL OK */ + object clientData /* in: NULL OK */ + ) + { + tableFilterCallback = callback; + tableFilterClientData = clientData; + + if (callback == null) + { + if (xFilter != null) + xFilter = null; + + return null; + } + + if (xFilter == null) + xFilter = new UnsafeNativeMethods.xSessionFilter(Filter); + + return xFilter; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Makes sure the instance + /// is available, creating it if necessary. + /// + private void InitializeStreamManager() + { + if (streamManager != null) + return; + + streamManager = new SQLiteSessionStreamManager(GetFlags()); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to return a instance + /// suitable for the specified . + /// + /// + /// The instance. If this value is null, a null + /// value will be returned. + /// + /// + /// A instance. Typically, these + /// are always freshly created; however, this method is designed to + /// return the existing instance + /// associated with the specified stream, should one exist. + /// + private SQLiteStreamAdapter GetStreamAdapter( + Stream stream + ) + { + InitializeStreamManager(); + + return streamManager.GetAdapter(stream); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Native Callback Methods + /// + /// This method is called when determining if a table needs to be + /// included in the tracked changes for the associated database. + /// + /// + /// Optional extra context information. Currently, this will always + /// have a value of . + /// + /// + /// The native pointer to the name of the table. + /// + /// + /// Non-zero if changes to the specified table should be considered; + /// otherwise, zero. + /// + private int Filter( + IntPtr context, /* NOT USED */ + IntPtr pTblName + ) + { + try + { + return tableFilterCallback(tableFilterClientData, + SQLiteString.StringFromUtf8IntPtr(pTblName)) ? 1 : 0; + } + catch (Exception e) + { + try + { + if (HelperMethods.LogCallbackExceptions(GetFlags())) + { + SQLiteLog.LogMessage( /* throw */ + SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "xSessionFilter", e)); + } + } + catch + { + // do nothing. + } + } + + return 0; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteSession Members + /// + /// Determines if this session is currently tracking changes to its + /// associated database. + /// + /// + /// Non-zero if changes to the associated database are being trakced; + /// otherwise, zero. + /// + public bool IsEnabled() + { + CheckDisposed(); + CheckHandle(); + + return UnsafeNativeMethods.sqlite3session_enable(session, -1) != 0; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Enables tracking of changes to the associated database. + /// + public void SetToEnabled() + { + CheckDisposed(); + CheckHandle(); + + UnsafeNativeMethods.sqlite3session_enable(session, 1); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disables tracking of changes to the associated database. + /// + public void SetToDisabled() + { + CheckDisposed(); + CheckHandle(); + + UnsafeNativeMethods.sqlite3session_enable(session, 0); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Determines if this session is currently set to mark changes as + /// indirect (i.e. as though they were made via a trigger or foreign + /// key action). + /// + /// + /// Non-zero if changes to the associated database are being marked as + /// indirect; otherwise, zero. + /// + public bool IsIndirect() + { + CheckDisposed(); + CheckHandle(); + + return UnsafeNativeMethods.sqlite3session_indirect(session, -1) != 0; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the indirect flag for this session. Subsequent changes will + /// be marked as indirect until this flag is changed again. + /// + public void SetToIndirect() + { + CheckDisposed(); + CheckHandle(); + + UnsafeNativeMethods.sqlite3session_indirect(session, 1); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Clears the indirect flag for this session. Subsequent changes will + /// be marked as direct until this flag is changed again. + /// + public void SetToDirect() + { + CheckDisposed(); + CheckHandle(); + + UnsafeNativeMethods.sqlite3session_indirect(session, 0); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Determines if there are any tracked changes currently within the + /// data for this session. + /// + /// + /// Non-zero if there are no changes within the data for this session; + /// otherwise, zero. + /// + public bool IsEmpty() + { + CheckDisposed(); + CheckHandle(); + + return UnsafeNativeMethods.sqlite3session_isempty(session) != 0; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Upon success, causes changes to the specified table(s) to start + /// being tracked. Any tables impacted by calls to this method will + /// not cause the callback + /// to be invoked. + /// + /// + /// The name of the table to be tracked -OR- null to track all + /// applicable tables within this database. + /// + public void AttachTable( + string name /* in: NULL OK */ + ) + { + CheckDisposed(); + CheckHandle(); + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_attach( + session, SQLiteString.GetUtf8BytesFromString(name)); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3session_attach"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method is used to set the table filter for this instance. + /// + /// + /// The table filter callback -OR- null to clear any existing table + /// filter callback. + /// + /// + /// The optional application-defined context data. This value may be + /// null. + /// + public void SetTableFilter( + SessionTableFilterCallback callback, /* in: NULL OK */ + object clientData /* in: NULL OK */ + ) + { + CheckDisposed(); + CheckHandle(); + + UnsafeNativeMethods.sqlite3session_table_filter( + session, ApplyTableFilter(callback, clientData), IntPtr.Zero); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to create and return, via , the + /// set of changes represented by this session instance. + /// + /// + /// Upon success, this will contain the raw byte data for all the + /// changes in this session instance. + /// + public void CreateChangeSet( + ref byte[] rawData + ) + { + CheckDisposed(); + CheckHandle(); + + IntPtr pData = IntPtr.Zero; + + try + { + int nData = 0; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_changeset( + session, ref nData, ref pData); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3session_changeset"); + + rawData = SQLiteBytes.FromIntPtr(pData, nData); + } + finally + { + if (pData != IntPtr.Zero) + { + SQLiteMemory.FreeUntracked(pData); + pData = IntPtr.Zero; + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to create and write, via , the + /// set of changes represented by this session instance. + /// + /// + /// Upon success, the raw byte data for all the changes in this session + /// instance will be written to this . + /// + public void CreateChangeSet( + Stream stream + ) + { + CheckDisposed(); + CheckHandle(); + + if (stream == null) + throw new ArgumentNullException("stream"); + + SQLiteStreamAdapter streamAdapter = GetStreamAdapter(stream); + + if (streamAdapter == null) + { + throw new SQLiteException( + "could not get or create adapter for output stream"); + } + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_changeset_strm( + session, streamAdapter.GetOutputDelegate(), IntPtr.Zero); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3session_changeset_strm"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to create and return, via , the + /// set of changes represented by this session instance as a patch set. + /// + /// + /// Upon success, this will contain the raw byte data for all the + /// changes in this session instance. + /// + public void CreatePatchSet( + ref byte[] rawData + ) + { + CheckDisposed(); + CheckHandle(); + + IntPtr pData = IntPtr.Zero; + + try + { + int nData = 0; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_patchset( + session, ref nData, ref pData); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3session_patchset"); + + rawData = SQLiteBytes.FromIntPtr(pData, nData); + } + finally + { + if (pData != IntPtr.Zero) + { + SQLiteMemory.FreeUntracked(pData); + pData = IntPtr.Zero; + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to create and write, via , the + /// set of changes represented by this session instance as a patch set. + /// + /// + /// Upon success, the raw byte data for all the changes in this session + /// instance will be written to this . + /// + public void CreatePatchSet( + Stream stream + ) + { + CheckDisposed(); + CheckHandle(); + + if (stream == null) + throw new ArgumentNullException("stream"); + + SQLiteStreamAdapter streamAdapter = GetStreamAdapter(stream); + + if (streamAdapter == null) + { + throw new SQLiteException( + "could not get or create adapter for output stream"); + } + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_patchset_strm( + session, streamAdapter.GetOutputDelegate(), IntPtr.Zero); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3session_patchset_strm"); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method loads the differences between two tables [with the same + /// name, set of columns, and primary key definition] into this session + /// instance. + /// + /// + /// The name of the database containing the table with the original + /// data (i.e. it will need updating in order to be identical to the + /// one within the database associated with this session instance). + /// + /// + /// The name of the table. + /// + public void LoadDifferencesFromTable( + string fromDatabaseName, + string tableName + ) + { + CheckDisposed(); + CheckHandle(); + + if (fromDatabaseName == null) + throw new ArgumentNullException("fromDatabaseName"); + + if (tableName == null) + throw new ArgumentNullException("tableName"); + + IntPtr pError = IntPtr.Zero; + + try + { + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_diff( + session, SQLiteString.GetUtf8BytesFromString(fromDatabaseName), + SQLiteString.GetUtf8BytesFromString(tableName), ref pError); + + if (rc != SQLiteErrorCode.Ok) + { + string error = null; + + if (pError != IntPtr.Zero) + { + error = SQLiteString.StringFromUtf8IntPtr(pError); + + if (!String.IsNullOrEmpty(error)) + { + error = HelperMethods.StringFormat( + CultureInfo.CurrentCulture, ": {0}", error); + } + } + + throw new SQLiteException(rc, HelperMethods.StringFormat( + CultureInfo.CurrentCulture, "{0}{1}", + "sqlite3session_diff", error)); + } + } + finally + { + if (pError != IntPtr.Zero) + { + SQLiteMemory.FreeUntracked(pError); + pError = IntPtr.Zero; + } + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteSession).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (xFilter != null) + xFilter = null; + + if (streamManager != null) + { + streamManager.Dispose(); + streamManager = null; + } + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + if (session != IntPtr.Zero) + { + UnsafeNativeMethods.sqlite3session_delete(session); + session = IntPtr.Zero; + } + + Unlock(); + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteChangeSetBase Class + /// + /// This class represents the abstract concept of a set of changes. It + /// acts as the base class for the + /// and classes. It derives from + /// the class, which is used to hold + /// the underlying native connection handle open until the instances of + /// this class are disposed or finalized. It also provides the ability + /// to construct wrapped native delegates of the + /// and + /// types. + /// + internal class SQLiteChangeSetBase : SQLiteConnectionLock + { + #region Private Constructors + /// + /// Constructs an instance of this class using the specified wrapped + /// native connection handle. + /// + /// + /// The wrapped native connection handle to be associated with this + /// change set. + /// + /// + /// The flags associated with the connection represented by the + /// value. + /// + internal SQLiteChangeSetBase( + SQLiteConnectionHandle handle, + SQLiteConnectionFlags flags + ) + : base(handle, flags, true) + { + // do nothing. + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// Creates and returns a concrete implementation of the + /// interface. + /// + /// + /// The native iterator handle to use. + /// + /// + /// An instance of the + /// interface, which can be used to fetch metadata associated with + /// the current item in this set of changes. + /// + private ISQLiteChangeSetMetadataItem CreateMetadataItem( + IntPtr iterator + ) + { + return new SQLiteChangeSetMetadataItem( + SQLiteChangeSetIterator.Attach(iterator)); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Protected Methods + /// + /// Attempts to create a + /// native delegate + /// that invokes the specified + /// delegate. + /// + /// + /// The to invoke when the + /// native delegate + /// is called. If this value is null then null is returned. + /// + /// + /// The optional application-defined context data. This value may be + /// null. + /// + /// + /// The created + /// native delegate -OR- null if it cannot be created. + /// + protected UnsafeNativeMethods.xSessionFilter GetDelegate( + SessionTableFilterCallback tableFilterCallback, + object clientData + ) + { + if (tableFilterCallback == null) + return null; + + UnsafeNativeMethods.xSessionFilter xFilter; + + xFilter = new UnsafeNativeMethods.xSessionFilter( + delegate(IntPtr context, IntPtr pTblName) + { + try + { + string name = SQLiteString.StringFromUtf8IntPtr( + pTblName); + + return tableFilterCallback(clientData, name) ? 1 : 0; + } + catch (Exception e) + { + try + { + if (HelperMethods.LogCallbackExceptions(GetFlags())) + { + SQLiteLog.LogMessage( /* throw */ + SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "xSessionFilter", e)); + } + } + catch + { + // do nothing. + } + } + + return 0; + }); + + return xFilter; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to create a + /// native delegate + /// that invokes the specified + /// delegate. + /// + /// + /// The to invoke when the + /// native delegate + /// is called. If this value is null then null is returned. + /// + /// + /// The optional application-defined context data. This value may be + /// null. + /// + /// + /// The created + /// native delegate -OR- null if it cannot be created. + /// + protected UnsafeNativeMethods.xSessionConflict GetDelegate( + SessionConflictCallback conflictCallback, + object clientData + ) + { + if (conflictCallback == null) + return null; + + UnsafeNativeMethods.xSessionConflict xConflict; + + xConflict = new UnsafeNativeMethods.xSessionConflict( + delegate(IntPtr context, + SQLiteChangeSetConflictType type, + IntPtr iterator) + { + try + { + ISQLiteChangeSetMetadataItem item = CreateMetadataItem( + iterator); + + if (item == null) + { + throw new SQLiteException( + "could not create metadata item"); + } + + return conflictCallback(clientData, type, item); + } + catch (Exception e) + { + try + { + if (HelperMethods.LogCallbackExceptions(GetFlags())) + { + SQLiteLog.LogMessage( /* throw */ + SQLiteBase.COR_E_EXCEPTION, + HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + UnsafeNativeMethods.ExceptionMessageFormat, + "xSessionConflict", e)); + } + } + catch + { + // do nothing. + } + } + + return SQLiteChangeSetConflictResult.Abort; + }); + + return xConflict; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteChangeSetBase).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + Unlock(); + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteMemoryChangeSet Class + /// + /// This class represents a set of changes contained entirely in memory. + /// + internal sealed class SQLiteMemoryChangeSet : + SQLiteChangeSetBase, ISQLiteChangeSet + { + #region Private Data + /// + /// The raw byte data for this set of changes. Since this data must + /// be marshalled to a native memory buffer before being used, there + /// must be enough memory available to store at least two times the + /// amount of data contained within it. + /// + private byte[] rawData; + + /// + /// The flags used to create the change set iterator. + /// + private SQLiteChangeSetStartFlags startFlags; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs an instance of this class using the specified raw byte + /// data and wrapped native connection handle. + /// + /// + /// The raw byte data for the specified change set (or patch set). + /// + /// + /// The wrapped native connection handle to be associated with this + /// set of changes. + /// + /// + /// The flags associated with the connection represented by the + /// value. + /// + internal SQLiteMemoryChangeSet( + byte[] rawData, + SQLiteConnectionHandle handle, + SQLiteConnectionFlags connectionFlags + ) + : base(handle, connectionFlags) + { + this.rawData = rawData; + this.startFlags = SQLiteChangeSetStartFlags.None; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Constructs an instance of this class using the specified raw byte + /// data and wrapped native connection handle. + /// + /// + /// The raw byte data for the specified change set (or patch set). + /// + /// + /// The wrapped native connection handle to be associated with this + /// set of changes. + /// + /// + /// The flags associated with the connection represented by the + /// value. + /// + /// + /// The flags used to create the change set iterator. + /// + internal SQLiteMemoryChangeSet( + byte[] rawData, + SQLiteConnectionHandle handle, + SQLiteConnectionFlags connectionFlags, + SQLiteChangeSetStartFlags startFlags + ) + : base(handle, connectionFlags) + { + this.rawData = rawData; + this.startFlags = startFlags; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteChangeSet Members + /// + /// This method "inverts" the set of changes within this instance. + /// Applying an inverted set of changes to a database reverses the + /// effects of applying the uninverted changes. Specifically: + /// ]]>]]> + /// Each DELETE change is changed to an INSERT, and + /// ]]>]]> + /// Each INSERT change is changed to a DELETE, and + /// ]]>]]> + /// For each UPDATE change, the old.* and new.* values are exchanged. + /// ]]>]]> + /// This method does not change the order in which changes appear + /// within the set of changes. It merely reverses the sense of each + /// individual change. + /// + /// + /// The new instance that represents + /// the resulting set of changes. + /// + public ISQLiteChangeSet Invert() + { + CheckDisposed(); + + SQLiteSessionHelpers.CheckRawData(rawData); + + IntPtr pInData = IntPtr.Zero; + IntPtr pOutData = IntPtr.Zero; + + try + { + int nInData = 0; + + pInData = SQLiteBytes.ToIntPtr(rawData, ref nInData); + + int nOutData = 0; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_invert( + nInData, pInData, ref nOutData, ref pOutData); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changeset_invert"); + + byte[] newData = SQLiteBytes.FromIntPtr(pOutData, nOutData); + + return new SQLiteMemoryChangeSet( + newData, GetHandle(), GetFlags()); + } + finally + { + if (pOutData != IntPtr.Zero) + { + SQLiteMemory.FreeUntracked(pOutData); + pOutData = IntPtr.Zero; + } + + if (pInData != IntPtr.Zero) + { + SQLiteMemory.Free(pInData); + pInData = IntPtr.Zero; + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method combines the specified set of changes with the ones + /// contained in this instance. + /// + /// + /// The changes to be combined with those in this instance. + /// + /// + /// The new instance that represents + /// the resulting set of changes. + /// + public ISQLiteChangeSet CombineWith( + ISQLiteChangeSet changeSet + ) + { + CheckDisposed(); + + SQLiteSessionHelpers.CheckRawData(rawData); + + SQLiteMemoryChangeSet memoryChangeSet = + changeSet as SQLiteMemoryChangeSet; + + if (memoryChangeSet == null) + { + throw new ArgumentException( + "not a memory based change set", "changeSet"); + } + + SQLiteSessionHelpers.CheckRawData(memoryChangeSet.rawData); + + IntPtr pInData1 = IntPtr.Zero; + IntPtr pInData2 = IntPtr.Zero; + IntPtr pOutData = IntPtr.Zero; + + try + { + int nInData1 = 0; + + pInData1 = SQLiteBytes.ToIntPtr(rawData, ref nInData1); + + int nInData2 = 0; + + pInData2 = SQLiteBytes.ToIntPtr( + memoryChangeSet.rawData, ref nInData2); + + int nOutData = 0; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_concat( + nInData1, pInData1, nInData2, pInData2, ref nOutData, + ref pOutData); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changeset_concat"); + + byte[] newData = SQLiteBytes.FromIntPtr(pOutData, nOutData); + + return new SQLiteMemoryChangeSet( + newData, GetHandle(), GetFlags()); + } + finally + { + if (pOutData != IntPtr.Zero) + { + SQLiteMemory.FreeUntracked(pOutData); + pOutData = IntPtr.Zero; + } + + if (pInData2 != IntPtr.Zero) + { + SQLiteMemory.Free(pInData2); + pInData2 = IntPtr.Zero; + } + + if (pInData1 != IntPtr.Zero) + { + SQLiteMemory.Free(pInData1); + pInData1 = IntPtr.Zero; + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to apply the set of changes in this instance to the + /// associated database. + /// + /// + /// The delegate that will need + /// to handle any conflicting changes that may arise. + /// + /// + /// The optional application-defined context data. This value may be + /// null. + /// + public void Apply( + SessionConflictCallback conflictCallback, + object clientData + ) + { + CheckDisposed(); + + Apply(conflictCallback, null, clientData); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to apply the set of changes in this instance to the + /// associated database. + /// + /// + /// The delegate that will need + /// to handle any conflicting changes that may arise. + /// + /// + /// The optional delegate + /// that can be used to filter the list of tables impacted by the set + /// of changes. + /// + /// + /// The optional application-defined context data. This value may be + /// null. + /// + public void Apply( + SessionConflictCallback conflictCallback, + SessionTableFilterCallback tableFilterCallback, + object clientData + ) + { + CheckDisposed(); + + SQLiteSessionHelpers.CheckRawData(rawData); + + if (conflictCallback == null) + throw new ArgumentNullException("conflictCallback"); + + UnsafeNativeMethods.xSessionFilter xFilter = GetDelegate( + tableFilterCallback, clientData); + + UnsafeNativeMethods.xSessionConflict xConflict = GetDelegate( + conflictCallback, clientData); + + IntPtr pData = IntPtr.Zero; + + try + { + int nData = 0; + + pData = SQLiteBytes.ToIntPtr(rawData, ref nData); + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_apply( + GetIntPtr(), nData, pData, xFilter, xConflict, IntPtr.Zero); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changeset_apply"); + } + finally + { + if (pData != IntPtr.Zero) + { + SQLiteMemory.Free(pData); + pData = IntPtr.Zero; + } + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IEnumerable Members + /// + /// Creates an capable of iterating over the + /// items within this set of changes. + /// + /// + /// The new + /// instance. + /// + public IEnumerator GetEnumerator() + { + if (startFlags != SQLiteChangeSetStartFlags.None) + { + return new SQLiteMemoryChangeSetEnumerator( + rawData, startFlags); + } + else + { + return new SQLiteMemoryChangeSetEnumerator(rawData); + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IEnumerable Members + /// + /// Creates an capable of iterating over the + /// items within this set of changes. + /// + /// + /// The new instance. + /// + IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteMemoryChangeSet).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (rawData != null) + rawData = null; + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteStreamChangeSet Class + /// + /// This class represents a set of changes that are backed by a + /// instance. + /// + internal sealed class SQLiteStreamChangeSet : + SQLiteChangeSetBase, ISQLiteChangeSet + { + #region Private Data + /// + /// The instance that is managing + /// the underlying input used as the backing + /// store for the set of changes associated with this instance. + /// + private SQLiteStreamAdapter inputStreamAdapter; + + /// + /// The instance that is managing + /// the underlying output used as the backing + /// store for the set of changes generated by the + /// or methods. + /// + private SQLiteStreamAdapter outputStreamAdapter; + + /// + /// The instance used as the backing store for + /// the set of changes associated with this instance. + /// + private Stream inputStream; + + /// + /// The instance used as the backing store for + /// the set of changes generated by the or + /// methods. + /// + private Stream outputStream; + + /// + /// The flags used to create the change set iterator. + /// + private SQLiteChangeSetStartFlags startFlags; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Constructors + /// + /// Constructs an instance of this class using the specified streams + /// and wrapped native connection handle. + /// + /// + /// The where the raw byte data for the set of + /// changes may be read. + /// + /// + /// The where the raw byte data for resulting + /// sets of changes may be written. + /// + /// + /// The wrapped native connection handle to be associated with this + /// set of changes. + /// + /// + /// The flags associated with the connection represented by the + /// value. + /// + internal SQLiteStreamChangeSet( + Stream inputStream, + Stream outputStream, + SQLiteConnectionHandle handle, + SQLiteConnectionFlags connectionFlags + ) + : base(handle, connectionFlags) + { + this.inputStream = inputStream; + this.outputStream = outputStream; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Constructs an instance of this class using the specified streams + /// and wrapped native connection handle. + /// + /// + /// The where the raw byte data for the set of + /// changes may be read. + /// + /// + /// The where the raw byte data for resulting + /// sets of changes may be written. + /// + /// + /// The wrapped native connection handle to be associated with this + /// set of changes. + /// + /// + /// The flags associated with the connection represented by the + /// value. + /// + /// + /// The flags used to create the change set iterator. + /// + internal SQLiteStreamChangeSet( + Stream inputStream, + Stream outputStream, + SQLiteConnectionHandle handle, + SQLiteConnectionFlags connectionFlags, + SQLiteChangeSetStartFlags startFlags + ) + : base(handle, connectionFlags) + { + this.inputStream = inputStream; + this.outputStream = outputStream; + this.startFlags = startFlags; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// Throws an exception if the input stream or its associated stream + /// adapter are invalid. + /// + private void CheckInputStream() + { + if (inputStream == null) + { + throw new InvalidOperationException( + "input stream unavailable"); + } + + if (inputStreamAdapter == null) + { + inputStreamAdapter = new SQLiteStreamAdapter( + inputStream, GetFlags()); + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Throws an exception if the output stream or its associated stream + /// adapter are invalid. + /// + private void CheckOutputStream() + { + if (outputStream == null) + { + throw new InvalidOperationException( + "output stream unavailable"); + } + + if (outputStreamAdapter == null) + { + outputStreamAdapter = new SQLiteStreamAdapter( + outputStream, GetFlags()); + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteChangeSet Members + /// + /// This method "inverts" the set of changes within this instance. + /// Applying an inverted set of changes to a database reverses the + /// effects of applying the uninverted changes. Specifically: + /// ]]>]]> + /// Each DELETE change is changed to an INSERT, and + /// ]]>]]> + /// Each INSERT change is changed to a DELETE, and + /// ]]>]]> + /// For each UPDATE change, the old.* and new.* values are exchanged. + /// ]]>]]> + /// This method does not change the order in which changes appear + /// within the set of changes. It merely reverses the sense of each + /// individual change. + /// + /// + /// Since the resulting set of changes is written to the output stream, + /// this method always returns null. + /// + public ISQLiteChangeSet Invert() + { + CheckDisposed(); + CheckInputStream(); + CheckOutputStream(); + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_invert_strm( + inputStreamAdapter.GetInputDelegate(), IntPtr.Zero, + outputStreamAdapter.GetOutputDelegate(), IntPtr.Zero); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changeset_invert_strm"); + + return null; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// This method combines the specified set of changes with the ones + /// contained in this instance. + /// + /// + /// The changes to be combined with those in this instance. + /// + /// + /// Since the resulting set of changes is written to the output stream, + /// this method always returns null. + /// + public ISQLiteChangeSet CombineWith( + ISQLiteChangeSet changeSet + ) + { + CheckDisposed(); + CheckInputStream(); + CheckOutputStream(); + + SQLiteStreamChangeSet streamChangeSet = + changeSet as SQLiteStreamChangeSet; + + if (streamChangeSet == null) + { + throw new ArgumentException( + "not a stream based change set", "changeSet"); + } + + streamChangeSet.CheckInputStream(); + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_concat_strm( + inputStreamAdapter.GetInputDelegate(), IntPtr.Zero, + streamChangeSet.inputStreamAdapter.GetInputDelegate(), + IntPtr.Zero, outputStreamAdapter.GetOutputDelegate(), + IntPtr.Zero); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changeset_concat_strm"); + + return null; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to apply the set of changes in this instance to the + /// associated database. + /// + /// + /// The delegate that will need + /// to handle any conflicting changes that may arise. + /// + /// + /// The optional application-defined context data. This value may be + /// null. + /// + public void Apply( + SessionConflictCallback conflictCallback, + object clientData + ) + { + CheckDisposed(); + + Apply(conflictCallback, null, clientData); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to apply the set of changes in this instance to the + /// associated database. + /// + /// + /// The delegate that will need + /// to handle any conflicting changes that may arise. + /// + /// + /// The optional delegate + /// that can be used to filter the list of tables impacted by the set + /// of changes. + /// + /// + /// The optional application-defined context data. This value may be + /// null. + /// + public void Apply( + SessionConflictCallback conflictCallback, + SessionTableFilterCallback tableFilterCallback, + object clientData + ) + { + CheckDisposed(); + CheckInputStream(); + + if (conflictCallback == null) + throw new ArgumentNullException("conflictCallback"); + + UnsafeNativeMethods.xSessionFilter xFilter = GetDelegate( + tableFilterCallback, clientData); + + UnsafeNativeMethods.xSessionConflict xConflict = GetDelegate( + conflictCallback, clientData); + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_apply_strm( + GetIntPtr(), inputStreamAdapter.GetInputDelegate(), IntPtr.Zero, + xFilter, xConflict, IntPtr.Zero); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changeset_apply_strm"); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IEnumerable Members + /// + /// Creates an capable of iterating over the + /// items within this set of changes. + /// + /// + /// The new + /// instance. + /// + public IEnumerator GetEnumerator() + { + if (startFlags != SQLiteChangeSetStartFlags.None) + { + return new SQLiteStreamChangeSetEnumerator( + inputStream, GetFlags(), startFlags); + } + else + { + return new SQLiteStreamChangeSetEnumerator( + inputStream, GetFlags()); + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IEnumerable Members + /// + /// Creates an capable of iterating over the + /// items within this set of changes. + /// + /// + /// The new instance. + /// + IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteStreamChangeSet).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (outputStreamAdapter != null) + { + outputStreamAdapter.Dispose(); + outputStreamAdapter = null; + } + + if (inputStreamAdapter != null) + { + inputStreamAdapter.Dispose(); + inputStreamAdapter = null; + } + + if (outputStream != null) + outputStream = null; /* NOT OWNED */ + + if (inputStream != null) + inputStream = null; /* NOT OWNED */ + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteChangeSetEnumerator Class + /// + /// This class represents an that is capable of + /// enumerating over a set of changes. It serves as the base class for the + /// and + /// classes. It manages and + /// owns an instance of the class. + /// + internal abstract class SQLiteChangeSetEnumerator : + IEnumerator + { + #region Private Data + /// + /// This managed change set iterator is managed and owned by this + /// class. It will be disposed when this class is disposed. + /// + private SQLiteChangeSetIterator iterator; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs an instance of this class using the specified managed + /// change set iterator. + /// + /// + /// The managed iterator instance to use. + /// + public SQLiteChangeSetEnumerator( + SQLiteChangeSetIterator iterator + ) + { + SetIterator(iterator); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// Throws an exception if the managed iterator instance is invalid. + /// + private void CheckIterator() + { + if (iterator == null) + throw new InvalidOperationException("iterator unavailable"); + + iterator.CheckHandle(); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Sets the managed iterator instance to a new value. + /// + /// + /// The new managed iterator instance to use. + /// + private void SetIterator( + SQLiteChangeSetIterator iterator + ) + { + this.iterator = iterator; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes of the managed iterator instance and sets its value to + /// null. + /// + private void CloseIterator() + { + if (iterator != null) + { + iterator.Dispose(); + iterator = null; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Protected Methods + /// + /// Disposes of the existing managed iterator instance and then sets it + /// to a new value. + /// + /// + /// The new managed iterator instance to use. + /// + protected void ResetIterator( + SQLiteChangeSetIterator iterator + ) + { + CloseIterator(); + SetIterator(iterator); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IEnumerator Members + /// + /// Returns the current change within the set of changes, represented + /// by a instance. + /// + public ISQLiteChangeSetMetadataItem Current + { + get + { + CheckDisposed(); + + return new SQLiteChangeSetMetadataItem(iterator); + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IEnumerator Members + /// + /// Returns the current change within the set of changes, represented + /// by a instance. + /// + object Collections.IEnumerator.Current + { + get + { + CheckDisposed(); + + return Current; + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to advance to the next item in the set of changes. + /// + /// + /// Non-zero if more items are available; otherwise, zero. + /// + public bool MoveNext() + { + CheckDisposed(); + CheckIterator(); + + return iterator.Next(); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Throws because not all the + /// derived classes are able to support reset functionality. + /// + public virtual void Reset() + { + CheckDisposed(); + + throw new NotImplementedException(); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes of this object instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteChangeSetEnumerator).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + protected virtual void Dispose(bool disposing) + { + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + CloseIterator(); + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Destructor + /// + /// Finalizes this object instance. + /// + ~SQLiteChangeSetEnumerator() + { + Dispose(false); + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteMemoryChangeSetEnumerator Class + /// + /// This class represents an that is capable of + /// enumerating over a set of changes contained entirely in memory. + /// + internal sealed class SQLiteMemoryChangeSetEnumerator : + SQLiteChangeSetEnumerator + { + #region Private Data + /// + /// The raw byte data for this set of changes. Since this data must + /// be marshalled to a native memory buffer before being used, there + /// must be enough memory available to store at least two times the + /// amount of data contained within it. + /// + private byte[] rawData; + + /// + /// The flags used to create the change set iterator. + /// + private SQLiteChangeSetStartFlags flags; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs an instance of this class using the specified raw byte + /// data. + /// + /// + /// The raw byte data containing the set of changes for this + /// enumerator. + /// + public SQLiteMemoryChangeSetEnumerator( + byte[] rawData + ) + : base(SQLiteMemoryChangeSetIterator.Create(rawData)) + { + this.rawData = rawData; + this.flags = SQLiteChangeSetStartFlags.None; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Constructs an instance of this class using the specified raw byte + /// data. + /// + /// + /// The raw byte data containing the set of changes for this + /// enumerator. + /// + /// + /// The flags used to create the change set iterator. + /// + public SQLiteMemoryChangeSetEnumerator( + byte[] rawData, + SQLiteChangeSetStartFlags flags + ) + : base(SQLiteMemoryChangeSetIterator.Create(rawData, flags)) + { + this.rawData = rawData; + this.flags = flags; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IEnumerator Overrides + /// + /// Resets the enumerator to its initial position. + /// + public override void Reset() + { + CheckDisposed(); + + SQLiteMemoryChangeSetIterator result; + + if (flags != SQLiteChangeSetStartFlags.None) + result = SQLiteMemoryChangeSetIterator.Create(rawData, flags); + else + result = SQLiteMemoryChangeSetIterator.Create(rawData); + + ResetIterator(result); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteMemoryChangeSetEnumerator).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteStreamChangeSetEnumerator Class + /// + /// This class represents an that is capable of + /// enumerating over a set of changes backed by a + /// instance. + /// + internal sealed class SQLiteStreamChangeSetEnumerator : + SQLiteChangeSetEnumerator + { + #region Public Constructors + /// + /// Constructs an instance of this class using the specified stream. + /// + /// + /// The where the raw byte data for the set of + /// changes may be read. + /// + /// + /// The flags associated with the parent connection. + /// + public SQLiteStreamChangeSetEnumerator( + Stream stream, + SQLiteConnectionFlags connectionFlags + ) + : base(SQLiteStreamChangeSetIterator.Create( + stream, connectionFlags)) + { + // do nothing. + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Constructs an instance of this class using the specified stream. + /// + /// + /// The where the raw byte data for the set of + /// changes may be read. + /// + /// + /// The flags associated with the parent connection. + /// + /// + /// The flags used to create the change set iterator. + /// + public SQLiteStreamChangeSetEnumerator( + Stream stream, + SQLiteConnectionFlags connectionFlags, + SQLiteChangeSetStartFlags startFlags + ) + : base(SQLiteStreamChangeSetIterator.Create( + stream, connectionFlags, startFlags)) + { + // do nothing. + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteStreamChangeSetEnumerator).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + protected override void Dispose(bool disposing) + { + try + { + //if (!disposed) + //{ + // if (disposing) + // { + // //////////////////////////////////// + // // dispose managed resources here... + // //////////////////////////////////// + // } + + // ////////////////////////////////////// + // // release unmanaged resources here... + // ////////////////////////////////////// + //} + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteChangeSetMetadataItem Class + /// + /// This interface implements properties and methods used to fetch metadata + /// about one change within a set of changes for a database. + /// + internal sealed class SQLiteChangeSetMetadataItem : + ISQLiteChangeSetMetadataItem + { + #region Private Data + /// + /// The instance to use. This + /// will NOT be owned by this class and will not be disposed upon this + /// class being disposed or finalized. + /// + private SQLiteChangeSetIterator iterator; + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Public Constructors + /// + /// Constructs an instance of this class using the specified iterator + /// instance. + /// + /// + /// The managed iterator instance to use. + /// + public SQLiteChangeSetMetadataItem( + SQLiteChangeSetIterator iterator + ) + { + this.iterator = iterator; + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// Throws an exception if the managed iterator instance is invalid. + /// + private void CheckIterator() + { + if (iterator == null) + throw new InvalidOperationException("iterator unavailable"); + + iterator.CheckHandle(); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Populates the underlying data for the , + /// , , and + /// properties, using the appropriate native + /// API. + /// + private void PopulateOperationMetadata() + { + if ((tableName == null) || (numberOfColumns == null) || + (operationCode == null) || (indirect == null)) + { + CheckIterator(); + + IntPtr pTblName = IntPtr.Zero; + SQLiteAuthorizerActionCode op = SQLiteAuthorizerActionCode.None; + int bIndirect = 0; + int nColumns = 0; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_op( + iterator.GetIntPtr(), ref pTblName, ref nColumns, ref op, + ref bIndirect); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changeset_op"); + + tableName = SQLiteString.StringFromUtf8IntPtr(pTblName); + numberOfColumns = nColumns; + operationCode = op; + indirect = (bIndirect != 0); + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Populates the underlying data for the + /// property using the appropriate + /// native API. + /// + private void PopulatePrimaryKeyColumns() + { + if (primaryKeyColumns == null) + { + CheckIterator(); + + IntPtr pPrimaryKeys = IntPtr.Zero; + int nColumns = 0; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_pk( + iterator.GetIntPtr(), ref pPrimaryKeys, ref nColumns); + + if (rc != SQLiteErrorCode.Ok) + throw new SQLiteException(rc, "sqlite3changeset_pk"); + + byte[] bytes = SQLiteBytes.FromIntPtr(pPrimaryKeys, nColumns); + + if (bytes != null) + { + primaryKeyColumns = new bool[nColumns]; + + for (int index = 0; index < bytes.Length; index++) + primaryKeyColumns[index] = (bytes[index] != 0); + } + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Populates the underlying data for the + /// property using the + /// appropriate native API. + /// + private void PopulateNumberOfForeignKeyConflicts() + { + if (numberOfForeignKeyConflicts == null) + { + CheckIterator(); + + int conflicts = 0; + + SQLiteErrorCode rc = + UnsafeNativeMethods.sqlite3changeset_fk_conflicts( + iterator.GetIntPtr(), ref conflicts); + + if (rc != SQLiteErrorCode.Ok) + { + throw new SQLiteException(rc, + "sqlite3changeset_fk_conflicts"); + } + + numberOfForeignKeyConflicts = conflicts; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region ISQLiteChangeSetMetadataItem Members + /// + /// Backing field for the property. This value + /// will be null if this field has not yet been populated via the + /// underlying native API. + /// + private string tableName; + + /// + /// The name of the table the change was made to. + /// + public string TableName + { + get + { + CheckDisposed(); + PopulateOperationMetadata(); + + return tableName; + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Backing field for the property. This + /// value will be null if this field has not yet been populated via the + /// underlying native API. + /// + private int? numberOfColumns; + + /// + /// The number of columns impacted by this change. This value can be + /// used to determine the highest valid column index that may be used + /// with the , , + /// and methods of this interface. It + /// will be this value minus one. + /// + public int NumberOfColumns + { + get + { + CheckDisposed(); + PopulateOperationMetadata(); + + return (int)numberOfColumns; + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Backing field for the property. This + /// value will be null if this field has not yet been populated via the + /// underlying native API. + /// + private SQLiteAuthorizerActionCode? operationCode; + + /// + /// This will contain the value + /// , + /// , or + /// , corresponding to + /// the overall type of change this item represents. + /// + public SQLiteAuthorizerActionCode OperationCode + { + get + { + CheckDisposed(); + PopulateOperationMetadata(); + + return (SQLiteAuthorizerActionCode)operationCode; + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Backing field for the property. This value + /// will be null if this field has not yet been populated via the + /// underlying native API. + /// + private bool? indirect; + + /// + /// Non-zero if this change is considered to be indirect (i.e. as + /// though they were made via a trigger or foreign key action). + /// + public bool Indirect + { + get + { + CheckDisposed(); + PopulateOperationMetadata(); + + return (bool)indirect; + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Backing field for the property. + /// This value will be null if this field has not yet been populated + /// via the underlying native API. + /// + private bool[] primaryKeyColumns; + + /// + /// This array contains a for each column in + /// the table associated with this change. The element will be zero + /// if the column is not part of the primary key; otherwise, it will + /// be non-zero. + /// + public bool[] PrimaryKeyColumns + { + get + { + CheckDisposed(); + PopulatePrimaryKeyColumns(); + + return primaryKeyColumns; + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Backing field for the + /// property. This value will be null if this field has not yet been + /// populated via the underlying native API. + /// + private int? numberOfForeignKeyConflicts; + + /// + /// This method may only be called from within a + /// delegate when the conflict + /// type is . It + /// returns the total number of known foreign key violations in the + /// destination database. + /// + public int NumberOfForeignKeyConflicts + { + get + { + CheckDisposed(); + PopulateNumberOfForeignKeyConflicts(); + + return (int)numberOfForeignKeyConflicts; + } + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Queries and returns the original value of a given column for this + /// change. This method may only be called when the + /// has a value of + /// or + /// . + /// + /// + /// The index for the column. This value must be between zero and one + /// less than the total number of columns for this table. + /// + /// + /// The original value of a given column for this change. + /// + public SQLiteValue GetOldValue( + int columnIndex + ) + { + CheckDisposed(); + CheckIterator(); + + IntPtr pValue = IntPtr.Zero; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_old( + iterator.GetIntPtr(), columnIndex, ref pValue); + + return SQLiteValue.FromIntPtr(pValue); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Queries and returns the updated value of a given column for this + /// change. This method may only be called when the + /// has a value of + /// or + /// . + /// + /// + /// The index for the column. This value must be between zero and one + /// less than the total number of columns for this table. + /// + /// + /// The updated value of a given column for this change. + /// + public SQLiteValue GetNewValue( + int columnIndex + ) + { + CheckDisposed(); + CheckIterator(); + + IntPtr pValue = IntPtr.Zero; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_new( + iterator.GetIntPtr(), columnIndex, ref pValue); + + return SQLiteValue.FromIntPtr(pValue); + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Queries and returns the conflicting value of a given column for + /// this change. This method may only be called from within a + /// delegate when the conflict + /// type is or + /// . + /// + /// + /// The index for the column. This value must be between zero and one + /// less than the total number of columns for this table. + /// + /// + /// The conflicting value of a given column for this change. + /// + public SQLiteValue GetConflictValue( + int columnIndex + ) + { + CheckDisposed(); + CheckIterator(); + + IntPtr pValue = IntPtr.Zero; + + SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_conflict( + iterator.GetIntPtr(), columnIndex, ref pValue); + + return SQLiteValue.FromIntPtr(pValue); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes of this object instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + /// + /// Non-zero if this object instance has been disposed. + /// + private bool disposed; + + /// + /// Throws an exception if this object instance has been disposed. + /// + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteChangeSetMetadataItem).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Disposes or finalizes this object instance. + /// + /// + /// Non-zero if this object is being disposed; otherwise, this object + /// is being finalized. + /// + private /* protected virtual */ void Dispose(bool disposing) + { + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (iterator != null) + iterator = null; /* NOT OWNED */ + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Destructor + /// + /// Finalizes this object instance. + /// + ~SQLiteChangeSetMetadataItem() + { + Dispose(false); + } + #endregion + } + #endregion +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteStatement.cs b/Native.Csharp.Tool/SQLite/SQLiteStatement.cs new file mode 100644 index 00000000..9c8f36a0 --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteStatement.cs @@ -0,0 +1,558 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Globalization; + + /// + /// Represents a single SQL statement in SQLite. + /// + internal sealed class SQLiteStatement : IDisposable + { + /// + /// The underlying SQLite object this statement is bound to + /// + internal SQLiteBase _sql; + /// + /// The command text of this SQL statement + /// + internal string _sqlStatement; + /// + /// The actual statement pointer + /// + internal SQLiteStatementHandle _sqlite_stmt; + /// + /// An index from which unnamed parameters begin + /// + internal int _unnamedParameters; + /// + /// Names of the parameters as SQLite understands them to be + /// + internal string[] _paramNames; + /// + /// Parameters for this statement + /// + internal SQLiteParameter[] _paramValues; + /// + /// Command this statement belongs to (if any) + /// + internal SQLiteCommand _command; + + /// + /// The flags associated with the parent connection object. + /// + private SQLiteConnectionFlags _flags; + + private string[] _types; + + /// + /// Initializes the statement and attempts to get all information about parameters in the statement + /// + /// The base SQLite object + /// The flags associated with the parent connection object + /// The statement + /// The command text for this statement + /// The previous command in a multi-statement command + internal SQLiteStatement(SQLiteBase sqlbase, SQLiteConnectionFlags flags, SQLiteStatementHandle stmt, string strCommand, SQLiteStatement previous) + { + _sql = sqlbase; + _sqlite_stmt = stmt; + _sqlStatement = strCommand; + _flags = flags; + + // Determine parameters for this statement (if any) and prepare space for them. + int nCmdStart = 0; + int n = _sql.Bind_ParamCount(this, _flags); + int x; + string s; + + if (n > 0) + { + if (previous != null) + nCmdStart = previous._unnamedParameters; + + _paramNames = new string[n]; + _paramValues = new SQLiteParameter[n]; + + for (x = 0; x < n; x++) + { + s = _sql.Bind_ParamName(this, _flags, x + 1); + if (String.IsNullOrEmpty(s)) + { + s = HelperMethods.StringFormat(CultureInfo.InvariantCulture, ";{0}", nCmdStart); + nCmdStart++; + _unnamedParameters++; + } + _paramNames[x] = s; + _paramValues[x] = null; + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable Members + /// + /// Disposes and finalizes the statement + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteStatement).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + private void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (_sqlite_stmt != null) + { + _sqlite_stmt.Dispose(); + _sqlite_stmt = null; + } + + _paramNames = null; + _paramValues = null; + _sql = null; + _sqlStatement = null; + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region Destructor + ~SQLiteStatement() + { + Dispose(false); + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// If the underlying database connection is open, fetches the number of changed rows + /// resulting from the most recent query; otherwise, does nothing. + /// + /// + /// The number of changes when true is returned. + /// Undefined if false is returned. + /// + /// + /// The read-only flag when true is returned. + /// Undefined if false is returned. + /// + /// Non-zero if the number of changed rows was fetched. + internal bool TryGetChanges( + ref int changes, + ref bool readOnly + ) + { + if ((_sql != null) && _sql.IsOpen()) + { + changes = _sql.Changes; + readOnly = _sql.IsReadOnly(this); + + return true; + } + + return false; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Called by SQLiteParameterCollection, this function determines if the specified parameter name belongs to + /// this statement, and if so, keeps a reference to the parameter so it can be bound later. + /// + /// The parameter name to map + /// The parameter to assign it + internal bool MapParameter(string s, SQLiteParameter p) + { + if (_paramNames == null) return false; + + int startAt = 0; + if (s.Length > 0) + { + if (":$@;".IndexOf(s[0]) == -1) + startAt = 1; + } + + int x = _paramNames.Length; + for (int n = 0; n < x; n++) + { + if (String.Compare(_paramNames[n], startAt, s, 0, Math.Max(_paramNames[n].Length - startAt, s.Length), StringComparison.OrdinalIgnoreCase) == 0) + { + _paramValues[n] = p; + return true; + } + } + return false; + } + + /// + /// Bind all parameters, making sure the caller didn't miss any + /// + internal void BindParameters() + { + if (_paramNames == null) return; + + int x = _paramNames.Length; + for (int n = 0; n < x; n++) + { + BindParameter(n + 1, _paramValues[n]); + } + } + + /// + /// This method attempts to query the database connection associated with + /// the statement in use. If the underlying command or connection is + /// unavailable, a null value will be returned. + /// + /// + /// The connection object -OR- null if it is unavailable. + /// + private static SQLiteConnection GetConnection( + SQLiteStatement statement + ) + { + try + { + if (statement != null) + { + SQLiteCommand command = statement._command; + + if (command != null) + { + SQLiteConnection connection = command.Connection; + + if (connection != null) + return connection; + } + } + } + catch (ObjectDisposedException) + { + // do nothing. + } + + return null; + } + + /// + /// Invokes the parameter binding callback configured for the database + /// type name associated with the specified column. If no parameter + /// binding callback is available for the database type name, do + /// nothing. + /// + /// + /// The index of the column being read. + /// + /// + /// The instance being bound to the + /// command. + /// + /// + /// Non-zero if the default handling for the parameter binding call + /// should be skipped (i.e. the parameter should not be bound at all). + /// Great care should be used when setting this to non-zero. + /// + private void InvokeBindValueCallback( + int index, + SQLiteParameter parameter, + out bool complete + ) + { + complete = false; + SQLiteConnectionFlags oldFlags = _flags; + _flags &= ~SQLiteConnectionFlags.UseConnectionBindValueCallbacks; + + try + { + if (parameter == null) + return; + + SQLiteConnection connection = GetConnection(this); + + if (connection == null) + return; + + // + // NOTE: First, always look for an explicitly set database type + // name. + // + string typeName = parameter.TypeName; + + if (typeName == null) + { + // + // NOTE: Are we allowed to fallback to using the parameter name + // as the basis for looking up the binding callback? + // + if (HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.UseParameterNameForTypeName)) + { + typeName = parameter.ParameterName; + } + } + + if (typeName == null) + { + // + // NOTE: Are we allowed to fallback to using the database type + // name translated from the DbType as the basis for looking + // up the binding callback? + // + if (HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.UseParameterDbTypeForTypeName)) + { + typeName = SQLiteConvert.DbTypeToTypeName( + connection, parameter.DbType, _flags); + } + } + + if (typeName == null) + return; + + SQLiteTypeCallbacks callbacks; + + if (!connection.TryGetTypeCallbacks(typeName, out callbacks) || + (callbacks == null)) + { + return; + } + + SQLiteBindValueCallback callback = callbacks.BindValueCallback; + + if (callback == null) + return; + + object userData = callbacks.BindValueUserData; + + callback( + _sql, _command, oldFlags, parameter, typeName, index, + userData, out complete); /* throw */ + } + finally + { + _flags |= SQLiteConnectionFlags.UseConnectionBindValueCallbacks; + } + } + + /// + /// Perform the bind operation for an individual parameter + /// + /// The index of the parameter to bind + /// The parameter we're binding + private void BindParameter(int index, SQLiteParameter param) + { + if (param == null) + throw new SQLiteException("Insufficient parameters supplied to the command"); + + if (HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.UseConnectionBindValueCallbacks)) + { + bool complete; + + InvokeBindValueCallback(index, param, out complete); + + if (complete) + return; + } + + object obj = param.Value; + DbType objType = param.DbType; + + if ((obj != null) && (objType == DbType.Object)) + objType = SQLiteConvert.TypeToDbType(obj.GetType()); + + if (SQLite3.ForceLogPrepare() || HelperMethods.LogPreBind(_flags)) + { + IntPtr handle = _sqlite_stmt; + + SQLiteLog.LogMessage(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Binding statement {0} paramter #{1} with database type {2} and raw value {{{3}}}...", + handle, index, objType, obj)); + } + + if ((obj == null) || Convert.IsDBNull(obj)) + { + _sql.Bind_Null(this, _flags, index); + return; + } + + CultureInfo invariantCultureInfo = CultureInfo.InvariantCulture; + + bool invariantText = HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.BindInvariantText); + + CultureInfo cultureInfo = CultureInfo.CurrentCulture; + + if (HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.ConvertInvariantText)) + { + cultureInfo = invariantCultureInfo; + } + + if (HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.BindAllAsText)) + { + if (obj is DateTime) + { + _sql.Bind_DateTime(this, _flags, index, (DateTime)obj); + } + else + { + _sql.Bind_Text(this, _flags, index, invariantText ? + SQLiteConvert.ToStringWithProvider(obj, invariantCultureInfo) : + SQLiteConvert.ToStringWithProvider(obj, cultureInfo)); + } + + return; + } + + bool invariantDecimal = HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.BindInvariantDecimal); + + if (HelperMethods.HasFlags( + _flags, SQLiteConnectionFlags.BindDecimalAsText)) + { + if (obj is Decimal) + { + _sql.Bind_Text(this, _flags, index, invariantText || invariantDecimal ? + SQLiteConvert.ToStringWithProvider(obj, invariantCultureInfo) : + SQLiteConvert.ToStringWithProvider(obj, cultureInfo)); + + return; + } + } + + switch (objType) + { + case DbType.Date: + case DbType.Time: + case DbType.DateTime: + // + // NOTE: The old method (commented below) does not honor the selected date format + // for the connection. + // _sql.Bind_DateTime(this, index, Convert.ToDateTime(obj, cultureInfo)); + _sql.Bind_DateTime(this, _flags, index, (obj is string) ? + _sql.ToDateTime((string)obj) : Convert.ToDateTime(obj, cultureInfo)); + break; + case DbType.Boolean: + _sql.Bind_Boolean(this, _flags, index, SQLiteConvert.ToBoolean(obj, cultureInfo, true)); + break; + case DbType.SByte: + _sql.Bind_Int32(this, _flags, index, Convert.ToSByte(obj, cultureInfo)); + break; + case DbType.Int16: + _sql.Bind_Int32(this, _flags, index, Convert.ToInt16(obj, cultureInfo)); + break; + case DbType.Int32: + _sql.Bind_Int32(this, _flags, index, Convert.ToInt32(obj, cultureInfo)); + break; + case DbType.Int64: + _sql.Bind_Int64(this, _flags, index, Convert.ToInt64(obj, cultureInfo)); + break; + case DbType.Byte: + _sql.Bind_UInt32(this, _flags, index, Convert.ToByte(obj, cultureInfo)); + break; + case DbType.UInt16: + _sql.Bind_UInt32(this, _flags, index, Convert.ToUInt16(obj, cultureInfo)); + break; + case DbType.UInt32: + _sql.Bind_UInt32(this, _flags, index, Convert.ToUInt32(obj, cultureInfo)); + break; + case DbType.UInt64: + _sql.Bind_UInt64(this, _flags, index, Convert.ToUInt64(obj, cultureInfo)); + break; + case DbType.Single: + case DbType.Double: + case DbType.Currency: + _sql.Bind_Double(this, _flags, index, Convert.ToDouble(obj, cultureInfo)); + break; + case DbType.Binary: + _sql.Bind_Blob(this, _flags, index, (byte[])obj); + break; + case DbType.Guid: + if (_command.Connection._binaryGuid == true) + { + _sql.Bind_Blob(this, _flags, index, ((Guid)obj).ToByteArray()); + } + else + { + _sql.Bind_Text(this, _flags, index, invariantText ? + SQLiteConvert.ToStringWithProvider(obj, invariantCultureInfo) : + SQLiteConvert.ToStringWithProvider(obj, cultureInfo)); + } + break; + case DbType.Decimal: // Dont store decimal as double ... loses precision + _sql.Bind_Text(this, _flags, index, invariantText || invariantDecimal ? + SQLiteConvert.ToStringWithProvider(Convert.ToDecimal(obj, cultureInfo), invariantCultureInfo) : + SQLiteConvert.ToStringWithProvider(Convert.ToDecimal(obj, cultureInfo), cultureInfo)); + break; + default: + _sql.Bind_Text(this, _flags, index, invariantText ? + SQLiteConvert.ToStringWithProvider(obj, invariantCultureInfo) : + SQLiteConvert.ToStringWithProvider(obj, cultureInfo)); + break; + } + } + + internal string[] TypeDefinitions + { + get { return _types; } + } + + internal void SetTypes(string typedefs) + { + int pos = typedefs.IndexOf("TYPES", 0, StringComparison.OrdinalIgnoreCase); + if (pos == -1) throw new ArgumentOutOfRangeException(); + + string[] types = typedefs.Substring(pos + 6).Replace(" ", String.Empty).Replace(";", String.Empty).Replace("\"", String.Empty).Replace("[", String.Empty).Replace("]", String.Empty).Replace("`", String.Empty).Split(',', '\r', '\n', '\t'); + + int n; + for (n = 0; n < types.Length; n++) + { + if (String.IsNullOrEmpty(types[n]) == true) + types[n] = null; + } + _types = types; + } + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteTransaction.cs b/Native.Csharp.Tool/SQLite/SQLiteTransaction.cs new file mode 100644 index 00000000..4549c87c --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteTransaction.cs @@ -0,0 +1,175 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Threading; + + /////////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// SQLite implementation of DbTransaction that does not support nested transactions. + /// + public class SQLiteTransaction : SQLiteTransactionBase + { + /// + /// Constructs the transaction object, binding it to the supplied connection + /// + /// The connection to open a transaction on + /// TRUE to defer the writelock, or FALSE to lock immediately + internal SQLiteTransaction(SQLiteConnection connection, bool deferredLock) + : base(connection, deferredLock) + { + // do nothing. + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteTransaction).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Disposes the transaction. If it is currently active, any changes are rolled back. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (IsValid(false)) + { + IssueRollback(false); + } + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Commits the current transaction. + /// + public override void Commit() + { + CheckDisposed(); + SQLiteConnection.Check(_cnn); + IsValid(true); + + if (_cnn._transactionLevel - 1 == 0) + { + using (SQLiteCommand cmd = _cnn.CreateCommand()) + { + cmd.CommandText = "COMMIT;"; + cmd.ExecuteNonQuery(); + } + } + _cnn._transactionLevel--; + _cnn = null; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to start a transaction. An exception will be thrown if the transaction cannot + /// be started for any reason. + /// + /// TRUE to defer the writelock, or FALSE to lock immediately + protected override void Begin( + bool deferredLock + ) + { + if (_cnn._transactionLevel++ == 0) + { + try + { + using (SQLiteCommand cmd = _cnn.CreateCommand()) + { + if (!deferredLock) + cmd.CommandText = "BEGIN IMMEDIATE;"; + else + cmd.CommandText = "BEGIN;"; + + cmd.ExecuteNonQuery(); + } + } + catch (SQLiteException) + { + _cnn._transactionLevel--; + _cnn = null; + + throw; + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Issue a ROLLBACK command against the database connection, + /// optionally re-throwing any caught exception. + /// + /// + /// Non-zero to re-throw caught exceptions. + /// + protected override void IssueRollback( + bool throwError + ) + { + SQLiteConnection cnn = Interlocked.Exchange(ref _cnn, null); + + if (cnn != null) + { + try + { + using (SQLiteCommand cmd = cnn.CreateCommand()) + { + cmd.CommandText = "ROLLBACK;"; + cmd.ExecuteNonQuery(); + } + } + catch + { + if (throwError) + throw; + } + cnn._transactionLevel = 0; + } + } + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteTransaction2.cs b/Native.Csharp.Tool/SQLite/SQLiteTransaction2.cs new file mode 100644 index 00000000..044395bc --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteTransaction2.cs @@ -0,0 +1,276 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Threading; + + /////////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// SQLite implementation of DbTransaction that does support nested transactions. + /// + public sealed class SQLiteTransaction2 : SQLiteTransaction + { + /// + /// The original transaction level for the associated connection + /// when this transaction was created (i.e. begun). + /// + private int _beginLevel; + + /// + /// The SAVEPOINT name for this transaction, if any. This will + /// only be non-null if this transaction is a nested one. + /// + private string _savePointName; + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Constructs the transaction object, binding it to the supplied connection + /// + /// The connection to open a transaction on + /// TRUE to defer the writelock, or FALSE to lock immediately + internal SQLiteTransaction2(SQLiteConnection connection, bool deferredLock) + : base(connection, deferredLock) + { + // do nothing. + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + throw new ObjectDisposedException(typeof(SQLiteTransaction2).Name); +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Disposes the transaction. If it is currently active, any changes are rolled back. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (IsValid(false)) + { + IssueRollback(false); + } + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Commits the current transaction. + /// + public override void Commit() + { + CheckDisposed(); + SQLiteConnection.Check(_cnn); + IsValid(true); + + if (_beginLevel == 0) + { + using (SQLiteCommand cmd = _cnn.CreateCommand()) + { + cmd.CommandText = "COMMIT;"; + cmd.ExecuteNonQuery(); + } + + _cnn._transactionLevel = 0; + _cnn = null; + } + else + { + using (SQLiteCommand cmd = _cnn.CreateCommand()) + { + if (String.IsNullOrEmpty(_savePointName)) + throw new SQLiteException("Cannot commit, unknown SAVEPOINT"); + + cmd.CommandText = String.Format( + "RELEASE {0};", _savePointName); + + cmd.ExecuteNonQuery(); + } + + _cnn._transactionLevel--; + _cnn = null; + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to start a transaction. An exception will be thrown if the transaction cannot + /// be started for any reason. + /// + /// TRUE to defer the writelock, or FALSE to lock immediately + protected override void Begin( + bool deferredLock + ) + { + int transactionLevel; + + if ((transactionLevel = _cnn._transactionLevel++) == 0) + { + try + { + using (SQLiteCommand cmd = _cnn.CreateCommand()) + { + if (!deferredLock) + cmd.CommandText = "BEGIN IMMEDIATE;"; + else + cmd.CommandText = "BEGIN;"; + + cmd.ExecuteNonQuery(); + + _beginLevel = transactionLevel; + } + } + catch (SQLiteException) + { + _cnn._transactionLevel--; + _cnn = null; + + throw; + } + } + else + { + try + { + using (SQLiteCommand cmd = _cnn.CreateCommand()) + { + _savePointName = GetSavePointName(); + + cmd.CommandText = String.Format( + "SAVEPOINT {0};", _savePointName); + + cmd.ExecuteNonQuery(); + + _beginLevel = transactionLevel; + } + } + catch (SQLiteException) + { + _cnn._transactionLevel--; + _cnn = null; + + throw; + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Issue a ROLLBACK command against the database connection, + /// optionally re-throwing any caught exception. + /// + /// + /// Non-zero to re-throw caught exceptions. + /// + protected override void IssueRollback(bool throwError) + { + SQLiteConnection cnn = Interlocked.Exchange(ref _cnn, null); + + if (cnn != null) + { + if (_beginLevel == 0) + { + try + { + using (SQLiteCommand cmd = cnn.CreateCommand()) + { + cmd.CommandText = "ROLLBACK;"; + cmd.ExecuteNonQuery(); + } + + cnn._transactionLevel = 0; + } + catch + { + if (throwError) + throw; + } + } + else + { + try + { + using (SQLiteCommand cmd = cnn.CreateCommand()) + { + if (String.IsNullOrEmpty(_savePointName)) + throw new SQLiteException("Cannot rollback, unknown SAVEPOINT"); + + cmd.CommandText = String.Format( + "ROLLBACK TO {0};", _savePointName); + + cmd.ExecuteNonQuery(); + } + + cnn._transactionLevel--; + } + catch + { + if (throwError) + throw; + } + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Constructs the name of a new savepoint for this transaction. It + /// should only be called from the constructor of this class. + /// + /// + /// The name of the new savepoint -OR- null if it cannot be constructed. + /// + private string GetSavePointName() + { + int sequence = ++_cnn._transactionSequence; + + return String.Format( + "sqlite_dotnet_savepoint_{0}", sequence); + } + } +} diff --git a/Native.Csharp.Tool/SQLite/SQLiteTransactionBase.cs b/Native.Csharp.Tool/SQLite/SQLiteTransactionBase.cs new file mode 100644 index 00000000..724a843a --- /dev/null +++ b/Native.Csharp.Tool/SQLite/SQLiteTransactionBase.cs @@ -0,0 +1,214 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Data; + using System.Data.Common; + + /////////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Base class used by to implement DbTransaction for SQLite. + /// + public abstract class SQLiteTransactionBase : DbTransaction + { + /// + /// The connection to which this transaction is bound. + /// + internal SQLiteConnection _cnn; + + /// + /// Matches the version of the connection. + /// + internal int _version; + + /// + /// The isolation level for this transaction. + /// + private IsolationLevel _level; + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Constructs the transaction object, binding it to the supplied connection + /// + /// The connection to open a transaction on + /// TRUE to defer the writelock, or FALSE to lock immediately + internal SQLiteTransactionBase(SQLiteConnection connection, bool deferredLock) + { + _cnn = connection; + _version = _cnn._version; + + _level = (deferredLock == true) ? + SQLiteConnection.DeferredIsolationLevel : + SQLiteConnection.ImmediateIsolationLevel; + + Begin(deferredLock); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Gets the isolation level of the transaction. SQLite only supports Serializable transactions. + /// + public override IsolationLevel IsolationLevel + { + get { CheckDisposed(); return _level; } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + #region IDisposable "Pattern" Members + private bool disposed; + private void CheckDisposed() /* throw */ + { +#if THROW_ON_DISPOSED + if (disposed) + { + throw new ObjectDisposedException( + typeof(SQLiteTransactionBase).Name); + } +#endif + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Disposes the transaction. If it is currently active, any changes are rolled back. + /// + protected override void Dispose(bool disposing) + { + try + { + if (!disposed) + { + if (disposing) + { + //////////////////////////////////// + // dispose managed resources here... + //////////////////////////////////// + + if (IsValid(false)) + { + IssueRollback(false); + } + } + + ////////////////////////////////////// + // release unmanaged resources here... + ////////////////////////////////////// + } + } + finally + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } + } + #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Returns the underlying connection to which this transaction applies. + /// + public new SQLiteConnection Connection + { + get { CheckDisposed(); return _cnn; } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Forwards to the local Connection property + /// + protected override DbConnection DbConnection + { + get { return Connection; } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Rolls back the active transaction. + /// + public override void Rollback() + { + CheckDisposed(); + SQLiteConnection.Check(_cnn); + IsValid(true); + IssueRollback(true); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to start a transaction. An exception will be thrown if the transaction cannot + /// be started for any reason. + /// + /// TRUE to defer the writelock, or FALSE to lock immediately + protected abstract void Begin(bool deferredLock); + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Issue a ROLLBACK command against the database connection, + /// optionally re-throwing any caught exception. + /// + /// + /// Non-zero to re-throw caught exceptions. + /// + protected abstract void IssueRollback(bool throwError); + + /////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Checks the state of this transaction, optionally throwing an exception if a state + /// inconsistency is found. + /// + /// + /// Non-zero to throw an exception if a state inconsistency is found. + /// + /// + /// Non-zero if this transaction is valid; otherwise, false. + /// + internal bool IsValid(bool throwError) + { + if (_cnn == null) + { + if (throwError == true) throw new ArgumentNullException("No connection associated with this transaction"); + else return false; + } + + if (_cnn._version != _version) + { + if (throwError == true) throw new SQLiteException("The connection was closed and re-opened, changes were already rolled back"); + else return false; + } + if (_cnn.State != ConnectionState.Open) + { + if (throwError == true) throw new SQLiteException("Connection was closed"); + else return false; + } + + if (_cnn._transactionLevel == 0 || _cnn._sql.AutoCommit == true) + { + _cnn._transactionLevel = 0; // Make sure the transaction level is reset before returning + if (throwError == true) throw new SQLiteException("No transaction is active on this connection"); + else return false; + } + + return true; + } + } +} diff --git a/Native.Csharp.Tool/SQLite/UnsafeNativeMethods.cs b/Native.Csharp.Tool/SQLite/UnsafeNativeMethods.cs new file mode 100644 index 00000000..f5db906f --- /dev/null +++ b/Native.Csharp.Tool/SQLite/UnsafeNativeMethods.cs @@ -0,0 +1,6013 @@ +/******************************************************** + * ADO.NET 2.0 Data Provider for SQLite Version 3.X + * Written by Robert Simpson (robert@blackcastlesoft.com) + * + * Released to the public domain, use at your own risk! + ********************************************************/ + +namespace System.Data.SQLite +{ + using System; + using System.Globalization; + +#if TRACE_DETECTION || TRACE_SHARED || TRACE_PRELOAD || TRACE_HANDLE + using System.Diagnostics; +#endif + + using System.Collections.Generic; + using System.IO; + using System.Reflection; + +#if !PLATFORM_COMPACTFRAMEWORK + using System.Security; +#endif + + using System.Runtime.InteropServices; + +#if (NET_40 || NET_45 || NET_451 || NET_452 || NET_46 || NET_461 || NET_462 || NET_47 || NET_471 || NET_472) && !PLATFORM_COMPACTFRAMEWORK + using System.Runtime.Versioning; +#endif + + using System.Text; + +#if !PLATFORM_COMPACTFRAMEWORK || COUNT_HANDLE + using System.Threading; +#endif + + using System.Xml; + + #region Debug Data Static Class +#if COUNT_HANDLE || DEBUG + /// + /// This class encapsulates some tracking data that is used for debugging + /// and testing purposes. + /// + internal static class DebugData + { + #region Private Data +#if DEBUG + /// + /// This lock is used to protect several static fields. + /// + private static readonly object staticSyncRoot = new object(); +#endif + + ///////////////////////////////////////////////////////////////////////// + + #region Critical Handle Counts (Debug Build Only) +#if COUNT_HANDLE + // + // NOTE: These counts represent the total number of outstanding + // (non-disposed) CriticalHandle derived object instances + // created by this library and are primarily for use by + // the test suite. These counts are incremented by the + // associated constructors and are decremented upon the + // successful completion of the associated ReleaseHandle + // methods. + // + internal static int connectionCount; + internal static int statementCount; + internal static int backupCount; + internal static int blobCount; +#endif + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Settings Read Counts (Debug Build Only) +#if DEBUG + /// + /// This dictionary stores the read counts for the runtime configuration + /// settings. This information is only recorded when compiled in the + /// "Debug" build configuration. + /// + private static Dictionary settingReadCounts; + + ///////////////////////////////////////////////////////////////////////// + + /// + /// This dictionary stores the read counts for the runtime configuration + /// settings via the XML configuration file. This information is only + /// recorded when compiled in the "Debug" build configuration. + /// + private static Dictionary settingFileReadCounts; +#endif + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Other Counts (Debug Build Only) +#if DEBUG + /// + /// This dictionary stores miscellaneous counts used for debugging + /// purposes. This information is only recorded when compiled in the + /// "Debug" build configuration. + /// + private static Dictionary otherCounts; +#endif + #endregion + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Public Methods +#if DEBUG + /// + /// Creates dictionaries used to store the read counts for each of + /// the runtime configuration settings. These numbers are used for + /// debugging and testing purposes only. + /// + public static void Initialize() + { + lock (staticSyncRoot) + { + // + // NOTE: Create the dictionaries of statistics that will + // contain the number of times each setting value + // has been read. + // + if (settingReadCounts == null) + settingReadCounts = new Dictionary(); + + if (settingFileReadCounts == null) + settingFileReadCounts = new Dictionary(); + + if (otherCounts == null) + otherCounts = new Dictionary(); + } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Queries the read counts for the runtime configuration settings. + /// These numbers are used for debugging and testing purposes only. + /// + /// + /// Non-zero if the specified settings were read from the XML + /// configuration file. + /// + /// + /// A copy of the statistics for the specified runtime configuration + /// settings -OR- null if they are not available. + /// + public static object GetSettingReadCounts( + bool viaFile + ) + { + lock (staticSyncRoot) + { + if (viaFile) + { + if (settingFileReadCounts == null) + return null; + + return new Dictionary(settingFileReadCounts); + } + else + { + if (settingReadCounts == null) + return null; + + return new Dictionary(settingReadCounts); + } + } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Clears the read counts for the runtime configuration settings. + /// These numbers are used for debugging and testing purposes only. + /// + /// + /// Non-zero if the specified settings were read from the XML + /// configuration file. + /// + public static void ClearSettingReadCounts( + bool viaFile + ) + { + lock (staticSyncRoot) + { + if (viaFile) + { + if (settingFileReadCounts != null) + settingFileReadCounts.Clear(); + } + else + { + if (settingReadCounts != null) + settingReadCounts.Clear(); + } + } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Increments the read count for the specified runtime configuration + /// setting. These numbers are used for debugging and testing purposes + /// only. + /// + /// + /// The name of the setting being read. + /// + /// + /// Non-zero if the specified setting is being read from the XML + /// configuration file. + /// + public static void IncrementSettingReadCount( + string name, + bool viaFile + ) + { + lock (staticSyncRoot) + { + // + // NOTE: Update statistics for this setting value. + // + if (viaFile) + { + if (settingFileReadCounts != null) + { + int count; + + if (settingFileReadCounts.TryGetValue(name, out count)) + settingFileReadCounts[name] = count + 1; + else + settingFileReadCounts.Add(name, 1); + } + } + else + { + if (settingReadCounts != null) + { + int count; + + if (settingReadCounts.TryGetValue(name, out count)) + settingReadCounts[name] = count + 1; + else + settingReadCounts.Add(name, 1); + } + } + } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Queries the counters. These numbers are used for debugging and + /// testing purposes only. + /// + /// + /// A copy of the counters -OR- null if they are not available. + /// + public static object GetOtherCounts() + { + lock (staticSyncRoot) + { + if (otherCounts == null) + return null; + + return new Dictionary(otherCounts); + } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Clears the counters. These numbers are used for debugging and + /// testing purposes only. + /// + public static void ClearOtherCounts() + { + lock (staticSyncRoot) + { + if (otherCounts != null) + otherCounts.Clear(); + } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Increments the specified counter. + /// + /// + /// The name of the counter being incremented. + /// + public static void IncrementOtherCount( + string name + ) + { + lock (staticSyncRoot) + { + if (otherCounts != null) + { + int count; + + if (otherCounts.TryGetValue(name, out count)) + otherCounts[name] = count + 1; + else + otherCounts.Add(name, 1); + } + } + } +#endif + #endregion + } +#endif + #endregion + + ///////////////////////////////////////////////////////////////////////////// + + #region Helper Methods Static Class + /// + /// This static class provides some methods that are shared between the + /// native library pre-loader and other classes. + /// + internal static class HelperMethods + { + #region Private Constants + private const string DisplayNullObject = ""; + private const string DisplayEmptyString = ""; + private const string DisplayStringFormat = "\"{0}\""; + + ///////////////////////////////////////////////////////////////////////// + + private const string DisplayNullArray = ""; + private const string DisplayEmptyArray = ""; + + ///////////////////////////////////////////////////////////////////////// + + private const char ArrayOpen = '['; + private const string ElementSeparator = ", "; + private const char ArrayClose = ']'; + + ///////////////////////////////////////////////////////////////////////// + + private static readonly char[] SpaceChars = { + '\t', '\n', '\r', '\v', '\f', ' ' + }; + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Private Data + /// + /// This lock is used to protect the static and + /// fields. + /// + private static readonly object staticSyncRoot = new object(); + + ///////////////////////////////////////////////////////////////////////// + /// + /// This type is only present when running on Mono. + /// + private static readonly string MonoRuntimeType = "Mono.Runtime"; + + ///////////////////////////////////////////////////////////////////////// + /// + /// This type is only present when running on .NET Core. + /// + private static readonly string DotNetCoreLibType = "System.CoreLib"; + + ///////////////////////////////////////////////////////////////////////// + /// + /// Keeps track of whether we are running on Mono. Initially null, it is + /// set by the method on its first call. Later, it + /// is returned verbatim by the method. + /// + private static bool? isMono = null; + + ///////////////////////////////////////////////////////////////////////// + /// + /// Keeps track of whether we are running on .NET Core. Initially null, + /// it is set by the method on its first + /// call. Later, it is returned verbatim by the + /// method. + /// + private static bool? isDotNetCore = null; + + ///////////////////////////////////////////////////////////////////////// + /// + /// Keeps track of whether we successfully invoked the + /// method. Initially null, it is set by + /// the method on its first call. + /// + private static bool? debuggerBreak = null; + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// Determines the ID of the current process. Only used for debugging. + /// + /// + /// The ID of the current process -OR- zero if it cannot be determined. + /// + private static int GetProcessId() + { + Process process = Process.GetCurrentProcess(); + + if (process == null) + return 0; + + return process.Id; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Determines whether or not this assembly is running on Mono. + /// + /// + /// Non-zero if this assembly is running on Mono. + /// + private static bool IsMono() + { + try + { + lock (staticSyncRoot) + { + if (isMono == null) + isMono = (Type.GetType(MonoRuntimeType) != null); + + return (bool)isMono; + } + } + catch + { + // do nothing. + } + + return false; + } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Determines whether or not this assembly is running on .NET Core. + /// + /// + /// Non-zero if this assembly is running on .NET Core. + /// + public static bool IsDotNetCore() + { + try + { + lock (staticSyncRoot) + { + if (isDotNetCore == null) + { + isDotNetCore = (Type.GetType( + DotNetCoreLibType) != null); + } + + return (bool)isDotNetCore; + } + } + catch + { + // do nothing. + } + + return false; + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Internal Methods + /// + /// Resets the cached value for the "PreLoadSQLite_BreakIntoDebugger" + /// configuration setting. + /// + internal static void ResetBreakIntoDebugger() + { + lock (staticSyncRoot) + { + debuggerBreak = null; + } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// If the "PreLoadSQLite_BreakIntoDebugger" configuration setting is + /// present (e.g. via the environment), give the interactive user an + /// opportunity to attach a debugger to the current process; otherwise, + /// do nothing. + /// + internal static void MaybeBreakIntoDebugger() + { + lock (staticSyncRoot) + { + if (debuggerBreak != null) + return; + } + + if (UnsafeNativeMethods.GetSettingValue( + "PreLoadSQLite_BreakIntoDebugger", null) != null) + { + // + // NOTE: Attempt to use the Console in order to prompt the + // interactive user (if any). This may fail for any + // number of reasons. Even in those cases, we still + // want to issue the actual request to break into the + // debugger. + // + try + { + Console.WriteLine(StringFormat( + CultureInfo.CurrentCulture, + "Attach a debugger to process {0} " + + "and press any key to continue.", + GetProcessId())); + +#if PLATFORM_COMPACTFRAMEWORK + Console.ReadLine(); +#else + Console.ReadKey(); +#endif + } +#if !NET_COMPACT_20 && TRACE_SHARED + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_SHARED + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Failed to issue debugger prompt, " + + "{0} may be unusable: {1}", + typeof(Console), e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + } + + try + { + Debugger.Break(); + + lock (staticSyncRoot) + { + debuggerBreak = true; + } + } + catch + { + lock (staticSyncRoot) + { + debuggerBreak = false; + } + + throw; + } + } + else + { + // + // BUGFIX: There is (almost) no point in checking for the + // associated configuration setting repeatedly. + // Prevent that here by setting the cached value + // to false. + // + lock (staticSyncRoot) + { + debuggerBreak = false; + } + } + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Determines the ID of the current thread. Only used for debugging. + /// + /// + /// The ID of the current thread -OR- zero if it cannot be determined. + /// + internal static int GetThreadId() + { +#if !PLATFORM_COMPACTFRAMEWORK + return AppDomain.GetCurrentThreadId(); +#else + return 0; +#endif + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Determines if the specified flags are present within the flags + /// associated with the parent connection object. + /// + /// + /// The flags associated with the parent connection object. + /// + /// + /// The flags to check for. + /// + /// + /// Non-zero if the specified flag or flags were present; otherwise, + /// zero. + /// + internal static bool HasFlags( + SQLiteConnectionFlags flags, + SQLiteConnectionFlags hasFlags + ) + { + return ((flags & hasFlags) == hasFlags); + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Determines if preparing a query should be logged. + /// + /// + /// The flags associated with the parent connection object. + /// + /// + /// Non-zero if the query preparation should be logged; otherwise, zero. + /// + internal static bool LogPrepare( + SQLiteConnectionFlags flags + ) + { + return HasFlags(flags, SQLiteConnectionFlags.LogPrepare); + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Determines if pre-parameter binding should be logged. + /// + /// + /// The flags associated with the parent connection object. + /// + /// + /// Non-zero if the pre-parameter binding should be logged; otherwise, + /// zero. + /// + internal static bool LogPreBind( + SQLiteConnectionFlags flags + ) + { + return HasFlags(flags, SQLiteConnectionFlags.LogPreBind); + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Determines if parameter binding should be logged. + /// + /// + /// The flags associated with the parent connection object. + /// + /// + /// Non-zero if the parameter binding should be logged; otherwise, zero. + /// + internal static bool LogBind( + SQLiteConnectionFlags flags + ) + { + return HasFlags(flags, SQLiteConnectionFlags.LogBind); + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Determines if an exception in a native callback should be logged. + /// + /// + /// The flags associated with the parent connection object. + /// + /// + /// Non-zero if the exception should be logged; otherwise, zero. + /// + internal static bool LogCallbackExceptions( + SQLiteConnectionFlags flags + ) + { + return HasFlags(flags, SQLiteConnectionFlags.LogCallbackException); + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Determines if backup API errors should be logged. + /// + /// + /// The flags associated with the parent connection object. + /// + /// + /// Non-zero if the backup API error should be logged; otherwise, zero. + /// + internal static bool LogBackup( + SQLiteConnectionFlags flags + ) + { + return HasFlags(flags, SQLiteConnectionFlags.LogBackup); + } + +#if INTEROP_VIRTUAL_TABLE + ///////////////////////////////////////////////////////////////////////// + /// + /// Determines if logging for the class is + /// disabled. + /// + /// + /// The flags associated with the parent connection object. + /// + /// + /// Non-zero if logging for the class is + /// disabled; otherwise, zero. + /// + internal static bool NoLogModule( + SQLiteConnectionFlags flags + ) + { + return HasFlags(flags, SQLiteConnectionFlags.NoLogModule); + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Determines if errors should be logged. + /// + /// + /// The flags associated with the parent connection object. + /// + /// + /// Non-zero if the error should be logged; + /// otherwise, zero. + /// + internal static bool LogModuleError( + SQLiteConnectionFlags flags + ) + { + return HasFlags(flags, SQLiteConnectionFlags.LogModuleError); + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Determines if exceptions should be + /// logged. + /// + /// + /// The flags associated with the parent connection object. + /// + /// + /// Non-zero if the exception should be + /// logged; otherwise, zero. + /// + internal static bool LogModuleException( + SQLiteConnectionFlags flags + ) + { + return HasFlags(flags, SQLiteConnectionFlags.LogModuleException); + } +#endif + + ///////////////////////////////////////////////////////////////////////// + /// + /// Determines if the current process is running on one of the Windows + /// [sub-]platforms. + /// + /// + /// Non-zero when running on Windows; otherwise, zero. + /// + internal static bool IsWindows() + { + PlatformID platformId = Environment.OSVersion.Platform; + + if ((platformId == PlatformID.Win32S) || + (platformId == PlatformID.Win32Windows) || + (platformId == PlatformID.Win32NT) || + (platformId == PlatformID.WinCE)) + { + return true; + } + + return false; + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// This is a wrapper around the + /// method. + /// On Mono, it has to call the method overload without the + /// parameter, due to a bug in Mono. + /// + /// + /// This is used for culture-specific formatting. + /// + /// + /// The format string. + /// + /// + /// An array the objects to format. + /// + /// + /// The resulting string. + /// + internal static string StringFormat( + IFormatProvider provider, + string format, + params object[] args + ) + { + if (IsMono()) + return String.Format(format, args); + else + return String.Format(provider, format, args); + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Public Methods + public static string ToDisplayString( + object value + ) + { + if (value == null) + return DisplayNullObject; + + string stringValue = value.ToString(); + + if (stringValue.Length == 0) + return DisplayEmptyString; + + if (stringValue.IndexOfAny(SpaceChars) < 0) + return stringValue; + + return HelperMethods.StringFormat( + CultureInfo.InvariantCulture, DisplayStringFormat, + stringValue); + } + + ///////////////////////////////////////////////////////////////////////// + + public static string ToDisplayString( + Array array + ) + { + if (array == null) + return DisplayNullArray; + + if (array.Length == 0) + return DisplayEmptyArray; + + StringBuilder result = new StringBuilder(); + + foreach (object value in array) + { + if (result.Length > 0) + result.Append(ElementSeparator); + + result.Append(ToDisplayString(value)); + } + + if (result.Length > 0) + { +#if PLATFORM_COMPACTFRAMEWORK + result.Insert(0, ArrayOpen.ToString()); +#else + result.Insert(0, ArrayOpen); +#endif + + result.Append(ArrayClose); + } + + return result.ToString(); + } + #endregion + } + #endregion + + ///////////////////////////////////////////////////////////////////////////// + + #region Native Library Helper Class + /// + /// This static class provides a thin wrapper around the native library + /// loading features of the underlying platform. + /// + internal static class NativeLibraryHelper + { + #region Private Delegates + /// + /// This delegate is used to wrap the concept of loading a native + /// library, based on a file name, and returning the loaded module + /// handle. + /// + /// + /// The file name of the native library to load. + /// + /// + /// The native module handle upon success -OR- IntPtr.Zero on failure. + /// + private delegate IntPtr LoadLibraryCallback( + string fileName + ); + + ///////////////////////////////////////////////////////////////////////// + + /// + /// This delegate is used to wrap the concept of querying the machine + /// name of the current process. + /// + /// + /// The machine name for the current process -OR- null on failure. + /// + private delegate string GetMachineCallback(); + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// Attempts to load the specified native library file using the Win32 + /// API. + /// + /// + /// The file name of the native library to load. + /// + /// + /// The native module handle upon success -OR- IntPtr.Zero on failure. + /// + private static IntPtr LoadLibraryWin32( + string fileName + ) + { + return UnsafeNativeMethodsWin32.LoadLibrary(fileName); + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to determine the machine name of the current process using + /// the Win32 API. + /// + /// + /// The machine name for the current process -OR- null on failure. + /// + private static string GetMachineWin32() + { + // + // NOTE: When running on Windows, attempt to use the native Win32 + // API function (via P/Invoke) that can provide us with the + // processor architecture. + // + try + { + UnsafeNativeMethodsWin32.SYSTEM_INFO systemInfo; + + // + // NOTE: Query the system information via P/Invoke, thus + // filling the structure. + // + UnsafeNativeMethodsWin32.GetSystemInfo(out systemInfo); + + // + // NOTE: Return the processor architecture value as a string. + // + return systemInfo.wProcessorArchitecture.ToString(); + } + catch + { + // do nothing. + } + + return null; + } + + ///////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// Attempts to load the specified native library file using the POSIX + /// API. + /// + /// + /// The file name of the native library to load. + /// + /// + /// The native module handle upon success -OR- IntPtr.Zero on failure. + /// + private static IntPtr LoadLibraryPosix( + string fileName + ) + { + return UnsafeNativeMethodsPosix.dlopen( + fileName, UnsafeNativeMethodsPosix.RTLD_DEFAULT); + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to determine the machine name of the current process using + /// the POSIX API. + /// + /// + /// The machine name for the current process -OR- null on failure. + /// + private static string GetMachinePosix() + { + // + // NOTE: When running on POSIX (non-Windows), attempt to query the + // machine from the operating system via uname(). + // + try + { + UnsafeNativeMethodsPosix.utsname utsName = null; + + if (UnsafeNativeMethodsPosix.GetOsVersionInfo(ref utsName) && + (utsName != null)) + { + return utsName.machine; + } + } + catch + { + // do nothing. + } + + return null; + } +#endif + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Public Methods + /// + /// Attempts to load the specified native library file. + /// + /// + /// The file name of the native library to load. + /// + /// + /// The native module handle upon success -OR- IntPtr.Zero on failure. + /// + public static IntPtr LoadLibrary( + string fileName + ) + { + LoadLibraryCallback callback = LoadLibraryWin32; + +#if !PLATFORM_COMPACTFRAMEWORK + if (!HelperMethods.IsWindows()) + callback = LoadLibraryPosix; +#endif + + return callback(fileName); + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Attempts to determine the machine name of the current process. + /// + /// + /// The machine name for the current process -OR- null on failure. + /// + public static string GetMachine() + { + GetMachineCallback callback = GetMachineWin32; + +#if !PLATFORM_COMPACTFRAMEWORK + if (!HelperMethods.IsWindows()) + callback = GetMachinePosix; +#endif + + return callback(); + } + #endregion + } + #endregion + + ///////////////////////////////////////////////////////////////////////////// + + #region Unmanaged Interop Methods Static Class (POSIX) +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// This class declares P/Invoke methods to call native POSIX APIs. + /// + [SuppressUnmanagedCodeSecurity] + internal static class UnsafeNativeMethodsPosix + { + /// + /// This structure is used when running on POSIX operating systems + /// to store information about the current machine, including the + /// human readable name of the operating system as well as that of + /// the underlying hardware. + /// + internal sealed class utsname + { + public string sysname; /* Name of this implementation of + * the operating system. */ + public string nodename; /* Name of this node within the + * communications network to which + * this node is attached, if any. */ + public string release; /* Current release level of this + * implementation. */ + public string version; /* Current version level of this + * release. */ + public string machine; /* Name of the hardware type on + * which the system is running. */ + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// This structure is passed directly to the P/Invoke method to + /// obtain the information about the current machine, including + /// the human readable name of the operating system as well as + /// that of the underlying hardware. + /// + [StructLayout(LayoutKind.Sequential)] + private struct utsname_interop + { + // + // NOTE: The following string fields should be present in + // this buffer, all of which will be zero-terminated: + // + // sysname + // nodename + // release + // version + // machine + // + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4096)] + public byte[] buffer; + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// This is the P/Invoke method that wraps the native Unix uname + /// function. See the POSIX documentation for full details on what it + /// does. + /// + /// + /// Structure containing a preallocated byte buffer to fill with the + /// requested information. + /// + /// + /// Zero for success and less than zero upon failure. + /// +#if NET_STANDARD_20 + [DllImport("libc", +#else + [DllImport("__Internal", +#endif + CallingConvention = CallingConvention.Cdecl)] + private static extern int uname(out utsname_interop name); + + ///////////////////////////////////////////////////////////////////////// + /// + /// This is the P/Invoke method that wraps the native Unix dlopen + /// function. See the POSIX documentation for full details on what it + /// does. + /// + /// + /// The name of the executable library. + /// + /// + /// This must be a combination of the individual bit flags RTLD_LAZY, + /// RTLD_NOW, RTLD_GLOBAL, and/or RTLD_LOCAL. + /// + /// + /// The native module handle upon success -OR- IntPtr.Zero on failure. + /// +#if NET_STANDARD_20 + [DllImport("libdl", +#else + [DllImport("__Internal", +#endif + EntryPoint = "dlopen", + CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, + BestFitMapping = false, ThrowOnUnmappableChar = true, + SetLastError = true)] + internal static extern IntPtr dlopen(string fileName, int mode); + + ///////////////////////////////////////////////////////////////////////// + /// + /// This is the P/Invoke method that wraps the native Unix dlclose + /// function. See the POSIX documentation for full details on what it + /// does. + /// + /// + /// The handle to the loaded native library. + /// + /// + /// Zero upon success -OR- non-zero on failure. + /// +#if NET_STANDARD_20 + [DllImport("libdl", +#else + [DllImport("__Internal", +#endif + EntryPoint = "dlclose", + CallingConvention = CallingConvention.Cdecl, SetLastError = true)] + internal static extern int dlclose(IntPtr module); + + ///////////////////////////////////////////////////////////////////////// + + #region Private Constants + /// + /// For use with dlopen(), bind function calls lazily. + /// + internal const int RTLD_LAZY = 0x1; + + ///////////////////////////////////////////////////////////////////////// + /// + /// For use with dlopen(), bind function calls immediately. + /// + internal const int RTLD_NOW = 0x2; + + ///////////////////////////////////////////////////////////////////////// + /// + /// For use with dlopen(), make symbols globally available. + /// + internal const int RTLD_GLOBAL = 0x100; + + ///////////////////////////////////////////////////////////////////////// + /// + /// For use with dlopen(), opposite of RTLD_GLOBAL, and the default. + /// + internal const int RTLD_LOCAL = 0x000; + + ///////////////////////////////////////////////////////////////////////// + /// + /// For use with dlopen(), the defaults used by this class. + /// + internal const int RTLD_DEFAULT = RTLD_NOW | RTLD_GLOBAL; + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Private Data + /// + /// These are the characters used to separate the string fields within + /// the raw buffer returned by the P/Invoke method. + /// + private static readonly char[] utsNameSeparators = { '\0' }; + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Private Methods + /// + /// This method is a wrapper around the P/Invoke + /// method that extracts and returns the human readable strings from + /// the raw buffer. + /// + /// + /// This structure, which contains strings, will be filled based on the + /// data placed in the raw buffer returned by the + /// P/Invoke method. + /// + /// + /// Non-zero upon success; otherwise, zero. + /// + internal static bool GetOsVersionInfo( + ref utsname utsName + ) + { + try + { + utsname_interop utfNameInterop; + + if (uname(out utfNameInterop) < 0) + return false; + + if (utfNameInterop.buffer == null) + return false; + + string bufferAsString = Encoding.UTF8.GetString( + utfNameInterop.buffer); + + if ((bufferAsString == null) || (utsNameSeparators == null)) + return false; + + bufferAsString = bufferAsString.Trim(utsNameSeparators); + + string[] parts = bufferAsString.Split( + utsNameSeparators, StringSplitOptions.RemoveEmptyEntries); + + if (parts == null) + return false; + + utsname localUtsName = new utsname(); + + if (parts.Length >= 1) + localUtsName.sysname = parts[0]; + + if (parts.Length >= 2) + localUtsName.nodename = parts[1]; + + if (parts.Length >= 3) + localUtsName.release = parts[2]; + + if (parts.Length >= 4) + localUtsName.version = parts[3]; + + if (parts.Length >= 5) + localUtsName.machine = parts[4]; + + utsName = localUtsName; + return true; + } + catch + { + // do nothing. + } + + return false; + } + #endregion + } +#endif + #endregion + + ///////////////////////////////////////////////////////////////////////////// + + #region Unmanaged Interop Methods Static Class (Win32) + /// + /// This class declares P/Invoke methods to call native Win32 APIs. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [SuppressUnmanagedCodeSecurity] +#endif + internal static class UnsafeNativeMethodsWin32 + { + ///////////////////////////////////////////////////////////////////////// + /// + /// This is the P/Invoke method that wraps the native Win32 LoadLibrary + /// function. See the MSDN documentation for full details on what it + /// does. + /// + /// + /// The name of the executable library. + /// + /// + /// The native module handle upon success -OR- IntPtr.Zero on failure. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport("kernel32", +#else + [DllImport("coredll", +#endif + CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, +#if !PLATFORM_COMPACTFRAMEWORK + BestFitMapping = false, ThrowOnUnmappableChar = true, +#endif + SetLastError = true)] + internal static extern IntPtr LoadLibrary(string fileName); + + ///////////////////////////////////////////////////////////////////////// + + /// + /// This is the P/Invoke method that wraps the native Win32 GetSystemInfo + /// function. See the MSDN documentation for full details on what it + /// does. + /// + /// + /// The system information structure to be filled in by the function. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport("kernel32", +#else + [DllImport("coredll", +#endif + CallingConvention = CallingConvention.Winapi)] + internal static extern void GetSystemInfo(out SYSTEM_INFO systemInfo); + + ///////////////////////////////////////////////////////////////////////// + /// + /// This enumeration contains the possible values for the processor + /// architecture field of the system information structure. + /// + internal enum ProcessorArchitecture : ushort /* COMPAT: Win32. */ + { + Intel = 0, + MIPS = 1, + Alpha = 2, + PowerPC = 3, + SHx = 4, + ARM = 5, + IA64 = 6, + Alpha64 = 7, + MSIL = 8, + AMD64 = 9, + IA32_on_Win64 = 10, + Unknown = 0xFFFF + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// This structure contains information about the current computer. This + /// includes the processor type, page size, memory addresses, etc. + /// + [StructLayout(LayoutKind.Sequential)] + internal struct SYSTEM_INFO + { + public ProcessorArchitecture wProcessorArchitecture; + public ushort wReserved; /* NOT USED */ + public uint dwPageSize; /* NOT USED */ + public IntPtr lpMinimumApplicationAddress; /* NOT USED */ + public IntPtr lpMaximumApplicationAddress; /* NOT USED */ +#if PLATFORM_COMPACTFRAMEWORK + public uint dwActiveProcessorMask; /* NOT USED */ +#else + public IntPtr dwActiveProcessorMask; /* NOT USED */ +#endif + public uint dwNumberOfProcessors; /* NOT USED */ + public uint dwProcessorType; /* NOT USED */ + public uint dwAllocationGranularity; /* NOT USED */ + public ushort wProcessorLevel; /* NOT USED */ + public ushort wProcessorRevision; /* NOT USED */ + } + } + #endregion + + ///////////////////////////////////////////////////////////////////////////// + + #region Unmanaged Interop Methods Static Class (SQLite) + /// + /// This class declares P/Invoke methods to call native SQLite APIs. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [SuppressUnmanagedCodeSecurity] +#endif + internal static class UnsafeNativeMethods + { + public const string ExceptionMessageFormat = + "Caught exception in \"{0}\" method: {1}"; + + ///////////////////////////////////////////////////////////////////////// + + #region Shared Native SQLite Library Pre-Loading Code + #region Private Constants + /// + /// The file extension used for dynamic link libraries. + /// + private static readonly string DllFileExtension = ".dll"; + + ///////////////////////////////////////////////////////////////////////// + /// + /// The file extension used for the XML configuration file. + /// + private static readonly string ConfigFileExtension = ".config"; + + ///////////////////////////////////////////////////////////////////////// + /// + /// This is the name of the XML configuration file specific to the + /// System.Data.SQLite assembly. + /// + private static readonly string XmlConfigFileName = + typeof(UnsafeNativeMethods).Namespace + DllFileExtension + + ConfigFileExtension; + + ///////////////////////////////////////////////////////////////////////// + /// + /// This is the XML configuratrion file token that will be replaced with + /// the qualified path to the directory containing the XML configuration + /// file. + /// + private static readonly string XmlConfigDirectoryToken = + "%PreLoadSQLite_XmlConfigDirectory%"; + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Private Constants (Desktop Framework Only) +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// This is the environment variable token that will be replaced with + /// the qualified path to the directory containing this assembly. + /// + private static readonly string AssemblyDirectoryToken = + "%PreLoadSQLite_AssemblyDirectory%"; + + ///////////////////////////////////////////////////////////////////////// + /// + /// This is the environment variable token that will be replaced with an + /// abbreviation of the target framework attribute value associated with + /// this assembly. + /// + private static readonly string TargetFrameworkToken = + "%PreLoadSQLite_TargetFramework%"; +#endif + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Private Data + /// + /// This lock is used to protect the static _SQLiteNativeModuleFileName, + /// _SQLiteNativeModuleHandle, and processorArchitecturePlatforms fields. + /// + private static readonly object staticSyncRoot = new object(); + + ///////////////////////////////////////////////////////////////////////// + /// + /// This dictionary stores the mappings between processor architecture + /// names and platform names. These mappings are now used for two + /// purposes. First, they are used to determine if the assembly code + /// base should be used instead of the location, based upon whether one + /// or more of the named sub-directories exist within the assembly code + /// base. Second, they are used to assist in loading the appropriate + /// SQLite interop assembly into the current process. + /// + private static Dictionary processorArchitecturePlatforms; + + ///////////////////////////////////////////////////////////////////////// + /// + /// This is the cached return value from the + /// method -OR- null if that method + /// has never returned a valid value. + /// + private static string cachedAssemblyDirectory; + + ///////////////////////////////////////////////////////////////////////// + /// + /// When this field is non-zero, it indicates the + /// method was not able to locate a + /// suitable assembly directory. The + /// method will check this + /// field and skips calls into the + /// method whenever it is non-zero. + /// + private static bool noAssemblyDirectory; + + ///////////////////////////////////////////////////////////////////////// + /// + /// This is the cached return value from the + /// method -OR- null if that method + /// has never returned a valid value. + /// + private static string cachedXmlConfigFileName; + + ///////////////////////////////////////////////////////////////////////// + /// + /// When this field is non-zero, it indicates the + /// method was not able to locate a + /// suitable XML configuration file name. The + /// method will check this + /// field and skips calls into the + /// method whenever it is non-zero. + /// + private static bool noXmlConfigFileName; + #endregion + + ///////////////////////////////////////////////////////////////////////// + /// + /// For now, this method simply calls the Initialize method. + /// + static UnsafeNativeMethods() + { + Initialize(); + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Attempts to initialize this class by pre-loading the native SQLite + /// library for the processor architecture of the current process. + /// + internal static void Initialize() + { + #region Debug Build Only +#if DEBUG + // + // NOTE: Create the lists of statistics that will contain + // various counts used in debugging, including the + // number of times each setting value has been read. + // + DebugData.Initialize(); +#endif + #endregion + + // + // NOTE: Check if a debugger needs to be attached before doing any + // real work. + // + HelperMethods.MaybeBreakIntoDebugger(); + +#if SQLITE_STANDARD || USE_INTEROP_DLL || PLATFORM_COMPACTFRAMEWORK +#if PRELOAD_NATIVE_LIBRARY + // + // NOTE: If the "No_PreLoadSQLite" environment variable is set (to + // anything), skip all of our special code and simply return. + // + if (GetSettingValue("No_PreLoadSQLite", null) != null) + return; +#endif +#endif + + lock (staticSyncRoot) + { + // + // TODO: Make sure this list is updated if the supported + // processor architecture names and/or platform names + // changes. + // + if (processorArchitecturePlatforms == null) + { + // + // NOTE: Create the map of processor architecture names + // to platform names using a case-insensitive string + // comparer. + // + processorArchitecturePlatforms = + new Dictionary( + StringComparer.OrdinalIgnoreCase); + + // + // NOTE: Setup the list of platform names associated with + // the supported processor architectures. + // + processorArchitecturePlatforms.Add("x86", "Win32"); + processorArchitecturePlatforms.Add("x86_64", "x64"); + processorArchitecturePlatforms.Add("AMD64", "x64"); + processorArchitecturePlatforms.Add("IA64", "Itanium"); + processorArchitecturePlatforms.Add("ARM", "WinCE"); + } + +#if SQLITE_STANDARD || USE_INTEROP_DLL || PLATFORM_COMPACTFRAMEWORK +#if PRELOAD_NATIVE_LIBRARY + // + // BUGBUG: What about other application domains? + // + if (_SQLiteNativeModuleHandle == IntPtr.Zero) + { + string baseDirectory = null; + string processorArchitecture = null; + bool allowBaseDirectoryOnly = false; + + /* IGNORED */ + SearchForDirectory( + ref baseDirectory, ref processorArchitecture, + ref allowBaseDirectoryOnly); + + // + // NOTE: Attempt to pre-load the SQLite core library (or + // interop assembly) and store both the file name + // and native module handle for later usage. + // + /* IGNORED */ + PreLoadSQLiteDll(baseDirectory, + processorArchitecture, allowBaseDirectoryOnly, + ref _SQLiteNativeModuleFileName, + ref _SQLiteNativeModuleHandle); + } +#endif +#endif + } + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Combines two path strings. + /// + /// + /// The first path -OR- null. + /// + /// + /// The second path -OR- null. + /// + /// + /// The combined path string -OR- null if both of the original path + /// strings are null. + /// + private static string MaybeCombinePath( + string path1, + string path2 + ) + { + if (path1 != null) + { + if (path2 != null) + return Path.Combine(path1, path2); + else + return path1; + } + else + { + if (path2 != null) + return path2; + else + return null; + } + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Resets the cached XML configuration file name value, thus forcing the + /// next call to method to rely + /// upon the method to fetch the + /// XML configuration file name. + /// + private static void ResetCachedXmlConfigFileName() + { + #region Debug Build Only +#if DEBUG + DebugData.IncrementOtherCount("Method_ResetCachedXmlConfigFileName"); +#endif + #endregion + + lock (staticSyncRoot) + { + cachedXmlConfigFileName = null; + noXmlConfigFileName = false; + } + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Queries and returns the cached XML configuration file name for the + /// assembly containing the managed System.Data.SQLite components, if + /// available. If the cached XML configuration file name value is not + /// available, the method will + /// be used to obtain the XML configuration file name. + /// + /// + /// The XML configuration file name -OR- null if it cannot be determined + /// or does not exist. + /// + private static string GetCachedXmlConfigFileName() + { + #region Debug Build Only +#if DEBUG + DebugData.IncrementOtherCount("Method_GetCachedXmlConfigFileName"); +#endif + #endregion + + lock (staticSyncRoot) + { + if (cachedXmlConfigFileName != null) + return cachedXmlConfigFileName; + + if (noXmlConfigFileName) + return null; + } + + return GetXmlConfigFileName(); + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Queries and returns the XML configuration file name for the assembly + /// containing the managed System.Data.SQLite components. + /// + /// + /// The XML configuration file name -OR- null if it cannot be determined + /// or does not exist. + /// + private static string GetXmlConfigFileName() + { + #region Debug Build Only +#if DEBUG + DebugData.IncrementOtherCount("Method_GetXmlConfigFileName"); +#endif + #endregion + + string directory; + string fileName; + +#if !PLATFORM_COMPACTFRAMEWORK + directory = AppDomain.CurrentDomain.BaseDirectory; + fileName = MaybeCombinePath(directory, XmlConfigFileName); + + if (File.Exists(fileName)) + { + lock (staticSyncRoot) + { + cachedXmlConfigFileName = fileName; + } + + return fileName; + } +#endif + + directory = GetCachedAssemblyDirectory(); + fileName = MaybeCombinePath(directory, XmlConfigFileName); + + if (File.Exists(fileName)) + { + lock (staticSyncRoot) + { + cachedXmlConfigFileName = fileName; + } + + return fileName; + } + + lock (staticSyncRoot) + { + noXmlConfigFileName = true; + } + + return null; + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// If necessary, replaces all supported XML configuration file tokens + /// with their associated values. + /// + /// + /// The name of the XML configuration file being read. + /// + /// + /// A setting value read from the XML configuration file. + /// + /// + /// The value of the will all supported XML + /// configuration file tokens replaced. No return value is reserved + /// to indicate an error. This method cannot fail. + /// + private static string ReplaceXmlConfigFileTokens( + string fileName, + string value + ) + { + if (!String.IsNullOrEmpty(value)) + { + if (!String.IsNullOrEmpty(fileName)) + { + try + { + string directory = Path.GetDirectoryName(fileName); + + if (!String.IsNullOrEmpty(directory)) + { + value = value.Replace( + XmlConfigDirectoryToken, directory); + } + } +#if !NET_COMPACT_20 && TRACE_SHARED + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_SHARED + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, "Native library " + + "pre-loader failed to replace XML " + + "configuration file \"{0}\" tokens: {1}", + fileName, e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + } + } + } + + return value; + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Queries and returns the value of the specified setting, using the + /// specified XML configuration file. + /// + /// + /// The name of the XML configuration file to read. + /// + /// + /// The name of the setting. + /// + /// + /// The value to be returned if the setting has not been set explicitly + /// or cannot be determined. + /// + /// + /// Non-zero to expand any environment variable references contained in + /// the setting value to be returned. This has no effect on the .NET + /// Compact Framework. + /// + /// + /// The value of the setting -OR- the default value specified by + /// if it has not been set explicitly or + /// cannot be determined. + /// + private static string GetSettingValueViaXmlConfigFile( + string fileName, /* in */ + string name, /* in */ + string @default, /* in */ + bool expand /* in */ + ) + { + try + { + if ((fileName == null) || (name == null)) + return @default; + + XmlDocument document = new XmlDocument(); + + document.Load(fileName); /* throw */ + + XmlElement element = document.SelectSingleNode( + HelperMethods.StringFormat(CultureInfo.InvariantCulture, + "/configuration/appSettings/add[@key='{0}']", name)) as + XmlElement; /* throw */ + + if (element != null) + { + string value = null; + + if (element.HasAttribute("value")) + value = element.GetAttribute("value"); + + if (!String.IsNullOrEmpty(value)) + { +#if !PLATFORM_COMPACTFRAMEWORK + if (expand) + value = Environment.ExpandEnvironmentVariables(value); + + value = ReplaceEnvironmentVariableTokens(value); +#endif + + value = ReplaceXmlConfigFileTokens(fileName, value); + } + + if (value != null) + return value; + } + } +#if !NET_COMPACT_20 && TRACE_SHARED + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_SHARED + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, "Native library " + + "pre-loader failed to get setting \"{0}\" value " + + "from XML configuration file \"{1}\": {2}", name, + fileName, e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + } + + return @default; + } + + ///////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + /// + /// Attempts to determine the target framework attribute value that is + /// associated with the specified managed assembly, if applicable. + /// + /// + /// The managed assembly to read the target framework attribute value + /// from. + /// + /// + /// The value of the target framework attribute value for the specified + /// managed assembly -OR- null if it cannot be determined. If this + /// assembly was compiled with a version of the .NET Framework prior to + /// version 4.0, the value returned MAY reflect that version of the .NET + /// Framework instead of the one associated with the specified managed + /// assembly. + /// + private static string GetAssemblyTargetFramework( + Assembly assembly + ) + { + if (assembly != null) + { +#if NET_40 || NET_45 || NET_451 || NET_452 || NET_46 || NET_461 || NET_462 || NET_47 || NET_471 || NET_472 || NET_STANDARD_20 + try + { + if (assembly.IsDefined( + typeof(TargetFrameworkAttribute), false)) + { + TargetFrameworkAttribute targetFramework = + (TargetFrameworkAttribute) + assembly.GetCustomAttributes( + typeof(TargetFrameworkAttribute), false)[0]; + + return targetFramework.FrameworkName; + } + } + catch + { + // do nothing. + } +#elif NET_35 + return ".NETFramework,Version=v3.5"; +#elif NET_20 + return ".NETFramework,Version=v2.0"; +#endif + } + + return null; + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Accepts a long target framework attribute value and makes it into a + /// much shorter version, suitable for use with NuGet packages. + /// + /// + /// The long target framework attribute value to convert. + /// + /// + /// The short target framework attribute value -OR- null if it cannot + /// be determined or converted. + /// + private static string AbbreviateTargetFramework( + string value + ) + { + if (String.IsNullOrEmpty(value)) + return value; + + value = value.Replace(".NETFramework,Version=v", "net"); + value = value.Replace(".", String.Empty); + + int index = value.IndexOf(','); + + if (index != -1) + value = value.Substring(0, index); + + return value; + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// If necessary, replaces all supported environment variable tokens + /// with their associated values. + /// + /// + /// A setting value read from an environment variable. + /// + /// + /// The value of the will all supported + /// environment variable tokens replaced. No return value is reserved + /// to indicate an error. This method cannot fail. + /// + private static string ReplaceEnvironmentVariableTokens( + string value + ) + { + if (!String.IsNullOrEmpty(value)) + { + string directory = GetCachedAssemblyDirectory(); + + if (!String.IsNullOrEmpty(directory)) + { + try + { + value = value.Replace( + AssemblyDirectoryToken, directory); + } +#if !NET_COMPACT_20 && TRACE_SHARED + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_SHARED + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, "Native library " + + "pre-loader failed to replace assembly " + + "directory token: {0}", e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + } + } + + Assembly assembly = null; + + try + { + assembly = Assembly.GetExecutingAssembly(); + } +#if !NET_COMPACT_20 && TRACE_SHARED + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_SHARED + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, "Native library " + + "pre-loader failed to obtain executing " + + "assembly: {0}", e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + } + + string targetFramework = AbbreviateTargetFramework( + GetAssemblyTargetFramework(assembly)); + + if (!String.IsNullOrEmpty(targetFramework)) + { + try + { + value = value.Replace( + TargetFrameworkToken, targetFramework); + } +#if !NET_COMPACT_20 && TRACE_SHARED + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_SHARED + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, "Native library " + + "pre-loader failed to replace target " + + "framework token: {0}", e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + } + } + } + + return value; + } +#endif + + ///////////////////////////////////////////////////////////////////////// + /// + /// Queries and returns the value of the specified setting, using the XML + /// configuration file and/or the environment variables for the current + /// process and/or the current system, when available. + /// + /// + /// The name of the setting. + /// + /// + /// The value to be returned if the setting has not been set explicitly + /// or cannot be determined. + /// + /// + /// The value of the setting -OR- the default value specified by + /// if it has not been set explicitly or + /// cannot be determined. By default, all references to existing + /// environment variables will be expanded to their corresponding values + /// within the value to be returned unless either the "No_Expand" or + /// "No_Expand_" environment variable is set [to + /// anything]. + /// + internal static string GetSettingValue( + string name, /* in */ + string @default /* in */ + ) + { +#if !PLATFORM_COMPACTFRAMEWORK + // + // NOTE: If the special "No_SQLiteGetSettingValue" environment + // variable is set [to anything], this method will always + // return the default value. + // + if (Environment.GetEnvironmentVariable( + "No_SQLiteGetSettingValue") != null) + { + return @default; + } +#endif + + ///////////////////////////////////////////////////////////////////// + + if (name == null) + return @default; + + ///////////////////////////////////////////////////////////////////// + + #region Debug Build Only +#if DEBUG + // + // NOTE: We are about to read a setting value from the environment + // or possibly from the XML configuration file; create or + // increment the appropriate statistic now. + // + DebugData.IncrementSettingReadCount(name, false); +#endif + #endregion + + ///////////////////////////////////////////////////////////////////// + + bool expand = true; /* SHARED: Environment -AND- XML config file. */ + + ///////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + string value = null; + + if (Environment.GetEnvironmentVariable("No_Expand") != null) + { + expand = false; + } + else if (Environment.GetEnvironmentVariable( + HelperMethods.StringFormat(CultureInfo.InvariantCulture, + "No_Expand_{0}", name)) != null) + { + expand = false; + } + + value = Environment.GetEnvironmentVariable(name); + + if (!String.IsNullOrEmpty(value)) + { + if (expand) + value = Environment.ExpandEnvironmentVariables(value); + + value = ReplaceEnvironmentVariableTokens(value); + } + + if (value != null) + return value; + + // + // NOTE: If the "No_SQLiteXmlConfigFile" environment variable is + // set [to anything], this method will NEVER read from the + // XML configuration file. + // + if (Environment.GetEnvironmentVariable( + "No_SQLiteXmlConfigFile") != null) + { + return @default; + } +#endif + + ///////////////////////////////////////////////////////////////////// + + #region Debug Build Only +#if DEBUG + // + // NOTE: We are about to read a setting value from the XML + // configuration file; create or increment the appropriate + // statistic now. + // + DebugData.IncrementSettingReadCount(name, true); +#endif + #endregion + + ///////////////////////////////////////////////////////////////////// + + return GetSettingValueViaXmlConfigFile( + GetCachedXmlConfigFileName(), name, @default, expand); + } + + ///////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + private static string ListToString(IList list) + { + if (list == null) + return null; + + StringBuilder result = new StringBuilder(); + + foreach (string element in list) + { + if (element == null) + continue; + + if (result.Length > 0) + result.Append(' '); + + result.Append(element); + } + + return result.ToString(); + } + + ///////////////////////////////////////////////////////////////////////// + + private static int CheckForArchitecturesAndPlatforms( + string directory, + ref List matches + ) + { + int result = 0; + + if (matches == null) + matches = new List(); + + lock (staticSyncRoot) + { + if (!String.IsNullOrEmpty(directory) && + (processorArchitecturePlatforms != null)) + { + foreach (KeyValuePair pair + in processorArchitecturePlatforms) + { + if (Directory.Exists(MaybeCombinePath(directory, pair.Key))) + { + matches.Add(pair.Key); + result++; + } + + string value = pair.Value; + + if (value == null) + continue; + + if (Directory.Exists(MaybeCombinePath(directory, value))) + { + matches.Add(value); + result++; + } + } + } + } + + return result; + } + + ///////////////////////////////////////////////////////////////////////// + + private static bool CheckAssemblyCodeBase( + Assembly assembly, + ref string fileName + ) + { + try + { + if (assembly == null) + return false; + + string codeBase = assembly.CodeBase; + + if (String.IsNullOrEmpty(codeBase)) + return false; + + Uri uri = new Uri(codeBase); + string localFileName = uri.LocalPath; + + if (!File.Exists(localFileName)) + return false; + + string directory = Path.GetDirectoryName( + localFileName); /* throw */ + + string xmlConfigFileName = MaybeCombinePath( + directory, XmlConfigFileName); + + if (File.Exists(xmlConfigFileName)) + { +#if !NET_COMPACT_20 && TRACE_DETECTION + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Native library pre-loader found XML configuration file " + + "via code base for currently executing assembly: \"{0}\"", + xmlConfigFileName)); /* throw */ + } + catch + { + // do nothing. + } +#endif + + fileName = localFileName; + return true; + } + + List matches = null; + + if (CheckForArchitecturesAndPlatforms(directory, ref matches) > 0) + { +#if !NET_COMPACT_20 && TRACE_DETECTION + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Native library pre-loader found native sub-directories " + + "via code base for currently executing assembly: \"{0}\"", + ListToString(matches))); /* throw */ + } + catch + { + // do nothing. + } +#endif + + fileName = localFileName; + return true; + } + + return false; + } +#if !NET_COMPACT_20 && TRACE_SHARED + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_SHARED + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Native library pre-loader failed to check code base " + + "for currently executing assembly: {0}", e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + } + + return false; + } +#endif + + ///////////////////////////////////////////////////////////////////////// + /// + /// Resets the cached assembly directory value, thus forcing the next + /// call to method to rely + /// upon the method to fetch the + /// assembly directory. + /// + private static void ResetCachedAssemblyDirectory() + { + #region Debug Build Only +#if DEBUG + DebugData.IncrementOtherCount("Method_ResetCachedAssemblyDirectory"); +#endif + #endregion + + lock (staticSyncRoot) + { + cachedAssemblyDirectory = null; + noAssemblyDirectory = false; + } + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Queries and returns the cached directory for the assembly currently + /// being executed, if available. If the cached assembly directory value + /// is not available, the method will + /// be used to obtain the assembly directory. + /// + /// + /// The directory for the assembly currently being executed -OR- null if + /// it cannot be determined. + /// + private static string GetCachedAssemblyDirectory() + { + #region Debug Build Only +#if DEBUG + DebugData.IncrementOtherCount("Method_GetCachedAssemblyDirectory"); +#endif + #endregion + + lock (staticSyncRoot) + { + if (cachedAssemblyDirectory != null) + return cachedAssemblyDirectory; + + if (noAssemblyDirectory) + return null; + } + + return GetAssemblyDirectory(); + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Queries and returns the directory for the assembly currently being + /// executed. + /// + /// + /// The directory for the assembly currently being executed -OR- null if + /// it cannot be determined. + /// + private static string GetAssemblyDirectory() + { + #region Debug Build Only +#if DEBUG + DebugData.IncrementOtherCount("Method_GetAssemblyDirectory"); +#endif + #endregion + + try + { + Assembly assembly = Assembly.GetExecutingAssembly(); + + if (assembly == null) + { + lock (staticSyncRoot) + { + noAssemblyDirectory = true; + } + + return null; + } + + string fileName = null; + +#if PLATFORM_COMPACTFRAMEWORK + AssemblyName assemblyName = assembly.GetName(); + + if (assemblyName == null) + { + lock (staticSyncRoot) + { + noAssemblyDirectory = true; + } + + return null; + } + + fileName = assemblyName.CodeBase; +#else + if (!CheckAssemblyCodeBase(assembly, ref fileName)) + fileName = assembly.Location; +#endif + + if (String.IsNullOrEmpty(fileName)) + { + lock (staticSyncRoot) + { + noAssemblyDirectory = true; + } + + return null; + } + + string directory = Path.GetDirectoryName(fileName); + + if (String.IsNullOrEmpty(directory)) + { + lock (staticSyncRoot) + { + noAssemblyDirectory = true; + } + + return null; + } + + lock (staticSyncRoot) + { + cachedAssemblyDirectory = directory; + } + + return directory; + } +#if !NET_COMPACT_20 && TRACE_SHARED + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_SHARED + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Native library pre-loader failed to get directory " + + "for currently executing assembly: {0}", e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + } + + lock (staticSyncRoot) + { + noAssemblyDirectory = true; + } + + return null; + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Optional Native SQLite Library Pre-Loading Code + // + // NOTE: If we are looking for the standard SQLite DLL ("sqlite3.dll"), + // the interop DLL ("SQLite.Interop.dll"), or we are running on the + // .NET Compact Framework, we should include this code (only if the + // feature has actually been enabled). This code would be totally + // redundant if this module has been bundled into the mixed-mode + // assembly. + // +#if SQLITE_STANDARD || USE_INTEROP_DLL || PLATFORM_COMPACTFRAMEWORK + + // + // NOTE: Only compile in the native library pre-load code if the feature + // has been enabled for this build. + // +#if PRELOAD_NATIVE_LIBRARY + /// + /// The name of the environment variable containing the processor + /// architecture of the current process. + /// + private static readonly string PROCESSOR_ARCHITECTURE = + "PROCESSOR_ARCHITECTURE"; + + ///////////////////////////////////////////////////////////////////////// + + #region Private Data + /// + /// The native module file name for the native SQLite library or null. + /// + internal static string _SQLiteNativeModuleFileName = null; + + ///////////////////////////////////////////////////////////////////////// + /// + /// The native module handle for the native SQLite library or the value + /// IntPtr.Zero. + /// + private static IntPtr _SQLiteNativeModuleHandle = IntPtr.Zero; + #endregion + + ///////////////////////////////////////////////////////////////////////// + /// + /// Determines the base file name (without any directory information) + /// for the native SQLite library to be pre-loaded by this class. + /// + /// + /// The base file name for the native SQLite library to be pre-loaded by + /// this class -OR- null if its value cannot be determined. + /// + internal static string GetNativeLibraryFileNameOnly() + { + string fileNameOnly = GetSettingValue( + "PreLoadSQLite_LibraryFileNameOnly", null); + + if (fileNameOnly != null) + return fileNameOnly; + + return SQLITE_DLL; /* COMPAT */ + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Searches for the native SQLite library in the directory containing + /// the assembly currently being executed as well as the base directory + /// for the current application domain. + /// + /// + /// Upon success, this parameter will be modified to refer to the base + /// directory containing the native SQLite library. + /// + /// + /// Upon success, this parameter will be modified to refer to the name + /// of the immediate directory (i.e. the offset from the base directory) + /// containing the native SQLite library. + /// + /// + /// Upon success, this parameter will be modified to non-zero only if + /// the base directory itself should be allowed for loading the native + /// library. + /// + /// + /// Non-zero (success) if the native SQLite library was found; otherwise, + /// zero (failure). + /// + private static bool SearchForDirectory( + ref string baseDirectory, /* out */ + ref string processorArchitecture, /* out */ + ref bool allowBaseDirectoryOnly /* out */ + ) + { + if (GetSettingValue( + "PreLoadSQLite_NoSearchForDirectory", null) != null) + { + return false; /* DISABLED */ + } + + // + // NOTE: Determine the base file name for the native SQLite library. + // If this is not known by this class, we cannot continue. + // + string fileNameOnly = GetNativeLibraryFileNameOnly(); + + if (fileNameOnly == null) + return false; + + // + // NOTE: Build the list of base directories and processor/platform + // names. These lists will be used to help locate the native + // SQLite core library (or interop assembly) to pre-load into + // this process. + // + string[] directories = { + GetAssemblyDirectory(), +#if !PLATFORM_COMPACTFRAMEWORK + AppDomain.CurrentDomain.BaseDirectory, +#endif + }; + + string extraSubDirectory = null; + + if ((GetSettingValue( + "PreLoadSQLite_AllowBaseDirectoryOnly", null) != null) || + (HelperMethods.IsDotNetCore() && !HelperMethods.IsWindows())) + { + extraSubDirectory = String.Empty; /* .NET Core on POSIX */ + } + + string[] subDirectories = { + GetProcessorArchitecture(), /* e.g. "x86" */ + GetPlatformName(null), /* e.g. "Win32" */ + extraSubDirectory /* base directory only? */ + }; + + foreach (string directory in directories) + { + if (directory == null) + continue; + + foreach (string subDirectory in subDirectories) + { + if (subDirectory == null) + continue; + + string fileName = FixUpDllFileName(MaybeCombinePath( + MaybeCombinePath(directory, subDirectory), + fileNameOnly)); + + // + // NOTE: If the SQLite DLL file exists, return success. + // Prior to returning, set the base directory and + // processor architecture to reflect the location + // where it was found. + // + if (File.Exists(fileName)) + { +#if !NET_COMPACT_20 && TRACE_DETECTION + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Native library pre-loader found native file " + + "name \"{0}\", returning directory \"{1}\" and " + + "sub-directory \"{2}\"...", fileName, directory, + subDirectory)); /* throw */ + } + catch + { + // do nothing. + } +#endif + + baseDirectory = directory; + processorArchitecture = subDirectory; + allowBaseDirectoryOnly = (subDirectory.Length == 0); + + return true; /* FOUND */ + } + } + } + + return false; /* NOT FOUND */ + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Queries and returns the base directory of the current application + /// domain. + /// + /// + /// The base directory for the current application domain -OR- null if it + /// cannot be determined. + /// + private static string GetBaseDirectory() + { + // + // NOTE: If the "PreLoadSQLite_BaseDirectory" environment variable + // is set, use it verbatim for the base directory. + // + string directory = GetSettingValue("PreLoadSQLite_BaseDirectory", + null); + + if (directory != null) + return directory; + +#if !PLATFORM_COMPACTFRAMEWORK + // + // NOTE: If the "PreLoadSQLite_UseAssemblyDirectory" environment + // variable is set (to anything), then attempt to use the + // directory containing the currently executing assembly + // (i.e. System.Data.SQLite) intsead of the application + // domain base directory. + // + if (GetSettingValue( + "PreLoadSQLite_UseAssemblyDirectory", null) != null) + { + directory = GetAssemblyDirectory(); + + if (directory != null) + return directory; + } + + // + // NOTE: Otherwise, fallback on using the base directory of the + // current application domain. + // + return AppDomain.CurrentDomain.BaseDirectory; +#else + // + // NOTE: Otherwise, fallback on using the directory containing + // the currently executing assembly. + // + return GetAssemblyDirectory(); +#endif + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Determines if the dynamic link library file name requires a suffix + /// and adds it if necessary. + /// + /// + /// The original dynamic link library file name to inspect. + /// + /// + /// The dynamic link library file name, possibly modified to include an + /// extension. + /// + private static string FixUpDllFileName( + string fileName /* in */ + ) + { + if (!String.IsNullOrEmpty(fileName)) + { + if (HelperMethods.IsWindows()) + { + if (!fileName.EndsWith(DllFileExtension, + StringComparison.OrdinalIgnoreCase)) + { + return fileName + DllFileExtension; + } + } + } + + return fileName; + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Queries and returns the processor architecture of the current + /// process. + /// + /// + /// The processor architecture of the current process -OR- null if it + /// cannot be determined. + /// + private static string GetProcessorArchitecture() + { + // + // NOTE: If the "PreLoadSQLite_ProcessorArchitecture" environment + // variable is set, use it verbatim for the current processor + // architecture. + // + string processorArchitecture = GetSettingValue( + "PreLoadSQLite_ProcessorArchitecture", null); + + if (processorArchitecture != null) + return processorArchitecture; + + // + // BUGBUG: Will this always be reliable? + // + processorArchitecture = GetSettingValue(PROCESSOR_ARCHITECTURE, null); + + ///////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + // + // HACK: Check for an "impossible" situation. If the pointer size + // is 32-bits, the processor architecture cannot be "AMD64". + // In that case, we are almost certainly hitting a bug in the + // operating system and/or Visual Studio that causes the + // PROCESSOR_ARCHITECTURE environment variable to contain the + // wrong value in some circumstances. Please refer to ticket + // [9ac9862611] for further information. + // + if ((IntPtr.Size == sizeof(int)) && + String.Equals(processorArchitecture, "AMD64", + StringComparison.OrdinalIgnoreCase)) + { +#if !NET_COMPACT_20 && TRACE_DETECTION + // + // NOTE: When tracing is enabled, save the originally detected + // processor architecture before changing it. + // + string savedProcessorArchitecture = processorArchitecture; +#endif + + // + // NOTE: We know that operating systems that return "AMD64" as + // the processor architecture are actually a superset of + // the "x86" processor architecture; therefore, return + // "x86" when the pointer size is 32-bits. + // + processorArchitecture = "x86"; + +#if !NET_COMPACT_20 && TRACE_DETECTION + try + { + // + // NOTE: Show that we hit a fairly unusual situation (i.e. + // the "wrong" processor architecture was detected). + // + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Native library pre-loader detected {0}-bit pointer " + + "size with processor architecture \"{1}\", using " + + "processor architecture \"{2}\" instead...", + IntPtr.Size * 8 /* bits */, savedProcessorArchitecture, + processorArchitecture)); /* throw */ + } + catch + { + // do nothing. + } +#endif + } +#endif + + ///////////////////////////////////////////////////////////////////// + + if (processorArchitecture == null) + { + // + // NOTE: Default to the processor architecture reported by the + // appropriate native operating system API, if any. + // + processorArchitecture = NativeLibraryHelper.GetMachine(); + + // + // NOTE: Upon failure, return empty string. This will prevent + // the calling method from considering this method call + // a "failure". + // + if (processorArchitecture == null) + processorArchitecture = String.Empty; + } + + ///////////////////////////////////////////////////////////////////// + + return processorArchitecture; + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Given the processor architecture, returns the name of the platform. + /// + /// + /// The processor architecture to be translated to a platform name. + /// + /// + /// The platform name for the specified processor architecture -OR- null + /// if it cannot be determined. + /// + private static string GetPlatformName( + string processorArchitecture /* in */ + ) + { + if (processorArchitecture == null) + processorArchitecture = GetProcessorArchitecture(); + + if (String.IsNullOrEmpty(processorArchitecture)) + return null; + + lock (staticSyncRoot) + { + if (processorArchitecturePlatforms == null) + return null; + + string platformName; + + if (processorArchitecturePlatforms.TryGetValue( + processorArchitecture, out platformName)) + { + return platformName; + } + } + + return null; + } + + ///////////////////////////////////////////////////////////////////////// + /// + /// Attempts to load the native SQLite library based on the specified + /// directory and processor architecture. + /// + /// + /// The base directory to use, null for default (the base directory of + /// the current application domain). This directory should contain the + /// processor architecture specific sub-directories. + /// + /// + /// The requested processor architecture, null for default (the + /// processor architecture of the current process). This caller should + /// almost always specify null for this parameter. + /// + /// + /// Non-zero indicates that the native SQLite library can be loaded + /// from the base directory itself. + /// + /// + /// The candidate native module file name to load will be stored here, + /// if necessary. + /// + /// + /// The native module handle as returned by LoadLibrary will be stored + /// here, if necessary. This value will be IntPtr.Zero if the call to + /// LoadLibrary fails. + /// + /// + /// Non-zero if the native module was loaded successfully; otherwise, + /// zero. + /// + private static bool PreLoadSQLiteDll( + string baseDirectory, /* in */ + string processorArchitecture, /* in */ + bool allowBaseDirectoryOnly, /* in */ + ref string nativeModuleFileName, /* out */ + ref IntPtr nativeModuleHandle /* out */ + ) + { + // + // NOTE: If the specified base directory is null, use the default + // (i.e. attempt to automatically detect it). + // + if (baseDirectory == null) + baseDirectory = GetBaseDirectory(); + + // + // NOTE: If we failed to query the base directory, stop now. + // + if (baseDirectory == null) + return false; + + // + // NOTE: Determine the base file name for the native SQLite library. + // If this is not known by this class, we cannot continue. + // + string fileNameOnly = GetNativeLibraryFileNameOnly(); + + if (fileNameOnly == null) + return false; + + // + // NOTE: If the native SQLite library exists in the base directory + // itself, possibly stop now. + // + string fileName = FixUpDllFileName(MaybeCombinePath(baseDirectory, + fileNameOnly)); + + if (File.Exists(fileName)) + { + // + // NOTE: If the caller is allowing the base directory itself + // to be used, also make sure a processor architecture + // was not specified; if either condition is false just + // stop now and return failure. + // + if (allowBaseDirectoryOnly && + String.IsNullOrEmpty(processorArchitecture)) + { + goto baseDirOnly; + } + else + { + return false; + } + } + + // + // NOTE: If the specified processor architecture is null, use the + // default. + // + if (processorArchitecture == null) + processorArchitecture = GetProcessorArchitecture(); + + // + // NOTE: If we failed to query the processor architecture, stop now. + // + if (processorArchitecture == null) + return false; + + // + // NOTE: Build the full path and file name for the native SQLite + // library using the processor architecture name. + // + fileName = FixUpDllFileName(MaybeCombinePath(MaybeCombinePath( + baseDirectory, processorArchitecture), fileNameOnly)); + + // + // NOTE: If the file name based on the processor architecture name + // is not found, try using the associated platform name. + // + if (!File.Exists(fileName)) + { + // + // NOTE: Attempt to translate the processor architecture to a + // platform name. + // + string platformName = GetPlatformName(processorArchitecture); + + // + // NOTE: If we failed to translate the platform name, stop now. + // + if (platformName == null) + return false; + + // + // NOTE: Build the full path and file name for the native SQLite + // library using the platform name. + // + fileName = FixUpDllFileName(MaybeCombinePath(MaybeCombinePath( + baseDirectory, platformName), fileNameOnly)); + + // + // NOTE: If the file does not exist, skip trying to load it. + // + if (!File.Exists(fileName)) + return false; + } + + baseDirOnly: + + try + { +#if !NET_COMPACT_20 && TRACE_PRELOAD + try + { + // + // NOTE: Show exactly where we are trying to load the native + // SQLite library from. + // + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Native library pre-loader is trying to load native " + + "SQLite library \"{0}\"...", fileName)); /* throw */ + } + catch + { + // do nothing. + } +#endif + + // + // NOTE: Attempt to load the native library. This will either + // return a valid native module handle, return IntPtr.Zero, + // or throw an exception. This must use the appropriate + // P/Invoke method for the current operating system. + // + nativeModuleFileName = fileName; + nativeModuleHandle = NativeLibraryHelper.LoadLibrary(fileName); + + return (nativeModuleHandle != IntPtr.Zero); + } +#if !NET_COMPACT_20 && TRACE_PRELOAD + catch (Exception e) +#else + catch (Exception) +#endif + { +#if !NET_COMPACT_20 && TRACE_PRELOAD + try + { + // + // NOTE: First, grab the last Win32 error number. + // + int lastError = Marshal.GetLastWin32Error(); /* throw */ + + // + // NOTE: Show where we failed to load the native SQLite + // library from along with the Win32 error code and + // exception information. + // + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "Native library pre-loader failed to load native " + + "SQLite library \"{0}\" (getLastError = {1}): {2}", + fileName, lastError, e)); /* throw */ + } + catch + { + // do nothing. + } +#endif + } + + return false; + } +#endif +#endif + #endregion + + ///////////////////////////////////////////////////////////////////////// + +#if PLATFORM_COMPACTFRAMEWORK + // + // NOTE: On the .NET Compact Framework, the native interop assembly must + // be used because it provides several workarounds to .NET Compact + // Framework limitations important for proper operation of the core + // System.Data.SQLite functionality (e.g. being able to bind + // parameters and handle column values of types Int64 and Double). + // + internal const string SQLITE_DLL = "SQLite.Interop.110.dll"; +#elif SQLITE_STANDARD + // + // NOTE: Otherwise, if the standard SQLite library is enabled, use it. + // + internal const string SQLITE_DLL = "sqlite3"; +#elif USE_INTEROP_DLL + // + // NOTE: Otherwise, if the native SQLite interop assembly is enabled, + // use it. + // + internal const string SQLITE_DLL = "SQLite.Interop.dll"; +#else + // + // NOTE: Finally, assume that the mixed-mode assembly is being used. + // + internal const string SQLITE_DLL = "System.Data.SQLite.dll"; +#endif + + // This section uses interop calls that also fetch text length to optimize conversion. + // When using the standard dll, we can replace these calls with normal sqlite calls and + // do unoptimized conversions instead afterwards + #region interop added textlength calls + +#if !SQLITE_STANDARD + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_bind_parameter_name_interop(IntPtr stmt, int index, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_column_database_name_interop(IntPtr stmt, int index, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_column_database_name16_interop(IntPtr stmt, int index, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_column_decltype_interop(IntPtr stmt, int index, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_column_decltype16_interop(IntPtr stmt, int index, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_column_name_interop(IntPtr stmt, int index, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_column_name16_interop(IntPtr stmt, int index, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_column_origin_name_interop(IntPtr stmt, int index, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_column_origin_name16_interop(IntPtr stmt, int index, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_column_table_name_interop(IntPtr stmt, int index, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_column_table_name16_interop(IntPtr stmt, int index, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_column_text_interop(IntPtr stmt, int index, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_column_text16_interop(IntPtr stmt, int index, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_errmsg_interop(IntPtr db, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_prepare_interop(IntPtr db, IntPtr pSql, int nBytes, ref IntPtr stmt, ref IntPtr ptrRemain, ref int nRemain); + + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_table_column_metadata_interop(IntPtr db, byte[] dbName, byte[] tblName, byte[] colName, ref IntPtr ptrDataType, ref IntPtr ptrCollSeq, ref int notNull, ref int primaryKey, ref int autoInc, ref int dtLen, ref int csLen); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_value_text_interop(IntPtr p, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_value_text16_interop(IntPtr p, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern int sqlite3_malloc_size_interop(IntPtr p); + +#if INTEROP_LOG + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_config_log_interop(); +#endif +#endif +// !SQLITE_STANDARD + + #endregion + + // These functions add existing functionality on top of SQLite and require a little effort to + // get working when using the standard SQLite library. + #region interop added functionality + +#if !SQLITE_STANDARD + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr interop_libversion(); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr interop_sourceid(); + + [DllImport(SQLITE_DLL)] + internal static extern int interop_compileoption_used(IntPtr zOptName); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr interop_compileoption_get(int N); + + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_close_interop(IntPtr db); + + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_create_function_interop(IntPtr db, byte[] strName, int nArgs, int nType, IntPtr pvUser, SQLiteCallback func, SQLiteCallback fstep, SQLiteFinalCallback ffinal, int needCollSeq); + + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_finalize_interop(IntPtr stmt); + + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_backup_finish_interop(IntPtr backup); + + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_blob_close_interop(IntPtr blob); + + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_open_interop(byte[] utf8Filename, byte[] vfsName, SQLiteOpenFlagsEnum flags, int extFuncs, ref IntPtr db); + + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_open16_interop(byte[] utf8Filename, byte[] vfsName, SQLiteOpenFlagsEnum flags, int extFuncs, ref IntPtr db); + + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_reset_interop(IntPtr stmt); + + [DllImport(SQLITE_DLL)] + internal static extern int sqlite3_changes_interop(IntPtr db); +#endif +// !SQLITE_STANDARD + + #endregion + + // The standard api call equivalents of the above interop calls + #region standard versions of interop functions + +#if SQLITE_STANDARD + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_close(IntPtr db); + +#if !INTEROP_LEGACY_CLOSE +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_close_v2(IntPtr db); /* 3.7.14+ */ +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_create_function(IntPtr db, byte[] strName, int nArgs, int nType, IntPtr pvUser, SQLiteCallback func, SQLiteCallback fstep, SQLiteFinalCallback ffinal); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_finalize(IntPtr stmt); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_backup_finish(IntPtr backup); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_reset(IntPtr stmt); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_bind_parameter_name(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_column_database_name(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_column_database_name16(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_column_decltype(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_column_decltype16(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_column_name(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_column_name16(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_column_origin_name(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_column_origin_name16(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_column_table_name(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_column_table_name16(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_column_text(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_column_text16(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_errmsg(IntPtr db); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_prepare(IntPtr db, IntPtr pSql, int nBytes, ref IntPtr stmt, ref IntPtr ptrRemain); + +#if USE_PREPARE_V2 +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_prepare_v2(IntPtr db, IntPtr pSql, int nBytes, ref IntPtr stmt, ref IntPtr ptrRemain); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_table_column_metadata(IntPtr db, byte[] dbName, byte[] tblName, byte[] colName, ref IntPtr ptrDataType, ref IntPtr ptrCollSeq, ref int notNull, ref int primaryKey, ref int autoInc); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_value_text(IntPtr p); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_value_text16(IntPtr p); + +#endif + // SQLITE_STANDARD + + #endregion + + // These functions are custom and have no equivalent standard library method. + // All of them are "nice to haves" and not necessarily "need to haves". + #region no equivalent standard method + +#if !SQLITE_STANDARD + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_context_collseq_interop(IntPtr context, ref int type, ref int enc, ref int len); + + [DllImport(SQLITE_DLL)] + internal static extern int sqlite3_context_collcompare_interop(IntPtr context, byte[] p1, int p1len, byte[] p2, int p2len); + + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_cursor_rowid_interop(IntPtr stmt, int cursor, ref long rowid); + + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_index_column_info_interop(IntPtr db, byte[] catalog, byte[] IndexName, byte[] ColumnName, ref int sortOrder, ref int onError, ref IntPtr Collation, ref int colllen); + + [DllImport(SQLITE_DLL)] + internal static extern int sqlite3_table_cursor_interop(IntPtr stmt, int db, int tableRootPage); + +#endif +// !SQLITE_STANDARD + + #endregion + + // Standard API calls global across versions. There are a few instances of interop calls + // scattered in here, but they are only active when PLATFORM_COMPACTFRAMEWORK is declared. + #region standard sqlite api calls + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_libversion(); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_libversion_number(); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_sourceid(); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_compileoption_used(IntPtr zOptName); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_compileoption_get(int N); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_enable_shared_cache( + int enable); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_enable_load_extension( + IntPtr db, int enable); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_load_extension( + IntPtr db, byte[] fileName, byte[] procName, ref IntPtr pError); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_overload_function(IntPtr db, IntPtr zName, int nArgs); + +#if WINDOWS +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] +#else + [DllImport(SQLITE_DLL, CharSet = CharSet.Unicode)] +#endif + // + // NOTE: The "sqlite3_win32_set_directory" SQLite core library function is + // only supported on Windows. + // + internal static extern SQLiteErrorCode sqlite3_win32_set_directory(uint type, string value); + +#if !DEBUG // NOTE: Should be "WIN32HEAP && !MEMDEBUG" +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + // + // NOTE: The "sqlite3_win32_reset_heap" SQLite core library function is + // only supported on Windows when the Win32 native allocator is in + // use (i.e. by default, in "Release" builds of System.Data.SQLite + // only). By default, in "Debug" builds of System.Data.SQLite, the + // MEMDEBUG allocator is used. + // + internal static extern SQLiteErrorCode sqlite3_win32_reset_heap(); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + // + // NOTE: The "sqlite3_win32_compact_heap" SQLite core library function is + // only supported on Windows when the Win32 native allocator is in + // use (i.e. by default, in "Release" builds of System.Data.SQLite + // only). By default, in "Debug" builds of System.Data.SQLite, the + // MEMDEBUG allocator is used. + // + internal static extern SQLiteErrorCode sqlite3_win32_compact_heap(ref uint largest); +#endif +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_malloc(int n); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_malloc64(ulong n); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_realloc(IntPtr p, int n); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_realloc64(IntPtr p, ulong n); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern ulong sqlite3_msize(IntPtr p); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_free(IntPtr p); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_open_v2(byte[] utf8Filename, ref IntPtr db, SQLiteOpenFlagsEnum flags, byte[] vfsName); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] +#else + [DllImport(SQLITE_DLL, CharSet = CharSet.Unicode)] +#endif + internal static extern SQLiteErrorCode sqlite3_open16(string fileName, ref IntPtr db); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_interrupt(IntPtr db); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] + internal static extern long sqlite3_last_insert_rowid(IntPtr db); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_changes(IntPtr db); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] + internal static extern long sqlite3_memory_used(); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] + internal static extern long sqlite3_memory_highwater(int resetFlag); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_shutdown(); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_busy_timeout(IntPtr db, int ms); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_clear_bindings(IntPtr stmt); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_bind_blob(IntPtr stmt, int index, Byte[] value, int nSize, IntPtr nTransient); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] + internal static extern SQLiteErrorCode sqlite3_bind_double(IntPtr stmt, int index, double value); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_bind_int(IntPtr stmt, int index, int value); + + // + // NOTE: This really just calls "sqlite3_bind_int"; however, it has the + // correct type signature for an unsigned (32-bit) integer. + // +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_bind_int", CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_bind_int")] +#endif + internal static extern SQLiteErrorCode sqlite3_bind_uint(IntPtr stmt, int index, uint value); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] + internal static extern SQLiteErrorCode sqlite3_bind_int64(IntPtr stmt, int index, long value); +#endif + + // + // NOTE: This really just calls "sqlite3_bind_int64"; however, it has the + // correct type signature for an unsigned long (64-bit) integer. + // +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_bind_int64", CallingConvention = CallingConvention.Cdecl)] + internal static extern SQLiteErrorCode sqlite3_bind_uint64(IntPtr stmt, int index, ulong value); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_bind_null(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_bind_text(IntPtr stmt, int index, byte[] value, int nlen, IntPtr pvReserved); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_bind_parameter_count(IntPtr stmt); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_bind_parameter_index(IntPtr stmt, byte[] strName); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_column_count(IntPtr stmt); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_step(IntPtr stmt); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_stmt_readonly(IntPtr stmt); /* 3.7.4+ */ + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] + internal static extern double sqlite3_column_double(IntPtr stmt, int index); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_column_int(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] + internal static extern long sqlite3_column_int64(IntPtr stmt, int index); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_column_blob(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_column_bytes(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_column_bytes16(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern TypeAffinity sqlite3_column_type(IntPtr stmt, int index); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_create_collation(IntPtr db, byte[] strName, int nType, IntPtr pvUser, SQLiteCollation func); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_aggregate_count(IntPtr context); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_value_blob(IntPtr p); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_value_bytes(IntPtr p); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_value_bytes16(IntPtr p); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] + internal static extern double sqlite3_value_double(IntPtr p); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_value_int(IntPtr p); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] + internal static extern long sqlite3_value_int64(IntPtr p); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern TypeAffinity sqlite3_value_type(IntPtr p); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_result_blob(IntPtr context, byte[] value, int nSize, IntPtr pvReserved); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] + internal static extern void sqlite3_result_double(IntPtr context, double value); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_result_error(IntPtr context, byte[] strErr, int nLen); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_result_error_code(IntPtr context, SQLiteErrorCode value); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_result_error_toobig(IntPtr context); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_result_error_nomem(IntPtr context); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_result_value(IntPtr context, IntPtr value); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_result_zeroblob(IntPtr context, int nLen); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_result_int(IntPtr context, int value); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] + internal static extern void sqlite3_result_int64(IntPtr context, long value); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_result_null(IntPtr context); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_result_text(IntPtr context, byte[] value, int nLen, IntPtr pvReserved); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_aggregate_context(IntPtr context, int nBytes); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] +#else + [DllImport(SQLITE_DLL, CharSet = CharSet.Unicode)] +#endif + internal static extern SQLiteErrorCode sqlite3_bind_text16(IntPtr stmt, int index, string value, int nlen, IntPtr pvReserved); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] +#else + [DllImport(SQLITE_DLL, CharSet = CharSet.Unicode)] +#endif + internal static extern void sqlite3_result_error16(IntPtr context, string strName, int nLen); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] +#else + [DllImport(SQLITE_DLL, CharSet = CharSet.Unicode)] +#endif + internal static extern void sqlite3_result_text16(IntPtr context, string strName, int nLen, IntPtr pvReserved); + +#if INTEROP_CODEC || INTEROP_INCLUDE_SEE +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_key(IntPtr db, byte[] key, int keylen); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_rekey(IntPtr db, byte[] key, int keylen); +#endif + +#if INTEROP_INCLUDE_ZIPVFS +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void zipvfsInit_v2(); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void zipvfsInit_v3(int regDflt); +#endif + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_progress_handler(IntPtr db, int ops, SQLiteProgressCallback func, IntPtr pvUser); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_set_authorizer(IntPtr db, SQLiteAuthorizerCallback func, IntPtr pvUser); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_update_hook(IntPtr db, SQLiteUpdateCallback func, IntPtr pvUser); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_commit_hook(IntPtr db, SQLiteCommitCallback func, IntPtr pvUser); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_trace(IntPtr db, SQLiteTraceCallback func, IntPtr pvUser); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_trace_v2(IntPtr db, SQLiteTraceFlags mask, SQLiteTraceCallback2 func, IntPtr pvUser); + + // Since sqlite3_config() takes a variable argument list, we have to overload declarations + // for all possible calls that we want to use. +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_config", CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_config")] +#endif + internal static extern SQLiteErrorCode sqlite3_config_none(SQLiteConfigOpsEnum op); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_config", CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_config")] +#endif + internal static extern SQLiteErrorCode sqlite3_config_int(SQLiteConfigOpsEnum op, int value); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_config", CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_config")] +#endif + internal static extern SQLiteErrorCode sqlite3_config_log(SQLiteConfigOpsEnum op, SQLiteLogCallback func, IntPtr pvUser); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_db_config", CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_db_config")] +#endif + internal static extern SQLiteErrorCode sqlite3_db_config_charptr(IntPtr db, SQLiteConfigDbOpsEnum op, IntPtr charPtr); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_db_config", CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_db_config")] +#endif + internal static extern SQLiteErrorCode sqlite3_db_config_int_refint(IntPtr db, SQLiteConfigDbOpsEnum op, int value, ref int result); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_db_config", CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_db_config")] +#endif + internal static extern SQLiteErrorCode sqlite3_db_config_intptr_two_ints(IntPtr db, SQLiteConfigDbOpsEnum op, IntPtr ptr, int int0, int int1); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_db_status(IntPtr db, SQLiteStatusOpsEnum op, ref int current, ref int highwater, int resetFlag); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_rollback_hook(IntPtr db, SQLiteRollbackCallback func, IntPtr pvUser); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_db_handle(IntPtr stmt); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_db_release_memory(IntPtr db); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_db_filename(IntPtr db, IntPtr dbName); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_db_readonly(IntPtr db, IntPtr dbName); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_db_filename", CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_db_filename")] +#endif + internal static extern IntPtr sqlite3_db_filename_bytes(IntPtr db, byte[] dbName); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_next_stmt(IntPtr db, IntPtr stmt); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_exec(IntPtr db, byte[] strSql, IntPtr pvCallback, IntPtr pvParam, ref IntPtr errMsg); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_release_memory(int nBytes); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_get_autocommit(IntPtr db); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_extended_result_codes(IntPtr db, int onoff); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_errcode(IntPtr db); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_extended_errcode(IntPtr db); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_errstr(SQLiteErrorCode rc); /* 3.7.15+ */ + + // Since sqlite3_log() takes a variable argument list, we have to overload declarations + // for all possible calls. For now, we are only exposing a single string, and + // depend on the caller to format the string. +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_log(SQLiteErrorCode iErrCode, byte[] zFormat); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_file_control(IntPtr db, byte[] zDbName, int op, IntPtr pArg); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_backup_init(IntPtr destDb, byte[] zDestName, IntPtr sourceDb, byte[] zSourceName); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_backup_step(IntPtr backup, int nPage); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_backup_remaining(IntPtr backup); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_backup_pagecount(IntPtr backup); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_blob_close(IntPtr blob); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3_blob_bytes(IntPtr blob); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_blob_open(IntPtr db, byte[] dbName, byte[] tblName, byte[] colName, long rowId, int flags, ref IntPtr ptrBlob); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_blob_read(IntPtr blob, [MarshalAs(UnmanagedType.LPArray)] byte[] buffer, int count, int offset); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_blob_reopen(IntPtr blob, long rowId); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_blob_write(IntPtr blob, [MarshalAs(UnmanagedType.LPArray)] byte[] buffer, int count, int offset); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3_declare_vtab(IntPtr db, IntPtr zSQL); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_mprintf(IntPtr format, __arglist); + #endregion + + /////////////////////////////////////////////////////////////////////////// + + // SQLite API calls that are provided by "well-known" extensions that may be statically + // linked with the SQLite core native library currently in use. + #region extension sqlite api calls + #region virtual table +#if INTEROP_VIRTUAL_TABLE +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern IntPtr sqlite3_create_disposable_module(IntPtr db, IntPtr name, ref sqlite3_module module, IntPtr pClientData, xDestroyModule xDestroy); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3_dispose_module(IntPtr pModule); +#endif + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region session extension +#if INTEROP_SESSION_EXTENSION +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate int xSessionFilter(IntPtr context, IntPtr pTblName); + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate SQLiteChangeSetConflictResult xSessionConflict(IntPtr context, SQLiteChangeSetConflictType type, IntPtr iterator); + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate SQLiteErrorCode xSessionInput(IntPtr context, IntPtr pData, ref int nData); + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate SQLiteErrorCode xSessionOutput(IntPtr context, IntPtr pData, int nData); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3session_create(IntPtr db, byte[] dbName, ref IntPtr session); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3session_delete(IntPtr session); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3session_enable(IntPtr session, int enable); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3session_indirect(IntPtr session, int indirect); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3session_attach(IntPtr session, byte[] tblName); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3session_table_filter(IntPtr session, xSessionFilter xFilter, IntPtr context); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3session_changeset(IntPtr session, ref int nChangeSet, ref IntPtr pChangeSet); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3session_diff(IntPtr session, byte[] fromDbName, byte[] tblName, ref IntPtr errMsg); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3session_patchset(IntPtr session, ref int nPatchSet, ref IntPtr pPatchSet); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern int sqlite3session_isempty(IntPtr session); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_start(ref IntPtr iterator, int nChangeSet, IntPtr pChangeSet); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_start_v2(ref IntPtr iterator, int nChangeSet, IntPtr pChangeSet, SQLiteChangeSetStartFlags flags); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_next(IntPtr iterator); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_op(IntPtr iterator, ref IntPtr pTblName, ref int nColumns, ref SQLiteAuthorizerActionCode op, ref int bIndirect); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_pk(IntPtr iterator, ref IntPtr pPrimaryKeys, ref int nColumns); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_old(IntPtr iterator, int columnIndex, ref IntPtr pValue); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_new(IntPtr iterator, int columnIndex, ref IntPtr pValue); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_conflict(IntPtr iterator, int columnIndex, ref IntPtr pValue); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_fk_conflicts(IntPtr iterator, ref int conflicts); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_finalize(IntPtr iterator); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_invert(int nIn, IntPtr pIn, ref int nOut, ref IntPtr pOut); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_concat(int nA, IntPtr pA, int nB, IntPtr pB, ref int nOut, ref IntPtr pOut); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changegroup_new(ref IntPtr changeGroup); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changegroup_add(IntPtr changeGroup, int nData, IntPtr pData); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changegroup_output(IntPtr changeGroup, ref int nData, ref IntPtr pData); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern void sqlite3changegroup_delete(IntPtr changeGroup); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_apply(IntPtr db, int nChangeSet, IntPtr pChangeSet, xSessionFilter xFilter, xSessionConflict xConflict, IntPtr context); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_apply_strm(IntPtr db, xSessionInput xInput, IntPtr pIn, xSessionFilter xFilter, xSessionConflict xConflict, IntPtr context); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_concat_strm(xSessionInput xInputA, IntPtr pInA, xSessionInput xInputB, IntPtr pInB, xSessionOutput xOutput, IntPtr pOut); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_invert_strm(xSessionInput xInput, IntPtr pIn, xSessionOutput xOutput, IntPtr pOut); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_start_strm(ref IntPtr iterator, xSessionInput xInput, IntPtr pIn); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changeset_start_v2_strm(ref IntPtr iterator, xSessionInput xInput, IntPtr pIn, SQLiteChangeSetStartFlags flags); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3session_changeset_strm(IntPtr session, xSessionOutput xOutput, IntPtr pOut); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3session_patchset_strm(IntPtr session, xSessionOutput xOutput, IntPtr pOut); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changegroup_add_strm(IntPtr changeGroup, xSessionInput xInput, IntPtr pIn); + +#if !PLATFORM_COMPACTFRAMEWORK + [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] +#else + [DllImport(SQLITE_DLL)] +#endif + internal static extern SQLiteErrorCode sqlite3changegroup_output_strm(IntPtr changeGroup, xSessionOutput xOutput, IntPtr pOut); +#endif + #endregion + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region sqlite interop api calls (.NET Compact Framework only) +#if PLATFORM_COMPACTFRAMEWORK && !SQLITE_STANDARD + [DllImport(SQLITE_DLL)] + internal static extern void sqlite3_last_insert_rowid_interop(IntPtr db, ref long rowId); + + [DllImport(SQLITE_DLL)] + internal static extern void sqlite3_memory_used_interop(ref long bytes); + + [DllImport(SQLITE_DLL)] + internal static extern void sqlite3_memory_highwater_interop(int resetFlag, ref long bytes); + + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_bind_double_interop(IntPtr stmt, int index, ref double value); + + [DllImport(SQLITE_DLL)] + internal static extern SQLiteErrorCode sqlite3_bind_int64_interop(IntPtr stmt, int index, ref long value); + + [DllImport(SQLITE_DLL, EntryPoint = "sqlite3_bind_int64_interop")] + internal static extern SQLiteErrorCode sqlite3_bind_uint64_interop(IntPtr stmt, int index, ref ulong value); + + [DllImport(SQLITE_DLL)] + internal static extern void sqlite3_column_double_interop(IntPtr stmt, int index, ref double value); + + [DllImport(SQLITE_DLL)] + internal static extern void sqlite3_column_int64_interop(IntPtr stmt, int index, ref long value); + + [DllImport(SQLITE_DLL)] + internal static extern void sqlite3_value_double_interop(IntPtr p, ref double value); + + [DllImport(SQLITE_DLL)] + internal static extern void sqlite3_value_int64_interop(IntPtr p, ref Int64 value); + + [DllImport(SQLITE_DLL)] + internal static extern void sqlite3_result_double_interop(IntPtr context, ref double value); + + [DllImport(SQLITE_DLL)] + internal static extern void sqlite3_result_int64_interop(IntPtr context, ref Int64 value); + + [DllImport(SQLITE_DLL)] + internal static extern void sqlite3_msize_interop(IntPtr p, ref ulong size); + + [DllImport(SQLITE_DLL)] + internal static extern IntPtr sqlite3_create_disposable_module_interop( + IntPtr db, IntPtr name, IntPtr pModule, int iVersion, xCreate xCreate, + xConnect xConnect, xBestIndex xBestIndex, xDisconnect xDisconnect, + xDestroy xDestroy, xOpen xOpen, xClose xClose, xFilter xFilter, + xNext xNext, xEof xEof, xColumn xColumn, xRowId xRowId, xUpdate xUpdate, + xBegin xBegin, xSync xSync, xCommit xCommit, xRollback xRollback, + xFindFunction xFindFunction, xRename xRename, xSavepoint xSavepoint, + xRelease xRelease, xRollbackTo xRollbackTo, IntPtr pClientData, + xDestroyModule xDestroyModule); +#endif + // PLATFORM_COMPACTFRAMEWORK && !SQLITE_STANDARD + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region Native Delegates +#if INTEROP_VIRTUAL_TABLE +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xCreate( + IntPtr pDb, + IntPtr pAux, + int argc, + IntPtr argv, + ref IntPtr pVtab, + ref IntPtr pError + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xConnect( + IntPtr pDb, + IntPtr pAux, + int argc, + IntPtr argv, + ref IntPtr pVtab, + ref IntPtr pError + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xBestIndex( + IntPtr pVtab, + IntPtr pIndex + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xDisconnect( + IntPtr pVtab + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xDestroy( + IntPtr pVtab + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xOpen( + IntPtr pVtab, + ref IntPtr pCursor + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xClose( + IntPtr pCursor + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xFilter( + IntPtr pCursor, + int idxNum, + IntPtr idxStr, + int argc, + IntPtr argv + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xNext( + IntPtr pCursor + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate int xEof( + IntPtr pCursor + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xColumn( + IntPtr pCursor, + IntPtr pContext, + int index + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xRowId( + IntPtr pCursor, + ref long rowId + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xUpdate( + IntPtr pVtab, + int argc, + IntPtr argv, + ref long rowId + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xBegin( + IntPtr pVtab + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xSync( + IntPtr pVtab + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xCommit( + IntPtr pVtab + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xRollback( + IntPtr pVtab + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate int xFindFunction( + IntPtr pVtab, + int nArg, + IntPtr zName, + ref SQLiteCallback callback, + ref IntPtr pUserData + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xRename( + IntPtr pVtab, + IntPtr zNew + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xSavepoint( + IntPtr pVtab, + int iSavepoint + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xRelease( + IntPtr pVtab, + int iSavepoint + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate SQLiteErrorCode xRollbackTo( + IntPtr pVtab, + int iSavepoint + ); + + /////////////////////////////////////////////////////////////////////////// + +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + public delegate void xDestroyModule(IntPtr pClientData); +#endif + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region Native Structures +#if INTEROP_VIRTUAL_TABLE + [StructLayout(LayoutKind.Sequential)] + internal struct sqlite3_module + { + /* 0 */ public int iVersion; + /* 8 */ public xCreate xCreate; + /* 16 */ public xConnect xConnect; + /* 24 */ public xBestIndex xBestIndex; + /* 32 */ public xDisconnect xDisconnect; + /* 40 */ public xDestroy xDestroy; + /* 48 */ public xOpen xOpen; + /* 56 */ public xClose xClose; + /* 64 */ public xFilter xFilter; + /* 72 */ public xNext xNext; + /* 80 */ public xEof xEof; + /* 88 */ public xColumn xColumn; + /* 96 */ public xRowId xRowId; + /* 104 */ public xUpdate xUpdate; + /* 112 */ public xBegin xBegin; + /* 120 */ public xSync xSync; + /* 128 */ public xCommit xCommit; + /* 136 */ public xRollback xRollback; + /* 144 */ public xFindFunction xFindFunction; + /* 152 */ public xRename xRename; + /* The methods above are in version 1 of the sqlite3_module + * object. Those below are for version 2 and greater. */ + /* 160 */ public xSavepoint xSavepoint; + /* 168 */ public xRelease xRelease; + /* 176 */ public xRollbackTo xRollbackTo; + } + + /////////////////////////////////////////////////////////////////////////// + + [StructLayout(LayoutKind.Sequential)] + internal struct sqlite3_vtab + { + /* 0 */ public IntPtr pModule; + /* 8 */ public int nRef; /* NO LONGER USED */ + /* 16 */ public IntPtr zErrMsg; + } + + /////////////////////////////////////////////////////////////////////////// + + [StructLayout(LayoutKind.Sequential)] + internal struct sqlite3_vtab_cursor + { + /* 0 */ public IntPtr pVTab; + } + + /////////////////////////////////////////////////////////////////////////// + + [StructLayout(LayoutKind.Sequential)] + internal struct sqlite3_index_constraint + { + public sqlite3_index_constraint( + SQLiteIndexConstraint constraint + ) + : this() + { + if (constraint != null) + { + iColumn = constraint.iColumn; + op = constraint.op; + usable = constraint.usable; + iTermOffset = constraint.iTermOffset; + } + } + + /////////////////////////////////////////////////////////////////////// + + /* 0 */ public int iColumn; + /* 4 */ public SQLiteIndexConstraintOp op; + /* 5 */ public byte usable; + /* 8 */ public int iTermOffset; + } + + /////////////////////////////////////////////////////////////////////////// + + [StructLayout(LayoutKind.Sequential)] + internal struct sqlite3_index_orderby + { + public sqlite3_index_orderby( + SQLiteIndexOrderBy orderBy + ) + : this() + { + if (orderBy != null) + { + iColumn = orderBy.iColumn; + desc = orderBy.desc; + } + } + + /////////////////////////////////////////////////////////////////////// + + /* 0 */ public int iColumn; /* Column number */ + /* 4 */ public byte desc; /* True for DESC. False for ASC. */ + } + + /////////////////////////////////////////////////////////////////////////// + + [StructLayout(LayoutKind.Sequential)] + internal struct sqlite3_index_constraint_usage + { + public sqlite3_index_constraint_usage( + SQLiteIndexConstraintUsage constraintUsage + ) + : this() + { + if (constraintUsage != null) + { + argvIndex = constraintUsage.argvIndex; + omit = constraintUsage.omit; + } + } + + /////////////////////////////////////////////////////////////////////// + + public int argvIndex; /* if >0, constraint is part of argv to xFilter */ + public byte omit; /* Do not code a test for this constraint */ + } + + /////////////////////////////////////////////////////////////////////////// + + [StructLayout(LayoutKind.Sequential)] + internal struct sqlite3_index_info + { + /* Inputs */ + /* 0 */ public int nConstraint; /* Number of entries in aConstraint */ + /* 8 */ public IntPtr aConstraint; + /* 16 */ public int nOrderBy; /* Number of entries in aOrderBy */ + /* 24 */ public IntPtr aOrderBy; + /* Outputs */ + /* 32 */ public IntPtr aConstraintUsage; + /* 40 */ public int idxNum; /* Number used to identify the index */ + /* 48 */ public string idxStr; /* String, possibly obtained from sqlite3_malloc */ + /* 56 */ public int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */ + /* 60 */ public int orderByConsumed; /* True if output is already ordered */ + /* 64 */ public double estimatedCost; /* Estimated cost of using this index */ + /* 72 */ public long estimatedRows; /* Estimated number of rows returned */ + /* 80 */ public SQLiteIndexFlags idxFlags; /* Mask of SQLITE_INDEX_SCAN_* flags */ + /* 88 */ public long colUsed; /* Input: Mask of columns used by statement */ + } +#endif + #endregion + } + #endregion + + ///////////////////////////////////////////////////////////////////////////// + + #region .NET Compact Framework (only) CriticalHandle Class +#if PLATFORM_COMPACTFRAMEWORK + internal abstract class CriticalHandle : IDisposable + { + private bool _isClosed; + protected IntPtr handle; + + protected CriticalHandle(IntPtr invalidHandleValue) + { + handle = invalidHandleValue; + _isClosed = false; + } + + ~CriticalHandle() + { + Dispose(false); + } + + private void Cleanup() + { + if (!IsClosed) + { + this._isClosed = true; + if (!IsInvalid) + { + ReleaseHandle(); + GC.SuppressFinalize(this); + } + } + } + + public void Close() + { + Dispose(true); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + Cleanup(); + } + + protected abstract bool ReleaseHandle(); + + protected void SetHandle(IntPtr value) + { + handle = value; + } + + public void SetHandleAsInvalid() + { + _isClosed = true; + GC.SuppressFinalize(this); + } + + public bool IsClosed + { + get { return _isClosed; } + } + + public abstract bool IsInvalid + { + get; + } + + } +#endif + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteConnectionHandle Class + // Handles the unmanaged database pointer, and provides finalization + // support for it. + internal sealed class SQLiteConnectionHandle : CriticalHandle + { +#if SQLITE_STANDARD && !PLATFORM_COMPACTFRAMEWORK + internal delegate void CloseConnectionCallback( + SQLiteConnectionHandle hdl, IntPtr db); + + internal static CloseConnectionCallback closeConnection = + SQLiteBase.CloseConnection; +#endif + + /////////////////////////////////////////////////////////////////////// + +#if PLATFORM_COMPACTFRAMEWORK + internal readonly object syncRoot = new object(); +#endif + + /////////////////////////////////////////////////////////////////////// + + private bool ownHandle; + + /////////////////////////////////////////////////////////////////////// + + public static implicit operator IntPtr(SQLiteConnectionHandle db) + { + if (db != null) + { +#if PLATFORM_COMPACTFRAMEWORK + lock (db.syncRoot) +#endif + { + return db.handle; + } + } + return IntPtr.Zero; + } + + /////////////////////////////////////////////////////////////////////// + + internal SQLiteConnectionHandle(IntPtr db, bool ownHandle) + : this(ownHandle) + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + this.ownHandle = ownHandle; + SetHandle(db); + } + } + + /////////////////////////////////////////////////////////////////////// + + private SQLiteConnectionHandle(bool ownHandle) + : base(IntPtr.Zero) + { +#if COUNT_HANDLE + if (ownHandle) + Interlocked.Increment(ref DebugData.connectionCount); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + protected override bool ReleaseHandle() + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + if (!ownHandle) return true; + } + + try + { +#if !PLATFORM_COMPACTFRAMEWORK + IntPtr localHandle = Interlocked.Exchange( + ref handle, IntPtr.Zero); + +#if SQLITE_STANDARD + if (localHandle != IntPtr.Zero) + closeConnection(this, localHandle); +#else + if (localHandle != IntPtr.Zero) + SQLiteBase.CloseConnection(this, localHandle); +#endif + +#if !NET_COMPACT_20 && TRACE_HANDLE + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "CloseConnection: {0}", localHandle)); /* throw */ + } + catch + { + } +#endif +#else + lock (syncRoot) + { + if (handle != IntPtr.Zero) + { + SQLiteBase.CloseConnection(this, handle); + SetHandle(IntPtr.Zero); + } + } +#endif +#if COUNT_HANDLE + Interlocked.Decrement(ref DebugData.connectionCount); +#endif +#if DEBUG + return true; +#endif + } +#if !NET_COMPACT_20 && TRACE_HANDLE + catch (SQLiteException e) +#else + catch (SQLiteException) +#endif + { +#if !NET_COMPACT_20 && TRACE_HANDLE + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "CloseConnection: {0}, exception: {1}", + handle, e)); /* throw */ + } + catch + { + } +#endif + } + finally + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + SetHandleAsInvalid(); + } + } +#if DEBUG + return false; +#else + return true; +#endif + } + + /////////////////////////////////////////////////////////////////////// + +#if COUNT_HANDLE + public int WasReleasedOk() + { + return Interlocked.Decrement(ref DebugData.connectionCount); + } +#endif + + /////////////////////////////////////////////////////////////////////// + + public bool OwnHandle + { + get + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + return ownHandle; + } + } + } + + /////////////////////////////////////////////////////////////////////// + + public override bool IsInvalid + { + get + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + return (handle == IntPtr.Zero); + } + } + } + + /////////////////////////////////////////////////////////////////////// + +#if DEBUG + public override string ToString() + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + return handle.ToString(); + } + } +#endif + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteStatementHandle Class + // Provides finalization support for unmanaged SQLite statements. + internal sealed class SQLiteStatementHandle : CriticalHandle + { +#if PLATFORM_COMPACTFRAMEWORK + internal readonly object syncRoot = new object(); +#endif + + /////////////////////////////////////////////////////////////////////// + + private SQLiteConnectionHandle cnn; + + /////////////////////////////////////////////////////////////////////// + + public static implicit operator IntPtr(SQLiteStatementHandle stmt) + { + if (stmt != null) + { +#if PLATFORM_COMPACTFRAMEWORK + lock (stmt.syncRoot) +#endif + { + return stmt.handle; + } + } + return IntPtr.Zero; + } + + /////////////////////////////////////////////////////////////////////// + + internal SQLiteStatementHandle(SQLiteConnectionHandle cnn, IntPtr stmt) + : this() + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + this.cnn = cnn; + SetHandle(stmt); + } + } + + /////////////////////////////////////////////////////////////////////// + + private SQLiteStatementHandle() + : base(IntPtr.Zero) + { +#if COUNT_HANDLE + Interlocked.Increment(ref DebugData.statementCount); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + protected override bool ReleaseHandle() + { + try + { +#if !PLATFORM_COMPACTFRAMEWORK + IntPtr localHandle = Interlocked.Exchange( + ref handle, IntPtr.Zero); + + if (localHandle != IntPtr.Zero) + SQLiteBase.FinalizeStatement(cnn, localHandle); + +#if !NET_COMPACT_20 && TRACE_HANDLE + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "FinalizeStatement: {0}", localHandle)); /* throw */ + } + catch + { + } +#endif +#else + lock (syncRoot) + { + if (handle != IntPtr.Zero) + { + SQLiteBase.FinalizeStatement(cnn, handle); + SetHandle(IntPtr.Zero); + } + } +#endif +#if COUNT_HANDLE + Interlocked.Decrement(ref DebugData.statementCount); +#endif +#if DEBUG + return true; +#endif + } +#if !NET_COMPACT_20 && TRACE_HANDLE + catch (SQLiteException e) +#else + catch (SQLiteException) +#endif + { +#if !NET_COMPACT_20 && TRACE_HANDLE + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "FinalizeStatement: {0}, exception: {1}", + handle, e)); /* throw */ + } + catch + { + } +#endif + } + finally + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + SetHandleAsInvalid(); + } + } +#if DEBUG + return false; +#else + return true; +#endif + } + + /////////////////////////////////////////////////////////////////////// + +#if COUNT_HANDLE + public int WasReleasedOk() + { + return Interlocked.Decrement(ref DebugData.statementCount); + } +#endif + + /////////////////////////////////////////////////////////////////////// + + public override bool IsInvalid + { + get + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + return (handle == IntPtr.Zero); + } + } + } + + /////////////////////////////////////////////////////////////////////// + +#if DEBUG + public override string ToString() + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + return handle.ToString(); + } + } +#endif + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteBackupHandle Class + // Provides finalization support for unmanaged SQLite backup objects. + internal sealed class SQLiteBackupHandle : CriticalHandle + { +#if PLATFORM_COMPACTFRAMEWORK + internal readonly object syncRoot = new object(); +#endif + + /////////////////////////////////////////////////////////////////////// + + private SQLiteConnectionHandle cnn; + + /////////////////////////////////////////////////////////////////////// + + public static implicit operator IntPtr(SQLiteBackupHandle backup) + { + if (backup != null) + { +#if PLATFORM_COMPACTFRAMEWORK + lock (backup.syncRoot) +#endif + { + return backup.handle; + } + } + return IntPtr.Zero; + } + + /////////////////////////////////////////////////////////////////////// + + internal SQLiteBackupHandle(SQLiteConnectionHandle cnn, IntPtr backup) + : this() + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + this.cnn = cnn; + SetHandle(backup); + } + } + + /////////////////////////////////////////////////////////////////////// + + private SQLiteBackupHandle() + : base(IntPtr.Zero) + { +#if COUNT_HANDLE + Interlocked.Increment(ref DebugData.backupCount); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + protected override bool ReleaseHandle() + { + try + { +#if !PLATFORM_COMPACTFRAMEWORK + IntPtr localHandle = Interlocked.Exchange( + ref handle, IntPtr.Zero); + + if (localHandle != IntPtr.Zero) + SQLiteBase.FinishBackup(cnn, localHandle); + +#if !NET_COMPACT_20 && TRACE_HANDLE + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "FinishBackup: {0}", localHandle)); /* throw */ + } + catch + { + } +#endif +#else + lock (syncRoot) + { + if (handle != IntPtr.Zero) + { + SQLiteBase.FinishBackup(cnn, handle); + SetHandle(IntPtr.Zero); + } + } +#endif +#if COUNT_HANDLE + Interlocked.Decrement(ref DebugData.backupCount); +#endif +#if DEBUG + return true; +#endif + } +#if !NET_COMPACT_20 && TRACE_HANDLE + catch (SQLiteException e) +#else + catch (SQLiteException) +#endif + { +#if !NET_COMPACT_20 && TRACE_HANDLE + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "FinishBackup: {0}, exception: {1}", + handle, e)); /* throw */ + } + catch + { + } +#endif + } + finally + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + SetHandleAsInvalid(); + } + } +#if DEBUG + return false; +#else + return true; +#endif + } + + /////////////////////////////////////////////////////////////////////// + +#if COUNT_HANDLE + public int WasReleasedOk() + { + return Interlocked.Decrement(ref DebugData.backupCount); + } +#endif + + /////////////////////////////////////////////////////////////////////// + + public override bool IsInvalid + { + get + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + return (handle == IntPtr.Zero); + } + } + } + + /////////////////////////////////////////////////////////////////////// + +#if DEBUG + public override string ToString() + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + return handle.ToString(); + } + } +#endif + } + #endregion + + /////////////////////////////////////////////////////////////////////////// + + #region SQLiteBlobHandle Class + // Provides finalization support for unmanaged SQLite blob objects. + internal sealed class SQLiteBlobHandle : CriticalHandle + { +#if PLATFORM_COMPACTFRAMEWORK + internal readonly object syncRoot = new object(); +#endif + + /////////////////////////////////////////////////////////////////////// + + private SQLiteConnectionHandle cnn; + + /////////////////////////////////////////////////////////////////////// + + public static implicit operator IntPtr(SQLiteBlobHandle blob) + { + if (blob != null) + { +#if PLATFORM_COMPACTFRAMEWORK + lock (blob.syncRoot) +#endif + { + return blob.handle; + } + } + return IntPtr.Zero; + } + + /////////////////////////////////////////////////////////////////////// + + internal SQLiteBlobHandle(SQLiteConnectionHandle cnn, IntPtr blob) + : this() + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + this.cnn = cnn; + SetHandle(blob); + } + } + + /////////////////////////////////////////////////////////////////////// + + private SQLiteBlobHandle() + : base(IntPtr.Zero) + { +#if COUNT_HANDLE + Interlocked.Increment(ref DebugData.blobCount); +#endif + } + + /////////////////////////////////////////////////////////////////////// + + protected override bool ReleaseHandle() + { + try + { +#if !PLATFORM_COMPACTFRAMEWORK + IntPtr localHandle = Interlocked.Exchange( + ref handle, IntPtr.Zero); + + if (localHandle != IntPtr.Zero) + SQLiteBase.CloseBlob(cnn, localHandle); + +#if !NET_COMPACT_20 && TRACE_HANDLE + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "CloseBlob: {0}", localHandle)); /* throw */ + } + catch + { + } +#endif +#else + lock (syncRoot) + { + if (handle != IntPtr.Zero) + { + SQLiteBase.CloseBlob(cnn, handle); + SetHandle(IntPtr.Zero); + } + } +#endif +#if COUNT_HANDLE + Interlocked.Decrement(ref DebugData.blobCount); +#endif +#if DEBUG + return true; +#endif + } +#if !NET_COMPACT_20 && TRACE_HANDLE + catch (SQLiteException e) +#else + catch (SQLiteException) +#endif + { +#if !NET_COMPACT_20 && TRACE_HANDLE + try + { + Trace.WriteLine(HelperMethods.StringFormat( + CultureInfo.CurrentCulture, + "CloseBlob: {0}, exception: {1}", + handle, e)); /* throw */ + } + catch + { + } +#endif + } + finally + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + SetHandleAsInvalid(); + } + } +#if DEBUG + return false; +#else + return true; +#endif + } + + /////////////////////////////////////////////////////////////////////// + +#if COUNT_HANDLE + public int WasReleasedOk() + { + return Interlocked.Decrement(ref DebugData.blobCount); + } +#endif + + /////////////////////////////////////////////////////////////////////// + + public override bool IsInvalid + { + get + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + return (handle == IntPtr.Zero); + } + } + } + + /////////////////////////////////////////////////////////////////////// + +#if DEBUG + public override string ToString() + { +#if PLATFORM_COMPACTFRAMEWORK + lock (syncRoot) +#endif + { + return handle.ToString(); + } + } +#endif + } + #endregion +} diff --git a/Native.Csharp/App/Core/LibExport.cs b/Native.Csharp/App/Core/LibExport.cs index c21da702..920b7491 100644 --- a/Native.Csharp/App/Core/LibExport.cs +++ b/Native.Csharp/App/Core/LibExport.cs @@ -15,13 +15,15 @@ using Native.Csharp.App.Interface; using Native.Csharp.Sdk.Cqp; using Native.Csharp.Sdk.Cqp.Enum; -using Native.Csharp.Tool; +using Native.Csharp.Sdk.Cqp.Other; using Native.Csharp.Repair; namespace Native.Csharp.App.Core { public class LibExport { + private static Encoding _defaultEncoding = null; + #region --构造函数-- /// /// 静态构造函数, 注册依赖注入回调 @@ -29,6 +31,8 @@ public class LibExport /// static LibExport () { + _defaultEncoding = Encoding.GetEncoding ("GB18030"); + // 初始化 Costura CosturaUtility.Initialize (); @@ -334,7 +338,7 @@ private static int EventPrivateMsg (int subType, int msgId, long fromQQ, IntPtr PrivateMessageEventArgs args = new PrivateMessageEventArgs (); args.MsgId = msgId; args.FromQQ = fromQQ; - args.Msg = NativeConvert.ToPtrString (msg, Encoding.GetEncoding ("GB18030")); + args.Msg = msg.ToString (_defaultEncoding); args.Handled = false; if (subType == 11) // 来自好友 @@ -368,7 +372,7 @@ private static int EventGroupMsg (int subType, int msgId, long fromGroup, long f args.MsgId = msgId; args.FromGroup = fromGroup; args.FromQQ = fromQQ; - args.Msg = NativeConvert.ToPtrString (msg, Encoding.GetEncoding ("GB18030")); + args.Msg = msg.ToString (_defaultEncoding); args.FromAnonymous = null; args.IsAnonymousMsg = false; args.Handled = false; @@ -398,7 +402,7 @@ private static int EventDiscussMsg (int subType, int msgId, long fromDiscuss, lo args.MsgId = msgId; args.FromDiscuss = fromDiscuss; args.FromQQ = fromQQ; - args.Msg = NativeConvert.ToPtrString (msg, Encoding.GetEncoding ("GB18030")); + args.Msg = msg.ToString (_defaultEncoding); args.Handled = false; if (subType == 1) // 讨论组消息 @@ -417,7 +421,7 @@ private static int EventDiscussMsg (int subType, int msgId, long fromDiscuss, lo private static int EventGroupUpload (int subType, int sendTime, long fromGroup, long fromQQ, string file) { FileUploadMessageEventArgs args = new FileUploadMessageEventArgs (); - args.SendTime = NativeConvert.FotmatUnixTime (sendTime.ToString ()); + args.SendTime = sendTime.ToDateTime (); args.FromGroup = fromGroup; args.FromQQ = fromQQ; args.File = Common.CqApi.GetFile (file); @@ -429,7 +433,7 @@ private static int EventGroupUpload (int subType, int sendTime, long fromGroup, private static int EventSystemGroupAdmin (int subType, int sendTime, long fromGroup, long beingOperateQQ) { GroupManageAlterEventArgs args = new GroupManageAlterEventArgs (); - args.SendTime = NativeConvert.FotmatUnixTime (sendTime.ToString ()); + args.SendTime = sendTime.ToDateTime (); args.FromGroup = fromGroup; args.BeingOperateQQ = beingOperateQQ; args.Handled = false; @@ -454,7 +458,7 @@ private static int EventSystemGroupAdmin (int subType, int sendTime, long fromGr private static int EventSystemGroupMemberDecrease (int subType, int sendTime, long fromGroup, long fromQQ, long beingOperateQQ) { GroupMemberAlterEventArgs args = new GroupMemberAlterEventArgs (); - args.SendTime = NativeConvert.FotmatUnixTime (sendTime.ToString ()); + args.SendTime = sendTime.ToDateTime (); args.FromGroup = fromGroup; args.FromQQ = fromQQ; args.BeingOperateQQ = beingOperateQQ; @@ -481,7 +485,7 @@ private static int EventSystemGroupMemberDecrease (int subType, int sendTime, lo private static int EventSystemGroupMemberIncrease (int subType, int sendTime, long fromGroup, long fromQQ, long beingOperateQQ) { GroupMemberAlterEventArgs args = new GroupMemberAlterEventArgs (); - args.SendTime = NativeConvert.FotmatUnixTime (sendTime.ToString ()); + args.SendTime = sendTime.ToDateTime (); args.FromGroup = fromGroup; args.FromQQ = fromQQ; args.BeingOperateQQ = beingOperateQQ; @@ -507,7 +511,7 @@ private static int EventSystemGroupMemberIncrease (int subType, int sendTime, lo private static int EventFriendAdd (int subType, int sendTime, long fromQQ) { FriendIncreaseEventArgs args = new FriendIncreaseEventArgs (); - args.SendTime = NativeConvert.FotmatUnixTime (sendTime.ToString ()); + args.SendTime = sendTime.ToDateTime (); args.FromQQ = fromQQ; args.Handled = false; @@ -527,9 +531,9 @@ private static int EventFriendAdd (int subType, int sendTime, long fromQQ) private static int EventRequestAddFriend (int subType, int sendTime, long fromQQ, IntPtr msg, string responseFlag) { FriendAddRequestEventArgs args = new FriendAddRequestEventArgs (); - args.SendTime = NativeConvert.FotmatUnixTime (sendTime.ToString ()); + args.SendTime = sendTime.ToDateTime (); args.FromQQ = fromQQ; - args.AppendMsg = NativeConvert.ToPtrString (msg, Encoding.GetEncoding ("GB18030")); + args.AppendMsg = msg.ToString (_defaultEncoding); args.Tag = responseFlag; args.Handled = false; @@ -549,10 +553,10 @@ private static int EventRequestAddFriend (int subType, int sendTime, long fromQQ private static int EventRequestAddGroup (int subType, int sendTime, long fromGroup, long fromQQ, IntPtr msg, string responseFlag) { GroupAddRequestEventArgs args = new GroupAddRequestEventArgs (); - args.SendTime = NativeConvert.FotmatUnixTime (sendTime.ToString ()); + args.SendTime = sendTime.ToDateTime (); args.FromGroup = fromGroup; args.FromQQ = fromQQ; - args.AppendMsg = NativeConvert.ToPtrString (msg, Encoding.GetEncoding ("GB18030")); + args.AppendMsg = msg.ToString (_defaultEncoding); args.Tag = responseFlag; args.Handled = false; diff --git a/Native.Csharp/App/Core/LibExport.tt b/Native.Csharp/App/Core/LibExport.tt index c075351f..5d0d7610 100644 --- a/Native.Csharp/App/Core/LibExport.tt +++ b/Native.Csharp/App/Core/LibExport.tt @@ -21,13 +21,15 @@ using Native.Csharp.App.Model; using Native.Csharp.App.Interface; using Native.Csharp.Sdk.Cqp; using Native.Csharp.Sdk.Cqp.Enum; -using Native.Csharp.Tool; +using Native.Csharp.Sdk.Cqp.Other; using Native.Csharp.Repair; namespace Native.Csharp.App.Core { public class LibExport { + private static Encoding _defaultEncoding = null; + #region --构造函数-- /// /// 静态构造函数, 注册依赖注入回调 @@ -35,6 +37,8 @@ namespace Native.Csharp.App.Core /// static LibExport () { + _defaultEncoding = Encoding.GetEncoding ("GB18030"); + // 初始化 Costura CosturaUtility.Initialize (); @@ -340,7 +344,7 @@ namespace Native.Csharp.App.Core PrivateMessageEventArgs args = new PrivateMessageEventArgs (); args.MsgId = msgId; args.FromQQ = fromQQ; - args.Msg = NativeConvert.ToPtrString (msg, Encoding.GetEncoding ("GB18030")); + args.Msg = msg.ToString (_defaultEncoding); args.Handled = false; if (subType == 11) // 来自好友 @@ -374,7 +378,7 @@ namespace Native.Csharp.App.Core args.MsgId = msgId; args.FromGroup = fromGroup; args.FromQQ = fromQQ; - args.Msg = NativeConvert.ToPtrString (msg, Encoding.GetEncoding ("GB18030")); + args.Msg = msg.ToString (_defaultEncoding); args.FromAnonymous = null; args.IsAnonymousMsg = false; args.Handled = false; @@ -404,7 +408,7 @@ namespace Native.Csharp.App.Core args.MsgId = msgId; args.FromDiscuss = fromDiscuss; args.FromQQ = fromQQ; - args.Msg = NativeConvert.ToPtrString (msg, Encoding.GetEncoding ("GB18030")); + args.Msg = msg.ToString (_defaultEncoding); args.Handled = false; if (subType == 1) // 讨论组消息 @@ -423,7 +427,7 @@ namespace Native.Csharp.App.Core private static int EventGroupUpload (int subType, int sendTime, long fromGroup, long fromQQ, string file) { FileUploadMessageEventArgs args = new FileUploadMessageEventArgs (); - args.SendTime = NativeConvert.FotmatUnixTime (sendTime.ToString ()); + args.SendTime = sendTime.ToDateTime (); args.FromGroup = fromGroup; args.FromQQ = fromQQ; args.File = Common.CqApi.GetFile (file); @@ -435,7 +439,7 @@ namespace Native.Csharp.App.Core private static int EventSystemGroupAdmin (int subType, int sendTime, long fromGroup, long beingOperateQQ) { GroupManageAlterEventArgs args = new GroupManageAlterEventArgs (); - args.SendTime = NativeConvert.FotmatUnixTime (sendTime.ToString ()); + args.SendTime = sendTime.ToDateTime (); args.FromGroup = fromGroup; args.BeingOperateQQ = beingOperateQQ; args.Handled = false; @@ -460,7 +464,7 @@ namespace Native.Csharp.App.Core private static int EventSystemGroupMemberDecrease (int subType, int sendTime, long fromGroup, long fromQQ, long beingOperateQQ) { GroupMemberAlterEventArgs args = new GroupMemberAlterEventArgs (); - args.SendTime = NativeConvert.FotmatUnixTime (sendTime.ToString ()); + args.SendTime = sendTime.ToDateTime (); args.FromGroup = fromGroup; args.FromQQ = fromQQ; args.BeingOperateQQ = beingOperateQQ; @@ -487,7 +491,7 @@ namespace Native.Csharp.App.Core private static int EventSystemGroupMemberIncrease (int subType, int sendTime, long fromGroup, long fromQQ, long beingOperateQQ) { GroupMemberAlterEventArgs args = new GroupMemberAlterEventArgs (); - args.SendTime = NativeConvert.FotmatUnixTime (sendTime.ToString ()); + args.SendTime = sendTime.ToDateTime (); args.FromGroup = fromGroup; args.FromQQ = fromQQ; args.BeingOperateQQ = beingOperateQQ; @@ -513,7 +517,7 @@ namespace Native.Csharp.App.Core private static int EventFriendAdd (int subType, int sendTime, long fromQQ) { FriendIncreaseEventArgs args = new FriendIncreaseEventArgs (); - args.SendTime = NativeConvert.FotmatUnixTime (sendTime.ToString ()); + args.SendTime = sendTime.ToDateTime (); args.FromQQ = fromQQ; args.Handled = false; @@ -533,9 +537,9 @@ namespace Native.Csharp.App.Core private static int EventRequestAddFriend (int subType, int sendTime, long fromQQ, IntPtr msg, string responseFlag) { FriendAddRequestEventArgs args = new FriendAddRequestEventArgs (); - args.SendTime = NativeConvert.FotmatUnixTime (sendTime.ToString ()); + args.SendTime = sendTime.ToDateTime (); args.FromQQ = fromQQ; - args.AppendMsg = NativeConvert.ToPtrString (msg, Encoding.GetEncoding ("GB18030")); + args.AppendMsg = msg.ToString (_defaultEncoding); args.Tag = responseFlag; args.Handled = false; @@ -555,10 +559,10 @@ namespace Native.Csharp.App.Core private static int EventRequestAddGroup (int subType, int sendTime, long fromGroup, long fromQQ, IntPtr msg, string responseFlag) { GroupAddRequestEventArgs args = new GroupAddRequestEventArgs (); - args.SendTime = NativeConvert.FotmatUnixTime (sendTime.ToString ()); + args.SendTime = sendTime.ToDateTime (); args.FromGroup = fromGroup; args.FromQQ = fromQQ; - args.AppendMsg = NativeConvert.ToPtrString (msg, Encoding.GetEncoding ("GB18030")); + args.AppendMsg = msg.ToString (_defaultEncoding); args.Tag = responseFlag; args.Handled = false; diff --git a/Native.Csharp/Native.Csharp.csproj b/Native.Csharp/Native.Csharp.csproj index 312cf64f..5d96cecf 100644 --- a/Native.Csharp/Native.Csharp.csproj +++ b/Native.Csharp/Native.Csharp.csproj @@ -129,10 +129,6 @@ {797eaebc-4d5b-4eef-87f4-a508fda2cb6a} Native.Csharp.Sdk - - {9db94cbf-6843-4ea3-9241-769124416fe9} - Native.Csharp.Tool - diff --git a/Native.Csharp/Properties/AssemblyInfo.cs b/Native.Csharp/Properties/AssemblyInfo.cs index b622e213..fc50a463 100644 --- a/Native.Csharp/Properties/AssemblyInfo.cs +++ b/Native.Csharp/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 //通过使用 "*",如下所示: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion ("3.0.6.0525")] -[assembly: AssemblyFileVersion ("3.0.6.0525")] +[assembly: AssemblyVersion ("3.0.7.0607")] +[assembly: AssemblyFileVersion ("3.0.7.0607")] diff --git a/README.md b/README.md index edb36fe9..c689bfde 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,18 @@ > 4. ~~对于接收消息时, 颜文字表情, 特殊符号乱码, 当前正在寻找转换方式~~ (已修复) ## Native.SDK 更新日志 +> 2019年06月07日 版本: V3.0.7.0607 + + 由于 酷Q 停止对 Windows XP/Vista 系统的支持, 所以 Native.SDK 将停止继续使用 .Net 4.0 + 并将此版本作为最终发布版归档处理, 下个版本开始仅对 .Net 4.5+ 更新 + + 1. 修复 悬浮窗数据转换错误 (由 Pack -> BinaryWriter) + 2. 优化 部分 Api 接口的数据处理效率 (由 UnPack -> BinaryReader) + 3. 优化 分离 Native.Csharp.Tool 项目, 使 SDK 更轻量 + 4. 优化 调整 Native.Csharp.Tool 项目结构, 每个模块为一个根文件夹. 排除即可在编译时移除功能 + 5. 优化 新增 HttpTool (位于 Native.Csharp.Tool.Http) + 6. 新增 SQLite 操作类 (不包含EF, 需要可自行添加), 完全移植自 System.Data.SQLite (.Net 4.0) + > 2019年05月25日 版本: V3.0.6.0525 1. 修复 HttpWebClient 类在请求 Internet 资源时响应重定向的部分代码错误