lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit b5ddc8fd1c926a1f04f8e505ee0ac8174e6a390b
parent 5449ef4f2e1d4663f4352016a342f2677f0fc925
Author: triesap <tyson@radroots.org>
Date:   Thu, 13 Nov 2025 00:28:03 +0000

workspace: add `tangle-sql` crate providing structured SQL operations

Diffstat:
Atangle-sql/Cargo.toml | 21+++++++++++++++++++++
Atangle-sql/src/lib.rs | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atangle-sql/src/tables/log_error.rs | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atangle-sql/src/tables/mod.rs | 1+
4 files changed, 182 insertions(+), 0 deletions(-)

diff --git a/tangle-sql/Cargo.toml b/tangle-sql/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "radroots-tangle-sql" +version.workspace = true +edition.workspace = true +authors = ["Radroots Authors"] +rust-version.workspace = true +license.workspace = true + +[lib] +crate-type = ["rlib"] + +[features] +default = [] +web = ["radroots-sql-core/web"] +native = ["radroots-sql-core/native"] +embedded = ["radroots-sql-core/embedded"] + +[dependencies] +radroots-sql-core = { workspace = true } +radroots-tangle-schema = { workspace = true } +serde_json = { workspace = true } diff --git a/tangle-sql/src/lib.rs b/tangle-sql/src/lib.rs @@ -0,0 +1,55 @@ +pub use radroots_sql_core::error::SqlError; +pub use radroots_sql_core::{ExecOutcome, SqlExecutor}; + +pub mod tables; +pub use tables::log_error; + +pub struct TangleSql<E: SqlExecutor> { + executor: E, +} + +impl<E: SqlExecutor> TangleSql<E> { + pub fn new(executor: E) -> Self { + Self { executor } + } + + pub fn executor(&self) -> &E { + &self.executor + } + + pub fn insert_log_error( + &self, + fields: radroots_tangle_schema::log_error::ILogErrorFields, + ) -> Result<radroots_tangle_schema::log_error::LogError, SqlError> { + tables::log_error::insert(self.executor(), fields) + } + + pub fn find_log_errors( + &self, + filter: Option<&radroots_tangle_schema::log_error::ILogErrorFieldsFilter>, + ) -> Result<Vec<radroots_tangle_schema::log_error::LogError>, SqlError> { + tables::log_error::find_many(self.executor(), filter) + } + + pub fn find_log_error( + &self, + bind: &radroots_tangle_schema::log_error::LogErrorQueryBindValues, + ) -> Result<Option<radroots_tangle_schema::log_error::LogError>, SqlError> { + tables::log_error::find_one(self.executor(), bind) + } + + pub fn update_log_error( + &self, + id: &str, + fields: radroots_tangle_schema::log_error::ILogErrorFieldsPartial, + ) -> Result<ExecOutcome, SqlError> { + tables::log_error::update(self.executor(), id, fields) + } + + pub fn delete_log_error( + &self, + bind: &radroots_tangle_schema::log_error::LogErrorQueryBindValues, + ) -> Result<ExecOutcome, SqlError> { + tables::log_error::delete(self.executor(), bind) + } +} diff --git a/tangle-sql/src/tables/log_error.rs b/tangle-sql/src/tables/log_error.rs @@ -0,0 +1,105 @@ +use radroots_sql_core::error::SqlError; +use radroots_sql_core::utils; +use radroots_sql_core::{ExecOutcome, SqlExecutor}; +use radroots_tangle_schema::log_error::{ + ILogErrorFields, ILogErrorFieldsFilter, ILogErrorFieldsPartial, LogError, + LogErrorQueryBindValues, +}; +use serde_json::Value; + +const TABLE_NAME: &str = "log_error"; + +pub fn insert<E: SqlExecutor>(exec: &E, fields: ILogErrorFields) -> Result<LogError, SqlError> { + let field_map = utils::to_object_map(&fields)?; + let id = utils::uuidv4(); + let now = utils::time_created_on(); + let meta: [(&str, Value); 3] = [ + ("id", Value::from(id.clone())), + ("created_at", Value::from(now.clone())), + ("updated_at", Value::from(now.clone())), + ]; + let (sql, bind_values) = utils::build_insert_query_with_meta(TABLE_NAME, &meta, &field_map); + let params_json = utils::to_params_json(bind_values)?; + let _ = exec.exec(&sql, &params_json)?; + let log_error = LogError { + id, + created_at: now.clone(), + updated_at: now, + error: fields.error, + message: fields.message, + stack_trace: fields.stack_trace, + cause: fields.cause, + app_system: fields.app_system, + app_version: fields.app_version, + nostr_pubkey: fields.nostr_pubkey, + data: fields.data, + }; + Ok(log_error) +} + +pub fn find_many<E: SqlExecutor>( + exec: &E, + filter: Option<&ILogErrorFieldsFilter>, +) -> Result<Vec<LogError>, SqlError> { + let (sql, bind_values) = utils::build_select_query_with_meta(TABLE_NAME, filter); + let params_json = utils::to_params_json(bind_values)?; + let json = exec.query_raw(&sql, &params_json)?; + let rows: Vec<LogError> = utils::parse_json(&json)?; + Ok(rows) +} + +pub fn find_one<E: SqlExecutor>( + exec: &E, + bind: &LogErrorQueryBindValues, +) -> Result<Option<LogError>, SqlError> { + let (sql, bind_values) = utils::build_select_query_with_meta(TABLE_NAME, Some(bind)); + let params_json = utils::to_params_json(bind_values)?; + let json = exec.query_raw(&sql, &params_json)?; + let mut rows: Vec<LogError> = utils::parse_json(&json)?; + Ok(rows.pop()) +} + +pub fn update<E: SqlExecutor>( + exec: &E, + id: &str, + fields: ILogErrorFieldsPartial, +) -> Result<ExecOutcome, SqlError> { + let mut updates = utils::to_partial_object_map(fields)?; + if updates.is_empty() { + return Err(SqlError::InvalidArgument(String::from( + "no fields to update", + ))); + } + updates.insert( + String::from("updated_at"), + Value::from(utils::time_created_on()), + ); + let mut set_parts = Vec::with_capacity(updates.len()); + let mut bind_values = Vec::with_capacity(updates.len() + 1); + for (column, value) in updates { + set_parts.push(format!("{column} = ?")); + bind_values.push(utils::to_db_bind_value(&value)); + } + bind_values.push(Value::from(String::from(id))); + let sql = format!( + "UPDATE {TABLE_NAME} SET {} WHERE id = ?;", + set_parts.join(", ") + ); + let params_json = utils::to_params_json(bind_values)?; + exec.exec(&sql, &params_json) +} + +pub fn delete<E: SqlExecutor>( + exec: &E, + bind: &LogErrorQueryBindValues, +) -> Result<ExecOutcome, SqlError> { + let (where_clause, bind_values) = utils::build_where_clause_eq(bind)?; + if where_clause.is_empty() { + return Err(SqlError::InvalidArgument(String::from( + "delete requires at least one filter field", + ))); + } + let sql = format!("DELETE FROM {TABLE_NAME}{where_clause};"); + let params_json = utils::to_params_json(bind_values)?; + exec.exec(&sql, &params_json) +} diff --git a/tangle-sql/src/tables/mod.rs b/tangle-sql/src/tables/mod.rs @@ -0,0 +1 @@ +pub mod log_error;