I have the following SQL table scheme
CREATE TABLE reservation (
id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
timespan TSTZRANGE
);
INSERT INTO reservation(timespan) VALUES
(TSTZRANGE(now() + INTERVAL '0 hour', now() + INTERVAL '1 hour')),
(TSTZRANGE(now() + INTERVAL '2 hour', now() + INTERVAL '3 hour'));
and using rust-sqlx, I would like to retrieve those rows directly into a struct.
/*
[dependencies]
chrono = "0.4"
tokio = { version = "1", features = ["full"] }
sqlx = { version = "0.5", features = [ "runtime-tokio-native-tls" , "postgres" ] }
*/
use chrono::prelude::*;
use sqlx::FromRow;
use sqlx::postgres::{types::PgRange, PgPoolOptions};
#[derive(Debug, FromRow)]
struct Reservation {
id: i32,
timespan: PgRange<DateTime<Utc>>,
}
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
let db_url: String = "postgresql://postgres:postgres#localhost/mydb".to_owned();
let pool = PgPoolOptions::new()
.max_connections(2)
.connect(&db_url)
.await?;
let select_query = sqlx::query_as::<_, Reservation>("SELECT id, timespan FROM reservation");
let reservations: Vec<Reservation> = select_query.fetch_all(&pool).await?;
dbg!("{:?}", reservations);
Ok(())
}
I am getting the following 2 following errors
error[E0277]: the trait bound `PgRange<chrono::DateTime<chrono::Utc>>: Type<_>` is not satisfied
...
...
error[E0277]: the trait bound `chrono::DateTime<chrono::Utc>: Type<Postgres>` is not satisfied
How do I go about manually implementing these trait bounds? or is there a simpler way to achieve the same thing?
Newbie mistake! I just had to add chrono flag for sqlx in Cargo.toml
sqlx = { version = "0.5", features = [ "runtime-tokio-native-tls" , "postgres" , "chrono" ] }
Related
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.
I'm using the rust-postgres crate to ingest data. This is a working example adding rows successfully:
let name: &str = "hello from rust";
let val: i32 = 123;
let now: DateTime<Utc> = Utc::now();
let timestamp = now.format("%Y-%m-%dT%H:%M:%S%.6f").to_string();
client.execute(
"INSERT INTO trades VALUES(to_timestamp($1, 'yyyy-MM-ddTHH:mm:ss.SSSUUU'),$2,$3)",
&[×tamp, &name, &val],
)?;
This doesn't look so nice as I have to do this forward and back string conversion, I would like to be able to write something like
let name: &str = "hello from rust";
let val: i32 = 123;
let now: DateTime<Utc> = Utc::now();
client.execute(
"INSERT INTO trades VALUES($1,$2,$3)",
&[&now, &name, &val],
)?;
What's the most performant way of ingesting timestamps in this way?
Edit:
Here's the returned error from the second example above
Error: Error { kind: ToSql(0), cause: Some(WrongType { postgres: Timestamp, rust: "chrono::datetime::DateTime<chrono::offset::utc::Utc>" }) }
And my cargo.toml looks like this (which has the chrono feature enabled for the rust postgres crate):
[dependencies]
chrono = "0.4.19"
postgres={version="0.19.0", features=["with-serde_json-1", "with-bit-vec-0_6", "with-chrono-0_4"]}
I think the problem is a mismatch between your postgres schema and your Rust type: the error seems to say that your postgres type is timestamp, while your rust type is DateTime<Utc>.
If you check the conversion table, DateTime<Utc> converts to a TIMESTAMP WITH TIME ZONE. The only types which convert to TIMESTAMP are NaiveDateTime and PrimitiveDateTime.
As per Masklinn's response, I needed to pass a NaiveDateTime type for this to work, the full example with naive_local looks like:
use postgres::{Client, NoTls, Error};
use chrono::{Utc};
use std::time::SystemTime;
fn main() -> Result<(), Error> {
let mut client = Client::connect("postgresql://admin:quest#localhost:8812/qdb", NoTls)?;
// Basic query
client.batch_execute("CREATE TABLE IF NOT EXISTS trades (ts TIMESTAMP, date DATE, name STRING, value INT) timestamp(ts);")?;
// Parameterized query
let name: &str = "rust example";
let val: i32 = 123;
let utc = Utc::now();
let sys_time = SystemTime::now();
client.execute(
"INSERT INTO trades VALUES($1,$2,$3,$4)",
&[&utc.naive_local(), &sys_time, &name, &val],
)?;
// Prepared statement
let mut txn = client.transaction()?;
let statement = txn.prepare("insert into trades values ($1,$2,$3,$4)")?;
for value in 0..10 {
let utc = Utc::now();
let sys_time = SystemTime::now();
txn.execute(&statement, &[&utc.naive_local(), &sys_time, &name, &value])?;
}
txn.commit()?;
println!("import finished");
Ok(())
}
contacts has a data structure as HashMap, I'm using PostgreSQL client -rust-postgres to insert contact's key and value into a table, then I want to select from the table. Below is what I tried so far. I need help with writing the right syntax.
use postgres::{Client, NoTls};
use std::collections::HashMap;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::connect("host=127.0.0.1 user=postgres", NoTls)?;
client.simple_query("
DROP TABLE
IF EXISTS following_relation;
")?;
client.simple_query("
CREATE TABLE following_relation (
id SERIAL NOT NULL PRIMARY KEY,
relation JSON NOT NULL
)
")?;
let mut contacts = HashMap::new();
let mut v: Vec<String> = Vec::new();
v = vec!["jump".to_owned(), "jog".to_string()];
contacts.insert("Ashley", v.clone());
for (name, hobby) in contacts.iter() {
// 1. How to write sql statement with parameters?
client.execute(
"INSERT INTO following_relation(relation)
VALUE ('{"name" : $1, "hobby" : $2}')",
&[&name, &hobby],
)?;
}
for row in client.query("SELECT id, relation FROM following_relation", &[])? {
// 2. How to read from parse the result?
let id: i32 = row.get(0);
let relation = row.get(1);
//println!("found person: {} {} {:?}", id, relation["name"], relation["hobby"]);
}
Ok(())
}
I've been given the hints
Like the error message says, your query has VALUE but it needs to be VALUES.
Query parameters cannot be interpolated into strings. You should build the object in Rust, and use https://docs.rs/postgres/0.17.0/postgres/types/struct.Json.html to wrap the types when inserting.
I have no idea how to apply pub struct Json<T>(pub T); here.
How to build the query required in function execute?
pub fn execute<T: ?Sized>(
&mut self,
query: &T,
params: &[&(dyn ToSql + Sync)]
) -> Result<u64, Error>
where
T: ToStatement,
UPDATED, I tried with a more brief code sample
use postgres::{Client, NoTls};
use postgres::types::Json;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct relations {
name : String,
hobby: Vec<String>
}
pub struct Json<T>(pub T);
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::connect("host=127.0.0.1 user=postgres", NoTls)?;
client.simple_query("
DROP TABLE
IF EXISTS following_relation;
")?;
client.simple_query("
CREATE TABLE following_relation (
id SERIAL PRIMARY KEY,
relation JSON NOT NULL
)
")?;
let rel = relations {
name: "czfzdxx".to_string(),
hobby: vec![
"basketball".to_string(),
"jogging".to_string()
],
};
client.execute(
r#"INSERT INTO following_relation(relation)
VALUE ($1)"#,
&[&Json(&rel)]
)?;
Ok(())
}
I get
error[E0432]: unresolved import `postgres::types::Json`
You want Rust raw string literal:
for (name, hobby) in contacts.iter() {
client.execute(
r#"INSERT INTO following_relation(relation)
VALUE ('{"name" : ($1), "hobby" : ($2)}')"#,
&[&name, &following],
)?;
}
Between the start r#" and the end "#, your string literals can have any character except # itself without escaping. If you also want # itself, then starts the raw string literals with multiple #s and ends with matching number of #s.
Here is main.rs:
use postgres::{Client, NoTls};
use serde::{Deserialize, Serialize};
use postgres_types::Json;
use postgres_types::{FromSql};
#[derive(Debug, Deserialize, Serialize, FromSql)]
struct Relation {
name : String,
hobby: Vec<String>
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::connect("host=127.0.0.1 user=postgres", NoTls)?;
client.simple_query("
DROP TABLE
IF EXISTS following_relation;
")?;
client.simple_query("
CREATE TABLE following_relation (
id SERIAL PRIMARY KEY,
relation JSON NOT NULL
)
")?;
let rel = Relation {
name: "czfzdxx".to_string(),
hobby: vec![
"basketball".to_string(),
"jogging".to_string()
],
};
client.execute(
"INSERT INTO following_relation (relation) VALUES ($1)",
&[&Json::<Relation>(rel)]
)?;
for row in &client.query("SELECT relation FROM following_relation", &[]).unwrap() {
let rel: Json<Relation> = row.get(0);
println!("{:?}", rel);
}
Ok(())
}
and Cargo.toml:
[package]
name = "testapp"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
postgres = {version = "0.17.0"}
tokio-postgres = "0.5.1"
serde = {version = "1.0.104", features = ["derive"]}
postgres-types = {version = "0.1.0", features = ["derive", "with-serde_json-1"]}
serde_json = "1.0.45"
And here is the relevant documentation used: postgres_types and postgres. Search for serde_json, ToSql and FromSql traits are implemented for this third-party type.
What's the right Rust data type to use for a timestamptz when using postgres version 0.17.0 with Rust 1.40.0?
I read the docs for Timestamp but have no idea what this means or how to implement it.
The readme for 0.17.0-alpha.1 has a table which says that timezone corresponds to Rust types time::Timespec or chrono::DateTime<Utc> but neither works for me.
When I try to use the stipulated features in my Cargo.toml using:
[dependencies]
postgres = {version="0.17.0-alpha.1", features=["with-chrono", "with-time"]}
I get this error:
the package `mypackage` depends on `postgres`, with features: `with-time, with-chrono` but `postgres` does not have these features.
Here's some functional code and corresponding dependencies. I want to be able to read and print the timezone per row (commented out)
main.rs
use postgres::{Client, Error, NoTls};
extern crate chrono;
use chrono::{DateTime, Local, NaiveDateTime, TimeZone, Utc};
extern crate time;
use time::Timespec;
pub fn main() -> Result<(), Error> {
let mut client = Client::connect("host=localhost user=postgres", NoTls)?;
client.simple_query(
"
CREATE TABLE mytable (
name text NOT NULL,
timestamp timestamptz NOT NULL)",
)?;
client.execute("INSERT INTO mytable VALUES ('bob', now());", &[])?;
for row in client.query("SELECT * FROM mytable", &[])? {
let name: &str = row.get(0);
// let timestamp: chrono::DateTime<Utc> = row.get(1); //doesnt work
// let timestamp: Timespec = row.get(1); //doesnt work
println!("name: {}", name);
// println!("timestamp: {}", timestamp);
}
Ok(())
}
Uncommenting
let timestamp: Timespec = row.get(1); //doesnt work
error[E0277]: the trait bound `time::Timespec: postgres_types::FromSql<'_>` is not satisfied
--> src/main.rs:30:39 | 30 |
let timestamp: Timespec = row.get(1); //doesnt work
^^^ the trait `postgres_types::FromSql<'_>` is not implemented for `time::Timespec`
Uncommenting
let timestamp: chrono::DateTime<Utc> = row.get(1); //doesnt work
error[E0277]: the trait bound `chrono::DateTime<chrono::Utc>: postgres_types::FromSql<'_>` is not satisfied
--> src/main.rs:29:52 29 |
let timestamp: chrono::DateTime<Utc> = row.get(1); //doesnt work
^^^ the trait `postgres_types::FromSql<'_>` is not implemented for `chrono::DateTime<chrono::Utc>`
Cargo.toml
[dependencies]
postgres = "0.17.0"
chrono = "0.4.10"
time = "0.1.14"
This link says to use time = "0.1.14". latest version also fails https://crates.io/crates/postgres/0.17.0-alpha.1
Once you know what features are available, it's reasonably direct to see that you need to use the with-chrono-0_4 feature.
use chrono::{DateTime, Utc}; // 0.4.10
use postgres::{Client, Error, NoTls}; // 0.17.0, features = ["with-chrono-0_4"]
pub fn main() -> Result<(), Error> {
let mut client = Client::connect("host=localhost user=stack-overflow", NoTls)?;
client.simple_query(
r#"
CREATE TABLE mytable (
name text NOT NULL,
timestamp timestamptz NOT NULL
)"#,
)?;
client.execute("INSERT INTO mytable VALUES ('bob', now());", &[])?;
for row in client.query("SELECT * FROM mytable", &[])? {
let name: &str = row.get(0);
let timestamp: DateTime<Utc> = row.get(1);
dbg!(name, timestamp);
}
Ok(())
}
[src/main.rs:20] name = "bob"
[src/main.rs:20] timestamp = 2020-01-16T01:21:58.755804Z
Thanks to https://github.com/sfackler/rust-postgres/issues/211, this works using version 0.15.0 of the postgres crate, but I'd like a solution using version 0.17.0.
main.rs
extern crate postgres;
use postgres::{Connection, TlsMode};
extern crate chrono;
use chrono::{DateTime, Local, NaiveDateTime, TimeZone, Utc};
fn main() {
let conn = Connection::connect("postgresql://postgres#localhost:5432", TlsMode::None).unwrap();
conn.execute(
"CREATE TABLE person (
name VARCHAR NOT NULL,
timestamp timestamptz
)",
&[],).unwrap();
conn.execute("INSERT INTO person VALUES ('bob', now());", &[]).unwrap();
for row in &conn.query("SELECT * FROM person", &[]).unwrap() {
let name: String = row.get(0);
let timestamp: chrono::DateTime<Utc> = row.get(1);
println!("name: {}", name);
println!("timestamp: {}", timestamp);
}
}
Output:
name: bob
timestamp: 2020-01-15 23:56:05.411304 UTC
Cargo.toml
[dependencies]
postgres = { version = "0.15", features = ["with-chrono"] }
chrono = "0.4.10"
time = "0.1.14"
I'm querying an instance of PostgreSQL and selecting a sum of a decimal value:
db=# SELECT SUM(distance) AS total_distance FROM table_name WHERE deleted_at IS NULL;
total_distance
-----------------------
3808.0666666666666578
(1 row)
When I try to execute this query in Rust:
extern crate postgres;
use postgres::{Connection, TlsMode};
fn main() {
let conn = Connection::connect("postgresql://u:p#localhost:5432/db", TlsMode::None).unwrap();
let query = "SELECT SUM(distance) AS total_distance FROM table_name WHERE deleted_at IS NULL;";
for row in &conn.query(query, &[]).unwrap() {
let total_distance: f64 = row.get("total_distance");
println!("{}", total_distance);
}
}
Results in:
thread 'main' panicked at 'error retrieving column "total_distance": Error(Conversion(WrongType(Type(Numeric))))'
I've seen in various threads that the Numeric type isn't supported by the Postgres crate, so I've tried creating my own numeric type:
#[derive(Debug)]
struct Float64(f64);
impl FromSql for Float64 {
fn from_sql(ty: &Type, raw: &[u8]) -> Result<Float64, Box<Error + Sync + Send>> {
let bytes = raw.try_into().expect("failed!");
Ok(Float64(f64::from_be_bytes(bytes)))
}
fn from_sql_null(ty: &Type) -> Result<Float64, Box<Error + Sync + Send>> {
Ok(Float64(0.0))
}
fn from_sql_nullable(
ty: &Type,
raw: Option<&[u8]>,
) -> Result<Float64, Box<Error + Sync + Send>> {
match raw {
None => Ok(Float64(0.0)),
Some(value) => Float64::from_sql(ty, value),
}
}
fn accepts(ty: &Type) -> bool {
NUMERIC.eq(ty)
}
}
impl Display for Float64 {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string())
}
}
But this still doesn't work as the raw bytes fail to unwrap:
thread 'main' panicked at 'failed!: TryFromSliceError(())', src/libcore/result.rs:1165:5
raw: &[u8] has the length of 18, which is why it can't unwrap. What would be the best way to convert an 18 byte slice to f64?