I made many updates to my implementation, which can be found here:
https://chiselapp.com/user/andy/repository/sqlite-andy/timeline?r=andygoth-tcl-function https://chiselapp.com/user/andy/repository/sqlite-andy/artifact?fn=src/tclsqlite.c&ci=andygoth-tcl-function https://chiselapp.com/user/andy/repository/sqlite-andy/vdiff?from=root:andygoth-tcl-function&to=andygoth-tcl-function Documentation and test suite still to come. [db function] now accepts the following switches: -argcount N: Required argument count -deterministic: Promise to be consistent -state: Pass state value as argument after method name (default) -class: Use an object-oriented approach instead of -state -initial VAL: Constructor argument(s), defaults to empty -scalar: Create a scalar function (default) -aggregate: Create a non-window aggregate function -window: Create a window-enabled aggregate function -class and -initial require -window or -aggregate. First, let me demonstrate the -state (default) calling convention: db function square -deterministic { apply {{arg} { expr {$arg * $arg} }} } db function list -deterministic -window { apply {{method state args} { switch $method { step {concat $state $args} inverse {lrange $state [llength $args] end} value {set state} } }} } db function sum -deterministic -initial 0 -window { apply {{method state args} { switch $method { step {expr {$state + [lindex $args 0]}} inverse {expr {$state - [lindex $args 0]}} new - value {set state} } }} } db eval { WITH data (label, number) AS ( VALUES ('f', -6), ('b', -2), ('a', 1), ('e', 5), ('d', -4), ('c', 3)) SELECT sum(square(number)) OVER win AS sumsqu , list(label, number) OVER win AS list FROM data WINDOW win AS (ORDER BY label ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) } { puts [list $sumsqu $list] } 5 {a 1 b -2} 14 {a 1 b -2 c 3} 29 {b -2 c 3 d -4} 50 {c 3 d -4 e 5} 77 {d -4 e 5 f -6} 61 {e 5 f -6} Now, here's the -class calling convention: oo::class create ListAggregate { variable state method step {args} {lappend state {*}$args} method inverse {args} {set state [lrange $state [llength $args] end]} method value {} {return $state} } db function list -deterministic -window -class ListAggregate oo::class create SumAggregate { variable state constructor {{i 0}} {set state $i} method step {i} {set state [expr {$state + $i}]} method inverse {i} {set state [expr {$state - $i}]} method value {} {return $state} } db function sum -deterministic -window -class SumAggregate The output is the same as with the first syntax example. Here, I don't use the -initial switch, instead relying on its empty default. Furthermore, I rely on the default constructor argument of 0. With -initial, I could explicitly set an initial value for the sum. -initial behaves a little differently between -state and -class modes. For -state, it gives the argument to the "new" method. For -class, it gives the argument *list* to the "new" method. Hence, when empty or omitted, for -state mode the "new" method gets empty string as its argument, whereas for -class mode it gets no arguments. -initial can be used to implement a function once in Tcl, then instantiate it in SQLite several times with some varying parameters to create a family of functions. You may have noticed that I don't have "final" anymore. Instead, I rolled it into "value" and "destroy", the latter of which I haven't shown in my examples because the default is good. To see what's going on under the hood, try this: db function list -deterministic -window -initial {foo bar} -class { apply {{new args} { puts \[[lrange [info level 0] 2 end]\] list apply {{method args} { global state puts [lrange [info level 0] 2 end] switch $method { new {set state {}} step {lappend state {*}$args} inverse {set state [lrange $state [llength $args] end]} value {set state} destroy {unset state} } }} }} } db eval { WITH data (label, number) AS ( VALUES ('f', -6), ('b', -2), ('a', 1), ('e', 5), ('d', -4), ('c', 3)) SELECT label , list(label, number) OVER win AS list FROM data WINDOW win AS (ORDER BY label ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) } { puts [list $label $list] } The above prints: [new foo bar] [step a 1] [step b -2] [value] a {a 1 b -2} [step c 3] [value] b {a 1 b -2 c 3} [inverse a 1] [step d -4] [value] c {b -2 c 3 d -4} [inverse b -2] [step e 5] [value] d {c 3 d -4 e 5} [inverse c 3] [step f -6] [value] e {d -4 e 5 f -6} [inverse d -4] [value] f {e 5 f -6} [destroy] Here's the above example, reworked to not use -class: db function list -deterministic -window -initial {foo bar} { apply {{method state args} { puts \[[lrange [info level 0] 2 end]\] switch $method { step {concat $state $args} inverse {lrange $state [llength $args] end} value {set state} destroy {unset state} } }} } Used with the prior [db eval] query, the above prints: [new {foo bar}] [step {} a 1] [step {a 1} b -2] [value {a 1 b -2}] a {a 1 b -2} [step {a 1 b -2} c 3] [value {a 1 b -2 c 3}] b {a 1 b -2 c 3} [inverse {a 1 b -2 c 3} a 1] [step {b -2 c 3} d -4] [value {b -2 c 3 d -4}] c {b -2 c 3 d -4} [inverse {b -2 c 3 d -4} b -2] [step {c 3 d -4} e 5] [value {c 3 d -4 e 5}] d {c 3 d -4 e 5} [inverse {c 3 d -4 e 5} c 3] [step {d -4 e 5} f -6] [value {d -4 e 5 f -6}] e {d -4 e 5 f -6} [inverse {d -4 e 5 f -6} d -4] [value {e 5 f -6}] f {e 5 f -6} [destroy {e 5 f -6}] -- Andy Goth | <andrew.m.goth/at/gmail/dot/com> _______________________________________________ sqlite-users mailing list sqlite-users@mailinglists.sqlite.org http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/sqlite-users