Permalink
Browse files

add some public marshalling to mappings, built-in mappings again requ…

…ire struct instances on init
  • Loading branch information...
1 parent 4baea69 commit 6e034592a09659fa36382ad085892079a701c764 @carloscm committed May 6, 2012
Showing with 98 additions and 120 deletions.
  1. +5 −15 README.md
  2. +70 −38 src/gossie/mapping.go
  3. +12 −56 src/gossie/mapping_test.go
  4. +10 −10 src/gossie/query.go
  5. +1 −1 src/gossie/struct.go
View
@@ -71,7 +71,7 @@ The low level interface is based on passing []byte values for everything, mirror
### Struct mapping
-The Mapping interface and its implementations allow to convert Go structs into Rows, and they have support of advanced features like composites or overriding column names and types. NewSparse() returns a Mapping for the new CQL 3.0 pattern of composite "primary keys":
+The Mapping interface and its implementations allow to convert Go structs into Rows, and they have support of advanced features like composites or overriding column names and types. Built-in NewMapping() returns a Mapping implementation that can map and unmap Go structs from Cassandra rows, serialized in classic key/value rows or in composited column names, with support for both sparse and compact storage. For example:
```Go
/*
@@ -87,30 +87,20 @@ CREATE TABLE Timeline (
// In Gossie:
type Tweet struct {
- UserID string
+ UserID string `cf:"Timeline" key:"UserID" cols:"TweetID"`
TweetID int64
Author string
Body string
}
-mapping := gossie.NewSparse("Timeline", "UserID", "TweetID")
+mapping := gossie.NewMapping(&Tweet{})
row, err = mapping.Map(&Tweet{"userid", 10000000000004, "Author Name", "Hey this thing rocks!"})
err = pool.Writer().Insert("Timeline", row).Run()
````
-When calling Mapping.Map() you can tag your struct fiels with `name`, `type` and `skip`. The `name` field tag will change the column name to its value when the field it appears on is (un)marhsaled to/from a Cassandra row column. The `type` field tag allows to override the default type Go<->Cassandra type mapping used by Gossie for the field it appears on. If `skip:"true"` is present the field will be ignored by Gossie.
-
-The tags `cf`, `key` and `cols` can be used in any field in the struct to document a mapping. It can later be extracted with `MappingFromTags()` by passing any instance of the struct, even an empty one. For example this is equivalent to the mapping created with `NewSparse()` in the previous example:
+When calling NewMapping() you can tag your struct fiels with `name`, `type` and `skip`. The `name` field tag will change the column name to its value when the field it appears on is (un)marhsaled to/from a Cassandra row column. The `type` field tag allows to override the default type Go<->Cassandra type mapping used by Gossie for the field it appears on. If `skip:"true"` is present the field will be ignored by Gossie.
-```Go
-type Tweet struct {
- UserID string `cf:"Timeline" key:"UserID" cols:"TweetID"`
- TweetID int64
- Author string
- Body string
-}
-mapping := gossie.MappingFromTags(&Tweet{})
-```
+The tags `cf`, `key`, `cols` and `value` can be used in any field in the struct to document a mapping. `cf` is the column family name. `key` is the field name in the struct that stores the Cassandra row key value. `cols` is a list of struct fiels that build up the composite column name, if there is any. `value` is the field that stores the column value for compact storage rows.
### Query and Result interfaces (planned)
View
@@ -13,15 +13,15 @@ import (
mapping for Go slices (N slices?)
*/
-// Mapping maps the type of a Go object to/from (a slice of) a Cassandra row.
+// Mapping maps the type of a Go object to/from a Cassandra row.
type Mapping interface {
// Cf returns the column family name
Cf() string
- // MinColumns returns the minimal number of columns required by the mapped Go
- // object
- MinColumns(source interface{}) int
+ MarshalKey(key interface{}) ([]byte, error)
+
+ MarshalComponent(component interface{}, position int) ([]byte, error)
// Map converts a Go object compatible with this Mapping into a Row
Map(source interface{}) (*Row, error)
@@ -32,56 +32,78 @@ type Mapping interface {
Unmap(destination interface{}, offset int, row *Row) (int, error)
}
-// MappingFromTags looks up the field tag 'mapping' in the passed struct type
+var (
+ noMoreComponents = errors.New("No more components allowed")
+)
+
+// NewMapping looks up the field tag 'mapping' in the passed struct type
// to decide which mapping it is using, then builds a mapping using the 'cf',
// 'key', 'cols' and 'value' field tags.
-func MappingFromTags(source interface{}) (Mapping, error) {
+func NewMapping(source interface{}) (Mapping, error) {
_, si, err := validateAndInspectStruct(source)
if err != nil {
return nil, err
}
- // mandatory tags for all the provided mappings
- for _, t := range []string{"cf", "key"} {
- _, found := si.globalTags[t]
- if !found {
- return nil, errors.New(fmt.Sprint("Mandatory struct tag ", t, " not found in passed struct of type ", si.rtype.Name()))
- }
+
+ cf, found := si.globalTags["cf"]
+ if !found {
+ return nil, errors.New(fmt.Sprint("Mandatory struct tag 'cf' not found in passed struct of type ", si.rtype.Name()))
+ }
+
+ key, found := si.globalTags["key"]
+ if !found {
+ return nil, errors.New(fmt.Sprint("Mandatory struct tag 'key' not found in passed struct of type ", si.rtype.Name()))
+ }
+ _, found = si.goFields[key]
+ if !found {
+ return nil, errors.New(fmt.Sprint("Key field ", key, " not found in passed struct of type ", si.rtype.Name()))
}
- // optional tags
+
colsS := []string{}
cols, found := si.globalTags["cols"]
if found {
colsS = strings.Split(cols, ",")
}
+ for _, c := range colsS {
+ _, found := si.goFields[c]
+ if !found {
+ return nil, errors.New(fmt.Sprint("Composite field ", c, " not found in passed struct of type ", si.rtype.Name()))
+ }
+ }
+
+ value, found := si.globalTags["value"]
+ if found {
+ _, found := si.goFields[value]
+ if !found {
+ return nil, errors.New(fmt.Sprint("Value field ", value, " not found in passed struct of type ", si.rtype.Name()))
+ }
+ }
+
mapping, found := si.globalTags["mapping"]
if !found {
mapping = "sparse"
}
- value := si.globalTags["value"]
switch mapping {
case "sparse":
- return NewSparse(si.globalTags["cf"], si.globalTags["key"], colsS...), nil
+ return newSparseMapping(si, cf, key, colsS...), nil
case "compact":
if value == "" {
return nil, errors.New(fmt.Sprint("Mandatory struct tag value for compact mapping not found in passed struct of type ", si.rtype.Name()))
}
- return NewCompact(si.globalTags["cf"], si.globalTags["key"], si.globalTags["value"], colsS...), nil
+ return newCompactMapping(si, cf, key, value, colsS...), nil
}
return nil, errors.New(fmt.Sprint("Unrecognized mapping type ", mapping, " in passed struct of type ", si.rtype.Name()))
}
-// Sparse returns a mapping for Go structs that represents a Cassandra row key
-// as a struct field, zero or more composite column names as zero or more
-// struct fields, and the rest of the struct fields as extra columns with the
-// name being the last composite column name, and the value the column value.
-func NewSparse(cf string, keyField string, componentFields ...string) Mapping {
+func newSparseMapping(si *structInspection, cf string, keyField string, componentFields ...string) Mapping {
cm := make(map[string]bool, 0)
for _, f := range componentFields {
cm[f] = true
}
return &sparseMapping{
+ si: si,
cf: cf,
key: keyField,
components: componentFields,
@@ -90,6 +112,7 @@ func NewSparse(cf string, keyField string, componentFields ...string) Mapping {
}
type sparseMapping struct {
+ si *structInspection
cf string
key string
components []string
@@ -100,13 +123,25 @@ func (m *sparseMapping) Cf() string {
return m.cf
}
-func (m *sparseMapping) MinColumns(source interface{}) int {
- _, si, err := validateAndInspectStruct(source)
+func (m *sparseMapping) MarshalKey(key interface{}) ([]byte, error) {
+ f := m.si.goFields[m.key]
+ b, err := Marshal(key, f.cassandraType)
if err != nil {
- return -1
+ return nil, errors.New(fmt.Sprint("Error marshaling passed value for the key in field ", f.name, ":", err))
}
- // struct fields minus the components fields minus one field for the key
- return len(si.goFields) - len(m.components) - 1
+ return b, nil
+}
+
+func (m *sparseMapping) MarshalComponent(component interface{}, position int) ([]byte, error) {
+ if position >= len(m.components) {
+ return nil, errors.New(fmt.Sprint("The mapping has a component length of ", len(m.components), " and the passed position is ", position))
+ }
+ f := m.si.goFields[m.components[position]]
+ b, err := Marshal(component, f.cassandraType)
+ if err != nil {
+ return nil, errors.New(fmt.Sprint("Error marshaling passed value for a composite component in field ", f.name, ":", err))
+ }
+ return b, nil
}
func (m *sparseMapping) startMap(source interface{}) (*Row, *reflect.Value, *structInspection, []byte, error) {
@@ -233,11 +268,16 @@ func (m *sparseMapping) Unmap(destination interface{}, offset int, row *Row) (in
compositeFieldsAreSet := false
- // unmarshal col/values
- min := m.MinColumns(destination)
+ // FIXME: change this code to NOT expect a fixed number of columns and
+ // instead adapt itself to the data by assuming the first column composite
+ // to be uniform for all the struct values (except field name), then
+ // request column by column with some kind of interface that does
+ // buffering reads on demand from an underlying query
+ min := len(si.goFields) - len(m.components) - 1
if min > len(row.Columns) {
return -1, nil
}
+
columns := row.Columns[offset : offset+min]
for _, column := range columns {
readColumns++
@@ -275,13 +315,9 @@ func (m *sparseMapping) Unmap(destination interface{}, offset int, row *Row) (in
return readColumns, nil
}
-// NewCompact returns a mapping for Go structs that represents a Cassandra row
-// column as a full Go struct. The field named by the value is mapped to the
-// column value. Each passed component field name is mapped, in order, to the
-// column composite values.
-func NewCompact(cf string, keyField string, valueField string, componentFields ...string) Mapping {
+func newCompactMapping(si *structInspection, cf string, keyField string, valueField string, componentFields ...string) Mapping {
return &compactMapping{
- sparseMapping: *(NewSparse(cf, keyField, componentFields...).(*sparseMapping)),
+ sparseMapping: *(newSparseMapping(si, cf, keyField, componentFields...).(*sparseMapping)),
value: valueField,
}
}
@@ -295,10 +331,6 @@ func (m *compactMapping) Cf() string {
return m.cf
}
-func (m *compactMapping) MinColumns(source interface{}) int {
- return 1
-}
-
func (m *compactMapping) Map(source interface{}) (*Row, error) {
row, v, si, composite, err := m.startMap(source)
if err != nil {
View
@@ -7,11 +7,11 @@ import (
/*
todo:
- deeper tests
+ deeper tests, over more methods, and over all internal types
*/
type everythingComp struct {
- Key string
+ Key string `cf:"1" key:"Key" cols:"FBytes,FBool,FInt8,FInt16,FInt32,FInt,FInt64,FFloat32,FFloat64,FString,FUUID"`
FBytes []byte
FBool bool
FInt8 int8
@@ -35,9 +35,9 @@ type tagsA struct {
type tagsB struct {
A int `cf:"1" key:"A" cols:"B"`
- B int
- C int
- D int
+ B int `type:"AsciiType"`
+ C int `skip:"true"`
+ D int `name:"Z"`
}
type tagsC struct {
@@ -86,53 +86,13 @@ func (shell *structTestShell) checkFullMap(t *testing.T) {
}
func TestMap(t *testing.T) {
- mA, _ := MappingFromTags(&tagsA{})
- mB, _ := MappingFromTags(&tagsB{})
- mC, _ := MappingFromTags(&tagsC{})
+ mA, _ := NewMapping(&tagsA{})
+ mB, _ := NewMapping(&tagsB{})
+ mC, _ := NewMapping(&tagsC{})
+ mE, _ := NewMapping(&everythingComp{})
- t.Log(mC)
shells := []*structTestShell{
&structTestShell{
- mapping: NewSparse("cfname", "A"),
- name: "noErrA",
- expectedStruct: &noErrA{1, 2, 3},
- resultStruct: &noErrA{},
- expectedRow: &Row{
- Key: []byte{0, 0, 0, 0, 0, 0, 0, 1},
- Columns: []*Column{
- &Column{
- Name: []byte{'B'},
- Value: []byte{0, 0, 0, 0, 0, 0, 0, 2},
- },
- &Column{
- Name: []byte{'C'},
- Value: []byte{0, 0, 0, 0, 0, 0, 0, 3},
- },
- },
- },
- },
-
- &structTestShell{
- mapping: NewSparse("cfname", "A", "B"),
- name: "noErrB",
- expectedStruct: &noErrB{1, 2, 3, 4},
- resultStruct: &noErrB{},
- expectedRow: &Row{
- Key: []byte{0, 0, 0, 0, 0, 0, 0, 1},
- Columns: []*Column{
- &Column{
- Name: []byte{0, 8, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 'C', 0},
- Value: []byte{'3'},
- },
- &Column{
- Name: []byte{0, 8, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 'Z', 0},
- Value: []byte{0, 0, 0, 0, 0, 0, 0, 4},
- },
- },
- },
- },
-
- &structTestShell{
mapping: mA,
name: "tagsA",
expectedStruct: &tagsA{1, 2, 3, 4},
@@ -159,17 +119,13 @@ func TestMap(t *testing.T) {
&structTestShell{
mapping: mB,
name: "tagsB",
- expectedStruct: &tagsB{1, 2, 3, 4},
+ expectedStruct: &tagsB{1, 2, 0, 4},
resultStruct: &tagsB{},
expectedRow: &Row{
Key: []byte{0, 0, 0, 0, 0, 0, 0, 1},
Columns: []*Column{
&Column{
- Name: []byte{0, 8, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 'C', 0},
- Value: []byte{0, 0, 0, 0, 0, 0, 0, 3},
- },
- &Column{
- Name: []byte{0, 8, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 'D', 0},
+ Name: []byte{0, 1, '2', 0, 0, 1, 'Z', 0},
Value: []byte{0, 0, 0, 0, 0, 0, 0, 4},
},
},
@@ -193,7 +149,7 @@ func TestMap(t *testing.T) {
},
&structTestShell{
- mapping: NewSparse("cfname", "Key", "FBytes", "FBool", "FInt8", "FInt16", "FInt32", "FInt", "FInt64", "FFloat32", "FFloat64", "FString", "FUUID"),
+ mapping: mE,
name: "everythingComp",
expectedStruct: &everythingComp{"a", []byte{1, 2}, true, 3, 4, 5, 6, 7, 8.0, 9.0, "b",
[16]byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, "c"},
Oops, something went wrong.

0 comments on commit 6e03459

Please sign in to comment.