Firestore Golang run query with filters in transaction - firebase

I can't figure out how to run a firestore.Query in a transaction in the Golang Admin SDK.
The firestore.Transaction has a GetAll() method that takes an array of *firestore.DocumentRef, which I assume is how one would query multiple documents in a transaction. This works if I want to query an entire collection, since I can use tx.DocumentRefs to convert a *firestore.CollectionRef into document refs but there doesn't seem to be an equivalent method for queries (for example if I want to filter the collection).
In the NodeJS Admin SDK I could do something like:
admin.firestore().runTransaction(async (t) => {
const fooCollectionRef = admin.firestore().collection('foos').where('bar', '==', true);
const foosSnapshot = await t.get(fooCollectionRef);
// do stuff with the foos
})
How can I accomplish the same in Golang?

Use the Transaction.Documents method. It accepts a Queryier which can be a firestore.Query. See this method in the docs https://pkg.go.dev/cloud.google.com/go/firestore#Transaction.Documents
Following is a simple example:
err := client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
col := client.Collection("myCollection")
query := col.Where("myCondition", "==", 1)
docs := tx.Documents(query)
for {
d, err := docs.Next()
if err != nil {
if err == iterator.Done {
break
}
//handle error
}
//do something with the document
}
return nil
})
if err != nil {
// handle error.
}

Related

How to get any data from my real-time database in firebase using Go

