Skip to content

Commit 8343fa8

Browse files
committed
Support many-to-one configurations with 'norm'
1 parent fdbb5da commit 8343fa8

File tree

1 file changed

+75
-43
lines changed

1 file changed

+75
-43
lines changed

src/main.rs

+75-43
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ struct Table<'a> {
4444
file: RefCell<Box<dyn Write>>,
4545
columns: Vec<Column<'a>>,
4646
domain: Box<Option<RefCell<Domain<'a>>>>,
47+
normalized: bool,
4748
emit_copyfrom: bool,
4849
emit_starttransaction: bool
4950
}
@@ -67,6 +68,7 @@ impl<'a> Table<'a> {
6768
},
6869
columns: Vec::new(),
6970
domain: Box::new(None),
71+
normalized: false,
7072
emit_copyfrom: settings.emit_copyfrom,
7173
emit_starttransaction: settings.emit_starttransaction
7274
}
@@ -93,11 +95,11 @@ struct Domain<'a> {
9395
table: Table<'a>
9496
}
9597
impl<'a> Domain<'a> {
96-
fn new(tabname: &str, path: &str, filename: &str, settings: &Settings) -> Domain<'a> {
98+
fn new(tabname: &str, path: &str, filename: Option<&str>, settings: &Settings) -> Domain<'a> {
9799
Domain {
98100
lastid: 0,
99101
map: HashMap::new(),
100-
table: Table::new(tabname, path, Some(filename), settings)
102+
table: Table::new(tabname, path, filename, settings)
101103
}
102104
}
103105
}
@@ -242,7 +244,7 @@ fn add_table<'a>(name: &str, rowpath: &str, outfile: Option<&str>, settings: &Se
242244
let colname = col["name"].as_str().unwrap_or_else(|| fatalerr!("Error: column has no 'name' entry in configuration file"));
243245
let colpath = match col["seri"].as_bool() {
244246
Some(true) => "/",
245-
_ => col["path"].as_str().unwrap_or_else(|| fatalerr!("Error: column has no 'path' entry in configuration file"))
247+
_ => col["path"].as_str().unwrap_or_else(|| fatalerr!("Error: table '{}' column '{}' has no 'path' entry in configuration file", name, colname))
246248
};
247249
let mut path = String::from(&table.path);
248250
if !colpath.is_empty() && !colpath.starts_with('/') { path.push('/'); }
@@ -256,12 +258,20 @@ fn add_table<'a>(name: &str, rowpath: &str, outfile: Option<&str>, settings: &Se
256258
_ => None
257259
};
258260
let mut datatype = col["type"].as_str().unwrap_or("text").to_string();
261+
let norm = col["norm"].as_str();
259262
let mut subtable: Option<Table> = match col["cols"].is_badvalue() {
260263
true => None,
261264
false => {
262-
if table.columns.is_empty() { fatalerr!("Error: table '{}' cannot have a subtable as first column", name); }
263-
let filename = col["file"].as_str().unwrap_or_else(|| fatalerr!("Error: subtable {} has no 'file' entry", colname));
264-
Some(add_table(colname, &path, Some(filename), settings, col["cols"].as_vec().unwrap_or_else(|| fatalerr!("Error: subtable 'cols' entry is not an array")), Some(format!("{} {}", name, table.columns[0].datatype))))
265+
if norm.is_some() && col["file"].is_badvalue() { // Normalized subtable; writes its primary key into the parent table
266+
let mut subtable = add_table(colname, &path, norm, settings, col["cols"].as_vec().unwrap_or_else(|| fatalerr!("Error: subtable 'cols' entry is not an array")), None);
267+
subtable.normalized = true;
268+
Some(subtable)
269+
}
270+
else {
271+
let filename = col["file"].as_str().unwrap_or_else(|| fatalerr!("Error: subtable {} has no 'file' entry", colname));
272+
if table.columns.is_empty() { fatalerr!("Error: table '{}' cannot have a subtable as first column", name); }
273+
Some(add_table(colname, &path, Some(filename), settings, col["cols"].as_vec().unwrap_or_else(|| fatalerr!("Error: subtable 'cols' entry is not an array")), Some(format!("{} {}", name, table.columns[0].datatype))))
274+
}
265275
}
266276
};
267277
let hide = col["hide"].as_bool().unwrap_or(false);
@@ -273,11 +283,14 @@ fn add_table<'a>(name: &str, rowpath: &str, outfile: Option<&str>, settings: &Se
273283
let find = col["find"].as_str();
274284
let replace = col["repl"].as_str();
275285
let aggr = col["aggr"].as_str();
276-
let norm = col["norm"].as_str();
277286
let domain = match norm {
278287
Some(filename) => {
279288
if filename == "true" { fatalerr!("Error: 'norm' option now takes a file path instead of a boolean"); }
280-
let mut domain = Domain::new(colname, colpath, filename, settings);
289+
let file = match col["file"].is_badvalue() {
290+
true => None, // One-to-many relation
291+
false => Some(filename) // Many-to-many relation
292+
};
293+
let mut domain = Domain::new(colname, colpath, file, settings);
281294
domain.table.columns.push(Column { name: String::from("id"), path: String::new(), datatype: String::from("integer"), ..Default::default()});
282295
domain.table.columns.push(Column { name: String::from("value"), path: String::new(), datatype, ..Default::default()});
283296
emit_preamble(&domain.table, settings, None);
@@ -415,6 +428,7 @@ fn main() {
415428
}
416429
let mut tables: Vec<&Table> = Vec::new();
417430
let mut table = &maintable;
431+
let mut parentcol = None;
418432

419433
let mut filtered = false;
420434
let mut skipped = false;
@@ -518,6 +532,7 @@ fn main() {
518532
// Handle 'subtable' case (the 'cols' entry has 'cols' of its own)
519533
if table.columns[i].subtable.is_some() {
520534
tables.push(table);
535+
parentcol = Some(&table.columns[i]);
521536
table = table.columns[i].subtable.as_ref().unwrap();
522537
continue 'restart; // Continue the restart loop because a subtable column may also match the current path
523538
}
@@ -638,49 +653,66 @@ fn main() {
638653
}
639654
else {
640655

641-
if !tables.is_empty() { // This is a subtable; write the first column value of the parent table as the first column of the subtable (for use as a foreign key)
642-
let key = tables.last().unwrap().columns[0].value.borrow();
643-
if key.is_empty() && !settings.hush_warning { println!("Warning: subtable {} has no foreign key for parent (you may need to add a 'seri' column)", table.name); }
644-
table.write(&format!("{}\t", key));
645-
if let Some(domain) = table.domain.as_ref() {
646-
table.write(&format!("{}\n" , table.columns[0].value.borrow())); // This is a many-to-may relation; write the two keys into the link table
647-
let mut domain = domain.borrow_mut();
648-
if !domain.map.contains_key(&table.columns[0].value.borrow().to_string()) {
649-
domain.map.insert(table.columns[0].value.borrow().to_string(), 0); // Table domains use the HashMap as a HashSet
650-
for i in 0..table.columns.len() {
651-
if table.columns[i].subtable.is_some() { continue; }
652-
if table.columns[i].hide { continue; }
653-
if i > 0 { domain.table.write("\t"); }
654-
if table.columns[i].value.borrow().is_empty() { domain.table.write("\\N"); }
655-
else if let Some(domain) = table.columns[i].domain.as_ref() {
656-
let mut domain = domain.borrow_mut();
657-
let id = match domain.map.get(&table.columns[i].value.borrow().to_string()) {
658-
Some(id) => *id,
659-
None => {
660-
domain.lastid += 1;
661-
let id = domain.lastid;
662-
domain.map.insert(table.columns[i].value.borrow().to_string(), id);
663-
domain.table.write(&format!("{}\t{}\n", id, *table.columns[i].value.borrow()));
664-
id
665-
}
666-
};
667-
domain.table.write(&format!("{}", id));
668-
}
669-
else {
670-
domain.table.write(&table.columns[i].value.borrow());
656+
if !tables.is_empty() { // This is a subtable
657+
if !table.normalized { // Write the first column value of the parent table as the first column of the subtable (for use as a foreign key)
658+
let key = tables.last().unwrap().columns[0].value.borrow();
659+
if key.is_empty() && !settings.hush_warning { println!("Warning: subtable {} has no foreign key for parent (you may need to add a 'seri' column)", table.name); }
660+
table.write(&format!("{}\t", key));
661+
if let Some(domain) = table.domain.as_ref() {
662+
if table.columns[0].value.borrow().is_empty() { println!("Warning: subtable {} has no primary key to normalize on", table.name); }
663+
table.write(&format!("{}" , table.columns[0].value.borrow())); // This is a many-to-may relation; write the two keys into the link table
664+
let mut domain = domain.borrow_mut();
665+
if !domain.map.contains_key(&table.columns[0].value.borrow().to_string()) {
666+
domain.map.insert(table.columns[0].value.borrow().to_string(), 0); // Table domains use the HashMap as a HashSet
667+
for i in 0..table.columns.len() {
668+
if table.columns[i].subtable.is_some() { continue; }
669+
if table.columns[i].hide { continue; }
670+
if i > 0 { domain.table.write("\t"); }
671+
if table.columns[i].value.borrow().is_empty() { domain.table.write("\\N"); }
672+
else if let Some(domain) = table.columns[i].domain.as_ref() {
673+
let mut domain = domain.borrow_mut();
674+
let id = match domain.map.get(&table.columns[i].value.borrow().to_string()) {
675+
Some(id) => *id,
676+
None => {
677+
domain.lastid += 1;
678+
let id = domain.lastid;
679+
domain.map.insert(table.columns[i].value.borrow().to_string(), id);
680+
domain.table.write(&format!("{}\t{}\n", id, *table.columns[i].value.borrow()));
681+
id
682+
}
683+
};
684+
domain.table.write(&format!("{}", id));
685+
}
686+
else {
687+
domain.table.write(&table.columns[i].value.borrow());
688+
}
671689
}
690+
domain.table.write("\n");
672691
}
673-
domain.table.write("\n");
692+
table.write("\n");
693+
table.clear_columns();
694+
table = tables.pop().unwrap();
695+
continue 'restart;
696+
}
697+
}
698+
else { // Normalized; write the id of this subtable into the parent table
699+
parentcol.unwrap().value.borrow_mut().push_str(&table.columns[0].value.borrow());
700+
if let Some(domain) = table.domain.as_ref() {
701+
let mut domain = domain.borrow_mut();
702+
if domain.map.contains_key(&table.columns[0].value.borrow().to_string()) {
703+
table.clear_columns();
704+
table = tables.pop().unwrap();
705+
continue 'restart;
706+
}
707+
domain.map.insert(table.columns[0].value.borrow().to_string(), 0);
708+
// The for loop below will now write out the new row
674709
}
675-
table.clear_columns();
676-
table = tables.pop().unwrap();
677-
continue 'restart;
678710
}
679711
}
680712

681713
// Now write out the other column values
682714
for i in 0..table.columns.len() {
683-
if table.columns[i].subtable.is_some() { continue; }
715+
if table.columns[i].subtable.is_some() && !table.columns[i].subtable.as_ref().unwrap().normalized { continue; }
684716
if table.columns[i].hide {
685717
table.columns[i].value.borrow_mut().clear();
686718
continue;

0 commit comments

Comments
 (0)