Skip to content

Commit fc86ca3

Browse files
committed
Fix emit options when combined with normalized subtables
1 parent a2d5b0c commit fc86ca3

File tree

1 file changed

+80
-38
lines changed

1 file changed

+80
-38
lines changed

Diff for: src/main.rs

+80-38
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,27 @@ struct Settings {
3838
hush_warning: bool
3939
}
4040

41+
#[derive(Copy, Clone, PartialEq, Debug)]
42+
enum Cardinality {
43+
Default,
44+
OneToMany,
45+
ManyToOne,
46+
ManyToMany,
47+
None
48+
}
49+
4150
struct Table<'a> {
4251
name: String,
4352
path: String,
4453
file: RefCell<Box<dyn Write>>,
4554
columns: Vec<Column<'a>>,
4655
domain: Box<Option<RefCell<Domain<'a>>>>,
47-
normalized: bool,
56+
cardinality: Cardinality,
4857
emit_copyfrom: bool,
4958
emit_starttransaction: bool
5059
}
5160
impl<'a> Table<'a> {
52-
fn new(name: &str, path: &str, file: Option<&str>, settings: &Settings) -> Table<'a> {
61+
fn new(name: &str, path: &str, file: Option<&str>, settings: &Settings, cardinality: Cardinality) -> Table<'a> {
5362
let mut ownpath = String::from(path);
5463
if !ownpath.is_empty() && !ownpath.starts_with('/') { ownpath.insert(0, '/'); }
5564
if ownpath.ends_with('/') { ownpath.pop(); }
@@ -68,9 +77,9 @@ impl<'a> Table<'a> {
6877
},
6978
columns: Vec::new(),
7079
domain: Box::new(None),
71-
normalized: false,
72-
emit_copyfrom: settings.emit_copyfrom,
73-
emit_starttransaction: settings.emit_starttransaction
80+
cardinality,
81+
emit_copyfrom: if cardinality != Cardinality::None { settings.emit_copyfrom } else { false },
82+
emit_starttransaction: if cardinality != Cardinality::None { settings.emit_starttransaction } else { false }
7483
}
7584
}
7685
fn write(&self, text: &str) {
@@ -99,7 +108,7 @@ impl<'a> Domain<'a> {
99108
Domain {
100109
lastid: 0,
101110
map: HashMap::new(),
102-
table: Table::new(tabname, path, filename, settings)
111+
table: Table::new(tabname, path, filename, settings, match filename { Some(_) => Cardinality::ManyToOne, None => Cardinality::None })
103112
}
104113
}
105114
}
@@ -238,8 +247,8 @@ fn gml_to_ewkb(cell: &RefCell<String>, coll: &[Geometry], bbox: Option<&BBox>, m
238247
true
239248
}
240249

