v0.1.0-pre.050
This commit is contained in:
363
kb-app-demo-desktop/src/demo_sql_common.rs
Normal file
363
kb-app-demo-desktop/src/demo_sql_common.rs
Normal file
@@ -0,0 +1,363 @@
|
||||
// file: kb-app-demo-desktop/src/demo_sql_common.rs
|
||||
// version: 8
|
||||
|
||||
//! Shared SQL demo helpers and serializable payloads.
|
||||
|
||||
use tauri::Manager; // rust-rules: trait-import
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
/// UI-safe table diagnostics shown by SQL demo windows.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_sql/DemoSqlTableSnapshot.ts"
|
||||
)]
|
||||
pub(crate) struct DemoSqlTableSnapshot {
|
||||
/// Table name.
|
||||
pub(crate) table_name: std::string::String,
|
||||
/// Logical Solana domain encoded in the table name.
|
||||
pub(crate) domain: std::string::String,
|
||||
/// Human-readable table role.
|
||||
pub(crate) role: std::string::String,
|
||||
/// Whether the table currently exists.
|
||||
pub(crate) exists: bool,
|
||||
/// Row count when the table exists.
|
||||
#[ts(type = "number | null")]
|
||||
pub(crate) row_count: std::option::Option<i64>,
|
||||
/// Minimum slot when available.
|
||||
#[ts(type = "number | null")]
|
||||
pub(crate) min_slot: std::option::Option<i64>,
|
||||
/// Maximum slot when available.
|
||||
#[ts(type = "number | null")]
|
||||
pub(crate) max_slot: std::option::Option<i64>,
|
||||
/// Latest insertion timestamp rendered by PostgreSQL when available.
|
||||
pub(crate) latest_created_at: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
/// Opens or focuses a SQL demo window.
|
||||
pub(crate) fn open_sql_demo_window(
|
||||
app_handle: tauri::AppHandle,
|
||||
label: &str,
|
||||
html_path: &str,
|
||||
title: &str,
|
||||
) -> std::result::Result<(), std::string::String> {
|
||||
tracing::info!(target: crate::TRACING_TARGET, window = label, "open SQL demo window");
|
||||
let existing_window = app_handle.get_webview_window(label);
|
||||
if let std::option::Option::Some(window) = existing_window {
|
||||
let show_result = window.show();
|
||||
if let std::result::Result::Err(error) = show_result {
|
||||
return std::result::Result::Err(
|
||||
kb_core::Error::tauri(format!("cannot show {label} window: {error}")).to_string(),
|
||||
);
|
||||
}
|
||||
let focus_result = window.set_focus();
|
||||
if let std::result::Result::Err(error) = focus_result {
|
||||
return std::result::Result::Err(
|
||||
kb_core::Error::tauri(format!("cannot focus {label} window: {error}")).to_string(),
|
||||
);
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
let builder = tauri::WebviewWindowBuilder::new(
|
||||
&app_handle,
|
||||
label,
|
||||
tauri::WebviewUrl::App(html_path.into()),
|
||||
)
|
||||
.title(title)
|
||||
.inner_size(1220.0, 780.0)
|
||||
.min_inner_size(920.0, 560.0)
|
||||
.resizable(true)
|
||||
.visible(true);
|
||||
let build_result = builder.build();
|
||||
return match build_result {
|
||||
std::result::Result::Ok(window) => {
|
||||
let focus_result = window.set_focus();
|
||||
if let std::result::Result::Err(error) = focus_result {
|
||||
return std::result::Result::Err(
|
||||
kb_core::Error::tauri(format!("cannot focus created {label} window: {error}"))
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
std::result::Result::Ok(())
|
||||
},
|
||||
std::result::Result::Err(error) => std::result::Result::Err(
|
||||
kb_core::Error::tauri(format!("cannot create {label} window: {error}")).to_string(),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// Builds PostgreSQL options from the active profile without coupling kb-store to kb-config.
|
||||
pub(crate) fn postgres_store_options_from_profile(
|
||||
profile: &kb_config::ProfileConfig,
|
||||
auto_initialize_schema: bool,
|
||||
) -> kb_core::Result<kb_store::PostgresStoreOptions> {
|
||||
return kb_store::PostgresStoreOptions::new(
|
||||
profile.database.postgres.url.clone(),
|
||||
profile.database.postgres.max_connections,
|
||||
profile.database.postgres.connect_timeout_ms,
|
||||
auto_initialize_schema,
|
||||
);
|
||||
}
|
||||
|
||||
/// Connects to PostgreSQL without reapplying schemas initialized at application startup.
|
||||
pub(crate) async fn connect_postgres_store(
|
||||
profile: &kb_config::ProfileConfig,
|
||||
) -> std::result::Result<kb_store::PostgresStore, std::string::String> {
|
||||
if !profile.database.enabled {
|
||||
return std::result::Result::Err(std::string::String::from(
|
||||
"database configuration is disabled in the active profile",
|
||||
));
|
||||
}
|
||||
let options_result = postgres_store_options_from_profile(profile, false);
|
||||
let mut options = match options_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
options.auto_initialize_schema = false;
|
||||
let store_result = kb_store::PostgresStore::connect(options).await;
|
||||
return match store_result {
|
||||
std::result::Result::Ok(store) => std::result::Result::Ok(store),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
}
|
||||
|
||||
/// Converts store table diagnostics to the UI payload shape.
|
||||
pub(crate) fn table_snapshot_from_pg(
|
||||
value: &kb_store::PostgresTableDiagnostics,
|
||||
) -> crate::DemoSqlTableSnapshot {
|
||||
let row_count = match &value.statistics {
|
||||
std::option::Option::Some(statistics) => std::option::Option::Some(statistics.row_count),
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
};
|
||||
let min_slot = match &value.statistics {
|
||||
std::option::Option::Some(statistics) => statistics.min_slot,
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
};
|
||||
let max_slot = match &value.statistics {
|
||||
std::option::Option::Some(statistics) => statistics.max_slot,
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
};
|
||||
let latest_created_at = match &value.statistics {
|
||||
std::option::Option::Some(statistics) => statistics.latest_created_at.clone(),
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
};
|
||||
return crate::DemoSqlTableSnapshot {
|
||||
table_name: value.table_name.clone(),
|
||||
domain: value.domain.clone(),
|
||||
role: value.role.clone(),
|
||||
exists: value.exists,
|
||||
row_count,
|
||||
min_slot,
|
||||
max_slot,
|
||||
latest_created_at,
|
||||
};
|
||||
}
|
||||
|
||||
/// Converts many table diagnostics to UI payloads.
|
||||
pub(crate) fn table_snapshots_from_pg(
|
||||
values: &[kb_store::PostgresTableDiagnostics],
|
||||
) -> std::vec::Vec<crate::DemoSqlTableSnapshot> {
|
||||
let mut output = std::vec::Vec::new();
|
||||
for value in values {
|
||||
output.push(table_snapshot_from_pg(value));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/// Initializes PostgreSQL raw, core, decode and materialization schemas during Tauri startup.
|
||||
pub(crate) async fn initialize_postgres_schema_for_startup(
|
||||
state: &crate::AppState,
|
||||
splash_window: &tauri::WebviewWindow,
|
||||
) {
|
||||
let profile = state.active_profile();
|
||||
if !profile.database.enabled {
|
||||
emit_sql_startup_splash(
|
||||
splash_window,
|
||||
"SQL store disabled; schema initialization skipped.",
|
||||
"info",
|
||||
false,
|
||||
);
|
||||
tracing::debug!(target: crate::TRACING_TARGET, "database is disabled; skip SQL schema initialization");
|
||||
return;
|
||||
}
|
||||
if profile.database.backend != "postgres" {
|
||||
emit_sql_startup_splash(
|
||||
splash_window,
|
||||
"SQL backend is not PostgreSQL; schema initialization skipped.",
|
||||
"info",
|
||||
false,
|
||||
);
|
||||
tracing::debug!(target: crate::TRACING_TARGET, backend = profile.database.backend.as_str(), "database backend is not postgres; skip SQL schema initialization");
|
||||
return;
|
||||
}
|
||||
let options_result = postgres_store_options_from_profile(profile, false);
|
||||
let mut options = match options_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
let error_message = error.to_string();
|
||||
emit_sql_startup_error(splash_window, error_message.as_str());
|
||||
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "cannot build PostgreSQL store options");
|
||||
return;
|
||||
},
|
||||
};
|
||||
if !options.auto_initialize_schema {
|
||||
emit_sql_startup_splash(
|
||||
splash_window,
|
||||
"PostgreSQL schema auto-initialization disabled.",
|
||||
"info",
|
||||
false,
|
||||
);
|
||||
let masked_dsn = options.masked_dsn();
|
||||
tracing::debug!(target: crate::TRACING_TARGET, dsn = masked_dsn.as_str(), "PostgreSQL schema auto-initialization disabled");
|
||||
return;
|
||||
}
|
||||
let masked_dsn = options.masked_dsn();
|
||||
options.auto_initialize_schema = false;
|
||||
emit_sql_startup_splash(
|
||||
splash_window,
|
||||
"Checking PostgreSQL raw/core/decode tables...",
|
||||
"info",
|
||||
true,
|
||||
);
|
||||
tracing::debug!(target: crate::TRACING_TARGET, dsn = masked_dsn.as_str(), "start PostgreSQL schema initialization");
|
||||
let store_result = kb_store::PostgresStore::connect(options).await;
|
||||
let store = match store_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
let error_message = error.to_string();
|
||||
emit_sql_startup_error(splash_window, error_message.as_str());
|
||||
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "PostgreSQL connection failed during startup schema initialization");
|
||||
return;
|
||||
},
|
||||
};
|
||||
let before_result = store.known_table_diagnostics().await;
|
||||
let before_tables = match before_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
let error_message = error.to_string();
|
||||
emit_sql_startup_error(splash_window, error_message.as_str());
|
||||
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "cannot read table diagnostics before schema initialization");
|
||||
return;
|
||||
},
|
||||
};
|
||||
let initialize_result = store.initialize_store_schema().await;
|
||||
if let std::result::Result::Err(error) = initialize_result {
|
||||
let error_message = error.to_string();
|
||||
emit_sql_startup_error(splash_window, error_message.as_str());
|
||||
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "PostgreSQL store schema initialization failed");
|
||||
return;
|
||||
}
|
||||
let after_result = store.known_table_diagnostics().await;
|
||||
let after_tables = match after_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
let error_message = error.to_string();
|
||||
emit_sql_startup_error(splash_window, error_message.as_str());
|
||||
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "cannot read table diagnostics after schema initialization");
|
||||
return;
|
||||
},
|
||||
};
|
||||
emit_sql_startup_table_report(splash_window, before_tables.as_slice(), after_tables.as_slice());
|
||||
}
|
||||
|
||||
fn emit_sql_startup_table_report(
|
||||
splash_window: &tauri::WebviewWindow,
|
||||
before_tables: &[kb_store::PostgresTableDiagnostics],
|
||||
after_tables: &[kb_store::PostgresTableDiagnostics],
|
||||
) {
|
||||
let mut created_count = 0_u32;
|
||||
let mut existing_count = 0_u32;
|
||||
let mut missing_count = 0_u32;
|
||||
for table in after_tables {
|
||||
let before_exists = table_exists_in(before_tables, table.table_name.as_str());
|
||||
if table.exists && before_exists == std::option::Option::Some(false) {
|
||||
created_count += 1;
|
||||
emit_sql_startup_splash(
|
||||
splash_window,
|
||||
format!("Created table {}", table.table_name).as_str(),
|
||||
"success",
|
||||
true,
|
||||
);
|
||||
tracing::debug!(target: crate::TRACING_TARGET, table = table.table_name.as_str(), "PostgreSQL table created");
|
||||
} else if table.exists {
|
||||
existing_count += 1;
|
||||
emit_sql_startup_splash(
|
||||
splash_window,
|
||||
format!("Table {} already exists", table.table_name).as_str(),
|
||||
"info",
|
||||
true,
|
||||
);
|
||||
tracing::debug!(target: crate::TRACING_TARGET, table = table.table_name.as_str(), "PostgreSQL table already exists");
|
||||
} else {
|
||||
missing_count += 1;
|
||||
emit_sql_startup_splash(
|
||||
splash_window,
|
||||
format!("Table {} is missing after initialization", table.table_name).as_str(),
|
||||
"warning",
|
||||
true,
|
||||
);
|
||||
tracing::error!(target: crate::TRACING_TARGET, table = table.table_name.as_str(), "PostgreSQL table missing after initialization");
|
||||
}
|
||||
}
|
||||
emit_sql_startup_splash(
|
||||
splash_window,
|
||||
format!(
|
||||
"PostgreSQL schema ready: {created_count} created, {existing_count} already present, {missing_count} missing."
|
||||
)
|
||||
.as_str(),
|
||||
schema_report_status(missing_count),
|
||||
false,
|
||||
);
|
||||
tracing::debug!(target: crate::TRACING_TARGET, created = created_count, existing = existing_count, missing = missing_count, "PostgreSQL schema initialization completed");
|
||||
}
|
||||
|
||||
fn table_exists_in(
|
||||
tables: &[kb_store::PostgresTableDiagnostics],
|
||||
table_name: &str,
|
||||
) -> std::option::Option<bool> {
|
||||
for table in tables {
|
||||
if table.table_name.as_str() == table_name {
|
||||
return std::option::Option::Some(table.exists);
|
||||
}
|
||||
}
|
||||
return std::option::Option::None;
|
||||
}
|
||||
|
||||
fn schema_report_status(missing_count: u32) -> &'static str {
|
||||
if missing_count == 0 {
|
||||
return "success";
|
||||
}
|
||||
return "warning";
|
||||
}
|
||||
|
||||
fn emit_sql_startup_error(splash_window: &tauri::WebviewWindow, message: &str) {
|
||||
emit_sql_startup_splash(
|
||||
splash_window,
|
||||
format!("PostgreSQL schema initialization error: {message}").as_str(),
|
||||
"danger",
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
fn emit_sql_startup_splash(
|
||||
splash_window: &tauri::WebviewWindow,
|
||||
message: &str,
|
||||
status: &str,
|
||||
log_only: bool,
|
||||
) {
|
||||
let order = if log_only { "add_log" } else { "add_msg" };
|
||||
crate::emit_splash_order(
|
||||
splash_window,
|
||||
order,
|
||||
std::option::Option::Some(message),
|
||||
std::option::Option::Some(status),
|
||||
std::option::Option::None,
|
||||
);
|
||||
}
|
||||
|
||||
/// Formats debug enums for UI display.
|
||||
pub(crate) fn debug_status<T: std::fmt::Debug>(value: T) -> std::string::String {
|
||||
return format!("{value:?}");
|
||||
}
|
||||
Reference in New Issue
Block a user