Skip to content

Commit 0e21380

Browse files
committed
controllers/krate/search: Build query filter for seek-based pagination
...based on direction Most of this should be intuitive, as it simply flips the comparison operator. To make it easier to identify the difference, I chose to implement it in a single function and place it next to the forward ordering conditions. The only part that requires a bit of thought is the case involving nulls last.
1 parent 624ab10 commit 0e21380

File tree

1 file changed

+196
-97
lines changed

1 file changed

+196
-97
lines changed

src/controllers/krate/search.rs

Lines changed: 196 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ pub async fn list_crates(
228228
let seek = seek.unwrap();
229229
if let Some(condition) = seek
230230
.decode(&pagination.page)?
231-
.map(|s| filter_params.seek_after(&s))
231+
.map(|s| filter_params.seek(&s, is_forward))
232232
{
233233
query = query.filter(condition);
234234
}
@@ -506,7 +506,7 @@ impl FilterParams {
506506
query
507507
}
508508

509-
fn seek_after(&self, seek_payload: &seek::SeekPayload) -> BoxedCondition<'_> {
509+
fn seek(&self, seek_payload: &seek::SeekPayload, is_forward: bool) -> BoxedCondition<'_> {
510510
use seek::*;
511511

512512
let crates_aliased = alias!(crates as crates_aliased);
@@ -518,63 +518,85 @@ impl FilterParams {
518518
};
519519
let conditions: Vec<BoxedCondition<'_>> = match *seek_payload {
520520
SeekPayload::Name(Name { id }) => {
521-
// Equivalent of:
522-
// ```
523-
// WHERE name > name'
524-
// ORDER BY name ASC
525-
// ```
526-
vec![Box::new(crates::name.nullable().gt(crate_name_by_id(id)))]
521+
if is_forward {
522+
// Equivalent of:
523+
// ```
524+
// WHERE name > name'
525+
// ORDER BY name ASC
526+
// ```
527+
vec![Box::new(crates::name.nullable().gt(crate_name_by_id(id)))]
528+
} else {
529+
vec![Box::new(crates::name.nullable().lt(crate_name_by_id(id)))]
530+
}
527531
}
528532
SeekPayload::New(New { created_at, id }) => {
529-
// Equivalent of:
530-
// ```
531-
// WHERE (created_at = created_at' AND id < id') OR created_at < created_at'
532-
// ORDER BY created_at DESC, id DESC
533-
// ```
534-
vec![
535-
Box::new(
536-
crates::created_at
537-
.eq(created_at)
538-
.and(crates::id.lt(id))
539-
.nullable(),
540-
),
541-
Box::new(crates::created_at.lt(created_at).nullable()),
542-
]
533+
if is_forward {
534+
// Equivalent of:
535+
// ```
536+
// WHERE (created_at = created_at' AND id < id') OR created_at < created_at'
537+
// ORDER BY created_at DESC, id DESC
538+
// ```
539+
vec![
540+
Box::new(
541+
crates::created_at
542+
.eq(created_at)
543+
.and(crates::id.lt(id))
544+
.nullable(),
545+
),
546+
Box::new(crates::created_at.lt(created_at).nullable()),
547+
]
548+
} else {
549+
vec![
550+
Box::new(
551+
crates::created_at
552+
.eq(created_at)
553+
.and(crates::id.gt(id))
554+
.nullable(),
555+
),
556+
Box::new(crates::created_at.gt(created_at).nullable()),
557+
]
558+
}
543559
}
544560
SeekPayload::RecentUpdates(RecentUpdates { updated_at, id }) => {
545-
// Equivalent of:
546-
// ```
547-
// WHERE (updated_at = updated_at' AND id < id') OR updated_at < updated_at'
548-
// ORDER BY updated_at DESC, id DESC
549-
// ```
550-
vec![
551-
Box::new(
552-
crates::updated_at
553-
.eq(updated_at)
554-
.and(crates::id.lt(id))
555-
.nullable(),
556-
),
557-
Box::new(crates::updated_at.lt(updated_at).nullable()),
558-
]
561+
if is_forward {
562+
// Equivalent of:
563+
// ```
564+
// WHERE (updated_at = updated_at' AND id < id') OR updated_at < updated_at'
565+
// ORDER BY updated_at DESC, id DESC
566+
// ```
567+
vec![
568+
Box::new(
569+
crates::updated_at
570+
.eq(updated_at)
571+
.and(crates::id.lt(id))
572+
.nullable(),
573+
),
574+
Box::new(crates::updated_at.lt(updated_at).nullable()),
575+
]
576+
} else {
577+
vec![
578+
Box::new(
579+
crates::updated_at
580+
.eq(updated_at)
581+
.and(crates::id.gt(id))
582+
.nullable(),
583+
),
584+
Box::new(crates::updated_at.gt(updated_at).nullable()),
585+
]
586+
}
559587
}
560588
SeekPayload::RecentDownloads(RecentDownloads {
561589
recent_downloads,
562590
id,
563591
}) => {
564-
// Equivalent of:
565-
// for recent_downloads is not None:
566-
// ```
567-
// WHERE (recent_downloads = recent_downloads' AND id < id')
568-
// OR (recent_downloads < recent_downloads' OR recent_downloads IS NULL)
569-
// ORDER BY recent_downloads DESC NULLS LAST, id DESC
570-
// ```
571-
// for recent_downloads is None:
572-
// ```
573-
// WHERE (recent_downloads IS NULL AND id < id')
574-
// ORDER BY recent_downloads DESC NULLS LAST, id DESC
575-
// ```
576-
match recent_downloads {
577-
Some(dl) => {
592+
match (recent_downloads, is_forward) {
593+
(Some(dl), true) => {
594+
// Equivalent of:
595+
// ```
596+
// WHERE (recent_downloads = recent_downloads' AND id < id')
597+
// OR (recent_downloads < recent_downloads' OR recent_downloads IS NULL)
598+
// ORDER BY recent_downloads DESC NULLS LAST, id DESC
599+
// ```
578600
vec![
579601
Box::new(
580602
recent_crate_downloads::downloads
@@ -590,80 +612,157 @@ impl FilterParams {
590612
),
591613
]
592614
}
593-
None => {
615+
(None, true) => {
616+
// Equivalent of:
617+
// ```
618+
// WHERE (recent_downloads IS NULL AND id < id')
619+
// ORDER BY recent_downloads DESC NULLS LAST, id DESC
620+
// ```
594621
vec![Box::new(
595622
recent_crate_downloads::downloads
596623
.is_null()
597624
.and(crates::id.lt(id))
598625
.nullable(),
599626
)]
600627
}
628+
(Some(dl), false) => {
629+
// Equivalent of:
630+
// ```
631+
// WHERE (recent_downloads = recent_downloads' AND id > id')
632+
// OR (recent_downloads > recent_downloads')
633+
// ORDER BY recent_downloads ASC NULLS FIRST, id ASC
634+
// ```
635+
vec![
636+
Box::new(
637+
recent_crate_downloads::downloads
638+
.eq(dl)
639+
.and(crates::id.gt(id))
640+
.nullable(),
641+
),
642+
Box::new(recent_crate_downloads::downloads.gt(dl).nullable()),
643+
]
644+
}
645+
(None, false) => {
646+
// Equivalent of:
647+
// ```
648+
// WHERE (recent_downloads IS NULL AND id > id')
649+
// OR (recent_downloads IS NOT NULL)
650+
// ORDER BY recent_downloads ASC NULLS FIRST, id ASC
651+
// ```
652+
vec![
653+
Box::new(
654+
recent_crate_downloads::downloads
655+
.is_null()
656+
.and(crates::id.gt(id))
657+
.nullable(),
658+
),
659+
Box::new(recent_crate_downloads::downloads.is_not_null().nullable()),
660+
]
661+
}
601662
}
602663
}
603664
SeekPayload::Downloads(Downloads { downloads, id }) => {
604-
// Equivalent of:
605-
// ```
606-
// WHERE (downloads = downloads' AND id < id') OR downloads < downloads'
607-
// ORDER BY downloads DESC, id DESC
608-
// ```
609-
vec![
610-
Box::new(
611-
crate_downloads::downloads
612-
.eq(downloads)
613-
.and(crates::id.lt(id))
614-
.nullable(),
615-
),
616-
Box::new(crate_downloads::downloads.lt(downloads).nullable()),
617-
]
665+
if is_forward {
666+
// Equivalent of:
667+
// ```
668+
// WHERE (downloads = downloads' AND id < id') OR downloads < downloads'
669+
// ORDER BY downloads DESC, id DESC
670+
// ```
671+
vec![
672+
Box::new(
673+
crate_downloads::downloads
674+
.eq(downloads)
675+
.and(crates::id.lt(id))
676+
.nullable(),
677+
),
678+
Box::new(crate_downloads::downloads.lt(downloads).nullable()),
679+
]
680+
} else {
681+
vec![
682+
Box::new(
683+
crate_downloads::downloads
684+
.eq(downloads)
685+
.and(crates::id.gt(id))
686+
.nullable(),
687+
),
688+
Box::new(crate_downloads::downloads.gt(downloads).nullable()),
689+
]
690+
}
618691
}
619692
SeekPayload::Query(Query { exact_match, id }) => {
620-
// Equivalent of:
621-
// ```
622-
// WHERE (exact_match = exact_match' AND name > name') OR exact_match < exact_match'
623-
// ORDER BY exact_match DESC, NAME ASC
624-
// ```
625693
let q_string = self.q_string.as_ref().expect("q_string should not be None");
626694
let name_exact_match = Crate::with_name(q_string);
627-
vec![
628-
Box::new(
629-
name_exact_match
630-
.eq(exact_match)
631-
.and(crates::name.nullable().gt(crate_name_by_id(id)))
632-
.nullable(),
633-
),
634-
Box::new(name_exact_match.lt(exact_match).nullable()),
635-
]
695+
if is_forward {
696+
// Equivalent of:
697+
// ```
698+
// WHERE (exact_match = exact_match' AND name > name') OR exact_match < exact_match'
699+
// ORDER BY exact_match DESC, NAME ASC
700+
// ```
701+
vec![
702+
Box::new(
703+
name_exact_match
704+
.eq(exact_match)
705+
.and(crates::name.nullable().gt(crate_name_by_id(id)))
706+
.nullable(),
707+
),
708+
Box::new(name_exact_match.lt(exact_match).nullable()),
709+
]
710+
} else {
711+
vec![
712+
Box::new(
713+
name_exact_match
714+
.eq(exact_match)
715+
.and(crates::name.nullable().lt(crate_name_by_id(id)))
716+
.nullable(),
717+
),
718+
Box::new(name_exact_match.gt(exact_match).nullable()),
719+
]
720+
}
636721
}
637722
SeekPayload::Relevance(Relevance {
638723
exact_match: exact,
639724
rank: rank_in,
640725
id,
641726
}) => {
642-
// Equivalent of:
643-
// ```
644-
// WHERE (exact_match = exact_match' AND rank = rank' AND name > name')
645-
// OR (exact_match = exact_match' AND rank < rank')
646-
// OR exact_match < exact_match'
647-
// ORDER BY exact_match DESC, rank DESC, name ASC
648-
// ```
649727
let q_string = self.q_string.as_ref().expect("q_string should not be None");
650728
let q = plainto_tsquery_with_search_config(
651729
TsConfigurationByName("english"),
652730
q_string.as_str(),
653731
);
654732
let rank = ts_rank_cd(crates::textsearchable_index_col, q);
655733
let name_exact_match = Crate::with_name(q_string.as_str());
656-
vec![
657-
Box::new(
658-
name_exact_match
659-
.eq(exact)
660-
.and(rank.eq(rank_in))
661-
.and(crates::name.nullable().gt(crate_name_by_id(id)))
662-
.nullable(),
663-
),
664-
Box::new(name_exact_match.eq(exact).and(rank.lt(rank_in)).nullable()),
665-
Box::new(name_exact_match.lt(exact).nullable()),
666-
]
734+
if is_forward {
735+
// Equivalent of:
736+
// ```
737+
// WHERE (exact_match = exact_match' AND rank = rank' AND name > name')
738+
// OR (exact_match = exact_match' AND rank < rank')
739+
// OR exact_match < exact_match'
740+
// ORDER BY exact_match DESC, rank DESC, name ASC
741+
// ```
742+
vec![
743+
Box::new(
744+
name_exact_match
745+
.eq(exact)
746+
.and(rank.eq(rank_in))
747+
.and(crates::name.nullable().gt(crate_name_by_id(id)))
748+
.nullable(),
749+
),
750+
Box::new(name_exact_match.eq(exact).and(rank.lt(rank_in)).nullable()),
751+
Box::new(name_exact_match.lt(exact).nullable()),
752+
]
753+
} else {
754+
vec![
755+
Box::new(
756+
name_exact_match
757+
.eq(exact)
758+
.and(rank.eq(rank_in))
759+
.and(crates::name.nullable().lt(crate_name_by_id(id)))
760+
.nullable(),
761+
),
762+
Box::new(name_exact_match.eq(exact).and(rank.gt(rank_in)).nullable()),
763+
Box::new(name_exact_match.gt(exact).nullable()),
764+
]
765+
}
667766
}
668767
};
669768

0 commit comments

Comments
 (0)