Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialization documentation #880

Merged
merged 2 commits into from
Dec 15, 2023
Merged
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
29 changes: 21 additions & 8 deletions docs/source/data-types/udt.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
Expand All @@ -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
Expand All @@ -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<dyn Error>> {
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<String>,
Expand Down
13 changes: 13 additions & 0 deletions docs/source/queries/paged.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ allow to receive the whole result page by page.
`Session::query_iter` and `Session::execute_iter` take a [simple query](simple.md) or a [prepared query](prepared.md)
and return an `async` iterator over result `Rows`.

> ***Warning***\
> In case of unprepared variant (`Session::query_iter`) if the values are not empty
> driver will first fully prepare a query (which means issuing additional request to each
> node in a cluster). This will have a performance penalty - how big it is depends on
> the size of your cluster (more nodes - more requests) and the size of returned
> result (more returned pages - more amortized penalty). In any case, it is preferable to
> use `Session::execute_iter`.

### Examples
Use `query_iter` to perform a [simple query](simple.md) with paging:
```rust
Expand Down Expand Up @@ -119,6 +127,11 @@ let res2 = session
# }
```

> ***Warning***\
> If the values are not empty, driver first needs to send a `PREPARE` request
> in order to fetch information required to serialize values. This will affect
> performance because 2 round trips will be required instead of 1.

On a `PreparedStatement`:
```rust
# extern crate scylla;
Expand Down
5 changes: 5 additions & 0 deletions docs/source/queries/simple.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ session
>
> When page size is set, `query` will return only the first page of results.

> ***Warning***\
> If the values are not empty, driver first needs to send a `PREPARE` request
> in order to fetch information required to serialize values. This will affect
> performance because 2 round trips will be required instead of 1.

### First argument - the query
As the first argument `Session::query` takes anything implementing `Into<Query>`.\
You can create a query manually to set custom options. For example to change query consistency:
Expand Down
41 changes: 32 additions & 9 deletions docs/source/queries/values.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn Error>> {
Expand All @@ -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?;

Expand Down
18 changes: 18 additions & 0 deletions scylla/src/transport/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,10 @@ impl Session {
///
/// This is the easiest way to make a query, but performance is worse than that of prepared queries.
///
/// It is discouraged to use this method with non-empty values argument (`is_empty()` method from `SerializeRow`
/// trait returns false). In such case, query first needs to be prepared (on a single connection), so
/// driver will perform 2 round trips instead of 1. Please use [`Session::execute()`] instead.
///
/// See [the book](https://rust-driver.docs.scylladb.com/stable/queries/simple.html) for more information
/// # Arguments
/// * `query` - query to perform, can be just a `&str` or the [Query] struct.
Expand Down Expand Up @@ -608,6 +612,11 @@ impl Session {
}

/// Queries the database with a custom paging state.
///
/// It is discouraged to use this method with non-empty values argument (`is_empty()` method from `SerializeRow`
/// trait returns false). In such case, query first needs to be prepared (on a single connection), so
/// driver will perform 2 round trips instead of 1. Please use [`Session::execute_paged()`] instead.
///
/// # Arguments
///
/// * `query` - query to be performed
Expand Down Expand Up @@ -749,6 +758,10 @@ impl Session {
/// Returns an async iterator (stream) over all received rows\
/// Page size can be specified in the [Query] passed to the function
///
/// It is discouraged to use this method with non-empty values argument (`is_empty()` method from `SerializeRow`
/// trait returns false). In such case, query first needs to be prepared (on a single connection), so
/// driver will initially perform 2 round trips instead of 1. Please use [`Session::execute_iter()`] instead.
///
/// See [the book](https://rust-driver.docs.scylladb.com/stable/queries/paged.html) for more information
///
/// # Arguments
Expand Down Expand Up @@ -1128,6 +1141,11 @@ impl Session {
///
/// Batch values must contain values for each of the queries
///
/// Avoid using non-empty values (`SerializeRow::is_empty()` return false) for simple queries
/// inside the batch. Such queries will first need to be prepared, so the driver will need to
/// send (numer_of_unprepared_queries_with_values + 1) requests instead of 1 request, severly
/// affecting performance.
///
/// See [the book](https://rust-driver.docs.scylladb.com/stable/queries/batch.html) for more information
///
/// # Arguments
Expand Down