Repository: ariga/atlas
Pull request #692 (2022-04-10 08:13:26)

sql/schema: add support for controlling the amount/depth of inspection


Patch:
diff --git a/go.sum b/go.sum
index c7b6f9f0..80b7c2cb 100644
--- a/go.sum
+++ b/go.sum
@@ -99,7 +99,6 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
 github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
 github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -334,7 +333,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
@@ -810,7 +808,6 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
diff --git a/sql/internal/sqlx/sqlx.go b/sql/internal/sqlx/sqlx.go
index 7b601397..d3183cca 100644
--- a/sql/internal/sqlx/sqlx.go
+++ b/sql/internal/sqlx/sqlx.go
@@ -76,6 +76,20 @@ func ScanNullBool(rows *sql.Rows) (sql.NullBool, error) {
 	return b, ScanOne(rows, &b)
 }
 
+// ScanStrings scans sql.Rows into a slice of strings and closes it at the end.
+func ScanStrings(rows *sql.Rows) ([]string, error) {
+	defer rows.Close()
+	var vs []string
+	for rows.Next() {
+		var v string
+		if err := rows.Scan(&v); err != nil {
+			return nil, err
+		}
+		vs = append(vs, v)
+	}
+	return vs, nil
+}
+
 // SchemaFKs scans the rows and adds the foreign-key to the schema table.
 // Reference elements are added as stubs and should be linked manually by the
 // caller.
@@ -168,20 +182,6 @@ func LinkSchemaTables(schemas []*schema.Schema) {
 	}
 }
 
