tangled
alpha
login
or
join now
microcosm.blue
/
microcosm-rs
65
fork
atom
Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm
65
fork
atom
overview
issues
8
pulls
2
pipelines
implement top collections over arbitrary times
bad-example.com
9 months ago
91baf420
31ac134e
+225
-235
5 changed files
expand all
collapse all
unified
split
ufos
src
server.rs
storage.rs
storage_fjall.rs
storage_mem.rs
store_types.rs
+1
-76
ufos/src/server.rs
···
284
284
method = GET,
285
285
path = "/collections"
286
286
}]
287
287
-
/// Get a list of collection NSIDs with statistics
287
287
+
/// Get collection with statistics
288
288
///
289
289
/// ## To fetch a full list:
290
290
///
···
356
356
})
357
357
}
358
358
359
359
-
#[derive(Debug, Deserialize, JsonSchema)]
360
360
-
struct TopByQuery {
361
361
-
/// The maximum number of collections to return in one request.
362
362
-
///
363
363
-
/// Default: 32
364
364
-
#[schemars(range(min = 1, max = 200), default = "top_collections_default_limit")]
365
365
-
#[serde(default = "top_collections_default_limit")]
366
366
-
limit: usize,
367
367
-
/// Limit collections and statistics to those seen after this UTC datetime
368
368
-
since: Option<DateTime<Utc>>,
369
369
-
/// Limit collections and statistics to those seen before this UTC datetime
370
370
-
until: Option<DateTime<Utc>>,
371
371
-
}
372
372
-
fn top_collections_default_limit() -> usize {
373
373
-
32
374
374
-
}
375
375
-
376
376
-
/// Get top collections by record count
377
377
-
#[endpoint {
378
378
-
method = GET,
379
379
-
path = "/collections/by-count"
380
380
-
}]
381
381
-
async fn get_top_collections_by_count(
382
382
-
ctx: RequestContext<Context>,
383
383
-
query: Query<TopByQuery>,
384
384
-
) -> OkCorsResponse<Vec<NsidCount>> {
385
385
-
let Context { storage, .. } = ctx.context();
386
386
-
let q = query.into_inner();
387
387
-
388
388
-
if !(1..=200).contains(&q.limit) {
389
389
-
let msg = format!("limit not in 1..=200: {}", q.limit);
390
390
-
return Err(HttpError::for_bad_request(None, msg));
391
391
-
}
392
392
-
393
393
-
let since = q.since.map(dt_to_cursor).transpose()?;
394
394
-
let until = q.until.map(dt_to_cursor).transpose()?;
395
395
-
396
396
-
let collections = storage
397
397
-
.get_top_collections_by_count(100, since, until)
398
398
-
.await
399
399
-
.map_err(|e| HttpError::for_internal_error(format!("oh shoot: {e:?}")))?;
400
400
-
401
401
-
ok_cors(collections)
402
402
-
}
403
403
-
404
404
-
/// Get top collections by estimated unique DIDs
405
405
-
#[endpoint {
406
406
-
method = GET,
407
407
-
path = "/collections/by-dids"
408
408
-
}]
409
409
-
async fn get_top_collections_by_dids(
410
410
-
ctx: RequestContext<Context>,
411
411
-
query: Query<TopByQuery>,
412
412
-
) -> OkCorsResponse<Vec<NsidCount>> {
413
413
-
let Context { storage, .. } = ctx.context();
414
414
-
let q = query.into_inner();
415
415
-
416
416
-
if !(1..=200).contains(&q.limit) {
417
417
-
let msg = format!("limit not in 1..=200: {}", q.limit);
418
418
-
return Err(HttpError::for_bad_request(None, msg));
419
419
-
}
420
420
-
421
421
-
let since = q.since.map(dt_to_cursor).transpose()?;
422
422
-
let until = q.until.map(dt_to_cursor).transpose()?;
423
423
-
424
424
-
let collections = storage
425
425
-
.get_top_collections_by_dids(100, since, until)
426
426
-
.await
427
427
-
.map_err(|e| HttpError::for_internal_error(format!("oh shoot: {e:?}")))?;
428
428
-
429
429
-
ok_cors(collections)
430
430
-
}
431
431
-
432
359
pub async fn serve(storage: impl StoreReader + 'static) -> Result<(), String> {
433
360
let log = ConfigLogging::StderrTerminal {
434
361
level: ConfigLoggingLevel::Info,
···
444
371
api.register(get_records_by_collections).unwrap();
445
372
api.register(get_records_total_seen).unwrap();
446
373
api.register(get_collections).unwrap();
447
447
-
api.register(get_top_collections_by_count).unwrap();
448
448
-
api.register(get_top_collections_by_dids).unwrap();
449
374
450
375
let context = Context {
451
376
spec: Arc::new(
-14
ufos/src/storage.rs
···
84
84
until: Option<HourTruncatedCursor>,
85
85
) -> StorageResult<(Vec<NsidCount>, Option<Vec<u8>>)>;
86
86
87
87
-
async fn get_top_collections_by_count(
88
88
-
&self,
89
89
-
limit: usize,
90
90
-
since: Option<HourTruncatedCursor>,
91
91
-
until: Option<HourTruncatedCursor>,
92
92
-
) -> StorageResult<Vec<NsidCount>>;
93
93
-
94
94
-
async fn get_top_collections_by_dids(
95
95
-
&self,
96
96
-
limit: usize,
97
97
-
since: Option<HourTruncatedCursor>,
98
98
-
until: Option<HourTruncatedCursor>,
99
99
-
) -> StorageResult<Vec<NsidCount>>;
100
100
-
101
87
async fn get_counts_by_collection(&self, collection: &Nsid) -> StorageResult<(u64, u64)>;
102
88
103
89
async fn get_records_by_collections(
+187
-129
ufos/src/storage_fjall.rs
···
1
1
-
use crate::db_types::{db_complete, DbBytes, DbStaticStr, StaticStr};
1
1
+
use crate::db_types::{db_complete, DbBytes, DbStaticStr, EncodingResult, StaticStr};
2
2
use crate::error::StorageError;
3
3
use crate::storage::{StorageResult, StorageWhatever, StoreBackground, StoreReader, StoreWriter};
4
4
use crate::store_types::{
···
9
9
NewRollupCursorKey, NewRollupCursorValue, NsidRecordFeedKey, NsidRecordFeedVal,
10
10
RecordLocationKey, RecordLocationMeta, RecordLocationVal, RecordRawValue, SketchSecretKey,
11
11
SketchSecretPrefix, TakeoffKey, TakeoffValue, TrimCollectionCursorKey, WeekTruncatedCursor,
12
12
-
WeeklyDidsKey, WeeklyRecordsKey, WeeklyRollupKey, WithCollection,
12
12
+
WeeklyDidsKey, WeeklyRecordsKey, WeeklyRollupKey, WithCollection, WithRank,
13
13
};
14
14
use crate::{
15
15
CommitAction, ConsumerInfo, Did, EventBatch, Nsid, NsidCount, OrderCollectionsBy, UFOsRecord,
···
346
346
Ok((nsid, get_counts))
347
347
})))
348
348
}
349
349
+
type GetRollupKey = Arc<dyn Fn(&Nsid) -> EncodingResult<Vec<u8>>>;
350
350
+
fn get_lookup_iter<T: WithCollection + WithRank + DbBytes + 'static>(
351
351
+
snapshot: lsm_tree::Snapshot,
352
352
+
start: Bound<Vec<u8>>,
353
353
+
end: Bound<Vec<u8>>,
354
354
+
get_rollup_key: GetRollupKey,
355
355
+
) -> StorageResult<NsidCounter> {
356
356
+
Ok(Box::new(snapshot.range((start, end)).rev().map(
357
357
+
move |kv| {
358
358
+
let (k_bytes, _) = kv?;
359
359
+
let key = db_complete::<T>(&k_bytes)?;
360
360
+
let nsid = key.collection().clone();
361
361
+
let get_counts: GetCounts = Box::new({
362
362
+
let nsid = nsid.clone();
363
363
+
let snapshot = snapshot.clone();
364
364
+
let get_rollup_key = get_rollup_key.clone();
365
365
+
move || {
366
366
+
let db_count_bytes = snapshot.get(get_rollup_key(&nsid)?)?.expect(
367
367
+
"integrity: all-time rank rollup must have corresponding all-time count rollup",
368
368
+
);
369
369
+
Ok(db_complete::<CountsValue>(&db_count_bytes)?)
370
370
+
}
371
371
+
});
372
372
+
Ok((nsid, get_counts))
373
373
+
},
374
374
+
)))
375
375
+
}
349
376
350
377
impl FjallReader {
351
378
fn get_storage_stats(&self) -> StorageResult<serde_json::Value> {
···
406
433
Ok(cursor)
407
434
}
408
435
409
409
-
fn get_collections(
436
436
+
fn get_lexi_collections(
410
437
&self,
438
438
+
snapshot: Snapshot,
411
439
limit: usize,
412
412
-
order: OrderCollectionsBy,
413
413
-
since: Option<HourTruncatedCursor>,
414
414
-
until: Option<HourTruncatedCursor>,
440
440
+
cursor: Option<Vec<u8>>,
441
441
+
buckets: Vec<CursorBucket>,
415
442
) -> StorageResult<(Vec<NsidCount>, Option<Vec<u8>>)> {
416
416
-
let snapshot = self.rollups.snapshot();
417
417
-
418
418
-
let buckets = if let (None, None) = (since, until) {
419
419
-
vec![CursorBucket::AllTime]
420
420
-
} else {
421
421
-
let mut lower = self.get_earliest_hour(Some(&snapshot))?;
422
422
-
if let Some(specified) = since {
423
423
-
if specified > lower {
424
424
-
lower = specified;
425
425
-
}
426
426
-
}
427
427
-
let upper = until.unwrap_or_else(|| Cursor::at(SystemTime::now()).into());
428
428
-
CursorBucket::buckets_spanning(lower, upper)
429
429
-
};
430
430
-
443
443
+
let cursor_nsid = cursor.as_deref().map(db_complete::<Nsid>).transpose()?;
431
444
let mut iters: Vec<Peekable<NsidCounter>> = Vec::with_capacity(buckets.len());
432
432
-
433
433
-
match order {
434
434
-
OrderCollectionsBy::Lexi { cursor } => {
435
435
-
let cursor_nsid = cursor.as_deref().map(db_complete::<Nsid>).transpose()?;
436
436
-
for bucket in &buckets {
437
437
-
let it: NsidCounter = match bucket {
438
438
-
CursorBucket::Hour(t) => {
439
439
-
let start = cursor_nsid
440
440
-
.as_ref()
441
441
-
.map(|nsid| HourlyRollupKey::after_nsid(*t, nsid))
442
442
-
.unwrap_or_else(|| HourlyRollupKey::start(*t))?;
443
443
-
let end = HourlyRollupKey::end(*t)?;
444
444
-
get_lexi_iter::<HourlyRollupKey>(&snapshot, start, end)?
445
445
-
}
446
446
-
CursorBucket::Week(t) => {
447
447
-
let start = cursor_nsid
448
448
-
.as_ref()
449
449
-
.map(|nsid| WeeklyRollupKey::after_nsid(*t, nsid))
450
450
-
.unwrap_or_else(|| WeeklyRollupKey::start(*t))?;
451
451
-
let end = WeeklyRollupKey::end(*t)?;
452
452
-
get_lexi_iter::<WeeklyRollupKey>(&snapshot, start, end)?
453
453
-
}
454
454
-
CursorBucket::AllTime => {
455
455
-
let start = cursor_nsid
456
456
-
.as_ref()
457
457
-
.map(AllTimeRollupKey::after_nsid)
458
458
-
.unwrap_or_else(AllTimeRollupKey::start)?;
459
459
-
let end = AllTimeRollupKey::end()?;
460
460
-
get_lexi_iter::<AllTimeRollupKey>(&snapshot, start, end)?
461
461
-
}
462
462
-
};
463
463
-
iters.push(it.peekable());
445
445
+
for bucket in &buckets {
446
446
+
let it: NsidCounter = match bucket {
447
447
+
CursorBucket::Hour(t) => {
448
448
+
let start = cursor_nsid
449
449
+
.as_ref()
450
450
+
.map(|nsid| HourlyRollupKey::after_nsid(*t, nsid))
451
451
+
.unwrap_or_else(|| HourlyRollupKey::start(*t))?;
452
452
+
let end = HourlyRollupKey::end(*t)?;
453
453
+
get_lexi_iter::<HourlyRollupKey>(&snapshot, start, end)?
464
454
}
465
465
-
}
466
466
-
OrderCollectionsBy::RecordsCreated => todo!(),
467
467
-
OrderCollectionsBy::DidsEstimate => todo!(),
455
455
+
CursorBucket::Week(t) => {
456
456
+
let start = cursor_nsid
457
457
+
.as_ref()
458
458
+
.map(|nsid| WeeklyRollupKey::after_nsid(*t, nsid))
459
459
+
.unwrap_or_else(|| WeeklyRollupKey::start(*t))?;
460
460
+
let end = WeeklyRollupKey::end(*t)?;
461
461
+
get_lexi_iter::<WeeklyRollupKey>(&snapshot, start, end)?
462
462
+
}
463
463
+
CursorBucket::AllTime => {
464
464
+
let start = cursor_nsid
465
465
+
.as_ref()
466
466
+
.map(AllTimeRollupKey::after_nsid)
467
467
+
.unwrap_or_else(AllTimeRollupKey::start)?;
468
468
+
let end = AllTimeRollupKey::end()?;
469
469
+
get_lexi_iter::<AllTimeRollupKey>(&snapshot, start, end)?
470
470
+
}
471
471
+
};
472
472
+
iters.push(it.peekable());
468
473
}
469
474
470
475
let mut out = Vec::new();
···
508
513
Ok((out, next_cursor))
509
514
}
510
515
511
511
-
fn get_top_collections_by_count(
516
516
+
fn get_ordered_collections(
512
517
&self,
518
518
+
snapshot: Snapshot,
513
519
limit: usize,
514
514
-
since: Option<HourTruncatedCursor>,
515
515
-
until: Option<HourTruncatedCursor>,
520
520
+
order: OrderCollectionsBy,
521
521
+
buckets: Vec<CursorBucket>,
516
522
) -> StorageResult<Vec<NsidCount>> {
517
517
-
Ok(if since.is_none() && until.is_none() {
518
518
-
let snapshot = self.rollups.snapshot();
519
519
-
let mut out = Vec::with_capacity(limit);
520
520
-
let prefix = AllTimeRecordsKey::from_prefix_to_db_bytes(&Default::default())?;
521
521
-
for kv in snapshot.prefix(prefix).rev().take(limit) {
522
522
-
let (key_bytes, _) = kv?;
523
523
-
let key = db_complete::<AllTimeRecordsKey>(&key_bytes)?;
524
524
-
let rollup_key = AllTimeRollupKey::new(key.collection());
525
525
-
let db_count_bytes = snapshot.get(rollup_key.to_db_bytes()?)?.expect(
526
526
-
"integrity: all-time rank rollup must have corresponding all-time count rollup",
527
527
-
);
528
528
-
let db_counts = db_complete::<CountsValue>(&db_count_bytes)?;
529
529
-
assert_eq!(db_counts.records(), key.count());
530
530
-
out.push(NsidCount {
531
531
-
nsid: key.collection().to_string(),
532
532
-
records: db_counts.records(),
533
533
-
dids_estimate: db_counts.dids().estimate() as u64,
534
534
-
});
523
523
+
let mut iters: Vec<NsidCounter> = Vec::with_capacity(buckets.len());
524
524
+
525
525
+
for bucket in buckets {
526
526
+
let it: NsidCounter = match (&order, bucket) {
527
527
+
(OrderCollectionsBy::RecordsCreated, CursorBucket::Hour(t)) => {
528
528
+
get_lookup_iter::<HourlyRecordsKey>(
529
529
+
snapshot.clone(),
530
530
+
HourlyRecordsKey::start(t)?,
531
531
+
HourlyRecordsKey::end(t)?,
532
532
+
Arc::new({
533
533
+
move |collection| HourlyRollupKey::new(t, collection).to_db_bytes()
534
534
+
}),
535
535
+
)?
536
536
+
}
537
537
+
(OrderCollectionsBy::DidsEstimate, CursorBucket::Hour(t)) => {
538
538
+
get_lookup_iter::<HourlyDidsKey>(
539
539
+
snapshot.clone(),
540
540
+
HourlyDidsKey::start(t)?,
541
541
+
HourlyDidsKey::end(t)?,
542
542
+
Arc::new({
543
543
+
move |collection| HourlyRollupKey::new(t, collection).to_db_bytes()
544
544
+
}),
545
545
+
)?
546
546
+
}
547
547
+
(OrderCollectionsBy::RecordsCreated, CursorBucket::Week(t)) => {
548
548
+
get_lookup_iter::<WeeklyRecordsKey>(
549
549
+
snapshot.clone(),
550
550
+
WeeklyRecordsKey::start(t)?,
551
551
+
WeeklyRecordsKey::end(t)?,
552
552
+
Arc::new({
553
553
+
move |collection| WeeklyRollupKey::new(t, collection).to_db_bytes()
554
554
+
}),
555
555
+
)?
556
556
+
}
557
557
+
(OrderCollectionsBy::DidsEstimate, CursorBucket::Week(t)) => {
558
558
+
get_lookup_iter::<WeeklyDidsKey>(
559
559
+
snapshot.clone(),
560
560
+
WeeklyDidsKey::start(t)?,
561
561
+
WeeklyDidsKey::end(t)?,
562
562
+
Arc::new({
563
563
+
move |collection| WeeklyRollupKey::new(t, collection).to_db_bytes()
564
564
+
}),
565
565
+
)?
566
566
+
}
567
567
+
(OrderCollectionsBy::RecordsCreated, CursorBucket::AllTime) => {
568
568
+
get_lookup_iter::<AllTimeRecordsKey>(
569
569
+
snapshot.clone(),
570
570
+
AllTimeRecordsKey::start()?,
571
571
+
AllTimeRecordsKey::end()?,
572
572
+
Arc::new(|collection| AllTimeRollupKey::new(collection).to_db_bytes()),
573
573
+
)?
574
574
+
}
575
575
+
(OrderCollectionsBy::DidsEstimate, CursorBucket::AllTime) => {
576
576
+
get_lookup_iter::<AllTimeDidsKey>(
577
577
+
snapshot.clone(),
578
578
+
AllTimeDidsKey::start()?,
579
579
+
AllTimeDidsKey::end()?,
580
580
+
Arc::new(|collection| AllTimeRollupKey::new(collection).to_db_bytes()),
581
581
+
)?
582
582
+
}
583
583
+
(OrderCollectionsBy::Lexi { .. }, _) => unreachable!(),
584
584
+
};
585
585
+
iters.push(it);
586
586
+
}
587
587
+
588
588
+
// overfetch by taking a bit more than the limit
589
589
+
// merge by collection
590
590
+
// sort by requested order, take limit, discard all remaining
591
591
+
//
592
592
+
// this isn't guaranteed to be correct, but it will hopefully be close most of the time:
593
593
+
// - it's possible that some NSIDs might score low during some time-buckets, and miss being merged
594
594
+
// - overfetching hopefully helps a bit by catching nsids near the threshold more often, but. yeah.
595
595
+
//
596
596
+
// this thing is heavy, there's probably a better way
597
597
+
let mut ranked: HashMap<Nsid, CountsValue> = HashMap::with_capacity(limit * 2);
598
598
+
for iter in iters {
599
599
+
for pair in iter.take((limit as f64 * 1.3).ceil() as usize) {
600
600
+
let (nsid, get_counts) = pair?;
601
601
+
let counts = get_counts()?;
602
602
+
ranked.entry(nsid).or_default().merge(&counts);
535
603
}
536
536
-
out
537
537
-
} else {
538
538
-
todo!()
539
539
-
})
604
604
+
}
605
605
+
let mut ranked: Vec<(Nsid, CountsValue)> = ranked.into_iter().collect();
606
606
+
match order {
607
607
+
OrderCollectionsBy::RecordsCreated => ranked.sort_by_key(|(_, c)| c.records()),
608
608
+
OrderCollectionsBy::DidsEstimate => ranked.sort_by_key(|(_, c)| c.dids().estimate()),
609
609
+
OrderCollectionsBy::Lexi { .. } => unreachable!(),
610
610
+
}
611
611
+
let counts = ranked
612
612
+
.into_iter()
613
613
+
.rev()
614
614
+
.take(limit)
615
615
+
.map(|(nsid, cv)| NsidCount {
616
616
+
nsid: nsid.to_string(),
617
617
+
records: cv.records(),
618
618
+
dids_estimate: cv.dids().estimate() as u64,
619
619
+
})
620
620
+
.collect();
621
621
+
Ok(counts)
540
622
}
541
623
542
542
-
fn get_top_collections_by_dids(
624
624
+
fn get_collections(
543
625
&self,
544
626
limit: usize,
627
627
+
order: OrderCollectionsBy,
545
628
since: Option<HourTruncatedCursor>,
546
629
until: Option<HourTruncatedCursor>,
547
547
-
) -> StorageResult<Vec<NsidCount>> {
548
548
-
Ok(if since.is_none() && until.is_none() {
549
549
-
let snapshot = self.rollups.snapshot();
550
550
-
let mut out = Vec::with_capacity(limit);
551
551
-
let prefix = AllTimeDidsKey::from_prefix_to_db_bytes(&Default::default())?;
552
552
-
for kv in snapshot.prefix(prefix).rev().take(limit) {
553
553
-
let (key_bytes, _) = kv?;
554
554
-
let key = db_complete::<AllTimeDidsKey>(&key_bytes)?;
555
555
-
let rollup_key = AllTimeRollupKey::new(key.collection());
556
556
-
let db_count_bytes = snapshot.get(rollup_key.to_db_bytes()?)?.expect(
557
557
-
"integrity: all-time rank rollup must have corresponding all-time count rollup",
558
558
-
);
559
559
-
let db_counts = db_complete::<CountsValue>(&db_count_bytes)?;
560
560
-
assert_eq!(db_counts.dids().estimate() as u64, key.count());
561
561
-
out.push(NsidCount {
562
562
-
nsid: key.collection().to_string(),
563
563
-
records: db_counts.records(),
564
564
-
dids_estimate: db_counts.dids().estimate() as u64,
565
565
-
});
630
630
+
) -> StorageResult<(Vec<NsidCount>, Option<Vec<u8>>)> {
631
631
+
let snapshot = self.rollups.snapshot();
632
632
+
let buckets = if let (None, None) = (since, until) {
633
633
+
vec![CursorBucket::AllTime]
634
634
+
} else {
635
635
+
let mut lower = self.get_earliest_hour(Some(&snapshot))?;
636
636
+
if let Some(specified) = since {
637
637
+
if specified > lower {
638
638
+
lower = specified;
639
639
+
}
640
640
+
}
641
641
+
let upper = until.unwrap_or_else(|| Cursor::at(SystemTime::now()).into());
642
642
+
CursorBucket::buckets_spanning(lower, upper)
643
643
+
};
644
644
+
match order {
645
645
+
OrderCollectionsBy::Lexi { cursor } => {
646
646
+
self.get_lexi_collections(snapshot, limit, cursor, buckets)
566
647
}
567
567
-
out
568
568
-
} else {
569
569
-
todo!()
570
570
-
})
648
648
+
_ => Ok((
649
649
+
self.get_ordered_collections(snapshot, limit, order, buckets)?,
650
650
+
None,
651
651
+
)),
652
652
+
}
571
653
}
572
654
573
655
fn get_counts_by_collection(&self, collection: &Nsid) -> StorageResult<(u64, u64)> {
···
679
761
let s = self.clone();
680
762
tokio::task::spawn_blocking(move || {
681
763
FjallReader::get_collections(&s, limit, order, since, until)
682
682
-
})
683
683
-
.await?
684
684
-
}
685
685
-
async fn get_top_collections_by_count(
686
686
-
&self,
687
687
-
limit: usize,
688
688
-
since: Option<HourTruncatedCursor>,
689
689
-
until: Option<HourTruncatedCursor>,
690
690
-
) -> StorageResult<Vec<NsidCount>> {
691
691
-
let s = self.clone();
692
692
-
tokio::task::spawn_blocking(move || {
693
693
-
FjallReader::get_top_collections_by_count(&s, limit, since, until)
694
694
-
})
695
695
-
.await?
696
696
-
}
697
697
-
async fn get_top_collections_by_dids(
698
698
-
&self,
699
699
-
limit: usize,
700
700
-
since: Option<HourTruncatedCursor>,
701
701
-
until: Option<HourTruncatedCursor>,
702
702
-
) -> StorageResult<Vec<NsidCount>> {
703
703
-
let s = self.clone();
704
704
-
tokio::task::spawn_blocking(move || {
705
705
-
FjallReader::get_top_collections_by_dids(&s, limit, since, until)
706
764
})
707
765
.await?
708
766
}
-16
ufos/src/storage_mem.rs
···
556
556
) -> StorageResult<(Vec<NsidCount>, Option<Vec<u8>>)> {
557
557
todo!()
558
558
}
559
559
-
async fn get_top_collections_by_count(
560
560
-
&self,
561
561
-
_: usize,
562
562
-
_: Option<HourTruncatedCursor>,
563
563
-
_: Option<HourTruncatedCursor>,
564
564
-
) -> StorageResult<Vec<NsidCount>> {
565
565
-
todo!()
566
566
-
}
567
567
-
async fn get_top_collections_by_dids(
568
568
-
&self,
569
569
-
_: usize,
570
570
-
_: Option<HourTruncatedCursor>,
571
571
-
_: Option<HourTruncatedCursor>,
572
572
-
) -> StorageResult<Vec<NsidCount>> {
573
573
-
todo!()
574
574
-
}
575
559
async fn get_counts_by_collection(&self, collection: &Nsid) -> StorageResult<(u64, u64)> {
576
560
let s = self.clone();
577
561
let collection = collection.clone();
+37
ufos/src/store_types.rs
···
69
69
fn collection(&self) -> &Nsid;
70
70
}
71
71
72
72
+
pub trait WithRank {
73
73
+
fn rank(&self) -> u64;
74
74
+
}
75
75
+
72
76
pub type NsidRecordFeedKey = DbConcat<Nsid, Cursor>;
73
77
impl NsidRecordFeedKey {
74
78
pub fn collection(&self) -> &Nsid {
···
313
317
pub fn with_rank(&self, new_rank: KeyRank) -> Self {
314
318
Self::new(self.prefix.suffix.clone(), new_rank, &self.suffix.suffix)
315
319
}
320
320
+
pub fn start(cursor: C) -> EncodingResult<Bound<Vec<u8>>> {
321
321
+
let prefix: DbConcat<DbStaticStr<P>, C> = DbConcat::from_pair(Default::default(), cursor);
322
322
+
Ok(Bound::Included(Self::from_prefix_to_db_bytes(&prefix)?))
323
323
+
}
324
324
+
pub fn end(cursor: C) -> EncodingResult<Bound<Vec<u8>>> {
325
325
+
let prefix: DbConcat<DbStaticStr<P>, C> = DbConcat::from_pair(Default::default(), cursor);
326
326
+
Ok(Bound::Excluded(Self::prefix_range_end(&prefix)?))
327
327
+
}
328
328
+
}
329
329
+
impl<P: StaticStr, C: DbBytes> WithCollection for BucketedRankRecordsKey<P, C> {
330
330
+
fn collection(&self) -> &Nsid {
331
331
+
&self.suffix.suffix
332
332
+
}
333
333
+
}
334
334
+
impl<P: StaticStr, C: DbBytes> WithRank for BucketedRankRecordsKey<P, C> {
335
335
+
fn rank(&self) -> u64 {
336
336
+
self.suffix.prefix.into()
337
337
+
}
316
338
}
317
339
318
340
static_str!("hourly_counts", _HourlyRollupStaticStr);
···
437
459
pub fn count(&self) -> u64 {
438
460
self.suffix.prefix.0
439
461
}
462
462
+
pub fn start() -> EncodingResult<Bound<Vec<u8>>> {
463
463
+
Ok(Bound::Included(Self::from_prefix_to_db_bytes(
464
464
+
&Default::default(),
465
465
+
)?))
466
466
+
}
467
467
+
pub fn end() -> EncodingResult<Bound<Vec<u8>>> {
468
468
+
Ok(Bound::Excluded(
469
469
+
Self::prefix_range_end(&Default::default())?,
470
470
+
))
471
471
+
}
440
472
}
441
473
impl<P: StaticStr> WithCollection for AllTimeRankRecordsKey<P> {
442
474
fn collection(&self) -> &Nsid {
443
475
&self.suffix.suffix
476
476
+
}
477
477
+
}
478
478
+
impl<P: StaticStr> WithRank for AllTimeRankRecordsKey<P> {
479
479
+
fn rank(&self) -> u64 {
480
480
+
self.suffix.prefix.into()
444
481
}
445
482
}
446
483