Repository: k1LoW/tbls
Pull request #302 (2021-03-22 12:42:32)

[BREAKING]Add a list of tables referenced by the view table.
![image](https://user-images.githubusercontent.com/57114/112092984-0c475700-8bdc-11eb-912c-c535e977ab28.png)

https://github.com/k1LoW/tbls/blob/ddlparser/sample/mysql/post_comments.md



Patch:
diff --git a/Makefile b/Makefile
index f99e9b1..8e0f597 100644
--- a/Makefile
+++ b/Makefile
@@ -58,10 +58,10 @@ doc: build doc_sqlite
 	./tbls doc my://root:mypass@localhost:33308/testdb -c testdata/dict_test_tbls.yml -f sample/dict
 	./tbls doc my://root:mypass@localhost:33308/testdb -c testdata/font_test_tbls.yml -f sample/font
 
-doc_sqlite:
+doc_sqlite: build
 	./tbls doc sq://$(PWD)/testdata/testdb.sqlite3 -c testdata/test_tbls.yml -f sample/sqlite
 
-testdoc: testdoc_sqlite
+testdoc: build testdoc_sqlite
 	./tbls diff pg://postgres:pgpass@localhost:55432/testdb?sslmode=disable -c testdata/test_tbls_postgres.yml sample/postgres95
 	./tbls diff pg://postgres:pgpass@localhost:55413/testdb?sslmode=disable -c testdata/test_tbls_postgres.yml sample/postgres
 	./tbls diff my://root:mypass@localhost:33306/testdb -c testdata/test_tbls.yml sample/mysql56
@@ -76,23 +76,23 @@ testdoc: testdoc_sqlite
 	./tbls diff my://root:mypass@localhost:33308/testdb -c testdata/dict_test_tbls.yml sample/dict
 	./tbls diff my://root:mypass@localhost:33308/testdb -c testdata/font_test_tbls.yml sample/font
 
-testdoc_sqlite:
+testdoc_sqlite: build
 	./tbls diff sq://$(PWD)/testdata/testdb.sqlite3 -c testdata/test_tbls.yml sample/sqlite
 
-testdoc_hide_auto_increment:
+testdoc_hide_auto_increment: build
 	usql my://root:mypass@localhost:33308/testdb -c "CREATE DATABASE IF NOT EXISTS auto_increment;"
 	usql my://root:mypass@localhost:33308/auto_increment -f testdata/ddl/auto_increment.sql
 	./tbls doc my://root:mypass@localhost:33308/auto_increment?hide_auto_increment -f $(TMPDIR)/auto_increment
 	usql my://root:mypass@localhost:33308/auto_increment -c "INSERT INTO users (username, password, email, created) VALUES ('alice', 'PASS', 'alice@example.com', now());"
 	./tbls diff my://root:mypass@localhost:33308/auto_increment?hide_auto_increment $(TMPDIR)/auto_increment
 
-test_too_many_tables:
+test_too_many_tables: build
 	usql pg://postgres:pgpass@localhost:55432/testdb?sslmode=disable -c "DROP DATABASE IF EXISTS too_many;CREATE DATABASE too_many;"
 	usql pg://postgres:pgpass@localhost:55432/too_many?sslmode=disable -f testdata/ddl/createtable_too_many.sql
 	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:
+test_json: build
 	./tbls out my://root:mypass@localhost:33308/testdb -c testdata/test_tbls.yml -t json > /tmp/tbls.json
 	./tbls diff json:///tmp/tbls.json sample/mysql
 	./tbls out pg://postgres:pgpass@localhost:55413/testdb?sslmode=disable -c testdata/test_tbls_postgres.yml -t json > /tmp/tbls.json
@@ -100,29 +100,29 @@ test_json:
 	./tbls out sq://$(PWD)/testdata/testdb.sqlite3 -c testdata/test_tbls.yml -t json > /tmp/tbls.json
 	./tbls diff json:///tmp/tbls.json sample/sqlite
 
-test_env:
+test_env: build
 	env TBLS_DSN=my://root:mypass@localhost:33306/testdb TBLS_DOC_PATH=sample/mysql ./tbls doc -c testdata/test_tbls.yml -f
 	env TBLS_DSN=my://root:mypass@localhost:33306/testdb TBLS_DOC_PATH=sample/mysql ./tbls diff -c testdata/test_tbls.yml
 
-test_config:
+test_config: build
 	./tbls doc -c testdata/mysql_testdb_config.yml -f
 	./tbls diff -c testdata/mysql_testdb_config.yml
 	cp testdata/mysql_testdb_config.yml .tbls.yml
 	./tbls diff
 	rm .tbls.yml
 
-doc_bigquery:
+doc_bigquery: build
 	./tbls doc bq://bigquery-public-data/crypto_bitcoin?creds=client_secrets.json -c testdata/crypto_bitcoin_tbls.yml -f sample/bigquery_crypto_bitcoin
 	./tbls doc bq://bigquery-public-data/census_bureau_international?creds=client_secrets.json -f sample/bigquery_census_bureau_international
 
-test_bigquery:
+test_bigquery: build
 	./tbls diff bq://bigquery-public-data/crypto_bitcoin?creds=client_secrets.json -c testdata/crypto_bitcoin_tbls.yml sample/bigquery_crypto_bitcoin
 	./tbls diff bq://bigquery-public-data/census_bureau_international?creds=client_secrets.json sample/bigquery_census_bureau_international
 
-doc_spanner:
+doc_spanner: build
 	./tbls doc spanner://$(GCLOUD_PROJECT)/test-instance/testdb?creds=spanner_client_secrets.json -c testdata/spanner_tbls.yml -f sample/spanner
 
-test_spanner:
+test_spanner: build
 	./tbls diff spanner://$(GCLOUD_PROJECT)/test-instance/testdb?creds=spanner_client_secrets.json -c testdata/spanner_tbls.yml sample/spanner
 
 test_ext_subcommand: build
diff --git a/ddl/ddl.go b/ddl/ddl.go
new file mode 100644
index 0000000..c9c88f6
--- /dev/null
+++ b/ddl/ddl.go
@@ -0,0 +1,143 @@
+package ddl
+
+import (
+	"bufio"
+	"strings"
+	"unicode/utf8"
+)
+
+// ParseReferencedTables parse DDL of view table and list tables referenced by view table.
+func ParseReferencedTables(src string) []string {
+	scanner := bufio.NewScanner(strings.NewReader(src))
+	// original: bufio.ScanWords()
+	scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
+		sq := false // '***'
+		dq := false // "***"
+		bq := false // `***`
+
+		// Skip leading spaces.
+		start := 0
+		for width := 0; start < len(data); start += width {
+			var r rune
+			r, width = utf8.DecodeRune(data[start:])
+			if !isSpace(r) && !isSkipSymbol(r) {
+				break
+			}
+		}
+		// Scan until space, marking end of word.
+		for width, i := 0, start; i < len(data); i += width {
+			if data[i] == '\'' {
+				sq = !sq
+			}
+			if data[i] == '"' {
+				dq = !dq
+			}
+			if data[i] == '`' {
+				bq = !bq
+			}
+
+			var r rune
+			r, width = utf8.DecodeRune(data[i:])
+			if isSpace(r) || isSkipSymbol(r) {
+				if !sq && !dq && !bq {
+					return i + width, data[start:i], nil
+				}
+			}
+		}
+		// If we're at EOF, we have a final, non-empty, non-terminated word. Return it.
+		if atEOF && len(data) > start {
+			return len(data), data[start:], nil
+		}
+		// Request more data.
+		return start, nil, nil
+	})
+
+	tables := []string{}
+	with := []string{}
+	tFrom := false
+	tJoin := false
+	tWith := false
+	for scanner.Scan() {
+		token := scanner.Text()
+		switch strings.ToUpper(token) {
+		case "FROM":
+			tFrom = true
+		case "JOIN":
+			tJoin = true
+		case "WITH":
+			tWith = true
+		case "SELECT":
+			tFrom = false
+			tJoin = false
+			tWith = false
+		default:
+			if tFrom == true {
+				tables = append(tables, strings.Replace(token, "`", "", -1))
+			}
+			if tJoin == true {
+				tables = append(tables, strings.Replace(token, "`", "", -1))
+			}
+			if tWith == true {
+				with = append(with, strings.Replace(token, "`", "", -1))
+			}
+			tFrom = false
+			tJoin = false
+			tWith = false
+		}
+	}
+
+	result := []string{}
+	for _, t := range tables {
+		if contains(with, t) {
+			continue
+		}
+		result = append(result, t)
+	}
+	return unique(result)
+}
+
+func isSkipSymbol(r rune) bool {
+	switch r {
+	case ',':
+		return true
+	case '+', '-', '*', '/', '%':
+		return true
+	case '=', '<', '>':
+		return true
+	case '(', ')':
+		return true
+	case '&', '|':
+		return true
+	}
+	return false
+}
+
+func isSpace(r rune) bool {
+	switch r {
+	case ' ', '\t', '\n', '\r':
+		return true
+	}
+	return false
+}
+
+func contains(s []string, e string) bool {
+	for _, v := range s {
+		if e == v {
+			return true
+		}
+	}
+	return false
+}
+
+func unique(in []string) []string {
+	u := []string{}
+	m := map[string]struct{}{}
+	for _, s := range in {
+		if _, ok := m[s]; ok {
+			continue
+		}
+		u = append(u, s)
+		m[s] = struct{}{}
+	}
+	return u
+}
diff --git a/drivers/bq/bq.go b/drivers/bq/bq.go
index 5709cfa..99df2e9 100644
--- a/drivers/bq/bq.go
+++ b/drivers/bq/bq.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 
 	"cloud.google.com/go/bigquery"
