use std::marker::PhantomData;
use serde_json::{Map, Value};
use super::{Geo, GeoPoint, NumericType, ScriptSortType};
use crate::query::{AsQuery, Root};
use crate::{FlussoDocument, nested_boundaries};
#[derive(Debug, Clone, Copy)]
pub enum SortOrder {
Asc,
Desc,
}
impl SortOrder {
pub(crate) fn as_str(self) -> &'static str {
match self {
SortOrder::Asc => "asc",
SortOrder::Desc => "desc",
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum SortMode {
Min,
Max,
Avg,
Sum,
Median,
}
impl SortMode {
fn as_str(self) -> &'static str {
match self {
SortMode::Min => "min",
SortMode::Max => "max",
SortMode::Avg => "avg",
SortMode::Sum => "sum",
SortMode::Median => "median",
}
}
}
#[derive(Debug, Clone)]
pub struct Sort {
key: String,
body: Map<String, Value>,
dedup_id: String,
script_kind: Option<MapSortValueKind>,
}
impl Sort {
pub(crate) fn new(field: &str, order: SortOrder) -> Self {
let mut body = Map::new();
body.insert(
"order".to_string(),
Value::String(order.as_str().to_string()),
);
Self::plain(field.to_string(), body)
}
fn plain(key: String, body: Map<String, Value>) -> Self {
Self {
dedup_id: key.clone(),
key,
body,
script_kind: None,
}
}
#[must_use]
pub fn score() -> Self {
let mut body = Map::new();
body.insert("order".to_string(), Value::String("desc".to_string()));
Self::plain("_score".to_string(), body)
}
#[must_use]
pub fn script(
script_type: ScriptSortType,
source: impl Into<String>,
order: SortOrder,
) -> Self {
let mut script = Map::new();
script.insert("source".to_string(), Value::String(source.into()));
let mut body = Map::new();
body.insert(
"type".to_string(),
Value::String(script_type.as_str().to_string()),
);
body.insert("script".to_string(), Value::Object(script));
body.insert(
"order".to_string(),
Value::String(order.as_str().to_string()),
);
Self::plain("_script".to_string(), body)
}
pub(crate) fn from_parts(key: String, body: Map<String, Value>) -> Self {
Self::plain(key, body)
}
pub(crate) fn map_script(
dedup_id: String,
body: Map<String, Value>,
kind: MapSortValueKind,
) -> Self {
Self {
key: "_script".to_string(),
body,
dedup_id,
script_kind: Some(kind),
}
}
pub(crate) fn field<S: FlussoDocument>(path: &str, order: SortOrder) -> Self {
let mut sort = Sort::new(path, order);
let boundaries = nested_boundaries(S::PATH);
if let Some(nested) = nested_clause(&boundaries) {
sort.body.insert("nested".to_string(), nested);
sort.body.insert(
"mode".to_string(),
Value::String(default_mode(order).to_string()),
);
}
sort
}
#[must_use]
pub fn asc(mut self) -> Self {
self.body
.insert("order".to_string(), Value::String("asc".to_string()));
self
}
#[must_use]
pub fn desc(mut self) -> Self {
self.body
.insert("order".to_string(), Value::String("desc".to_string()));
self
}
#[must_use]
pub fn missing_first(mut self) -> Self {
match self.script_kind {
Some(kind) => {
let value = missing_sentinel(false, self.current_order(), kind);
self.set_script_missing(value);
}
None => {
self.body
.insert("missing".to_string(), Value::String("_first".to_string()));
}
}
self
}
#[must_use]
pub fn missing_last(mut self) -> Self {
match self.script_kind {
Some(kind) => {
let value = missing_sentinel(true, self.current_order(), kind);
self.set_script_missing(value);
}
None => {
self.body
.insert("missing".to_string(), Value::String("_last".to_string()));
}
}
self
}
#[must_use]
pub fn missing(mut self, value: impl Into<Value>) -> Self {
let value = value.into();
match self.script_kind {
Some(_) => self.set_script_missing(value),
None => {
self.body.insert("missing".to_string(), value);
}
}
self
}
fn current_order(&self) -> SortOrder {
match self.body.get("order").and_then(Value::as_str) {
Some("desc") => SortOrder::Desc,
_ => SortOrder::Asc,
}
}
fn set_script_missing(&mut self, value: Value) {
if let Some(params) = self
.body
.get_mut("script")
.and_then(Value::as_object_mut)
.and_then(|script| script.get_mut("params"))
.and_then(Value::as_object_mut)
{
params.insert("missing".to_string(), value);
}
}
#[must_use]
pub fn mode(mut self, mode: SortMode) -> Self {
self.body
.insert("mode".to_string(), Value::String(mode.as_str().to_string()));
self
}
#[must_use]
pub fn unmapped_type(mut self, unmapped_type: impl Into<String>) -> Self {
if self.script_kind.is_none() {
self.body.insert(
"unmapped_type".to_string(),
Value::String(unmapped_type.into()),
);
}
self
}
#[must_use]
pub fn numeric_type(mut self, numeric_type: NumericType) -> Self {
if self.script_kind.is_none() {
self.body.insert(
"numeric_type".to_string(),
Value::String(numeric_type.as_str().to_string()),
);
}
self
}
#[must_use]
pub fn format(mut self, format: impl Into<String>) -> Self {
if self.script_kind.is_none() {
self.body
.insert("format".to_string(), Value::String(format.into()));
}
self
}
#[must_use]
pub fn nested_filtered<S>(mut self, path: impl Into<String>, filter: impl AsQuery<S>) -> Self {
let mut nested = Map::new();
nested.insert("path".to_string(), Value::String(path.into()));
if let Some(query) = filter.into_query() {
nested.insert("filter".to_string(), query.to_value());
}
self.body
.insert("nested".to_string(), Value::Object(nested));
self
}
pub(crate) fn to_value(&self) -> Value {
let mut outer = Map::new();
outer.insert(self.key.clone(), Value::Object(self.body.clone()));
Value::Object(outer)
}
pub(crate) fn dedup_id(&self) -> &str {
&self.dedup_id
}
pub(crate) fn without_nested_context(mut self) -> Self {
if self.body.remove("nested").is_some() {
self.body.remove("mode");
}
self
}
}
fn nested_clause(boundaries: &[String]) -> Option<Value> {
let (path, rest) = boundaries.split_first()?;
let mut clause = Map::new();
clause.insert("path".to_string(), Value::String(path.clone()));
if let Some(inner) = nested_clause(rest) {
clause.insert("nested".to_string(), inner);
}
Some(Value::Object(clause))
}
fn default_mode(order: SortOrder) -> &'static str {
match order {
SortOrder::Asc => "min",
SortOrder::Desc => "max",
}
}
pub trait Sortable {
fn asc(&self) -> Sort;
fn desc(&self) -> Sort;
}
#[derive(Debug, Clone)]
pub enum Missing {
First,
Last,
Value(Value),
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum MapSortValueKind {
String,
Number,
Date,
}
const STRING_SOURCE: &str = "for (def f : params.fields) { if (doc.containsKey(f) && doc[f].size() > 0) { return doc[f].value.toLowerCase(); } } return params.missing;";
const NUMBER_SOURCE: &str = "for (def f : params.fields) { if (doc.containsKey(f) && doc[f].size() > 0) { return doc[f].value; } } return params.missing;";
const DATE_SOURCE: &str = "for (def f : params.fields) { if (doc.containsKey(f) && doc[f].size() > 0) { return doc[f].value.toInstant().toEpochMilli(); } } return params.missing;";
fn missing_sentinel(last: bool, order: SortOrder, kind: MapSortValueKind) -> Value {
let high = last == matches!(order, SortOrder::Asc);
match kind {
MapSortValueKind::String => Value::String(if high {
"\u{10ffff}".to_string()
} else {
String::new()
}),
MapSortValueKind::Number | MapSortValueKind::Date => {
Value::from(if high { i64::MAX } else { i64::MIN })
}
}
}
#[derive(Debug, Clone)]
pub struct MapKeySort<S = Root> {
path: String,
keys: Vec<String>,
kind: MapSortValueKind,
_scope: PhantomData<fn() -> S>,
}
impl<S> MapKeySort<S> {
pub(crate) fn new(path: String, key: impl Into<String>, kind: MapSortValueKind) -> Self {
Self {
path,
keys: vec![key.into()],
kind,
_scope: PhantomData,
}
}
#[must_use]
pub fn or(mut self, key: impl Into<String>) -> Self {
self.keys.push(key.into());
self
}
fn leaf_field(&self, key: &str) -> String {
match self.kind {
MapSortValueKind::String => format!("{}.{key}.keyword", self.path),
MapSortValueKind::Number | MapSortValueKind::Date => format!("{}.{key}", self.path),
}
}
}
impl<S: FlussoDocument> MapKeySort<S> {
fn build(&self, order: SortOrder) -> Sort {
let fields: Vec<Value> = self
.keys
.iter()
.map(|key| Value::String(self.leaf_field(key)))
.collect();
let (sort_type, source, default_missing) = match self.kind {
MapSortValueKind::String => ("string", STRING_SOURCE, Value::String(String::new())),
MapSortValueKind::Number => ("number", NUMBER_SOURCE, Value::from(0)),
MapSortValueKind::Date => ("number", DATE_SOURCE, Value::from(0)),
};
let mut params = Map::new();
params.insert("fields".to_string(), Value::Array(fields));
params.insert("missing".to_string(), default_missing);
let mut script = Map::new();
script.insert("source".to_string(), Value::String(source.to_string()));
script.insert("params".to_string(), Value::Object(params));
let mut body = Map::new();
body.insert("type".to_string(), Value::String(sort_type.to_string()));
body.insert("script".to_string(), Value::Object(script));
body.insert(
"order".to_string(),
Value::String(order.as_str().to_string()),
);
let boundaries = nested_boundaries(S::PATH);
if let Some(nested) = nested_clause(&boundaries) {
body.insert("nested".to_string(), nested);
body.insert(
"mode".to_string(),
Value::String(default_mode(order).to_string()),
);
}
Sort::map_script(self.path.clone(), body, self.kind)
}
}
impl<S: FlussoDocument> Sortable for MapKeySort<S> {
fn asc(&self) -> Sort {
self.build(SortOrder::Asc)
}
fn desc(&self) -> Sort {
self.build(SortOrder::Desc)
}
}
impl<S: FlussoDocument> From<MapKeySort<S>> for Sort {
fn from(map_sort: MapKeySort<S>) -> Self {
map_sort.build(SortOrder::Asc)
}
}
impl<S: FlussoDocument> From<MapKeySort<S>> for Option<Sort> {
fn from(map_sort: MapKeySort<S>) -> Self {
Some(map_sort.into())
}
}
#[derive(Debug, Clone)]
pub struct OrderBy {
order: SortOrder,
missing: Option<Missing>,
mode: Option<SortMode>,
numeric_type: Option<NumericType>,
unmapped_type: Option<String>,
format: Option<String>,
}
impl OrderBy {
#[must_use]
pub fn asc() -> Self {
Self::new(SortOrder::Asc)
}
#[must_use]
pub fn desc() -> Self {
Self::new(SortOrder::Desc)
}
fn new(order: SortOrder) -> Self {
Self {
order,
missing: None,
mode: None,
numeric_type: None,
unmapped_type: None,
format: None,
}
}
#[must_use]
pub fn missing_first(mut self) -> Self {
self.missing = Some(Missing::First);
self
}
#[must_use]
pub fn missing_last(mut self) -> Self {
self.missing = Some(Missing::Last);
self
}
#[must_use]
pub fn missing(mut self, value: impl Into<Value>) -> Self {
self.missing = Some(Missing::Value(value.into()));
self
}
#[must_use]
pub fn mode(mut self, mode: SortMode) -> Self {
self.mode = Some(mode);
self
}
#[must_use]
pub fn numeric_type(mut self, numeric_type: NumericType) -> Self {
self.numeric_type = Some(numeric_type);
self
}
#[must_use]
pub fn unmapped_type(mut self, unmapped_type: impl Into<String>) -> Self {
self.unmapped_type = Some(unmapped_type.into());
self
}
#[must_use]
pub fn format(mut self, format: impl Into<String>) -> Self {
self.format = Some(format.into());
self
}
fn into_sort<H: Sortable>(self, handle: &H) -> Sort {
let mut sort = match self.order {
SortOrder::Asc => handle.asc(),
SortOrder::Desc => handle.desc(),
};
sort = match self.missing {
Some(Missing::First) => sort.missing_first(),
Some(Missing::Last) => sort.missing_last(),
Some(Missing::Value(value)) => sort.missing(value),
None => sort,
};
if let Some(mode) = self.mode {
sort = sort.mode(mode);
}
if let Some(numeric_type) = self.numeric_type {
sort = sort.numeric_type(numeric_type);
}
if let Some(unmapped_type) = self.unmapped_type {
sort = sort.unmapped_type(unmapped_type);
}
if let Some(format) = self.format {
sort = sort.format(format);
}
sort
}
}
impl From<SortOrder> for OrderBy {
fn from(order: SortOrder) -> Self {
Self::new(order)
}
}
#[derive(Debug, Clone)]
pub struct MaybeOrderBy(Option<OrderBy>);
impl From<OrderBy> for MaybeOrderBy {
fn from(order: OrderBy) -> Self {
Self(Some(order))
}
}
impl From<SortOrder> for MaybeOrderBy {
fn from(order: SortOrder) -> Self {
Self(Some(order.into()))
}
}
impl<T: Into<OrderBy>> From<Option<T>> for MaybeOrderBy {
fn from(order: Option<T>) -> Self {
Self(order.map(Into::into))
}
}
#[derive(Debug, Default)]
pub struct SortBuilder {
sorts: Vec<Sort>,
fallback: Option<Sort>,
}
impl SortBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
fn push_unique(&mut self, sort: Sort) {
if !self
.sorts
.iter()
.any(|existing| existing.dedup_id() == sort.dedup_id())
{
self.sorts.push(sort);
}
}
#[must_use]
pub fn by<H: Sortable>(mut self, handle: H, dir: impl Into<MaybeOrderBy>) -> Self {
if let Some(order) = dir.into().0 {
self.push_unique(order.into_sort(&handle));
}
self
}
#[must_use]
pub fn near<S>(mut self, handle: Geo<S>, center: impl Into<Option<GeoPoint>>) -> Self {
if let Some(center) = center.into() {
self.push_unique(handle.distance_from(center));
}
self
}
#[must_use]
pub fn score(mut self) -> Self {
self.push_unique(Sort::score());
self
}
#[must_use]
pub fn score_if(self, cond: bool) -> Self {
if cond { self.score() } else { self }
}
#[must_use]
pub fn raw(mut self, sort: impl Into<Option<Sort>>) -> Self {
if let Some(sort) = sort.into() {
self.sorts.push(sort);
}
self
}
#[must_use]
pub fn tiebreak<H: Sortable>(mut self, handle: H) -> Self {
self.push_unique(handle.asc());
self
}
#[must_use]
pub fn or_default(mut self, sort: impl Into<Sort>) -> Self {
if self.fallback.is_none() {
self.fallback = Some(sort.into());
}
self
}
#[must_use]
pub fn build(mut self) -> Vec<Sort> {
if self.sorts.is_empty()
&& let Some(fallback) = self.fallback
{
self.sorts.push(fallback);
}
self.sorts
}
}
impl IntoIterator for SortBuilder {
type Item = Sort;
type IntoIter = std::vec::IntoIter<Sort>;
fn into_iter(self) -> Self::IntoIter {
self.build().into_iter()
}
}