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,不能是事务上下文。