aerospike / aerospike-client-go

Aerospike Client Go
Apache License 2.0
436 stars 199 forks source link

Struct? #84

Closed clanstyles closed 8 years ago

clanstyles commented 9 years ago

Can you take a struct, have it save into columns based on annotations as:"test" and then query over them? I haven't seen any examples. I've only seen the EncodeBlob interface.

khaf commented 9 years ago

Yes. Please have a look at GetObject and PutObject methods in Client. You can find a few examples in Object Tests.

clanstyles commented 9 years ago

I've tried using PutObject from Client.

I error out with "panic: Value type 'Page' not supported" - Page is a struct.

khaf commented 9 years ago

Please share a code snippet so that I can help.

clanstyles commented 9 years ago

I was able to figure out the panic, it was because of how I registered the object.

How are keys supposed to be managed? Should I attach a key as the "Id" to the record? Should I just assume my own string then create the key each time with NewKey before referencing it?

Thank you

khaf commented 9 years ago

You need to create a Key yourself and pass it to the Get/PutObject methods.

Keys can get quite complex if they are composite, and unfortunately there's no easy or universal way of doing it automatically that supports all use cases.

clanstyles commented 9 years ago

Hi khaf, I'd like to follow up asking if my example is correct.

type aeroRepo struct {
    client *aerospike.Client
    qp     *aerospike.QueryPolicy
}

func NewAeroRepo(c *aerospike.Client) aeroRepo {
    return aeroRepo{
        client: c,
        qp:     aerospike.NewQueryPolicy(),
    }
}

func (c aeroRepo) Get(addr string) (ipAddr *IPAddress, err error) {
    var key *aerospike.Key
    key, err = aerospike.NewKey(namespace, set, addr)
    if err != nil {
        return
    }

    err = c.client.GetObject(nil, key, ipAddr)
    return
}

func (c aeroRepo) Save(ipAddr *IPAddress) (err error) {
    var key *aerospike.Key
    key, err = aerospike.NewKey(namespace, set, ipAddr.IPAddress)
    if err != nil {
        return
    }

    err = c.client.PutObject(nil, key, ipAddr)
    return
}

type IPAddress struct {
    IPAddress  string    `as:"ipaddress"`
    ReverseDNS string    `as:"rdns"`
    Service    int       `as:"service"`
    Created    time.Time `as:"created"`
}

I'm having an issue with this example. PutObject seems to work fine, looking at the data with aql looks fine, the GetObject is crashing.

panic: reflect: call of reflect.Value.Type on zero Value

goroutine 21 [running]:
reflect.Value.Type(0x0, 0x0, 0x0, 0x0, 0x0)
    /usr/lib/go/src/reflect/value.go:1664 +0x7b
gopkg.in/aerospike/aerospike-client-go%2ev1.(*SyncMap).mappingExists(0x951f20, 0x0, 0x0, 0x0, 0x0)
    /home/styles/Code/golang/src/gopkg.in/aerospike/aerospike-client-go.v1/marshal.go:172 +0x3f
gopkg.in/aerospike/aerospike-client-go%2ev1.cacheObjectTags(0x0, 0x0, 0x0)
    /home/styles/Code/golang/src/gopkg.in/aerospike/aerospike-client-go.v1/marshal.go:199 +0x68
gopkg.in/aerospike/aerospike-client-go%2ev1.(*readCommand).parseObject(0xc8200146e0, 0x4, 0x0, 0x1, 0x278bbe, 0x0, 0x0)
    /home/styles/Code/golang/src/gopkg.in/aerospike/aerospike-client-go.v1/read_command.go:194 +0x214
khaf commented 9 years ago

This looks like a bug. I'll fix it in the next release during this week.

clanstyles commented 9 years ago

Thank you, is there any work around I can do right now?

I also noticed type Service int if I use Service (which is actually an int), that isn't supported. Any chance that would be?

khaf commented 9 years ago

