pub mod dynamodb;
pub mod error;
pub mod model;
pub mod sqlite;
use std::collections::BTreeMap;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use dynamodb::DynamoSource;
use error::DbError;
use model::{QueryOutcome, SchemaColumn, TablePage, Value};
use sqlite::SqliteSource;
pub enum DataSource {
Sqlite(SqliteSource),
Dynamo(DynamoSource),
}
impl DataSource {
pub async fn prepare_operation(&self, interrupted: Arc<AtomicBool>) -> Result<(), DbError> {
if let DataSource::Sqlite(source) = self {
source.prepare_operation(interrupted).await?;
}
Ok(())
}
pub async fn list_tables(&self) -> Result<Vec<String>, DbError> {
match self {
DataSource::Sqlite(source) => source.list_tables().await,
DataSource::Dynamo(source) => source.list_tables().await,
}
}
pub async fn fetch_rows(
&self,
table: &str,
limit: i64,
offset: i64,
) -> Result<TablePage, DbError> {
match self {
DataSource::Sqlite(source) => source.fetch_rows(table, limit, offset).await,
DataSource::Dynamo(source) => source.fetch_rows(table, limit, offset).await,
}
}
pub async fn table_schema(&self, table: &str) -> Result<Vec<SchemaColumn>, DbError> {
match self {
DataSource::Sqlite(source) => source.table_schema(table).await,
DataSource::Dynamo(source) => source.table_schema(table).await,
}
}
pub async fn schema_catalog(
&self,
) -> Result<Option<BTreeMap<String, Vec<SchemaColumn>>>, DbError> {
match self {
DataSource::Sqlite(source) => source.schema_catalog().await.map(Some),
DataSource::Dynamo(_) => Ok(None),
}
}
pub async fn execute_sql(&self, sql: &str, max_rows: usize) -> Result<QueryOutcome, DbError> {
match self {
DataSource::Sqlite(source) => source.execute_sql(sql, max_rows).await,
DataSource::Dynamo(source) => source.execute_sql(sql, max_rows).await,
}
}
pub async fn execute_table_filter(
&self,
table: &str,
sql: &str,
max_rows: usize,
) -> Result<Option<(TablePage, bool)>, DbError> {
match self {
DataSource::Sqlite(source) => source
.execute_table_filter(table, sql, max_rows)
.await
.map(Some),
DataSource::Dynamo(_) => Ok(None),
}
}
pub fn is_read_only(&self) -> bool {
match self {
DataSource::Sqlite(source) => source.is_read_only(),
DataSource::Dynamo(source) => source.is_read_only(),
}
}
pub async fn update_cell(
&self,
table: &str,
rowid: i64,
column: &str,
value: &Value,
) -> Result<(), DbError> {
match self {
DataSource::Sqlite(source) => source.update_cell(table, rowid, column, value).await,
DataSource::Dynamo(_) => Err(DbError::Unsupported(
"inline DynamoDB item editing is not available; use `tuible dynamodb put`"
.to_string(),
)),
}
}
pub fn backend_name(&self) -> &'static str {
match self {
DataSource::Sqlite(_) => "SQLite",
DataSource::Dynamo(_) => "DynamoDB",
}
}
pub fn supports_cell_edit(&self) -> bool {
matches!(self, DataSource::Sqlite(_))
}
pub fn auto_preview(&self) -> bool {
matches!(self, DataSource::Sqlite(_))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::unwrap_used)]
use super::*;
use tempfile::tempdir;
#[tokio::test]
async fn delegates_list_tables_to_sqlite_variant() {
let dir = tempdir().unwrap();
let path = dir.path().join("demo.sqlite");
sqlite::ensure_demo_db(&path).await.unwrap();
let source = DataSource::Sqlite(SqliteSource::connect(&path).await.unwrap());
let mut tables = source.list_tables().await.unwrap();
tables.sort();
assert_eq!(
tables,
vec![
"authors".to_string(),
"books".to_string(),
"events".to_string()
]
);
}
}