-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 17e1f48
Showing
23 changed files
with
5,178 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
name: goreleaser | ||
|
||
on: | ||
push: | ||
tags: | ||
- "*" | ||
|
||
jobs: | ||
goreleaser: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- | ||
name: Checkout | ||
uses: actions/checkout@v2 | ||
with: | ||
fetch-depth: 0 | ||
- | ||
name: Set up Go | ||
uses: actions/setup-go@v2 | ||
with: | ||
go-version: 1.17 | ||
- | ||
name: Run GoReleaser | ||
uses: goreleaser/goreleaser-action@v2 | ||
with: | ||
distribution: goreleaser | ||
version: latest | ||
args: release --rm-dist | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.idea/ | ||
result.mmd | ||
mermerd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
builds: | ||
- goos: | ||
- darwin | ||
- linux | ||
- windows | ||
goarch: | ||
- amd64 | ||
- arm64 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package analyzer | ||
|
||
import ( | ||
"errors" | ||
"github.com/AlecAivazis/survey/v2" | ||
"mermerd/config" | ||
"mermerd/database" | ||
"mermerd/util" | ||
) | ||
|
||
func Analyze() (*database.Result, error) { | ||
loading, err := util.NewLoadingSpinner() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var connectionString string | ||
if config.ConnectionString == "" { | ||
err = survey.AskOne(ConnectionQuestion(), &connectionString, survey.WithValidator(survey.Required)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} else { | ||
connectionString = config.ConnectionString | ||
} | ||
|
||
loading.Start("Connecting to database and getting schemas") | ||
db, err := database.NewConnector(connectionString) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
err = db.Connect() | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer db.Close() | ||
|
||
var selectedSchema string | ||
if config.Schema == "" { | ||
schemas, err := db.GetSchemas() | ||
if err != nil { | ||
return nil, err | ||
} | ||
loading.Stop() | ||
|
||
switch len(schemas) { | ||
case 0: | ||
return nil, errors.New("no schemas available") | ||
case 1: | ||
selectedSchema = schemas[0] | ||
break | ||
default: | ||
err = survey.AskOne(SchemaQuestion(schemas), &selectedSchema) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
} else { | ||
selectedSchema = config.Schema | ||
} | ||
|
||
// get tables | ||
var selectedTables []string | ||
loading.Start("Getting tables") | ||
tables, err := db.GetTables(selectedSchema) | ||
if err != nil { | ||
return nil, err | ||
} | ||
loading.Stop() | ||
|
||
err = survey.AskOne(TableQuestion(tables), &selectedTables, survey.WithValidator(survey.MinItems(1))) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// get columns and constraints | ||
var tableResults []database.TableResult | ||
loading.Start("Getting columns and constraints") | ||
for _, table := range selectedTables { | ||
columns, err := db.GetColumns(table) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
constraints, err := db.GetConstraints(table) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
tableResults = append(tableResults, database.TableResult{TableName: table, Columns: columns, Constraints: constraints}) | ||
} | ||
loading.Stop() | ||
|
||
return &database.Result{Tables: tableResults}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package analyzer | ||
|
||
import "github.com/AlecAivazis/survey/v2" | ||
|
||
func ConnectionQuestion() survey.Prompt { | ||
return &survey.Input{ | ||
Message: "Connection string", | ||
Suggest: func(toComplete string) []string { | ||
return []string{ | ||
"postgresql://user:password@localhost:5432/dvdrental", | ||
"mysql://root:password@tcp(127.0.0.1:3306)/db", | ||
} | ||
}, | ||
} | ||
} | ||
|
||
func SchemaQuestion(schemas []string) survey.Prompt { | ||
return &survey.Select{ | ||
Message: "Choose a schema:", | ||
Options: schemas, | ||
} | ||
} | ||
|
||
func TableQuestion(tables []string) survey.Prompt { | ||
return &survey.MultiSelect{ | ||
Message: "Choose tables:", | ||
Options: tables, | ||
PageSize: 15, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package config | ||
|
||
var ShowAllConstraints bool | ||
var Schema string | ||
var ConnectionString string |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package database | ||
|
||
import ( | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
func SanitizeColumnType(columnType string) string { | ||
switch { | ||
case columnType == "character varying", columnType == "varchar": | ||
return "string" | ||
case strings.Contains(columnType, "timestamp"): | ||
return "date" | ||
default: | ||
return sanitize(strings.ReplaceAll(columnType, " ", "_")) | ||
} | ||
} | ||
|
||
func SanitizeTableName(tableName string) string { | ||
return strings.ReplaceAll(sanitize(tableName), " ", "_") | ||
} | ||
|
||
func SanitizeColumnName(columnName string) string { | ||
return strings.ReplaceAll(sanitize(columnName), " ", "_") | ||
} | ||
|
||
func sanitize(value string) string { | ||
reg := regexp.MustCompile("[^a-zA-Z0-9_-]+") | ||
return reg.ReplaceAllString(value, "") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package database | ||
|
||
import ( | ||
"database/sql" | ||
"errors" | ||
"strings" | ||
) | ||
|
||
type baseConnector struct { | ||
dbType DbType | ||
dataSourceName string | ||
db *sql.DB | ||
} | ||
|
||
type Connector interface { | ||
Connect() error | ||
Close() | ||
GetSchemas() ([]string, error) | ||
GetTables(schemaName string) ([]string, error) | ||
GetColumns(tableName string) ([]ColumnResult, error) | ||
GetConstraints(tableName string) ([]ConstraintResult, error) | ||
} | ||
|
||
func NewConnector(dataSourceName string) (Connector, error) { | ||
switch { | ||
case strings.HasPrefix(dataSourceName, "postgresql") || strings.HasPrefix(dataSourceName, "postgres"): | ||
return &postgresConnector{ | ||
dbType: Postgres, | ||
dataSourceName: dataSourceName, | ||
}, nil | ||
case strings.HasPrefix(dataSourceName, "mysql"): | ||
return &mysqlConnector{ | ||
dbType: MySql, | ||
dataSourceName: strings.ReplaceAll(dataSourceName, "mysql://", ""), | ||
}, nil | ||
default: | ||
return nil, errors.New("could not create connector for db") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package database | ||
|
||
import ( | ||
"database/sql" | ||
"fmt" | ||
_ "github.com/go-sql-driver/mysql" | ||
) | ||
|
||
type mysqlConnector baseConnector | ||
|
||
func (c *mysqlConnector) Connect() error { | ||
db, err := sql.Open(c.dbType.String(), c.dataSourceName) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := db.Ping(); err != nil { | ||
return err | ||
} | ||
|
||
c.db = db | ||
return nil | ||
} | ||
|
||
func (c mysqlConnector) Close() { | ||
err := c.db.Close() | ||
if err != nil { | ||
fmt.Println("could not close database connection", err) | ||
} | ||
} | ||
|
||
func (c mysqlConnector) GetSchemas() ([]string, error) { | ||
rows, err := c.db.Query("select schema_name from information_schema.schemata") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var schemas []string | ||
for rows.Next() { | ||
var schema string | ||
if err = rows.Scan(&schema); err != nil { | ||
return nil, err | ||
} | ||
|
||
schemas = append(schemas, schema) | ||
} | ||
|
||
return schemas, nil | ||
} | ||
|
||
func (c mysqlConnector) GetTables(schemaName string) ([]string, error) { | ||
rows, err := c.db.Query(` | ||
select table_name | ||
from information_schema.tables | ||
where table_type = 'BASE TABLE' | ||
and table_schema = ? | ||
`, schemaName) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var tables []string | ||
for rows.Next() { | ||
var table string | ||
if err = rows.Scan(&table); err != nil { | ||
return nil, err | ||
} | ||
|
||
tables = append(tables, SanitizeTableName(table)) | ||
} | ||
|
||
return tables, nil | ||
} | ||
|
||
func (c mysqlConnector) GetColumns(tableName string) ([]ColumnResult, error) { | ||
rows, err := c.db.Query(` | ||
select column_name, data_type | ||
from information_schema.columns | ||
where table_name = ? | ||
order by ordinal_position | ||
`, tableName) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var columns []ColumnResult | ||
for rows.Next() { | ||
var column ColumnResult | ||
if err = rows.Scan(&column.Name, &column.DataType); err != nil { | ||
return nil, err | ||
} | ||
|
||
column.Name = SanitizeColumnName(column.Name) | ||
column.DataType = SanitizeColumnType(column.DataType) | ||
|
||
columns = append(columns, column) | ||
} | ||
|
||
return columns, nil | ||
} | ||
|
||
func (c mysqlConnector) GetConstraints(tableName string) ([]ConstraintResult, error) { | ||
rows, err := c.db.Query(` | ||
select c.TABLE_NAME, | ||
c.REFERENCED_TABLE_NAME, | ||
c.CONSTRAINT_NAME, | ||
( | ||
select kc2.CONSTRAINT_NAME is not null "isPrimary" | ||
from information_schema.KEY_COLUMN_USAGE kc | ||
left join information_schema.KEY_COLUMN_USAGE kc2 | ||
ON kc.COLUMN_NAME = kc2.COLUMN_NAME AND kc2.CONSTRAINT_NAME = 'PRIMARY' AND | ||
kc2.TABLE_NAME = c.TABLE_NAME | ||
where kc.CONSTRAINT_NAME = c.CONSTRAINT_NAME | ||
) "isPrimary" | ||
from information_schema.REFERENTIAL_CONSTRAINTS c | ||
where c.TABLE_NAME = ? | ||
or c.REFERENCED_TABLE_NAME = ? | ||
`, tableName, tableName) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var constraints []ConstraintResult | ||
for rows.Next() { | ||
var constraint ConstraintResult | ||
if err = rows.Scan(&constraint.FkTable, &constraint.PKTable, &constraint.ConstraintName, &constraint.IsPrimary); err != nil { | ||
return nil, err | ||
} | ||
|
||
constraints = append(constraints, constraint) | ||
} | ||
|
||
return constraints, nil | ||
} |
Oops, something went wrong.