#![allow(unsafe_code)]
use std::collections::HashMap;
use std::hash::Hash;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use rustc_hash::{FxHashMap, FxHashSet};
use arc_swap::ArcSwap;
use parking_lot::Mutex;
use super::arena::{
make_prefix_arena_fa, make_shellstyle_arena_fa, make_string_arena_fa, merge_arena_nfas,
traverse_arena_dfa, traverse_arena_dfa_backward, traverse_arena_nfa, ArenaNfaBuffers,
StateArena, StateId,
};
use super::mutable_matcher::{
EventField, EventFieldRef, MultiConditionNfa, MutableFieldMatcher, MutableValueMatcher,
};
use super::small_table::{FieldMatcher, NfaBuffers};
pub(crate) enum Transitions<T> {
Empty,
One(T),
Many(Vec<T>),
}
impl<T> Transitions<T> {
#[inline(always)]
fn iter(&self) -> TransitionsIter<'_, T> {
match self {
Self::Empty => TransitionsIter::Empty,
Self::One(val) => TransitionsIter::One(std::iter::once(val)),
Self::Many(vec) => TransitionsIter::Many(vec.iter()),
}
}
#[inline(always)]
fn push(&mut self, val: T) {
*self = match std::mem::replace(self, Self::Empty) {
Self::Empty => Self::One(val),
Self::One(first) => Self::Many(vec![first, val]),
Self::Many(mut vec) => {
vec.push(val);
Self::Many(vec)
}
};
}
}
enum TransitionsIter<'a, T> {
Empty,
One(std::iter::Once<&'a T>),
Many(std::slice::Iter<'a, T>),
}
impl<'a, T> Iterator for TransitionsIter<'a, T> {
type Item = &'a T;
#[inline(always)]
fn next(&mut self) -> Option<&'a T> {
match self {
TransitionsIter::Empty => None,
TransitionsIter::One(iter) => iter.next(),
TransitionsIter::Many(iter) => iter.next(),
}
}
}
fn no_array_trail_conflict_ref(
from: &[crate::flatten_json::ArrayPos],
to: &[crate::flatten_json::ArrayPos],
) -> bool {
for from_pos in from {
for to_pos in to {
if from_pos.array == to_pos.array && from_pos.pos != to_pos.pos {
return false;
}
}
}
true
}
fn no_array_trail_conflict(from: &[crate::json::ArrayPos], to: &[crate::json::ArrayPos]) -> bool {
for from_pos in from {
for to_pos in to {
if from_pos.array == to_pos.array && from_pos.pos != to_pos.pos {
return false;
}
}
}
true
}
#[derive(Clone, Default)]
pub struct FrozenFieldMatcher<X: Clone + Eq + Hash> {
pub transitions: FxHashMap<String, Arc<FrozenValueMatcher<X>>>,
pub matches: Vec<X>,
pub exists_true: FxHashMap<String, Arc<Self>>,
pub exists_false: FxHashMap<String, Arc<Self>>,
}
unsafe impl<X: Clone + Eq + Hash + Send + Sync> Send for FrozenFieldMatcher<X> {}
unsafe impl<X: Clone + Eq + Hash + Send + Sync> Sync for FrozenFieldMatcher<X> {}
impl<X: Clone + Eq + Hash> FrozenFieldMatcher<X> {
pub fn new() -> Self {
Self {
transitions: FxHashMap::default(),
matches: Vec::new(),
exists_true: FxHashMap::default(),
exists_false: FxHashMap::default(),
}
}
pub(crate) fn transition_on(
&self,
path: &str,
value: &[u8],
is_number: bool,
bufs: &mut NfaBuffers,
) -> Transitions<Arc<Self>> {
if let Some(vm) = self.transitions.get(path) {
vm.transition_on(value, is_number, bufs)
} else {
Transitions::Empty
}
}
}
#[derive(Clone, Default)]
pub struct FrozenValueMatcher<X: Clone + Eq + Hash> {
singleton_match: Option<Vec<u8>>,
singleton_transition: Option<Arc<FrozenFieldMatcher<X>>>,
has_numbers: bool,
transition_map: FxHashMap<usize, Arc<FrozenFieldMatcher<X>>>,
multi_condition_nfas: Vec<MultiConditionNfa>,
main_arena: Option<(StateArena, StateId)>,
main_arena_is_nfa: bool,
suffix_arena: Option<(StateArena, StateId)>,
}
unsafe impl<X: Clone + Eq + Hash + Send + Sync> Send for FrozenValueMatcher<X> {}
unsafe impl<X: Clone + Eq + Hash + Send + Sync> Sync for FrozenValueMatcher<X> {}
impl<X: Clone + Eq + Hash> FrozenValueMatcher<X> {
pub fn new() -> Self {
Self {
singleton_match: None,
singleton_transition: None,
has_numbers: false,
transition_map: FxHashMap::default(),
multi_condition_nfas: Vec::new(),
main_arena: None,
main_arena_is_nfa: false,
suffix_arena: None,
}
}
#[inline]
pub(crate) fn transition_on(
&self,
value: &[u8],
is_number: bool,
bufs: &mut NfaBuffers,
) -> Transitions<Arc<FrozenFieldMatcher<X>>> {
if self.multi_condition_nfas.is_empty() {
if let Some(ref singleton_val) = self.singleton_match {
if singleton_val == value {
if let Some(ref trans) = self.singleton_transition {
return Transitions::One(trans.clone());
}
}
return Transitions::Empty;
}
}
let mut result = Transitions::Empty;
let has_singleton = if let Some(ref singleton_val) = self.singleton_match {
if singleton_val == value {
if let Some(ref trans) = self.singleton_transition {
result.push(trans.clone());
}
}
true
} else {
false
};
let q_num_storage: Option<crate::numbits::QNumberStack> = if self.has_numbers && is_number {
fast_float2::parse(value)
.ok()
.map(crate::numbits::q_num_stack)
} else {
None
};
let value_to_match: &[u8] = match &q_num_storage {
Some(q) => q.as_slice(),
None => value,
};
if !has_singleton {
if let Some((ref arena, start)) = self.main_arena {
if self.main_arena_is_nfa {
bufs.arena_bufs.clear();
traverse_arena_nfa(arena, start, value_to_match, &mut bufs.arena_bufs);
} else {
bufs.arena_bufs.transitions.clear();
traverse_arena_dfa(
arena,
start,
value_to_match,
&mut bufs.arena_bufs.transitions,
);
}
for arc_fm in &bufs.arena_bufs.transitions {
let ptr = Arc::as_ptr(arc_fm) as usize;
if let Some(frozen_fm) = self.transition_map.get(&ptr) {
result.push(frozen_fm.clone());
}
}
}
if let Some((ref arena, start)) = self.suffix_arena {
bufs.arena_bufs.transitions.clear();
traverse_arena_dfa_backward(
arena,
start,
value_to_match,
&mut bufs.arena_bufs.transitions,
);
for arc_fm in &bufs.arena_bufs.transitions {
let ptr = Arc::as_ptr(arc_fm) as usize;
if let Some(frozen_fm) = self.transition_map.get(&ptr) {
result.push(frozen_fm.clone());
}
}
}
}
if !self.multi_condition_nfas.is_empty() {
for mc_nfa in self.multi_condition_nfas.iter() {
let mut all_conditions_pass = true;
for condition in &mc_nfa.conditions {
bufs.arena_bufs.clear();
traverse_arena_nfa(
&condition.arena,
condition.start,
value_to_match,
&mut bufs.arena_bufs,
);
let condition_matched = !bufs.arena_bufs.transitions.is_empty();
let condition_passes = if condition.is_negative {
!condition_matched
} else {
condition_matched
};
if !condition_passes {
all_conditions_pass = false;
break; }
}
if all_conditions_pass {
let ptr = mc_nfa.field_matcher_ptr as usize;
if let Some(frozen_fm) = self.transition_map.get(&ptr) {
if !result.iter().any(|r| Arc::ptr_eq(r, frozen_fm)) {
result.push(frozen_fm.clone());
}
}
}
}
}
result
}
}
struct BuildState<X: Clone + Eq + Hash> {
root: Rc<MutableFieldMatcher<X>>,
}
unsafe impl<X: Clone + Eq + Hash + Send> Send for BuildState<X> {}
impl<X: Clone + Eq + Hash> BuildState<X> {
fn new() -> Self {
Self {
root: Rc::new(MutableFieldMatcher::new()),
}
}
}
pub struct ThreadSafeCoreMatcher<X: Clone + Eq + Hash + Send + Sync> {
root: ArcSwap<FrozenFieldMatcher<X>>,
build_lock: Mutex<BuildState<X>>,
needs_freeze: AtomicBool,
arena_byte_budget: usize,
max_states_per_pattern: usize,
}
impl<X: Clone + Eq + Hash + Send + Sync> ThreadSafeCoreMatcher<X> {
pub fn new() -> Self {
let defaults = crate::PatternLimits::default();
Self::with_limits(defaults.arena_byte_budget, defaults.max_states_per_pattern)
}
pub fn with_limits(arena_byte_budget: usize, max_states_per_pattern: usize) -> Self {
Self {
root: ArcSwap::from_pointee(FrozenFieldMatcher::new()),
build_lock: Mutex::new(BuildState::new()),
needs_freeze: AtomicBool::new(false),
arena_byte_budget,
max_states_per_pattern,
}
}
fn ensure_frozen(&self) {
if self.needs_freeze.load(Ordering::Acquire) {
let build_state = self.build_lock.lock();
if self.needs_freeze.load(Ordering::Relaxed) {
let frozen = self.freeze_field_matcher(&build_state.root);
self.root.store(Arc::new(frozen));
self.needs_freeze.store(false, Ordering::Release);
}
}
}
pub fn add_pattern(
&self,
x: X,
pattern_fields: &[(String, Vec<crate::json::Matcher>)],
) -> Result<(), crate::QuaminaError> {
let build_state = self.build_lock.lock();
let mut sorted_fields: Vec<_> = pattern_fields.to_vec();
sorted_fields.sort_by(|a, b| a.0.cmp(&b.0));
let mut states: Vec<Rc<MutableFieldMatcher<X>>> = vec![build_state.root.clone()];
for (path, matchers) in &sorted_fields {
if matchers.is_empty() {
continue;
}
let mut next_states = Vec::new();
for state in &states {
let first_matcher = &matchers[0];
match first_matcher {
crate::json::Matcher::Exists(true) => {
let next = state.add_exists(true, path);
next_states.push(next);
}
crate::json::Matcher::Exists(false) => {
let next = state.add_exists(false, path);
next_states.push(next);
}
_ => {
let nexts = state.add_transition(path, matchers, self.arena_byte_budget)?;
next_states.extend(nexts);
}
}
}
if next_states.len() > self.max_states_per_pattern {
return Err(crate::QuaminaError::PatternTooComplex(format!(
"field-matcher state count {} exceeds maximum of {} \
(pattern has too many mixed-type matchers across fields)",
next_states.len(),
self.max_states_per_pattern
)));
}
states = next_states;
}
for state in states {
state.add_match(x.clone());
}
self.needs_freeze.store(true, Ordering::Release);
Ok(())
}
fn freeze_field_matcher(&self, mutable: &Rc<MutableFieldMatcher<X>>) -> FrozenFieldMatcher<X> {
let mut cache: HashMap<*const MutableFieldMatcher<X>, Arc<FrozenFieldMatcher<X>>> =
HashMap::new();
self.freeze_field_matcher_impl(mutable, &mut cache)
}
fn freeze_field_matcher_impl(
&self,
mutable: &Rc<MutableFieldMatcher<X>>,
cache: &mut HashMap<*const MutableFieldMatcher<X>, Arc<FrozenFieldMatcher<X>>>,
) -> FrozenFieldMatcher<X> {
let ptr = Rc::as_ptr(mutable);
if let Some(cached) = cache.get(&ptr) {
return (*cached.as_ref()).clone();
}
let placeholder = Arc::new(FrozenFieldMatcher::new());
cache.insert(ptr, placeholder);
let mut frozen_transitions = FxHashMap::default();
for (path, vm) in mutable.transitions.borrow().iter() {
let frozen_vm = self.freeze_value_matcher(vm, cache);
frozen_transitions.insert(path.clone(), Arc::new(frozen_vm));
}
let mut frozen_exists_true = FxHashMap::default();
for (path, fm) in mutable.exists_true.borrow().iter() {
let frozen_fm = self.freeze_field_matcher_impl(fm, cache);
frozen_exists_true.insert(path.clone(), Arc::new(frozen_fm));
}
let mut frozen_exists_false = FxHashMap::default();
for (path, fm) in mutable.exists_false.borrow().iter() {
let frozen_fm = self.freeze_field_matcher_impl(fm, cache);
frozen_exists_false.insert(path.clone(), Arc::new(frozen_fm));
}
FrozenFieldMatcher {
transitions: frozen_transitions,
matches: mutable.matches.borrow().clone(),
exists_true: frozen_exists_true,
exists_false: frozen_exists_false,
}
}
fn freeze_value_matcher(
&self,
mutable: &Rc<MutableValueMatcher<X>>,
cache: &mut HashMap<*const MutableFieldMatcher<X>, Arc<FrozenFieldMatcher<X>>>,
) -> FrozenValueMatcher<X> {
let singleton_match = mutable.singleton_match.borrow().clone();
let singleton_transition = mutable.singleton_transition.borrow().as_ref().map(|fm| {
let frozen = self.freeze_field_matcher_impl(fm, cache);
Arc::new(frozen)
});
let mut transition_map = FxHashMap::default();
for (ptr, mutable_fm) in mutable.transition_map.borrow().iter() {
let frozen_fm = self.freeze_field_matcher_impl(mutable_fm, cache);
transition_map.insert(*ptr as usize, Arc::new(frozen_fm));
}
let multi_condition_nfas = mutable.multi_condition_nfas.borrow().clone();
let main_arena = mutable.main_arena.borrow().clone();
let suffix_arena = mutable.suffix_arena.borrow().clone();
FrozenValueMatcher {
singleton_match,
singleton_transition,
has_numbers: mutable.has_numbers.get(),
transition_map,
multi_condition_nfas,
main_arena,
main_arena_is_nfa: *mutable.main_arena_is_nfa.borrow(),
suffix_arena,
}
}
pub fn matches_for_fields(&self, fields: &[EventField]) -> Vec<X> {
self.ensure_frozen();
let root = self.root.load();
if fields.is_empty() {
return self.collect_exists_false_matches(&root);
}
let mut matches = FrozenMatchSet::new();
let mut bufs = NfaBuffers::new();
for i in 0..fields.len() {
self.try_to_match(fields, i, &root, &mut matches, &mut bufs);
}
matches.into_vec()
}
fn try_to_match(
&self,
fields: &[EventField],
index: usize,
state: &Arc<FrozenFieldMatcher<X>>,
matches: &mut FrozenMatchSet<X>,
bufs: &mut NfaBuffers,
) {
let field = &fields[index];
if let Some(exists_trans) = state.exists_true.get(&field.path) {
for m in &exists_trans.matches {
matches.add(m.clone());
}
for next_idx in (index + 1)..fields.len() {
if no_array_trail_conflict(&field.array_trail, &fields[next_idx].array_trail) {
self.try_to_match(fields, next_idx, exists_trans, matches, bufs);
}
}
self.check_exists_false(state, fields, index, matches, bufs);
}
self.check_exists_false(state, fields, index, matches, bufs);
let next_states =
state.transition_on(&field.path, field.value.as_bytes(), field.is_number, bufs);
for next_state in next_states.iter() {
for m in &next_state.matches {
matches.add(m.clone());
}
for next_idx in (index + 1)..fields.len() {
if no_array_trail_conflict(&field.array_trail, &fields[next_idx].array_trail) {
self.try_to_match(fields, next_idx, next_state, matches, bufs);
}
}
self.check_exists_false(next_state, fields, index, matches, bufs);
}
}
fn check_exists_false(
&self,
state: &Arc<FrozenFieldMatcher<X>>,
fields: &[EventField],
index: usize,
matches: &mut FrozenMatchSet<X>,
bufs: &mut NfaBuffers,
) {
for (path, exists_trans) in &state.exists_false {
let field_exists = fields.iter().any(|f| &f.path == path);
if !field_exists {
for m in &exists_trans.matches {
matches.add(m.clone());
}
self.try_to_match(fields, index, exists_trans, matches, bufs);
}
}
}
fn collect_exists_false_matches(&self, state: &Arc<FrozenFieldMatcher<X>>) -> Vec<X> {
let mut result = Vec::new();
for exists_trans in state.exists_false.values() {
result.extend(exists_trans.matches.iter().cloned());
}
result
}
pub fn matches_for_fields_ref(
&self,
fields: &[EventFieldRef<'_>],
bufs: &mut NfaBuffers,
) -> Vec<X> {
self.ensure_frozen();
let root = self.root.load();
if fields.is_empty() {
return self.collect_exists_false_matches(&root);
}
let mut matches = FrozenMatchSet::new();
bufs.clear();
for i in 0..fields.len() {
self.try_to_match_ref(fields, i, &root, &mut matches, bufs);
}
matches.into_vec()
}
fn try_to_match_ref(
&self,
fields: &[EventFieldRef<'_>],
index: usize,
state: &Arc<FrozenFieldMatcher<X>>,
matches: &mut FrozenMatchSet<X>,
bufs: &mut NfaBuffers,
) {
let field = &fields[index];
if let Some(exists_trans) = state.exists_true.get(field.path) {
for m in &exists_trans.matches {
matches.add(m.clone());
}
for next_idx in (index + 1)..fields.len() {
if no_array_trail_conflict_ref(field.array_trail, fields[next_idx].array_trail) {
self.try_to_match_ref(fields, next_idx, exists_trans, matches, bufs);
}
}
self.check_exists_false_ref(state, fields, index, matches, bufs);
}
self.check_exists_false_ref(state, fields, index, matches, bufs);
let next_states = state.transition_on(field.path, field.value, field.is_number, bufs);
for next_state in next_states.iter() {
for m in &next_state.matches {
matches.add(m.clone());
}
for next_idx in (index + 1)..fields.len() {
if no_array_trail_conflict_ref(field.array_trail, fields[next_idx].array_trail) {
self.try_to_match_ref(fields, next_idx, next_state, matches, bufs);
}
}
self.check_exists_false_ref(next_state, fields, index, matches, bufs);
}
}
fn check_exists_false_ref(
&self,
state: &Arc<FrozenFieldMatcher<X>>,
fields: &[EventFieldRef<'_>],
index: usize,
matches: &mut FrozenMatchSet<X>,
bufs: &mut NfaBuffers,
) {
for (path, exists_trans) in &state.exists_false {
let field_exists = fields.iter().any(|f| f.path == path);
if !field_exists {
for m in &exists_trans.matches {
matches.add(m.clone());
}
self.try_to_match_ref(fields, index, exists_trans, matches, bufs);
}
}
}
pub fn matches_for_fields_direct(
&self,
fields: &[crate::flatten_json::Field<'_>],
bufs: &mut NfaBuffers,
) -> Vec<X> {
self.ensure_frozen();
let root = self.root.load();
if fields.is_empty() {
return self.collect_exists_false_matches(&root);
}
let mut matches = FrozenMatchSet::new();
bufs.clear();
for i in 0..fields.len() {
self.try_to_match_direct(fields, i, &root, &mut matches, bufs);
}
matches.into_vec()
}
fn try_to_match_direct(
&self,
fields: &[crate::flatten_json::Field<'_>],
index: usize,
state: &Arc<FrozenFieldMatcher<X>>,
matches: &mut FrozenMatchSet<X>,
bufs: &mut NfaBuffers,
) {
let field = &fields[index];
let path = field.path_str();
let value = field.value_bytes();
let array_trail = field.array_trail_slice();
if let Some(exists_trans) = state.exists_true.get(path) {
for m in &exists_trans.matches {
matches.add(m.clone());
}
for next_idx in (index + 1)..fields.len() {
if no_array_trail_conflict_ref(array_trail, fields[next_idx].array_trail_slice()) {
self.try_to_match_direct(fields, next_idx, exists_trans, matches, bufs);
}
}
self.check_exists_false_direct(state, fields, index, matches, bufs);
}
self.check_exists_false_direct(state, fields, index, matches, bufs);
let next_states = state.transition_on(path, value, field.is_number, bufs);
for next_state in next_states.iter() {
for m in &next_state.matches {
matches.add(m.clone());
}
for next_idx in (index + 1)..fields.len() {
if no_array_trail_conflict_ref(array_trail, fields[next_idx].array_trail_slice()) {
self.try_to_match_direct(fields, next_idx, next_state, matches, bufs);
}
}
self.check_exists_false_direct(next_state, fields, index, matches, bufs);
}
}
fn check_exists_false_direct(
&self,
state: &Arc<FrozenFieldMatcher<X>>,
fields: &[crate::flatten_json::Field<'_>],
index: usize,
matches: &mut FrozenMatchSet<X>,
bufs: &mut NfaBuffers,
) {
for (path, exists_trans) in &state.exists_false {
let field_exists = fields.iter().any(|f| f.path_str() == path);
if !field_exists {
for m in &exists_trans.matches {
matches.add(m.clone());
}
self.try_to_match_direct(fields, index, exists_trans, matches, bufs);
}
}
}
}
impl<X: Clone + Eq + Hash + Send + Sync> Default for ThreadSafeCoreMatcher<X> {
fn default() -> Self {
Self::new()
}
}
struct FrozenMatchSet<X: Clone + Eq> {
matches: Vec<X>,
}
impl<X: Clone + Eq> FrozenMatchSet<X> {
fn new() -> Self {
Self {
matches: Vec::new(),
}
}
fn add(&mut self, x: X) {
if !self.matches.contains(&x) {
self.matches.push(x);
}
}
fn into_vec(self) -> Vec<X> {
self.matches
}
}
#[derive(Clone, Default)]
pub struct AutomatonValueMatcher<X: Clone + Eq + std::hash::Hash> {
arena: Option<(StateArena, StateId)>,
pattern_map: FxHashMap<u64, X>,
next_match_id: u64,
}
impl<X: Clone + Eq + std::hash::Hash> AutomatonValueMatcher<X> {
pub fn new() -> Self {
Self {
arena: None,
pattern_map: FxHashMap::default(),
next_match_id: 1,
}
}
pub fn add_string_match(&mut self, value: &[u8], x: X) {
let match_id = self.next_match_id;
self.next_match_id += 1;
self.pattern_map.insert(match_id, x);
let next_field = Arc::new(FieldMatcher::with_match_id(match_id));
let (new_arena, new_start) = make_string_arena_fa(value, next_field);
self.merge_arena(new_arena, new_start);
}
pub fn add_prefix_match(&mut self, prefix: &[u8], x: X) {
let match_id = self.next_match_id;
self.next_match_id += 1;
self.pattern_map.insert(match_id, x);
let next_field = Arc::new(FieldMatcher::with_match_id(match_id));
let (new_arena, new_start) = make_prefix_arena_fa(prefix, next_field);
self.merge_arena(new_arena, new_start);
}
pub fn add_shellstyle_match(&mut self, pattern: &[u8], x: X) {
let match_id = self.next_match_id;
self.next_match_id += 1;
self.pattern_map.insert(match_id, x);
let next_field = Arc::new(FieldMatcher::with_match_id(match_id));
let (new_arena, new_start) = make_shellstyle_arena_fa(pattern, next_field);
self.merge_arena(new_arena, new_start);
}
fn merge_arena(&mut self, new_arena: StateArena, new_start: StateId) {
match self.arena.take() {
Some((existing_arena, existing_start)) => {
let (merged_arena, merged_start) =
merge_arena_nfas(&existing_arena, existing_start, &new_arena, new_start);
self.arena = Some((merged_arena, merged_start));
}
None => {
self.arena = Some((new_arena, new_start));
}
}
}
pub fn match_value(&self, value: &[u8]) -> Vec<X> {
let (arena, start) = match &self.arena {
Some((a, s)) => (a, *s),
None => return vec![],
};
let mut bufs = ArenaNfaBuffers::new();
traverse_arena_nfa(arena, start, value, &mut bufs);
let mut matches = Vec::new();
let mut seen_ids = FxHashSet::default();
for fm in &bufs.transitions {
if let Some(match_id) = fm.match_id {
if seen_ids.insert(match_id) {
if let Some(x) = self.pattern_map.get(&match_id) {
matches.push(x.clone());
}
}
}
}
matches
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::json::Matcher;
#[test]
fn transitions_size_must_stay_compact() {
let size = std::mem::size_of::<Transitions<Arc<FrozenFieldMatcher<String>>>>();
assert!(
size <= 24,
"Transitions<Arc<...>> is {size} bytes, must be <= 24 (Vec size). \
Larger types cause no-match benchmark regressions in recursive try_to_match."
);
}
#[test]
fn test_thread_safe_core_matcher_basic() {
let matcher: ThreadSafeCoreMatcher<String> = ThreadSafeCoreMatcher::new();
matcher
.add_pattern(
"p1".to_string(),
&[(
"status".to_string(),
vec![Matcher::Exact("active".to_string())],
)],
)
.unwrap();
let fields = vec![EventField {
path: "status".to_string(),
value: "active".to_string(),
array_trail: vec![],
is_number: false,
}];
let matches = matcher.matches_for_fields(&fields);
assert_eq!(matches, vec!["p1".to_string()]);
}
}