-// ScanStrings scans sql.Rows into a slice of strings and closes it at the end.
-func ScanStrings(rows *sql.Rows) ([]string, error) {
-	defer rows.Close()
-	var vs []string
-	for rows.Next() {
-		var v string
-		if err := rows.Scan(&v); err != nil {
-			return nil, err
-		}
-		vs = append(vs, v)
-	}
-	return vs, nil
-}
-
 // ValuesEqual checks if the 2 string slices are equal (including their order).
 func ValuesEqual(v1, v2 []string) bool {
 	if len(v1) != len(v2) {
@@ -195,24 +195,20 @@ func ValuesEqual(v1, v2 []string) bool {
 	return true
 }
 
-// VersionPermutations returns permutations of the dialect version sorted
-// from coarse to fine grained. For example:
-//
-//   VersionPermutations("mysql", "1.2.3") => ["mysql", "mysql 1", "mysql 1.2", "mysql 1.2.3"]
-//
-// VersionPermutations will split the version number by ".", " ", "-" or "_", and rejoin them
-// with ".". The output slice can be used by drivers to generate a list of permutations
-// for searching for relevant overrides in schema element specs.
-func VersionPermutations(dialect, version string) []string {
-	parts := strings.FieldsFunc(version, func(r rune) bool {
-		return r == '.' || r == ' ' || r == '-' || r == '_'
-	})
-	names := []string{dialect}
-	for i := range parts {
-		version := strings.Join(parts[0:i+1], ".")
-		names = append(names, dialect+" "+version)
+// ModeInspectSchema returns the InspectMode or its default.
+func ModeInspectSchema(o *schema.InspectOptions) schema.InspectMode {
+	if o == nil || o.Mode == 0 {
+		return schema.InspectSchemas | schema.InspectTables
+	}
+	return o.Mode
+}
+
+// ModeInspectRealm returns the InspectMode or its default.
+func ModeInspectRealm(o *schema.InspectRealmOption) schema.InspectMode {
+	if o == nil || o.Mode == 0 {
+		return schema.InspectSchemas | schema.InspectTables
 	}
-	return names
+	return o.Mode
 }
 
 // A Builder provides a syntactic sugar API for writing SQL statements.
diff --git a/sql/mysql/inspect.go b/sql/mysql/inspect.go
index b49e710a..fd687913 100644
--- a/sql/mysql/inspect.go
+++ b/sql/mysql/inspect.go
@@ -28,7 +28,7 @@ func (i *inspect) InspectRealm(ctx context.Context, opts *schema.InspectRealmOpt
 		return nil, err
 	}
 	r := schema.NewRealm(schemas...).SetCharset(i.charset).SetCollation(i.collate)
-	if len(schemas) == 0 {
+	if len(schemas) == 0 || !sqlx.ModeInspectRealm(opts).Is(schema.InspectTables) {
 		return r, nil
 	}
 	if err := i.inspectTables(ctx, r, nil); err != nil {
@@ -52,10 +52,12 @@ func (i *inspect) InspectSchema(ctx context.Context, name string, opts *schema.I
 		return nil, fmt.Errorf("mysql: %d schemas were found for %q", n, name)
 	}
 	r := schema.NewRealm(schemas...).SetCharset(i.charset).SetCollation(i.collate)
-	if err := i.inspectTables(ctx, r, opts); err != nil {
-		return nil, err
+	if sqlx.ModeInspectSchema(opts).Is(schema.InspectTables) {
+		if err := i.inspectTables(ctx, r, opts); err != nil {
+			return nil, err
+		}
+		sqlx.LinkSchemaTables(schemas)
 	}
-	sqlx.LinkSchemaTables(schemas)
 	return r.Schemas[0], nil
 }
 
diff --git a/sql/postgres/inspect.go b/sql/postgres/inspect.go
index cd03f4da..44d408cd 100644
--- a/sql/postgres/inspect.go
+++ b/sql/postgres/inspect.go
@@ -28,7 +28,7 @@ func (i *inspect) InspectRealm(ctx context.Context, opts *schema.InspectRealmOpt
 	}
 	r := schema.NewRealm(schemas...).SetCollation(i.collate)
 	r.Attrs = append(r.Attrs, &CType{V: i.ctype})
-	if len(schemas) == 0 {
+	if len(schemas) == 0 || !sqlx.ModeInspectRealm(opts).Is(schema.InspectTables) {
 		return r, nil
 	}
 	if err := i.inspectTables(ctx, r, nil); err != nil {
@@ -53,10 +53,12 @@ func (i *inspect) InspectSchema(ctx context.Context, name string, opts *schema.I
 	}
 	r := schema.NewRealm(schemas...).SetCollation(i.collate)
 	r.Attrs = append(r.Attrs, &CType{V: i.ctype})
-	if err := i.inspectTables(ctx, r, opts); err != nil {
-		return nil, err
+	if sqlx.ModeInspectSchema(opts).Is(schema.InspectTables) {
+		if err := i.inspectTables(ctx, r, opts); err != nil {
+			return nil, err
+		}
+		sqlx.LinkSchemaTables(schemas)
 	}
-	sqlx.LinkSchemaTables(schemas)
 	return r.Schemas[0], nil
 }
 
diff --git a/sql/schema/inspect.go b/sql/schema/inspect.go
index 930cb6b7..26f56453 100644
--- a/sql/schema/inspect.go
+++ b/sql/schema/inspect.go
@@ -33,21 +33,38 @@ type ExecQuerier interface {
 	ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
 }
 
+// An InspectMode controls the amount and depth of information returned on inspection.
+type InspectMode uint
+
+const (
+	// InspectSchemas enables schema inspection.
+	InspectSchemas InspectMode = 1 << iota
+
+	// InspectTables enables schema tables inspection including
+	// all its child resources (e.g. columns or indexes).
+	InspectTables
+)
+
+// Is reports whether the given mode is enabled.
+func (m InspectMode) Is(i InspectMode) bool { return m&i != 0 }
+
 type (
 	// InspectOptions describes options for Inspector.
 	InspectOptions struct {
+		// Mode defines the amount of information returned by InspectSchema.
+		// If zero, InspectSchema inspects whole resources in the schema.
+		Mode InspectMode
+
 		// Tables to inspect. Empty means all tables in the schema.
 		Tables []string
 	}
 
-	// InspectTableOptions describes options for TableInspector.
-	InspectTableOptions struct {
-		// Schema defines an optional schema to inspect.
-		Schema string
-	}
-
 	// InspectRealmOption describes options for RealmInspector.
 	InspectRealmOption struct {
+		// Mode defines the amount of information returned by InspectRealm.
+		// If zero, InspectRealm inspects all schemas and their child resources.
+		Mode InspectMode
+
 		// Schemas to inspect. Empty means all tables in the schema.
 		Schemas []string
 	}
@@ -57,7 +74,7 @@ type (
 	Inspector interface {
 		// InspectSchema returns the schema description by its name. An empty name means the
 		// "attached schema" (e.g. SCHEMA() in MySQL or CURRENT_SCHEMA() in PostgreSQL).
-		// A NotExistError error is returned if the schema does not exists in the database.
+		// A NotExistError error is returned if the schema does not exist in the database.
 		InspectSchema(ctx context.Context, name string, opts *InspectOptions) (*Schema, error)
 
 		// InspectRealm returns the description of the connected database.
diff --git a/sql/sqlite/inspect.go b/sql/sqlite/inspect.go
index 17446399..c862b319 100644
--- a/sql/sqlite/inspect.go
+++ b/sql/sqlite/inspect.go
@@ -17,7 +17,7 @@ import (
 	"ariga.io/atlas/sql/schema"
 )
 
-// A diff provides a PostgreSQL implementation for schema.Inspector.
+// A diff provides an SQLite implementation for schema.Inspector.
 type inspect struct{ conn }
 
 var _ schema.Inspector = (*inspect)(nil)
@@ -32,6 +32,9 @@ func (i *inspect) InspectRealm(ctx context.Context, opts *schema.InspectRealmOpt
 		return nil, fmt.Errorf("sqlite: multiple database files are not supported by the driver. got: %d", len(schemas))
 	}
 	realm := &schema.Realm{Schemas: schemas}
+	if !sqlx.ModeInspectRealm(opts).Is(schema.InspectTables) {
+		return realm, nil
+	}
 	for _, s := range schemas {
 		tables, err := i.tables(ctx, nil)
 		if err != nil {
@@ -68,11 +71,15 @@ func (i *inspect) InspectSchema(ctx context.Context, name string, opts *schema.I
 			Err: fmt.Errorf("sqlite: schema %q was not found", name),
 		}
 	}
+	s := schemas[0]
+	s.Realm = schema.NewRealm(schemas...)
+	if !sqlx.ModeInspectSchema(opts).Is(schema.InspectTables) {
+		return s, nil
+	}
 	tables, err := i.tables(ctx, opts)
 	if err != nil {
 		return nil, err
 	}
-	s := schemas[0]
 	for _, t := range tables {
 		t.Schema = s
 		t, err := i.inspectTable(ctx, t)
@@ -82,7 +89,6 @@ func (i *inspect) InspectSchema(ctx context.Context, name string, opts *schema.I
 		s.Tables = append(s.Tables, t)
 	}
 	sqlx.LinkSchemaTables(schemas)
-	s.Realm = &schema.Realm{Schemas: schemas}
 	return s, nil
 }
 


Test patch:
diff --git a/sql/internal/sqlx/sqlx_test.go b/sql/internal/sqlx/sqlx_test.go
index 0b7922d6..b839d112 100644
--- a/sql/internal/sqlx/sqlx_test.go
+++ b/sql/internal/sqlx/sqlx_test.go
@@ -13,12 +13,32 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func TestVersionNames(t *testing.T) {
-	names := VersionPermutations("mysql", "1.2.3")
-	require.EqualValues(t, []string{"mysql", "mysql 1", "mysql 1.2", "mysql 1.2.3"}, names)
+func TestModeInspectRealm(t *testing.T) {
+	m := ModeInspectRealm(nil)
+	require.True(t, m.Is(schema.InspectSchemas))
+	require.True(t, m.Is(schema.InspectTables))
 
-	names = VersionPermutations("postgres", "11.3 nightly")
-	require.EqualValues(t, []string{"postgres", "postgres 11", "postgres 11.3", "postgres 11.3.nightly"}, names)
+	m = ModeInspectRealm(&schema.InspectRealmOption{})
+	require.True(t, m.Is(schema.InspectSchemas))
+	require.True(t, m.Is(schema.InspectTables))
+
+	m = ModeInspectRealm(&schema.InspectRealmOption{Mode: schema.InspectSchemas})
+	require.True(t, m.Is(schema.InspectSchemas))
+	require.False(t, m.Is(schema.InspectTables))
+}
+
+func TestModeInspectSchema(t *testing.T) {
+	m := ModeInspectSchema(nil)
+	require.True(t, m.Is(schema.InspectSchemas))
+	require.True(t, m.Is(schema.InspectTables))
+
+	m = ModeInspectSchema(&schema.InspectOptions{})
+	require.True(t, m.Is(schema.InspectSchemas))
+	require.True(t, m.Is(schema.InspectTables))
+
+	m = ModeInspectSchema(&schema.InspectOptions{Mode: schema.InspectSchemas})
+	require.True(t, m.Is(schema.InspectSchemas))
+	require.False(t, m.Is(schema.InspectTables))
 }
 
 func TestBuilder(t *testing.T) {
diff --git a/sql/mysql/inspect_test.go b/sql/mysql/inspect_test.go
index b66763df..f048f7a3 100644
--- a/sql/mysql/inspect_test.go
+++ b/sql/mysql/inspect_test.go
@@ -894,6 +894,49 @@ func TestDriver_Realm(t *testing.T) {
 	}(), realm)
 }
 
+func TestInspectMode_InspectRealm(t *testing.T) {
+	db, m, err := sqlmock.New()
+	require.NoError(t, err)
+	mk := mock{m}
+	mk.version("8.0.13")
+	mk.ExpectQuery(sqltest.Escape(schemasQuery)).
+		WillReturnRows(sqltest.Rows(`
++-------------+----------------------------+------------------------+
+| SCHEMA_NAME | DEFAULT_CHARACTER_SET_NAME | DEFAULT_COLLATION_NAME |
++-------------+----------------------------+------------------------+
+| test        | latin1                     | lain1_ci               |
++-------------+----------------------------+------------------------+
+`))
+	drv, err := Open(db)
+	require.NoError(t, err)
+	realm, err := drv.InspectRealm(context.Background(), &schema.InspectRealmOption{Mode: schema.InspectSchemas})
+	require.NoError(t, err)
+	require.EqualValues(t, func() *schema.Realm {
+		r := &schema.Realm{
+			Schemas: []*schema.Schema{
+				{
+					Name: "test",
+					Attrs: []schema.Attr{
+						&schema.Charset{V: "latin1"},
+						&schema.Collation{V: "lain1_ci"},
+					},
+				},
+			},
+			// Server default configuration.
+			Attrs: []schema.Attr{
+				&schema.Charset{
+					V: "utf8",
+				},
+				&schema.Collation{
+					V: "utf8_general_ci",
+				},
+			},
+		}
+		r.Schemas[0].Realm = r
+		return r
+	}(), realm)
+}
+
 type mock struct {
 	sqlmock.Sqlmock
 }
diff --git a/sql/postgres/inspect_test.go b/sql/postgres/inspect_test.go
index 773f4ee0..d6ee15a7 100644
--- a/sql/postgres/inspect_test.go
+++ b/sql/postgres/inspect_test.go
@@ -28,7 +28,6 @@ var (
 func TestDriver_InspectTable(t *testing.T) {
 	tests := []struct {
 		name   string
-		opts   *schema.InspectTableOptions
 		before func(mock)
 		expect func(*require.Assertions, *schema.Table, error)
 	}{
@@ -450,6 +449,47 @@ public
 	}(), realm)
 }
 
+func TestInspectMode_InspectRealm(t *testing.T) {
+	db, m, err := sqlmock.New()
+	require.NoError(t, err)
+	mk := mock{m}
+	mk.version("130000")
+	mk.ExpectQuery(sqltest.Escape(schemasQuery)).
+		WillReturnRows(sqltest.Rows(`
+   schema_name
+--------------------
+test
+public
+`))
+	drv, err := Open(db)
+	realm, err := drv.InspectRealm(context.Background(), &schema.InspectRealmOption{Mode: schema.InspectSchemas})
+	require.NoError(t, err)
+	require.EqualValues(t, func() *schema.Realm {
+		r := &schema.Realm{
+			Schemas: []*schema.Schema{
+				{
+					Name: "test",
+				},
+				{
+					Name: "public",
+				},
+			},
+			// Server default configuration.
+			Attrs: []schema.Attr{
+				&schema.Collation{
+					V: "en_US.utf8",
+				},
+				&CType{
+					V: "en_US.utf8",
+				},
+			},
+		}
+		r.Schemas[0].Realm = r
+		r.Schemas[1].Realm = r
+		return r
+	}(), realm)
+}
+
 type mock struct {
 	sqlmock.Sqlmock
 }
diff --git a/sql/sqlite/inspect_test.go b/sql/sqlite/inspect_test.go
index 24042d58..31fc949d 100644
--- a/sql/sqlite/inspect_test.go
+++ b/sql/sqlite/inspect_test.go
@@ -19,7 +19,6 @@ import (
 func TestDriver_InspectTable(t *testing.T) {
 	tests := []struct {
 		name   string
-		opts   *schema.InspectTableOptions
 		before func(mock)
 		expect func(*require.Assertions, *schema.Table, error)
 	}{