1// Copyright 2019 The Go Cloud Development Kit Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package docstore_test
16
17import (
18	"context"
19	"fmt"
20	"io"
21	"log"
22	"time"
23
24	firestore "cloud.google.com/go/firestore/apiv1"
25	"github.com/aws/aws-sdk-go/aws/awserr"
26	"gocloud.dev/docstore"
27	_ "gocloud.dev/docstore/awsdynamodb"
28	_ "gocloud.dev/docstore/gcpfirestore"
29	"gocloud.dev/docstore/memdocstore"
30	"gocloud.dev/gcerrors"
31)
32
33type Player struct {
34	Name             string
35	Score            int
36	DocstoreRevision interface{}
37}
38
39func ExampleCollection_Actions_bulkWrite() {
40	// PRAGMA: This example is used on gocloud.dev; PRAGMA comments adjust how it is shown and can be ignored.
41	// PRAGMA: On gocloud.dev, hide lines until the next blank line.
42	ctx := context.Background()
43	var coll *docstore.Collection
44
45	// Build an ActionList to create several new players, then execute it.
46	// The actions may happen in any order.
47	newPlayers := []string{"Pat", "Mel", "Fran"}
48	actionList := coll.Actions()
49	for _, p := range newPlayers {
50		actionList.Create(&Player{Name: p, Score: 0})
51	}
52	if err := actionList.Do(ctx); err != nil {
53		log.Fatal(err)
54	}
55}
56
57func ExampleCollection_Actions_getAfterWrite() {
58	// PRAGMA: This example is used on gocloud.dev; PRAGMA comments adjust how it is shown and can be ignored.
59	// PRAGMA: On gocloud.dev, hide lines until the next blank line.
60	ctx := context.Background()
61	var coll *docstore.Collection
62
63	// Add a document to the collection, then retrieve it.
64	// Because both the Put and the Get refer to the same document,
65	// they happen in order.
66	got := Player{Name: "Pat"}
67	err := coll.Actions().Put(&Player{Name: "Pat", Score: 88}).Get(&got).Do(ctx)
68	if err != nil {
69		log.Fatal(err)
70	}
71	fmt.Println(got.Name, got.Score)
72}
73
74func ExampleCollection_Update() {
75	// PRAGMA: This example is used on gocloud.dev; PRAGMA comments adjust how it is shown and can be ignored.
76	// PRAGMA: On gocloud.dev, hide lines until the next blank line.
77	ctx := context.Background()
78	var coll *docstore.Collection
79
80	// Create a player.
81	pat := &Player{Name: "Pat", Score: 0}
82	if err := coll.Create(ctx, pat); err != nil {
83		log.Fatal(err)
84	}
85
86	// Set the score to a new value.
87	pat2 := &Player{Name: "Pat"}
88	err := coll.Actions().Update(pat, docstore.Mods{"Score": 15}).Get(pat2).Do(ctx)
89	if err != nil {
90		log.Fatal(err)
91	}
92
93	// Increment the score.
94	err = coll.Actions().Update(pat, docstore.Mods{"Score": docstore.Increment(5)}).Get(pat2).Do(ctx)
95	if err != nil {
96		log.Fatal(err)
97	}
98}
99
100func ExampleOpenCollection() {
101	ctx := context.Background()
102	// Open a collection using the gcpfirestore package.
103	// You will need to blank-import the package for this to work:
104	//   import _ "gocloud.dev/docstore/gcpfirestore"
105	coll, err := docstore.OpenCollection(ctx, "firestore://my-collection")
106	if err != nil {
107		log.Fatal(err)
108	}
109	defer coll.Close()
110
111	_ = coll // Use the collection.
112}
113
114func ExampleCollection_As() {
115	// This example is specific to the gcpfirestore implementation; it demonstrates
116	// access to the underlying *cloud.google.com/go/firestore/apiv1.Client.
117
118	// You will need to blank-import the package for this to work:
119	//   import _ "gocloud.dev/docstore/gcpfirestore"
120
121	// The types exposed for As by gcpfirestore are documented in
122	// https://godoc.org/gocloud.dev/docstore/gcpfirestore#hdr-As
123
124	// This URL will open the collection using default credentials.
125	ctx := context.Background()
126	coll, err := docstore.OpenCollection(ctx,
127		"firestore://projects/myproject/databases/(default)/documents/mycollection?name_field=myID")
128	if err != nil {
129		log.Fatal(err)
130	}
131	defer coll.Close()
132
133	// Try to access and use the underlying mongo.Collection.
134	var fsClient *firestore.Client
135	if coll.As(&fsClient) {
136		_ = fsClient // TODO: Use the client.
137	} else {
138		log.Println("Unable to access firestore.Client through Collection.As")
139	}
140}
141
142func ExampleCollection_ErrorAs() {
143	// This example is specific to the awsdynamodb implementation.
144	// You will need to blank-import the package for this to work:
145	//   import _ "gocloud.dev/docstore/awsdynamodb"
146
147	// The types exposed for As by mongodocstore are documented in
148	// https://godoc.org/gocloud.dev/docstore/mongodocstore#hdr-As
149
150	// This URL will open the collection using default credentials.
151	ctx := context.Background()
152	coll, err := docstore.OpenCollection(ctx, "dynamodb://mytable?partition_key=partkey")
153	if err != nil {
154		log.Fatal(err)
155	}
156	defer coll.Close()
157
158	doc := map[string]interface{}{"_id": "a"}
159	if err := coll.Create(ctx, doc); err != nil {
160		var aerr awserr.Error
161		if coll.ErrorAs(err, &aerr) {
162			fmt.Println("got", aerr)
163		} else {
164			fmt.Println("could not convert error")
165		}
166	}
167}
168
169func ExampleQuery_Get() {
170	// PRAGMA: This example is used on gocloud.dev; PRAGMA comments adjust how it is shown and can be ignored.
171	// PRAGMA: On gocloud.dev, hide lines until the next blank line.
172	ctx := context.Background()
173	var coll *docstore.Collection
174
175	// Ask for all players with scores at least 20.
176	iter := coll.Query().Where("Score", ">=", 20).OrderBy("Score", docstore.Descending).Get(ctx)
177	defer iter.Stop()
178
179	// Query.Get returns an iterator. Call Next on it until io.EOF.
180	for {
181		var p Player
182		err := iter.Next(ctx, &p)
183		if err == io.EOF {
184			break
185		} else if err != nil {
186			log.Fatal(err)
187		} else {
188			fmt.Printf("%s: %d\n", p.Name, p.Score)
189		}
190	}
191}
192
193func ExampleQuery_Get_full() {
194	ctx := context.Background()
195	coll, err := memdocstore.OpenCollection("Name", nil)
196	if err != nil {
197		log.Fatal(err)
198	}
199	defer coll.Close()
200
201	// Add some documents to the collection.
202	err = coll.Actions().
203		Put(&Player{Name: "Pat", Score: 10}).
204		Put(&Player{Name: "Mel", Score: 20}).
205		Put(&Player{Name: "Fran", Score: 30}).
206		Do(ctx)
207	if err != nil {
208		log.Fatal(err)
209	}
210
211	// Ask for all players with scores at least 20.
212	iter := coll.Query().Where("Score", ">=", 20).OrderBy("Score", docstore.Descending).Get(ctx)
213	defer iter.Stop()
214
215	// Query.Get returns an iterator. Call Next on it until io.EOF.
216	for {
217		var p Player
218		err := iter.Next(ctx, &p)
219		if err == io.EOF {
220			break
221		} else if err != nil {
222			log.Fatal(err)
223		} else {
224			fmt.Printf("%s: %d\n", p.Name, p.Score)
225		}
226	}
227
228	// Output:
229	// Fran: 30
230	// Mel: 20
231}
232
233func Example_optimisticLocking() {
234	// PRAGMA: This example is used on gocloud.dev; PRAGMA comments adjust how it is shown and can be ignored.
235	// PRAGMA: On gocloud.dev, hide lines until the next blank line.
236	ctx := context.Background()
237
238	coll, err := memdocstore.OpenCollection("Name", nil)
239	if err != nil {
240		log.Fatal(err)
241	}
242	defer coll.Close()
243
244	// Create a player.
245	pat := &Player{Name: "Pat", Score: 7}
246	if err := coll.Create(ctx, pat); err != nil {
247		log.Fatal(err)
248	}
249	fmt.Println(pat) // memdocstore revisions are deterministic, so we can check the output.
250
251	// Double a player's score. We cannot use Update to multiply, so we use optimistic
252	// locking instead.
253
254	// We may have to retry a few times; put a time limit on that.
255	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
256	defer cancel()
257	for {
258		// Get the document.
259		player := &Player{Name: "Pat"}
260		if err := coll.Get(ctx, player); err != nil {
261			log.Fatal(err)
262		}
263		// player.DocstoreRevision is set to the document's revision.
264
265		// Modify the document locally.
266		player.Score *= 2
267
268		// Replace the document. player.DocstoreRevision will be checked against
269		// the stored document's revision.
270		err := coll.Replace(ctx, player)
271		if err != nil {
272			code := gcerrors.Code(err)
273			// On FailedPrecondition or NotFound, try again.
274			if code == gcerrors.FailedPrecondition || code == gcerrors.NotFound {
275				continue
276			}
277			log.Fatal(err)
278		}
279		fmt.Println(player)
280		break
281	}
282
283	// Output:
284	// &{Pat 7 1}
285	// &{Pat 14 2}
286}
287