#[derive(Debug, Clone)]
pub struct ColumnBuilder {
name: String,
type_sql: String,
nullable: bool,
default: Option<String>,
unique: bool,
primary_key: bool,
references: Option<(String, String)>, }
impl ColumnBuilder {
pub(crate) fn new(name: impl Into<String>, type_sql: impl Into<String>) -> Self {
Self {
name: name.into(),
type_sql: type_sql.into(),
nullable: true,
default: None,
unique: false,
primary_key: false,
references: None,
}
}
pub fn not_null(&mut self) -> &mut Self {
self.nullable = false;
self
}
pub fn nullable(&mut self) -> &mut Self {
self.nullable = true;
self
}
pub fn default(&mut self, expr: impl Into<String>) -> &mut Self {
self.default = Some(expr.into());
self
}
pub fn unique(&mut self) -> &mut Self {
self.unique = true;
self
}
pub fn primary_key(&mut self) -> &mut Self {
self.primary_key = true;
self
}
pub fn references(&mut self, table: impl Into<String>, column: impl Into<String>) -> &mut Self {
self.references = Some((table.into(), column.into()));
self
}
pub(crate) fn to_sql(&self) -> String {
let mut parts = vec![format!("\"{}\" {}", self.name, self.type_sql)];
if self.primary_key {
parts.push("PRIMARY KEY".to_string());
}
if !self.nullable {
parts.push("NOT NULL".to_string());
}
if self.unique {
parts.push("UNIQUE".to_string());
}
if let Some(ref def) = self.default {
parts.push(format!("DEFAULT {def}"));
}
if let Some((ref tbl, ref col)) = self.references {
parts.push(format!("REFERENCES \"{tbl}\"(\"{col}\")"));
}
parts.join(" ")
}
}
#[derive(Debug, Default)]
pub struct TableBuilder {
pub(crate) columns: Vec<ColumnBuilder>,
pub(crate) indices: Vec<String>,
}
impl TableBuilder {
pub(crate) fn new() -> Self {
Self::default()
}
pub fn id(&mut self) -> &mut ColumnBuilder {
let mut col = ColumnBuilder::new("id", "BIGSERIAL");
col.primary_key();
col.not_null();
self.columns.push(col);
self.columns.last_mut().unwrap()
}
pub fn uuid_id(&mut self) -> &mut ColumnBuilder {
let mut col = ColumnBuilder::new("id", "UUID");
col.primary_key();
col.not_null();
col.default("gen_random_uuid()");
self.columns.push(col);
self.columns.last_mut().unwrap()
}
pub fn ulid_pk(&mut self) -> &mut ColumnBuilder {
let mut col = ColumnBuilder::new("id", "TEXT");
col.primary_key();
col.not_null();
self.columns.push(col);
self.columns.last_mut().unwrap()
}
pub fn cuid2_pk(&mut self) -> &mut ColumnBuilder {
let mut col = ColumnBuilder::new("id", "TEXT");
col.primary_key();
col.not_null();
self.columns.push(col);
self.columns.last_mut().unwrap()
}
pub fn snowflake_pk(&mut self) -> &mut ColumnBuilder {
let mut col = ColumnBuilder::new("id", "BIGINT");
col.primary_key();
col.not_null();
self.columns.push(col);
self.columns.last_mut().unwrap()
}
pub fn uuid_v7_pk(&mut self) -> &mut ColumnBuilder {
let mut col = ColumnBuilder::new("id", "VARCHAR(36)");
col.primary_key();
col.not_null();
self.columns.push(col);
self.columns.last_mut().unwrap()
}
pub fn nanoid_pk(&mut self) -> &mut ColumnBuilder {
let mut col = ColumnBuilder::new("id", "TEXT");
col.primary_key();
col.not_null();
self.columns.push(col);
self.columns.last_mut().unwrap()
}
pub fn string(&mut self, name: impl Into<String>) -> &mut ColumnBuilder {
self.columns.push(ColumnBuilder::new(name, "TEXT"));
self.columns.last_mut().unwrap()
}
pub fn varchar(&mut self, name: impl Into<String>, len: u32) -> &mut ColumnBuilder {
self.columns
.push(ColumnBuilder::new(name, format!("VARCHAR({len})")));
self.columns.last_mut().unwrap()
}
pub fn big_integer(&mut self, name: impl Into<String>) -> &mut ColumnBuilder {
self.columns.push(ColumnBuilder::new(name, "BIGINT"));
self.columns.last_mut().unwrap()
}
pub fn integer(&mut self, name: impl Into<String>) -> &mut ColumnBuilder {
self.columns.push(ColumnBuilder::new(name, "INTEGER"));
self.columns.last_mut().unwrap()
}
pub fn small_integer(&mut self, name: impl Into<String>) -> &mut ColumnBuilder {
self.columns.push(ColumnBuilder::new(name, "SMALLINT"));
self.columns.last_mut().unwrap()
}
pub fn boolean(&mut self, name: impl Into<String>) -> &mut ColumnBuilder {
self.columns.push(ColumnBuilder::new(name, "BOOLEAN"));
self.columns.last_mut().unwrap()
}
pub fn double(&mut self, name: impl Into<String>) -> &mut ColumnBuilder {
self.columns
.push(ColumnBuilder::new(name, "DOUBLE PRECISION"));
self.columns.last_mut().unwrap()
}
pub fn decimal(
&mut self,
name: impl Into<String>,
precision: u8,
scale: u8,
) -> &mut ColumnBuilder {
self.columns.push(ColumnBuilder::new(
name,
format!("DECIMAL({precision}, {scale})"),
));
self.columns.last_mut().unwrap()
}
pub fn json(&mut self, name: impl Into<String>) -> &mut ColumnBuilder {
self.columns.push(ColumnBuilder::new(name, "JSONB"));
self.columns.last_mut().unwrap()
}
pub fn timestamp(&mut self, name: impl Into<String>) -> &mut ColumnBuilder {
self.columns.push(ColumnBuilder::new(name, "TIMESTAMPTZ"));
self.columns.last_mut().unwrap()
}
pub fn date(&mut self, name: impl Into<String>) -> &mut ColumnBuilder {
self.columns.push(ColumnBuilder::new(name, "DATE"));
self.columns.last_mut().unwrap()
}
pub fn binary(&mut self, name: impl Into<String>) -> &mut ColumnBuilder {
self.columns.push(ColumnBuilder::new(name, "BYTEA"));
self.columns.last_mut().unwrap()
}
pub fn enum_col(&mut self, name: impl Into<String>, enum_name: &str) -> &mut ColumnBuilder {
self.columns
.push(ColumnBuilder::new(name, enum_name.to_string()));
self.columns.last_mut().unwrap()
}
pub fn column(
&mut self,
name: impl Into<String>,
type_sql: impl Into<String>,
) -> &mut ColumnBuilder {
self.columns.push(ColumnBuilder::new(name, type_sql));
self.columns.last_mut().unwrap()
}
pub fn money(&mut self, name: impl Into<String>) -> &mut ColumnBuilder {
self.columns
.push(ColumnBuilder::new(name, "NUMERIC(19, 4)"));
self.columns.last_mut().unwrap()
}
pub fn ip_address(&mut self, name: impl Into<String>) -> &mut ColumnBuilder {
self.columns.push(ColumnBuilder::new(name, "INET"));
self.columns.last_mut().unwrap()
}
pub fn mac_address(&mut self, name: impl Into<String>) -> &mut ColumnBuilder {
self.columns.push(ColumnBuilder::new(name, "MACADDR"));
self.columns.last_mut().unwrap()
}
pub fn encrypted(&mut self, name: impl Into<String>) -> &mut ColumnBuilder {
self.columns.push(ColumnBuilder::new(name, "BYTEA"));
self.columns.last_mut().unwrap()
}
pub fn vector(&mut self, name: impl Into<String>, dims: u32) -> &mut ColumnBuilder {
self.columns
.push(ColumnBuilder::new(name, format!("vector({dims})")));
self.columns.last_mut().unwrap()
}
pub fn foreign_id(
&mut self,
name: impl Into<String>,
references_table: impl Into<String>,
) -> &mut ColumnBuilder {
let table = references_table.into();
let mut col = ColumnBuilder::new(name, "BIGINT");
col.not_null();
col.references(&table, "id");
self.columns.push(col);
self.columns.last_mut().unwrap()
}
pub fn timestamps(&mut self) {
let mut c = ColumnBuilder::new("created_at", "TIMESTAMPTZ");
c.not_null();
c.default("NOW()");
self.columns.push(c);
let mut u = ColumnBuilder::new("updated_at", "TIMESTAMPTZ");
u.not_null();
u.default("NOW()");
self.columns.push(u);
}
pub fn soft_deletes(&mut self) {
self.columns
.push(ColumnBuilder::new("deleted_at", "TIMESTAMPTZ"));
}
pub fn index(&mut self, index_sql: impl Into<String>) {
self.indices.push(index_sql.into());
}
}
pub struct AlterColumnBuilder {
pub(crate) type_sql: Option<String>,
pub(crate) nullable: Option<bool>,
}
impl AlterColumnBuilder {
pub fn text(&mut self) -> &mut Self {
self.type_sql = Some("TEXT".into());
self
}
pub fn string(&mut self) -> &mut Self {
self.type_sql = Some("TEXT".into());
self
}
pub fn big_integer(&mut self) -> &mut Self {
self.type_sql = Some("BIGINT".into());
self
}
pub fn integer(&mut self) -> &mut Self {
self.type_sql = Some("INTEGER".into());
self
}
pub fn small_integer(&mut self) -> &mut Self {
self.type_sql = Some("SMALLINT".into());
self
}
pub fn boolean(&mut self) -> &mut Self {
self.type_sql = Some("BOOLEAN".into());
self
}
pub fn double(&mut self) -> &mut Self {
self.type_sql = Some("DOUBLE PRECISION".into());
self
}
pub fn json(&mut self) -> &mut Self {
self.type_sql = Some("JSONB".into());
self
}
pub fn timestamp(&mut self) -> &mut Self {
self.type_sql = Some("TIMESTAMPTZ".into());
self
}
pub fn uuid(&mut self) -> &mut Self {
self.type_sql = Some("UUID".into());
self
}
pub fn decimal(&mut self, p: u8, s: u8) -> &mut Self {
self.type_sql = Some(format!("DECIMAL({p}, {s})"));
self
}
pub fn type_sql(&mut self, sql: impl Into<String>) -> &mut Self {
self.type_sql = Some(sql.into());
self
}
pub fn nullable(&mut self) -> &mut Self {
self.nullable = Some(true);
self
}
pub fn not_null(&mut self) -> &mut Self {
self.nullable = Some(false);
self
}
}