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

Fix parsing foreign key constraints #489

Merged
merged 4 commits into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
21 changes: 13 additions & 8 deletions drivers/mysql/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/pkg/errors"
)

var reFK = regexp.MustCompile(`FOREIGN KEY \((.+)\) REFERENCES ([^\s]+)\s?\((.+)\)`)
var reFK = regexp.MustCompile(`FOREIGN KEY \((.+)\) REFERENCES ([^\s\)]+)\s?\(([^\)]+)\)`)
var reAI = regexp.MustCompile(` AUTO_INCREMENT=[\d]+`)
var supportGeneratedColumn = true
var supportCheckConstraint = true
Expand Down Expand Up @@ -498,13 +498,7 @@ WHERE t.table_schema = ?
return tableOrderMap[relations[i].Table.Name] < tableOrderMap[relations[j].Table.Name]
})
for _, r := range relations {
result := reFK.FindAllStringSubmatch(r.Def, -1)
if len(result) == 0 || len(result[0]) < 4 {
return errors.Errorf("can not parse foreign key: %s", r.Def)
}
strColumns := strings.Split(result[0][1], ", ")
strParentTable := result[0][2]
strParentColumns := strings.Split(result[0][3], ", ")
strColumns, strParentTable, strParentColumns, err := parseFK(r.Def)
for _, c := range strColumns {
column, err := r.Table.FindColumnByName(c)
if err != nil {
Expand Down Expand Up @@ -639,3 +633,14 @@ SELECT table_name, table_type, table_comment FROM information_schema.tables WHER
func convertColumnNullable(str string) bool {
return str != "NO"
}

func parseFK(def string) ([]string, string, []string, error) {
result := reFK.FindAllStringSubmatch(def, -1)
if len(result) < 1 || len(result[0]) < 4 {
return nil, "", nil, errors.Errorf("can not parse foreign key: %s", def)
}
strColumns := strings.Split(result[0][1], ", ")
strParentTable := strings.Trim(result[0][2], `"`)
strParentColumns := strings.Split(result[0][3], ", ")
return strColumns, strParentTable, strParentColumns, nil
}
35 changes: 21 additions & 14 deletions drivers/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/pkg/errors"
)

var reFK = regexp.MustCompile(`FOREIGN KEY \((.+)\) REFERENCES ([^\s]+)\s?\((.+)\)`)
var reFK = regexp.MustCompile(`FOREIGN KEY \((.+)\) REFERENCES ([^\s\)]+)\s?\(([^\)]+)\)`)
var reVersion = regexp.MustCompile(`([0-9]+(\.[0-9]+)*)`)

// Postgres struct
Expand Down Expand Up @@ -57,7 +57,6 @@ func (p *Postgres) Analyze(s *schema.Schema) error {
s.Driver.Meta.CurrentSchema = currentSchema.String
}


// search_path
var searchPaths string
pathRows, err := p.db.Query(`SHOW search_path`)
Expand Down Expand Up @@ -311,18 +310,9 @@ ORDER BY tgrelid

// Relations
for _, r := range relations {
result := reFK.FindAllStringSubmatch(r.Def, -1)
if len(result) < 1 || len(result[0]) < 4 {
return errors.Errorf("can not parse foreign key: %s", r.Def)
}
strColumns := []string{}
for _, c := range strings.Split(result[0][1], ", ") {
strColumns = append(strColumns, strings.ReplaceAll(c, `"`, ""))
}
strParentTable := strings.ReplaceAll(result[0][2], `"`, "")
strParentColumns := []string{}
for _, c := range strings.Split(result[0][3], ", ") {
strParentColumns = append(strParentColumns, strings.ReplaceAll(c, `"`, ""))
strColumns, strParentTable, strParentColumns, err := parseFK(r.Def)
if err != nil {
return err
}
for _, c := range strColumns {
column, err := r.Table.FindColumnByName(c)
Expand Down Expand Up @@ -688,3 +678,20 @@ func convertConstraintType(t string) string {
return t
}
}

func parseFK(def string) ([]string, string, []string, error) {
result := reFK.FindAllStringSubmatch(def, -1)
if len(result) < 1 || len(result[0]) < 4 {
return nil, "", nil, errors.Errorf("can not parse foreign key: %s", def)
}
strColumns := []string{}
for _, c := range strings.Split(result[0][1], ", ") {
strColumns = append(strColumns, strings.ReplaceAll(c, `"`, ""))
}
strParentTable := strings.ReplaceAll(result[0][2], `"`, "")
strParentColumns := []string{}
for _, c := range strings.Split(result[0][3], ", ") {
strParentColumns = append(strParentColumns, strings.ReplaceAll(c, `"`, ""))
}
return strColumns, strParentTable, strParentColumns, nil
}
31 changes: 31 additions & 0 deletions drivers/postgres/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/k1LoW/tbls/schema"
_ "github.com/lib/pq"
"github.com/xo/dburl"
Expand Down Expand Up @@ -68,3 +69,33 @@ func TestInfo(t *testing.T) {
t.Errorf("got not empty string.")
}
}

func TestParseFK(t *testing.T) {
tests := []struct {
in string
wantCols []string
wantParentTable string
wantParentCols []string
}{
{"FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE", []string{"user_id"}, "users", []string{"id"}},
{"FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL (user_id)", []string{"user_id"}, "users", []string{"id"}},
}
for _, tt := range tests {
t.Run(tt.in, func(t *testing.T) {
gotCols, gotParentTable, gotParentCols, err := parseFK(tt.in)
if err != nil {
t.Error(err)
return
}
if diff := cmp.Diff(gotCols, tt.wantCols, nil); diff != "" {
t.Error(diff)
}
if gotParentTable != tt.wantParentTable {
t.Errorf("got %v want %v", gotParentTable, tt.wantParentTable)
}
if diff := cmp.Diff(gotParentCols, tt.wantParentCols, nil); diff != "" {
t.Error(diff)
}
})
}
}
21 changes: 13 additions & 8 deletions drivers/sqlite/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/pkg/errors"
)

var reFK = regexp.MustCompile(`FOREIGN KEY \((.+)\) REFERENCES ([^\s]+)\s?\((.+)\)`)
var reFK = regexp.MustCompile(`FOREIGN KEY \((.+)\) REFERENCES ([^\s\)]+)\s?\(([^\)]+)\)`)
var reFTS = regexp.MustCompile(`(?i)USING\s+fts([34])`)

var shadowTables []string
Expand Down Expand Up @@ -362,13 +362,7 @@ SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND tbl_name = ?;

// Relations
for _, r := range relations {
result := reFK.FindAllStringSubmatch(r.Def, -1)
if len(result) < 1 || len(result[0]) < 4 {
return errors.Errorf("can not parse foreign key: %s", r.Def)
}
strColumns := strings.Split(result[0][1], ", ")
strParentTable := result[0][2]
strParentColumns := strings.Split(result[0][3], ", ")
strColumns, strParentTable, strParentColumns, err := parseFK(r.Def)
for _, c := range strColumns {
column, err := r.Table.FindColumnByName(c)
if err != nil {
Expand Down Expand Up @@ -499,3 +493,14 @@ func contains(s []string, e string) bool {
}
return false
}

func parseFK(def string) ([]string, string, []string, error) {
result := reFK.FindAllStringSubmatch(def, -1)
if len(result) < 1 || len(result[0]) < 4 {
return nil, "", nil, errors.Errorf("can not parse foreign key: %s", def)
}
strColumns := strings.Split(result[0][1], ", ")
strParentTable := strings.Trim(result[0][2], `"`)
strParentColumns := strings.Split(result[0][3], ", ")
return strColumns, strParentTable, strParentColumns, nil
}
2 changes: 1 addition & 1 deletion sample/adjust/public.posts.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Posts table
| Name | Type | Definition | Comment |
| ----------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
| update_posts_updated | TRIGGER | CREATE CONSTRAINT TRIGGER update_posts_updated AFTER INSERT OR UPDATE ON public.posts NOT DEFERRABLE INITIALLY IMMEDIATE FOR EACH ROW EXECUTE FUNCTION update_updated() | |
| posts_user_id_fk | FOREIGN KEY | FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE | posts -> users |
| posts_user_id_fk | FOREIGN KEY | FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL (user_id) | posts -> users |
| posts_id_pk | PRIMARY KEY | PRIMARY KEY (id) | |
| posts_user_id_title_key | UNIQUE | UNIQUE (user_id, title) | |

Expand Down
Loading