timshannon / bolthold

BoltHold is an embeddable NoSQL store for Go types built on BoltDB
MIT License
648 stars 46 forks source link

Query panic on nil embedded anonymous struct #100

Closed ftKnox closed 4 years ago

ftKnox commented 4 years ago

so I finally had a chance to get back to this project, and I am still seeing issues when querying


2020/03/15 13:08:49 USING QUERY: Where Name matches the regular expression (?i:.*?bolt.*?)
ERROR 2020/03/15 13:08:49.477124 panic_handler.go:26: PANIC
URL: /list/users
ERROR: reflect: indirection through nil pointer to embedded struct
STACK:
goroutine 53 [running]:
_/Users/user/code/app/lib/web.(*Router).handlePanic(0xc0002151e0, 0xc000110100, 0xc000110120, 0x151b160, 0x1694500)
        /Users/user/code/app/lib/web/router_serve.go:360 +0x4fc
_/Users/user/code/app/lib/web.(*Router).ServeHTTP.func1(0xc0002151e0, 0xc000110100)
        /Users/user/code/app/lib/web/router_serve.go:61 +0x6f
panic(0x151b160, 0x1694500)
        /opt/local/lib/go/src/runtime/panic.go:679 +0x1b2
reflect.Value.FieldByIndex(0x15dd960, 0xc0001746c0, 0x199, 0xc00017a980, 0x2, 0x2, 0x0, 0x16b3480, 0x151b160)
        /opt/local/lib/go/src/reflect/value.go:876 +0x39c
reflect.Value.FieldByName(0x15dd960, 0xc0001746c0, 0x199, 0x15e2eaa, 0x4, 0x199, 0xc0004b8c20, 0x1)
        /opt/local/lib/go/src/reflect/value.go:892 +0x104
_/Users/user/code/app/lib/bolthold.fieldValue(0x15d4200, 0xc0001746c0, 0x16, 0x15e2eaa, 0x4, 0x15043c0, 0xc0004b8c00, 0x15043c0, 0xc0004b8c00, 0x5)
        /Users/user/code/app/lib/bolthold/criteria.go:266 +0x147
_/Users/user/code/app/lib/bolthold.(*Query).matchesAllFields(0xc0000ae960, 0xc0004a4500, 0x68ec20c, 0x26, 0x26, 0x15d4200, 0xc0001746c0, 0x16, 0x15d4200, 0xc0001746c0, ...)
        /Users/user/code/app/lib/bolthold/criteria.go:243 +0x238
_/Users/user/code/app/lib/bolthold.(*Store).runQuery(0xc0004a4500, 0x16a34e0, 0xc00023e540, 0x15d4200, 0xc000174300, 0xc0000ae960, 0x0, 0x0, 0x0, 0x0, ...)
        /Users/user/code/app/lib/bolthold/query.go:68 +0x633
_/Users/user/code/app/lib/bolthold.(*Store).findQuery(0xc0004a4500, 0x16a34e0, 0xc00023e540, 0x14fea80, 0xc00027cac0, 0xc0000ae960, 0x0, 0x0)
        /Users/user/code/app/lib/bolthold/query.go:267 +0x515
_/Users/user/code/app/lib/bolthold.(*Store).TxFind(...)
        /Users/user/code/app/lib/bolthold/get.go:92
_/Users/user/code/app/lib/bolthold.(*Store).Find.func1(0xc00023e540, 0x0, 0xc00023e540)
        /Users/user/code/app/lib/bolthold/get.go:86 +0x5b
go.etcd.io/bbolt.(*DB).View(0xc0002f7200, 0xc0002ecf18, 0x0, 0x0)
        /opt/local/src/go.etcd.io/bbolt/db.go:719 +0xa8
_/Users/user/code/app/lib/bolthold.(*Store).Find(0xc0004a4500, 0x14fea80, 0xc00027cac0, 0xc0000ae960, 0x1, 0xc000496020)
        /Users/user/code/app/lib/bolthold/get.go:85 +0x81

here is a test that demonstrates issue

package main

import (
    "encoding/json"
    "log"
    "os"
    "regexp"

    "github.com/timshannon/bolthold"
    "github.com/satori/uuid"
)

type (
    Profile struct {
        Username   string `boltholdIndex:"Username" json:"name"`
        Name       string `boltholdIndex:"Name" json:"name"`
        Phone      string `boltholdIndex:"Phone" json:"name"`
        Address1   string `json:"address1"`
        Address2   string `json:"address2"`
        City       string `json:"city"`
        State      string `json:"state"`
        PostalCode string `json:"postal_code"`
    }

    Parent struct {
        ID string `json:"id"`
    }

    User struct {
        *Parent
        *Profile

        ID       string `boltholdKey:"ID" json:"id"`
        District string `json:"district"`
    }
)

func dbEncoder(value interface{}) ([]byte, error) {
    return json.Marshal(value)
}

func dbDecoder(data []byte, value interface{}) error {
    return json.Unmarshal(data, &value)
}

func main() {
    os.RemoveAll("__bolthold.test__")
    defer os.RemoveAll("__bolthold.test__")

    db, err := bolthold.Open("__bolthold.test__", 0600, &bolthold.Options{
        Encoder: dbEncoder,
        Decoder: dbDecoder,
    })
    if err != nil {
        log.Printf("Couldn't open db [ERR: %s]", err.Error())
    }
    defer db.Close()

    userID := uuid.Must(uuid.NewV4()).String()
    db.Insert(userID, &User{
        ID:       userID,
        District: "East",
        Profile: &Profile{
            Username:   "bolt",
            Name:       "Bolt DB",
            Phone:      "123-123-1234",
            Address1:   "123 Test Street",
            Address2:   "Suite 100",
            City:       "Test City",
            State:      "CA",
            PostalCode: "11111",
        },
    })

    //create a second user where the embedded anonymous struct will be nil
    userID = uuid.Must(uuid.NewV4()).String()
    db.Insert(userID, &User{
        ID:       userID,
        District: "West",
    })

    users := []*User{}
    query := bolthold.Where("Name").RegExp(regexp.MustCompile("(?i:.*?bolt.*?)"))
    err = db.Find(&users, query)
    if err != nil {
        log.Fatalf("could not query for users [ERR: %s]", err.Error())
    }

    for _, user := range users {
        log.Printf("Found user account \"%s\"", user.Name)
    }

    return
}
timshannon commented 4 years ago

Fixed with #102 Thanks again for continuing to submit these Anon/Embedded struct issues. They can be tricky.

ftKnox commented 4 years ago

This doesn't appear to be working. Using the test above, the panic is gone. However, no results are returned. I think it should return the first inserted record, correct? Am I missing something?

timshannon commented 4 years ago

Yeah that doesn't look right, sorry

timshannon commented 4 years ago

I just updated the Test to better reflect your example above, and it all seems to work fine in the test:

https://github.com/timshannon/bolthold/blob/master/nested_structs_test.go#L621

ftKnox commented 4 years ago

Ok, thanks for looking. I will go remove and update my bolthold repos to make sure it's not something stupid I did.

timshannon commented 4 years ago

@ftKnox Feel free to reopen this if you are still seeing issues.

Thanks,