+	"github.com/k1LoW/tbls/ddl"
 	"github.com/k1LoW/tbls/dict"
 	"github.com/k1LoW/tbls/schema"
 	"github.com/pkg/errors"
@@ -82,6 +83,25 @@ func (b *Bigquery) Analyze(s *schema.Schema) error {
 		tables = append(tables, table)
 	}
 	s.Tables = tables
+
+	// referenced tables of view
+	for _, t := range s.Tables {
+		if t.Type != "VIEW" {
+			continue
+		}
+		for _, rts := range ddl.ParseReferencedTables(t.Def) {
+			splitted := strings.Split(rts, ".")
+			rt, err := s.FindTableByName(splitted[len(splitted)-1])
+			if err != nil {
+				rt = &schema.Table{
+					Name:     rts,
+					External: true,
+				}
+			}
+			t.ReferencedTables = append(t.ReferencedTables, rt)
+		}
+	}
+
 	return nil
 }
 
diff --git a/drivers/mssql/mssql.go b/drivers/mssql/mssql.go
index 1c87231..153ac50 100644
--- a/drivers/mssql/mssql.go
+++ b/drivers/mssql/mssql.go
@@ -7,6 +7,7 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/k1LoW/tbls/ddl"
 	"github.com/k1LoW/tbls/schema"
 	"github.com/pkg/errors"
 )
@@ -253,13 +254,13 @@ GROUP BY f.name, f.parent_object_id, f.referenced_object_id, delete_referential_
 			}
 			fkDef := fmt.Sprintf("FOREIGN KEY(%s) REFERENCES %s(%s) ON UPDATE %s ON DELETE %s", fkColumnNames, fkParentTableName, fkParentColumnNames, fkUpdateAction, fkDeleteAction) // #nosec
 			constraint := &schema.Constraint{
-				Name:             convertSystemNamed(fkName, fkIsSystemNamed),
-				Type:             typeFk,
-				Def:              fkDef,
-				Table:            &table.Name,
-				Columns:          strings.Split(fkColumnNames, ", "),
-				ReferenceTable:   &fkParentTableName,
-				ReferenceColumns: strings.Split(fkParentColumnNames, ", "),
+				Name:              convertSystemNamed(fkName, fkIsSystemNamed),
+				Type:              typeFk,
+				Def:               fkDef,
+				Table:             &table.Name,
+				Columns:           strings.Split(fkColumnNames, ", "),
+				ReferencedTable:   &fkParentTableName,
+				ReferencedColumns: strings.Split(fkParentColumnNames, ", "),
 			}
 			links = append(links, relationLink{
 				table:         table.Name,
@@ -438,6 +439,23 @@ ORDER BY i.index_id
 
 	s.Relations = relations
 
+	// referenced tables of view
+	for _, t := range s.Tables {
+		if t.Type != "VIEW" {
+			continue
+		}
+		for _, rts := range ddl.ParseReferencedTables(t.Def) {
+			rt, err := s.FindTableByName(rts)
+			if err != nil {
+				rt = &schema.Table{
+					Name:     rts,
+					External: true,
+				}
+			}
+			t.ReferencedTables = append(t.ReferencedTables, rt)
+		}
+	}
+
 	return nil
 }
 
diff --git a/drivers/mysql/mysql.go b/drivers/mysql/mysql.go
index 5d29cbf..fffc335 100644
--- a/drivers/mysql/mysql.go
+++ b/drivers/mysql/mysql.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 
 	"github.com/aquasecurity/go-version/pkg/version"
+	"github.com/k1LoW/tbls/ddl"
 	"github.com/k1LoW/tbls/drivers"
 	"github.com/k1LoW/tbls/schema"
 	"github.com/pkg/errors"
@@ -303,8 +304,8 @@ GROUP BY kcu.constraint_name, sub.costraint_type, kcu.referenced_table_name`, ta
 				Columns: strings.Split(constraintColumnName, ", "),
 			}
 			if constraintRefTableName.String != "" {
-				constraint.ReferenceTable = &constraintRefTableName.String
-				constraint.ReferenceColumns = strings.Split(constraintRefColumnName.String, ", ")
+				constraint.ReferencedTable = &constraintRefTableName.String
+				constraint.ReferencedColumns = strings.Split(constraintRefColumnName.String, ", ")
 			}
 
 			constraints = append(constraints, constraint)
@@ -452,6 +453,23 @@ WHERE table_schema = ? AND table_name = ? ORDER BY ordinal_position`
 	}
 	s.Relations = relations
 
+	// referenced tables of view
+	for _, t := range s.Tables {
+		if t.Type != "VIEW" {
+			continue
+		}
+		for _, rts := range ddl.ParseReferencedTables(t.Def) {
+			rt, err := s.FindTableByName(strings.TrimPrefix(rts, fmt.Sprintf("%s.", s.Name)))
+			if err != nil {
+				rt = &schema.Table{
+					Name:     rts,
+					External: true,
+				}
+			}
+			t.ReferencedTables = append(t.ReferencedTables, rt)
+		}
+	}
+
 	return nil
 }
 
diff --git a/drivers/postgres/postgres.go b/drivers/postgres/postgres.go
index a61c141..fc492af 100644
--- a/drivers/postgres/postgres.go
+++ b/drivers/postgres/postgres.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 
 	"github.com/aquasecurity/go-version/pkg/version"
