Skip to main content

rok_orm_core/
model.rs

1//! [`Model`] trait — implemented automatically by `#[derive(Model)]`.
2
3use crate::query::QueryBuilder;
4
5/// The core ORM trait.  Implemented via `#[derive(Model)]` from `rok-orm`.
6///
7/// ```rust,ignore
8/// use rok_orm::Model;
9///
10/// #[derive(Model)]
11/// pub struct Post {
12///     pub id: i64,
13///     pub title: String,
14///     pub body: String,
15/// }
16///
17/// let q = Post::query()
18///     .where_eq("title", "Hello")
19///     .order_by_desc("id")
20///     .limit(10);
21///
22/// let (sql, params) = q.to_sql();
23/// assert!(sql.starts_with("SELECT * FROM posts"));
24/// ```
25pub trait Model: Sized {
26    /// SQL table name (e.g. `"users"`).
27    fn table_name() -> &'static str;
28
29    /// Primary-key column.  Defaults to `"id"`.
30    fn primary_key() -> &'static str {
31        "id"
32    }
33
34    /// All column names in declaration order.
35    fn columns() -> &'static [&'static str];
36
37    /// The soft-delete column name, if this model uses soft deletes.
38    ///
39    /// When `Some("deleted_at")`, fluent queries automatically append
40    /// `WHERE deleted_at IS NULL` and delete operations update the timestamp
41    /// instead of issuing a `DELETE FROM`.
42    fn soft_delete_column() -> Option<&'static str> {
43        None
44    }
45
46    /// Auto-timestamp column names `(created_at, updated_at)`, if configured.
47    ///
48    /// Used for introspection; the DB `DEFAULT NOW()` handles the actual value.
49    fn timestamp_columns() -> Option<(&'static str, &'static str)> {
50        None
51    }
52
53    /// The tenant column for row-level multi-tenancy, if this model is tenant-scoped.
54    ///
55    /// Set via `#[rok_orm(tenant_column = "tenant_id")]` on the struct.
56    /// When set and a tenant is active (via [`TenantLayer`]), all queries produced by
57    /// [`query()`] automatically include `WHERE tenant_id = $current_tenant`.
58    ///
59    /// [`TenantLayer`]: crate::tenant::TenantLayer
60    /// [`query()`]: Model::query
61    fn tenant_column() -> Option<&'static str> {
62        None
63    }
64
65    /// Start a new [`QueryBuilder`] scoped to this model.
66    ///
67    /// If this model has a [`tenant_column`] and the `tenant` feature is enabled,
68    /// the current request's tenant ID (from [`TenantLayer`]) is automatically
69    /// injected as the first WHERE condition.
70    ///
71    /// [`tenant_column`]: Model::tenant_column
72    /// [`TenantLayer`]: crate::tenant::TenantLayer
73    fn query() -> QueryBuilder<Self> {
74        let q = QueryBuilder::new(Self::table_name());
75        #[cfg(feature = "tenant")]
76        if let Some(col) = Self::tenant_column() {
77            if let Some(tid) = crate::tenant::current_tenant_id() {
78                return q.where_eq(col, tid);
79            }
80        }
81        q
82    }
83
84    /// Start a new [`QueryBuilder`] that **bypasses** the tenant scope.
85    ///
86    /// Use for admin/cross-tenant queries where the tenant filter should not apply.
87    fn without_tenant_scope() -> QueryBuilder<Self> {
88        QueryBuilder::new(Self::table_name())
89    }
90
91    /// The primary key value of this model instance as a [`SqlValue`].
92    ///
93    /// Generated automatically by `#[derive(Model)]`.  The default panics to
94    /// catch models that weren't generated via the derive macro.
95    fn pk_value(&self) -> crate::condition::SqlValue {
96        panic!(
97            "`pk_value` not implemented for `{}` — use `#[derive(Model)]` to generate it",
98            std::any::type_name::<Self>()
99        )
100    }
101
102    /// Build a `SELECT … WHERE <pk> = $1` query.
103    fn find(id: impl Into<crate::condition::SqlValue>) -> QueryBuilder<Self> {
104        Self::query().where_eq(Self::primary_key(), id)
105    }
106
107    /// KNN similarity search: `ORDER BY {col} <-> embedding LIMIT k`.
108    ///
109    /// Uses the `"embedding"` column by default.  For a different column use
110    /// `Self::query().nearest_to("other_col", embedding, k)`.
111    fn nearest_to(embedding: &[f32], k: usize) -> QueryBuilder<Self> {
112        Self::query().nearest_to("embedding", embedding, k)
113    }
114
115    /// Cosine-distance filter: `WHERE {col} <=> embedding {op} {threshold}`.
116    fn where_cosine_distance(
117        col: &str,
118        embedding: &[f32],
119        op: &str,
120        threshold: f64,
121    ) -> QueryBuilder<Self> {
122        Self::query().where_cosine_distance(col, embedding, op, threshold)
123    }
124}