Skip to content

Commit

Permalink
Merge pull request #2786 from lann/sqlite-summary-hook
Browse files Browse the repository at this point in the history
Add SqliteDefaultStoreSummaryHook
  • Loading branch information
lann authored Aug 29, 2024
2 parents 2cd5826 + 99547a9 commit fe39e66
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 24 deletions.
8 changes: 8 additions & 0 deletions crates/factor-key-value/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,17 @@ pub struct AppState {
}

impl AppState {
/// Returns the [`StoreManager::summary`] for the given store label.
pub fn store_summary(&self, label: &str) -> Option<String> {
self.store_manager.summary(label)
}

/// Returns true if the given store label is used by any component.
pub fn store_is_used(&self, label: &str) -> bool {
self.component_allowed_stores
.values()
.any(|stores| stores.contains(label))
}
}

pub struct InstanceBuilder {
Expand Down
19 changes: 15 additions & 4 deletions crates/factor-sqlite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,17 @@ impl AppState {
/// Get a connection for a given database label.
///
/// Returns `None` if there is no connection creator for the given label.
pub async fn get_connection(
&self,
label: &str,
) -> Option<Result<Box<dyn Connection>, v2::Error>> {
pub fn get_connection(&self, label: &str) -> Option<Result<Box<dyn Connection>, v2::Error>> {
let connection = (self.get_connection_creator)(label)?.create_connection();
Some(connection)
}

/// Returns true if the given database label is used by any component.
pub fn database_is_used(&self, label: &str) -> bool {
self.allowed_databases
.values()
.any(|stores| stores.contains(label))
}
}

/// A creator of a connections for a particular SQLite database.
Expand Down Expand Up @@ -205,4 +209,11 @@ pub trait Connection: Send + Sync {
) -> Result<v2::QueryResult, v2::Error>;

async fn execute_batch(&self, statements: &str) -> anyhow::Result<()>;

/// A human-readable summary of the connection's configuration
///
/// Example: "libSQL at libsql://example.com"
fn summary(&self) -> Option<String> {
None
}
}
11 changes: 2 additions & 9 deletions crates/factors-executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ impl<T: RuntimeFactors, U: Send + 'static> FactorsExecutor<T, U> {
}
}

