Fix doubling tables in n-ary relationships with 0,1 / 1,1
[erdiag.git] / parser.js
index f718f31..6d028fd 100644 (file)
--- a/parser.js
+++ b/parser.js
@@ -89,12 +89,12 @@ class ErDiags
        // attributes: ArrayOf {name, [isKey], [type], [qualifiers]}
        parseAttributes(lines, start, end)
        {
-               let attributes = [];
+               let attributes = [ ];
                for (let i=start; i<end; i++)
                {
                        let field = { };
                        let line = lines[i];
-                       if (line.charAt(0) == '#')
+                       if (line.charAt(0) == '+')
                        {
                                field.isKey = true;
                                line = line.slice(1);
@@ -175,24 +175,40 @@ class ErDiags
                        });
                        this.tables[name] = newTable;
                });
+               // Add foreign keys information for children (inheritance). TODO: allow several levels
+               // NOTE: modelisation assume each child has its own table, refering parent (other options exist)
+               this.inheritances.forEach( inh => {
+                       let idx = this.tables[inh.parent].findIndex( item => { return item.isKey; });
+                       inh.children.forEach( c => {
+                               this.tables[c].push({
+                                       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,
+                               });
+                       });
+               });
                // 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)
                                                        return;
-                                               e2.attributes.forEach( attr => {
+                                               this.entities[e2.name].attributes.forEach( attr => {
                                                        if (attr.isKey)
                                                        {
                                                                this.tables[e.name].push({
                                                                        isKey: e.card.length >= 2 && e.card[1] == 'R', //"weak tables" foreign keys become part of the key
-                                                                       name: "#" + e2.name + "_" + attr.name,
+                                                                       name: e2.name + "_" + attr.name,
                                                                        type: attr.type,
-                                                                       qualifiers: "foreign key references " + e2.name + " " + (e.card[0]=='1' : "not null" : ""),
+                                                                       qualifiers: "foreign key references " + e2.name + " " + (e.card[0]=='1' ? "not null" : ""),
                                                                        ref: e2.name, //easier drawMld function (fewer regexps)
                                                                });
                                                        }
@@ -202,18 +218,18 @@ class ErDiags
                                else
                                {
                                        // Add all keys in current entity
-                                       let fields = e.attributes.filter( attr => { return attr.isKey; });
+                                       let fields = this.entities[e.name].attributes.filter( attr => { return attr.isKey; });
                                        newTableAttrs.push({
                                                fields: fields,
                                                entity: e.name,
                                        });
                                }
-                       }
-                       if (newTableAttrs.length > 1)
+                       });
+                       if (!hasZeroOne && newTableAttrs.length > 1)
                        {
                                // Ok, really create a new table
                                let newTable = {
-                                       name: a.name || newTableAttrs.map( item => { return item.entity; }).join("_");
+                                       name: a.name || newTableAttrs.map( item => { return item.entity; }).join("_"),
                                        fields: [ ],
                                };
                                newTableAttrs.forEach( item => {
@@ -221,14 +237,14 @@ class ErDiags
                                                newTable.fields.push({
                                                        name: item.entity + "_" + f.name,
                                                        isKey: true,
-                                                       type: f.type,,
-                                                       qualifiers: (f.qualifiers+" " || "") + "foreign key references " + item.entity + " not null",
+                                                       type: f.type,
+                                                       qualifiers: (f.qualifiers || "") + " foreign key references " + item.entity + " not null",
                                                        ref: item.entity,
                                                });
                                        });
                                });
                                // Add relationship potential own attributes
