Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
54c4f6e
fixed tests
daniel-sanche Oct 23, 2025
107e412
added vector expressions
daniel-sanche Oct 23, 2025
a0c36ca
added new math expressions
daniel-sanche Oct 24, 2025
dcd6af1
added string manipulation expressions
daniel-sanche Oct 24, 2025
71308dc
added not_nan, not_null, and is_absent
daniel-sanche Oct 24, 2025
0260014
added new Array type
daniel-sanche Oct 24, 2025
1b69435
added map and related expressions
daniel-sanche Oct 24, 2025
be749e0
remove dict and list from constant types
daniel-sanche Oct 24, 2025
789f29c
Fixed lint
daniel-sanche Oct 25, 2025
64be10d
added count_if and count_distinct
daniel-sanche Oct 27, 2025
5d4f878
added misc expressions
daniel-sanche Oct 27, 2025
6d6c57f
added error functions
daniel-sanche Oct 27, 2025
f1690d8
fixed lint
daniel-sanche Oct 27, 2025
559ad80
removed AliasedAggregate in favor of generics
daniel-sanche Oct 27, 2025
86ad143
improved e2e tests
daniel-sanche Oct 28, 2025
f2697ca
fixed broken stages
daniel-sanche Oct 28, 2025
d18e1f9
added options to generic stage
daniel-sanche Oct 28, 2025
68b9eff
fixed unit tests
daniel-sanche Oct 28, 2025
9b2cc6b
ran blacken
daniel-sanche Oct 28, 2025
3ebdb13
broke out aggregates
daniel-sanche Oct 28, 2025
a951789
broke up test file
daniel-sanche Oct 28, 2025
1d48d4d
removed duplicates
daniel-sanche Oct 28, 2025
390ea72
added math file
daniel-sanche Oct 28, 2025
7c2f41f
renamed tests
daniel-sanche Oct 28, 2025
41ff06d
include test file in test name
daniel-sanche Oct 28, 2025
d1dc233
fixed lint
daniel-sanche Oct 28, 2025
ca2caa9
broke out tests into multiple yaml files
daniel-sanche Oct 28, 2025
7425832
fixed static function access
daniel-sanche Oct 28, 2025
086c3af
ignore upgrade warning
daniel-sanche Oct 28, 2025
ce2fab2
renamed Expr to Expression
daniel-sanche Oct 28, 2025
d45b8b7
Merge branch 'pipeline_queries_improve_tests' into pipeline_queries_c…
daniel-sanche Oct 28, 2025
9eb32e1
renamed variables
daniel-sanche Oct 28, 2025
adbfd21
renamed GenericStage to RawStage
daniel-sanche Oct 28, 2025
5d4d6b0
ran blacken
daniel-sanche Oct 28, 2025
ea3aaa5
made pipeline_stages public
daniel-sanche Oct 28, 2025
dc42a85
added replace_with stage
daniel-sanche Oct 29, 2025
e24fa74
renamed modes
daniel-sanche Oct 29, 2025
dc2f569
added e2e tests
daniel-sanche Oct 29, 2025
404aacf
removed mode
daniel-sanche Oct 29, 2025
1c5314c
fixed docstring
daniel-sanche Oct 29, 2025
5c61811
Merge branch 'pipeline_queries_approved' into pipeline_queries_replace
daniel-sanche Oct 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions google/cloud/firestore_v1/base_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,47 @@ def find_nearest(
stages.FindNearest(field, vector, distance_measure, options)
)

def replace_with(
self,
field: Selectable,
) -> "_BasePipeline":
"""
Fully overwrites all fields in a document with those coming from a nested map.

This stage allows you to emit a map value as a document. Each key of the map becomes a field
on the document that contains the corresponding value.

Example:
Input document:
```json
{
"name": "John Doe Jr.",
"parents": {
"father": "John Doe Sr.",
"mother": "Jane Doe"
}
}
```

>>> # Emit the 'parents' map as the document
>>> pipeline = client.pipeline().collection("people").replace_with(Field.of("parents"))

Output document:
```json
{
"father": "John Doe Sr.",
"mother": "Jane Doe"
}
```

Args:
field: The `Selectable` field containing the map whose content will
replace the document.
Returns:
A new Pipeline object with this stage appended to the stage list
"""
return self._append(stages.ReplaceWith(field))

