Skip to content

Commit 3c8ad91

Browse files
committed
feat(firestore): refine Pipelines API, error handling, and integration tests
1 parent d927755 commit 3c8ad91

8 files changed

Lines changed: 324 additions & 277 deletions

firestore/integration_test.go

Lines changed: 109 additions & 122 deletions
Large diffs are not rendered by default.

firestore/pipeline.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,20 @@ package firestore
1616

1717
import (
1818
"context"
19+
"errors"
1920
"fmt"
2021
"reflect"
2122

2223
pb "cloud.google.com/go/firestore/apiv1/firestorepb"
2324
)
2425

26+
var (
27+
// ErrPipelineWithoutDatabase is returned when a pipeline is executed without a database such as a subcollection pipeline.
28+
ErrPipelineWithoutDatabase = errors.New("firestore: pipeline without a database cannot be executed directly, only as part of another pipeline")
29+
// ErrUnionNotSupportRelativeScope is returned when a union is used with a relative scope pipeline.
30+
ErrUnionNotSupportRelativeScope = errors.New("firestore: union only supports combining root pipelines; relative scope pipelines (like subcollection pipelines) are not supported")
31+
)
32+
2533
// Pipeline class provides a flexible and expressive framework for building complex data
2634
// transformation and query pipelines for Firestore.
2735
//
@@ -256,7 +264,7 @@ func (p *Pipeline) Execute(ctx context.Context, opts ...ExecuteOption) *Pipeline
256264
}
257265

