2018-06-19 15:30:26 +02:00
|
|
|
// Copyright 2017 Google LLC
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package firestore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/golang/protobuf/ptypes"
|
2019-03-17 20:19:56 +01:00
|
|
|
pb "google.golang.org/genproto/googleapis/firestore/v1"
|
2018-06-19 15:30:26 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// A Precondition modifies a Firestore update or delete operation.
|
|
|
|
type Precondition interface {
|
|
|
|
// Returns the corresponding Precondition proto.
|
|
|
|
preconditionProto() (*pb.Precondition, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Exists is a Precondition that checks for the existence of a resource before
|
|
|
|
// writing to it. If the check fails, the write does not occur.
|
|
|
|
var Exists Precondition
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
// Initialize here so godoc doesn't show the internal value.
|
|
|
|
Exists = exists(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
type exists bool
|
|
|
|
|
|
|
|
func (e exists) preconditionProto() (*pb.Precondition, error) {
|
|
|
|
return &pb.Precondition{
|
|
|
|
ConditionType: &pb.Precondition_Exists{bool(e)},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e exists) String() string {
|
|
|
|
if e {
|
|
|
|
return "Exists"
|
|
|
|
}
|
2019-03-17 20:19:56 +01:00
|
|
|
return "DoesNotExist"
|
2018-06-19 15:30:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// LastUpdateTime returns a Precondition that checks that a resource must exist and
|
|
|
|
// must have last been updated at the given time. If the check fails, the write
|
|
|
|
// does not occur.
|
|
|
|
func LastUpdateTime(t time.Time) Precondition { return lastUpdateTime(t) }
|
|
|
|
|
|
|
|
type lastUpdateTime time.Time
|
|
|
|
|
|
|
|
func (u lastUpdateTime) preconditionProto() (*pb.Precondition, error) {
|
|
|
|
ts, err := ptypes.TimestampProto(time.Time(u))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &pb.Precondition{
|
|
|
|
ConditionType: &pb.Precondition_UpdateTime{ts},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u lastUpdateTime) String() string { return fmt.Sprintf("LastUpdateTime(%s)", time.Time(u)) }
|
|
|
|
|
|
|
|
func processPreconditionsForDelete(preconds []Precondition) (*pb.Precondition, error) {
|
|
|
|
// At most one option permitted.
|
|
|
|
switch len(preconds) {
|
|
|
|
case 0:
|
|
|
|
return nil, nil
|
|
|
|
case 1:
|
|
|
|
return preconds[0].preconditionProto()
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("firestore: conflicting preconditions: %+v", preconds)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func processPreconditionsForUpdate(preconds []Precondition) (*pb.Precondition, error) {
|
|
|
|
// At most one option permitted, and it cannot be Exists.
|
|
|
|
switch len(preconds) {
|
|
|
|
case 0:
|
|
|
|
// If the user doesn't provide any options, default to Exists(true).
|
|
|
|
return exists(true).preconditionProto()
|
|
|
|
case 1:
|
|
|
|
if _, ok := preconds[0].(exists); ok {
|
2019-03-17 20:19:56 +01:00
|
|
|
return nil, errors.New("cannot use Exists with Update")
|
2018-06-19 15:30:26 +02:00
|
|
|
}
|
|
|
|
return preconds[0].preconditionProto()
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("firestore: conflicting preconditions: %+v", preconds)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func processPreconditionsForVerify(preconds []Precondition) (*pb.Precondition, error) {
|
|
|
|
// At most one option permitted.
|
|
|
|
switch len(preconds) {
|
|
|
|
case 0:
|
|
|
|
return nil, nil
|
|
|
|
case 1:
|
|
|
|
return preconds[0].preconditionProto()
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("firestore: conflicting preconditions: %+v", preconds)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// A SetOption modifies a Firestore set operation.
|
|
|
|
type SetOption interface {
|
|
|
|
fieldPaths() (fps []FieldPath, all bool, err error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// MergeAll is a SetOption that causes all the field paths given in the data argument
|
|
|
|
// to Set to be overwritten. It is not supported for struct data.
|
|
|
|
var MergeAll SetOption = merge{all: true}
|
|
|
|
|
|
|
|
// Merge returns a SetOption that causes only the given field paths to be
|
|
|
|
// overwritten. Other fields on the existing document will be untouched. It is an
|
|
|
|
// error if a provided field path does not refer to a value in the data passed to
|
|
|
|
// Set.
|
|
|
|
func Merge(fps ...FieldPath) SetOption {
|
|
|
|
for _, fp := range fps {
|
|
|
|
if err := fp.validate(); err != nil {
|
|
|
|
return merge{err: err}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return merge{paths: fps}
|
|
|
|
}
|
|
|
|
|
|
|
|
type merge struct {
|
|
|
|
all bool
|
|
|
|
paths []FieldPath
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m merge) String() string {
|
|
|
|
if m.err != nil {
|
|
|
|
return fmt.Sprintf("<Merge error: %v>", m.err)
|
|
|
|
}
|
|
|
|
if m.all {
|
|
|
|
return "MergeAll"
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("Merge(%+v)", m.paths)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m merge) fieldPaths() (fps []FieldPath, all bool, err error) {
|
|
|
|
if m.err != nil {
|
|
|
|
return nil, false, m.err
|
|
|
|
}
|
|
|
|
if err := checkNoDupOrPrefix(m.paths); err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
if m.all {
|
|
|
|
return nil, true, nil
|
|
|
|
}
|
|
|
|
return m.paths, false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func processSetOptions(opts []SetOption) (fps []FieldPath, all bool, err error) {
|
|
|
|
switch len(opts) {
|
|
|
|
case 0:
|
|
|
|
return nil, false, nil
|
|
|
|
case 1:
|
|
|
|
return opts[0].fieldPaths()
|
|
|
|
default:
|
|
|
|
return nil, false, fmt.Errorf("conflicting options: %+v", opts)
|
|
|
|
}
|
|
|
|
}
|