Skip to content

Pipelines Implementation #6710

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 82 commits into
base: feat/pipelines
Choose a base branch
from
Open

Conversation

tom-andersen
Copy link
Contributor

No description provided.

@google-oss-bot
Copy link
Contributor

google-oss-bot commented Feb 20, 2025

Coverage Report 1

Affected Products

No changes between base commit (7999ab4) and merge commit (42b9aaf).

Test Logs

  1. https://storage.googleapis.com/firebase-sdk-metric-reports/vxC6MxXIOn.html

Copy link
Contributor

github-actions bot commented Feb 20, 2025

Test Results

 8 files   8 suites   1m 0s ⏱️
12 tests 12 ✅ 0 💤 0 ❌
24 runs  24 ✅ 0 💤 0 ❌

Results for commit f579bf4.

♻️ This comment has been updated with latest results.

Copy link
Contributor

@MarkDuckworth MarkDuckworth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review is WIP. There is lots of great cleanup in this. Nice work. Only a few comments so far.

@@ -120,6 +120,15 @@ public String getPath() {
return key.getPath().canonicalString();
}

@RestrictTo(RestrictTo.Scope.LIBRARY)
@NonNull
public String getFullPath() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should javadocs for RestrictTo Library indicate that status? Or will this be visible even without use of the linter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should restrict visibility from my understanding.

fun replace(field: String): Pipeline = replace(Expr.field(field))

/**
* Fully overwrites all fields in a document with those coming from a nested map.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think this has to be a nested map, it can be any expression that evaluates to a map

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

unnest(Expr.field(arrayField).alias(alias))

/**
* Takes a specified array from the input documents and outputs a document for each element with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, i think this can be any Selectable with an expression that evaluates to an array.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Takes a specified array" -> "Transforms an array expression" ?

Copy link
Contributor

@MarkDuckworth MarkDuckworth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really impressive code. I learned a lot about kotlin. I'm offering a handful of questions and comments.


companion object {

@JvmStatic fun generic(name: String, vararg expr: Expr) = AggregateFunction(name, expr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be raw?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think so.

*
* @return A new [AggregateFunction] representing the countAll aggregation.
*/
@JvmStatic fun countAll() = AggregateFunction("count")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the SDK name and server API name match?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we discussed this and were sticking to countAll ... but feel free to change in my absence.

@JvmStatic
fun constant(value: Boolean): BooleanExpr {
val encodedValue = encodeValue(value)
return object : BooleanExpr("N/A", emptyArray()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be useful to include some documentation covering the reason why you are creating an anonymous BooleanExpr class here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comment

* @return A new [Expr] constant instance representing the Blob.
*/
@JvmStatic
fun blob(bytes: ByteArray): Expr {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible discussion topic, but it might be more clear to indicate in the function name that this will create a Constant Expr. blobConstant(...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worthy discussion topic. I tend to agree with you.

I'll leave this in your hands.

* @return A [Expr] constant instance.
*/
@JvmStatic
fun vector(vector: DoubleArray): Expr {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, might be worth naming vectorConstant(...) or constantVector(...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dido


@JvmField val JSON = OutputFormat("json")

@JvmField val STRUCT = OutputFormat("struct")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't seen 'struct' in the spec. Is this different from JSON?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whereas JSON is a large string, Struct is the equivalent proto format that takes advantage of protocol buffers to be much smaller. So Struct is the same as JSON, the same way YAML is the same as JSON. They both offer the same expressiveness, in a slightly different format. There are of course nuances, JSON can contain very large numeric since they are stored as strings. And YAML can contain comments.

This is a remnant from an early discussion. So yes, it should be removed for now.

At some point I hope Firestore will concern itself more with wire format, instead of stuffing JSON into protocol buffers. Which I imagine is like stuffing JSON inside a JSON string (which I believe is the case of REST variant of explain)?

}
}

class Verbosity private constructor(internal val value: String) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, i haven't seen this in the design. is this new?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

}
}

class Profiles private constructor(internal val value: String) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, i haven't seen this in the design. is this new?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

override fun args(userDataReader: UserDataReader): Sequence<Value> = emptySequence()
}

class CollectionSource
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in node, i found that i liked calling all of these *StageOptions. It gave consistency and meaning across the Pipeline api. Maybe you would consider calling these *Stage.

E.g. CollectionStage or CollectionSourceStage, DatabaseStage, LimitStage, WhereStage, ...

fields.asSequence().map(Field::toProto)
}

internal class ReplaceStage
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have intent to make all of these internal stages public to support options overrides?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Does it need to be done before release? Maybe (your call)

I only exposed the stage classes when the API required it, not just future proofing raw options. Exposing the stages will require also adding constructors / create methods that offer same semantics and documentation as Pipeline API. I wasn't going to do this work until the dust settled on options, and it is not critical for release in my opinion. There is always the rawStage.

@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
@firebase firebase deleted a comment from google-oss-bot Jul 28, 2025
Copy link
Contributor

Firebase AI Mock Responses Check ⚠️

A newer major version of the mock responses for Firebase AI unit tests is available. update_responses.sh should be updated to clone the latest version of the responses: v14.2

Copy link
Contributor Author

@tom-andersen tom-andersen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MarkDuckworth I have addressed your feedback. Let me know if there is anything else.

@@ -120,6 +120,15 @@ public String getPath() {
return key.getPath().canonicalString();
}

@RestrictTo(RestrictTo.Scope.LIBRARY)
@NonNull
public String getFullPath() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should restrict visibility from my understanding.

fun replace(field: String): Pipeline = replace(Expr.field(field))

/**
* Fully overwrites all fields in a document with those coming from a nested map.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

unnest(Expr.field(arrayField).alias(alias))

/**
* Takes a specified array from the input documents and outputs a document for each element with
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Takes a specified array" -> "Transforms an array expression" ?

*
* @return A new [AggregateFunction] representing the countAll aggregation.
*/
@JvmStatic fun countAll() = AggregateFunction("count")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we discussed this and were sticking to countAll ... but feel free to change in my absence.


companion object {

@JvmStatic fun generic(name: String, vararg expr: Expr) = AggregateFunction(name, expr)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think so.

companion object {
@JvmField val EXECUTE = ExplainMode("execute")

@JvmField val EXPLAIN = ExplainMode("explain")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the entire explain options.


@JvmField val JSON = OutputFormat("json")

@JvmField val STRUCT = OutputFormat("struct")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whereas JSON is a large string, Struct is the equivalent proto format that takes advantage of protocol buffers to be much smaller. So Struct is the same as JSON, the same way YAML is the same as JSON. They both offer the same expressiveness, in a slightly different format. There are of course nuances, JSON can contain very large numeric since they are stored as strings. And YAML can contain comments.

This is a remnant from an early discussion. So yes, it should be removed for now.

At some point I hope Firestore will concern itself more with wire format, instead of stuffing JSON into protocol buffers. Which I imagine is like stuffing JSON inside a JSON string (which I believe is the case of REST variant of explain)?

}
}

class Verbosity private constructor(internal val value: String) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

}
}

class Profiles private constructor(internal val value: String) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

fields.asSequence().map(Field::toProto)
}

internal class ReplaceStage
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Does it need to be done before release? Maybe (your call)

I only exposed the stage classes when the API required it, not just future proofing raw options. Exposing the stages will require also adding constructors / create methods that offer same semantics and documentation as Pipeline API. I wasn't going to do this work until the dust settled on options, and it is not critical for release in my opinion. There is always the rawStage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants