1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Go+Google Cloud Firestore

Posted at

Go の勉強がてら Google Cloud Datastore へ読み書きする簡単なプログラムを作成したため共有する。
Go は Java と比べてリフレクション周りが癖があって扱いにくかった。
あと、オーバーロードがないのが辛い。

fb.go
package fb

import (
	"context"
	"fmt"
	"reflect"

	"cloud.google.com/go/firestore"
	firebase "firebase.google.com/go"
	"google.golang.org/api/iterator"
)

type Document interface {
	ID() string
	SetID(id string)
	Collection() string
}

func NewApp(ctx context.Context) (*firebase.App, error) {
	return NewAppWithProjectID(ctx, "your project id here.") // FIXME
}

func NewAppWithProjectID(ctx context.Context, projectID string) (*firebase.App, error) {
	conf := &firebase.Config{ProjectID: projectID}
	app, err := firebase.NewApp(ctx, conf)
	if err == nil {
		return app, err
	}

	return nil, err
}

func NewClient(ctx context.Context) (*firestore.Client, error) {
	return NewClientWithProjectID(ctx, "your project id here.") // FIXME
}

func NewClientWithProjectID(ctx context.Context, projectID string) (*firestore.Client, error) {
	app, err := NewAppWithProjectID(ctx, projectID)
	if err != nil {
		return nil, err
	}

	client, err := app.Firestore(ctx)
	if err == nil {
		return client, err
	}

	return nil, err
}

func GetDoc(ctx context.Context, client *firestore.Client, doc Document) error {
	var dsnap *firestore.DocumentSnapshot
	var err error

	if client == nil {
		dsnap, err = Client.Collection(doc.Collection()).Doc(doc.ID()).Get(ctx)
	} else {
		dsnap, err = client.Collection(doc.Collection()).Doc(doc.ID()).Get(ctx)
	}

	if err != nil {
		return err
	}

	err = dsnap.DataTo(doc)
	if err != nil {
		return err
	}

	return nil
}

func GetDocs(ctx context.Context, q firestore.Query, dst interface{}) error {
	var elemIsPtr bool
	slice := reflect.ValueOf(dst).Elem() // dv はスライスになる
	elemType := slice.Type().Elem()      // elemType はスライス slice の要素の型になる

	switch elemType.Kind() {
	case reflect.Struct:
		elemIsPtr = false
	case reflect.Ptr:
		elemIsPtr = true
		elemType = elemType.Elem()
	default:
		return fmt.Errorf("unsupported slice element type: %v", elemType)
	}

	iter := q.Documents(ctx)

	for {
		doc, err := iter.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return err
		}

		entity := reflect.New(elemType)
		entity.Interface().(Document).SetID(doc.Ref.ID)
		err = doc.DataTo(entity.Interface())
		if err != nil {
			return err
		}

		if elemIsPtr {
			slice.Set(reflect.Append(slice, entity))
		} else {
			slice.Set(reflect.Append(slice, entity.Elem()))
		}
	}

	return nil
}

func PutDoc(ctx context.Context, client *firestore.Client, doc Document) error {
	var ref *firestore.DocumentRef
	var err error

	if doc.ID() == "" {
		if client == nil {
			ref, _, err = Client.Collection(doc.Collection()).Add(ctx, doc)
		} else {
			ref, _, err = client.Collection(doc.Collection()).Add(ctx, doc)
		}
	} else {
		if client == nil {
			_, err = Client.Collection(doc.Collection()).Doc(doc.ID()).Set(ctx, doc)
		} else {
			_, err = client.Collection(doc.Collection()).Doc(doc.ID()).Set(ctx, doc)
		}
	}

	if err != nil {
		return err
	}

	if ref != nil {
		doc.SetID(ref.ID)
	}

	return nil
}

func PutDocs(ctx context.Context, client *firestore.Client, entities interface{}) error {
	var elemIsPtr bool
	slice := reflect.ValueOf(entities)
	elemType := slice.Type().Elem() // elemType はスライス slice の要素の型になる

	switch elemType.Kind() {
	case reflect.Struct:
		elemIsPtr = false
	case reflect.Ptr:
		elemIsPtr = true
		elemType = elemType.Elem()
	default:
		return fmt.Errorf("unsupported slice element type: %v", elemType)
	}

	var ref *firestore.CollectionRef
	var batch *firestore.WriteBatch

	if client == nil {
		ref = Client.Collection(reflect.New(elemType).Interface().(Document).Collection())
		batch = Client.Batch()
	} else {
		ref = client.Collection(reflect.New(elemType).Interface().(Document).Collection())
		batch = client.Batch()
	}

	for i := 0; i < slice.Len(); i++ {
		var doc Document
		if elemIsPtr {
			doc = slice.Index(i).Interface().(Document)
		} else {
			doc = slice.Index(i).Addr().Interface().(Document)
		}

		if doc.ID() == "" {
			newDoc := ref.NewDoc()
			_ = batch.Set(newDoc, doc)
			doc.SetID(newDoc.ID)
		} else {
			_ = batch.Set(ref.Doc(doc.ID()), doc)
		}
	}

	_, err := batch.Commit(ctx)
	if err != nil {
		return err
	}

	return nil
}

func DeleteDoc(ctx context.Context, client *firestore.Client, doc Document) error {
	var err error
	if client == nil {
		_, err = Client.Collection(doc.Collection()).Doc(doc.ID()).Delete(ctx)
	} else {
		_, err = client.Collection(doc.Collection()).Doc(doc.ID()).Delete(ctx)
	}

	if err != nil {
		return err
	}

	return nil
}

func DeleteDocs(ctx context.Context, client *firestore.Client, entities interface{}) error {
	var elemIsPtr bool
	slice := reflect.ValueOf(entities)
	elemType := slice.Type().Elem() // elemType はスライス slice の要素の型になる

	switch elemType.Kind() {
	case reflect.Struct:
		elemIsPtr = false
	case reflect.Ptr:
		elemIsPtr = true
		elemType = elemType.Elem()
	default:
		return fmt.Errorf("unsupported slice element type: %v", elemType)
	}

	var ref *firestore.CollectionRef
	var batch *firestore.WriteBatch

	if client == nil {
		ref = Client.Collection(reflect.New(elemType).Interface().(Document).Collection())
		batch = Client.Batch()
	} else {
		ref = client.Collection(reflect.New(elemType).Interface().(Document).Collection())
		batch = client.Batch()
	}

	for i := 0; i < slice.Len(); i++ {
		var doc Document
		if elemIsPtr {
			doc = slice.Index(i).Interface().(Document)
		} else {
			doc = slice.Index(i).Addr().Interface().(Document)
		}
		_ = batch.Delete(ref.Doc(doc.ID()))
	}

	_, err := batch.Commit(ctx)
	if err != nil {
		return err
	}

	return nil
}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?