mod call;
mod input;
mod interactive;
mod perf;
mod render;
mod route;
use std::path::PathBuf;
use candid::Decode;
use icydb::db::sql::SqlQueryResult;
use crate::{
cli::SqlArgs,
config::{SQL_DDL_ENDPOINT, SQL_QUERY_ENDPOINT, require_configured_endpoint},
icp::require_created_canister,
shell::render::{ShellSqlQueryPerfResult, render_shell_text_from_perf_result},
};
struct ShellConfig {
canister: String,
environment: String,
history_file: PathBuf,
sql: Option<String>,
}
impl ShellConfig {
fn from_sql_args(args: SqlArgs) -> Self {
let (canister, environment, history_file, sql, trailing_sql) = args.into_shell_fields();
let sql = sql.or_else(|| (!trailing_sql.is_empty()).then(|| trailing_sql.join(" ")));
Self {
canister,
environment,
history_file,
sql,
}
}
}
pub(crate) fn run_sql_command(args: SqlArgs) -> Result<(), String> {
let config = ShellConfig::from_sql_args(args);
if let Some(sql) = config.sql {
let output = execute_sql(
config.environment.as_str(),
config.canister.as_str(),
sql.as_str(),
)?;
print!(
"{}",
render::finalize_successful_command_output(output.as_str())
);
} else {
require_created_canister(config.environment.as_str(), config.canister.as_str())?;
interactive::run_interactive_shell(&config)?;
}
Ok(())
}
fn execute_sql(environment: &str, canister: &str, sql: &str) -> Result<String, String> {
let call_kind = route::sql_shell_call_kind(sql);
let endpoint = match call_kind {
route::SqlShellCallKind::Query => SQL_QUERY_ENDPOINT,
route::SqlShellCallKind::Ddl => SQL_DDL_ENDPOINT,
};
require_configured_endpoint(canister, endpoint)?;
require_created_canister(environment, canister)?;
let escaped_sql = call::candid_escape_string(sql);
match call_kind {
route::SqlShellCallKind::Query => {
let candid_bytes = call::icp_query(
environment,
canister,
endpoint.method(),
escaped_sql.as_str(),
)?;
let response = Decode!(
candid_bytes.as_slice(),
Result<ShellSqlQueryPerfResult, icydb::Error>
)
.map_err(|err| err.to_string())?;
match response {
Ok(result) => Ok(render_shell_text_from_perf_result(result)),
Err(err) => Ok(format!(
"ERROR: {}",
call::sql_error_with_recovery_hint(&err.to_string(), environment, canister)
)),
}
}
route::SqlShellCallKind::Ddl => {
let candid_bytes = call::icp_update(
environment,
canister,
endpoint.method(),
escaped_sql.as_str(),
)?;
let response = Decode!(candid_bytes.as_slice(), Result<SqlQueryResult, icydb::Error>)
.map_err(|err| err.to_string())?;
match response {
Ok(result) => Ok(result.render_text()),
Err(err) => Ok(format!(
"ERROR: {}",
call::sql_error_with_recovery_hint(&err.to_string(), environment, canister)
)),
}
}
}
}
#[cfg(test)]
pub(crate) mod test_support {
pub(crate) use super::{perf::ShellPerfAttribution, route::SqlShellCallKind};
pub(crate) fn drain_complete_shell_statements(
statement: &mut String,
) -> std::collections::VecDeque<String> {
super::input::drain_complete_shell_statements(statement)
}
pub(crate) fn is_shell_help_command(input: &str) -> bool {
super::input::is_shell_help_command(input)
}
pub(crate) fn normalize_shell_statement_line(line: &str) -> String {
super::input::normalize_shell_statement_line(line)
}
pub(crate) fn normalize_grouped_next_cursor_json(value: &mut serde_json::Value) {
super::perf::normalize_grouped_next_cursor_json(value);
}
pub(crate) fn parse_perf_result(
value: &serde_json::Value,
) -> Result<(icydb::db::sql::SqlQueryResult, ShellPerfAttribution), String> {
super::perf::parse_perf_result(value)
}
pub(crate) fn render_perf_suffix(attribution: Option<&ShellPerfAttribution>) -> Option<String> {
super::perf::render_perf_suffix(attribution)
}
pub(crate) const fn shell_perf_attribution(
total: u64,
compiler: u64,
planner: u64,
store: u64,
executor: u64,
decode: u64,
) -> ShellPerfAttribution {
super::perf::ShellPerfAttribution::new(super::perf::ShellPerfAttributionInput {
total,
planner,
store,
executor,
pure_covering_decode: 0,
pure_covering_row_assembly: 0,
decode,
compiler,
})
}
pub(crate) const fn shell_help_text() -> &'static str {
super::input::shell_help_text()
}
pub(crate) fn sql_shell_call_kind(sql: &str) -> SqlShellCallKind {
super::route::sql_shell_call_kind(sql)
}
pub(crate) fn sql_error_with_recovery_hint(
error: &str,
environment: &str,
canister: &str,
) -> String {
super::call::sql_error_with_recovery_hint(error, environment, canister)
}
pub(crate) fn finalize_successful_command_output(rendered: &str) -> String {
super::render::finalize_successful_command_output(rendered)
}
pub(crate) fn render_grouped_shell_text(
rows: icydb::db::sql::SqlGroupedRowsOutput,
attribution: Option<ShellPerfAttribution>,
) -> String {
super::render::render_grouped_shell_text(rows, attribution, None)
}
pub(crate) fn render_projection_shell_text(
rows: icydb::db::sql::SqlQueryRowsOutput,
attribution: Option<ShellPerfAttribution>,
) -> String {
super::render::render_projection_shell_text(rows, attribution, None)
}
pub(crate) fn sql_config_parts(
args: super::SqlArgs,
) -> (String, String, std::path::PathBuf, Option<String>) {
let config = super::ShellConfig::from_sql_args(args);
(
config.canister,
config.environment,
config.history_file,
config.sql,
)
}
}