blob: 63b5d8fcdd1927a2145bf450348a35d3a2dec07e [file] [log] [blame]
/*
* Copyright (c) 2012 The Goon Authors
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package goon
import (
"bytes"
"encoding/binary"
"fmt"
"math"
"reflect"
"strings"
"sync"
"time"
"google.golang.org/appengine"
"google.golang.org/appengine/datastore"
)
// KindNameResolver takes an Entity and returns what the Kind should be for
// Datastore.
type KindNameResolver func(src interface{}) string
// ### Entity serialization ###
const serializationFormatVersion = 5 // Increase this whenever the format changes
// The entities are encoded to bytes with little endian ordering, as follows:
//
// [header][?prop1][..][?propN]
//
// header | uint32 | Always present
// The first 30 bits are used for propCount. The last 2 bits for flags.
//
// propX | []byte | X <= propCount
// Each property is serialized separately.
//
//
// A property gets serialized into bytes with little endian ordering as follows:
//
// [nameLen][?name][header][?valueLen][?value]
//
// nameLen | uint16 | Always present
// The length of the property name.
//
// name | string | nameLen > 0
// The property name.
//
// header | byte | Always present
// The first 4 bits specify the property value type (propType..), the last 4 bits are used for flags.
//
// valueLen | uint24 | type == (string|BlobKey|ByteString|[]byte|*Key) && value != zeroValue
// The length of the value bytes.
//
// value | []byte | type != (none|bool) && value != zeroValue
//
// None N/A
// Int64 8 bytes
// Bool N/A (value == propHasValue header flag)
// String valueLen bytes
// Float64 8 bytes
// ByteString valueLen bytes
// KeyPtr valueLen bytes
// Time 8 bytes
// BlobKey valueLen bytes
// GeoPoint 16 bytes, Lat+Lng float64
// ByteSlice valueLen bytes
// Entity header flags
const (
entityExists = 1 << 30
//entityRESERVED = 1 << 31 // Unused flag
)
const entityHeaderMaskPropCount = 1<<30 - 1 // All the bits used for propCount
const entityHeaderMaskFlags = ^entityHeaderMaskPropCount // All the bits used for flags
func serializeEntityHeader(propCount, flags int) uint32 {
return uint32((flags & entityHeaderMaskFlags) | (propCount & entityHeaderMaskPropCount))
}
func deserializeEntityHeader(header uint32) (propCount, flags int) {
return int(header) & entityHeaderMaskPropCount, int(header) & entityHeaderMaskFlags
}
// The valid datastore.Property.Value types are:
// - int64
// - bool
// - string
// - float64
// - datastore.ByteString
// - *datastore.Key
// - time.Time
// - appengine.BlobKey
// - appengine.GeoPoint
// - []byte (up to 1 megabyte in length)
// - *Entity (representing a nested struct)
const (
propTypeNone = 0
propTypeInt64 = 1
propTypeBool = 2
propTypeString = 3
propTypeFloat64 = 4
propTypeByteString = 5
propTypeKeyPtr = 6
propTypeTime = 7
propTypeBlobKey = 8
propTypeGeoPoint = 9
propTypeByteSlice = 10
//propTypeEntityPtr = 11 // TODO: Implement this?
// Space for 4 more types, as the propType value must fit in 4 bits (0-15)
)
// Property header flags
const (
propHasValue = 1 << 4
propMultiple = 1 << 5
propNoIndex = 1 << 6
//propRESERVED = 1 << 7 // Unused flag
)
// We limit the maximum length of datastore.Property.Name,
// however this is our implementation specific and not datastore specific.
const propMaxNameLength = 1<<16 - 1
// Keep a pool of buffers around for more efficient allocation
var bufferPool = sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 0, 16384)) // 16 KiB initial capacity
},
}
// getBuffer returns a reusable buffer from a pool.
// Every buffer acquired with this function must be later freed via freeBuffer.
func getBuffer() *bytes.Buffer {
return bufferPool.Get().(*bytes.Buffer)
}
// freeBuffer returns the buffer to the pool, allowing for reuse.
func freeBuffer(buf *bytes.Buffer) {
buf.Reset()
bufferPool.Put(buf)
}
func writeInt16(buf *bytes.Buffer, i int) {
buf.WriteByte(byte(i))
buf.WriteByte(byte(i >> 8))
}
func writeInt24(buf *bytes.Buffer, i int) {
buf.WriteByte(byte(i))
buf.WriteByte(byte(i >> 8))
buf.WriteByte(byte(i >> 16))
}
func toUnixMicro(t time.Time) int64 {
// We cannot use t.UnixNano() / 1e3 because we want to handle times more than
// 2^63 nanoseconds (which is about 292 years) away from 1970, and those cannot
// be represented in the numerator of a single int64 divide.
return t.Unix()*1e6 + int64(t.Nanosecond()/1e3)
}
func fromUnixMicro(t int64) time.Time {
return time.Unix(t/1e6, (t%1e6)*1e3).UTC()
}
func serializeProperty(buf *bytes.Buffer, p *datastore.Property) error {
nameLen := len(p.Name)
if nameLen > propMaxNameLength {
return fmt.Errorf("Maximum property name length is %d, but received %d", propMaxNameLength, nameLen)
}
writeInt16(buf, nameLen)
if nameLen > 0 {
buf.WriteString(p.Name)
}
var header byte
if p.Multiple {
header |= propMultiple
}
if p.NoIndex {
header |= propNoIndex
}
v := reflect.ValueOf(p.Value)
unsupported := false
switch v.Kind() {
case reflect.Invalid:
// Has no type and no value, but is legal
buf.WriteByte(header)
case reflect.Int64:
header |= propTypeInt64
val := uint64(v.Int())
if val == 0 {
buf.WriteByte(header)
} else {
header |= propHasValue
buf.WriteByte(header)
data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, val)
buf.Write(data)
}
case reflect.Bool:
header |= propTypeBool
if v.Bool() {
header |= propHasValue
}
buf.WriteByte(header)
case reflect.String:
switch v.Interface().(type) {
case appengine.BlobKey:
header |= propTypeBlobKey
default:
header |= propTypeString
}
val := v.String()
if valLen := len(val); valLen == 0 {
buf.WriteByte(header)
} else {
header |= propHasValue
buf.WriteByte(header)
writeInt24(buf, valLen)
buf.WriteString(val)
}
case reflect.Float64:
header |= propTypeFloat64
val := v.Float()
if val == 0 {
buf.WriteByte(header)
} else {
header |= propHasValue
buf.WriteByte(header)
data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, math.Float64bits(val))
buf.Write(data)
}
case reflect.Ptr:
if k, ok := v.Interface().(*datastore.Key); ok {
header |= propTypeKeyPtr
if k == nil {
buf.WriteByte(header)
} else {
header |= propHasValue
buf.WriteByte(header)
val := k.Encode()
writeInt24(buf, len(val))
buf.WriteString(val)
}
} else {
unsupported = true
}
case reflect.Struct:
switch s := v.Interface().(type) {
case time.Time:
header |= propTypeTime
if s.IsZero() {
buf.WriteByte(header)
} else {
header |= propHasValue
buf.WriteByte(header)
data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, uint64(toUnixMicro(s)))
buf.Write(data)
}
case appengine.GeoPoint:
header |= propTypeGeoPoint
if s.Lat == 0 && s.Lng == 0 {
buf.WriteByte(header)
} else {
header |= propHasValue
buf.WriteByte(header)
data := make([]byte, 16)
binary.LittleEndian.PutUint64(data[:8], math.Float64bits(s.Lat))
binary.LittleEndian.PutUint64(data[8:], math.Float64bits(s.Lng))
buf.Write(data)
}
default:
unsupported = true
}
case reflect.Slice:
switch b := v.Interface().(type) {
case datastore.ByteString:
header |= propTypeByteString
if bLen := len(b); bLen == 0 {
buf.WriteByte(header)
} else {
header |= propHasValue
buf.WriteByte(header)
writeInt24(buf, bLen)
buf.Write(b)
}
case []byte:
header |= propTypeByteSlice
if bLen := len(b); bLen == 0 {
buf.WriteByte(header)
} else {
header |= propHasValue
buf.WriteByte(header)
writeInt24(buf, bLen)
buf.Write(b)
}
default:
unsupported = true
}
default:
unsupported = true
}
if unsupported {
return fmt.Errorf("unsupported datastore.Property value type: " + v.Type().String())
}
return nil
}
func deserializeProperty(buf *bytes.Buffer, prop *datastore.Property) error {
next := func(n int) ([]byte, error) {
b := buf.Next(n)
if bLen := len(b); bLen != n {
return b, fmt.Errorf("Buffer EOF, expected %d bytes but got %v", n, bLen)
}
return b, nil
}
getSize := func() (int, error) {
sizeBytes, err := next(3)
if err != nil {
return 0, err
}
return int(sizeBytes[0]) | int(sizeBytes[1])<<8 | int(sizeBytes[2])<<16, nil
}
// Read the name length
nameLenBytes, err := next(2)
if err != nil {
return err
}
nameLen := int(nameLenBytes[0]) | int(nameLenBytes[1])<<8
// If thehre's a name, read it
if nameLen > 0 {
nameBytes, err := next(nameLen)
if err != nil {
return err
}
prop.Name = string(nameBytes)
}
// Read the header
header, err := buf.ReadByte()
if err != nil {
return err
}
// Apply the flags
prop.Multiple = (header&propMultiple != 0)
prop.NoIndex = (header&propNoIndex != 0)
// Determine the value
valueType := header & 0xF
zeroValue := header&propHasValue == 0
switch valueType {
case propTypeNone:
// nil interface, so nothing to do
case propTypeInt64:
if zeroValue {
prop.Value = int64(0)
} else {
valBytes, err := next(8)
if err != nil {
return err
}
prop.Value = int64(binary.LittleEndian.Uint64(valBytes))
}
case propTypeBool:
prop.Value = !zeroValue
case propTypeString:
if zeroValue {
prop.Value = ""
} else {
size, err := getSize()
if err != nil {
return err
}
valBytes, err := next(size)
if err != nil {
return err
}
prop.Value = string(valBytes)
}
case propTypeBlobKey:
if zeroValue {
prop.Value = appengine.BlobKey("")
} else {
size, err := getSize()
if err != nil {
return err
}
valBytes, err := next(size)
if err != nil {
return err
}
prop.Value = appengine.BlobKey(valBytes)
}
case propTypeFloat64:
if zeroValue {
prop.Value = float64(0)
} else {
valBytes, err := next(8)
if err != nil {
return err
}
prop.Value = math.Float64frombits(binary.LittleEndian.Uint64(valBytes))
}
case propTypeKeyPtr:
if zeroValue {
var key *datastore.Key
prop.Value = key
} else {
size, err := getSize()
if err != nil {
return err
}
valBytes, err := next(size)
if err != nil {
return err
}
prop.Value, err = datastore.DecodeKey(string(valBytes))
if err != nil {
return err
}
}
case propTypeTime:
if zeroValue {
prop.Value = time.Time{}
} else {
valBytes, err := next(8)
if err != nil {
return err
}
prop.Value = fromUnixMicro(int64(binary.LittleEndian.Uint64(valBytes)))
}
case propTypeGeoPoint:
if zeroValue {
prop.Value = appengine.GeoPoint{}
} else {
valBytes, err := next(16)
if err != nil {
return err
}
prop.Value = appengine.GeoPoint{
Lat: math.Float64frombits(binary.LittleEndian.Uint64(valBytes[:8])),
Lng: math.Float64frombits(binary.LittleEndian.Uint64(valBytes[8:])),
}
}
case propTypeByteString:
if zeroValue {
prop.Value = datastore.ByteString{}
} else {
size, err := getSize()
if err != nil {
return err
}
prop.Value = make(datastore.ByteString, size)
if _, err := buf.Read(prop.Value.(datastore.ByteString)); err != nil {
return err
}
}
case propTypeByteSlice:
if zeroValue {
prop.Value = []byte{}
} else {
size, err := getSize()
if err != nil {
return err
}
prop.Value = make([]byte, size)
if _, err := buf.Read(prop.Value.([]byte)); err != nil {
return err
}
}
default:
return fmt.Errorf("Unrecognized value type %d", valueType)
}
return nil
}
// serializeStruct takes a struct and serializes it to portable bytes.
func serializeStruct(src interface{}) ([]byte, error) {
if src == nil {
return serializeProperties(nil, false)
}
if k := reflect.Indirect(reflect.ValueOf(src)).Type().Kind(); k != reflect.Struct {
return nil, fmt.Errorf("goon: Expected struct, got instead: %v", k)
}
var err error
var props []datastore.Property
if pls, ok := src.(datastore.PropertyLoadSaver); ok {
props, err = pls.Save()
} else {
props, err = datastore.SaveStruct(src)
}
if err != nil {
return nil, err
}
return serializeProperties(props, true)
}
// serializeProperties takes a slice of properties and serializes it to portable bytes.
func serializeProperties(props []datastore.Property, exists bool) ([]byte, error) {
// NOTE: We use a separate exists bool to support nil-props for existing structs
if !exists {
return []byte{0, 0, 0, 0}, nil
}
buf := getBuffer()
defer freeBuffer(buf)
// Serialize the entity header
header := serializeEntityHeader(len(props), entityExists)
headerBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(headerBytes, header)
buf.Write(headerBytes)
// Serialize the properties
for i := range props {
if err := serializeProperty(buf, &props[i]); err != nil {
return nil, err
}
}
output := make([]byte, buf.Len())
copy(output, buf.Bytes())
return output, nil
}
// deserializeStruct takes portable bytes b, generated by serializeStruct, and assigns correct values to struct dst.
func deserializeStruct(dst interface{}, b []byte) error {
if len(b) == 0 {
return fmt.Errorf("goon: Expected some data to deserialize, got none.")
}
if k := reflect.Indirect(reflect.ValueOf(dst)).Type().Kind(); k != reflect.Struct {
return fmt.Errorf("goon: Expected struct, got instead: %v", k)
}
// Deserialize the header
header := binary.LittleEndian.Uint32(b[:4])
propCount, flags := deserializeEntityHeader(header)
if flags&entityExists == 0 {
return datastore.ErrNoSuchEntity
}
// Deserialize the properties
buf := bytes.NewBuffer(b[4:])
props := make([]datastore.Property, propCount)
for i := 0; i < propCount; i++ {
if err := deserializeProperty(buf, &props[i]); err != nil {
return err
}
}
return deserializeProperties(dst, props)
}
// deserializeProperties takes a slice of properties and assigns correct values to struct dst.
func deserializeProperties(dst interface{}, props []datastore.Property) error {
if k := reflect.Indirect(reflect.ValueOf(dst)).Type().Kind(); k != reflect.Struct {
return fmt.Errorf("goon: Expected struct, got instead: %v", k)
}
if pls, ok := dst.(datastore.PropertyLoadSaver); ok {
return pls.Load(props)
}
return datastore.LoadStruct(dst, props)
}
// getStructKey returns the key of the struct based in its reflected or
// specified kind and id. The second return parameter is true if src has a
// string id.
func (g *Goon) getStructKey(src interface{}) (key *datastore.Key, hasStringId bool, err error) {
v := reflect.Indirect(reflect.ValueOf(src))
t := v.Type()
k := t.Kind()
if k != reflect.Struct {
err = fmt.Errorf("goon: Expected struct, got instead: %v", k)
return
}
var parent *datastore.Key
var stringID string
var intID int64
var kind string
for i := 0; i < v.NumField(); i++ {
tf := t.Field(i)
vf := v.Field(i)
tag := tf.Tag.Get("goon")
tagValues := strings.Split(tag, ",")
if len(tagValues) > 0 {
tagValue := tagValues[0]
if tagValue == "id" {
switch vf.Kind() {
case reflect.Int64:
if intID != 0 || stringID != "" {
err = fmt.Errorf("goon: Only one field may be marked id")
return
}
intID = vf.Int()
case reflect.String:
if intID != 0 || stringID != "" {
err = fmt.Errorf("goon: Only one field may be marked id")
return
}
stringID = vf.String()
hasStringId = true
default:
err = fmt.Errorf("goon: ID field must be int64 or string in %v", t.Name())
return
}
} else if tagValue == "kind" {
if vf.Kind() == reflect.String {
if kind != "" {
err = fmt.Errorf("goon: Only one field may be marked kind")
return
}
kind = vf.String()
if kind == "" && len(tagValues) > 1 && tagValues[1] != "" {
kind = tagValues[1]
}
}
} else if tagValue == "parent" {
dskeyType := reflect.TypeOf(&datastore.Key{})
if vf.Type().ConvertibleTo(dskeyType) {
if parent != nil {
err = fmt.Errorf("goon: Only one field may be marked parent")
return
}
parent = vf.Convert(dskeyType).Interface().(*datastore.Key)
}
}
}
}
// if kind has not been manually set, fetch it from src's type
if kind == "" {
kind = g.KindNameResolver(src)
}
key = datastore.NewKey(g.Context, kind, stringID, intID, parent)
return
}
// DefaultKindName is the default implementation to determine the Kind
// an Entity has. Returns the basic Type of the src (no package name included).
func DefaultKindName(src interface{}) string {
v := reflect.ValueOf(src)
v = reflect.Indirect(v)
t := v.Type()
return t.Name()
}
func (g *Goon) setStructKey(src interface{}, key *datastore.Key) error {
v := reflect.ValueOf(src)
t := v.Type()
k := t.Kind()
if k != reflect.Ptr {
return fmt.Errorf("goon: Expected pointer to struct, got instead: %v", k)
}
v = reflect.Indirect(v)
t = v.Type()
k = t.Kind()
if k != reflect.Struct {
return fmt.Errorf(fmt.Sprintf("goon: Expected struct, got instead: %v", k))
}
idSet := false
kindSet := false
parentSet := false
for i := 0; i < v.NumField(); i++ {
tf := t.Field(i)
vf := v.Field(i)
if !vf.CanSet() {
continue
}
tag := tf.Tag.Get("goon")
tagValues := strings.Split(tag, ",")
if len(tagValues) > 0 {
tagValue := tagValues[0]
if tagValue == "id" {
if idSet {
return fmt.Errorf("goon: Only one field may be marked id")
}
switch vf.Kind() {
case reflect.Int64:
vf.SetInt(key.IntID())
idSet = true
case reflect.String:
vf.SetString(key.StringID())
idSet = true
}
} else if tagValue == "kind" {
if kindSet {
return fmt.Errorf("goon: Only one field may be marked kind")
}
if vf.Kind() == reflect.String {
if (len(tagValues) <= 1 || key.Kind() != tagValues[1]) && g.KindNameResolver(src) != key.Kind() {
vf.Set(reflect.ValueOf(key.Kind()))
}
kindSet = true
}
} else if tagValue == "parent" {
if parentSet {
return fmt.Errorf("goon: Only one field may be marked parent")
}
dskeyType := reflect.TypeOf(&datastore.Key{})
vfType := vf.Type()
if vfType.ConvertibleTo(dskeyType) {
vf.Set(reflect.ValueOf(key.Parent()).Convert(vfType))
parentSet = true
}
}
}
}
if !idSet {
return fmt.Errorf("goon: Could not set id field")
}
return nil
}