241-
fn add_table<'a>(name: &str, rowpath: &str, outfile: Option<&str>, settings: &Settings, colspec: &'a [Yaml], fkey: Option<String>) -> Table<'a> {
242-
let mut table = Table::new(name, rowpath, outfile, settings);
250+
fn add_table<'a>(name: &str, rowpath: &str, outfile: Option<&str>, settings: &Settings, colspec: &'a [Yaml], fkey: Option<String>, cardinality: Cardinality) -> Table<'a> {
251+
let mut table = Table::new(name, rowpath, outfile, settings, cardinality);
243252
for col in colspec {
244253
let colname = col["name"].as_str().unwrap_or_else(|| fatalerr!("Error: column has no 'name' entry in configuration file"));
245254
let colpath = match col["seri"].as_bool() {
@@ -259,19 +268,29 @@ fn add_table<'a>(name: &str, rowpath: &str, outfile: Option<&str>, settings: &Se
259268
};
260269
let mut datatype = col["type"].as_str().unwrap_or("text").to_string();
261270
let norm = col["norm"].as_str();
271+
let file = col["file"].as_str();
272+
let cardinality = match (file, norm) {
273+
(None, None) => Cardinality::Default,
274+
(Some(_), None) => Cardinality::OneToMany,
275+
(None, Some(_)) => Cardinality::ManyToOne,
276+
(Some(_), Some(_)) => Cardinality::ManyToMany
277+
};
262278
let mut subtable: Option<Table> = match col["cols"].is_badvalue() {
263279
true => None,
264280
false => {
265281
if norm.is_some() && col["file"].is_badvalue() { // Many-to-one relation (subtable with fkey in 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;
282+
let subtable = add_table(colname, &path, norm, settings, col["cols"].as_vec().unwrap_or_else(|| fatalerr!("Error: subtable 'cols' entry is not an array")), None, cardinality);
268283
Some(subtable)
269284
}
270-
else { // If norm.is_some(): many-to-many relation (this file will contain the crosslink table)
271-
// Otherwise: one-to-many relation (this file will contain the subtable with the parent table fkey)
285+
else if norm.is_some() { // Many-to-many relation (this file will contain the crosslink table)
286+
let filename = col["file"].as_str().unwrap_or_else(|| fatalerr!("Error: subtable {} has no 'file' entry", colname));
287+
if table.columns.is_empty() { fatalerr!("Error: table '{}' cannot have a subtable as first column", name); }
288+
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)), cardinality))
289+
}
290+
else { // One-to-many relation (this file will contain the subtable with the parent table fkey)
272291
let filename = col["file"].as_str().unwrap_or_else(|| fatalerr!("Error: subtable {} has no 'file' entry", colname));
273292
if table.columns.is_empty() { fatalerr!("Error: table '{}' cannot have a subtable as first column", name); }
274-
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))))
293+
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)), cardinality))
275294
}
276295
}
277296
};
@@ -288,14 +307,25 @@ fn add_table<'a>(name: &str, rowpath: &str, outfile: Option<&str>, settings: &Se
288307
Some(filename) => {
289308
if filename == "true" { fatalerr!("Error: 'norm' option now takes a file path instead of a boolean"); }
290309
let file = match subtable {
291-
Some(_) if col["file"].is_badvalue() => None, // Many-to-one relation (subtable with fkey in parent table)
292-
Some(_) => Some(filename), // Many-to-many relation (subtable with crosslink table)
293-
None => Some(filename) // Many-to-one relation (single column) with auto serial
310+
Some(_) if col["file"].is_badvalue() => None, // Many-to-one relation (subtable with fkey in parent table); rows go into subtable file
311+
Some(_) => Some(filename), // Many-to-many relation (subtable with crosslink table); rows go into this file
312+
None => Some(filename) // Many-to-one relation (single column) with auto serial; rows go into this file
294313
};
295314
let mut domain = Domain::new(colname, colpath, file, settings);
296-
domain.table.columns.push(Column { name: String::from("id"), path: String::new(), datatype: String::from("integer"), ..Default::default()});
297-
domain.table.columns.push(Column { name: String::from("value"), path: String::new(), datatype, ..Default::default()});
298-
emit_preamble(&domain.table, settings, None);
315+
if file.is_some() {
316+
if subtable.is_some() {
317+
for col in col["cols"].as_vec().unwrap() {
318+
let colname = col["name"].as_str().unwrap_or_else(|| fatalerr!("Error: column has no 'name' entry in configuration file"));
319+
let datatype = col["type"].as_str().unwrap_or("text");
320+
domain.table.columns.push(Column { name: colname.to_string(), path: String::new(), datatype: datatype.to_string(), ..Default::default() });
321+
}
322+
}
323+
else {
324+
domain.table.columns.push(Column { name: String::from("id"), path: String::new(), datatype: String::from("integer"), ..Default::default() });
325+
domain.table.columns.push(Column { name: colname.to_string(), path: String::new(), datatype, ..Default::default() });
326+
}
327+
emit_preamble(&domain.table, settings, None);
328+
}
299329
datatype = String::from("integer");
300330
if let Some(ref mut table) = subtable { // Push the domain down to the subtable
301331
table.domain = Box::new(Some(RefCell::new(domain)));
@@ -351,28 +381,40 @@ fn emit_preamble(table: &Table, settings: &Settings, fkey: Option<String>) {
351381
table.write(&format!("DROP TABLE IF EXISTS {};\n", table.name));
352382
}
353383
if settings.emit_createtable {
354-
let mut cols = table.columns.iter().filter_map(|c| {
355-
if c.hide || c.subtable.is_some() { return None; }
356-
let mut spec = String::from(&c.name);
357-
spec.push(' ');
358-
spec.push_str(&c.datatype);
359-
Some(spec)
360-
}).collect::<Vec<String>>().join(", ");
361-
if fkey.is_some() { cols.insert_str(0, &format!("{}, ", fkey.as_ref().unwrap())); }
362-
table.write(&format!("CREATE TABLE IF NOT EXISTS {} ({});\n", table.name, cols));
384+
if table.cardinality == Cardinality::ManyToMany {
385+
let fkey = fkey.as_ref().unwrap();
386+
table.write(&format!("CREATE TABLE IF NOT EXISTS {}_{} ({}, {} {});\n", fkey.split_once(' ').unwrap().0, table.name, fkey, table.name, table.columns[0].datatype));
387+
}
388+
else {
389+
let mut cols = table.columns.iter().filter_map(|c| {
390+
if c.hide || (c.subtable.is_some() && c.subtable.as_ref().unwrap().cardinality != Cardinality::ManyToOne) { return None; }
391+
let mut spec = String::from(&c.name);
392+
spec.push(' ');
393+
spec.push_str(&c.datatype);
394+
Some(spec)
395+
}).collect::<Vec<String>>().join(", ");
396+
if fkey.is_some() { cols.insert_str(0, &format!("{}, ", fkey.as_ref().unwrap())); }
397+
table.write(&format!("CREATE TABLE IF NOT EXISTS {} ({});\n", table.name, cols));
398+
}
363399
}
364400
if settings.emit_truncate {
365401
table.write(&format!("TRUNCATE {};\n", table.name));
366402
}
367403
if settings.emit_copyfrom {
368-
let cols = table.columns.iter().filter_map(|c| {
369-
if c.hide || c.subtable.is_some() { return None; }
370-
Some(String::from(&c.name))
371-
}).collect::<Vec<String>>().join(", ");
372-
if fkey.is_some() {
373-
table.write(&format!("COPY {} ({}, {}) FROM stdin;\n", table.name, fkey.unwrap().split(' ').next().unwrap(), cols));
404+
if table.cardinality == Cardinality::ManyToMany {
405+
let parent = fkey.as_ref().unwrap().split_once(' ').unwrap().0;
406+
table.write(&format!("COPY {}_{} ({}, {}) FROM stdin;\n", parent, table.name, parent, table.name));
407+
}
408+
else {
409+
let cols = table.columns.iter().filter_map(|c| {
410+
if c.hide || (c.subtable.is_some() && c.subtable.as_ref().unwrap().cardinality != Cardinality::ManyToOne) { return None; }
411+
Some(String::from(&c.name))
412+
}).collect::<Vec<String>>().join(", ");
413+
if fkey.is_some() {
414+
table.write(&format!("COPY {} ({}, {}) FROM stdin;\n", table.name, fkey.unwrap().split(' ').next().unwrap(), cols));
415+
}
416+
else { table.write(&format!("COPY {} ({}) FROM stdin;\n", table.name, cols)); }
374417
}
375-
else { table.write(&format!("COPY {} ({}) FROM stdin;\n", table.name, cols)); }
376418
}
377419
}
378420

@@ -423,7 +465,7 @@ fn main() {
423465
hush_notice: hush.contains("notice"),
424466
hush_warning: hush.contains("warning")
425467
};
426-
let maintable = add_table(name, rowpath, outfile, &settings, colspec, None);
468+
let maintable = add_table(name, rowpath, outfile, &settings, colspec, None, Cardinality::Default);
427469
if !settings.skip.is_empty() {
428470
if !settings.skip.starts_with('/') { settings.skip.insert(0, '/'); }
429471
settings.skip.insert_str(0, &maintable.path); // Maintable path is normalized in add_table()
@@ -647,7 +689,7 @@ fn main() {
647689
}
648690
else {
649691
if !tables.is_empty() { // This is a subtable
650-
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)
692+
if table.cardinality != Cardinality::ManyToOne { // Write the first column value of the parent table as the first column of the subtable (for use as a foreign key)
651693
let key = tables.last().unwrap().columns[0].value.borrow();
652694
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); }
653695
table.write(&format!("{}\t", key));
@@ -705,7 +747,7 @@ fn main() {
705747

706748
// Now write out the other column values
707749
for i in 0..table.columns.len() {
708-
if table.columns[i].subtable.is_some() && !table.columns[i].subtable.as_ref().unwrap().normalized { continue; }
750+
if table.columns[i].subtable.is_some() && table.columns[i].subtable.as_ref().unwrap().cardinality != Cardinality::ManyToOne { continue; }
709751
if table.columns[i].hide {
710752
table.columns[i].value.borrow_mut().clear();
711753
continue;

0 commit comments

Comments
 (0)