258266
if newP.c == nil {
259-
newP.err = fmt.Errorf("pipeline created without a database (e.g., as a subcollection pipeline) cannot be executed directly; it can only be used as part of another pipeline")
267+
newP.err = ErrPipelineWithoutDatabase
260268
return &PipelineSnapshot{
261269
iter: &PipelineResultIterator{
262270
err: newP.err,
@@ -900,7 +908,7 @@ func (p *Pipeline) Union(other *Pipeline, opts ...UnionOption) *Pipeline {
900908
return p
901909
}
902910
if other.c == nil {
903-
p.err = fmt.Errorf("union only supports combining root pipelines; relative scope pipelines (like subcollection pipelines) are not supported")
911+
p.err = ErrUnionNotSupportRelativeScope
904912
return p
905913
}
906914
options := make(map[string]any)

firestore/pipeline_expression.go

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,11 @@ type Expression interface {
174174
//
175175
// The parameter 'offset' is the 0-based index of the first element to include. It can be an int, int32, int64 or [Expression].
176176
ArraySlice(offset any) Expression
177-
// ArraySliceWithLength creates an expression that returns a slice of an array starting from the specified offset with a given length.
177+
// ArraySliceToEnd creates an expression that returns a slice of an array starting from the specified offset with a given length.
178178
//
179179
// The parameter 'offset' is the 0-based index of the first element to include. It can be an int, int32, int64 or [Expression].
180180
// The parameter 'length' is the number of elements to include. It can be an int, int32, int64 or [Expression].
181-
ArraySliceWithLength(offset, length any) Expression
181+
ArraySliceToEnd(offset, length any) Expression
182182
// ArrayIndexOf creates an expression that returns the first index of a search value in an array.
183183
//
184184
// The parameter 'search' is the value to search for. It can be a constant or [Expression].
@@ -205,12 +205,11 @@ type Expression interface {
205205
// If the expression resolves to an absent value, it is converted to NULL.
206206
// The order of elements in the output array is not stable and shouldn't be relied upon.
207207
ArrayAggDistinct() AggregateFunction
208-
// TODO: Uncomment this after fixing the proto representation of this function.
209208
// ArrayFilter creates an expression for array_filter(array, param, body).
210209
//
211210
// The parameter 'param' is the name of the parameter to use in the body expression.
212211
// The parameter 'body' is the expression to evaluate for each element of the array.
213-
// ArrayFilter(param string, body BooleanExpression) Expression
212+
ArrayFilter(param string, body BooleanExpression) Expression
214213
// LogicalMaximum returns the maximum value of the expression and the specified values.
215214
LogicalMaximum(others ...any) Expression
216215
// LogicalMinimum returns the minimum value of the expression and the specified values.
@@ -242,7 +241,7 @@ type Expression interface {
242241
// "week(wednesday)", "week(thursday)", "week(friday)", "week(saturday)", "week(sunday)", "isoweek", "month", "quarter", "year", and "isoyear".
243242
// The parameter 'timezone' can be a string constant (e.g., "America/Los_Angeles") or an [Expression] that evaluates to a valid timezone string.
244243
// Valid values are from the TZ database or in the format "Etc/GMT-1".
245-
TimestampTruncateWithTimezone(granularity any, timezone string) Expression
244+
TimestampTruncateWithTimezone(granularity any, timezone any) Expression
246245
// TimestampToUnixMicros creates an expression that converts a timestamp expression to the number of microseconds since
247246
// the Unix epoch (1970-01-01 00:00:00 UTC).
248247
TimestampToUnixMicros() Expression
@@ -588,8 +587,8 @@ func (b *baseExpression) ArrayFirstN(n any) Expression { return ArrayFirstN(
588587
func (b *baseExpression) ArrayLast() Expression { return ArrayLast(b) }
589588
func (b *baseExpression) ArrayLastN(n any) Expression { return ArrayLastN(b, n) }
590589
func (b *baseExpression) ArraySlice(offset any) Expression { return ArraySlice(b, offset) }
591-
func (b *baseExpression) ArraySliceWithLength(offset, length any) Expression {
592-
return ArraySliceLength(b, offset, length)
590+
func (b *baseExpression) ArraySliceToEnd(offset, length any) Expression {
591+
return ArraySliceToEnd(b, offset, length)
593592
}
594593
func (b *baseExpression) ArrayIndexOf(search any) Expression {
595594
return ArrayIndexOf(b, search)
@@ -605,11 +604,9 @@ func (b *baseExpression) Last() AggregateFunction { return Last(b) }
605604
func (b *baseExpression) ArrayAgg() AggregateFunction { return ArrayAgg(b) }
606605
func (b *baseExpression) ArrayAggDistinct() AggregateFunction { return ArrayAggDistinct(b) }
607606

608-
// TODO: Uncomment this after fixing the proto representation of this function.
609-
//
610-
// func (b *baseExpression) ArrayFilter(param string, body BooleanExpression) Expression {
611-
// return ArrayFilter(b, param, body)
612-
// }
607+
func (b *baseExpression) ArrayFilter(param string, body BooleanExpression) Expression {
608+
return ArrayFilter(b, param, body)
609+
}
613610
func (b *baseExpression) LogicalMaximum(others ...any) Expression {
614611
return LogicalMaximum(b, others...)
615612
}
@@ -627,7 +624,7 @@ func (b *baseExpression) TimestampSubtract(unit, amount any) Expression {
627624
func (b *baseExpression) TimestampTruncate(granularity any) Expression {
628625
return TimestampTruncate(b, granularity)
629626
}
630-
func (b *baseExpression) TimestampTruncateWithTimezone(granularity any, timezone string) Expression {
627+
func (b *baseExpression) TimestampTruncateWithTimezone(granularity any, timezone any) Expression {
631628
return TimestampTruncateWithTimezone(b, granularity, timezone)
632629
}
633630
func (b *baseExpression) TimestampToUnixMicros() Expression { return TimestampToUnixMicros(b) }

firestore/pipeline_function.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ func TimestampTruncate(timestamp, granularity any) Expression {
322322
//
323323
// Experimental: Firestore Pipelines is currently in preview and is subject to potential breaking changes in future versions,
324324
// regardless of any other documented package stability guarantees.
325-
func TimestampTruncateWithTimezone(timestamp, granularity any, timezone string) Expression {
325+
func TimestampTruncateWithTimezone(timestamp, granularity any, timezone any) Expression {
326326
return newBaseFunction("timestamp_trunc", []Expression{asFieldExpr(timestamp), asStringExpr(granularity), asStringExpr(timezone)})
327327
}
328328

@@ -579,14 +579,14 @@ func ArraySlice(exprOrFieldPath any, offset any) Expression {
579579
return newBaseFunction("array_slice", []Expression{asFieldExpr(exprOrFieldPath), asInt64Expr(offset)})
580580
}
581581

582-
// ArraySliceLength creates an expression that returns a slice of an array starting from the specified offset with a given length.
582+
// ArraySliceToEnd creates an expression that returns a slice of an array starting from the specified offset with a given length.
583583
// - exprOrFieldPath can be a field path string, [FieldPath] or an [Expression] that evaluates to an array.
584584
// - offset is the 0-based index of the first element to include. It can be an int, int32, int64 or [Expression].
585585
// - length is the number of elements to include. It can be an int, int32, int64 or [Expression].
586586
//
587587
// Experimental: Firestore Pipelines is currently in preview and is subject to potential breaking changes in future versions,
588588
// regardless of any other documented package stability guarantees.
589-
func ArraySliceLength(exprOrFieldPath any, offset any, length any) Expression {
589+
func ArraySliceToEnd(exprOrFieldPath any, offset any, length any) Expression {
590590
return newBaseFunction("array_slice", []Expression{asFieldExpr(exprOrFieldPath), asInt64Expr(offset), asInt64Expr(length)})
591591
}
592592

@@ -597,10 +597,9 @@ func ArraySliceLength(exprOrFieldPath any, offset any, length any) Expression {
597597
//
598598
// Experimental: Firestore Pipelines is currently in preview and is subject to potential breaking changes in future versions,
599599
// regardless of any other documented package stability guarantees.
600-
// TODO: Uncomment this after fixing the proto representation of this function.
601-
// func ArrayFilter(array any, param string, body BooleanExpression) Expression {
602-
// return newBaseFunction("array_filter", []Expression{asFieldExpr(array), ConstantOf(param), body})
603-
// }
600+
func ArrayFilter(array any, param string, body BooleanExpression) Expression {
601+
return newBaseFunction("array_filter", []Expression{asFieldExpr(array), ConstantOf(param), body})
602+
}
604603

605604
// ArrayIndexOf creates an expression that returns the first index of a search value in an array.
606605
// - exprOrFieldPath can be a field path string, [FieldPath] or an [Expression] that evaluates to an array.

firestore/pipeline_function_test.go

Lines changed: 45 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -983,7 +983,7 @@ func TestArrayFunctions(t *testing.T) {
983983
},
984984
{
985985
desc: "ArraySliceWithLength",
986-
expr: ArraySliceLength("field", 1, 2),
986+
expr: ArraySliceToEnd("field", 1, 2),
987987
want: &pb.Value{ValueType: &pb.Value_FunctionValue{
988988
FunctionValue: &pb.Function{
989989
Name: "array_slice",
@@ -995,51 +995,50 @@ func TestArrayFunctions(t *testing.T) {
995995
},
996996
}},
997997
},
998-
// TODO: Uncomment this after fixing the proto representation of this function.
999-
// {
1000-
// desc: "ArrayFilter",
1001-
// expr: ArrayFilter("field", "item", FieldOf("item").GreaterThan(5)),
1002-
// want: &pb.Value{ValueType: &pb.Value_FunctionValue{
1003-
// FunctionValue: &pb.Function{
1004-
// Name: "array_filter",
1005-
// Args: []*pb.Value{
1006-
// {ValueType: &pb.Value_FieldReferenceValue{FieldReferenceValue: "field"}},
1007-
// {ValueType: &pb.Value_StringValue{StringValue: "item"}},
1008-
// {ValueType: &pb.Value_FunctionValue{
1009-
// FunctionValue: &pb.Function{
1010-
// Name: "greater_than",
1011-
// Args: []*pb.Value{
1012-
// {ValueType: &pb.Value_FieldReferenceValue{FieldReferenceValue: "item"}},
1013-
// {ValueType: &pb.Value_IntegerValue{IntegerValue: 5}},
1014-
// },
1015-
// },
1016-
// }},
1017-
// },
1018-
// },
1019-
// }},
1020-
// },
1021-
// {
1022-
// desc: "baseExpression ArrayFilter",
1023-
// expr: FieldOf("field").ArrayFilter("item", FieldOf("item").GreaterThan(5)),
1024-
// want: &pb.Value{ValueType: &pb.Value_FunctionValue{
1025-
// FunctionValue: &pb.Function{
1026-
// Name: "array_filter",
1027-
// Args: []*pb.Value{
1028-
// {ValueType: &pb.Value_FieldReferenceValue{FieldReferenceValue: "field"}},
1029-
// {ValueType: &pb.Value_StringValue{StringValue: "item"}},
1030-
// {ValueType: &pb.Value_FunctionValue{
1031-
// FunctionValue: &pb.Function{
1032-
// Name: "greater_than",
1033-
// Args: []*pb.Value{
1034-
// {ValueType: &pb.Value_FieldReferenceValue{FieldReferenceValue: "item"}},
1035-
// {ValueType: &pb.Value_IntegerValue{IntegerValue: 5}},
1036-
// },
1037-
// },
1038-
// }},
1039-
// },
1040-
// },
1041-
// }},
1042-
// },
998+
{
999+
desc: "ArrayFilter",
1000+
expr: ArrayFilter("field", "item", FieldOf("item").GreaterThan(5)),
1001+
want: &pb.Value{ValueType: &pb.Value_FunctionValue{
1002+
FunctionValue: &pb.Function{
1003+
Name: "array_filter",
1004+
Args: []*pb.Value{
1005+
{ValueType: &pb.Value_FieldReferenceValue{FieldReferenceValue: "field"}},
1006+
{ValueType: &pb.Value_StringValue{StringValue: "item"}},
1007+
{ValueType: &pb.Value_FunctionValue{
1008+
FunctionValue: &pb.Function{
1009+
Name: "greater_than",
1010+
Args: []*pb.Value{
1011+
{ValueType: &pb.Value_FieldReferenceValue{FieldReferenceValue: "item"}},
1012+
{ValueType: &pb.Value_IntegerValue{IntegerValue: 5}},
1013+
},
1014+
},
1015+
}},
1016+
},
1017+
},
1018+
}},
1019+
},
1020+
{
1021+
desc: "baseExpression ArrayFilter",
1022+
expr: FieldOf("field").ArrayFilter("item", FieldOf("item").GreaterThan(5)),
1023+
want: &pb.Value{ValueType: &pb.Value_FunctionValue{
1024+
FunctionValue: &pb.Function{
1025+
Name: "array_filter",
1026+
Args: []*pb.Value{
1027+
{ValueType: &pb.Value_FieldReferenceValue{FieldReferenceValue: "field"}},
1028+
{ValueType: &pb.Value_StringValue{StringValue: "item"}},
1029+
{ValueType: &pb.Value_FunctionValue{
1030+
FunctionValue: &pb.Function{
1031+
Name: "greater_than",
1032+
Args: []*pb.Value{
1033+
{ValueType: &pb.Value_FieldReferenceValue{FieldReferenceValue: "item"}},
1034+
{ValueType: &pb.Value_IntegerValue{IntegerValue: 5}},
1035+
},
1036+
},
1037+
}},
1038+
},
1039+
},
1040+
}},
1041+
},
10431042
{
10441043
desc: "ArrayIndexOf",
10451044
expr: ArrayIndexOf("field", "search"),

0 commit comments

Comments
 (0)