/******************************************************** * 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 ~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)) { 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 ((_flags & SQLiteConnectionFlags.LogCallbackException) == SQLiteConnectionFlags.LogCallbackException) { SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, String.Format(CultureInfo.CurrentCulture, "Caught exception in \"Invoke\" method: {0}", 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 ((_flags & SQLiteConnectionFlags.LogCallbackException) == SQLiteConnectionFlags.LogCallbackException) { SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, String.Format(CultureInfo.CurrentCulture, "Caught exception in \"Compare\" (UTF8) method: {0}", 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 ((_flags & SQLiteConnectionFlags.LogCallbackException) == SQLiteConnectionFlags.LogCallbackException) { SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, String.Format(CultureInfo.CurrentCulture, "Caught exception in \"Compare\" (UTF16) method: {0}", 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 ((_flags & SQLiteConnectionFlags.LogCallbackException) == SQLiteConnectionFlags.LogCallbackException) { SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, String.Format(CultureInfo.CurrentCulture, "Caught exception in \"Step\" method: {1}", 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 ((_flags & SQLiteConnectionFlags.LogCallbackException) == SQLiteConnectionFlags.LogCallbackException) { SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, String.Format(CultureInfo.CurrentCulture, "Caught exception in \"Final\" method: {1}", 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 [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]; _registeredFunctions.Add(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, at.InstanceType, 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. /// 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; _registeredFunctions.Add(at, null); } /// /// 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)) { 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 ); ///////////////////////////////////////////////////////////////////////////// /// /// This class implements a SQLite function using a . /// All the virtual methods of the class are /// implemented using calls to 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. /// 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 ) /* CANNOT RETURN NULL */ { 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 ) /* CANNOT RETURN NULL */ { 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 ) /* CANNOT RETURN NULL */ { 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 ) /* CANNOT RETURN NULL */ { 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 ) /* CANNOT RETURN NULL */ { 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(String.Format( NoCallbackError, "Invoke")); } SQLiteInvokeDelegate invokeDelegate = callback1 as SQLiteInvokeDelegate; if (invokeDelegate != null) { return invokeDelegate.Invoke("Invoke", args); /* throw */ } else { return callback1.DynamicInvoke( GetInvokeArgs(args, false)); /* throw */ } } ///////////////////////////////////////////////////////////////////////// /// /// 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(String.Format( NoCallbackError, "Step")); } SQLiteStepDelegate stepDelegate = callback1 as SQLiteStepDelegate; if (stepDelegate != null) { stepDelegate.Invoke( "Step", args, stepNumber, ref contextData); /* throw */ } else { object[] newArgs = GetStepArgs( args, stepNumber, contextData, false); /* IGNORED */ callback1.DynamicInvoke(newArgs); /* throw */ UpdateStepArgs(newArgs, ref contextData, false); } } ///////////////////////////////////////////////////////////////////////// /// /// 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(String.Format( NoCallbackError, "Final")); } SQLiteFinalDelegate finalDelegate = callback2 as SQLiteFinalDelegate; if (finalDelegate != null) { return finalDelegate.Invoke("Final", contextData); /* throw */ } else { return callback1.DynamicInvoke(GetFinalArgs( contextData, false)); /* throw */ } } ///////////////////////////////////////////////////////////////////////// /// /// 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(String.Format( NoCallbackError, "Compare")); } SQLiteCompareDelegate compareDelegate = callback1 as SQLiteCompareDelegate; if (compareDelegate != null) { return compareDelegate.Invoke( "Compare", param1, param2); /* throw */ } else { object result = callback1.DynamicInvoke(GetCompareArgs( param1, param2, false)); /* throw */ if (result is int) return (int)result; throw new InvalidOperationException(String.Format( ResultInt32Error, "Compare")); } } #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 } /////////////////////////////////////////////////////////////////////////////////////////////// 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); } } }