From c10f73758eddaebc47bab8ed55123b17bdeda6d7 Mon Sep 17 00:00:00 2001 From: JieGG <947295340@qq.com> Date: Sat, 8 Jun 2019 15:29:27 +0800 Subject: [PATCH] V2.0.6.0607 --- Native.Basic/App/Core/LibExport.tt | 32 +- Native.Basic/App/Core/LibExport1.vb | 32 +- Native.Basic/My Project/AssemblyInfo.vb | 4 +- Native.Basic/Native.Basic.vbproj | 15 +- Native.Csharp.Sdk/Cqp/CqApi.cs | 190 +- .../Cqp/Other/BinaryReaderExpand.cs | 140 + .../Cqp/Other/BinaryWriterExpand.cs | 97 + Native.Csharp.Sdk/Cqp/Other/OtherExpand.cs | 86 + Native.Csharp.Sdk/Cqp/Other/Pack.cs | 128 - Native.Csharp.Sdk/Cqp/Other/UnPack.cs | 133 - Native.Csharp.Sdk/Native.Csharp.Sdk.csproj | 11 +- Native.Csharp.Sdk/Properties/AssemblyInfo.cs | 4 +- Native.Csharp.Tool/Core/Kernel32.cs | 32 - Native.Csharp.Tool/Http/HttpHelper.cs | 424 - Native.Csharp.Tool/Http/HttpTool.cs | 56 + Native.Csharp.Tool/Http/HttpWebClient.cs | 1404 ++- .../IniConfig/Linq/IniObject.cs | 6 +- Native.Csharp.Tool/IniConfig/Linq/IniValue.cs | 1 - Native.Csharp.Tool/IniConfig/README.md | 5 +- Native.Csharp.Tool/IniFile.cs | 331 - Native.Csharp.Tool/Native.Csharp.Tool.csproj | 71 +- Native.Csharp.Tool/NativeConvert.cs | 78 +- Native.Csharp.Tool/Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblySourceIdAttribute.cs | 42 + .../AssemblySourceTimeStampAttribute.cs | 42 + .../System.Data.SQLite.dll.config | 288 + .../SQLite/Generated/SR.resources | Bin 0 -> 35255 bytes .../SQLite/ISQLiteNativeModule.cs | 1642 +++ .../SQLite/LINQ/SQLiteConnection_Linq.cs | 22 + .../SQLite/LINQ/SQLiteFactory_Linq.cs | 110 + .../SQLite/Resources/DataTypes.xml | 806 ++ .../SQLite/Resources/MetaDataCollections.xml | 78 + .../SQLite/Resources/SQLiteCommand.bmp | Bin 0 -> 246 bytes .../SQLite/Resources/SQLiteConnection.bmp | Bin 0 -> 246 bytes .../SQLite/Resources/SQLiteDataAdapter.bmp | Bin 0 -> 246 bytes .../SQLite/Resources/SR.Designer.cs | 121 + Native.Csharp.Tool/SQLite/Resources/SR.resx | 138 + Native.Csharp.Tool/SQLite/SQLite3.cs | 4143 ++++++++ Native.Csharp.Tool/SQLite/SQLite3_UTF16.cs | 384 + Native.Csharp.Tool/SQLite/SQLiteBackup.cs | 149 + Native.Csharp.Tool/SQLite/SQLiteBase.cs | 1656 ++++ Native.Csharp.Tool/SQLite/SQLiteBlob.cs | 410 + Native.Csharp.Tool/SQLite/SQLiteCommand.cs | 1160 +++ .../SQLite/SQLiteCommandBuilder.cs | 415 + Native.Csharp.Tool/SQLite/SQLiteConnection.cs | 7724 +++++++++++++++ .../SQLite/SQLiteConnectionPool.cs | 1028 ++ .../SQLite/SQLiteConnectionStringBuilder.cs | 991 ++ Native.Csharp.Tool/SQLite/SQLiteConvert.cs | 3026 ++++++ .../SQLite/SQLiteDataAdapter.cs | 328 + Native.Csharp.Tool/SQLite/SQLiteDataReader.cs | 2165 ++++ .../SQLite/SQLiteDefineConstants.cs | 230 + Native.Csharp.Tool/SQLite/SQLiteEnlistment.cs | 297 + Native.Csharp.Tool/SQLite/SQLiteException.cs | 853 ++ Native.Csharp.Tool/SQLite/SQLiteFactory.cs | 169 + Native.Csharp.Tool/SQLite/SQLiteFunction.cs | 1948 ++++ .../SQLite/SQLiteFunctionAttribute.cs | 125 + Native.Csharp.Tool/SQLite/SQLiteKeyReader.cs | 764 ++ Native.Csharp.Tool/SQLite/SQLiteLog.cs | 652 ++ .../SQLite/SQLiteMetaDataCollectionNames.cs | 52 + Native.Csharp.Tool/SQLite/SQLiteModule.cs | 8765 +++++++++++++++++ .../SQLite/SQLiteModuleCommon.cs | 286 + .../SQLite/SQLiteModuleEnumerable.cs | 1270 +++ Native.Csharp.Tool/SQLite/SQLiteModuleNoop.cs | 786 ++ Native.Csharp.Tool/SQLite/SQLiteParameter.cs | 511 + .../SQLite/SQLiteParameterCollection.cs | 477 + Native.Csharp.Tool/SQLite/SQLitePatchLevel.cs | 16 + Native.Csharp.Tool/SQLite/SQLiteSession.cs | 5568 +++++++++++ Native.Csharp.Tool/SQLite/SQLiteStatement.cs | 558 ++ .../SQLite/SQLiteTransaction.cs | 175 + .../SQLite/SQLiteTransaction2.cs | 276 + .../SQLite/SQLiteTransactionBase.cs | 214 + .../SQLite/UnsafeNativeMethods.cs | 6013 +++++++++++ README.md | 12 + 73 files changed, 58222 insertions(+), 1947 deletions(-) create mode 100644 Native.Csharp.Sdk/Cqp/Other/BinaryReaderExpand.cs create mode 100644 Native.Csharp.Sdk/Cqp/Other/BinaryWriterExpand.cs create mode 100644 Native.Csharp.Sdk/Cqp/Other/OtherExpand.cs delete mode 100644 Native.Csharp.Sdk/Cqp/Other/Pack.cs delete mode 100644 Native.Csharp.Sdk/Cqp/Other/UnPack.cs delete mode 100644 Native.Csharp.Tool/Core/Kernel32.cs delete mode 100644 Native.Csharp.Tool/Http/HttpHelper.cs create mode 100644 Native.Csharp.Tool/Http/HttpTool.cs delete mode 100644 Native.Csharp.Tool/IniFile.cs create mode 100644 Native.Csharp.Tool/Properties/AssemblySourceIdAttribute.cs create mode 100644 Native.Csharp.Tool/Properties/AssemblySourceTimeStampAttribute.cs create mode 100644 Native.Csharp.Tool/SQLite/Configurations/System.Data.SQLite.dll.config create mode 100644 Native.Csharp.Tool/SQLite/Generated/SR.resources create mode 100644 Native.Csharp.Tool/SQLite/ISQLiteNativeModule.cs create mode 100644 Native.Csharp.Tool/SQLite/LINQ/SQLiteConnection_Linq.cs create mode 100644 Native.Csharp.Tool/SQLite/LINQ/SQLiteFactory_Linq.cs create mode 100644 Native.Csharp.Tool/SQLite/Resources/DataTypes.xml create mode 100644 Native.Csharp.Tool/SQLite/Resources/MetaDataCollections.xml create mode 100644 Native.Csharp.Tool/SQLite/Resources/SQLiteCommand.bmp create mode 100644 Native.Csharp.Tool/SQLite/Resources/SQLiteConnection.bmp create mode 100644 Native.Csharp.Tool/SQLite/Resources/SQLiteDataAdapter.bmp create mode 100644 Native.Csharp.Tool/SQLite/Resources/SR.Designer.cs create mode 100644 Native.Csharp.Tool/SQLite/Resources/SR.resx create mode 100644 Native.Csharp.Tool/SQLite/SQLite3.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLite3_UTF16.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteBackup.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteBase.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteBlob.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteCommand.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteCommandBuilder.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteConnection.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteConnectionPool.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteConnectionStringBuilder.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteConvert.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteDataAdapter.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteDataReader.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteDefineConstants.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteEnlistment.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteException.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteFactory.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteFunction.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteFunctionAttribute.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteKeyReader.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteLog.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteMetaDataCollectionNames.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteModule.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteModuleCommon.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteModuleEnumerable.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteModuleNoop.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteParameter.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteParameterCollection.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLitePatchLevel.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteSession.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteStatement.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteTransaction.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteTransaction2.cs create mode 100644 Native.Csharp.Tool/SQLite/SQLiteTransactionBase.cs create mode 100644 Native.Csharp.Tool/SQLite/UnsafeNativeMethods.cs diff --git a/Native.Basic/App/Core/LibExport.tt b/Native.Basic/App/Core/LibExport.tt index e407787..b1d5976 100644 --- a/Native.Basic/App/Core/LibExport.tt +++ b/Native.Basic/App/Core/LibExport.tt @@ -16,16 +16,20 @@ Imports Native.Basic.App.Interface Imports Native.Basic.Repair Imports Native.Csharp.Sdk.Cqp Imports Native.Csharp.Sdk.Cqp.Enum -Imports Native.Csharp.Tool +Imports Native.Csharp.Sdk.Cqp.Other Imports Unity Namespace App.Core Public Class LibExport + + Private Shared Property DefaultEncoding As Encoding -''' + ''' ''' 静态构造函数, 初始化应用基础服务 ''' Shared Sub New() + DefaultEncoding = Encoding.GetEncoding("GB18030") + ' 初始化 Costura CosturaUtility.Initialize () @@ -296,7 +300,7 @@ Namespace App.Core Dim args As PrivateMessageEventArgs = 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 Then @@ -320,7 +324,7 @@ Namespace 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 = Nothing args.IsAnonymousMsg = False args.Handled = False @@ -345,7 +349,7 @@ Namespace 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 Then @@ -360,7 +364,7 @@ Namespace App.Core Private Shared Function EventGroupUpload(ByVal subType As Integer, ByVal sendTime As Integer, ByVal fromGroup As Long, ByVal fromQQ As Long, ByVal file As String) As Integer Dim args As FileUploadMessageEventArgs = New FileUploadMessageEventArgs() - args.SendTime = NativeConvert.FotmatUnixTime(sendTime.ToString()) + args.SendTime = sendTime.ToDateTime() args.FromGroup = fromGroup args.FromQQ = fromQQ args.File = Common.CqApi.GetFile(file) @@ -371,7 +375,7 @@ Namespace App.Core Private Shared Function EventSystemGroupAdmin(ByVal subType As Integer, ByVal sendTime As Integer, ByVal fromGroup As Long, ByVal beingOperateQQ As Long) As Integer Dim args As GroupManageAlterEventArgs = New GroupManageAlterEventArgs() - args.SendTime = NativeConvert.FotmatUnixTime(sendTime.ToString()) + args.SendTime = sendTime.ToDateTime() args.FromGroup = fromGroup args.BeingOperateQQ = beingOperateQQ args.Handled = False @@ -390,7 +394,7 @@ Namespace App.Core Private Shared Function EventSystemGroupMemberDecrease(ByVal subType As Integer, ByVal sendTime As Integer, ByVal fromGroup As Long, ByVal fromQQ As Long, ByVal beingOperateQQ As Long) As Integer Dim args As GroupMemberAlterEventArgs = New GroupMemberAlterEventArgs() - args.SendTime = NativeConvert.FotmatUnixTime(sendTime.ToString()) + args.SendTime = sendTime.ToDateTime() args.FromGroup = fromGroup args.FromQQ = fromQQ args.BeingOperateQQ = beingOperateQQ @@ -411,7 +415,7 @@ Namespace App.Core Private Shared Function EventSystemGroupMemberIncrease(ByVal subType As Integer, ByVal sendTime As Integer, ByVal fromGroup As Long, ByVal fromQQ As Long, ByVal beingOperateQQ As Long) As Integer Dim args As GroupMemberAlterEventArgs = New GroupMemberAlterEventArgs() - args.SendTime = NativeConvert.FotmatUnixTime(sendTime.ToString()) + args.SendTime = sendTime.ToDateTime() args.FromGroup = fromGroup args.FromQQ = fromQQ args.BeingOperateQQ = beingOperateQQ @@ -431,7 +435,7 @@ Namespace App.Core Private Shared Function EventFriendAdd(ByVal subType As Integer, ByVal sendTime As Integer, ByVal fromQQ As Long) As Integer Dim args As FriendIncreaseEventArgs = New FriendIncreaseEventArgs() - args.SendTime = NativeConvert.FotmatUnixTime(sendTime.ToString()) + args.SendTime = sendTime.ToDateTime() args.FromQQ = fromQQ args.Handled = False @@ -447,9 +451,9 @@ Namespace App.Core Private Shared Function EventRequestAddFriend(ByVal subType As Integer, ByVal sendTime As Integer, ByVal fromQQ As Long, ByVal msg As IntPtr, ByVal responseFlag As String) As Integer Dim args As FriendAddRequestEventArgs = 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 @@ -465,10 +469,10 @@ Namespace App.Core Private Shared Function EventRequestAddGroup(ByVal subType As Integer, ByVal sendTime As Integer, ByVal fromGroup As Long, ByVal fromQQ As Long, ByVal msg As IntPtr, ByVal responseFlag As String) As Integer Dim args As GroupAddRequestEventArgs = 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.Basic/App/Core/LibExport1.vb b/Native.Basic/App/Core/LibExport1.vb index f9e6d8a..032b2bc 100644 --- a/Native.Basic/App/Core/LibExport1.vb +++ b/Native.Basic/App/Core/LibExport1.vb @@ -10,16 +10,20 @@ Imports Native.Basic.App.Interface Imports Native.Basic.Repair Imports Native.Csharp.Sdk.Cqp Imports Native.Csharp.Sdk.Cqp.Enum -Imports Native.Csharp.Tool +Imports Native.Csharp.Sdk.Cqp.Other Imports Unity Namespace App.Core Public Class LibExport + + Private Shared Property DefaultEncoding As Encoding -''' + ''' ''' 静态构造函数, 初始化应用基础服务 ''' Shared Sub New() + DefaultEncoding = Encoding.GetEncoding("GB18030") + ' 初始化 Costura CosturaUtility.Initialize () @@ -290,7 +294,7 @@ Namespace App.Core Dim args As PrivateMessageEventArgs = 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 Then @@ -314,7 +318,7 @@ Namespace 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 = Nothing args.IsAnonymousMsg = False args.Handled = False @@ -339,7 +343,7 @@ Namespace 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 Then @@ -354,7 +358,7 @@ Namespace App.Core Private Shared Function EventGroupUpload(ByVal subType As Integer, ByVal sendTime As Integer, ByVal fromGroup As Long, ByVal fromQQ As Long, ByVal file As String) As Integer Dim args As FileUploadMessageEventArgs = New FileUploadMessageEventArgs() - args.SendTime = NativeConvert.FotmatUnixTime(sendTime.ToString()) + args.SendTime = sendTime.ToDateTime() args.FromGroup = fromGroup args.FromQQ = fromQQ args.File = Common.CqApi.GetFile(file) @@ -365,7 +369,7 @@ Namespace App.Core Private Shared Function EventSystemGroupAdmin(ByVal subType As Integer, ByVal sendTime As Integer, ByVal fromGroup As Long, ByVal beingOperateQQ As Long) As Integer Dim args As GroupManageAlterEventArgs = New GroupManageAlterEventArgs() - args.SendTime = NativeConvert.FotmatUnixTime(sendTime.ToString()) + args.SendTime = sendTime.ToDateTime() args.FromGroup = fromGroup args.BeingOperateQQ = beingOperateQQ args.Handled = False @@ -384,7 +388,7 @@ Namespace App.Core Private Shared Function EventSystemGroupMemberDecrease(ByVal subType As Integer, ByVal sendTime As Integer, ByVal fromGroup As Long, ByVal fromQQ As Long, ByVal beingOperateQQ As Long) As Integer Dim args As GroupMemberAlterEventArgs = New GroupMemberAlterEventArgs() - args.SendTime = NativeConvert.FotmatUnixTime(sendTime.ToString()) + args.SendTime = sendTime.ToDateTime() args.FromGroup = fromGroup args.FromQQ = fromQQ args.BeingOperateQQ = beingOperateQQ @@ -405,7 +409,7 @@ Namespace App.Core Private Shared Function EventSystemGroupMemberIncrease(ByVal subType As Integer, ByVal sendTime As Integer, ByVal fromGroup As Long, ByVal fromQQ As Long, ByVal beingOperateQQ As Long) As Integer Dim args As GroupMemberAlterEventArgs = New GroupMemberAlterEventArgs() - args.SendTime = NativeConvert.FotmatUnixTime(sendTime.ToString()) + args.SendTime = sendTime.ToDateTime() args.FromGroup = fromGroup args.FromQQ = fromQQ args.BeingOperateQQ = beingOperateQQ @@ -425,7 +429,7 @@ Namespace App.Core Private Shared Function EventFriendAdd(ByVal subType As Integer, ByVal sendTime As Integer, ByVal fromQQ As Long) As Integer Dim args As FriendIncreaseEventArgs = New FriendIncreaseEventArgs() - args.SendTime = NativeConvert.FotmatUnixTime(sendTime.ToString()) + args.SendTime = sendTime.ToDateTime() args.FromQQ = fromQQ args.Handled = False @@ -441,9 +445,9 @@ Namespace App.Core Private Shared Function EventRequestAddFriend(ByVal subType As Integer, ByVal sendTime As Integer, ByVal fromQQ As Long, ByVal msg As IntPtr, ByVal responseFlag As String) As Integer Dim args As FriendAddRequestEventArgs = 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 @@ -459,10 +463,10 @@ Namespace App.Core Private Shared Function EventRequestAddGroup(ByVal subType As Integer, ByVal sendTime As Integer, ByVal fromGroup As Long, ByVal fromQQ As Long, ByVal msg As IntPtr, ByVal responseFlag As String) As Integer Dim args As GroupAddRequestEventArgs = 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.Basic/My Project/AssemblyInfo.vb b/Native.Basic/My Project/AssemblyInfo.vb index 3de3bb2..86be2f9 100644 --- a/Native.Basic/My Project/AssemblyInfo.vb +++ b/Native.Basic/My Project/AssemblyInfo.vb @@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices ' 方法是按如下所示使用“*”: : ' - - + + diff --git a/Native.Basic/Native.Basic.vbproj b/Native.Basic/Native.Basic.vbproj index 51fdaa1..da0a047 100644 --- a/Native.Basic/Native.Basic.vbproj +++ b/Native.Basic/Native.Basic.vbproj @@ -179,10 +179,6 @@ - - TextTemplatingFileGenerator - LibExport1.vb - @@ -193,12 +189,13 @@ {797eaebc-4d5b-4eef-87f4-a508fda2cb6a} Native.Csharp.Sdk - - {9db94cbf-6843-4ea3-9241-769124416fe9} - Native.Csharp.Tool - - + + + TextTemplatingFileGenerator + LibExport1.vb + + diff --git a/Native.Csharp.Sdk/Cqp/CqApi.cs b/Native.Csharp.Sdk/Cqp/CqApi.cs index a63172b..8829bff 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 0000000..ca50d6e --- /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 0000000..bdc378e --- /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 0000000..f51fe2f --- /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 175ab0e..0000000 --- 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 417702e..0000000 --- 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 110c450..26d5152 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 8cbdda1..f53030d 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 ("2.0.5.0525")] -[assembly: AssemblyFileVersion ("2.0.5.0525")] +[assembly: AssemblyVersion ("2.0.6.0607")] +[assembly: AssemblyFileVersion ("2.0.6.0607")] diff --git a/Native.Csharp.Tool/Core/Kernel32.cs b/Native.Csharp.Tool/Core/Kernel32.cs deleted file mode 100644 index 57998f8..0000000 --- a/Native.Csharp.Tool/Core/Kernel32.cs +++ /dev/null @@ -1,32 +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 - { - [DllImport ("kernel32.dll")] - internal extern static int GetPrivateProfileIntA (string segName, string keyName, int iDefault, string fileName); - - [DllImport ("kernel32.dll")] - internal extern static int GetPrivateProfileStringA (string segName, string keyName, string sDefault, StringBuilder buffer, int nSize, string fileName); - - [DllImport ("kernel32.dll")] - internal extern static int GetPrivateProfileSectionA (string segName, StringBuilder buffer, int nSize, string fileName); - - [DllImport ("kernel32.dll")] - internal extern static int GetPrivateProfileSectionNamesA (byte[] buffer, int iLen, string fileName); - - [DllImport ("kernel32.dll")] - internal extern static int WritePrivateProfileSectionA (string segName, string sValue, string fileName); - - [DllImport ("kernel32.dll")] - internal extern static int WritePrivateProfileStringA (string segName, string keyName, string sValue, string fileName); - - [DllImport ("kernel32.dll", 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 65e760b..0000000 --- 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 0000000..74f6308 --- /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 f3750f3..00d34ab 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 dd6475b..13b08b8 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 4ac65e7..f0f451d 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 0386363..9313ae0 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 配置文件 @@ -79,4 +79,7 @@ IniValue value2 = section2["节点"]["键1"]; 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 864b361..0000000 --- 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 c83fbd9..27d601b 100644 --- a/Native.Csharp.Tool/Native.Csharp.Tool.csproj +++ b/Native.Csharp.Tool/Native.Csharp.Tool.csproj @@ -16,10 +16,13 @@ true bin\x86\Debug\ - DEBUG;TRACE + TRACE;DEBUG;NET_40;SQLITE_STANDARD;INTEROP_VIRTUAL_TABLE;INTEROP_SESSION_EXTENSION;TRACE_SHARED full prompt MinimumRecommendedRules.ruleset + 618,1591;3001 + NU1605 + bin\x86\Debug\Navite.Csharp.Tool.xml bin\x86\Release\ @@ -33,6 +36,7 @@ + @@ -41,20 +45,79 @@ - - + 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 aa09c9f..6fb187e 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 b5bd19a..9cfd9ab 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 ("2.0.5.0525")] -[assembly: AssemblyFileVersion ("2.0.5.0525")] +[assembly: AssemblyVersion ("2.0.6.0607")] +[assembly: AssemblyFileVersion ("2.0.6.0607")] diff --git a/Native.Csharp.Tool/Properties/AssemblySourceIdAttribute.cs b/Native.Csharp.Tool/Properties/AssemblySourceIdAttribute.cs new file mode 100644 index 0000000..3666fe1 --- /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 0000000..828aca6 --- /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 0000000..96afee2 --- /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 0000000000000000000000000000000000000000..1c26a86d8ecce15665501adc4c035fa25322929b GIT binary patch literal 35255 zcmeHQOK;oC6;@iH3j+a)Ztj-Xw`lLZVC*=~%>!_h(h?=}TB3AG%8pyK1tpCYHWZnX zRATk9?z(@W=%T-(KcMJi)kT+Gc2%GSy6!g=IV2@jq?JmMl#wk`JaZ0t&dkS|Gw(nD z;h(?1Q!15yjjyeb{eTP)G~&A>uSb0LOCx5Vc%m@$d#-0&T~W9oo^QF%vj+$F>7OW6 zMs_gr$TNqG0?)KXp*`x_R_~mQ4fhpso^>BTHoxe7@#yI3@k4U|$m>LZl=?cp^wXBA^#4!4`SMqP`S#!c{iDDA_CF%XABBOX(kC$DTh)?T3h*^b${Wj65NzX*lzO0BxN^YyyI+(_cmDdXJ15U? zhPLn~GJ5~$;Qqeg2d2|EZPy{s_Q%BEe_p=3cX#jP<9qk+?j3%<NX-*tpf zz3}>bUEA!v>Y09EBZ04jgPuG5jEaWh8nKD#lfDqRLhzCZufyaM`tH!Q98nngL@)zk z3}6WETSxFL|JBD-aLLc%-MxGFCJ|P3d!r$70@)_?1fn3EP_vEkYvPBOAkYid9Fnp> zG;P~*f|El=q7q<}#l0+wtZw1zy zlth-yxNFu8={JZo2ws*0Zxr4z0E85&g;03>AW<)VF*0xqRmxD9;T(V`il3l|;XOrSah1>*$k^LD zCydE4xG`=P^Kh>a{-8q#?H+KpA-NfGPivERb3Pb$Ws<(gAAP@8{XFx%bR~Dn3%`J-`7eS#OJ#ZlkW34z2<2+{9%i?R>HS=A<_2z|< zP8@_E3JCjA37L}_t`~)|QXGa`0z>A7EbSK#(Izp@Et65)<`&6EPm7evRa7KVE6;Ha zBdxX+hcS4#YnW-7S>PJ>MxIBU-sS-@QH*0TY9-om_we1b#D1Jb<&@3q&J>wm9E#fk z#kd?;x^Q;T*zH-pjVqp+UL1+rfy8&u6uVY_mHD`cmsrF}Jat|4FgZC+VyM!VB9JA> z7W;@vw)V~$9r{EaH#eJGGrl6FIKtxOUAyO(AdI~)3F(#{AhtWO(0RFOAY*EAnC%42 zx+uCf4Pz6XIKXxWV4-i6Q#)OAVM#F-s>NZp6EF)bXIx~^3PzdawhRM``9lUy$3fnJ z0jATIVk`#67W@2cj7n?^BdlW33IIq3#LF#DkBXqMSpS6L1dkr(R6LnlMR6EM*6g?{ z7HipIdd%I;k{Nf1AU=9;U8DDUF2);g9uApW9EXzutZT2JvfPiou#Cdq)uhJCm=Y4@ zN^+arQG6yw23zsS;7!XGr-jWFUChs8+VZjb1Oh^Na3ke7ayfEmkvkv`^=39T z(`D*$^m6phq8CFa-0n|EFMo*bd-8Zw3UUN<1kWNERU$0bQlO_E%SYsn7g=0on*gwF zdRXBHRan0~G;ahuT*(IGOu_|qQh`wgl`R1fEz_e19u^14X{Csa`t;}bfB9K_>u3od zR_~o{kYLq^*&rsB?bc@!Su*2JH%lNMbNe;qo)>exbG(1S@E}YYaZ>u z4aiLa=X{$mA6vz7&v8$?S!R8D$E72(^SWTR3G>tgk<=I3L6_nPIYMI7%O@AL8QaKNv8!wA-<#yg?j zwG)a(<u#cjBBgFGC|lVBX?*N9Y2-7Xj*+_ z^%Ji<_PR0SH7@2Y%?2^4Y^{q)WXX&>UGa)hl%{1Fr|Mhm=9^g=W9lVd z@VeaEI4YO+Y6l^VSoRww!xTo>irR@r9I0{A{y+WL_lQ*X=tWRPhBmX!@0~w>Lpp6~ zhA34GrKO5ewJJ(AL)Jv8(ZDzSrAm#f@5`d3B234BMU)Ifs+@^Z$7m^PMU$JdYKW(D zt)nz&KwLN}ylTLh1EN*`O5Yf`6S(6g**(rF_k z;ip^$tsz5=stm)ba%fmp(G3^|{+3wPT5VCL=E@D+Nk%MyQ)=Vqi;CPf=&8}sl?zIM zoQ_kw0lmeVq%=gt3H{1S9R#h_6l)#CY`xJs73*57(-zO93q`GqN=*a{jXc}`E{SarGjHC%;NTcyDs})%Xv5ttsX=0*=RG=w^Pzhfm#)|Mcfg(sFo$gwZe + /// 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 0000000..6f16dfc --- /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 0000000..ac95d5c --- /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 0000000..03cd3f3 --- /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 0000000..fa57aab --- /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 0000000000000000000000000000000000000000..13dbda04939c110ae2013a0ddb3d0306d27ce23e GIT binary patch literal 246 zcmZ?r{l)+RWk5;;hy|dSk%0v)(Eucm@If$G08Rj9fQX@?0YU*uAd}(1fdfDqB=H}N z82tAHegfYK`@xWIgCX#)_w_;+D3m(;>l xi+67q<^r0q`1fXGFukLJnM+V`)#CpB8$cRX{QkWgM6c@KzXL?ATG7!3q5#{vS=;~s literal 0 HcmV?d00001 diff --git a/Native.Csharp.Tool/SQLite/Resources/SQLiteConnection.bmp b/Native.Csharp.Tool/SQLite/Resources/SQLiteConnection.bmp new file mode 100644 index 0000000000000000000000000000000000000000..f787a2db7d4de7d06f9664be36099f94f721592d GIT binary patch literal 246 zcmX|(Jr2S!426wgKpharsKkPlu^T(rVd-LZp=vJC(ehM@p-5daa3>oU07pqFCD0bxlXA8S!m|-fEk<|P*^5!iSP2v+ tMM`W!rCN2%C)~k!YXVLUTd&^Z_ukihv@3mVC*ymbmkE^Eeg{yccdQpeS;YVV literal 0 HcmV?d00001 diff --git a/Native.Csharp.Tool/SQLite/Resources/SQLiteDataAdapter.bmp b/Native.Csharp.Tool/SQLite/Resources/SQLiteDataAdapter.bmp new file mode 100644 index 0000000000000000000000000000000000000000..27186a09377e784812e492890ef898de09a60336 GIT binary patch literal 246 zcmX|)F%AMD5JkttL}Ov3+aq`b3p+#Wb=(Atg>LR)K|;G1pzuIe+u{RsKlA^eKa&i% z`yw3=>+h7$olz86_rG;K{&MZnRDW!IhS4T*yU=~L(l}}VKSv(l+ s*{HRa9J&u0)TO9@*I^*PM2+Itd2edQIpoqsb{ux;IUm|racs8A7nLGc=>Px# literal 0 HcmV?d00001 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 0000000..f33df39 --- /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 0000000..5c6589d --- /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 0000000..d7817c3 --- /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 0000000..6744136 --- /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 0000000..ab6d13b --- /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 0000000..e10e64b --- /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 0000000..3dca96a --- /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 0000000..7c8d7c7 --- /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 0000000..8dd35c0 --- /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 0000000..f0e44eb --- /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 0000000..1e140f8 --- /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 0000000..ea0e7f1 --- /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 0000000..a1aadeb --- /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 0000000..1801c04 --- /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 0000000..6894199 --- /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 0000000..033ff43 --- /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 0000000..61c7ffa --- /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 0000000..df51ad2 --- /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 0000000..db2d748 --- /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 0000000..ab4f5b9 --- /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 0000000..f506bf9 --- /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 0000000..38fb126 --- /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 0000000..6cca81e --- /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 0000000..e1c2134 --- /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 0000000..9448967 --- /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 0000000..897c172 --- /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 0000000..dc10bcb --- /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 0000000..32a967e --- /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 0000000..40ca651 --- /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 0000000..2945cd4 --- /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 0000000..1e13a58 --- /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 0000000..a02dd2c --- /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 0000000..9c8f36a --- /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 0000000..4549c87 --- /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 0000000..044395b --- /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 0000000..724a843 --- /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 0000000..f5db906 --- /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/README.md b/README.md index d4e23f7..24bb685 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,18 @@ > 1. ~~对于 VisualBasic 项目不知道为什么安装高版本的 Fody 就编译不通过, 现 Fody 版本为 1.6.2, 所以暂时不支持无缝升级到 .Net Framewrok 4.5+~~(提供了4.5版本) ## Native.SDK 更新日志 +> 2019年06月07日 版本: V2.0.6.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日 版本: V2.0.5.0525 1. 修复 HttpWebClient 类在请求 Internet 资源时响应重定向的部分代码错误