use core::marker::PhantomData;
use core::num::{
NonZeroI8,
NonZeroU8,
NonZeroI16,
NonZeroU16,
NonZeroI32,
NonZeroU32,
NonZeroI64,
NonZeroU64,
NonZeroIsize,
NonZeroUsize,
};
use core::fmt::{self, Display, Debug, Formatter, Write};
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::rc::Rc;
use std::sync::Arc;
use rusqlite::types::{Value, ValueRef, ToSqlOutput};
use crate::{
query::Query,
param::{Param, ParamPrefix},
row::{ResultRecord, ResultSet},
};
#[cfg(feature = "not-nan")]
use ordered_float::NotNan;
#[cfg(feature = "chrono")]
use chrono::{DateTime, Utc, FixedOffset, Local};
#[cfg(feature = "uuid")]
use uuid::Uuid;
#[cfg(feature = "json")]
use serde_json::Value as JsonValue;
pub trait Table {
type InsertInput<'p>: InsertInput<'p, Table = Self>;
type PrimaryKey<'p>;
fn description() -> TableDesc;
}
impl<'p, T: Table> Table for &'p T
where
T: Table<InsertInput<'p> = T>,
T::InsertInput<'p>: 'p,
T::InsertInput<'p>: InsertInput<'p, Table = Self>,
{
type InsertInput<'q> = &'p T::InsertInput<'p>;
type PrimaryKey<'q> = T::PrimaryKey<'q>;
fn description() -> TableDesc {
<T as Table>::description()
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum PrimaryKeyMissing {}
pub trait InsertInput<'p>: Param {
type Table: Table<InsertInput<'p> = Self>;
}
impl<'p, T> InsertInput<'p> for T
where
T: Param + Table<InsertInput<'p> = Self>
{
type Table = Self;
}
#[derive(Clone, Debug)]
pub struct TableDesc {
pub name: String,
pub columns: Vec<Column>,
pub constraints: BTreeSet<TableConstraint>,
pub indexes: Vec<TableIndexSpec>,
}
impl TableDesc {
pub fn new(name: impl Into<String>) -> Self {
TableDesc {
name: name.into(),
columns: Vec::new(),
constraints: BTreeSet::new(),
indexes: Vec::new(),
}
}
fn columns_for_insert(&self) -> impl Iterator<Item = &Column> {
self.columns.iter().filter(|column| !column.is_generated())
}
pub fn column(mut self, column: Column) -> Self {
self.columns.push(column);
self
}
pub fn constrain(mut self, constraint: TableConstraint) -> Self {
self.constraints.insert(constraint);
self
}
pub fn primary_key<I>(self, columns: I) -> Self
where
I: IntoIterator,
I::Item: Into<String>,
{
self.constrain(TableConstraint::PrimaryKey {
columns: columns.into_iter().map(Into::into).collect(),
})
}
pub fn foreign_key<T, I, K1, K2>(self, table: T, columns: I) -> Self
where
T: Into<String>,
I: IntoIterator<Item = (K1, K2)>,
K1: Into<String>,
K2: Into<String>,
{
let column_pairs = columns
.into_iter()
.map(|(own, foreign)| (own.into(), foreign.into()))
.collect();
self.constrain(TableConstraint::ForeignKey {
table: table.into(),
column_pairs,
})
}
pub fn unique<I>(self, columns: I) -> Self
where
I: IntoIterator,
I::Item: Into<String>,
{
self.constrain(TableConstraint::Unique {
columns: columns.into_iter().map(Into::into).collect(),
})
}
pub fn check(self, condition: impl Into<String>) -> Self {
self.constrain(TableConstraint::Check {
condition: condition.into(),
})
}
pub fn add_index<S1, S2, I>(mut self, unique: bool, columns: I, predicate: Option<S2>) -> Self
where
S1: Into<String>,
S2: Into<String>,
I: IntoIterator<Item = (S1, SortOrder)>,
{
let columns: Vec<_> = columns
.into_iter()
.map(|(col, order)| (col.into(), order))
.collect();
let index = TableIndexSpec {
table: self.name.clone(),
id: self.indexes.len() + 1,
unique,
columns,
predicate: predicate.map(Into::into),
};
self.indexes.push(index);
self
}
pub fn index_specs(&self) -> Vec<TableIndexSpec> {
let mut indexes = self.indexes.clone();
for constraint in &self.constraints {
let TableConstraint::ForeignKey { column_pairs, .. } = constraint else {
continue;
};
let id = indexes.len() + 1;
let columns = column_pairs
.iter()
.map(|(own_name, _)| (own_name.clone(), SortOrder::Ascending))
.collect();
indexes.push(TableIndexSpec {
table: self.name.clone(),
id,
unique: false,
columns,
predicate: None,
});
}
for column in &self.columns {
let Some(col_spec) = column.index_spec() else { continue };
let id = indexes.len() + 1;
let spec = TableIndexSpec::single_column(self.name.clone(), id, col_spec);
indexes.push(spec);
}
indexes
}
pub fn primary_key_columns(&self) -> &[String] {
for constr in &self.constraints {
if let TableConstraint::PrimaryKey { columns } = constr {
return columns;
}
}
for col in &self.columns {
for constr in &col.constraints {
if let ColumnConstraint::PrimaryKey = constr {
return std::slice::from_ref(&col.name);
}
}
}
&[]
}
}
#[derive(Clone, Debug)]
pub struct Column {
pub name: String,
pub ty: Option<SqlTy>,
pub constraints: BTreeSet<ColumnConstraint>,
pub index: Option<ColumnIndexSpec>,
}
impl Column {
pub fn new(name: impl Into<String>) -> Self {
Column {
name: name.into(),
ty: None,
constraints: BTreeSet::new(),
index: None,
}
}
pub fn ty(mut self, ty: impl Into<SqlTy>) -> Self {
self.ty = Some(ty.into());
self
}
pub fn constrain(mut self, constraint: ColumnConstraint) -> Self {
self.constraints.insert(constraint);
self
}
pub fn primary_key(self) -> Self {
self.constrain(ColumnConstraint::PrimaryKey)
}
pub fn foreign_key(self, table: impl Into<String>, column: impl Into<String>) -> Self {
self.constrain(ColumnConstraint::ForeignKey {
table: table.into(),
column: column.into(),
})
}
pub fn unique(self) -> Self {
self.constrain(ColumnConstraint::Unique)
}
pub fn default_value(self, expr: impl Into<String>) -> Self {
self.constrain(ColumnConstraint::Default {
expr: expr.into(),
})
}
pub fn generate_virtual(self, expr: impl Into<String>) -> Self {
self.constrain(ColumnConstraint::Generated {
expr: expr.into(),
kind: GeneratedColumnKind::Virtual,
})
}
pub fn generate_stored(self, expr: impl Into<String>) -> Self {
self.constrain(ColumnConstraint::Generated {
expr: expr.into(),
kind: GeneratedColumnKind::Stored,
})
}
pub fn check(self, condition: impl Into<String>) -> Self {
let condition = condition.into();
if condition.is_empty() {
self
} else {
self.constrain(ColumnConstraint::Check { condition })
}
}
pub fn set_index(
mut self,
unique: bool,
sort_order: SortOrder,
predicate: Option<impl Into<String>>,
) -> Self {
self.index = Some(ColumnIndexSpec {
name: self.name.clone(),
unique,
sort_order,
predicate: predicate.map(Into::into),
});
self
}
pub fn is_generated(&self) -> bool {
self.constraints.iter().any(|constraint| {
matches!(constraint, ColumnConstraint::Generated { .. })
})
}
pub fn index_spec(&self) -> Option<ColumnIndexSpec> {
if let Some(index) = self.index.as_ref() {
return Some(index.clone());
}
self.constraints
.iter()
.find_map(|c| {
let ColumnConstraint::ForeignKey { .. } = c else {
return None;
};
Some(ColumnIndexSpec {
name: self.name.clone(),
unique: false,
sort_order: SortOrder::Ascending,
predicate: None,
})
})
}
}
impl Display for Column {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(formatter, r#""{}""#, self.name)?;
if let Some(ty) = self.ty {
write!(formatter, " {ty}")?;
}
for c in &self.constraints {
write!(formatter, " {c}")?;
}
Ok(())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct SqlTy {
pub prim: TyPrim,
pub is_nullable: bool,
}
impl SqlTy {
pub const fn new(prim: TyPrim) -> Self {
SqlTy { prim, is_nullable: false }
}
pub const fn nullable(prim: TyPrim) -> Self {
SqlTy { prim, is_nullable: true }
}
pub const fn as_nullable(mut self) -> Self {
self.is_nullable = true;
self
}
}
impl From<TyPrim> for SqlTy {
fn from(prim: TyPrim) -> Self {
SqlTy::new(prim)
}
}
impl Display for SqlTy {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(
formatter,
"{} {}",
self.prim,
if self.is_nullable { "NULL" } else { "NOT NULL" },
)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum TyPrim {
Integer,
Real,
Text,
Blob,
}
impl Display for TyPrim {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str(match *self {
TyPrim::Integer => "INTEGER",
TyPrim::Real => "REAL",
TyPrim::Text => "TEXT",
TyPrim::Blob => "BLOB",
})
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum ColumnConstraint {
PrimaryKey,
ForeignKey {
table: String,
column: String,
},
Unique,
Default {
expr: String,
},
Generated {
expr: String,
kind: GeneratedColumnKind,
},
Check {
condition: String,
},
}
impl Display for ColumnConstraint {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
ColumnConstraint::PrimaryKey => {
formatter.write_str("PRIMARY KEY")
}
ColumnConstraint::ForeignKey { table, column } => {
write!(formatter, r#"REFERENCES "{table}"("{column}") DEFERRABLE INITIALLY DEFERRED"#)
}
ColumnConstraint::Unique => {
formatter.write_str("UNIQUE")
}
ColumnConstraint::Default { expr } => {
write!(formatter, "DEFAULT ({expr})")
}
ColumnConstraint::Generated { expr, kind } => {
write!(formatter, "GENERATED ALWAYS AS ({expr}) {kind}")
}
ColumnConstraint::Check { condition } => {
write!(formatter, "CHECK ({condition})")
}
}
}
}
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum GeneratedColumnKind {
#[default]
Virtual,
Stored,
}
impl Display for GeneratedColumnKind {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str(match *self {
GeneratedColumnKind::Virtual => "VIRTUAL",
GeneratedColumnKind::Stored => "STORED",
})
}
}
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum SortOrder {
#[default]
Ascending,
Descending,
}
impl Display for SortOrder {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str(match *self {
SortOrder::Ascending => "ASC",
SortOrder::Descending => "DESC",
})
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct ColumnIndexSpec {
pub name: String,
pub unique: bool,
pub sort_order: SortOrder,
pub predicate: Option<String>,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct TableIndexSpec {
pub table: String,
pub id: usize,
pub unique: bool,
pub columns: Vec<(String, SortOrder)>,
pub predicate: Option<String>,
}
impl TableIndexSpec {
pub fn single_column(table: String, id: usize, column: ColumnIndexSpec) -> Self {
let ColumnIndexSpec { name: col_name, unique, sort_order, predicate } = column;
TableIndexSpec {
table,
id,
unique,
columns: vec![(col_name, sort_order)],
predicate,
}
}
}
impl Query for TableIndexSpec {
type Input<'p> = ();
type Output = ();
fn format_sql(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
let &TableIndexSpec { ref table, id, unique, ref columns, ref predicate } = self;
let create_stmt = if unique { "CREATE UNIQUE INDEX" } else { "CREATE INDEX" };
let mut sep = "";
write!(
formatter,
r#"{create_stmt} IF NOT EXISTS "__nanosql_index_{table}_{id}" ON "{table}"("#,
)?;
for (col_name, sort_order) in columns {
write!(formatter, "{sep}\n \"{col_name}\" {sort_order}")?;
sep = ",";
}
formatter.write_str("\n)")?;
if let Some(predicate) = predicate.as_ref() {
write!(formatter, " WHERE (\n {predicate}\n)")?;
}
formatter.write_char(';')
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum TableConstraint {
PrimaryKey {
columns: Vec<String>,
},
ForeignKey {
table: String,
column_pairs: Vec<(String, String)>,
},
Unique {
columns: Vec<String>,
},
Check {
condition: String,
},
}
impl Display for TableConstraint {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
TableConstraint::PrimaryKey { columns } => {
formatter.write_str("PRIMARY KEY(")?;
let mut sep = "";
for col in columns {
write!(formatter, r#"{sep}"{col}""#)?;
sep = ", ";
}
formatter.write_char(')')
}
TableConstraint::ForeignKey { table, column_pairs } => {
write!(formatter, "FOREIGN KEY(")?;
let mut sep = "";
for (own_col, _) in column_pairs {
write!(formatter, r#"{sep}"{own_col}""#)?;
sep = ", ";
}
write!(formatter, r#") REFERENCES "{table}"("#)?;
sep = "";
for (_, foreign_col) in column_pairs {
write!(formatter, r#"{sep}"{foreign_col}""#)?;
sep = ", ";
}
formatter.write_str(") DEFERRABLE INITIALLY DEFERRED")
}
TableConstraint::Unique { columns } => {
formatter.write_str("UNIQUE(")?;
let mut sep = "";
for col in columns {
write!(formatter, r#"{sep}"{col}""#)?;
sep = ", ";
}
formatter.write_char(')')
}
TableConstraint::Check { condition } => {
write!(formatter, "CHECK ({condition})")
}
}
}
}
pub trait AsSqlTy {
const SQL_TY: SqlTy;
type Borrowed<'p>;
fn format_check_constraint(column: &dyn Display, formatter: &mut Formatter<'_>) -> fmt::Result {
let _ = column;
let _ = formatter;
Ok(())
}
}
macro_rules! impl_as_sql_ty_for_primitive {
($($rust_ty:ty as $borrowed_ty:ty => $sql_ty:expr,)*) => {$(
impl AsSqlTy for $rust_ty {
const SQL_TY: SqlTy = $sql_ty;
type Borrowed<'p> = $borrowed_ty;
}
)*}
}
impl_as_sql_ty_for_primitive!{
i8 as Self => SqlTy::new(TyPrim::Integer),
i16 as Self => SqlTy::new(TyPrim::Integer),
i32 as Self => SqlTy::new(TyPrim::Integer),
i64 as Self => SqlTy::new(TyPrim::Integer),
isize as Self => SqlTy::new(TyPrim::Integer),
u8 as Self => SqlTy::new(TyPrim::Integer),
u16 as Self => SqlTy::new(TyPrim::Integer),
u32 as Self => SqlTy::new(TyPrim::Integer),
u64 as Self => SqlTy::new(TyPrim::Integer),
usize as Self => SqlTy::new(TyPrim::Integer),
f32 as Self => SqlTy::nullable(TyPrim::Real),
f64 as Self => SqlTy::nullable(TyPrim::Real),
str as &'p str => SqlTy::new(TyPrim::Text),
String as &'p str => SqlTy::new(TyPrim::Text),
[u8] as &'p [u8] => SqlTy::new(TyPrim::Blob),
Vec<u8> as &'p [u8] => SqlTy::new(TyPrim::Blob),
Value as ValueRef<'p> => SqlTy::nullable(TyPrim::Blob),
ValueRef<'_> as ValueRef<'p> => SqlTy::nullable(TyPrim::Blob),
ToSqlOutput<'_> as ToSqlOutput<'p> => SqlTy::nullable(TyPrim::Blob),
}
#[cfg(feature = "chrono")]
impl_as_sql_ty_for_primitive!{
DateTime<Utc> as Self => SqlTy::new(TyPrim::Text),
DateTime<FixedOffset> as Self => SqlTy::new(TyPrim::Text),
DateTime<Local> as Self => SqlTy::new(TyPrim::Text),
}
#[cfg(feature = "uuid")]
impl_as_sql_ty_for_primitive!{
Uuid as Self => SqlTy::new(TyPrim::Blob),
}
#[cfg(feature = "json")]
impl_as_sql_ty_for_primitive!{
JsonValue as &'p JsonValue => SqlTy::nullable(TyPrim::Blob),
}
macro_rules! impl_as_sql_ty_for_non_zero {
($($ty:ty,)*) => {$(
impl AsSqlTy for $ty {
const SQL_TY: SqlTy = SqlTy::new(TyPrim::Integer);
type Borrowed<'p> = Self;
fn format_check_constraint(
column: &dyn Display,
formatter: &mut Formatter<'_>,
) -> fmt::Result {
write!(formatter, "{column} != 0")
}
}
)*}
}
impl_as_sql_ty_for_non_zero!{
NonZeroI8,
NonZeroU8,
NonZeroI16,
NonZeroU16,
NonZeroI32,
NonZeroU32,
NonZeroI64,
NonZeroU64,
NonZeroIsize,
NonZeroUsize,
}
impl AsSqlTy for bool {
const SQL_TY: SqlTy = SqlTy::new(TyPrim::Integer);
type Borrowed<'p> = Self;
fn format_check_constraint(column: &dyn Display, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(formatter, "{column} IN (0, 1)")
}
}
#[cfg(feature = "not-nan")]
impl AsSqlTy for NotNan<f32> {
const SQL_TY: SqlTy = SqlTy::new(TyPrim::Real);
type Borrowed<'p> = Self;
}
#[cfg(feature = "not-nan")]
impl AsSqlTy for NotNan<f64> {
const SQL_TY: SqlTy = SqlTy::new(TyPrim::Real);
type Borrowed<'p> = Self;
}
impl<const N: usize> AsSqlTy for [u8; N] {
const SQL_TY: SqlTy = SqlTy::new(TyPrim::Blob);
type Borrowed<'p> = &'p Self;
}
impl<T: AsSqlTy> AsSqlTy for Option<T> {
const SQL_TY: SqlTy = T::SQL_TY.as_nullable();
type Borrowed<'p> = Option<T::Borrowed<'p>>;
fn format_check_constraint(
column: &dyn Display,
formatter: &mut Formatter<'_>,
) -> fmt::Result {
T::format_check_constraint(column, formatter)
}
}
impl<T: ?Sized + AsSqlTy> AsSqlTy for &T {
const SQL_TY: SqlTy = T::SQL_TY;
type Borrowed<'p> = T::Borrowed<'p>;
fn format_check_constraint(
column: &dyn Display,
formatter: &mut Formatter<'_>,
) -> fmt::Result {
T::format_check_constraint(column, formatter)
}
}
impl<T: ?Sized + AsSqlTy> AsSqlTy for &mut T {
const SQL_TY: SqlTy = T::SQL_TY;
type Borrowed<'p> = T::Borrowed<'p>;
fn format_check_constraint(
column: &dyn Display,
formatter: &mut Formatter<'_>,
) -> fmt::Result {
T::format_check_constraint(column, formatter)
}
}
impl<T: ?Sized + AsSqlTy> AsSqlTy for Box<T> {
const SQL_TY: SqlTy = T::SQL_TY;
type Borrowed<'p> = T::Borrowed<'p>;
fn format_check_constraint(
column: &dyn Display,
formatter: &mut Formatter<'_>,
) -> fmt::Result {
T::format_check_constraint(column, formatter)
}
}
impl<T: ?Sized + AsSqlTy> AsSqlTy for Rc<T> {
const SQL_TY: SqlTy = T::SQL_TY;
type Borrowed<'p> = T::Borrowed<'p>;
fn format_check_constraint(
column: &dyn Display,
formatter: &mut Formatter<'_>,
) -> fmt::Result {
T::format_check_constraint(column, formatter)
}
}
impl<T: ?Sized + AsSqlTy> AsSqlTy for Arc<T> {
const SQL_TY: SqlTy = T::SQL_TY;
type Borrowed<'p> = T::Borrowed<'p>;
fn format_check_constraint(
column: &dyn Display,
formatter: &mut Formatter<'_>,
) -> fmt::Result {
T::format_check_constraint(column, formatter)
}
}
impl<T> AsSqlTy for Cow<'_, T>
where
T: ?Sized + ToOwned + AsSqlTy
{
const SQL_TY: SqlTy = T::SQL_TY;
type Borrowed<'p> = T::Borrowed<'p>;
fn format_check_constraint(
column: &dyn Display,
formatter: &mut Formatter<'_>,
) -> fmt::Result {
T::format_check_constraint(column, formatter)
}
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug)]
pub struct ColumnConstraintFormatter<'a, T: ?Sized> {
name: &'a str,
ty: PhantomData<fn() -> &'a T>,
}
impl<'a, T: ?Sized> ColumnConstraintFormatter<'a, T> {
#[doc(hidden)]
pub fn new(name: &'a str) -> Self {
ColumnConstraintFormatter {
name,
ty: PhantomData,
}
}
}
impl<T: ?Sized + AsSqlTy> Display for ColumnConstraintFormatter<'_, T> {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
T::format_check_constraint(&format_args!(r#""{}""#, self.name), formatter)
}
}
pub struct CreateTable<T>(PhantomData<fn() -> T>);
impl<T> Clone for CreateTable<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for CreateTable<T> {}
impl<T> Default for CreateTable<T> {
fn default() -> Self {
CreateTable(PhantomData)
}
}
impl<T: Table> Debug for CreateTable<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_tuple("CreateTable").field(&T::description().name).finish()
}
}
impl<T: Table> Query for CreateTable<T> {
type Input<'p> = ();
type Output = ();
fn format_sql(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
let desc = T::description();
let mut sep = "";
write!(formatter, r#"CREATE TABLE IF NOT EXISTS "{}"("#, desc.name)?;
for column in &desc.columns {
write!(formatter, "{sep}\n {column}")?;
sep = ", ";
}
for constraint in &desc.constraints {
write!(formatter, "{sep}\n {constraint}")?;
}
formatter.write_str("\n);")
}
}
pub struct Insert<T> {
behavior: ConflictResolution,
marker: PhantomData<fn() -> T>,
}
impl<T> Insert<T> {
pub const fn new() -> Self {
Self::with_behavior(ConflictResolution::Abort)
}
pub const fn with_behavior(behavior: ConflictResolution) -> Self {
Insert {
behavior,
marker: PhantomData,
}
}
pub const fn or_ignore() -> Self {
Self::with_behavior(ConflictResolution::Ignore)
}
pub const fn or_replace() -> Self {
Self::with_behavior(ConflictResolution::Replace)
}
}
impl<T> Clone for Insert<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Insert<T> {}
impl<T> Default for Insert<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Table> Debug for Insert<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Insert")
.field("table", &T::description().name)
.field("behavior", &self.behavior)
.finish()
}
}
impl<T> Query for Insert<T>
where
T: Table + ResultRecord
{
type Input<'p> = T::InsertInput<'p>;
type Output = Option<T>;
fn format_sql(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
let desc = T::description();
let pfx = <Self::Input<'static> as Param>::PREFIX;
let mut sep = "";
write!(formatter, r#"INSERT OR {} INTO "{}"("#, self.behavior, desc.name)?;
for col in desc.columns_for_insert() {
write!(formatter, "{sep}\n \"{col}\"", col = col.name)?;
sep = ", ";
}
formatter.write_str("\n)\nVALUES(")?;
sep = "";
for (idx, col) in (1_usize..).zip(desc.columns_for_insert()) {
let param_name: &dyn Display = match pfx {
ParamPrefix::Question => &idx,
ParamPrefix::Dollar | ParamPrefix::At | ParamPrefix::Colon => &col.name,
};
write!(formatter, "{sep}\n {pfx}{param_name}")?;
sep = ", ";
}
formatter.write_str("\n)\nRETURNING")?;
sep = "";
for col in desc.columns {
write!(formatter, "{sep}\n \"{col}\" AS '{col}'", col = col.name)?;
sep = ",";
}
formatter.write_char(';')
}
}
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
pub enum ConflictResolution {
Rollback,
#[default]
Abort,
Fail,
Ignore,
Replace,
}
impl Display for ConflictResolution {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
ConflictResolution::Rollback => "ROLLBACK",
ConflictResolution::Abort => "ABORT",
ConflictResolution::Fail => "FAIL",
ConflictResolution::Ignore => "IGNORE",
ConflictResolution::Replace => "REPLACE",
})
}
}
pub struct Select<T, C = Vec<T>> {
distinct: bool,
marker: PhantomData<fn() -> (T, C)>,
}
impl<T, C> Select<T, C> {
pub const fn all() -> Self {
Select {
distinct: false,
marker: PhantomData,
}
}
pub const fn distinct() -> Self {
Select {
distinct: true,
marker: PhantomData,
}
}
}
impl<T, C> Clone for Select<T, C> {
fn clone(&self) -> Self {
*self
}
}
impl<T, C> Copy for Select<T, C> {}
impl<T, C> Default for Select<T, C> {
fn default() -> Self {
Self::all()
}
}
impl<T: Table, C> Debug for Select<T, C> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Select")
.field("table", &T::description().name)
.field("distinct", &self.distinct)
.finish()
}
}
impl<T, C> Query for Select<T, C>
where
T: Table + ResultRecord,
C: FromIterator<T> + ResultSet,
{
type Input<'p> = ();
type Output = C;
fn format_sql(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
let desc = T::description();
let select_kw = if self.distinct { "SELECT DISTINCT" } else { "SELECT" };
let table_name = &desc.name;
let mut sep = "";
formatter.write_str(select_kw)?;
for col in &desc.columns {
let col_name = &col.name;
write!(formatter, "{sep}\n \"{table_name}\".\"{col_name}\" AS '{col_name}'")?;
sep = ",";
}
write!(formatter, "\nFROM \"{table_name}\";")
}
}
pub struct SelectByKey<T>(PhantomData<fn() -> T>);
impl<T> SelectByKey<T> {
pub const fn new() -> Self {
SelectByKey(PhantomData)
}
}
impl<T> Clone for SelectByKey<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for SelectByKey<T> {}
impl<T> Default for SelectByKey<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Table> Debug for SelectByKey<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_tuple("SelectByKey").field(&T::description().name).finish()
}
}
impl<T> Query for SelectByKey<T>
where
T: Table + ResultRecord,
for<'p> T::PrimaryKey<'p>: Param,
{
type Input<'p> = T::PrimaryKey<'p>;
type Output = Option<T>;
fn format_sql(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
let desc = T::description();
let table_name = &desc.name;
let pk_cols = desc.primary_key_columns();
let pfx = <Self::Input<'static> as Param>::PREFIX;
let mut sep = "";
formatter.write_str("SELECT")?;
for col in &desc.columns {
let col_name = &col.name;
write!(formatter, "{sep}\n \"{table_name}\".\"{col_name}\" AS '{col_name}'")?;
sep = ",";
}
write!(formatter, "\nFROM \"{table_name}\"\nWHERE (")?;
sep = "";
for col in pk_cols {
write!(formatter, r#"{sep}"{col}""#)?;
sep = ", ";
}
formatter.write_str(") = (")?;
sep = "";
for (idx, col) in (1_usize..).zip(pk_cols) {
let param_name: &dyn Display = match pfx {
ParamPrefix::Question => &idx,
ParamPrefix::Dollar | ParamPrefix::At | ParamPrefix::Colon => col,
};
write!(formatter, "{sep}{pfx}{param_name}")?;
sep = ", ";
}
formatter.write_str(");")
}
}
pub struct DeleteByKey<T>(PhantomData<fn() -> T>);
impl<T> DeleteByKey<T> {
pub const fn new() -> Self {
DeleteByKey(PhantomData)
}
}
impl<T> Clone for DeleteByKey<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for DeleteByKey<T> {}
impl<T> Default for DeleteByKey<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Table> Debug for DeleteByKey<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_tuple("DeleteByKey").field(&T::description().name).finish()
}
}
impl<T> Query for DeleteByKey<T>
where
T: Table + ResultRecord,
for<'p> T::PrimaryKey<'p>: Param,
{
type Input<'p> = T::PrimaryKey<'p>;
type Output = Option<T>;
fn format_sql(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
let desc = T::description();
let table_name = &desc.name;
let pk_cols = desc.primary_key_columns();
let pfx = <Self::Input<'static> as Param>::PREFIX;
let mut sep = "";
write!(formatter, "DELETE FROM \"{table_name}\"\nWHERE (")?;
for col in pk_cols {
write!(formatter, r#"{sep}"{col}""#)?;
sep = ", ";
}
formatter.write_str(") = (")?;
sep = "";
for (idx, col) in (1_usize..).zip(pk_cols) {
let param_name: &dyn Display = match pfx {
ParamPrefix::Question => &idx,
ParamPrefix::Dollar | ParamPrefix::At | ParamPrefix::Colon => col,
};
write!(formatter, "{sep}{pfx}{param_name}")?;
sep = ", ";
}
formatter.write_str(")\nRETURNING")?;
sep = "";
for col in desc.columns {
write!(formatter, "{sep}\n \"{col}\" AS '{col}'", col = col.name)?;
sep = ",";
}
formatter.write_char(';')
}
}