Datastore KeysOnly pricing & implementation in Go - google-app-engine

I know KeysOnly queries are cheaper, but I'm wondering if I'm implementing it correctly.
For instance, if I make a GetAll func for KeysOnly (like the one below), should I still initialize the users struct ([]User)?
func GetAllUserKeysByFilter(ctx context.Context, filters ...UserFilter) ([]*datastore.Key, error) {
var users []User
query := datastore.NewQuery("User").KeysOnly()
for _, filter := range filters {
if filter.Age != 0 {
query = query.Filter("Age =", filter.Age)
}
if filter.Limit != 0 {
query = query.Limit(filter.Limit)
}
}
keys, err := DatastoreClient().GetAll(ctx, query, &users)
if err != nil {
return nil, err
}
return keys, nil
}
Moreover, what if I only want to get key of a direct call (where I already know the namekey - in this case, namekey == username) for the purpose of checking if this key exists or not, then what's the right way to do it?
Right now I do like this:
func GetUserByNameKey(ctx context.Context, key *datastore.Key) (User, error) {
var user User
err := DatastoreClient().Get(ctx, key, &user)
if err != nil {
return user, err
}
return user, nil
}
But can it be converted somehow to become cheaper since I only care about finding out if the User key exists or not?
Maybe it would be cheaper to use the GetAllUserKeysByFilter with Limit == 1?

Query / Get operation cost
The cost for a Get operation (get by key) is 1 read op (and no small ops). Whether there is an actual entity for the key or not does not matter in terms of cost: both will cost you 1 read op. The "hard" part is finding the entity for the key, or finding out that there is no such entity, returning the result is the "easy" part.
The cost for keys-only queries is 1 read op + 1 small op for each result (for each result key).
For completeness, the cost for a "normal" query (meaning not keys-only) is 1 read op + 1 read op for each returned entity.
Example: A normal query returning 10 entities is 11 read ops. A keys-only query returning 10 keys is 1 read op + 10 small ops. A Get call is 1 read op (regardless of whether the entity is found and returned).
So calling GetAllUserKeysByFilter() with limit = 1 would theoretically cost more: it costs 1 read operation and 1 small operation. But since small operations are free (cost no real money), they are basically equal. But know that a Get() operation (by key) is likely to be faster unless your entity is large. If your entity is large, use a keys-only query with limit = 1. If your entity is small, Get() will most likely be faster (measure if it does matter to you).
Some information about query costs can be found on this page: Datastore Queries
Keys-only query example
Your implementation of the keys-only query is unnecessarily complex. When you're executing a keys-only query, the dst destination parameter of Query.GetAll() is omitted (not used), only the return value is useful which is exactly that: the slice of result keys:
If q is a “keys-only” query, GetAll ignores dst and only returns the keys.
So basically your solution also works, but the users slice will not be used. Your GetAllUserKeysByFilter() function should look like this:
func GetAllUserKeysByFilter(ctx context.Context, filters ...UserFilter)
([]*datastore.Key, error) {
query := datastore.NewQuery("User").KeysOnly()
for _, filter := range filters {
if filter.Age != 0 {
query = query.Filter("Age =", filter.Age)
}
if filter.Limit != 0 {
query = query.Limit(filter.Limit)
}
}
keys, err := DatastoreClient().GetAll(ctx, query, nil)
if err != nil {
return nil, err
}
return keys, nil
}
P.S. If you want to reuse GetAllUserKeysByFilter() for both keys-only and normal queries, you may continue to do so, but then you should also return the users slice.

Related

Querying with an array of arbitrary keys on Google datastore in Golang

A continuation from this question:
Doing a "IN Array" query on google app engine datastore with golang
Right now, I am following the suggestion from the previous question on querying with an array of keys/ids ids []int64. These IDs may or may not actually exist (they have been deleted, but the reference on other instances have not been removed).
My method of trying to obtain these instances looks like so:
var keys []*datastore.Key
for _, id := range ids {
keys = append(keys, datastore.NewKey(c, "Category", "", id, nil))
}
categories := make([]Category, len(keys))
err := datastore.GetMulti(c, keys, categories)
if err != nil {
return nil, err
}
for i := 0; i < len(categories); i++ {
categories[i].Id = keys[i].IntID()
}
However, it errors out throwing me:
datastore: no such entity
I could on the other hand grab each one individually, but is there a more efficient way to approach this?
You need to type assert the error to an appengine.MultiError. This way you can get access to the errors for an individual entity.
if me, ok := err.(appengine.MultiError); ok {
for i, e := range me {
// e != nil if entity i failed
}
} else {
// something else went wrong (timeout, etc).
}
See the docs for MultiError here

Doing a "IN Array" query on google app engine datastore with golang

Is there a way to do a query with ids []int64 on datastore? I've tried the following with no avail.
Errors out
q := datastore.NewQuery("Category").Filter("Id IN", ids)
Just gets me all the the categories in the datastore
for _, id := range ids {
q.Filter("Id =", id)
}
After icza's answer
var keys []*datastore.Key
for _, id := range ids {
keys = append(keys, datastore.NewKey(c, "Category", "", id, nil))
}
categories := make([]Category, len(keys))
err := datastore.GetMulti(c, keys, categories)
if err != nil {
return nil, err
}
Generally "IN" filters are not supported by the Datastore. The documentation of Query.Filter() lists the allowed operators:
">", "<", ">=", "<=", or "="
What you can do is execute a separate query for each of the elements in the array you want to filter by. Also if the elements are in a continous range, you can substitute the IN with id>=min and id<=max. E.g.:
ids := []int64{1,2,3,4}
q := datastore.NewQuery("Category").Filter("Id>=", 1).Filter("Id<=", 4)
Also note that while the IN is not supported in general, if the property is the entity key itself, you can get a list of entities specified by an array of their keys using the datastore.GetMulti() function:
func GetMulti(c appengine.Context, key []*Key, dst interface{}) error
Note:
Your 2nd attempt returns all entities because you call Filter() on your query, but you don't store the return value, so the query you end up executing will have no filters at all. Query.Filter() returns a derivative query which contains the filter you just specified, you have to use the returned Query ongoing. So it should be:
q = q.Filter("Id=", id)
But even this won't work either: if multiple filters are specified, they will be in logical AND connection so it will most likely give you 0 results as I suspect no category will exists where Id is a list and which would contain all the ids you want to filter by.

