1mod axes;
10mod check;
11mod dispatch;
12
13use std::future::Future;
14use std::time::Duration;
15
16use ruststream::HeaderMap;
17use ruststream_sqlx_dialect::{Param, Role, TableSpec};
18use sqlx::{Decode, Encode, Error, FromRow, Type};
19
20pub use axes::{
21 AttemptAxis, ClockAxis, FormAxis, FormBind, HeadersAxis, KeyAxis, LeaseAxis, ManualHeaders,
22 MessageAxis, Named, OpeningAxis, TimeAxis,
23};
24pub use dispatch::{
25 AckAxis, ClaimAxes, DeadLetterAxis, DiscardAxis, ExtendAxis, LockAxis, RetryAfterAxis,
26 RetryAxis, UnlockAxis,
27};
28
29use super::columns::{AttemptColumn, KeyColumn};
30use super::database::QueueDatabase;
31use super::engine::{
32 self, Claimed, Claiming, Event, Events, Leasing, Now, Settled, Settling, Shape, Values,
33};
34use super::form::advisory::events::{self as advisory, Candidates};
35use super::headers::{Assembled, HeaderField};
36use super::named::kinds::{Kinds, KindsOf};
37use super::queue::Queue;
38use super::spec::Declaration;
39use super::time::LeaseRow;
40use super::{InboxRow, InboxSpec, InboxTable, QueueRow};
41
42#[diagnostic::on_unimplemented(
113 message = "`{Self}` sets a `partition_key` column and does not read it off its row",
114 label = "no `KeyRow` for this row",
115 note = "implement `KeyRow` for `{Self}`, or drop `.partition_key(..)` and `Key` from its \
116 description"
117)]
118pub trait KeyRow {
119 type Key: KeyColumn + 'static;
121
122 fn partition_key(&self) -> &Self::Key;
124}
125
126#[diagnostic::on_unimplemented(
209 message = "`{Self}` sets an `attempt` column and does not read it off its row",
210 label = "no `AttemptRow` for this row",
211 note = "implement `AttemptRow` for `{Self}`, or drop `.attempt(..)` and `Attempt` from its \
212 description"
213)]
214pub trait AttemptRow {
215 type Attempt: AttemptColumn + 'static;
217
218 fn attempt(&self) -> &Self::Attempt;
220}
221
222#[diagnostic::on_unimplemented(
297 message = "`{Self}` builds its headers from fields and does not say which",
298 label = "no `HeaderFields` for this row",
299 note = "implement `HeaderFields` for `{Self}`, or drop `.header_fields()` and \
300 `spec::HeaderFields` from its description"
301)]
302pub trait HeaderFields {
303 const NAMES: &'static [&'static str];
305
306 fn header_map(&self) -> HeaderMap;
308}
309
310pub fn put_header(headers: &mut HeaderMap, name: &'static str, field: &impl HeaderField) {
329 super::headers::put_header(headers, name, field);
330}
331
332impl<Row: HeaderFields> Assembled for Row {
333 fn header_map(&self) -> HeaderMap {
334 HeaderFields::header_map(self)
335 }
336}
337
338impl<Row, Settings> QueueRow for Row
339where
340 Row: InboxTable<Table = InboxSpec<Settings>>,
341 Settings: Declaration,
342 Settings::Message: MessageAxis<Row>,
343{
344 type Id = <Row as InboxTable>::Id;
345 type Lane = <Settings::Message as MessageAxis<Row>>::Lane;
346}
347
348impl<Row, Settings> InboxRow for Row
349where
350 Row: InboxTable<Table = InboxSpec<Settings>>,
351 Settings: Declaration,
352 Settings::Message: MessageAxis<Row>,
353 Settings::Form: FormAxis,
354 Settings::Opening: OpeningAxis,
355{
356 const SPEC: TableSpec<'static> = check::checked(&Row::TABLE.spec());
357 type Form = <Settings::Form as FormAxis>::Form;
358 type Opening = <Settings::Opening as OpeningAxis>::Opening;
359}
360
361#[diagnostic::do_not_recommend]
364impl<Row, Settings> LeaseRow for Row
365where
366 Row: InboxRow + InboxTable<Table = InboxSpec<Settings>>,
367 Settings: Declaration,
368 Settings::Form: LeaseAxis,
369{
370 type Lease = <Settings::Form as LeaseAxis>::Time;
371}
372
373pub trait Axes {
375 const SHAPE: Shape;
377 const OWN: bool;
379}
380
381impl<Settings> Axes for InboxSpec<Settings>
382where
383 Settings: Declaration,
384 Settings::OwnClaim: Named,
385 Settings::OwnFetch: Named,
386 Settings::OwnAck: Named,
387 Settings::OwnRetry: Named,
388 Settings::OwnRetryAfter: Named,
389 Settings::OwnDiscard: Named,
390 Settings::OwnDeadLetter: Named,
391 Settings::OwnExtend: Named,
392 Settings::OwnLock: Named,
393 Settings::OwnUnlock: Named,
394{
395 const SHAPE: Shape = Shape {
396 custom_claim: Settings::OwnClaim::NAMED,
397 custom_fetch: Settings::OwnFetch::NAMED,
398 custom_ack: Settings::OwnAck::NAMED,
399 custom_retry: Settings::OwnRetry::NAMED,
400 custom_retry_after: Settings::OwnRetryAfter::NAMED,
401 custom_discard: Settings::OwnDiscard::NAMED,
402 custom_dead_letter: Settings::OwnDeadLetter::NAMED,
403 custom_extend: Settings::OwnExtend::NAMED,
404 custom_lock: Settings::OwnLock::NAMED,
405 custom_unlock: Settings::OwnUnlock::NAMED,
406 };
407 const OWN: bool = Settings::OwnClaim::NAMED
408 || Settings::OwnFetch::NAMED
409 || Settings::OwnAck::NAMED
410 || Settings::OwnRetry::NAMED
411 || Settings::OwnRetryAfter::NAMED
412 || Settings::OwnDiscard::NAMED
413 || Settings::OwnDeadLetter::NAMED
414 || Settings::OwnExtend::NAMED
415 || Settings::OwnLock::NAMED
416 || Settings::OwnUnlock::NAMED;
417}
418
419type Own<Settings> = (
420 <Settings as Declaration>::OwnClaim,
421 <Settings as Declaration>::OwnFetch,
422);
423type Token<Settings> = <<Settings as Declaration>::Form as FormAxis>::Token;
424type Source<Settings> = <<Settings as Declaration>::Clock as ClockAxis>::Source;
425
426impl<DB, Row, Settings> Events<DB> for Row
427where
428 DB: QueueDatabase,
429 Row: InboxTable<Table = InboxSpec<Settings>> + for<'r> FromRow<'r, DB::Row> + Unpin,
430 <Row as InboxTable>::Id: for<'q> Encode<'q, DB> + for<'r> Decode<'r, DB> + Type<DB>,
431 Settings: Declaration,
432 InboxSpec<Settings>: Axes,
433 Settings::Message: MessageAxis<Row>,
434 Settings::Form: FormBind<DB>,
435 Settings::Key: KeyAxis<Row>,
436 Settings::Attempt: AttemptAxis<DB, Row>,
437 Settings::Headers: HeadersAxis<Row> + 'static,
438 Settings::RetryAfter: TimeAxis<DB>,
439 Settings::ProcessedAt: TimeAxis<DB>,
440 Settings::Clock: ClockAxis,
441 Settings::Opening: OpeningAxis,
442 Own<Settings>: ClaimAxes<DB, Row>,
443 Settings::OwnAck: AckAxis<DB, Row>,
444 Settings::OwnRetry: RetryAxis<DB, Row>,
445 Settings::OwnRetryAfter: RetryAfterAxis<DB, Row>,
446 Settings::OwnDiscard: DiscardAxis<DB, Row>,
447 Settings::OwnDeadLetter: DeadLetterAxis<DB, Row>,
448 Settings::OwnExtend: ExtendAxis<DB, Row, Token<Settings>>,
449 Settings::OwnLock: LockAxis<DB, Row>,
450 Settings::OwnUnlock: UnlockAxis<DB, Row>,
451{
452 const SHAPE: Shape = <InboxSpec<Settings> as Axes>::SHAPE;
453
454 type Token = Token<Settings>;
455
456 type Ids = <Own<Settings> as ClaimAxes<DB, Row>>::Ids;
457
458 type Headers = ManualHeaders<Settings::Headers, Row>;
459
460 fn kinds() -> Option<Kinds> {
461 if <InboxSpec<Settings> as Axes>::OWN {
463 return None;
464 }
465 let kinds = KindsOf::new::<<Row as InboxTable>::Id, Source<Settings>>();
466 let kinds = <Settings::Message as MessageAxis<Row>>::kinds(kinds);
467 let kinds = <Settings::Headers as HeadersAxis<Row>>::kinds(kinds);
468 let kinds = <Settings::Key as KeyAxis<Row>>::kinds(kinds);
469 let kinds = <Settings::Attempt as AttemptAxis<DB, Row>>::kinds(kinds);
470 let kinds = <Settings::RetryAfter as TimeAxis<DB>>::kinds(kinds, true);
471 let kinds = <Settings::ProcessedAt as TimeAxis<DB>>::kinds(kinds, false);
472 <Settings::Form as FormAxis>::kinds(kinds).finish()
473 }
474
475 fn id(&self) -> &<Row as InboxTable>::Id {
476 InboxTable::id(self)
477 }
478
479 fn take_headers(&mut self) -> HeaderMap {
480 <Settings::Headers as HeadersAxis<Row>>::take(self)
481 }
482
483 fn unfit_header(headers: &HeaderMap) -> Option<&str> {
484 <Settings::Headers as HeadersAxis<Row>>::unfit(headers)
485 }
486
487 fn partition_key(&self) -> Option<&[u8]> {
488 <Settings::Key as KeyAxis<Row>>::key(self)
489 }
490
491 fn attempt(&self) -> Option<u64> {
492 <Settings::Attempt as AttemptAxis<DB, Row>>::attempt(self)
493 }
494
495 fn read_attempt(row: &DB::Row, _: &'static Queue) -> Option<u64> {
496 let column = Row::TABLE
497 .spec()
498 .column(Role::Attempt)
499 .map(|column| column.name());
500 <Settings::Attempt as AttemptAxis<DB, Row>>::read(row, column)
501 }
502
503 fn bind(
504 param: Param,
505 arguments: &mut DB::Arguments,
506 values: &Values<'_, DB, Self>,
507 ) -> Result<bool, Error> {
508 Ok(match (param, values.event) {
509 (Param::Id, _) => match values.id {
510 Some(id) => {
511 engine::put::<DB, _>(arguments, id)?;
512 true
513 }
514 None => false,
515 },
516 (Param::Group, _) => {
517 DB::bind_str(arguments, values.queue.name)?;
518 true
519 }
520 (Param::Limit, _) => {
521 DB::bind_i64(arguments, values.limit)?;
522 true
523 }
524 (Param::Destination, Event::DeadLetter) => {
525 DB::bind_str(arguments, values.destination)?;
526 true
527 }
528 (Param::Delay, Event::RetryAfter) => {
529 DB::bind_i64(arguments, engine::micros(values.delay))?;
530 true
531 }
532 (Param::Key, _) => match values.key {
533 Some(key) => {
534 DB::bind_str(arguments, key)?;
535 true
536 }
537 None => false,
538 },
539 (Param::Now, Event::Claim | Event::Take) => {
540 <Settings::RetryAfter as TimeAxis<DB>>::now::<Source<Settings>, Self>(
541 arguments, values,
542 )?
543 }
544 (Param::RetryAfter, Event::RetryAfter) => {
545 <Settings::RetryAfter as TimeAxis<DB>>::later::<Source<Settings>, Self>(
546 arguments, values,
547 )?
548 }
549 (Param::Now, Event::Ack | Event::Discard) => {
550 <Settings::ProcessedAt as TimeAxis<DB>>::now::<Source<Settings>, Self>(
551 arguments, values,
552 )?
553 }
554 (Param::LeaseNow | Param::Lease | Param::Held, _) => {
555 <Settings::Form as FormBind<DB>>::bind(param, arguments, values)?
556 }
557 (Param::Ids, Event::Fetch) => {
558 <Own<Settings> as ClaimAxes<DB, Row>>::bind_ids(arguments, values)?
559 }
560 _ => false,
561 })
562 }
563
564 fn lease(queue: &'static Queue, now: Now) -> Result<Leasing<Token<Settings>>, Error> {
565 <Settings::Form as FormAxis>::lease::<Source<Settings>>(queue, now)
566 }
567
568 fn claim<'a>(
569 conn: &'a mut DB::Connection,
570 cx: &'a Claiming,
571 lease: Option<&'a Leasing<Token<Settings>>>,
572 ids: &'a mut Self::Ids,
573 out: &'a mut Vec<Claimed<Self>>,
574 ) -> impl Future<Output = Result<(), Error>> + Send + 'a {
575 <Own<Settings> as ClaimAxes<DB, Row>>::claim(conn, cx, lease, ids, out)
576 }
577
578 fn ack<'a>(
579 conn: &'a mut DB::Connection,
580 cx: &'a Settling,
581 id: &'a <Row as InboxTable>::Id,
582 held: Option<&'a Token<Settings>>,
583 ) -> impl Future<Output = Result<Settled, Error>> + Send + 'a {
584 <Settings::OwnAck as AckAxis<DB, Row>>::ack(conn, cx, id, held)
585 }
586
587 fn retry<'a>(
588 conn: &'a mut DB::Connection,
589 cx: &'a Settling,
590 id: &'a <Row as InboxTable>::Id,
591 held: Option<&'a Token<Settings>>,
592 ) -> impl Future<Output = Result<Settled, Error>> + Send + 'a {
593 <Settings::OwnRetry as RetryAxis<DB, Row>>::retry(conn, cx, id, held)
594 }
595
596 fn retry_after<'a>(
597 conn: &'a mut DB::Connection,
598 cx: &'a Settling,
599 id: &'a <Row as InboxTable>::Id,
600 held: Option<&'a Token<Settings>>,
601 delay: Duration,
602 ) -> impl Future<Output = Result<Settled, Error>> + Send + 'a {
603 <Settings::OwnRetryAfter as RetryAfterAxis<DB, Row>>::retry_after(conn, cx, id, held, delay)
604 }
605
606 fn discard<'a>(
607 conn: &'a mut DB::Connection,
608 cx: &'a Settling,
609 id: &'a <Row as InboxTable>::Id,
610 held: Option<&'a Token<Settings>>,
611 ) -> impl Future<Output = Result<Settled, Error>> + Send + 'a {
612 <Settings::OwnDiscard as DiscardAxis<DB, Row>>::discard(conn, cx, id, held)
613 }
614
615 fn dead_letter<'a>(
616 conn: &'a mut DB::Connection,
617 cx: &'a Settling,
618 id: &'a <Row as InboxTable>::Id,
619 held: Option<&'a Token<Settings>>,
620 destination: &'a str,
621 ) -> impl Future<Output = Result<Settled, Error>> + Send + 'a {
622 <Settings::OwnDeadLetter as DeadLetterAxis<DB, Row>>::dead_letter(
623 conn,
624 cx,
625 id,
626 held,
627 destination,
628 )
629 }
630
631 fn extend<'a>(
632 conn: &'a mut DB::Connection,
633 cx: &'a Settling,
634 id: &'a <Row as InboxTable>::Id,
635 held: &'a Token<Settings>,
636 until: &'a Token<Settings>,
637 ) -> impl Future<Output = Result<Settled, Error>> + Send + 'a {
638 <Settings::OwnExtend as ExtendAxis<DB, Row, Token<Settings>>>::extend(
639 conn, cx, id, held, until,
640 )
641 }
642
643 fn lock<'a>(
644 conn: &'a mut DB::Connection,
645 cx: &'a Claiming,
646 key: &'a str,
647 ) -> impl Future<Output = Result<bool, Error>> + Send + 'a {
648 <Settings::OwnLock as LockAxis<DB, Row>>::lock(conn, cx, key)
649 }
650
651 fn unlock<'a>(
652 conn: &'a mut DB::Connection,
653 cx: &'a Settling,
654 key: &'a str,
655 ) -> impl Future<Output = Result<bool, Error>> + Send + 'a {
656 <Settings::OwnUnlock as UnlockAxis<DB, Row>>::unlock(conn, cx, key)
657 }
658
659 fn take<'a>(
660 conn: &'a mut DB::Connection,
661 cx: &'a Claiming,
662 id: &'a <Row as InboxTable>::Id,
663 out: &'a mut Vec<Claimed<Self>>,
664 ) -> impl Future<Output = Result<bool, Error>> + Send + 'a {
665 <Own<Settings> as ClaimAxes<DB, Row>>::take(conn, cx, id, out)
666 }
667
668 fn candidates<'a>(
669 conn: &'a mut DB::Connection,
670 cx: &'a Claiming,
671 out: &'a mut Candidates<<Row as InboxTable>::Id>,
672 ) -> impl Future<Output = Result<(), Error>> + Send + 'a {
673 advisory::candidates::<DB, Self>(conn, cx, out)
674 }
675}