tiberius-derive

Derive macros for Tiberius.

This crate provides a FromRow derive macro that allows for easier parsing and error handling of rows. Struct fields must implement either FromSql or FromSqlOwned and FromRow parsing behaviour is controlled by struct container attributes.

It should be possible to customize macro expansion to get columns by 'name/key' or by position (by position being faster, but forcing the struct fields to be in the same order as the values in the SELECT clause), either by borrowing (struct fields implementing FromSql) or by value (struct fields implementing FromSqlOwned). Field renaming is also implemented similarly as in serde (right now only renameAll is implemented).

Everything is still in alpha, and there is a lot of functionality to be implemented but have been using this in production in a bunch of critical/HA services for close to an year now without major issues. Error handling could be vastly improved, and there is no support for changing the behaviour with field attributes (ex: renaming a single struct field instead of all of them) at the moment.

Known issues:

There is a tiberius bug(?) that breaks Option fields at the moment. Tiberius appears to always parse null Float(53) (double) columns as Nullable(f32) independently of the column type.

```rust // DoubleColumn is a Nullable float(53) let query = "SELECT [DoubleColumn] FROM [TestRow] ";

let rows = client .simplequery(query) .await? .intofirst_result() .await?;

let double : Option = rows[0].get("DoubleColumn") // Error: Conversion("cannot interpret F32(None) as an f64 value")

```

Examples

FromRow: Get by ref and name

Fields must implement FromSql. Since String does not implement FromSql, if you want to use strings you need the #[tiberius_derive(auto)] container attrbute.

https://github.com/mobiltracker/tiberius-derive/blob/master/tiberius-derive-tests/tests/by_ref.rs

```rust

[allow(nonsnakecase)]

[derive(FromRow)]

struct TestRow<'a> { pub Id: i32, pub VarCharRow: &'a str, pub NVarCharRow: &'a str, pub UuidRow: Uuid, pub LongRow: i64, pub DateTimeRow: chrono::NaiveDateTime, pub SmallIntRow: i16, pub BitRow: bool, pub FloatRow: f32, pub DoubleRow: f64, pub RealRow: f32, }

async fn byrefnot_null() -> Result<(), tiberius::error::Error> { let query = r" SELECT [Id],[VarCharRow],[NVarCharRow],[UuidRow],[LongRow],[DateTimeRow],[SmallIntRow],[BitRow],[FloatRow],[DoubleRow],[RealRow] FROM [TiberiusDeriveTest].[dbo].[TestRow] ";

let rows = client .simplequery(query) .await? .intofirst_result() .await?;

let rows = rows .iter() .map(TestRow::from_row) .collect::, _>>()?;

} ```

FromRow: Get by ref and position

#[tiberius_derive(by_position)]

Faster, but struct fields must be in the same order as the values in the SELECT clause.

https://github.com/mobiltracker/tiberius-derive/blob/master/tiberius-derive-tests/tests/byrefby_position.rs

``` rust

[derive(FromRow)]

[tiberiusderive(byposition)]

struct TestRow<'a> { pub id: i32, pub varcharrow: &'a str, pub nvarcharrow: &'a str, pub uuidrow: Uuid, pub longrow: i64, pub datetimerow: chrono::NaiveDateTime, pub smallintrow: i16, pub bitrow: bool, pub floatrow: f32, pub doublerow: f64, pub real_row: f32, }

async fn byrefindexednotnull() -> Result<(), tiberius::error::Error> { let mut client = connect_localhost().await.unwrap(); let query = r" SELECT [Id],[VarCharRow],[NVarCharRow],[UuidRow],[LongRow],[DateTimeRow],[SmallIntRow],[BitRow],[FloatRow],[DoubleRow],[RealRow] FROM [TiberiusDeriveTest].[dbo].[TestRow] ";

let rows = client
    .simple_query(query)
    .await?
    .into_first_result()
    .await?;

let rows = rows
    .iter()
    .map(TestRow::from_row)
    .collect::<Result<Vec<_>, _>>()?;

}

```

FromRow: Get by value (and by position)

#[tiberius_derive(owned)]