I have simple test database with one row (key - value) and I can't get any data from database, although I use docs for Go (admin sdk - https://firebase.google.com/docs/database/admin/start?authuser=0#go). I tried search but info how to use by Go is very small
Here my code.
Here my json-file.
In playground don't work, need use json-file and execute from code editor/IDE.
All I found was a couple of video tutorials from the firebase guys themselves where they show how to connect, but the same thing does not work for me. All other information is about how to use firebase via android, iphone and web (js).
package main
import (
"context"
"fmt"
"log"
firebase "firebase.google.com/go"
"google.golang.org/api/option"
)
type Data struct {
TypeClient string `json:"typeClient,omitempty"`
}
var responseData structs.Data
func main() {
ctx := context.Background()
// Initialize the app with a custom auth variable, limiting the server's
access
ao := map[string]interface{}{"uid": "my-service-worker"}
conf := &firebase.Config{
DatabaseURL: "https://test-v06f06-default-rtdb.firebaseio.com",
AuthOverride: &ao,
}
// Fetch the service account key JSON file contents
opt := option.WithCredentialsFile("./test-v06f06-firebase-adminsdk-1ze0m-
bbf3b57ef3.json")
app, err := firebase.NewApp(ctx, conf, opt)
if err != nil {
log.Fatalln("Error initializing app:", err)
}
// fmt.Printf("%T\n", app)
client, err := app.Database(ctx)
if err != nil {
log.Fatalln("Error initializing database client:", err)
}
// fmt.Printf("%T\n", client)
// The app only has access as defined in the Security Rules
ref := client.NewRef("/admin_permission")
// fmt.Printf("%T\n", ref)
// Get data
if err := ref.Get(ctx, &responseData); err != nil {
log.Fatalln("Error reading from database:", err)
}
fmt.Println("Client Type -", responseData)}
In last row where print I get empty variable. The data is simply not written to the variable.
Help if you can. Thanks in advance.
screenshot tiny db

Adding nested struct to Firestore

I am trying to add a nested struct to Firestore and for some reason the contents added are all non-structs, which look something like:
The structs look something like this:
type Status struct {
Title string `json:"title,omitempty" firestore:"title,omitempty"`
Message string `json:"message,omitempty" firestore:"title,omitempty"`
}
type Config struct {
Name string `json:"name,omitempty" firestore:"name,omitempty"`
Status Status `json:"status,omitempty" firestore:"status,omitempty"`
}
And the code looks something like this:
import (
"context"
firebase "firebase.google.com/go/v4"
"google.golang.org/api/option"
)
func main() {
configuration := Config{
Name: "Test",
Status: Status{
Title: "hello",
Message: "hi",
},
}
ctx := context.Background()
config := firebase.Config{
ProjectID: "",
StorageBucket: "",
}
opt := option.WithCredentialsFile("firebase_config.json")
app, err := firebase.NewApp(ctx, &config, opt)
if err != nil {
panic(err)
}
// Get an auth client from the firebase.App
client, err := app.Firestore(ctx)
if err != nil {
panic(err)
}
_, _, err = client.Collection("forecast").Add(ctx, configuration)
if err != nil {
panic(err)
}
}
The above code only works for elements that are not in the nested structure.
Any help on this would be appreciated
Update 1
Status is not a sub collection but an object, something like:
{
"name": "Test",
"status": {
"title": "hello",
"message": "hi"
}
}
Firestore is optimized as a hash entry and retrieval datastore. As a consequence, it's better to create maps out of your structs. Structs are good for Go data modeling but when it's time to submit to the database, convert it to a map.
I usually just use Fatih's struct to map converter
It makes it easy to reason about your data on the Go side and still be able to submit it for storage.
Posting this as Community Wiki answer, based in the discussion of the comments.
The solution for this case seems to be adding values manually in a field of type Map. The steps to achieve that are the following: Access the Firebase console -> Firestore -> Create a document -> Add field of type Map. Following this order it's possible to create a field of type Map, that has the format needed to add data as described in the description of the question.
More information about this type, how it works, sort options, etc., can be found in the official documentation here: Supported data types.

Create new client for every operation to firestore?

I see examples like these in firestore Go Docs. Should we have to create a client like below for every operation to firestore or can we create a client during application startup and use the same client to perform an operation? Please let me know.
ctx := context.Background()
client, err := firestore.NewClient(ctx, "project-id")
if err != nil {
// TODO: Handle error.
}
defer client.Close()
type State struct {
Capital string `firestore:"capital"`
Population float64 `firestore:"pop"` // in millions
}
wr, err := client.Doc("States/Colorado").Create(ctx, State{
Capital: "Denver",
Population: 5.5,
})
if err != nil {
// TODO: Handle error.
}
fmt.Println(wr.UpdateTime)
No.Create the client once and re-use the same client. More details on this post - Should a Firestore client be created per a request with Google App Engine?

How to listen to firestore through RPC

I want to listen to real time changes in firestore and I am also only allowed to use Go. Since firestore SDK for Go doesn't have any option to listen for real time changes, I decided to use the firestore v1beta1 sdk.
I have written the following code to do that
func TestRPCHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
c, err := firestore.NewClient(context.Background())
databaseName := "projects/[project_name]/databases/(default)"
if err != nil {
panic(err)
}
stream, err := client.Listen(context.Background())
if err != nil {
panic(err)
}
request := &firestorepb.ListenRequest{
Database: databaseName,
TargetChange: &firestorepb.ListenRequest_AddTarget{
AddTarget: &firestorepb.Target{
TargetType: &firestorepb.Target_Documents{
Documents: &firestorepb.Target_DocumentsTarget{
Documents: []string{"projects/[project_name]/databases/(default)/[collection_name]"} ,
},
},
},
},
}
if err := stream.Send(request); err != nil {
panic(err)
}
if err := stream.CloseSend(); err != nil {
panic(err)
}
for {
resp, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
panic(err)
}
}
}
When I am doing this, the code does not detect any changes I bring about manually in the database. stream.Recv() just returns EOF and exits immediately. I even tried manually waiting by adding time.Sleep() but that does not help either.
You don't need the beta SDK or hacks to make this happen, I found the solution, it's pretty easy actually.
The https://firebase.google.com/docs/firestore/query-data/listen documentation does not contain an example for Go.
The source code of the firestore client API for Go has an unexported watchStream which we cannot directly use: https://github.com/googleapis/google-cloud-go/blob/master/firestore/watch.go#L130
Deep search of the repository shows that this is actually used on the DocumentSnapshotIterator and QuerySnapshotIterator at: https://github.com/googleapis/google-cloud-go/blob/master/firestore/docref.go#L644 and: https://github.com/googleapis/google-cloud-go/blob/master/firestore/query.go#L716.
The Collection contains a Snapshots method which returns the snapshot iterator that we want, after that all is easy, we just make an infivitive loop through its Next method.
Example:
cols, err := client.Collections(context.Background()).GetAll()
for _, col := range cols {
iter := col.Snapshots(context.Background())
defer iter.Stop()
for {
doc, err := iter.Next()
if err != nil {
if err == iterator.Done {
break
}
return err
}
for _, change := range doc.Changes {
// access the change.Doc returns the Document,
// which contains Data() and DataTo(&p) methods.
switch change.Kind {
case firestore.DocumentAdded:
// on added it returns the existing ones.
isNew := change.Doc.CreateTime.After(l.startTime)
// [...]
case firestore.DocumentModified:
// [...]
case firestore.DocumentRemoved:
// [...]
}
}
}
}
Yours, Gerasimos Maropoulos aka #kataras
Firebase's Get realtime updates with Cloud Firestore documentation currently indicates that Go is not yet supported.
// Not yet supported in Go client library

