use anyhow::Result;
use clap::Args;
use futures_util::StreamExt;
use surrealdb::Connection;
use surrealdb::engine::any::{self, connect};
use surrealdb::method::{Export, ExportConfig};
use surrealdb_core::kvs::export::TableConfig;
use tokio::io::{self, AsyncWriteExt};
use crate::cli::abstraction::auth::{CredentialsBuilder, CredentialsLevel};
use crate::cli::abstraction::{AuthArguments, DatabaseSelectionArguments};
#[derive(Args, Debug)]
pub struct DatabaseConnectionArguments {
#[arg(help = "Database endpoint to export from")]
#[arg(short = 'e', long = "endpoint")]
#[arg(default_value = "http://localhost:8000")]
#[arg(value_parser = super::validator::endpoint_valid)]
pub(crate) endpoint: String,
}
#[derive(Args, Debug)]
struct ExportConfigArguments {
#[arg(long)]
only: bool,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
users: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
accesses: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
params: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
functions: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
analyzers: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true", value_parser = super::validator::export_tables)]
tables: Option<TableConfig>,
#[arg(long, value_parser = super::validator::export_tables_exclude)]
#[arg(conflicts_with = "tables")]
tables_exclude: Option<TableConfig>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
versions: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
records: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
apis: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
buckets: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
modules: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
configs: Option<bool>,
}
#[derive(Args, Debug)]
pub struct ExportCommandArguments {
#[arg(help = "Path to the SurrealQL file to export. Use dash - to write into stdout.")]
#[arg(default_value = "-")]
#[arg(index = 1)]
file: String,
#[command(flatten)]
conn: DatabaseConnectionArguments,
#[command(flatten)]
auth: AuthArguments,
#[command(flatten)]
sel: DatabaseSelectionArguments,
#[command(flatten)]
config: ExportConfigArguments,
}
pub async fn init(
ExportCommandArguments {
file,
conn: DatabaseConnectionArguments {
endpoint,
},
auth: AuthArguments {
username,
password,
token,
auth_level,
},
sel: DatabaseSelectionArguments {
namespace,
database,
},
config,
}: ExportCommandArguments,
) -> Result<()> {
let is_local = any::__into_endpoint(&endpoint)?.parse_kind()?.is_local();
let client = if username.is_some() && password.is_some() && !is_local {
debug!("Connecting to the database engine with authentication");
let creds = CredentialsBuilder::default()
.with_username(username.clone())
.with_password(password.clone())
.with_namespace(namespace.clone())
.with_database(database.clone());
let client = connect(endpoint).await?;
debug!("Signing in to the database engine at '{:?}' level", auth_level);
match auth_level {
CredentialsLevel::Root => client.signin(creds.root()?).await?,
CredentialsLevel::Namespace => client.signin(creds.namespace()?).await?,
CredentialsLevel::Database => client.signin(creds.database()?).await?,
};
client
} else if token.is_some() && !is_local {
let client = connect(endpoint).await?;
if let Some(t) = token {
client.authenticate(t).await?;
}
client
} else {
debug!("Connecting to the database engine without authentication");
connect(endpoint).await?
};
client.use_ns(namespace).use_db(database).await?;
debug!("Exporting data from the database");
if file == "-" {
let mut backup = apply_config(config, client.export(())).await?;
let mut stdout = io::stdout();
while let Some(bytes) = backup.next().await {
stdout.write_all(&bytes?).await?;
}
} else {
apply_config(config, client.export(file)).await?;
}
info!("The SurrealQL file was exported successfully");
Ok(())
}
fn apply_config<C: Connection, R>(
config: ExportConfigArguments,
export: Export<C, R>,
) -> Export<C, R, ExportConfig> {
let mut export = export.with_config();
if config.only {
export = export
.users(false)
.accesses(false)
.params(false)
.functions(false)
.analyzers(false)
.tables(false)
.versions(false)
.records(false);
}
if let Some(value) = config.users {
export = export.users(value);
}
if let Some(value) = config.accesses {
export = export.accesses(value);
}
if let Some(value) = config.params {
export = export.params(value);
}
if let Some(value) = config.functions {
export = export.functions(value);
}
if let Some(value) = config.analyzers {
export = export.analyzers(value);
}
match (config.tables, config.tables_exclude) {
(Some(_), Some(_)) => unreachable!("Cannot specify both --tables and --tables-exclude"),
(Some(tables), None) => export = export.tables(tables),
(None, Some(tables_exclude)) => export = export.tables(tables_exclude),
(None, None) => {}
}
if let Some(value) = config.versions {
export = export.versions(value);
}
if let Some(value) = config.records {
export = export.records(value);
}
if let Some(value) = config.apis {
export = export.apis(value);
}
if let Some(value) = config.buckets {
export = export.buckets(value);
}
if let Some(value) = config.modules {
export = export.modules(value);
}
if let Some(value) = config.configs {
export = export.configs(value);
}
export
}