Skip to content

Commit 5a318d0

Browse files
feat: Added public preview support for full-text search and geo search in Pipelines. (#2346)
1 parent 8e7bc22 commit 5a318d0

File tree

11 files changed

+1281
-14
lines changed

11 files changed

+1281
-14
lines changed

java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Pipeline.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import com.google.cloud.firestore.pipeline.stages.RemoveFields;
5555
import com.google.cloud.firestore.pipeline.stages.ReplaceWith;
5656
import com.google.cloud.firestore.pipeline.stages.Sample;
57+
import com.google.cloud.firestore.pipeline.stages.Search;
5758
import com.google.cloud.firestore.pipeline.stages.Select;
5859
import com.google.cloud.firestore.pipeline.stages.Sort;
5960
import com.google.cloud.firestore.pipeline.stages.Stage;
@@ -223,6 +224,21 @@ private Pipeline append(Stage stage) {
223224
return new Pipeline(this.rpcContext, stages.append(stage));
224225
}
225226

227+
/**
228+
* Adds a search stage to the Pipeline.
229+
*
230+
* <p>This must be the first stage of the pipeline.
231+
*
232+
* <p>A limited set of expressions are supported in the search stage.
233+
*
234+
* @param searchStage An object that specifies how search is performed.
235+
* @return A new {@code Pipeline} object with this stage appended to the stage list.
236+
*/
237+
@BetaApi
238+
public Pipeline search(Search searchStage) {
239+
return append(searchStage);
240+
}
241+
226242
/**
227243
* Adds new fields to outputs from previous stages.
228244
*
@@ -1575,9 +1591,9 @@ public void onError(Throwable t) {
15751591
}
15761592

15771593
@InternalApi
1578-
private com.google.firestore.v1.Pipeline toProto() {
1594+
public com.google.firestore.v1.Pipeline toProto() {
15791595
return com.google.firestore.v1.Pipeline.newBuilder()
1580-
.addAllStages(stages.transform(StageUtils::toStageProto))
1596+
.addAllStages(stages.transform(Stage::toStageProto))
15811597
.build();
15821598
}
15831599

java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/BooleanFunctionExpression.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ class BooleanFunctionExpression extends BooleanExpression {
3636
this(new FunctionExpression(name, java.util.Arrays.asList(params)));
3737
}
3838

39+
BooleanFunctionExpression(
40+
String name, List<? extends Expression> params, java.util.Map<String, Value> options) {
41+
this(new FunctionExpression(name, params, options));
42+
}
43+
3944
@Override
4045
Value toProto() {
4146
return expr.toProto();

java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/Expression.java

Lines changed: 195 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public static Expression constant(Timestamp value) {
121121
*/
122122
@BetaApi
123123
public static BooleanExpression constant(Boolean value) {
124-
return equal(new Constant(value), true);
124+
return new BooleanConstant(new Constant(value));
125125
}
126126

127127
/**
@@ -4593,6 +4593,200 @@ public static BooleanExpression isError(Expression expr) {
45934593
return new BooleanFunctionExpression("is_error", expr);
45944594
}
45954595

4596+
/**
4597+
* Evaluates to the distance in meters between the location in the specified field and the query
4598+
* location.
4599+
*
4600+
* <p>This Expression can only be used within a {@code Search} stage.
4601+
*
4602+
* @param fieldName Specifies the field in the document which contains the first {@link GeoPoint}
4603+
* for distance computation.
4604+
* @param location Compute distance to this {@link GeoPoint}.
4605+
* @return A new {@link Expression} representing the geoDistance operation.
4606+
*/
4607+
@BetaApi
4608+
public static Expression geoDistance(String fieldName, GeoPoint location) {
4609+
return geoDistance(field(fieldName), location);
4610+
}
4611+
4612+
/**
4613+
* Evaluates to the distance in meters between the location in the specified field and the query
4614+
* location.
4615+
*
4616+
* <p>This Expression can only be used within a {@code Search} stage.
4617+
*
4618+
* <p>Example:
4619+
*
4620+
* <pre>{@code
4621+
* db.pipeline().collection("restaurants").search(
4622+
* Search.withQuery("waffles").withSort(geoDistance(field("location"), new GeoPoint(37.0, -122.0)).ascending())
4623+
* )
4624+
* }</pre>
4625+
*
4626+
* @param field Specifies the field in the document which contains the first {@link GeoPoint} for
4627+
* distance computation.
4628+
* @param location Compute distance to this {@link GeoPoint}.
4629+
* @return A new {@link Expression} representing the geoDistance operation.
4630+
*/
4631+
@BetaApi
4632+
public static Expression geoDistance(Field field, GeoPoint location) {
4633+
return new FunctionExpression(
4634+
"geo_distance", java.util.Arrays.asList(field, constant(location)));
4635+
}
4636+
4637+
/**
4638+
* Perform a full-text search on all indexed search fields in the document.
4639+
*
4640+
* <p>This Expression can only be used within a {@code Search} stage.
4641+
*
4642+
* <p>Example:
4643+
*
4644+
* <pre>{@code
4645+
* db.pipeline().collection("restaurants").search(Search.withQuery(documentMatches("waffles OR pancakes")))
4646+
* }</pre>
4647+
*
4648+
* @param rquery Define the search query using the search domain-specific language (DSL).
4649+
* @return A new {@link BooleanExpression} representing the documentMatches operation.
4650+
*/
4651+
@BetaApi
4652+
public static BooleanExpression documentMatches(String rquery) {
4653+
return new BooleanFunctionExpression("document_matches", constant(rquery));
4654+
}
4655+
4656+
/**
4657+
* Perform a full-text search on the specified field.
4658+
*
4659+
* <p>This Expression can only be used within a {@code Search} stage.
4660+
*
4661+
* <p>Example:
4662+
*
4663+
* <pre>{@code
4664+
* db.pipeline().collection("restaurants").search(Search.withQuery(matches("menu", "waffles")))
4665+
* }</pre>
4666+
*
4667+
* @param fieldName Perform search on this field.
4668+
* @param rquery Define the search query using the search domain-specific language (DSL).
4669+
*/
4670+
@InternalApi
4671+
static BooleanExpression matches(String fieldName, String rquery) {
4672+
return matches(field(fieldName), rquery);
4673+
}
4674+
4675+
/**
4676+
* Perform a full-text search on the specified field.
4677+
*
4678+
* <p>This Expression can only be used within a {@code Search} stage.
4679+
*
4680+
* @param field Perform search on this field.
4681+
* @param rquery Define the search query using the search domain-specific language (DSL).
4682+
*/
4683+
@InternalApi
4684+
static BooleanExpression matches(Field field, String rquery) {
4685+
return new BooleanFunctionExpression("matches", field, constant(rquery));
4686+
}
4687+
4688+
/**
4689+
* Evaluates to the search score that reflects the topicality of the document to all of the text
4690+
* predicates (for example: {@code documentMatches}) in the search query.
4691+
*
4692+
* <p>This Expression can only be used within a {@code Search} stage.
4693+
*
4694+
* <p>Example:
4695+
*
4696+
* <pre>{@code
4697+
* db.pipeline().collection("restaurants").search(
4698+
* Search.withQuery("waffles").withSort(score().descending())
4699+
* )
4700+
* }</pre>
4701+
*
4702+
* @return A new {@link Expression} representing the score operation.
4703+
*/
4704+
@BetaApi
4705+
public static Expression score() {
4706+
return new FunctionExpression("score", com.google.common.collect.ImmutableList.of());
4707+
}
4708+
4709+
/**
4710+
* Evaluates to an HTML-formatted text snippet that highlights terms matching the search query in
4711+
* {@code <b>bold</b>}.
4712+
*
4713+
* <p>This Expression can only be used within a {@code Search} stage.
4714+
*
4715+
* <p>Example:
4716+
*
4717+
* <pre>{@code
4718+
* db.pipeline().collection("restaurants").search(
4719+
* Search.withQuery("waffles").withAddFields(snippet("menu", "waffles").as("snippet"))
4720+
* )
4721+
* }</pre>
4722+
*
4723+
* @param fieldName Search the specified field for matching terms.
4724+
* @param rquery Define the search query using the search domain-specific language (DSL).
4725+
* @return A new {@link Expression} representing the snippet operation.
4726+
*/
4727+
@BetaApi
4728+
public static Expression snippet(String fieldName, String rquery) {
4729+
return new FunctionExpression(
4730+
"snippet", java.util.Arrays.asList(field(fieldName), constant(rquery)));
4731+
}
4732+
4733+
/**
4734+
* Evaluates to an HTML-formatted text snippet that highlights terms matching the search query in
4735+
* {@code <b>bold</b>}.
4736+
*
4737+
* <p>This Expression can only be used within a {@code Search} stage.
4738+
*
4739+
* <p>Example:
4740+
*
4741+
* <pre>{@code
4742+
* db.pipeline().collection("restaurants").search(
4743+
* Search.withQuery("waffles").withAddFields(field("menu").snippet("waffles").as("snippet"))
4744+
* )
4745+
* }</pre>
4746+
*
4747+
* @param rquery Define the search query using the search domain-specific language (DSL).
4748+
* @return A new {@link Expression} representing the snippet operation.
4749+
*/
4750+
@BetaApi
4751+
public final Expression snippet(String rquery) {
4752+
return new FunctionExpression(
4753+
"snippet",
4754+
java.util.Arrays.asList(this, constant(rquery)),
4755+
java.util.Collections.singletonMap(
4756+
"query", com.google.cloud.firestore.PipelineUtils.encodeValue(rquery)));
4757+
}
4758+
4759+
@InternalApi
4760+
static BooleanExpression between(String fieldName, Expression lowerBound, Expression upperBound) {
4761+
return between(field(fieldName), lowerBound, upperBound);
4762+
}
4763+
4764+
@InternalApi
4765+
static BooleanExpression between(String fieldName, Object lowerBound, Object upperBound) {
4766+
return between(fieldName, toExprOrConstant(lowerBound), toExprOrConstant(upperBound));
4767+
}
4768+
4769+
@InternalApi
4770+
static BooleanExpression between(
4771+
Expression expression, Expression lowerBound, Expression upperBound) {
4772+
return new BooleanFunctionExpression("between", expression, lowerBound, upperBound);
4773+
}
4774+
4775+
@InternalApi
4776+
static BooleanExpression between(Expression expression, Object lowerBound, Object upperBound) {
4777+
return between(expression, toExprOrConstant(lowerBound), toExprOrConstant(upperBound));
4778+
}
4779+
4780+
@InternalApi
4781+
public final BooleanExpression between(Expression lowerBound, Expression upperBound) {
4782+
return Expression.between(this, lowerBound, upperBound);
4783+
}
4784+
4785+
@InternalApi
4786+
public final BooleanExpression between(Object lowerBound, Object upperBound) {
4787+
return Expression.between(this, lowerBound, upperBound);
4788+
}
4789+
45964790
// Other Utility Functions
45974791
/**
45984792
* Creates an expression that returns the document ID from a path.

java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/Field.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,32 @@ public Value toProto() {
9090
return Value.newBuilder().setFieldReferenceValue(path.toString()).build();
9191
}
9292

93+
/**
94+
* Evaluates to the distance in meters between the location specified by this field and the query
95+
* location.
96+
*
97+
* <p>This Expression can only be used within a {@code Search} stage.
98+
*
99+
* @param location Compute distance to this {@link com.google.cloud.firestore.GeoPoint}.
100+
* @return A new {@link Expression} representing the geoDistance operation.
101+
*/
102+
@BetaApi
103+
public Expression geoDistance(com.google.cloud.firestore.GeoPoint location) {
104+
return Expression.geoDistance(this, location);
105+
}
106+
107+
/**
108+
* Perform a full-text search on this field.
109+
*
110+
* <p>This Expression can only be used within a {@code Search} stage.
111+
*
112+
* @param rquery Define the search query using the rquery DTS.
113+
*/
114+
@InternalApi
115+
BooleanExpression matches(String rquery) {
116+
return Expression.matches(this, rquery);
117+
}
118+
93119
@Override
94120
public boolean equals(Object o) {
95121
if (this == o) {

java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/FunctionExpression.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,26 @@
1818

1919
import com.google.api.core.BetaApi;
2020
import com.google.api.core.InternalApi;
21-
import com.google.common.base.Objects;
2221
import com.google.firestore.v1.Value;
23-
import java.util.Collections;
2422
import java.util.List;
2523
import java.util.stream.Collectors;
2624

2725
@BetaApi
2826
public class FunctionExpression extends Expression {
2927
private final String name;
3028
private final List<Expression> params;
29+
private final java.util.Map<String, Value> options;
3130

3231
public FunctionExpression(String name, List<? extends Expression> params) {
32+
this(name, params, java.util.Collections.emptyMap());
33+
}
34+
35+
@InternalApi
36+
FunctionExpression(
37+
String name, List<? extends Expression> params, java.util.Map<String, Value> options) {
3338
this.name = name;
34-
this.params = Collections.unmodifiableList(params);
39+
this.params = java.util.Collections.unmodifiableList(params);
40+
this.options = java.util.Collections.unmodifiableMap(options);
3541
}
3642

3743
@InternalApi
@@ -44,7 +50,8 @@ Value toProto() {
4450
.addAllArgs(
4551
this.params.stream()
4652
.map(FunctionUtils::exprToValue)
47-
.collect(Collectors.toList())))
53+
.collect(Collectors.toList()))
54+
.putAllOptions(this.options))
4855
.build();
4956
}
5057

@@ -53,11 +60,13 @@ public boolean equals(Object o) {
5360
if (this == o) return true;
5461
if (o == null || getClass() != o.getClass()) return false;
5562
FunctionExpression that = (FunctionExpression) o;
56-
return Objects.equal(name, that.name) && Objects.equal(params, that.params);
63+
return java.util.Objects.equals(name, that.name)
64+
&& java.util.Objects.equals(params, that.params)
65+
&& java.util.Objects.equals(options, that.options);
5766
}
5867

5968
@Override
6069
public int hashCode() {
61-
return Objects.hashCode(name, params);
70+
return java.util.Objects.hash(name, params, options);
6271
}
6372
}

java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/Selectable.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,24 @@
1919
import com.google.api.core.BetaApi;
2020

2121
@BetaApi
22-
public interface Selectable {}
22+
public interface Selectable {
23+
/**
24+
* Converts an object to a {@link Selectable}.
25+
*
26+
* @param o The object to convert. Supported types: {@link Selectable}, {@link String}, {@link
27+
* com.google.cloud.firestore.FieldPath}.
28+
* @return The converted {@link Selectable}.
29+
*/
30+
@com.google.api.core.InternalApi
31+
static Selectable toSelectable(Object o) {
32+
if (o instanceof Selectable) {
33+
return (Selectable) o;
34+
} else if (o instanceof String) {
35+
return Expression.field((String) o);
36+
} else if (o instanceof com.google.cloud.firestore.FieldPath) {
37+
return Expression.field((com.google.cloud.firestore.FieldPath) o);
38+
} else {
39+
throw new IllegalArgumentException("Unknown Selectable type: " + o.getClass().getName());
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)