Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions spanner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ parking_lot = "0.12"
base64 = "0.22"
tokio-util = "0.7"
bigdecimal = { version="0.4", features=["serde"] }
uuid = { version = "1", optional = true }

token-source = "1.0"
google-cloud-longrunning = { package = "gcloud-longrunning", version = "1.3.1", path = "../foundation/longrunning" }
Expand All @@ -50,3 +51,4 @@ external-account = ["google-cloud-auth?/external-account"]
otel-metrics = ["opentelemetry"]
jwt-aws-lc-rs = ["google-cloud-auth?/jwt-aws-lc-rs"]
jwt-rust-crypto = ["google-cloud-auth?/jwt-rust-crypto"]
uuid = ["dep:uuid"]
62 changes: 62 additions & 0 deletions spanner/src/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ pub enum Error {
BigDecimalParseError(String, #[source] ParseBigDecimalError),
#[error("Failed to parse as Prost Timestamp field={0}")]
ProstTimestampParseError(String, #[source] ::prost_types::TimestampError),
#[cfg(feature = "uuid")]
#[error("Failed to parse as Uuid field={0}")]
UuidParseError(String, #[source] ::uuid::Error),
}

impl Row {
Expand All @@ -72,6 +75,14 @@ impl Row {
{
self.column(index(&self.index, column_name)?)
}

pub fn field(&self, column_index: usize) -> Option<&Field> {
self.fields.get(column_index)
}

pub fn field_by_name(&self, column_name: &str) -> Option<&Field> {
self.index.get(column_name).and_then(|&idx| self.fields.get(idx))
}
}

//don't use TryFrom trait to avoid the conflict
Expand Down Expand Up @@ -231,6 +242,33 @@ impl TryFromValue for BigDecimal {
}
}

#[cfg(feature = "uuid")]
impl TryFromValue for ::uuid::Uuid {
fn try_from(item: &Value, field: &Field) -> Result<Self, Error> {
use google_cloud_googleapis::spanner::v1::TypeCode;

let type_code = field
.r#type
.as_ref()
.map(|t| t.code)
.unwrap_or(TypeCode::Unspecified as i32);

if type_code != TypeCode::Uuid as i32 {
return Err(Error::KindMismatch(
field.name.to_string(),
format!("expected UUID type, got TypeCode={}", type_code),
));
}

match as_ref(item, field)? {
Kind::StringValue(s) => {
::uuid::Uuid::parse_str(s).map_err(|e| Error::UuidParseError(field.name.to_string(), e))
}
v => kind_to_error(v, field),
}
}
}

impl TryFromValue for String {
fn try_from(item: &Value, field: &Field) -> Result<Self, Error> {
match as_ref(item, field)? {
Expand Down Expand Up @@ -491,4 +529,28 @@ mod tests {
);
assert_eq!(format!("{}", struct_data[1].prost_timestamp), "2027-02-19T07:23:59Z");
}

#[cfg(feature = "uuid")]
#[test]
fn test_uuid_try_from() {
use crate::row::TryFromValue;
use crate::statement::ToKind;
use google_cloud_googleapis::spanner::v1::struct_type::Field;
use prost_types::Value;

let uuid_str = "550e8400-e29b-41d4-a716-446655440000";
let expected_uuid = ::uuid::Uuid::parse_str(uuid_str).unwrap();

let field = Field {
name: "uuid_field".to_string(),
r#type: Some(::uuid::Uuid::get_type()),
};

let value = Value {
kind: Some(uuid_str.to_string().to_kind()),
};

let parsed: ::uuid::Uuid = TryFromValue::try_from(&value, &field).unwrap();
assert_eq!(parsed, expected_uuid);
}
}
24 changes: 24 additions & 0 deletions spanner/src/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,16 @@ impl ToKind for BigDecimal {
}
}

#[cfg(feature = "uuid")]
impl ToKind for ::uuid::Uuid {
fn to_kind(&self) -> Kind {
self.to_string().to_kind()
}
fn get_type() -> Type {
single_type(TypeCode::Uuid)
}
}

impl ToKind for ::prost_types::Timestamp {
fn to_kind(&self) -> Kind {
// The protobuf timestamp type should be formatted in RFC3339
Expand Down Expand Up @@ -328,4 +338,18 @@ mod test {
// Prost's Timestamp type and OffsetDateTime should have the same representation in spanner
assert_eq!(prost_types::Timestamp::get_type(), OffsetDateTime::get_type());
}

#[cfg(feature = "uuid")]
#[test]
fn uuid_to_kind_works() {
use google_cloud_googleapis::spanner::v1::TypeCode;

let uuid = ::uuid::Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
let expected = "550e8400-e29b-41d4-a716-446655440000";
let kind = uuid.to_kind();
assert!(matches!(kind, Kind::StringValue(s) if s == expected));

let uuid_type = ::uuid::Uuid::get_type();
assert_eq!(uuid_type.code, TypeCode::Uuid as i32);
}
}