How to write a good select sub query in SeaORM? - postgresql

select wallets.*, users.name as name,
(select max(regist_at) from payments
where payment_item_id = 2 and receiver_id = 1) as newest_login_at
from wallets
inner join users on wallets.user_id = users.user_id
where wallets.user_id = 1;
I would like to execute the above sql statement in sea-orm, but I don't know how to do it.
If possible, I would like to write the following natural code, but this is a compile error.
Does anyone know a better way?
rustc 1.65.0-nightly | sea-orm "0.9.2"
#[derive(Debug, FromQueryResult, Serialize, Deserialize)]
pub struct WalletSummary {
pub user_id: i64,
pub name: String,
pub amount: i64,
pub regist_at: DateTimeWithTimeZone,
pub newest_login_at: Option<DateTimeWithTimeZone>,
}
let user_id: i64 = 2;
let wallet_summary = Wallets::find_by_id(user_id)
.column(users::Column::Name)
.column_as(
Payments::find()
.column(payments::Column::RegistAt.max())
.filter(payments::Column::PaymentItemId.eq(1))
.filter(payments::Column::ReceiverId.eq(user_id)),
"newest_login_at"
  )
.join(JoinType::InnerJoin, wallets::Relation::Users.def())
.into_model::<WalletSummary>()
.one(db)
.await?

Related

Problems working with rust and postgres data types

