use crate::error::SqlError;
use crate::guard::SizeGuards;
use crate::stream::{BoxRowStream, RowCursor};
use crate::value::{ColumnInfo, Row};
use async_trait::async_trait;
use secrecy::SecretString;
#[derive(Debug, Clone, Default)]
pub struct ConnectOptions {
pub insecure: bool,
pub password: Option<SecretString>,
}
impl ConnectOptions {
#[must_use]
pub fn effective_password(&self, url: &crate::url::DatabaseUrl) -> Option<SecretString> {
self.password.clone().or_else(|| url.password())
}
}
#[derive(Debug, Clone)]
pub struct QueryResult {
pub columns: Vec<ColumnInfo>,
pub rows: Vec<Row>,
}
#[derive(Debug, Clone)]
pub struct ExecutionSummary {
pub rows_affected: Option<u64>,
pub command_tag: Option<String>,
}
#[derive(Debug, Clone)]
pub enum StatementResult {
Query(QueryResult),
Summary(ExecutionSummary),
}
#[derive(Debug)]
pub struct BulkInsert<'a> {
pub table: &'a str,
pub columns: &'a [ColumnInfo],
pub rows: &'a [Row],
pub copy_format: crate::copy::CopyFormat,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ForeignKey {
pub child_table: String,
pub child_columns: Vec<String>,
pub parent_table: String,
pub parent_columns: Vec<String>,
pub on_delete: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SchemaInfo {
pub name: String,
pub is_default: bool,
}
pub(crate) fn is_default_from_value(value: Option<&crate::value::Value>) -> bool {
match value {
Some(crate::value::Value::Bool(b)) => *b,
Some(crate::value::Value::Int64(n)) => *n != 0,
_ => false,
}
}
#[async_trait]
pub(crate) trait AsyncConnection: Send {
async fn execute(&mut self, sql: &str) -> Result<ExecutionSummary, SqlError>;
async fn query(&mut self, sql: &str) -> Result<QueryResult, SqlError>;
async fn query_stream(
&mut self,
sql: &str,
) -> Result<(Vec<ColumnInfo>, BoxRowStream<'_>), SqlError>;
async fn execute_multi(&mut self, sql: &str) -> Result<Vec<StatementResult>, SqlError> {
match self.query(sql).await {
Ok(result) => Ok(vec![StatementResult::Query(result)]),
Err(SqlError::QueryFailed(_)) => {
let summary = self.execute(sql).await?;
Ok(vec![StatementResult::Summary(summary)])
}
Err(e) => Err(e),
}
}
async fn ping(&mut self) -> Result<(), SqlError>;
async fn list_tables(&mut self, schema: Option<&str>) -> Result<Vec<String>, SqlError>;
async fn list_schemas(&mut self) -> Result<Vec<SchemaInfo>, SqlError>;
async fn describe_table(
&mut self,
schema: Option<&str>,
table: &str,
) -> Result<QueryResult, SqlError>;
async fn primary_key(
&mut self,
schema: Option<&str>,
table: &str,
) -> Result<Vec<String>, SqlError>;
async fn list_foreign_keys(
&mut self,
schema: Option<&str>,
) -> Result<Vec<ForeignKey>, SqlError>;
async fn bulk_insert_rows(&mut self, target: BulkInsert<'_>) -> Result<usize, SqlError>;
}
pub trait Connection: Send {
fn execute(&mut self, sql: &str) -> Result<ExecutionSummary, SqlError>;
fn query(&mut self, sql: &str) -> Result<QueryResult, SqlError>;
fn query_cursor(&mut self, sql: &str) -> Result<RowCursor<'_>, SqlError>;
fn size_guards(&self) -> SizeGuards {
SizeGuards::default()
}
fn set_size_guards(&mut self, guards: SizeGuards) {
let _ = guards;
}
fn execute_multi(&mut self, sql: &str) -> Result<Vec<StatementResult>, SqlError>;
fn ping(&mut self) -> Result<(), SqlError>;
fn list_tables(&mut self, schema: Option<&str>) -> Result<Vec<String>, SqlError>;
fn list_schemas(&mut self) -> Result<Vec<SchemaInfo>, SqlError>;
fn describe_table(
&mut self,
schema: Option<&str>,
table: &str,
) -> Result<QueryResult, SqlError>;
fn primary_key(&mut self, schema: Option<&str>, table: &str) -> Result<Vec<String>, SqlError>;
fn list_foreign_keys(&mut self, schema: Option<&str>) -> Result<Vec<ForeignKey>, SqlError>;
fn bulk_insert_rows(&mut self, target: BulkInsert<'_>) -> Result<usize, SqlError>;
}
impl Connection for Box<dyn Connection> {
fn execute(&mut self, sql: &str) -> Result<ExecutionSummary, SqlError> {
(**self).execute(sql)
}
fn query(&mut self, sql: &str) -> Result<QueryResult, SqlError> {
(**self).query(sql)
}
fn query_cursor(&mut self, sql: &str) -> Result<RowCursor<'_>, SqlError> {
(**self).query_cursor(sql)
}
fn size_guards(&self) -> SizeGuards {
(**self).size_guards()
}
fn set_size_guards(&mut self, guards: SizeGuards) {
(**self).set_size_guards(guards)
}
fn execute_multi(&mut self, sql: &str) -> Result<Vec<StatementResult>, SqlError> {
(**self).execute_multi(sql)
}
fn ping(&mut self) -> Result<(), SqlError> {
(**self).ping()
}
fn list_tables(&mut self, schema: Option<&str>) -> Result<Vec<String>, SqlError> {
(**self).list_tables(schema)
}
fn list_schemas(&mut self) -> Result<Vec<SchemaInfo>, SqlError> {
(**self).list_schemas()
}
fn describe_table(
&mut self,
schema: Option<&str>,
table: &str,
) -> Result<QueryResult, SqlError> {
(**self).describe_table(schema, table)
}
fn primary_key(&mut self, schema: Option<&str>, table: &str) -> Result<Vec<String>, SqlError> {
(**self).primary_key(schema, table)
}
fn list_foreign_keys(&mut self, schema: Option<&str>) -> Result<Vec<ForeignKey>, SqlError> {
(**self).list_foreign_keys(schema)
}
fn bulk_insert_rows(&mut self, target: BulkInsert<'_>) -> Result<usize, SqlError> {
(**self).bulk_insert_rows(target)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::url::DatabaseUrl;
use secrecy::ExposeSecret;
#[test]
fn effective_password_prefers_opts_over_url() {
let url = DatabaseUrl::parse("postgres://user:url_pw@localhost/db").unwrap();
let opts = ConnectOptions {
password: Some(SecretString::new("resolved_pw".into())),
..Default::default()
};
let got = opts.effective_password(&url).expect("a password");
assert_eq!(got.expose_secret(), "resolved_pw");
}
#[test]
fn effective_password_falls_back_to_url() {
let url = DatabaseUrl::parse("postgres://user:url_pw@localhost/db").unwrap();
let opts = ConnectOptions::default();
let got = opts.effective_password(&url).expect("a password");
assert_eq!(got.expose_secret(), "url_pw");
}
#[test]
fn effective_password_none_when_absent_everywhere() {
let url = DatabaseUrl::parse("postgres://user@localhost/db").unwrap();
let opts = ConnectOptions::default();
assert!(opts.effective_password(&url).is_none());
}
}