pub trait HydrateRelated {
fn fk_id_for(&self, field_name: &str) -> Option<serde_json::Value>;
fn hydrate_fk(&mut self, field_name: &str, row: &serde_json::Value);
fn set_m2m_parent_ids(&mut self) {}
fn pk_as_json(&self) -> Option<serde_json::Value> {
None
}
fn set_m2m_resolved_json(&mut self, _field_name: &str, _rows: Vec<serde_json::Value>) {}
fn set_reverse_fk_resolved_json(&mut self, _field_name: &str, _rows: Vec<serde_json::Value>) {}
fn set_one_to_one_resolved_json(&mut self, _field_name: &str, _row: Option<serde_json::Value>) {
}
fn take_pending_m2m_into(&mut self, _dest: &mut Self) {}
fn write_pending_m2m<'a>(
&'a mut self,
) -> std::pin::Pin<
Box<
dyn std::future::Future<Output = Result<(), crate::orm::write::WriteError>> + Send + 'a,
>,
> {
Box::pin(async { Ok(()) })
}
}
pub trait Model: Sized + Send + Sync + Unpin + 'static {
type PrimaryKey: PrimaryKey;
const NAME: &'static str;
const TABLE: &'static str;
fn table_name() -> &'static str {
Self::TABLE
}
const APP_LABEL: &'static str = "app";
const FIELDS: &'static [FieldSpec];
const DISPLAY: &'static str = Self::NAME;
const ICON: &'static str = "database";
const DATABASE: Option<&'static str> = None;
const SINGLETON: bool = false;
const SOFT_DELETE: bool = false;
const AUDITED: bool = false;
const VIEW: Option<&'static str> = None;
const MATERIALIZED: bool = false;
const UNIQUE_TOGETHER: &'static [&'static [&'static str]] = &[];
const INDEXES: &'static [&'static [&'static str]] = &[];
const ORDERING: &'static [(&'static str, bool)] = &[];
const SIGNAL_SKIP_FIELDS: &'static [&'static str] = &[];
const M2M_RELATIONS: &'static [M2MRelationSpec] = &[];
const REVERSE_FK_RELATIONS: &'static [ReverseFkRelationSpec] = &[];
const ONE_TO_ONE_RELATIONS: &'static [OneToOneRelationSpec] = &[];
fn primary_key(&self) -> Self::PrimaryKey;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct M2MRelationSpec {
pub field_name: &'static str,
pub target_table: &'static str,
pub target_name: &'static str,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OneToOneRelationSpec {
pub field_name: &'static str,
pub target_table: &'static str,
pub target_name: &'static str,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReverseFkRelationSpec {
pub field_name: &'static str,
pub target_table: &'static str,
pub target_name: &'static str,
pub fk_column: &'static str,
pub soft_delete: bool,
}
pub trait PrimaryKey:
Clone + Send + Sync + 'static + Into<sea_query::Value> + std::fmt::Display
{
}
impl PrimaryKey for i8 {}
impl PrimaryKey for i16 {}
impl PrimaryKey for i32 {}
impl PrimaryKey for i64 {}
impl PrimaryKey for u8 {}
impl PrimaryKey for u16 {}
impl PrimaryKey for u32 {}
impl PrimaryKey for u64 {}
impl PrimaryKey for uuid::Uuid {}
impl PrimaryKey for String {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FieldSpec {
pub name: &'static str,
pub ty: SqlType,
pub primary_key: bool,
pub nullable: bool,
pub supported_backends: &'static [&'static str],
pub fk_target: Option<&'static str>,
pub noform: bool,
pub privileged: bool,
pub private: bool,
pub secret: bool,
pub db_constraint: bool,
pub noedit: bool,
pub is_string_repr: bool,
pub max_length: u32,
pub choices: &'static [&'static str],
pub choice_labels: &'static [&'static str],
pub default: &'static str,
pub is_multichoice: bool,
pub unique: bool,
pub on_delete: FkAction,
pub on_update: FkAction,
pub index: bool,
pub auto_now_add: bool,
pub auto_user_add: bool,
pub auto_user: bool,
pub auto_now: bool,
pub auto_uuid: bool,
pub trim: bool,
pub lowercase: bool,
pub case_insensitive: bool,
pub help: &'static str,
pub widget: Option<&'static str>,
pub example: &'static str,
pub min: Option<i64>,
pub max: Option<i64>,
pub text_format: Option<&'static str>,
pub slug_from: Option<&'static str>,
}
impl FieldSpec {
pub const PLACEHOLDER: FieldSpec = FieldSpec {
name: "",
ty: SqlType::Integer,
primary_key: false,
nullable: false,
supported_backends: &[],
fk_target: None,
noform: false,
privileged: false,
private: false,
secret: false,
db_constraint: true,
noedit: false,
is_string_repr: false,
max_length: 0,
choices: &[],
choice_labels: &[],
default: "",
is_multichoice: false,
unique: false,
on_delete: FkAction::NoAction,
on_update: FkAction::NoAction,
index: false,
auto_now_add: false,
auto_user_add: false,
auto_user: false,
auto_now: false,
auto_uuid: false,
trim: false,
lowercase: false,
case_insensitive: false,
help: "",
widget: None,
example: "",
min: None,
max: None,
text_format: None,
slug_from: None,
};
}
pub const fn concat_field_specs<const N: usize>(parts: &[&[FieldSpec]]) -> [FieldSpec; N] {
let mut out = [FieldSpec::PLACEHOLDER; N];
let mut oi = 0;
let mut pi = 0;
while pi < parts.len() {
let part = parts[pi];
let mut i = 0;
while i < part.len() {
out[oi] = part[i];
oi += 1;
i += 1;
}
pi += 1;
}
out
}
pub trait ModelBase {
const BASE_FIELDS: &'static [FieldSpec];
const BASE_PK: Option<&'static str>;
type BasePrimaryKey: PrimaryKey;
fn base_primary_key(&self) -> Self::BasePrimaryKey;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub enum FkAction {
#[default]
NoAction,
Cascade,
Restrict,
SetNull,
}
impl FkAction {
pub fn sql_keyword(self) -> Option<&'static str> {
match self {
Self::NoAction => None,
Self::Cascade => Some("CASCADE"),
Self::Restrict => Some("RESTRICT"),
Self::SetNull => Some("SET NULL"),
}
}
pub fn from_attr_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"no_action" | "no action" => Some(Self::NoAction),
"cascade" => Some(Self::Cascade),
"restrict" => Some(Self::Restrict),
"set_null" | "set null" => Some(Self::SetNull),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum SqlType {
ForeignKey,
SmallInt,
Integer,
BigInt,
Real,
Double,
Boolean,
Text,
Date,
Time,
Timestamptz,
Timestamp,
Uuid,
Json,
Array(ArrayElement),
Inet,
Cidr,
MacAddr,
Xml,
Ltree,
Bit,
FullText,
Bytes,
Decimal,
BigDecimal,
DecimalN(DecimalSpec),
Geometry(GeometrySpec),
Geography(GeometrySpec),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DecimalSpec {
pub precision: u16,
pub scale: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct GeometrySpec {
pub kind: GeometryKind,
pub srid: i32,
}
impl GeometrySpec {
pub const DEFAULT: GeometrySpec = GeometrySpec {
kind: GeometryKind::Geometry,
srid: 4326,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum GeometryKind {
Geometry,
Point,
LineString,
Polygon,
MultiPoint,
MultiLineString,
MultiPolygon,
GeometryCollection,
}
impl GeometryKind {
pub const fn pg_modifier(self) -> &'static str {
match self {
GeometryKind::Geometry => "Geometry",
GeometryKind::Point => "Point",
GeometryKind::LineString => "LineString",
GeometryKind::Polygon => "Polygon",
GeometryKind::MultiPoint => "MultiPoint",
GeometryKind::MultiLineString => "MultiLineString",
GeometryKind::MultiPolygon => "MultiPolygon",
GeometryKind::GeometryCollection => "GeometryCollection",
}
}
pub fn from_attr(s: &str) -> Option<GeometryKind> {
Some(match s.to_ascii_lowercase().as_str() {
"geometry" | "" => GeometryKind::Geometry,
"point" => GeometryKind::Point,
"linestring" => GeometryKind::LineString,
"polygon" => GeometryKind::Polygon,
"multipoint" => GeometryKind::MultiPoint,
"multilinestring" => GeometryKind::MultiLineString,
"multipolygon" => GeometryKind::MultiPolygon,
"geometrycollection" => GeometryKind::GeometryCollection,
_ => return None,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ArrayElement {
SmallInt,
Integer,
BigInt,
Real,
Double,
Boolean,
Text,
Uuid,
}
impl ArrayElement {
pub fn to_sql_type(self) -> SqlType {
match self {
ArrayElement::SmallInt => SqlType::SmallInt,
ArrayElement::Integer => SqlType::Integer,
ArrayElement::BigInt => SqlType::BigInt,
ArrayElement::Real => SqlType::Real,
ArrayElement::Double => SqlType::Double,
ArrayElement::Boolean => SqlType::Boolean,
ArrayElement::Text => SqlType::Text,
ArrayElement::Uuid => SqlType::Uuid,
}
}
}