I'm triying to do an Api REST with rust and postres but I cant make it work because the relation between these two.
The actual problem is that I have a column in postgres as jsonb and when I return the data and try to save it in a struct always gives error. Same problem when I try to save the data.
This are the models.(The option is only because I'm testing thing, it should return a value)
#[derive(Debug, Serialize, Deserialize)]
pub struct CategoryView {
pub id: i32,
pub category_name: String,
pub category_custom_fields: Option<serde_json::Value>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CategoryPayload {
pub category_name: String,
pub category_custom_fields: Option<serde_json::Value>,
}
This are the postgres queries:
fn find_all(conn: &mut DbPooled) -> Result<Vec<CategoryView>, DbError> {
let mut query = "SELECT id, category_name, category_custom_fields FROM accounting.categories".to_owned();
query.push_str(" WHERE user_id = $1");
query.push_str(" AND is_deleted = false");
let items = conn.query(&query, &[&unsafe { CURRENT_USER.to_owned() }])?;
let items_view: Vec<CategoryView> = items
.iter()
.map(|h| CategoryView {
id: h.get("id"),
category_name: h.get("category_name"),
category_custom_fields: h.get("category_custom_fields"),
})
.collect();
Ok(items_view)
}
fn add(payload: &CategoryPayload, conn: &mut DbPooled) -> Result<CategoryView, DbError> {
let mut query =
"INSERT INTO accounting.categories (user_id, category_name, category_custom_fields, create_date, update_date)"
.to_owned();
query.push_str(" VALUES ($1, $2, $3, now(), now())");
query.push_str(" RETURNING id");
let item_id = conn
.query_one(
&query,
&[
&unsafe { CURRENT_USER.to_owned() },
&payload.category_name,
&payload.category_custom_fields,
],
)?
.get(0);
let inserted_item = CategoryView {
id: item_id,
category_name: payload.category_name.to_string(),
category_custom_fields: payload.category_custom_fields,
};
Ok(inserted_item)
}
with update happens to but I think is the same solution that the one form the add function.
The error is:
the trait bound `serde_json::Value: ToSql` is not satisfied
the following other types implement trait `ToSql`:
&'a T
&'a [T]
&'a [u8]
&'a str
Box<[T]>
Box<str>
Cow<'a, str>
HashMap<std::string::String, std::option::Option<std::string::String>, H>
and 17 others
required for `std::option::Option<serde_json::Value>` to implement `ToSql`
required for the cast from `std::option::Option<serde_json::Value>` to the object type `dyn ToSql + Sync`rustcClick for full compiler diagnostic`
For what I read serde_json::Value is the equivalent to jsonb so I don't understand it.
I had a similar problem previously trying to work with a decimal value in postgres, I had to change it to integer and save the value multiplied in the database. Is a money column so maybe if you help me with that too I will change it back.
I was hopping some could explain to me how to fix it and why this happens so I can avoid have to ask for help with the datatypes in the future.
The problem was in the depencies.
It looks like some dependencies have features that add aditional functionablility.
I had installed the dependencie without any feature so when I added the features it started to work without issues.
Only had to change from:
[dependencies]
postgres = "0.19.4"
to:
[dependencies]
postgres = { version = "0.19.4", features = ["with-chrono-0_4", "with-serde_json-1"] }
Chrono for dates and serde_json for jsonb.
I'll check the decimal problem but I think will be the same solution.

Error inserting multiple columns into Postgresql DB From SQLX

I'm trying to perform an insert query into my Postgresql DB, but I am getting a mismatched type issue that I'm unsure how to solve.
Here's the code:
pub struct Product {
pub id: i32,
pub product_id: i64,
pub title: String,
pub handle: String,
pub tags: Json<Vec<String>>,
pub product_type: String,
pub image_url: String,
pub created_at: String,
pub updatedAt: String,
}
pub struct ProductPatch {
pub product_id: i64,
pub title: String,
pub handle: String,
pub tags: Vec<String>,
pub product_type: String,
pub image_url: String
}
async fn add_product(pool: &Db, product: &ProductPatch) -> Result<i64, sqlx::Error> {
let rec = sqlx::query!(
r#"
INSERT INTO products (product_id, title, handle, tags, product_type, image_url)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING product_id, title, handle, tags, product_type, image_url
"#,
&product.product_id,
&product.title,
&product.handle,
&product.tags,
&product.product_type,
&product.image_url
)
.fetch_one(pool)
.await?;
Ok(rec.product_id)
}
Here's the error:
mismatched types
expected enum Result<_, sqlx::Error>
found enum Result<Record, anyhow::Error>
I suppose you've imported anyhow's Result, so your return type Result<i64, sqlx::Error> is actually interpreted as anyhow::Result<i64, sqlx::Error>.
If you intend to return sqlx::Result, either change your return type to sqlx::Result<i64, sqlx::Error> or change your import statements.
If you intend to return anyhow::Result, maybe just return anyhow::Result<i64>.

how to using rust diesel to do the full text query

I am trying to using diesel diesel = { version = "1.4.8", features = ["postgres","64-column-tables","chrono","serde_json"] } to do a full text query, this is the sql command look like:
SELECT * FROM article a WHERE to_tsvector('english', title) ## to_tsquery('english','Rate|Limiting')
how to using rust diesel to do this query? I am using the like right now and want to switch to full text search, this is the like query code main.rs look like:
#[macro_use]
extern crate diesel;
use diesel::{TextExpressionMethods, QueryDsl, RunQueryDsl};
use rust_wheel::config::db::config;
use crate::model::diesel::dict::dict_models::Article;
mod model;
fn main() {
use model::diesel::dict::dict_schema::article as article_table;
let connection = config::establish_connection();
let mut query = article_table::table.into_boxed::<diesel::pg::Pg>();
query = query.filter(article_table::title.like(format!("{}{}{}","%","demo","%")));
let query_result = query.load::<Article>(&connection);
}
and this is the schema files dict_schema.rs look like:
table! {
article (id) {
id -> Int8,
user_id -> Int8,
title -> Varchar,
author -> Varchar,
guid -> Varchar,
created_time -> Int8,
updated_time -> Int8,
link -> Nullable<Varchar>,
sub_source_id -> Int8,
cover_image -> Nullable<Varchar>,
channel_reputation -> Int4,
editor_pick -> Nullable<Int4>,
}
}
and this is the model files dict_models.rs look like:
// Generated by diesel_ext
#![allow(unused)]
#![allow(clippy::all)]
use std::io::Write;
use diesel::deserialize::FromSql;
use diesel::pg::Pg;
use diesel::serialize::{Output, ToSql};
use diesel::sql_types::Jsonb;
use rocket::serde::Serialize;
use serde::Deserialize;
use chrono::DateTime;
use chrono::Utc;
use crate::model::diesel::dict::dict_schema::*;
#[derive(Queryable,QueryableByName,Debug,Serialize,Deserialize,Default,Clone)]
#[table_name = "article"]
pub struct Article {
pub id: i64,
pub user_id: i64,
pub title: String,
pub author: String,
pub guid: String,
pub created_time: i64,
pub updated_time: i64,
pub link: Option<String>,
pub sub_source_id: i64,
pub cover_image: Option<String>,
pub channel_reputation: i32,
pub editor_pick: Option<i32>,
}
and this is the dependencies Cargo.toml look like:
[package]
name = "rust-learn"
version = "0.1.0"
edition = "2018"
[dependencies]
rocket = { version = "=0.5.0-rc.2", features = ["json"] }
serde = { version = "1.0.64", features = ["derive"] }
serde_json = "1.0.64"
serde_derive = "1.0"
# database
diesel = { version = "1.4.7", features = ["postgres","serde_json"] }
dotenv = "0.15.0"
jsonwebtoken = "7"
chrono = "0.4"
config = "0.11"
ring = "0.16.20"
md5 = "0.7.0"
data-encoding = "2.3.2"
diesel_full_text_search = "1.0.1"
bigdecimal = "0.3.0"
# reddwarf public component
rust_wheel = { git = "https://github.com/jiangxiaoqiang/rust_wheel.git" }
What shuld I do to change to like query to full text search query? I am searching from internet but no one talk about this issue. BTW: this is the cargo version:
➜ rust-learn git:(group-by) ✗ cargo version
cargo 1.62.0 (a748cf5a3 2022-06-08)
and this is the rust version:
➜ rust-learn git:(group-by) ✗ rustc --version
rustc 1.62.0 (a8314ef7d 2022-06-27)
any idea about this issue? what should i do to using the full text search in diesel?
I have tried to add the dependencies diesel_full_text_search = "1.0.1" and tweak the main.rs code like this:
#[macro_use]
extern crate diesel;
use diesel::{TextExpressionMethods, QueryDsl, RunQueryDsl};
use diesel_full_text_search::{to_tsquery, to_tsvector, TsQueryExtensions};
use rust_wheel::config::db::config;
use diesel_full_text_search::TsVectorExtensions;
use crate::model::diesel::dict::dict_models::Article;
mod model;
fn main() {
use model::diesel::dict::dict_schema::article as article_table;
let connection = config::establish_connection();
let mut query = article_table::table.into_boxed::<diesel::pg::Pg>();
let filter_title = "经济 文化";
let query_items: Vec<&str> = filter_title.trim().split_whitespace().collect();
let query_array = query_items.join(" & ");
let tsquery = to_tsquery(query_array);
let tsvector = to_tsvector("'dolphinzhcfg', title");
query = query.filter(&tsvector.matches(&tsquery));
let query_result = query.load::<Article>(&connection);
}
shows error:
mismatched types [E0308] expected `char`, found `&to_tsquery<String>`
what should I do to fixed this problem?
Checkout the following functions:
diesel_full_text_search::to_tsvector
diesel_full_text_search::to_tsquery
diesel_full_text_search::TsVectorExtensions::matches

DateTime<Utc> compiles but not DateTime<Local> querying a table with a column defined as timestamp with time zone

I have a postgresql-table with a column defined as timestamp with time zone. The table is mapped to this struct:
#[derive(Serialize, Queryable)]
pub struct Location {
pub publication_time: DateTime<Utc>,
pub id: i32,
pub name: String,
pub latitude: BigDecimal,
pub longitude: BigDecimal,
}
The schema have this definition:
table! {
locations {
publication_time -> Timestamptz,
id -> Integer,
name -> Text,
latitude -> Numeric,
longitude -> Numeric,
}
}
(partial) Cargo.toml:
serde = "1.0.125"
serde_json = "1.0.64"
serde_derive = "1.0.125"
diesel = { version = "1.4.6", features = ["postgres", "r2d2", "chrono", "numeric"] }
bigdecimal = { version = "0.1.0", features = ["serde"] }
chrono = { version = "0.4.19", features = ["serde"] }
The function that queries the database:
fn get_all_locations(pool: web::Data<Pool>) -> Result<Vec<Location>, diesel::result::Error> {
let conn = pool.get().unwrap();
let items = locations.load::<Location>(&conn)?;
Ok(items)
}
This is then serialized to a JSON-array using serde_json. The DateTime in the database is 2021-04-08 15:02:02.514+02. When DateTime is Utc the program compiles fine, but the DateTime shown in UTC like 2021-04-08T13:02:02.514Z. I changed publication_time to DateTime<Local> to retain the time zone information but then cargo build fails with:
error[E0277]: the trait bound `DateTime<Local>: FromSql<diesel::sql_types::Timestamptz, Pg>` is not satisfied
--> src/controller.rs:21:27
|
21 | let items = locations.load::<Location>(&conn)?;
| ^^^^ the trait `FromSql<diesel::sql_types::Timestamptz, Pg>` is not implemented for `DateTime<Local>`
|
= help: the following implementations were found:
<DateTime<Utc> as FromSql<diesel::sql_types::Timestamptz, Pg>>
= note: required because of the requirements on the impl of `diesel::Queryable<diesel::sql_types::Timestamptz, Pg>` for `DateTime<Local>`
= note: 2 redundant requirements hidden
= note: required because of the requirements on the impl of `diesel::Queryable<(diesel::sql_types::Timestamptz, diesel::sql_types::Integer, diesel::sql_types::Text, diesel::sql_types::Numeric, diesel::sql_types::Numeric), Pg>` for `models::Location`
= note: required because of the requirements on the impl of `LoadQuery<_, models::Location>` for `locations::table`
I have another program that insert to this table and this works and the only difference is derive(Deserialize, Insertable).
#[derive(Deserialize, Insertable)]
pub struct Location {
pub publication_time: DateTime<Local>,
pub id: i32,
pub name: String,
pub latitude: BigDecimal,
pub longitude: BigDecimal,
}
Mapping a Timestamptz field to a DateTime<Local> is not supported by diesel itself, as it only provides the corresponding impl for DateTime<Utc>.
You can work this around by using the #[diesel(deserialize_as = "…")] attribute on the corresponding field and providing your own deserialization wrapper:
#[derive(Serialize, Queryable)]
pub struct Location {
#[diesel(deserialize_as = "MyDateTimeWrapper")]
pub publication_time: DateTime<Local>,
pub id: i32,
pub name: String,
pub latitude: BigDecimal,
pub longitude: BigDecimal,
}
pub struct MyDatetimeWrapper(DateTime<Local>);
impl Into<DateTime<Local>> for MyDatetimeWrapper {
fn into(self) -> DateTime<Local> {
self.0
}
}
impl<DB, ST> Queryable<ST, DB> for MyDateTimeWrapper
where
DB: Backend,
DateTime<Utc>: Queryable<ST, DB>,
{
type Row = <DateTime<Utc> as Queryable<ST, DB>>::Row;
fn build(row: Self::Row) -> Self {
Self(<DateTime<Utc> as Queryable<ST, DB>>::build(row).with_timezone(&Local))
}
}

Deleting from an associated table with a subquery using Diesel from a postgres database

I have a query that I am trying to translate from SQL into rust/diesel but am running into issues with creating a subquery using diesel.
I am using diesel = "1.4.2" along with the postgres feature.
I have the following schema and models...
#[macro_use]
extern crate diesel;
mod schema {
table! {
jobs (id) {
id -> Int4,
}
appointments (id) {
id -> Int4,
}
categories (id) {
id -> Int4
}
appointments_categories(appointment_id, category_id) {
appointment_id -> Int4,
category_id -> Int4
}
}
}
mod models {
#[derive(Debug, Identifiable)]
pub struct Job {
pub id: i32,
}
#[derive(Debug, Identifiable)]
pub struct Appointment {
pub id: i32,
}
#[derive(Debug, Identifiable)]
pub struct Category {
pub id: i32,
}
#[derive(Debug, Identifiable)]
#[primary_key(appointment_id, appointment_type_id)]
pub struct AppointmentCategory {
pub id: i32,
}
}
fn main() {}
And then I have this SQL query:
DELETE FROM appointments_categories
WHERE ROW ("appointment_id", "category_id")
IN (
SELECT
appointment.id AS appointment_id, appointments_categories. "category_id" FROM appointment
INNER JOIN appointments_categories ON appointments_categories. "appointment_id" = appointment.id
WHERE appointment."job_id" = 125
LIMIT 10000);
So far I have tried to use the following approach but unable to figure out how to bind the subquery/expression.
let sub_query = appointment_dsl::appointment
.inner_join(appt_cat_dsl::appointments_categories)
.filter(appointment_dsl::job_id.eq(job_id))
.select((appointment_dsl::id, appt_cat_dsl::category_id));
let rows_deleted = delete(appt_cat_dsl::appointments_categories
.filter(sql(format!("ROW(appointmentId, appointmentTypeId) IN {}", subquery))))?;
I understand that there are other ways to write the delete query but I need to be able to limit the number of rows that it deletes. The associated/junction table is massive with 3 million rows per job and the job runs every 15min. Deleting it all at once locks the DB up so it isn't an option.
Sorry I can't make a reproducible sample on the rust playground since it doesn't have diesel.