1use std::collections::{HashMap, HashSet, VecDeque};
67use std::fmt;
68use std::io::{BufRead, Read};
69use std::path::Path;
70
71use anybytes::{Bytes, View};
72use base64::engine::general_purpose::STANDARD as BASE64;
73use base64::Engine as _;
74use blake3::Hasher;
75use hifitime::prelude::*;
76use num_rational::Ratio;
77use winnow::error::InputError;
78use winnow::stream::Stream;
79use winnow::token::{take, take_while};
80use winnow::Parser;
81
82use crate::attribute::Attribute;
83use crate::blob::encodings::longstring::LongString;
84use crate::blob::encodings::rawbytes::RawBytes;
85use crate::blob::{Blob, IntoBlob};
86use crate::id::{ExclusiveId, Id, ID_LEN};
87use crate::macros::entity;
88use crate::prelude::inlineencodings;
89use crate::repo::{BlobStore, Workspace};
90use crate::trible::{Trible, TribleSet};
91use crate::inline::encodings::genid::GenId;
92use crate::inline::encodings::hash::Handle;
93use crate::inline::encodings::shortstring::ShortString;
94use crate::inline::encodings::time::{i128_to_ordered_be, NsDuration, NsTAIInterval};
95use crate::inline::encodings::UnknownInline;
96use crate::inline::encodings::boolean::Boolean;
97use crate::inline::encodings::f64::F64;
98use crate::inline::{RawInline, IntoInline, TryToInline, Inline};
99
100const XSD: &str = "http://www.w3.org/2001/XMLSchema#";
101
102#[derive(Debug, Clone)]
107pub enum IngestError {
108 BnodeCycle {
114 labels: Vec<String>,
116 },
117 Io(String),
119}
120
121impl fmt::Display for IngestError {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 match self {
124 Self::BnodeCycle { labels } => {
125 write!(f, "blank-node cycle in input: {}", labels.join(", "))
126 }
127 Self::Io(msg) => write!(f, "i/o error reading n-triples: {msg}"),
128 }
129 }
130}
131
132impl std::error::Error for IngestError {}
133
134enum OutgoingFact {
154 Resolved { attr_id: Id, value_raw: RawInline },
156 BnodeRef {
159 attr_id: Id,
160 target_label: View<str>,
161 },
162}
163
164struct IncomingFact {
167 subject_id: Id,
168 attr_id: Id,
169 target_label: View<str>,
170}
171
172struct BnodeBuffer {
176 outgoing: HashMap<View<str>, Vec<OutgoingFact>>,
178 incoming: Vec<IncomingFact>,
180 salt: [u8; 16],
182}
183
184impl BnodeBuffer {
185 fn new() -> Self {
186 let mut salt = [0u8; 16];
191 rand::Rng::fill(&mut rand::thread_rng(), &mut salt[..]);
192 Self {
193 outgoing: HashMap::new(),
194 incoming: Vec::new(),
195 salt,
196 }
197 }
198
199 fn is_empty(&self) -> bool {
200 self.outgoing.is_empty() && self.incoming.is_empty()
201 }
202
203 fn push_outgoing(&mut self, label: View<str>, fact: OutgoingFact) {
204 self.outgoing.entry(label).or_default().push(fact);
205 }
206
207 fn push_incoming(&mut self, fact: IncomingFact) {
208 self.incoming.push(fact);
209 }
210
211 fn flush(self, facts: &mut TribleSet) -> Result<(), IngestError> {
213 if self.is_empty() {
214 return Ok(());
215 }
216
217 let mut deps: HashMap<View<str>, HashSet<View<str>>> = HashMap::new();
220 let mut all_labels: HashSet<View<str>> = HashSet::new();
221 for (label, edges) in &self.outgoing {
222 all_labels.insert(label.clone());
223 let entry = deps.entry(label.clone()).or_default();
224 for edge in edges {
225 if let OutgoingFact::BnodeRef { target_label, .. } = edge {
226 entry.insert(target_label.clone());
227 all_labels.insert(target_label.clone());
228 }
229 }
230 }
231 for inc in &self.incoming {
232 all_labels.insert(inc.target_label.clone());
233 }
234
235 let order = topo_sort(&all_labels, &deps).map_err(|labels| {
237 let mut sorted: Vec<String> = labels.iter().map(|v| v.as_ref().to_owned()).collect();
238 sorted.sort();
239 IngestError::BnodeCycle { labels: sorted }
240 })?;
241
242 let mut resolved: HashMap<View<str>, Id> = HashMap::new();
246 for label in order {
247 let id = resolve_bnode_id(&label, &self.outgoing, &resolved, &self.salt);
248 resolved.insert(label, id);
249 }
250
251 for (label, edges) in self.outgoing {
253 let subject_id = resolved[&label];
254 let e = ExclusiveId::force_ref(&subject_id);
255 for edge in edges {
256 let (attr_id, value_raw) = match edge {
257 OutgoingFact::Resolved { attr_id, value_raw } => (attr_id, value_raw),
258 OutgoingFact::BnodeRef {
259 attr_id,
260 target_label,
261 } => {
262 let target_id = resolved[&target_label];
263 let v: Inline<GenId> = target_id.to_inline();
264 (attr_id, v.raw)
265 }
266 };
267 let v: Inline<UnknownInline> = Inline::new(value_raw);
268 facts.insert(&Trible::new(e, &attr_id, &v));
269 }
270 }
271
272 for inc in self.incoming {
274 let target_id = resolved[&inc.target_label];
275 let e = ExclusiveId::force_ref(&inc.subject_id);
276 let g: Inline<GenId> = target_id.to_inline();
277 let v: Inline<UnknownInline> = Inline::new(g.raw);
278 facts.insert(&Trible::new(e, &inc.attr_id, &v));
279 }
280
281 Ok(())
282 }
283}
284
285fn resolve_bnode_id(
293 label: &View<str>,
294 outgoing: &HashMap<View<str>, Vec<OutgoingFact>>,
295 resolved: &HashMap<View<str>, Id>,
296 salt: &[u8; 16],
297) -> Id {
298 let pairs: Vec<(Id, RawInline)> = outgoing
299 .get(label)
300 .map(|edges| {
301 edges
302 .iter()
303 .map(|edge| match edge {
304 OutgoingFact::Resolved { attr_id, value_raw } => (*attr_id, *value_raw),
305 OutgoingFact::BnodeRef {
306 attr_id,
307 target_label,
308 } => {
309 let target_id = resolved
310 .get(target_label)
311 .expect("topo order resolved this target first");
312 let v: Inline<GenId> = target_id.to_inline();
313 (*attr_id, v.raw)
314 }
315 })
316 .collect()
317 })
318 .unwrap_or_default();
319
320 if pairs.is_empty() {
321 let mut hasher = Hasher::new();
323 hasher.update(salt);
324 hasher.update(label.as_ref().as_bytes());
325 let digest = hasher.finalize();
326 let mut raw = [0u8; ID_LEN];
327 raw.copy_from_slice(&digest.as_bytes()[digest.as_bytes().len() - ID_LEN..]);
328 return Id::new(raw).expect("non-nil from random salt");
329 }
330
331 let mut pairs = pairs;
332 pairs.sort_unstable();
333 let mut hasher = Hasher::new();
334 let mut last: Option<(Id, RawInline)> = None;
335 for (a, v) in &pairs {
336 if let Some((la, lv)) = last {
337 if *a == la && *v == lv {
338 continue;
339 }
340 }
341 hasher.update(&a[..]);
342 hasher.update(&v[..]);
343 last = Some((*a, *v));
344 }
345 let digest = hasher.finalize();
346 let mut raw = [0u8; ID_LEN];
347 raw.copy_from_slice(&digest.as_bytes()[digest.as_bytes().len() - ID_LEN..]);
348 Id::new(raw).expect("intrinsic id from non-empty pairs")
349}
350
351fn topo_sort(
355 nodes: &HashSet<View<str>>,
356 edges: &HashMap<View<str>, HashSet<View<str>>>,
357) -> Result<Vec<View<str>>, Vec<View<str>>> {
358 let mut in_degree: HashMap<View<str>, usize> =
359 nodes.iter().map(|n| (n.clone(), 0)).collect();
360 for dsts in edges.values() {
361 for dst in dsts {
362 *in_degree.entry(dst.clone()).or_insert(0) += 1;
363 }
364 }
365 let mut queue: VecDeque<View<str>> = in_degree
366 .iter()
367 .filter(|(_, d)| **d == 0)
368 .map(|(n, _)| n.clone())
369 .collect();
370 let mut order: Vec<View<str>> = Vec::with_capacity(nodes.len());
371 while let Some(n) = queue.pop_front() {
372 if let Some(dsts) = edges.get(&n) {
373 for dst in dsts {
374 let d = in_degree.get_mut(dst).expect("dst recorded above");
375 *d -= 1;
376 if *d == 0 {
377 queue.push_back(dst.clone());
378 }
379 }
380 }
381 order.push(n);
382 }
383 if order.len() < nodes.len() {
384 let cycle: Vec<View<str>> = nodes
385 .iter()
386 .filter(|n| in_degree.get(*n).copied().unwrap_or(0) > 0)
387 .cloned()
388 .collect();
389 Err(cycle)
390 } else {
391 Ok(order)
392 }
393}
394
395enum LiteralSuffix {
406 None,
407 Datatype(View<str>),
408 Language(View<str>),
409}
410
411fn skip_ws_and_comments(bytes: &mut Bytes) {
412 loop {
413 while matches!(bytes.peek_token(), Some(b) if matches!(b, b' ' | b'\t' | b'\n' | b'\r')) {
415 bytes.pop_front();
416 }
417 if bytes.peek_token() == Some(b'#') {
419 while let Some(b) = bytes.pop_front() {
420 if b == b'\n' {
421 break;
422 }
423 }
424 continue;
425 }
426 break;
427 }
428}
429
430fn skip_inline_ws(bytes: &mut Bytes) {
431 while matches!(bytes.peek_token(), Some(b' ') | Some(b'\t')) {
432 bytes.pop_front();
433 }
434}
435
436fn take_iri(bytes: &mut Bytes) -> Option<View<str>> {
446 if bytes.peek_token() != Some(b'<') {
447 return None;
448 }
449 bytes.pop_front();
450
451 {
453 let mut tentative = bytes.clone();
454 let mut take = take_while::<_, _, InputError<Bytes>>(0.., |b: u8| {
455 b > 0x20 && !matches!(b, b'<' | b'>' | b'"' | b'{' | b'}' | b'|' | b'^' | b'`' | b'\\')
458 });
459 if let Ok(prefix) = take.parse_next(&mut tentative) {
460 if tentative.peek_token() == Some(b'>') {
461 tentative.pop_front();
462 *bytes = tentative;
463 return prefix.view::<str>().ok();
464 }
465 }
466 }
467
468 let mut out: Vec<u8> = Vec::new();
472 while let Some(b) = bytes.peek_token() {
473 match b {
474 b'>' => {
475 bytes.pop_front();
476 return Bytes::from_source(out).view::<str>().ok();
477 }
478 b'\\' => {
479 bytes.pop_front();
480 let kind = bytes.pop_front()?;
481 let decoded = match kind {
482 b'u' => parse_uchar(bytes, 4)?,
483 b'U' => parse_uchar(bytes, 8)?,
484 _ => return None, };
486 out.extend_from_slice(&decoded);
487 }
488 0..=0x20 | b'<' | b'"' | b'{' | b'}' | b'|' | b'^' | b'`' => return None,
490 _ => {
491 out.push(b);
492 bytes.pop_front();
493 }
494 }
495 }
496 None
497}
498
499fn take_bnode(bytes: &mut Bytes) -> Option<View<str>> {
503 if bytes.peek_token() != Some(b'_') {
504 return None;
505 }
506 let mut tentative = bytes.clone();
507 let mut prefix = take::<_, _, InputError<Bytes>>(2usize);
508 let head = prefix.parse_next(&mut tentative).ok()?;
509 if head.as_ref() != b"_:" {
510 return None;
511 }
512 let mut take_label = take_while::<_, _, InputError<Bytes>>(1.., |b: u8| {
513 !matches!(b, b' ' | b'\t' | b'\n' | b'\r')
514 });
515 let label = take_label.parse_next(&mut tentative).ok()?;
516 *bytes = tentative;
517
518 let mut combined = Vec::with_capacity(2 + label.len());
524 combined.extend_from_slice(b"_:");
525 combined.extend_from_slice(label.as_ref());
526 Bytes::from_source(combined).view::<str>().ok()
527}
528
529fn take_literal(bytes: &mut Bytes) -> Option<(Bytes, LiteralSuffix)> {
534 if bytes.peek_token() != Some(b'"') {
535 return None;
536 }
537 bytes.pop_front();
538
539 {
541 let mut tentative = bytes.clone();
542 let mut take = take_while::<_, _, InputError<Bytes>>(0.., |b: u8| {
543 b != b'"' && b != b'\\' && b != b'\n' && b != b'\r'
544 });
545 if let Ok(prefix) = take.parse_next(&mut tentative) {
546 if tentative.peek_token() == Some(b'"') {
547 tentative.pop_front();
548 *bytes = tentative;
549 let suffix = parse_literal_suffix(bytes)?;
550 return Some((prefix, suffix));
551 }
552 }
553 }
554
555 let mut out: Vec<u8> = Vec::new();
557 loop {
558 let b = bytes.peek_token()?;
559 match b {
560 b'"' => {
561 bytes.pop_front();
562 let suffix = parse_literal_suffix(bytes)?;
563 return Some((Bytes::from_source(out), suffix));
564 }
565 b'\\' => {
566 bytes.pop_front();
567 let kind = bytes.pop_front()?;
568 match kind {
569 b'n' => out.push(b'\n'),
570 b't' => out.push(b'\t'),
571 b'r' => out.push(b'\r'),
572 b'b' => out.push(0x08),
573 b'f' => out.push(0x0c),
574 b'"' => out.push(b'"'),
575 b'\'' => out.push(b'\''),
576 b'\\' => out.push(b'\\'),
577 b'u' => {
578 let decoded = parse_uchar(bytes, 4)?;
579 out.extend_from_slice(&decoded);
580 }
581 b'U' => {
582 let decoded = parse_uchar(bytes, 8)?;
583 out.extend_from_slice(&decoded);
584 }
585 _ => return None,
586 }
587 }
588 b'\n' | b'\r' => return None,
589 _ => {
590 out.push(b);
591 bytes.pop_front();
592 }
593 }
594 }
595}
596
597fn parse_uchar(bytes: &mut Bytes, hex_digits: usize) -> Option<Vec<u8>> {
600 let mut grab = take::<_, _, InputError<Bytes>>(hex_digits);
601 let hex = grab.parse_next(bytes).ok()?;
602 let mut code: u32 = 0;
603 for h in hex.as_ref() {
604 code = (code << 4)
605 | match h {
606 b'0'..=b'9' => (h - b'0') as u32,
607 b'a'..=b'f' => (h - b'a' + 10) as u32,
608 b'A'..=b'F' => (h - b'A' + 10) as u32,
609 _ => return None,
610 };
611 }
612 let ch = char::from_u32(code)?;
613 let mut buf = [0u8; 4];
614 Some(ch.encode_utf8(&mut buf).as_bytes().to_vec())
615}
616
617fn parse_literal_suffix(bytes: &mut Bytes) -> Option<LiteralSuffix> {
619 match bytes.peek_token() {
620 Some(b'^') => {
621 bytes.pop_front();
623 if bytes.pop_front() != Some(b'^') {
624 return None;
625 }
626 let dt = take_iri(bytes)?;
627 Some(LiteralSuffix::Datatype(dt))
628 }
629 Some(b'@') => {
630 bytes.pop_front();
631 let mut take = take_while::<_, _, InputError<Bytes>>(1.., |b: u8| {
632 b.is_ascii_alphanumeric() || b == b'-'
633 });
634 let tag = take.parse_next(bytes).ok()?;
635 tag.view::<str>().ok().map(LiteralSuffix::Language)
636 }
637 _ => Some(LiteralSuffix::None),
638 }
639}
640
641fn parse_decimal(s: &str) -> Option<Ratio<i128>> {
644 if let Some(dot_pos) = s.find('.') {
645 let decimals = s.len() - dot_pos - 1;
646 let without_dot: String = s.chars().filter(|c| *c != '.').collect();
647 let numerator: i128 = without_dot.parse().ok()?;
648 let denominator: i128 = 10i128.checked_pow(decimals as u32)?;
649 Some(Ratio::new(numerator, denominator))
650 } else {
651 let n: i128 = s.parse().ok()?;
652 Some(Ratio::from_integer(n))
653 }
654}
655
656fn parse_year(mut s: &str) -> Option<(i32, &str)> {
669 let neg = if let Some(rest) = s.strip_prefix('-') {
670 s = rest;
671 true
672 } else {
673 false
674 };
675 let digits_end = s
676 .as_bytes()
677 .iter()
678 .position(|b| !b.is_ascii_digit())
679 .unwrap_or(s.len());
680 if digits_end < 4 {
681 return None;
682 }
683 let year_abs: i64 = s[..digits_end].parse().ok()?;
684 let year: i32 = if neg {
685 i32::try_from(-year_abs).ok()?
686 } else {
687 i32::try_from(year_abs).ok()?
688 };
689 Some((year, &s[digits_end..]))
690}
691
692fn parse_timezone_offset(s: &str) -> Option<i64> {
695 if s.is_empty() {
696 return Some(0);
697 }
698 if s == "Z" {
699 return Some(0);
700 }
701 let bytes = s.as_bytes();
702 let sign = match bytes.first()? {
703 b'+' => 1i64,
704 b'-' => -1i64,
705 _ => return None,
706 };
707 if bytes.len() != 6 || bytes[3] != b':' {
708 return None;
709 }
710 let hh: i64 = std::str::from_utf8(&bytes[1..3]).ok()?.parse().ok()?;
711 let mm: i64 = std::str::from_utf8(&bytes[4..6]).ok()?.parse().ok()?;
712 Some(sign * (hh * 3600 + mm * 60))
713}
714
715fn epoch_from_gregorian_with_offset(
718 year: i32,
719 month: u8,
720 day: u8,
721 hh: u8,
722 mm: u8,
723 ss: u8,
724 ns: u32,
725 offset_secs: i64,
726) -> Option<Epoch> {
727 let local = Epoch::maybe_from_gregorian_utc(year, month, day, hh, mm, ss, ns).ok()?;
732 Some(local - Duration::from_seconds(offset_secs as f64))
733}
734
735fn parse_xsd_datetime(s: &str) -> Option<i128> {
737 let (year, rest) = parse_year(s)?;
738 let mut chars = rest.as_bytes();
739 if chars.first() != Some(&b'-') {
740 return None;
741 }
742 let month: u8 = std::str::from_utf8(chars.get(1..3)?).ok()?.parse().ok()?;
743 if chars.get(3) != Some(&b'-') {
744 return None;
745 }
746 let day: u8 = std::str::from_utf8(chars.get(4..6)?).ok()?.parse().ok()?;
747 if chars.get(6) != Some(&b'T') {
748 return None;
749 }
750 let hh: u8 = std::str::from_utf8(chars.get(7..9)?).ok()?.parse().ok()?;
751 if chars.get(9) != Some(&b':') {
752 return None;
753 }
754 let mm: u8 = std::str::from_utf8(chars.get(10..12)?).ok()?.parse().ok()?;
755 if chars.get(12) != Some(&b':') {
756 return None;
757 }
758 let ss: u8 = std::str::from_utf8(chars.get(13..15)?).ok()?.parse().ok()?;
759 chars = &chars[15..];
760
761 let mut ns: u32 = 0;
762 if chars.first() == Some(&b'.') {
763 chars = &chars[1..];
764 let frac_end = chars
765 .iter()
766 .position(|b| !b.is_ascii_digit())
767 .unwrap_or(chars.len());
768 let frac_str = std::str::from_utf8(&chars[..frac_end]).ok()?;
770 let mut padded = String::with_capacity(9);
771 padded.push_str(frac_str);
772 while padded.len() < 9 {
773 padded.push('0');
774 }
775 ns = padded[..9].parse().ok()?;
776 chars = &chars[frac_end..];
777 }
778
779 let tz = std::str::from_utf8(chars).ok()?;
780 let offset = parse_timezone_offset(tz)?;
781 let epoch = epoch_from_gregorian_with_offset(year, month, day, hh, mm, ss, ns, offset)?;
782 Some(epoch.to_tai_duration().total_nanoseconds())
783}
784
785fn parse_xsd_date(s: &str) -> Option<(i128, i128)> {
788 let (year, rest) = parse_year(s)?;
789 let bytes = rest.as_bytes();
790 if bytes.first() != Some(&b'-') {
791 return None;
792 }
793 let month: u8 = std::str::from_utf8(bytes.get(1..3)?).ok()?.parse().ok()?;
794 if bytes.get(3) != Some(&b'-') {
795 return None;
796 }
797 let day: u8 = std::str::from_utf8(bytes.get(4..6)?).ok()?.parse().ok()?;
798 let tz = std::str::from_utf8(&bytes[6..]).ok()?;
799 let offset = parse_timezone_offset(tz)?;
800 let lower = epoch_from_gregorian_with_offset(year, month, day, 0, 0, 0, 0, offset)?
801 .to_tai_duration()
802 .total_nanoseconds();
803 let upper = lower.checked_add(86_400_000_000_000i128 - 1)?;
805 Some((lower, upper))
806}
807
808fn parse_xsd_gyear(s: &str) -> Option<(i128, i128)> {
811 let (year, rest) = parse_year(s)?;
812 let offset = parse_timezone_offset(rest)?;
813 let lower = epoch_from_gregorian_with_offset(year, 1, 1, 0, 0, 0, 0, offset)?
814 .to_tai_duration()
815 .total_nanoseconds();
816 let next_year = year.checked_add(1)?;
817 let upper_excl = epoch_from_gregorian_with_offset(next_year, 1, 1, 0, 0, 0, 0, offset)?
818 .to_tai_duration()
819 .total_nanoseconds();
820 Some((lower, upper_excl.checked_sub(1)?))
821}
822
823fn parse_xsd_gyearmonth(s: &str) -> Option<(i128, i128)> {
825 let (year, rest) = parse_year(s)?;
826 let bytes = rest.as_bytes();
827 if bytes.first() != Some(&b'-') {
828 return None;
829 }
830 let month: u8 = std::str::from_utf8(bytes.get(1..3)?).ok()?.parse().ok()?;
831 if !(1..=12).contains(&month) {
832 return None;
833 }
834 let tz = std::str::from_utf8(&bytes[3..]).ok()?;
835 let offset = parse_timezone_offset(tz)?;
836 let lower = epoch_from_gregorian_with_offset(year, month, 1, 0, 0, 0, 0, offset)?
837 .to_tai_duration()
838 .total_nanoseconds();
839 let (next_year, next_month) = if month == 12 {
840 (year.checked_add(1)?, 1u8)
841 } else {
842 (year, month + 1)
843 };
844 let upper_excl = epoch_from_gregorian_with_offset(next_year, next_month, 1, 0, 0, 0, 0, offset)?
845 .to_tai_duration()
846 .total_nanoseconds();
847 Some((lower, upper_excl.checked_sub(1)?))
848}
849
850fn parse_xsd_duration(s: &str) -> Option<i128> {
854 let mut s = s;
855 let neg = if let Some(rest) = s.strip_prefix('-') {
856 s = rest;
857 true
858 } else {
859 false
860 };
861 let mut s = s.strip_prefix('P')?;
862 let mut total_ns: i128 = 0;
863
864 let mut in_time = false;
865 while !s.is_empty() {
866 if let Some(rest) = s.strip_prefix('T') {
867 in_time = true;
868 s = rest;
869 continue;
870 }
871 let num_end = s
872 .as_bytes()
873 .iter()
874 .position(|b| !(b.is_ascii_digit() || *b == b'.'))?;
875 let num_str = &s[..num_end];
876 let unit = s.as_bytes().get(num_end).copied()?;
877 s = &s[num_end + 1..];
878 let value: f64 = num_str.parse().ok()?;
879 match (in_time, unit) {
880 (false, b'Y') | (false, b'M') => {
881 return None;
884 }
885 (false, b'D') => total_ns = total_ns.checked_add((value * 86_400e9) as i128)?,
886 (true, b'H') => total_ns = total_ns.checked_add((value * 3_600e9) as i128)?,
887 (true, b'M') => total_ns = total_ns.checked_add((value * 60e9) as i128)?,
888 (true, b'S') => total_ns = total_ns.checked_add((value * 1e9) as i128)?,
889 _ => return None,
890 }
891 }
892 Some(if neg { -total_ns } else { total_ns })
893}
894
895pub fn uri_to_id<Blobs>(ws: &mut Workspace<Blobs>, uri: &str) -> Id
903where
904 Blobs: BlobStore,
905{
906 let handle: Inline<Handle<LongString>> = ws.put(uri.to_owned());
907 let fragment = entity! { crate::import::rdf_uri: handle };
908 fragment.root().expect("intrinsic URI entity")
909}
910
911pub fn uri_to_id_pure(uri: &str) -> Id {
919 let handle: Inline<Handle<LongString>> =
920 uri.to_owned().to_blob().get_handle();
921 let fragment = entity! { crate::import::rdf_uri: handle };
922 fragment.root().expect("intrinsic URI entity")
923}
924
925pub fn import_bytes<Blobs>(
931 ws: &mut Workspace<Blobs>,
932 mut bytes: Bytes,
933) -> Result<(TribleSet, usize), IngestError>
934where
935 Blobs: BlobStore,
936{
937 let mut facts = TribleSet::new();
938 let mut bnodes = BnodeBuffer::new();
939 let mut count = 0;
940 let mut attr_cache = NTriplesAttrCache::default();
941
942 loop {
943 skip_ws_and_comments(&mut bytes);
944 if bytes.peek_token().is_none() {
945 break;
946 }
947 if parse_triple(ws, &mut facts, &mut bnodes, &mut bytes, &mut attr_cache) {
948 count += 1;
949 } else {
950 while let Some(b) = bytes.pop_front() {
954 if b == b'\n' {
955 break;
956 }
957 }
958 }
959 }
960
961 bnodes.flush(&mut facts)?;
962 Ok((facts, count))
963}
964
965pub fn import_blob<Blobs>(
968 ws: &mut Workspace<Blobs>,
969 blob: Blob<LongString>,
970) -> Result<(TribleSet, usize), IngestError>
971where
972 Blobs: BlobStore,
973{
974 import_bytes(ws, blob.bytes)
975}
976
977pub fn ingest_ntriples<Blobs>(
980 ws: &mut Workspace<Blobs>,
981 mut reader: impl BufRead,
982) -> Result<(TribleSet, usize), IngestError>
983where
984 Blobs: BlobStore,
985{
986 let mut buf = Vec::new();
987 reader
988 .read_to_end(&mut buf)
989 .map_err(|e| IngestError::Io(e.to_string()))?;
990 import_bytes(ws, Bytes::from_source(buf))
991}
992
993#[derive(Default)]
1004struct NTriplesAttrCache {
1005 genid: HashMap<String, Id>,
1006 longstring: HashMap<String, Id>,
1007 rawbytes: HashMap<String, Id>,
1008 i256be: HashMap<String, Id>,
1009 u256be: HashMap<String, Id>,
1010 r256be: HashMap<String, Id>,
1011 f64: HashMap<String, Id>,
1012 boolean: HashMap<String, Id>,
1013 nsduration: HashMap<String, Id>,
1014 nstai: HashMap<String, Id>,
1015}
1016
1017impl NTriplesAttrCache {
1018 fn genid(&mut self, iri: &str) -> Id {
1019 *self.genid.entry(iri.to_string()).or_insert_with(|| {
1020 let h: Inline<Handle<crate::blob::encodings::longstring::LongString>> =
1021 String::from(iri).to_blob().get_handle();
1022 Attribute::<inlineencodings::GenId>::from(entity! {
1023 crate::metadata::iri: h,
1024 crate::metadata::value_encoding: <inlineencodings::GenId as crate::metadata::MetaDescribe>::id(),
1025 })
1026 .id()
1027 })
1028 }
1029 fn longstring(&mut self, iri: &str) -> Id {
1030 *self.longstring.entry(iri.to_string()).or_insert_with(|| {
1031 let h: Inline<Handle<crate::blob::encodings::longstring::LongString>> =
1032 String::from(iri).to_blob().get_handle();
1033 Attribute::<Handle<LongString>>::from(entity! {
1034 crate::metadata::iri: h,
1035 crate::metadata::value_encoding: <Handle<LongString> as crate::metadata::MetaDescribe>::id(),
1036 })
1037 .id()
1038 })
1039 }
1040 fn rawbytes(&mut self, iri: &str) -> Id {
1041 *self.rawbytes.entry(iri.to_string()).or_insert_with(|| {
1042 let h: Inline<Handle<crate::blob::encodings::longstring::LongString>> =
1043 String::from(iri).to_blob().get_handle();
1044 Attribute::<Handle<RawBytes>>::from(entity! {
1045 crate::metadata::iri: h,
1046 crate::metadata::value_encoding: <Handle<RawBytes> as crate::metadata::MetaDescribe>::id(),
1047 })
1048 .id()
1049 })
1050 }
1051 fn i256be(&mut self, iri: &str) -> Id {
1052 *self.i256be.entry(iri.to_string()).or_insert_with(|| {
1053 let h: Inline<Handle<crate::blob::encodings::longstring::LongString>> =
1054 String::from(iri).to_blob().get_handle();
1055 Attribute::<inlineencodings::I256BE>::from(entity! {
1056 crate::metadata::iri: h,
1057 crate::metadata::value_encoding: <inlineencodings::I256BE as crate::metadata::MetaDescribe>::id(),
1058 })
1059 .id()
1060 })
1061 }
1062 fn u256be(&mut self, iri: &str) -> Id {
1063 *self.u256be.entry(iri.to_string()).or_insert_with(|| {
1064 let h: Inline<Handle<crate::blob::encodings::longstring::LongString>> =
1065 String::from(iri).to_blob().get_handle();
1066 Attribute::<inlineencodings::U256BE>::from(entity! {
1067 crate::metadata::iri: h,
1068 crate::metadata::value_encoding: <inlineencodings::U256BE as crate::metadata::MetaDescribe>::id(),
1069 })
1070 .id()
1071 })
1072 }
1073 fn r256be(&mut self, iri: &str) -> Id {
1074 *self.r256be.entry(iri.to_string()).or_insert_with(|| {
1075 let h: Inline<Handle<crate::blob::encodings::longstring::LongString>> =
1076 String::from(iri).to_blob().get_handle();
1077 Attribute::<inlineencodings::R256BE>::from(entity! {
1078 crate::metadata::iri: h,
1079 crate::metadata::value_encoding: <inlineencodings::R256BE as crate::metadata::MetaDescribe>::id(),
1080 })
1081 .id()
1082 })
1083 }
1084 fn f64(&mut self, iri: &str) -> Id {
1085 *self.f64.entry(iri.to_string()).or_insert_with(|| {
1086 let h: Inline<Handle<crate::blob::encodings::longstring::LongString>> =
1087 String::from(iri).to_blob().get_handle();
1088 Attribute::<inlineencodings::F64>::from(entity! {
1089 crate::metadata::iri: h,
1090 crate::metadata::value_encoding: <inlineencodings::F64 as crate::metadata::MetaDescribe>::id(),
1091 })
1092 .id()
1093 })
1094 }
1095 fn boolean(&mut self, iri: &str) -> Id {
1096 *self.boolean.entry(iri.to_string()).or_insert_with(|| {
1097 let h: Inline<Handle<crate::blob::encodings::longstring::LongString>> =
1098 String::from(iri).to_blob().get_handle();
1099 Attribute::<inlineencodings::Boolean>::from(entity! {
1100 crate::metadata::iri: h,
1101 crate::metadata::value_encoding: <inlineencodings::Boolean as crate::metadata::MetaDescribe>::id(),
1102 })
1103 .id()
1104 })
1105 }
1106 fn nsduration(&mut self, iri: &str) -> Id {
1107 *self.nsduration.entry(iri.to_string()).or_insert_with(|| {
1108 let h: Inline<Handle<crate::blob::encodings::longstring::LongString>> =
1109 String::from(iri).to_blob().get_handle();
1110 Attribute::<NsDuration>::from(entity! {
1111 crate::metadata::iri: h,
1112 crate::metadata::value_encoding: <NsDuration as crate::metadata::MetaDescribe>::id(),
1113 })
1114 .id()
1115 })
1116 }
1117 fn nstai(&mut self, iri: &str) -> Id {
1118 *self.nstai.entry(iri.to_string()).or_insert_with(|| {
1119 let h: Inline<Handle<crate::blob::encodings::longstring::LongString>> =
1120 String::from(iri).to_blob().get_handle();
1121 Attribute::<NsTAIInterval>::from(entity! {
1122 crate::metadata::iri: h,
1123 crate::metadata::value_encoding: <NsTAIInterval as crate::metadata::MetaDescribe>::id(),
1124 })
1125 .id()
1126 })
1127 }
1128}
1129
1130fn parse_triple<Blobs>(
1131 ws: &mut Workspace<Blobs>,
1132 facts: &mut TribleSet,
1133 bnodes: &mut BnodeBuffer,
1134 bytes: &mut Bytes,
1135 attr_cache: &mut NTriplesAttrCache,
1136) -> bool
1137where
1138 Blobs: BlobStore,
1139{
1140 let (subject_iri, subject_label): (Option<View<str>>, Option<View<str>>) =
1142 match bytes.peek_token() {
1143 Some(b'<') => match take_iri(bytes) {
1144 Some(uri) => (Some(uri), None),
1145 None => return false,
1146 },
1147 Some(b'_') => match take_bnode(bytes) {
1148 Some(label) => (None, Some(label)),
1149 None => return false,
1150 },
1151 _ => return false,
1152 };
1153 skip_inline_ws(bytes);
1154
1155 let Some(predicate) = take_iri(bytes) else {
1156 return false;
1157 };
1158 skip_inline_ws(bytes);
1159
1160 let iri_subject_anchor: Option<Id> = subject_iri.as_ref().map(|uri| {
1163 let s = uri.as_ref();
1164 let id = uri_to_id(ws, s);
1165 let sub_h: Inline<Handle<LongString>> = ws.put(uri.clone());
1166 *facts += entity! { crate::import::rdf_uri: sub_h };
1167 id
1168 });
1169
1170 let outcome = match bytes.peek_token() {
1172 Some(b'<') => {
1173 let Some(obj_uri) = take_iri(bytes) else {
1174 return false;
1175 };
1176 emit_object_iri(
1177 ws,
1178 facts,
1179 bnodes,
1180 iri_subject_anchor,
1181 subject_label,
1182 predicate.as_ref(),
1183 obj_uri,
1184 attr_cache,
1185 );
1186 true
1187 }
1188 Some(b'_') => {
1189 let Some(target_label) = take_bnode(bytes) else {
1190 return false;
1191 };
1192 let attr_id = attr_cache.genid(predicate.as_ref());
1193 match (iri_subject_anchor, subject_label) {
1194 (Some(s_id), None) => {
1195 bnodes.push_incoming(IncomingFact {
1196 subject_id: s_id,
1197 attr_id,
1198 target_label,
1199 });
1200 }
1201 (None, Some(s_label)) => {
1202 bnodes.push_outgoing(
1203 s_label,
1204 OutgoingFact::BnodeRef {
1205 attr_id,
1206 target_label,
1207 },
1208 );
1209 }
1210 _ => return false,
1211 }
1212 true
1213 }
1214 Some(b'"') => {
1215 let Some((text_bytes, suffix)) = take_literal(bytes) else {
1216 return false;
1217 };
1218 let Ok(text) = text_bytes.view::<str>() else {
1219 return false;
1220 };
1221 match (iri_subject_anchor, subject_label) {
1222 (Some(s_id), None) => {
1223 let e = ExclusiveId::force_ref(&s_id);
1224 match suffix {
1225 LiteralSuffix::None => {
1226 emit_text_literal(ws, facts, e, predicate.as_ref(), text, attr_cache)
1227 }
1228 LiteralSuffix::Datatype(dt) => emit_typed_literal(
1229 ws,
1230 facts,
1231 e,
1232 predicate.as_ref(),
1233 text,
1234 dt.as_ref(),
1235 attr_cache,
1236 ),
1237 LiteralSuffix::Language(lang) => emit_lang_literal(
1238 ws,
1239 facts,
1240 e,
1241 predicate.as_ref(),
1242 lang.as_ref(),
1243 text,
1244 attr_cache,
1245 ),
1246 }
1247 }
1248 (None, Some(s_label)) => {
1249 if let Some(fact) = build_resolved_outgoing(
1250 ws,
1251 facts,
1252 predicate.as_ref(),
1253 text,
1254 suffix,
1255 attr_cache,
1256 ) {
1257 bnodes.push_outgoing(s_label, fact);
1258 }
1259 }
1260 _ => return false,
1261 }
1262 true
1263 }
1264 _ => false,
1265 };
1266
1267 if outcome {
1268 skip_inline_ws(bytes);
1269 if bytes.peek_token() != Some(b'.') {
1272 return false;
1273 }
1274 bytes.pop_front();
1275 }
1276 outcome
1277}
1278
1279fn emit_object_iri<Blobs>(
1280 ws: &mut Workspace<Blobs>,
1281 facts: &mut TribleSet,
1282 bnodes: &mut BnodeBuffer,
1283 iri_subject_anchor: Option<Id>,
1284 subject_label: Option<View<str>>,
1285 predicate: &str,
1286 obj_uri: View<str>,
1287 attr_cache: &mut NTriplesAttrCache,
1288) where
1289 Blobs: BlobStore,
1290{
1291 match (iri_subject_anchor, subject_label) {
1292 (Some(s_id), None) => {
1293 emit_uri_object(
1294 ws,
1295 facts,
1296 &ExclusiveId::force_ref(&s_id),
1297 predicate,
1298 obj_uri.as_ref(),
1299 attr_cache,
1300 );
1301 }
1302 (None, Some(s_label)) => {
1303 let attr_id = attr_cache.genid(predicate);
1304 let obj_id = uri_to_id(ws, obj_uri.as_ref());
1305 let obj_h: Inline<Handle<LongString>> = ws.put(obj_uri);
1306 *facts += entity! { crate::import::rdf_uri: obj_h };
1307 let g: Inline<GenId> = obj_id.to_inline();
1308 bnodes.push_outgoing(
1309 s_label,
1310 OutgoingFact::Resolved {
1311 attr_id,
1312 value_raw: g.raw,
1313 },
1314 );
1315 }
1316 _ => {}
1317 }
1318}
1319
1320fn build_resolved_outgoing<Blobs>(
1325 ws: &mut Workspace<Blobs>,
1326 facts: &mut TribleSet,
1327 predicate: &str,
1328 text: View<str>,
1329 suffix: LiteralSuffix,
1330 attr_cache: &mut NTriplesAttrCache,
1331) -> Option<OutgoingFact>
1332where
1333 Blobs: BlobStore,
1334{
1335 match suffix {
1336 LiteralSuffix::None => {
1337 let attr_id = attr_cache.longstring(predicate);
1338 let handle: Inline<Handle<LongString>> = ws.put(text);
1339 Some(OutgoingFact::Resolved {
1340 attr_id,
1341 value_raw: handle.raw,
1342 })
1343 }
1344 LiteralSuffix::Datatype(dt) => {
1345 let mut scratch = TribleSet::new();
1349 let scratch_id = Id::new([0xFF; ID_LEN]).expect("non-nil scratch id");
1350 let scratch_e = ExclusiveId::force_ref(&scratch_id);
1351 emit_typed_literal(
1352 ws,
1353 &mut scratch,
1354 scratch_e,
1355 predicate,
1356 text,
1357 dt.as_ref(),
1358 attr_cache,
1359 );
1360 let pair = scratch
1361 .iter()
1362 .next()
1363 .map(|t| (*t.a(), t.v::<UnknownInline>().raw));
1364 pair.map(|(attr_id, value_raw)| OutgoingFact::Resolved { attr_id, value_raw })
1365 }
1366 LiteralSuffix::Language(lang) => {
1367 let Ok(lang_value): Result<Inline<ShortString>, _> = lang.as_ref().try_to_inline() else {
1372 return None;
1373 };
1374 let text_handle: Inline<Handle<LongString>> = ws.put(text);
1375 let label_fragment = entity! {
1376 crate::import::rdf_lang: lang_value,
1377 crate::import::rdf_text: text_handle,
1378 };
1379 let label_id = label_fragment
1380 .root()
1381 .expect("intrinsic id from rdf_lang+rdf_text");
1382 *facts += label_fragment;
1383 let attr_id = attr_cache.genid(predicate);
1384 let g: Inline<GenId> = label_id.to_inline();
1385 Some(OutgoingFact::Resolved {
1386 attr_id,
1387 value_raw: g.raw,
1388 })
1389 }
1390 }
1391}
1392
1393fn emit_uri_object<Blobs>(
1394 ws: &mut Workspace<Blobs>,
1395 facts: &mut TribleSet,
1396 e: &ExclusiveId,
1397 predicate: &str,
1398 obj_uri: &str,
1399 attr_cache: &mut NTriplesAttrCache,
1400) where
1401 Blobs: BlobStore,
1402{
1403 let attr_id = attr_cache.genid(predicate);
1404 let obj_id = uri_to_id(ws, obj_uri);
1405 let obj_h: Inline<Handle<LongString>> = ws.put(obj_uri.to_owned());
1406 *facts += entity! { crate::import::rdf_uri: obj_h };
1407 let g: Inline<GenId> = obj_id.to_inline();
1408 facts.insert(&Trible::new(e, &attr_id, &g));
1409}
1410
1411fn emit_text_literal<Blobs>(
1412 ws: &mut Workspace<Blobs>,
1413 facts: &mut TribleSet,
1414 e: &ExclusiveId,
1415 predicate: &str,
1416 text: View<str>,
1417 attr_cache: &mut NTriplesAttrCache,
1418) where
1419 Blobs: BlobStore,
1420{
1421 let attr_id = attr_cache.longstring(predicate);
1422 let handle: Inline<Handle<LongString>> = ws.put(text);
1423 facts.insert(&Trible::new(e, &attr_id, &handle));
1424}
1425
1426fn emit_typed_literal<Blobs>(
1427 ws: &mut Workspace<Blobs>,
1428 facts: &mut TribleSet,
1429 e: &ExclusiveId,
1430 predicate: &str,
1431 text: View<str>,
1432 datatype: &str,
1433 attr_cache: &mut NTriplesAttrCache,
1434) where
1435 Blobs: BlobStore,
1436{
1437 if let Some(local) = datatype.strip_prefix(XSD) {
1438 match local {
1439 "integer" | "int" | "long" | "short" | "byte" | "negativeInteger"
1440 | "nonPositiveInteger" => {
1441 if let Ok(val) = text.parse::<i128>() {
1442 let attr_id = attr_cache.i256be(predicate);
1443 let v: Inline<inlineencodings::I256BE> = val.to_inline();
1444 facts.insert(&Trible::new(e, &attr_id, &v));
1445 return;
1446 }
1447 }
1448 "nonNegativeInteger" | "positiveInteger" | "unsignedInt" | "unsignedLong"
1449 | "unsignedShort" | "unsignedByte" => {
1450 if let Ok(val) = text.parse::<u128>() {
1451 let attr_id = attr_cache.u256be(predicate);
1452 let v: Inline<inlineencodings::U256BE> = val.to_inline();
1453 facts.insert(&Trible::new(e, &attr_id, &v));
1454 return;
1455 }
1456 }
1457 "decimal" => {
1458 if let Some(val) = parse_decimal(text.as_ref()) {
1459 let attr_id = attr_cache.r256be(predicate);
1460 let v: Inline<inlineencodings::R256BE> = val.to_inline();
1461 facts.insert(&Trible::new(e, &attr_id, &v));
1462 return;
1463 }
1464 }
1465 "float" | "double" => {
1466 if let Ok(val) = text.parse::<f64>() {
1467 let attr_id = attr_cache.f64(predicate);
1468 let v: Inline<F64> = val.to_inline();
1469 facts.insert(&Trible::new(e, &attr_id, &v));
1470 return;
1471 }
1472 }
1473 "boolean" => match text.as_ref() {
1474 "true" | "1" => {
1475 let attr_id = attr_cache.boolean(predicate);
1476 let v: Inline<Boolean> = true.to_inline();
1477 facts.insert(&Trible::new(e, &attr_id, &v));
1478 return;
1479 }
1480 "false" | "0" => {
1481 let attr_id = attr_cache.boolean(predicate);
1482 let v: Inline<Boolean> = false.to_inline();
1483 facts.insert(&Trible::new(e, &attr_id, &v));
1484 return;
1485 }
1486 _ => {}
1487 },
1488 "dateTime" => {
1489 if let Some(ns) = parse_xsd_datetime(text.as_ref()) {
1490 emit_interval(facts, e, predicate, ns, ns, attr_cache);
1491 return;
1492 }
1493 }
1494 "date" => {
1495 if let Some((lo, hi)) = parse_xsd_date(text.as_ref()) {
1496 emit_interval(facts, e, predicate, lo, hi, attr_cache);
1497 return;
1498 }
1499 }
1500 "gYear" => {
1501 if let Some((lo, hi)) = parse_xsd_gyear(text.as_ref()) {
1502 emit_interval(facts, e, predicate, lo, hi, attr_cache);
1503 return;
1504 }
1505 }
1506 "gYearMonth" => {
1507 if let Some((lo, hi)) = parse_xsd_gyearmonth(text.as_ref()) {
1508 emit_interval(facts, e, predicate, lo, hi, attr_cache);
1509 return;
1510 }
1511 }
1512 "duration" | "dayTimeDuration" => {
1513 if let Some(ns) = parse_xsd_duration(text.as_ref()) {
1514 let attr_id = attr_cache.nsduration(predicate);
1515 let v: Inline<NsDuration> = ns.to_inline();
1516 facts.insert(&Trible::new(e, &attr_id, &v));
1517 return;
1518 }
1519 }
1520 "hexBinary" => {
1521 if let Ok(bytes) = hex::decode(text.as_ref()) {
1522 let attr_id = attr_cache.rawbytes(predicate);
1523 let handle: Inline<Handle<RawBytes>> = ws.put(bytes);
1524 facts.insert(&Trible::new(e, &attr_id, &handle));
1525 return;
1526 }
1527 }
1528 "base64Binary" => {
1529 if let Ok(bytes) = BASE64.decode(text.as_ref()) {
1530 let attr_id = attr_cache.rawbytes(predicate);
1531 let handle: Inline<Handle<RawBytes>> = ws.put(bytes);
1532 facts.insert(&Trible::new(e, &attr_id, &handle));
1533 return;
1534 }
1535 }
1536 "anyURI" => {
1537 emit_uri_object(ws, facts, e, predicate, text.as_ref(), attr_cache);
1541 return;
1542 }
1543 _ => {}
1544 }
1545 }
1546 emit_text_literal(ws, facts, e, predicate, text, attr_cache);
1548}
1549
1550fn emit_interval(
1552 facts: &mut TribleSet,
1553 e: &ExclusiveId,
1554 predicate: &str,
1555 lo: i128,
1556 hi: i128,
1557 attr_cache: &mut NTriplesAttrCache,
1558) {
1559 let attr_id = attr_cache.nstai(predicate);
1560 let mut raw = [0u8; 32];
1561 raw[0..16].copy_from_slice(&i128_to_ordered_be(lo));
1562 raw[16..32].copy_from_slice(&i128_to_ordered_be(hi));
1563 let v: Inline<NsTAIInterval> = Inline::new(raw);
1564 facts.insert(&Trible::new(e, &attr_id, &v));
1565}
1566
1567fn emit_lang_literal<Blobs>(
1568 ws: &mut Workspace<Blobs>,
1569 facts: &mut TribleSet,
1570 e: &ExclusiveId,
1571 predicate: &str,
1572 lang: &str,
1573 text: View<str>,
1574 attr_cache: &mut NTriplesAttrCache,
1575) where
1576 Blobs: BlobStore,
1577{
1578 let Ok(lang_value): Result<Inline<ShortString>, _> = lang.try_to_inline() else {
1582 return; };
1584 let text_handle: Inline<Handle<LongString>> = ws.put(text);
1585 let label_fragment = entity! {
1586 crate::import::rdf_lang: lang_value,
1587 crate::import::rdf_text: text_handle,
1588 };
1589 let label_id = label_fragment
1590 .root()
1591 .expect("intrinsic id from rdf_lang+rdf_text");
1592 *facts += label_fragment;
1593 let attr_id = attr_cache.genid(predicate);
1594 let v: Inline<GenId> = label_id.to_inline();
1595 facts.insert(&Trible::new(e, &attr_id, &v));
1596}
1597
1598pub fn ingest_ntriples_file<Blobs>(
1601 ws: &mut Workspace<Blobs>,
1602 path: &Path,
1603) -> Result<(TribleSet, usize), IngestError>
1604where
1605 Blobs: BlobStore,
1606{
1607 let file = std::fs::File::open(path).map_err(|e| IngestError::Io(e.to_string()))?;
1608 let mut reader = std::io::BufReader::new(file);
1609 let mut buf = Vec::new();
1610 reader
1611 .read_to_end(&mut buf)
1612 .map_err(|e| IngestError::Io(e.to_string()))?;
1613 import_bytes(ws, Bytes::from_source(buf))
1614}
1615
1616#[cfg(test)]
1619mod tests {
1620 use super::*;
1621
1622 fn bytes_of(s: &str) -> Bytes {
1623 Bytes::from_source(s.as_bytes().to_vec())
1624 }
1625
1626 #[test]
1627 fn take_iri_consumes_brackets() {
1628 let mut input = bytes_of("<http://example.org/s> rest");
1629 let iri = take_iri(&mut input).unwrap();
1630 assert_eq!(iri.as_ref(), "http://example.org/s");
1631 let remaining: Vec<u8> = (0..)
1633 .scan(input.clone(), |b, _| b.pop_front())
1634 .collect();
1635 assert_eq!(&remaining[..5], b" rest");
1636 }
1637
1638 #[test]
1639 fn take_bnode_includes_prefix() {
1640 let mut input = bytes_of("_:bf55954f96378f65ddb1da9836e2eb87 .");
1641 let label = take_bnode(&mut input).unwrap();
1642 assert_eq!(label.as_ref(), "_:bf55954f96378f65ddb1da9836e2eb87");
1643 }
1644
1645 #[test]
1646 fn take_bnode_allows_internal_dot() {
1647 let mut input = bytes_of("_:foo.bar .");
1651 let label = take_bnode(&mut input).unwrap();
1652 assert_eq!(label.as_ref(), "_:foo.bar");
1653 }
1654
1655 #[test]
1656 fn take_literal_unescaped() {
1657 let mut input = bytes_of(r#""hello" ."#);
1658 let (text, suffix) = take_literal(&mut input).unwrap();
1659 assert_eq!(text.view::<str>().unwrap().as_ref(), "hello");
1660 assert!(matches!(suffix, LiteralSuffix::None));
1661 }
1662
1663 #[test]
1664 fn take_literal_with_datatype_suffix() {
1665 let mut input = bytes_of(r#""42"^^<http://www.w3.org/2001/XMLSchema#integer> ."#);
1666 let (text, suffix) = take_literal(&mut input).unwrap();
1667 assert_eq!(text.view::<str>().unwrap().as_ref(), "42");
1668 assert!(matches!(
1669 suffix,
1670 LiteralSuffix::Datatype(ref dt)
1671 if dt.as_ref() == "http://www.w3.org/2001/XMLSchema#integer"
1672 ));
1673 }
1674
1675 #[test]
1676 fn take_literal_with_lang_tag() {
1677 let mut input = bytes_of(r#""hello"@en ."#);
1678 let (text, suffix) = take_literal(&mut input).unwrap();
1679 assert_eq!(text.view::<str>().unwrap().as_ref(), "hello");
1680 assert!(matches!(
1681 suffix,
1682 LiteralSuffix::Language(ref tag) if tag.as_ref() == "en"
1683 ));
1684 }
1685
1686 #[test]
1687 fn take_literal_with_lang_region() {
1688 let mut input = bytes_of(r#""labor"@en-US ."#);
1689 let (text, suffix) = take_literal(&mut input).unwrap();
1690 assert_eq!(text.view::<str>().unwrap().as_ref(), "labor");
1691 assert!(matches!(
1692 suffix,
1693 LiteralSuffix::Language(ref tag) if tag.as_ref() == "en-US"
1694 ));
1695 }
1696
1697 #[test]
1698 fn take_literal_with_basic_escapes() {
1699 let mut input = bytes_of(r#""line\nbreak" ."#);
1700 let (text, _) = take_literal(&mut input).unwrap();
1701 assert_eq!(text.view::<str>().unwrap().as_ref(), "line\nbreak");
1702 }
1703
1704 #[test]
1705 fn take_literal_with_extended_echar() {
1706 let mut input = bytes_of(r#""a\bb\fc\'d" ."#);
1708 let (text, _) = take_literal(&mut input).unwrap();
1709 assert_eq!(
1710 text.view::<str>().unwrap().as_ref(),
1711 "a\u{0008}b\u{000c}c'd"
1712 );
1713 }
1714
1715 #[test]
1716 fn take_literal_with_unicode_escape_4() {
1717 let mut input = bytes_of(r#""smile ☺ here" ."#);
1718 let (text, _) = take_literal(&mut input).unwrap();
1719 assert_eq!(text.view::<str>().unwrap().as_ref(), "smile ☺ here");
1720 }
1721
1722 #[test]
1723 fn take_literal_with_unicode_escape_8() {
1724 let mut input = bytes_of(r#""grin \U0001F600 here" ."#);
1726 let (text, _) = take_literal(&mut input).unwrap();
1727 assert_eq!(text.view::<str>().unwrap().as_ref(), "grin 😀 here");
1728 }
1729
1730 #[test]
1731 fn take_iri_with_unicode_escape() {
1732 let mut input = bytes_of(r#"<http://ex/é> rest"#);
1734 let iri = take_iri(&mut input).unwrap();
1735 assert_eq!(iri.as_ref(), "http://ex/é");
1736 }
1737
1738 #[test]
1739 fn decimal_parse_helper() {
1740 let r = parse_decimal("3.14").unwrap();
1741 assert_eq!(*r.numer(), 157);
1742 assert_eq!(*r.denom(), 50);
1743
1744 let r = parse_decimal("42").unwrap();
1745 assert_eq!(*r.numer(), 42);
1746 assert_eq!(*r.denom(), 1);
1747
1748 let r = parse_decimal("-0.5").unwrap();
1749 assert_eq!(*r.numer(), -1);
1750 assert_eq!(*r.denom(), 2);
1751 }
1752
1753 #[test]
1754 fn xsd_datetime_z_and_offset() {
1755 let utc = parse_xsd_datetime("2020-01-01T12:00:00Z").unwrap();
1757 let plus5 = parse_xsd_datetime("2020-01-01T17:00:00+05:00").unwrap();
1758 assert_eq!(utc, plus5);
1759 }
1760
1761 #[test]
1762 fn xsd_datetime_with_fractional_seconds() {
1763 let no_frac = parse_xsd_datetime("2020-01-01T00:00:00Z").unwrap();
1764 let with_frac = parse_xsd_datetime("2020-01-01T00:00:00.5Z").unwrap();
1765 assert_eq!(with_frac - no_frac, 500_000_000);
1766 }
1767
1768 #[test]
1769 fn xsd_datetime_bce_year() {
1770 assert!(parse_xsd_datetime("-0500-01-01T00:00:00Z").is_some());
1773 }
1774
1775 #[test]
1776 fn xsd_date_spans_one_day() {
1777 let (lo, hi) = parse_xsd_date("2020-01-01").unwrap();
1778 assert_eq!(hi - lo, 86_400_000_000_000 - 1);
1780 }
1781
1782 #[test]
1783 fn xsd_gyear_spans_full_year() {
1784 let (lo_2020, hi_2020) = parse_xsd_gyear("2020").unwrap();
1785 let (lo_2021, _) = parse_xsd_gyear("2021").unwrap();
1786 assert_eq!(hi_2020 - lo_2020, 366 * 86_400_000_000_000 - 1);
1788 assert_eq!(hi_2020 + 1, lo_2021);
1790 }
1791
1792 #[test]
1793 fn xsd_gyearmonth_spans_one_month() {
1794 let (lo_jan, hi_jan) = parse_xsd_gyearmonth("2020-01").unwrap();
1795 assert_eq!(hi_jan - lo_jan, 31 * 86_400_000_000_000 - 1);
1797
1798 let (_, hi_feb) = parse_xsd_gyearmonth("2020-02").unwrap();
1799 let (lo_mar, _) = parse_xsd_gyearmonth("2020-03").unwrap();
1800 assert_eq!(hi_feb + 1, lo_mar);
1801 }
1802
1803 #[test]
1804 fn xsd_duration_daytime_only() {
1805 let ns = parse_xsd_duration("P1DT2H3M4.5S").unwrap();
1807 let expected = 86_400_000_000_000i128
1808 + 2 * 3_600_000_000_000
1809 + 3 * 60_000_000_000
1810 + 4_500_000_000;
1811 assert_eq!(ns, expected);
1812 }
1813
1814 #[test]
1815 fn xsd_duration_negative() {
1816 let ns = parse_xsd_duration("-PT5S").unwrap();
1817 assert_eq!(ns, -5_000_000_000);
1818 }
1819
1820 #[test]
1821 fn xsd_duration_rejects_year_month() {
1822 assert!(parse_xsd_duration("P1Y").is_none());
1824 assert!(parse_xsd_duration("P1M").is_none());
1825 assert!(parse_xsd_duration("P1Y2M").is_none());
1826 }
1827}