Skip to main content

dactyl_db/
contract.rs

1//! Backend-neutral physical operations.
2//!
3//! These types deliberately stop at the storage boundary. Callers own schema
4//! policy, migration ordering, retry policy, and domain meaning; Dactyl owns
5//! only execution, atomicity, access mode, and result normalization.
6
7use std::time::Duration;
8
9use serde::{Deserialize, Serialize};
10
11use crate::rows::{Parameter, Rows};
12
13/// Whether an opened route may mutate durable state.
14#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub enum AccessMode {
17    #[default]
18    ReadWrite,
19    ReadOnly,
20}
21
22/// The physical kind of an operation. Schema operations are caller-supplied
23/// and are not migrations: Dactyl never assigns ids or ordering to them.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "snake_case")]
26pub enum OperationKind {
27    Read,
28    Write,
29    Schema,
30}
31
32/// An opaque, backend-neutral operation accepted by [`crate::Connection`].
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub struct Operation {
35    pub(crate) kind: OperationKind,
36    pub(crate) sql: String,
37    #[serde(default)]
38    pub(crate) params: Vec<Parameter>,
39}
40
41impl Operation {
42    pub fn read(sql: impl Into<String>, params: impl Into<Vec<Parameter>>) -> Self {
43        Self {
44            kind: OperationKind::Read,
45            sql: sql.into(),
46            params: params.into(),
47        }
48    }
49
50    pub fn write(sql: impl Into<String>, params: impl Into<Vec<Parameter>>) -> Self {
51        Self {
52            kind: OperationKind::Write,
53            sql: sql.into(),
54            params: params.into(),
55        }
56    }
57
58    pub fn schema(sql: impl Into<String>, params: impl Into<Vec<Parameter>>) -> Self {
59        Self {
60            kind: OperationKind::Schema,
61            sql: sql.into(),
62            params: params.into(),
63        }
64    }
65
66    pub fn kind(&self) -> OperationKind {
67        self.kind
68    }
69
70    pub fn sql(&self) -> &str {
71        &self.sql
72    }
73
74    pub fn params(&self) -> &[Parameter] {
75        &self.params
76    }
77}
78
79/// A generated key explicitly returned by a write, never an ambient handle.
80#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
81#[serde(untagged)]
82pub enum GeneratedKey {
83    Integer(i64),
84    Text(String),
85}
86
87/// The normalized result of one physical write.
88#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
89pub struct WriteResult {
90    pub affected_rows: u64,
91    #[serde(default)]
92    pub generated_keys: Vec<GeneratedKey>,
93}
94
95impl WriteResult {
96    pub fn generated_key(&self) -> Option<&GeneratedKey> {
97        self.generated_keys.first()
98    }
99}
100
101/// A result in an atomic batch, preserving operation order.
102#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
103pub enum OperationResult {
104    Rows(Rows),
105    Write(WriteResult),
106}
107
108/// The result of an opaque atomic batch.
109#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
110pub struct AtomicResult {
111    pub results: Vec<OperationResult>,
112}
113
114/// Options that affect physical opening, not migration or application policy.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub struct OpenOptions {
117    pub access_mode: AccessMode,
118    pub lock_timeout: Duration,
119}
120
121impl Default for OpenOptions {
122    fn default() -> Self {
123        Self {
124            access_mode: AccessMode::ReadWrite,
125            lock_timeout: Duration::from_millis(250),
126        }
127    }
128}