///|
pub(all) struct FacetCount {
value : String
count : Int
} derive(Eq, @debug.Debug)
///|
pub struct RangeFacet {
label : String
lower : FieldValue?
lower_inclusive : Bool
upper : FieldValue?
upper_inclusive : Bool
}
///|
pub fn RangeFacet::new(
label : String,
lower : FieldValue?,
lower_inclusive : Bool,
upper : FieldValue?,
upper_inclusive : Bool,
) -> RangeFacet {
{ label, lower, lower_inclusive, upper, upper_inclusive }
}
///|
pub(all) struct RangeFacetCount {
label : String
count : Int
} derive(Eq, @debug.Debug)
///|
pub(all) struct HistogramBucket {
key : Double
count : Int
} derive(@debug.Debug)
///|
pub(all) struct NumericAggregation {
count : Int
min : Double?
max : Double?
sum : Double
avg : Double?
histogram : ReadOnlyArray[HistogramBucket]
} derive(@debug.Debug)
///|
fn facet_string(value : FieldValue) -> String? {
match value {
@core.Keyword(value) => Some(value)
@core.Text(value) => Some(value)
@core.Bool(value) => Some(if value { "true" } else { "false" })
_ => None
}
}
///|
pub fn Searcher::terms_facet(
self : Searcher,
query : &Query,
field_id : FieldId,
) -> ReadOnlyArray[FacetCount] {
let counts : Array[FacetCount] = []
for hit in self.all_hits(query) {
let segment = self.segments[hit.address.segment_ord].segment
let seen : Array[String] = []
for value in segment.fast_values(field_id, hit.address.doc_id) {
match facet_string(value) {
Some(value) if seen.search_by(item => item == value) is None => {
seen.push(value)
match counts.search_by(entry => entry.value == value) {
Some(index) =>
counts[index] = { value, count: counts[index].count + 1 }
None => counts.push({ value, count: 1 })
}
}
_ => ()
}
}
}
counts.sort_by((left, right) => {
if left.count != right.count {
right.count.compare(left.count)
} else {
left.value.compare(right.value)
}
})
ReadOnlyArray::from_array(counts)
}
///|
fn range_contains(range : RangeFacet, value : FieldValue) -> Bool {
let lower_matches = match range.lower {
None => true
Some(lower) =>
match compare_field_values(value, lower) {
Some(order) => order > 0 || (range.lower_inclusive && order == 0)
None => false
}
}
let upper_matches = match range.upper {
None => true
Some(upper) =>
match compare_field_values(value, upper) {
Some(order) => order < 0 || (range.upper_inclusive && order == 0)
None => false
}
}
lower_matches && upper_matches
}
///|
pub fn Searcher::range_facet(
self : Searcher,
query : &Query,
field_id : FieldId,
ranges : ReadOnlyArray[RangeFacet],
) -> ReadOnlyArray[RangeFacetCount] {
let counts : Array[RangeFacetCount] = []
for range in ranges {
counts.push({ label: range.label, count: 0 })
}
for hit in self.all_hits(query) {
let segment = self.segments[hit.address.segment_ord].segment
let values = segment.fast_values(field_id, hit.address.doc_id)
for range_index in 0.. range_contains(ranges[range_index], value))
is Some(_) {
counts[range_index] = {
label: counts[range_index].label,
count: counts[range_index].count + 1,
}
}
}
}
ReadOnlyArray::from_array(counts)
}
///|
pub fn Searcher::aggregate(
self : Searcher,
query : &Query,
field_id : FieldId,
histogram_interval : Double?,
) -> NumericAggregation {
match histogram_interval {
Some(interval) => {
guard interval > 0.0 else { abort("histogram interval must be positive") }
}
None => ()
}
let mut count = 0
let mut minimum : Double? = None
let mut maximum : Double? = None
let mut sum = 0.0
let histogram : Array[HistogramBucket] = []
for hit in self.all_hits(query) {
let segment = self.segments[hit.address.segment_ord].segment
for value in segment.fast_values(field_id, hit.address.doc_id) {
match field_value_as_double(value) {
Some(number) => {
count += 1
sum += number
minimum = match minimum {
Some(current) =>
Some(if number < current { number } else { current })
None => Some(number)
}
maximum = match maximum {
Some(current) =>
Some(if number > current { number } else { current })
None => Some(number)
}
match histogram_interval {
Some(interval) => {
let key = @math.floor(number / interval) * interval
match histogram.search_by(bucket => bucket.key == key) {
Some(index) =>
histogram[index] = { key, count: histogram[index].count + 1 }
None => histogram.push({ key, count: 1 })
}
}
None => ()
}
}
None => ()
}
}
}
histogram.sort_by((left, right) => {
if left.key < right.key {
-1
} else if left.key > right.key {
1
} else {
0
}
})
{
count,
min: minimum,
max: maximum,
sum,
avg: if count == 0 {
None
} else {
Some(sum / count.to_double())
},
histogram: ReadOnlyArray::from_array(histogram),
}
}