Index: System.Data.SQLite/SQLiteConnection.cs ================================================================== --- System.Data.SQLite/SQLiteConnection.cs +++ System.Data.SQLite/SQLiteConnection.cs @@ -1412,27 +1412,35 @@ /// /// /// The object instance containing /// the metadata for the function to be bound. /// - /// - /// The object instance that implements the - /// function to be bound. + /// + /// A object instance that helps implement the + /// function to be bound. For aggregate functions, this corresponds to the + /// callback. + /// + /// + /// A object instance that helps implement the + /// function to be bound. For aggregate functions, this corresponds to the + /// callback. For other callback types, + /// it is not used and must be null. /// public void BindFunction( SQLiteFunctionAttribute functionAttribute, - Delegate callback + Delegate callback1, + Delegate callback2 ) { CheckDisposed(); if (_sql == null) throw new InvalidOperationException( "Database connection not valid for binding functions."); _sql.BindFunction(functionAttribute, - new SQLiteDelegateFunction(callback), _flags); + new SQLiteDelegateFunction(callback1, callback2), _flags); } /////////////////////////////////////////////////////////////////////////////////////////////// /// Index: System.Data.SQLite/SQLiteFunction.cs ================================================================== --- System.Data.SQLite/SQLiteFunction.cs +++ System.Data.SQLite/SQLiteFunction.cs @@ -738,11 +738,11 @@ if (at == null) continue; RegisterFunction( at.Name, at.Arguments, at.FuncType, at.InstanceType, - at.Callback); + at.Callback1, at.Callback2); } } /// /// Alternative method of registering a function. This method @@ -759,31 +759,38 @@ /// 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. + /// This will only be used if the + /// and parameters are null. + /// + /// + /// The to be used for all calls into the + /// , + /// , + /// and virtual methods. /// - /// - /// The that implements the function. If - /// this is non-null, the parameter - /// will be ignored when the function is invoked. + /// + /// The to be used for all calls into the + /// virtual method. /// public static void RegisterFunction( string name, int argumentCount, FunctionType functionType, Type instanceType, - Delegate callback + Delegate callback1, + Delegate callback2 ) { SQLiteFunctionAttribute at = new SQLiteFunctionAttribute( name, argumentCount, functionType); at.InstanceType = instanceType; - at.Callback = callback; + at.Callback1 = callback1; + at.Callback2 = callback2; _registeredFunctions.Add(at, null); } /// @@ -808,14 +815,16 @@ if (functionAttribute == null) { function = null; return false; } - else if (functionAttribute.Callback != null) + else if ((functionAttribute.Callback1 != null) || + (functionAttribute.Callback2 != null)) { function = new SQLiteDelegateFunction( - functionAttribute.Callback); + functionAttribute.Callback1, + functionAttribute.Callback2); return true; } else if (functionAttribute.InstanceType != null) { @@ -1189,14 +1198,16 @@ /// public class SQLiteDelegateFunction : SQLiteFunction { #region Private Constants /// - /// This error message is used by the overridden virtual methods when the - /// callback has not been set. + /// 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 callback is set."; + private const string NoCallbackError = "No \"{0}\" callback is set."; ///////////////////////////////////////////////////////////////////////// /// /// This error message is used by the overridden @@ -1210,11 +1221,11 @@ #region Public Constructors /// /// Constructs an empty instance of this class. /// public SQLiteDelegateFunction() - : this(null) + : this(null, null) { // do nothing. } ///////////////////////////////////////////////////////////////////////// @@ -1222,20 +1233,28 @@ /// /// 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 - /// class. + /// virtual methods needed by the + /// base class. /// public SQLiteDelegateFunction( - Delegate callback + Delegate callback1, + Delegate callback2 ) { - this.callback = callback; + this.callback1 = callback1; + this.callback2 = callback2; } #endregion ///////////////////////////////////////////////////////////////////////// @@ -1418,20 +1437,35 @@ #endregion ///////////////////////////////////////////////////////////////////////// #region Public Properties - private Delegate callback; + 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 - /// class. + /// virtual methods needed by the + /// base class. /// - public virtual Delegate Callback + public virtual Delegate Callback2 { - get { return callback; } - set { callback = value; } + get { return callback2; } + set { callback2 = value; } } #endregion ///////////////////////////////////////////////////////////////////////// @@ -1449,23 +1483,26 @@ /// public override object Invoke( object[] args /* in */ ) { - if (callback == null) - throw new InvalidOperationException(NoCallbackError); + if (callback1 == null) + { + throw new InvalidOperationException(String.Format( + NoCallbackError, "Invoke")); + } SQLiteInvokeDelegate invokeDelegate = - callback as SQLiteInvokeDelegate; + callback1 as SQLiteInvokeDelegate; if (invokeDelegate != null) { return invokeDelegate.Invoke("Invoke", args); /* throw */ } else { - return callback.DynamicInvoke( + return callback1.DynamicInvoke( GetInvokeArgs(args, false)); /* throw */ } } ///////////////////////////////////////////////////////////////////////// @@ -1490,14 +1527,17 @@ object[] args, /* in */ int stepNumber, /* in */ ref object contextData /* in, out */ ) { - if (callback == null) - throw new InvalidOperationException(NoCallbackError); + if (callback1 == null) + { + throw new InvalidOperationException(String.Format( + NoCallbackError, "Step")); + } - SQLiteStepDelegate stepDelegate = callback as SQLiteStepDelegate; + SQLiteStepDelegate stepDelegate = callback1 as SQLiteStepDelegate; if (stepDelegate != null) { stepDelegate.Invoke( "Step", args, stepNumber, ref contextData); /* throw */ @@ -1506,11 +1546,11 @@ { object[] newArgs = GetStepArgs( args, stepNumber, contextData, false); /* IGNORED */ - callback.DynamicInvoke(newArgs); /* throw */ + callback1.DynamicInvoke(newArgs); /* throw */ UpdateStepArgs(newArgs, ref contextData, false); } } @@ -1530,23 +1570,26 @@ /// public override object Final( object contextData /* in */ ) { - if (callback == null) - throw new InvalidOperationException(NoCallbackError); + if (callback2 == null) + { + throw new InvalidOperationException(String.Format( + NoCallbackError, "Final")); + } - SQLiteFinalDelegate finalDelegate = callback as SQLiteFinalDelegate; + SQLiteFinalDelegate finalDelegate = callback2 as SQLiteFinalDelegate; if (finalDelegate != null) { - return finalDelegate.Invoke("Final", contextData); + return finalDelegate.Invoke("Final", contextData); /* throw */ } else { - return callback.DynamicInvoke(GetFinalArgs( - contextData, callback is SQLiteFinalDelegate)); /* throw */ + return callback1.DynamicInvoke(GetFinalArgs( + contextData, false)); /* throw */ } } ///////////////////////////////////////////////////////////////////////// @@ -1571,23 +1614,27 @@ public override int Compare( string param1, /* in */ string param2 /* in */ ) { - if (callback == null) - throw new InvalidOperationException(NoCallbackError); + if (callback1 == null) + { + throw new InvalidOperationException(String.Format( + NoCallbackError, "Compare")); + } SQLiteCompareDelegate compareDelegate = - callback as SQLiteCompareDelegate; + callback1 as SQLiteCompareDelegate; if (compareDelegate != null) { - return compareDelegate.Invoke("Compare", param1, param2); + return compareDelegate.Invoke( + "Compare", param1, param2); /* throw */ } else { - object result = callback.DynamicInvoke(GetCompareArgs( + object result = callback1.DynamicInvoke(GetCompareArgs( param1, param2, false)); /* throw */ if (result is int) return (int)result; Index: System.Data.SQLite/SQLiteFunctionAttribute.cs ================================================================== --- System.Data.SQLite/SQLiteFunctionAttribute.cs +++ System.Data.SQLite/SQLiteFunctionAttribute.cs @@ -18,11 +18,12 @@ { private string _name; private int _argumentCount; private FunctionType _functionType; private Type _instanceType; - private Delegate _callback; + private Delegate _callback1; + private Delegate _callback2; /// /// Default constructor, initializes the internal variables for the function. /// public SQLiteFunctionAttribute() @@ -52,11 +53,12 @@ { _name = name; _argumentCount = argumentCount; _functionType = functionType; _instanceType = null; - _callback = null; + _callback1 = null; + _callback2 = null; } /// /// The function's name as it will be used in SQLite command text. /// @@ -85,12 +87,12 @@ } /// /// 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. + /// 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; } @@ -99,12 +101,23 @@ /// /// 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 + internal Delegate Callback1 { - get { return _callback; } - set { _callback = value; } - } + 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 @@ -3600,64 +3600,134 @@ 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] - switch -exact -- $name { - Invoke { - return $args - } - Step { - set ctx [lindex $args 1] - - if {[string length $ctx] == 0} then { - error "invalid aggregate context" - } - - global aggregateData - - if {[info exists aggregateData($ctx)]} then { - incr aggregateData($ctx) - } else { - set aggregateData($ctx) 1 - } - } - Final { - set ctx [lindex $args 1] - - if {[string length $ctx] == 0} then { - error "invalid aggregate context" - } - - global aggregateData - - if {[info exists aggregateData($ctx)]} then { - return $aggregateData($ctx) - } else { - error "missing aggregate context data" - } - } - Compare { - return [string compare -nocase [lindex $args 1] [lindex $args 2]] + 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 INTEGER);" + 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');" @@ -3671,23 +3741,40 @@ 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 BindFunction $attribute(1,$argumentCount) myFuncCallback + $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 BindFunction $attribute(2,$argumentCount) myFuncCallback + $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] + System.Data.SQLite.SQLiteFunctionAttribute myFunc1_74_3 0 \ + Collation] - $connection BindFunction $attribute(3,0) myFuncCallback + $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 ( \ @@ -3700,11 +3787,12 @@ [join [getMyFuncArgs $argumentCount] ,] )\;] } error] $error } lappend result [catch { - sql execute $db "SELECT x FROM t1 ORDER BY x COLLATE myFunc1_74_3;" + 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} { @@ -3722,29 +3810,50 @@ } error] [expr {[string first [appendArgs \ "no such function: myFunc1_74_2_" $argumentCount] $error] != -1}] } lappend result [catch { - sql execute $db "SELECT x FROM t1 ORDER BY x COLLATE myFunc1_74_3;" + 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 myFuncCallback} + 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 aggregateData argumentCount attribute \ - connection db fileName + 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} -result {}} +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