storm icon indicating copy to clipboard operation
storm copied to clipboard

Support nested struct in queries

Open ake-persson opened this issue 7 years ago • 6 comments

It would be quite nice to be able to do queries on nested data structs like:

db.Select(q.Eq("Interfaces.Name", "eth0"))

Or:

db.Select(q.Sub("Interfaces", q.Eq("Name", "eth0")))

Example:

package main

import (
        "fmt"
        "log"
        "time"

        "github.com/asdine/storm"
        "github.com/asdine/storm/q"
        "github.com/boltdb/bolt"
        "github.com/pborman/uuid"
)

type Host struct {
        UUID       string `storm:"id"`
        Hostname   string
        Interfaces *Interfaces
}

type Hosts []*Host

type Interface struct {
        Name   string
        IPAddr string
}

type Interfaces []*Interface

var db *storm.DB

func main() {
        host := Host{
                UUID:     uuid.New(),
                Hostname: "foobar.example.com",
                Interfaces: &Interfaces{
                        &Interface{
                                Name:   "eth0",
                                IPAddr: "192.168.0.2",
                        },
                        &Interface{
                                Name:   "eth1",
                                IPAddr: "192.168.0.3",
                        },
                },
        }
        var err error
        db, err = storm.Open("test.db", storm.BoltOptions(0600, &bolt.Options{Timeout: 3 * time.Second}))
        if err != nil {
                log.Fatalln("failed to open database:", err)
        }
        defer db.Close()

        if err := db.Save(&host); err != nil {
                log.Fatalln("save to db:", err)
        }

        hosts := Hosts{}
        if err := db.Select(q.Eq("Interfaces.Name", "eth0")).Find(&hosts); err != nil {
                log.Fatalln("query db:", err)
        }

        for _, h := range hosts {
                fmt.Printf("%+v\n", h)
        }
}

This will currently generate "unknown field" since it doesn't support nested structs.

ake-persson avatar Jun 14 '17 21:06 ake-persson

This would be very useful indeed. For that we need to rework how the field names are stored in the matcher. Currently there are simply stored as string then compared against the field with the same name. With nested struct it will be way more difficult (but not impossible) but i think it's worth it.

asdine avatar Jun 14 '17 21:06 asdine

I have trying use PathString (like "FieldA.FieldB#1.FieldC")


func (f *Field) PathOk(path string) (*Field, bool) {
	value := &f.value
	// value must be settable so we need to make sure it holds the address of the
	// variable and not a copy, so we can pass the pointer to strctVal instead of a
	// copy (which is not assigned to any variable, hence not settable).
	// see "https://blog.golang.org/laws-of-reflection#TOC_8."
	if f.value.Kind() != reflect.Ptr {
		a := f.value.Addr()
		value = &a
	}
	//v := strctVal(value.Interface())
	//t := v.Type()
	paths := parsePath(path)
	var f1 *Field
	f1 = f
	f1.value = *value
	for i := range paths {
		p := strings.Split(paths[i], "#")
		if len(p) == 0 {
			return nil, false
		}
		var ok bool

		if f1, ok = f1.FieldOk(p[0]); !ok {
			return nil, false
		}

		if len(p) > 1 {
			for k := 1; k < len(p); k++ {
				if idx, er := strconv.Atoi(p[k]); er == nil {
					switch f.Kind() {
					case reflect.Slice, reflect.Array, reflect.String:
						f1 = &Field{
							field:      f1.field,
							value:      f1.value.Index(idx),
							defaultTag: f1.defaultTag,
						}
					case reflect.Ptr:
						f1 = &Field{
							field:      f1.field,
							value:      reflect.Indirect(f1.value).Index(idx),
							defaultTag: f1.defaultTag,
						}
					default:
						return nil, false
					}
				}
			}
		}
	}
	return f1, true
}

this Field is from github.com/fatih/structs maybe this will help

ZenLiuCN avatar Jun 15 '17 01:06 ZenLiuCN

You just need a recursive function that can traverse a nested struct using reflection. This is one example: https://github.com/mickep76/flatten/blob/master/flatten.go

ake-persson avatar Jun 15 '17 01:06 ake-persson

Is there any progress about this issue?

navono avatar Sep 30 '19 08:09 navono

No progress.

ake-persson avatar Oct 01 '19 06:10 ake-persson

Hey @mickep76 @navono,

Sorry this issue requires a major change in the architecture in Storm and I don't have time to handle this at the moment. I am working on another database called Genji that will support nested fields and advanced SQL queries on top of them and I'm thinking of having Storm v3 using it as an engine. But it still requires time.

Let's keep this issue open for now and see how it goes

asdine avatar Oct 01 '19 08:10 asdine