Rust diesel aggregation query - rust

I have some problems with performing a query using Diesel library.
I have table:
create table tasks
(
id serial not null
constraint tasks_pkey
primary key,
name varchar(128) not null,
rate numeric(10, 2) default 0 not null,
time_spent numeric(10, 2)
);
Model:
#[derive(Debug, Serialize, Deserialize, Identifiable, Queryable)]
#[table_name="tasks"]
pub struct Task {
pub id: i32,
pub name: String,
pub rate: BigDecimal,
pub time_spent: Option<BigDecimal>
}
Schema:
table! {
tasks (id) {
id -> Int4,
name -> Varchar,
rate -> Numeric,
time_spent -> Nullable<Numeric>,
}
}
The target is that I need to get Sum of rate * time_spent. I meam total amount. Who can help me with that?

Related

DeserializationError - failed to fill whole buffer

I'm writing a simple rust app using the Rocket web framework, Postgres database, and Diesel to manage database migrations. The code compiles OK, and other parts of the application run properly, but for some reason, my Expense endpoints don't seem to be working.
When hitting the /expense endpoint for example to get all the Expenses, I get the following error in the log:
Err(
DeserializationError(
Error {
kind: UnexpectedEof,
message: "failed to fill whole buffer"
}
)
)
This error is obviously not very helpful, and doesn't have a lot of detail. Why am I receiving this error, and how can I resolve this problem?
Here are the relevant parts of the code:
Expense Migration
CREATE TABLE expenses (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
description TEXT NULL,
amount DECIMAL NOT NULL,
tax_year INT NOT NULL,
purchase_date TIMESTAMP WITH TIME ZONE NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
Expense Model
#[derive( Debug, Serialize, AsChangeset, Deserialize, Queryable, Insertable )]
#[table_name = "expenses"]
pub struct Expense {
pub id: Option<i32>,
pub name: String,
pub description: Option<String>,
pub amount: BigDecimal,
pub tax_year: i32,
pub purchase_date: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl Expense {
pub fn get_all(conn: &PgConnection) -> Result<Vec<Expense>, Error> {
expenses::table.order(expenses::id.desc()).load::<Expense>(conn)
}
...
}
Controller
#[get("/", format = "json")]
pub fn get_all(conn: db::Connection) -> Result<ApiResponse, ApiError> {
let result = Expense::get_all(&conn);
match result {
Ok(r) => Ok(success(json!(r))),
Err(e) => Err(db_error(e)),
}
}
Schema
table! {
expenses (id) {
id -> Nullable<Int4>,
name -> Text,
description -> Nullable<Text>,
amount -> Numeric,
tax_year -> Int4,
purchase_date -> Nullable<Timestamptz>,
created_at -> Timestamptz,
updated_at -> Timestamptz,
}
}
In the database migration, you are not specifying a precision for the amount column(DECIMAL), which Diesel is expecting.
Try adding a precision to the amount column as per below, and reapplying the migration.
CREATE TABLE expenses (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
description TEXT NULL,
amount DECIMAL(6,2) NOT NULL,
tax_year INT NOT NULL,
purchase_date TIMESTAMP WITH TIME ZONE NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

How could I find a value using a column other than the primary key with Diesel?

I have a table of users and would like to perform a search using Diesel to ensure that the username is not already taken. The only way I've found to perform queries is to use the find() function, which uses the primary key. Is there another function that searches using other fields, similarly to SELECT * FROM users WHERE username = foo?
Here's my users table:
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE users (
user_id uuid PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL UNIQUE,
username VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(60) NOT NULL
);
And here is the Rust struct I'm using:
#[derive(Queryable, AsChangeset, Deserialize, Serialize)]
#[table_name = "users"]
pub struct User {
pub user_id: uuid::Uuid,
pub username: String,
pub password: String,
}
#[derive(Insertable, Deserialize, Serialize)]
#[table_name = "users"]
pub struct InsertableUser {
pub username: String,
pub password: String,
}
Diesel provides a general .filter method that let's you construct arbitrary complex where clauses.
For your example query you look for something like:
users::table.filter(users::username.eq("foo"))

Pass any Diesel table as an argument

I have Rust project with Diesel implemented and it generated schema.rs file which contains all of my tables:
table! {
users (id) {
id -> Uuid,
name -> Varchar,
}
}
table! {
items (id) {
id -> Uuid,
name -> Varchar,
}
}
How could I pass any table as an argument inside of my utility function?
For instance,
pub trait Search {
fn internal_get_by_id(
diesel_table: diesel::table, // this argument should pass any diesel table
table_id: diesel::table::id, // this argument should pass Uuid from table
conn: &Conn,
id: Uuid,
) -> Fallible<Option<Self>>
where
Self: Sized,
{
diesel_table
.filter(table_id.eq(id))
.first(conn.raw())
.optional()
.map_err(Error::from)
}
fn get_by_id(conn: &Conn, id: Uuid) -> Fallible<Option<Self>>
where
Self: Sized;
}
impl Search for User {
fn get_by_id(conn: &Conn, id: Uuid) -> Fallible<Option<User>> {
Self::internal_get_by_id(users::table, users::id, conn, id)
}
}
impl Search for Item {
fn get_by_id(conn: &Conn, id: Uuid) -> Fallible<Option<Item>> {
Self::internal_get_by_id(items::table, items::id, conn, id)
}
}
First of all: It is generally not a good idea to write code that is generic over multiple tables/columns using Diesel in Rust, especially if you are new to the language and don't have a really good understanding on trait bounds and where clauses yet.
You need to list all trait bounds that are required to allow building this generic query so that everything can be checked at compile time. The following implementation should solve this (not tested, hopefully I did not miss a trait bound)
fn internal_get_by_id<T, C>(
diesel_table: T,
table_id: C,
conn: &Conn,
id: Uuid,
) -> Fallible<Option<Self>>
where
Self: Sized,
T: Table + FilterDsl<dsl::Eq<C, Uuid>>,
C: Column + Expression<SqlType = diesel::sql_types::Uuid>,
dsl::Filter<T, dsl::Eq<C, Uuid>>: LimitDsl,
dsl::Limit<dsl::Filter<T, dsl::Eq<C, Uuid>>>: LoadQuery<Conn, Self>,
Self: Queryable<dsl::SqlTypeOf<dsl::Limit<dsl::Filter<T, dsl::Eq<C, Uuid>>>>, Conn::Backend>,
{
diesel_table
.filter(table_id.eq(id))
.first(conn.raw())
.optional()
.map_err(Error::from)
}

Why do I get a trait not implemented for an optional field in Diesel struct

I have this struct:
#[table_name = "clients"]
#[derive(Serialize, Deserialize, Queryable, Insertable, Identifiable, Associations)]
pub struct Client {
pub id: Option<i64>,
pub name: String,
pub rank: Option<i64>,
}
and the following implementation:
impl Client {
pub fn get(name: String, connection: &PgConnection) -> Option<Self> {
match clients::table
.filter(clients::name.eq(&name))
.limit(1)
.load::<Client>(connection)
{
Ok(clients) => Some(clients[0]),
Err(_) => None,
}
}
}
which gives me the following error:
.load::<Client>(connection) {
^^^^ the trait `diesel::Queryable<diesel::sql_types::BigInt, _>` is not implemented for `std::option::Option<i64>`
Your error message says that you cannot query a BigInt (a 64 bits int) into an Option<i64>. That is because you forgot to say that id is nullable in your table declaration. It must look like:
table! {
clients {
id -> Nullable<BigInt>,
name -> Text,
rank -> Nullable<BigInt>,
}
}
You can see the implementation of Queryable you are looking for in the documentation.
For me the issue was not realising that diesel maps fields based entirely on the order of fields in the struct. It completely ignores field names.
If your struct has fields defined in a different order to the schema.rs file then it will map them incorrectly and cause type errors.
https://docs.diesel.rs/diesel/deserialize/trait.Queryable.html#deriving
When this trait is derived, it will assume that the order of fields on your struct match the order of the fields in the query. This means that field order is significant if you are using #[derive(Queryable)]. Field name has no effect.

What is the standard pattern to relate three tables (many-to-many relation) within Diesel?

Database - Postgres
I have the following relation:
users <—>> users_organizations <<—> organizations
Schema:
table! {
organizations (id) {
id -> Int4,
name -> Varchar,
}
}
table! {
users (id) {
id -> Int4,
name -> Varchar,
email -> Varchar,
password -> Varchar,
}
}
table! {
users_organizations (id, user_id, organization_id) {
id -> Int4,
user_id -> Int4,
organization_id -> Int4,
}
}
Models:
#[derive(Identifiable, Queryable, Debug, Serialize, Deserialize)]
pub struct Organization {
pub id: i32,
pub name: String,
}
#[derive(Identifiable, Queryable, PartialEq, Debug, Serialize, Deserialize)]
pub struct User {
pub id: i32,
pub name: String,
pub email: String,
pub password: String,
}
#[derive(Identifiable, Queryable, Debug, Associations, Serialize, Deserialize)]
#[belongs_to(User)]
#[belongs_to(Organization)]
#[table_name = "users_organizations"]
pub struct UserOrganization {
pub id: i32,
pub user_id: i32,
pub organization_id: i32,
}
I want to create an organization. To support such relation, I have to manually add ids of user and organization to the users_organizations table. Is there any better approach to implement such relation?
let new_organization = NewOrganization { name: &msg.name };
let organization = insert_into(organizations::table)
.values(&new_organization)
.get_result::(conn)
.map_err(|_| error::ErrorInternalServerError(“Error creating organization”))?;
let new_user_org = NewUserOrganizationIDs {
user_id: msg.user_id,
organization_id: organization.id,
};
insert_into(users_organizations::table)
.values(&new_user_org)
.get_result::<UserOrganization>(conn)
.map_err(|_| error::ErrorInternalServerError("Error creating user-organization data"))
Same question here. In case of selecting all organizations that relate to user (and vice verse) I came up with the following code:
let user = users::table.filter(users::id.eq(&msg.user_id))
.get_result::<User>(conn)
.map_err(|_| error::ErrorNotFound("User doesn't exist"))?;
let user_organizations = UserOrganization::belonging_to(&user)
.get_results::<UserOrganization>(conn)
.map_err(|_| error::ErrorNotFound("User doesn't have any organization"))?;
let mut organization_ids = vec![];
for user_org in &user_organizations {
organization_ids.push(user_org.organization_id);
}
organizations::table.filter(organizations::id.eq_any(organization_ids))
.get_results::<Organization>(conn)
.map_err(|_| error::ErrorNotFound("Organizations don't exist"))
This answer is from the Diesel chat by #SRugina and #weiznich (edited and adapted for the question).
How do I write a many-to-many relationship with Diesel?
I normally combine belonging_to and join, so something like:
UserOrganization::belonging_to(&organizations)
.inner_join(user::table)
Is there anything akin to belonging_to_many?
No, belonging_to_many does not exist because Diesel does not try to hide the database from you. Doing that would cause issues as soon as you want to do complex or non-standard things. Depending on your exact use case, joining all three tables could also be an option.
How do I use inner_join in this scenario?
You have three tables: users, organizations and user_organizations and you want to get all organizations for a specific user.
There are two variants doing that. The first variant is only one query, but may not match your required data layout if you want to do that for all users:
users::table
.inner_join(user_organizations::table.inner_join(organizations::table))
.filter(users::id.eq(user_id))
.select(organizations::all_columns)
.load::<Organization>(conn)?;
The second variant allows grouping the results for each user using the built-in associations API:
let user = users::table
.find(user_id)
.first::<User>(conn)?;
UserOrganization::belonging_to(&user)
.inner_join(organizations::table)
.select(organizations::all_columns)
.load::<Organization>(conn)?;
Inserting requires three separate inserts. We do not try to hide that because in the end it's a user choice how to handle data consistency in case of a failed insert there. Using a transaction is a common choice.

Resources