+	"github.com/k1LoW/tbls/ddl"
 	"github.com/k1LoW/tbls/schema"
 	"github.com/lib/pq"
 	"github.com/pkg/errors"
@@ -147,28 +148,28 @@ ORDER BY oid`)
 
 		for constraintRows.Next() {
 			var (
-				constraintName                 string
-				constraintDef                  string
-				constraintType                 string
-				constraintReferenceTable       sql.NullString
-				constraintColumnNames          []sql.NullString
-				constraintReferenceColumnNames []sql.NullString
-				constraintComment              sql.NullString
+				constraintName                  string
+				constraintDef                   string
+				constraintType                  string
+				constraintReferencedTable       sql.NullString
+				constraintColumnNames           []sql.NullString
+				constraintReferencedColumnNames []sql.NullString
+				constraintComment               sql.NullString
 			)
-			err = constraintRows.Scan(&constraintName, &constraintDef, &constraintType, &constraintReferenceTable, pq.Array(&constraintColumnNames), pq.Array(&constraintReferenceColumnNames), &constraintComment)
+			err = constraintRows.Scan(&constraintName, &constraintDef, &constraintType, &constraintReferencedTable, pq.Array(&constraintColumnNames), pq.Array(&constraintReferencedColumnNames), &constraintComment)
 			if err != nil {
 				return errors.WithStack(err)
 			}
-			rt := constraintReferenceTable.String
+			rt := constraintReferencedTable.String
 			constraint := &schema.Constraint{
-				Name:             constraintName,
-				Type:             convertConstraintType(constraintType),
-				Def:              constraintDef,
-				Table:            &table.Name,
-				Columns:          arrayRemoveNull(constraintColumnNames),
-				ReferenceTable:   &rt,
-				ReferenceColumns: arrayRemoveNull(constraintReferenceColumnNames),
-				Comment:          constraintComment.String,
+				Name:              constraintName,
+				Type:              convertConstraintType(constraintType),
+				Def:               constraintDef,
+				Table:             &table.Name,
+				Columns:           arrayRemoveNull(constraintColumnNames),
+				ReferencedTable:   &rt,
+				ReferencedColumns: arrayRemoveNull(constraintReferencedColumnNames),
+				Comment:           constraintComment.String,
 			}
 
 			if constraintType == "f" {
@@ -343,6 +344,23 @@ ORDER BY tgrelid
 
 	s.Relations = relations
 
+	// referenced tables of view
+	for _, t := range s.Tables {
+		if t.Type != "VIEW" && t.Type != "MATERIALIZED VIEW" {
+			continue
+		}
+		for _, rts := range ddl.ParseReferencedTables(t.Def) {
+			rt, err := s.FindTableByName(rts)
+			if err != nil {
+				rt = &schema.Table{
+					Name:     rts,
+					External: true,
+				}
+			}
+			t.ReferencedTables = append(t.ReferencedTables, rt)
+		}
+	}
+
 	return nil
 }
 
diff --git a/drivers/spanner/spanner.go b/drivers/spanner/spanner.go
index 7604515..8c59b61 100644
--- a/drivers/spanner/spanner.go
+++ b/drivers/spanner/spanner.go
@@ -249,13 +249,13 @@ GROUP BY c.TABLE_CATALOG, c.TABLE_SCHEMA, c.TABLE_NAME, c.INDEX_NAME, c.INDEX_TY
 				indexes = append(indexes, index)
 			case "PRIMARY_KEY":
 				constraint := &schema.Constraint{
-					Name:             "PRIMARY_KEY",
-					Type:             "PRIMARY_KEY",
-					Def:              fmt.Sprintf("PRIMARY KEY(%s)", columns),
-					Table:            &table.Name,
-					Columns:          strings.Split(columns, ", "),
-					ReferenceTable:   nil,
-					ReferenceColumns: []string{},
+					Name:              "PRIMARY_KEY",
+					Type:              "PRIMARY_KEY",
+					Def:               fmt.Sprintf("PRIMARY KEY(%s)", columns),
+					Table:             &table.Name,
+					Columns:           strings.Split(columns, ", "),
+					ReferencedTable:   nil,
+					ReferencedColumns: []string{},
 				}
 				constraints = append(constraints, constraint)
 			default:
@@ -285,13 +285,13 @@ GROUP BY c.TABLE_CATALOG, c.TABLE_SCHEMA, c.TABLE_NAME, c.INDEX_NAME, c.INDEX_TY
 
 		// constraints
 		constraint := &schema.Constraint{
-			Name:             "INTERLEAVE",
-			Type:             "INTERLEAVE",
-			Def:              def,
-			Table:            &t.Name,
-			Columns:          []string{},
-			ReferenceTable:   &pt.Name,
-			ReferenceColumns: []string{},
+			Name:              "INTERLEAVE",
+			Type:              "INTERLEAVE",
+			Def:               def,
+			Table:             &t.Name,
+			Columns:           []string{},
+			ReferencedTable:   &pt.Name,
+			ReferencedColumns: []string{},
 		}
 
 		// relations
@@ -319,7 +319,7 @@ GROUP BY c.TABLE_CATALOG, c.TABLE_SCHEMA, c.TABLE_NAME, c.INDEX_NAME, c.INDEX_TY
 		}
 		for _, c := range pt.Constraints {
 			if c.Type == "PRIMARY_KEY" {
-				constraint.ReferenceColumns = c.Columns
+				constraint.ReferencedColumns = c.Columns
 				for _, cName := range c.Columns {
 					column, err := pt.FindColumnByName(cName)
 					if err != nil {
diff --git a/drivers/sqlite/sqlite.go b/drivers/sqlite/sqlite.go
index 68b18cb..2808281 100644
--- a/drivers/sqlite/sqlite.go
+++ b/drivers/sqlite/sqlite.go
@@ -8,6 +8,7 @@ import (
 
 	"regexp"
 
+	"github.com/k1LoW/tbls/ddl"
 	"github.com/k1LoW/tbls/schema"
 	"github.com/pkg/errors"
 )
@@ -198,13 +199,13 @@ WHERE name != 'sqlite_sequence' AND (type = 'table' OR type = 'view');`)
 			foreignKeyDef := fmt.Sprintf("FOREIGN KEY (%s) REFERENCES %s (%s) ON UPDATE %s ON DELETE %s MATCH %s",
 				strings.Join(f.ColumnNames, ", "), f.ForeignTableName, strings.Join(f.ForeignColumnNames, ", "), f.OnUpdate, f.OnDelete, f.Match) // #nosec
 			constraint := &schema.Constraint{
-				Name:             fmt.Sprintf("- (Foreign key ID: %s)", f.ID),
-				Type:             schema.TypeFK,
-				Def:              foreignKeyDef,
-				Table:            &table.Name,
-				Columns:          f.ColumnNames,
-				ReferenceTable:   &f.ForeignTableName,
-				ReferenceColumns: f.ForeignColumnNames,
+				Name:              fmt.Sprintf("- (Foreign key ID: %s)", f.ID),
+				Type:              schema.TypeFK,
+				Def:               foreignKeyDef,
+				Table:             &table.Name,
+				Columns:           f.ColumnNames,
+				ReferencedTable:   &f.ForeignTableName,
+				ReferencedColumns: f.ForeignColumnNames,
 			}
 			relation := &schema.Relation{
 				Table: table,
@@ -395,6 +396,23 @@ SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND tbl_name = ?;
 
 	s.Relations = relations
 
+	// referenced tables of view
+	for _, t := range s.Tables {
+		if t.Type != "view" {
+			continue
+		}
+		for _, rts := range ddl.ParseReferencedTables(t.Def) {
+			rt, err := s.FindTableByName(rts)
+			if err != nil {
+				rt = &schema.Table{
+					Name:     rts,
+					External: true,
+				}
+			}
+			t.ReferencedTables = append(t.ReferencedTables, rt)
+		}
+	}
+
 	return nil
 }
 
