#sqlite #rusqlite #from-row #mapper #column-name

rusqlite-from-row

Derivable trait that allows converting a rusqlite row to a struct

6 releases

0.2.4 May 1, 2024
0.2.3 Mar 14, 2024
0.2.2 Jan 2, 2024
0.2.1 Nov 29, 2023
0.1.0 May 28, 2023

#519 in Database interfaces

34 downloads per month

Custom license

10KB
73 lines

rusqlite-from-row

Derive FromRow to generate a mapping between a struct and rusqlite rows.

[dependencies]
rusqlite_from_row = "0.2.4"

Usage

Derive FromRow and execute a query that selects columns with the same names and types.

use rusqlite_from_row::FromRow;

#[derive(FromRow)]
struct Todo {
    todo_id: i32,
    text: String,
    author_id: i32,
}

let row = connection.query_row("SELECT todo_id, text, author_id FROM todos", [], Todo::try_from_row).unwrap();

Nesting, Joins and Flattening

You might want to represent a join between two tables as nested structs. This is possible using the #[from_row(flatten)] on the nested field. This will delegate the creation of that field to FromRow::from_row with the same row, instead of to FromSql.

Because tables might have naming collisions when joining them, you can specify a prefix = ".." to retrieve the columns uniquely. This prefix should match the prefix you specify when renaming the column in a select, like select <column> as <prefix><column>. Nested prefixing is supported.

One can also use the #[from_row(prefix)] without a value. In this case the field name following a underscore will be used.

Outer joins can be supported by wrapping the flattened type in an Option. The FromRow implementation of Option will still require all columns to present, but will produce a None if all the columns are an SQL null value.

use rusqlite_from_row::FromRow;

#[derive(FromRow)]
struct Todo {
    id: i32,
    name: String,
    text: String,
    #[from_row(flatten, prefix = "user_")]
    author: User
    #[from_row(flatten, prefix)]
    editor: User
}

#[derive(FromRow)]
struct User {
    id: i32,
    name: String
}

// Rename all `User` fields to have `user_` or `editor_` prefixes.
let row = client
    .query_one(
        "
    SELECT 
        t.id, 
        t.name, 
        t.text, 
        u.name as user_name, 
        u.id as user_id,
        e.name as editor_name,
        e.id as editor_id
    FROM 
        todos t 
    JOIN 
        user u ON t.author_id = u.id
    JOIN
        user e ON t.editor_id = e.id
        ",
        [],
        Todo::try_from_row,
    )
    .unwrap();

Renaming and Converting

If a struct contains a field with a name that differs from the name of the sql column, you can use the #[from_row(rename = "..")] attribute.

Normally if you have a custom wrapper type like struct DbId(i32), you'd need to implement FromSql in order to use it in a query. A simple alternative is to implement From<i32> or TryFrom<i32> for DbId and annotating a field with #[from_row(from = "i32")] or #[from_row(try_from = "i32")].

This will delegate the sql conversion to <i32 as FromSql> and subsequently convert it to DbId.


use rusqlite_from_row::FromRow;

struct DbId(i32);

impl From<i32> for DbId {
    fn from(value: i32) -> Self {
        Self(value)
    }
}

#[derive(FromRow)]
struct Todo {
    // If the sqlite column is named `todo_id`.
    #[from_row(rename = "todo_id", from = "i32")]
    id: i32,
    // If the sqlite column is `TEXT`, it will be decoded to `String`,
    // using `FromSql` and then converted to `Vec<u8>` using `std::convert::From`.
    #[from_row(from = "String")]
    todo: Vec<u8>
}

Dependencies

~23MB
~435K SLoC