1use crate::value::Value;
19
20#[derive(Debug, Clone)]
21pub struct ExecutionPlanSpec {
22 pub groups: Vec<Vec<usize>>,
23 pub concurrency: i64,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum PolicyKind {
28 Fail,
29 Retry,
30 Continue,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum RelationKind {
35 Single,
36 Connection,
37}
38
39#[derive(Debug, Clone)]
40pub struct OpSpec {
41 pub id: String,
42 pub parent: Option<usize>,
43 pub bind_field: Option<String>,
44 pub relation_kind: Option<RelationKind>,
45 pub policy: Option<String>,
48}
49
50#[derive(Debug, Clone)]
52pub enum ExecOutcome {
53 Ok(Value),
54 Error(String),
55}
56
57#[derive(Debug, Clone)]
58enum OpState {
59 Ok(Value),
60 Skipped,
61 }
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum PlanFailureCode {
66 OpFailed,
67 UnknownPolicy,
68 InvalidPlan,
69}
70
71impl PlanFailureCode {
72 pub fn as_str(self) -> &'static str {
73 match self {
74 PlanFailureCode::OpFailed => "OP_FAILED",
75 PlanFailureCode::UnknownPolicy => "UNKNOWN_POLICY",
76 PlanFailureCode::InvalidPlan => "INVALID_PLAN",
77 }
78 }
79}
80
81#[derive(Debug, Clone)]
82pub struct PlanFailure {
83 pub code: PlanFailureCode,
84 pub message: String,
85}
86
87impl std::fmt::Display for PlanFailure {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 write!(f, "{}: {}", self.code.as_str(), self.message)
90 }
91}
92impl std::error::Error for PlanFailure {}
93
94fn fail<T>(code: PlanFailureCode, message: impl Into<String>) -> Result<T, PlanFailure> {
95 Err(PlanFailure {
96 code,
97 message: message.into(),
98 })
99}
100
101#[derive(Debug)]
102pub struct RunResult {
103 states: Vec<Option<OpState>>,
104 pub executed: Vec<String>,
105 pub skipped: Vec<String>,
106 ops_ref: Vec<(String, Option<RelationKind>)>,
107}
108
109impl RunResult {
110 pub fn final_tree(&self) -> Vec<(String, Value)> {
112 let mut tree = Vec::new();
113 for (i, st) in self.states.iter().enumerate() {
114 match st {
115 Some(OpState::Ok(v)) => tree.push((self.ops_ref[i].0.clone(), v.clone())),
116 Some(OpState::Skipped) => tree.push((
117 self.ops_ref[i].0.clone(),
118 unproduced_value(self.ops_ref[i].1),
119 )),
120 None => {}
121 }
122 }
123 tree
124 }
125}
126
127fn unproduced_value(kind: Option<RelationKind>) -> Value {
128 if kind == Some(RelationKind::Connection) {
129 Value::Obj(vec![
130 ("items".into(), Value::Arr(vec![])),
131 ("cursor".into(), Value::Null),
132 ])
133 } else {
134 Value::Null
135 }
136}
137
138fn parse_policy(raw: &Option<String>) -> Result<PolicyKind, PlanFailure> {
139 match raw.as_deref() {
140 None | Some("fail") => Ok(PolicyKind::Fail),
141 Some("retry") => Ok(PolicyKind::Retry),
142 Some("continue") => Ok(PolicyKind::Continue),
143 Some(other) => fail(
144 PlanFailureCode::UnknownPolicy,
145 format!("unknown policy kind: {other}"),
146 ),
147 }
148}
149
150pub fn run_plan<F>(
152 plan: Option<&ExecutionPlanSpec>,
153 ops: &[OpSpec],
154 mut exec: F,
155) -> Result<RunResult, PlanFailure>
156where
157 F: FnMut(&OpSpec, Option<&Value>) -> ExecOutcome,
158{
159 let n = ops.len();
160 let mut states: Vec<Option<OpState>> = vec![None; n];
161 let mut executed_idx: Vec<usize> = Vec::new();
162 let mut skipped_idx: Vec<usize> = Vec::new();
163
164 let stages: Vec<Vec<usize>> = match plan {
165 Some(p) => p.groups.clone(),
166 None => (0..n).map(|i| vec![i]).collect(),
167 };
168
169 validate_coverage(&stages, n)?;
170
171 for stage in &stages {
172 let mut ordered = stage.clone();
173 ordered.sort_unstable(); for &i in &ordered {
175 let st = run_op(
176 i,
177 &ops[i],
178 &states,
179 &mut exec,
180 &mut executed_idx,
181 &mut skipped_idx,
182 )?;
183 states[i] = Some(st);
184 }
185 }
186
187 executed_idx.sort_unstable();
188 skipped_idx.sort_unstable();
189
190 Ok(RunResult {
191 executed: executed_idx.iter().map(|&i| ops[i].id.clone()).collect(),
192 skipped: skipped_idx.iter().map(|&i| ops[i].id.clone()).collect(),
193 ops_ref: ops
194 .iter()
195 .map(|o| (o.id.clone(), o.relation_kind))
196 .collect(),
197 states,
198 })
199}
200
201pub fn run_plan_parallel<F>(
221 plan: Option<&ExecutionPlanSpec>,
222 ops: &[OpSpec],
223 exec: F,
224) -> Result<RunResult, PlanFailure>
225where
226 F: Fn(&OpSpec, Option<&Value>) -> ExecOutcome + Sync,
227{
228 let n = ops.len();
229 let mut states: Vec<Option<OpState>> = vec![None; n];
230 let mut executed_idx: Vec<usize> = Vec::new();
231 let mut skipped_idx: Vec<usize> = Vec::new();
232
233 let stages: Vec<Vec<usize>> = match plan {
234 Some(p) => p.groups.clone(),
235 None => (0..n).map(|i| vec![i]).collect(),
236 };
237 let concurrency = plan.map(|p| p.concurrency).unwrap_or(1);
238
239 validate_coverage(&stages, n)?;
240
241 for stage in &stages {
242 let mut ordered = stage.clone();
243 ordered.sort_unstable();
244
245 let has_intra_deps = ordered
246 .iter()
247 .any(|&i| matches!(ops[i].parent, Some(p) if ordered.contains(&p)));
248
249 if concurrency <= 1 || ordered.len() <= 1 || has_intra_deps {
250 for &i in &ordered {
252 let st = run_op(
253 i,
254 &ops[i],
255 &states,
256 &mut |op: &OpSpec, b: Option<&Value>| exec(op, b),
257 &mut executed_idx,
258 &mut skipped_idx,
259 )?;
260 states[i] = Some(st);
261 }
262 continue;
263 }
264
265 let pres: Vec<Preflight> = ordered
267 .iter()
268 .map(|&i| preflight_op(&ops[i], &states))
269 .collect();
270
271 let jobs: Vec<usize> = (0..ordered.len())
274 .filter(|&k| matches!(pres[k], Preflight::Exec { .. }))
275 .collect();
276 let workers = (concurrency.max(1) as usize).min(jobs.len());
277 let cursor = std::sync::atomic::AtomicUsize::new(0);
278 let slots: Vec<std::sync::Mutex<Option<ExecOutcome>>> = (0..ordered.len())
279 .map(|_| std::sync::Mutex::new(None))
280 .collect();
281
282 std::thread::scope(|scope| {
283 for _ in 0..workers {
284 scope.spawn(|| loop {
285 let j = cursor.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
286 if j >= jobs.len() {
287 break;
288 }
289 let k = jobs[j];
290 let i = ordered[k];
291 let bound = match &pres[k] {
292 Preflight::Exec { bound, .. } => bound.as_ref(),
293 _ => unreachable!(),
294 };
295 let outcome = exec(&ops[i], bound);
296 *slots[k].lock().unwrap() = Some(outcome);
297 });
298 }
299 }); for (k, &i) in ordered.iter().enumerate() {
303 match &pres[k] {
304 Preflight::InvalidPolicy(msg) => {
305 return fail(PlanFailureCode::UnknownPolicy, msg.clone())
306 }
307 Preflight::Skip => {
308 skipped_idx.push(i);
309 states[i] = Some(OpState::Skipped);
310 }
311 Preflight::Exec { policy, .. } => {
312 let outcome = slots[k]
313 .lock()
314 .unwrap()
315 .take()
316 .expect("dispatched op must have an outcome");
317 let st = interpret_outcome(
318 i,
319 &ops[i],
320 *policy,
321 outcome,
322 &mut executed_idx,
323 &mut skipped_idx,
324 )?;
325 states[i] = Some(st);
326 }
327 }
328 }
329 }
330
331 executed_idx.sort_unstable();
332 skipped_idx.sort_unstable();
333
334 Ok(RunResult {
335 executed: executed_idx.iter().map(|&i| ops[i].id.clone()).collect(),
336 skipped: skipped_idx.iter().map(|&i| ops[i].id.clone()).collect(),
337 ops_ref: ops
338 .iter()
339 .map(|o| (o.id.clone(), o.relation_kind))
340 .collect(),
341 states,
342 })
343}
344
345enum Preflight {
350 InvalidPolicy(String),
351 Skip,
352 Exec {
353 policy: PolicyKind,
354 bound: Option<Value>,
355 },
356}
357
358fn preflight_op(op: &OpSpec, states: &[Option<OpState>]) -> Preflight {
359 let policy = match parse_policy(&op.policy) {
360 Ok(p) => p,
361 Err(e) => return Preflight::InvalidPolicy(e.message), };
363
364 let mut bound_value: Option<Value> = None;
365 if let Some(pidx) = op.parent {
366 match states.get(pidx).and_then(|s| s.as_ref()) {
367 Some(OpState::Ok(pv)) => {
368 if let Some(field) = &op.bind_field {
369 let bound = pv.obj_get(field);
370 match bound {
371 None | Some(Value::Null) => return Preflight::Skip,
372 Some(b) => bound_value = Some(b.clone()),
373 }
374 }
375 }
376 _ => {
377 return Preflight::Skip;
379 }
380 }
381 }
382 Preflight::Exec {
383 policy,
384 bound: bound_value,
385 }
386}
387
388fn interpret_outcome(
391 i: usize,
392 op: &OpSpec,
393 policy: PolicyKind,
394 outcome: ExecOutcome,
395 executed_idx: &mut Vec<usize>,
396 skipped_idx: &mut Vec<usize>,
397) -> Result<OpState, PlanFailure> {
398 executed_idx.push(i);
399
400 match outcome {
401 ExecOutcome::Ok(v) => Ok(OpState::Ok(v)),
402 ExecOutcome::Error(e) => match policy {
403 PolicyKind::Fail => fail(
404 PlanFailureCode::OpFailed,
405 format!("operation '{}' failed under 'fail' policy: {e}", op.id),
406 ),
407 PolicyKind::Retry => fail(
408 PlanFailureCode::OpFailed,
409 format!(
410 "operation '{}' failed under 'retry' policy (exhausted): {e}",
411 op.id
412 ),
413 ),
414 PolicyKind::Continue => {
415 skipped_idx.push(i);
417 executed_idx.pop(); Ok(OpState::Skipped)
419 }
420 },
421 }
422}
423
424fn run_op<F>(
425 i: usize,
426 op: &OpSpec,
427 states: &[Option<OpState>],
428 exec: &mut F,
429 executed_idx: &mut Vec<usize>,
430 skipped_idx: &mut Vec<usize>,
431) -> Result<OpState, PlanFailure>
432where
433 F: FnMut(&OpSpec, Option<&Value>) -> ExecOutcome,
434{
435 let (policy, bound_value) = match preflight_op(op, states) {
436 Preflight::InvalidPolicy(msg) => return fail(PlanFailureCode::UnknownPolicy, msg),
437 Preflight::Skip => {
438 skipped_idx.push(i);
439 return Ok(OpState::Skipped);
440 }
441 Preflight::Exec { policy, bound } => (policy, bound),
442 };
443
444 let outcome = exec(op, bound_value.as_ref());
445 interpret_outcome(i, op, policy, outcome, executed_idx, skipped_idx)
446}
447
448fn validate_coverage(stages: &[Vec<usize>], n: usize) -> Result<(), PlanFailure> {
449 let mut seen = std::collections::HashSet::new();
450 for stage in stages {
451 for &i in stage {
452 if i >= n {
453 return fail(
454 PlanFailureCode::InvalidPlan,
455 format!("stage index {i} out of range [0,{n})"),
456 );
457 }
458 if !seen.insert(i) {
459 return fail(
460 PlanFailureCode::InvalidPlan,
461 format!("operation {i} appears in more than one stage"),
462 );
463 }
464 }
465 }
466 if seen.len() != n {
467 return fail(
468 PlanFailureCode::InvalidPlan,
469 format!(
470 "plan does not cover all {n} operations (covered {})",
471 seen.len()
472 ),
473 );
474 }
475 Ok(())
476}