diff --git a/output/md/md.go b/output/md/md.go
index 70e21ab..4a21038 100644
--- a/output/md/md.go
+++ b/output/md/md.go
@@ -628,22 +628,34 @@ func (m *Md) makeTableTemplateData(t *schema.Table, adjust bool) map[string]inte
 		triggersData = append(triggersData, data)
 	}
 
+	// Referenced Tables
+	referencedTables := []string{}
+	for _, rt := range t.ReferencedTables {
+		if rt.External {
+			referencedTables = append(referencedTables, rt.Name)
+			continue
+		}
+		referencedTables = append(referencedTables, fmt.Sprintf("[%s](%s%s.md)", rt.Name, m.config.BaseUrl, rt.Name))
+	}
+
 	if adjust {
 		return map[string]interface{}{
-			"Table":       t,
-			"Columns":     adjustTable(columnsData),
-			"Constraints": adjustTable(constraintsData),
-			"Indexes":     adjustTable(indexesData),
-			"Triggers":    adjustTable(triggersData),
+			"Table":            t,
+			"Columns":          adjustTable(columnsData),
+			"Constraints":      adjustTable(constraintsData),
+			"Indexes":          adjustTable(indexesData),
+			"Triggers":         adjustTable(triggersData),
+			"ReferencedTables": referencedTables,
 		}
 	}
 
 	return map[string]interface{}{
-		"Table":       t,
-		"Columns":     columnsData,
-		"Constraints": constraintsData,
-		"Indexes":     indexesData,
-		"Triggers":    triggersData,
+		"Table":            t,
+		"Columns":          columnsData,
+		"Constraints":      constraintsData,
+		"Indexes":          indexesData,
+		"Triggers":         triggersData,
+		"ReferencedTables": referencedTables,
 	}
 }
 
diff --git a/output/md/templates/table.md.tmpl b/output/md/templates/table.md.tmpl
index e2dfb3c..497c71c 100644
--- a/output/md/templates/table.md.tmpl
+++ b/output/md/templates/table.md.tmpl
@@ -15,6 +15,11 @@
 ```
 
 </details>
+{{- end }}{{ $len := len .ReferencedTables }}{{ if ne $len 0 }}
+
+## {{ "Referenced Tables" | lookup }}
+{{ range $rt := .ReferencedTables }}
+- {{ $rt }}{{ end }}
 {{- end }}
 {{- if ne (len .Table.Labels) 0 }}
 
diff --git a/sample/adjust/public.post_comment_stars.md b/sample/adjust/public.post_comment_stars.md
index 8636599..7216664 100644
--- a/sample/adjust/public.post_comment_stars.md
+++ b/sample/adjust/public.post_comment_stars.md
@@ -21,6 +21,12 @@ CREATE MATERIALIZED VIEW post_comment_stars AS (
 
 </details>
 
+## Referenced Tables
+
+- [public.comments](public.comments.md)
+- [public.comment_stars](public.comment_stars.md)
+- [public.users](public.users.md)
+
 ## Columns
 
 | Name              | Type                        | Default | Nullable | Children | Parents | Comment |
diff --git a/sample/adjust/public.post_comments.md b/sample/adjust/public.post_comments.md
index b7dc260..191e90d 100644
--- a/sample/adjust/public.post_comments.md
+++ b/sample/adjust/public.post_comments.md
@@ -25,6 +25,12 @@ CREATE VIEW post_comments AS (
 
 </details>
 
+## Referenced Tables
+
+- [public.posts](public.posts.md)
+- [public.comments](public.comments.md)
+- [public.users](public.users.md)
+
 ## Columns
 
 | Name         | Type                        | Default | Nullable | Children | Parents | Comment                 |
diff --git a/sample/detect_relations/post_comments.md b/sample/detect_relations/post_comments.md
index 8d22646..b839ce6 100644
--- a/sample/detect_relations/post_comments.md
+++ b/sample/detect_relations/post_comments.md
@@ -13,6 +13,12 @@ CREATE VIEW post_comments AS (select `c`.`id` AS `id`,`p`.`title` AS `title`,`u2
 
 </details>
 
+## Referenced Tables
+
+- [posts](posts.md)
+- [comments](comments.md)
+- [users](users.md)
+
 ## Columns
 
 | Name | Type | Default | Nullable | Children | Parents | Comment |
