1use std::{
2 sync::Arc,
3 time::{Duration, Instant},
4};
5
6use sim_kernel::{
7 Cx, Datum, DatumStore, Effect, Ref, Symbol,
8 effect::{effect_abort_op_key, effect_resume_op_key, resolve_effect},
9};
10use sim_relation_core::{Row, RowType, ToRelationDatum};
11use sim_relation_migrate::CheckedProgram;
12use sim_relation_plan::{CheckedMutation, CheckedQuery};
13
14use crate::{
15 Bindings, Driver, Limits, Operation, ProviderStats, Receipt, RelationPlacement, RowSink,
16 Session, SiteError, Transaction,
17};
18
19pub struct RelationSite {
21 pub(crate) placement: RelationPlacement,
22 pub(crate) driver: Arc<dyn Driver>,
23}
24impl RelationSite {
25 pub fn new(placement: RelationPlacement, driver: Arc<dyn Driver>) -> Self {
27 Self { placement, driver }
28 }
29
30 pub fn query(
32 &self,
33 cx: &mut Cx,
34 plan: &CheckedQuery,
35 bindings: &Bindings,
36 limits: Limits,
37 sink: &mut dyn RowSink,
38 ) -> Result<Receipt, SiteError> {
39 self.rows_effect(
40 cx,
41 Operation::Read,
42 RowsSpec {
43 id: content_id_text(plan.plan_id().content_id()),
44 expected: plan.output(),
45 },
46 limits,
47 sink,
48 |session, bounded| session.query(plan, bindings, &limits, bounded),
49 )
50 }
51 pub fn mutate(
53 &self,
54 cx: &mut Cx,
55 plan: &CheckedMutation,
56 bindings: &Bindings,
57 limits: Limits,
58 sink: &mut dyn RowSink,
59 ) -> Result<Receipt, SiteError> {
60 self.rows_effect(
61 cx,
62 Operation::Write,
63 RowsSpec {
64 id: content_id_text(plan.plan_id().content_id()),
65 expected: plan.output(),
66 },
67 limits,
68 sink,
69 |session, bounded| session.mutate(plan, bindings, &limits, bounded),
70 )
71 }
72 pub fn migrate(
74 &self,
75 cx: &mut Cx,
76 program: &CheckedProgram,
77 limits: Limits,
78 ) -> Result<Receipt, SiteError> {
79 self.simple_effect(
80 cx,
81 Operation::Migrate,
82 "checked-migration".into(),
83 limits,
84 |s| s.migrate(program, &limits),
85 )
86 }
87 pub fn schema(
89 &self,
90 cx: &mut Cx,
91 program: &CheckedProgram,
92 limits: Limits,
93 ) -> Result<Receipt, SiteError> {
94 self.simple_effect(
95 cx,
96 Operation::Schema,
97 "checked-schema".into(),
98 limits,
99 |s| s.schema(program, &limits),
100 )
101 }
102 pub fn attach(
104 &self,
105 cx: &mut Cx,
106 locator: &Datum,
107 limits: Limits,
108 ) -> Result<Receipt, SiteError> {
109 self.simple_effect(cx, Operation::Attach, "attach".into(), limits, |s| {
110 s.attach(locator, &limits)
111 })
112 }
113 pub fn transaction(
115 &self,
116 cx: &mut Cx,
117 limits: Limits,
118 mut body: impl FnMut(&mut dyn Transaction) -> Result<(), SiteError>,
119 ) -> Result<Receipt, SiteError> {
120 self.simple_effect(
121 cx,
122 Operation::Transaction,
123 "transaction".into(),
124 limits,
125 |s| {
126 s.transaction(&mut body)?;
127 Ok(ProviderStats::default())
128 },
129 )
130 }
131
132 fn connect(&self, limits: &Limits) -> Result<Box<dyn Session>, SiteError> {
133 self.driver
134 .connect(self.placement.locator(), limits)
135 .map_err(|_| SiteError::Locator)
136 }
137 fn simple_effect(
138 &self,
139 cx: &mut Cx,
140 op: Operation,
141 id: String,
142 limits: Limits,
143 perform: impl FnOnce(&mut dyn Session) -> Result<ProviderStats, SiteError>,
144 ) -> Result<Receipt, SiteError> {
145 let mut stats = None;
146 self.effect(cx, op, |cx| {
147 let mut session = self.connect(&limits)?;
148 let started = Instant::now();
149 let got = perform(session.as_mut())?;
150 enforce_deadline(&limits, started.elapsed())?;
151 enforce_work(&limits, got.work)?;
152 stats = Some(got);
153 cx.datum_store_mut()
154 .intern(Datum::Symbol(Symbol::new("ok")))
155 .map(Ref::Content)
156 .map_err(kernel)
157 })?;
158 let got = stats.unwrap_or_default();
159 Ok(Receipt {
160 operation_id: id,
161 operation: op,
162 rows: 0,
163 cells: 0,
164 bytes: 0,
165 work: got.work,
166 affected: got.affected,
167 })
168 }
169 fn rows_effect(
170 &self,
171 cx: &mut Cx,
172 op: Operation,
173 spec: RowsSpec<'_>,
174 limits: Limits,
175 sink: &mut dyn RowSink,
176 perform: impl FnOnce(&mut dyn Session, &mut dyn RowSink) -> Result<ProviderStats, SiteError>,
177 ) -> Result<Receipt, SiteError> {
178 let mut counts = Counts::default();
179 let mut stats = None;
180 self.effect(cx, op, |cx| {
181 let mut session = self.connect(&limits)?;
182 let mut bounded = BoundedSink {
183 expected: spec.expected,
184 limits: &limits,
185 inner: sink,
186 counts: &mut counts,
187 };
188 let started = Instant::now();
189 let got = perform(session.as_mut(), &mut bounded)?;
190 enforce_deadline(&limits, started.elapsed())?;
191 enforce_work(&limits, got.work)?;
192 stats = Some(got);
193 cx.datum_store_mut()
194 .intern(Datum::Symbol(Symbol::new("ok")))
195 .map(Ref::Content)
196 .map_err(kernel)
197 })?;
198 let got = stats.unwrap_or_default();
199 Ok(Receipt {
200 operation_id: spec.id,
201 operation: op,
202 rows: counts.rows,
203 cells: counts.cells,
204 bytes: counts.bytes,
205 work: got.work,
206 affected: got.affected,
207 })
208 }
209 pub(crate) fn effect(
210 &self,
211 cx: &mut Cx,
212 op: Operation,
213 perform: impl FnOnce(&mut Cx) -> Result<Ref, SiteError>,
214 ) -> Result<(), SiteError> {
215 let input = cx
216 .datum_store_mut()
217 .intern(Datum::Node {
218 tag: Symbol::qualified("relation", "request"),
219 fields: vec![(
220 Symbol::new("site"),
221 Datum::Symbol(self.placement.site.clone()),
222 )],
223 })
224 .map(Ref::Content)
225 .map_err(kernel)?;
226 let effect = Effect::new(
227 cx.fresh_handle(),
228 op.effect(),
229 Ref::Symbol(self.placement.site.clone()),
230 input,
231 Ref::Symbol(Symbol::qualified("core", "Any")),
232 effect_resume_op_key(),
233 effect_abort_op_key(),
234 )
235 .requiring(op.capability());
236 let mut operation_error = None;
237 if let Err(error) = resolve_effect(cx, effect, |cx, _| match perform(cx) {
238 Ok(value) => Ok(value),
239 Err(error) => {
240 operation_error = Some(error.clone());
241 Err(sim_kernel::Error::Eval(error.to_string()))
242 }
243 }) {
244 return Err(operation_error.unwrap_or_else(|| kernel(error)));
245 }
246 Ok(())
247 }
248}
249
250struct RowsSpec<'a> {
251 id: String,
252 expected: &'a RowType,
253}
254
255#[derive(Default)]
256pub(crate) struct Counts {
257 rows: u64,
258 cells: u64,
259 bytes: u64,
260}
261pub(crate) struct BoundedSink<'a> {
262 pub(crate) expected: &'a RowType,
263 pub(crate) limits: &'a Limits,
264 pub(crate) inner: &'a mut dyn RowSink,
265 pub(crate) counts: &'a mut Counts,
266}
267impl RowSink for BoundedSink<'_> {
268 fn push(&mut self, row: Row) -> Result<(), SiteError> {
269 if row.row_type() != self.expected {
270 return Err(SiteError::RowType);
271 }
272 let cells = row.cells().len() as u64;
273 let bytes = format!("{:?}", row.to_datum()).len() as u64;
274 let rows = self
275 .counts
276 .rows
277 .checked_add(1)
278 .ok_or(SiteError::Limit(crate::LimitKind::Rows))?;
279 let cells = self
280 .counts
281 .cells
282 .checked_add(cells)
283 .ok_or(SiteError::Limit(crate::LimitKind::Cells))?;
284 let bytes = self
285 .counts
286 .bytes
287 .checked_add(bytes)
288 .ok_or(SiteError::Limit(crate::LimitKind::Bytes))?;
289 if rows > self.limits.rows {
290 return Err(SiteError::Limit(crate::LimitKind::Rows));
291 }
292 if cells > self.limits.cells {
293 return Err(SiteError::Limit(crate::LimitKind::Cells));
294 }
295 if bytes > self.limits.bytes {
296 return Err(SiteError::Limit(crate::LimitKind::Bytes));
297 }
298 self.inner.push(row)?;
299 self.counts.rows = rows;
300 self.counts.cells = cells;
301 self.counts.bytes = bytes;
302 Ok(())
303 }
304}
305pub(crate) fn enforce_work(l: &Limits, work: u64) -> Result<(), SiteError> {
306 if work > l.work {
307 Err(SiteError::Limit(crate::LimitKind::Work))
308 } else {
309 Ok(())
310 }
311}
312fn enforce_deadline(limits: &Limits, elapsed: Duration) -> Result<(), SiteError> {
313 if limits.deadline.is_some_and(|deadline| elapsed > deadline) {
314 Err(SiteError::Limit(crate::LimitKind::Deadline))
315 } else {
316 Ok(())
317 }
318}
319fn content_id_text(id: &sim_kernel::ContentId) -> String {
320 let digest: String = id.bytes.iter().map(|byte| format!("{byte:02x}")).collect();
321 format!("{}:{digest}", id.algorithm)
322}
323pub(crate) fn kernel(e: sim_kernel::Error) -> SiteError {
324 SiteError::Kernel(e.to_string())
325}