An entity is defined as follow
[Entity]
- #attr1 (*)
+ +attr1 (*)
attr2 (*)
-with (\*) = optional SQL indications, and # denoting a (part of) a key.
+with (\*) = optional SQL indications, and + denoting a (part of) an identifier.
A relationship is defined in this way
Special cardinalities are also available to indicate relative identification: `?R` and `1R`.
+And, in case of a self-relationship, symbols '>' and '<' can indicate the sense, as in
+
+ {manage}
+ Users *>
+ Users 1<
+
To mark a weak entity, just surround its name by extra-brackets
[[WeakEntity]]
Animal Cat Fish
Planet Mars Venus
-Finally, blocks must be separated by new lines. For a usage example, see example.html (it should render as seen in example\_\*.svg)
+Finally, blocks must be separated by new lines. For a usage example, see example.html (it should render as seen in example\_\*.svg);
+or example2.html for a bigger, more realistic illustration (small social network).
Note that the "drawMcd" method can take a second argument, which indicates the type of graph.
- * "bubble" draws the standard graph, as seen [here](https://en.wikipedia.org/wiki/Entity%E2%80%93relationship_model#/media/File:ER_Diagram_MMORPG.png) for example
+ * "bubble" draws the standard graph, as seen [here](https://en.wikipedia.org/wiki/Entity%E2%80%93relationship_model#/media/File:ER_Diagram_MMORPG.png)
* "compact" (default) use the same box for an entity and its attributes
-----
**TODO** list:
- functional integrity constraints (CIF)
- - inter-relations constraints (or, and, xor...)
+ - inter-relations constraints (or, and, xor, inclusion)
- inheritance with the right symbol (triangle)
- - put online somewhere (user enter graph description and get SVG + SQL)
-*Implementation note:* temporary dependency to [underscore](http://underscorejs.org/); good library but used so far only for its shuffle() method.
+*Implementation note:* temporary dependency to [underscore](http://underscorejs.org/); used only for its shuffle() method.
let er =
new ErDiags(`
[Musician]
- +id (integer)
+ +id integer
name
band
- role (varchar not null)
+ role varchar not null
[Instrument]
+name
- family (varchar not null default "Brass")
+ family varchar not null default "Brass"
[Piano]
type
--- /dev/null
+<h2>MCD graph:</h3>
+<div id="mcd"></div>
+
+<h2>MLD graph:</h2>
+<div id="mld"></div>
+
+<h2>SQL instructions:</h2>
+<div id="sql"></div>
+
+<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
+<script src="parser.js"></script>
+<script>
+ let er =
+ new ErDiags(`
+ [Users]
+ +id
+ name VARCHAR NOT NULL
+ email VARCHAR
+ location VARCHAR
+ birthdate DATE
+ gender CHARACTER
+ avatar BLOB
+
+ [Groups]
+ +id
+ name VARCHAR NOT NULL
+ description TEXT
+
+ [Events]
+ +id
+ name VARCHAR NOT NULL
+ description TEXT
+
+ [Messages]
+ +id
+ date DATE NOT NULL
+ content TEXT NOT NULL
+ receiver INTEGER REFERENCES Users(id) NOT NULL
+
+ [Posts]
+ +id
+ content TEXT NOT NULL
+ date DATE NOT NULL
+ type VARCHAR NOT NULL DEFAULT "Wall"
+ reference INTEGER NOT NULL
+
+ {publish}
+ Posts 1
+ Users *
+
+ {send}
+ Messages 1
+ Users *
+
+ {like}
+ Users *
+ Posts *
+
+ {follow}
+ Users *
+ Users *
+
+ {friend_with}
+ Users *
+ Users *
+
+ {participate}
+ Events *
+ Users *
+ --
+ degree VARCHAR NOT NULL DEFAULT "sure"
+ creator BOOLEAN
+
+ {belong_to}
+ Groups +
+ Users *
+ --
+ creator BOOLEAN
+ `);
+ er.drawMcd("mcd"); //,"bubble"
+ er.drawMld("mld");
+ er.fillSql("sql");
+</script>
--- /dev/null
+<!--?xml version="1.0" encoding="UTF-8" standalone="no"?-->
+
+<!-- Generated by graphviz version 2.40.1 (20161225.0304)
+ -->
+<!-- Title: %3 Pages: 1 -->
+<svg width="352pt" height="246pt" viewBox="0.00 0.00 352.00 246.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 242)">
+<title>%3</title>
+<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-242 348,-242 348,4 -4,4"></polygon>
+<!-- Musician -->
+<g id="node1" class="node">
+<title>Musician</title>
+<polygon fill="#ae7d4e" stroke="transparent" points="122.5,-207.5 122.5,-232.5 194.5,-232.5 194.5,-207.5 122.5,-207.5"></polygon>
+<text text-anchor="start" x="127.5" y="-216.3" font-family="Roboto" font-size="14.00" fill="#ffffff">Musician</text>
+<polygon fill="#ffffff" stroke="transparent" points="122.5,-182.5 122.5,-207.5 194.5,-207.5 194.5,-182.5 122.5,-182.5"></polygon>
+<text text-anchor="start" x="127.5" y="-192.3" font-family="Roboto" text-decoration="underline" font-size="14.00" fill="#000000">id</text>
+<polygon fill="#ffffff" stroke="transparent" points="122.5,-157.5 122.5,-182.5 194.5,-182.5 194.5,-157.5 122.5,-157.5"></polygon>
+<text text-anchor="start" x="127.5" y="-166.3" font-family="Roboto" font-size="14.00" fill="#000000">name</text>
+<polygon fill="#ffffff" stroke="transparent" points="122.5,-132.5 122.5,-157.5 194.5,-157.5 194.5,-132.5 122.5,-132.5"></polygon>
+<text text-anchor="start" x="127.5" y="-141.3" font-family="Roboto" font-size="14.00" fill="#000000">band</text>
+<polygon fill="#ffffff" stroke="transparent" points="122.5,-107.5 122.5,-132.5 194.5,-132.5 194.5,-107.5 122.5,-107.5"></polygon>
+<text text-anchor="start" x="127.5" y="-116.3" font-family="Roboto" font-size="14.00" fill="#000000">role</text>
+<polygon fill="none" stroke="#000000" points="121.5,-107 121.5,-234 195.5,-234 195.5,-107 121.5,-107"></polygon>
+</g>
+<!-- Play -->
+<g id="node5" class="node">
+<title>Play</title>
+<path fill="none" stroke="#d3d3d3" d="M330.5,-149.5C330.5,-149.5 284.5,-149.5 284.5,-149.5 278.5,-149.5 272.5,-143.5 272.5,-137.5 272.5,-137.5 272.5,-123.5 272.5,-123.5 272.5,-117.5 278.5,-111.5 284.5,-111.5 284.5,-111.5 330.5,-111.5 330.5,-111.5 336.5,-111.5 342.5,-117.5 342.5,-123.5 342.5,-123.5 342.5,-137.5 342.5,-137.5 342.5,-143.5 336.5,-149.5 330.5,-149.5"></path>
+<text text-anchor="middle" x="307.5" y="-134.3" font-family="Roboto" font-size="14.00" fill="#000000"><Play></text>
+<text text-anchor="middle" x="307.5" y="-119.3" font-family="Roboto" font-size="14.00" fill="#000000">event</text>
+</g>
+<!-- Musician--Play -->
+<g id="edge4" class="edge">
+<title>Musician:name--Play</title>
+<path fill="none" stroke="#000000" d="M195.7146,-160.5095C219.1602,-154.2154 249.2436,-146.1393 272.2952,-139.9509"></path>
+<text text-anchor="middle" x="241.5" y="-154.3" font-family="Roboto" font-size="14.00" fill="#000000">1,n</text>
+</g>
+<!-- Instrument -->
+<g id="node2" class="node">
+<title>Instrument</title>
+<polygon fill="#ae7d4e" stroke="transparent" points="114.5,-54.5 114.5,-79.5 203.5,-79.5 203.5,-54.5 114.5,-54.5"></polygon>
+<text text-anchor="start" x="119.5" y="-63.3" font-family="Roboto" font-size="14.00" fill="#ffffff">Instrument</text>
+<polygon fill="#ffffff" stroke="transparent" points="114.5,-29.5 114.5,-54.5 203.5,-54.5 203.5,-29.5 114.5,-29.5"></polygon>
+<text text-anchor="start" x="119.5" y="-39.3" font-family="Roboto" text-decoration="underline" font-size="14.00" fill="#000000">name</text>
+<polygon fill="#ffffff" stroke="transparent" points="114.5,-4.5 114.5,-29.5 203.5,-29.5 203.5,-4.5 114.5,-4.5"></polygon>
+<text text-anchor="start" x="119.5" y="-13.3" font-family="Roboto" font-size="14.00" fill="#000000">family</text>
+<polygon fill="none" stroke="#000000" points="113,-4 113,-81 204,-81 204,-4 113,-4"></polygon>
+</g>
+<!-- Guitar -->
+<g id="node4" class="node">
+<title>Guitar</title>
+<polygon fill="#ae7d4e" stroke="transparent" points="280.5,-42.5 280.5,-67.5 335.5,-67.5 335.5,-42.5 280.5,-42.5"></polygon>
+<text text-anchor="start" x="285.5" y="-51.3" font-family="Roboto" font-size="14.00" fill="#ffffff">Guitar</text>
+<polygon fill="#ffffff" stroke="transparent" points="280.5,-17.5 280.5,-42.5 335.5,-42.5 335.5,-17.5 280.5,-17.5"></polygon>
+<text text-anchor="start" x="285.5" y="-26.3" font-family="Roboto" font-size="14.00" fill="#000000">type</text>
+<polygon fill="none" stroke="#000000" points="279,-16.5 279,-68.5 336,-68.5 336,-16.5 279,-16.5"></polygon>
+</g>
+<!-- Instrument--Guitar -->
+<g id="edge2" class="edge">
+<title>Instrument:name--Guitar:name</title>
+<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M214.2241,-42.5C236.1496,-42.5 260.3961,-42.5 278.672,-42.5"></path>
+<polygon fill="#000000" stroke="#000000" points="204.0145,-42.5 214.0146,-38.0001 209.0145,-42.5 214.0145,-42.5001 214.0145,-42.5001 214.0145,-42.5001 209.0145,-42.5 214.0145,-47.0001 204.0145,-42.5 204.0145,-42.5"></polygon>
+</g>
+<!-- Instrument--Play -->
+<g id="edge3" class="edge">
+<title>Instrument:name--Play</title>
+<path fill="none" stroke="#000000" d="M204.0145,-69.3811C226.9506,-82.9272 254.2291,-99.038 275.0199,-111.3171"></path>
+<text text-anchor="middle" x="241.5" y="-100.3" font-family="Roboto" font-size="14.00" fill="#000000">0,n</text>
+</g>
+<!-- Piano -->
+<g id="node3" class="node">
+<title>Piano</title>
+<polygon fill="#ae7d4e" stroke="transparent" points="9,-42.5 9,-67.5 59,-67.5 59,-42.5 9,-42.5"></polygon>
+<text text-anchor="start" x="14" y="-51.3" font-family="Roboto" font-size="14.00" fill="#ffffff">Piano</text>
+<polygon fill="#ffffff" stroke="transparent" points="9,-17.5 9,-42.5 59,-42.5 59,-17.5 9,-17.5"></polygon>
+<text text-anchor="start" x="14" y="-26.3" font-family="Roboto" font-size="14.00" fill="#000000">type</text>
+<polygon fill="none" stroke="#000000" points="8,-16.5 8,-68.5 60,-68.5 60,-16.5 8,-16.5"></polygon>
+</g>
+<!-- Piano--Instrument -->
+<g id="edge1" class="edge">
+<title>Piano:name--Instrument:name</title>
+<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M60.0993,-42.5C72.4972,-42.5 87.8228,-42.5 102.6578,-42.5"></path>
+<polygon fill="#000000" stroke="#000000" points="112.8078,-42.5 102.8078,-47.0001 107.8078,-42.5 102.8078,-42.5001 102.8078,-42.5001 102.8078,-42.5001 107.8078,-42.5 102.8077,-38.0001 112.8078,-42.5 112.8078,-42.5"></polygon>
+</g>
+</g>
+</svg>
--- /dev/null
+<!--?xml version="1.0" encoding="UTF-8" standalone="no"?-->
+
+<!-- Generated by graphviz version 2.40.1 (20161225.0304)
+ -->
+<!-- Title: %3 Pages: 1 -->
+<svg width="495pt" height="272pt" viewBox="0.00 0.00 495.00 272.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 268)">
+<title>%3</title>
+<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-268 491,-268 491,4 -4,4"></polygon>
+<!-- Instrument -->
+<g id="node1" class="node">
+<title>Instrument</title>
+<polygon fill="#ae7d4e" stroke="transparent" points="213.5,-80.5 213.5,-105.5 302.5,-105.5 302.5,-80.5 213.5,-80.5"></polygon>
+<text text-anchor="start" x="218.5" y="-89.3" font-family="Roboto" font-size="14.00" fill="#ffffff">Instrument</text>
+<polygon fill="#ffffff" stroke="transparent" points="213.5,-55.5 213.5,-80.5 302.5,-80.5 302.5,-55.5 213.5,-55.5"></polygon>
+<text text-anchor="start" x="218.5" y="-65.3" font-family="Roboto" text-decoration="underline" font-size="14.00" fill="#000000">name</text>
+<polygon fill="#ffffff" stroke="transparent" points="213.5,-30.5 213.5,-55.5 302.5,-55.5 302.5,-30.5 213.5,-30.5"></polygon>
+<text text-anchor="start" x="218.5" y="-39.3" font-family="Roboto" font-size="14.00" fill="#000000">family</text>
+<polygon fill="none" stroke="#000000" points="212,-30 212,-107 303,-107 303,-30 212,-30"></polygon>
+</g>
+<!-- Guitar -->
+<g id="node2" class="node">
+<title>Guitar</title>
+<polygon fill="#ae7d4e" stroke="transparent" points="356,-157.5 356,-182.5 478,-182.5 478,-157.5 356,-157.5"></polygon>
+<text text-anchor="start" x="394.5" y="-166.3" font-family="Roboto" font-size="14.00" fill="#ffffff">Guitar</text>
+<polygon fill="#ffffff" stroke="transparent" points="356,-132.5 356,-157.5 478,-157.5 478,-132.5 356,-132.5"></polygon>
+<text text-anchor="start" x="361" y="-141.3" font-family="Roboto" font-size="14.00" fill="#000000">type</text>
+<polygon fill="#ffffff" stroke="transparent" points="356,-107.5 356,-132.5 478,-132.5 478,-107.5 356,-107.5"></polygon>
+<text text-anchor="start" x="361" y="-117.3" font-family="Roboto" text-decoration="underline" font-size="14.00" fill="#000000">#Instrument_id</text>
+<polygon fill="none" stroke="#000000" points="355,-107 355,-184 479,-184 479,-107 355,-107"></polygon>
+</g>
+<!-- Instrument--Guitar -->
+<g id="edge1" class="edge">
+<title>Instrument:name--Guitar:Instrument_id</title>
+<path fill="none" stroke="#000000" d="M302.5,-67.5C332.5502,-67.5 324.8404,-110.207 347.8106,-118.2146"></path>
+<ellipse fill="#000000" stroke="#000000" cx="352.0484" cy="-118.8797" rx="4" ry="4"></ellipse>
+</g>
+<!-- Piano -->
+<g id="node3" class="node">
+<title>Piano</title>
+<polygon fill="#ae7d4e" stroke="transparent" points="356,-54.5 356,-79.5 478,-79.5 478,-54.5 356,-54.5"></polygon>
+<text text-anchor="start" x="397" y="-63.3" font-family="Roboto" font-size="14.00" fill="#ffffff">Piano</text>
+<polygon fill="#ffffff" stroke="transparent" points="356,-29.5 356,-54.5 478,-54.5 478,-29.5 356,-29.5"></polygon>
+<text text-anchor="start" x="361" y="-38.3" font-family="Roboto" font-size="14.00" fill="#000000">type</text>
+<polygon fill="#ffffff" stroke="transparent" points="356,-4.5 356,-29.5 478,-29.5 478,-4.5 356,-4.5"></polygon>
+<text text-anchor="start" x="361" y="-14.3" font-family="Roboto" text-decoration="underline" font-size="14.00" fill="#000000">#Instrument_id</text>
+<polygon fill="none" stroke="#000000" points="355,-4 355,-81 479,-81 479,-4 355,-4"></polygon>
+</g>
+<!-- Instrument--Piano -->
+<g id="edge2" class="edge">
+<title>Instrument:name--Piano:Instrument_id</title>
+<path fill="none" stroke="#000000" d="M302.5,-67.5C332.2708,-67.5 325.0412,-25.6143 347.8744,-17.7607"></path>
+<ellipse fill="#000000" stroke="#000000" cx="352.0473" cy="-17.1133" rx="4" ry="4"></ellipse>
+</g>
+<!-- Musician -->
+<g id="node4" class="node">
+<title>Musician</title>
+<polygon fill="#ae7d4e" stroke="transparent" points="221.5,-233.5 221.5,-258.5 293.5,-258.5 293.5,-233.5 221.5,-233.5"></polygon>
+<text text-anchor="start" x="226.5" y="-242.3" font-family="Roboto" font-size="14.00" fill="#ffffff">Musician</text>
+<polygon fill="#ffffff" stroke="transparent" points="221.5,-208.5 221.5,-233.5 293.5,-233.5 293.5,-208.5 221.5,-208.5"></polygon>
+<text text-anchor="start" x="226.5" y="-218.3" font-family="Roboto" text-decoration="underline" font-size="14.00" fill="#000000">id</text>
+<polygon fill="#ffffff" stroke="transparent" points="221.5,-183.5 221.5,-208.5 293.5,-208.5 293.5,-183.5 221.5,-183.5"></polygon>
+<text text-anchor="start" x="226.5" y="-192.3" font-family="Roboto" font-size="14.00" fill="#000000">name</text>
+<polygon fill="#ffffff" stroke="transparent" points="221.5,-158.5 221.5,-183.5 293.5,-183.5 293.5,-158.5 221.5,-158.5"></polygon>
+<text text-anchor="start" x="226.5" y="-167.3" font-family="Roboto" font-size="14.00" fill="#000000">band</text>
+<polygon fill="#ffffff" stroke="transparent" points="221.5,-133.5 221.5,-158.5 293.5,-158.5 293.5,-133.5 221.5,-133.5"></polygon>
+<text text-anchor="start" x="226.5" y="-142.3" font-family="Roboto" font-size="14.00" fill="#000000">role</text>
+<polygon fill="none" stroke="#000000" points="220.5,-133 220.5,-260 294.5,-260 294.5,-133 220.5,-133"></polygon>
+</g>
+<!-- Play -->
+<g id="node5" class="node">
+<title>Play</title>
+<polygon fill="#ae7d4e" stroke="transparent" points="9,-169.5 9,-194.5 159,-194.5 159,-169.5 9,-169.5"></polygon>
+<text text-anchor="start" x="69" y="-178.3" font-family="Roboto" font-size="14.00" fill="#ffffff">Play</text>
+<polygon fill="#ffffff" stroke="transparent" points="9,-144.5 9,-169.5 159,-169.5 159,-144.5 9,-144.5"></polygon>
+<text text-anchor="start" x="14" y="-154.3" font-family="Roboto" text-decoration="underline" font-size="14.00" fill="#000000">#Musician_id</text>
+<polygon fill="#ffffff" stroke="transparent" points="9,-119.5 9,-144.5 159,-144.5 159,-119.5 9,-119.5"></polygon>
+<text text-anchor="start" x="14" y="-129.3" font-family="Roboto" text-decoration="underline" font-size="14.00" fill="#000000">#Instrument_name</text>
+<polygon fill="#ffffff" stroke="transparent" points="9,-94.5 9,-119.5 159,-119.5 159,-94.5 9,-94.5"></polygon>
+<text text-anchor="start" x="14" y="-103.3" font-family="Roboto" font-size="14.00" fill="#000000">event</text>
+<polygon fill="none" stroke="#000000" points="8,-93.5 8,-195.5 160,-195.5 160,-93.5 8,-93.5"></polygon>
+</g>
+<!-- Play--Instrument -->
+<g id="edge4" class="edge">
+<title>Play:Instrument_name--Instrument:name</title>
+<path fill="none" stroke="#000000" d="M167.0953,-130.2787C193.4304,-121.4307 179.2043,-67.5 213.5,-67.5"></path>
+<ellipse fill="#000000" stroke="#000000" cx="162.9552" cy="-130.9032" rx="4" ry="4"></ellipse>
+</g>
+<!-- Play--Musician -->
+<g id="edge3" class="edge">
+<title>Play:Musician_id--Musician:id</title>
+<path fill="none" stroke="#000000" d="M166.9606,-158.5053C196.5449,-166.6475 184.6929,-221.5 221.5,-221.5"></path>
+<ellipse fill="#000000" stroke="#000000" cx="162.9685" cy="-158.0012" rx="4" ry="4"></ellipse>
+</g>
+</g>
+</svg>
--- /dev/null
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <meta charset="utf-8"/>
+ <title>erdiag tool</title>
+ </head>
+
+ <body>
+ <h2>Graph description</h2>
+ <button onClick="processGraphDesc()">Send</button>
+ <div>
+ <span>MCD graph type:</span>
+ <input type="radio" name="mcd" value="compact" checked/> compact
+ <input type="radio" name="mcd" value="bubble"/> bubble
+ </div>
+ <div>
+ <span>Output type:</span>
+ <input type="radio" name="output" value="graph" checked/> drawn graph
+ <input type="radio" name="output" value="text"/> graphviz input
+ </div>
+ <div>
+ <span>Image type:</span>
+ <input type="radio" name="image" value="svg" checked/> SVG
+ <input type="radio" name="image" value="png"/> PNG
+ </div>
+
+ <textarea id="graphDesc" rows="15" style="width:100%"></textarea>
+ <div id="result" style="display:none">
+ <h2>MCD graph:</h3>
+ <div id="mcd"></div>
+ <h2>MLD graph:</h2>
+ <div id="mld"></div>
+ <h2>SQL instructions:</h2>
+ <div id="sql"></div>
+ </div>
+
+ <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
+ <script src="parser.js"></script>
+ <script>
+ const result = document.getElementById("result");
+ function getRadioValue(name) {
+ for (let el of document.getElementsByName(name))
+ {
+ if (el.checked)
+ return el.value;
+ }
+ }
+ function processGraphDesc() {
+ const graphDesc = document.getElementById("graphDesc").value;
+ const mcdType = getRadioValue("mcd");
+ const outputType = getRadioValue("output");
+ const imageType = getRadioValue("image");
+ const er = new ErDiags(graphDesc, outputType, imageType);
+ er.drawMcd("mcd", mcdType);
+ er.drawMld("mld");
+ er.fillSql("sql");
+ result.style.display = "block";
+ //document.location.href = "#result"; //TODO: not working (auto-scroll)
+ }
+ </script>
+ </body>
// ER diagram description parser
class ErDiags
{
- constructor(description)
+ constructor(description, output, image)
{
this.entities = { };
this.inheritances = [ ];
this.tables = { };
this.mcdParsing(description);
this.mldParsing();
- // Cache SVG graphs returned by server (in addition to server cache = good perfs)
- this.mcdGraph = "";
- this.mldGraph = "";
- this.sqlText = "";
+ this.output = output || "compact";
+ this.image = image || "svg";
}
- 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;
}
///////////////////////////////
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);
}
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]});
+ newField.qualifiers = attr.qualifiers.replace(/references [^\s]+/i, "");
+ }
+ newTable.push(newField);
});
this.tables[name] = newTable;
});
name: inh.parent + "_id",
type: this.tables[inh.parent][idx].type,
isKey: true,
+<<<<<<< HEAD
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 + ")",
+>>>>>>> 40b4a9d230d105a61e22bef0a63a6e8d515524e9
});
});
});
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 + ")",
});
}
});
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({
// DRAWING + GET SQL FROM PARSING
/////////////////////////////////
- static AjaxGet(dotInput, callback)
- {
- let xhr = new XMLHttpRequest();
- xhr.onreadystatechange = function() {
- if (this.readyState == 4 && this.status == 200)
- callback(this.responseText);
- };
- xhr.open("GET", "scripts/getGraphSvg.php?dot=" + encodeURIComponent(dotInput), true);
- xhr.send();
- }
-
// "Modèle conceptuel des données". TODO: option for graph size
// NOTE: randomizing helps to obtain better graphs (sometimes)
drawMcd(id, mcdStyle) //mcdStyle: bubble, or compact
{
let element = document.getElementById(id);
mcdStyle = mcdStyle || "compact";
- if (this.mcdGraph.length > 0)
- {
- element.innerHTML = this.mcdGraph;
- return;
- }
// Build dot graph input
let mcdDot = 'graph {\n';
mcdDot += 'rankdir="LR";\n';
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 += '}';
- //console.log(mcdDot);
- ErDiags.AjaxGet(mcdDot, graphSvg => {
- this.mcdGraph = graphSvg;
- element.innerHTML = graphSvg;
- });
+ if (this.output == "graph") //draw graph in element
+ element.innerHTML = "<img src='scripts/getGraph_" + this.image + ".php?dot=" + encodeURIComponent(mcdDot) + "'/>";
+ else //just show dot input
+ element.innerHTML = mcdDot.replace(/</g,"<").replace(/>/g,">");
}
// "Modèle logique des données", from MCD without anomalies
- // TODO: this one should draw links from foreign keys to keys (port=... in <TD>)
drawMld(id)
{
let element = document.getElementById(id);
- if (this.mldGraph.length > 0)
- {
- element.innerHTML = this.mcdGraph;
- return;
- }
// Build dot graph input (assuming foreign keys not already present...)
let mldDot = 'graph {\n';
mldDot += 'rankdir="LR";\n';
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].forEach( f => {
- let label = (f.isKey ? '<u>' : '') + (!!f.qualifiers && f.qualifiers.indexOf("foreign")>=0 ? '#' : '') + f.name + (f.isKey ? '</u>' : '');
+ let label = (f.isKey ? '<u>' : '') + (!!f.ref ? '#' : '') + f.name + (f.isKey ? '</u>' : '');
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;
- }
- }
+ 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;';
}
});
mldDot += '</table>>];\n';
});
mldDot += links + '\n';
- mldDot += '}\n';
- //console.log(mldDot);
- ErDiags.AjaxGet(mldDot, graphSvg => {
- this.mldGraph = graphSvg;
- element.innerHTML = graphSvg;
- });
+ mldDot += '}';
+ if (this.output == "graph")
+ element.innerHTML = "<img src='scripts/getGraph_" + this.image + ".php?dot=" + encodeURIComponent(mldDot) + "'/>";
+ else
+ element.innerHTML = mldDot.replace(/</g,"<").replace(/>/g,">");
}
fillSql(id)
{
let element = document.getElementById(id);
- if (this.sqlText.length > 0)
+ if (!!this.sqlText)
{
element.innerHTML = this.sqlText;
return;
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;
element.innerHTML = "<pre><code>" + sqlText + "</code></pre>";
}
+++ /dev/null
-<?php
-
-$dotInput = $_GET["dot"];
-
-// Call dot program on $dotInput, output as svg [TODO: offer more options]
-passthru("echo '" . $dotInput . "' | dot -Tsvg -Nfontname=Roboto -Nfontsize=14 -Efontname=Roboto -Efontsize=14");
-
-?>
--- /dev/null
+<?php
+header('Content-Type: image/png');
+$dotInput = $_GET["dot"];
+passthru("printf '$dotInput' | dot -Tpng -Nfontname=Roboto -Nfontsize=14 -Efontname=Roboto -Efontsize=14");
--- /dev/null
+<?php
+header('Content-Type: image/svg+xml');
+$dotInput = $_GET["dot"];
+passthru("printf '$dotInput' | dot -Tsvg -Nfontname=Roboto -Nfontsize=14 -Efontname=Roboto -Efontsize=14");