diff --git a/.github/workflows/release_build.yml b/.github/workflows/release_build.yml index 0314f3c..a105de8 100644 --- a/.github/workflows/release_build.yml +++ b/.github/workflows/release_build.yml @@ -3,6 +3,8 @@ on: push: tags: - "*" +permissions: + contents: write jobs: goreleaser: runs-on: ubuntu-latest diff --git a/diagram/diagram.go b/diagram/diagram.go index f04f9bf..a09b34d 100644 --- a/diagram/diagram.go +++ b/diagram/diagram.go @@ -57,7 +57,7 @@ func (d diagram) Create(wr io.Writer, result *database.Result) error { continue } - constraints = append(constraints, getConstraintData(d.config, relationshipLabelMap, constraint)) + constraints = append(constraints, getConstraintData(d.config, relationshipLabelMap, result.Tables, constraint)) } diagramData := ErdDiagramData{ diff --git a/diagram/diagram_data.go b/diagram/diagram_data.go index 6fe72df..a13fefb 100644 --- a/diagram/diagram_data.go +++ b/diagram/diagram_data.go @@ -3,8 +3,10 @@ package diagram type ErdRelationType string const ( - relationOneToOne ErdRelationType = "|o--||" - relationManyToOne ErdRelationType = "}o--||" + relationOneToOne ErdRelationType = "|o--||" + relationOneToMaybeOne ErdRelationType = "|o--o|" + relationManyToOne ErdRelationType = "}o--||" + relationManyToMaybeOne ErdRelationType = "}o--o|" ) type ErdAttributeKey string diff --git a/diagram/diagram_util.go b/diagram/diagram_util.go index d8bc297..d3f92ce 100644 --- a/diagram/diagram_util.go +++ b/diagram/diagram_util.go @@ -10,9 +10,15 @@ import ( "github.com/KarnerTh/mermerd/database" ) -func getRelation(constraint database.ConstraintResult) ErdRelationType { +func getRelation(constraint database.ConstraintResult, isUnique bool, isNullable bool) ErdRelationType { if constraint.IsPrimary && !constraint.HasMultiplePK { return relationOneToOne + } else if isUnique && isNullable { + return relationOneToMaybeOne + } else if isUnique { + return relationOneToOne + } else if isNullable { + return relationManyToMaybeOne } else { return relationManyToOne } @@ -96,7 +102,7 @@ func shouldSkipConstraint(config config.MermerdConfig, tables []ErdTableData, co return !(tableNameInSlice(tables, constraint.PkTable) && tableNameInSlice(tables, constraint.FkTable)) } -func getConstraintData(config config.MermerdConfig, labelMap RelationshipLabelMap, constraint database.ConstraintResult) ErdConstraintData { +func getConstraintData(config config.MermerdConfig, labelMap RelationshipLabelMap, tables []database.TableResult, constraint database.ConstraintResult) ErdConstraintData { pkTableName := getTableName(config, database.TableDetail{Schema: constraint.PkSchema, Name: constraint.PkTable}) fkTableName := getTableName(config, database.TableDetail{Schema: constraint.FkSchema, Name: constraint.FkTable}) @@ -108,10 +114,25 @@ func getConstraintData(config config.MermerdConfig, labelMap RelationshipLabelMa constraintLabel = relationshipLabel.Label } + isUnique := false + isNullable := true + for _, table := range tables { + if table.Table.Name == constraint.FkTable && table.Table.Schema == constraint.FkSchema { + for _, column := range table.Columns { + if column.Name == constraint.ColumnName { + isUnique = column.IsUnique + isNullable = column.IsNullable + goto double_break + } + } + } + } +double_break: + return ErdConstraintData{ PkTableName: pkTableName, FkTableName: fkTableName, - Relation: getRelation(constraint), + Relation: getRelation(constraint, isUnique, isNullable), ConstraintLabel: constraintLabel, } } diff --git a/diagram/diagram_util_test.go b/diagram/diagram_util_test.go index 32834b5..986f772 100644 --- a/diagram/diagram_util_test.go +++ b/diagram/diagram_util_test.go @@ -16,11 +16,26 @@ func TestGetRelation(t *testing.T) { isPrimary bool hasMultiplePK bool expectedRelation ErdRelationType + isUnique bool + isNullable bool }{ - {true, true, relationManyToOne}, - {false, true, relationManyToOne}, - {false, false, relationManyToOne}, - {true, false, relationOneToOne}, + {true, true, relationManyToOne, false, false}, + {false, true, relationManyToOne, false, false}, + {false, false, relationManyToOne, false, false}, + {true, false, relationOneToOne, false, false}, + + {false, false, relationOneToMaybeOne, true, true}, + {false, false, relationOneToOne, true, false}, + {false, false, relationManyToMaybeOne, false, true}, + + {true, false, relationOneToOne, true, false}, + // if PK is true it should not be nullable (thought technically possible in Sqlite) + + {false, true, relationOneToMaybeOne, true, true}, + {false, true, relationOneToOne, true, false}, + {false, true, relationManyToMaybeOne, false, true}, + + {true, true, relationOneToOne, true, false}, } for index, testCase := range testCases { @@ -33,9 +48,11 @@ func TestGetRelation(t *testing.T) { IsPrimary: testCase.isPrimary, HasMultiplePK: testCase.hasMultiplePK, } + isUnique := testCase.isUnique + isNullable := testCase.isNullable // Act - result := getRelation(constraint) + result := getRelation(constraint, isUnique, isNullable) // Assert assert.Equal(t, testCase.expectedRelation, result) @@ -335,9 +352,10 @@ func TestGetConstraintData(t *testing.T) { configMock.On("OmitConstraintLabels").Return(false).Once() configMock.On("ShowSchemaPrefix").Return(false).Twice() constraint := database.ConstraintResult{ColumnName: "Column1"} + tables := []database.TableResult{} // Act - result := getConstraintData(&configMock, &relationshipLabelMap{}, constraint) + result := getConstraintData(&configMock, &relationshipLabelMap{}, tables, constraint) // Assert configMock.AssertExpectations(t) @@ -350,9 +368,10 @@ func TestGetConstraintData(t *testing.T) { configMock.On("ShowSchemaPrefix").Return(false).Twice() constraint := database.ConstraintResult{ColumnName: "Column1"} + tables := []database.TableResult{} // Act - result := getConstraintData(&configMock, &relationshipLabelMap{}, constraint) + result := getConstraintData(&configMock, &relationshipLabelMap{}, tables, constraint) // Assert configMock.AssertExpectations(t) @@ -377,8 +396,24 @@ func TestGetConstraintData(t *testing.T) { ColumnName: "Column1", } + tables := []database.TableResult{ + { + Table: database.TableDetail{ + Name: "pk", + }, + Columns: []database.ColumnResult{ + { + Name: "Column1", + IsUnique: false, + IsNullable: false, + }, + }, + Constraints: database.ConstraintResultList{constraint}, + }, + } + // Act - result := getConstraintData(&configMock, labelsMap, constraint) + result := getConstraintData(&configMock, labelsMap, tables, constraint) // Assert configMock.AssertExpectations(t) @@ -403,8 +438,24 @@ func TestGetConstraintData(t *testing.T) { ColumnName: "Column1", } + tables := []database.TableResult{ + { + Table: database.TableDetail{ + Name: "pk", + }, + Columns: []database.ColumnResult{ + { + Name: "Column1", + IsUnique: false, + IsNullable: false, + }, + }, + Constraints: database.ConstraintResultList{constraint}, + }, + } + // Act - result := getConstraintData(&configMock, labelsMap, constraint) + result := getConstraintData(&configMock, labelsMap, tables, constraint) // Assert configMock.AssertExpectations(t)