Skip to content

perf(spanner): add by-value parameter and mutation binding methods#6071

Open
fornwall wants to merge 2 commits into
googleapis:mainfrom
fornwall:up-1-by-value-param-binding
Open

perf(spanner): add by-value parameter and mutation binding methods#6071
fornwall wants to merge 2 commits into
googleapis:mainfrom
fornwall:up-1-by-value-param-binding

Conversation

@fornwall

Copy link
Copy Markdown
Contributor

StatementBuilder::add_(typed_)param and ValueBinder::to bind values through the ToValue trait, whose impl ToValue for Value is self.clone().

A caller that already holds an owned Value therefore pays a redundant deep clone of the whole payload (every string, bytes and array cell) on each bound parameter and each mutation column. This can be costly on bound-DML rows and bulk-ingest cells

Add owned-value counterparts that accept impl Into<Value> and move the value straight into the builder, avoiding that clone.

@fornwall
fornwall requested review from a team as code owners July 16, 2026 13:12
@product-auto-label product-auto-label Bot added the api: spanner Issues related to the Spanner API. label Jul 16, 2026
@fornwall fornwall changed the title feat(spanner): add by-value parameter and mutation binding methods perf(spanner): add by-value parameter and mutation binding methods Jul 16, 2026
@fornwall
fornwall force-pushed the up-1-by-value-param-binding branch from f10d033 to dd06b32 Compare July 16, 2026 13:13

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces owned-value variants (to_value, add_param_value, and add_typed_param_value) to ValueBinder and StatementBuilder in the Spanner crate, allowing callers to pass owned values and avoid unnecessary deep clones. The existing reference-based methods have been refactored to delegate to these new methods, and corresponding unit tests have been added. The review feedback correctly points out that the newly added tests use .unwrap(), which violates the repository style guide; these should be replaced with .expect() to provide descriptive error messages upon failure.

Comment thread src/spanner/src/statement.rs Outdated
Comment thread src/spanner/src/statement.rs Outdated
@fornwall
fornwall force-pushed the up-1-by-value-param-binding branch 2 times, most recently from dd06b32 to 3b8e3e0 Compare July 16, 2026 13:37
StatementBuilder::add_param / add_typed_param and ValueBinder::to bind
values through the ToValue trait, whose `impl ToValue for Value` is
`self.clone()`.

A caller that already holds an owned Value therefore pays a redundant
deep clone of the whole payload (every string, bytes and array cell)
on each bound parameter and each mutation column. This can be costly
on bound-DML rows and bulk-ingest cells

Add owned-value counterparts that accept `impl Into<Value>` and move
the value straight into the builder, avoiding that clone.

Signed-off-by: Fredrik Fornwall <fredrik@fornwall.net>
@fornwall
fornwall force-pushed the up-1-by-value-param-binding branch from 3b8e3e0 to 49a6022 Compare July 16, 2026 13:39

@olavloite olavloite left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we solve this in a slightly different way that does not require adding new methods to the public API. Can we instead change the input argument of the existing methods to accept Into<Value>, and just add this blanket implementation:

impl<T: ToValue + ?Sized> From<&T> for Value {
    fn from(t: &T) -> Self {
        t.to_value()
    }
}

So the entire diff would then be something like this:

--- a/src/spanner/src/mutation.rs
+++ b/src/spanner/src/mutation.rs
@@ -298,9 +298,9 @@ pub struct ValueBinder {
 
 impl ValueBinder {
     /// Sets the value for the column.
-    pub fn to<T: ToValue + ?Sized>(mut self, value: &T) -> WriteBuilder {
+    pub fn to<T: Into<Value>>(mut self, value: T) -> WriteBuilder {
         self.builder.columns.push(self.column);
-        self.builder.values.push(value.to_value());
+        self.builder.values.push(value.into());
         self.builder
     }
 }
 
--- a/src/spanner/src/statement.rs
+++ b/src/spanner/src/statement.rs
@@ -69,8 +69,8 @@ impl StatementBuilder {
     /// It is recommended to use untyped parameter values, unless you explicitly want Spanner to
     /// verify that the type of the parameter value is exactly the same as the type that would
     /// otherwise be inferred from the SQL string.
-    pub fn add_param<T: ToValue + ?Sized>(mut self, name: impl Into<String>, value: &T) -> Self {
-        self.params.insert(name.into(), value.to_value());
+    pub fn add_param<T: Into<Value>>(mut self, name: impl Into<String>, value: T) -> Self {
+        self.params.insert(name.into(), value.into());
         self
     }
 
@@ -78,14 +78,14 @@ impl StatementBuilder {
     ///
     /// The parameter value is sent with an explicit type code to Spanner. The type code must
     /// correspond with the expression in the SQL string that the query parameter is bound to.
-    pub fn add_typed_param<T: ToValue + ?Sized>(
+    pub fn add_typed_param<T: Into<Value>>(
         mut self,
         name: impl Into<String>,
-        value: &T,
+        value: T,
         param_type: Type,
     ) -> Self {
         let name = name.into();
-        self.params.insert(name.clone(), value.to_value());
+        self.params.insert(name.clone(), value.into());
         self.param_types.insert(name, param_type);
         self
     }
 
--- a/src/spanner/src/to_value.rs
+++ b/src/spanner/src/to_value.rs
@@ -194,6 +194,13 @@ where
     }
 }
 
+// Blanket implementation to preserve backwards compatibility for callers passing &T
+// where T implements ToValue.
+impl<T: ToValue + ?Sized> From<&T> for Value {
+    fn from(t: &T) -> Self {
+        t.to_value()
+    }
+}
+
 #[cfg(test)]
 mod tests {

@fornwall
fornwall force-pushed the up-1-by-value-param-binding branch from 4f105d2 to 87189e2 Compare July 17, 2026 11:37
@fornwall

Copy link
Copy Markdown
Contributor Author

Thanks @olavloite, applied your suggestion. Slight changes compared to your diff:

  1. Regression tests for the new construction added.
  2. Make the blanket impl comment a doc comment since it shows up in the public API. And reworded it, since it's not just for backward compat IMHO, but also for caller ergonomy - not requiring .to_value() in stmt.add_param("age", age.to_value().

@fornwall
fornwall requested a review from olavloite July 17, 2026 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: spanner Issues related to the Spanner API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants