use rupg_common::{Error, Result, SqlState};
use rupg_types::{
QueryItem, TSQUERY_AND, TSQUERY_MAX_DISTANCE, TSQUERY_NOT, TSQUERY_OR, TSQUERY_PHRASE, Value,
};
use crate::{Call, Kernel, bad_value, type_error};
fn items(value: &Value) -> Result<&[QueryItem]> {
match value {
Value::TsQuery(items) => Ok(items),
_ => Err(bad_value()),
}
}
fn numnode(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let [query] = args else { return Err(bad_value()) };
Ok(Value::Int4(i32::try_from(items(query)?.len()).unwrap_or(i32::MAX)))
}
fn querytree(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let [query] = args else { return Err(bad_value()) };
Ok(Value::Text(rupg_types::tsquery_tree(items(query)?)))
}
fn join(op: u8, distance: u16, a: &Value, b: &Value) -> Result<Value> {
let (a, b) = (items(a)?, items(b)?);
if a.is_empty() {
return Ok(Value::TsQuery(b.to_vec()));
}
if b.is_empty() {
return Ok(Value::TsQuery(a.to_vec()));
}
Ok(Value::TsQuery(rupg_types::tsquery_join(op, distance, a, b).map_err(type_error)?))
}
fn and(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let [a, b] = args else { return Err(bad_value()) };
join(TSQUERY_AND, 0, a, b)
}
fn or(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let [a, b] = args else { return Err(bad_value()) };
join(TSQUERY_OR, 0, a, b)
}
fn phrase(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let [a, b] = args else { return Err(bad_value()) };
join(TSQUERY_PHRASE, 1, a, b)
}
fn phrase_distance(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let [a, b, Value::Int4(distance)] = args else { return Err(bad_value()) };
let distance = u16::try_from(*distance)
.ok()
.filter(|d| *d <= TSQUERY_MAX_DISTANCE)
.ok_or_else(|| type_error(rupg_types::tsquery_bad_distance()))?;
join(TSQUERY_PHRASE, distance, a, b)
}
fn not(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let [a] = args else { return Err(bad_value()) };
let a = items(a)?;
if a.is_empty() {
return Ok(Value::TsQuery(Vec::new()));
}
Ok(Value::TsQuery(rupg_types::tsquery_join(TSQUERY_NOT, 0, &[], a).map_err(type_error)?))
}
fn contains(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let [query, ex] = args else { return Err(bad_value()) };
Ok(Value::Bool(rupg_types::tsquery_contains(items(query)?, items(ex)?)))
}
fn contained(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let [ex, query] = args else { return Err(bad_value()) };
Ok(Value::Bool(rupg_types::tsquery_contains(items(query)?, items(ex)?)))
}
fn cmp(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let [a, b] = args else { return Err(bad_value()) };
Ok(Value::Int4(rupg_types::tsquery_cmp(items(a)?, items(b)?)))
}
pub(crate) fn no_lexemes(text: &str) -> Error {
Error::new(
SqlState::SUCCESSFUL_COMPLETION,
format!("text-search query doesn't contain lexemes: \"{text}\""),
)
}
pub(crate) fn by_src(src: &str) -> Option<Kernel> {
Some(match src {
"tsquery_numnode" => numnode,
"tsquerytree" => querytree,
"tsquery_and" => and,
"tsquery_or" => or,
"tsquery_phrase" => phrase,
"tsquery_phrase_distance" => phrase_distance,
"tsquery_not" => not,
"tsq_mcontains" => contains,
"tsq_mcontained" => contained,
"tsquery_cmp" => cmp,
_ => return None,
})
}