From 930c5908c69ab2ff99aa7e32c1c02689ce4dcb26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ko=C5=82aczkowski?= Date: Tue, 25 Jun 2024 15:06:12 +0200 Subject: [PATCH] Allow objects and structs as source of named query params --- README.md | 13 +++++++++ src/context.rs | 73 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 74 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 50bd535..cde656b 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,19 @@ pub async fn run(ctx, i) { } ``` +Query parameters can be bound and passed by names as well: +```rust +const INSERT = "my_insert"; + +pub async fn prepare(ctx) { + ctx.prepare(INSERT, "INSERT INTO test.test(id, data) VALUES (:id, :data)").await?; +} + +pub async fn run(ctx, i) { + ctx.execute_prepared(INSERT, #{id: 5, data: "foo"}).await +} +``` + ### Populating the database Read queries are more interesting when they return non-empty result sets. diff --git a/src/context.rs b/src/context.rs index 2900681..acceb10 100644 --- a/src/context.rs +++ b/src/context.rs @@ -624,22 +624,31 @@ mod bind { field_types, }, ) => { - let borrowed = v.borrow_ref().unwrap(); - let mut fields = Vec::new(); - for (field_name, field_type) in field_types { - let value = match borrowed.get_value(field_name) { - Err(_) => None, - Ok(None) => Some(CqlValue::Empty), - Ok(Some(value)) => Some(to_scylla_value(&value, field_type)?), - }; - fields.push((field_name.to_string(), value)) - } + let obj = v.borrow_ref().unwrap(); + let fields = read_fields(|s| obj.get(s), field_types)?; + Ok(CqlValue::UserDefinedType { + keyspace: keyspace.to_string(), + type_name: type_name.to_string(), + fields, + }) + } + ( + Value::Struct(v), + ColumnType::UserDefinedType { + keyspace, + type_name, + field_types, + }, + ) => { + let obj = v.borrow_ref().unwrap(); + let fields = read_fields(|s| obj.get(s), field_types)?; Ok(CqlValue::UserDefinedType { keyspace: keyspace.to_string(), type_name: type_name.to_string(), fields, }) } + (Value::Any(obj), ColumnType::Uuid) => { let obj = obj.borrow_ref().unwrap(); let h = obj.type_hash(); @@ -680,9 +689,9 @@ mod bind { params: &Value, types: &[ColumnSpec], ) -> Result, CassError> { - let mut values = Vec::new(); - match params { + Ok(match params { Value::Tuple(tuple) => { + let mut values = Vec::new(); let tuple = tuple.borrow_ref().unwrap(); if tuple.len() != types.len() { return Err(CassError(CassErrorKind::InvalidNumberOfQueryParams)); @@ -690,18 +699,58 @@ mod bind { for (v, t) in tuple.iter().zip(types) { values.push(to_scylla_value(v, &t.typ)?); } + values } Value::Vec(vec) => { + let mut values = Vec::new(); + let vec = vec.borrow_ref().unwrap(); for (v, t) in vec.iter().zip(types) { values.push(to_scylla_value(v, &t.typ)?); } + values + } + Value::Object(obj) => { + let obj = obj.borrow_ref().unwrap(); + read_params(|f| obj.get(f), types)? + } + Value::Struct(obj) => { + let obj = obj.borrow_ref().unwrap(); + read_params(|f| obj.get(f), types)? } other => { return Err(CassError(CassErrorKind::InvalidQueryParamsObject( other.type_info().unwrap(), ))); } + }) + } + + fn read_params<'a, 'b>( + get_value: impl Fn(&String) -> Option<&'a Value>, + params: &[ColumnSpec], + ) -> Result, CassError> { + let mut values = Vec::with_capacity(params.len()); + for column in params { + let value = match get_value(&column.name) { + Some(value) => to_scylla_value(value, &column.typ)?, + None => CqlValue::Empty, + }; + values.push(value) + } + Ok(values) + } + + fn read_fields<'a, 'b>( + get_value: impl Fn(&String) -> Option<&'a Value>, + fields: &[(String, ColumnType)], + ) -> Result)>, CassError> { + let mut values = Vec::with_capacity(fields.len()); + for (field_name, field_type) in fields { + if let Some(value) = get_value(field_name) { + let value = Some(to_scylla_value(value, field_type)?); + values.push((field_name.to_string(), value)) + }; } Ok(values) }