use std::fmt;
use anyhow::Result;
use async_channel::Sender;
use surrealdb_types::{SurrealValue, ToSql};
use super::{KVValue, Transaction};
use crate::catalog::providers::{
ApiProvider, AuthorisationProvider, BucketProvider, DatabaseProvider, TableProvider,
UserProvider,
};
use crate::catalog::{DatabaseId, NamespaceId, Record, TableDefinition};
use crate::err::Error;
use crate::expr::paths::{IN, OUT};
use crate::expr::statements::define::{DefineAccessStatement, DefineUserStatement};
use crate::expr::{Base, DefineAnalyzerStatement};
use crate::key::record;
use crate::sql::statements::OptionStatement;
#[derive(Clone, Debug, SurrealValue)]
#[surreal(crate = "surrealdb_types")]
#[surreal(default)]
pub struct Config {
pub users: bool,
pub accesses: bool,
pub params: bool,
pub functions: bool,
pub analyzers: bool,
pub apis: bool,
pub buckets: bool,
pub modules: bool,
pub configs: bool,
pub tables: TableConfig,
pub versions: bool,
pub records: bool,
pub sequences: bool,
}
impl Default for Config {
fn default() -> Config {
Config {
users: true,
accesses: true,
params: true,
functions: true,
analyzers: true,
apis: true,
buckets: true,
modules: true,
configs: true,
tables: TableConfig::default(),
versions: false,
records: true,
sequences: true,
}
}
}
#[derive(Clone, Debug, SurrealValue)]
#[surreal(crate = "surrealdb_types")]
pub struct ExcludedTables {
pub exclude: Vec<String>,
}
#[derive(Clone, Debug, Default, SurrealValue)]
#[surreal(crate = "surrealdb_types")]
#[surreal(untagged)]
pub enum TableConfig {
#[default]
#[surreal(value = true)]
All,
#[surreal(value = false)]
None,
Some(Vec<String>),
Exclude(ExcludedTables),
}
impl From<bool> for TableConfig {
fn from(value: bool) -> Self {
match value {
true => TableConfig::All,
false => TableConfig::None,
}
}
}
impl From<Vec<String>> for TableConfig {
fn from(value: Vec<String>) -> Self {
TableConfig::Some(value)
}
}
impl From<Vec<&str>> for TableConfig {
fn from(value: Vec<&str>) -> Self {
TableConfig::Some(value.into_iter().map(ToOwned::to_owned).collect())
}
}
impl TableConfig {
pub(crate) fn is_any(&self) -> bool {
matches!(self, Self::All | Self::Some(_) | Self::Exclude(_))
}
pub(crate) fn includes(&self, table: &str) -> bool {
match self {
Self::All => true,
Self::None => false,
Self::Some(v) => v.iter().any(|v| v.eq(table)),
Self::Exclude(v) => !v.exclude.iter().any(|v| v.eq(table)),
}
}
pub(crate) fn names(&self) -> Option<&[String]> {
match self {
Self::Some(v) => Some(v.as_slice()),
Self::Exclude(v) => Some(v.exclude.as_slice()),
_ => None,
}
}
}
struct InlineCommentWriter<'a, F>(&'a mut F);
impl<F: fmt::Write> fmt::Write for InlineCommentWriter<'_, F> {
fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.chars() {
self.write_char(c)?
}
Ok(())
}
fn write_char(&mut self, c: char) -> fmt::Result {
match c {
'\n' => self.0.write_str("\\n"),
'\r' => self.0.write_str("\\r"),
'\u{0085}' => self.0.write_str("\\u{0085}"),
'\u{2028}' => self.0.write_str("\\u{2028}"),
'\u{2029}' => self.0.write_str("\\u{2029}"),
_ => self.0.write_char(c),
}
}
}
struct InlineCommentDisplay<F>(F);
impl<F: fmt::Display> fmt::Display for InlineCommentDisplay<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Write::write_fmt(&mut InlineCommentWriter(f), format_args!("{}", self.0))
}
}
impl Transaction {
pub async fn export(
&self,
ns: &str,
db: &str,
cfg: Config,
batch_size: u32,
chn: Sender<Vec<u8>>,
) -> Result<()> {
let db = self.get_db_by_name(ns, db, None).await?.ok_or_else(|| {
anyhow::Error::new(Error::DbNotFound {
name: db.to_owned(),
})
})?;
self.export_metadata(&cfg, &chn, db.namespace_id, db.database_id).await?;
self.export_tables(&cfg, &chn, db.namespace_id, db.database_id, batch_size).await?;
Ok(())
}
async fn export_metadata(
&self,
cfg: &Config,
chn: &Sender<Vec<u8>>,
ns: NamespaceId,
db: DatabaseId,
) -> Result<()> {
self.export_section("OPTION", [OptionStatement::import()].into_iter(), chn).await?;
if cfg.users {
let users = self.all_db_users(ns, db, None).await?;
self.export_section(
"USERS",
users.iter().map(|x| DefineUserStatement::from_definition(Base::Db, x)),
chn,
)
.await?;
}
if cfg.accesses {
let accesses = self.all_db_accesses(ns, db, None).await?;
self.export_section(
"ACCESSES",
accesses
.iter()
.map(|x| DefineAccessStatement::from_definition(Base::Db, x).redact()),
chn,
)
.await?;
}
if cfg.params {
let params = self.all_db_params(ns, db, None).await?;
self.export_section("PARAMS", params.iter(), chn).await?;
}
if cfg.functions {
let functions = self.all_db_functions(ns, db, None).await?;
self.export_section("FUNCTIONS", functions.iter(), chn).await?;
}
if cfg.analyzers {
let analyzers = self.all_db_analyzers(ns, db, None).await?;
self.export_section(
"ANALYZERS",
analyzers.iter().map(DefineAnalyzerStatement::from_definition),
chn,
)
.await?;
}
if cfg.apis {
let apis = self.all_db_apis(ns, db, None).await?;
self.export_section("APIS", apis.iter(), chn).await?;
}
if cfg.buckets {
let buckets = self.all_db_buckets(ns, db, None).await?;
self.export_section("BUCKETS", buckets.iter(), chn).await?;
}
if cfg.modules {
let modules = self.all_db_modules(ns, db, None).await?;
self.export_section("MODULES", modules.iter(), chn).await?;
}
if cfg.configs {
let configs = self.all_db_configs(ns, db, None).await?;
self.export_section("CONFIGS", configs.iter(), chn).await?;
}
if cfg.sequences {
let sequences = self.all_db_sequences(ns, db, None).await?;
self.export_section("SEQUENCES", sequences.iter(), chn).await?;
}
Ok(())
}
async fn export_section<T>(
&self,
title: &str,
items: impl ExactSizeIterator<Item = T>,
chn: &Sender<Vec<u8>>,
) -> Result<()>
where
T: ToSql,
{
if items.len() == 0 {
return Ok(());
}
chn.send(bytes!("-- ------------------------------")).await?;
chn.send(bytes!(format!("-- {}", InlineCommentDisplay(title)))).await?;
chn.send(bytes!("-- ------------------------------")).await?;
chn.send(bytes!("")).await?;
for item in items {
chn.send(bytes!(format!("{};", item.to_sql()))).await?;
}
chn.send(bytes!("")).await?;
Ok(())
}
async fn export_tables(
&self,
cfg: &Config,
chn: &Sender<Vec<u8>>,
ns: NamespaceId,
db: DatabaseId,
batch_size: u32,
) -> Result<()> {
if !cfg.tables.is_any() {
return Ok(());
}
let tables = self.all_tb(ns, db, None).await?;
if let Some(names) = cfg.tables.names() {
let existing: Vec<&str> = tables.iter().map(|t| t.name.as_str()).collect();
for name in names {
if !existing.contains(&name.as_str()) {
warn!("Table '{name}' does not exist in the database");
}
}
}
for table in tables.iter() {
if !cfg.tables.includes(&table.name) {
continue;
}
self.export_table_structure(ns, db, table, chn).await?;
if cfg.records {
self.export_table_data(ns, db, table, chn, batch_size).await?;
}
}
Ok(())
}
async fn export_table_structure(
&self,
ns: NamespaceId,
db: DatabaseId,
table: &TableDefinition,
chn: &Sender<Vec<u8>>,
) -> Result<()> {
chn.send(bytes!("-- ------------------------------")).await?;
chn.send(bytes!(format!("-- TABLE: {}", InlineCommentDisplay(&table.name)))).await?;
chn.send(bytes!("-- ------------------------------")).await?;
chn.send(bytes!("")).await?;
chn.send(bytes!(format!("{};", table.to_sql()))).await?;
chn.send(bytes!("")).await?;
let fields = self.all_tb_fields(ns, db, &table.name, None).await?;
for field in fields.iter() {
let mut stmt = field.to_sql_definition();
stmt.kind = crate::sql::statements::define::DefineKind::Overwrite;
chn.send(bytes!(format!("{};", stmt.to_sql()))).await?;
}
chn.send(bytes!("")).await?;
let indexes = self.all_tb_indexes(ns, db, &table.name, None).await?;
for index in indexes.iter() {
chn.send(bytes!(format!("{};", index.to_sql()))).await?;
}
chn.send(bytes!("")).await?;
let events = self.all_tb_events(ns, db, &table.name, None).await?;
for event in events.iter() {
chn.send(bytes!(format!("{};", event.to_sql()))).await?;
}
chn.send(bytes!("")).await?;
Ok(())
}
async fn export_table_data(
&self,
ns: NamespaceId,
db: DatabaseId,
table: &TableDefinition,
chn: &Sender<Vec<u8>>,
batch_size: u32,
) -> Result<()> {
chn.send(bytes!("-- ------------------------------")).await?;
chn.send(bytes!(format!("-- TABLE DATA: {}", InlineCommentDisplay(&table.name)))).await?;
chn.send(bytes!("-- ------------------------------")).await?;
chn.send(bytes!("")).await?;
let beg = crate::key::record::prefix(ns, db, &table.name)?;
let end = crate::key::record::suffix(ns, db, &table.name)?;
let mut next = Some(beg..end);
while let Some(rng) = next {
let batch = self.batch_keys_vals(rng, batch_size, None).await?;
next = batch.next;
if batch.result.is_empty() {
break;
}
self.export_regular_data(batch.result, chn).await?;
}
chn.send(bytes!("")).await?;
Ok(())
}
fn process_record(record: &Record, records_relate: &mut String, records_normal: &mut String) {
if record.is_edge()
&& let crate::val::Value::RecordId(_) = record.data.pick(&IN)
&& let crate::val::Value::RecordId(_) = record.data.pick(&OUT)
{
if !records_relate.is_empty() {
records_relate.push_str(", ");
}
records_relate.push_str(&record.data.to_sql());
} else {
if !records_normal.is_empty() {
records_normal.push_str(", ");
}
records_normal.push_str(&record.data.to_sql());
}
}
async fn export_regular_data(
&self,
regular_values: Vec<(Vec<u8>, Vec<u8>)>,
chn: &Sender<Vec<u8>>,
) -> Result<()> {
let mut records_normal = String::new();
let mut records_relate = String::new();
for (k, v) in regular_values {
let k = record::RecordKey::decode_key(&k)?;
let rid = crate::val::RecordId {
table: k.tb.into_owned(),
key: k.id,
};
let v = Record::kv_decode_value(&v, rid)?;
Self::process_record(&v, &mut records_relate, &mut records_normal);
}
if !records_normal.is_empty() {
let sql = format!("INSERT [ {} ];", records_normal);
chn.send(bytes!(sql)).await?;
}
if !records_relate.is_empty() {
let sql = format!("INSERT RELATION [ {} ];", records_relate);
chn.send(bytes!(sql)).await?;
}
Ok(())
}
}