1use crate::conformance::{Leaf, Sql, Ty, drop_tables};
10use crate::contract;
11use crate::{RelError, RelationalBackend, RelationalTx, Value};
12use persistant_core::choose::Eligibility;
13use persistant_core::{Config, Reason};
14use std::time::Instant;
15
16pub use persistant_core::bench::{
17 Comparison, Environment, Left, Outcome, Ranked, SpeedParseError as ParseError, SpeedRow,
18 SpeedTable, Stats, compare,
19};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
23#[non_exhaustive]
24pub enum Workload {
25 Insert,
27 SelectByKey,
29 Update,
31 Transaction,
33}
34
35impl Workload {
36 pub const ALL: [Workload; 4] = [
38 Workload::Insert,
39 Workload::SelectByKey,
40 Workload::Update,
41 Workload::Transaction,
42 ];
43
44 pub fn id(self) -> &'static str {
46 match self {
47 Workload::Insert => "speed.rel.insert",
48 Workload::SelectByKey => "speed.rel.select_by_key",
49 Workload::Update => "speed.rel.update",
50 Workload::Transaction => "speed.rel.transaction",
51 }
52 }
53
54 pub fn required(self) -> Leaf {
56 match self {
57 Workload::Insert | Workload::SelectByKey | Workload::Update => Leaf::Values,
58 Workload::Transaction => Leaf::Transaction,
59 }
60 }
61}
62
63pub async fn run<R: RelationalBackend>(
66 target: &R,
67 config: &Config,
68 env: &Environment,
69 workloads: &[Workload],
70 iterations: u32,
71) -> Vec<SpeedRow> {
72 let mut out = Vec::with_capacity(workloads.len());
73 for w in workloads.iter().copied() {
74 let leaf = w.required();
75 let outcome = match contract::check(target, &[leaf]).await {
76 Eligibility::Eligible => match time(target, w, iterations).await {
77 Ok(Some(s)) => Outcome::Measured(s),
78 Ok(None) => Outcome::Excluded {
79 leaf: leaf.id().to_string(),
80 reason: Reason::Untested,
81 note: "no operations were timed".to_string(),
82 },
83 Err(e) => Outcome::Excluded {
84 leaf: leaf.id().to_string(),
85 reason: Reason::Missing,
86 note: format!("the workload failed on a backend that passed the leaf: {e}"),
87 },
88 },
89 Eligibility::Excluded(ex) => {
90 let e = &ex[0];
91 Outcome::Excluded {
92 leaf: e.leaf.clone(),
93 reason: e.reason,
94 note: e.note.clone(),
95 }
96 }
97 };
98 out.push(SpeedRow {
99 workload: w.id().to_string(),
100 backend: target.backend().to_string(),
101 version: target.version().to_string(),
102 config: config.clone(),
103 env: env.clone(),
104 outcome,
105 });
106 }
107 out
108}
109
110const TABLE: &str = "persistant_speed_rel";
111const SEED_ROWS: i64 = 100;
112
113async fn time<R: RelationalBackend>(
114 b: &R,
115 w: Workload,
116 iterations: u32,
117) -> Result<Option<Stats>, RelError> {
118 let s = Sql(b.dialect());
119 drop_tables(b, &[TABLE]).await?;
120 b.execute(
121 &format!(
122 "CREATE TABLE {TABLE} ({}, n BIGINT NOT NULL, t {} NOT NULL){}",
123 s.pk("id"),
124 s.ty(Ty::Text),
125 s.tail()
126 ),
127 &[],
128 )
129 .await?;
130 let result = timed_loop(b, &s, w, iterations).await;
131 let _ = drop_tables(b, &[TABLE]).await;
133 result
134}
135
136async fn timed_loop<R: RelationalBackend>(
137 b: &R,
138 s: &Sql,
139 w: Workload,
140 iterations: u32,
141) -> Result<Option<Stats>, RelError> {
142 let ins = format!(
143 "INSERT INTO {TABLE} (id, n, t) VALUES ({}, {}, {})",
144 s.p(1),
145 s.p(2),
146 s.p(3)
147 );
148 let sel = format!("SELECT n, t FROM {TABLE} WHERE id = {}", s.p(1));
149 let upd = format!("UPDATE {TABLE} SET n = n + 1 WHERE id = {}", s.p(1));
150 let row = |id: i64| [Value::Int(id), Value::Int(id), Value::Text("x".repeat(64))];
151 if matches!(w, Workload::SelectByKey | Workload::Update) {
152 for id in 0..SEED_ROWS {
153 b.execute(&ins, &row(id)).await?;
154 }
155 }
156 let mut us: Vec<u64> = Vec::with_capacity(iterations as usize);
157 macro_rules! timed {
158 ($e:expr) => {{
159 let t = Instant::now();
160 let r = $e;
161 us.push(u64::try_from(t.elapsed().as_nanos()).unwrap_or(u64::MAX));
162 r
163 }};
164 }
165 for i in 0..i64::from(iterations) {
166 match w {
167 Workload::Insert => {
168 timed!(b.execute(&ins, &row(i)).await?);
169 }
170 Workload::SelectByKey => {
171 let rows = timed!(b.query(&sel, &[Value::Int(i % SEED_ROWS)]).await?);
172 if rows.len() != 1 {
173 return Err(RelError::Backend(
174 "select by key did not return its row".to_string(),
175 ));
176 }
177 }
178 Workload::Update => {
179 let n = timed!(b.execute(&upd, &[Value::Int(i % SEED_ROWS)]).await?);
180 if n != 1 {
181 return Err(RelError::Backend(
182 "update by key did not touch one row".to_string(),
183 ));
184 }
185 }
186 Workload::Transaction => {
187 let t = Instant::now();
188 let mut tx = b.begin(crate::Isolation::ReadCommitted).await?;
189 tx.execute(&ins, &row(i)).await?;
190 tx.commit().await?;
191 us.push(u64::try_from(t.elapsed().as_nanos()).unwrap_or(u64::MAX));
192 }
193 }
194 }
195 Ok(Stats::from_samples(us))
196}