1use crate::{
7 jsonld::JsonLdParseError,
8 model::{NamedNode, Object, Predicate, Quad, Subject, Triple},
9 optimization::{SimdJsonProcessor, TermInterner, TermInternerExt, ZeroCopyBuffer},
10};
11
12use super::context::{JsonLdLoadDocumentOptions, JsonLdRemoteDocument};
13use super::profile::JsonLdProfileSet;
14use dashmap::DashMap;
16use parking_lot::Mutex;
18#[cfg(feature = "parallel")]
19use rayon::prelude::*;
20use serde_json::{Map, Value};
21use std::{
22 collections::VecDeque,
23 error::Error as StdError,
24 sync::{
25 atomic::{AtomicUsize, Ordering},
26 Arc,
27 },
28};
29use tokio::{
30 io::{AsyncRead, AsyncReadExt, BufReader},
31 sync::{mpsc, RwLock, Semaphore},
32 time::{Duration, Instant},
33};
34
35pub type StreamingDocumentLoader = dyn Fn(
47 &str,
48 &JsonLdLoadDocumentOptions,
49 ) -> Result<JsonLdRemoteDocument, Box<dyn StdError + Send + Sync>>
50 + Send
51 + Sync;
52
53pub struct UltraStreamingJsonLdParser {
55 config: StreamingConfig,
56 context_cache: Arc<DashMap<String, Arc<Value>>>,
57 term_interner: Arc<TermInterner>,
58 performance_monitor: Arc<PerformanceMonitor>,
59 simd_processor: SimdJsonProcessor,
60 buffer_pool: Arc<BufferPool>,
61 document_loader: Option<Arc<StreamingDocumentLoader>>,
62}
63
64#[derive(Debug, Clone)]
66pub struct StreamingConfig {
67 pub chunk_size: usize,
69 pub max_concurrent_threads: usize,
71 pub buffer_size: usize,
73 pub enable_simd: bool,
75 pub context_cache_size: usize,
77 pub adaptive_threshold: f64,
79 pub memory_pressure_threshold: usize,
81 pub zero_copy_level: ZeroCopyLevel,
83 pub enable_profiling: bool,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq)]
89pub enum ZeroCopyLevel {
90 None,
92 Basic,
94 Advanced,
96 Maximum,
98}
99
100pub struct PerformanceMonitor {
102 total_bytes_processed: AtomicUsize,
103 total_triples_parsed: AtomicUsize,
104 parse_errors: AtomicUsize,
105 context_cache_hits: AtomicUsize,
106 context_cache_misses: AtomicUsize,
107 simd_operations: AtomicUsize,
108 zero_copy_operations: AtomicUsize,
109 start_time: Instant,
110 chunk_processing_times: Arc<Mutex<VecDeque<Duration>>>,
111}
112
113pub struct BufferPool {
115 available_buffers: Arc<Mutex<Vec<ZeroCopyBuffer>>>,
116 buffer_size: usize,
117 max_buffers: usize,
118 current_buffers: AtomicUsize,
119}
120
121#[async_trait::async_trait]
123pub trait StreamingSink: Send + Sync {
124 type Error: Send + Sync + std::error::Error + 'static;
125
126 async fn process_triple_batch(&mut self, triples: Vec<Triple>) -> Result<(), Self::Error>;
127 async fn process_quad_batch(&mut self, quads: Vec<Quad>) -> Result<(), Self::Error>;
128 async fn flush(&mut self) -> Result<(), Self::Error>;
129 fn performance_statistics(&self) -> SinkStatistics;
130}
131
132#[derive(Debug, Clone)]
134pub struct SinkStatistics {
135 pub total_triples_processed: usize,
136 pub total_quads_processed: usize,
137 pub average_batch_size: f64,
138 pub processing_rate_per_second: f64,
139 pub memory_usage_bytes: usize,
140}
141
142impl Default for StreamingConfig {
143 fn default() -> Self {
144 Self {
145 chunk_size: 64 * 1024, max_concurrent_threads: std::thread::available_parallelism()
147 .map(|n| n.get())
148 .unwrap_or(1)
149 * 2,
150 buffer_size: 1024 * 1024, enable_simd: true,
152 context_cache_size: 10000,
153 adaptive_threshold: 0.8,
154 memory_pressure_threshold: 8 * 1024 * 1024 * 1024, zero_copy_level: ZeroCopyLevel::Advanced,
156 enable_profiling: true,
157 }
158 }
159}
160
161impl UltraStreamingJsonLdParser {
162 pub fn new(config: StreamingConfig) -> Self {
164 Self {
165 context_cache: Arc::new(DashMap::with_capacity(config.context_cache_size)),
166 term_interner: Arc::new(TermInterner::new()),
167 performance_monitor: Arc::new(PerformanceMonitor::new()),
168 simd_processor: SimdJsonProcessor::new(),
169 buffer_pool: Arc::new(BufferPool::new(config.buffer_size, 100)),
170 document_loader: None,
171 config,
172 }
173 }
174
175 pub fn with_document_loader(mut self, loader: Arc<StreamingDocumentLoader>) -> Self {
181 self.document_loader = Some(loader);
182 self
183 }
184
185 pub async fn stream_parse<R, S>(
187 &mut self,
188 reader: R,
189 mut sink: S,
190 ) -> Result<StreamingStatistics, JsonLdParseError>
191 where
192 R: AsyncRead + Unpin + Send + 'static,
193 S: StreamingSink + Send + 'static,
194 S::Error: 'static,
195 {
196 let mut buf_reader = BufReader::with_capacity(self.config.chunk_size, reader);
197 let (tx, mut rx) = mpsc::channel::<ProcessingChunk>(self.config.buffer_size);
198 let (triple_tx, mut triple_rx) = mpsc::channel::<Vec<Triple>>(100);
199 let semaphore = Arc::new(Semaphore::new(self.config.max_concurrent_threads));
200
201 let sink_handle = tokio::spawn(async move {
203 while let Some(batch) = triple_rx.recv().await {
204 sink.process_triple_batch(batch)
205 .await
206 .map_err(|e| JsonLdParseError::ProcessingError(e.to_string()))?;
207 }
208
209 sink.flush()
210 .await
211 .map_err(|e| JsonLdParseError::ProcessingError(e.to_string()))?;
212
213 Ok::<(), JsonLdParseError>(())
214 });
215
216 let processing_handle = tokio::spawn({
218 let config = self.config.clone();
219 let context_cache = Arc::clone(&self.context_cache);
220 let term_interner = Arc::clone(&self.term_interner);
221 let performance_monitor = Arc::clone(&self.performance_monitor);
222 let simd_processor = self.simd_processor.clone();
223 let document_loader = self.document_loader.clone();
224 let triple_tx = triple_tx.clone();
225
226 async move {
227 let mut batch_buffer = Vec::with_capacity(config.buffer_size);
228 let mut splitter = TopLevelJsonSplitter::new();
234 let mut eof = false;
235
236 while !eof {
237 match rx.recv().await {
238 Some(chunk) => {
239 let _permit = semaphore.acquire().await.map_err(|_| {
240 JsonLdParseError::ProcessingError(
241 "processing semaphore closed unexpectedly".to_string(),
242 )
243 })?;
244 splitter.push(&chunk.data);
245 }
246 None => {
247 eof = true;
250 splitter.mark_eof();
251 }
252 }
253
254 while let Some(document) = splitter.next_complete_value()? {
255 let processed_triples = if config.enable_simd {
258 Self::process_chunk_simd(
259 &document,
260 &context_cache,
261 &term_interner,
262 &simd_processor,
263 &document_loader,
264 )
265 .await?
266 } else {
267 Self::process_chunk_standard(
268 &document,
269 &context_cache,
270 &term_interner,
271 &document_loader,
272 )
273 .await?
274 };
275
276 performance_monitor.record_triples_parsed(processed_triples.len());
277
278 batch_buffer.extend(processed_triples);
279
280 if batch_buffer.len() >= config.buffer_size
282 || performance_monitor.should_flush_batch()
283 {
284 triple_tx
285 .send(std::mem::take(&mut batch_buffer))
286 .await
287 .map_err(|_| {
288 JsonLdParseError::ProcessingError(
289 "Triple channel send failed".to_string(),
290 )
291 })?;
292 }
293 }
294 }
295
296 splitter.finish()?;
300
301 if !batch_buffer.is_empty() {
303 triple_tx.send(batch_buffer).await.map_err(|_| {
304 JsonLdParseError::ProcessingError("Triple channel send failed".to_string())
305 })?;
306 }
307
308 Ok::<(), JsonLdParseError>(())
309 }
310 });
311
312 let mut buffer = self.buffer_pool.get_buffer().await;
314 let mut total_bytes = 0;
315
316 loop {
317 match buf_reader.read(buffer.as_mut_slice()).await {
318 Ok(0) => break, Ok(n) => {
320 buffer.set_len(n);
321 total_bytes += n;
322 self.performance_monitor.record_bytes_processed(n);
323
324 if self.should_adjust_chunk_size(n) {
326 self.adjust_chunk_size_adaptive().await;
327 }
328
329 let chunk = ProcessingChunk {
330 data: buffer.as_slice().to_vec(),
331 timestamp: Instant::now(),
332 sequence_id: total_bytes,
333 };
334
335 tx.send(chunk).await.map_err(|_| {
336 JsonLdParseError::ProcessingError("Channel send failed".to_string())
337 })?;
338
339 buffer = self.buffer_pool.get_buffer().await;
340 }
341 Err(e) => return Err(JsonLdParseError::Io(e)),
342 }
343 }
344
345 drop(tx); processing_handle
347 .await
348 .map_err(|e| JsonLdParseError::ProcessingError(e.to_string()))??;
349
350 drop(triple_tx); sink_handle
352 .await
353 .map_err(|e| JsonLdParseError::ProcessingError(e.to_string()))??;
354
355 Ok(self.performance_monitor.get_statistics())
356 }
357
358 async fn process_chunk_simd(
360 document: &[u8],
361 context_cache: &DashMap<String, Arc<Value>>,
362 term_interner: &TermInterner,
363 simd_processor: &SimdJsonProcessor,
364 document_loader: &Option<Arc<StreamingDocumentLoader>>,
365 ) -> Result<Vec<Triple>, JsonLdParseError> {
366 let start = Instant::now();
367
368 let json_value = simd_processor
371 .parse_json(document)
372 .map_err(|e| JsonLdParseError::ProcessingError(e.to_string()))?;
373
374 let context =
376 Self::resolve_context_zero_copy(&json_value, context_cache, document_loader).await?;
377
378 #[cfg(feature = "parallel")]
380 let triples = Self::extract_triples_parallel(&json_value, &context, term_interner).await?;
381 #[cfg(not(feature = "parallel"))]
382 let triples = Self::extract_triples_standard(&json_value, &context, term_interner).await?;
383
384 let _processing_time = start.elapsed();
386 Ok(triples)
389 }
390
391 async fn process_chunk_standard(
393 document: &[u8],
394 context_cache: &DashMap<String, Arc<Value>>,
395 term_interner: &TermInterner,
396 document_loader: &Option<Arc<StreamingDocumentLoader>>,
397 ) -> Result<Vec<Triple>, JsonLdParseError> {
398 let json_value: Value = serde_json::from_slice(document)
400 .map_err(|e| JsonLdParseError::ProcessingError(e.to_string()))?;
401
402 let context =
404 Self::resolve_context_cached(&json_value, context_cache, document_loader).await?;
405
406 let triples = Self::extract_triples_standard(&json_value, &context, term_interner).await?;
408
409 Ok(triples)
410 }
411
412 async fn resolve_context_zero_copy(
414 json_value: &Value,
415 context_cache: &DashMap<String, Arc<Value>>,
416 document_loader: &Option<Arc<StreamingDocumentLoader>>,
417 ) -> Result<Arc<Value>, JsonLdParseError> {
418 if let Some(context_ref) = json_value.get("@context") {
419 if let Some(context_str) = context_ref.as_str() {
420 if let Some(cached_context) = context_cache.get(context_str) {
421 return Ok(Arc::clone(&cached_context));
422 }
423
424 let resolved_context =
426 Self::resolve_remote_context(context_str, document_loader).await?;
427 let context_arc = Arc::new(resolved_context);
428 context_cache.insert(context_str.to_string(), Arc::clone(&context_arc));
429 return Ok(context_arc);
430 }
431
432 if context_ref.is_object() || context_ref.is_array() {
435 return Ok(Arc::new(context_ref.clone()));
436 }
437 }
438
439 Ok(Arc::new(Value::Object(Map::new())))
441 }
442
443 async fn resolve_context_cached(
445 json_value: &Value,
446 context_cache: &DashMap<String, Arc<Value>>,
447 document_loader: &Option<Arc<StreamingDocumentLoader>>,
448 ) -> Result<Arc<Value>, JsonLdParseError> {
449 Self::resolve_context_zero_copy(json_value, context_cache, document_loader).await
451 }
452
453 #[cfg(feature = "parallel")]
455 async fn extract_triples_parallel(
456 json_value: &Value,
457 context: &Value,
458 term_interner: &TermInterner,
459 ) -> Result<Vec<Triple>, JsonLdParseError> {
460 if let Value::Array(objects) = json_value {
461 let triples: Result<Vec<Vec<Triple>>, JsonLdParseError> = objects
463 .par_iter()
464 .map(|obj| Self::extract_triples_from_object(obj, context, term_interner))
465 .collect();
466
467 Ok(triples?.into_iter().flatten().collect())
468 } else {
469 Self::extract_triples_from_object(json_value, context, term_interner)
470 }
471 }
472
473 async fn extract_triples_standard(
475 json_value: &Value,
476 context: &Value,
477 term_interner: &TermInterner,
478 ) -> Result<Vec<Triple>, JsonLdParseError> {
479 if let Value::Array(objects) = json_value {
482 let mut triples = Vec::new();
483 for obj in objects {
484 triples.extend(Self::extract_triples_from_object(
485 obj,
486 context,
487 term_interner,
488 )?);
489 }
490 Ok(triples)
491 } else {
492 Self::extract_triples_from_object(json_value, context, term_interner)
493 }
494 }
495
496 fn extract_triples_from_object(
498 obj: &Value,
499 context: &Value,
500 term_interner: &TermInterner,
501 ) -> Result<Vec<Triple>, JsonLdParseError> {
502 let mut triples = Vec::new();
503
504 if let Value::Object(map) = obj {
505 let subject: Subject = if let Some(id) = map.get("@id") {
507 Subject::NamedNode(term_interner.intern_named_node(id.as_str().ok_or_else(
508 || JsonLdParseError::ProcessingError("Invalid @id".to_string()),
509 )?)?)
510 } else {
511 Subject::BlankNode(term_interner.intern_blank_node())
513 };
514
515 for (key, value) in map {
517 if key.starts_with('@') {
518 continue; }
520
521 let predicate_iri = Self::expand_property(key, context)?;
523 let predicate = term_interner.intern_named_node(&predicate_iri)?;
524
525 match value {
527 Value::Array(values) => {
528 for val in values {
529 if let Some(triple) = Self::create_triple_from_value(
530 subject.clone(),
531 predicate.clone(),
532 val,
533 context,
534 term_interner,
535 )? {
536 triples.push(triple);
537 }
538 }
539 }
540 _ => {
541 if let Some(triple) = Self::create_triple_from_value(
542 subject.clone(),
543 predicate,
544 value,
545 context,
546 term_interner,
547 )? {
548 triples.push(triple);
549 }
550 }
551 }
552 }
553 }
554
555 Ok(triples)
556 }
557
558 fn create_triple_from_value(
560 subject: Subject,
561 predicate: NamedNode,
562 value: &Value,
563 _context: &Value,
564 term_interner: &TermInterner,
565 ) -> Result<Option<Triple>, JsonLdParseError> {
566 let object: Object = match value {
567 Value::String(s) => {
568 if s.starts_with("http://") || s.starts_with("https://") {
570 Object::NamedNode(term_interner.intern_named_node(s)?)
571 } else {
572 Object::Literal(term_interner.intern_literal(s)?)
573 }
574 }
575 Value::Object(obj) => {
576 if let Some(id) = obj.get("@id") {
577 Object::NamedNode(term_interner.intern_named_node(id.as_str().ok_or_else(
579 || JsonLdParseError::ProcessingError("Invalid @id in object".to_string()),
580 )?)?)
581 } else if let Some(val) = obj.get("@value") {
582 let literal_value = val.as_str().ok_or_else(|| {
584 JsonLdParseError::ProcessingError("Invalid @value".to_string())
585 })?;
586
587 if let Some(datatype) = obj.get("@type") {
588 let datatype_iri = datatype.as_str().ok_or_else(|| {
589 JsonLdParseError::ProcessingError("Invalid @type".to_string())
590 })?;
591 Object::Literal(
592 term_interner
593 .intern_literal_with_datatype(literal_value, datatype_iri)?,
594 )
595 } else if let Some(lang) = obj.get("@language") {
596 let language = lang.as_str().ok_or_else(|| {
597 JsonLdParseError::ProcessingError("Invalid @language".to_string())
598 })?;
599 Object::Literal(
600 term_interner.intern_literal_with_language(literal_value, language)?,
601 )
602 } else {
603 Object::Literal(term_interner.intern_literal(literal_value)?)
604 }
605 } else {
606 return Ok(None); }
608 }
609 Value::Number(n) => Object::Literal(term_interner.intern_literal(&n.to_string())?),
610 Value::Bool(b) => Object::Literal(term_interner.intern_literal(&b.to_string())?),
611 _ => return Ok(None),
612 };
613
614 Ok(Some(Triple::new(
615 subject,
616 Predicate::NamedNode(predicate),
617 object,
618 )))
619 }
620
621 fn expand_property(property: &str, context: &Value) -> Result<String, JsonLdParseError> {
638 Self::expand_term(property, context, 0)
639 }
640
641 fn expand_term(term: &str, context: &Value, depth: usize) -> Result<String, JsonLdParseError> {
642 if depth > 16 {
643 return Err(JsonLdParseError::ProcessingError(format!(
644 "cyclic JSON-LD term definition while expanding '{term}'"
645 )));
646 }
647
648 if let Some(def) = Self::context_lookup(context, term) {
650 if let Some(mapping) = Self::term_definition_iri(def) {
651 if mapping != term {
652 return Self::expand_iri(&mapping, context, depth + 1);
654 }
655 }
656 }
657
658 Self::expand_iri(term, context, depth)
659 }
660
661 fn expand_iri(value: &str, context: &Value, depth: usize) -> Result<String, JsonLdParseError> {
664 if depth > 16 {
665 return Err(JsonLdParseError::ProcessingError(format!(
666 "cyclic JSON-LD prefix/term definition while expanding '{value}'"
667 )));
668 }
669 if let Some((prefix, suffix)) = value.split_once(':') {
670 if prefix.is_empty() || prefix == "_" || suffix.starts_with("//") {
673 return Ok(value.to_string());
674 }
675
676 if let Some(def) = Self::context_lookup(context, prefix) {
678 if let Some(prefix_iri) = Self::term_definition_iri(def) {
679 if prefix_iri != prefix {
680 let base = Self::expand_iri(&prefix_iri, context, depth + 1)?;
681 return Ok(format!("{base}{suffix}"));
682 }
683 }
684 }
685
686 return Ok(value.to_string());
689 }
690
691 if let Some(vocab) = Self::context_vocab(context) {
693 return Ok(format!("{vocab}{value}"));
694 }
695
696 Err(JsonLdParseError::ProcessingError(format!(
697 "cannot expand JSON-LD term '{value}' to an absolute IRI: no matching term \
698 definition, prefix, or @vocab in the active context"
699 )))
700 }
701
702 fn context_lookup<'a>(context: &'a Value, key: &str) -> Option<&'a Value> {
705 match context {
706 Value::Object(ctx) => ctx.get(key),
707 Value::Array(contexts) => contexts
708 .iter()
709 .rev()
710 .find_map(|c| Self::context_lookup(c, key)),
711 _ => None,
712 }
713 }
714
715 fn context_vocab(context: &Value) -> Option<String> {
717 Self::context_lookup(context, "@vocab")
718 .and_then(|v| v.as_str())
719 .map(|s| s.to_string())
720 }
721
722 fn term_definition_iri(def: &Value) -> Option<String> {
725 match def {
726 Value::String(s) => Some(s.clone()),
727 Value::Object(o) => o.get("@id").and_then(|v| v.as_str()).map(|s| s.to_string()),
728 _ => None,
729 }
730 }
731
732 async fn resolve_remote_context(
739 context_iri: &str,
740 document_loader: &Option<Arc<StreamingDocumentLoader>>,
741 ) -> Result<Value, JsonLdParseError> {
742 let loader = document_loader.as_ref().ok_or_else(|| {
743 JsonLdParseError::ProcessingError(format!(
744 "cannot resolve remote JSON-LD context '{context_iri}': no document loader \
745 configured. Supply one via UltraStreamingJsonLdParser::with_document_loader"
746 ))
747 })?;
748
749 let options = JsonLdLoadDocumentOptions {
750 request_profile: JsonLdProfileSet::from(super::profile::JsonLdProfile::Context),
751 };
752
753 let remote = loader(context_iri, &options).map_err(|e| {
754 JsonLdParseError::ProcessingError(format!(
755 "failed to load remote JSON-LD context '{context_iri}': {e}"
756 ))
757 })?;
758
759 let parsed: Value = serde_json::from_slice(&remote.document).map_err(|e| {
760 JsonLdParseError::ProcessingError(format!(
761 "remote JSON-LD context '{context_iri}' is not valid JSON: {e}"
762 ))
763 })?;
764
765 let context = match parsed {
768 Value::Object(mut obj) => match obj.remove("@context") {
769 Some(inner) => inner,
770 None => Value::Object(obj),
771 },
772 other => other,
773 };
774
775 Ok(context)
776 }
777
778 fn should_adjust_chunk_size(&self, bytes_read: usize) -> bool {
780 let target_size = self.config.chunk_size;
781 let threshold = (target_size as f64 * self.config.adaptive_threshold) as usize;
782 bytes_read < threshold || bytes_read > target_size * 2
783 }
784
785 async fn adjust_chunk_size_adaptive(&mut self) {
787 let avg_processing_time = self.performance_monitor.average_chunk_processing_time();
788 let memory_pressure = self.performance_monitor.memory_pressure_detected();
789
790 if memory_pressure {
791 self.config.chunk_size = (self.config.chunk_size / 2).max(1024);
792 } else if avg_processing_time < Duration::from_millis(10) {
793 self.config.chunk_size = (self.config.chunk_size * 2).min(1024 * 1024);
794 }
795 }
796}
797
798#[derive(Debug)]
800struct ProcessingChunk {
801 data: Vec<u8>,
802 #[allow(dead_code)]
803 timestamp: Instant,
804 #[allow(dead_code)]
805 sequence_id: usize,
806}
807
808#[derive(Debug, Clone, Copy)]
810enum SplitState {
811 Idle,
813 Container {
815 start: usize,
816 depth: usize,
817 in_string: bool,
818 escaped: bool,
819 },
820 StringScalar { start: usize, escaped: bool },
822 Primitive { start: usize },
825}
826
827#[derive(Debug)]
839struct TopLevelJsonSplitter {
840 buf: Vec<u8>,
841 pos: usize,
843 eof: bool,
845 state: SplitState,
846}
847
848impl TopLevelJsonSplitter {
849 fn new() -> Self {
850 Self {
851 buf: Vec::new(),
852 pos: 0,
853 eof: false,
854 state: SplitState::Idle,
855 }
856 }
857
858 #[inline]
859 fn is_ws(byte: u8) -> bool {
860 matches!(byte, b' ' | b'\t' | b'\n' | b'\r')
861 }
862
863 fn push(&mut self, data: &[u8]) {
865 self.buf.extend_from_slice(data);
866 }
867
868 fn mark_eof(&mut self) {
870 self.eof = true;
871 }
872
873 fn next_complete_value(&mut self) -> Result<Option<Vec<u8>>, JsonLdParseError> {
877 loop {
878 match self.state {
879 SplitState::Idle => {
880 if self.pos > 0 {
883 self.buf.drain(0..self.pos);
884 self.pos = 0;
885 }
886 while self.pos < self.buf.len() && Self::is_ws(self.buf[self.pos]) {
887 self.pos += 1;
888 }
889 if self.pos >= self.buf.len() {
890 self.buf.drain(0..self.pos);
891 self.pos = 0;
892 return Ok(None);
893 }
894 let start = self.pos;
895 match self.buf[self.pos] {
896 b'{' | b'[' => {
897 self.pos += 1;
898 self.state = SplitState::Container {
899 start,
900 depth: 1,
901 in_string: false,
902 escaped: false,
903 };
904 }
905 b'"' => {
906 self.pos += 1;
907 self.state = SplitState::StringScalar {
908 start,
909 escaped: false,
910 };
911 }
912 _ => {
913 self.state = SplitState::Primitive { start };
914 }
915 }
916 }
917 SplitState::Container {
918 start,
919 mut depth,
920 mut in_string,
921 mut escaped,
922 } => {
923 while self.pos < self.buf.len() {
924 let byte = self.buf[self.pos];
925 self.pos += 1;
926 if in_string {
927 if escaped {
928 escaped = false;
929 } else if byte == b'\\' {
930 escaped = true;
931 } else if byte == b'"' {
932 in_string = false;
933 }
934 } else {
935 match byte {
936 b'"' => in_string = true,
937 b'{' | b'[' => depth += 1,
938 b'}' | b']' => {
939 depth -= 1;
940 if depth == 0 {
941 let value = self.buf[start..self.pos].to_vec();
942 self.state = SplitState::Idle;
943 return Ok(Some(value));
944 }
945 }
946 _ => {}
947 }
948 }
949 }
950 self.state = SplitState::Container {
953 start,
954 depth,
955 in_string,
956 escaped,
957 };
958 return Ok(None);
959 }
960 SplitState::StringScalar { start, mut escaped } => {
961 while self.pos < self.buf.len() {
962 let byte = self.buf[self.pos];
963 self.pos += 1;
964 if escaped {
965 escaped = false;
966 } else if byte == b'\\' {
967 escaped = true;
968 } else if byte == b'"' {
969 let value = self.buf[start..self.pos].to_vec();
970 self.state = SplitState::Idle;
971 return Ok(Some(value));
972 }
973 }
974 self.state = SplitState::StringScalar { start, escaped };
975 return Ok(None);
976 }
977 SplitState::Primitive { start } => {
978 while self.pos < self.buf.len() {
979 let byte = self.buf[self.pos];
980 if Self::is_ws(byte) || matches!(byte, b',' | b']' | b'}') {
981 let value = self.buf[start..self.pos].to_vec();
982 self.state = SplitState::Idle;
983 return Ok(Some(value));
984 }
985 self.pos += 1;
986 }
987 if self.eof {
988 let value = self.buf[start..self.pos].to_vec();
990 self.state = SplitState::Idle;
991 if value.is_empty() {
992 return Ok(None);
993 }
994 return Ok(Some(value));
995 }
996 self.state = SplitState::Primitive { start };
997 return Ok(None);
998 }
999 }
1000 }
1001 }
1002
1003 fn finish(&self) -> Result<(), JsonLdParseError> {
1008 match self.state {
1009 SplitState::Idle => {
1010 if self.buf[self.pos..].iter().any(|b| !Self::is_ws(*b)) {
1011 return Err(JsonLdParseError::ProcessingError(
1012 "trailing non-whitespace bytes after the final JSON-LD value".to_string(),
1013 ));
1014 }
1015 Ok(())
1016 }
1017 _ => Err(JsonLdParseError::ProcessingError(
1018 "incomplete JSON-LD document: input ended in the middle of a value \
1019 (truncated at a chunk boundary or malformed JSON)"
1020 .to_string(),
1021 )),
1022 }
1023 }
1024}
1025
1026#[derive(Debug, Clone)]
1028pub struct StreamingStatistics {
1029 pub total_bytes_processed: usize,
1030 pub total_triples_parsed: usize,
1031 pub processing_time: Duration,
1032 pub average_throughput_mbps: f64,
1033 pub parse_errors: usize,
1034 pub context_cache_hit_ratio: f64,
1035 pub simd_operations_count: usize,
1036 pub zero_copy_operations_count: usize,
1037}
1038
1039impl PerformanceMonitor {
1040 fn new() -> Self {
1041 Self {
1042 total_bytes_processed: AtomicUsize::new(0),
1043 total_triples_parsed: AtomicUsize::new(0),
1044 parse_errors: AtomicUsize::new(0),
1045 context_cache_hits: AtomicUsize::new(0),
1046 context_cache_misses: AtomicUsize::new(0),
1047 simd_operations: AtomicUsize::new(0),
1048 zero_copy_operations: AtomicUsize::new(0),
1049 start_time: Instant::now(),
1050 chunk_processing_times: Arc::new(Mutex::new(VecDeque::with_capacity(1000))),
1051 }
1052 }
1053
1054 fn record_bytes_processed(&self, bytes: usize) {
1055 self.total_bytes_processed
1056 .fetch_add(bytes, Ordering::Relaxed);
1057 }
1058
1059 fn record_triples_parsed(&self, count: usize) {
1060 self.total_triples_parsed
1061 .fetch_add(count, Ordering::Relaxed);
1062 }
1063
1064 fn should_flush_batch(&self) -> bool {
1065 self.average_chunk_processing_time() > Duration::from_millis(100)
1067 }
1068
1069 fn average_chunk_processing_time(&self) -> Duration {
1070 let times = self.chunk_processing_times.lock();
1071 if times.is_empty() {
1072 return Duration::from_millis(1);
1073 }
1074
1075 let total: Duration = times.iter().sum();
1076 total / times.len() as u32
1077 }
1078
1079 fn memory_pressure_detected(&self) -> bool {
1080 false }
1083
1084 fn get_statistics(&self) -> StreamingStatistics {
1085 let elapsed = self.start_time.elapsed();
1086 let bytes = self.total_bytes_processed.load(Ordering::Relaxed);
1087 let triples = self.total_triples_parsed.load(Ordering::Relaxed);
1088 let errors = self.parse_errors.load(Ordering::Relaxed);
1089 let cache_hits = self.context_cache_hits.load(Ordering::Relaxed);
1090 let cache_misses = self.context_cache_misses.load(Ordering::Relaxed);
1091 let simd_ops = self.simd_operations.load(Ordering::Relaxed);
1092 let zero_copy_ops = self.zero_copy_operations.load(Ordering::Relaxed);
1093
1094 let throughput_mbps = if elapsed.as_secs() > 0 {
1095 (bytes as f64) / (1024.0 * 1024.0) / elapsed.as_secs_f64()
1096 } else {
1097 0.0
1098 };
1099
1100 let cache_hit_ratio = if cache_hits + cache_misses > 0 {
1101 cache_hits as f64 / (cache_hits + cache_misses) as f64
1102 } else {
1103 0.0
1104 };
1105
1106 StreamingStatistics {
1107 total_bytes_processed: bytes,
1108 total_triples_parsed: triples,
1109 processing_time: elapsed,
1110 average_throughput_mbps: throughput_mbps,
1111 parse_errors: errors,
1112 context_cache_hit_ratio: cache_hit_ratio,
1113 simd_operations_count: simd_ops,
1114 zero_copy_operations_count: zero_copy_ops,
1115 }
1116 }
1117}
1118
1119impl BufferPool {
1120 fn new(buffer_size: usize, max_buffers: usize) -> Self {
1121 Self {
1122 available_buffers: Arc::new(Mutex::new(Vec::with_capacity(max_buffers))),
1123 buffer_size,
1124 max_buffers,
1125 current_buffers: AtomicUsize::new(0),
1126 }
1127 }
1128
1129 async fn get_buffer(&self) -> ZeroCopyBuffer {
1130 loop {
1131 {
1133 let mut buffers = self.available_buffers.lock();
1134 if let Some(buffer) = buffers.pop() {
1135 return buffer;
1136 }
1137 } if self.current_buffers.load(Ordering::Relaxed) < self.max_buffers {
1140 self.current_buffers.fetch_add(1, Ordering::Relaxed);
1141 return ZeroCopyBuffer::new(self.buffer_size);
1142 } else {
1143 tokio::time::sleep(Duration::from_millis(1)).await;
1145 }
1146 }
1147 }
1148
1149 #[allow(dead_code)]
1150 fn return_buffer(&self, mut buffer: ZeroCopyBuffer) {
1151 buffer.reset();
1152 let mut buffers = self.available_buffers.lock();
1153 if buffers.len() < self.max_buffers {
1154 buffers.push(buffer);
1155 } else {
1156 self.current_buffers.fetch_sub(1, Ordering::Relaxed);
1157 }
1158 }
1159}
1160
1161pub struct MemoryStreamingSink {
1163 triples: Arc<RwLock<Vec<Triple>>>,
1164 quads: Arc<RwLock<Vec<Quad>>>,
1165 statistics: Arc<RwLock<SinkStatistics>>,
1166}
1167
1168impl Default for MemoryStreamingSink {
1169 fn default() -> Self {
1170 Self::new()
1171 }
1172}
1173
1174impl MemoryStreamingSink {
1175 pub fn new() -> Self {
1176 Self {
1177 triples: Arc::new(RwLock::new(Vec::new())),
1178 quads: Arc::new(RwLock::new(Vec::new())),
1179 statistics: Arc::new(RwLock::new(SinkStatistics {
1180 total_triples_processed: 0,
1181 total_quads_processed: 0,
1182 average_batch_size: 0.0,
1183 processing_rate_per_second: 0.0,
1184 memory_usage_bytes: 0,
1185 })),
1186 }
1187 }
1188
1189 pub fn into_triples(self) -> Arc<RwLock<Vec<Triple>>> {
1190 self.triples
1191 }
1192
1193 pub async fn get_triples(&self) -> Vec<Triple> {
1194 self.triples.read().await.clone()
1195 }
1196
1197 pub async fn get_quads(&self) -> Vec<Quad> {
1198 self.quads.read().await.clone()
1199 }
1200}
1201
1202#[derive(Debug)]
1204pub struct StreamingError(Box<dyn StdError + Send + Sync>);
1205
1206impl std::fmt::Display for StreamingError {
1207 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1208 write!(f, "Streaming error: {}", self.0)
1209 }
1210}
1211
1212impl StdError for StreamingError {
1213 fn source(&self) -> Option<&(dyn StdError + 'static)> {
1214 Some(&*self.0)
1215 }
1216}
1217
1218impl From<Box<dyn StdError + Send + Sync>> for StreamingError {
1219 fn from(err: Box<dyn StdError + Send + Sync>) -> Self {
1220 StreamingError(err)
1221 }
1222}
1223
1224#[async_trait::async_trait]
1225impl StreamingSink for MemoryStreamingSink {
1226 type Error = StreamingError;
1227
1228 async fn process_triple_batch(&mut self, triples: Vec<Triple>) -> Result<(), Self::Error> {
1229 let batch_size = triples.len();
1230 self.triples.write().await.extend(triples);
1231
1232 let mut stats = self.statistics.write().await;
1233 stats.total_triples_processed += batch_size;
1234 stats.average_batch_size = (stats.average_batch_size + batch_size as f64) / 2.0;
1235
1236 Ok(())
1237 }
1238
1239 async fn process_quad_batch(&mut self, quads: Vec<Quad>) -> Result<(), Self::Error> {
1240 let batch_size = quads.len();
1241 self.quads.write().await.extend(quads);
1242
1243 let mut stats = self.statistics.write().await;
1244 stats.total_quads_processed += batch_size;
1245
1246 Ok(())
1247 }
1248
1249 async fn flush(&mut self) -> Result<(), Self::Error> {
1250 Ok(())
1252 }
1253
1254 fn performance_statistics(&self) -> SinkStatistics {
1255 SinkStatistics {
1257 total_triples_processed: 0,
1258 total_quads_processed: 0,
1259 average_batch_size: 0.0,
1260 processing_rate_per_second: 0.0,
1261 memory_usage_bytes: 0,
1262 }
1263 }
1264}
1265
1266#[cfg(test)]
1267mod tests {
1268 use super::*;
1269 use crate::model::Predicate;
1270 use std::io::Cursor;
1271
1272 fn predicates(triples: &[Triple]) -> Vec<String> {
1274 triples
1275 .iter()
1276 .map(|t| match t.predicate() {
1277 Predicate::NamedNode(n) => n.as_str().to_string(),
1278 Predicate::Variable(v) => v.as_str().to_string(),
1279 })
1280 .collect()
1281 }
1282
1283 async fn run_parse(
1284 config: StreamingConfig,
1285 data: &str,
1286 ) -> Result<Vec<Triple>, JsonLdParseError> {
1287 let mut parser = UltraStreamingJsonLdParser::new(config);
1288 let reader = Cursor::new(data.as_bytes().to_vec());
1289 let sink = MemoryStreamingSink::new();
1290 let triples_arc = Arc::clone(&sink.triples);
1291 parser.stream_parse(reader, sink).await?;
1292 let triples = triples_arc.read().await.clone();
1293 Ok(triples)
1294 }
1295
1296 async fn run_parse_with_loader(
1297 config: StreamingConfig,
1298 data: &str,
1299 loader: Arc<StreamingDocumentLoader>,
1300 ) -> Result<Vec<Triple>, JsonLdParseError> {
1301 let mut parser = UltraStreamingJsonLdParser::new(config).with_document_loader(loader);
1302 let reader = Cursor::new(data.as_bytes().to_vec());
1303 let sink = MemoryStreamingSink::new();
1304 let triples_arc = Arc::clone(&sink.triples);
1305 parser.stream_parse(reader, sink).await?;
1306 let triples = triples_arc.read().await.clone();
1307 Ok(triples)
1308 }
1309
1310 #[tokio::test]
1311 async fn regression_streaming_array_multiple_objects() {
1312 let json = r#"[
1315 {"@id": "http://example.org/person/1", "http://schema.org/name": "Alice"},
1316 {"@id": "http://example.org/person/2", "http://schema.org/name": "Bob"}
1317 ]"#;
1318
1319 let triples = run_parse(StreamingConfig::default(), json)
1320 .await
1321 .expect("array document should parse");
1322 assert_eq!(triples.len(), 2, "expected one triple per object");
1323 for p in predicates(&triples) {
1324 assert_eq!(p, "http://schema.org/name");
1325 }
1326 }
1327
1328 #[tokio::test]
1329 async fn regression_large_object_split_across_chunks() {
1330 let mut props = String::new();
1335 for i in 0..60 {
1336 props.push_str(&format!(
1337 ",\n \"prop{i}\": \"value number {i} with spaces\""
1338 ));
1339 }
1340 let json = format!(
1341 "{{\n \"@context\": {{\"@vocab\": \"http://example.com/vocab#\"}},\n \
1342 \"@id\": \"http://example.org/subject\"{props}\n}}"
1343 );
1344
1345 let config = StreamingConfig {
1349 buffer_size: 64,
1350 enable_simd: false,
1351 ..Default::default()
1352 };
1353
1354 let triples = run_parse(config, &json)
1355 .await
1356 .expect("large document split across chunks should parse");
1357 assert_eq!(triples.len(), 60, "all 60 properties should yield triples");
1358 for p in predicates(&triples) {
1359 assert!(
1360 p.starts_with("http://example.com/vocab#prop"),
1361 "predicate should be @vocab-expanded, got {p}"
1362 );
1363 }
1364 }
1365
1366 #[tokio::test]
1367 async fn regression_large_object_split_across_chunks_simd() {
1368 let mut props = String::new();
1370 for i in 0..40 {
1371 props.push_str(&format!(",\n \"p{i}\": \"v{i}\""));
1372 }
1373 let json = format!(
1374 "{{\"@context\": {{\"@vocab\": \"http://ex.com/v#\"}}, \
1375 \"@id\": \"http://example.org/s\"{props}}}"
1376 );
1377
1378 let config = StreamingConfig {
1379 buffer_size: 8,
1380 enable_simd: true,
1381 ..Default::default()
1382 };
1383
1384 let triples = run_parse(config, &json)
1385 .await
1386 .expect("SIMD path large document should parse");
1387 assert_eq!(triples.len(), 40);
1388 }
1389
1390 #[tokio::test]
1391 async fn regression_expand_property_no_fabricated_namespace() {
1392 let json = r#"{
1396 "@context": {
1397 "@vocab": "http://example.com/vocab#",
1398 "name": "http://schema.org/name"
1399 },
1400 "@id": "http://example.org/s",
1401 "name": "Alice",
1402 "age": "30"
1403 }"#;
1404
1405 let triples = run_parse(StreamingConfig::default(), json)
1406 .await
1407 .expect("document with @vocab should parse");
1408 let preds = predicates(&triples);
1409 assert!(
1410 preds.contains(&"http://schema.org/name".to_string()),
1411 "explicit term mapping must win: {preds:?}"
1412 );
1413 assert!(
1414 preds.contains(&"http://example.com/vocab#age".to_string()),
1415 "unmapped term must use @vocab: {preds:?}"
1416 );
1417 assert!(
1418 !preds.iter().any(|p| p.contains("http://example.org/age")),
1419 "no fabricated example.org namespace allowed: {preds:?}"
1420 );
1421 }
1422
1423 #[tokio::test]
1424 async fn regression_unmapped_term_without_vocab_fails_loud() {
1425 let json = r#"{"@id": "http://example.org/s", "name": "Alice"}"#;
1428 let result = run_parse(StreamingConfig::default(), json).await;
1429 assert!(
1430 result.is_err(),
1431 "unmappable term must fail loudly, got {result:?}"
1432 );
1433 }
1434
1435 #[tokio::test]
1436 async fn regression_remote_context_without_loader_fails_loud() {
1437 let json = r#"{"@context": "https://schema.org/", "@id": "http://example.org/s", "name": "Alice"}"#;
1440 let result = run_parse(StreamingConfig::default(), json).await;
1441 assert!(
1442 result.is_err(),
1443 "remote @context without loader must fail loudly, got {result:?}"
1444 );
1445 }
1446
1447 #[tokio::test]
1448 async fn regression_remote_context_resolved_with_loader() {
1449 let json = r#"{"@context": "https://example.test/ctx", "@id": "http://example.org/s", "name": "Alice"}"#;
1452 let loader: Arc<StreamingDocumentLoader> =
1453 Arc::new(|iri: &str, _opts: &JsonLdLoadDocumentOptions| {
1454 assert_eq!(iri, "https://example.test/ctx");
1455 Ok(JsonLdRemoteDocument {
1456 document: br#"{"@context": {"name": "https://schema.org/name"}}"#.to_vec(),
1457 document_url: iri.to_string(),
1458 })
1459 });
1460
1461 let triples = run_parse_with_loader(StreamingConfig::default(), json, loader)
1462 .await
1463 .expect("remote context should resolve via loader");
1464 let preds = predicates(&triples);
1465 assert_eq!(preds, vec!["https://schema.org/name".to_string()]);
1466 }
1467
1468 #[tokio::test]
1469 async fn regression_remote_context_loader_failure_propagates() {
1470 let json = r#"{"@context": "https://broken.test/ctx", "@id": "http://example.org/s", "name": "Alice"}"#;
1472 let loader: Arc<StreamingDocumentLoader> =
1473 Arc::new(|_iri: &str, _opts: &JsonLdLoadDocumentOptions| {
1474 Err::<JsonLdRemoteDocument, _>("network unreachable".into())
1475 });
1476
1477 let result = run_parse_with_loader(StreamingConfig::default(), json, loader).await;
1478 assert!(result.is_err(), "loader failure must propagate: {result:?}");
1479 }
1480
1481 #[tokio::test]
1482 async fn regression_truncated_document_fails_loud() {
1483 let json = r#"[{"@id": "http://example.org/1", "http://schema.org/name": "Alice"}"#; let result = run_parse(StreamingConfig::default(), json).await;
1487 assert!(
1488 result.is_err(),
1489 "truncated document must fail loudly, got {result:?}"
1490 );
1491 }
1492
1493 #[test]
1494 fn regression_splitter_reassembles_across_chunk_boundaries() {
1495 let doc = br#"{"a": "b\"c", "d": [1, 2, {"e": "}"}]}"#;
1498 let mut splitter = TopLevelJsonSplitter::new();
1499 let mut out = Vec::new();
1500 for byte in doc.iter() {
1501 splitter.push(&[*byte]);
1502 while let Some(v) = splitter
1503 .next_complete_value()
1504 .expect("splitter should not error on valid input")
1505 {
1506 out.push(v);
1507 }
1508 }
1509 splitter.mark_eof();
1510 while let Some(v) = splitter
1511 .next_complete_value()
1512 .expect("splitter should not error at eof")
1513 {
1514 out.push(v);
1515 }
1516 splitter
1517 .finish()
1518 .expect("valid document should finish cleanly");
1519 assert_eq!(out.len(), 1);
1520 assert_eq!(out[0], doc.to_vec());
1521 }
1522
1523 #[test]
1524 fn regression_splitter_yields_multiple_concatenated_values() {
1525 let doc = b"{\"a\":1}\n{\"b\":2}\n[3,4]";
1527 let mut splitter = TopLevelJsonSplitter::new();
1528 splitter.push(doc);
1529 splitter.mark_eof();
1530 let mut out = Vec::new();
1531 while let Some(v) = splitter.next_complete_value().expect("no error") {
1532 out.push(String::from_utf8(v).expect("utf8"));
1533 }
1534 splitter.finish().expect("clean finish");
1535 assert_eq!(out, vec!["{\"a\":1}", "{\"b\":2}", "[3,4]"]);
1536 }
1537
1538 #[test]
1539 fn regression_splitter_incomplete_container_errors_on_finish() {
1540 let mut splitter = TopLevelJsonSplitter::new();
1541 splitter.push(b"{\"a\": [1, 2");
1542 splitter.mark_eof();
1543 while splitter
1544 .next_complete_value()
1545 .expect("no error while draining")
1546 .is_some()
1547 {}
1548 assert!(
1549 splitter.finish().is_err(),
1550 "unclosed container must be reported as truncated"
1551 );
1552 }
1553}