X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FMono.Data.Sqlite%2FMono.Data.Sqlite_2.0%2FSQLiteFunction.cs;h=6aefe804586af9c963af74e3a548331662b97c94;hb=66321daeda387e7fe76ac7ebfa04a44fc85e905d;hp=104f8ce7cc661549ce6051c13ba8bd8b1754bc41;hpb=3d693eeb90339833968d66c3dc9fde2fa3ba2cef;p=mono.git diff --git a/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteFunction.cs b/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteFunction.cs index 104f8ce7cc6..6aefe804586 100644 --- a/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteFunction.cs +++ b/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteFunction.cs @@ -1,567 +1,710 @@ -// -// Mono.Data.Sqlite.SQLiteFunction.cs -// -// Author(s): -// Robert Simpson (robert@blackcastlesoft.com) -// -// Adapted and modified for the Mono Project by -// Marek Habersack (grendello@gmail.com) -// -// -// Copyright (C) 2006 Novell, Inc (http://www.novell.com) -// Copyright (C) 2007 Marek Habersack -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -/******************************************************** - * 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 NET_2_0 -namespace Mono.Data.Sqlite -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Runtime.InteropServices; - using System.Globalization; - - /// - /// 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, - /// - /// Collation 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 context pointer for the user function - /// Count of arguments to the function - /// A pointer to the array of argument pointers - internal delegate void SqliteCallback(IntPtr context, int nArgs, IntPtr argsptr); - /// - /// Internal callback delegate for implementing collation sequences - /// - /// 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. - internal delegate int SqliteCollation(int len1, IntPtr pv1, int len2, IntPtr pv2); - - /// - /// 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 - { - /// - /// The base connection this function is attached to - /// - private SqliteBase _base; - /// - /// Used internally to keep track of memory allocated for aggregate functions - /// - private IntPtr _interopCookie; - /// - /// Internal array used to keep track of aggregate function context data - /// - private Dictionary _contextDataList; - - /// - /// 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 SqliteCallback _FinalFunc; - /// - /// Holds a reference to the callback function for collation sequences - /// - private SqliteCollation _CompareFunc; - - /// - /// This static list contains all the user-defined functions declared using the proper attributes. - /// - private static List _registeredFunctions = new List(); - - /// - /// Internal constructor, initializes the function's internal variables. - /// - protected SqliteFunction() - { - _contextDataList = new Dictionary(); - } - - /// - /// 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 - { - 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) - { - 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) - { - } - - /// - /// 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) - { - return null; - } - - /// - /// User-defined collation 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) - { - 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 - 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)) - { - 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. - /// - /// 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) - { - SetReturnValue(context, Invoke(ConvertParams(nArgs, argsptr))); - } - - /// - /// Internal collation sequence function, which wraps up the raw string pointers and executes the Compare() virtual function. - /// - /// 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. - internal int CompareCallback(int len1, IntPtr ptr1, int len2, IntPtr ptr2) - { - return Compare(_base.ToString(ptr1), _base.ToString(ptr2)); - } - - /// - /// The internal aggregate Step function callback, which wraps the raw context pointer and calls the virtual Step() method. - /// - /// - /// 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) - { - int n = _base.AggregateCount(context); - long nAux; - object obj = null; - - nAux = (long)_base.AggregateContext(context); - if (n > 1) obj = _contextDataList[nAux]; - - Step(ConvertParams(nArgs, argsptr), n, ref obj); - _contextDataList[nAux] = obj; - } - - /// - /// An internal aggregate Final function callback, which wraps the context pointer and calls the virtual Final() method. - /// - /// A raw context pointer - /// Not used, always zero - /// Not used, always zero - internal void FinalCallback(IntPtr context, int nArgs, IntPtr argsptr) - { - long n = (long)_base.AggregateContext(context); - object obj = null; - - if (_contextDataList.ContainsKey(n)) - { - obj = _contextDataList[n]; - _contextDataList.Remove(n); - } - - SetReturnValue(context, Final(obj)); - - IDisposable disp = obj as IDisposable; - if (disp != null) disp.Dispose(); - } - - /// - /// Placeholder for a user-defined disposal routine - /// - /// True if the object is being disposed explicitly - protected virtual void Dispose(bool disposing) - { - } - - /// - /// 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); - - IDisposable disp; - - foreach (KeyValuePair kv in _contextDataList) - { - disp = kv.Value as IDisposable; - if (disp != null) - disp.Dispose(); - } - _contextDataList.Clear(); - - _InvokeFunc = null; - _StepFunc = null; - _FinalFunc = null; - _CompareFunc = null; - _base = null; - _contextDataList = null; - - GC.SuppressFinalize(this); - } - -#if !PLATFORM_COMPACTFRAMEWORK - /// - /// Using reflection, enumerate all assemblies in the current appdomain looking for classes that - /// have a SqliteFunctionAttribute attribute, and registering them accordingly. - /// - static SqliteFunction() - { - SqliteFunctionAttribute at; - System.Reflection.Assembly[] arAssemblies = System.AppDomain.CurrentDomain.GetAssemblies(); - int w = arAssemblies.Length; - - for (int n = 0; n < w; n++) - { - Type[] arTypes; - try - { - arTypes = arAssemblies[n].GetTypes(); - } - catch (System.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]; - _registeredFunctions.Add(at); - } - } - } - } - } -#else - /// - /// 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); - int u = arAtt.Length; - SqliteFunctionAttribute at; - - for (int y = 0; y < u; y++) - { - at = arAtt[y] as SqliteFunctionAttribute; - if (at != null) - { - at._instanceType = typ; - _registeredFunctions.Add(at); - } - } - } -#endif - - /// - /// Called by SqliteBase derived classes, this function binds all 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 cooke 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 - /// Returns an array of functions which the connection object should retain until the connection is closed. - internal static SqliteFunction[] BindFunctions(SqliteBase sqlbase) - { - SqliteFunction f; - List lFunctions = new List(); - - foreach (SqliteFunctionAttribute pr in _registeredFunctions) - { - f = (SqliteFunction)Activator.CreateInstance(pr._instanceType); - f._base = sqlbase; - f._InvokeFunc = (pr.FuncType == FunctionType.Scalar) ? new SqliteCallback(f.ScalarCallback) : null; - f._StepFunc = (pr.FuncType == FunctionType.Aggregate) ? new SqliteCallback(f.StepCallback) : null; - f._FinalFunc = (pr.FuncType == FunctionType.Aggregate) ? new SqliteCallback(f.FinalCallback) : null; - f._CompareFunc = (pr.FuncType == FunctionType.Collation) ? new SqliteCollation(f.CompareCallback) : null; - - if (pr.FuncType != FunctionType.Collation) - f._interopCookie = sqlbase.CreateFunction(pr.Name, pr.Arguments, f._InvokeFunc, f._StepFunc, f._FinalFunc); - else - f._interopCookie = sqlbase.CreateCollation(pr.Name, f._CompareFunc); - - - lFunctions.Add(f); - } - - SqliteFunction[] arFunctions = new SqliteFunction[lFunctions.Count]; - lFunctions.CopyTo(arFunctions, 0); - - return arFunctions; - } - - /// - /// Issued after the base connection is closed, this function cleans up all user-defined functions and disposes of them. - /// - /// - /// Cleaning up here is done mainly because of the interop wrapper. It allocated memory to hold a reference to all the - /// delegates, and now must free that memory. - /// Freeing is done after the connection is closed to ensure no callbacks get hit after we've freed the cookie. - /// - /// The base Sqlite connection object - /// An array of user-defined functions for this object - internal static void UnbindFunctions(SqliteBase sqlbase, SqliteFunction[] ar) - { - if (ar == null) return; - - int x = ar.Length; - for (int n = 0; n < x; n++) - { - sqlbase.FreeFunction(ar[n]._interopCookie); - ar[n].Dispose(); - } - } - } -} -#endif +/******************************************************** + * 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 Mono.Data.Sqlite +{ + using System; + using System.Collections; + 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 = null; + } + + /// + /// 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; + + /// + /// 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 collation sequences + /// + private SQLiteCollation _CompareFunc; + + private SQLiteCollation _CompareFunc16; + + /// + /// Current context of the current callback. Only valid during a callback + /// + internal IntPtr _context; + + /// + /// This static list contains all the user-defined functions declared using the proper attributes. + /// + private static List _registeredFunctions = new List(); + + /// + /// Internal constructor, initializes the function's internal variables. + /// + protected SqliteFunction() + { + _contextDataList = new Dictionary(); + } + + /// + /// 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 + { + 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) + { + 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) + { + } + + /// + /// 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) + { + return null; + } + + /// + /// User-defined collation 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) + { + 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 + 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)) + { + 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. + /// + /// 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) + { + _context = context; + SetReturnValue(context, Invoke(ConvertParams(nArgs, argsptr))); + } + + /// + /// Internal collation sequence function, which wraps up the raw string pointers and executes the Compare() virtual function. + /// + /// 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. + internal int CompareCallback(IntPtr ptr, int len1, IntPtr ptr1, int len2, IntPtr ptr2) + { + return Compare(SqliteConvert.UTF8ToString(ptr1, len1), SqliteConvert.UTF8ToString(ptr2, len2)); + } + + internal int CompareCallback16(IntPtr ptr, int len1, IntPtr ptr1, int len2, IntPtr ptr2) + { + return Compare(SQLite3_UTF16.UTF16ToString(ptr1, len1), SQLite3_UTF16.UTF16ToString(ptr2, len2)); + } + + /// + /// The internal aggregate Step function callback, which wraps the raw context pointer and calls the virtual Step() method. + /// + /// + /// 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) + { + long nAux; + AggregateData data; + + nAux = (long)_base.AggregateContext(context); + if (_contextDataList.TryGetValue(nAux, out data) == false) + { + data = new AggregateData(); + _contextDataList[nAux] = data; + } + + try + { + _context = context; + Step(ConvertParams(nArgs, argsptr), data._count, ref data._data); + } + finally + { + data._count++; + } + } + + /// + /// An internal aggregate Final function callback, which wraps the context pointer and calls the virtual Final() method. + /// + /// A raw context pointer + internal void FinalCallback(IntPtr context) + { + long n = (long)_base.AggregateContext(context); + object obj = null; + + if (_contextDataList.ContainsKey(n)) + { + obj = _contextDataList[n]._data; + _contextDataList.Remove(n); + } + + _context = context; + SetReturnValue(context, Final(obj)); + + IDisposable disp = obj as IDisposable; + if (disp != null) disp.Dispose(); + } + + /// + /// Placeholder for a user-defined disposal routine + /// + /// True if the object is being disposed explicitly + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + IDisposable disp; + + foreach (KeyValuePair kv in _contextDataList) + { + disp = kv.Value._data as IDisposable; + if (disp != null) + disp.Dispose(); + } + _contextDataList.Clear(); + + _InvokeFunc = null; + _StepFunc = null; + _FinalFunc = null; + _CompareFunc = null; + _base = null; + _contextDataList = null; + } + } + + /// + /// 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); + } + + /// + /// Using reflection, enumerate all assemblies in the current appdomain looking for classes that + /// have a SqliteFunctionAttribute attribute, and registering them accordingly. + /// +#if !PLATFORM_COMPACTFRAMEWORK + [global::System.Security.Permissions.FileIOPermission(global::System.Security.Permissions.SecurityAction.Assert, AllFiles = global::System.Security.Permissions.FileIOPermissionAccess.PathDiscovery)] +#endif + static SqliteFunction() + { + try + { +#if !PLATFORM_COMPACTFRAMEWORK + SqliteFunctionAttribute at; + System.Reflection.Assembly[] arAssemblies = System.AppDomain.CurrentDomain.GetAssemblies(); + int w = arAssemblies.Length; + System.Reflection.AssemblyName sqlite = System.Reflection.Assembly.GetCallingAssembly().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 (global::System.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]; + _registeredFunctions.Add(at); + } + } + } + } +#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); + int u = arAtt.Length; + SqliteFunctionAttribute at; + + for (int y = 0; y < u; y++) + { + at = arAtt[y] as SqliteFunctionAttribute; + if (at != null) + { + at._instanceType = typ; + _registeredFunctions.Add(at); + } + } + } + + /// + /// Called by SQLiteBase derived classes, this function binds all 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 + /// Returns an array of functions which the connection object should retain until the connection is closed. + internal static SqliteFunction[] BindFunctions(SQLiteBase sqlbase) + { + SqliteFunction f; + List lFunctions = new List(); + + foreach (SqliteFunctionAttribute pr in _registeredFunctions) + { + f = (SqliteFunction)Activator.CreateInstance(pr._instanceType); + f._base = sqlbase; + f._InvokeFunc = (pr.FuncType == FunctionType.Scalar) ? new SQLiteCallback(f.ScalarCallback) : null; + f._StepFunc = (pr.FuncType == FunctionType.Aggregate) ? new SQLiteCallback(f.StepCallback) : null; + f._FinalFunc = (pr.FuncType == FunctionType.Aggregate) ? new SQLiteFinalCallback(f.FinalCallback) : null; + f._CompareFunc = (pr.FuncType == FunctionType.Collation) ? new SQLiteCollation(f.CompareCallback) : null; + f._CompareFunc16 = (pr.FuncType == FunctionType.Collation) ? new SQLiteCollation(f.CompareCallback16) : null; + + if (pr.FuncType != FunctionType.Collation) + sqlbase.CreateFunction(pr.Name, pr.Arguments, (f is SqliteFunctionEx), f._InvokeFunc, f._StepFunc, f._FinalFunc); + else { +#if MONOTOUCH + GCHandle handle = GCHandle.Alloc (f); + sqlbase.CreateCollation(pr.Name, collation_callback, collation_callback16, GCHandle.ToIntPtr (handle)); +#else + sqlbase.CreateCollation(pr.Name, f._CompareFunc, f._CompareFunc16, IntPtr.Zero); +#endif + } + + + lFunctions.Add(f); + } + + SqliteFunction[] arFunctions = new SqliteFunction[lFunctions.Count]; + lFunctions.CopyTo(arFunctions, 0); + + return arFunctions; + } + +#if MONOTOUCH + [MonoTouch.MonoPInvokeCallback (typeof (SQLiteCollation))] + internal static int collation_callback (IntPtr puser, int len1, IntPtr pv1, int len2, IntPtr pv2) + { + var handle = GCHandle.FromIntPtr (puser); + var func = (SqliteFunction) handle.Target; + return func._CompareFunc (IntPtr.Zero, len1, pv1, len2, pv2); + } + + [MonoTouch.MonoPInvokeCallback (typeof (SQLiteCollation))] + internal static int collation_callback16 (IntPtr puser, int len1, IntPtr pv1, int len2, IntPtr pv2) + { + var handle = GCHandle.FromIntPtr (puser); + var func = (SqliteFunction) handle.Target; + return func._CompareFunc16 (IntPtr.Zero, len1, pv1, len2, pv2); + } +#endif + } + + /// + /// 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); + } + } + + /// + /// 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, + /// + /// Collation 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 context pointer for the user function + /// Count of arguments to the function + /// A pointer to the array of argument pointers +#if !PLATFORM_COMPACTFRAMEWORK + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] +#endif + internal delegate void SQLiteCallback(IntPtr context, int nArgs, IntPtr argsptr); + /// + /// 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 collation 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); + } + } +}