X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=parser.js;h=5ef2b3f25ba7ebb72fe58a6e0c93e3b30fdcc8e9;hb=2906c5327f141d94aacc7ee65c3595a4f940cf37;hp=f41a755ef87dc3ab7b933aa0bad911a866d3b25e;hpb=5fe4fa10fa947c09ae37841bad3bc76e5767fa0b;p=erdiag.git diff --git a/parser.js b/parser.js index f41a755..5ef2b3f 100644 --- a/parser.js +++ b/parser.js @@ -15,16 +15,17 @@ class ErDiags this.sqlText = ""; } - static get CARDINAL() + static CARDINAL(symbol) { - return { - "*": "0,n", - "+": "1,n", - "?": "0,1", - "1": "1,1", - "?R": "(0,1)", - "1R": "(1,1)", - }; + let res = { "*": "0,n", "+": "1,n", "?": "0,1", "1": "1,1" } [ symbol[0] ]; + if (symbol.length >= 2) + { + if (symbol[1] == 'R') + res = '(' + res + ')'; + else if (['>','<'].includes(symbol[1])) + res += symbol[1]; + } + return res; } /////////////////////////////// @@ -94,18 +95,17 @@ class ErDiags { let field = { }; let line = lines[i]; - if (line.charAt(0) == '#') + if (line.charAt(0) == '+') { field.isKey = true; line = line.slice(1); } - field.name = line.match(/[^()"\s]+/)[0]; - let parenthesis = line.match(/\((.+)\)/); - if (parenthesis !== null) + field.name = line.match(/[^"\s]+/)[0]; + let sqlClues = line.substring(field.name.length).trim(); + if (sqlClues.length > 0) { - let sqlClues = parenthesis[1]; field.type = sqlClues.match(/[^\s]+/)[0]; //type is always the first indication (mandatory) - field.qualifiers = sqlClues.substring(field.type.length).trim(); + field.qualifiers = sqlClues.substring(field.type.length); } attributes.push(field); } @@ -166,12 +166,18 @@ class ErDiags Object.keys(this.entities).forEach( name => { let newTable = [ ]; //array of fields this.entities[name].attributes.forEach( attr => { - newTable.push({ + let newField = { name: attr.name, type: attr.type, isKey: attr.isKey, qualifiers: attr.qualifiers, - }); + }; + if (!!attr.qualifiers && !!attr.qualifiers.match(/references/i)) + { + Object.assign(newField, {ref: attr.qualifiers.match(/references ([^\s]+)/i)[1]}); + attr.qualifiers = attr.qualifiers.replace(/references [^\s]+/, ""); + } + newTable.push(newField); }); this.tables[name] = newTable; }); @@ -184,17 +190,19 @@ class ErDiags name: inh.parent + "_id", type: this.tables[inh.parent][idx].type, isKey: true, - qualifiers: (this.tables[inh.parent][idx].qualifiers || "") + " foreign key references " + inh.parent, - ref: inh.parent, + qualifiers: this.tables[inh.parent][idx].qualifiers || "", + ref: inh.parent + "(" + this.tables[inh.parent][idx].name + ")", }); }); }); // Pass 2: parse associations, add foreign keys when cardinality is 0,1 or 1,1 this.associations.forEach( a => { let newTableAttrs = [ ]; + let hasZeroOne = false; a.entities.forEach( e => { if (['?','1'].includes(e.card[0])) { + hasZeroOne = true; // Foreign key apparition (for each entity in association minus current one, for each identifying attribute) a.entities.forEach( e2 => { if (e2.name == e.name) @@ -202,12 +210,14 @@ class ErDiags this.entities[e2.name].attributes.forEach( attr => { if (attr.isKey) { + // For "weak tables", foreign keys become part of the key + const isKey = e.card.length >= 2 && e.card[1] == 'R'; this.tables[e.name].push({ - isKey: e.card.length >= 2 && e.card[1] == 'R', //"weak tables" foreign keys become part of the key + isKey: isKey, name: e2.name + "_" + attr.name, type: attr.type, - qualifiers: "foreign key references " + e2.name + " " + (e.card[0]=='1' ? "not null" : ""), - ref: e2.name, //easier drawMld function (fewer regexps) + qualifiers: !isKey && e.card[0]=='1' ? "not null" : "", + ref: e2.name + "(" + attr.name + ")", }); } }); @@ -223,7 +233,7 @@ class ErDiags }); } }); - if (newTableAttrs.length > 1) + if (!hasZeroOne && newTableAttrs.length > 1) { // Ok, really create a new table let newTable = { @@ -236,11 +246,27 @@ class ErDiags name: item.entity + "_" + f.name, isKey: true, type: f.type, - qualifiers: (f.qualifiers || "") + " foreign key references " + item.entity + " not null", - ref: item.entity, + qualifiers: f.qualifiers || "", + ref: item.entity + "(" + f.name + ")", }); }); }); + // Check for duplicates (in case of self-relationship), rename if needed + newTable.fields.forEach( (f,i) => { + const idx = newTable.fields.findIndex( item => { return item.name == f.name; }); + if (idx < i) + { + // Current field is a duplicate + let suffix = 2; + let newName = f.name + suffix; + while (newTable.fields.findIndex( item => { return item.name == newName; }) >= 0) + { + suffix++; + newName = f.name + suffix; + } + f.name = newName; + } + }); // Add relationship potential own attributes (a.attributes || [ ]).forEach( attr => { newTable.fields.push({ @@ -390,7 +416,7 @@ class ErDiags mcdDot += '"' + e.name + '":name -- "' + name + '"'; else mcdDot += '"' + name + '" -- "' + e.name + '":name'; - mcdDot += '[label="' + ErDiags.CARDINAL[e.card] + '"];\n'; + mcdDot += '[label="' + ErDiags.CARDINAL(e.card) + '"];\n'; }); }); mcdDot += '}'; @@ -420,24 +446,15 @@ class ErDiags mldDot += '"' + name + '" [label=<\n'; mldDot += '\n'; this.tables[name].forEach( f => { - let label = (f.isKey ? '' : '') + (!!f.qualifiers && f.qualifiers.indexOf("foreign")>=0 ? '#' : '') + f.name + (f.isKey ? '' : ''); + let label = (f.isKey ? '' : '') + (!!f.ref ? '#' : '') + f.name + (f.isKey ? '' : ''); mldDot += '\n'; if (!!f.ref) { - // Need to find a key attribute in reference entity (the first...) - let keyInRef = ""; - for (let field of this.tables[f.ref]) - { - if (field.isKey) - { - keyInRef = field.name; - break; - } - } + const refPort = f.ref.slice(0,-1).replace('(',':'); if (Math.random() < 0.5) - links += '"' + f.ref + '":"' + keyInRef + '" -- "' + name+'":"'+f.name + '" [dir="forward",arrowhead="dot"'; + links += refPort + ' -- "' + name+'":"'+f.name + '" [dir="forward",arrowhead="dot"'; else - links += '"'+name+'":"'+f.name+'" -- "' + f.ref + '":"' + keyInRef + '" [dir="back",arrowtail="dot"'; + links += '"'+name+'":"'+f.name+'" -- ' + refPort + ' [dir="back",arrowtail="dot"'; links += ']\n;'; } }); @@ -464,13 +481,23 @@ class ErDiags Object.keys(this.tables).forEach( name => { sqlText += "CREATE TABLE " + name + " (\n"; let key = ""; + let foreignKey = [ ]; this.tables[name].forEach( f => { - sqlText += "\t" + f.name + " " + (f.type || "TEXT") + " " + (f.qualifiers || "") + ",\n"; + let type = f.type || (f.isKey ? "INTEGER" : "TEXT"); + if (!!f.ref) + foreignKey.push({name: f.name, ref: f.ref}); + sqlText += "\t" + f.name + " " + type + " " + (f.qualifiers || "") + ",\n"; if (f.isKey) key += (key.length>0 ? "," : "") + f.name; }); - sqlText += "\tPRIMARY KEY (" + key + ")\n"; - sqlText += ");\n"; + sqlText += "\tPRIMARY KEY (" + key + ")"; + foreignKey.forEach( f => { + let refParts = f.ref.split("("); + const table = refParts[0]; + const field = refParts[1].slice(0,-1); //remove last parenthesis + sqlText += ",\n\tFOREIGN KEY (" + f.name + ") REFERENCES " + table + "(" + field + ")"; + }); + sqlText += "\n);\n"; }); //console.log(sqlText); this.sqlText = sqlText;
' + name + '
' + label + '