Add postgres code
- Set is currently working - Other methods to test Signed-off-by: Lee Smet <lee.smet@hotmail.com>
This commit is contained in:
parent
0ad4cc2fb4
commit
0a8e2040ef
829
heromodels/Cargo.lock
generated
829
heromodels/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -15,11 +15,20 @@ tst = { path = "../tst" }
|
|||||||
heromodels-derive = { path = "../heromodels-derive" }
|
heromodels-derive = { path = "../heromodels-derive" }
|
||||||
heromodels_core = { path = "../heromodels_core" }
|
heromodels_core = { path = "../heromodels_core" }
|
||||||
rhailib_derive = { package = "derive", path = "../../rhailib/src/derive" }
|
rhailib_derive = { package = "derive", path = "../../rhailib/src/derive" }
|
||||||
rhai = { version = "1.21.0", features = ["std", "sync", "decimal", "internals"] } # Added "decimal" feature, sync for Arc<Mutex<>>
|
rhai = { version = "1.21.0", features = [
|
||||||
|
"std",
|
||||||
|
"sync",
|
||||||
|
"decimal",
|
||||||
|
"internals",
|
||||||
|
] } # Added "decimal" feature, sync for Arc<Mutex<>>
|
||||||
rhai_client_macros = { path = "../rhai_client_macros" }
|
rhai_client_macros = { path = "../rhai_client_macros" }
|
||||||
strum = "0.26"
|
strum = "0.26"
|
||||||
strum_macros = "0.26"
|
strum_macros = "0.26"
|
||||||
uuid = { version = "1.17.0", features = ["v4"] }
|
uuid = { version = "1.17.0", features = ["v4"] }
|
||||||
|
postgres = { version = "0.19.10", features = ["with-serde_json-1"] }
|
||||||
|
jsonb = "0.5.2"
|
||||||
|
r2d2 = "0.8.10"
|
||||||
|
r2d2_postgres = "0.18.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
225
heromodels/examples/postgres_model_example.rs
Normal file
225
heromodels/examples/postgres_model_example.rs
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
use heromodels::db::postgres::Config;
|
||||||
|
use heromodels::db::{Collection, Db};
|
||||||
|
use heromodels::models::userexample::user::user_index::{is_active, username};
|
||||||
|
use heromodels::models::{Comment, User};
|
||||||
|
use heromodels_core::Model;
|
||||||
|
|
||||||
|
// Helper function to print user details
|
||||||
|
fn print_user_details(user: &User) {
|
||||||
|
println!("\n--- User Details ---");
|
||||||
|
println!("ID: {}", user.get_id());
|
||||||
|
println!("Username: {}", user.username);
|
||||||
|
println!("Email: {}", user.email);
|
||||||
|
println!("Full Name: {}", user.full_name);
|
||||||
|
println!("Active: {}", user.is_active);
|
||||||
|
println!("Created At: {}", user.base_data.created_at);
|
||||||
|
println!("Modified At: {}", user.base_data.modified_at);
|
||||||
|
println!("Comments: {:?}", user.base_data.comments);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to print comment details
|
||||||
|
fn print_comment_details(comment: &Comment) {
|
||||||
|
println!("\n--- Comment Details ---");
|
||||||
|
println!("ID: {}", comment.get_id());
|
||||||
|
println!("User ID: {}", comment.user_id);
|
||||||
|
println!("Content: {}", comment.content);
|
||||||
|
println!("Created At: {}", comment.base_data.created_at);
|
||||||
|
println!("Modified At: {}", comment.base_data.modified_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let db = heromodels::db::postgres::Postgres::new(
|
||||||
|
Config::new()
|
||||||
|
.user(Some("postgres".into()))
|
||||||
|
.password(Some("test123".into()))
|
||||||
|
.host(Some("localhost".into()))
|
||||||
|
.port(Some(5432)),
|
||||||
|
)
|
||||||
|
.expect("Can connect to postgress");
|
||||||
|
|
||||||
|
println!("Hero Models - Basic Usage Example");
|
||||||
|
println!("================================");
|
||||||
|
|
||||||
|
// Create users with auto-generated IDs
|
||||||
|
|
||||||
|
// User 1
|
||||||
|
let user1 = User::new()
|
||||||
|
.username("johndoe")
|
||||||
|
.email("john.doe@example.com")
|
||||||
|
.full_name("John Doe")
|
||||||
|
.is_active(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// User 2
|
||||||
|
let user2 = User::new()
|
||||||
|
.username("janesmith")
|
||||||
|
.email("jane.smith@example.com")
|
||||||
|
.full_name("Jane Smith")
|
||||||
|
.is_active(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// User 3
|
||||||
|
let user3 = User::new()
|
||||||
|
.username("willism")
|
||||||
|
.email("willis.masters@example.com")
|
||||||
|
.full_name("Willis Masters")
|
||||||
|
.is_active(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// User 4
|
||||||
|
let user4 = User::new()
|
||||||
|
.username("carrols")
|
||||||
|
.email("carrol.smith@example.com")
|
||||||
|
.full_name("Carrol Smith")
|
||||||
|
.is_active(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Save all users to database and get their assigned IDs and updated models
|
||||||
|
let (user1_id, db_user1) = db
|
||||||
|
.collection()
|
||||||
|
.expect("can open user collection")
|
||||||
|
.set(&user1)
|
||||||
|
.expect("can set user");
|
||||||
|
let (user2_id, db_user2) = db
|
||||||
|
.collection()
|
||||||
|
.expect("can open user collection")
|
||||||
|
.set(&user2)
|
||||||
|
.expect("can set user");
|
||||||
|
let (user3_id, db_user3) = db
|
||||||
|
.collection()
|
||||||
|
.expect("can open user collection")
|
||||||
|
.set(&user3)
|
||||||
|
.expect("can set user");
|
||||||
|
let (user4_id, db_user4) = db
|
||||||
|
.collection()
|
||||||
|
.expect("can open user collection")
|
||||||
|
.set(&user4)
|
||||||
|
.expect("can set user");
|
||||||
|
|
||||||
|
println!("User 1 assigned ID: {}", user1_id);
|
||||||
|
println!("User 2 assigned ID: {}", user2_id);
|
||||||
|
println!("User 3 assigned ID: {}", user3_id);
|
||||||
|
println!("User 4 assigned ID: {}", user4_id);
|
||||||
|
|
||||||
|
// We already have the updated models from the set method, so we don't need to retrieve them again
|
||||||
|
|
||||||
|
// Print all users retrieved from database
|
||||||
|
println!("\n--- Users Retrieved from Database ---");
|
||||||
|
println!("\n1. First user:");
|
||||||
|
print_user_details(&db_user1);
|
||||||
|
|
||||||
|
println!("\n2. Second user:");
|
||||||
|
print_user_details(&db_user2);
|
||||||
|
|
||||||
|
println!("\n3. Third user:");
|
||||||
|
print_user_details(&db_user3);
|
||||||
|
|
||||||
|
println!("\n4. Fourth user:");
|
||||||
|
print_user_details(&db_user4);
|
||||||
|
|
||||||
|
// Demonstrate different ways to retrieve users from the database
|
||||||
|
|
||||||
|
// 1. Retrieve by username index
|
||||||
|
println!("\n--- Retrieving Users by Different Methods ---");
|
||||||
|
println!("\n1. By Username Index:");
|
||||||
|
let stored_users = db
|
||||||
|
.collection::<User>()
|
||||||
|
.expect("can open user collection")
|
||||||
|
.get::<username, _>("johndoe")
|
||||||
|
.expect("can load stored user");
|
||||||
|
|
||||||
|
assert_eq!(stored_users.len(), 1);
|
||||||
|
print_user_details(&stored_users[0]);
|
||||||
|
|
||||||
|
// 2. Retrieve by active status
|
||||||
|
println!("\n2. By Active Status (Active = true):");
|
||||||
|
let active_users = db
|
||||||
|
.collection::<User>()
|
||||||
|
.expect("can open user collection")
|
||||||
|
.get::<is_active, _>(&true)
|
||||||
|
.expect("can load stored users");
|
||||||
|
|
||||||
|
assert_eq!(active_users.len(), 2);
|
||||||
|
for (_i, active_user) in active_users.iter().enumerate() {
|
||||||
|
print_user_details(active_user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Delete a user and show the updated results
|
||||||
|
println!("\n3. After Deleting a User:");
|
||||||
|
let user_to_delete_id = active_users[0].get_id();
|
||||||
|
println!("Deleting user with ID: {}", user_to_delete_id);
|
||||||
|
db.collection::<User>()
|
||||||
|
.expect("can open user collection")
|
||||||
|
.delete_by_id(user_to_delete_id)
|
||||||
|
.expect("can delete existing user");
|
||||||
|
|
||||||
|
// Show remaining active users
|
||||||
|
let active_users = db
|
||||||
|
.collection::<User>()
|
||||||
|
.expect("can open user collection")
|
||||||
|
.get::<is_active, _>(&true)
|
||||||
|
.expect("can load stored users");
|
||||||
|
|
||||||
|
println!(" a. Remaining Active Users:");
|
||||||
|
assert_eq!(active_users.len(), 1);
|
||||||
|
for (_i, active_user) in active_users.iter().enumerate() {
|
||||||
|
print_user_details(active_user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show inactive users
|
||||||
|
let inactive_users = db
|
||||||
|
.collection::<User>()
|
||||||
|
.expect("can open user collection")
|
||||||
|
.get::<is_active, _>(&false)
|
||||||
|
.expect("can load stored users");
|
||||||
|
|
||||||
|
println!(" b. Inactive Users:");
|
||||||
|
assert_eq!(inactive_users.len(), 2);
|
||||||
|
for (_i, inactive_user) in inactive_users.iter().enumerate() {
|
||||||
|
print_user_details(inactive_user);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n--- User Model Information ---");
|
||||||
|
println!("User DB Prefix: {}", User::db_prefix());
|
||||||
|
|
||||||
|
// Demonstrate comment creation and association with a user
|
||||||
|
println!("\n--- Working with Comments ---");
|
||||||
|
|
||||||
|
// 1. Create and save a comment
|
||||||
|
println!("\n1. Creating a Comment:");
|
||||||
|
let comment = Comment::new()
|
||||||
|
.user_id(db_user1.get_id()) // commenter's user ID
|
||||||
|
.content("This is a comment on the user")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Save the comment and get its assigned ID and updated model
|
||||||
|
let (comment_id, db_comment) = db
|
||||||
|
.collection()
|
||||||
|
.expect("can open comment collection")
|
||||||
|
.set(&comment)
|
||||||
|
.expect("can set comment");
|
||||||
|
|
||||||
|
println!("Comment assigned ID: {}", comment_id);
|
||||||
|
|
||||||
|
println!(" a. Comment Retrieved from Database:");
|
||||||
|
print_comment_details(&db_comment);
|
||||||
|
|
||||||
|
// 3. Associate the comment with a user
|
||||||
|
println!("\n2. Associating Comment with User:");
|
||||||
|
let mut updated_user = db_user1.clone();
|
||||||
|
updated_user.base_data.add_comment(db_comment.get_id());
|
||||||
|
|
||||||
|
// Save the updated user and get the new version
|
||||||
|
let (_, user_with_comment) = db
|
||||||
|
.collection::<User>()
|
||||||
|
.expect("can open user collection")
|
||||||
|
.set(&updated_user)
|
||||||
|
.expect("can set updated user");
|
||||||
|
|
||||||
|
println!(" a. User with Associated Comment:");
|
||||||
|
print_user_details(&user_with_comment);
|
||||||
|
|
||||||
|
println!("\n--- Model Information ---");
|
||||||
|
println!("User DB Prefix: {}", User::db_prefix());
|
||||||
|
println!("Comment DB Prefix: {}", Comment::db_prefix());
|
||||||
|
}
|
@ -4,6 +4,7 @@ use heromodels_core::{Index, Model};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub mod hero;
|
pub mod hero;
|
||||||
|
pub mod postgres;
|
||||||
|
|
||||||
pub trait Db {
|
pub trait Db {
|
||||||
/// Error type returned by database operations.
|
/// Error type returned by database operations.
|
||||||
|
400
heromodels/src/db/postgres.rs
Normal file
400
heromodels/src/db/postgres.rs
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use heromodels_core::Model;
|
||||||
|
use postgres::{Client, NoTls};
|
||||||
|
use r2d2::Pool;
|
||||||
|
use r2d2_postgres::PostgresConnectionManager;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Postgres {
|
||||||
|
pool: Pool<PostgresConnectionManager<NoTls>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration used to connect to a postgres server. All values are optional, if they are
|
||||||
|
/// [Option::None], the postgres defaults are used.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Config {
|
||||||
|
pub user: Option<String>,
|
||||||
|
pub password: Option<String>,
|
||||||
|
pub dbname: Option<String>,
|
||||||
|
pub host: Option<String>,
|
||||||
|
pub port: Option<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Create a new empty config
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the user value in the config
|
||||||
|
pub fn user(mut self, user: Option<String>) -> Self {
|
||||||
|
self.user = user;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the password value in the config
|
||||||
|
pub fn password(mut self, password: Option<String>) -> Self {
|
||||||
|
self.password = password;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the dbname value in the config
|
||||||
|
pub fn dbname(mut self, dbname: Option<String>) -> Self {
|
||||||
|
self.dbname = dbname;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the host value in the config
|
||||||
|
pub fn host(mut self, host: Option<String>) -> Self {
|
||||||
|
self.host = host;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the port value in the config
|
||||||
|
pub fn port(mut self, port: Option<u16>) -> Self {
|
||||||
|
self.port = port;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// An error communicating with the postgres server
|
||||||
|
Postgres(postgres::error::Error),
|
||||||
|
/// An error originating from the connection pool
|
||||||
|
ConnectionPool(r2d2::Error),
|
||||||
|
/// An error encoding to or decoding from json
|
||||||
|
Json(serde_json::Error),
|
||||||
|
/// An error encoding to or decoding from jsonb
|
||||||
|
JsonB(jsonb::Error),
|
||||||
|
/// We tried to insert a value but the row was not returned
|
||||||
|
FailedInsert,
|
||||||
|
/// We tried to update a value but we didn't get the expected 1 modified row
|
||||||
|
FailedUpdate(usize),
|
||||||
|
/// We tried to query the existence of a table but it failed
|
||||||
|
TableExistenceQuery,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Postgres {
|
||||||
|
/// Create a new connection to a postgres instance.
|
||||||
|
pub fn new(config: Config) -> Result<Self, Error> {
|
||||||
|
let mut cfg = Client::configure();
|
||||||
|
cfg.user(config.user.unwrap_or_default().as_ref())
|
||||||
|
.password(config.password.unwrap_or_default())
|
||||||
|
.dbname(config.dbname.unwrap_or_default().as_ref())
|
||||||
|
.host(config.host.unwrap_or_default().as_ref())
|
||||||
|
.port(config.port.unwrap_or_default());
|
||||||
|
|
||||||
|
// No TLS support for now
|
||||||
|
// let client = cfg.connect(postgres::tls::NoTls)?;
|
||||||
|
let manager = PostgresConnectionManager::new(cfg, NoTls);
|
||||||
|
|
||||||
|
let pool = Pool::builder()
|
||||||
|
.connection_timeout(Duration::from_secs(1))
|
||||||
|
.max_size(5)
|
||||||
|
.build(manager)?;
|
||||||
|
|
||||||
|
Ok(Postgres { pool })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method which generates a table name for a model, to alleviate some keyword
|
||||||
|
/// conflicts.
|
||||||
|
fn collection_name<M: Model>() -> String {
|
||||||
|
format!("model_{}", M::db_prefix())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::Db for Postgres {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn collection<M: heromodels_core::Model>(
|
||||||
|
&self,
|
||||||
|
) -> Result<impl super::Collection<&str, M>, super::Error<Self::Error>> {
|
||||||
|
// Check if the table exists, if not create it with proper indexes
|
||||||
|
let mut con = self.pool.get().map_err(Error::from)?;
|
||||||
|
|
||||||
|
let row = con
|
||||||
|
.query_one(
|
||||||
|
r#"SELECT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_name = $1
|
||||||
|
) AS table_existence;"#,
|
||||||
|
&[&Self::collection_name::<M>()],
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?;
|
||||||
|
|
||||||
|
let exists: bool = row.get("table_existence");
|
||||||
|
|
||||||
|
// If table does not exist set it up
|
||||||
|
if !exists {
|
||||||
|
eprintln!(
|
||||||
|
"Table {} does not exist, create it now",
|
||||||
|
Self::collection_name::<M>()
|
||||||
|
);
|
||||||
|
// Use a transaction here so if index creation failed the table is also not created.
|
||||||
|
let mut tx = con.transaction().map_err(Error::from)?;
|
||||||
|
|
||||||
|
tx.execute(
|
||||||
|
&format!(
|
||||||
|
"CREATE TABLE {} (key SERIAL PRIMARY KEY, value JSONB NOT NULL);",
|
||||||
|
Self::collection_name::<M>(),
|
||||||
|
),
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?;
|
||||||
|
|
||||||
|
// TODO: Create indexes
|
||||||
|
|
||||||
|
tx.commit().map_err(Error::from)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> super::Collection<&str, M> for Postgres
|
||||||
|
where
|
||||||
|
M: Model,
|
||||||
|
{
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn get<I, Q>(&self, key: &Q) -> Result<Vec<M>, super::Error<Self::Error>>
|
||||||
|
where
|
||||||
|
I: heromodels_core::Index<Model = M>,
|
||||||
|
I::Key: std::borrow::Borrow<Q>,
|
||||||
|
Q: ToString + ?Sized,
|
||||||
|
{
|
||||||
|
let mut con = self.pool.get().map_err(Error::from)?;
|
||||||
|
|
||||||
|
Ok(con
|
||||||
|
.query(
|
||||||
|
&format!(
|
||||||
|
"SELECT (value) FROM {} WHERE value->$1 = $2;",
|
||||||
|
Self::collection_name::<M>(),
|
||||||
|
),
|
||||||
|
&[&I::key(), &key.to_string()],
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|row| serde_json::from_str::<M>(row.get("value")).map_err(Error::from))
|
||||||
|
.collect::<Result<Vec<M>, _>>()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_by_id(&self, id: u32) -> Result<Option<M>, super::Error<Self::Error>> {
|
||||||
|
let mut con = self.pool.get().map_err(Error::from)?;
|
||||||
|
|
||||||
|
if let Some(row) = con
|
||||||
|
.query(
|
||||||
|
&format!(
|
||||||
|
"SELECT (value) FROM {} WHERE key = $1;",
|
||||||
|
Self::collection_name::<M>()
|
||||||
|
),
|
||||||
|
&[&id],
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
{
|
||||||
|
Ok(Some(
|
||||||
|
serde_json::from_str::<M>(row.get("value")).map_err(Error::from)?,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&self, value: &M) -> Result<(u32, M), super::Error<Self::Error>> {
|
||||||
|
let mut con = self.pool.get().map_err(Error::from)?;
|
||||||
|
|
||||||
|
// let ser_val = serde_json::to_string(&value).map_err(Error::from)?;
|
||||||
|
let ser_val = jsonb::to_owned_jsonb(&value).map_err(Error::from)?;
|
||||||
|
|
||||||
|
if value.get_id() == 0 {
|
||||||
|
// NOTE: We perform a query here since we want the returned value which has the updated ID
|
||||||
|
let Some(row) = con
|
||||||
|
.query(
|
||||||
|
&format!(
|
||||||
|
"INSERT INTO {} (value) VALUES ($1) RETURNING key, value;",
|
||||||
|
Self::collection_name::<M>()
|
||||||
|
),
|
||||||
|
&[&postgres::types::Json(value)],
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
else {
|
||||||
|
return Err(Error::FailedInsert.into());
|
||||||
|
};
|
||||||
|
|
||||||
|
eprintln!("insert done");
|
||||||
|
|
||||||
|
// Get the generated ID
|
||||||
|
let id = row.get::<_, i32>("key") as u32;
|
||||||
|
let mut value = row.get::<_, postgres::types::Json<M>>("value").0;
|
||||||
|
// .map_err(Error::from)?;
|
||||||
|
value.base_data_mut().id = id;
|
||||||
|
|
||||||
|
// NOTE: Update the value so the id is set correctly in the value itself
|
||||||
|
// let ser_val = serde_json::to_string(&value).map_err(Error::from)?;
|
||||||
|
|
||||||
|
let updated = con
|
||||||
|
.execute(
|
||||||
|
&format!(
|
||||||
|
"UPDATE {} SET value = $1 WHERE key = $2;",
|
||||||
|
Self::collection_name::<M>()
|
||||||
|
),
|
||||||
|
&[
|
||||||
|
&postgres::types::Json(value.clone()),
|
||||||
|
&(value.get_id() as i32),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?;
|
||||||
|
|
||||||
|
if updated != 1 {
|
||||||
|
return Err(Error::FailedUpdate(updated as usize).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((id, value))
|
||||||
|
} else {
|
||||||
|
let updated = con
|
||||||
|
.execute(
|
||||||
|
&format!(
|
||||||
|
"UPDATE {} SET value = $1 WHERE key = $2;",
|
||||||
|
Self::collection_name::<M>(),
|
||||||
|
),
|
||||||
|
&[
|
||||||
|
&postgres::types::Json(value.clone()),
|
||||||
|
&(value.get_id() as i32),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?;
|
||||||
|
|
||||||
|
if updated != 1 {
|
||||||
|
return Err(Error::FailedUpdate(updated as usize).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((value.get_id(), value.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete<I, Q>(&self, key: &Q) -> Result<(), super::Error<Self::Error>>
|
||||||
|
where
|
||||||
|
I: heromodels_core::Index<Model = M>,
|
||||||
|
I::Key: std::borrow::Borrow<Q>,
|
||||||
|
Q: ToString + ?Sized,
|
||||||
|
{
|
||||||
|
let mut con = self.pool.get().map_err(Error::from)?;
|
||||||
|
|
||||||
|
con.execute(
|
||||||
|
&format!(
|
||||||
|
"DELETE FROM {} WHERE value->$1 = $2;",
|
||||||
|
Self::collection_name::<M>()
|
||||||
|
),
|
||||||
|
&[&I::key(), &key.to_string()],
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_by_id(&self, id: u32) -> Result<(), super::Error<Self::Error>> {
|
||||||
|
let mut con = self.pool.get().map_err(Error::from)?;
|
||||||
|
|
||||||
|
con.execute(
|
||||||
|
&format!(
|
||||||
|
"DELETE FROM {} WHERE key = $1;",
|
||||||
|
Self::collection_name::<M>()
|
||||||
|
),
|
||||||
|
&[&id],
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_all(&self) -> Result<Vec<M>, super::Error<Self::Error>> {
|
||||||
|
let mut con = self.pool.get().map_err(Error::from)?;
|
||||||
|
|
||||||
|
Ok(con
|
||||||
|
.query(
|
||||||
|
&format!("SELECT (value) FROM {};", Self::collection_name::<M>()),
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.map_err(Error::from)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|row| serde_json::from_str::<M>(row.get("value")).map_err(Error::from))
|
||||||
|
.collect::<Result<Vec<M>, _>>()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn begin_transaction(
|
||||||
|
&self,
|
||||||
|
) -> Result<Box<dyn super::Transaction<Error = Self::Error>>, super::Error<Self::Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Postgres(e) => f.write_fmt(format_args!("postgres error: {e}")),
|
||||||
|
Self::ConnectionPool(e) => {
|
||||||
|
f.write_fmt(format_args!("postgres connection pool error: {e}"))
|
||||||
|
}
|
||||||
|
Self::Json(e) => {
|
||||||
|
f.write_fmt(format_args!("could not decode from or encode to json: {e}"))
|
||||||
|
}
|
||||||
|
Self::JsonB(e) => f.write_fmt(format_args!(
|
||||||
|
"could not decode from or encode to jsonb: {e}"
|
||||||
|
)),
|
||||||
|
Self::FailedInsert => f.write_str("insert did not return any result"),
|
||||||
|
Self::FailedUpdate(amount) => f.write_fmt(format_args!(
|
||||||
|
"update did not return the expected 1 modified row (got {amount})"
|
||||||
|
)),
|
||||||
|
Self::TableExistenceQuery => f.write_str("query to check if table exists failed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::error::Error for Error {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
match self {
|
||||||
|
Self::Postgres(e) => Some(e),
|
||||||
|
Self::ConnectionPool(e) => Some(e),
|
||||||
|
Self::Json(e) => Some(e),
|
||||||
|
Self::JsonB(e) => Some(e),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<postgres::error::Error> for Error {
|
||||||
|
fn from(value: postgres::error::Error) -> Self {
|
||||||
|
Self::Postgres(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<r2d2::Error> for Error {
|
||||||
|
fn from(value: r2d2::Error) -> Self {
|
||||||
|
Self::ConnectionPool(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::Error> for Error {
|
||||||
|
fn from(value: serde_json::Error) -> Self {
|
||||||
|
Self::Json(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<jsonb::Error> for Error {
|
||||||
|
fn from(value: jsonb::Error) -> Self {
|
||||||
|
Self::JsonB(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Error> for super::Error<Error> {
|
||||||
|
fn from(value: Error) -> Self {
|
||||||
|
super::Error::DB(value)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user