Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
KarnerTh committed Jan 17, 2022
0 parents commit 17e1f48
Show file tree
Hide file tree
Showing 23 changed files with 5,178 additions and 0 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/release_build.yml
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 }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea/
result.mmd
mermerd
8 changes: 8 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
builds:
- goos:
- darwin
- linux
- windows
goarch:
- amd64
- arm64
96 changes: 96 additions & 0 deletions analyzer/analyzer.go
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
}
30 changes: 30 additions & 0 deletions analyzer/questions.go
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,
}
}
5 changes: 5 additions & 0 deletions config/config.go
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
30 changes: 30 additions & 0 deletions database/column_type_sanitizer.go
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, "")
}
39 changes: 39 additions & 0 deletions database/connector.go
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")
}
}
134 changes: 134 additions & 0 deletions database/mysql.go
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
}
Loading

0 comments on commit 17e1f48

Please sign in to comment.