Fields must implement FromSqlOwned, and struct fields must be in the same order as the values in the SELECT clause.

https://github.com/mobiltracker/tiberius-derive/blob/master/tiberius-derive-tests/tests/by_value.rs

``` rust

[allow(nonsnakecase)]

[derive(FromRow)]

[tiberius_derive(owned)]

struct TestRow { pub id: i32, pub varcharrow: String, pub nvarcharrow: String, pub uuidrow: Uuid, pub longrow: i64, pub datetimerow: chrono::NaiveDateTime, pub smallintrow: i16, pub bitrow: bool, pub floatrow: f32, pub doublerow: f64, pub real_row: f32, }

[tokio::test]

async fn byvalue() -> Result<(), tiberius::error::Error> { let mut client = connectlocalhost().await.unwrap(); let query = r" SELECT [Id],[VarCharRow],[NVarCharRow],[UuidRow],[LongRow],[DateTimeRow],[SmallIntRow],[BitRow],[FloatRow],[DoubleRow],[RealRow] FROM [TiberiusDeriveTest].[dbo].[TestRow] ";

let rows = client
    .simple_query(query)
    .await?
    .into_first_result()
    .await?;

let rows = rows
    .into_iter()
    .map(TestRow::from_row)
    .collect::<Result<Vec<_>, _>>()?;

}

```

FromRow: RenameAll

#[tiberius_derive(rename_all = "PascalCase")]

https://github.com/mobiltracker/tiberius-derive/blob/master/tiberius-derive-tests/tests/rename_all.rs

Renames fields (if getting column data by name)

``` rust

[derive(FromRowa)]

[tiberiusderive(renameall = "PascalCase")]

struct TestRow<'a> { pub id: i32, pub varcharrow: &'a str, pub nvarcharrow: &'a str, pub uuidrow: Uuid, pub longrow: i64, pub datetimerow: chrono::NaiveDateTime, pub smallintrow: i16, pub bitrow: bool, pub floatrow: f32, pub doublerow: f64, pub real_row: f32, }

[tokio::test]

async fn renameall() -> Result<(), tiberius::error::Error> { let mut client = connectlocalhost().await.unwrap(); let query = r" SELECT [Id],[VarCharRow],[NVarCharRow],[UuidRow],[LongRow],[DateTimeRow],[SmallIntRow],[BitRow],[FloatRow],[DoubleRow],[RealRow] FROM [TiberiusDeriveTest].[dbo].[TestRow] ";

let rows = client
    .simple_query(query)
    .await?
    .into_first_result()
    .await?;

let rows = rows
    .iter()
    .map(TestRow::from_row)
    .collect::<Result<Vec<_>, _>>()?;

} ```

FromRow: Converting &str to String by cloning

#[tiberius_derive(auto)]

https://github.com/mobiltracker/tiberius-derive/blob/master/tiberius-derive-tests/tests/strautoowned.rs

Since strings do not implement FromSql, having an attribute to do this is reasonably ergonomic.

``` rust

[allow(nonsnakecase)]

[derive(FromRow)]

[tiberius_derive(auto)]

struct TestRow { pub Id: i32, pub VarCharRow: String, pub NVarCharRow: String, pub UuidRow: Uuid, pub LongRow: i64, pub DateTimeRow: chrono::NaiveDateTime, pub SmallIntRow: i16, pub BitRow: bool, pub FloatRow: f32, pub DoubleRow: f64, pub RealRow: f32, }

[tokio::test]

async fn byrefautonotnull() -> Result<(), tiberius::error::Error> { let mut client = connect_localhost().await.unwrap(); let query = r" SELECT [Id],[VarCharRow],[NVarCharRow],[UuidRow],[LongRow],[DateTimeRow],[SmallIntRow],[BitRow],[FloatRow],[DoubleRow],[RealRow] FROM [TiberiusDeriveTest].[dbo].[TestRow] ";

let rows = client
    .simple_query(query)
    .await?
    .into_first_result()
    .await?;

let rows = rows
    .iter()
    .map(TestRow::from_row)
    .collect::<Result<Vec<_>, _>>()?;

}

```