mod adapter;
mod contract;
mod rows;
pub mod error;
pub use crate::contract::{
AccessMode, AtomicResult, GeneratedKey, OpenOptions, Operation, OperationKind, OperationResult,
StorageContext, WriteResult, STORAGE_CONTEXT_VERSION,
};
pub use crate::error::{AdapterErrorKind, DactylError};
pub use crate::rows::{Parameter, Row, Rows};
use crate::adapter::Adapter;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Datastore {
Sqlite,
Neon,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DatastoreRoute {
datastore: Datastore,
route: String,
token: Option<String>,
}
impl DatastoreRoute {
pub fn sqlite(path: impl Into<String>) -> Self {
Self {
datastore: Datastore::Sqlite,
route: path.into(),
token: None,
}
}
pub fn neon(endpoint: impl Into<String>, token: Option<String>) -> Self {
Self {
datastore: Datastore::Neon,
route: endpoint.into(),
token,
}
}
pub fn datastore(&self) -> Datastore {
self.datastore
}
pub fn route(&self) -> &str {
&self.route
}
pub fn token(&self) -> Option<&str> {
self.token.as_deref()
}
pub fn from_env() -> Result<Self, DactylError> {
let datastore = std::env::var("DATASTORE")
.map_err(|_| DactylError::Config("DATASTORE is not set: use sqlite or neon".into()))?;
let route = std::env::var("DATASTORE_ROUTE")
.map_err(|_| DactylError::Config("DATASTORE_ROUTE is not set".into()))?;
match datastore.as_str() {
"sqlite" => Ok(Self::sqlite(route)),
"neon" => Ok(Self::neon(route, std::env::var("DATASTORE_TOKEN").ok())),
other => Err(DactylError::Config(format!(
"invalid DATASTORE value {other:?}: use sqlite or neon"
))),
}
}
}
pub struct Connection {
adapter: Box<dyn Adapter>,
route: DatastoreRoute,
context: Option<StorageContext>,
}
impl Connection {
pub fn open(route: DatastoreRoute) -> Result<Self, DactylError> {
Self::open_with_options_and_context(route, OpenOptions::default(), None)
}
pub fn open_with_options(
route: DatastoreRoute,
options: OpenOptions,
) -> Result<Self, DactylError> {
Self::open_with_options_and_context(route, options, None)
}
pub fn open_with_context(
route: DatastoreRoute,
context: Option<StorageContext>,
) -> Result<Self, DactylError> {
Self::open_with_options_and_context(route, OpenOptions::default(), context)
}
pub fn open_with_options_and_context(
route: DatastoreRoute,
options: OpenOptions,
context: Option<StorageContext>,
) -> Result<Self, DactylError> {
if let Some(context) = &context {
context.validate()?;
}
let adapter = build_adapter(&route, options, context.clone())?;
Ok(Self {
adapter,
route,
context,
})
}
pub fn from_env() -> Result<Self, DactylError> {
Self::open(DatastoreRoute::from_env()?)
}
pub fn datastore(&self) -> Datastore {
self.route.datastore
}
pub fn route(&self) -> &DatastoreRoute {
&self.route
}
pub fn context(&self) -> Option<&StorageContext> {
self.context.as_ref()
}
pub fn read(&self, sql: &str, params: &[Parameter]) -> Result<Rows, DactylError> {
self.adapter.read(sql, params)
}
pub fn write_result(
&self,
sql: &str,
params: &[Parameter],
) -> Result<WriteResult, DactylError> {
self.adapter.write(sql, params)
}
pub fn write(&self, sql: &str, params: &[Parameter]) -> Result<u64, DactylError> {
Ok(self.write_result(sql, params)?.affected_rows)
}
pub fn atomic(&self, operations: &[Operation]) -> Result<AtomicResult, DactylError> {
self.adapter.atomic(operations)
}
pub fn access_mode(&self) -> AccessMode {
self.adapter.access_mode()
}
}
pub type Driver = Connection;
pub fn read(sql: &str, params: &[Parameter]) -> Result<Rows, DactylError> {
Connection::from_env()?.read(sql, params)
}
pub fn read_with_context(
context: Option<StorageContext>,
sql: &str,
params: &[Parameter],
) -> Result<Rows, DactylError> {
Connection::open_with_context(DatastoreRoute::from_env()?, context)?.read(sql, params)
}
pub fn write(sql: &str, params: &[Parameter]) -> Result<u64, DactylError> {
Connection::from_env()?.write(sql, params)
}
pub fn write_with_context(
context: Option<StorageContext>,
sql: &str,
params: &[Parameter],
) -> Result<u64, DactylError> {
Connection::open_with_context(DatastoreRoute::from_env()?, context)?.write(sql, params)
}
#[deprecated(note = "use dactyl_db::read")]
pub fn query(sql: &str, params: &[Parameter]) -> Result<Rows, DactylError> {
read(sql, params)
}
#[deprecated(note = "use dactyl_db::write")]
pub fn execute(sql: &str, params: &[Parameter]) -> Result<u64, DactylError> {
write(sql, params)
}
fn build_adapter(
route: &DatastoreRoute,
_options: OpenOptions,
_context: Option<StorageContext>,
) -> Result<Box<dyn Adapter>, DactylError> {
match route.datastore {
Datastore::Sqlite => {
#[cfg(feature = "sqlite")]
{
Ok(Box::new(
crate::adapter::sqlite::SqliteAdapter::open_with_options(
&route.route,
_options,
)?,
))
}
#[cfg(not(feature = "sqlite"))]
{
Err(DactylError::Config(
"sqlite support is disabled; enable the `sqlite` feature".into(),
))
}
}
Datastore::Neon => {
#[cfg(feature = "neon")]
{
Ok(Box::new(
crate::adapter::neon::NeonAdapter::new_with_options(
&route.route,
route.token.clone(),
_options,
_context,
),
))
}
#[cfg(not(feature = "neon"))]
{
Err(DactylError::Config(
"neon support is disabled; enable the `neon` feature".into(),
))
}
}
}
}