rainier_orm/executor.rs
1//! The [`Executor`] port — the one trait a backend implements to be usable.
2//!
3//! Everything above it (the repository, the schema builder, the derive) is
4//! backend-agnostic; everything below it is backend-specific. An executor
5//! reports its [`Dialect`] (so the layers above render the right SQL) and runs
6//! two primitives: read rows, or run a write and report its outcome. That is
7//! the entire surface a new backend must satisfy — sea-orm, D1-over-HTTP, and
8//! a D1 native binding each implement just this.
9
10use crate::{Dialect, Result, Row, ShardRoute};
11use sea_query::Value;
12
13/// The result of a write statement.
14#[derive(Debug, Clone, Copy, Default)]
15pub struct ExecOutcome {
16 /// Rows inserted/updated/deleted.
17 pub rows_affected: u64,
18 /// The autogenerated key from an `INSERT`, where the backend reports one
19 /// (`LAST_INSERT_ID()` / `last_insert_rowid()` / D1 `meta.last_row_id`).
20 /// `0` when there was none.
21 pub last_insert_id: i64,
22}
23
24/// A database backend. The single integration point: implement this and the
25/// generic CRUD layer works against your store unchanged.
26///
27/// `async fn` in trait (stable since Rust 1.75) keeps this `dyn`-free and
28/// wasm-safe; callers hold a concrete executor and monomorphise.
29///
30/// The trait deliberately does **not** require `Send + Sync`. A multi-threaded
31/// server's concrete executors (`SeaOrmExecutor`, `D1Executor<reqwest-transport>`)
32/// *are* `Send + Sync`, so state that holds them is too — the bound is provided
33/// by the concrete type, not demanded by the trait. Leaving it off lets a
34/// single-threaded Cloudflare Worker implement the trait over its `!Send`
35/// native D1 binding, so the exact same generic services run there.
36#[allow(async_fn_in_trait)]
37pub trait Executor {
38 /// The SQL dialect this backend speaks, so callers render matching SQL.
39 fn dialect(&self) -> Dialect;
40
41 /// The **shard family** this connector routes within, or `None` for a
42 /// single, unsharded database.
43 ///
44 /// This is the explicit "am I sharded, and as what?" marker: a `Some(name)`
45 /// connector uses sharded reasoning (ids are shard-encoded; operations
46 /// route within the named family), while `None` is a plain one-database
47 /// connector. Callers and migrations can branch on it without knowing the
48 /// concrete executor — and an entity's [`shard_columns`](crate::Entity::shard_columns) should match
49 /// the connector it runs on. Defaults to `None`.
50 fn shard_family(&self) -> Option<&str> {
51 None
52 }
53
54 /// `true` when this connector is sharded — sugar for
55 /// `shard_family().is_some()`.
56 fn is_sharded(&self) -> bool {
57 self.shard_family().is_some()
58 }
59
60 /// Mint a shard-encoded primary key for a row whose shard-key value is
61 /// `shard_key` (e.g. the owning `user_id`). `repo::insert` calls this when
62 /// an entity's primary key is itself a shard key, is *not* auto-increment,
63 /// and is unset — so id assignment for the sharded data tier happens **here,
64 /// in the connector**, transparently, not in the service. The default is
65 /// `None` (no allocation: the caller's id or the DB's auto-increment stands)
66 /// so single, unsharded executors are unaffected.
67 fn allocate_id(&self, _shard_key: u64) -> Option<u64> {
68 None
69 }
70
71 /// Run a query and return every row. Parameters are bound positionally in
72 /// the order sea-query emitted them.
73 async fn fetch_all(&self, sql: &str, params: Vec<Value>) -> Result<Vec<Box<dyn Row>>>;
74
75 /// Run a write and report its [`ExecOutcome`].
76 async fn execute(&self, sql: &str, params: Vec<Value>) -> Result<ExecOutcome>;
77
78 /// Run a query against the physical database the [`ShardRoute`] selects.
79 ///
80 /// **This is the sharding transparency seam.** The `repo`/`query` layer
81 /// always calls the routed variants; a single-database executor inherits
82 /// the default below — which *ignores* the route and runs on its one
83 /// connection — so it needs no change and sharding stays invisible above.
84 /// A sharding executor overrides this to dispatch to the right shard.
85 async fn fetch_all_routed(
86 &self,
87 _route: ShardRoute,
88 sql: &str,
89 params: Vec<Value>,
90 ) -> Result<Vec<Box<dyn Row>>> {
91 self.fetch_all(sql, params).await
92 }
93
94 /// Routed counterpart of [`execute`](Self::execute). Defaults to ignoring
95 /// the route — see [`fetch_all_routed`](Self::fetch_all_routed).
96 async fn execute_routed(
97 &self,
98 _route: ShardRoute,
99 sql: &str,
100 params: Vec<Value>,
101 ) -> Result<ExecOutcome> {
102 self.execute(sql, params).await
103 }
104}