Index: System.Data.SQLite/SQLiteConnection.cs ================================================================== --- System.Data.SQLite/SQLiteConnection.cs +++ System.Data.SQLite/SQLiteConnection.cs @@ -1381,15 +1381,15 @@ /// /// Attempts to bind the specified object /// instance to this connection. /// /// - /// The object instance containing + /// The object instance containing /// the metadata for the function to be bound. /// /// - /// The object instance that implements the + /// The object instance that implements the /// function to be bound. /// public void BindFunction( SQLiteFunctionAttribute functionAttribute, SQLiteFunction function @@ -1401,10 +1401,39 @@ 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. + /// + /// + /// The object instance that implements the + /// function to be bound. + /// + public void BindFunction( + SQLiteFunctionAttribute functionAttribute, + Delegate callback + ) + { + CheckDisposed(); + + if (_sql == null) + throw new InvalidOperationException( + "Database connection not valid for binding functions."); + + _sql.BindFunction(functionAttribute, + new SQLiteDelegateFunction(callback), _flags); + } /////////////////////////////////////////////////////////////////////////////////////////////// /// /// Attempts to unbind the specified object Index: System.Data.SQLite/SQLiteFunction.cs ================================================================== --- System.Data.SQLite/SQLiteFunction.cs +++ System.Data.SQLite/SQLiteFunction.cs @@ -65,11 +65,11 @@ /// /// 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 + /// Holds a reference to the callback function for collating sequences /// private SQLiteCollation _CompareFunc; private SQLiteCollation _CompareFunc16; @@ -275,11 +275,11 @@ CheckDisposed(); return null; } /// - /// User-defined collation sequences override this method to provide a custom string sorting algorithm. + /// 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) @@ -428,11 +428,11 @@ } } } /// - /// Internal collation sequence function, which wraps up the raw string pointers and executes the Compare() virtual function. + /// 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 @@ -475,11 +475,11 @@ return 0; } /// - /// Internal collation sequence function, which wraps up the raw string pointers and executes the Compare() virtual function. + /// 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 @@ -726,23 +726,111 @@ /// 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, null); - } - } + 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.Callback); + } + } + + /// + /// 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 + /// parameter is null. + /// + /// + /// The that implements the function. If + /// this is non-null, the parameter + /// will be ignored when the function is invoked. + /// + public static void RegisterFunction( + string name, + int argumentCount, + FunctionType functionType, + Type instanceType, + Delegate callback + ) + { + SQLiteFunctionAttribute at = new SQLiteFunctionAttribute( + name, argumentCount, functionType); + + at.InstanceType = instanceType; + at.Callback = callback; + + _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.Callback != null) + { + function = new SQLiteDelegateFunction( + functionAttribute.Callback); + + 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 @@ -769,15 +857,21 @@ SQLiteFunctionAttribute pr = pair.Key; if (pr == null) continue; - SQLiteFunction f = (SQLiteFunction)Activator.CreateInstance( - pr.InstanceType); + SQLiteFunction f; - BindFunction(sqlbase, pr, f, flags); - lFunctions[pr] = f; + if (CreateFunction(pr, out f)) + { + BindFunction(sqlbase, pr, f, flags); + lFunctions[pr] = f; + } + else + { + lFunctions[pr] = null; + } } return lFunctions; } @@ -859,11 +953,11 @@ return result; } /// - /// This function binds a user-defined functions to a connection. + /// This function binds a user-defined function to a connection. /// /// /// The object instance associated with the /// that the function should be bound to. /// @@ -987,10 +1081,337 @@ } } + ///////////////////////////////////////////////////////////////////////////// + + /// + /// 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 the + /// callback has not been set. + /// + private const string NoCallbackError = "No 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) + { + // do nothing. + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// Constructs an instance of this class using the specified + /// as the + /// implementation. + /// + /// + /// The to be used for all calls into the + /// virtual methods needed by the + /// class. + /// + public SQLiteDelegateFunction( + Delegate callback + ) + { + this.callback = callback; + } + #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. + /// + /// + /// The arguments to pass to the configured . + /// + protected virtual object[] GetInvokeArgs( + object[] args + ) /* CANNOT RETURN NULL */ + { + if (args == null) + return new object[] { "Invoke" }; + + object[] newArgs = new object[args.Length + 1]; + + newArgs[0] = "Invoke"; + + for (int index = 0; index < args.Length; index++) + newArgs[index + 1] = args[index]; + + 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. + /// + /// + /// The arguments to pass to the configured . + /// + protected virtual object[] GetStepArgs( + object[] args, + int stepNumber, + object contextData + ) /* CANNOT RETURN NULL */ + { + int newLength = 3; /* "Step", stepNumber, contextData */ + + if (args != null) + newLength += args.Length; + + object[] newArgs = new object[newLength]; + + newArgs[0] = "Step"; + + if (args != null) + for (int index = 0; index < args.Length; index++) + newArgs[index + 1] = args[index]; + + newArgs[newLength - 2] = stepNumber; + newArgs[newLength - 1] = contextData; + + return newArgs; + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// 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. + /// + /// + /// The arguments to pass to the configured . + /// + protected virtual object[] GetFinalArgs( + object contextData + ) /* CANNOT RETURN NULL */ + { + return new object[] { "Final", contextData }; + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// 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. + /// + /// + /// The arguments to pass to the configured . + /// + protected virtual object[] GetCompareArgs( + string param1, + string param2 + ) /* CANNOT RETURN NULL */ + { + return new object[] { "Compare", param1, param2 }; + } + #endregion + + ///////////////////////////////////////////////////////////////////////// + + #region Public Properties + private Delegate callback; + /// + /// The to be used for all calls into the + /// virtual methods needed by the + /// class. + /// + public virtual Delegate Callback + { + get { return callback; } + set { callback = 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 first argument is always + /// the literal string "Invoke". The remaining arguments, if any, are + /// passed exactly as they are received. + /// + /// + /// The result of the scalar function. + /// + public override object Invoke( + object[] args /* in */ + ) + { + if (callback == null) + throw new InvalidOperationException(NoCallbackError); + + return callback.DynamicInvoke(GetInvokeArgs(args)); /* 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 first argument is + /// always the literal string "Step". The remaining arguments, if + /// any, are passed exactly as they are received. + /// + /// + /// 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 (callback == null) + throw new InvalidOperationException(NoCallbackError); + + object[] newArgs = GetStepArgs(args, stepNumber, contextData); + + /* IGNORED */ + callback.DynamicInvoke(newArgs); /* throw */ + + contextData = newArgs[newArgs.Length - 1]; /* out */ + } + + ///////////////////////////////////////////////////////////////////////// + + /// + /// 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 (callback == null) + throw new InvalidOperationException(NoCallbackError); + + return callback.DynamicInvoke(GetFinalArgs(contextData)); /* 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 (callback == null) + throw new InvalidOperationException(NoCallbackError); + + object[] newArgs = GetCompareArgs(param1, param2); + object result = callback.DynamicInvoke(newArgs); /* throw */ + + if (result is int) + return (int)result; + + throw new InvalidOperationException(String.Format( + ResultInt32Error, newArgs[0])); + } + #endregion + } + + ///////////////////////////////////////////////////////////////////////////// /// /// Extends SQLiteFunction and allows an inherited class to obtain the collating sequence associated with a function call. /// /// @@ -1065,11 +1486,11 @@ /// 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 + /// 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, } @@ -1091,11 +1512,11 @@ #if !PLATFORM_COMPACTFRAMEWORK [UnmanagedFunctionPointer(CallingConvention.Cdecl)] #endif internal delegate void SQLiteFinalCallback(IntPtr context); /// - /// Internal callback delegate for implementing collation sequences + /// 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 Index: System.Data.SQLite/SQLiteFunctionAttribute.cs ================================================================== --- System.Data.SQLite/SQLiteFunctionAttribute.cs +++ System.Data.SQLite/SQLiteFunctionAttribute.cs @@ -1,60 +1,62 @@ /******************************************************** * 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; + /// /// 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 string _name; private int _argumentCount; private FunctionType _functionType; private Type _instanceType; + private Delegate _callback; /// /// Default constructor, initializes the internal variables for the function. - /// - public SQLiteFunctionAttribute() - : this(String.Empty, -1, FunctionType.Scalar) - { - // do nothing. - } - - /// - /// Constructs an instance of this class. - /// - /// - /// 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; + /// + public SQLiteFunctionAttribute() + : this(String.Empty, -1, FunctionType.Scalar) + { + // do nothing. + } + + /// + /// Constructs an instance of this class. + /// + /// + /// 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; + _callback = null; } /// /// The function's name as it will be used in SQLite command text. /// @@ -66,12 +68,12 @@ /// /// The number of arguments this function expects. -1 if the number of arguments is variable. /// public int Arguments - { - get { return _argumentCount; } + { + get { return _argumentCount; } set { _argumentCount = value; } } /// /// The type of function this implementation will be. @@ -80,16 +82,29 @@ { get { return _functionType; } set { _functionType = value; } } - /// - /// The object instance that describes the class - /// containing the implementation for the associated function. + /// + /// The object instance that describes the class + /// containing the implementation for the associated function. The value of + /// this property will not be used if the property + /// value is set to non-null. /// internal Type InstanceType - { - get { return _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 Callback + { + get { return _callback; } + set { _callback = value; } + } } }