Skip to main content

oxirs_core/
optimization.rs

1//! Zero-copy operations and performance optimizations
2//!
3//! This module provides advanced performance optimizations including zero-copy
4//! operations, memory-efficient data structures, and SIMD-accelerated processing
5//! for RDF data manipulation.
6
7use crate::interning::{InternedString, StringInterner};
8use crate::model::*;
9use bumpalo::Bump;
10use crossbeam::epoch::{self, Atomic, Owned};
11use crossbeam::queue::SegQueue;
12use dashmap::DashMap;
13use parking_lot::RwLock;
14#[cfg(feature = "parallel")]
15use rayon::iter::IntoParallelRefIterator;
16#[cfg(feature = "parallel")]
17use rayon::iter::ParallelIterator;
18use simd_json;
19use std::collections::{BTreeSet, HashMap};
20use std::pin::Pin;
21use std::sync::atomic::Ordering;
22use std::sync::Arc;
23
24/// Type alias for string interner used throughout optimization module
25pub type TermInterner = StringInterner;
26
27/// Extension trait for TermInterner to create RDF terms
28pub trait TermInternerExt {
29    /// Intern a named node and return it
30    fn intern_named_node(&self, iri: &str) -> Result<NamedNode, crate::OxirsError>;
31
32    /// Create and intern a new blank node
33    fn intern_blank_node(&self) -> BlankNode;
34
35    /// Intern a simple literal
36    fn intern_literal(&self, value: &str) -> Result<Literal, crate::OxirsError>;
37
38    /// Intern a literal with datatype
39    fn intern_literal_with_datatype(
40        &self,
41        value: &str,
42        datatype_iri: &str,
43    ) -> Result<Literal, crate::OxirsError>;
44
45    /// Intern a literal with language tag
46    fn intern_literal_with_language(
47        &self,
48        value: &str,
49        language: &str,
50    ) -> Result<Literal, crate::OxirsError>;
51}
52
53impl TermInternerExt for TermInterner {
54    fn intern_named_node(&self, iri: &str) -> Result<NamedNode, crate::OxirsError> {
55        // Intern the IRI string
56        let interned = self.intern(iri);
57        // Create NamedNode from the interned string
58        NamedNode::new(interned.as_ref())
59    }
60
61    fn intern_blank_node(&self) -> BlankNode {
62        // Generate a unique blank node
63        BlankNode::new_unique()
64    }
65
66    fn intern_literal(&self, value: &str) -> Result<Literal, crate::OxirsError> {
67        // Intern the literal value
68        let interned = self.intern(value);
69        // Create simple literal
70        Ok(Literal::new_simple_literal(interned.as_ref()))
71    }
72
73    fn intern_literal_with_datatype(
74        &self,
75        value: &str,
76        datatype_iri: &str,
77    ) -> Result<Literal, crate::OxirsError> {
78        // Intern both value and datatype IRI
79        let value_interned = self.intern(value);
80        let datatype_interned = self.intern(datatype_iri);
81        // Create datatype node and literal
82        let datatype_node = NamedNode::new(datatype_interned.as_ref())?;
83        Ok(Literal::new_typed_literal(
84            value_interned.as_ref(),
85            datatype_node,
86        ))
87    }
88
89    fn intern_literal_with_language(
90        &self,
91        value: &str,
92        language: &str,
93    ) -> Result<Literal, crate::OxirsError> {
94        // Intern both value and language tag
95        let value_interned = self.intern(value);
96        let language_interned = self.intern(language);
97        // Create language-tagged literal
98        let literal = Literal::new_language_tagged_literal(
99            value_interned.as_ref(),
100            language_interned.as_ref(),
101        )?;
102        Ok(literal)
103    }
104}
105
106/// Arena-based memory allocator for RDF terms
107///
108/// Provides fast allocation and automatic cleanup for temporary RDF operations
109#[derive(Debug)]
110pub struct RdfArena {
111    /// Main allocation arena (wrapped in Mutex for thread safety)
112    arena: std::sync::Mutex<Bump>,
113    /// String interner for the arena
114    interner: StringInterner,
115    /// Statistics
116    allocated_bytes: std::sync::atomic::AtomicUsize,
117    allocation_count: std::sync::atomic::AtomicUsize,
118}
119
120impl RdfArena {
121    /// Create a new RDF arena with the given capacity hint
122    pub fn new() -> Self {
123        RdfArena {
124            arena: std::sync::Mutex::new(Bump::new()),
125            interner: StringInterner::new(),
126            allocated_bytes: std::sync::atomic::AtomicUsize::new(0),
127            allocation_count: std::sync::atomic::AtomicUsize::new(0),
128        }
129    }
130
131    /// Create a new arena with pre-allocated capacity
132    pub fn with_capacity(capacity: usize) -> Self {
133        RdfArena {
134            arena: std::sync::Mutex::new(Bump::with_capacity(capacity)),
135            interner: StringInterner::new(),
136            allocated_bytes: std::sync::atomic::AtomicUsize::new(0),
137            allocation_count: std::sync::atomic::AtomicUsize::new(0),
138        }
139    }
140
141    /// Allocate a string in the arena
142    pub fn alloc_str(&self, s: &str) -> String {
143        // Since we can't return a reference with Mutex, return an owned String
144        // For ultra-performance mode, the caller should use string interning instead
145        self.allocated_bytes
146            .fetch_add(s.len(), std::sync::atomic::Ordering::Relaxed);
147        self.allocation_count
148            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
149        s.to_string()
150    }
151
152    /// Allocate and intern a string for efficient reuse
153    pub fn intern_str(&self, s: &str) -> InternedString {
154        InternedString::new_with_interner(s, &self.interner)
155    }
156
157    /// Reset the arena, freeing all allocated memory
158    pub fn reset(&self) {
159        if let Ok(mut arena) = self.arena.lock() {
160            arena.reset();
161            self.allocated_bytes
162                .store(0, std::sync::atomic::Ordering::Relaxed);
163            self.allocation_count
164                .store(0, std::sync::atomic::Ordering::Relaxed);
165        }
166    }
167
168    /// Get total bytes allocated
169    pub fn allocated_bytes(&self) -> usize {
170        self.allocated_bytes
171            .load(std::sync::atomic::Ordering::Relaxed)
172    }
173
174    /// Get total allocation count
175    pub fn allocation_count(&self) -> usize {
176        self.allocation_count
177            .load(std::sync::atomic::Ordering::Relaxed)
178    }
179}
180
181impl Default for RdfArena {
182    fn default() -> Self {
183        Self::new()
184    }
185}
186
187/// Zero-copy RDF term reference that avoids allocations
188///
189/// This provides efficient operations on RDF terms without copying data
190#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
191pub enum TermRef<'a> {
192    NamedNode(&'a str),
193    BlankNode(&'a str),
194    Literal(&'a str, Option<&'a str>, Option<&'a str>), // value, datatype, language
195    Variable(&'a str),
196}
197
198impl<'a> TermRef<'a> {
199    /// Create a term reference from a named node
200    pub fn from_named_node(node: &'a NamedNode) -> Self {
201        TermRef::NamedNode(node.as_str())
202    }
203
204    /// Create a term reference from a blank node
205    pub fn from_blank_node(node: &'a BlankNode) -> Self {
206        TermRef::BlankNode(node.as_str())
207    }
208
209    /// Create a term reference from a literal
210    pub fn from_literal(literal: &'a Literal) -> Self {
211        let language = literal.language();
212        // Always include datatype IRI for now to avoid lifetime issues
213        // Skip datatype for now due to lifetime issues - would need redesign
214        TermRef::Literal(literal.value(), None, language)
215    }
216
217    /// Get the string representation of this term
218    pub fn as_str(&self) -> &'a str {
219        match self {
220            TermRef::NamedNode(s) => s,
221            TermRef::BlankNode(s) => s,
222            TermRef::Literal(s, _, _) => s,
223            TermRef::Variable(s) => s,
224        }
225    }
226
227    /// Convert to an owned Term (allocating if necessary)
228    pub fn to_owned(&self) -> Result<Term, crate::OxirsError> {
229        match self {
230            TermRef::NamedNode(iri) => NamedNode::new(*iri).map(Term::NamedNode),
231            TermRef::BlankNode(id) => BlankNode::new(*id).map(Term::BlankNode),
232            TermRef::Literal(value, datatype, language) => {
233                let literal = if let Some(lang) = language {
234                    Literal::new_lang(*value, *lang)?
235                } else if let Some(dt) = datatype {
236                    let dt_node = NamedNode::new(*dt)?;
237                    Literal::new_typed(*value, dt_node)
238                } else {
239                    Literal::new(*value)
240                };
241                Ok(Term::Literal(literal))
242            }
243            TermRef::Variable(name) => Variable::new(*name).map(Term::Variable),
244        }
245    }
246
247    /// Returns true if this is a named node
248    pub fn is_named_node(&self) -> bool {
249        matches!(self, TermRef::NamedNode(_))
250    }
251
252    /// Returns true if this is a blank node
253    pub fn is_blank_node(&self) -> bool {
254        matches!(self, TermRef::BlankNode(_))
255    }
256
257    /// Returns true if this is a literal
258    pub fn is_literal(&self) -> bool {
259        matches!(self, TermRef::Literal(_, _, _))
260    }
261
262    /// Returns true if this is a variable
263    pub fn is_variable(&self) -> bool {
264        matches!(self, TermRef::Variable(_))
265    }
266}
267
268impl<'a> std::fmt::Display for TermRef<'a> {
269    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270        match self {
271            TermRef::NamedNode(iri) => write!(f, "<{iri}>"),
272            TermRef::BlankNode(id) => write!(f, "{id}"),
273            TermRef::Literal(value, datatype, language) => {
274                write!(f, "\"{value}\"")?;
275                if let Some(lang) = language {
276                    write!(f, "@{lang}")?;
277                } else if let Some(dt) = datatype {
278                    write!(f, "^^<{dt}>")?;
279                }
280                Ok(())
281            }
282            TermRef::Variable(name) => write!(f, "?{name}"),
283        }
284    }
285}
286
287/// Zero-copy triple reference for efficient operations
288#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
289pub struct TripleRef<'a> {
290    pub subject: TermRef<'a>,
291    pub predicate: TermRef<'a>,
292    pub object: TermRef<'a>,
293}
294
295impl<'a> TripleRef<'a> {
296    /// Create a new triple reference
297    pub fn new(subject: TermRef<'a>, predicate: TermRef<'a>, object: TermRef<'a>) -> Self {
298        TripleRef {
299            subject,
300            predicate,
301            object,
302        }
303    }
304
305    /// Create from an owned triple
306    ///
307    /// # Quoted triples (RDF-star)
308    ///
309    /// `TermRef` is a zero-copy, `Copy` view (`&'a str` payloads only), so it
310    /// has no owned-string variant available to hold a freshly serialized
311    /// quoted-triple description; a quoted subject/object is therefore
312    /// mapped to the same non-identifying `"<<quoted-triple>>"` placeholder
313    /// documented on [`crate::model::RdfTerm::as_str`]. Do not use the
314    /// `TermRef` produced for a quoted-triple component as an identity or
315    /// lookup key -- distinct quoted triples are indistinguishable through
316    /// this path. Code that needs real quoted-triple identity (e.g.
317    /// [`OptimizedGraph`]'s own subject/object interning) serializes the
318    /// full `<< s p o >>` content instead; see `intern_subject`/
319    /// `intern_object`.
320    pub fn from_triple(triple: &'a Triple) -> Self {
321        TripleRef {
322            subject: match triple.subject() {
323                Subject::NamedNode(n) => TermRef::NamedNode(n.as_str()),
324                Subject::BlankNode(b) => TermRef::BlankNode(b.as_str()),
325                Subject::Variable(v) => TermRef::Variable(v.as_str()),
326                Subject::QuotedTriple(_) => TermRef::NamedNode("<<quoted-triple>>"),
327            },
328            predicate: match triple.predicate() {
329                Predicate::NamedNode(n) => TermRef::NamedNode(n.as_str()),
330                Predicate::Variable(v) => TermRef::Variable(v.as_str()),
331            },
332            object: match triple.object() {
333                Object::NamedNode(n) => TermRef::NamedNode(n.as_str()),
334                Object::BlankNode(b) => TermRef::BlankNode(b.as_str()),
335                Object::Literal(l) => TermRef::from_literal(l),
336                Object::Variable(v) => TermRef::Variable(v.as_str()),
337                Object::QuotedTriple(_) => TermRef::NamedNode("<<quoted-triple>>"),
338            },
339        }
340    }
341
342    /// Convert to an owned triple
343    pub fn to_owned(&self) -> Result<Triple, crate::OxirsError> {
344        let subject = match self.subject.to_owned()? {
345            Term::NamedNode(n) => Subject::NamedNode(n),
346            Term::BlankNode(b) => Subject::BlankNode(b),
347            _ => return Err(crate::OxirsError::Parse("Invalid subject term".to_string())),
348        };
349
350        let predicate = match self.predicate.to_owned()? {
351            Term::NamedNode(n) => Predicate::NamedNode(n),
352            _ => {
353                return Err(crate::OxirsError::Parse(
354                    "Invalid predicate term".to_string(),
355                ))
356            }
357        };
358
359        let object = match self.object.to_owned()? {
360            Term::NamedNode(n) => Object::NamedNode(n),
361            Term::BlankNode(b) => Object::BlankNode(b),
362            Term::Literal(l) => Object::Literal(l),
363            _ => return Err(crate::OxirsError::Parse("Invalid object term".to_string())),
364        };
365
366        Ok(Triple::new(subject, predicate, object))
367    }
368}
369
370impl<'a> std::fmt::Display for TripleRef<'a> {
371    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
372        write!(f, "{} {} {} .", self.subject, self.predicate, self.object)
373    }
374}
375
376/// Lock-free graph operations using epoch-based memory management
377#[derive(Debug)]
378pub struct LockFreeGraph {
379    /// Atomic pointer to the current graph data
380    data: Atomic<GraphData>,
381    /// Epoch for safe memory reclamation
382    epoch: epoch::Guard,
383}
384
385/// Internal graph data structure for lock-free operations
386#[derive(Debug)]
387struct GraphData {
388    /// Triples stored in a B-tree for ordered access
389    triples: BTreeSet<Triple>,
390    /// Version number for optimistic updates
391    version: u64,
392}
393
394impl LockFreeGraph {
395    /// Create a new lock-free graph
396    pub fn new() -> Self {
397        let initial_data = GraphData {
398            triples: BTreeSet::new(),
399            version: 0,
400        };
401
402        LockFreeGraph {
403            data: Atomic::new(initial_data),
404            epoch: epoch::pin(),
405        }
406    }
407
408    /// Insert a triple using compare-and-swap
409    pub fn insert(&self, triple: Triple) -> bool {
410        loop {
411            let current = self.data.load(Ordering::Acquire, &self.epoch);
412            let current_ref = unsafe { current.deref() };
413
414            // Check if triple already exists
415            if current_ref.triples.contains(&triple) {
416                return false;
417            }
418
419            // Create new data with the inserted triple
420            let mut new_triples = current_ref.triples.clone();
421            new_triples.insert(triple.clone());
422
423            let new_data = GraphData {
424                triples: new_triples,
425                version: current_ref.version + 1,
426            };
427
428            // Try to update atomically
429            match self.data.compare_exchange_weak(
430                current,
431                Owned::new(new_data),
432                Ordering::Release,
433                Ordering::Relaxed,
434                &self.epoch,
435            ) {
436                Ok(_) => {
437                    // Successfully updated
438                    unsafe {
439                        self.epoch.defer_destroy(current);
440                    }
441                    return true;
442                }
443                Err(_) => {
444                    // Retry with new current value
445                    continue;
446                }
447            }
448        }
449    }
450
451    /// Get the current number of triples
452    pub fn len(&self) -> usize {
453        let current = self.data.load(Ordering::Acquire, &self.epoch);
454        unsafe { current.deref().triples.len() }
455    }
456
457    /// Check if the graph is empty
458    pub fn is_empty(&self) -> bool {
459        self.len() == 0
460    }
461
462    /// Check if a triple exists
463    pub fn contains(&self, triple: &Triple) -> bool {
464        let current = self.data.load(Ordering::Acquire, &self.epoch);
465        unsafe { current.deref().triples.contains(triple) }
466    }
467}
468
469impl Default for LockFreeGraph {
470    fn default() -> Self {
471        Self::new()
472    }
473}
474
475/// High-performance graph with multiple indexing strategies
476#[derive(Debug)]
477pub struct OptimizedGraph {
478    /// Subject-Predicate-Object index
479    spo: DashMap<InternedString, DashMap<InternedString, BTreeSet<InternedString>>>,
480    /// Predicate-Object-Subject index
481    pos: DashMap<InternedString, DashMap<InternedString, BTreeSet<InternedString>>>,
482    /// Object-Subject-Predicate index
483    osp: DashMap<InternedString, DashMap<InternedString, BTreeSet<InternedString>>>,
484    /// String interner for memory efficiency
485    interner: Arc<StringInterner>,
486    /// Statistics
487    stats: Arc<RwLock<GraphStats>>,
488}
489
490/// Statistics for the optimized graph
491#[derive(Debug, Clone, Default)]
492pub struct GraphStats {
493    pub triple_count: usize,
494    pub unique_subjects: usize,
495    pub unique_predicates: usize,
496    pub unique_objects: usize,
497    pub index_memory_usage: usize,
498    pub intern_hit_ratio: f64,
499}
500
501impl OptimizedGraph {
502    /// Create a new optimized graph
503    pub fn new() -> Self {
504        OptimizedGraph {
505            spo: DashMap::new(),
506            pos: DashMap::new(),
507            osp: DashMap::new(),
508            interner: Arc::new(StringInterner::new()),
509            stats: Arc::new(RwLock::new(GraphStats::default())),
510        }
511    }
512
513    /// Insert a triple into all indexes
514    pub fn insert(&self, triple: &Triple) -> bool {
515        let subject = self.intern_subject(triple.subject());
516        let predicate = self.intern_predicate(triple.predicate());
517        let object = self.intern_object(triple.object());
518
519        // Insert into SPO index
520        let spo_entry = self.spo.entry(subject.clone()).or_default();
521        let mut po_entry = spo_entry.entry(predicate.clone()).or_default();
522        let was_new = po_entry.insert(object.clone());
523
524        if was_new {
525            // Insert into POS index
526            let pos_entry = self.pos.entry(predicate.clone()).or_default();
527            let mut os_entry = pos_entry.entry(object.clone()).or_default();
528            os_entry.insert(subject.clone());
529
530            // Insert into OSP index
531            let osp_entry = self.osp.entry(object.clone()).or_default();
532            let mut sp_entry = osp_entry.entry(subject.clone()).or_default();
533            sp_entry.insert(predicate);
534
535            // Update statistics
536            {
537                let mut stats = self.stats.write();
538                stats.triple_count += 1;
539                stats.intern_hit_ratio = self.interner.stats().hit_ratio();
540            }
541        }
542
543        was_new
544    }
545
546    /// Query triples by pattern (None = wildcard)
547    pub fn query(
548        &self,
549        subject: Option<&Subject>,
550        predicate: Option<&Predicate>,
551        object: Option<&Object>,
552    ) -> Vec<Triple> {
553        let mut results = Vec::new();
554
555        // Choose the most selective index based on bound variables
556        match (subject.is_some(), predicate.is_some(), object.is_some()) {
557            (true, true, true) => {
558                // Exact match - use SPO index
559                if let (Some(s), Some(p), Some(o)) = (subject, predicate, object) {
560                    let s_intern = self.intern_subject(s);
561                    let p_intern = self.intern_predicate(p);
562                    let o_intern = self.intern_object(o);
563
564                    if let Some(po_map) = self.spo.get(&s_intern) {
565                        if let Some(o_set) = po_map.get(&p_intern) {
566                            if o_set.contains(&o_intern) {
567                                let triple = Triple::new(s.clone(), p.clone(), o.clone());
568                                results.push(triple);
569                            }
570                        }
571                    }
572                }
573            }
574            (true, true, false) => {
575                // Subject and predicate bound - use SPO index
576                if let (Some(s), Some(p)) = (subject, predicate) {
577                    let s_intern = self.intern_subject(s);
578                    let p_intern = self.intern_predicate(p);
579
580                    if let Some(po_map) = self.spo.get(&s_intern) {
581                        if let Some(o_set) = po_map.get(&p_intern) {
582                            for o_intern in o_set.iter() {
583                                if let Ok(object) = self.unintern_object(o_intern) {
584                                    let triple = Triple::new(s.clone(), p.clone(), object);
585                                    results.push(triple);
586                                }
587                            }
588                        }
589                    }
590                }
591            }
592            (false, true, true) => {
593                // Predicate and object bound - use POS index
594                if let (Some(p), Some(o)) = (predicate, object) {
595                    let p_intern = self.intern_predicate(p);
596                    let o_intern = self.intern_object(o);
597
598                    if let Some(os_map) = self.pos.get(&p_intern) {
599                        if let Some(s_set) = os_map.get(&o_intern) {
600                            for s_intern in s_set.iter() {
601                                if let Ok(subject) = self.unintern_subject(s_intern) {
602                                    let triple = Triple::new(subject, p.clone(), o.clone());
603                                    results.push(triple);
604                                }
605                            }
606                        }
607                    }
608                }
609            }
610            _ => {
611                // Other patterns - full scan (could be optimized further)
612                for s_entry in &self.spo {
613                    let s_intern = s_entry.key();
614                    if let Ok(s) = self.unintern_subject(s_intern) {
615                        if let Some(subj) = subject {
616                            if subj != &s {
617                                continue;
618                            }
619                        }
620
621                        for po_entry in s_entry.value().iter() {
622                            let p_intern = po_entry.key();
623                            if let Ok(p) = self.unintern_predicate(p_intern) {
624                                if let Some(pred) = predicate {
625                                    if pred != &p {
626                                        continue;
627                                    }
628                                }
629
630                                for o_intern in po_entry.value().iter() {
631                                    if let Ok(o) = self.unintern_object(o_intern) {
632                                        if let Some(obj) = object {
633                                            if obj != &o {
634                                                continue;
635                                            }
636                                        }
637
638                                        let triple = Triple::new(s.clone(), p.clone(), o);
639                                        results.push(triple);
640                                    }
641                                }
642                            }
643                        }
644                    }
645                }
646            }
647        }
648
649        results
650    }
651
652    /// Get current statistics
653    pub fn stats(&self) -> GraphStats {
654        self.stats.read().clone()
655    }
656
657    /// Intern a subject term
658    fn intern_subject(&self, subject: &Subject) -> InternedString {
659        match subject {
660            Subject::NamedNode(n) => InternedString::new_with_interner(n.as_str(), &self.interner),
661            Subject::BlankNode(b) => InternedString::new_with_interner(b.as_str(), &self.interner),
662            Subject::Variable(v) => InternedString::new_with_interner(v.as_str(), &self.interner),
663            Subject::QuotedTriple(qt) => {
664                // `RdfTerm::as_str()` returns the fixed, non-identifying
665                // placeholder "<<quoted-triple>>" for every quoted triple
666                // (see its doc comment); interning that constant would
667                // collapse every distinct quoted-triple subject into a
668                // single dictionary entry, silently deduplicating unrelated
669                // RDF-star statements. Interning the full `<< s p o >>`
670                // serialization instead (via `QuotedTriple`'s `Display`)
671                // keeps distinct quoted triples distinguishable. This can
672                // never collide with a NamedNode/BlankNode/Variable string
673                // since those never start with "<<".
674                let serialized = format!("{qt}");
675                InternedString::new_with_interner(&serialized, &self.interner)
676            }
677        }
678    }
679
680    /// Intern a predicate term
681    fn intern_predicate(&self, predicate: &Predicate) -> InternedString {
682        match predicate {
683            Predicate::NamedNode(n) => {
684                InternedString::new_with_interner(n.as_str(), &self.interner)
685            }
686            Predicate::Variable(v) => InternedString::new_with_interner(v.as_str(), &self.interner),
687        }
688    }
689
690    /// Intern an object term
691    fn intern_object(&self, object: &Object) -> InternedString {
692        match object {
693            Object::NamedNode(n) => InternedString::new_with_interner(n.as_str(), &self.interner),
694            Object::BlankNode(b) => InternedString::new_with_interner(b.as_str(), &self.interner),
695            Object::Literal(l) => {
696                // For literals, we store a serialized representation
697                let serialized = format!("{l}");
698                InternedString::new_with_interner(&serialized, &self.interner)
699            }
700            Object::Variable(v) => InternedString::new_with_interner(v.as_str(), &self.interner),
701            Object::QuotedTriple(qt) => {
702                // See the matching comment in `intern_subject`: interning the
703                // full serialized content (rather than the fixed
704                // "<<quoted-triple>>" placeholder) keeps distinct quoted
705                // triples distinguishable in the index.
706                let serialized = format!("{qt}");
707                InternedString::new_with_interner(&serialized, &self.interner)
708            }
709        }
710    }
711
712    /// Convert interned subject back to Subject
713    fn unintern_subject(&self, interned: &InternedString) -> Result<Subject, crate::OxirsError> {
714        let s = interned.as_str();
715        if s.starts_with("?") || s.starts_with("$") {
716            Variable::new(&s[1..]).map(Subject::Variable)
717        } else if s.starts_with("_:") {
718            BlankNode::new(s).map(Subject::BlankNode)
719        } else {
720            NamedNode::new(s).map(Subject::NamedNode)
721        }
722    }
723
724    /// Convert interned predicate back to Predicate
725    fn unintern_predicate(
726        &self,
727        interned: &InternedString,
728    ) -> Result<Predicate, crate::OxirsError> {
729        let s = interned.as_str();
730        if s.starts_with("?") || s.starts_with("$") {
731            Variable::new(&s[1..]).map(Predicate::Variable)
732        } else {
733            NamedNode::new(s).map(Predicate::NamedNode)
734        }
735    }
736
737    /// Convert interned object back to Object
738    fn unintern_object(&self, interned: &InternedString) -> Result<Object, crate::OxirsError> {
739        let s = interned.as_str();
740        if s.starts_with("?") || s.starts_with("$") {
741            return Variable::new(&s[1..]).map(Object::Variable);
742        } else if let Some(stripped) = s.strip_prefix("\"") {
743            // Parse literal (simplified - would need full parser for production)
744            if let Some(end_quote) = stripped.find('"') {
745                let value = &stripped[..end_quote];
746                return Ok(Object::Literal(Literal::new(value)));
747            }
748            // If no end quote found, treat as a simple literal
749            return Ok(Object::Literal(Literal::new(s)));
750        }
751
752        if s.starts_with("_:") {
753            BlankNode::new(s).map(Object::BlankNode)
754        } else {
755            NamedNode::new(s).map(Object::NamedNode)
756        }
757    }
758}
759
760impl Default for OptimizedGraph {
761    fn default() -> Self {
762        Self::new()
763    }
764}
765
766/// Lock-free queue for batch processing operations
767#[cfg(feature = "parallel")]
768#[derive(Debug)]
769pub struct BatchProcessor {
770    /// Queue for pending operations
771    operation_queue: SegQueue<BatchOperation>,
772    /// Background processing threads
773    processing_pool: rayon::ThreadPool,
774    /// Statistics
775    stats: Arc<RwLock<BatchStats>>,
776}
777
778/// Batch operation types
779#[derive(Debug, Clone)]
780pub enum BatchOperation {
781    Insert(Quad),
782    Delete(Quad),
783    Update { old: Quad, new: Quad },
784    Compact,
785}
786
787/// Batch processing statistics
788#[derive(Debug, Default, Clone)]
789pub struct BatchStats {
790    pub operations_processed: usize,
791    pub batch_size: usize,
792    pub processing_time_ms: u64,
793    pub throughput_ops_per_sec: f64,
794}
795
796#[cfg(feature = "parallel")]
797impl BatchProcessor {
798    /// Create a new batch processor with specified thread count
799    pub fn new(num_threads: usize) -> Self {
800        let pool = rayon::ThreadPoolBuilder::new()
801            .num_threads(num_threads)
802            .build()
803            .expect("thread pool builder should succeed");
804
805        BatchProcessor {
806            operation_queue: SegQueue::new(),
807            processing_pool: pool,
808            stats: Arc::new(RwLock::new(BatchStats::default())),
809        }
810    }
811
812    /// Add an operation to the batch queue
813    pub fn push(&self, operation: BatchOperation) {
814        self.operation_queue.push(operation);
815    }
816
817    /// Process all pending operations in batches
818    pub fn process_batch(&self, batch_size: usize) -> Result<usize, crate::OxirsError> {
819        let start_time = std::time::Instant::now();
820        let mut operations = Vec::with_capacity(batch_size);
821
822        // Collect operations from queue
823        for _ in 0..batch_size {
824            if let Some(op) = self.operation_queue.pop() {
825                operations.push(op);
826            } else {
827                break;
828            }
829        }
830
831        if operations.is_empty() {
832            return Ok(0);
833        }
834
835        let operations_count = operations.len();
836
837        // Process operations in parallel using Rayon
838        self.processing_pool.install(|| {
839            operations.par_iter().for_each(|operation| {
840                match operation {
841                    BatchOperation::Insert(_quad) => {
842                        // Parallel insert logic would go here
843                    }
844                    BatchOperation::Delete(_quad) => {
845                        // Parallel delete logic would go here
846                    }
847                    BatchOperation::Update {
848                        old: _old,
849                        new: _new,
850                    } => {
851                        // Parallel update logic would go here
852                    }
853                    BatchOperation::Compact => {
854                        // Compaction logic would go here
855                    }
856                }
857            });
858        });
859
860        // Update statistics
861        let processing_time = start_time.elapsed();
862        {
863            let mut stats = self.stats.write();
864            stats.operations_processed += operations_count;
865            stats.batch_size = batch_size;
866            stats.processing_time_ms = processing_time.as_millis() as u64;
867            if processing_time.as_secs_f64() > 0.0 {
868                stats.throughput_ops_per_sec =
869                    operations_count as f64 / processing_time.as_secs_f64();
870            }
871        }
872
873        Ok(operations_count)
874    }
875
876    /// Get current processing statistics
877    pub fn stats(&self) -> BatchStats {
878        self.stats.read().clone()
879    }
880
881    /// Get the number of pending operations
882    pub fn pending_operations(&self) -> usize {
883        self.operation_queue.len()
884    }
885}
886
887#[cfg(feature = "parallel")]
888impl Default for BatchProcessor {
889    fn default() -> Self {
890        Self::new(
891            std::thread::available_parallelism()
892                .map(|n| n.get())
893                .unwrap_or(1),
894        )
895    }
896}
897
898/// SIMD-accelerated string operations for RDF processing
899pub mod simd {
900    #[cfg(feature = "simd")]
901    use wide::u8x32;
902
903    /// Fast IRI validation using SIMD operations
904    #[cfg(feature = "simd")]
905    pub fn validate_iri_fast(iri: &str) -> bool {
906        if iri.is_empty() {
907            return false;
908        }
909
910        let bytes = iri.as_bytes();
911        let len = bytes.len();
912
913        // Process 32 bytes at a time using SIMD
914        let chunks = len / 32;
915        let _remainder = len % 32;
916
917        for i in 0..chunks {
918            let start = i * 32;
919            let chunk = &bytes[start..start + 32];
920
921            // Load 32 bytes
922            let data = u8x32::from([
923                chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
924                chunk[8], chunk[9], chunk[10], chunk[11], chunk[12], chunk[13], chunk[14],
925                chunk[15], chunk[16], chunk[17], chunk[18], chunk[19], chunk[20], chunk[21],
926                chunk[22], chunk[23], chunk[24], chunk[25], chunk[26], chunk[27], chunk[28],
927                chunk[29], chunk[30], chunk[31],
928            ]);
929
930            // Check for forbidden characters (< > " { } | \ ^ ` space)
931            let forbidden_chars = [b'<', b'>', b'"', b'{', b'}', b'|', b'\\', b'^', b'`', b' '];
932
933            for &forbidden in &forbidden_chars {
934                let forbidden_vec = u8x32::splat(forbidden);
935                let matches = data.simd_eq(forbidden_vec);
936                if matches.any() {
937                    return false;
938                }
939            }
940
941            // Check for control characters (0-31, 127-159)
942            for &byte in chunk {
943                if matches!(byte, 0..=31 | 127..=159) {
944                    return false;
945                }
946            }
947        }
948
949        // Process remaining bytes
950        for &byte in &bytes[chunks * 32..] {
951            if matches!(byte,
952                0..=31 | 127..=159 | // Control characters
953                b'<' | b'>' | b'"' | b'{' | b'}' | b'|' | b'\\' | b'^' | b'`' | b' ' // Forbidden
954            ) {
955                return false;
956            }
957        }
958
959        true
960    }
961
962    /// Fast IRI validation (non-SIMD fallback)
963    #[cfg(not(feature = "simd"))]
964    pub fn validate_iri_fast(iri: &str) -> bool {
965        if iri.is_empty() {
966            return false;
967        }
968
969        for byte in iri.bytes() {
970            if matches!(
971                byte,
972                b'<' | b'>' | b'"' | b'{' | b'}' | b'|' | b'\\' | b'^' | b'`' | b' ' // Forbidden
973            ) {
974                return false;
975            }
976        }
977
978        true
979    }
980
981    /// Fast string comparison using SIMD
982    pub fn compare_strings_fast(a: &str, b: &str) -> std::cmp::Ordering {
983        if a.len() != b.len() {
984            return a.len().cmp(&b.len());
985        }
986
987        let a_bytes = a.as_bytes();
988        let b_bytes = b.as_bytes();
989        let len = a_bytes.len();
990
991        // Process 32 bytes at a time
992        let chunks = len / 32;
993
994        for i in 0..chunks {
995            let start = i * 32;
996            let a_chunk = &a_bytes[start..start + 32];
997            let b_chunk = &b_bytes[start..start + 32];
998
999            // Compare chunks bytewise
1000            for j in 0..32 {
1001                match a_chunk[j].cmp(&b_chunk[j]) {
1002                    std::cmp::Ordering::Equal => continue,
1003                    other => return other,
1004                }
1005            }
1006        }
1007
1008        // Process remaining bytes
1009        for i in chunks * 32..len {
1010            match a_bytes[i].cmp(&b_bytes[i]) {
1011                std::cmp::Ordering::Equal => continue,
1012                other => return other,
1013            }
1014        }
1015
1016        std::cmp::Ordering::Equal
1017    }
1018}
1019
1020#[cfg(test)]
1021mod tests {
1022    use super::*;
1023
1024    #[test]
1025    fn test_rdf_arena() {
1026        let arena = RdfArena::new();
1027
1028        let s1 = arena.alloc_str("test string 1");
1029        let s2 = arena.alloc_str("test string 2");
1030
1031        assert_eq!(s1, "test string 1");
1032        assert_eq!(s2, "test string 2");
1033        assert!(arena.allocated_bytes() > 0);
1034        assert_eq!(arena.allocation_count(), 2);
1035    }
1036
1037    #[test]
1038    fn test_term_ref() {
1039        let node = NamedNode::new("http://example.org/test").expect("valid IRI");
1040        let term_ref = TermRef::from_named_node(&node);
1041
1042        assert!(term_ref.is_named_node());
1043        assert_eq!(term_ref.as_str(), "http://example.org/test");
1044
1045        let owned = term_ref.to_owned().expect("operation should succeed");
1046        assert!(owned.is_named_node());
1047    }
1048
1049    #[test]
1050    fn test_triple_ref() {
1051        let subject = NamedNode::new("http://example.org/s").expect("valid IRI");
1052        let predicate = NamedNode::new("http://example.org/p").expect("valid IRI");
1053        let object = Literal::new("test object");
1054        let triple = Triple::new(subject, predicate, object);
1055
1056        let triple_ref = TripleRef::from_triple(&triple);
1057        assert!(triple_ref.subject.is_named_node());
1058        assert!(triple_ref.predicate.is_named_node());
1059        assert!(triple_ref.object.is_literal());
1060
1061        let owned = triple_ref.to_owned().expect("operation should succeed");
1062        assert_eq!(owned, triple);
1063    }
1064
1065    #[test]
1066    fn test_lock_free_graph() {
1067        let graph = LockFreeGraph::new();
1068        assert!(graph.is_empty());
1069
1070        let subject = NamedNode::new("http://example.org/s").expect("valid IRI");
1071        let predicate = NamedNode::new("http://example.org/p").expect("valid IRI");
1072        let object = Literal::new("test object");
1073        let triple = Triple::new(subject, predicate, object);
1074
1075        assert!(graph.insert(triple.clone()));
1076        assert!(!graph.insert(triple.clone())); // Duplicate
1077        assert_eq!(graph.len(), 1);
1078        assert!(graph.contains(&triple));
1079    }
1080
1081    #[test]
1082    fn test_optimized_graph() {
1083        let graph = OptimizedGraph::new();
1084
1085        let subject = NamedNode::new("http://example.org/s").expect("valid IRI");
1086        let predicate = NamedNode::new("http://example.org/p").expect("valid IRI");
1087        let object = Literal::new("test object");
1088        let triple = Triple::new(subject.clone(), predicate.clone(), object.clone());
1089
1090        assert!(graph.insert(&triple));
1091        assert!(!graph.insert(&triple)); // Duplicate
1092
1093        // Query by exact match
1094        let results = graph.query(
1095            Some(&Subject::NamedNode(subject.clone())),
1096            Some(&Predicate::NamedNode(predicate.clone())),
1097            Some(&Object::Literal(object.clone())),
1098        );
1099        assert_eq!(results.len(), 1);
1100        assert_eq!(results[0], triple);
1101
1102        // Query by subject only
1103        let results = graph.query(Some(&Subject::NamedNode(subject)), None, None);
1104        assert_eq!(results.len(), 1);
1105
1106        let stats = graph.stats();
1107        assert_eq!(stats.triple_count, 1);
1108    }
1109
1110    /// Regression test: two *distinct* quoted-triple subjects must not
1111    /// collapse into one `OptimizedGraph` index entry. Before the fix,
1112    /// `intern_subject` interned every quoted triple as the same fixed
1113    /// placeholder string, so a second triple whose subject was a
1114    /// different quoted triple (but shared predicate/object with the
1115    /// first) was wrongly treated as a duplicate of the first.
1116    #[test]
1117    fn regression_optimized_graph_distinguishes_quoted_triple_subjects() {
1118        let graph = OptimizedGraph::new();
1119
1120        let inner1 = Triple::new(
1121            NamedNode::new("http://example.org/a1").expect("valid IRI"),
1122            NamedNode::new("http://example.org/rel").expect("valid IRI"),
1123            NamedNode::new("http://example.org/b1").expect("valid IRI"),
1124        );
1125        let inner2 = Triple::new(
1126            NamedNode::new("http://example.org/a2").expect("valid IRI"),
1127            NamedNode::new("http://example.org/rel").expect("valid IRI"),
1128            NamedNode::new("http://example.org/b2").expect("valid IRI"),
1129        );
1130        assert_ne!(inner1, inner2, "test fixture triples must differ");
1131
1132        let predicate = NamedNode::new("http://example.org/certainty").expect("valid IRI");
1133        let object = Literal::new("high");
1134
1135        let triple1 = Triple::new(
1136            Subject::QuotedTriple(Box::new(QuotedTriple::new(inner1))),
1137            predicate.clone(),
1138            object.clone(),
1139        );
1140        let triple2 = Triple::new(
1141            Subject::QuotedTriple(Box::new(QuotedTriple::new(inner2))),
1142            predicate,
1143            object,
1144        );
1145
1146        // Both inserts must be reported as genuinely new: the two quoted
1147        // triples are different subjects even though the shared placeholder
1148        // string used to make them indistinguishable.
1149        assert!(graph.insert(&triple1));
1150        assert!(
1151            graph.insert(&triple2),
1152            "a second, distinct quoted-triple subject must not be treated as a duplicate"
1153        );
1154
1155        let stats = graph.stats();
1156        assert_eq!(stats.triple_count, 2);
1157    }
1158
1159    #[test]
1160    fn test_simd_iri_validation() {
1161        assert!(simd::validate_iri_fast("http://example.org/test"));
1162        assert!(!simd::validate_iri_fast("http://example.org/<invalid>"));
1163        assert!(!simd::validate_iri_fast(""));
1164        assert!(!simd::validate_iri_fast(
1165            "http://example.org/test with spaces"
1166        ));
1167    }
1168
1169    #[test]
1170    fn test_simd_string_comparison() {
1171        assert_eq!(
1172            simd::compare_strings_fast("abc", "abc"),
1173            std::cmp::Ordering::Equal
1174        );
1175        assert_eq!(
1176            simd::compare_strings_fast("abc", "def"),
1177            std::cmp::Ordering::Less
1178        );
1179        assert_eq!(
1180            simd::compare_strings_fast("def", "abc"),
1181            std::cmp::Ordering::Greater
1182        );
1183        assert_eq!(
1184            simd::compare_strings_fast("short", "longer"),
1185            std::cmp::Ordering::Less
1186        );
1187    }
1188
1189    #[test]
1190    fn test_arena_reset() {
1191        let arena = RdfArena::new();
1192
1193        arena.alloc_str("test");
1194        assert!(arena.allocated_bytes() > 0);
1195
1196        arena.reset();
1197        assert_eq!(arena.allocated_bytes(), 0);
1198        assert_eq!(arena.allocation_count(), 0);
1199    }
1200
1201    #[test]
1202    fn test_concurrent_optimized_graph() {
1203        use std::sync::Arc;
1204        use std::thread;
1205
1206        let graph = Arc::new(OptimizedGraph::new());
1207        let handles: Vec<_> = (0..10)
1208            .map(|i| {
1209                let graph = Arc::clone(&graph);
1210                thread::spawn(move || {
1211                    let subject = NamedNode::new(format!("http://example.org/s{i}"))
1212                        .expect("valid IRI from format");
1213                    let predicate = NamedNode::new("http://example.org/p").expect("valid IRI");
1214                    let object = Literal::new(format!("object{i}"));
1215                    let triple = Triple::new(subject, predicate, object);
1216
1217                    graph.insert(&triple)
1218                })
1219            })
1220            .collect();
1221
1222        let results: Vec<bool> = handles
1223            .into_iter()
1224            .map(|h| h.join().expect("thread should not panic"))
1225            .collect();
1226        assert!(results.iter().all(|&inserted| inserted));
1227
1228        let stats = graph.stats();
1229        assert_eq!(stats.triple_count, 10);
1230    }
1231}
1232
1233/// Zero-copy buffer for efficient data manipulation
1234pub struct ZeroCopyBuffer {
1235    data: Pin<Box<[u8]>>,
1236    len: usize,
1237}
1238
1239impl ZeroCopyBuffer {
1240    /// Create a new zero-copy buffer with the given capacity
1241    pub fn new(capacity: usize) -> Self {
1242        Self::with_capacity(capacity)
1243    }
1244
1245    /// Create a new zero-copy buffer with the given capacity
1246    pub fn with_capacity(capacity: usize) -> Self {
1247        let vec = vec![0; capacity];
1248        let data = vec.into_boxed_slice();
1249
1250        ZeroCopyBuffer {
1251            data: Pin::new(data),
1252            len: 0,
1253        }
1254    }
1255
1256    /// Get a slice of the buffer data
1257    pub fn as_slice(&self) -> &[u8] {
1258        &self.data[..self.len]
1259    }
1260
1261    /// Get a mutable slice of the entire buffer for reading into
1262    pub fn as_mut_slice(&mut self) -> &mut [u8] {
1263        &mut self.data[..]
1264    }
1265
1266    /// Get the buffer capacity
1267    pub fn capacity(&self) -> usize {
1268        self.data.len()
1269    }
1270
1271    /// Get the current length of data in the buffer
1272    pub fn len(&self) -> usize {
1273        self.len
1274    }
1275
1276    /// Check if the buffer is empty
1277    pub fn is_empty(&self) -> bool {
1278        self.len == 0
1279    }
1280
1281    /// Clear the buffer
1282    pub fn clear(&mut self) {
1283        self.len = 0;
1284    }
1285
1286    /// Reset the buffer (alias for clear)
1287    pub fn reset(&mut self) {
1288        self.clear();
1289    }
1290
1291    /// Set the length of valid data in the buffer
1292    pub fn set_len(&mut self, len: usize) {
1293        assert!(len <= self.capacity());
1294        self.len = len;
1295    }
1296
1297    /// Write data to the buffer
1298    pub fn write(&mut self, data: &[u8]) -> Result<usize, std::io::Error> {
1299        let available = self.capacity() - self.len;
1300        let to_write = data.len().min(available);
1301
1302        if to_write == 0 {
1303            return Err(std::io::Error::new(
1304                std::io::ErrorKind::WriteZero,
1305                "Buffer is full",
1306            ));
1307        }
1308
1309        // SAFETY: We're writing within bounds
1310        unsafe {
1311            let dst = self.data.as_mut_ptr().add(self.len);
1312            std::ptr::copy_nonoverlapping(data.as_ptr(), dst, to_write);
1313        }
1314
1315        self.len += to_write;
1316        Ok(to_write)
1317    }
1318}
1319
1320/// SIMD JSON processor for fast JSON parsing
1321#[derive(Clone)]
1322pub struct SimdJsonProcessor;
1323
1324impl SimdJsonProcessor {
1325    /// Create a new SIMD JSON processor
1326    pub fn new() -> Self {
1327        SimdJsonProcessor
1328    }
1329
1330    /// Parse JSON bytes into a Value
1331    pub fn parse<'a>(
1332        &mut self,
1333        json: &'a mut [u8],
1334    ) -> Result<simd_json::BorrowedValue<'a>, simd_json::Error> {
1335        simd_json::to_borrowed_value(json)
1336    }
1337
1338    /// Parse JSON string into a Value
1339    pub fn parse_str<'a>(
1340        &mut self,
1341        json: &'a mut str,
1342    ) -> Result<simd_json::BorrowedValue<'a>, simd_json::Error> {
1343        let bytes = unsafe { json.as_bytes_mut() };
1344        simd_json::to_borrowed_value(bytes)
1345    }
1346
1347    /// Parse JSON bytes into an owned Value
1348    pub fn parse_owned(
1349        &mut self,
1350        json: &mut [u8],
1351    ) -> Result<simd_json::OwnedValue, simd_json::Error> {
1352        simd_json::to_owned_value(json)
1353    }
1354
1355    /// Parse JSON bytes into a serde_json::Value (compatibility method)
1356    pub fn parse_json(&self, json: &[u8]) -> Result<serde_json::Value, serde_json::Error> {
1357        serde_json::from_slice(json)
1358    }
1359}
1360
1361impl Default for SimdJsonProcessor {
1362    fn default() -> Self {
1363        Self::new()
1364    }
1365}
1366
1367/// SIMD XML processor for fast XML parsing
1368///
1369/// Provides SIMD-accelerated string operations for RDF/XML streaming processing.
1370/// Uses SIMD instructions for fast character scanning and pattern matching.
1371#[derive(Clone, Debug)]
1372pub struct SimdXmlProcessor {
1373    /// Buffer for SIMD-optimized string operations
1374    scan_buffer: Vec<u8>,
1375}
1376
1377impl SimdXmlProcessor {
1378    /// Create a new SIMD XML processor
1379    pub fn new() -> Self {
1380        SimdXmlProcessor {
1381            scan_buffer: Vec::with_capacity(4096),
1382        }
1383    }
1384
1385    /// SIMD-accelerated scan for XML special characters
1386    /// Returns the index of the first special character or None
1387    #[cfg(target_arch = "x86_64")]
1388    pub fn find_special_char(&self, data: &[u8]) -> Option<usize> {
1389        use std::arch::x86_64::*;
1390
1391        // Use SIMD for chunks of 16 bytes
1392        const CHUNK_SIZE: usize = 16;
1393        let mut offset = 0;
1394
1395        if data.len() >= CHUNK_SIZE {
1396            unsafe {
1397                // Create SIMD masks for special characters: < > & " '
1398                let lt = _mm_set1_epi8(b'<' as i8);
1399                let gt = _mm_set1_epi8(b'>' as i8);
1400                let amp = _mm_set1_epi8(b'&' as i8);
1401                let quot = _mm_set1_epi8(b'"' as i8);
1402                let apos = _mm_set1_epi8(b'\'' as i8);
1403
1404                while offset + CHUNK_SIZE <= data.len() {
1405                    let chunk = _mm_loadu_si128(data.as_ptr().add(offset) as *const __m128i);
1406
1407                    // Compare against each special character
1408                    let eq_lt = _mm_cmpeq_epi8(chunk, lt);
1409                    let eq_gt = _mm_cmpeq_epi8(chunk, gt);
1410                    let eq_amp = _mm_cmpeq_epi8(chunk, amp);
1411                    let eq_quot = _mm_cmpeq_epi8(chunk, quot);
1412                    let eq_apos = _mm_cmpeq_epi8(chunk, apos);
1413
1414                    // Combine all matches
1415                    let any_match = _mm_or_si128(
1416                        _mm_or_si128(_mm_or_si128(eq_lt, eq_gt), eq_amp),
1417                        _mm_or_si128(eq_quot, eq_apos),
1418                    );
1419
1420                    let mask = _mm_movemask_epi8(any_match);
1421                    if mask != 0 {
1422                        return Some(offset + mask.trailing_zeros() as usize);
1423                    }
1424
1425                    offset += CHUNK_SIZE;
1426                }
1427            }
1428        }
1429
1430        // Handle remaining bytes with scalar code
1431        data[offset..]
1432            .iter()
1433            .position(|&b| matches!(b, b'<' | b'>' | b'&' | b'"' | b'\''))
1434            .map(|i| i + offset)
1435    }
1436
1437    /// Fallback for non-x86_64 platforms
1438    #[cfg(not(target_arch = "x86_64"))]
1439    pub fn find_special_char(&self, data: &[u8]) -> Option<usize> {
1440        data.iter()
1441            .position(|&b| matches!(b, b'<' | b'>' | b'&' | b'"' | b'\''))
1442    }
1443
1444    /// SIMD-accelerated UTF-8 validation
1445    /// Returns true if the data is valid UTF-8
1446    pub fn is_valid_utf8(&self, data: &[u8]) -> bool {
1447        std::str::from_utf8(data).is_ok()
1448    }
1449
1450    /// SIMD-accelerated whitespace trimming
1451    /// Returns slice with leading/trailing whitespace removed
1452    pub fn trim_whitespace<'a>(&self, data: &'a [u8]) -> &'a [u8] {
1453        let start = data
1454            .iter()
1455            .position(|&b| !matches!(b, b' ' | b'\t' | b'\n' | b'\r'))
1456            .unwrap_or(data.len());
1457        let end = data
1458            .iter()
1459            .rposition(|&b| !matches!(b, b' ' | b'\t' | b'\n' | b'\r'))
1460            .map(|i| i + 1)
1461            .unwrap_or(0);
1462
1463        if start >= end {
1464            &[]
1465        } else {
1466            &data[start..end]
1467        }
1468    }
1469
1470    /// SIMD-accelerated scan for namespace prefix separator ':'
1471    #[cfg(target_arch = "x86_64")]
1472    pub fn find_colon(&self, data: &[u8]) -> Option<usize> {
1473        use std::arch::x86_64::*;
1474
1475        const CHUNK_SIZE: usize = 16;
1476        let mut offset = 0;
1477
1478        if data.len() >= CHUNK_SIZE {
1479            unsafe {
1480                let colon = _mm_set1_epi8(b':' as i8);
1481
1482                while offset + CHUNK_SIZE <= data.len() {
1483                    let chunk = _mm_loadu_si128(data.as_ptr().add(offset) as *const __m128i);
1484                    let eq = _mm_cmpeq_epi8(chunk, colon);
1485                    let mask = _mm_movemask_epi8(eq);
1486
1487                    if mask != 0 {
1488                        return Some(offset + mask.trailing_zeros() as usize);
1489                    }
1490
1491                    offset += CHUNK_SIZE;
1492                }
1493            }
1494        }
1495
1496        data[offset..]
1497            .iter()
1498            .position(|&b| b == b':')
1499            .map(|i| i + offset)
1500    }
1501
1502    /// Fallback for non-x86_64 platforms
1503    #[cfg(not(target_arch = "x86_64"))]
1504    pub fn find_colon(&self, data: &[u8]) -> Option<usize> {
1505        data.iter().position(|&b| b == b':')
1506    }
1507
1508    /// Parse a qualified name (prefix:localname) into parts
1509    pub fn parse_qname<'a>(&self, qname: &'a [u8]) -> (&'a [u8], &'a [u8]) {
1510        match self.find_colon(qname) {
1511            Some(pos) => (&qname[..pos], &qname[pos + 1..]),
1512            None => (&[], qname),
1513        }
1514    }
1515
1516    /// Expand a prefixed name using namespace mappings
1517    pub fn expand_name<'a>(
1518        &self,
1519        prefix: &'a [u8],
1520        local: &'a [u8],
1521        namespaces: &HashMap<String, String>,
1522    ) -> Option<String> {
1523        let prefix_str = std::str::from_utf8(prefix).ok()?;
1524        let local_str = std::str::from_utf8(local).ok()?;
1525
1526        namespaces
1527            .get(prefix_str)
1528            .map(|ns| format!("{}{}", ns, local_str))
1529    }
1530
1531    /// Resize internal buffer for larger operations
1532    pub fn ensure_buffer_capacity(&mut self, capacity: usize) {
1533        if self.scan_buffer.capacity() < capacity {
1534            self.scan_buffer
1535                .reserve(capacity - self.scan_buffer.capacity());
1536        }
1537    }
1538}
1539
1540impl Default for SimdXmlProcessor {
1541    fn default() -> Self {
1542        Self::new()
1543    }
1544}