This repository has been archived by the owner on Aug 8, 2022. It is now read-only.
forked from philippgille/gokv
/
datastore.go
168 lines (144 loc) · 4.46 KB
/
datastore.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package datastore
import (
"context"
"errors"
"time"
"cloud.google.com/go/datastore"
"google.golang.org/api/option"
"github.com/philippgille/gokv/encoding"
"github.com/philippgille/gokv/util"
)
const kind = "gokv"
// entity is a struct that holds the actual value as a slice of bytes named "V"
// (translated to lowercase "v" in Cloud Datastore).
// Cloud Datastore requires a pointer to a struct as value.
// The key doesn't need to be part of the struct.
type entity struct {
V []byte `datastore:"v,noindex"`
}
// Client is a gokv.Store implementation for Cloud Datastore.
type Client struct {
c *datastore.Client
codec encoding.Codec
}
// Set stores the given value for the given key.
// Values are automatically marshalled to JSON or gob (depending on the configuration).
// The key must not be "" and the value must not be nil.
func (c Client) Set(k string, v interface{}) error {
if err := util.CheckKeyAndValue(k, v); err != nil {
return err
}
// First turn the passed object into something that Cloud Datastore can handle.
data, err := c.codec.Marshal(v)
if err != nil {
return err
}
tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
key := datastore.Key{
Kind: kind,
Name: k,
}
src := entity{
V: data,
}
_, err = c.c.Put(tctx, &key, &src)
return err
}
// Get retrieves the stored value for the given key.
// You need to pass a pointer to the value, so in case of a struct
// the automatic unmarshalling can populate the fields of the object
// that v points to with the values of the retrieved object's values.
// If no value is found it returns (false, nil).
// The key must not be "" and the pointer must not be nil.
func (c Client) Get(k string, v interface{}) (found bool, err error) {
if err := util.CheckKeyAndValue(k, v); err != nil {
return false, err
}
tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
key := datastore.Key{
Kind: kind,
Name: k,
}
dst := new(entity)
err = c.c.Get(tctx, &key, dst)
if err != nil {
if err == datastore.ErrNoSuchEntity {
return false, nil
}
return false, err
}
data := dst.V
return true, c.codec.Unmarshal(data, v)
}
// Delete deletes the stored value for the given key.
// Deleting a non-existing key-value pair does NOT lead to an error.
// The key must not be "".
func (c Client) Delete(k string) error {
if err := util.CheckKey(k); err != nil {
return err
}
tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
key := datastore.Key{
Kind: kind,
Name: k,
}
return c.c.Delete(tctx, &key)
}
// Close closes the client.
func (c Client) Close() error {
return c.c.Close()
}
// Options are the options for the Cloud Datastore client.
type Options struct {
// ID of the Google Cloud project.
ProjectID string
// Path to the credentials file. For example:
// "/home/user/Downloads/[FILE_NAME].json".
// If you don't set a credentials file explicitly,
// the GCP SDK will look for the file path in the
// GOOGLE_APPLICATION_CREDENTIALS environment variable.
// Optional ("" by default, leading to a lookup via environment variable).
CredentialsFile string
// Encoding format.
// Optional (encoding.JSON by default).
Codec encoding.Codec
}
// DefaultOptions is an Options object with default values.
// CredentialsFile: "", Codec: encoding.JSON
var DefaultOptions = Options{
Codec: encoding.JSON,
// No need to set CredentialsFile because its Go zero value is fine.
}
// NewClient creates a new Cloud Datastore client.
//
// You must call the Close() method on the store when you're done working with it.
func NewClient(options Options) (Client, error) {
result := Client{}
// Precondition check
if options.ProjectID == "" {
return result, errors.New("The ProjectID in the options must not be empty")
}
// Set default values
if options.Codec == nil {
options.Codec = DefaultOptions.Codec
}
// Don't pass a context with timeout to NewClient,
// because the Dial() call in NewClient() is non-blocking anyway
// and it would interfere with credential refreshing.
var dsClient *datastore.Client
var err error
if options.CredentialsFile == "" {
dsClient, err = datastore.NewClient(context.Background(), options.ProjectID)
} else {
dsClient, err = datastore.NewClient(context.Background(), options.ProjectID, option.WithCredentialsFile(options.CredentialsFile))
}
if err != nil {
return result, err
}
result.c = dsClient
result.codec = options.Codec
return result, nil
}