use super::error::Result;
use crate::evaluator::TqlEvaluator;
use crate::parser::TqlParser;
use serde_json::Value as JsonValue;
pub struct PostProcessor {
parser: TqlParser,
evaluator: TqlEvaluator,
}
impl Default for PostProcessor {
fn default() -> Self {
Self::new()
}
}
impl PostProcessor {
pub fn new() -> Self {
Self {
parser: TqlParser::new(),
evaluator: TqlEvaluator::new(),
}
}
pub fn process_results(
&self,
results: Vec<JsonValue>,
tql_query: &str,
) -> Result<Vec<JsonValue>> {
let ast = self.parser.parse(tql_query).map_err(|e| {
super::error::OpenSearchError::TranslationError(format!("Failed to parse TQL: {}", e))
})?;
let resolved = if results.is_empty() {
None
} else {
TqlEvaluator::resolve_value_mutators(&ast)?
};
let ast = resolved.unwrap_or(ast);
let mut filtered = Vec::with_capacity(results.len());
for record in results {
if self.evaluator.evaluate(&ast, &record)? {
filtered.push(record);
}
}
Ok(filtered)
}
pub fn process_results_with_enrichment(
&self,
results: Vec<JsonValue>,
tql_query: &str,
) -> Result<Vec<JsonValue>> {
let ast = self.parser.parse(tql_query).map_err(|e| {
super::error::OpenSearchError::TranslationError(format!("Failed to parse TQL: {}", e))
})?;
let enriched = self.evaluator.filter_and_enrich(&ast, &results)?;
Ok(enriched)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn a_value_mutator_chain_resolves_once_per_batch_not_once_per_document() {
use crate::evaluator::VALUE_MUTATOR_RESOLUTIONS;
let results: Vec<JsonValue> = (0..40).map(|_| json!({"f": "abc"})).collect();
VALUE_MUTATOR_RESOLUTIONS.with(|n| n.set(0));
let hits = PostProcessor::new()
.process_results(results, "f eq 'ABC' | lowercase")
.unwrap();
let resolutions = VALUE_MUTATOR_RESOLUTIONS.with(|n| n.get());
assert_eq!(hits.len(), 40, "every document still matches");
assert_eq!(
resolutions, 1,
"40 documents cost {resolutions} resolutions of an operand that does \
not depend on the document"
);
}
#[test]
fn test_process_results_simple() {
let results = vec![
json!({"name": "John", "age": 30}),
json!({"name": "Jane", "age": 25}),
json!({"name": "Bob", "age": 35}),
];
let processor = PostProcessor::new();
let filtered = processor.process_results(results, "age > 25").unwrap();
assert_eq!(filtered.len(), 2);
}
#[test]
fn test_process_results_with_mutators() {
let results = vec![
json!({"name": "JOHN"}),
json!({"name": "jane"}),
json!({"name": "BOB"}),
];
let processor = PostProcessor::new();
let filtered = processor
.process_results(results, "name | lowercase eq 'john'")
.unwrap();
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0]["name"], "JOHN");
}
#[test]
fn test_process_results_with_is_private() {
let results = vec![
json!({"source": {"ip": "192.168.1.1"}}),
json!({"source": {"ip": "8.8.8.8"}}),
json!({"source": {"ip": "10.0.0.1"}}),
];
let processor = PostProcessor::new();
let filtered = processor
.process_results(results, "source.ip | is_private eq true")
.unwrap();
assert_eq!(filtered.len(), 2);
}
#[test]
fn test_process_results_compound_query() {
let results = vec![
json!({"name": "ADMIN", "age": 35}),
json!({"name": "USER", "age": 25}),
json!({"name": "admin", "age": 40}),
];
let processor = PostProcessor::new();
let filtered = processor
.process_results(results, "name | lowercase eq 'admin' AND age > 30")
.unwrap();
assert_eq!(filtered.len(), 2); }
}