impl<T: RuntimeFactors, U: Send + 'static> FactorsExecutor<T, U>
where
T::AppState: Sync,
{
impl<T: RuntimeFactors, U: Send + 'static> FactorsExecutor<T, U> {
/// Adds the given [`ExecutorHooks`] to this executor.
///
/// Hooks are run in the order they are added.
Expand Down Expand Up @@ -84,7 +81,6 @@ where
pub trait ExecutorHooks<T, U>: Send + Sync
where
T: RuntimeFactors,
T::AppState: Sync,
{
/// Configure app hooks run immediately after [`RuntimeFactors::configure_app`].
async fn configure_app(&mut self, configured_app: &ConfiguredApp<T>) -> anyhow::Result<()> {
Expand Down Expand Up @@ -143,10 +139,7 @@ impl<T: RuntimeFactors, U: Send + 'static> FactorsExecutorApp<T, U> {
}
}

impl<T: RuntimeFactors, U: Send + 'static> FactorsExecutorApp<T, U>
where
T::AppState: Sync,
{
impl<T: RuntimeFactors, U: Send + 'static> FactorsExecutorApp<T, U> {
/// Returns an instance builder for the given component ID.
pub fn prepare(&self, component_id: &str) -> anyhow::Result<FactorsInstanceBuilder<T, U>> {
let app_component = self
Expand Down
2 changes: 1 addition & 1 deletion crates/factors/src/factor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub trait Factor: Any + Sized {
/// The application state of this factor.
///
/// This state *may* be cached by the runtime across multiple requests.
type AppState;
type AppState: Sync;

/// The builder of instance state for this factor.
type InstanceBuilder: FactorInstanceBuilder;
Expand Down
2 changes: 1 addition & 1 deletion crates/factors/src/runtime_factors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::{factor::FactorInstanceState, App, ConfiguredApp, Factor};
/// ```
pub trait RuntimeFactors: Sized + 'static {
/// The per application state of all the factors.
type AppState;
type AppState: Sync;
/// The per instance state of the factors.
type InstanceState: RuntimeFactorsInstanceState;
/// The collection of all the `InstanceBuilder`s of the factors.
Expand Down
7 changes: 7 additions & 0 deletions crates/sqlite-inproc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ impl Connection for InProcConnection {
async fn execute_batch(&self, statements: &str) -> anyhow::Result<()> {
self.execute_batch(statements).await
}

fn summary(&self) -> Option<String> {
Some(match &self.location {
InProcDatabaseLocation::InMemory => "a temporary in-memory database".to_string(),
InProcDatabaseLocation::Path(path) => format!("\"{}\"", path.display()),
})
}
}

fn execute_query(
Expand Down
4 changes: 4 additions & 0 deletions crates/sqlite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ impl Connection for LibSqlConnection {
let client = self.get_client().await?;
client.execute_batch(statements).await
}

fn summary(&self) -> Option<String> {
Some(format!("libSQL at {}", self.url))
}
}

/// Configuration for a local SQLite database.
Expand Down
4 changes: 2 additions & 2 deletions crates/trigger/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use spin_core::async_trait;
use spin_factors_executor::{ComponentLoader, FactorsExecutor};
use spin_runtime_config::{ResolvedRuntimeConfig, UserProvidedPath};
use sqlite_statements::SqlStatementExecutorHook;
use summary::KeyValueDefaultStoreSummaryHook;
use summary::{KeyValueDefaultStoreSummaryHook, SqliteDefaultStoreSummaryHook};

use crate::factors::{TriggerFactors, TriggerFactorsRuntimeConfig};
use crate::stdio::{FollowComponents, StdioLoggingExecutorHooks};
Expand Down Expand Up @@ -424,8 +424,8 @@ impl<T: Trigger> TriggerAppBuilder<T> {
// TODO:
// builder.hooks(SummariseRuntimeConfigHook::new(&self.runtime_config_file));
executor.add_hooks(KeyValueDefaultStoreSummaryHook);
executor.add_hooks(SqliteDefaultStoreSummaryHook);
executor.add_hooks(SqlStatementExecutorHook::new(options.sqlite_statements));
// builder.hooks(SqlitePersistenceMessageHook);

let configured_app = {
let _sloth_guard = warn_if_wasm_build_slothful();
Expand Down
2 changes: 0 additions & 2 deletions crates/trigger/src/cli/sqlite_statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ impl SqlStatementExecutorHook {
let get_database = |label| async move {
sqlite
.get_connection(label)
.await
.transpose()
.with_context(|| format!("failed connect to database with label '{label}'"))
};
Expand Down Expand Up @@ -69,7 +68,6 @@ impl SqlStatementExecutorHook {
impl<F, U> ExecutorHooks<F, U> for SqlStatementExecutorHook
where
F: RuntimeFactors,
F::AppState: Sync,
{
async fn configure_app(
&mut self,
Expand Down
42 changes: 37 additions & 5 deletions crates/trigger/src/cli/summary.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use spin_core::async_trait;
use spin_factor_key_value::KeyValueFactor;
use spin_factor_sqlite::SqliteFactor;
use spin_factors_executor::ExecutorHooks;

use crate::factors::TriggerFactors;

/// An [`ExecutorHooks`] that prints information about the default KV store.
pub struct KeyValueDefaultStoreSummaryHook;

#[async_trait]
Expand All @@ -12,13 +14,43 @@ impl<U> ExecutorHooks<TriggerFactors, U> for KeyValueDefaultStoreSummaryHook {
&mut self,
configured_app: &spin_factors::ConfiguredApp<TriggerFactors>,
) -> anyhow::Result<()> {
if let Some(default_store_summary) = configured_app
.app_state::<KeyValueFactor>()
.ok()
.and_then(|kv_state| kv_state.store_summary("default"))
{
let Ok(kv_app_state) = configured_app.app_state::<KeyValueFactor>() else {
return Ok(());
};
if !kv_app_state.store_is_used("default") {
// We don't talk about unused default stores
return Ok(());
}
if let Some(default_store_summary) = kv_app_state.store_summary("default") {
println!("Storing default key-value data to {default_store_summary}.");
}
Ok(())
}
}

/// An [`ExecutorHooks`] that prints information about the default KV store.
pub struct SqliteDefaultStoreSummaryHook;

#[async_trait]
impl<U> ExecutorHooks<TriggerFactors, U> for SqliteDefaultStoreSummaryHook {
async fn configure_app(
&mut self,
configured_app: &spin_factors::ConfiguredApp<TriggerFactors>,
) -> anyhow::Result<()> {
let Ok(sqlite_app_state) = configured_app.app_state::<SqliteFactor>() else {
return Ok(());
};
if !sqlite_app_state.database_is_used("default") {
// We don't talk about unused default databases
return Ok(());
}
if let Some(default_database_summary) = sqlite_app_state
.get_connection("default")
.and_then(Result::ok)
.and_then(|conn| conn.summary())
{
println!("Storing default SQLite data to {default_database_summary}.");
}
Ok(())
}
}

0 comments on commit fe39e66

Please sign in to comment.