def sort(self, *orders: stages.Ordering) -> "_BasePipeline":
"""
Sorts the documents from previous stages based on one or more `Ordering` criteria.
Expand Down
11 changes: 11 additions & 0 deletions google/cloud/firestore_v1/pipeline_stages.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,17 @@ def _pb_args(self) -> list[Value]:
return [f._to_pb() for f in self.fields]


class ReplaceWith(Stage):
"""Replaces the document content with the value of a specified field."""

def __init__(self, field: Selectable):
super().__init__("replace_with")
self.field = Field(field) if isinstance(field, str) else field

def _pb_args(self):
return [self.field._to_pb(), Value(string_value="full_replace")]


class Sample(Stage):
"""Performs pseudo-random sampling of documents."""

Expand Down
31 changes: 30 additions & 1 deletion tests/system/pipeline_e2e/general.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -655,4 +655,33 @@ tests:
fieldReferenceValue: tags_alias
index:
fieldReferenceValue: index
name: select
name: select
- description: replaceWith
pipeline:
- Collection: books
- Where:
- Function.equal:
- Field: title
- Constant: "The Hitchhiker's Guide to the Galaxy"
- ReplaceWith:
- Field: awards
assert_results:
- hugo: True
nebula: False
assert_proto:
pipeline:
stages:
- args:
- referenceValue: /books
name: collection
- args:
- functionValue:
args:
- fieldReferenceValue: title
- stringValue: "The Hitchhiker's Guide to the Galaxy"
name: equal
name: where
- args:
- fieldReferenceValue: awards
- stringValue: full_replace
name: replace_with
2 changes: 2 additions & 0 deletions tests/unit/v1/test_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,8 @@ def test_pipeline_execute_stream_equivalence_mocked():
("name", [0.1], "cosine", stages.FindNearestOptions(10)),
stages.FindNearest,
),
("replace_with", ("name",), stages.ReplaceWith),
("replace_with", (Field.of("n"),), stages.ReplaceWith),
("sort", (Field.of("n").descending(),), stages.Sort),
("sort", (Field.of("n").descending(), Field.of("m").ascending()), stages.Sort),
("sample", (10,), stages.Sample),
Expand Down
18 changes: 9 additions & 9 deletions tests/unit/v1/test_pipeline_expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,14 @@ def test__from_query_filter_pb_unknown_filter_type(self, mock_client):
BooleanExpression._from_query_filter_pb(document_pb.Value(), mock_client)


class TestFunction:
def test_equals(self):
assert expr.Function.sqrt("1") == expr.Function.sqrt("1")
assert expr.Function.sqrt("1") != expr.Function.sqrt("2")
assert expr.Function.sqrt("1") != expr.Function.sum("1")
assert expr.Function.sqrt("1") != object()


class TestArray:
"""Tests for the array class"""

Expand Down Expand Up @@ -618,15 +626,7 @@ def test_w_exprs(self):
)


class TestFunction:
def test_equals(self):
assert expr.Function.sqrt("1") == expr.Function.sqrt("1")
assert expr.Function.sqrt("1") != expr.Function.sqrt("2")
assert expr.Function.sqrt("1") != expr.Function.sum("1")
assert expr.Function.sqrt("1") != object()


class TestExpressionMethods:
class TestExpressionessionMethods:
"""
contains test methods for each Expression method
"""
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/v1/test_pipeline_stages.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,39 @@ def test_to_pb(self):
assert len(result.options) == 0


class TestReplaceWith:
def _make_one(self, *args, **kwargs):
return stages.ReplaceWith(*args, **kwargs)

@pytest.mark.parametrize(
"in_field,expected_field",
[
("test", Field.of("test")),
("test", Field.of("test")),
("test", Field.of("test")),
(Field.of("test"), Field.of("test")),
(Field.of("test"), Field.of("test")),
],
)
def test_ctor(self, in_field, expected_field):
instance = self._make_one(in_field)
assert instance.field == expected_field
assert instance.name == "replace_with"

def test_repr(self):
instance = self._make_one("test")
repr_str = repr(instance)
assert repr_str == "ReplaceWith(field=Field.of('test'))"

def test_to_pb(self):
instance = self._make_one(Field.of("test"))
result = instance._to_pb()
assert result.name == "replace_with"
assert len(result.args) == 2
assert result.args[0].field_reference_value == "test"
assert result.args[1].string_value == "full_replace"


class TestSample:
class TestSampleOptions:
def test_ctor_percent(self):
Expand Down