Query Firebase Firestore documents by the ID

As I got from 'Cloud Firestore Data Model' guide "each document is identified by a name." Is it possible to query a collection by that document identifier (which is the name or ID)?
For example, documents in the collection "Things" have IDs: 0, 1, 2 etc.:
Is it possible to query documents which IDs are less than 100?
You can query by documentId using the special sentinel FieldPath.documentId(), e.g.:
const querySnap = collection.where(firebase.firestore.FieldPath.documentId(), '<', '100').get();
But be aware that document IDs are strings and therefore this will include documents with ID '0' or '1', but not '2' since '2' > '100' lexicographically.
So if you want a numeric query, you'll need to write the document ID as a numeric field in the document and then do a normal query on it.
In python, you should use full documents names
from google.cloud import firestore
from google.cloud.firestore_v1.field_path import FieldPath
db = firestore.Client()
colRef = db.collection(u'docs')
filter = [db.document(u'docs/doc1'), db.collection(u'docs/doc3')]
query = colRef.where(FieldPath.document_id(), u'in', filter)
I was struggling to find this for the Golang Firebase SDK but finally got it. Hope this helps somebody out there!
package main
import (
"context"
"fmt"
"log"
"cloud.google.com/go/firestore"
firebase "firebase.google.com/go/v4"
"google.golang.org/api/option"
)
type (
Car struct {
ID string
Name string `firestore:"name"`
Make string `firestore:"make"`
Price float64 `firestore:"make"`
}
)
func main() {
ctx := context.Background()
// Use a service account
options := option.WithCredentialsFile("PATH/TO/SERVICE/FILE.json")
// Set project id
conf := &firebase.Config{ProjectID: "PROJECT_NAME"}
// Initialize app
app, err := firebase.NewApp(ctx, conf, options)
if err != nil {
log.Fatal(err)
}
// Get firestore client
client, err := app.Firestore(ctx)
if err != nil {
log.Fatal(err)
}
defer client.Close()
collectionRef := client.Collection("CAR_COLLECTION")
// Create docment list of documents from "CAR_COLLECTION"
var skipDocs []*firestore.DocumentRef
idList := []string{"001", "002", "003"}
for _, id := range idList {
skipDocs = append(skipDocs, collectionRef.Doc(id))
}
// firestore.DocumentID == "__name__"
docs, err := collectionRef.Where(firestore.DocumentID, "not-in", skipDocs).Documents(ctx).GetAll()
if err != nil {
log.Fatal(err)
}
var carList []Car
for _, doc := range docs {
var car Car
// Unmarshall item
doc.DataTo(&car)
car.ID = doc.Ref.ID
// Add car to list
carList = append(carList, car)
}
// Print car list
fmt.Println(carList)
}

Resources