Golang - Appengine datastore filter query with []byte comparison

I am trying to perform a filter query on a set of entities in the datastore, but the entity field I am trying to query against, with the equality operator, is of type []byte and I don't know if appengine datastore can perform this comparison
This is my entity:
type Data struct {
Id int64 `json:"id"`
Version int32 `json:"-"`
HMAC []byte `json:"-"`
Status string `json:"status"`
}
And here is my query logic
func (view *DataView) GetDataByHMAC(hmac []byte) (Data, error) {
view_key := datastore.NewKey(view.context, "View", "data-view", 0, nil)
data := make([]Data, 0)
query := datastore.
NewQuery("ViewData").
Ancestor(view_key).
Filter("HMAC = ", hmac)
_, err := query.GetAll(view.context, &data)
if err != nil {
return Data{}, err
}
if len(data) == 0 {
return Data{}, ErrNoData
}
return data[0], nil
}
It builds but does not return any results, even after programmatically retrying over the course of 10 seconds so i do not believe it is an issue of eventual consistency between the datastore and the view data that I've stored there.
My main question is: does the appengine datastore allow for a query to use a comparison filter on a field with type []byte?
In 1.9.11, the ByteString type was introduced to the datastore package. It can be used for storing short, indexed byte slices.
If you change your entity to the following, it should work:
type Data struct {
ID int64 `json:"id"`
Version int32 `json:"-"`
HMAC datastore.ByteString `json:"-"`
Status string `json:"status"`
}
More info: https://developers.google.com/appengine/docs/go/datastore/entities#Go_Properties_and_value_types
The answer to your main question is No, because []byte is stored as blob, which is not indexed by the app engine datastore.
queries with a filter or sort order on the unindexed property
will never match that entity.
Note: In addition to any unindexed properties you declare explicitly,
those typed as []byte are automatically treated as unindexed.
Here is the documentation: https://developers.google.com/appengine/docs/go/datastore/indexes#Go_Unindexed_properties

Kindless Queries in App Engine Go

In Python it's
q = db.Query()
q.ancestor(ancestor_key)
I tried:
q := datastore.NewQuery("")
q.Ancestor(ancestor_key)
I get the error "datastore: empty kind" when running GetAll
I also tried:
q := &datastore.Query{}
q.Ancestor(ancestor_key)
I get the error "datastore: empty query kind"
Thanks in advance for any help with this matter.
func NewQuery
func NewQuery(kind string) *Query
NewQuery creates a new Query for a specific entity kind. The kind must
be non-empty.
In your code,
q := datastore.NewQuery("")
the kind is empty.
Rich Churcher's comment seems to be right, at least at this point in time.
I don't think the Python kindless ancestor query is supported in Go.
For a moment there I thought you could use the ancestor key's Kind()
method, then I had some more coffee and came to my senses.
GetAll doesn't seem to work, but you can do kindless queries.
ctx := appengine.NewContext(r)
q := datastore.NewQuery("")
for it := q.Run(ctx); ; {
key, err := t.Next(nil)
if err == datastore.Done {
break
}
if err != nil {
break
}
fmt.Printf("%v\n", key)
}

Trouble with Queries/Datastore in Google App Engine - Go API

I'm playing with Google App Engine using the Go APIs and despite everything I've tried I can't get the Queries to return data that is in the datastore. I can see that Put() works as expected, and the entities are listable/accessible from inspection of the Admin Console available from dev_appserver.py
The struct I'm storing is defined this way:
type TweetData struct {
Id int64 `datastore:",noindex" json:"id"`
Text string `datastore:",noindex" json:"text"`
Screen_name string `json:"screen_name"`
}
And my calls to query it are such:
func getDatastoreTweets(c appengine.Context, twitterUser string) []*TweetData {
q := datastore.NewQuery("TweetData").Filter("Screen_name =", twitterUser).Order("-Id").Limit(10)
var oldTweets []*TweetData
if _, err := q.GetAll(c, &oldTweets); err != nil {
fmt.Printf("Getall had non-nil error! %v\n", err)
}
return oldTweets
}
For this query, err is never non-nil, but always returns 0 results, even when the Console tells me there are many. I've tried it without the Filter call as well, after the guestbook example the SDK provides, to no avail.
In case it's an issue with keys (unlikely if I understand correctly, because I'm querying on a property), the call to Put is as follows:
// tweetData passed in via parameter...
key := datastore.NewIncompleteKey(c, "TweetData", nil)
_, err := datastore.Put(c, key, &tweetData)
Any help would be appreciated, thanks! ^_^
The query ask for ordering by Id desc, while the index field is un-indexed, you should either:
rewrite the TweetData annotation to index Id field:
Id int64 `json:"id"`
Remove the Order clause of your query:
q := datastore.NewQuery("TweetData").Filter("Screen_name =", twitterUser).Limit(10)

Resources