use std::fmt;
use async_trait::async_trait;
use crate::error::Error;
use crate::query::sql_builder::{SqlBuilder, SqlParam};
use crate::schema_cache::db::DbIntrospector;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DbVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
pub engine: String,
}
impl fmt::Display for DbVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {}.{}.{}",
self.engine, self.major, self.minor, self.patch
)
}
}
#[derive(Debug, Clone)]
pub struct StatementResult {
pub total: Option<i64>,
pub page_total: i64,
pub body: String,
pub response_headers: Option<serde_json::Value>,
pub response_status: Option<i32>,
}
impl StatementResult {
pub fn empty() -> Self {
Self {
total: None,
page_total: 0,
body: "[]".to_string(),
response_headers: None,
response_status: None,
}
}
}
#[async_trait]
pub trait DatabaseBackend: Send + Sync + 'static {
async fn connect(
uri: &str,
pool_size: u32,
acquire_timeout_secs: u64,
max_lifetime_secs: u64,
idle_timeout_secs: u64,
) -> Result<Self, Error>
where
Self: Sized;
async fn version(&self) -> Result<DbVersion, Error>;
fn min_version(&self) -> (u32, u32);
async fn exec_raw(&self, sql: &str, params: &[SqlParam]) -> Result<(), Error>;
async fn exec_statement(
&self,
sql: &str,
params: &[SqlParam],
) -> Result<StatementResult, Error>;
async fn exec_in_transaction(
&self,
tx_vars: Option<&SqlBuilder>,
pre_req: Option<&SqlBuilder>,
mutation: Option<&SqlBuilder>,
main: Option<&SqlBuilder>,
) -> Result<StatementResult, Error>;
fn introspector(&self) -> Box<dyn DbIntrospector + '_>;
async fn start_listener(
&self,
channel: &str,
cancel: tokio::sync::watch::Receiver<bool>,
on_event: std::sync::Arc<dyn Fn(String) + Send + Sync>,
) -> Result<(), Error>;
fn map_error(&self, err: Box<dyn std::error::Error + Send + Sync>) -> Error;
}
pub trait SqlDialect: Send + Sync {
fn json_agg(&self, b: &mut SqlBuilder, alias: &str) {
self.json_agg_with_columns(b, alias, &[]);
}
fn json_agg_with_columns(&self, b: &mut SqlBuilder, alias: &str, columns: &[&str]);
fn row_to_json(&self, b: &mut SqlBuilder, alias: &str) {
self.row_to_json_with_columns(b, alias, &[]);
}
fn row_to_json_with_columns(&self, b: &mut SqlBuilder, alias: &str, columns: &[&str]);
fn count_expr(&self, b: &mut SqlBuilder, expr: &str);
fn count_star(&self, b: &mut SqlBuilder);
fn set_session_var(&self, b: &mut SqlBuilder, key: &str, value: &str);
fn get_session_var(&self, b: &mut SqlBuilder, key: &str, column_alias: &str);
fn session_vars_are_select_exprs(&self) -> bool {
true
}
fn build_tx_vars_statement(&self, _b: &mut SqlBuilder, _vars: &[(&str, &str)]) {
unimplemented!(
"backends with session_vars_are_select_exprs() == false must implement build_tx_vars_statement"
)
}
fn type_cast(&self, b: &mut SqlBuilder, expr: &str, ty: &str);
#[allow(clippy::wrong_self_convention)]
fn from_json_body(
&self,
b: &mut SqlBuilder,
columns: &[crate::plan::types::CoercibleField],
json_bytes: &[u8],
);
fn push_type_cast_suffix(&self, b: &mut SqlBuilder, ty: &str);
fn push_array_type_cast_suffix(&self, b: &mut SqlBuilder, ty: &str);
fn quote_ident(&self, ident: &str) -> String;
fn quote_literal(&self, lit: &str) -> String;
fn supports_fts(&self) -> bool;
fn fts_predicate(&self, b: &mut SqlBuilder, config: Option<&str>, column: &str, operator: &str);
fn row_to_json_star(&self, b: &mut SqlBuilder, source: &str) {
b.push("row_to_json(");
b.push(source);
b.push(".*)::text");
}
fn count_star_from(&self, b: &mut SqlBuilder, source: &str) {
b.push("(SELECT pg_catalog.count(*) FROM ");
b.push(source);
b.push(")");
}
fn push_literal(&self, b: &mut SqlBuilder, s: &str) {
let has_backslash = s.contains('\\');
if has_backslash {
b.push("E");
}
b.push("'");
for ch in s.chars() {
if ch == '\'' {
b.push("'");
}
b.push_char(ch);
}
b.push("'");
}
fn supports_lateral_join(&self) -> bool;
fn named_param_assign(&self) -> &str {
" := "
}
fn supports_dml_cte(&self) -> bool {
true
}
}