Skip to content

Commit 0fa936c

Browse files
committed
Allow the use of 'file'+'norm' options without 'cols'
This generates a many-to-many relation with crosslink table for that single column with an autogenerated id.
1 parent 7c2fad8 commit 0fa936c

File tree

1 file changed

+44
-20
lines changed

1 file changed

+44
-20
lines changed

src/main.rs

+44-20
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ struct Table<'a> {
5959
}
6060
impl<'a> Table<'a> {
6161
fn new(name: &str, path: &str, file: Option<&str>, settings: &Settings, cardinality: Cardinality) -> Table<'a> {
62+
//println!("Table {} path {} file {:?} cardinality {:?}", name, path, file, cardinality);
6263
let mut ownpath = String::from(path);
6364
if !ownpath.is_empty() && !ownpath.starts_with('/') { ownpath.insert(0, '/'); }
6465
if ownpath.ends_with('/') { ownpath.pop(); }
@@ -104,11 +105,11 @@ struct Domain<'a> {
104105
table: Table<'a>
105106
}
106107
impl<'a> Domain<'a> {
107-
fn new(tabname: &str, path: &str, filename: Option<&str>, settings: &Settings) -> Domain<'a> {
108+
fn new(tabname: &str, filename: Option<&str>, settings: &Settings) -> Domain<'a> {
108109
Domain {
109110
lastid: 0,
110111
map: HashMap::new(),
111-
table: Table::new(tabname, path, filename, settings, match filename { Some(_) => Cardinality::ManyToOne, None => Cardinality::None })
112+
table: Table::new(tabname, "_domain_", filename, settings, match filename { Some(_) => Cardinality::ManyToOne, None => Cardinality::None })
112113
}
113114
}
114115
}
@@ -269,14 +270,14 @@ fn add_table<'a>(name: &str, rowpath: &str, outfile: Option<&str>, settings: &Se
269270
let mut datatype = col["type"].as_str().unwrap_or("text").to_string();
270271
let norm = col["norm"].as_str();
271272
let file = col["file"].as_str();
272-
let cardinality = match (file, norm) {
273+
let cardinality = match (file, norm) { // The combination of 'file' and 'norm' options determine relation to the subtable (if any)
273274
(None, None) => Cardinality::Default,
274275
(Some(_), None) => Cardinality::OneToMany,
275276
(None, Some(_)) => Cardinality::ManyToOne,
276277
(Some(_), Some(_)) => Cardinality::ManyToMany
277278
};
278279
let mut subtable: Option<Table> = match col["cols"].is_badvalue() {
279-
true => match cardinality {
280+
true => match cardinality { // No 'cols' setting; the current column is the only one in the subtable
280281
Cardinality::OneToMany => {
281282
let filename = col["file"].as_str().unwrap();
282283
if table.columns.is_empty() { fatalerr!("Error: table '{}' cannot have a subtable as first column", name); }
@@ -285,22 +286,31 @@ fn add_table<'a>(name: &str, rowpath: &str, outfile: Option<&str>, settings: &Se
285286
emit_preamble(&subtable, settings, Some(format!("{} {}", name, table.columns[0].datatype)));
286287
Some(subtable)
287288
},
289+
Cardinality::ManyToMany => {
290+
let filename = col["file"].as_str().unwrap();
291+
if table.columns.is_empty() { fatalerr!("Error: table '{}' cannot have a subtable as first column", name); }
292+
let mut subtable = add_table(colname, &path, Some(filename), settings, &[], cardinality);
293+
// subtable.columns.push(Column { name: String::from("id"), path: String::new(), datatype: String::from("integer"), ..Default::default() });
294+
subtable.columns.push(Column { name: colname.to_string(), path: path.clone(), datatype: "integer".to_string(), ..Default::default() });
295+
emit_preamble(&subtable, settings, Some(format!("{} {}", name, table.columns[0].datatype)));
296+
Some(subtable)
297+
},
288298
_ => None
289299
},
290-
false => {
291-
if norm.is_some() && col["file"].is_badvalue() { // Many-to-one relation (subtable with fkey in parent table)
300+
false => match cardinality {
301+
Cardinality::ManyToOne => { // Many-to-one relation (subtable with fkey in parent table)
292302
let subtable = add_table(colname, &path, norm, settings, col["cols"].as_vec().unwrap_or_else(|| fatalerr!("Error: subtable 'cols' entry is not an array")), cardinality);
293303
emit_preamble(&subtable, settings, None);
294304
Some(subtable)
295-
}
296-
else if norm.is_some() { // Many-to-many relation (this file will contain the crosslink table)
305+
},
306+
Cardinality::ManyToMany => { // Many-to-many relation (this file will contain the crosslink table)
297307
let filename = col["file"].as_str().unwrap_or_else(|| fatalerr!("Error: subtable {} has no 'file' entry", colname));
298308
if table.columns.is_empty() { fatalerr!("Error: table '{}' cannot have a subtable as first column", name); }
299309
let subtable = add_table(colname, &path, Some(filename), settings, col["cols"].as_vec().unwrap_or_else(|| fatalerr!("Error: subtable 'cols' entry is not an array")), cardinality);
300310
emit_preamble(&subtable, settings, Some(format!("{} {}", name, table.columns[0].datatype)));
301311
Some(subtable)
302-
}
303-
else { // One-to-many relation (this file will contain the subtable with the parent table fkey)
312+
},
313+
_ => { // One-to-many relation (this file will contain the subtable with the parent table fkey)
304314
let filename = col["file"].as_str().unwrap_or_else(|| fatalerr!("Error: subtable {} has no 'file' entry", colname));
305315
if table.columns.is_empty() { fatalerr!("Error: table '{}' cannot have a subtable as first column", name); }
306316
let subtable = add_table(colname, &path, Some(filename), settings, col["cols"].as_vec().unwrap_or_else(|| fatalerr!("Error: subtable 'cols' entry is not an array")), cardinality);
@@ -326,9 +336,9 @@ fn add_table<'a>(name: &str, rowpath: &str, outfile: Option<&str>, settings: &Se
326336
Some(_) => Some(filename), // Many-to-many relation (subtable with crosslink table); rows go into this file
327337
None => Some(filename) // Many-to-one relation (single column) with auto serial; rows go into this file
328338
};
329-
let mut domain = Domain::new(colname, colpath, file, settings);
339+
let mut domain = Domain::new(colname, file, settings);
330340
if file.is_some() {
331-
if subtable.is_some() {
341+
if subtable.is_some() && !col["cols"].is_badvalue() {
332342
for col in col["cols"].as_vec().unwrap() {
333343
let colname = col["name"].as_str().unwrap_or_else(|| fatalerr!("Error: column has no 'name' entry in configuration file"));
334344
let datatype = col["type"].as_str().unwrap_or("text");
@@ -397,7 +407,7 @@ fn emit_preamble(table: &Table, settings: &Settings, fkey: Option<String>) {
397407
if settings.emit_createtable {
398408
if table.cardinality == Cardinality::ManyToMany {
399409
let fkey = fkey.as_ref().unwrap();
400-
table.write(&format!("CREATE TABLE IF NOT EXISTS {}_{} ({}, {} {});\n", fkey.split_once(' ').unwrap().0, table.name, fkey, table.name, table.columns[0].datatype));
410+
table.write(&format!("CREATE TABLE IF NOT EXISTS {}_{} ({}, {} {});\n", fkey.split_once(' ').unwrap().0, table.name, fkey, table.name, if table.columns.is_empty() { "integer" } else { &table.columns[0].datatype }));
401411
}
402412
else {
403413
let mut cols = table.columns.iter().filter_map(|c| {
@@ -582,10 +592,12 @@ fn main() {
582592

583593
// Handle the 'seri' case where this column is a virtual auto-incrementing serial
584594
if let Some(ref serial) = table.columns[i].serial {
585-
let id = serial.get()+1;
586-
table.columns[i].value.borrow_mut().push_str(&id.to_string());
587-
serial.set(id);
588-
continue;
595+
if table.columns[i].value.borrow().is_empty() {
596+
let id = serial.get()+1;
597+
table.columns[i].value.borrow_mut().push_str(&id.to_string());
598+
serial.set(id);
599+
continue 'restart; // Continue the restart loop because another column may also match the current path
600+
}
589601
}
590602

591603
// Handle 'subtable' case (the 'cols' entry has 'cols' of its own)
@@ -708,12 +720,16 @@ fn main() {
708720
let key = tables.last().unwrap().columns[0].value.borrow();
709721
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); }
710722
table.write(&format!("{}\t", key));
723+
let rowid;
711724
if let Some(domain) = table.domain.as_ref() {
712-
if table.columns[0].value.borrow().is_empty() { println!("Warning: subtable {} has no primary key to normalize on", table.name); }
713-
table.write(&format!("{}" , table.columns[0].value.borrow())); // This is a many-to-may relation; write the two keys into the link table
714725
let mut domain = domain.borrow_mut();
715726
if !domain.map.contains_key(&table.columns[0].value.borrow().to_string()) {
716-
domain.map.insert(table.columns[0].value.borrow().to_string(), 0); // Table domains use the HashMap as a HashSet
727+
domain.lastid += 1;
728+
rowid = domain.lastid;
729+
domain.map.insert(table.columns[0].value.borrow().to_string(), rowid);
730+
if table.columns.len() == 1 {
731+
domain.table.write(&format!("{}\t", rowid));
732+
}
717733
for i in 0..table.columns.len() {
718734
if table.columns[i].subtable.is_some() { continue; }
719735
if table.columns[i].hide { continue; }
@@ -739,6 +755,14 @@ fn main() {
739755
}
740756
domain.table.write("\n");
741757
}
758+
else { rowid = *domain.map.get(&table.columns[0].value.borrow().to_string()).unwrap(); }
759+
if table.columns.len() == 1 { // Single column many-to-many subtable; needs the id from the domain map
760+
table.write(&format!("{}" , rowid));
761+
}
762+
else {
763+
if table.columns[0].value.borrow().is_empty() { println!("Warning: subtable {} has no primary key to normalize on", table.name); }
764+
table.write(&format!("{}" , table.columns[0].value.borrow())); // This is a many-to-many relation; write the two keys into the link table
765+
}
742766
table.write("\n");
743767
table.clear_columns();
744768
table = tables.pop().unwrap();

0 commit comments

Comments
 (0)