import "code.google.com/p/appengine-go/appengine/datastore"
datastore提供App Engine的datastore服务。
实体是存储的基本单元,并且与一个键关联。一个键包含一个可选的父键,一个AppID字符串,一个类型字符串(也被认为是实体的类型),以及一个字符串StringID或者数字IntID,StringID也被认为是实体的名称或者键的名称。
创建一个StringID和IntID都为零值的键是合法的,这种键被称作不完全键,和任何已保存的实体都不关联。将一个实体存在datastore里一个不完全键下时,就会生成一个对应于该实体的唯一键,该键包含一个非零的IntID。
一个实体的内容是从大小写敏感的名称到值的映射,合法的值类型如下:
- 有符号整数(int, int8, int16, int32 and int64), - 布尔值, - 字符串, - 浮点数(float32, float64), - []byte(最大1mb长度), - 底层类型为以上类型的任意类型, - *Key, - time.Time(保存为微妙精度), - appengine.BlobKey, - 所有字段都是上述合法类型的结构体类型, - 以上任一类型的切片.
结构体的切片是合法的,含有切片的结构体也合法。但是,如果结构包含另一个结构,那么最多允许重复一次。这就排除了递归的定义结构体类型:某结构体T(直接或间接的)包含[]T。Get和Put函数加载或保存实体的内容。一个实体的内容应使用一个结构体的指针表示。
示例:
type Entity struct {
Value string
}
func handle(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
k := datastore.NewKey(c, "Entity", "stringID", 0, nil)
e := new(Entity)
if err := datastore.Get(c, k, e); err != nil {
http.Error(w, err.Error(), 500)
return
}
old := e.Value
e.Value = r.URL.Path
if _, err := datastore.Put(c, k, e); err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "old=%q\nnew=%q\n", old, e.Value)
}
GetMulti、PutMulti和DeleteMulti是Get、Put和Delete函数的批处理版本,他们接受[]*Key而不是*Key,并且在遭遇部分错误时,返回appengine.MultiError。
一个实体的内容可以被多个类型表示。一般应使用结构体的指针,但也可以是任何实现了PropertyLoadSaver接口的类型。如果使用结构体指针的话,就不需要显式的实现PropertyLoadSaver接口了,datastore会自动使用反射进行转换。当然了,如果你闲的蛋疼实现了该接口,那么就会优先使用该接口的方法。结构体指针更稳定更易于使用,相对的PropertyLoadSaver更灵活。
提供给Get和Put函数的实际类型不必相同,即使在不同请求中也没事。一般来说,任何实体都是保存为一系列属性,也是属性对属性的装载到目标值里。当装载进一个结构体指针时,如果实体不能被完整的表达出来(如缺失了某个字段),将会返回ErrFieldMismatch,但是调用者可以自行决定怎么处理该错误(记录/恢复/忽略)。
默认情况下,对于结构体的指针,所有属性都潜在的被索引,属性名一般和字段名一致(因此必须以大写字母起始)。字段可以使用标签,格式为`datastore:"name,options"`。标签名name为属性名,必须是一个或多个"."连接起来的Go标识符,但可以小写字母起始。空的标签名代表使用字段名,标签名为"-"代表忽略该字段。如果options是"noindex"表示该字段不应被索引,如果options是""则逗号可省略。不支持其他options。
字段(除了[]byte以外)都默认被索引。超过500字符的字符串不能被索引,用于保存长字符串的字段应使用标签注明options为"noindex"。
示例:
// A和B重命名为a和b
// A、C和J不被索引
// D的标签相当于没有标签(E)
// I会被datastore完全忽略
// J的标签信息既有datastore的,也有json包的
type TaggedStruct struct {
A int `datastore:"a,noindex"`
B int `datastore:"b"`
C int `datastore:",noindex"`
D int `datastore:""`
E int
I int `datastore:"-"`
J int `datastore:",noindex" json:"j"`
}
如果结构体内包含另一个结构体,那么嵌套/内嵌的结构体会被压平。如下例所示,下面给出的定义:
type Inner1 struct {
W int32
X string
}
type Inner2 struct {
Y float64
}
type Inner3 struct {
Z bool
}
type Outer struct {
A int16
I []Inner1
J Inner2
Inner3
}
结构体Outer产生的属性等价于如下的情况:
type OuterEquivalent struct {
A int16
IDotW []int32 `datastore:"I.W"`
IDotX []string `datastore:"I.X"`
JDotY float64 `datastore:"J.Y"`
Z bool
}
如果Outer内嵌的Inner3字段设标签为`datastore:"Foo"`,那么等价的字段将是FooDotZ bool `datastore:"Foo.Z"`。
如果一个结构体字段标记为"noindex",它所有内含的字段压平后都会设为"noindex"。
一个实体的内容也可以被任何实现了PropertyLoadSaver 接口的类型表示。该类型可以是结构体的指针,但也可以是别的类型。Datastore包会调用Load方法获取实体的内容,Save方法来保存实体的内容。合理的用法包括获取未存储的字段、验证字段和只在值为正时索引字段等。
示例:
type CustomPropsExample struct {
I, J int
// Sum未存储,但它应该总是等于I + J
Sum int `datastore:"-"`
}
func (x *CustomPropsExample) Load(c <-chan Property) error {
// 像通常那样获取I和J
if err := datastore.LoadStruct(x, c); err != nil {
return err
}
// 取得Sum字段的值
x.Sum = x.I + x.J
return nil
}
func (x *CustomPropsExample) Save(c chan<- Property) error {
defer close(c)
// 验证Sum字段
if x.Sum != x.I + x.J {
return errors.New("CustomPropsExample has inconsistent sum")
}
// 像通常那样保存I和J。
// 下面的代码出于示范的目的手动执行,
// 等价于调用"return datastore.SaveStruct(x, c)"。
c <- datastore.Property{
Name: "I",
Value: int64(x.I),
}
c <- datastore.Property{
Name: "J",
Value: int64(x.J),
}
return nil
}
*PropertyList类型实现了PropertyLoadSaver,因此可以保存任意实体的内容。
使用实体的属性或键的父子关系进行检索。执行query时,输出结果的迭代器,键集或者键值对集。检索可以重用,并发安全。但是迭代器是并发不安全的。
查询是不可变的,要么是调用NewQuery创建,要么是现有的查询调用Filter或Order之类的方法产生的。一个query一般都是由调用NewQuery后跟一条零到多个的这些方法构成的。这些方法有:
- Ancestor和Filter 约束/过滤一个query返回的实体集 - Order 控制返回的实体集里实体的顺序 - Project 约束返回的字段 - Distinct 对映射构造的实体进行去重 - KeysOnly 让迭代器只返回键,而不返回键值对 - Start、End、Offset和Limit 定义匹配得到的实体集应返回的子序列 Start和End接受Cursor类型,Offset和Limit接受整数 Start和Offset控制第一个结果的位置,End和Limit控制最后一个结果的位置 如果Start和Offset都设置了,offset以Start为基准 如果End和Limit都设置了,那么第一个到达的起效,Limit以Start+Offset为基准,而非End 如果limit为负数,表示不设约束
示例:
type Widget struct {
Description string
Price int
}
func handle(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
q := datastore.NewQuery("Widget").
Filter("Price <", 1000).
Order("-Price")
b := new(bytes.Buffer)
for t := q.Run(c); ; {
var x Widget
key, err := t.Next(&x)
if err == datastore.Done {
break
}
if err != nil {
serveError(c, w, err)
return
}
fmt.Fprintf(b, "Key=%v\nWidget=%#v\n\n", key, x)
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
io.Copy(w, b)
}
RunInTransaction在一次事务中运行一个函数。
示例:
type Counter struct {
Count int
}
func inc(c appengine.Context, key *datastore.Key) (int, error) {
var x Counter
if err := datastore.Get(c, key, &x); err != nil && err != datastore.ErrNoSuchEntity {
return 0, err
}
x.Count++
if _, err := datastore.Put(c, key, &x); err != nil {
return 0, err
}
return x.Count, nil
}
func handle(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
var count int
err := datastore.RunInTransaction(c, func(c appengine.Context) error {
var err1 error
count, err1 = inc(c, datastore.NewKey(c, "Counter", "singleton", 0, nil))
return err1
}, nil)
if err != nil {
serveError(c, w, err)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "Count=%d", count)
}
var (
// Get和Next之类的函数在接受的参数类型不合法时返回本错误
ErrInvalidEntityType = errors.New("datastore: invalid entity type")
// 当提供的键不合法时返回本错误
ErrInvalidKey = errors.New("datastore: invalid key")
// 当给出的键没有发现对应的实体时,返回本错误
ErrNoSuchEntity = errors.New("datastore: no such entity")
)
var Done = errors.New("datastore: query has no more results")
当一个query的迭代器迭代结束时,返回Done。
var ErrConcurrentTransaction = errors.New("datastore: concurrent transaction")
当一个事务因为并发事务的冲突回滚时,将返回本错误。
type ErrFieldMismatch struct {
StructType reflect.Type
FieldName string
Reason string
}
当字段类型不匹配,或者有字段未导出到目标结构体时返回本类型错误。StructType为提供的结构体的类型。
func (e *ErrFieldMismatch) Error() string
type Key struct {
// 内含隐藏字段
}
Key代表一个datastore保存的实体的键,是不可变的。
func DecodeKey(encoded string) (*Key, error)
解码一个不透明描述(*Key的Encode方法生成)来获取键。
func NewIncompleteKey(c appengine.Context, kind string, parent *Key) *Key
创建一个不完全键,kind参数不能为空。
func NewKey(c appengine.Context, kind, stringID string, intID int64, parent *Key) *Key
创建一个新的键,kind参数不可为空。stringID和intID两个参数至少应有一个为零值。如果都为零值,则键为不完全键。Parent参数必须是完全键或者nil。
func (k *Key) AppID() string
返回键的AppID。
func (k *Key) IntID() int64
返回键的IntID,可以为0。
func (k *Key) StringID() string
返回键的StringID(也被视为实体的名字或键的名字),可以是""。
func (k *Key) Kind() string
返回键的类型(也被视为实体的类型)。
func (k *Key) Parent() *Key
返回键的父键,可以是nil。
func (k *Key) Incomplete() bool
返回键是否不完整,即是否键的StringID和IntID都为零值。
func (k *Key) Namespace() string
返回键的命名空间。
func (k *Key) Equal(o *Key) bool
返回两个键是否相等。
func (k *Key) String() string
返回键的字符串表示。
func (k *Key) Encode() string
返回键的不透明描述,该描述可以用于HTML文件和URL中,并且和Python、Jave运行时环境兼容。
func (k *Key) GobEncode() ([]byte, error)
func (k *Key) GobDecode(buf []byte) error
func (k *Key) MarshalJSON() ([]byte, error)
func (k *Key) UnmarshalJSON(buf []byte) error
type Property struct {
// Name是属性名
Name string
// Value是属性的值,合法类型如下
// - int64
// - bool
// - string
// - float64
// - *Key
// - time.Time
// - appengine.BlobKey
// - appengine.GeoPoint
// - []byte (最长1mb)
// 这个集合比合法结构体字段的类型集合要小
// 属性的值不能是切片(除了[]byte以外),应使用多个属性替代
// 属性的类型必须显式的属于上面的列表,只是底层类型在该列表是不行的
// 如"type myInt64 int64",myInt64类型就是非法的
// 因此,比合法结构体字段类型要严格的多
//
// 当从索引装载实体时,如通过映射查询,Value会拥有不透明的类型
// 当使用映射查询时,应该将实体装载到结构体中,而非PropertyLoadSaver接口中
//
// 一个Value也可以是nil的接口值;等价于Python的None,但不能被go结构体直接表示
// 装载一个nil值的属性到结构体会将对应的字段设为零值
Value interface{}
// NoIndex控制datastore是否应索引该属性
NoIndex bool
// 控制实体是否可以包含多个同名的属性,用来表示类型为[]T而非T的字段的一系列值
// 即使某个特定的实例的某个名称的属性只有一个,也应设置该Multiple为真
Multiple bool
}
Property是附加了一些元数据的键值对。一个datastore的实体的内容都以Property序列的形式保存和加载。一个实体可以包含同名的多个Property,这些Property都应设值Multiple为真,表示一个切片。
type PropertyLoadSaver interface {
Load(<-chan Property) error
Save(chan<- Property) error
}
PropertyLoadSaver接口可以与[]Property类型相互转换。
type PropertyList []Property
PropertyList为 []Property实现了PropertyLoadSaver接口。
func (l *PropertyList) Load(c <-chan Property) error
装载所有提供的属性到l,本函数不会先重置*l为空切片。
func (l *PropertyList) Save(c chan<- Property) error
保存l的所有属性为一个Property的切片。
type Query struct {
// 内含隐藏字段
}
Query表示一个datastore的查询。
func NewQuery(kind string) *Query
为某类型的实体创建一个新的Query。
kind为空表示返回所有实体,包括被其他App Engine产品创建和管理的实体,这被称为无类型实体,无类型实体不能对属性值进行过滤和排序。
func (q *Query) Ancestor(ancestor *Key) *Query
返回具有同一根键的一个衍生查询,过滤器。Ancestor参数不能为nil。
func (q *Query) Filter(filterStr string, value interface{}) *Query
返回一个对字段进行过滤的衍生查询。filterStr参数必须是一个字段名后跟可选的空格以及一个操作符,操作符为">"、"<"、">="、"<="和"="其中之一。字段根据操作符与提供的value进行比较。
func (q *Query) EventualConsistency() *Query
返回会输出具有最终一致性的结果的衍生查询,只对Ancestor方法有影响。
func (q *Query) Project(fieldNames ...string) *Query
返回一个只输出给定字段的映射查询。
func (q *Query) Distinct() *Query
返回对映射字段集合进行去重的一个衍生的映射查询,只能用于映射查询。
func (q *Query) KeysOnly() *Query
返回一个只输出键的衍生的唯键查询,本方法不能用于映射查询。
func (q *Query) Count(c appengine.Context) (int, error)
返回查询得到的结果的数量。
func (q *Query) Order(fieldName string) *Query
返回一个根据参数进行排序后的查询。函数将按参数递增顺序排序的(从小到大输出),如果要反过来,在fieldName参数的字段名前添加'-'。
func (q *Query) Start(c Cursor) *Query
返回一个从给定位置起始的查询。
func (q *Query) End(c Cursor) *Query
返回一个在给定位置结束的查询。
func (q *Query) Limit(limit int) *Query
返回一个限定结果个数的查询,limit小于0代表无限制。
func (q *Query) Offset(offset int) *Query
返回一个跳过起始offset个结果的查询,offset不能小于0。
func (q *Query) Run(c appengine.Context) *Iterator
在给定的上下文环境下输出查询结果的迭代器。
func (q *Query) GetAll(c appengine.Context, dst interface{}) ([]*Key, error)
方法在给定的上下文环境下执行查询,将结果附加进dst并返回所有对应的键。
对dst的要求和GetMulti函数一样。返回的键和附加进dst的实体一一对应,如果是唯键查询,会忽略dst。
type Iterator struct {
// 内含隐藏字段
}
Iterator是一个查询返回的结果的迭代器。
func (t *Iterator) Cursor() (Cursor, error)
返回迭代器当前位置的Cursor。
func (t *Iterator) Next(dst interface{}) (*Key, error)
返回下一个结果的键,当没有下一个时,会返回Done作为错误值。
如果查询未使用KeysOnly,并且dst不是nil,方法还会将返回的key对应的实体装载到dst中,dst的要求和Get函数一样,也可能返回和Get函数一样的错误。
type Cursor struct {
// 内含隐藏字段
}
Cursor 代表一个迭代器的位置。它可以编解码为一个不透明字符串。一个Cursor可以用于不同的HTTP请求,但是只有在类型、父键、过滤和顺序约束都一致的情况下才代表同一个位置。
func DecodeCursor(s string) (Cursor, error)
解码一个表示Cursor的base64字符串获取该Cursor。
func (c Cursor) String() string
返回一个Cursor的base64字符串表示。
type TransactionOptions struct {
// 表示是否事务跨越多个实体组。
// 单实体组的事务指它使用的所有键都有同一个根键。
// 注意跨组事务和单组事务的行为是不一样的。
// 特别注意,在全局查询中,更倾向于出现跨组事务。
// 即使单组事务,将XG设为真也是合法的;反过来就不行。
XG bool
}
TransactionOptions是执行事务的选项。
func AllocateIDs(c appengine.Context, kind string, parent *Key, n int) (low, high int64, err error)
AllocateIDs返回一个n个整数ID的范围,可用于和给出的kind和父键联合构建key。 kind不能为空,parent可以为nil。返回的范围内的整数ID不会被datastore键生成器所使用,可以用于NewKey函数而不会导致冲突。
合法的整数ID 要求不小于low且小于high。如果没有出错,返回值low + n == high。
func Get(c appengine.Context, key *Key, dst interface{}) error
函数将key对应的实体装载到dst,dst必须是结构体的指针或者实现了PropertyLoadSaver接口。如果没有对应key的实体,函数返回ErrNoSuchEntity。
dst中与实体不匹配的字段不会被修改,匹配的切片类型字段不会重置,对应的属性值直接添加到后面。因此,强烈建议对每次调用Get函数都使用指向零值结构体的指针。
如果某属性对应的字段类型错误时,或者某属性在dst结构体中无对应字段或字段隐藏时,返回ErrFieldMismatch类型错误。这个错误只会在dst是结构体指针时可能被返回。
func GetMulti(c appengine.Context, key []*Key, dst interface{}) error
函数是Get函数的批处理版本。
dst必须是[]S、[]*S、[]I、[]P,其中S表示结构体类型,I表示接口类型,P表示实现了PropertyLoadSaver接口的某种类型,如果dst类型为[]I,其中每个元素都应为结构体指针或者实现了PropertyLoadSaver接口。
有一个特例,PropertyList类型不适用于dst,虽然该类型是结构体的切片,PropertyList类型视为非法以免当我们需要[]PropertyList时被错误的传递。
func Put(c appengine.Context, key *Key, src interface{}) (*Key, error)
将src保存到key对应的实体中,src必须是结构体指针或者实现了PropertyLoadSaver接口。如果是结构体指针,结构体的所有隐藏字段将忽略。如果key是不完全键,返回的键为datastore生成的对应该实体的专有完全键。
func PutMulti(c appengine.Context, key []*Key, src interface{}) ([]*Key, error)
函数为Put的批处理版本,对src的要求同GetMulti的dst参数。
func Delete(c appengine.Context, key *Key) error
删除一个键对应的实体。
func DeleteMulti(c appengine.Context, key []*Key) error
Delete的批处理版本。
func LoadStruct(dst interface{}, c <-chan Property) error
从c中装载属性到dst,直到c关闭。dst必须是结构体的指针。
func SaveStruct(src interface{}, c chan<- Property) error
将src的属性保存到c,写完后会关闭c。src必须是结构体的指针。
func RunInTransaction(c appengine.Context, f func(tc appengine.Context) error, opts *TransactionOptions) error
函数在一次事务中执行f。它使用一个事务上下文tc调用f,f应在全部的操作中都使用tc。
如果f返回nil,RunInTransaction会尝试递交事务,如果成功会返回nil。如果因为事务冲突递交失败,函数会重试f,每次重试都会重新产生一个新的事务上下文tc。在递交失败3次后,函数会放弃并返回ErrConcurrentTransaction。
如果f返回非nil的错误值,则datastore不会被修改,函数不会重试,而直接返回该错误。
注意当f返回时,事务还没有递交。调用代码必须注意不能假设f的任何操作已经被递交,除非函数返回了nil。
不支持嵌套事务,c只能是从user或appengine包获取的Context,不能是事务上下文。