Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unique constraint to key column #53

Merged
merged 6 commits into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func init() {
rootCmd.Flags().Bool(config.UseAllSchemasKey, false, "use all available schemas")
rootCmd.Flags().Bool(config.DebugKey, false, "show debug logs")
rootCmd.Flags().Bool(config.OmitConstraintLabelsKey, false, "omit the constraint labels")
rootCmd.Flags().Bool(config.OmitAttributeKeysKey, false, "omit the attribute keys (PK, FK)")
rootCmd.Flags().Bool(config.OmitAttributeKeysKey, false, "omit the attribute keys (PK, FK, UK)")
rootCmd.Flags().Bool(config.ShowSchemaPrefix, false, "show schema prefix in table name")
rootCmd.Flags().BoolP(config.EncloseWithMermaidBackticksKey, "e", false, "enclose output with mermaid backticks (needed for e.g. in markdown viewer)")
rootCmd.Flags().StringP(config.ConnectionStringKey, "c", "", "connection string that should be used")
Expand Down
34 changes: 18 additions & 16 deletions database/database_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type columnTestResult struct {
Name string
isPrimary bool
isForeign bool
isUnique bool
isNullable bool
}

Expand Down Expand Up @@ -136,34 +137,34 @@ func TestDatabaseIntegrations(t *testing.T) {
expectedColumns []columnTestResult
}{
{tableName: "article", expectedColumns: []columnTestResult{
{Name: "id", isPrimary: true, isForeign: false, isNullable: false},
{Name: "title", isPrimary: false, isForeign: false, isNullable: false},
{Name: "subtitle", isPrimary: false, isForeign: false, isNullable: true},
{Name: "id", isPrimary: true, isForeign: false, isUnique: false, isNullable: false},
{Name: "title", isPrimary: false, isForeign: false, isUnique: false, isNullable: false},
{Name: "subtitle", isPrimary: false, isForeign: false, isUnique: false, isNullable: true},
}},
{tableName: "article_detail", expectedColumns: []columnTestResult{
{Name: "id", isPrimary: true, isForeign: true, isNullable: false},
{Name: "created_at", isPrimary: false, isForeign: false, isNullable: false},
{Name: "id", isPrimary: true, isForeign: true, isUnique: false, isNullable: false},
{Name: "created_at", isPrimary: false, isForeign: false, isUnique: false, isNullable: false},
}},
{tableName: "article_comment", expectedColumns: []columnTestResult{
{Name: "id", isPrimary: true, isForeign: false, isNullable: false},
{Name: "article_id", isPrimary: false, isForeign: true, isNullable: false},
{Name: "comment", isPrimary: false, isForeign: false, isNullable: false},
{Name: "id", isPrimary: true, isForeign: false, isUnique: false, isNullable: false},
{Name: "article_id", isPrimary: false, isForeign: true, isUnique: false, isNullable: false},
{Name: "comment", isPrimary: false, isForeign: false, isUnique: false, isNullable: false},
}},
{tableName: "label", expectedColumns: []columnTestResult{
{Name: "id", isPrimary: true, isForeign: false, isNullable: false},
{Name: "label", isPrimary: false, isForeign: false, isNullable: false},
{Name: "id", isPrimary: true, isForeign: false, isUnique: false, isNullable: false},
{Name: "label", isPrimary: false, isForeign: false, isUnique: true, isNullable: false},
}},
{tableName: "article_label", expectedColumns: []columnTestResult{
{Name: "article_id", isPrimary: true, isForeign: true, isNullable: false},
{Name: "label_id", isPrimary: true, isForeign: true, isNullable: false},
{Name: "article_id", isPrimary: true, isForeign: true, isUnique: false, isNullable: false},
{Name: "label_id", isPrimary: true, isForeign: true, isUnique: false, isNullable: false},
}},
{tableName: "test_1_a", expectedColumns: []columnTestResult{
{Name: "id", isPrimary: true, isForeign: false, isNullable: false},
{Name: "xid", isPrimary: true, isForeign: false, isNullable: false},
{Name: "id", isPrimary: true, isForeign: false, isUnique: false, isNullable: false},
{Name: "xid", isPrimary: true, isForeign: false, isUnique: false, isNullable: false},
}},
{tableName: "test_1_b", expectedColumns: []columnTestResult{
{Name: "aid", isPrimary: true, isForeign: true, isNullable: false},
{Name: "bid", isPrimary: true, isForeign: true, isNullable: false},
{Name: "aid", isPrimary: true, isForeign: true, isUnique: false, isNullable: false},
{Name: "bid", isPrimary: true, isForeign: true, isUnique: false, isNullable: false},
}},
}

Expand All @@ -182,6 +183,7 @@ func TestDatabaseIntegrations(t *testing.T) {
Name: column.Name,
isPrimary: column.IsPrimary,
isForeign: column.IsForeign,
isUnique: column.IsUnique,
isNullable: column.IsNullable,
})
}
Expand Down
8 changes: 7 additions & 1 deletion database/mssql.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ func (c *mssqlConnector) GetColumns(tableName TableDetail) ([]ColumnResult, erro
where cu.column_name = c.column_name
and cu.table_name = c.table_name
and tc.constraint_type = 'FOREIGN KEY') as is_foreign,
(select IIF(count(*) > 0, 1, 0)
from information_schema.key_column_usage cu
left join information_schema.table_constraints tc on tc.constraint_name = cu.constraint_name
where cu.column_name = c.column_name
and cu.table_name = c.table_name
and tc.constraint_type = 'UNIQUE') as is_unique,
case when c.is_nullable = 'YES' then 1 else 0 end as is_nullable,
(select ISNULL(ep.value, '') from sys.tables t
inner join sys.columns col on col.object_id = t.object_id and col.name = c.column_name
Expand All @@ -118,7 +124,7 @@ func (c *mssqlConnector) GetColumns(tableName TableDetail) ([]ColumnResult, erro
var columns []ColumnResult
for rows.Next() {
var column ColumnResult
if err = rows.Scan(&column.Name, &column.DataType, &column.IsPrimary, &column.IsForeign, &column.IsNullable, &column.Comment); err != nil {
if err = rows.Scan(&column.Name, &column.DataType, &column.IsPrimary, &column.IsForeign, &column.IsUnique, &column.IsNullable, &column.Comment); err != nil {
return nil, err
}

Expand Down
8 changes: 7 additions & 1 deletion database/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ func (c *mySqlConnector) GetColumns(tableName TableDetail) ([]ColumnResult, erro
where cu.column_name = c.column_name
and cu.table_name = c.table_name
and tc.constraint_type = 'FOREIGN KEY') as is_foreign,
(select count(*) > 0
from information_schema.key_column_usage cu
left join information_schema.table_constraints tc on tc.constraint_name = cu.constraint_name
where cu.column_name = c.column_name
and cu.table_name = c.table_name
and tc.constraint_type = 'UNIQUE') as is_unique,
IF(c.is_nullable = 'YES', 1, 0) as is_nullable,
case when c.data_type = 'enum' then REPLACE(REPLACE(REPLACE(REPLACE(c.column_type, 'enum', ''), '\'', ''), '(', ''), ')', '') else '' end as enum_values,
c.column_comment as comment
Expand All @@ -113,7 +119,7 @@ func (c *mySqlConnector) GetColumns(tableName TableDetail) ([]ColumnResult, erro
var columns []ColumnResult
for rows.Next() {
var column ColumnResult
if err = rows.Scan(&column.Name, &column.DataType, &column.IsPrimary, &column.IsForeign, &column.IsNullable, &column.EnumValues, &column.Comment); err != nil {
if err = rows.Scan(&column.Name, &column.DataType, &column.IsPrimary, &column.IsForeign, &column.IsUnique, &column.IsNullable, &column.EnumValues, &column.Comment); err != nil {
return nil, err
}

Expand Down
8 changes: 7 additions & 1 deletion database/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ func (c *postgresConnector) GetColumns(tableName TableDetail) ([]ColumnResult, e
where cu.column_name = c.column_name
and cu.table_name = c.table_name
and tc.constraint_type = 'FOREIGN KEY') as is_foreign,
(select count(*) > 0
from information_schema.key_column_usage cu
left join information_schema.table_constraints tc on tc.constraint_name = cu.constraint_name
where cu.column_name = c.column_name
and cu.table_name = c.table_name
and tc.constraint_type = 'UNIQUE') as is_unique,
bool_or(c.is_nullable = 'YES') as is_not_null,
coalesce(string_agg(enumlabel, ',' order by enumsortorder), '') as enum_values,
coalesce(pd.description, '') as comment
Expand All @@ -126,7 +132,7 @@ func (c *postgresConnector) GetColumns(tableName TableDetail) ([]ColumnResult, e
var columns []ColumnResult
for rows.Next() {
var column ColumnResult
if err = rows.Scan(&column.Name, &column.DataType, &column.IsPrimary, &column.IsForeign, &column.IsNullable, &column.EnumValues, &column.Comment); err != nil {
if err = rows.Scan(&column.Name, &column.DataType, &column.IsPrimary, &column.IsForeign, &column.IsUnique, &column.IsNullable, &column.EnumValues, &column.Comment); err != nil {
return nil, err
}

Expand Down
1 change: 1 addition & 0 deletions database/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type ColumnResult struct {
DataType string
IsPrimary bool
IsForeign bool
IsUnique bool
IsNullable bool
EnumValues string
Comment string
Expand Down
10 changes: 5 additions & 5 deletions diagram/diagram_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type ErdAttributeKey string
const (
primaryKey ErdAttributeKey = "PK"
foreignKey ErdAttributeKey = "FK"
none ErdAttributeKey = ""
uniqueKey ErdAttributeKey = "UK"
)

type ErdDiagramData struct {
Expand All @@ -27,10 +27,10 @@ type ErdTableData struct {
}

type ErdColumnData struct {
Name string
DataType string
Description string
AttributeKey ErdAttributeKey
Name string
DataType string
Description string
AttributeKeys []ErdAttributeKey
}

type ErdConstraintData struct {
Expand Down
25 changes: 15 additions & 10 deletions diagram/diagram_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,34 @@ func tableNameInSlice(slice []ErdTableData, tableName string) bool {
return false
}

func getAttributeKey(column database.ColumnResult) ErdAttributeKey {
func getAttributeKeys(column database.ColumnResult) []ErdAttributeKey {
var attributeKeys []ErdAttributeKey
if column.IsPrimary {
return primaryKey
attributeKeys = append(attributeKeys, primaryKey)
}

if column.IsForeign {
return foreignKey
attributeKeys = append(attributeKeys, foreignKey)
}

return none
if column.IsUnique {
attributeKeys = append(attributeKeys, uniqueKey)
}

return attributeKeys
}

func getColumnData(config config.MermerdConfig, column database.ColumnResult) ErdColumnData {
attributeKey := getAttributeKey(column)
attributeKeys := getAttributeKeys(column)
if config.OmitAttributeKeys() {
attributeKey = none
attributeKeys = []ErdAttributeKey(nil)
}

return ErdColumnData{
Name: column.Name,
DataType: column.DataType,
Description: getDescription(config.ShowDescriptions(), column),
AttributeKey: attributeKey,
Name: column.Name,
DataType: column.DataType,
Description: getDescription(config.ShowDescriptions(), column),
AttributeKeys: attributeKeys,
}
}

Expand Down
68 changes: 56 additions & 12 deletions diagram/diagram_util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,43 +45,87 @@ func TestGetRelation(t *testing.T) {
func TestGetAttributeKey(t *testing.T) {
testCases := []struct {
column database.ColumnResult
expectedAttributeResult ErdAttributeKey
expectedAttributeResult []ErdAttributeKey
}{
{
column: database.ColumnResult{
Name: "",
DataType: "",
IsPrimary: true,
IsForeign: false,
IsUnique: false,
},
expectedAttributeResult: primaryKey,
expectedAttributeResult: []ErdAttributeKey{primaryKey},
},
{
column: database.ColumnResult{
Name: "",
DataType: "",
IsPrimary: false,
IsForeign: true,
IsUnique: false,
},
expectedAttributeResult: foreignKey,
expectedAttributeResult: []ErdAttributeKey{foreignKey},
},
{
column: database.ColumnResult{
Name: "",
DataType: "",
IsPrimary: true,
IsForeign: true,
IsUnique: false,
},
expectedAttributeResult: primaryKey,
expectedAttributeResult: []ErdAttributeKey{primaryKey, foreignKey},
},
{
column: database.ColumnResult{
Name: "",
DataType: "",
IsPrimary: false,
IsForeign: false,
IsUnique: true,
},
expectedAttributeResult: none,
expectedAttributeResult: []ErdAttributeKey{uniqueKey},
},
{
column: database.ColumnResult{
Name: "",
DataType: "",
IsPrimary: true,
IsForeign: false,
IsUnique: true,
},
expectedAttributeResult: []ErdAttributeKey{primaryKey, uniqueKey},
},
{
column: database.ColumnResult{
Name: "",
DataType: "",
IsPrimary: false,
IsForeign: true,
IsUnique: true,
},
expectedAttributeResult: []ErdAttributeKey{foreignKey, uniqueKey},
},
{
column: database.ColumnResult{
Name: "",
DataType: "",
IsPrimary: true,
IsForeign: true,
IsUnique: true,
},
expectedAttributeResult: []ErdAttributeKey{primaryKey, foreignKey, uniqueKey},
},
{
column: database.ColumnResult{
Name: "",
DataType: "",
IsPrimary: false,
IsForeign: false,
IsUnique: false,
},
expectedAttributeResult: []ErdAttributeKey(nil),
},
}

Expand All @@ -91,7 +135,7 @@ func TestGetAttributeKey(t *testing.T) {
column := testCase.column

// Act
result := getAttributeKey(column)
result := getAttributeKeys(column)

// Assert
assert.Equal(t, testCase.expectedAttributeResult, result)
Expand Down Expand Up @@ -151,7 +195,7 @@ func TestGetColumnData(t *testing.T) {
configMock.AssertExpectations(t)
assert.Equal(t, columnName, result.Name)
assert.Equal(t, "<"+enumValues+"> "+expectedComment, result.Description)
assert.Equal(t, primaryKey, result.AttributeKey)
assert.Equal(t, []ErdAttributeKey{primaryKey}, result.AttributeKeys)
})

t.Run("Get all fields with enum values", func(t *testing.T) {
Expand All @@ -167,7 +211,7 @@ func TestGetColumnData(t *testing.T) {
configMock.AssertExpectations(t)
assert.Equal(t, columnName, result.Name)
assert.Equal(t, "<"+enumValues+">", result.Description)
assert.Equal(t, primaryKey, result.AttributeKey)
assert.Equal(t, []ErdAttributeKey{primaryKey}, result.AttributeKeys)
})

t.Run("Get all fields with column comments", func(t *testing.T) {
Expand All @@ -183,7 +227,7 @@ func TestGetColumnData(t *testing.T) {
configMock.AssertExpectations(t)
assert.Equal(t, columnName, result.Name)
assert.Equal(t, expectedComment, result.Description)
assert.Equal(t, primaryKey, result.AttributeKey)
assert.Equal(t, []ErdAttributeKey{primaryKey}, result.AttributeKeys)
})

t.Run("Get all fields except description", func(t *testing.T) {
Expand All @@ -199,7 +243,7 @@ func TestGetColumnData(t *testing.T) {
configMock.AssertExpectations(t)
assert.Equal(t, columnName, result.Name)
assert.Equal(t, "", result.Description)
assert.Equal(t, primaryKey, result.AttributeKey)
assert.Equal(t, []ErdAttributeKey{primaryKey}, result.AttributeKeys)
})

t.Run("Get all fields except attribute key", func(t *testing.T) {
Expand All @@ -215,7 +259,7 @@ func TestGetColumnData(t *testing.T) {
configMock.AssertExpectations(t)
assert.Equal(t, columnName, result.Name)
assert.Equal(t, "<"+enumValues+"> "+expectedComment, result.Description)
assert.Equal(t, none, result.AttributeKey)
assert.Equal(t, []ErdAttributeKey(nil), result.AttributeKeys)
})

t.Run("Get only minimal fields", func(t *testing.T) {
Expand All @@ -231,7 +275,7 @@ func TestGetColumnData(t *testing.T) {
configMock.AssertExpectations(t)
assert.Equal(t, columnName, result.Name)
assert.Equal(t, "", result.Description)
assert.Equal(t, none, result.AttributeKey)
assert.Equal(t, []ErdAttributeKey(nil), result.AttributeKeys)
})
}

Expand Down
7 changes: 6 additions & 1 deletion diagram/erd_template.gommd
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ erDiagram
{{- range .Tables}}
{{.Name}} {
{{- range .Columns}}
{{.DataType}} {{.Name}} {{- if .AttributeKey}} {{.AttributeKey}}{{end -}} {{- if .Description}} "{{.Description}}"{{end -}}
{{.DataType}} {{.Name}} {{- if .AttributeKeys}} {{range $index, $attributeKey := .AttributeKeys}}
{{- if $index}},{{end -}}
{{$attributeKey}}
{{- end}}{{end}} {{if .Description -}}
"{{.Description}}"
{{- end}}
{{- end}}
}
{{end -}}
Expand Down
Loading