Skip to content

Aggregator Done() can't return any/interface{} #1045

Closed
@eatonphil

Description

@eatonphil

Hey, I'm trying to write some aggregator functions that work on any data type. Here's a minimal example:

package main

import (
        "database/sql"
        "fmt"
        "log"

        sqlite "github.com/mattn/go-sqlite3"
)


type mode struct {
        counts   map[any]int
        top      any
        topCount int
}

func newMode() *mode {
        return &mode{
                counts: map[any]int{},
        }
}

func (m *mode) Step(x any) {
        m.counts[x]++
        c := m.counts[x]
        if c > m.topCount {
                m.top = x
                m.topCount = c
        }
}

func (m *mode) Done() any {
        return m.top
}

func main() {
        sql.Register("sqlite3_custom", &sqlite.SQLiteDriver{
                ConnectHook: func(conn *sqlite.SQLiteConn) error {
                        if err := conn.RegisterAggregator("mode", newMode, true); err != nil {
                                return err
                        }
                        return nil
                },
        })

        db, err := sql.Open("sqlite3_custom", ":memory:")
        if err != nil {
                log.Fatal("Failed to open database:", err)
        }
        defer db.Close()

        _, err = db.Exec("create table foo (department integer, profits integer)")
        if err != nil {
                log.Fatal("Failed to create table:", err)
        }
        _, err = db.Exec("insert into foo values (1, 10), (1, 20), (1, 45), (2, 42), (2, 115), (2, 20)")
        if err != nil {
                log.Fatal("Failed to insert records:", err)
        }

        rows, err := db.Query("select mode(profits) from foo")
        if err != nil {
                log.Fatal("MODE query error:", err)
        }
        defer rows.Close()
        for rows.Next() {
                //var dept int64
                var dev string
                if err := rows.Scan( &dev); err != nil {
                        log.Fatal(err)
                }
                fmt.Printf("mode=%s\n", dev)
        }
        if err := rows.Err(); err != nil {
                log.Fatal(err)
        }
}

When I run this though I get a panic:

panic: reflect: Elem of invalid type interface {}

goroutine 1 [running]:
reflect.(*rtype).Elem(0x64b2a0?)
        /usr/local/go/src/reflect/type.go:965 +0x134
github.com/mattn/go-sqlite3.callbackRet({0x64bd60, 0x5fdfe0})
        /home/phil/tmp/sqlite3-test/vendor/github.com/mattn/go-sqlite3/callback.go:366 +0x17c
github.com/mattn/go-sqlite3.(*SQLiteConn).RegisterAggregator(0xc000076180, {0x61a96a, 0x4}, {0x5f6fa0, 0x625d60}, 0x1)
        /home/phil/tmp/sqlite3-test/vendor/github.com/mattn/go-sqlite3/sqlite3.go:776 +0xbaf
main.main.func1(0xc0000161a0?)
        /home/phil/tmp/sqlite3-test/main2.go:40 +0x39
github.com/mattn/go-sqlite3.(*SQLiteDriver).Open(0xc00006a040, {0x61b08b, 0x8})
        /home/phil/tmp/sqlite3-test/vendor/github.com/mattn/go-sqlite3/sqlite3.go:1765 +0x3c79
database/sql.dsnConnector.Connect(...)
        /usr/local/go/src/database/sql/sql.go:761
database/sql.(*DB).conn(0xc00010ea90, {0x64b7a0, 0xc00001e0e0}, 0x1)
        /usr/local/go/src/database/sql/sql.go:1395 +0x782
database/sql.(*DB).exec(0x4d770e?, {0x64b7a0, 0xc00001e0e0}, {0x6244c3, 0x36}, {0x0, 0x0, 0x0}, 0x40?)
        /usr/local/go/src/database/sql/sql.go:1657 +0x5d
database/sql.(*DB).ExecContext(0x61c4c1?, {0x64b7a0, 0xc00001e0e0}, {0x6244c3, 0x36}, {0x0, 0x0, 0x0})
        /usr/local/go/src/database/sql/sql.go:1635 +0xe5
database/sql.(*DB).Exec(...)
        /usr/local/go/src/database/sql/sql.go:1653
main.main()
        /home/phil/tmp/sqlite3-test/main2.go:53 +0x172

I changed Done() to return a string here and did:

func (m *mode) Done() string {
        return fmt.Sprintf("%v", m.top)
}

And that works. But I don't want to force every result to a string here.

Are you open to patching the callbackRet function to support returning any from Done()? Or is that just not possible?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions