diff --git a/Cargo.lock b/Cargo.lock index 9294c44b..73f3838d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1293,7 +1293,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2484,7 +2484,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2693,7 +2693,7 @@ dependencies = [ "oxilangtag", "oxiri", "oxsdatatypes", - "rand 0.8.6", + "rand 0.9.4", "serde", "thiserror", ] @@ -3776,7 +3776,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4275,7 +4275,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4333,7 +4333,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5528,7 +5528,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/lib/maplib/src/model.rs b/lib/maplib/src/model.rs index d795f77d..19c9854b 100644 --- a/lib/maplib/src/model.rs +++ b/lib/maplib/src/model.rs @@ -201,12 +201,17 @@ impl Model { path: &Path, graph: &NamedGraph, transient: bool, + uuid_namespace: Option, ) -> Result<(), MaplibError> { let mut u8s = fs::read(path).map_err(|x| TriplestoreError::ReadJSONFileError(x.to_string()))?; - + let use_uuid_namespace = if let Some(uuid_namespace) = uuid_namespace { + uuid_namespace + } else { + String::from(path.to_string_lossy()) + }; self.triplestore - .map_json(&mut u8s, graph, transient) + .map_json(&mut u8s, Some(use_uuid_namespace), graph, transient) .map_err(MaplibError::TriplestoreError) } @@ -216,18 +221,24 @@ impl Model { mut p: String, graph: &NamedGraph, transient: bool, + uuid_namespace: Option, ) -> Result<(), MaplibError> { //Safety: we are never reading this vec back to a string let u8s = unsafe { p.as_mut_vec() }; self.triplestore - .map_json(u8s, graph, transient) + .map_json(u8s, uuid_namespace, graph, transient) .map_err(MaplibError::TriplestoreError) } #[instrument(skip_all)] - pub fn map_df(&mut self, df: &DataFrame, graph: &NamedGraph) -> Result<(), MaplibError> { + pub fn map_df( + &mut self, + df: &DataFrame, + graph: &NamedGraph, + uuid_namespace: Option, + ) -> Result<(), MaplibError> { self.triplestore - .map_df(df, graph) + .map_df(df, graph, uuid_namespace) .map_err(MaplibError::TriplestoreError) } @@ -237,10 +248,16 @@ impl Model { path: &Path, graph: &NamedGraph, transient: bool, + uuid_namespace: Option, ) -> Result<(), MaplibError> { let mut u8s = fs::read(path).map_err(|x| TriplestoreError::XMLError(x.to_string()))?; + let use_uuid_namespace = if let Some(uuid_namespace) = uuid_namespace { + uuid_namespace + } else { + String::from(path.to_string_lossy()) + }; self.triplestore - .map_xml(&mut u8s, graph, transient) + .map_xml(&mut u8s, graph, transient, Some(use_uuid_namespace)) .map_err(MaplibError::TriplestoreError) } @@ -250,11 +267,12 @@ impl Model { mut p: String, graph: &NamedGraph, transient: bool, + uuid_namespace: Option, ) -> Result<(), MaplibError> { //Safety: we are never reading this vec back to a string let u8s = unsafe { p.as_mut_vec() }; self.triplestore - .map_xml(u8s, graph, transient) + .map_xml(u8s, graph, transient, uuid_namespace) .map_err(MaplibError::TriplestoreError) } diff --git a/lib/triplestore/src/map_df.rs b/lib/triplestore/src/map_df.rs index ace76842..d19aba15 100644 --- a/lib/triplestore/src/map_df.rs +++ b/lib/triplestore/src/map_df.rs @@ -5,20 +5,26 @@ use oxrdf::NamedNode; use polars::prelude::{col, lit, DataFrame, IntoLazy}; use polars_core::prelude::{Column, IntoColumn, NamedFrom, Series}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use representation::constants::{ - FX_CHILD, FX_CHILD_NUMBER, FX_ROOT, MAPLIB_PREFIX_IRI, XYZ_PREFIX_IRI, -}; +use representation::constants::{FX_CHILD, FX_CHILD_NUMBER, FX_ROOT, XYZ_PREFIX_IRI}; use representation::dataset::NamedGraph; use representation::polars_to_rdf::polars_type_to_literal_type; use representation::{BaseRDFNodeType, OBJECT_COL_NAME, SUBJECT_COL_NAME}; use std::collections::HashMap; +use uuid::Uuid; impl Triplestore { pub fn map_df( &mut self, df: &DataFrame, named_graph: &NamedGraph, + uuid_namespace: Option, ) -> Result<(), TriplestoreError> { + let use_uuid_namespace = if let Some(uuid_namespace) = uuid_namespace { + Uuid::new_v5(&Uuid::NAMESPACE_DNS, uuid_namespace.as_bytes()) + } else { + Uuid::new_v4() + }; + let root_node_uuri = new_iri_subject(&use_uuid_namespace, "".as_bytes()); let mut column_types = HashMap::new(); let col_names: Vec<_> = df.columns().iter().map(|x| x.name().to_string()).collect(); for c in df.columns() { @@ -26,12 +32,11 @@ impl Triplestore { let dt = polars_type_to_literal_type(c.dtype()).unwrap(); column_types.insert(c.name().to_string(), dt); } - let id_col = uuid::Uuid::new_v4().to_string(); + let id_col = Uuid::new_v5(&use_uuid_namespace, "id".as_bytes()).to_string(); let mut df = df.clone(); - let root_node_uuri = format!("{}{}", MAPLIB_PREFIX_IRI, uuid::Uuid::new_v4()); let uuids: Vec<_> = (0..df.height()) .into_par_iter() - .map(|_| format!("{}{}", MAPLIB_PREFIX_IRI, uuid::Uuid::new_v4())) + .map(|i| new_iri_subject(&use_uuid_namespace, &i.to_string().into_bytes())) .collect(); df.with_column(Series::new(id_col.as_str().into(), uuids).into_column()) .unwrap(); @@ -129,3 +134,6 @@ impl Triplestore { Ok(()) } } +fn new_iri_subject(namespace: &Uuid, name: &[u8]) -> String { + format!("urn:maplib:{}", Uuid::new_v5(namespace, name)) +} diff --git a/lib/triplestore/src/map_json.rs b/lib/triplestore/src/map_json.rs index a552abe0..5663f032 100644 --- a/lib/triplestore/src/map_json.rs +++ b/lib/triplestore/src/map_json.rs @@ -17,6 +17,7 @@ use representation::dataset::NamedGraph; use representation::{BaseRDFNodeType, OBJECT_COL_NAME, SUBJECT_COL_NAME}; use serde_json::Value; use std::collections::HashMap; +use uuid::Uuid; const BOOLEAN: u8 = 0; const INTEGER: u8 = 1; @@ -131,6 +132,7 @@ impl Triplestore { pub fn map_json( &mut self, u8s: &mut [u8], + uuid_namespace: Option, named_graph: &NamedGraph, transient: bool, ) -> Result<(), TriplestoreError> { @@ -140,8 +142,21 @@ impl Triplestore { let mut pred_map = HashMap::new(); let rdf_type = rdf::TYPE.into_owned(); pred_map.insert(rdf_type.clone(), TripleTableBuilder::new()); + let mut path: Vec = Vec::new(); - let doc_subject = new_iri_typed_subject(FX_ROOT, &rdf_type, &mut pred_map); + let use_uuid_namespace = if let Some(uuid_namespace) = uuid_namespace { + Uuid::new_v5(&Uuid::NAMESPACE_DNS, uuid_namespace.as_bytes()) + } else { + Uuid::new_v4() + }; + + let doc_subject = new_iri_typed_subject( + FX_ROOT, + &rdf_type, + &use_uuid_namespace, + "".as_bytes(), + &mut pred_map, + ); let root_elem_property = NamedNode::new_unchecked(ROOT_ELEMENT_PROPERTY); pred_map.insert(root_elem_property.clone(), TripleTableBuilder::new()); @@ -153,6 +168,8 @@ impl Triplestore { v, &rdf_type, &root_elem_property, + &mut path, + &use_uuid_namespace, &mut pred_map, ); let mut triples_to_add = Vec::new(); @@ -194,6 +211,8 @@ fn process_value( value: Value, rdf_type: &NamedNode, root_elem_property: &NamedNode, + path: &mut Vec, + uuid_namespace: &Uuid, map: &mut HashMap, ) { match value { @@ -220,8 +239,9 @@ fn process_value( builder.push_iri_iri(subject, FX_NULL); } Value::Object(obj) => { + let name: String = path.join("."); let new_subject = if let Some(property) = property { - let new_subject = new_iri_subject(); + let new_subject = new_iri_subject(uuid_namespace, name.as_bytes()); let builder = map.get_mut(property).unwrap(); builder.push_iri_iri(subject, &new_subject); new_subject @@ -230,7 +250,8 @@ fn process_value( }; for (key, val) in obj { - let property = new_key_property(key, prefix, map); + let property = new_key_property(key.clone(), prefix, map); + path.push(key.to_string()); process_value( &new_subject, Some(&property), @@ -238,13 +259,17 @@ fn process_value( val, rdf_type, root_elem_property, + path, + uuid_namespace, map, ); + path.pop(); } } Value::Array(arr) => { + let name: String = path.join("."); let array_subject = if let Some(property) = property { - let array_subject = new_iri_subject(); + let array_subject = new_iri_subject(uuid_namespace, name.as_bytes()); let builder = map.get_mut(property).unwrap(); builder.push_iri_iri(subject, &array_subject); array_subject @@ -252,8 +277,9 @@ fn process_value( subject.to_string() }; let ch = NamedNode::new_unchecked(FX_CHILD); - for v in arr.into_iter() { + for (i, v) in arr.into_iter().enumerate() { add_new_property(&ch, map); + path.push(i.to_string()); process_value( &array_subject, Some(&ch), @@ -261,8 +287,11 @@ fn process_value( v, rdf_type, root_elem_property, + path, + uuid_namespace, map, ); + path.pop(); } } } @@ -271,15 +300,17 @@ fn process_value( fn new_iri_typed_subject( t: &str, rdf_type: &NamedNode, + namespace: &Uuid, + name: &[u8], map: &mut HashMap, ) -> String { - let bstr = new_iri_subject(); + let bstr = new_iri_subject(namespace, name); map.get_mut(rdf_type).unwrap().push_iri_iri(&bstr, t); bstr } -fn new_iri_subject() -> String { - let bstr = format!("urn:maplib:{}", uuid::Uuid::new_v4()); +fn new_iri_subject(namespace: &Uuid, name: &[u8]) -> String { + let bstr = format!("urn:maplib:{}", Uuid::new_v5(namespace, name)); bstr } diff --git a/lib/triplestore/src/map_xml.rs b/lib/triplestore/src/map_xml.rs index b4357558..1ffb6289 100644 --- a/lib/triplestore/src/map_xml.rs +++ b/lib/triplestore/src/map_xml.rs @@ -17,6 +17,7 @@ use representation::{BaseRDFNodeType, OBJECT_COL_NAME, SUBJECT_COL_NAME}; use std::collections::HashMap; use std::io::Cursor; use std::sync::Arc; +use uuid::Uuid; struct Frame { subject: String, @@ -31,6 +32,7 @@ impl Triplestore { u8s: &mut Vec, named_graph: &NamedGraph, transient: bool, + uuid_namespace: Option, ) -> Result<(), TriplestoreError> { let mut reader = Reader::from_reader(Cursor::new(u8s.as_slice())); reader.config_mut().trim_text(true); @@ -43,6 +45,14 @@ impl Triplestore { let mut base_prefix = NamedNode::new_unchecked(XYZ_PREFIX_IRI); let mut stack: Vec = Vec::new(); let mut buf = Vec::new(); + let mut path = Vec::new(); + let mut current_counter = 0; // keep track of how many elements that are on the same level for uuid v5 + let mut counter_stack: Vec = Vec::new(); + let use_uuid_namespace = if let Some(uuid_namespace) = uuid_namespace { + Uuid::new_v5(&Uuid::NAMESPACE_DNS, uuid_namespace.as_bytes()) + } else { + Uuid::new_v4() + }; loop { match reader .read_event_into(&mut buf) @@ -50,6 +60,14 @@ impl Triplestore { { Event::Eof => break, Event::Start(e) => { + let name = e.name(); + let name_bytes = name.as_ref(); + let name_path = std::str::from_utf8(name_bytes) + .map_err(|e| TriplestoreError::XMLError(e.to_string()))?; + path.push(format!("{}_{}", &name_path, ¤t_counter)); + current_counter += 1; + counter_stack.push(current_counter); + current_counter = 0; let (subj, new_base_prefix, introduced_prefixed_namespaces) = open_element( e.name().as_ref(), e.attributes(), @@ -58,6 +76,8 @@ impl Triplestore { &mut prefix_map, &mut datatypes_map, &base_prefix, + &use_uuid_namespace, + &mut path, )?; let previous_base_prefix = if let Some(new_base_prefix) = new_base_prefix { let previous_base_prefix = base_prefix; @@ -74,6 +94,8 @@ impl Triplestore { }); } Event::Empty(e) => { + current_counter += 1; + path.push(current_counter.to_string()); let _ = open_element( e.name().as_ref(), e.attributes(), @@ -82,7 +104,10 @@ impl Triplestore { &mut prefix_map, &mut datatypes_map, &base_prefix, + &use_uuid_namespace, + &mut path, )?; + path.pop(); } Event::End(_) => { if let Some(Frame { @@ -102,6 +127,8 @@ impl Triplestore { base_prefix = old; } } + current_counter = counter_stack.pop().unwrap_or(0); + path.pop(); } Event::Text(t) => { let raw = t @@ -139,9 +166,11 @@ fn open_element( prefix_map: &mut HashMap, datatypes_map: &mut HashMap>, base_prefix: &NamedNode, + uuid_namespace: &Uuid, + path: &mut Vec, ) -> Result<(String, Option, Vec<(String, Option)>), TriplestoreError> { let name = std::str::from_utf8(name).map_err(|e| TriplestoreError::XMLError(e.to_string()))?; - let subject = new_iri_subject(); + let subject = new_iri_subject(uuid_namespace, path.join(".").as_bytes()); if let Some(parent) = stack.last_mut() { let parent_subject = parent.subject.clone(); let n = parent.next_child; @@ -188,8 +217,9 @@ fn open_element( let nn = NamedNode::new(value.clone()).map_err(|e| { TriplestoreError::XMLError(format!("Error parsing {}: {}", value, e)) })?; - - let prefix_subject = new_iri_subject(); + path.push(key.to_string()); + let prefix_subject = new_iri_subject(uuid_namespace, path.join(".").as_bytes()); + path.pop(); push_iri_object(pred_map, XYZ_PREFIX_IRI, &subject, &prefix_subject); push_iri_object(pred_map, FX_XYZ_PREFIX_IRI, &prefix_subject, nn.as_str()); let use_sep = use_sep(nn.as_str()); @@ -330,8 +360,8 @@ fn push_iri_u32(pred_map: &mut PredMap, predicate: &str, subject: &str, object: pair.1.push_u32(object); } -fn new_iri_subject() -> String { - format!("urn:maplib:{}", uuid::Uuid::new_v4()) +fn new_iri_subject(namespace: &Uuid, name: &[u8]) -> String { + format!("urn:maplib:{}", Uuid::new_v5(namespace, name)) } fn qname_to_iri( diff --git a/py_maplib/maplib/__init__.pyi b/py_maplib/maplib/__init__.pyi index 71d02935..a313bfd0 100644 --- a/py_maplib/maplib/__init__.pyi +++ b/py_maplib/maplib/__init__.pyi @@ -635,6 +635,7 @@ class Model: path_or_string: Path | str, graph: str = None, transient: bool = True, + uuid_namespace: str = None, ) -> None: """ Map a JSON file or string to triples. @@ -649,6 +650,7 @@ class Model: :param path_or_string: Path to a JSON document or a JSON string. :param graph: The IRI of the graph to add triples to. None is the default graph. :param transient: Should the triples be included when serializing the graph? + :param uuid_namespace: Generates uuid v5 uris based on this namespace. """ def map_xml( @@ -656,6 +658,7 @@ class Model: path_or_string: Path | str, graph: str = None, transient: bool = True, + uuid_namespace: str = None, ) -> None: """ Map an XML file or string to triples. @@ -670,6 +673,7 @@ class Model: :param path_or_string: Path to an XML document or an XML string. :param graph: The IRI of the graph to add triples to. None is the default graph. :param transient: Should the triples be included when serializing the graph? + :param uuid_namespace: Generates uuid v5 uris based on this namespace. """ def map_triples( @@ -721,6 +725,7 @@ class Model: self, df: DataFrame, graph: str = None, + uuid_namespace: str = None, ): """ Create a default template and map it based on a dataframe. @@ -731,6 +736,7 @@ class Model: :param df: DataFrame to map using Facade-X (using approximately the CSV-mapping) :param graph: The IRI of the graph to add triples to. + :param uuid_namespace: Generates uuid v5 uris based on this namespace. :return: None """ diff --git a/py_maplib/src/mutexes.rs b/py_maplib/src/mutexes.rs index 0155920c..707f4294 100644 --- a/py_maplib/src/mutexes.rs +++ b/py_maplib/src/mutexes.rs @@ -182,6 +182,7 @@ pub(crate) fn map_json_mutex( string_or_path: StringOrPathBuf, graph: Option, transient: Option, + uuid_namespace: Option, ) -> PyResult<()> { let graph = parse_optional_named_node(graph)?; let named_graph = NamedGraph::from_maybe_named_node(graph.as_ref()); @@ -196,6 +197,7 @@ pub(crate) fn map_json_mutex( string, &named_graph, transient.unwrap_or(DEFAULT_MAP_TO_TRANSIENT), + uuid_namespace, ) .map_err(PyMaplibError::from)?; } else { @@ -205,6 +207,7 @@ pub(crate) fn map_json_mutex( p.as_ref(), &named_graph, transient.unwrap_or(DEFAULT_MAP_TO_TRANSIENT), + uuid_namespace, ) .map_err(PyMaplibError::from)?; } @@ -215,6 +218,7 @@ pub(crate) fn map_json_mutex( path.as_ref(), &named_graph, transient.unwrap_or(DEFAULT_MAP_TO_TRANSIENT), + uuid_namespace, ) .map_err(PyMaplibError::from)?; } @@ -227,6 +231,7 @@ pub(crate) fn map_xml_mutex( string_or_path: StringOrPathBuf, graph: Option, transient: Option, + uuid_namespace: Option, ) -> PyResult<()> { let graph = parse_optional_named_node(graph)?; let named_graph = NamedGraph::from_maybe_named_node(graph.as_ref()); @@ -241,6 +246,7 @@ pub(crate) fn map_xml_mutex( string, &named_graph, transient.unwrap_or(DEFAULT_MAP_TO_TRANSIENT), + uuid_namespace, ) .map_err(PyMaplibError::from)?; } else { @@ -250,6 +256,7 @@ pub(crate) fn map_xml_mutex( p.as_ref(), &named_graph, transient.unwrap_or(DEFAULT_MAP_TO_TRANSIENT), + uuid_namespace, ) .map_err(PyMaplibError::from)?; } @@ -260,6 +267,7 @@ pub(crate) fn map_xml_mutex( path.as_ref(), &named_graph, transient.unwrap_or(DEFAULT_MAP_TO_TRANSIENT), + uuid_namespace, ) .map_err(PyMaplibError::from)?; } @@ -316,8 +324,11 @@ pub fn map_df_mutex( inner: &mut MutexGuard, df: DataFrame, graph: NamedGraph, + uuid_namespace: Option, ) -> PyResult<()> { - inner.map_df(&df, &graph).map_err(PyMaplibError::from)?; + inner + .map_df(&df, &graph, uuid_namespace) + .map_err(PyMaplibError::from)?; Ok(()) } diff --git a/py_maplib/src/py_model.rs b/py_maplib/src/py_model.rs index 51494b9e..3312c04b 100644 --- a/py_maplib/src/py_model.rs +++ b/py_maplib/src/py_model.rs @@ -3,14 +3,10 @@ use crate::mutexes::{ add_prefixes_mutex, add_template_mutex, add_udf_mutex, add_virtualization_mutex, compact_mutex, create_index_mutex, detach_graph_mutex, get_predicate_iris_mutex, get_predicate_mutex, infer_mutex, infer_rdfs_mutex, insert_mutex, list_udfs_mutex, map_default_mutex, map_df_mutex, - map_json_mutex, - map_mutex, map_triples_mutex, map_xml_mutex, query_external_mutex, query_mutex, - read_mutex, - read_template_mutex, reads_mutex, serialize_triples_mutex, size_mutex, - truncate_graph_mutex, - update_mutex, validate_mutex, write_cim_xml_mutex, - - write_triples_mutex, writes_mutex, + map_json_mutex, map_mutex, map_triples_mutex, map_xml_mutex, query_external_mutex, query_mutex, + read_mutex, read_template_mutex, reads_mutex, serialize_triples_mutex, size_mutex, + truncate_graph_mutex, update_mutex, validate_mutex, write_cim_xml_mutex, write_triples_mutex, + writes_mutex, }; use crate::shacl::PyValidationReport; use crate::{ @@ -154,7 +150,7 @@ impl PyModel { }) } - #[pyo3(signature = (path_or_string, graph=None, transient=None))] + #[pyo3(signature = (path_or_string, graph=None, transient=None, uuid_namespace=None))] #[instrument(skip_all)] fn map_json( &self, @@ -162,14 +158,15 @@ impl PyModel { path_or_string: StringOrPathBuf, graph: Option, transient: Option, + uuid_namespace: Option, ) -> PyResult<()> { py.detach(move || { let mut inner = self.inner.lock().unwrap(); - map_json_mutex(&mut inner, path_or_string, graph, transient) + map_json_mutex(&mut inner, path_or_string, graph, transient, uuid_namespace) }) } - #[pyo3(signature = (path_or_string, graph=None, transient=None))] + #[pyo3(signature = (path_or_string, graph=None, transient=None, uuid_namespace=None))] #[instrument(skip_all)] fn map_xml( &self, @@ -177,10 +174,11 @@ impl PyModel { path_or_string: StringOrPathBuf, graph: Option, transient: Option, + uuid_namespace: Option, ) -> PyResult<()> { py.detach(move || { let mut inner = self.inner.lock().unwrap(); - map_xml_mutex(&mut inner, path_or_string, graph, transient) + map_xml_mutex(&mut inner, path_or_string, graph, transient, uuid_namespace) }) } @@ -243,15 +241,21 @@ impl PyModel { }) } - #[pyo3(signature = (df, graph=None))] + #[pyo3(signature = (df, graph=None, uuid_namespace=None))] #[instrument(skip_all)] - fn map_df(&self, py: Python<'_>, df: &Bound<'_, PyAny>, graph: Option) -> PyResult<()> { + fn map_df( + &self, + py: Python<'_>, + df: &Bound<'_, PyAny>, + graph: Option, + uuid_namespace: Option, + ) -> PyResult<()> { let (df, _) = data_to_mappings_types(df, py)?; py.detach(move || -> PyResult<()> { let mut inner = self.inner.lock().unwrap(); let graph = parse_optional_named_node(graph)?; let named_graph = NamedGraph::from_maybe_named_node(graph.as_ref()); - map_df_mutex(&mut inner, df, named_graph) + map_df_mutex(&mut inner, df, named_graph, uuid_namespace) }) } diff --git a/py_maplib/tests/test_json.py b/py_maplib/tests/test_json.py index d0ed3284..46c6fb0c 100644 --- a/py_maplib/tests/test_json.py +++ b/py_maplib/tests/test_json.py @@ -239,4 +239,40 @@ def test_insert(disk): m.insert(users, source_graph="urn:graph:tmp") #print(m.writes(format="turtle", prefixes={"":"https://github.com/DataTreehouse/maplib/users#"})) df = m.query("SELECT * WHERE {?a ?b ?c}") - assert df.height == 4 \ No newline at end of file + assert df.height == 4 + +def test_map_json_uuid_v5(): + json_2 = TESTDATA_PATH / "2.json" + m = Model() + m.map_json(str(json_2)) + + m2 = Model() + m2.map_json(str(json_2)) + df1 = m.query("""SELECT * WHERE {?a ?b ?c}""") + df2 = m2.query("""SELECT * WHERE {?a ?b ?c}""") + + assert df1.get_column("a").sort().to_list() == df2.get_column("a").sort().to_list() + +def test_map_json_uuid_v5_different_uuids(): + json_2 = TESTDATA_PATH / "2.json" + m = Model() + m.map_json(str(json_2), uuid_namespace="abc") + + m2 = Model() + m2.map_json(str(json_2)) + df1 = m.query("""SELECT * WHERE {?a ?b ?c}""") + df2 = m2.query("""SELECT * WHERE {?a ?b ?c}""") + + assert df1.get_column("a").sort().to_list() != df2.get_column("a").sort().to_list() + +def test_map_json_uuid_v5_same_uuid_args(): + json_2 = TESTDATA_PATH / "2.json" + m = Model() + m.map_json(str(json_2), uuid_namespace="abc") + + m2 = Model() + m2.map_json(str(json_2), uuid_namespace="abc") + df1 = m.query("""SELECT * WHERE {?a ?b ?c}""") + df2 = m2.query("""SELECT * WHERE {?a ?b ?c}""") + + assert df1.get_column("a").sort().to_list() == df2.get_column("a").sort().to_list() \ No newline at end of file diff --git a/py_maplib/tests/test_map_df.py b/py_maplib/tests/test_map_df.py index d3615de5..0ef05fa0 100644 --- a/py_maplib/tests/test_map_df.py +++ b/py_maplib/tests/test_map_df.py @@ -99,4 +99,54 @@ def test_map_df_datetime(): SELECT ?a ?c WHERE {?a xyz:b ?c} ORDER BY ?a ?c """, solution_mappings=True ) - assert res.rdf_types["c"] == RDFType.Literal(xsd.dateTime) \ No newline at end of file + assert res.rdf_types["c"] == RDFType.Literal(xsd.dateTime) + +def test_map_df_uuid_v5_same_args(): + df = pl.DataFrame( + { + "a": [ + "abc", + "def", + "ghi", + ], + "b": [ + 1.4, + 4.2, + 7.8, + ], + } + ) + m = Model() + m.map_df(df, uuid_namespace = "abc") + df1 = m.query("""SELECT * WHERE {?a ?b ?c} ORDER BY ?a ?c""") + + m2 = Model() + m2.map_df(df, uuid_namespace = "abc") + df2 = m2.query("""SELECT * WHERE {?a ?b ?c} ORDER BY ?a ?c""") + assert df1.height == 13 and df2.height == 13 + assert df1.get_column("a").sort().to_list() == df2.get_column("a").sort().to_list() + +def test_map_df_uuid_v5_diff_args(): + df = pl.DataFrame( + { + "a": [ + "abc", + "def", + "ghi", + ], + "b": [ + 1.4, + 4.2, + 7.8, + ], + } + ) + m = Model() + m.map_df(df, uuid_namespace = "abc") + df1 = m.query("""SELECT * WHERE {?a ?b ?c}""") + + m2 = Model() + m2.map_df(df, uuid_namespace = "def") + df2 = m2.query("""SELECT * WHERE {?a ?b ?c}""") + assert df1.height == 13 and df2.height == 13 + assert df1.get_column("a").sort().to_list() != df2.get_column("a").sort().to_list() \ No newline at end of file diff --git a/py_maplib/tests/test_xml.py b/py_maplib/tests/test_xml.py index 58fa81be..f34f58f9 100644 --- a/py_maplib/tests/test_xml.py +++ b/py_maplib/tests/test_xml.py @@ -2,6 +2,7 @@ import pytest +import polars as pl from .disk import disk_params from maplib import Model @@ -171,3 +172,38 @@ def test_map_xml_attributes(): assert df.height == 1 assert df.get_column("id")[0] == "42" assert df.get_column("name")[0] == "hello" + +def test_map_xml_uuid_v5_no_args(): + xml_1 = TESTDATA_PATH / "1.xml" + m = Model() + m.map_xml(str(xml_1)) + df = m.query("""SELECT * WHERE {?s ?p ?o}""") + + m2 = Model() + m2.map_xml(str(xml_1)) + df2 = m2.query("""SELECT * WHERE {?s ?p ?o}""") + assert df.height == 61 + assert df.get_column("s").sort().to_list() == df2.get_column("s").sort().to_list() + +def test_map_xml_uuid_v5_different_uuids(): + xml_2 = TESTDATA_PATH / "1.xml" + m = Model() + m.map_xml(str(xml_2), uuid_namespace="abc") + + m2 = Model() + m2.map_xml(str(xml_2)) + df = m.query("""SELECT * WHERE {?s ?p ?o}""") + df2 = m2.query("""SELECT * WHERE {?s ?p ?o}""") + assert df.get_column("s").sort().to_list() != df2.get_column("s").sort().to_list() + +def test_map_xml_uuid_v5_same_args(): + xml_1 = TESTDATA_PATH / "2.xml" + m = Model() + m.map_xml(str(xml_1), uuid_namespace="abc") + df = m.query("""SELECT * WHERE {?s ?p ?o}""") + + m2 = Model() + m2.map_xml(str(xml_1), uuid_namespace="abc") + df2 = m2.query("""SELECT * WHERE {?s ?p ?o}""") + assert df.height == 195 + assert df.get_column("s").sort().to_list() == df2.get_column("s").sort().to_list() \ No newline at end of file