-                               a.attributes.forEach( attr => {
+                               (a.attributes || [ ]).forEach( attr => {
                                        newTable.fields.push({
                                                name: attr.name,
                                                isKey: false,
@@ -325,10 +341,10 @@ class ErDiags
                        // https://www.developpez.net/forums/d1088964/general-developpement/alm/modelisation/structure-agregation-l-association-d-association/
                        _.shuffle(i.children).forEach( c => {
                                if (Math.random() < 0.5)
-                                       mcdDot += '"' + c + '":name -- "' + i.parent;
+                                       mcdDot += '"' + c + '":name -- "' + i.parent + '":name [dir="forward",arrowhead="vee",';
                                else
-                                       mcdDot += '"' + i.parent + '":name -- "' + c;
-                               mcdDot += '":name [dir="forward", arrowhead="vee", style="dashed"];\n';
+                                       mcdDot += '"' + i.parent + '":name -- "' + c + '":name [dir="back",arrowtail="vee",';
+                               mcdDot += 'style="dashed"];\n';
                        });
                });
                // Relationships:
@@ -380,7 +396,7 @@ class ErDiags
                        });
                });
                mcdDot += '}';
-               console.log(mcdDot);
+               //console.log(mcdDot);
                ErDiags.AjaxGet(mcdDot, graphSvg => {
                        this.mcdGraph = graphSvg;
                        element.innerHTML = graphSvg;
@@ -399,21 +415,32 @@ class ErDiags
                }
                // Build dot graph input (assuming foreign keys not already present...)
                let mldDot = 'graph {\n';
+               mldDot += 'rankdir="LR";\n';
                mldDot += 'node [shape=plaintext];\n';
                let links = "";
                _.shuffle(Object.keys(this.tables)).forEach( name => {
                        mldDot += '"' + name + '" [label=<<table BORDER="1" ALIGN="LEFT" CELLPADDING="5" CELLSPACING="0">\n';
                        mldDot += '<tr><td BGCOLOR="#ae7d4e" BORDER="0"><font COLOR="#FFFFFF">' + name + '</font></td></tr>\n';
-                       this.tables[name].fields.forEach( f => {
+                       this.tables[name].forEach( f => {
                                let label = (f.isKey ? '<u>' : '') + (!!f.qualifiers && f.qualifiers.indexOf("foreign")>=0 ? '#' : '') + f.name + (f.isKey ? '</u>' : '');
-                               mldDot += '<tr><td port="' + f.name + '"' + (f.isKey ? ' port="__key"' : '')
-                                       + ' BGCOLOR="#FFFFFF" BORDER="0" ALIGN="LEFT"><font COLOR="#000000" >' + label + '</font></td></tr>\n';
+                               mldDot += '<tr><td port="' + f.name + '"' + ' BGCOLOR="#FFFFFF" BORDER="0" ALIGN="LEFT"><font COLOR="#000000" >' + label + '</font></td></tr>\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;
+                                               }
+                                       }
                                        if (Math.random() < 0.5)
-                                               links += '"' + f.ref + '":__key -- "' + '"'+name+'":"'+f.name+'"\n';
+                                               links += '"' + f.ref + '":"' + keyInRef + '" -- "' + name+'":"'+f.name + '" [dir="forward",arrowhead="dot"';
                                        else
-                                               links += '"'+name+'":"'+f.name+'" -- "' + f.ref + '":__key\n';
+                                               links += '"'+name+'":"'+f.name+'" -- "' + f.ref + '":"' + keyInRef + '" [dir="back",arrowtail="dot"';
+                                       links += ']\n;';
                                }
                        });
                        mldDot += '</table>>];\n';
@@ -440,11 +467,11 @@ class ErDiags
                        sqlText += "CREATE TABLE " + name + " (\n";
                        let key = "";
                        this.tables[name].forEach( f => {
-                               sqlText += f.name + " " + (f.type || "TEXT") + (" "+f.qualifiers || "") + ",\n";
+                               sqlText += "\t" + f.name + " " + (f.type || "TEXT") + " " + (f.qualifiers || "") + ",\n";
                                if (f.isKey)
                                        key += (key.length>0 ? "," : "") + f.name;
                        });
-                       sqlText += "PRIMARY KEY (" + key + ")\n";
+                       sqlText += "\tPRIMARY KEY (" + key + ")\n";
                        sqlText += ");\n";
                });
                //console.log(sqlText);