Skip to content

Commit

Permalink
Merge pull request #67 from k1LoW/schema-json
Browse files Browse the repository at this point in the history
Support `json://`
  • Loading branch information
k1LoW committed Nov 23, 2018
2 parents d1775e5 + b9775c2 commit d2b7f74
Show file tree
Hide file tree
Showing 7 changed files with 2,473 additions and 0 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ before_install:
script:
- make test
- make test_too_many_tables
- make test_json
after_script:
- codecov
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ test_too_many_tables: build
ulimit -n 256 && ./tbls doc pg://postgres:pgpass@localhost:55432/too_many?sslmode=disable -f /tmp
ulimit -n 256 && ./tbls diff pg://postgres:pgpass@localhost:55432/too_many?sslmode=disable /tmp

test_json: build
./tbls out my://root:mypass@localhost:33306/testdb -a testdata/additional_data.yml -t json > /tmp/tbls.json
./tbls diff json:///tmp/tbls.json sample/mysql

build:
packr
$(GO) build -ldflags="$(BUILD_LDFLAGS)"
Expand Down
22 changes: 22 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package db

import (
"database/sql"
"encoding/json"
"fmt"
"os"
"strings"

"github.com/k1LoW/tbls/drivers/mysql"
Expand All @@ -20,6 +22,9 @@ type Driver interface {

// Analyze database
func Analyze(urlstr string) (*schema.Schema, error) {
if strings.Index(urlstr, "json://") == 0 {
return AnalizeJSON(urlstr)
}
s := &schema.Schema{}
u, err := dburl.Parse(urlstr)
if err != nil {
Expand Down Expand Up @@ -60,3 +65,20 @@ func Analyze(urlstr string) (*schema.Schema, error) {
}
return s, nil
}

// AnalizeJSON analize `json://`
func AnalizeJSON(urlstr string) (*schema.Schema, error) {
s := &schema.Schema{}
splitted := strings.Split(urlstr, "json://")
file, err := os.Open(splitted[1])
if err != nil {
return s, errors.WithStack(err)
}
dec := json.NewDecoder(file)
dec.Decode(s)
err = s.Repair()
if err != nil {
return s, errors.WithStack(err)
}
return s, nil
}
1 change: 1 addition & 0 deletions db/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var tests = []struct {
}{
{"my://root:mypass@localhost:33306/testdb", 7, 5},
{"pg://postgres:pgpass@localhost:55432/testdb?sslmode=disable", 8, 6},
{"json://../testdata/testdb.json", 7, 9},
}

func TestAnalyzeSchema(t *testing.T) {
Expand Down
59 changes: 59 additions & 0 deletions schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,32 @@ func (c Column) MarshalJSON() ([]byte, error) {
})
}

// UnmarshalJSON ...
func (c *Column) UnmarshalJSON(data []byte) error {
s := struct {
Name string `json:"name"`
Type string `json:"type"`
Nullable bool `json:"nullable"`
Default *string `json:"default"`
Comment string `json:"comment"`
ParentRelations []*Relation `json:"-"`
ChildRelations []*Relation `json:"-"`
}{}
json.Unmarshal(data, &s)
c.Name = s.Name
c.Type = s.Type
c.Nullable = s.Nullable
if s.Default != nil {
c.Default.Valid = true
c.Default.String = *s.Default
} else {
c.Default.Valid = false
c.Default.String = ""
}
c.Comment = s.Comment
return nil
}

// FindTableByName find table by table name
func (s *Schema) FindTableByName(name string) (*Table, error) {
for _, t := range s.Tables {
Expand Down Expand Up @@ -226,6 +252,39 @@ func (s *Schema) AddAdditionalData(buf []byte) error {
return nil
}

// Repair column relations
func (s *Schema) Repair() error {
for _, r := range s.Relations {
t, err := s.FindTableByName(r.Table.Name)
if err != nil {
return errors.Wrap(err, "failed to repair relation")
}
for _, rc := range r.Columns {
c, err := t.FindColumnByName(rc.Name)
if err != nil {
return errors.Wrap(err, "failed to repair relation")
}
c.ParentRelations = append(c.ParentRelations, r)
rc = c
}
r.Table = t
pt, err := s.FindTableByName(r.ParentTable.Name)
if err != nil {
return errors.Wrap(err, "failed to repair relation")
}
for _, rc := range r.ParentColumns {
pc, err := pt.FindColumnByName(rc.Name)
if err != nil {
return errors.Wrap(err, "failed to repair relation")
}
pc.ChildRelations = append(pc.ChildRelations, r)
rc = pc
}
r.ParentTable = pt
}
return nil
}

func addAdditionalRelations(s *Schema, relations []AdditionalRelation) error {
for _, r := range relations {
relation := &Relation{
Expand Down
111 changes: 111 additions & 0 deletions schema/schema_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package schema

import (
"database/sql"
"encoding/json"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -143,8 +145,117 @@ func TestAddAditionalData(t *testing.T) {
}
}

func TestRepair(t *testing.T) {
actual := &Schema{}
file, err := os.Open(filepath.Join(testdataDir(), "json_test_schema.json.golden"))
if err != nil {
t.Error(err)
}
dec := json.NewDecoder(file)
dec.Decode(actual)
expected := newTestSchema()
err = actual.Repair()
if err != nil {
t.Error(err)
}

for i, tt := range actual.Tables {
compareStrings(t, actual.Tables[i].Name, expected.Tables[i].Name)
for j := range tt.Columns {
compareStrings(t, actual.Tables[i].Columns[j].Name, expected.Tables[i].Columns[j].Name)
for k := range actual.Tables[i].Columns[j].ParentRelations {
compareStrings(t, actual.Tables[i].Columns[j].ParentRelations[k].Table.Name, expected.Tables[i].Columns[j].ParentRelations[k].Table.Name)
compareStrings(t, actual.Tables[i].Columns[j].ParentRelations[k].ParentTable.Name, expected.Tables[i].Columns[j].ParentRelations[k].ParentTable.Name)
}
for k := range actual.Tables[i].Columns[j].ChildRelations {
compareStrings(t, actual.Tables[i].Columns[j].ChildRelations[k].Table.Name, expected.Tables[i].Columns[j].ChildRelations[k].Table.Name)
compareStrings(t, actual.Tables[i].Columns[j].ChildRelations[k].ParentTable.Name, expected.Tables[i].Columns[j].ChildRelations[k].ParentTable.Name)
}
}
}

if len(actual.Relations) != len(expected.Relations) {
t.Errorf("actual %#v\nwant %#v", actual.Relations, expected.Relations)
}
}

func compareStrings(tb testing.TB, actual, expected string) {
tb.Helper()
if actual != expected {
tb.Errorf("actual %#v\nwant %#v", actual, expected)
}
}

func testdataDir() string {
wd, _ := os.Getwd()
dir, _ := filepath.Abs(filepath.Join(filepath.Dir(wd), "testdata"))
return dir
}

func newTestSchema() *Schema {
ca := &Column{
Name: "a",
Type: "bigint(20)",
Comment: "column a",
Nullable: false,
}
cb := &Column{
Name: "b",
Type: "text",
Comment: "column b",
Nullable: true,
}

ta := &Table{
Name: "a",
Type: "BASE TABLE",
Comment: "table a",
Columns: []*Column{
ca,
&Column{
Name: "a2",
Type: "datetime",
Comment: "column a2",
Nullable: false,
Default: sql.NullString{
String: "CURRENT_TIMESTAMP",
Valid: true,
},
},
},
}
tb := &Table{
Name: "b",
Type: "BASE TABLE",
Comment: "table b",
Columns: []*Column{
cb,
&Column{
Name: "b2",
Comment: "column b2",
Type: "text",
Nullable: true,
},
},
}
r := &Relation{
Table: ta,
Columns: []*Column{ca},
ParentTable: tb,
ParentColumns: []*Column{cb},
}
ca.ParentRelations = []*Relation{r}
cb.ChildRelations = []*Relation{r}

s := &Schema{
Name: "testschema",
Tables: []*Table{
ta,
tb,
},
Relations: []*Relation{
r,
},
}
return s
}
Loading

0 comments on commit d2b7f74

Please sign in to comment.