This bug is likely the time.Time that is giving you the error, since the field doesn't exist in the database. The type aliases are supported, as tested in the client_object_test.go

clanstyles commented 9 years ago

I removed the time and the service, same error. I only have two strings in a structure.

clanstyles commented 9 years ago

+-------+---------------+-------+-----------+ | quota | bin | count | namespace | +-------+---------------+-------+-----------+ | 32768 | "IPAddress" | 5 | "xxxx" | | 32768 | "service" | 5 | "xxxx" | | 32768 | "Service" | 5 | "xxxx" | | 32768 | "ipaddress" | 5 | "xxxx" | | 32768 | "reverse_dns" | 5 | "xxxx" | +-------+---------------+-------+-----------+

This is what's generated, I think that's an error and might be the cause? It's not saving it right into bins. I tried it with the annotations and without. I've been deleting the dat file to retest, same thing.

khaf commented 9 years ago

Can you test this with an memory-only database? Restarting the asd will wipe it clean, so you will make sure there is no residual data in the DB.

EDIT: Are you passing the ipAddr in err = c.client.PutObject(nil, key, ipAddr) as nil?

clanstyles commented 9 years ago

Yeah I shutdown the db and removed the dat file then restarted and saw no data in the namespace.

I'll make another test but I'm pretty sure that ip is never nil.

I'll test with in memory and ill see if u can create a simple test to reproduce it.

clanstyles commented 9 years ago
package main

import (
    "time"

    "github.com/aerospike/aerospike-client-go"
)

const (
    namespace = "test"
    set       = "ips"
)

type aeroRepo struct {
    client *aerospike.Client
    qp     *aerospike.QueryPolicy
}

func NewAeroRepo(c *aerospike.Client) aeroRepo {
    return aeroRepo{
        client: c,
        qp:     aerospike.NewQueryPolicy(),
    }
}

func (c aeroRepo) Get(addr string) (ipAddr *IPAddress, err error) {
    var key *aerospike.Key
    key, err = aerospike.NewKey(namespace, set, addr)
    if err != nil {
        return
    }

    err = c.client.GetObject(nil, key, ipAddr)
    return
}

func (c aeroRepo) Save(ipAddr *IPAddress) (err error) {
    var key *aerospike.Key
    key, err = aerospike.NewKey(namespace, set, ipAddr.IPAddress)
    if err != nil {
        return
    }

    err = c.client.PutObject(nil, key, ipAddr)
    return
}

type IPAddress struct {
    IPAddress  string    `as:"ipaddress"`
    ReverseDNS string    `as:"rdns"`
    Service    int       `as:"service"`
    Created    time.Time `as:"created"`
}

func main() {
    client, err := aerospike.NewClient("127.0.0.1", 3000)
    if err != nil {
        panic(err)
    }

    repo := NewAeroRepo(client)

    ip := IPAddress{
        IPAddress:  "192.168.2.1",
        ReverseDNS: "blah.arpa",
        Service:    3,
        Created:    time.Now(),
    }

    if err := repo.Save(&ip); err != nil {
        panic(err)
    }

    repo.Get("192.168.2.1")
}

This will reproduce it. Modify the namespace const and the connection string (default 127.0.0.1)

khaf commented 9 years ago

Thanks for the snippet. As I suspected, you're not allocating the *ipAddr. Change your Get method to this:

func (c aeroRepo) Get(addr string) (ipAddr *IPAddress, err error) {
    var key *aerospike.Key
    key, err = aerospike.NewKey(namespace, set, addr)
    if err != nil {
        return
    }

   // the following line is what makes the difference
   ipAddr = &IPAddress{}

   err = c.client.GetObject(nil, key, ipAddr)
    return
}```
clanstyles commented 9 years ago

Weird, I've used that pattern before. ipAddr was supposed to be instantiated by the language. I'll do this as a work around though. Thank you!

khaf commented 9 years ago

As an improvement, I'll do it inside the library to make it easier.