diff --git a/docs/source/data-types/udt.md b/docs/source/data-types/udt.md index e79ad3feae..ac5b134a62 100644 --- a/docs/source/data-types/udt.md +++ b/docs/source/data-types/udt.md @@ -8,17 +8,25 @@ For example let's say `my_type` was created using this query: CREATE TYPE ks.my_type (int_val int, text_val text) ``` -To use this type in the driver, create a matching struct and derive `IntoUserType` and `FromUserType`: +To use this type in the driver, create a matching struct and derive: +- `SerializeCql`: in order to be able to use this struct in query parameters. \ + This macro requires fields of UDT and struct to have matching names, but the order + of the fields is not required to be the same. \ + Note: you can use different name using `rename` attribute - see `SerializeCql` macro documentation. +- `FromUserType`: in order to be able to use this struct in query results. \ + This macro requires fields of UDT and struct to be in the same *ORDER*. \ + This mismatch between `SerializeCql` and `FromUserType` requirements is a temporary situation - in the future `FromUserType` (or the macro that replaces it) will also require matching names. ```rust # extern crate scylla; # async fn check_only_compiles() { -use scylla::macros::{FromUserType, IntoUserType}; +use scylla::macros::{FromUserType, SerializeCql}; // Define a custom struct that matches the User Defined Type created earlier. -// Fields must be in the same order as they are in the database. +// Fields must be in the same order as they are in the database and also +// have the same names. // Wrapping a field in Option will gracefully handle null field values. -#[derive(Debug, IntoUserType, FromUserType)] +#[derive(Debug, FromUserType, SerializeCql)] struct MyType { int_val: i32, text_val: Option, @@ -27,8 +35,13 @@ struct MyType { ``` > ***Important***\ -> Fields in the Rust struct must be defined in the same order as they are in the database. -> When sending and receiving values, the driver will (de)serialize fields one after another, without looking at field names. +> For deserialization, fields in the Rust struct must be defined in the same order as they are in the database. +> When receiving values, the driver will (de)serialize fields one after another, without looking at field names. + +> ***Important***\ +> For serialization, by default fields in the Rust struct must be defined with the same names as they are in the database. +> The driver will serialize the fields in the order defined by the UDT, matching Rust fields by name. +> You can change this behaviour using macro attributes, see `SerializeCql` macro documentation for more information. Now it can be sent and received just like any other CQL value: ```rust @@ -37,10 +50,10 @@ Now it can be sent and received just like any other CQL value: # use std::error::Error; # async fn check_only_compiles(session: &Session) -> Result<(), Box> { use scylla::IntoTypedRows; -use scylla::macros::{FromUserType, IntoUserType, SerializeCql}; +use scylla::macros::{FromUserType, SerializeCql}; use scylla::cql_to_rust::FromCqlVal; -#[derive(Debug, IntoUserType, FromUserType, SerializeCql)] +#[derive(Debug, FromUserType, SerializeCql)] struct MyType { int_val: i32, text_val: Option, diff --git a/docs/source/queries/values.md b/docs/source/queries/values.md index 400e7139ab..a8ba9dcf71 100644 --- a/docs/source/queries/values.md +++ b/docs/source/queries/values.md @@ -5,14 +5,14 @@ Each `?` in query text will be filled with the matching value. > **Never** pass values by adding strings, this could lead to [SQL Injection](https://en.wikipedia.org/wiki/SQL_injection) -Each list of values to send in a query must implement the trait `ValueList`.\ +Each list of values to send in a query must implement the trait `SerializeRow`.\ By default this can be a slice `&[]`, a tuple `()` (max 16 elements) of values to send, -or a custom struct which derives from `ValueList`. +or a custom struct which derives from `SerializeRow`. A few examples: ```rust # extern crate scylla; -# use scylla::{Session, ValueList, SerializeRow, frame::response::result::CqlValue}; +# use scylla::{Session, SerializeRow, frame::response::result::CqlValue}; # use std::error::Error; # use std::collections::HashMap; # async fn check_only_compiles(session: &Session) -> Result<(), Box> { @@ -33,22 +33,45 @@ session .await?; // Sending an integer and a string using a named struct. -// The values will be passed in the order from the struct definition -#[derive(ValueList, SerializeRow)] +// Names of fields must match names of columns in request, +// but having them in the same order is not required. +// If the fields are in the same order, you can use attribute: +// `#[scylla(flavor = "enforce_order")]` +// in order to skip sorting the fields and just check if they +// are in the same order. See documentation of this macro +// for more information. +#[derive(SerializeRow)] struct IntString { - first_col: i32, - second_col: String, + a: i32, + b: String, } let int_string = IntString { - first_col: 42_i32, - second_col: "hello".to_owned(), + a: 42_i32, + b: "hello".to_owned(), }; session .query("INSERT INTO ks.tab (a, b) VALUES(?, ?)", int_string) .await?; +// You can use named bind markers in query if you want +// your names in struct to be different than column names. +#[derive(SerializeRow)] +struct IntStringCustom { + first_value: i32, + second_value: String, +} + +let int_string_custom = IntStringCustom { + first_value: 42_i32, + second_value: "hello".to_owned(), +}; + +session + .query("INSERT INTO ks.tab (a, b) VALUES(:first_value, :second_value)", int_string_custom) + .await?; + // Sending a single value as a tuple requires a trailing coma (Rust syntax): session.query("INSERT INTO ks.tab (a) VALUES(?)", (2_i32,)).await?;