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 Savepoint,
35 UpsertWhere,
37}
38
39impl Workload {
40 pub const ALL: [Workload; 6] = [
42 Workload::Insert,
43 Workload::SelectByKey,
44 Workload::Update,
45 Workload::Transaction,
46 Workload::Savepoint,
47 Workload::UpsertWhere,
48 ];
49
50 pub fn id(self) -> &'static str {
52 match self {
53 Workload::Insert => "speed.rel.insert",
54 Workload::SelectByKey => "speed.rel.select_by_key",
55 Workload::Update => "speed.rel.update",
56 Workload::Transaction => "speed.rel.transaction",
57 Workload::Savepoint => "speed.rel.savepoint",
58 Workload::UpsertWhere => "speed.rel.upsert_where",
59 }
60 }
61
62 pub fn required(self) -> Leaf {
64 match self {
65 Workload::Insert | Workload::SelectByKey | Workload::Update => Leaf::Values,
66 Workload::Transaction => Leaf::Transaction,
67 Workload::Savepoint => Leaf::Savepoint,
68 Workload::UpsertWhere => Leaf::UpsertWhere,
69 }
70 }
71}
72
73pub async fn run<R: RelationalBackend>(
76 target: &R,
77 config: &Config,
78 env: &Environment,
79 workloads: &[Workload],
80 iterations: u32,
81) -> Vec<SpeedRow> {
82 let mut out = Vec::with_capacity(workloads.len());
83 for w in workloads.iter().copied() {
84 let leaf = w.required();
85 let outcome = match contract::check(target, &[leaf]).await {
86 Eligibility::Eligible => match time(target, w, iterations).await {
87 Ok(Some(s)) => Outcome::Measured(s),
88 Ok(None) => Outcome::Excluded {
89 leaf: leaf.id().to_string(),
90 reason: Reason::Untested,
91 note: "no operations were timed".to_string(),
92 },
93 Err(e) => Outcome::Excluded {
94 leaf: leaf.id().to_string(),
95 reason: Reason::Missing,
96 note: format!("the workload failed on a backend that passed the leaf: {e}"),
97 },
98 },
99 Eligibility::Excluded(ex) => {
100 let e = &ex[0];
101 Outcome::Excluded {
102 leaf: e.leaf.clone(),
103 reason: e.reason,
104 note: e.note.clone(),
105 }
106 }
107 };
108 out.push(SpeedRow {
109 workload: w.id().to_string(),
110 backend: target.backend().to_string(),
111 version: target.version().to_string(),
112 config: config.clone(),
113 env: env.clone(),
114 outcome,
115 });
116 }
117 out
118}
119
120const TABLE: &str = "persistant_speed_rel";
121const SEED_ROWS: i64 = 100;
122
123async fn time<R: RelationalBackend>(
124 b: &R,
125 w: Workload,
126 iterations: u32,
127) -> Result<Option<Stats>, RelError> {
128 let s = Sql(b.dialect());
129 drop_tables(b, &[TABLE]).await?;
130 b.execute(
131 &format!(
132 "CREATE TABLE {TABLE} ({}, n BIGINT NOT NULL, t {} NOT NULL){}",
133 s.pk("id"),
134 s.ty(Ty::Text),
135 s.tail()
136 ),
137 &[],
138 )
139 .await?;
140 let result = timed_loop(b, &s, w, iterations).await;
141 let _ = drop_tables(b, &[TABLE]).await;
143 result
144}
145
146async fn timed_loop<R: RelationalBackend>(
147 b: &R,
148 s: &Sql,
149 w: Workload,
150 iterations: u32,
151) -> Result<Option<Stats>, RelError> {
152 let ins = format!(
153 "INSERT INTO {TABLE} (id, n, t) VALUES ({}, {}, {})",
154 s.p(1),
155 s.p(2),
156 s.p(3)
157 );
158 let sel = format!("SELECT n, t FROM {TABLE} WHERE id = {}", s.p(1));
159 let upd = format!("UPDATE {TABLE} SET n = n + 1 WHERE id = {}", s.p(1));
160 let row = |id: i64| [Value::Int(id), Value::Int(id), Value::Text("x".repeat(64))];
161 let ups = format!(
163 "INSERT INTO {TABLE} (id, n, t) VALUES ({}, {}, {}) ON CONFLICT (id) DO UPDATE SET n = EXCLUDED.n WHERE {TABLE}.n < EXCLUDED.n",
164 s.p(1),
165 s.p(2),
166 s.p(3)
167 );
168 if matches!(
169 w,
170 Workload::SelectByKey | Workload::Update | Workload::UpsertWhere
171 ) {
172 for id in 0..SEED_ROWS {
173 b.execute(&ins, &row(id)).await?;
174 }
175 }
176 let mut us: Vec<u64> = Vec::with_capacity(iterations as usize);
177 macro_rules! timed {
178 ($e:expr) => {{
179 let t = Instant::now();
180 let r = $e;
181 us.push(u64::try_from(t.elapsed().as_nanos()).unwrap_or(u64::MAX));
182 r
183 }};
184 }
185 let mut sp_tx = if w == Workload::Savepoint {
187 Some(b.begin(crate::Isolation::ReadCommitted).await?)
188 } else {
189 None
190 };
191 for i in 0..i64::from(iterations) {
192 match w {
193 Workload::Insert => {
194 timed!(b.execute(&ins, &row(i)).await?);
195 }
196 Workload::SelectByKey => {
197 let rows = timed!(b.query(&sel, &[Value::Int(i % SEED_ROWS)]).await?);
198 if rows.len() != 1 {
199 return Err(RelError::Backend(
200 "select by key did not return its row".to_string(),
201 ));
202 }
203 }
204 Workload::Update => {
205 let n = timed!(b.execute(&upd, &[Value::Int(i % SEED_ROWS)]).await?);
206 if n != 1 {
207 return Err(RelError::Backend(
208 "update by key did not touch one row".to_string(),
209 ));
210 }
211 }
212 Workload::Transaction => {
213 let t = Instant::now();
214 let mut tx = b.begin(crate::Isolation::ReadCommitted).await?;
215 tx.execute(&ins, &row(i)).await?;
216 tx.commit().await?;
217 us.push(u64::try_from(t.elapsed().as_nanos()).unwrap_or(u64::MAX));
218 }
219 Workload::Savepoint => {
220 let tx = sp_tx.as_mut().expect("opened above for this workload");
221 let t = Instant::now();
222 tx.execute("SAVEPOINT persistant_speed_sp", &[]).await?;
223 tx.execute(&ins, &row(i)).await?;
224 tx.execute("ROLLBACK TO SAVEPOINT persistant_speed_sp", &[])
225 .await?;
226 us.push(u64::try_from(t.elapsed().as_nanos()).unwrap_or(u64::MAX));
227 }
228 Workload::UpsertWhere => {
229 let id = i % SEED_ROWS;
230 let r = [
231 Value::Int(id),
232 Value::Int(1000 + i),
233 Value::Text("x".repeat(64)),
234 ];
235 timed!(b.execute(&ups, &r).await?);
236 let got = b
237 .query(
238 &format!("SELECT n FROM {TABLE} WHERE id = {}", s.p(1)),
239 &[Value::Int(id)],
240 )
241 .await?;
242 if got.first().and_then(|r| r.int(0)) != Some(1000 + i) {
243 return Err(RelError::Backend(
244 "the upsert did not take the update branch".to_string(),
245 ));
246 }
247 }
248 }
249 }
250 if let Some(tx) = sp_tx {
251 tx.rollback().await?;
252 }
253 Ok(Stats::from_samples(us))
254}