diff --git a/src/dialect_translation/postgresql.rs b/src/dialect_translation/postgresql.rs index afed1b32..1fe094fe 100644 --- a/src/dialect_translation/postgresql.rs +++ b/src/dialect_translation/postgresql.rs @@ -99,13 +99,7 @@ mod tests { use super::*; use crate::{ - builder::{Ready, With}, - data_type::{DataType, Value as _}, - display::Dot, - expr::Expr, - namer, - relation::{schema::Schema, Relation, TableBuilder}, - sql::{parse, relation::QueryWithRelations}, + builder::{Ready, With}, data_type::{DataType, Value as _}, display::Dot, expr::Expr, io::{postgresql, Database as _}, namer, relation::{schema::Schema, Relation, TableBuilder}, sql::{parse, relation::QueryWithRelations} }; use std::sync::Arc; @@ -154,19 +148,8 @@ mod tests { #[test] fn test_table_special() -> Result<()> { - let table: Relation = TableBuilder::new() - .path(["MY SPECIAL TABLE"]) - .name("my_table") - .size(100) - .schema( - Schema::empty() - .with(("Id", DataType::integer_interval(0, 1000))) - .with(("Na.Me", DataType::text())) - .with(("inc&ome", DataType::float_interval(100.0, 200000.0))) - .with(("normal_col", DataType::text())), - ) - .build(); - let relations = Hierarchy::from([(["schema", "MY SPECIAL TABLE"], Arc::new(table))]); + let mut database = postgresql::test_database(); + let relations = database.relations(); let query_str = r#"SELECT "Id", NORMAL_COL, "Na.Me" FROM "MY SPECIAL TABLE" ORDER BY "Id" "#; let translator = PostgreSqlTranslator; let query = parse_with_dialect(query_str, translator.dialect())?; @@ -174,16 +157,13 @@ mod tests { let relation = Relation::try_from((query_with_relation, translator))?; println!("\n {} \n", relation); let rel_with_traslator = RelationWithTranslator(&relation, translator); - let retranslated = ast::Query::from(rel_with_traslator); - print!("{}", retranslated); - let translated = r#" - WITH "map_mou5" ("Id","normal_col","Na.Me") AS ( - SELECT "Id" AS "Id", "normal_col" AS "normal_col", "Na.Me" AS "Na.Me" FROM "MY SPECIAL TABLE" - ), "map_0swv"("Id","normal_col","Na.Me") AS ( - SELECT "Id" AS "Id", "normal_col" AS "normal_col", "Na.Me" AS "Na.Me" FROM "map_mou5" ORDER BY "Id" ASC - ) SELECT * FROM "map_0swv" - "#; - // assert_same_query_str(&retranslated.to_string(), translated); + let translated = ast::Query::from(rel_with_traslator); + print!("{}", translated); + _ = database + .query(translated.to_string().as_str()) + .unwrap() + .iter() + .map(ToString::to_string); Ok(()) } } diff --git a/src/hierarchy.rs b/src/hierarchy.rs index 03a36056..520cf501 100644 --- a/src/hierarchy.rs +++ b/src/hierarchy.rs @@ -211,6 +211,25 @@ impl Hierarchy { .filter_map(|(p, o)| Some((p.clone(), f(o)?))) .collect() } + + /// It checks whether the path without the head is ambiguous or not. + /// It returns the full paths if the suffix is ambiguous. + pub fn ambiguous_subpaths(&self) -> Vec> { + self.iter() + .filter_map(|(qualified_key, _)| { + let headless_path = if qualified_key.len() > 1 { + &qualified_key[1..] + } else { + &qualified_key[..] + }; + if let Some(_) = self.get(&headless_path) { + None + } else { + Some(qualified_key.clone()) + } + }) + .collect() + } } impl Hierarchy

{ @@ -468,4 +487,39 @@ mod tests { )) ); } + + #[test] + fn test_ambiguous_paths() { + let values = Hierarchy::from([ + (vec!["a", "b", "c"], 1), + (vec!["a", "b", "d"], 2), + (vec!["a", "c"], 3), + (vec!["a", "e"], 4), + (vec!["a", "e", "f"], 5), + (vec!["b", "c"], 6), + ]); + let ambiguous = values.ambiguous_subpaths(); + assert_eq!(ambiguous, vec![ + vec!["a", "c"], + vec!["b", "c"], + ]); + + let values = Hierarchy::from([ + (vec!["a", "b", "d"], 2), + (vec!["a", "b"], 4), + (vec!["a", "e", "f"], 5), + ]); + let ambiguous: Vec> = values.ambiguous_subpaths(); + let empty: Vec> = vec![]; + assert_eq!(ambiguous, empty); + + let values = Hierarchy::from([ + (vec![], 2), + (vec!["b"], 4), + (vec!["c"], 5), + ]); + let ambiguous: Vec> = values.ambiguous_subpaths(); + let empty: Vec> = vec![]; + assert_eq!(ambiguous, empty); + } } diff --git a/src/relation/rewriting.rs b/src/relation/rewriting.rs index 16a95d76..416f071a 100644 --- a/src/relation/rewriting.rs +++ b/src/relation/rewriting.rs @@ -234,14 +234,14 @@ impl Join { }) .collect::>(); - let fields = coalesced_fields + let all_fields = coalesced_fields .into_iter() .chain(remaining_fields.into_iter()) .collect::>(); Relation::map() .input(Relation::from(self)) - .with_iter(fields) + .with_iter(all_fields) .build() } } diff --git a/src/rewriting/mod.rs b/src/rewriting/mod.rs index 192c4624..4b444816 100644 --- a/src/rewriting/mod.rs +++ b/src/rewriting/mod.rs @@ -198,6 +198,7 @@ mod tests { println!("=================================\n{q}"); let query = parse(q).unwrap(); let relation = Relation::try_from(query.with(&relations)).unwrap(); + relation.display_dot().unwrap(); let relation_with_dp_event = relation .rewrite_with_differential_privacy(&relations, synthetic_data.clone(), privacy_unit.clone(), dp_parameters.clone()) .unwrap(); diff --git a/src/sql/relation.rs b/src/sql/relation.rs index e0d09a8c..2f85db23 100644 --- a/src/sql/relation.rs +++ b/src/sql/relation.rs @@ -270,12 +270,12 @@ impl<'a, T: QueryToRelationTranslator + Copy + Clone> VisitedQueryRelations<'a, // fully qualified input names -> fully qualified JOIN names let all_fully_qualified_columns: Hierarchy = left_columns.with(right_columns); - let ambiguous_cols= ambiguous_columns(&all_fully_qualified_columns); + let ambiguous_cols= all_fully_qualified_columns.ambiguous_subpaths(); // fully qualified JOIN names -> non_ambiguous col names let non_ambiguous_join_col_names: Hierarchy = all_fully_qualified_columns .iter() .filter_map(|(k, v)| { - if ambiguous_cols.contains(&Identifier::from(k.clone()) ) { + if ambiguous_cols.contains(k) { None } else { Some((v.clone(), k.clone().last().unwrap().to_string())) @@ -754,22 +754,6 @@ impl<'a, T: QueryToRelationTranslator + Copy + Clone> TryFrom<(QueryWithRelation } } -/// It returns a vector of identifiers of ambiguous columns in a hierarchy of columns -/// It uses the properties of the Hierarchy: For ambiguous columns it checks -/// that Hierarchy::get returns None if using only the suffix of the path -pub fn ambiguous_columns(columns: &Hierarchy) -> Vec { - columns - .iter() - .filter_map(|(key, _)| { - if let Some(v) = columns.get(&[key.last().unwrap().as_str().to_string()]) { - None - } else { - Some(key.clone().into()) - } - }) - .collect() -} - /// A simple SQL query parser with dialect pub fn parse_with_dialect(query: &str, dialect: D) -> Result { let mut tokenizer = Tokenizer::new(&dialect, query);