use crate::alloc_prelude::*;
#[cfg(feature = "serde")]
use crate::serde_helpers::{cow_from_string, cow_option_from_string};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum GeneratedType {
#[default]
Stored,
Virtual,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct GeneratedDef {
pub expression: &'static str,
pub gen_type: GeneratedType,
}
impl GeneratedDef {
#[must_use]
pub const fn stored(expression: &'static str) -> Self {
Self {
expression,
gen_type: GeneratedType::Stored,
}
}
#[must_use]
pub const fn virtual_col(expression: &'static str) -> Self {
Self {
expression,
gen_type: GeneratedType::Virtual,
}
}
#[must_use]
pub const fn into_generated(self) -> Generated {
Generated {
expression: Cow::Borrowed(self.expression),
gen_type: self.gen_type,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Generated {
#[cfg_attr(
feature = "serde",
serde(rename = "as", deserialize_with = "cow_from_string")
)]
pub expression: Cow<'static, str>,
#[cfg_attr(feature = "serde", serde(rename = "type"))]
pub gen_type: GeneratedType,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PrimaryKeyKind {
Plain,
Autoincrement,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ColumnDef {
pub table: &'static str,
pub name: &'static str,
pub sql_type: &'static str,
pub not_null: bool,
pub primary_key: Option<PrimaryKeyKind>,
pub unique: bool,
pub default: Option<&'static str>,
pub generated: Option<GeneratedDef>,
pub collate: Option<&'static str>,
}
impl ColumnDef {
#[must_use]
pub const fn new(table: &'static str, name: &'static str, sql_type: &'static str) -> Self {
Self {
table,
name,
sql_type,
not_null: false,
primary_key: None,
unique: false,
default: None,
generated: None,
collate: None,
}
}
#[must_use]
pub const fn not_null(self) -> Self {
Self {
not_null: true,
..self
}
}
#[must_use]
pub const fn autoincrement(self) -> Self {
Self {
primary_key: Some(PrimaryKeyKind::Autoincrement),
not_null: true,
..self
}
}
#[must_use]
pub const fn primary_key(self) -> Self {
let primary_key = match self.primary_key {
Some(kind) => Some(kind),
None => Some(PrimaryKeyKind::Plain),
};
Self {
primary_key,
not_null: true,
..self
}
}
#[must_use]
pub const fn primary(self) -> Self {
self.primary_key()
}
#[must_use]
pub const fn unique(self) -> Self {
Self {
unique: true,
..self
}
}
#[must_use]
pub const fn default_value(self, value: &'static str) -> Self {
Self {
default: Some(value),
..self
}
}
#[must_use]
pub const fn generated_stored(self, expression: &'static str) -> Self {
Self {
generated: Some(GeneratedDef::stored(expression)),
..self
}
}
#[must_use]
pub const fn generated_virtual(self, expression: &'static str) -> Self {
Self {
generated: Some(GeneratedDef::virtual_col(expression)),
..self
}
}
#[must_use]
pub const fn collate(self, name: &'static str) -> Self {
Self {
collate: Some(name),
..self
}
}
#[must_use]
pub const fn into_column(self) -> Column {
Column {
table: Cow::Borrowed(self.table),
name: Cow::Borrowed(self.name),
sql_type: Cow::Borrowed(self.sql_type),
not_null: self.not_null,
autoincrement: match self.primary_key {
Some(PrimaryKeyKind::Autoincrement) => Some(true),
_ => None,
},
primary_key: if self.primary_key.is_some() {
Some(true)
} else {
None
},
unique: if self.unique { Some(true) } else { None },
default: match self.default {
Some(s) => Some(Cow::Borrowed(s)),
None => None,
},
generated: match self.generated {
Some(g) => Some(g.into_generated()),
None => None,
},
collate: match self.collate {
Some(s) => Some(Cow::Borrowed(s)),
None => None,
},
ordinal_position: None,
}
}
}
impl Default for ColumnDef {
fn default() -> Self {
Self::new("", "", "")
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Column {
#[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
pub table: Cow<'static, str>,
#[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
pub name: Cow<'static, str>,
#[cfg_attr(
feature = "serde",
serde(rename = "type", deserialize_with = "cow_from_string")
)]
pub sql_type: Cow<'static, str>,
#[cfg_attr(feature = "serde", serde(default))]
pub not_null: bool,
#[cfg_attr(feature = "serde", serde(default))]
pub autoincrement: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub primary_key: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub unique: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(default, deserialize_with = "cow_option_from_string")
)]
pub default: Option<Cow<'static, str>>,
#[cfg_attr(feature = "serde", serde(default))]
pub generated: Option<Generated>,
#[cfg_attr(
feature = "serde",
serde(default, deserialize_with = "cow_option_from_string")
)]
pub collate: Option<Cow<'static, str>>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub ordinal_position: Option<i32>,
}
impl Column {
#[must_use]
pub fn new(
table: impl Into<Cow<'static, str>>,
name: impl Into<Cow<'static, str>>,
sql_type: impl Into<Cow<'static, str>>,
) -> Self {
Self {
table: table.into(),
name: name.into(),
sql_type: sql_type.into(),
not_null: false,
autoincrement: None,
primary_key: None,
unique: None,
default: None,
generated: None,
collate: None,
ordinal_position: None,
}
}
#[must_use]
pub const fn not_null(mut self) -> Self {
self.not_null = true;
self
}
#[must_use]
pub const fn autoincrement(mut self) -> Self {
self.autoincrement = Some(true);
self
}
#[must_use]
pub fn default_value(mut self, value: impl Into<Cow<'static, str>>) -> Self {
self.default = Some(value.into());
self
}
#[inline]
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
#[must_use]
pub fn table(&self) -> &str {
&self.table
}
#[inline]
#[must_use]
pub fn sql_type(&self) -> &str {
&self.sql_type
}
#[inline]
#[must_use]
pub const fn is_primary_key(&self) -> bool {
matches!(self.primary_key, Some(true))
}
#[inline]
#[must_use]
pub const fn is_autoincrement(&self) -> bool {
matches!(self.autoincrement, Some(true))
}
#[inline]
#[must_use]
pub const fn is_unique(&self) -> bool {
matches!(self.unique, Some(true))
}
}
impl Default for Column {
fn default() -> Self {
Self::new("", "", "")
}
}
impl From<ColumnDef> for Column {
fn from(def: ColumnDef) -> Self {
let mut col = def.into_column();
if let Some(generated_def) = def.generated {
col.generated = Some(generated_def.into_generated());
}
col
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_const_column_def() {
const COL_DEF: ColumnDef = ColumnDef::new("users", "id", "INTEGER")
.primary_key()
.autoincrement();
assert_eq!(COL_DEF.name, "id");
assert_eq!(COL_DEF.table, "users");
assert_eq!(COL_DEF.sql_type, "INTEGER");
const {
assert!(COL_DEF.not_null);
}
const {
assert!(COL_DEF.primary_key.is_some());
}
const {
assert!(matches!(
COL_DEF.primary_key,
Some(PrimaryKeyKind::Autoincrement)
));
}
let col: Column = COL_DEF.into_column();
assert_eq!(col.name, Cow::Borrowed("id"));
assert_eq!(col.table, Cow::Borrowed("users"));
assert_eq!(col.sql_type, Cow::Borrowed("INTEGER"));
assert!(col.not_null);
}
#[test]
fn test_const_columns_array() {
const COLUMNS: &[ColumnDef] = &[
ColumnDef::new("users", "id", "INTEGER")
.primary_key()
.autoincrement(),
ColumnDef::new("users", "name", "TEXT").not_null(),
ColumnDef::new("users", "email", "TEXT"),
];
assert_eq!(COLUMNS.len(), 3);
assert_eq!(COLUMNS[0].name, "id");
assert_eq!(COLUMNS[1].name, "name");
assert_eq!(COLUMNS[2].name, "email");
assert!(COLUMNS[1].not_null);
assert!(!COLUMNS[2].not_null);
}
#[test]
fn test_generated_column() {
const GEN_COL: ColumnDef = ColumnDef::new("users", "full_name", "TEXT")
.generated_stored("first_name || ' ' || last_name");
assert!(GEN_COL.generated.is_some());
assert_eq!(GEN_COL.generated.unwrap().gen_type, GeneratedType::Stored);
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_roundtrip() {
let col = Column::new("users", "id", "INTEGER");
let json = serde_json::to_string(&col).unwrap();
let parsed: Column = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.name(), "id");
}
}