Skip to content

Commit

Permalink
Add enum support (#26)
Browse files Browse the repository at this point in the history
* add enum support for postgres; add enum test data; add test case for postgres

* add flag to show enum values

* add new flag to readme; updated mocks

* add mysql support; add mysql test

* update changelog
  • Loading branch information
KarnerTh authored Nov 30, 2022
1 parent c36f824 commit 924a43d
Show file tree
Hide file tree
Showing 22 changed files with 202 additions and 41 deletions.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ test-all:
test-unit:
go test --short $(test_target) -cover -json | tparse -all

.PHONY: test-cleanup
test-cleanup:
go clean -testcache

.PHONY: publish-package
publish-package:
GOPROXY=proxy.golang.org go list -m github.com/KarnerTh/mermerd@$(GIT_TAG)
GOPROXY=proxy.golang.org go list -m github.com/KarnerTh/mermerd@$(GIT_TAG)
6 changes: 6 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (after version 0.0.5).

## [0.5.0] - 2022-12-xx
### Added
- Support enum description ([Issue #15](https://github.com/KarnerTh/mermerd/issues/15))

## [0.4.1] - 2022-09-28
### Fixed
- Fix wrong column format for `is_primary` ([Issue #24](https://github.com/KarnerTh/mermerd/issues/24))
Expand Down Expand Up @@ -95,6 +99,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (after version 0.0
### Added
- Initial release of mermerd

[0.5.0]: https://github.com/KarnerTh/mermerd/releases/tag/v0.5.0

[0.4.1]: https://github.com/KarnerTh/mermerd/releases/tag/v0.4.1

[0.4.0]: https://github.com/KarnerTh/mermerd/releases/tag/v0.4.0
Expand Down
3 changes: 2 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func init() {
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.ShowEnumValuesKey, false, "show enum values in description column")
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")
rootCmd.Flags().StringP(config.SchemaKey, "s", "", "schema that should be used")
Expand All @@ -84,7 +85,7 @@ func init() {
bindFlagToViper(config.SchemaKey)
bindFlagToViper(config.OutputFileNameKey)
bindFlagToViper(config.SelectedTablesKey)

bindFlagToViper(config.ShowEnumValuesKey)
}

func bindFlagToViper(key string) {
Expand Down
6 changes: 6 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (
DebugKey = "debug"
OmitConstraintLabelsKey = "omitConstraintLabels"
OmitAttributeKeysKey = "omitAttributeKeys"
ShowEnumValuesKey = "showEnumValues"
)

type config struct{}
Expand All @@ -30,6 +31,7 @@ type MermerdConfig interface {
Debug() bool
OmitConstraintLabels() bool
OmitAttributeKeys() bool
ShowEnumValues() bool
}

func NewConfig() MermerdConfig {
Expand Down Expand Up @@ -79,3 +81,7 @@ func (c config) OmitConstraintLabels() bool {
func (c config) OmitAttributeKeys() bool {
return viper.GetBool(OmitAttributeKeysKey)
}

func (c config) ShowEnumValues() bool {
return viper.GetBool(ShowEnumValuesKey)
}
24 changes: 18 additions & 6 deletions database/database_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ type columnTestResult struct {
isForeign bool
}

type connectionParameter struct {
connectionString string
schema string
}

var (
testConnectionPostgres connectionParameter = connectionParameter{connectionString: "postgresql://user:password@localhost:5432/mermerd_test", schema: "public"}
testConnectionMySql connectionParameter = connectionParameter{connectionString: "mysql://user:password@tcp(127.0.0.1:3306)/mermerd_test", schema: "mermerd_test"}
testConnectionMsSql connectionParameter = connectionParameter{connectionString: "sqlserver://sa:securePassword1!@localhost:1433?database=mermerd_test", schema: "dbo"}
)

func TestDatabaseIntegrations(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
Expand All @@ -27,18 +38,18 @@ func TestDatabaseIntegrations(t *testing.T) {
}{
{
dbType: Postgres,
connectionString: "postgresql://user:password@localhost:5432/mermerd_test",
schema: "public",
connectionString: testConnectionPostgres.connectionString,
schema: testConnectionPostgres.schema,
},
{
dbType: MySql,
connectionString: "mysql://user:password@tcp(127.0.0.1:3306)/mermerd_test",
schema: "mermerd_test",
connectionString: testConnectionMySql.connectionString,
schema: testConnectionMySql.schema,
},
{
dbType: MsSql,
connectionString: "sqlserver://sa:securePassword1!@localhost:1433?database=mermerd_test",
schema: "dbo",
connectionString: testConnectionMsSql.connectionString,
schema: testConnectionMsSql.schema,
},
}

Expand Down Expand Up @@ -95,6 +106,7 @@ func TestDatabaseIntegrations(t *testing.T) {
"article_label",
"test_1_a",
"test_1_b",
"test_2_enum",
}
assert.Nil(t, err)
assert.ElementsMatch(t, expectedResult, tables)
Expand Down
5 changes: 3 additions & 2 deletions database/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ func (c *mySqlConnector) GetColumns(tableName string) ([]ColumnResult, error) {
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 = 'FOREIGN KEY') as is_foreign
and tc.constraint_type = 'FOREIGN KEY') as is_foreign,
case when c.data_type = 'enum' then REPLACE(REPLACE(REPLACE(REPLACE(c.column_type, 'enum', ''), '\'', ''), '(', ''), ')', '') else '' end as enum_values
from information_schema.columns c
where c.table_name = ?
order by c.ordinal_position;
Expand All @@ -103,7 +104,7 @@ func (c *mySqlConnector) GetColumns(tableName string) ([]ColumnResult, error) {
var columns []ColumnResult
for rows.Next() {
var column ColumnResult
if err = rows.Scan(&column.Name, &column.DataType, &column.IsPrimary, &column.IsForeign); err != nil {
if err = rows.Scan(&column.Name, &column.DataType, &column.IsPrimary, &column.IsForeign, &column.EnumValues); err != nil {
return nil, err
}

Expand Down
35 changes: 35 additions & 0 deletions database/mysql_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package database

import (
"testing"

"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func TestMysqlEnums(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}

// Arrange
var enumValues string

// Act
connector, _ := NewConnectorFactory().NewConnector(testConnectionMySql.connectionString)
if err := connector.Connect(); err != nil {
logrus.Error(err)
t.FailNow()
}
columns, err := connector.GetColumns("test_2_enum")

// Assert
for _, column := range columns {
if column.Name == "fruit" {
enumValues = column.EnumValues
}
}

assert.Nil(t, err)
assert.Equal(t, "apple,banana", enumValues)
}
48 changes: 30 additions & 18 deletions database/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,23 +79,35 @@ func (c *postgresConnector) GetTables(schemaName string) ([]string, error) {

func (c *postgresConnector) GetColumns(tableName string) ([]ColumnResult, error) {
rows, err := c.db.Query(`
select c.column_name,
c.data_type,
(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 = 'PRIMARY KEY') as is_primary,
(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 = 'FOREIGN KEY') as is_foreign
from information_schema.columns c
where c.table_name = $1
order by c.ordinal_position;
select c.column_name,
(case
when c.data_type = 'USER-DEFINED'
then c.udt_name
else c.data_type
end) as data_type,
(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 = 'PRIMARY KEY') as is_primary,
(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 = 'FOREIGN KEY') as is_foreign,
coalesce(string_agg(enumlabel, ',' order by enumsortorder), '') as enum_values
from information_schema.columns c
left join pg_type typ on c.udt_name = typ.typname
left join pg_enum enu on typ.oid = enu.enumtypid
where c.table_name = $1
group by c.column_name,
c.table_name,
c.data_type,
c.udt_name,
c.ordinal_position
order by c.ordinal_position;
`, tableName)
if err != nil {
return nil, err
Expand All @@ -104,7 +116,7 @@ func (c *postgresConnector) GetColumns(tableName string) ([]ColumnResult, error)
var columns []ColumnResult
for rows.Next() {
var column ColumnResult
if err = rows.Scan(&column.Name, &column.DataType, &column.IsPrimary, &column.IsForeign); err != nil {
if err = rows.Scan(&column.Name, &column.DataType, &column.IsPrimary, &column.IsForeign, &column.EnumValues); err != nil {
return nil, err
}

Expand Down
35 changes: 35 additions & 0 deletions database/postgres_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package database

import (
"testing"

"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func TestPostgresEnums(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}

// Arrange
var enumValues string

// Act
connector, _ := NewConnectorFactory().NewConnector(testConnectionPostgres.connectionString)
if err := connector.Connect(); err != nil {
logrus.Error(err)
t.FailNow()
}
columns, err := connector.GetColumns("test_2_enum")

// Assert
for _, column := range columns {
if column.Name == "fruit" {
enumValues = column.EnumValues
}
}

assert.Nil(t, err)
assert.Equal(t, "apple,banana", enumValues)
}
9 changes: 5 additions & 4 deletions database/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ type TableResult struct {
}

type ColumnResult struct {
Name string
DataType string
IsPrimary bool
IsForeign bool
Name string
DataType string
IsPrimary bool
IsForeign bool
EnumValues string
}

type ConstraintResultList []ConstraintResult
Expand Down
6 changes: 6 additions & 0 deletions diagram/diagram.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,15 @@ func (d diagram) Create(result *database.Result) error {
attributeKey = none
}

var enumValues string
if d.config.ShowEnumValues() {
enumValues = column.EnumValues
}

columnData[columnIndex] = ErdColumnData{
Name: column.Name,
DataType: column.DataType,
EnumValues: enumValues,
AttributeKey: attributeKey,
}
}
Expand Down
1 change: 1 addition & 0 deletions diagram/diagram_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type ErdTableData struct {
type ErdColumnData struct {
Name string
DataType string
EnumValues string
AttributeKey ErdAttributeKey
}

Expand Down
2 changes: 1 addition & 1 deletion diagram/erd_template.gommd
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ erDiagram
{{- range .Tables}}
{{.Name}} {
{{- range .Columns}}
{{.DataType}} {{.Name}} {{.AttributeKey}}
{{.DataType}} {{.Name}} {{.AttributeKey}} {{- if .EnumValues}}"{{.EnumValues}}"{{end -}}
{{- end}}
}
{{end -}}
Expand Down
1 change: 1 addition & 0 deletions exampleRunConfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ encloseWithMermaidBackticks: false
debug: false
omitConstraintLabels: false
omitAttributeKeys: false
showEnumValues: false
14 changes: 14 additions & 0 deletions mocks/MermerdConfig.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ for your operating system. To be able to use it globally on your system, add the
* Use it in CI/CD pipeline via a run configuration
* Either generate plain mermaid syntax or enclose it with mermaid backticks to use directly in e.g. GitHub markdown
* Show primary and foreign keys
* Show enum values of enum column

## Why would I need it / Why should I care?

Expand Down Expand Up @@ -78,6 +79,7 @@ via `mermerd -h`
-s, --schema string schema that should be used
--selectedTables strings tables to include
--showAllConstraints show all constraints, even though the table of the resulting constraint was not selected
--showEnumValues show enum values in description column
--useAllTables use all available tables
```

Expand All @@ -98,6 +100,7 @@ outputFileName: "my-db.mmd"
debug: false
omitConstraintLabels: false
omitAttributeKeys: false
showEnumValues: false

# These connection strings are available as suggestions in the cli (use tab to access)
connectionStringSuggestions:
Expand Down Expand Up @@ -130,6 +133,7 @@ outputFileName: "my-db.mmd"
debug: true
omitConstraintLabels: true
omitAttributeKeys: true
showEnumValues: true
```

## Example usages
Expand Down
Loading

0 comments on commit 924a43d

Please sign in to comment.