From eb39e5d01d5ae24d9566f6016b682ed224c7255d Mon Sep 17 00:00:00 2001 From: gunnaraasen Date: Thu, 22 Sep 2016 16:22:41 -0700 Subject: [PATCH 1/4] Implement ExplorationStore with boltDB and protobuf --- store/bolt/client.go | 57 ++++++++++++ store/bolt/client_test.go | 45 +++++++++ store/bolt/errors.go | 7 ++ store/bolt/exploration_store.go | 131 +++++++++++++++++++++++++++ store/bolt/exploration_store_test.go | 125 +++++++++++++++++++++++++ store/bolt/internal/internal.go | 39 ++++++++ store/bolt/internal/internal.pb.go | 64 +++++++++++++ store/bolt/internal/internal.proto | 11 +++ store/bolt/internal/internal_test.go | 31 +++++++ store/bolt/session.go | 26 ++++++ store/bolt/util.go | 12 +++ stores.go | 16 +++- 12 files changed, 559 insertions(+), 5 deletions(-) create mode 100644 store/bolt/client.go create mode 100644 store/bolt/client_test.go create mode 100644 store/bolt/errors.go create mode 100644 store/bolt/exploration_store.go create mode 100644 store/bolt/exploration_store_test.go create mode 100644 store/bolt/internal/internal.go create mode 100644 store/bolt/internal/internal.pb.go create mode 100644 store/bolt/internal/internal.proto create mode 100644 store/bolt/internal/internal_test.go create mode 100644 store/bolt/session.go create mode 100644 store/bolt/util.go diff --git a/store/bolt/client.go b/store/bolt/client.go new file mode 100644 index 0000000000..c6ec33093b --- /dev/null +++ b/store/bolt/client.go @@ -0,0 +1,57 @@ +package bolt + +import ( + "time" + + "github.com/boltdb/bolt" +) + +// Client is a client for the boltDB data store. +type Client struct { + Path string + db *bolt.DB + Now func() time.Time +} + +func NewClient() *Client { + return &Client{ + Now: time.Now, + } +} + +// Open and initializ boltDB. Initial buckets are created if they do not exist. +func (c *Client) Open() error { + // Open database file. + db, err := bolt.Open(c.Path, 0666, &bolt.Options{Timeout: 1 * time.Second}) + if err != nil { + return err + } + c.db = db + + tx, err := c.db.Begin(true) + if err != nil { + return err + } + defer tx.Rollback() + + // Always create explorations bucket. + if _, err := tx.CreateBucketIfNotExists([]byte("Explorations")); err != nil { + return err + } + + return tx.Commit() +} + +func (c *Client) Close() error { + if c.db != nil { + return c.db.Close() + } + return nil +} + +// Connect creates a new session for boltDB. +func (c *Client) Connect() *Session { + s := newSession(c.db) + s.now = c.Now() + return s +} diff --git a/store/bolt/client_test.go b/store/bolt/client_test.go new file mode 100644 index 0000000000..41a37ae752 --- /dev/null +++ b/store/bolt/client_test.go @@ -0,0 +1,45 @@ +package bolt_test + +import ( + "io/ioutil" + "os" + "time" + + "github.com/influxdata/mrfusion/store/bolt" +) + +// Mock specific time for testing. +var Now = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) + +type Client struct { + *bolt.Client +} + +func NewClient() *Client { + f, err := ioutil.TempFile("", "mrfusion-bolt-") + if err != nil { + panic(err) + } + f.Close() + + c := &Client{ + Client: bolt.NewClient(), + } + c.Path = f.Name() + c.Now = func() time.Time { return Now } + + return c +} + +func MustOpenClient() *Client { + c := NewClient() + if err := c.Open(); err != nil { + panic(err) + } + return c +} + +func (c *Client) Close() error { + defer os.Remove(c.Path) + return c.Client.Close() +} diff --git a/store/bolt/errors.go b/store/bolt/errors.go new file mode 100644 index 0000000000..1ca48203f9 --- /dev/null +++ b/store/bolt/errors.go @@ -0,0 +1,7 @@ +package bolt + +import "errors" + +var ( + ErrExplorationNotFound = errors.New("exploration not found") +) diff --git a/store/bolt/exploration_store.go b/store/bolt/exploration_store.go new file mode 100644 index 0000000000..e1cc63cdee --- /dev/null +++ b/store/bolt/exploration_store.go @@ -0,0 +1,131 @@ +package bolt + +import ( + "github.com/influxdata/mrfusion" + "github.com/influxdata/mrfusion/store/bolt/internal" + "golang.org/x/net/context" +) + +// Ensure ExplorationStore implements mrfusion.ExplorationStore. +var _ mrfusion.ExplorationStore = &ExplorationStore{} + +type ExplorationStore struct { + session *Session +} + +// Search the ExplorationStore for all explorations owned by userID. +func (s *ExplorationStore) Query(ctx context.Context, uid mrfusion.UserID) ([]mrfusion.Exploration, error) { + // Begin read transaction. + tx, err := s.session.db.Begin(false) + if err != nil { + return nil, err + } + defer tx.Rollback() + + var explorations []mrfusion.Exploration + if err := tx.Bucket([]byte("Explorations")).ForEach(func(k, v []byte) error { + var e mrfusion.Exploration + if err := internal.UnmarshalExploration(v, &e); err != nil { + return err + } else if e.UserID != uid { + return nil + } + explorations = append(explorations, e) + return nil + }); err != nil { + return nil, err + } + + return explorations, nil +} + +// Create a new Exploration in the ExplorationStore. +func (s *ExplorationStore) Add(ctx context.Context, e mrfusion.Exploration) error { + // Begin read-write transaction. + tx, err := s.session.db.Begin(true) + if err != nil { + return err + } + defer tx.Rollback() + + b := tx.Bucket([]byte("Explorations")) + seq, _ := b.NextSequence() + e.ID = mrfusion.ExplorationID(seq) + e.CreatedAt = s.session.now + + if v, err := internal.MarshalExploration(&e); err != nil { + return err + } else if err := b.Put(itob(int(e.ID)), v); err != nil { + return err + } + + return tx.Commit() +} + +// Delete the exploration from the ExplorationStore +func (s *ExplorationStore) Delete(ctx context.Context, e mrfusion.Exploration) error { + // Begin read transaction. + tx, err := s.session.db.Begin(false) + if err != nil { + return err + } + defer tx.Rollback() + + if err := tx.Bucket([]byte("Explorations")).Delete(itob(int(e.ID))); err != nil { + return err + } + + return nil +} + +// Retrieve an exploration for an id exists. +func (s *ExplorationStore) Get(ctx context.Context, id mrfusion.ExplorationID) (mrfusion.Exploration, error) { + var e mrfusion.Exploration + + // Begin read transaction. + tx, err := s.session.db.Begin(false) + if err != nil { + return e, err + } + defer tx.Rollback() + + if v := tx.Bucket([]byte("Explorations")).Get(itob(int(id))); v == nil { + return e, nil + } else if err := internal.UnmarshalExploration(v, &e); err != nil { + return e, err + } + + return e, nil +} + +// Update the exploration with the exploration `ne`; will update `UpdatedAt`. +func (s *ExplorationStore) Update(ctx context.Context, ne mrfusion.Exploration) error { + // Begin read-write transaction. + tx, err := s.session.db.Begin(true) + if err != nil { + return err + } + defer tx.Rollback() + + b := tx.Bucket([]byte("Explorations")) + + var e mrfusion.Exploration + if v := b.Get(itob(int(ne.ID))); v == nil { + return ErrExplorationNotFound + } else if err := internal.UnmarshalExploration(v, &e); err != nil { + return err + } + + e.Name = ne.Name + e.UserID = ne.UserID + e.Data = ne.Data + e.UpdatedAt = s.session.now + + if v, err := internal.MarshalExploration(&e); err != nil { + return err + } else if err := b.Put(itob(int(e.ID)), v); err != nil { + return err + } + + return tx.Commit() +} diff --git a/store/bolt/exploration_store_test.go b/store/bolt/exploration_store_test.go new file mode 100644 index 0000000000..5fd02392ad --- /dev/null +++ b/store/bolt/exploration_store_test.go @@ -0,0 +1,125 @@ +package bolt_test + +import ( + "testing" + + "github.com/influxdata/mrfusion" +) + +// Ensure Exploration can be added and retrieved. +func TestExplorationStore_Add(t *testing.T) { + c := MustOpenClient() + defer c.Close() + s := c.Connect().ExplorationStore() + + data := "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}" + + exploration := mrfusion.Exploration{ + Name: "Ferdinand Magellan", + UserID: 2, + Data: data, + } + + // Add new exploration. + if err := s.Add(nil, exploration); err != nil { + t.Fatal(err) + } else if exploration.ID != 0 { + t.Fatalf("exploration ID error: got %v, expected %v", exploration.ID, 1) + } + + // Confirm exploration in the store is the same as the original. + e, err := s.Get(nil, 1) + if err != nil { + t.Fatal(err) + } else if e.Name != exploration.Name { + t.Fatalf("exploration Name error: got %v, expected %v", e.Name, exploration.Name) + } else if e.UserID != exploration.UserID { + t.Fatalf("exploration UserID error: got %v, expected %v", e.UserID, exploration.UserID) + } else if e.Data != exploration.Data { + t.Fatalf("exploration Data error: got %v, expected %v", e.Data, exploration.Data) + } +} + +// Ensure Explorations can be queried by UserID. +func TestExplorationStore_Query(t *testing.T) { + c := MustOpenClient() + defer c.Close() + s := c.Connect().ExplorationStore() + + explorations := make(map[int]mrfusion.Exploration) + explorations[0] = mrfusion.Exploration{ + Name: "Ferdinand Magellan", + UserID: 2, + Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}", + } + explorations[1] = mrfusion.Exploration{ + Name: "Marco Polo", + UserID: 3, + Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}", + } + explorations[2] = mrfusion.Exploration{ + Name: "Leif Ericson", + UserID: 3, + Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}", + } + + for i := range explorations { + if err := s.Add(nil, explorations[i]); err != nil { + t.Fatal(err) + } + } + + // Query for explorations. + if explorations, err := s.Query(nil, 3); err != nil { + t.Fatal(err) + } else if len(explorations) != 2 { + t.Fatalf("exploration query length error: got %v, expected %v", len(explorations), 2) + } else if explorations[0].Name != "Marco Polo" { + t.Fatalf("exploration query error: got %v, expected %v", explorations[0].Name, "Marco Polo") + } else if explorations[1].Name != "Leif Ericson" { + t.Fatalf("exploration query error: got %v, expected %v", explorations[1].Name, "Leif Ericson") + } + +} + +// Ensure an exploration can be deleted. +func TestExplorationStore_Delete(t *testing.T) { + // TODO: Make sure deleting an exploration works. + t.Skip() +} + +// Ensure explorations can be updated. +func TestExplorationStore_Update(t *testing.T) { + c := MustOpenClient() + defer c.Close() + s := c.Connect().ExplorationStore() + + if err := s.Add(nil, mrfusion.Exploration{Name: "Ferdinand Magellan"}); err != nil { + t.Fatal(err) + } + if err := s.Add(nil, mrfusion.Exploration{UserID: 3}); err != nil { + t.Fatal(err) + } + + // Update explorations. + if err := s.Update(nil, mrfusion.Exploration{ID: 1, Name: "Francis Drake"}); err != nil { + t.Fatal(err) + } + if err := s.Update(nil, mrfusion.Exploration{ID: 2, UserID: 4}); err != nil { + t.Fatal(err) + } + + // Confirm first Exploration update updated Name. + if e, err := s.Get(nil, 1); err != nil { + t.Fatal(err) + } else if e.Name != "Francis Drake" { + t.Fatalf("exploration 1 update error: got %v, expected %v", e.Name, "Francis Drake") + } + + // Confirm second Exploration has updated UserID. + if e, err := s.Get(nil, 2); err != nil { + t.Fatal(err) + } else if e.UserID != 4 { + t.Fatalf("exploration 2 update error: got: %v, expected: %v", e.UserID, 4) + } +} diff --git a/store/bolt/internal/internal.go b/store/bolt/internal/internal.go new file mode 100644 index 0000000000..a7304c5975 --- /dev/null +++ b/store/bolt/internal/internal.go @@ -0,0 +1,39 @@ +package internal + +import ( + "time" + + "github.com/gogo/protobuf/proto" + "github.com/influxdata/mrfusion" +) + +//go:generate protoc --gogo_out=. internal.proto + +// MarshalExploration encodes an exploration to binary protobuf format. +func MarshalExploration(e *mrfusion.Exploration) ([]byte, error) { + return proto.Marshal(&Exploration{ + ID: int64(e.ID), + Name: e.Name, + UserID: int64(e.UserID), + Data: e.Data, + CreatedAt: e.CreatedAt.UnixNano(), + UpdatedAt: e.UpdatedAt.UnixNano(), + }) +} + +// UnmarshalExploration decodes an exploration from binary protobuf data. +func UnmarshalExploration(data []byte, e *mrfusion.Exploration) error { + var pb Exploration + if err := proto.Unmarshal(data, &pb); err != nil { + return err + } + + e.ID = mrfusion.ExplorationID(pb.ID) + e.Name = pb.Name + e.UserID = mrfusion.UserID(pb.UserID) + e.Data = pb.Data + e.CreatedAt = time.Unix(0, pb.CreatedAt).UTC() + e.UpdatedAt = time.Unix(0, pb.UpdatedAt).UTC() + + return nil +} diff --git a/store/bolt/internal/internal.pb.go b/store/bolt/internal/internal.pb.go new file mode 100644 index 0000000000..0ac31de6e9 --- /dev/null +++ b/store/bolt/internal/internal.pb.go @@ -0,0 +1,64 @@ +// Code generated by protoc-gen-gogo. +// source: internal.proto +// DO NOT EDIT! + +/* +Package internal is a generated protocol buffer package. + +It is generated from these files: + internal.proto + +It has these top-level messages: + Exploration +*/ +package internal + +import proto "github.com/gogo/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package + +type Exploration struct { + ID int64 `protobuf:"varint,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"` + Name string `protobuf:"bytes,2,opt,name=Name,json=name,proto3" json:"Name,omitempty"` + UserID int64 `protobuf:"varint,3,opt,name=UserID,json=userID,proto3" json:"UserID,omitempty"` + Data string `protobuf:"bytes,4,opt,name=Data,json=data,proto3" json:"Data,omitempty"` + CreatedAt int64 `protobuf:"varint,5,opt,name=CreatedAt,json=createdAt,proto3" json:"CreatedAt,omitempty"` + UpdatedAt int64 `protobuf:"varint,6,opt,name=UpdatedAt,json=updatedAt,proto3" json:"UpdatedAt,omitempty"` +} + +func (m *Exploration) Reset() { *m = Exploration{} } +func (m *Exploration) String() string { return proto.CompactTextString(m) } +func (*Exploration) ProtoMessage() {} +func (*Exploration) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{0} } + +func init() { + proto.RegisterType((*Exploration)(nil), "internal.Exploration") +} + +func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } + +var fileDescriptorInternal = []byte{ + // 166 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xcb, 0xcc, 0x2b, 0x49, + 0x2d, 0xca, 0x4b, 0xcc, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x80, 0xf1, 0x95, 0x66, + 0x32, 0x72, 0x71, 0xbb, 0x56, 0x14, 0xe4, 0xe4, 0x17, 0x25, 0x96, 0x64, 0xe6, 0xe7, 0x09, 0xf1, + 0x71, 0x31, 0x79, 0xba, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x30, 0x07, 0x31, 0x65, 0xba, 0x08, 0x09, + 0x71, 0xb1, 0xf8, 0x25, 0xe6, 0xa6, 0x4a, 0x30, 0x29, 0x30, 0x6a, 0x70, 0x06, 0xb1, 0xe4, 0x25, + 0xe6, 0xa6, 0x0a, 0x89, 0x71, 0xb1, 0x85, 0x16, 0xa7, 0x16, 0x79, 0xba, 0x48, 0x30, 0x83, 0xd5, + 0xb1, 0x95, 0x82, 0x79, 0x20, 0xb5, 0x2e, 0x89, 0x25, 0x89, 0x12, 0x2c, 0x10, 0xb5, 0x29, 0x89, + 0x25, 0x89, 0x42, 0x32, 0x5c, 0x9c, 0xce, 0x45, 0xa9, 0x89, 0x25, 0xa9, 0x29, 0x8e, 0x25, 0x12, + 0xac, 0x60, 0xe5, 0x9c, 0xc9, 0x30, 0x01, 0x90, 0x6c, 0x68, 0x41, 0x0a, 0x54, 0x96, 0x0d, 0x22, + 0x5b, 0x0a, 0x13, 0x48, 0x62, 0x03, 0x3b, 0xd6, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x64, 0xd7, + 0x3d, 0xfe, 0xbe, 0x00, 0x00, 0x00, +} diff --git a/store/bolt/internal/internal.proto b/store/bolt/internal/internal.proto new file mode 100644 index 0000000000..c49c8222eb --- /dev/null +++ b/store/bolt/internal/internal.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package internal; + +message Exploration { + int64 ID = 1; + string Name = 2; + int64 UserID = 3; + string Data = 4; + int64 CreatedAt = 5; + int64 UpdatedAt = 6; +} diff --git a/store/bolt/internal/internal_test.go b/store/bolt/internal/internal_test.go new file mode 100644 index 0000000000..297684f95c --- /dev/null +++ b/store/bolt/internal/internal_test.go @@ -0,0 +1,31 @@ +package internal_test + +import ( + "reflect" + "testing" + "time" + + "github.com/influxdata/mrfusion" + "github.com/influxdata/mrfusion/store/bolt/internal" +) + +// Ensure an exploration can be marshaled and unmarshaled. +func TestMarshalExploration(t *testing.T) { + v := mrfusion.Exploration{ + ID: 12, + Name: "Some Exploration", + UserID: 34, + Data: "{\"data\":\"something\"}", + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), + } + + var other mrfusion.Exploration + if buf, err := internal.MarshalExploration(&v); err != nil { + t.Fatal(err) + } else if err := internal.UnmarshalExploration(buf, &other); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(v, other) { + t.Fatalf("unexpected copy: %#v", other) + } +} diff --git a/store/bolt/session.go b/store/bolt/session.go new file mode 100644 index 0000000000..af1bc24cc4 --- /dev/null +++ b/store/bolt/session.go @@ -0,0 +1,26 @@ +package bolt + +import ( + "time" + + "github.com/boltdb/bolt" + "github.com/influxdata/mrfusion" +) + +// Session is a connection to a boltDB database. +// TODO: Hook up authentication here. +type Session struct { + db *bolt.DB + now time.Time + + // Services + explorationStore ExplorationStore +} + +func newSession(db *bolt.DB) *Session { + s := &Session{db: db} + s.explorationStore.session = s + return s +} + +func (s *Session) ExplorationStore() mrfusion.ExplorationStore { return &s.explorationStore } diff --git a/store/bolt/util.go b/store/bolt/util.go new file mode 100644 index 0000000000..0ee028cad6 --- /dev/null +++ b/store/bolt/util.go @@ -0,0 +1,12 @@ +package bolt + +import ( + "encoding/binary" +) + +// itob returns an 8-byte big endian representation of v. +func itob(v int) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(v)) + return b +} diff --git a/stores.go b/stores.go index 9b2fb310a1..f8700c4290 100644 --- a/stores.go +++ b/stores.go @@ -57,26 +57,32 @@ type AuthStore struct { } } +// UserID is a unique ID for a source user. +type UserID int + +// ExplorationID is a unique ID for an exploration. +type ExplorationID int + // Exploration is a serialization of front-end Data Explorer. type Exploration struct { - ID int + ID ExplorationID Name string // User provided name of the exploration. - UserID int // UserID is the owner of this exploration. + UserID UserID // UserID is the owner of this exploration. Data string // Opaque blob of JSON data CreatedAt time.Time // Time the exploration was first created UpdatedAt time.Time // Latest time the exploration was updated. } -// ExplorationStore stores front-end serializations of explorater sessions +// ExplorationStore stores front-end serializations of data explorer sessions. type ExplorationStore interface { // Search the ExplorationStore for all explorations owned by userID. - Query(ctx context.Context, userID int) ([]Exploration, error) + Query(ctx context.Context, userID UserID) ([]Exploration, error) // Create a new Exploration in the ExplorationStore Add(context.Context, Exploration) error // Delete the exploration from the ExplorationStore Delete(context.Context, Exploration) error // Retrieve an exploration if `ID` exists. - Get(ctx context.Context, ID int) (Exploration, error) + Get(ctx context.Context, ID ExplorationID) (Exploration, error) // Update the exploration; will update `UpdatedAt`. Update(context.Context, Exploration) error } From 5c4a60208381dd5f01848d141fbd1ccac8f0eaef Mon Sep 17 00:00:00 2001 From: gunnaraasen Date: Wed, 28 Sep 2016 10:57:15 -0400 Subject: [PATCH 2/4] Fix ExplorationID and UserID in mock --- mock/handlers.go | 14 +++++++------- mock/mock.go | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/mock/handlers.go b/mock/handlers.go index e7902be523..0ae7d9a9b7 100644 --- a/mock/handlers.go +++ b/mock/handlers.go @@ -103,7 +103,7 @@ func (m *Handler) Explorations(ctx context.Context, params op.GetSourcesIDUsersU if err != nil { return op.NewGetSourcesIDUsersUserIDExplorationsDefault(500) } - exs, err := m.Store.Query(ctx, id) + exs, err := m.Store.Query(ctx, mrfusion.UserID(id)) if err != nil { return op.NewGetSourcesIDUsersUserIDExplorationsNotFound() } @@ -139,7 +139,7 @@ func (m *Handler) Exploration(ctx context.Context, params op.GetSourcesIDUsersUs return op.NewGetSourcesIDUsersUserIDExplorationsExplorationIDDefault(500).WithPayload(errMsg) } - e, err := m.Store.Get(ctx, eID) + e, err := m.Store.Get(ctx, mrfusion.ExplorationID(eID)) if err != nil { log.Printf("Error unknown exploration id: %d: %v", eID, err) errMsg := &models.Error{Code: 404, Message: "Error unknown exploration id"} @@ -167,14 +167,14 @@ func (m *Handler) UpdateExploration(ctx context.Context, params op.PatchSourcesI return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDDefault(500) } - e, err := m.Store.Get(ctx, eID) + e, err := m.Store.Get(ctx, mrfusion.ExplorationID(eID)) if err != nil { log.Printf("Error unknown exploration id: %d: %v", eID, err) errMsg := &models.Error{Code: 404, Message: "Error unknown exploration id"} return op.NewPatchSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg) } if params.Exploration != nil { - e.ID = eID + e.ID = mrfusion.ExplorationID(eID) e.Data = params.Exploration.Data.(string) e.Name = params.Exploration.Name m.Store.Update(ctx, e) @@ -188,7 +188,7 @@ func (m *Handler) NewExploration(ctx context.Context, params op.PostSourcesIDUse return op.NewPostSourcesIDUsersUserIDExplorationsDefault(500) } - exs, err := m.Store.Query(ctx, id) + exs, err := m.Store.Query(ctx, mrfusion.UserID(id)) if err != nil { log.Printf("Error unknown user id: %d: %v", id, err) errMsg := &models.Error{Code: 404, Message: "Error unknown user id"} @@ -200,7 +200,7 @@ func (m *Handler) NewExploration(ctx context.Context, params op.PostSourcesIDUse e := mrfusion.Exploration{ Data: params.Exploration.Data.(string), Name: params.Exploration.Name, - ID: eID, + ID: mrfusion.ExplorationID(eID), } m.Store.Add(ctx, e) } @@ -225,7 +225,7 @@ func (m *Handler) DeleteExploration(ctx context.Context, params op.DeleteSources return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDDefault(500) } - if err := m.Store.Delete(ctx, mrfusion.Exploration{ID: ID}); err != nil { + if err := m.Store.Delete(ctx, mrfusion.Exploration{ID: mrfusion.ExplorationID(ID)}); err != nil { log.Printf("Error unknown explorations id: %d: %v", ID, err) errMsg := &models.Error{Code: 404, Message: "Error unknown user id"} return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg) diff --git a/mock/mock.go b/mock/mock.go index 27a7f1c0e9..9208d7fff3 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -36,7 +36,7 @@ func NewExplorationStore(nowFunc func() time.Time) mrfusion.ExplorationStore { var DefaultExplorationStore mrfusion.ExplorationStore = NewExplorationStore(time.Now) -func (m *ExplorationStore) Query(ctx context.Context, userID int) ([]mrfusion.Exploration, error) { +func (m *ExplorationStore) Query(ctx context.Context, userID mrfusion.UserID) ([]mrfusion.Exploration, error) { res := []mrfusion.Exploration{} for _, v := range m.db { res = append(res, v) @@ -52,12 +52,12 @@ func (m *ExplorationStore) Add(ctx context.Context, e mrfusion.Exploration) erro } func (m *ExplorationStore) Delete(ctx context.Context, e mrfusion.Exploration) error { - delete(m.db, e.ID) + delete(m.db, int(e.ID)) return nil } -func (m *ExplorationStore) Get(ctx context.Context, ID int) (mrfusion.Exploration, error) { - e, ok := m.db[ID] +func (m *ExplorationStore) Get(ctx context.Context, ID mrfusion.ExplorationID) (mrfusion.Exploration, error) { + e, ok := m.db[int(ID)] if !ok { return mrfusion.Exploration{}, fmt.Errorf("Unknown ID %d", ID) } @@ -65,12 +65,12 @@ func (m *ExplorationStore) Get(ctx context.Context, ID int) (mrfusion.Exploratio } func (m *ExplorationStore) Update(ctx context.Context, e mrfusion.Exploration) error { - _, ok := m.db[e.ID] + _, ok := m.db[int(e.ID)] if !ok { return fmt.Errorf("Unknown ID %d", e.ID) } e.UpdatedAt = m.NowFunc() - m.db[e.ID] = e + m.db[int(e.ID)] = e return nil } From e45dbdc6308bc21779faa3e7570d595c8cab26f6 Mon Sep 17 00:00:00 2001 From: gunnaraasen Date: Wed, 28 Sep 2016 15:32:58 -0400 Subject: [PATCH 3/4] Refactor the exploration store --- Godeps | 2 + Makefile | 3 + bolt/client.go | 53 ++++++ bolt/exploration.go | 126 +++++++++++++ bolt/exploration_test.go | 175 ++++++++++++++++++ {store/bolt => bolt}/internal/internal.go | 2 + {store/bolt => bolt}/internal/internal.pb.go | 35 ++-- bolt/internal/internal.proto | 12 ++ .../bolt => bolt}/internal/internal_test.go | 10 +- {store/bolt => bolt}/util.go | 0 errors.go | 3 +- mock/handlers.go | 4 +- mock/mock.go | 22 +-- store/bolt/client.go | 57 ------ store/bolt/client_test.go | 45 ----- store/bolt/errors.go | 7 - store/bolt/exploration_store.go | 131 ------------- store/bolt/exploration_store_test.go | 125 ------------- store/bolt/internal/internal.proto | 11 -- store/bolt/session.go | 26 --- stores.go | 39 ++-- 21 files changed, 431 insertions(+), 457 deletions(-) create mode 100644 bolt/client.go create mode 100644 bolt/exploration.go create mode 100644 bolt/exploration_test.go rename {store/bolt => bolt}/internal/internal.go (95%) rename {store/bolt => bolt}/internal/internal.pb.go (54%) create mode 100644 bolt/internal/internal.proto rename {store/bolt => bolt}/internal/internal_test.go (65%) rename {store/bolt => bolt}/util.go (100%) delete mode 100644 store/bolt/client.go delete mode 100644 store/bolt/client_test.go delete mode 100644 store/bolt/errors.go delete mode 100644 store/bolt/exploration_store.go delete mode 100644 store/bolt/exploration_store_test.go delete mode 100644 store/bolt/internal/internal.proto delete mode 100644 store/bolt/session.go diff --git a/Godeps b/Godeps index 9dda7fc572..8e91935e04 100644 --- a/Godeps +++ b/Godeps @@ -1,6 +1,7 @@ github.com/PuerkitoBio/purell 8a290539e2e8629dbc4e6bad948158f790ec31f4 github.com/PuerkitoBio/urlesc 5bd2802263f21d8788851d5305584c82a5c75d7e github.com/asaskevich/govalidator 593d64559f7600f29581a3ee42177f5dbded27a9 +github.com/boltdb/bolt 5cc10bbbc5c141029940133bb33c9e969512a698 github.com/elazarl/go-bindata-assetfs 9a6736ed45b44bf3835afeebb3034b57ed329f3e github.com/go-openapi/analysis b44dc874b601d9e4e2f6e19140e794ba24bead3b github.com/go-openapi/errors 4178436c9f2430cdd945c50301cfb61563b56573 @@ -12,6 +13,7 @@ github.com/go-openapi/spec 6aced65f8501fe1217321abf0749d354824ba2ff github.com/go-openapi/strfmt d65c7fdb29eca313476e529628176fe17e58c488 github.com/go-openapi/swag 0e04f5e499b19bf51031c01a00f098f25067d8dc github.com/go-openapi/validate deaf2c9013bc1a7f4c774662259a506ba874d80f +github.com/gogo/protobuf 6abcf94fd4c97dcb423fdafd42fe9f96ca7e421b github.com/gorilla/context 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 github.com/jessevdk/go-flags 4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa github.com/mailru/easyjson e978125a7e335d8f4db746a9ac5b44643f27416b diff --git a/Makefile b/Makefile index 10dd0350e6..673e1ad228 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,9 @@ godep: jsdep: cd ui && npm install +gen: bolt/internal/internal.proto + go generate github.com/influxdata/mrfusion/bolt/internal + test: jstest gotest gotest: diff --git a/bolt/client.go b/bolt/client.go new file mode 100644 index 0000000000..bb94723bf5 --- /dev/null +++ b/bolt/client.go @@ -0,0 +1,53 @@ +package bolt + +import ( + "time" + + "github.com/boltdb/bolt" +) + +// Client is a client for the boltDB data store. +type Client struct { + Path string + db *bolt.DB + Now func() time.Time + + ExplorationStore *ExplorationStore +} + +func NewClient() *Client { + c := &Client{Now: time.Now} + c.ExplorationStore = &ExplorationStore{client: c} + return c +} + +// Open and initialize boltDB. Initial buckets are created if they do not exist. +func (c *Client) Open() error { + // Open database file. + db, err := bolt.Open(c.Path, 0644, &bolt.Options{Timeout: 1 * time.Second}) + if err != nil { + return err + } + c.db = db + + if err := c.db.Update(func(tx *bolt.Tx) error { + // Always create explorations bucket. + if _, err := tx.CreateBucketIfNotExists(ExplorationBucket); err != nil { + return err + } + return nil + }); err != nil { + return err + } + + c.ExplorationStore = &ExplorationStore{client: c} + + return nil +} + +func (c *Client) Close() error { + if c.db != nil { + return c.db.Close() + } + return nil +} diff --git a/bolt/exploration.go b/bolt/exploration.go new file mode 100644 index 0000000000..3e0f04898f --- /dev/null +++ b/bolt/exploration.go @@ -0,0 +1,126 @@ +package bolt + +import ( + "github.com/boltdb/bolt" + "github.com/influxdata/mrfusion" + "github.com/influxdata/mrfusion/bolt/internal" + "golang.org/x/net/context" +) + +// Ensure ExplorationStore implements mrfusion.ExplorationStore. +var _ mrfusion.ExplorationStore = &ExplorationStore{} + +var ExplorationBucket = []byte("Explorations") + +type ExplorationStore struct { + client *Client +} + +// Search the ExplorationStore for all explorations owned by userID. +func (s *ExplorationStore) Query(ctx context.Context, uid mrfusion.UserID) ([]*mrfusion.Exploration, error) { + var explorations []*mrfusion.Exploration + if err := s.client.db.View(func(tx *bolt.Tx) error { + if err := tx.Bucket(ExplorationBucket).ForEach(func(k, v []byte) error { + var e mrfusion.Exploration + if err := internal.UnmarshalExploration(v, &e); err != nil { + return err + } else if e.UserID != uid { + return nil + } + explorations = append(explorations, &e) + return nil + }); err != nil { + return err + } + return nil + }); err != nil { + return nil, err + } + + return explorations, nil +} + +// Create a new Exploration in the ExplorationStore. +func (s *ExplorationStore) Add(ctx context.Context, e *mrfusion.Exploration) error { + if err := s.client.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(ExplorationBucket) + seq, err := b.NextSequence() + if err != nil { + return err + } + e.ID = mrfusion.ExplorationID(seq) + e.CreatedAt = s.client.Now() + + if v, err := internal.MarshalExploration(e); err != nil { + return err + } else if err := b.Put(itob(int(e.ID)), v); err != nil { + return err + } + return nil + }); err != nil { + return err + } + + return nil +} + +// Delete the exploration from the ExplorationStore +func (s *ExplorationStore) Delete(ctx context.Context, e *mrfusion.Exploration) error { + if err := s.client.db.Update(func(tx *bolt.Tx) error { + if err := tx.Bucket(ExplorationBucket).Delete(itob(int(e.ID))); err != nil { + return err + } + return nil + }); err != nil { + return err + } + + return nil +} + +// Retrieve an exploration for an id exists. +func (s *ExplorationStore) Get(ctx context.Context, id mrfusion.ExplorationID) (*mrfusion.Exploration, error) { + var e mrfusion.Exploration + if err := s.client.db.View(func(tx *bolt.Tx) error { + if v := tx.Bucket(ExplorationBucket).Get(itob(int(id))); v == nil { + return mrfusion.ErrExplorationNotFound + } else if err := internal.UnmarshalExploration(v, &e); err != nil { + return err + } + return nil + }); err != nil { + return nil, err + } + + return &e, nil +} + +// Update an exploration; will also update the `UpdatedAt` time. +func (s *ExplorationStore) Update(ctx context.Context, e *mrfusion.Exploration) error { + if err := s.client.db.Update(func(tx *bolt.Tx) error { + // Retreive an existing exploration with the same exploration ID. + var ee mrfusion.Exploration + b := tx.Bucket(ExplorationBucket) + if v := b.Get(itob(int(e.ID))); v == nil { + return mrfusion.ErrExplorationNotFound + } else if err := internal.UnmarshalExploration(v, &ee); err != nil { + return err + } + + ee.Name = e.Name + ee.UserID = e.UserID + ee.Data = e.Data + ee.UpdatedAt = s.client.Now() + + if v, err := internal.MarshalExploration(&ee); err != nil { + return err + } else if err := b.Put(itob(int(ee.ID)), v); err != nil { + return err + } + return nil + }); err != nil { + return err + } + + return nil +} diff --git a/bolt/exploration_test.go b/bolt/exploration_test.go new file mode 100644 index 0000000000..e700902909 --- /dev/null +++ b/bolt/exploration_test.go @@ -0,0 +1,175 @@ +package bolt_test + +import ( + "errors" + // "fmt" + "io/ioutil" + "os" + "testing" + "time" + + "github.com/influxdata/mrfusion" + "github.com/influxdata/mrfusion/bolt" +) + +// TestNow is a set time for testing. +var TestNow = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) + +// TestClient wraps *bolt.Client. +type TestClient struct { + *bolt.Client +} + +// NewTestClient creates new *bolt.Client with a set time and temp path. +func NewTestClient() (*TestClient, error) { + f, err := ioutil.TempFile("", "mrfusion-bolt-") + if err != nil { + return nil, errors.New("unable to open temporary boltdb file") + } + f.Close() + + c := &TestClient{ + Client: bolt.NewClient(), + } + c.Path = f.Name() + c.Now = func() time.Time { return TestNow } + + return c, nil +} + +func (c *TestClient) Close() error { + defer os.Remove(c.Path) + return c.Client.Close() +} + +// Ensure an ExplorationStore can store, retrieve, update, and delete explorations. +func TestExplorationStore_CRUD(t *testing.T) { + c, err := NewTestClient() + if err != nil { + t.Fatal(err) + } + if err := c.Open(); err != nil { + t.Fatal(err) + } + defer c.Close() + s := c.ExplorationStore + + explorations := []*mrfusion.Exploration{ + &mrfusion.Exploration{ + Name: "Ferdinand Magellan", + UserID: 2, + Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}", + }, + &mrfusion.Exploration{ + Name: "Marco Polo", + UserID: 3, + Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}", + }, + &mrfusion.Exploration{ + Name: "Leif Ericson", + UserID: 3, + Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}", + }, + } + + // Add new explorations. + for i := range explorations { + if err := s.Add(nil, explorations[i]); err != nil { + t.Fatal(err) + } + } + + // Confirm first exploration in the store is the same as the original. + if e, err := s.Get(nil, explorations[0].ID); err != nil { + t.Fatal(err) + } else if e.ID != explorations[0].ID { + t.Fatalf("exploration ID error: got %v, expected %v", e.ID, explorations[1].ID) + } else if e.Name != explorations[0].Name { + t.Fatalf("exploration Name error: got %v, expected %v", e.Name, explorations[1].Name) + } else if e.UserID != explorations[0].UserID { + t.Fatalf("exploration UserID error: got %v, expected %v", e.UserID, explorations[1].UserID) + } else if e.Data != explorations[0].Data { + t.Fatalf("exploration Data error: got %v, expected %v", e.Data, explorations[1].Data) + } + + // Update explorations. + explorations[1].Name = "Francis Drake" + explorations[2].UserID = 4 + if err := s.Update(nil, explorations[1]); err != nil { + t.Fatal(err) + } else if err := s.Update(nil, explorations[2]); err != nil { + t.Fatal(err) + } + + // Confirm explorations are updated. + if e, err := s.Get(nil, explorations[1].ID); err != nil { + t.Fatal(err) + } else if e.Name != "Francis Drake" { + t.Fatalf("exploration 1 update error: got %v, expected %v", e.Name, "Francis Drake") + } + if e, err := s.Get(nil, explorations[2].ID); err != nil { + t.Fatal(err) + } else if e.UserID != 4 { + t.Fatalf("exploration 2 update error: got %v, expected %v", e.UserID, 4) + } + + // Delete an exploration. + if err := s.Delete(nil, explorations[2]); err != nil { + t.Fatal(err) + } + + // Confirm exploration has been deleted. + if e, err := s.Get(nil, explorations[2].ID); err != mrfusion.ErrExplorationNotFound { + t.Fatalf("exploration delete error: got %v, expected %v", e, mrfusion.ErrExplorationNotFound) + } +} + +// Ensure Explorations can be queried by UserID. +func TestExplorationStore_Query(t *testing.T) { + c, err := NewTestClient() + if err != nil { + t.Fatal(err) + } + if err := c.Open(); err != nil { + t.Fatal(err) + } + defer c.Close() + s := c.ExplorationStore + + explorations := []*mrfusion.Exploration{ + &mrfusion.Exploration{ + Name: "Ferdinand Magellan", + UserID: 2, + Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}", + }, + &mrfusion.Exploration{ + Name: "Marco Polo", + UserID: 3, + Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}", + }, + &mrfusion.Exploration{ + Name: "Leif Ericson", + UserID: 3, + Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}", + }, + } + + // Add new explorations. + for i := range explorations { + if err := s.Add(nil, explorations[i]); err != nil { + t.Fatal(err) + } + } + + // Query for explorations. + if e, err := s.Query(nil, 3); err != nil { + t.Fatal(err) + } else if len(e) != 2 { + t.Fatalf("exploration query length error: got %v, expected %v", len(explorations), len(e)) + } else if e[0].Name != explorations[1].Name { + t.Fatalf("exploration query error: got %v, expected %v", explorations[0].Name, "Marco Polo") + } else if e[1].Name != explorations[2].Name { + t.Fatalf("exploration query error: got %v, expected %v", explorations[1].Name, "Leif Ericson") + } + +} diff --git a/store/bolt/internal/internal.go b/bolt/internal/internal.go similarity index 95% rename from store/bolt/internal/internal.go rename to bolt/internal/internal.go index a7304c5975..92c07dd046 100644 --- a/store/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -18,6 +18,7 @@ func MarshalExploration(e *mrfusion.Exploration) ([]byte, error) { Data: e.Data, CreatedAt: e.CreatedAt.UnixNano(), UpdatedAt: e.UpdatedAt.UnixNano(), + Default: e.Default, }) } @@ -34,6 +35,7 @@ func UnmarshalExploration(data []byte, e *mrfusion.Exploration) error { e.Data = pb.Data e.CreatedAt = time.Unix(0, pb.CreatedAt).UTC() e.UpdatedAt = time.Unix(0, pb.UpdatedAt).UTC() + e.Default = pb.Default return nil } diff --git a/store/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go similarity index 54% rename from store/bolt/internal/internal.pb.go rename to bolt/internal/internal.pb.go index 0ac31de6e9..3c6a6f9172 100644 --- a/store/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -29,12 +29,13 @@ var _ = math.Inf const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package type Exploration struct { - ID int64 `protobuf:"varint,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,json=name,proto3" json:"Name,omitempty"` - UserID int64 `protobuf:"varint,3,opt,name=UserID,json=userID,proto3" json:"UserID,omitempty"` - Data string `protobuf:"bytes,4,opt,name=Data,json=data,proto3" json:"Data,omitempty"` - CreatedAt int64 `protobuf:"varint,5,opt,name=CreatedAt,json=createdAt,proto3" json:"CreatedAt,omitempty"` - UpdatedAt int64 `protobuf:"varint,6,opt,name=UpdatedAt,json=updatedAt,proto3" json:"UpdatedAt,omitempty"` + ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` + Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` + UserID int64 `protobuf:"varint,3,opt,name=UserID,proto3" json:"UserID,omitempty"` + Data string `protobuf:"bytes,4,opt,name=Data,proto3" json:"Data,omitempty"` + CreatedAt int64 `protobuf:"varint,5,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` + UpdatedAt int64 `protobuf:"varint,6,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"` + Default bool `protobuf:"varint,7,opt,name=Default,proto3" json:"Default,omitempty"` } func (m *Exploration) Reset() { *m = Exploration{} } @@ -49,16 +50,16 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 166 bytes of a gzipped FileDescriptorProto + // 176 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xcb, 0xcc, 0x2b, 0x49, - 0x2d, 0xca, 0x4b, 0xcc, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x80, 0xf1, 0x95, 0x66, - 0x32, 0x72, 0x71, 0xbb, 0x56, 0x14, 0xe4, 0xe4, 0x17, 0x25, 0x96, 0x64, 0xe6, 0xe7, 0x09, 0xf1, - 0x71, 0x31, 0x79, 0xba, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x30, 0x07, 0x31, 0x65, 0xba, 0x08, 0x09, - 0x71, 0xb1, 0xf8, 0x25, 0xe6, 0xa6, 0x4a, 0x30, 0x29, 0x30, 0x6a, 0x70, 0x06, 0xb1, 0xe4, 0x25, - 0xe6, 0xa6, 0x0a, 0x89, 0x71, 0xb1, 0x85, 0x16, 0xa7, 0x16, 0x79, 0xba, 0x48, 0x30, 0x83, 0xd5, - 0xb1, 0x95, 0x82, 0x79, 0x20, 0xb5, 0x2e, 0x89, 0x25, 0x89, 0x12, 0x2c, 0x10, 0xb5, 0x29, 0x89, - 0x25, 0x89, 0x42, 0x32, 0x5c, 0x9c, 0xce, 0x45, 0xa9, 0x89, 0x25, 0xa9, 0x29, 0x8e, 0x25, 0x12, - 0xac, 0x60, 0xe5, 0x9c, 0xc9, 0x30, 0x01, 0x90, 0x6c, 0x68, 0x41, 0x0a, 0x54, 0x96, 0x0d, 0x22, - 0x5b, 0x0a, 0x13, 0x48, 0x62, 0x03, 0x3b, 0xd6, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x64, 0xd7, - 0x3d, 0xfe, 0xbe, 0x00, 0x00, 0x00, + 0x2d, 0xca, 0x4b, 0xcc, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x80, 0xf1, 0x95, 0x36, + 0x33, 0x72, 0x71, 0xbb, 0x56, 0x14, 0xe4, 0xe4, 0x17, 0x25, 0x96, 0x64, 0xe6, 0xe7, 0x09, 0xf1, + 0x71, 0x31, 0x79, 0xba, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x30, 0x07, 0x31, 0x79, 0xba, 0x08, 0x09, + 0x71, 0xb1, 0xf8, 0x25, 0xe6, 0xa6, 0x4a, 0x30, 0x29, 0x30, 0x6a, 0x70, 0x06, 0x81, 0xd9, 0x42, + 0x62, 0x5c, 0x6c, 0xa1, 0xc5, 0xa9, 0x45, 0x9e, 0x2e, 0x12, 0xcc, 0x60, 0x75, 0x50, 0x1e, 0x48, + 0xad, 0x4b, 0x62, 0x49, 0xa2, 0x04, 0x0b, 0x44, 0x2d, 0x88, 0x2d, 0x24, 0xc3, 0xc5, 0xe9, 0x5c, + 0x94, 0x9a, 0x58, 0x92, 0x9a, 0xe2, 0x58, 0x22, 0xc1, 0x0a, 0x56, 0x8e, 0x10, 0x00, 0xc9, 0x86, + 0x16, 0xa4, 0x40, 0x65, 0xd9, 0x20, 0xb2, 0x70, 0x01, 0x21, 0x09, 0x2e, 0x76, 0x97, 0xd4, 0xb4, + 0xc4, 0xd2, 0x9c, 0x12, 0x09, 0x76, 0x05, 0x46, 0x0d, 0x8e, 0x20, 0x18, 0x37, 0x89, 0x0d, 0xec, + 0x0d, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8b, 0x69, 0xb2, 0xe7, 0xd8, 0x00, 0x00, 0x00, } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto new file mode 100644 index 0000000000..e2b7488c9d --- /dev/null +++ b/bolt/internal/internal.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package internal; + +message Exploration { + int64 ID = 1; // ExplorationID is a unique ID for an Exploration. + string Name = 2; // User provided name of the Exploration. + int64 UserID = 3; // UserID is the owner of this Exploration. + string Data = 4; // Opaque blob of JSON data. + int64 CreatedAt = 5; // Time the exploration was first created. + int64 UpdatedAt = 6; // Latest time the exploration was updated. + bool Default = 7; // Flags an exploration as the default. +} diff --git a/store/bolt/internal/internal_test.go b/bolt/internal/internal_test.go similarity index 65% rename from store/bolt/internal/internal_test.go rename to bolt/internal/internal_test.go index 297684f95c..b3210d6594 100644 --- a/store/bolt/internal/internal_test.go +++ b/bolt/internal/internal_test.go @@ -6,7 +6,7 @@ import ( "time" "github.com/influxdata/mrfusion" - "github.com/influxdata/mrfusion/store/bolt/internal" + "github.com/influxdata/mrfusion/bolt/internal" ) // Ensure an exploration can be marshaled and unmarshaled. @@ -20,12 +20,12 @@ func TestMarshalExploration(t *testing.T) { UpdatedAt: time.Now().UTC(), } - var other mrfusion.Exploration + var vv mrfusion.Exploration if buf, err := internal.MarshalExploration(&v); err != nil { t.Fatal(err) - } else if err := internal.UnmarshalExploration(buf, &other); err != nil { + } else if err := internal.UnmarshalExploration(buf, &vv); err != nil { t.Fatal(err) - } else if !reflect.DeepEqual(v, other) { - t.Fatalf("unexpected copy: %#v", other) + } else if !reflect.DeepEqual(v, vv) { + t.Fatalf("exploration protobuf copy error: got %#v, expected %#v", vv, v) } } diff --git a/store/bolt/util.go b/bolt/util.go similarity index 100% rename from store/bolt/util.go rename to bolt/util.go diff --git a/errors.go b/errors.go index 9bfa163ff1..a32a5aa0fa 100644 --- a/errors.go +++ b/errors.go @@ -2,7 +2,8 @@ package mrfusion // General errors. const ( - ErrUpstreamTimeout = Error("request to backend timed out") + ErrUpstreamTimeout = Error("request to backend timed out") + ErrExplorationNotFound = Error("exploration not found") ) // Error is a domain error encountered while processing mrfusion requests diff --git a/mock/handlers.go b/mock/handlers.go index 0ae7d9a9b7..94ffa16dcf 100644 --- a/mock/handlers.go +++ b/mock/handlers.go @@ -202,7 +202,7 @@ func (m *Handler) NewExploration(ctx context.Context, params op.PostSourcesIDUse Name: params.Exploration.Name, ID: mrfusion.ExplorationID(eID), } - m.Store.Add(ctx, e) + m.Store.Add(ctx, &e) } params.Exploration.UpdatedAt = strfmt.DateTime(time.Now()) params.Exploration.CreatedAt = strfmt.DateTime(time.Now()) @@ -225,7 +225,7 @@ func (m *Handler) DeleteExploration(ctx context.Context, params op.DeleteSources return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDDefault(500) } - if err := m.Store.Delete(ctx, mrfusion.Exploration{ID: mrfusion.ExplorationID(ID)}); err != nil { + if err := m.Store.Delete(ctx, &mrfusion.Exploration{ID: mrfusion.ExplorationID(ID)}); err != nil { log.Printf("Error unknown explorations id: %d: %v", ID, err) errMsg := &models.Error{Code: 404, Message: "Error unknown user id"} return op.NewDeleteSourcesIDUsersUserIDExplorationsExplorationIDNotFound().WithPayload(errMsg) diff --git a/mock/mock.go b/mock/mock.go index 9208d7fff3..5a2c852b6d 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -9,22 +9,22 @@ import ( ) type ExplorationStore struct { - db map[int]mrfusion.Exploration + db map[int]*mrfusion.Exploration NowFunc func() time.Time } func NewExplorationStore(nowFunc func() time.Time) mrfusion.ExplorationStore { e := ExplorationStore{ NowFunc: nowFunc, - db: map[int]mrfusion.Exploration{}, + db: map[int]*mrfusion.Exploration{}, } - e.db[0] = mrfusion.Exploration{ + e.db[0] = &mrfusion.Exploration{ Name: "Ferdinand Magellan", Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}", CreatedAt: nowFunc(), UpdatedAt: nowFunc(), } - e.db[1] = mrfusion.Exploration{ + e.db[1] = &mrfusion.Exploration{ Name: "Ferdinand Magellan", Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}", CreatedAt: nowFunc(), @@ -36,35 +36,35 @@ func NewExplorationStore(nowFunc func() time.Time) mrfusion.ExplorationStore { var DefaultExplorationStore mrfusion.ExplorationStore = NewExplorationStore(time.Now) -func (m *ExplorationStore) Query(ctx context.Context, userID mrfusion.UserID) ([]mrfusion.Exploration, error) { - res := []mrfusion.Exploration{} +func (m *ExplorationStore) Query(ctx context.Context, userID mrfusion.UserID) ([]*mrfusion.Exploration, error) { + res := []*mrfusion.Exploration{} for _, v := range m.db { res = append(res, v) } return res, nil } -func (m *ExplorationStore) Add(ctx context.Context, e mrfusion.Exploration) error { +func (m *ExplorationStore) Add(ctx context.Context, e *mrfusion.Exploration) error { e.CreatedAt = m.NowFunc() e.UpdatedAt = m.NowFunc() m.db[len(m.db)] = e return nil } -func (m *ExplorationStore) Delete(ctx context.Context, e mrfusion.Exploration) error { +func (m *ExplorationStore) Delete(ctx context.Context, e *mrfusion.Exploration) error { delete(m.db, int(e.ID)) return nil } -func (m *ExplorationStore) Get(ctx context.Context, ID mrfusion.ExplorationID) (mrfusion.Exploration, error) { +func (m *ExplorationStore) Get(ctx context.Context, ID mrfusion.ExplorationID) (*mrfusion.Exploration, error) { e, ok := m.db[int(ID)] if !ok { - return mrfusion.Exploration{}, fmt.Errorf("Unknown ID %d", ID) + return nil, fmt.Errorf("Unknown ID %d", ID) } return e, nil } -func (m *ExplorationStore) Update(ctx context.Context, e mrfusion.Exploration) error { +func (m *ExplorationStore) Update(ctx context.Context, e *mrfusion.Exploration) error { _, ok := m.db[int(e.ID)] if !ok { return fmt.Errorf("Unknown ID %d", e.ID) diff --git a/store/bolt/client.go b/store/bolt/client.go deleted file mode 100644 index c6ec33093b..0000000000 --- a/store/bolt/client.go +++ /dev/null @@ -1,57 +0,0 @@ -package bolt - -import ( - "time" - - "github.com/boltdb/bolt" -) - -// Client is a client for the boltDB data store. -type Client struct { - Path string - db *bolt.DB - Now func() time.Time -} - -func NewClient() *Client { - return &Client{ - Now: time.Now, - } -} - -// Open and initializ boltDB. Initial buckets are created if they do not exist. -func (c *Client) Open() error { - // Open database file. - db, err := bolt.Open(c.Path, 0666, &bolt.Options{Timeout: 1 * time.Second}) - if err != nil { - return err - } - c.db = db - - tx, err := c.db.Begin(true) - if err != nil { - return err - } - defer tx.Rollback() - - // Always create explorations bucket. - if _, err := tx.CreateBucketIfNotExists([]byte("Explorations")); err != nil { - return err - } - - return tx.Commit() -} - -func (c *Client) Close() error { - if c.db != nil { - return c.db.Close() - } - return nil -} - -// Connect creates a new session for boltDB. -func (c *Client) Connect() *Session { - s := newSession(c.db) - s.now = c.Now() - return s -} diff --git a/store/bolt/client_test.go b/store/bolt/client_test.go deleted file mode 100644 index 41a37ae752..0000000000 --- a/store/bolt/client_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package bolt_test - -import ( - "io/ioutil" - "os" - "time" - - "github.com/influxdata/mrfusion/store/bolt" -) - -// Mock specific time for testing. -var Now = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) - -type Client struct { - *bolt.Client -} - -func NewClient() *Client { - f, err := ioutil.TempFile("", "mrfusion-bolt-") - if err != nil { - panic(err) - } - f.Close() - - c := &Client{ - Client: bolt.NewClient(), - } - c.Path = f.Name() - c.Now = func() time.Time { return Now } - - return c -} - -func MustOpenClient() *Client { - c := NewClient() - if err := c.Open(); err != nil { - panic(err) - } - return c -} - -func (c *Client) Close() error { - defer os.Remove(c.Path) - return c.Client.Close() -} diff --git a/store/bolt/errors.go b/store/bolt/errors.go deleted file mode 100644 index 1ca48203f9..0000000000 --- a/store/bolt/errors.go +++ /dev/null @@ -1,7 +0,0 @@ -package bolt - -import "errors" - -var ( - ErrExplorationNotFound = errors.New("exploration not found") -) diff --git a/store/bolt/exploration_store.go b/store/bolt/exploration_store.go deleted file mode 100644 index e1cc63cdee..0000000000 --- a/store/bolt/exploration_store.go +++ /dev/null @@ -1,131 +0,0 @@ -package bolt - -import ( - "github.com/influxdata/mrfusion" - "github.com/influxdata/mrfusion/store/bolt/internal" - "golang.org/x/net/context" -) - -// Ensure ExplorationStore implements mrfusion.ExplorationStore. -var _ mrfusion.ExplorationStore = &ExplorationStore{} - -type ExplorationStore struct { - session *Session -} - -// Search the ExplorationStore for all explorations owned by userID. -func (s *ExplorationStore) Query(ctx context.Context, uid mrfusion.UserID) ([]mrfusion.Exploration, error) { - // Begin read transaction. - tx, err := s.session.db.Begin(false) - if err != nil { - return nil, err - } - defer tx.Rollback() - - var explorations []mrfusion.Exploration - if err := tx.Bucket([]byte("Explorations")).ForEach(func(k, v []byte) error { - var e mrfusion.Exploration - if err := internal.UnmarshalExploration(v, &e); err != nil { - return err - } else if e.UserID != uid { - return nil - } - explorations = append(explorations, e) - return nil - }); err != nil { - return nil, err - } - - return explorations, nil -} - -// Create a new Exploration in the ExplorationStore. -func (s *ExplorationStore) Add(ctx context.Context, e mrfusion.Exploration) error { - // Begin read-write transaction. - tx, err := s.session.db.Begin(true) - if err != nil { - return err - } - defer tx.Rollback() - - b := tx.Bucket([]byte("Explorations")) - seq, _ := b.NextSequence() - e.ID = mrfusion.ExplorationID(seq) - e.CreatedAt = s.session.now - - if v, err := internal.MarshalExploration(&e); err != nil { - return err - } else if err := b.Put(itob(int(e.ID)), v); err != nil { - return err - } - - return tx.Commit() -} - -// Delete the exploration from the ExplorationStore -func (s *ExplorationStore) Delete(ctx context.Context, e mrfusion.Exploration) error { - // Begin read transaction. - tx, err := s.session.db.Begin(false) - if err != nil { - return err - } - defer tx.Rollback() - - if err := tx.Bucket([]byte("Explorations")).Delete(itob(int(e.ID))); err != nil { - return err - } - - return nil -} - -// Retrieve an exploration for an id exists. -func (s *ExplorationStore) Get(ctx context.Context, id mrfusion.ExplorationID) (mrfusion.Exploration, error) { - var e mrfusion.Exploration - - // Begin read transaction. - tx, err := s.session.db.Begin(false) - if err != nil { - return e, err - } - defer tx.Rollback() - - if v := tx.Bucket([]byte("Explorations")).Get(itob(int(id))); v == nil { - return e, nil - } else if err := internal.UnmarshalExploration(v, &e); err != nil { - return e, err - } - - return e, nil -} - -// Update the exploration with the exploration `ne`; will update `UpdatedAt`. -func (s *ExplorationStore) Update(ctx context.Context, ne mrfusion.Exploration) error { - // Begin read-write transaction. - tx, err := s.session.db.Begin(true) - if err != nil { - return err - } - defer tx.Rollback() - - b := tx.Bucket([]byte("Explorations")) - - var e mrfusion.Exploration - if v := b.Get(itob(int(ne.ID))); v == nil { - return ErrExplorationNotFound - } else if err := internal.UnmarshalExploration(v, &e); err != nil { - return err - } - - e.Name = ne.Name - e.UserID = ne.UserID - e.Data = ne.Data - e.UpdatedAt = s.session.now - - if v, err := internal.MarshalExploration(&e); err != nil { - return err - } else if err := b.Put(itob(int(e.ID)), v); err != nil { - return err - } - - return tx.Commit() -} diff --git a/store/bolt/exploration_store_test.go b/store/bolt/exploration_store_test.go deleted file mode 100644 index 5fd02392ad..0000000000 --- a/store/bolt/exploration_store_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package bolt_test - -import ( - "testing" - - "github.com/influxdata/mrfusion" -) - -// Ensure Exploration can be added and retrieved. -func TestExplorationStore_Add(t *testing.T) { - c := MustOpenClient() - defer c.Close() - s := c.Connect().ExplorationStore() - - data := "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}" - - exploration := mrfusion.Exploration{ - Name: "Ferdinand Magellan", - UserID: 2, - Data: data, - } - - // Add new exploration. - if err := s.Add(nil, exploration); err != nil { - t.Fatal(err) - } else if exploration.ID != 0 { - t.Fatalf("exploration ID error: got %v, expected %v", exploration.ID, 1) - } - - // Confirm exploration in the store is the same as the original. - e, err := s.Get(nil, 1) - if err != nil { - t.Fatal(err) - } else if e.Name != exploration.Name { - t.Fatalf("exploration Name error: got %v, expected %v", e.Name, exploration.Name) - } else if e.UserID != exploration.UserID { - t.Fatalf("exploration UserID error: got %v, expected %v", e.UserID, exploration.UserID) - } else if e.Data != exploration.Data { - t.Fatalf("exploration Data error: got %v, expected %v", e.Data, exploration.Data) - } -} - -// Ensure Explorations can be queried by UserID. -func TestExplorationStore_Query(t *testing.T) { - c := MustOpenClient() - defer c.Close() - s := c.Connect().ExplorationStore() - - explorations := make(map[int]mrfusion.Exploration) - explorations[0] = mrfusion.Exploration{ - Name: "Ferdinand Magellan", - UserID: 2, - Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}", - } - explorations[1] = mrfusion.Exploration{ - Name: "Marco Polo", - UserID: 3, - Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}", - } - explorations[2] = mrfusion.Exploration{ - Name: "Leif Ericson", - UserID: 3, - Data: "{\"panels\":{\"123\":{\"id\":\"123\",\"queryIds\":[\"456\"]}},\"queryConfigs\":{\"456\":{\"id\":\"456\",\"database\":null,\"measurement\":null,\"retentionPolicy\":null,\"fields\":[],\"tags\":{},\"groupBy\":{\"time\":null,\"tags\":[]},\"areTagsAccepted\":true,\"rawText\":null}}}", - } - - for i := range explorations { - if err := s.Add(nil, explorations[i]); err != nil { - t.Fatal(err) - } - } - - // Query for explorations. - if explorations, err := s.Query(nil, 3); err != nil { - t.Fatal(err) - } else if len(explorations) != 2 { - t.Fatalf("exploration query length error: got %v, expected %v", len(explorations), 2) - } else if explorations[0].Name != "Marco Polo" { - t.Fatalf("exploration query error: got %v, expected %v", explorations[0].Name, "Marco Polo") - } else if explorations[1].Name != "Leif Ericson" { - t.Fatalf("exploration query error: got %v, expected %v", explorations[1].Name, "Leif Ericson") - } - -} - -// Ensure an exploration can be deleted. -func TestExplorationStore_Delete(t *testing.T) { - // TODO: Make sure deleting an exploration works. - t.Skip() -} - -// Ensure explorations can be updated. -func TestExplorationStore_Update(t *testing.T) { - c := MustOpenClient() - defer c.Close() - s := c.Connect().ExplorationStore() - - if err := s.Add(nil, mrfusion.Exploration{Name: "Ferdinand Magellan"}); err != nil { - t.Fatal(err) - } - if err := s.Add(nil, mrfusion.Exploration{UserID: 3}); err != nil { - t.Fatal(err) - } - - // Update explorations. - if err := s.Update(nil, mrfusion.Exploration{ID: 1, Name: "Francis Drake"}); err != nil { - t.Fatal(err) - } - if err := s.Update(nil, mrfusion.Exploration{ID: 2, UserID: 4}); err != nil { - t.Fatal(err) - } - - // Confirm first Exploration update updated Name. - if e, err := s.Get(nil, 1); err != nil { - t.Fatal(err) - } else if e.Name != "Francis Drake" { - t.Fatalf("exploration 1 update error: got %v, expected %v", e.Name, "Francis Drake") - } - - // Confirm second Exploration has updated UserID. - if e, err := s.Get(nil, 2); err != nil { - t.Fatal(err) - } else if e.UserID != 4 { - t.Fatalf("exploration 2 update error: got: %v, expected: %v", e.UserID, 4) - } -} diff --git a/store/bolt/internal/internal.proto b/store/bolt/internal/internal.proto deleted file mode 100644 index c49c8222eb..0000000000 --- a/store/bolt/internal/internal.proto +++ /dev/null @@ -1,11 +0,0 @@ -syntax = "proto3"; -package internal; - -message Exploration { - int64 ID = 1; - string Name = 2; - int64 UserID = 3; - string Data = 4; - int64 CreatedAt = 5; - int64 UpdatedAt = 6; -} diff --git a/store/bolt/session.go b/store/bolt/session.go deleted file mode 100644 index af1bc24cc4..0000000000 --- a/store/bolt/session.go +++ /dev/null @@ -1,26 +0,0 @@ -package bolt - -import ( - "time" - - "github.com/boltdb/bolt" - "github.com/influxdata/mrfusion" -) - -// Session is a connection to a boltDB database. -// TODO: Hook up authentication here. -type Session struct { - db *bolt.DB - now time.Time - - // Services - explorationStore ExplorationStore -} - -func newSession(db *bolt.DB) *Session { - s := &Session{db: db} - s.explorationStore.session = s - return s -} - -func (s *Session) ExplorationStore() mrfusion.ExplorationStore { return &s.explorationStore } diff --git a/stores.go b/stores.go index f8700c4290..548ae01ae8 100644 --- a/stores.go +++ b/stores.go @@ -10,9 +10,12 @@ import ( type Permission string type Permissions []Permission +// UserID is a unique ID for a source user. +type UserID int + // Represents an authenticated user. type User struct { - ID int + ID UserID Name string Permissions Permissions Roles []Role @@ -57,34 +60,32 @@ type AuthStore struct { } } -// UserID is a unique ID for a source user. -type UserID int - -// ExplorationID is a unique ID for an exploration. +// ExplorationID is a unique ID for an Exploration. type ExplorationID int // Exploration is a serialization of front-end Data Explorer. type Exploration struct { ID ExplorationID - Name string // User provided name of the exploration. - UserID UserID // UserID is the owner of this exploration. - Data string // Opaque blob of JSON data - CreatedAt time.Time // Time the exploration was first created + Name string // User provided name of the Exploration. + UserID UserID // UserID is the owner of this Exploration. + Data string // Opaque blob of JSON data. + CreatedAt time.Time // Time the exploration was first created. UpdatedAt time.Time // Latest time the exploration was updated. + Default bool // Flags an exploration as the default. } // ExplorationStore stores front-end serializations of data explorer sessions. type ExplorationStore interface { - // Search the ExplorationStore for all explorations owned by userID. - Query(ctx context.Context, userID UserID) ([]Exploration, error) - // Create a new Exploration in the ExplorationStore - Add(context.Context, Exploration) error - // Delete the exploration from the ExplorationStore - Delete(context.Context, Exploration) error - // Retrieve an exploration if `ID` exists. - Get(ctx context.Context, ID ExplorationID) (Exploration, error) - // Update the exploration; will update `UpdatedAt`. - Update(context.Context, Exploration) error + // Search the ExplorationStore for each Exploration owned by `UserID`. + Query(ctx context.Context, userID UserID) ([]*Exploration, error) + // Create a new Exploration in the ExplorationStore. + Add(context.Context, *Exploration) error + // Delete the Exploration from the ExplorationStore. + Delete(context.Context, *Exploration) error + // Retrieve an Exploration if `ID` exists. + Get(ctx context.Context, ID ExplorationID) (*Exploration, error) + // Update the Exploration; will also update the `UpdatedAt` time. + Update(context.Context, *Exploration) error } // Cell is a rectangle and multiple time series queries to visualize. From 16b79cebec84209b8851acc81d735d55fa76be0f Mon Sep 17 00:00:00 2001 From: gunnaraasen Date: Thu, 29 Sep 2016 10:24:54 -0400 Subject: [PATCH 4/4] Update exploration store interface to return exploration on add --- bolt/exploration.go | 6 +++--- bolt/exploration_test.go | 4 ++-- mock/mock.go | 4 ++-- stores.go | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bolt/exploration.go b/bolt/exploration.go index 3e0f04898f..dd02529384 100644 --- a/bolt/exploration.go +++ b/bolt/exploration.go @@ -41,7 +41,7 @@ func (s *ExplorationStore) Query(ctx context.Context, uid mrfusion.UserID) ([]*m } // Create a new Exploration in the ExplorationStore. -func (s *ExplorationStore) Add(ctx context.Context, e *mrfusion.Exploration) error { +func (s *ExplorationStore) Add(ctx context.Context, e *mrfusion.Exploration) (*mrfusion.Exploration, error) { if err := s.client.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(ExplorationBucket) seq, err := b.NextSequence() @@ -58,10 +58,10 @@ func (s *ExplorationStore) Add(ctx context.Context, e *mrfusion.Exploration) err } return nil }); err != nil { - return err + return nil, err } - return nil + return e, nil } // Delete the exploration from the ExplorationStore diff --git a/bolt/exploration_test.go b/bolt/exploration_test.go index e700902909..01cdaaba22 100644 --- a/bolt/exploration_test.go +++ b/bolt/exploration_test.go @@ -74,7 +74,7 @@ func TestExplorationStore_CRUD(t *testing.T) { // Add new explorations. for i := range explorations { - if err := s.Add(nil, explorations[i]); err != nil { + if _, err := s.Add(nil, explorations[i]); err != nil { t.Fatal(err) } } @@ -156,7 +156,7 @@ func TestExplorationStore_Query(t *testing.T) { // Add new explorations. for i := range explorations { - if err := s.Add(nil, explorations[i]); err != nil { + if _, err := s.Add(nil, explorations[i]); err != nil { t.Fatal(err) } } diff --git a/mock/mock.go b/mock/mock.go index 5a2c852b6d..37bf4424f9 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -44,11 +44,11 @@ func (m *ExplorationStore) Query(ctx context.Context, userID mrfusion.UserID) ([ return res, nil } -func (m *ExplorationStore) Add(ctx context.Context, e *mrfusion.Exploration) error { +func (m *ExplorationStore) Add(ctx context.Context, e *mrfusion.Exploration) (*mrfusion.Exploration, error) { e.CreatedAt = m.NowFunc() e.UpdatedAt = m.NowFunc() m.db[len(m.db)] = e - return nil + return e, nil } func (m *ExplorationStore) Delete(ctx context.Context, e *mrfusion.Exploration) error { diff --git a/stores.go b/stores.go index 548ae01ae8..f68d45ecd3 100644 --- a/stores.go +++ b/stores.go @@ -79,7 +79,7 @@ type ExplorationStore interface { // Search the ExplorationStore for each Exploration owned by `UserID`. Query(ctx context.Context, userID UserID) ([]*Exploration, error) // Create a new Exploration in the ExplorationStore. - Add(context.Context, *Exploration) error + Add(context.Context, *Exploration) (*Exploration, error) // Delete the Exploration from the ExplorationStore. Delete(context.Context, *Exploration) error // Retrieve an Exploration if `ID` exists.