-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
101 lines (91 loc) · 2.49 KB
/
Copy pathmain.go
File metadata and controls
101 lines (91 loc) · 2.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// window-function example: register a Go window-function on a
// connection and use it inside SQL. The WindowAccumulator interface
// has Step / Inverse / Value — SQLite drives the three callbacks as
// the engine moves through frames. Final is an optional
// WindowFinalizer interface for accumulators that need cleanup; this
// pure-math one doesn't.
package main
import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
"log"
sqlite "gosqlite.org"
)
// runningSum keeps the moving sum over a numeric column. Inverse
// undoes a row's contribution when it leaves the frame, so this
// works for any windowed plan including sliding frames where SQLite
// can't recompute from scratch.
type runningSum struct{ total float64 }
func (s *runningSum) Step(_ *sqlite.FunctionContext, args []driver.Value) error {
s.total += toFloat(args[0])
return nil
}
func (s *runningSum) Inverse(_ *sqlite.FunctionContext, args []driver.Value) error {
s.total -= toFloat(args[0])
return nil
}
func (s *runningSum) Value(_ *sqlite.FunctionContext) (driver.Value, error) {
return s.total, nil
}
func toFloat(v driver.Value) float64 {
switch x := v.(type) {
case float64:
return x
case int64:
return float64(x)
}
return 0
}
func main() {
ctx := context.Background()
db, err := sql.Open("sqlite", ":memory:")
if err != nil {
log.Fatal(err)
}
defer db.Close()
db.SetMaxOpenConns(1) // pin so the registered UDF stays reachable
sc, err := db.Conn(ctx)
if err != nil {
log.Fatal(err)
}
defer sc.Close()
if err := sc.Raw(func(dc any) error {
c := dc.(*sqlite.Conn)
return c.RegisterWindowFunction("rsum", 1,
func() sqlite.WindowAccumulator { return &runningSum{} }, true)
}); err != nil {
log.Fatal(err)
}
if _, err := sc.ExecContext(ctx,
`CREATE TABLE t (id INTEGER PRIMARY KEY, v REAL)`); err != nil {
log.Fatal(err)
}
for i, v := range []float64{10, 20, 30, 40, 50} {
if _, err := sc.ExecContext(ctx,
`INSERT INTO t (id, v) VALUES (?, ?)`, i+1, v); err != nil {
log.Fatal(err)
}
}
rows, err := sc.QueryContext(ctx, `
SELECT id, v,
rsum(v) OVER (
ORDER BY id
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
) AS moving_sum_2
FROM t ORDER BY id`)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
fmt.Println("id v moving_sum_2")
for rows.Next() {
var id int64
var v, sum float64
if err := rows.Scan(&id, &v, &sum); err != nil {
log.Fatal(err)
}
fmt.Printf("%d %3.0f %3.0f\n", id, v, sum)
}
}