use std::any::Any;
use rustc_hash::FxHashMap;
use super::{find_column_index, resolve_alias, Expression};
use crate::core::{Result, Row, Schema};
#[derive(Debug, Clone)]
pub struct NullCheckExpr {
column: String,
is_null: bool,
col_index: Option<usize>,
aliases: FxHashMap<String, String>,
original_column: Option<String>,
}
impl NullCheckExpr {
pub fn new(column: impl Into<String>, is_null: bool) -> Self {
Self {
column: column.into(),
is_null,
col_index: None,
aliases: FxHashMap::default(),
original_column: None,
}
}
pub fn is_null(column: impl Into<String>) -> Self {
Self::new(column, true)
}
pub fn is_not_null(column: impl Into<String>) -> Self {
Self::new(column, false)
}
pub fn checks_for_null(&self) -> bool {
self.is_null
}
pub fn is_null_check(&self) -> bool {
self.is_null
}
}
impl Expression for NullCheckExpr {
fn evaluate(&self, row: &Row) -> Result<bool> {
let col_idx = match self.col_index {
Some(idx) if idx < row.len() => idx,
_ => {
return Ok(self.is_null);
}
};
let col_value = &row[col_idx];
let value_is_null = col_value.is_null();
Ok(self.is_null == value_is_null)
}
fn evaluate_fast(&self, row: &Row) -> bool {
let col_idx = match self.col_index {
Some(idx) if idx < row.len() => idx,
_ => return self.is_null,
};
let col_value = &row[col_idx];
let value_is_null = col_value.is_null();
self.is_null == value_is_null
}
fn with_aliases(&self, aliases: &FxHashMap<String, String>) -> Box<dyn Expression> {
let resolved = resolve_alias(&self.column, aliases);
let mut expr = self.clone();
if resolved != self.column {
expr.original_column = Some(self.column.clone());
expr.column = resolved.to_string();
}
expr.aliases = aliases.clone();
expr.col_index = None;
Box::new(expr)
}
fn prepare_for_schema(&mut self, schema: &Schema) {
if self.col_index.is_some() {
return;
}
self.col_index = find_column_index(schema, &self.column);
}
fn collect_column_indices(&self, out: &mut Vec<usize>) -> bool {
if let Some(idx) = self.col_index {
out.push(idx);
true
} else {
false
}
}
fn is_prepared(&self) -> bool {
self.col_index.is_some()
}
fn get_column_name(&self) -> Option<&str> {
Some(&self.column)
}
fn can_use_index(&self) -> bool {
true
}
fn is_conjunctive_simple(&self) -> bool {
false
}
fn clone_box(&self) -> Box<dyn Expression> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{DataType, SchemaBuilder, Value};
fn test_schema() -> Schema {
SchemaBuilder::new("test")
.add_primary_key("id", DataType::Integer)
.add_nullable("name", DataType::Text)
.build()
}
#[test]
fn test_is_null_with_null_value() {
let schema = test_schema();
let row = Row::from_values(vec![
Value::integer(1),
Value::null(DataType::Text), ]);
let mut expr = NullCheckExpr::is_null("name");
expr.prepare_for_schema(&schema);
assert!(expr.evaluate(&row).unwrap());
assert!(expr.evaluate_fast(&row));
}
#[test]
fn test_is_null_with_non_null_value() {
let schema = test_schema();
let row = Row::from_values(vec![
Value::integer(1),
Value::text("Alice"), ]);
let mut expr = NullCheckExpr::is_null("name");
expr.prepare_for_schema(&schema);
assert!(!expr.evaluate(&row).unwrap());
assert!(!expr.evaluate_fast(&row));
}
#[test]
fn test_is_not_null_with_null_value() {
let schema = test_schema();
let row = Row::from_values(vec![Value::integer(1), Value::null(DataType::Text)]);
let mut expr = NullCheckExpr::is_not_null("name");
expr.prepare_for_schema(&schema);
assert!(!expr.evaluate(&row).unwrap());
assert!(!expr.evaluate_fast(&row));
}
#[test]
fn test_is_not_null_with_non_null_value() {
let schema = test_schema();
let row = Row::from_values(vec![Value::integer(1), Value::text("Alice")]);
let mut expr = NullCheckExpr::is_not_null("name");
expr.prepare_for_schema(&schema);
assert!(expr.evaluate(&row).unwrap());
assert!(expr.evaluate_fast(&row));
}
#[test]
fn test_unprepared_behavior() {
let row = Row::from_values(vec![Value::integer(1)]);
let expr = NullCheckExpr::is_null("whatever");
assert!(expr.evaluate(&row).unwrap());
let expr = NullCheckExpr::is_not_null("whatever");
assert!(!expr.evaluate(&row).unwrap());
}
#[test]
fn test_with_aliases() {
let schema = test_schema();
let row = Row::from_values(vec![Value::integer(1), Value::null(DataType::Text)]);
let mut aliases = FxHashMap::default();
aliases.insert("n".to_string(), "name".to_string());
let expr = NullCheckExpr::is_null("n");
let mut aliased = expr.with_aliases(&aliases);
aliased.prepare_for_schema(&schema);
assert!(aliased.evaluate(&row).unwrap());
}
#[test]
fn test_checks_for_null() {
let expr = NullCheckExpr::is_null("col");
assert!(expr.checks_for_null());
let expr = NullCheckExpr::is_not_null("col");
assert!(!expr.checks_for_null());
}
}