Skip to content

Commit b928312

Browse files
authored
Correct cardinality display in Mermaid diagrams (#2379)
* Update * Add coverage for script support * Fix cardinality symbols
1 parent 65099a1 commit b928312

6 files changed

Lines changed: 260 additions & 24 deletions

File tree

schemacrawler-scripting/src/main/java/schemacrawler/tools/command/script/ScriptSupport.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,14 +243,21 @@ public String type(final Table table) {
243243
return MetaDataUtility.getSimpleTypeName(table).toString();
244244
}
245245

246+
/**
247+
* This cardinality symbol syntax is used by Mermaid. When the graph is generated, the foreign key
248+
* is on the left and the primary key is on the right.
249+
*
250+
* @param cardinality Relationship cardinality
251+
* @return Mermaid cardinality symbol from foreign key to primary key
252+
*/
246253
private String cardinalitySymbol(final RelationshipCardinality cardinality) {
247254
return switch (cardinality) {
248-
case zero_one -> "||--o|";
249-
case zero_many -> "||--o{";
255+
case zero_one -> "|o--||";
256+
case zero_many -> "}o--||";
250257
case one_one -> "||--||";
251-
case one_many -> "||--|{";
258+
case one_many -> "}|--||";
252259
case many_many -> "}o--o{"; // bridge table implied
253-
default -> "||--o{";
260+
default -> "}o--||";
254261
};
255262
}
256263

schemacrawler-scripting/src/main/resources/scripts/plantuml.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
pk_col_name = support.cleanName(columnReference.getPrimaryKeyColumn())
9191
fk_col_name = support.cleanName(columnReference.getForeignKeyColumn())
9292
cardinality = support.cardinalitySymbol(fk)
93-
print(f'{pk_schema_slug}.{pk_table_slug}::{pk_col_name} {cardinality} {fk_schema_slug}.{fk_table_slug}::{fk_col_name}', end='')
93+
print(f'{fk_schema_slug}.{fk_table_slug}::{fk_col_name} {cardinality} {pk_schema_slug}.{pk_table_slug}::{pk_col_name}', end='')
9494
if support.hasName(fk):
9595
print(f' : {fk.getName()}', end='')
9696
print()

schemacrawler-scripting/src/main/resources/templates/plantuml.vm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ end note
9595
#set($pkColName = $support.cleanName($columnReference.getPrimaryKeyColumn()))
9696
#set($fkColName = $support.cleanName($columnReference.getForeignKeyColumn()))
9797
#set($cardinality = $support.cardinalitySymbol($fk))
98-
$pkSchemaSlug.$pkTableSlug::$pkColName $cardinality $fkSchemaSlug.$fkTableSlug::$fkColName#if($support.hasName($fk)) : $fk.getName()#end
98+
$fkSchemaSlug.$fkTableSlug::$fkColName $cardinality $pkSchemaSlug.$pkTableSlug::$pkColName#if($support.hasName($fk)) : $fk.getName()#end
9999
#end
100100
#end
101101
#end

schemacrawler-scripting/src/test/java/schemacrawler/test/script/ScriptSupportTest.java

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,38 @@
1010

1111
import static org.hamcrest.CoreMatchers.containsString;
1212
import static org.hamcrest.CoreMatchers.is;
13+
import static org.hamcrest.CoreMatchers.nullValue;
1314
import static org.hamcrest.MatcherAssert.assertThat;
1415
import static org.mockito.Mockito.mock;
16+
import static org.mockito.Mockito.spy;
1517
import static org.mockito.Mockito.when;
18+
import static org.mockito.Mockito.withSettings;
19+
import static schemacrawler.test.utility.crawl.LightCatalogUtility.lightCatalog;
1620
import static schemacrawler.test.utility.crawl.LightCatalogUtility.lightNamedObject;
1721

22+
import java.util.Collection;
1823
import java.util.List;
1924
import org.junit.jupiter.api.Test;
25+
import schemacrawler.ermodel.implementation.ERModelBuilder;
26+
import schemacrawler.ermodel.model.ERModel;
27+
import schemacrawler.ermodel.model.Entity;
28+
import schemacrawler.ermodel.model.Relationship;
29+
import schemacrawler.ermodel.model.RelationshipCardinality;
30+
import schemacrawler.schema.Catalog;
2031
import schemacrawler.schema.Column;
2132
import schemacrawler.schema.ColumnReference;
2233
import schemacrawler.schema.ForeignKey;
2334
import schemacrawler.schema.Index;
2435
import schemacrawler.schema.NamedObject;
36+
import schemacrawler.schema.NamedObjectKey;
37+
import schemacrawler.schema.PartialDatabaseObject;
2538
import schemacrawler.schema.PrimaryKey;
2639
import schemacrawler.schema.Table;
40+
import schemacrawler.schema.TableReference;
41+
import schemacrawler.schema.TableType;
42+
import schemacrawler.schema.View;
2743
import schemacrawler.test.utility.crawl.LightColumn;
44+
import schemacrawler.test.utility.crawl.LightColumnReference;
2845
import schemacrawler.test.utility.crawl.LightForeignKey;
2946
import schemacrawler.test.utility.crawl.LightPrimaryKey;
3047
import schemacrawler.test.utility.crawl.LightTable;
@@ -34,6 +51,110 @@ public class ScriptSupportTest {
3451

3552
private final ScriptSupport support = new ScriptSupport();
3653

54+
@Test
55+
public void cardinality() {
56+
// Null FK → unknown
57+
assertThat(support.cardinality(null), is(RelationshipCardinality.unknown));
58+
59+
// FK whose table is a PartialDatabaseObject → unknown
60+
final Table partialTable =
61+
mock(Table.class, withSettings().extraInterfaces(PartialDatabaseObject.class));
62+
final TableReference fkWithPartialTable = mock(TableReference.class);
63+
when(fkWithPartialTable.getForeignKeyTable()).thenReturn(partialTable);
64+
assertThat(support.cardinality(fkWithPartialTable), is(RelationshipCardinality.unknown));
65+
66+
// Non-unique (no PK/index on FK table) + non-optional
67+
// (LightForeignKey.isOptional() = false)
68+
// → one_many
69+
final LightTable pkTable = new LightTable("pk_table");
70+
final LightTable fkTableOneMay = new LightTable("fk_table_one_many");
71+
final LightColumn fkCol1 = fkTableOneMay.addColumn("fk_col");
72+
final LightColumn pkCol1 = pkTable.addColumn("pk_col1");
73+
final LightForeignKey fkOneMay = new LightForeignKey("FK_ONE_MANY", fkCol1, pkCol1);
74+
assertThat(support.cardinality(fkOneMay), is(RelationshipCardinality.one_many));
75+
76+
// Unique (FK col matches FK-table's PK) + non-optional → one_one
77+
final LightTable fkTableOneOne = new LightTable("fk_table_one_one");
78+
final LightColumn fkCol2 = fkTableOneOne.addColumn("fk_col");
79+
final LightColumn pkCol2 = pkTable.addColumn("pk_col2");
80+
fkTableOneOne.setPrimaryKey(new LightPrimaryKey(fkCol2));
81+
final LightForeignKey fkOneOne = new LightForeignKey("FK_ONE_ONE", fkCol2, pkCol2);
82+
assertThat(support.cardinality(fkOneOne), is(RelationshipCardinality.one_one));
83+
84+
// Non-unique + optional (mocked TableReference) → zero_many
85+
final LightTable fkTableZeroMany = new LightTable("fk_table_zero_many");
86+
final LightColumn fkCol3 = fkTableZeroMany.addColumn("fk_col");
87+
final LightColumn pkCol3 = pkTable.addColumn("pk_col3");
88+
final LightColumnReference colRef3 = new LightColumnReference(fkCol3, pkCol3);
89+
final TableReference optionalFkZeroMany = mock(TableReference.class);
90+
when(optionalFkZeroMany.getForeignKeyTable()).thenReturn(fkTableZeroMany);
91+
when(optionalFkZeroMany.isOptional()).thenReturn(true);
92+
when(optionalFkZeroMany.getColumnReferences()).thenReturn(List.of(colRef3));
93+
when(optionalFkZeroMany.key()).thenReturn(new NamedObjectKey("schema", "FK_ZERO_MANY"));
94+
assertThat(support.cardinality(optionalFkZeroMany), is(RelationshipCardinality.zero_many));
95+
96+
// Unique (FK col matches FK-table's PK) + optional → zero_one
97+
final LightTable fkTableZeroOne = new LightTable("fk_table_zero_one");
98+
final LightColumn fkCol4 = fkTableZeroOne.addColumn("fk_col");
99+
final LightColumn pkCol4 = pkTable.addColumn("pk_col4");
100+
fkTableZeroOne.setPrimaryKey(new LightPrimaryKey(fkCol4));
101+
final LightColumnReference colRef4 = new LightColumnReference(fkCol4, pkCol4);
102+
final TableReference optionalFkZeroOne = mock(TableReference.class);
103+
when(optionalFkZeroOne.getForeignKeyTable()).thenReturn(fkTableZeroOne);
104+
when(optionalFkZeroOne.isOptional()).thenReturn(true);
105+
when(optionalFkZeroOne.getColumnReferences()).thenReturn(List.of(colRef4));
106+
when(optionalFkZeroOne.key()).thenReturn(new NamedObjectKey("schema", "FK_ZERO_ONE"));
107+
assertThat(support.cardinality(optionalFkZeroOne), is(RelationshipCardinality.zero_one));
108+
}
109+
110+
@Test
111+
public void cardinalitySymbolForRelationship() {
112+
// Null relationship falls back to unknown → default Mermaid symbol
113+
assertThat(support.cardinalitySymbol((Relationship) null), is("}o--||"));
114+
115+
final Relationship rel = mock(Relationship.class);
116+
117+
when(rel.getType()).thenReturn(RelationshipCardinality.unknown);
118+
assertThat(support.cardinalitySymbol(rel), is("}o--||"));
119+
120+
when(rel.getType()).thenReturn(RelationshipCardinality.zero_one);
121+
assertThat(support.cardinalitySymbol(rel), is("|o--||"));
122+
123+
when(rel.getType()).thenReturn(RelationshipCardinality.zero_many);
124+
assertThat(support.cardinalitySymbol(rel), is("}o--||"));
125+
126+
when(rel.getType()).thenReturn(RelationshipCardinality.one_one);
127+
assertThat(support.cardinalitySymbol(rel), is("||--||"));
128+
129+
when(rel.getType()).thenReturn(RelationshipCardinality.one_many);
130+
assertThat(support.cardinalitySymbol(rel), is("}|--||"));
131+
132+
when(rel.getType()).thenReturn(RelationshipCardinality.many_many);
133+
assertThat(support.cardinalitySymbol(rel), is("}o--o{"));
134+
}
135+
136+
@Test
137+
public void cardinalitySymbolForTableReference() {
138+
// Null FK → unknown cardinality → default Mermaid symbol
139+
assertThat(support.cardinalitySymbol((TableReference) null), is("}o--||"));
140+
141+
// Non-unique, non-optional FK → one_many
142+
final LightTable pkTableSym = new LightTable("pk_table_sym");
143+
final LightTable fkTableOneMay = new LightTable("fk_table_sym_one_many");
144+
final LightColumn fkCol1 = fkTableOneMay.addColumn("fk_col");
145+
final LightColumn pkCol1 = pkTableSym.addColumn("pk_col1");
146+
final LightForeignKey fkOneMay = new LightForeignKey("FK_SYM_ONE_MANY", fkCol1, pkCol1);
147+
assertThat(support.cardinalitySymbol(fkOneMay), is("}|--||"));
148+
149+
// Unique (FK col = PK), non-optional FK → one_one
150+
final LightTable fkTableOneOne = new LightTable("fk_table_sym_one_one");
151+
final LightColumn fkCol2 = fkTableOneOne.addColumn("fk_col");
152+
final LightColumn pkCol2 = pkTableSym.addColumn("pk_col2");
153+
fkTableOneOne.setPrimaryKey(new LightPrimaryKey(fkCol2));
154+
final LightForeignKey fkOneOne = new LightForeignKey("FK_SYM_ONE_ONE", fkCol2, pkCol2);
155+
assertThat(support.cardinalitySymbol(fkOneOne), is("||--||"));
156+
}
157+
37158
@Test
38159
public void cleanFullName() {
39160
assertThat(support.cleanFullName(null), is(""));
@@ -113,6 +234,36 @@ public void columnType() {
113234
assertThat(support.columnType(varcharColumn), is("VARCHAR"));
114235
}
115236

237+
@Test
238+
public void entities() {
239+
// No ER model set → empty
240+
assertThat(support.entities().isEmpty(), is(true));
241+
242+
// Strong entity table (table with PK) → entity included in result
243+
final LightTable entityTable = new LightTable("ENTITY_TABLE");
244+
final LightColumn pkCol = entityTable.addColumn("ID");
245+
entityTable.setPrimaryKey(new LightPrimaryKey(pkCol));
246+
final Catalog catalogWithEntity = lightCatalog(entityTable);
247+
final ERModel erModelWithEntity = ERModelBuilder.builder(catalogWithEntity).build();
248+
final ScriptSupport supportWithEntity = new ScriptSupport();
249+
supportWithEntity.setERModel(erModelWithEntity);
250+
final Collection<Entity> entities = supportWithEntity.entities();
251+
assertThat(entities.size(), is(1));
252+
assertThat(entities.iterator().next().getTable(), is(entityTable));
253+
254+
// View table → excluded from result
255+
final View mockView = mock(View.class);
256+
when(mockView.key()).thenReturn(new NamedObjectKey("schema", "MOCK_VIEW"));
257+
when(mockView.getImportedForeignKeys()).thenReturn(List.of());
258+
when(mockView.getColumns()).thenReturn(List.of());
259+
when(mockView.getTableType()).thenReturn(new TableType("VIEW"));
260+
final Catalog catalogWithView = lightCatalog(mockView);
261+
final ERModel erModelWithView = ERModelBuilder.builder(catalogWithView).build();
262+
final ScriptSupport supportWithView = new ScriptSupport();
263+
supportWithView.setERModel(erModelWithView);
264+
assertThat(supportWithView.entities().isEmpty(), is(true));
265+
}
266+
116267
@Test
117268
public void foreignKeyColumns() {
118269
assertThat(support.fkColumns(null), is(""));
@@ -154,6 +305,54 @@ public void indent() {
154305
assertThat(support.indent("x", 2), is(" x\n"));
155306
}
156307

308+
@Test
309+
public void isToMany() {
310+
// Null FK → unknown cardinality → not to-many
311+
assertThat(support.isToMany(null), is(false));
312+
313+
final LightTable pkTableToMany = new LightTable("pk_table_to_many");
314+
315+
// Non-unique, non-optional → one_many → to-many
316+
final LightTable fkTableOneMay = new LightTable("fk_table_to_many_one_many");
317+
final LightColumn fkCol1 = fkTableOneMay.addColumn("fk_col");
318+
final LightColumn pkCol1 = pkTableToMany.addColumn("pk_col1");
319+
final LightForeignKey fkOneMay = new LightForeignKey("FK_TO_MANY_ONE_MANY", fkCol1, pkCol1);
320+
assertThat(support.isToMany(fkOneMay), is(true));
321+
322+
// Non-unique, optional → zero_many → to-many
323+
final LightTable fkTableZeroMany = new LightTable("fk_table_to_many_zero_many");
324+
final LightColumn fkCol2 = fkTableZeroMany.addColumn("fk_col");
325+
final LightColumn pkCol2 = pkTableToMany.addColumn("pk_col2");
326+
final LightColumnReference colRef2 = new LightColumnReference(fkCol2, pkCol2);
327+
final TableReference optionalFkZeroMany = mock(TableReference.class);
328+
when(optionalFkZeroMany.getForeignKeyTable()).thenReturn(fkTableZeroMany);
329+
when(optionalFkZeroMany.isOptional()).thenReturn(true);
330+
when(optionalFkZeroMany.getColumnReferences()).thenReturn(List.of(colRef2));
331+
when(optionalFkZeroMany.key()).thenReturn(new NamedObjectKey("schema", "FK_TO_MANY_ZERO_MANY"));
332+
assertThat(support.isToMany(optionalFkZeroMany), is(true));
333+
334+
// Unique (FK col = PK), non-optional → one_one → not to-many
335+
final LightTable fkTableOneOne = new LightTable("fk_table_to_many_one_one");
336+
final LightColumn fkCol3 = fkTableOneOne.addColumn("fk_col");
337+
final LightColumn pkCol3 = pkTableToMany.addColumn("pk_col3");
338+
fkTableOneOne.setPrimaryKey(new LightPrimaryKey(fkCol3));
339+
final LightForeignKey fkOneOne = new LightForeignKey("FK_TO_MANY_ONE_ONE", fkCol3, pkCol3);
340+
assertThat(support.isToMany(fkOneOne), is(false));
341+
342+
// Unique (FK col = PK), optional → zero_one → not to-many
343+
final LightTable fkTableZeroOne = new LightTable("fk_table_to_many_zero_one");
344+
final LightColumn fkCol4 = fkTableZeroOne.addColumn("fk_col");
345+
final LightColumn pkCol4 = pkTableToMany.addColumn("pk_col4");
346+
fkTableZeroOne.setPrimaryKey(new LightPrimaryKey(fkCol4));
347+
final LightColumnReference colRef4 = new LightColumnReference(fkCol4, pkCol4);
348+
final TableReference optionalFkZeroOne = mock(TableReference.class);
349+
when(optionalFkZeroOne.getForeignKeyTable()).thenReturn(fkTableZeroOne);
350+
when(optionalFkZeroOne.isOptional()).thenReturn(true);
351+
when(optionalFkZeroOne.getColumnReferences()).thenReturn(List.of(colRef4));
352+
when(optionalFkZeroOne.key()).thenReturn(new NamedObjectKey("schema", "FK_TO_MANY_ZERO_ONE"));
353+
assertThat(support.isToMany(optionalFkZeroOne), is(false));
354+
}
355+
157356
@Test
158357
public void nonPrimaryIndexes() {
159358
assertThat(support.nonPrimaryIndexes(null).isEmpty(), is(true));
@@ -229,6 +428,36 @@ public void stripName() {
229428
assertThat(support.stripName(namedObject), is("abcxyz"));
230429
}
231430

431+
@Test
432+
public void tableReference() {
433+
// Null column → null (isPartial(null) short-circuits to true)
434+
assertThat(support.tableReference(null), is(nullValue()));
435+
436+
// Partial column → null
437+
final Column partialColumn =
438+
mock(Column.class, withSettings().extraInterfaces(PartialDatabaseObject.class));
439+
assertThat(support.tableReference(partialColumn), is(nullValue()));
440+
441+
// LightColumn (isPartOfForeignKey() = false) → null
442+
final LightTable table = new LightTable("t");
443+
final LightColumn notFkCol = table.addColumn("col");
444+
assertThat(support.tableReference(notFkCol), is(nullValue()));
445+
446+
// Column is part of an FK, and the matching FK column reference is found →
447+
// returns the FK
448+
final LightTable pkTable = new LightTable("pk_table");
449+
final LightColumn pkCol = pkTable.addColumn("pk_col");
450+
final LightTable fkTable = spy(new LightTable("fk_table"));
451+
final Column fkCol = mock(Column.class);
452+
when(fkCol.isPartOfForeignKey()).thenReturn(true);
453+
when(fkCol.getParent()).thenReturn(fkTable);
454+
final LightColumnReference colRef = new LightColumnReference(fkCol, pkCol);
455+
final ForeignKey fk = mock(ForeignKey.class);
456+
when(fk.iterator()).thenAnswer(inv -> List.of(colRef).iterator());
457+
when(fkTable.getImportedForeignKeys()).thenReturn(List.of(fk));
458+
assertThat(support.tableReference(fkCol), is(fk));
459+
}
460+
232461
@Test
233462
public void type() {
234463
// getSimpleTypeName(null) returns "unknown", no NPE

schemacrawler-scripting/src/test/resources/BuiltIn.mermaid.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ erDiagram
8080
}
8181

8282
"PUBLIC.BOOKS.AUTHORS" }o--o{ "PUBLIC.BOOKS.BOOKS" : "via bridge table BOOKAUTHORS"
83-
"PUBLIC.BOOKS.BOOKS" ||--o| "PUBLIC.BOOKS.BOOKS" : "self-reference"
84-
"PUBLIC.BOOKS.CUSTOMERDATA" ||--|{ "PUBLIC.BOOKS.CUSTOMERS" : "foreign key"
83+
"PUBLIC.BOOKS.BOOKS" |o--|| "PUBLIC.BOOKS.BOOKS" : "self-reference"
84+
"PUBLIC.BOOKS.CUSTOMERDATA" }|--|| "PUBLIC.BOOKS.CUSTOMERS" : "foreign key"
8585
"PUBLIC.BOOKS.Celebrity Updates" ||--|| "PUBLIC.BOOKS.Celebrities" : "foreign key"
86-
"PUBLIC.BOOKS.ΒΙΒΛΊΑ" ||--|{ "PUBLIC.BOOKS.PUBLISHERS" : "foreign key"
87-
"PUBLIC.PUBLISHER SALES.SALES" ||--o{ "PUBLIC.BOOKS.BOOKS" : "foreign key"
88-
"PUBLIC.PUBLISHER SALES.SALES" ||--|{ "PUBLIC.PUBLISHER SALES.REGIONS" : "foreign key"
89-
"PUBLIC.PUBLISHER SALES.SALES" ||--o{ "PUBLIC.PUBLISHER SALES.SALESDATA" : "foreign key"
86+
"PUBLIC.BOOKS.ΒΙΒΛΊΑ" }|--|| "PUBLIC.BOOKS.PUBLISHERS" : "foreign key"
87+
"PUBLIC.PUBLISHER SALES.SALES" }o--|| "PUBLIC.BOOKS.BOOKS" : "foreign key"
88+
"PUBLIC.PUBLISHER SALES.SALES" }|--|| "PUBLIC.PUBLISHER SALES.REGIONS" : "foreign key"
89+
"PUBLIC.PUBLISHER SALES.SALES" }o--|| "PUBLIC.PUBLISHER SALES.SALESDATA" : "foreign key"

schemacrawler-scripting/src/test/resources/BuiltIn.plantuml.txt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ entity "<b>$name</b>" as $slug << (V, Aquamarine) view >>
3131
title "FROM TEST: Database Schema Diagram"
3232

3333
legend bottom right
34-
generated by SchemaCrawler 17.10.1
35-
generated on 2026-04-21 02:01:18
34+
generated by SchemaCrawler 17.11.2
35+
generated on 2026-06-09 12:10:49
3636
end legend
3737

3838

@@ -222,16 +222,16 @@ $table("SALES", "sales_751e68cc") {
222222
}
223223

224224

225-
books_208b5502.authors_f521e766::ID ||--|{ books_208b5502.bookauthors_ead84c5d::AUTHORID : Z_FK_AUTHOR
226-
books_208b5502.books_f4a675c8::ID ||--o| books_208b5502.books_f4a675c8::PREVIOUSEDITIONID : FK_PREVIOUSEDITION
227-
books_208b5502.books_f4a675c8::ID ||--o{ publishersales_7f7f6c20.sales_751e68cc::BOOKID : FK_SALES_BOOK
228-
books_208b5502.books_f4a675c8::ID ||--|{ books_208b5502.bookauthors_ead84c5d::BOOKID
229-
books_208b5502.celebrities_db1258df::Id ||--|| books_208b5502.celebrityupdates_f5088e8b::Celebrity Id
230-
books_208b5502.customers_f705d773::ID ||--|{ books_208b5502.customerdata_b0af83c6::CUSTOMERID : FK_CUSTOMER
231-
books_208b5502.publishers_8037d535::ID ||--|{ books_208b5502.βιβλία_3cecb755::ΕΚΔΌΤΗΣ : FK_ΒΙΒΛΊΑ_PUBLISHERS
232-
publishersales_7f7f6c20.regions_dbf65bbf::POSTALCODE ||--|{ publishersales_7f7f6c20.sales_751e68cc::POSTALCODE : FK_SALES_REGIONS
233-
publishersales_7f7f6c20.regions_dbf65bbf::COUNTRY ||--|{ publishersales_7f7f6c20.sales_751e68cc::COUNTRY : FK_SALES_REGIONS
234-
publishersales_7f7f6c20.salesdata_1193fa76::SALESDATAID ||--o{ publishersales_7f7f6c20.sales_751e68cc::SALESDATAID : FK_SALES_SALESDATA
225+
books_208b5502.bookauthors_ead84c5d::AUTHORID }|--|| books_208b5502.authors_f521e766::ID : Z_FK_AUTHOR
226+
books_208b5502.books_f4a675c8::PREVIOUSEDITIONID |o--|| books_208b5502.books_f4a675c8::ID : FK_PREVIOUSEDITION
227+
publishersales_7f7f6c20.sales_751e68cc::BOOKID }o--|| books_208b5502.books_f4a675c8::ID : FK_SALES_BOOK
228+
books_208b5502.bookauthors_ead84c5d::BOOKID }|--|| books_208b5502.books_f4a675c8::ID
229+
books_208b5502.celebrityupdates_f5088e8b::Celebrity Id ||--|| books_208b5502.celebrities_db1258df::Id
230+
books_208b5502.customerdata_b0af83c6::CUSTOMERID }|--|| books_208b5502.customers_f705d773::ID : FK_CUSTOMER
231+
books_208b5502.βιβλία_3cecb755::ΕΚΔΌΤΗΣ }|--|| books_208b5502.publishers_8037d535::ID : FK_ΒΙΒΛΊΑ_PUBLISHERS
232+
publishersales_7f7f6c20.sales_751e68cc::POSTALCODE }|--|| publishersales_7f7f6c20.regions_dbf65bbf::POSTALCODE : FK_SALES_REGIONS
233+
publishersales_7f7f6c20.sales_751e68cc::COUNTRY }|--|| publishersales_7f7f6c20.regions_dbf65bbf::COUNTRY : FK_SALES_REGIONS
234+
publishersales_7f7f6c20.sales_751e68cc::SALESDATAID }o--|| publishersales_7f7f6c20.salesdata_1193fa76::SALESDATAID : FK_SALES_SALESDATA
235235

236236
@enduml
237237

0 commit comments

Comments
 (0)