+ // Pass 1: initialize tables
+ Object.keys(this.entities).forEach( name => {
+ let newTable = [ ]; //array of fields
+ this.entities[name].attributes.forEach( attr => {
+ let newField = {
+ name: attr.name,
+ type: attr.type,
+ isKey: attr.isKey,
+ };
+ 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;
+ });
+ // 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 || "",
+ 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)
+ return;
+ 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: isKey,
+ name: e2.name + "_" + attr.name,
+ type: attr.type,
+ qualifiers: !isKey && e.card[0]=='1' ? "not null" : "",
+ ref: e2.name + "(" + attr.name + ")",
+ });
+ }
+ });
+ });
+ }
+ else
+ {
+ // Add all keys in current entity
+ let fields = this.entities[e.name].attributes.filter( attr => { return attr.isKey; });
+ newTableAttrs.push({
+ fields: fields,
+ entity: e.name,
+ });
+ }
+ });
+ if (!hasZeroOne && newTableAttrs.length > 1)
+ {
+ // Ok, really create a new table
+ let newTable = {
+ name: a.name || newTableAttrs.map( item => { return item.entity; }).join("_"),
+ fields: [ ],
+ };
+ newTableAttrs.forEach( item => {
+ item.fields.forEach( f => {
+ newTable.fields.push({
+ name: item.entity + "_" + f.name,
+ isKey: true,
+ type: f.type,
+ 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({
+ name: attr.name,
+ isKey: false,
+ type: attr.type,
+ qualifiers: attr.qualifiers,
+ });
+ });
+ this.tables[newTable.name] = newTable.fields;
+ }
+ });