diff --git a/sample/dict/post_comments.md b/sample/dict/post_comments.md
index c13c89e..8fcddc4 100644
--- a/sample/dict/post_comments.md
+++ b/sample/dict/post_comments.md
@@ -13,6 +13,12 @@ CREATE VIEW post_comments AS (select `c`.`id` AS `id`,`p`.`title` AS `title`,`u2
 
 </details>
 
+## Referenced Tables
+
+- [posts](posts.md)
+- [comments](comments.md)
+- [users](users.md)
+
 ## カラム一覧
 
 | 名前           | タイプ          | デフォルト値       | Nullable | 子テーブル      | 親テーブル      | コメント                                       |
diff --git a/sample/exclude/post_comments.md b/sample/exclude/post_comments.md
index 16cbd3c..f6558ac 100644
--- a/sample/exclude/post_comments.md
+++ b/sample/exclude/post_comments.md
@@ -13,6 +13,12 @@ CREATE VIEW post_comments AS (select `c`.`id` AS `id`,`p`.`title` AS `title`,`u2
 
 </details>
 
+## Referenced Tables
+
+- [posts](posts.md)
+- [comments](comments.md)
+- [users](users.md)
+
 ## Columns
 
 | Name | Type | Default | Nullable | Children | Parents | Comment |
diff --git a/sample/font/post_comments.md b/sample/font/post_comments.md
index bcb73f7..15da335 100644
--- a/sample/font/post_comments.md
+++ b/sample/font/post_comments.md
@@ -13,6 +13,12 @@ CREATE VIEW post_comments AS (select `c`.`id` AS `id`,`p`.`title` AS `title`,`u2
 
 </details>
 
+## Referenced Tables
+
+- [posts](posts.md)
+- [comments](comments.md)
+- [users](users.md)
+
 ## Columns
 
 | Name | Type | Default | Nullable | Children | Parents | Comment |
diff --git a/sample/mariadb/post_comments.md b/sample/mariadb/post_comments.md
index c142c7d..ecadc01 100644
--- a/sample/mariadb/post_comments.md
+++ b/sample/mariadb/post_comments.md
@@ -13,6 +13,12 @@ CREATE VIEW post_comments AS ((select `c`.`id` AS `id`,`p`.`title` AS `title`,`u
 
 </details>
 
+## Referenced Tables
+
+- [posts](posts.md)
+- [comments](comments.md)
+- [users](users.md)
+
 ## Columns
 
 | Name | Type | Default | Nullable | Children | Parents | Comment |
diff --git a/sample/mssql/post_comments.md b/sample/mssql/post_comments.md
index 4735072..d3a28eb 100644
--- a/sample/mssql/post_comments.md
+++ b/sample/mssql/post_comments.md
@@ -19,6 +19,12 @@ CREATE VIEW post_comments AS (
 
 </details>
 
+## Referenced Tables
+
+- [posts](posts.md)
+- [comments](comments.md)
+- [users](users.md)
+
 ## Columns
 
 | Name | Type | Default | Nullable | Children | Parents | Comment |
diff --git a/sample/mysql/post_comments.md b/sample/mysql/post_comments.md
index 16cbd3c..f6558ac 100644
--- a/sample/mysql/post_comments.md
+++ b/sample/mysql/post_comments.md
@@ -13,6 +13,12 @@ CREATE VIEW post_comments AS (select `c`.`id` AS `id`,`p`.`title` AS `title`,`u2
 
 </details>
 
+## Referenced Tables
+
+- [posts](posts.md)
+- [comments](comments.md)
+- [users](users.md)
+
 ## Columns
 
 | Name | Type | Default | Nullable | Children | Parents | Comment |
diff --git a/sample/mysql56/post_comments.md b/sample/mysql56/post_comments.md
index d94e11e..baed244 100644
--- a/sample/mysql56/post_comments.md
+++ b/sample/mysql56/post_comments.md
@@ -13,6 +13,12 @@ CREATE VIEW post_comments AS ((select `c`.`id` AS `id`,`p`.`title` AS `title`,`u
 
 </details>
 
+## Referenced Tables
+
+- [posts](posts.md)
+- [comments](comments.md)
+- [users](users.md)
+
 ## Columns
 
 | Name | Type | Default | Nullable | Children | Parents | Comment |
diff --git a/sample/png/post_comments.md b/sample/png/post_comments.md
index bcb73f7..15da335 100644
--- a/sample/png/post_comments.md
+++ b/sample/png/post_comments.md
@@ -13,6 +13,12 @@ CREATE VIEW post_comments AS (select `c`.`id` AS `id`,`p`.`title` AS `title`,`u2
 
 </details>
 
+## Referenced Tables
+
+- [posts](posts.md)
+- [comments](comments.md)
+- [users](users.md)
+
 ## Columns
 
 | Name | Type | Default | Nullable | Children | Parents | Comment |
diff --git a/sample/postgres/public.post_comment_stars.md b/sample/postgres/public.post_comment_stars.md
index 6567d46..13e965d 100644
--- a/sample/postgres/public.post_comment_stars.md
+++ b/sample/postgres/public.post_comment_stars.md
@@ -21,6 +21,12 @@ CREATE MATERIALIZED VIEW post_comment_stars AS (
 
 </details>
 
+## Referenced Tables
+
+- [public.comments](public.comments.md)
+- [public.comment_stars](public.comment_stars.md)
+- [public.users](public.users.md)
+
 ## Columns
 
 | Name | Type | Default | Nullable | Children | Parents | Comment |
diff --git a/sample/postgres/public.post_comments.md b/sample/postgres/public.post_comments.md
index 035a44e..8ea572f 100644
--- a/sample/postgres/public.post_comments.md
+++ b/sample/postgres/public.post_comments.md
@@ -25,6 +25,12 @@ CREATE VIEW post_comments AS (
 
 </details>
 
+## Referenced Tables
+
+- [public.posts](public.posts.md)
+- [public.comments](public.comments.md)
+- [public.users](public.users.md)
+
 ## Columns
 
 | Name | Type | Default | Nullable | Children | Parents | Comment |
diff --git a/sample/postgres95/public.post_comment_stars.md b/sample/postgres95/public.post_comment_stars.md
index 6567d46..13e965d 100644
--- a/sample/postgres95/public.post_comment_stars.md
+++ b/sample/postgres95/public.post_comment_stars.md
@@ -21,6 +21,12 @@ CREATE MATERIALIZED VIEW post_comment_stars AS (
 
 </details>
 
+## Referenced Tables
+
+- [public.comments](public.comments.md)
+- [public.comment_stars](public.comment_stars.md)
+- [public.users](public.users.md)
+
 ## Columns
 
 | Name | Type | Default | Nullable | Children | Parents | Comment |
diff --git a/sample/postgres95/public.post_comments.md b/sample/postgres95/public.post_comments.md
index 035a44e..8ea572f 100644
--- a/sample/postgres95/public.post_comments.md
+++ b/sample/postgres95/public.post_comments.md
@@ -25,6 +25,12 @@ CREATE VIEW post_comments AS (
 
 </details>
 
+## Referenced Tables
+
+- [public.posts](public.posts.md)
+- [public.comments](public.comments.md)
+- [public.users](public.users.md)
+
 ## Columns
 
 | Name | Type | Default | Nullable | Children | Parents | Comment |
diff --git a/sample/sqlite/post_comments.md b/sample/sqlite/post_comments.md
index 800a108..2a19f29 100644
--- a/sample/sqlite/post_comments.md
+++ b/sample/sqlite/post_comments.md
@@ -18,6 +18,12 @@ CREATE VIEW post_comments AS
 
 </details>
 
+## Referenced Tables
+
+- [posts](posts.md)
+- [comments](comments.md)
+- [users](users.md)
+
 ## Columns
 
 | Name | Type | Default | Nullable | Children | Parents | Comment |
diff --git a/schema/json.go b/schema/json.go
index 874dee1..192e630 100644
--- a/schema/json.go
+++ b/schema/json.go
@@ -60,26 +60,33 @@ func (t Table) MarshalJSON() ([]byte, error) {
 		t.Triggers = []*Trigger{}
 	}
 
+	referencedTables := []string{}
+	for _, rt := range t.ReferencedTables {
+		referencedTables = append(referencedTables, rt.Name)
+	}
+
 	return json.Marshal(&struct {
-		Name        string        `json:"name"`
-		Type        string        `json:"type"`
-		Comment     string        `json:"comment"`
-		Columns     []*Column     `json:"columns"`
-		Indexes     []*Index      `json:"indexes"`
-		Constraints []*Constraint `json:"constraints"`
-		Triggers    []*Trigger    `json:"triggers"`
-		Def         string        `json:"def"`
-		Labels      Labels        `json:"labels,omitempty"`
+		Name             string        `json:"name"`
+		Type             string        `json:"type"`
+		Comment          string        `json:"comment"`
+		Columns          []*Column     `json:"columns"`
+		Indexes          []*Index      `json:"indexes"`
+		Constraints      []*Constraint `json:"constraints"`
+		Triggers         []*Trigger    `json:"triggers"`
+		Def              string        `json:"def"`
+		Labels           Labels        `json:"labels,omitempty"`
+		ReferencedTables []string      `json:"referenced_tables,omitempty"`
 	}{
-		Name:        t.Name,
-		Type:        t.Type,
-		Comment:     t.Comment,
-		Columns:     t.Columns,
-		Indexes:     t.Indexes,
-		Constraints: t.Constraints,
-		Triggers:    t.Triggers,
-		Def:         t.Def,
-		Labels:      t.Labels,
+		Name:             t.Name,
+		Type:             t.Type,
+		Comment:          t.Comment,
+		Columns:          t.Columns,
+		Indexes:          t.Indexes,
+		Constraints:      t.Constraints,
+		Triggers:         t.Triggers,
+		Def:              t.Def,
+		Labels:           t.Labels,
+		ReferencedTables: referencedTables,
 	})
 }
 
@@ -155,6 +162,41 @@ func (r Relation) MarshalJSON() ([]byte, error) {
 	})
 }
 
+// UnmarshalJSON unmarshal JSON to schema.Table
+func (t *Table) UnmarshalJSON(data []byte) error {
+	s := struct {
+		Name             string        `json:"name"`
+		Type             string        `json:"type"`
+		Comment          string        `json:"comment"`
+		Columns          []*Column     `json:"columns"`
+		Indexes          []*Index      `json:"indexes"`
+		Constraints      []*Constraint `json:"constraints"`
+		Triggers         []*Trigger    `json:"triggers"`
+		Def              string        `json:"def"`
+		Labels           Labels        `json:"labels,omitempty"`
+		ReferencedTables []string      `json:"referenced_tables,omitempty"`
+	}{}
+	err := json.Unmarshal(data, &s)
+	if err != nil {
+		return err
+	}
+	t.Name = s.Name
+	t.Type = s.Type
+	t.Comment = s.Comment
+	t.Columns = s.Columns
+	t.Indexes = s.Indexes
+	t.Constraints = s.Constraints
+	t.Triggers = s.Triggers
+	t.Def = s.Def
+	t.Labels = s.Labels
+	for _, rt := range s.ReferencedTables {
+		t.ReferencedTables = append(t.ReferencedTables, &Table{
+			Name: rt,
+		})
+	}
+	return nil
+}
+
 // UnmarshalJSON unmarshal JSON to schema.Column
 func (c *Column) UnmarshalJSON(data []byte) error {
 	s := struct {
@@ -186,7 +228,7 @@ func (c *Column) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
-// UnmarshalJSON unmarshal JSON to schema.Column
+// UnmarshalJSON unmarshal JSON to schema.Relation
 func (r *Relation) UnmarshalJSON(data []byte) error {
 	s := struct {
 		Table         string   `json:"table"`
diff --git a/schema/schema.go b/schema/schema.go
index fb6b9ba..4d1664a 100644
--- a/schema/schema.go
+++ b/schema/schema.go
@@ -41,14 +41,14 @@ type Index struct {
 
 // Constraint is the struct for database constraint
 type Constraint struct {
-	Name             string   `json:"name"`
-	Type             string   `json:"type"`
-	Def              string   `json:"def"`
-	Table            *string  `json:"table"`
-	ReferenceTable   *string  `json:"reference_table" yaml:"referenceTable"`
-	Columns          []string `json:"columns"`
-	ReferenceColumns []string `json:"reference_columns" yaml:"referenceColumns"`
-	Comment          string   `json:"comment"`
+	Name              string   `json:"name"`
+	Type              string   `json:"type"`
+	Def               string   `json:"def"`
+	Table             *string  `json:"table"`
+	ReferencedTable   *string  `json:"referenced_table" yaml:"referencedTable"`
+	Columns           []string `json:"columns"`
+	ReferencedColumns []string `json:"referenced_columns" yaml:"referencedColumns"`
+	Comment           string   `json:"comment"`
 }
 
 // Trigger is the struct for database trigger
@@ -72,15 +72,17 @@ type Column struct {
 
 // Table is the struct for database table
 type Table struct {
-	Name        string        `json:"name"`
-	Type        string        `json:"type"`
-	Comment     string        `json:"comment"`
-	Columns     []*Column     `json:"columns"`
-	Indexes     []*Index      `json:"indexes"`
-	Constraints []*Constraint `json:"constraints"`
-	Triggers    []*Trigger    `json:"triggers"`
-	Def         string        `json:"def"`
-	Labels      Labels        `json:"labels,omitempty"`
+	Name             string        `json:"name"`
+	Type             string        `json:"type"`
+	Comment          string        `json:"comment"`
+	Columns          []*Column     `json:"columns"`
+	Indexes          []*Index      `json:"indexes"`
+	Constraints      []*Constraint `json:"constraints"`
+	Triggers         []*Trigger    `json:"triggers"`
+	Def              string        `json:"def"`
+	Labels           Labels        `json:"labels,omitempty"`
+	ReferencedTables []*Table      `json:"referenced_tables,omitempty" yaml:"referencedTables,omitempty"`
+	External         bool          `json:"-"` // Table external to the schema
 }
 
 // Relation is the struct for table relation
@@ -284,6 +286,14 @@ func (s *Schema) Repair() error {
 		if len(t.Triggers) == 0 {
 			t.Triggers = nil
 		}
+		for i, rt := range t.ReferencedTables {
+			tt, err := s.FindTableByName(rt.Name)
+			if err != nil {
+				rt.External = true
+				tt = rt
+			}
+			t.ReferencedTables[i] = tt
+		}
 	}
 
 	for _, r := range s.Relations {
@@ -314,6 +324,7 @@ func (s *Schema) Repair() error {
 		}
 		r.ParentTable = pt
 	}
+
 	return nil
 }
 
diff --git a/schema/yaml.go b/schema/yaml.go
index d6139c0..796f01e 100644
--- a/schema/yaml.go
+++ b/schema/yaml.go
@@ -1,6 +1,53 @@
 package schema
 
-import "github.com/goccy/go-yaml"
+import (
+	"github.com/goccy/go-yaml"
+)
+
+// MarshalYAML return custom JSON byte
+func (t Table) MarshalYAML() ([]byte, error) {
+	if len(t.Columns) == 0 {
+		t.Columns = []*Column{}
+	}
+	if len(t.Indexes) == 0 {
+		t.Indexes = []*Index{}
+	}
+	if len(t.Constraints) == 0 {
+		t.Constraints = []*Constraint{}
+	}
+	if len(t.Triggers) == 0 {
+		t.Triggers = []*Trigger{}
+	}
+
+	referencedTables := []string{}
+	for _, rt := range t.ReferencedTables {
+		referencedTables = append(referencedTables, rt.Name)
+	}
+
+	return yaml.Marshal(&struct {
+		Name             string        `yaml:"name"`
+		Type             string        `yaml:"type"`
+		Comment          string        `yaml:"comment"`
+		Columns          []*Column     `yaml:"columns"`
+		Indexes          []*Index      `yaml:"indexes"`
+		Constraints      []*Constraint `yaml:"constraints"`
+		Triggers         []*Trigger    `yaml:"triggers"`
+		Def              string        `yaml:"def"`
+		Labels           Labels        `yaml:"labels,omitempty"`
+		ReferencedTables []string      `yaml:"referencedTables,omitempty"`
+	}{
+		Name:             t.Name,
+		Type:             t.Type,
+		Comment:          t.Comment,
+		Columns:          t.Columns,
+		Indexes:          t.Indexes,
+		Constraints:      t.Constraints,
+		Triggers:         t.Triggers,
+		Def:              t.Def,
+		Labels:           t.Labels,
+		ReferencedTables: referencedTables,
+	})
+}
 
 // MarshalYAML return custom YAML byte
 func (c Column) MarshalYAML() ([]byte, error) {
@@ -10,7 +57,7 @@ func (c Column) MarshalYAML() ([]byte, error) {
 			Type            string      `yaml:"type"`
 			Nullable        bool        `yaml:"nullable"`
 			Default         string      `yaml:"default"`
-			ExtraDef        string      `json:"extraDef,omitempty"`
+			ExtraDef        string      `yaml:"extraDef,omitempty"`
 			Comment         string      `yaml:"comment"`
 			ParentRelations []*Relation `yaml:"-"`
 			ChildRelations  []*Relation `yaml:"-"`
@@ -30,7 +77,7 @@ func (c Column) MarshalYAML() ([]byte, error) {
 		Type            string      `yaml:"type"`
 		Nullable        bool        `yaml:"nullable"`
 		Default         *string     `yaml:"default"`
-		ExtraDef        string      `json:"extraDef,omitempty"`
+		ExtraDef        string      `yaml:"extraDef,omitempty"`
 		Comment         string      `yaml:"comment"`
 		ParentRelations []*Relation `yaml:"-"`
 		ChildRelations  []*Relation `yaml:"-"`
@@ -74,6 +121,41 @@ func (r Relation) MarshalYAML() ([]byte, error) {
 	})
 }
 
+// UnMarshalYAML unmarshal JSON to schema.Table
+func (t *Table) UnMarshalYAML(data []byte) error {
+	s := struct {
+		Name             string        `yaml:"name"`
+		Type             string        `yaml:"type"`
+		Comment          string        `yaml:"comment"`
+		Columns          []*Column     `yaml:"columns"`
+		Indexes          []*Index      `yaml:"indexes"`
+		Constraints      []*Constraint `yaml:"constraints"`
+		Triggers         []*Trigger    `yaml:"triggers"`
+		Def              string        `yaml:"def"`
+		Labels           Labels        `yaml:"labels,omitempty"`
+		ReferencedTables []string      `yaml:"referencedTables,omitempty"`
+	}{}
+	err := yaml.Unmarshal(data, &s)
+	if err != nil {
+		return err
+	}
+	t.Name = s.Name
+	t.Type = s.Type
+	t.Comment = s.Comment
+	t.Columns = s.Columns
+	t.Indexes = s.Indexes
+	t.Constraints = s.Constraints
+	t.Triggers = s.Triggers
+	t.Def = s.Def
+	t.Labels = s.Labels
+	for _, rt := range s.ReferencedTables {
+		t.ReferencedTables = append(t.ReferencedTables, &Table{
+			Name: rt,
+		})
+	}
+	return nil
+}
+
 // UnmarshalYAML unmarshal YAML to schema.Column
 func (c *Column) UnmarshalYAML(data []byte) error {
 	s := struct {
@@ -82,7 +164,7 @@ func (c *Column) UnmarshalYAML(data []byte) error {
 		Nullable        bool        `yaml:"nullable"`
 		Default         *string     `yaml:"default"`
 		Comment         string      `yaml:"comment"`
-		ExtraDef        string      `json:"extraDef,omitempty"`
+		ExtraDef        string      `yaml:"extraDef,omitempty"`
 		ParentRelations []*Relation `yaml:"-"`
 		ChildRelations  []*Relation `yaml:"-"`
 	}{}


Test patch:
diff --git a/config/lint_test.go b/config/lint_test.go
index eb2dc7e..ff51f98 100644
--- a/config/lint_test.go
+++ b/config/lint_test.go
@@ -564,21 +564,21 @@ func newTestSchema() *schema.Schema {
 
 	ta.Constraints = []*schema.Constraint{
 		&schema.Constraint{
-			Name:             "a1_b1_fk",
-			Type:             schema.TypeFK,
-			Table:            &ta.Name,
-			ReferenceTable:   &tb.Name,
-			Columns:          []string{"column_a1"},
-			ReferenceColumns: []string{"column_b1"},
-			Comment:          "a1_b1_fk comment",
+			Name:              "a1_b1_fk",
+			Type:              schema.TypeFK,
+			Table:             &ta.Name,
+			ReferencedTable:   &tb.Name,
+			Columns:           []string{"column_a1"},
+			ReferencedColumns: []string{"column_b1"},
+			Comment:           "a1_b1_fk comment",
 		},
 		&schema.Constraint{
-			Name:           "a1_unique",
-			Type:           "UNIQUE",
-			Table:          &ta.Name,
-			ReferenceTable: nil,
-			Columns:        []string{"column_a1"},
-			Comment:        "", // empty comment
+			Name:            "a1_unique",
+			Type:            "UNIQUE",
+			Table:           &ta.Name,
+			ReferencedTable: nil,
+			Columns:         []string{"column_a1"},
+			Comment:         "", // empty comment
 		},
 	}
 
diff --git a/coverage/coverage_test.go b/coverage/coverage_test.go
index 4e6ba25..6bc080c 100644
--- a/coverage/coverage_test.go
+++ b/coverage/coverage_test.go
@@ -140,19 +140,19 @@ func newTestSchema() *schema.Schema {
 
 	ta.Constraints = []*schema.Constraint{
 		&schema.Constraint{
-			Name:             "a1_b1_fk",
-			Type:             schema.TypeFK,
-			Table:            &ta.Name,
-			ReferenceTable:   &tb.Name,
-			Columns:          []string{"column_a1"},
-			ReferenceColumns: []string{"column_b1"},
+			Name:              "a1_b1_fk",
+			Type:              schema.TypeFK,
+			Table:             &ta.Name,
+			ReferencedTable:   &tb.Name,
+			Columns:           []string{"column_a1"},
+			ReferencedColumns: []string{"column_b1"},
 		},
 		&schema.Constraint{
-			Name:           "a1_unique",
-			Type:           "UNIQUE",
-			Table:          &ta.Name,
-			ReferenceTable: nil,
-			Columns:        []string{"column_a1"},
+			Name:            "a1_unique",
+			Type:            "UNIQUE",
+			Table:           &ta.Name,
+			ReferencedTable: nil,
+			Columns:         []string{"column_a1"},
 		},
 	}
 
diff --git a/ddl/ddl_test.go b/ddl/ddl_test.go
new file mode 100644
index 0000000..5f98dbf
--- /dev/null
+++ b/ddl/ddl_test.go
@@ -0,0 +1,57 @@
+package ddl
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestParseReferencedTables(t *testing.T) {
+	tests := []struct {
+		in   string
+		want []string
+	}{
+		{
+			`SELECT * FROM posts`,
+			[]string{"posts"},
+		},
+		{
+			`CREATE VIEW post_comments AS (
+ SELECT c.id,
+    p.title,
+    u.username AS post_user,
+    c.comment,
+    u2.username AS comment_user,
+    c.created,
+    c.updated
+   FROM (((posts p
+     LEFT JOIN comments c ON ((p.id = c.post_id)))
+     LEFT JOIN users u ON ((u.id = p.user_id)))
+     LEFT JOIN users u2 ON ((u2.id = c.user_id)))
+)`,
+			[]string{"posts", "comments", "users"},
+		},
+		{
+			"CREATE VIEW post_comments AS (select `c`.`id` AS `id`,`p`.`title` AS `title`,`u2`.`username` AS `post_user`,`c`.`comment` AS `comment`,`u2`.`username` AS `comment_user`,`c`.`created` AS `created`,`c`.`updated` AS `updated` from (((`testdb`.`posts` `p` left join `testdb`.`comments` `c` on((`p`.`id` = `c`.`post_id`))) left join `testdb`.`users` `u` on((`u`.`id` = `p`.`user_id`))) left join `testdb`.`users` `u2` on((`u2`.`id` = `c`.`user_id`))))",
+			[]string{"testdb.posts", "testdb.comments", "testdb.users"},
+		},
+		{
+			"CREATE VIEW k1low_posts AS (SELECT * FROM posts WHERE user_id IN (SELECT id FROM users WHERE email = 'k1lowxb@gmail.com'))",
+			[]string{"posts", "users"},
+		},
+		{
+			`CREATE VIEW k1low_posts AS (
+WITH k1low AS (SELECT * FROM users WHERE email = 'k1lowxb@gmail.com')
+SELECT * FROM posts WHERE user_id IN (SELECT id FROM k1low)
+)
+`,
+			[]string{"users", "posts"},
+		},
+	}
+	for _, tt := range tests {
+		got := ParseReferencedTables(tt.in)
+		if diff := cmp.Diff(got, tt.want, nil); diff != "" {
+			t.Errorf("%s", diff)
+		}
+	}
+}
diff --git a/schema/schema_test.go b/schema/schema_test.go
index 3608e3a..31e5d06 100644
--- a/schema/schema_test.go
+++ b/schema/schema_test.go
@@ -68,22 +68,22 @@ func TestTable_FindConstrainsByColumnName(t *testing.T) {
 	}
 	table.Constraints = []*Constraint{
 		&Constraint{
-			Name:             "PRIMARY",
-			Type:             "PRIMARY KEY",
-			Def:              "PRIMARY KEY(a)",
-			ReferenceTable:   nil,
-			Table:            &table.Name,
-			Columns:          []string{"a"},
-			ReferenceColumns: []string{},
+			Name:              "PRIMARY",
+			Type:              "PRIMARY KEY",
+			Def:               "PRIMARY KEY(a)",
+			ReferencedTable:   nil,
+			Table:             &table.Name,
+			Columns:           []string{"a"},
+			ReferencedColumns: []string{},
 		},
 		&Constraint{
-			Name:             "UNIQUE",
-			Type:             "UNIQUE",
-			Def:              "UNIQUE KEY a (b)",
-			ReferenceTable:   nil,
-			Table:            &table.Name,
-			Columns:          []string{"b"},
-			ReferenceColumns: []string{},
+			Name:              "UNIQUE",
+			Type:              "UNIQUE",
+			Def:               "UNIQUE KEY a (b)",
+			ReferencedTable:   nil,
+			Table:             &table.Name,
+			Columns:           []string{"b"},
+			ReferencedColumns: []string{},
 		},
 	}
 
diff --git a/testdata/testdb.json b/testdata/testdb.json
index 757c157..9eb6445 100644
--- a/testdata/testdb.json
+++ b/testdata/testdb.json
@@ -81,11 +81,11 @@
           "type": "CHECK",
           "def": "CHECK ((char_length((username)::text) \u003e 4))",
           "table": "public.users",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "username"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         },
@@ -94,11 +94,11 @@
           "type": "PRIMARY KEY",
           "def": "PRIMARY KEY (id)",
           "table": "public.users",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         },
@@ -107,11 +107,11 @@
           "type": "UNIQUE",
           "def": "UNIQUE (username)",
           "table": "public.users",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "username"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         },
@@ -120,11 +120,11 @@
           "type": "UNIQUE",
           "def": "UNIQUE (email)",
           "table": "public.users",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "email"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         }
@@ -187,11 +187,11 @@
           "type": "FOREIGN KEY",
           "def": "FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE",
           "table": "public.user_options",
-          "reference_table": "users",
+          "referenced_table": "users",
           "columns": [
             "user_id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             "id"
           ]
         },
@@ -200,11 +200,11 @@
           "type": "PRIMARY KEY",
           "def": "PRIMARY KEY (user_id)",
           "table": "public.user_options",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "user_id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         }
@@ -307,7 +307,7 @@
           "type": "TRIGGER",
           "def": "CREATE CONSTRAINT TRIGGER update_posts_updated AFTER INSERT OR UPDATE ON public.posts NOT DEFERRABLE INITIALLY IMMEDIATE FOR EACH ROW EXECUTE PROCEDURE update_updated()",
           "table": "public.posts",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "updated",
             "tableoid",
@@ -324,7 +324,7 @@
             "labels",
             "created"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         },
@@ -333,11 +333,11 @@
           "type": "FOREIGN KEY",
           "def": "FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE",
           "table": "public.posts",
-          "reference_table": "users",
+          "referenced_table": "users",
           "columns": [
             "user_id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             "id"
           ]
         },
@@ -346,11 +346,11 @@
           "type": "PRIMARY KEY",
           "def": "PRIMARY KEY (id)",
           "table": "public.posts",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         },
@@ -359,12 +359,12 @@
           "type": "UNIQUE",
           "def": "UNIQUE (user_id, title)",
           "table": "public.posts",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "user_id",
             "title"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         }
@@ -459,11 +459,11 @@
           "type": "FOREIGN KEY",
           "def": "FOREIGN KEY (user_id) REFERENCES users(id)",
           "table": "public.comments",
-          "reference_table": "users",
+          "referenced_table": "users",
           "columns": [
             "user_id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             "id"
           ]
         },
@@ -472,11 +472,11 @@
           "type": "FOREIGN KEY",
           "def": "FOREIGN KEY (post_id) REFERENCES posts(id)",
           "table": "public.comments",
-          "reference_table": "posts",
+          "referenced_table": "posts",
           "columns": [
             "post_id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             "id"
           ]
         },
@@ -485,11 +485,11 @@
           "type": "PRIMARY KEY",
           "def": "PRIMARY KEY (id)",
           "table": "public.comments",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         },
@@ -498,12 +498,12 @@
           "type": "UNIQUE",
           "def": "UNIQUE (post_id, user_id)",
           "table": "public.comments",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "post_id",
             "user_id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         }
@@ -577,11 +577,11 @@
           "type": "FOREIGN KEY",
           "def": "FOREIGN KEY (comment_user_id) REFERENCES users(id)",
           "table": "public.comment_stars",
-          "reference_table": "users",
+          "referenced_table": "users",
           "columns": [
             "comment_user_id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             "id"
           ]
         },
@@ -590,14 +590,14 @@
           "type": "FOREIGN KEY",
           "def": "FOREIGN KEY (comment_post_id, comment_user_id) REFERENCES comments(post_id, user_id)",
           "table": "public.comment_stars",
-          "reference_table": "comments",
+          "referenced_table": "comments",
           "columns": [
             "comment_post_id",
             "comment_post_id",
             "comment_user_id",
             "comment_user_id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             "post_id",
             "user_id",
             "post_id",
@@ -609,13 +609,13 @@
           "type": "UNIQUE",
           "def": "UNIQUE (user_id, comment_post_id, comment_user_id)",
           "table": "public.comment_stars",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "user_id",
             "comment_post_id",
             "comment_user_id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         }
@@ -779,11 +779,11 @@
           "type": "UNIQUE",
           "def": "UNIQUE (id)",
           "table": "public.CamelizeTable",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         }
@@ -841,11 +841,11 @@
           "type": "FOREIGN KEY",
           "def": "FOREIGN KEY (\"CamelizeTableId\") REFERENCES \"CamelizeTable\"(id) ON DELETE CASCADE",
           "table": "public.hyphen-table",
-          "reference_table": "CamelizeTable",
+          "referenced_table": "CamelizeTable",
           "columns": [
             "CamelizeTableId"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             "id"
           ]
         },
@@ -854,11 +854,11 @@
           "type": "UNIQUE",
           "def": "UNIQUE (\"hyphen-column\")",
           "table": "public.hyphen-table",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "hyphen-column"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         }
@@ -930,11 +930,11 @@
           "type": "FOREIGN KEY",
           "def": "FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE",
           "table": "administrator.blogs",
-          "reference_table": "users",
+          "referenced_table": "users",
           "columns": [
             "user_id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             "id"
           ]
         },
@@ -943,11 +943,11 @@
           "type": "PRIMARY KEY",
           "def": "PRIMARY KEY (id)",
           "table": "administrator.blogs",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         }
@@ -1012,11 +1012,11 @@
           "type": "PRIMARY KEY",
           "def": "PRIMARY KEY (id)",
           "table": "backup.blogs",
-          "reference_table": "",
+          "referenced_table": "",
           "columns": [
             "id"
           ],
-          "reference_columns": [
+          "referenced_columns": [
             ""
           ]
         }