toasty_core/driver.rs
1//! Database driver interface for Toasty.
2//!
3//! This module defines the traits and types that database drivers must implement
4//! to integrate with the Toasty query engine. The two core traits are [`Driver`]
5//! (factory for connections and schema operations) and [`Connection`] (executes
6//! operations against a live database session).
7//!
8//! The query planner inspects [`Capability`] to decide which [`Operation`]
9//! variants to emit. SQL-based drivers receive [`Operation::QuerySql`] and
10//! [`Operation::Insert`], while key-value drivers (e.g., DynamoDB) receive
11//! [`Operation::GetByKey`], [`Operation::QueryPk`], etc.
12//!
13//! # Architecture
14//!
15//! ```text
16//! Query Engine ──▶ Operation ──▶ Connection::exec() ──▶ ExecResponse
17//! ▲
18//! │
19//! Driver::capability()
20//! ```
21
22mod capability;
23pub use capability::{Capability, StorageTypes};
24
25mod response;
26pub use response::{ExecResponse, Rows};
27
28pub mod operation;
29pub use operation::{IsolationLevel, Operation};
30
31use crate::schema::{
32 Schema,
33 db::{AppliedMigration, Migration, SchemaDiff},
34};
35
36use async_trait::async_trait;
37
38use std::{borrow::Cow, fmt::Debug, sync::Arc};
39
40/// Factory for database connections and provider of driver-level metadata.
41///
42/// Each database backend (SQLite, PostgreSQL, MySQL, DynamoDB) implements this
43/// trait to tell Toasty what the backend supports ([`Capability`]) and to
44/// create [`Connection`] instances on demand.
45///
46/// # Examples
47///
48/// ```ignore
49/// use toasty_core::driver::Driver;
50///
51/// // Drivers are typically constructed from a connection URL:
52/// let driver: Box<dyn Driver> = make_driver("sqlite::memory:").await;
53/// assert!(!driver.url().is_empty());
54///
55/// let capability = driver.capability();
56/// assert!(capability.sql);
57///
58/// let conn = driver.connect().await.unwrap();
59/// ```
60#[async_trait]
61pub trait Driver: Debug + Send + Sync + 'static {
62 /// Returns the URL this driver is connecting to.
63 fn url(&self) -> Cow<'_, str>;
64
65 /// Describes the driver's capability, which informs the query planner.
66 fn capability(&self) -> &'static Capability;
67
68 /// Creates a new connection to the database.
69 ///
70 /// This method is called by the [`Pool`] whenever a [`Connection`] is requested while none is
71 /// available and there is room to create a new [`Connection`].
72 async fn connect(&self) -> crate::Result<Box<dyn Connection>>;
73
74 /// Returns the maximum number of simultaneous database connections supported. For example,
75 /// this is `Some(1)` for the in-memory SQLite driver which cannot be pooled.
76 fn max_connections(&self) -> Option<usize> {
77 None
78 }
79
80 /// Generates a migration from a [`SchemaDiff`].
81 fn generate_migration(&self, schema_diff: &SchemaDiff<'_>) -> Migration;
82
83 /// Drops the entire database and recreates an empty one without applying migrations.
84 ///
85 /// Used primarily in tests to start with a clean slate.
86 async fn reset_db(&self) -> crate::Result<()>;
87}
88
89/// A live database session that can execute [`Operation`]s.
90///
91/// Connections are obtained from [`Driver::connect`] and are managed by the
92/// connection pool. All query execution flows through [`Connection::exec`],
93/// which accepts an [`Operation`] and returns an [`ExecResponse`].
94///
95/// # Examples
96///
97/// ```ignore
98/// use toasty_core::driver::{Connection, Operation, ExecResponse};
99/// use toasty_core::driver::operation::Transaction;
100///
101/// // Execute a transaction start operation on a connection:
102/// let response = conn.exec(&schema, Transaction::start().into()).await?;
103/// ```
104#[async_trait]
105pub trait Connection: Debug + Send + 'static {
106 /// Executes a database operation and returns the result.
107 ///
108 /// This is the single entry point for all database interactions. The
109 /// query engine compiles user queries into [`Operation`] values and
110 /// dispatches them here. The driver translates each operation into
111 /// backend-specific calls and returns an [`ExecResponse`].
112 async fn exec(&mut self, schema: &Arc<Schema>, plan: Operation) -> crate::Result<ExecResponse>;
113
114 /// Creates tables and indices defined in the schema on the database.
115 /// TODO: This will probably use database introspection in the future.
116 async fn push_schema(&mut self, _schema: &Schema) -> crate::Result<()>;
117
118 /// Returns a list of currently applied database migrations.
119 async fn applied_migrations(&mut self) -> crate::Result<Vec<AppliedMigration>>;
120
121 /// Applies a single migration to the database and records it as applied.
122 async fn apply_migration(
123 &mut self,
124 id: u64,
125 name: &str,
126 migration: &Migration,
127 ) -> crate::Result<()>;
128}