Query Firebase Firestore documents by the ID - firebase

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)
}

Related

Do I need extra round trip to firestore for reading Created & Updated timestamps fields?

Ok so I have a REST API in GO which stores a ticket resource using firestore. For this I use: firestore go client
I want to be able to order my documents by date created / date updated, so by following the docs I store these 2 fields as timestamps in the document.
I use the tag serverTimestamp on these 2 fields. By doing this, the value should be the time at which the firestore server processed the request.
The HTTP response of the update operation should have this body:
{
"ticket": {
"id": "af41766e-76ea-43b5-86c1-8ba382edd4dc",
"title": "Ticket updated title",
"price": 9,
"date_created": "2023-01-06 09:07:24",
"date_updated": "2023-01-06 10:08:24"
}
}
So it means after I update the ticket document, besides an updated title or price I also need to have the updated value fo the date_updated field.
This is working for the moment but I'm curious if the way I coded this is the way to do it. As you can see in the code samples, I use a transaction to update a ticket. I didn't find a way to retrieve the updated value for the DateUpdated field, other than reading again the updated ticket.
The domain entity is defined as this:
package tixer
import (
"context"
"time"
"github.com/google/uuid"
)
type (
// TicketID represents a unique identifier for a ticket.
// It's a domain type.
TicketID uuid.UUID
// Ticket represents an individual ticket in the system.
// It's a domain type.
Ticket struct {
ID TicketID
Title string
Price float64
DateCreated time.Time
DateUpdated time.Time
}
)
I'll attach here the communication with firestore from the create and update perspective:
// Storer persists tickets in Firestore.
type Storer struct {
client *firestore.Client
}
func NewStorer(client *firestore.Client) *Storer {
return &Storer{client}
}
func (s *Storer) CreateTicket(ctx context.Context, ticket *tixer.Ticket) error {
writeRes, err := s.client.Collection("tickets").Doc(ticket.ID.String()).Set(ctx, createTicket{
Title: ticket.Title,
Price: ticket.Price,
})
// In this case writeRes.UpdateTime is the time the document was created.
ticket.DateCreated = writeRes.UpdateTime
return err
}
func (s *Storer) UpdateTicket(ctx context.Context, ticket *tixer.Ticket) error {
docRef := s.client.Collection("tickets").Doc(ticket.ID.String())
err := s.client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
doc, err := tx.Get(docRef)
if err != nil {
switch {
case status.Code(err) == codes.NotFound:
return tixer.ErrTicketNotFound
default:
return err
}
}
var t persistedTicket
if err := doc.DataTo(&t); err != nil {
return err
}
t.ID = doc.Ref.ID
if ticket.Title != "" {
t.Title = ticket.Title
}
if ticket.Price != 0 {
t.Price = ticket.Price
}
return tx.Set(docRef, updateTicket{
Title: t.Title,
Price: t.Price,
DateCreated: t.DateCreated,
})
})
if err != nil {
return err
}
updatedTicket, err := s.readTicket(ctx, ticket.ID)
if err != nil {
return err
}
*ticket = updatedTicket
return nil
}
func (s *Storer) readTicket(ctx context.Context, id tixer.TicketID) (tixer.Ticket, error) {
doc, err := s.client.Collection("tickets").Doc(id.String()).Get(ctx)
if err != nil {
switch {
case status.Code(err) == codes.NotFound:
return tixer.Ticket{}, tixer.ErrTicketNotFound
default:
return tixer.Ticket{}, err
}
}
var t persistedTicket
if err := doc.DataTo(&t); err != nil {
return tixer.Ticket{}, err
}
t.ID = doc.Ref.ID
return toDomainTicket(t), nil
}
type (
// persistedTicket represents a stored ticket in Firestore.
persistedTicket struct {
ID string `firestore:"id"`
Title string `firestore:"title"`
Price float64 `firestore:"price"`
DateCreated time.Time `firestore:"dateCreated"`
DateUpdated time.Time `firestore:"dateUpdate"`
}
// createTicket contains the data needed to create a Ticket in Firestore.
createTicket struct {
Title string `firestore:"title"`
Price float64 `firestore:"price"`
DateCreated time.Time `firestore:"dateCreated,serverTimestamp"`
DateUpdated time.Time `firestore:"dateUpdate,serverTimestamp"`
}
// updateTicket contains the data needed to update a Ticket in Firestore.
updateTicket struct {
Title string `firestore:"title"`
Price float64 `firestore:"price"`
DateCreated time.Time `firestore:"dateCreated"`
DateUpdated time.Time `firestore:"dateUpdate,serverTimestamp"`
}
)
func toDomainTicket(t persistedTicket) tixer.Ticket {
return tixer.Ticket{
ID: tixer.TicketID(uuid.MustParse(t.ID)),
Title: t.Title,
Price: t.Price,
DateCreated: t.DateCreated,
DateUpdated: t.DateUpdated,
}
}
If I understand correctly, the DateUpdated field is a server-side timestamp, which means that its value is determined by the server (as a so-called field transformation) when the value is written to the storage layer. Since a write operation in the Firestore SDK doesn't return the resulting data of that operation, the only way to get that value back into your application is indeed to perform an extra read operation after the write to get it.
The SDK doesn't automatically perform this read is because it is a charged operation, which in many cases is not needed. So by leaving it up to your code to perform that read, you can decide whether to incur this cost or not.

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

Firestore Golang run query with filters in transaction

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.
}

How can I read the value of a field in Firestore (Swift)

I want to read out the Value of an Field of my document (in Firebase Firestore with SwiftUI).
What I already have I this:
let value = myDataBase
// My Database instance
//
value.collection("My Collection").whereField("Code", isEqualTo: codeTextInput)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
}
}
}
(This Code works fine)
And now I want to store the Value of all Documents, which are in my collection and have for the key "Code" the value, which is typed in. But I want to store the Data for the key "Wert"
When I've saved it, I want to use it as an User-Default...
Btw. I don’t want collect more then 1 item with this code, I just want that this item which I collect is the right.
Let sum it up:
You want all documents in your collection with a certain value to be fetched
You want to save all of these values and be able to access them.
I can only recommend working with objects in this scenario. Let's make an example:
Lets import all modules
import Foundation
import Firebase
import FirebaseFirestoreSwift
import FirebaseStorage
import Combine
First we declare the structure:
https://firebase.google.com/docs/firestore/manage-data/add-data#custom_objects
public struct MyObject: Codable {
let id: String
let code: String?
// Needed to identify them in Firestore
enum CodingKeys: String, CodingKey {
case id
case code = "code"
}
}
Now we access it and generate an Object for each document we can fetch that contains your desired value:
https://firebase.google.com/docs/firestore/query-data/get-data#custom_objects
var myArray: Array<MyObject> = [] // Empty array where we will store all objects in
var codeTextInput = "Test"
// Fetch only desired documents
let db = Firestore.firestore()
let docRef = db.collection("My Collection").whereField("Code", isEqualTo: codeTextInput)
func getDocumentsAsObjects() { // docRef.getDocuments Needs to be in function or else: Expressions are not allowed at the top level
docRef.getDocuments { (querySnapshot, err) in //getDocuments (s) as in multiple
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents { // iterate them and add them to your array
let result = Result {
try document.data(as: MyObject.self)
}
switch result {
case .success(let myObject):
if let myObject = myObject {
myObject.id = document!.documentID // Get the ID of the Document as we might need it later
myArray.append(myObject) // Save the document into your array
} else {
// A nil value was successfully initialized from the DocumentSnapshot,
// or the DocumentSnapshot was nil.
print("Document does not exist")
}
case .failure(let error):
// A `MyObject` value could not be initialized from the DocumentSnapshot.
print("Error decoding city: \(error)")
}
}
}
}
}
Now you have your Objects in your array and can access them

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.

Resources