Index: Doc/Extra/Provider/version.html
==================================================================
--- Doc/Extra/Provider/version.html
+++ Doc/Extra/Provider/version.html
@@ -45,10 +45,11 @@
Version History
1.0.98.0 - August XX, 2015 (release scheduled)
- Updated to SQLite 3.8.11.1.
- Add full support for Visual Studio 2015 and the .NET Framework 4.6.
+ - Add support for creating custom SQL functions using delegates.
- Implement the Substring method for LINQ using the "substr" core SQL function. ** Potentially Incompatible Change **
- Prevent encrypted connections from being used with the connection pool. Pursuant to [89d3a159f1]. ** Potentially Incompatible Change **
- Honor the second argument to Math.Round when using LINQ. ** Potentially Incompatible Change **
- Honor the pre-existing flags for connections during the Open method. Fix for [964063da16]. ** Potentially Incompatible Change **
- Remove errant semi-colons from the SQL used by LINQ to INSERT and then SELECT rows with composite primary keys. Fix for [9d353b0bd8].
Index: System.Data.SQLite/SQLiteConnection.cs
==================================================================
--- System.Data.SQLite/SQLiteConnection.cs
+++ System.Data.SQLite/SQLiteConnection.cs
@@ -1410,10 +1410,50 @@
throw new InvalidOperationException(
"Database connection not valid for binding functions.");
_sql.BindFunction(functionAttribute, function, _flags);
}
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Attempts to bind the specified object
+ /// instance to this connection.
+ ///
+ ///
+ /// The object instance containing
+ /// the metadata for the function to be bound.
+ ///
+ ///
+ /// A object instance that helps implement the
+ /// function to be bound. For scalar functions, this corresponds to the
+ /// type. For aggregate functions,
+ /// this corresponds to the type. For
+ /// collation functions, this corresponds to the
+ /// type.
+ ///
+ ///
+ /// A object instance that helps implement the
+ /// function to be bound. For aggregate functions, this corresponds to the
+ /// type. For other callback types, it
+ /// is not used and must be null.
+ ///
+ public void BindFunction(
+ SQLiteFunctionAttribute functionAttribute,
+ Delegate callback1,
+ Delegate callback2
+ )
+ {
+ CheckDisposed();
+
+ if (_sql == null)
+ throw new InvalidOperationException(
+ "Database connection not valid for binding functions.");
+
+ _sql.BindFunction(functionAttribute,
+ new SQLiteDelegateFunction(callback1, callback2), _flags);
+ }
///////////////////////////////////////////////////////////////////////////////////////////////
///
/// Attempts to unbind the specified object
Index: System.Data.SQLite/SQLiteFunction.cs
==================================================================
--- System.Data.SQLite/SQLiteFunction.cs
+++ System.Data.SQLite/SQLiteFunction.cs
@@ -739,12 +739,110 @@
SQLiteFunctionAttribute at = arAtt[y] as SQLiteFunctionAttribute;
if (at == null)
continue;
- at.InstanceType = typ;
- _registeredFunctions.Add(at, null);
+ RegisterFunction(
+ at.Name, at.Arguments, at.FuncType, typ,
+ at.Callback1, at.Callback2);
+ }
+ }
+
+ ///
+ /// Alternative method of registering a function. This method
+ /// does not require the specified type to be annotated with
+ /// .
+ ///
+ ///
+ /// The name of the function to register.
+ ///
+ ///
+ /// The number of arguments accepted by the function.
+ ///
+ ///
+ /// The type of SQLite function being resitered (e.g. scalar,
+ /// aggregate, or collating sequence).
+ ///
+ ///
+ /// The that actually implements the function.
+ /// This will only be used if the
+ /// and parameters are null.
+ ///
+ ///
+ /// The to be used for all calls into the
+ /// ,
+ /// ,
+ /// and virtual methods.
+ ///
+ ///
+ /// The to be used for all calls into the
+ /// virtual method. This
+ /// parameter is only necessary for aggregate functions.
+ ///
+ public static void RegisterFunction(
+ string name,
+ int argumentCount,
+ FunctionType functionType,
+ Type instanceType,
+ Delegate callback1,
+ Delegate callback2
+ )
+ {
+ SQLiteFunctionAttribute at = new SQLiteFunctionAttribute(
+ name, argumentCount, functionType);
+
+ at.InstanceType = instanceType;
+ at.Callback1 = callback1;
+ at.Callback2 = callback2;
+
+ _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.
@@ -772,15 +870,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;
}
@@ -988,12 +1092,576 @@
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
+ )
+ {
+ object[] newArgs = new object[] { "Invoke", args };
+
+ if (!earlyBound)
+ newArgs = new object[] { newArgs }; // WRAP
+
+ return newArgs;
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Returns the list of arguments for the method,
+ /// as an of . The first
+ /// argument is always the literal string "Step".
+ ///
+ ///
+ /// The original arguments received by the method.
+ ///
+ ///
+ /// The step number (one based). This is incrememted each time the
+ /// method is called.
+ ///
+ ///
+ /// A placeholder for implementers to store contextual data pertaining
+ /// to the current context.
+ ///
+ ///
+ /// Non-zero if the returned arguments are going to be used with the
+ /// type; otherwise, zero.
+ ///
+ ///
+ /// The arguments to pass to the configured .
+ ///
+ protected virtual object[] GetStepArgs(
+ object[] args,
+ int stepNumber,
+ object contextData,
+ bool earlyBound
+ )
+ {
+ object[] newArgs = new object[] {
+ "Step", args, stepNumber, contextData
+ };
+
+ if (!earlyBound)
+ newArgs = new object[] { newArgs }; // WRAP
+
+ return newArgs;
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Updates the output arguments for the method,
+ /// using an of . The first
+ /// argument is always the literal string "Step". Currently, only the
+ /// parameter is updated.
+ ///
+ ///
+ /// The original arguments received by the method.
+ ///
+ ///
+ /// A placeholder for implementers to store contextual data pertaining
+ /// to the current context.
+ ///
+ ///
+ /// Non-zero if the returned arguments are going to be used with the
+ /// type; otherwise, zero.
+ ///
+ ///
+ /// The arguments to pass to the configured .
+ ///
+ protected virtual void UpdateStepArgs(
+ object[] args,
+ ref object contextData,
+ bool earlyBound
+ )
+ {
+ object[] newArgs;
+
+ if (earlyBound)
+ newArgs = args;
+ else
+ newArgs = args[0] as object[];
+
+ if (newArgs == null)
+ return;
+
+ contextData = newArgs[newArgs.Length - 1];
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Returns the list of arguments for the method,
+ /// as an of . The first
+ /// argument is always the literal string "Final".
+ ///
+ ///
+ /// A placeholder for implementers to store contextual data pertaining
+ /// to the current context.
+ ///
+ ///
+ /// Non-zero if the returned arguments are going to be used with the
+ /// type; otherwise, zero.
+ ///
+ ///
+ /// The arguments to pass to the configured .
+ ///
+ protected virtual object[] GetFinalArgs(
+ object contextData,
+ bool earlyBound
+ )
+ {
+ object[] newArgs = new object[] { "Final", contextData };
+
+ if (!earlyBound)
+ newArgs = new object[] { newArgs }; // WRAP
+
+ return newArgs;
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Returns the list of arguments for the method,
+ /// as an of . The first
+ /// argument is always the literal string "Compare".
+ ///
+ ///
+ /// The first string to compare.
+ ///
+ ///
+ /// The second strnig to compare.
+ ///
+ ///
+ /// Non-zero if the returned arguments are going to be used with the
+ /// type; otherwise, zero.
+ ///
+ ///
+ /// The arguments to pass to the configured .
+ ///
+ protected virtual object[] GetCompareArgs(
+ string param1,
+ string param2,
+ bool earlyBound
+ )
+ {
+ object[] newArgs = new object[] { "Compare", param1, param2 };
+
+ if (!earlyBound)
+ newArgs = new object[] { newArgs }; // WRAP
+
+ return newArgs;
+ }
+ #endregion
+
+ /////////////////////////////////////////////////////////////////////////
+
+ #region Public Properties
+ private Delegate callback1;
+ ///
+ /// The to be used for all calls into the
+ /// , , and
+ /// virtual methods needed by the
+ /// base class.
+ ///
+ public virtual Delegate Callback1
+ {
+ get { return callback1; }
+ set { callback1 = value; }
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ private Delegate callback2;
+ ///
+ /// The to be used for all calls into the
+ /// virtual methods needed by the
+ /// base class.
+ ///
+ public virtual Delegate Callback2
+ {
+ get { return callback2; }
+ set { callback2 = value; }
+ }
+ #endregion
+
+ /////////////////////////////////////////////////////////////////////////
+
+ #region System.Data.SQLite.SQLiteFunction Overrides
+ ///
+ /// This virtual method is the implementation for scalar functions.
+ /// See the method for more
+ /// details.
+ ///
+ ///
+ /// The arguments for the scalar function.
+ ///
+ ///
+ /// The result of the scalar function.
+ ///
+ public override object Invoke(
+ object[] args /* in */
+ )
+ {
+ if (callback1 == null)
+ {
+ throw new InvalidOperationException(
+ UnsafeNativeMethods.StringFormat(
+ CultureInfo.CurrentCulture,
+ 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(
+ UnsafeNativeMethods.StringFormat(
+ CultureInfo.CurrentCulture,
+ 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(
+ UnsafeNativeMethods.StringFormat(
+ CultureInfo.CurrentCulture,
+ 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(
+ UnsafeNativeMethods.StringFormat(
+ CultureInfo.CurrentCulture,
+ 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(
+ UnsafeNativeMethods.StringFormat(
+ CultureInfo.CurrentCulture,
+ ResultInt32Error, "Compare"));
+ }
+ }
+ #endregion
+ }
+ /////////////////////////////////////////////////////////////////////////////
///
/// Extends SQLiteFunction and allows an inherited class to obtain the collating sequence associated with a function call.
///
///
Index: System.Data.SQLite/SQLiteFunctionAttribute.cs
==================================================================
--- System.Data.SQLite/SQLiteFunctionAttribute.cs
+++ System.Data.SQLite/SQLiteFunctionAttribute.cs
@@ -18,22 +18,26 @@
{
private string _name;
private int _argumentCount;
private FunctionType _functionType;
private Type _instanceType;
+ private Delegate _callback1;
+ private Delegate _callback2;
///
/// Default constructor, initializes the internal variables for the function.
///
public SQLiteFunctionAttribute()
- : this(String.Empty, -1, FunctionType.Scalar)
+ : this(null, -1, FunctionType.Scalar)
{
// do nothing.
}
///
- /// Constructs an instance of this class.
+ /// Constructs an instance of this class. This sets the initial
+ /// , , and
+ /// properties to null.
///
///
/// The name of the function, as seen by the SQLite core library.
///
///
@@ -51,10 +55,12 @@
{
_name = name;
_argumentCount = argumentCount;
_functionType = functionType;
_instanceType = null;
+ _callback1 = null;
+ _callback2 = null;
}
///
/// The function's name as it will be used in SQLite command text.
///
@@ -82,14 +88,38 @@
set { _functionType = value; }
}
///
/// The object instance that describes the class
- /// containing the implementation for the associated function.
+ /// containing the implementation for the associated function. The value of
+ /// this property will not be used if either the or
+ /// property values are set to non-null.
///
internal Type InstanceType
{
get { return _instanceType; }
set { _instanceType = value; }
}
+
+ ///
+ /// The that refers to the implementation for the
+ /// associated function. If this property value is set to non-null, it will
+ /// be used instead of the property value.
+ ///
+ internal Delegate Callback1
+ {
+ get { return _callback1; }
+ set { _callback1 = value; }
+ }
+
+ ///
+ /// The that refers to the implementation for the
+ /// associated function. If this property value is set to non-null, it will
+ /// be used instead of the property value.
+ ///
+ internal Delegate Callback2
+ {
+ get { return _callback2; }
+ set { _callback2 = value; }
+ }
}
}
Index: Tests/basic.eagle
==================================================================
--- Tests/basic.eagle
+++ Tests/basic.eagle
@@ -3589,10 +3589,273 @@
\{\} 0 \{\} 0 (?:-)?\d+ 0 \{\} 1\
\{System\.Reflection\.TargetInvocationException: Exception has been thrown by\
the target of an invocation\. ---> System\.Data\.SQLite\.SQLiteException: SQL\
logic error or missing database
no such function: MyRandom.*\} 0 \{\} 0 (?:-)?\d+ 0 \{\}$}]}
+
+###############################################################################
+
+runTest {test data-1.74 {bind functions using delegate} -setup {
+ proc getMyFuncArgs { argumentCount } {
+ set result [list]
+
+ for {set index 0} {$index < $argumentCount} {incr index} {
+ lappend result [appendArgs 'myFuncArg [expr {$index + 1}] ']
+ }
+
+ return $result
+ }
+
+ proc getHashCode { value } {
+ if {[isObjectHandle $value]} then {
+ if {$value eq "null"} then {
+ return 0
+ } else {
+ return [object invoke $value GetHashCode]
+ }
+ } else {
+ if {[string length $value] == 0} then {
+ return 0
+ } else {
+ set string [object create String $value]
+
+ return [object invoke $string GetHashCode]
+ }
+ }
+ }
+
+ proc hashManagedArray { array } {
+ set data ""
+
+ if {[isObjectHandle $array] && $array ne "null"} then {
+ if {[object invoke $array GetType.IsArray]} then {
+ for {set index 0} {$index < [$array Length]} {incr index} {
+ set element [$array -create -alias GetValue $index]
+
+ if {[string length $element] > 0} then {
+ append data [$element ToString]
+ } else {
+ append data null
+ }
+ }
+ }
+ }
+
+ return [getHashCode [hash normal sha1 $data]]
+ }
+
+ proc myFuncCallback { args } {
+ if {[llength $args] == 0} then {
+ error "no function arguments"
+ }
+
+ set name [lindex $args 0]
+
+ if {[isObjectHandle $name] && $name ne "null"} then {
+ set name [object invoke $name ToString]
+ }
+
+ switch -exact -- $name {
+ Invoke {
+ return [hashManagedArray [lindex $args end]]
+ }
+ Step {
+ set varName [lindex $args end]
+
+ if {[string length $varName] == 0} then {
+ error "invalid aggregate context variable name"
+ }
+
+ upvar 1 $varName ctx
+
+ if {![info exists ctx] || [string length $ctx] == 0} then {
+ set ctx [pid]
+ }
+
+ set hashCtx [getHashCode $ctx]
+ set hashArgs [hashManagedArray [lindex $args end-2]]
+
+ if {[info exists ::aggregateData($hashCtx)]} then {
+ incr ::aggregateData($hashCtx) $hashArgs
+ } else {
+ set ::aggregateData($hashCtx) $hashArgs
+ }
+ }
+ Final {
+ set ctx [lindex $args end]
+
+ if {[string length $ctx] == 0} then {
+ error "invalid aggregate context"
+ }
+
+ set hashCtx [getHashCode $ctx]
+
+ if {[info exists ::aggregateData($hashCtx)]} then {
+ return $::aggregateData($hashCtx)
+ } else {
+ error "missing aggregate context data"
+ }
+ }
+ Compare {
+ lappend ::compareResults [object invoke -create \
+ Int32 Parse [string compare -nocase [lindex \
+ $args 1] [lindex $args 2]]]
+
+ return [lindex $::compareResults end]
+ }
+ default {
+ error [appendArgs "unknown function callback \"" $name \"]
+ }
+ }
+ }
+
+ proc myFuncInvokeCallback { param0 objs } {
+ return [myFuncCallback $param0 $objs]
+ }
+
+ proc myFuncStepCallback { param0 objs stepNumber contextDataVarName } {
+ upvar 1 $contextDataVarName $contextDataVarName
+ return [myFuncCallback $param0 $objs $stepNumber $contextDataVarName]
+ }
+
+ proc myFuncFinalCallback { param0 contextData } {
+ return [myFuncCallback $param0 $contextData ]
+ }
+
+ proc myFuncCompareCallback { param0 param1 param2 } {
+ return [myFuncCallback $param0 $param1 $param2]
+ }
+
+ setupDb [set fileName data-1.74.db]
+} -body {
+ sql execute $db "CREATE TABLE t1(x);"
+ sql execute $db "INSERT INTO t1 (x) VALUES(1);"
+ sql execute $db "INSERT INTO t1 (x) VALUES(2);"
+ sql execute $db "INSERT INTO t1 (x) VALUES(3);"
+ sql execute $db "INSERT INTO t1 (x) VALUES('A');"
+ sql execute $db "INSERT INTO t1 (x) VALUES('a');"
+ sql execute $db "INSERT INTO t1 (x) VALUES('M');"
+ sql execute $db "INSERT INTO t1 (x) VALUES('m');"
+ sql execute $db "INSERT INTO t1 (x) VALUES('Z');"
+ sql execute $db "INSERT INTO t1 (x) VALUES('z');"
+
+ set connection [getDbConnection]
+
+ for {set argumentCount 0} {$argumentCount < 3} {incr argumentCount} {
+ set attribute(1,$argumentCount) [object create \
+ System.Data.SQLite.SQLiteFunctionAttribute [appendArgs \
+ myFunc1_74_1_ $argumentCount] $argumentCount Scalar]
+
+ $connection -marshalflags \
+ {-StrictMatchType +DynamicCallback ForceParameterType} \
+ -parametertypes [list System.Data.SQLite.SQLiteFunctionAttribute \
+ System.Data.SQLite.SQLiteInvokeDelegate Delegate] \
+ BindFunction $attribute(1,$argumentCount) \
+ myFuncInvokeCallback null
+
+ set attribute(2,$argumentCount) [object create \
+ System.Data.SQLite.SQLiteFunctionAttribute [appendArgs \
+ myFunc1_74_2_ $argumentCount] $argumentCount Aggregate]
+
+ $connection -marshalflags \
+ {-StrictMatchType +DynamicCallback ForceParameterType} \
+ -parametertypes [list System.Data.SQLite.SQLiteFunctionAttribute \
+ System.Data.SQLite.SQLiteStepDelegate \
+ System.Data.SQLite.SQLiteFinalDelegate] \
+ BindFunction $attribute(2,$argumentCount) \
+ myFuncStepCallback myFuncFinalCallback
+ }
+
+ set attribute(3,0) [object create \
+ System.Data.SQLite.SQLiteFunctionAttribute myFunc1_74_3 0 \
+ Collation]
+
+ $connection -marshalflags \
+ {-StrictMatchType +DynamicCallback ForceParameterType} \
+ -parametertypes [list System.Data.SQLite.SQLiteFunctionAttribute \
+ System.Data.SQLite.SQLiteCompareDelegate Delegate] \
+ BindFunction $attribute(3,0) \
+ myFuncCompareCallback null
+
+ for {set argumentCount 0} {$argumentCount < 3} {incr argumentCount} {
+ lappend result [catch {
+ sql execute $db [appendArgs \
+ "SELECT " myFunc1_74_1_ $argumentCount ( \
+ [join [getMyFuncArgs $argumentCount] ,] )\;]
+ } error] $error
+
+ lappend result [catch {
+ sql execute $db [appendArgs \
+ "SELECT " myFunc1_74_2_ $argumentCount ( \
+ [join [getMyFuncArgs $argumentCount] ,] )\;]
+ } error] $error
+ }
+
+ lappend result [catch {
+ sql execute -execute reader -format list $db \
+ "SELECT x FROM t1 ORDER BY x COLLATE myFunc1_74_3;"
+ } error] $error
+
+ lappend result [$connection UnbindAllFunctions false]
+
+ for {set argumentCount 0} {$argumentCount < 3} {incr argumentCount} {
+ lappend result [catch {
+ sql execute $db [appendArgs \
+ "SELECT " myFunc1_74_1_ $argumentCount ( \
+ [join [getMyFuncArgs $argumentCount] ,] )\;]
+ } error] [expr {[string first [appendArgs \
+ "no such function: myFunc1_74_1_" $argumentCount] $error] != -1}]
+
+ lappend result [catch {
+ sql execute $db [appendArgs \
+ "SELECT " myFunc1_74_2_ $argumentCount ( \
+ [join [getMyFuncArgs $argumentCount] ,] )\;]
+ } error] [expr {[string first [appendArgs \
+ "no such function: myFunc1_74_2_" $argumentCount] $error] != -1}]
+ }
+
+ lappend result [catch {
+ sql execute -execute reader -format list $db \
+ "SELECT x FROM t1 ORDER BY x COLLATE myFunc1_74_3;"
+ } error] [expr {[string first "no such collation sequence: myFunc1_74_3" \
+ $error] != -1}]
+
+ lappend result [array size aggregateData]
+ lappend result [testArrayGet aggregateData]
+
+ set result
+} -cleanup {
+ cleanupDb $fileName
+
+ freeDbConnection
+
+ catch {object removecallback myFuncCompareCallback}
+ catch {object removecallback myFuncFinalCallback}
+ catch {object removecallback myFuncStepCallback}
+ catch {object removecallback myFuncInvokeCallback}
+
+ catch {
+ foreach compareResult $compareResults {
+ catch {object dispose $compareResult}
+ }
+ }
+
+ unset -nocomplain result error compareResult compareResults \
+ aggregateData argumentCount attribute connection db fileName
+
+ rename myFuncCompareCallback ""
+ rename myFuncFinalCallback ""
+ rename myFuncStepCallback ""
+ rename myFuncInvokeCallback ""
+ rename myFuncCallback ""
+ rename hashManagedArray ""
+ rename getHashCode ""
+ rename getMyFuncArgs ""
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite} -match regexp -result {^0 -1 0 -1 0 -1 0 -1 0 -1 0 -1 0 \{1\
+2 3 A a M m Z z\} True 1 True 1 True 1 True 1 True 1 True 1 True 1 True 1\
+\{(?:-)?\d+ (?:-)?\d+\}$}}
###############################################################################
reportSQLiteResources $test_channel
Index: readme.htm
==================================================================
--- readme.htm
+++ readme.htm
@@ -212,10 +212,11 @@
1.0.98.0 - August XX, 2015 (release scheduled)
- Updated to SQLite 3.8.11.1.
- Add full support for Visual Studio 2015 and the .NET Framework 4.6.
+ - Add support for creating custom SQL functions using delegates.
- Implement the Substring method for LINQ using the "substr" core SQL function. ** Potentially Incompatible Change **
- Prevent encrypted connections from being used with the connection pool. Pursuant to [89d3a159f1]. ** Potentially Incompatible Change **
- Honor the second argument to Math.Round when using LINQ. ** Potentially Incompatible Change **
- Honor the pre-existing flags for connections during the Open method. Fix for [964063da16]. ** Potentially Incompatible Change **
- Remove errant semi-colons from the SQL used by LINQ to INSERT and then SELECT rows with composite primary keys. Fix for [9d353b0bd8].
Index: www/news.wiki
==================================================================
--- www/news.wiki
+++ www/news.wiki
@@ -6,10 +6,11 @@
1.0.98.0 - August XX, 2015 (release scheduled)
- Updated to [https://www.sqlite.org/releaselog/3_8_11_1.html|SQLite 3.8.11.1].
- Add full support for Visual Studio 2015 and the .NET Framework 4.6.
+ - Add support for creating custom SQL functions using delegates.
- Implement the Substring method for LINQ using the "substr" core SQL function. ** Potentially Incompatible Change **
- Prevent encrypted connections from being used with the connection pool. Pursuant to [89d3a159f1]. ** Potentially Incompatible Change **
- Honor the second argument to Math.Round when using LINQ. ** Potentially Incompatible Change **
- Honor the pre-existing flags for connections during the Open method. Fix for [964063da16]. ** Potentially Incompatible Change **
- Remove errant semi-colons from the SQL used by LINQ to INSERT and then SELECT rows with composite primary keys. Fix for [9d353b0bd8].