perf(spanner): add by-value parameter and mutation binding methods - #6071
perf(spanner): add by-value parameter and mutation binding methods#6071fornwall wants to merge 5 commits into
Conversation
f10d033 to
dd06b32
Compare
There was a problem hiding this comment.
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.
dd06b32 to
3b8e3e0
Compare
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>
3b8e3e0 to
49a6022
Compare
There was a problem hiding this comment.
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 {
4f105d2 to
87189e2
Compare
|
Thanks @olavloite, applied your suggestion. Slight changes compared to your diff:
|
|
/gcbrun |
|
/gcbrun |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6071 +/- ##
========================================
Coverage 96.57% 96.57%
========================================
Files 264 264
Lines 66577 66872 +295
========================================
+ Hits 64297 64584 +287
- Misses 2280 2288 +8 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request refactors Spanner's parameter binding APIs to accept types implementing Into<Value> instead of references to ToValue types, enabling owned value passing and adding support for untyped nulls. However, the reviewer points out that because From is not implemented for common owned types (like String, i64, bool) or &str directly, callers will face compilation errors or usability issues. Implementing From for these common types is recommended to fully realize the performance and usability benefits of the API change.
|
/gcbrun |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request refactors the Spanner client's parameter binding and mutation APIs to accept values by-value using the Into<Value> trait instead of by-reference using ToValue. To support this, numerous From<T> for Value implementations have been added for common types, along with a Value::null() helper and comprehensive unit tests verifying the new conversions. I have no feedback to provide as there are no review comments and the changes are clean and well-tested.
da133a3 to
2465719
Compare
|
/gcbrun |
Changes
StatementBuilder::add_param,StatementBuilder::add_typed_param, andValueBinder::toto acceptT: Into<Value>instead of&T: ToValue.This allows callers to pass owned values directly (like
String,Value, orOption<T>) zero-copy without unnecessary cloning, while maintaining complete backward compatibility for references (&str,&i64,&Option<T>).Key Changes
From<T> for ValueImplementations: Added zero-copyFromconversions for owned types (String,i64,i32,bool,f64,f32,Decimal,Date,OffsetDateTime,SystemTime,wkt::Timestamp,Vec<u8>,Option<T>,Vec<T>,ProtoValue).ToValue: AllToValuetrait implementations now delegate directly toFrom/.into(), decoupling conversion logic fromToValueand paving the way for eventual deprecation of theToValuetrait.[u8]andstr): ImplementedToValuefor[u8]andstr(and&[u8],&str), enabling zero-copy slice conversions and generic trait boundsT: ToValue + ?Sized. This PR supersedes both PR perf(spanner): add ToValue for &[u8] #6070 and PR feat(spanner): implement ToValue for str #6087.Value::null(),From<()>andToValue for ()to easily support untyped NULL parameters (Value::null(),None::<()>,None::<Value>).clippy::needless_borrows_for_generic_args).Breaking Change
This is a minor breaking change only for code that passes inline
&Nonewith a method-level turbofish:Code passing existing
Optionvariables (like.to(&my_var)) is unaffected and continues to compile unchanged.