Skip to content

firestore: AggregationQuery.Get() returns different types for emulator vs production (*firestorepb.Value vs int64) #13646

@toshi38

Description

@toshi38

Client

Firestore

Environment

  • Firestore Emulator (via gcloud emulators firestore start or testcontainers)
  • macOS / Linux
  • go version go1.25

Code and Dependencies

package main
import (
	"context"
	"fmt"
	"log"
	"cloud.google.com/go/firestore"
	"cloud.google.com/go/firestore/apiv1/firestorepb"
)
func main() {
	ctx := context.Background()
	// Connect to emulator (FIRESTORE_EMULATOR_HOST must be set)
	client, err := firestore.NewClient(ctx, "my-project")
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()
	// Create some test data
	client.Collection("items").Doc("1").Set(ctx, map[string]interface{}{"active": true})
	client.Collection("items").Doc("2").Set(ctx, map[string]interface{}{"active": true})
	// Run aggregation query
	query := client.Collection("items").Where("active", "==", true)
	results, err := query.NewAggregationQuery().WithCount("count").Get(ctx)
	if err != nil {
		log.Fatal(err)
	}
	count := results["count"]
	fmt.Printf("Type: %T, Value: %v\n", count, count)
	// Emulator returns: Type: *firestorepb.Value, Value: integer_value:2
	// Production returns: Type: int64, Value: 2
	// Workaround required:
	switch v := count.(type) {
	case int64:
		fmt.Println("Got int64:", v)
	case *firestorepb.Value:
		fmt.Println("Got *firestorepb.Value:", v.GetIntegerValue())
	}
}

go.mod

module example
go 1.25
require (
	cloud.google.com/go/firestore v1.18.0
)

Expected behavior

AggregationQuery.Get() should return consistent native Go types (e.g., int64 for COUNT) in the AggregationResult map, regardless of whether running against production Firestore or the Firestore emulator.

Actual behavior

  • Production Firestore: Returns int64
  • Firestore Emulator: Returns *firestorepb.Value
    This forces users to handle both types with a type switch.

Copilot thinks the issue is in query.go where aggregation results are processed, I haven't verified that myself:
// query.go lines ~1751-1752

for k, v := range f {
    resp[k] = v  // v is *pb.Value, assigned directly without conversion
}

The fix should use createFromProtoValue() to convert proto values to native Go types:

for k, v := range f {
    converted, err := createFromProtoValue(v, a.query.c)
    if err != nil {
        return nil, err
    }
    resp[k] = converted
}

Additional context

Metadata

Metadata

Assignees

Labels

api: firestoreIssues related to the Firestore API.triage meI really want to be triaged.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions