Permalink
Browse files

working Cursor Read/Write

  • Loading branch information...
carloscm committed Apr 15, 2012
1 parent a6601cc commit 8ea771ca8cb21e6cdbb25638c1cc0493b31e7f3a
Showing with 207 additions and 144 deletions.
  1. +12 −20 README.md
  2. +3 −3 src/gossie/connection.go
  3. +61 −62 src/gossie/cursor.go
  4. +36 −8 src/gossie/cursor_test.go
  5. +57 −32 src/gossie/struct.go
  6. +21 −12 src/gossie/struct_test.go
  7. +17 −7 src/gossie/types.go
View
@@ -91,37 +91,29 @@ type Timeline struct {
}
row, err = Map(&Timeline{"userid", 10000000000004, "Author Name", "Hey this thing rocks!"})
+err = pool.Mutation().Insert("Timeline", row).Run()
````
### Cursors
As a convenient wrapper over Map/Unmap and the Query/Mutation interfaces Gossie provides the Cursor interface. This wrapper implements a classic database cursor over rows and composite row slices. Example:
```Go
-
-type Timeline struct {
- UserID string `cf:"Timeline" key:"UserID" col:"TweetID,*name" val:"*value"`
- TweetID int64
- Author string
- Body string
-}
-
-// initialize a cursor over a struct instance. we can reuse both the cursor and the struct for all operations
-tweet := &Timeline{"userid", 10000000000004, "Author Name", "Hey this thing rocks!"}
-cursor := pool.Cursor(tweet)
+// initialize a cursor
+cursor := pool.Cursor()
// write a single tweet
-cursor.Write()
+tweet := &Timeline{"userid", 10000000000004, "Author Name", "Hey this thing rocks!"}
+err = cursor.Write(tweet)
-// read a single tweet. this will implicitly use the key field for the row key, and will add a slice operation
-// if the struct has fixed composite columns
-tweet.Read(1)
+// read a single tweet. this will implicitly use the key field for the row key, and will add a slice
+// operation if the struct has fixed composite columns
+err = tweet.Read(tweet)
-// change the row key and the fixed composite filed and read a different tweet, reusing both the struct and the cursor
-tweet.UserID = "anotheruser"
-tweet.TweetID = 20000000000001
-cursor.Read(1)
-// tweet now contains the just read tweet
+// init a new struct instance with just the key and composite fields to read a different tweet
+tweet2 := &Timeline{"userid", 10000000000004}
+err = cursor.Read(tweet2)
+// tweet no
````
Comming soon: range reads for composites with buffering and paging
View
@@ -60,7 +60,7 @@ type ConnectionPool interface {
Mutation() Mutation
// Cursor returns a high level interface for read and write operations over structs
- Cursor(interface{}) Cursor
+ Cursor() Cursor
// Close all the connections in the pool
Close()
@@ -311,8 +311,8 @@ func (cp *connectionPool) Mutation() Mutation {
return makeMutation(cp, cp.options.WriteConsistency)
}
-func (cp *connectionPool) Cursor(source interface{}) Cursor {
- return makeCursor(cp, source)
+func (cp *connectionPool) Cursor() Cursor {
+ return makeCursor(cp)
}
func (cp *connectionPool) Keyspace() string {
View
@@ -22,51 +22,42 @@ todo:
*/
+var (
+ ErrorNotFound = errors.New("Row (plus any composites) not found")
+)
+
// Cursor is a simple cursor-based interface for reading and writing structs from a Cassandra column family.
type Cursor interface {
- // Read rows (or slices of a row) to fill up the struct.
- //
- // For limit == 1 a single get operation will be issued. If a composite is present then this operation will
- // issue a slice with an exact match for the composite value
- //
- // For limit > 1, and for structs with no composite, a range get operation will be issued, the result buffered
- // internally in the Cursor, and the first returned row unmapped into the struct. Next() is then available for
- // paging inside the returned results and will issue new range get operations when neede. Please note that row
- // range get is unordered in most Cassandra configurations. TO DO
- //
- // If the struct has a composite column name then this operation will issue a single row get, but with a slice
- // predicate that allows for iteration over the row with Next()/Prev() TO DO
- Read(limit int) error
-
+ //Search(...)
//Next() bool
//Prev() bool
-
//First()
+ // Read a single row (or slice of a row) to fill up the struct.
+ // A single get operation will be issued. If a composite is present then this operation will issue a slice with
+ // an exact match for the composite value with the field values present in the struct
+ Read(interface{}) error
+
// Write a single row (or slice of a row) with the data currently in the struct.
- Write() error
+ Write(interface{}) error
//Delete()
}
type cursor struct {
- source interface{}
- pool *connectionPool
- //position int
- //buffer []interface{}
+ pool *connectionPool
}
-func makeCursor(cp *connectionPool, source interface{}) (c *cursor) {
+func makeCursor(cp *connectionPool) (c *cursor) {
return &cursor{
- source: source,
- pool: cp,
+ pool: cp,
}
}
-func (c *cursor) Write() error {
+func (c *cursor) Write(source interface{}) error {
- row, ms, err := internalMap(c.source)
+ row, ms, err := internalMap(source)
if err != nil {
return err
}
@@ -78,26 +69,20 @@ func (c *cursor) Write() error {
return nil
}
-func (c *cursor) Read(limit int) error {
- if limit < 0 {
- return errors.New("Limit is less than 1, nothing to read")
- }
+func (c *cursor) Read(source interface{}) error {
// deconstruct the source struct into a reflect.Value and a (cached) struct mapping
- ms, err := newMappedStruct(c.source)
+ ms, err := newMappedStruct(source)
if err != nil {
return err
}
// sanity checks
- if !ms.sm.isStarNameColumn && !ms.sm.isSliceColumn {
- return errors.New(fmt.Sprint("Struct ", ms.v.Type().Name(), " has no *name nor slice field in its col tag, nothing to read"))
- }
if ms.sm.isSliceColumn {
return errors.New(fmt.Sprint("Slice field in col tag is unsuported in Cursor for now, check back soon!"))
}
- // marshal the key field for the key to look up
+ // marshal the key field
key, err := ms.marshalKey()
if err != nil {
return err
@@ -106,41 +91,55 @@ func (c *cursor) Read(limit int) error {
// start building the query
q := c.pool.Query().Cf(ms.sm.cf)
+ // build a slice composite comparator if needed
+ if ms.sm.isCompositeColumn {
+ // iterate over the components and set an equality comparison for every simple field
+ start := make([]byte, 0)
+ end := make([]byte, 0)
+ var component int
+ for component = 0; component < len(ms.sm.columns); component++ {
+ fm := ms.sm.columns[component]
+ if fm.fieldKind != baseTypeField {
+ break
+ }
+ b, err := ms.mapColumn(baseTypeField, fm, 0)
+ if err != nil {
+ return err
+ }
+ start = packComposite(start, b, eocEquals)
+ end = packComposite(end, b, eocGreater)
+ }
+
+ /*if component < len(ms.sm.columns) {
+ // we still got one to go, this means the last one was an iterable non-fixed type (*name or go slice)
+ //fm := ms.sm.columns[component]
+ // TODO: this will only work for *name
+ b := make([]byte, 0)
+ start = packComposite(start, b, o[6], o[7], o[8])
+ end = packComposite(end, b, o[9], o[10], o[11])
+ }*/
+
+ // TODO: fix hardcoded number of columns
+ q.Slice(&Slice{Start: start, End: end, Count: 100})
+ }
+
//isCompositeColumn bool
//isSliceColumn bool
//isStarNameColumn bool
- if limit == 1 {
+ row, err := q.Get(key)
- if ms.sm.isCompositeColumn {
- return errors.New(fmt.Sprint("Cursor composite support will be implemented soon!"))
- /*
-
- // we only want a single result so issue an exact match composite comparator slice
- start := make([]byte, 0)
- start = packComposite(start, component []byte, true, true, true)
-
- packComposite(start, component []byte, true, sliceStart, inclusive bool) []byte {
+ if err != nil {
+ return err
+ }
- s := &Slice{
- Start:
- End:
- Count: 1
- }
- q.Slice(s)
- */
+ if row == nil {
+ return ErrorNotFound
+ }
- }
- row, err := q.Get(key)
- if err != nil {
- return err
- }
- err = Unmap(row, c.source)
- if err != nil {
- return err
- }
- } else {
- return errors.New(fmt.Sprint("Limit > 1 will be implemented soon!"))
+ err = Unmap(row, source)
+ if err != nil {
+ return err
}
return nil
View
@@ -5,6 +5,14 @@ import (
"testing"
)
+/*
+
+todo:
+
+ since most of the Cursor interface is still in flux the current tests are minimal
+
+*/
+
type ReasonableOne struct {
Username string `cf:"Reasonable" key:"Username" col:"TweetID,*name" val:"*value"`
TweetID int64
@@ -28,25 +36,45 @@ func TestRead(t *testing.T) {
Body: "hey this thing appears to work, nice!",
}
- cursor := cp.Cursor(ro)
- err = cursor.Write()
+ cursor := cp.Cursor()
+ err = cursor.Write(ro)
if err != nil {
t.Error("Writing struct:", err)
}
ro2 := &ReasonableOne{
Username: "testuser",
- TweetID: 100000000000002,
+ TweetID: 100000000000003,
+ Lat: 2.00002,
+ Lon: 1.11,
+ Body: "more words",
}
-
- cursor2 := cp.Cursor(ro2)
- err = cursor2.Read(1)
+ err = cursor.Write(ro2)
if err != nil {
- t.Error("Reading struct:", err)
+ t.Error("Writing struct:", err)
}
- if !reflect.DeepEqual(ro, ro2) {
+ ro3 := &ReasonableOne{
+ Username: "testuser",
+ TweetID: 100000000000002,
+ }
+ err = cursor.Read(ro3)
+ if err != nil {
+ t.Fatal("Reading struct:", err)
+ }
+ if !reflect.DeepEqual(ro, ro3) {
t.Error("Read does not match Write")
}
+ ro3 = &ReasonableOne{
+ Username: "testuser",
+ TweetID: 100000000000003,
+ }
+ err = cursor.Read(ro3)
+ if err != nil {
+ t.Fatal("Reading struct:", err)
+ }
+ if !reflect.DeepEqual(ro2, ro3) {
+ t.Error("Read does not match Write")
+ }
}
Oops, something went wrong.

0 comments on commit 8ea771c

Please sign in to comment.