Files
cdk/crates/cdk-sql-common/src/common.rs
Cesar Rodas 937d9ac43b Introduce run_db_operation_sync and run_db_operation
These functions are designed as a single funnel to talk to the database,
whether it is synchronous or asynchronous.

This single funnel will log SQL queries and slow operations, providing a clear
and unified debug message for the problematic query, so it can be optimized
accordingly (for instance, missing indexes or unbound SQL requests).
2025-08-11 11:55:52 -03:00

116 lines
2.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::fmt::Debug;
use std::future::Future;
use std::time::Instant;
use cdk_common::database::Error;
use crate::database::DatabaseExecutor;
use crate::stmt::query;
const SLOW_QUERY_THRESHOLD_MS: u128 = 20;
/// Run a database operation and log slow operations, it also converts and logs any error with a
/// given info for more context. This function is expecting a synchronous database operation
#[inline(always)]
pub fn run_db_operation_sync<F, E, E1, T>(
info: &str,
operation: F,
error_map: E,
) -> Result<T, Error>
where
F: FnOnce() -> Result<T, E1>,
E1: Debug,
E: FnOnce(E1) -> Error,
{
let start = Instant::now();
tracing::trace!("Running db operation {}", info);
let result = operation().map_err(|e| {
tracing::error!("Query {} failed with error {:?}", info, e);
error_map(e)
});
let duration = start.elapsed();
if duration.as_millis() > SLOW_QUERY_THRESHOLD_MS {
tracing::warn!("[SLOW QUERY] Took {} ms: {}", duration.as_millis(), info);
}
result
}
/// Run a database operation and log slow operations, it also converts and logs any error with a
/// given info for more context
#[inline(always)]
pub async fn run_db_operation<Fut, E, E1, T>(
info: &str,
operation: Fut,
error_map: E,
) -> Result<T, Error>
where
Fut: Future<Output = Result<T, E1>>,
E1: Debug,
E: FnOnce(E1) -> Error,
{
let start = Instant::now();
tracing::trace!("Running db operation {}", info);
let result = operation.await.map_err(|e| {
tracing::error!("Query {} failed with error {:?}", info, e);
error_map(e)
});
let duration = start.elapsed();
if duration.as_millis() > SLOW_QUERY_THRESHOLD_MS {
tracing::warn!("[SLOW QUERY] Took {} ms: {}", duration.as_millis(), info);
}
result
}
/// Migrates the migration generated by `build.rs`
#[inline(always)]
pub async fn migrate<C>(
conn: &C,
db_prefix: &str,
migrations: &[(&str, &str, &str)],
) -> Result<(), Error>
where
C: DatabaseExecutor,
{
query(
r#"
CREATE TABLE IF NOT EXISTS migrations (
name TEXT PRIMARY KEY,
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"#,
)?
.execute(conn)
.await?;
// Apply each migration if it hasnt been applied yet
for (prefix, name, sql) in migrations {
if !prefix.is_empty() && *prefix != db_prefix {
continue;
}
let is_missing = query("SELECT name FROM migrations WHERE name = :name")?
.bind("name", name)
.pluck(conn)
.await?
.is_none();
if is_missing {
query(sql)?.batch(conn).await?;
query(r#"INSERT INTO migrations (name) VALUES (:name)"#)?
.bind("name", name)
.execute(conn)
.await?;
}
}
Ok(())
}