Repository: schemahero/schemahero
Pull request #882 (2023-02-21 17:51:23)

Find databases from tables and views
Right now controller cannot find databases for view migrations because it's trying to lookup migration -> table -> database, but there is no table resource.  This updates controller to lookup views as well.


Patch:
diff --git a/pkg/client/schemaheroclientset/clientset.go b/pkg/client/schemaheroclientset/clientset.go
index bd274725..2e230ac7 100644
--- a/pkg/client/schemaheroclientset/clientset.go
+++ b/pkg/client/schemaheroclientset/clientset.go
@@ -20,6 +20,7 @@ package schemaheroclientset
 
 import (
 	"fmt"
+	"net/http"
 
 	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
 	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
@@ -63,26 +64,49 @@ func (c *Clientset) Discovery() discovery.DiscoveryInterface {
 // NewForConfig creates a new Clientset for the given config.
 // If config's RateLimiter is not set and QPS and Burst are acceptable,
 // NewForConfig will generate a rate-limiter in configShallowCopy.
+// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
+// where httpClient was generated with rest.HTTPClientFor(c).
 func NewForConfig(c *rest.Config) (*Clientset, error) {
 	configShallowCopy := *c
+
+	if configShallowCopy.UserAgent == "" {
+		configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()
+	}
+
+	// share the transport between all clients
+	httpClient, err := rest.HTTPClientFor(&configShallowCopy)
+	if err != nil {
+		return nil, err
+	}
+
+	return NewForConfigAndClient(&configShallowCopy, httpClient)
+}
+
+// NewForConfigAndClient creates a new Clientset for the given config and http client.
+// Note the http client provided takes precedence over the configured transport values.
+// If config's RateLimiter is not set and QPS and Burst are acceptable,
+// NewForConfigAndClient will generate a rate-limiter in configShallowCopy.
+func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) {
+	configShallowCopy := *c
 	if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
 		if configShallowCopy.Burst <= 0 {
 			return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0")
 		}
 		configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
 	}
+
 	var cs Clientset
 	var err error
-	cs.databasesV1alpha4, err = databasesv1alpha4.NewForConfig(&configShallowCopy)
+	cs.databasesV1alpha4, err = databasesv1alpha4.NewForConfigAndClient(&configShallowCopy, httpClient)
 	if err != nil {
 		return nil, err
 	}
-	cs.schemasV1alpha4, err = schemasv1alpha4.NewForConfig(&configShallowCopy)
+	cs.schemasV1alpha4, err = schemasv1alpha4.NewForConfigAndClient(&configShallowCopy, httpClient)
 	if err != nil {
 		return nil, err
 	}
 
-	cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)
+	cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)
 	if err != nil {
 		return nil, err
 	}
@@ -92,12 +116,11 @@ func NewForConfig(c *rest.Config) (*Clientset, error) {
 // NewForConfigOrDie creates a new Clientset for the given config and
 // panics if there is an error in the config.
 func NewForConfigOrDie(c *rest.Config) *Clientset {
-	var cs Clientset
-	cs.databasesV1alpha4 = databasesv1alpha4.NewForConfigOrDie(c)
-	cs.schemasV1alpha4 = schemasv1alpha4.NewForConfigOrDie(c)
-
-	cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)
-	return &cs
+	cs, err := NewForConfig(c)
+	if err != nil {
+		panic(err)
+	}
+	return cs
 }
 
 // New creates a new Clientset for the given RESTClient.
diff --git a/pkg/client/schemaheroclientset/fake/register.go b/pkg/client/schemaheroclientset/fake/register.go
index 0776dd5a..b39edb93 100644
--- a/pkg/client/schemaheroclientset/fake/register.go
+++ b/pkg/client/schemaheroclientset/fake/register.go
@@ -39,14 +39,14 @@ var localSchemeBuilder = runtime.SchemeBuilder{
 // AddToScheme adds all types of this clientset into the given scheme. This allows composition
 // of clientsets, like in:
 //
-//   import (
-//     "k8s.io/client-go/kubernetes"
-//     clientsetscheme "k8s.io/client-go/kubernetes/scheme"
-//     aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
-//   )
+//	import (
+//	  "k8s.io/client-go/kubernetes"
+//	  clientsetscheme "k8s.io/client-go/kubernetes/scheme"
+//	  aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
+//	)
 //
-//   kclientset, _ := kubernetes.NewForConfig(c)
-//   _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
+//	kclientset, _ := kubernetes.NewForConfig(c)
+//	_ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
 //
 // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
 // correctly.
diff --git a/pkg/client/schemaheroclientset/scheme/register.go b/pkg/client/schemaheroclientset/scheme/register.go
index 25749105..41bbe307 100644
--- a/pkg/client/schemaheroclientset/scheme/register.go
+++ b/pkg/client/schemaheroclientset/scheme/register.go
@@ -39,14 +39,14 @@ var localSchemeBuilder = runtime.SchemeBuilder{
 // AddToScheme adds all types of this clientset into the given scheme. This allows composition
 // of clientsets, like in:
 //
-//   import (
-//     "k8s.io/client-go/kubernetes"
-//     clientsetscheme "k8s.io/client-go/kubernetes/scheme"
-//     aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
-//   )
+//	import (
+//	  "k8s.io/client-go/kubernetes"
+//	  clientsetscheme "k8s.io/client-go/kubernetes/scheme"
+//	  aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
+//	)
 //
-//   kclientset, _ := kubernetes.NewForConfig(c)
-//   _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
+//	kclientset, _ := kubernetes.NewForConfig(c)
+//	_ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
 //
 // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
 // correctly.
diff --git a/pkg/client/schemaheroclientset/typed/databases/v1alpha4/databases_client.go b/pkg/client/schemaheroclientset/typed/databases/v1alpha4/databases_client.go
index 649838b8..5c5bad43 100644
--- a/pkg/client/schemaheroclientset/typed/databases/v1alpha4/databases_client.go
+++ b/pkg/client/schemaheroclientset/typed/databases/v1alpha4/databases_client.go
@@ -19,6 +19,8 @@ limitations under the License.
 package v1alpha4
 
 import (
+	"net/http"
+
 	v1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/client/schemaheroclientset/scheme"
 	rest "k8s.io/client-go/rest"
@@ -39,12 +41,28 @@ func (c *DatabasesV1alpha4Client) Databases(namespace string) DatabaseInterface
 }
 
 // NewForConfig creates a new DatabasesV1alpha4Client for the given config.
+// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
+// where httpClient was generated with rest.HTTPClientFor(c).
 func NewForConfig(c *rest.Config) (*DatabasesV1alpha4Client, error) {
 	config := *c
 	if err := setConfigDefaults(&config); err != nil {
 		return nil, err
 	}
-	client, err := rest.RESTClientFor(&config)
+	httpClient, err := rest.HTTPClientFor(&config)
+	if err != nil {
+		return nil, err
+	}
+	return NewForConfigAndClient(&config, httpClient)
+}
+
+// NewForConfigAndClient creates a new DatabasesV1alpha4Client for the given config and http client.
+// Note the http client provided takes precedence over the configured transport values.
+func NewForConfigAndClient(c *rest.Config, h *http.Client) (*DatabasesV1alpha4Client, error) {
+	config := *c
+	if err := setConfigDefaults(&config); err != nil {
+		return nil, err
+	}
+	client, err := rest.RESTClientForConfigAndClient(&config, h)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/client/schemaheroclientset/typed/databases/v1alpha4/fake/fake_database.go b/pkg/client/schemaheroclientset/typed/databases/v1alpha4/fake/fake_database.go
index 738c627f..556d2cfe 100644
--- a/pkg/client/schemaheroclientset/typed/databases/v1alpha4/fake/fake_database.go
+++ b/pkg/client/schemaheroclientset/typed/databases/v1alpha4/fake/fake_database.go
@@ -117,7 +117,7 @@ func (c *FakeDatabases) UpdateStatus(ctx context.Context, database *v1alpha4.Dat
 // Delete takes name of the database and deletes it. Returns an error if one occurs.
 func (c *FakeDatabases) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(databasesResource, c.ns, name), &v1alpha4.Database{})
+		Invokes(testing.NewDeleteActionWithOptions(databasesResource, c.ns, name, opts), &v1alpha4.Database{})
 
 	return err
 }
diff --git a/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_datatype.go b/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_datatype.go
index e9cacfbd..ad4f00bb 100644
--- a/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_datatype.go
+++ b/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_datatype.go
@@ -117,7 +117,7 @@ func (c *FakeDataTypes) UpdateStatus(ctx context.Context, dataType *v1alpha4.Dat
 // Delete takes name of the dataType and deletes it. Returns an error if one occurs.
 func (c *FakeDataTypes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(datatypesResource, c.ns, name), &v1alpha4.DataType{})
+		Invokes(testing.NewDeleteActionWithOptions(datatypesResource, c.ns, name, opts), &v1alpha4.DataType{})
 
 	return err
 }
diff --git a/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_migration.go b/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_migration.go
index 604b24f6..6565583d 100644
--- a/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_migration.go
+++ b/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_migration.go
@@ -117,7 +117,7 @@ func (c *FakeMigrations) UpdateStatus(ctx context.Context, migration *v1alpha4.M
 // Delete takes name of the migration and deletes it. Returns an error if one occurs.
 func (c *FakeMigrations) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(migrationsResource, c.ns, name), &v1alpha4.Migration{})
+		Invokes(testing.NewDeleteActionWithOptions(migrationsResource, c.ns, name, opts), &v1alpha4.Migration{})
 
 	return err
 }
diff --git a/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_table.go b/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_table.go
index 03211dd9..42a6be7e 100644
--- a/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_table.go
+++ b/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_table.go
@@ -117,7 +117,7 @@ func (c *FakeTables) UpdateStatus(ctx context.Context, table *v1alpha4.Table, op
 // Delete takes name of the table and deletes it. Returns an error if one occurs.
 func (c *FakeTables) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(tablesResource, c.ns, name), &v1alpha4.Table{})
+		Invokes(testing.NewDeleteActionWithOptions(tablesResource, c.ns, name, opts), &v1alpha4.Table{})
 
 	return err
 }
diff --git a/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_view.go b/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_view.go
index a9a9ebb6..2c1d83b4 100644
--- a/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_view.go
+++ b/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/fake/fake_view.go
@@ -117,7 +117,7 @@ func (c *FakeViews) UpdateStatus(ctx context.Context, view *v1alpha4.View, opts
 // Delete takes name of the view and deletes it. Returns an error if one occurs.
 func (c *FakeViews) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
 	_, err := c.Fake.
-		Invokes(testing.NewDeleteAction(viewsResource, c.ns, name), &v1alpha4.View{})
+		Invokes(testing.NewDeleteActionWithOptions(viewsResource, c.ns, name, opts), &v1alpha4.View{})
 
 	return err
 }
diff --git a/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/schemas_client.go b/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/schemas_client.go
index 7feda6a6..e3998a9d 100644
--- a/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/schemas_client.go
+++ b/pkg/client/schemaheroclientset/typed/schemas/v1alpha4/schemas_client.go
@@ -19,6 +19,8 @@ limitations under the License.
 package v1alpha4
 
 import (
+	"net/http"
+
 	v1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/client/schemaheroclientset/scheme"
 	rest "k8s.io/client-go/rest"
@@ -54,12 +56,28 @@ func (c *SchemasV1alpha4Client) Views(namespace string) ViewInterface {
 }
 
 // NewForConfig creates a new SchemasV1alpha4Client for the given config.
+// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
+// where httpClient was generated with rest.HTTPClientFor(c).
 func NewForConfig(c *rest.Config) (*SchemasV1alpha4Client, error) {
 	config := *c
 	if err := setConfigDefaults(&config); err != nil {
 		return nil, err
 	}
-	client, err := rest.RESTClientFor(&config)
+	httpClient, err := rest.HTTPClientFor(&config)
+	if err != nil {
+		return nil, err
+	}
+	return NewForConfigAndClient(&config, httpClient)
+}
+
+// NewForConfigAndClient creates a new SchemasV1alpha4Client for the given config and http client.
+// Note the http client provided takes precedence over the configured transport values.
+func NewForConfigAndClient(c *rest.Config, h *http.Client) (*SchemasV1alpha4Client, error) {
+	config := *c
+	if err := setConfigDefaults(&config); err != nil {
+		return nil, err
+	}
+	client, err := rest.RESTClientForConfigAndClient(&config, h)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/controller/migration/lookup.go b/pkg/controller/migration/lookup.go
index 21d579f1..8cfec423 100644
--- a/pkg/controller/migration/lookup.go
+++ b/pkg/controller/migration/lookup.go
@@ -13,11 +13,11 @@ import (
 )
 
 var (
-	schemasClient   *schemasclientv1alpha4.SchemasV1alpha4Client
-	databasesClient *databasesclientv1alpha4.DatabasesV1alpha4Client
+	schemasClient   schemasclientv1alpha4.SchemasV1alpha4Interface
+	databasesClient databasesclientv1alpha4.DatabasesV1alpha4Interface
 )
 
-func getSchemasClient() (*schemasclientv1alpha4.SchemasV1alpha4Client, error) {
+func getSchemasClient() (schemasclientv1alpha4.SchemasV1alpha4Interface, error) {
 	if schemasClient != nil {
 		return schemasClient, nil
 	}
@@ -35,7 +35,7 @@ func getSchemasClient() (*schemasclientv1alpha4.SchemasV1alpha4Client, error) {
 	return schemasClient, nil
 }
 
-func getDatabasesClient() (*databasesclientv1alpha4.DatabasesV1alpha4Client, error) {
+func getDatabasesClient() (databasesclientv1alpha4.DatabasesV1alpha4Interface, error) {
 	if databasesClient != nil {
 		return databasesClient, nil
 	}
@@ -80,3 +80,31 @@ func DatabaseFromTable(ctx context.Context, table *schemasv1alpha4.Table) (*data
 
 	return database, nil
 }
+
+func ViewFromMigration(ctx context.Context, migration *schemasv1alpha4.Migration) (*schemasv1alpha4.View, error) {
+	schemasClient, err := getSchemasClient()
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get schemas client")
+	}
+
+	table, err := schemasClient.Views(migration.Spec.TableNamespace).Get(ctx, migration.Spec.TableName, metav1.GetOptions{})
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get view")
+	}
+
+	return table, nil
+}
+
+func DatabaseFromView(ctx context.Context, view *schemasv1alpha4.View) (*databasesv1alpha4.Database, error) {
+	databasesClient, err := getDatabasesClient()
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get databases client")
+	}
+
+	database, err := databasesClient.Databases(view.Namespace).Get(ctx, view.Spec.Database, metav1.GetOptions{})
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get database")
+	}
+
+	return database, nil
+}
diff --git a/pkg/controller/migration/reconcile_migration.go b/pkg/controller/migration/reconcile_migration.go
index f7f673da..2312181f 100644
--- a/pkg/controller/migration/reconcile_migration.go
+++ b/pkg/controller/migration/reconcile_migration.go
@@ -5,6 +5,7 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
+	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
 	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database"
 	"github.com/schemahero/schemahero/pkg/logger"
@@ -14,65 +15,89 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 )
 
-func (r *ReconcileMigration) reconcileMigration(ctx context.Context, instance *schemasv1alpha4.Migration) (reconcile.Result, error) {
-	logger.Debug("reconciling migration",
-		zap.String("name", instance.Name),
-		zap.String("tableName", instance.Spec.TableName))
+func (r *ReconcileMigration) reconcileMigration(ctx context.Context, migration *schemasv1alpha4.Migration) (reconcile.Result, error) {
+	logger.Debug("checking migration",
+		zap.String("name", migration.Name),
+		zap.String("tableName", migration.Spec.TableName))
 
-	if instance.Status.ApprovedAt > 0 && instance.Status.ExecutedAt == 0 {
-		tableInstance, err := TableFromMigration(ctx, instance)
-		if err != nil {
-			return reconcile.Result{}, nil
-		}
+	if migration.Status.ApprovedAt == 0 && migration.Status.ExecutedAt > 0 {
+		logger.Debug("migration not yet approved or already executed",
+			zap.String("name", migration.Name),
+			zap.String("tableName", migration.Spec.TableName))
+		return reconcile.Result{}, nil
+	}
 
-		databaseInstance, err := DatabaseFromTable(ctx, tableInstance)
-		if err != nil {
-			return reconcile.Result{}, nil
-		}
+	databaseInstance, err := getDatabaseFromMigration(ctx, migration)
+	if err != nil {
+		return reconcile.Result{}, errors.Wrapf(err, "failed to get database from migration %s", migration.Name)
+	}
 
-		driver, connectionURI, err := databaseInstance.GetConnection(ctx)
-		if err != nil {
-			return reconcile.Result{}, errors.Wrap(err, "failed to get connection details for database")
-		}
+	driver, connectionURI, err := databaseInstance.GetConnection(ctx)
+	if err != nil {
+		return reconcile.Result{}, errors.Wrap(err, "failed to get connection details for database")
+	}
 
-		db := database.Database{
-			Driver: driver,
-			URI:    connectionURI,
-		}
+	db := database.Database{
+		Driver: driver,
+		URI:    connectionURI,
+	}
 
-		statements := db.GetStatementsFromDDL(instance.Spec.GeneratedDDL)
+	statements := db.GetStatementsFromDDL(migration.Spec.GeneratedDDL)
 
-		if err := db.ApplySync(statements); err != nil {
-			return reconcile.Result{}, errors.Wrap(err, "failed to apply statements")
-		}
+	if err := db.ApplySync(statements); err != nil {
+		return reconcile.Result{}, errors.Wrap(err, "failed to apply statements")
+	}
 
-		// update the status to applied
-		instance.Status.ExecutedAt = time.Now().Unix()
-		instance.Status.Phase = schemasv1alpha4.Executed
-		err = r.Update(context.Background(), instance)
+	// update the status to applied
+	migration.Status.ExecutedAt = time.Now().Unix()
+	migration.Status.Phase = schemasv1alpha4.Executed
+	err = r.Update(context.Background(), migration)
 
-		if err != nil {
-			if kuberneteserrors.IsConflict(err) {
-				updatedInstance := &schemasv1alpha4.Migration{}
-				err := r.Get(context.Background(), types.NamespacedName{
-					Name:      instance.Name,
-					Namespace: instance.Namespace,
-				}, updatedInstance)
-				if err != nil {
-					return reconcile.Result{}, errors.Wrap(err, "failed to get updated instance")
-				}
+	if err != nil {
+		if kuberneteserrors.IsConflict(err) {
+			updatedMigration := &schemasv1alpha4.Migration{}
+			err := r.Get(context.Background(), types.NamespacedName{
+				Name:      migration.Name,
+				Namespace: migration.Namespace,
+			}, updatedMigration)
+			if err != nil {
+				return reconcile.Result{}, errors.Wrap(err, "failed to get updated instance")
+			}
 
-				updatedInstance.Status.ExecutedAt = time.Now().Unix()
-				instance.Status.Phase = schemasv1alpha4.Executed
-				if err := r.Update(context.Background(), updatedInstance); err != nil {
-					return reconcile.Result{}, errors.Wrap(err, "failed to update")
-				}
-			} else {
+			updatedMigration.Status.ExecutedAt = time.Now().Unix()
+			migration.Status.Phase = schemasv1alpha4.Executed
+			if err := r.Update(context.Background(), updatedMigration); err != nil {
 				return reconcile.Result{}, errors.Wrap(err, "failed to update")
 			}
+		} else {
+			return reconcile.Result{}, errors.Wrap(err, "failed to update")
 		}
-
 	}
 
 	return reconcile.Result{}, nil
 }
+
+func getDatabaseFromMigration(ctx context.Context, migration *schemasv1alpha4.Migration) (*databasesv1alpha4.Database, error) {
+	table, err := TableFromMigration(ctx, migration)
+	if err != nil {
+		if !kuberneteserrors.IsNotFound(err) {
+			return nil, errors.Wrap(err, "failed to get table")
+		}
+	} else {
+		database, err := DatabaseFromTable(ctx, table)
+		if err != nil {
+			return nil, errors.Wrapf(err, "failed to get database from table %s", table.Name)
+		}
+		return database, nil
+	}
+
+	view, err := ViewFromMigration(ctx, migration)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get view")
+	}
+	database, err := DatabaseFromView(ctx, view)
+	if err != nil {
+		return nil, errors.Wrapf(err, "failed to get database from view %s", view.Name)
+	}
+	return database, nil
+}
diff --git a/pkg/installer/assets/v1/databases.schemahero.io_databases.yaml b/pkg/installer/assets/v1/databases.schemahero.io_databases.yaml
index ba701596..7fc92355 100644
--- a/pkg/installer/assets/v1/databases.schemahero.io_databases.yaml
+++ b/pkg/installer/assets/v1/databases.schemahero.io_databases.yaml
@@ -2818,6 +2818,737 @@ spec:
                     required:
                     - dsn
                     type: object
+                  timescaledb:
+                    properties:
+                      dbname:
+                        properties:
+                          value:
+                            type: string
+                          valueFrom:
+                            properties:
+                              secretKeyRef:
+                                properties:
+                                  key:
+                                    type: string
+                                  name:
+                                    type: string
+                                required:
+                                - key
+                                - name
+                                type: object
+                              ssm:
+                                properties:
+                                  accessKeyId:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  name:
+                                    type: string
+                                  region:
+                                    type: string
+                                  secretAccessKey:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  withDecryption:
+                                    type: boolean
+                                required:
+                                - name
+                                type: object
+                              vault:
+                                properties:
+                                  agentInject:
+                                    type: boolean
+                                  connectionTemplate:
+                                    type: string
+                                  endpoint:
+                                    type: string
+                                  kubernetesAuthEndpoint:
+                                    type: string
+                                  role:
+                                    type: string
+                                  secret:
+                                    type: string
+                                  serviceAccount:
+                                    type: string
+                                  serviceAccountNamespace:
+                                    type: string
+                                required:
+                                - role
+                                - secret
+                                type: object
+                            type: object
+                        type: object
+                      host:
+                        properties:
+                          value:
+                            type: string
+                          valueFrom:
+                            properties:
+                              secretKeyRef:
+                                properties:
+                                  key:
+                                    type: string
+                                  name:
+                                    type: string
+                                required:
+                                - key
+                                - name
+                                type: object
+                              ssm:
+                                properties:
+                                  accessKeyId:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  name:
+                                    type: string
+                                  region:
+                                    type: string
+                                  secretAccessKey:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  withDecryption:
+                                    type: boolean
+                                required:
+                                - name
+                                type: object
+                              vault:
+                                properties:
+                                  agentInject:
+                                    type: boolean
+                                  connectionTemplate:
+                                    type: string
+                                  endpoint:
+                                    type: string
+                                  kubernetesAuthEndpoint:
+                                    type: string
+                                  role:
+                                    type: string
+                                  secret:
+                                    type: string
+                                  serviceAccount:
+                                    type: string
+                                  serviceAccountNamespace:
+                                    type: string
+                                required:
+                                - role
+                                - secret
+                                type: object
+                            type: object
+                        type: object
+                      password:
+                        properties:
+                          value:
+                            type: string
+                          valueFrom:
+                            properties:
+                              secretKeyRef:
+                                properties:
+                                  key:
+                                    type: string
+                                  name:
+                                    type: string
+                                required:
+                                - key
+                                - name
+                                type: object
+                              ssm:
+                                properties:
+                                  accessKeyId:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  name:
+                                    type: string
+                                  region:
+                                    type: string
+                                  secretAccessKey:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  withDecryption:
+                                    type: boolean
+                                required:
+                                - name
+                                type: object
+                              vault:
+                                properties:
+                                  agentInject:
+                                    type: boolean
+                                  connectionTemplate:
+                                    type: string
+                                  endpoint:
+                                    type: string
+                                  kubernetesAuthEndpoint:
+                                    type: string
+                                  role:
+                                    type: string
+                                  secret:
+                                    type: string
+                                  serviceAccount:
+                                    type: string
+                                  serviceAccountNamespace:
+                                    type: string
+                                required:
+                                - role
+                                - secret
+                                type: object
+                            type: object
+                        type: object
+                      port:
+                        properties:
+                          value:
+                            type: string
+                          valueFrom:
+                            properties:
+                              secretKeyRef:
+                                properties:
+                                  key:
+                                    type: string
+                                  name:
+                                    type: string
+                                required:
+                                - key
+                                - name
+                                type: object
+                              ssm:
+                                properties:
+                                  accessKeyId:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  name:
+                                    type: string
+                                  region:
+                                    type: string
+                                  secretAccessKey:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  withDecryption:
+                                    type: boolean
+                                required:
+                                - name
+                                type: object
+                              vault:
+                                properties:
+                                  agentInject:
+                                    type: boolean
+                                  connectionTemplate:
+                                    type: string
+                                  endpoint:
+                                    type: string
+                                  kubernetesAuthEndpoint:
+                                    type: string
+                                  role:
+                                    type: string
+                                  secret:
+                                    type: string
+                                  serviceAccount:
+                                    type: string
+                                  serviceAccountNamespace:
+                                    type: string
+                                required:
+                                - role
+                                - secret
+                                type: object
+                            type: object
+                        type: object
+                      schema:
+                        properties:
+                          value:
+                            type: string
+                          valueFrom:
+                            properties:
+                              secretKeyRef:
+                                properties:
+                                  key:
+                                    type: string
+                                  name:
+                                    type: string
+                                required:
+                                - key
+                                - name
+                                type: object
+                              ssm:
+                                properties:
+                                  accessKeyId:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  name:
+                                    type: string
+                                  region:
+                                    type: string
+                                  secretAccessKey:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  withDecryption:
+                                    type: boolean
+                                required:
+                                - name
+                                type: object
+                              vault:
+                                properties:
+                                  agentInject:
+                                    type: boolean
+                                  connectionTemplate:
+                                    type: string
+                                  endpoint:
+                                    type: string
+                                  kubernetesAuthEndpoint:
+                                    type: string
+                                  role:
+                                    type: string
+                                  secret:
+                                    type: string
+                                  serviceAccount:
+                                    type: string
+                                  serviceAccountNamespace:
+                                    type: string
+                                required:
+                                - role
+                                - secret
+                                type: object
+                            type: object
+                        type: object
+                      sslmode:
+                        properties:
+                          value:
+                            type: string
+                          valueFrom:
+                            properties:
+                              secretKeyRef:
+                                properties:
+                                  key:
+                                    type: string
+                                  name:
+                                    type: string
+                                required:
+                                - key
+                                - name
+                                type: object
+                              ssm:
+                                properties:
+                                  accessKeyId:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  name:
+                                    type: string
+                                  region:
+                                    type: string
+                                  secretAccessKey:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  withDecryption:
+                                    type: boolean
+                                required:
+                                - name
+                                type: object
+                              vault:
+                                properties:
+                                  agentInject:
+                                    type: boolean
+                                  connectionTemplate:
+                                    type: string
+                                  endpoint:
+                                    type: string
+                                  kubernetesAuthEndpoint:
+                                    type: string
+                                  role:
+                                    type: string
+                                  secret:
+                                    type: string
+                                  serviceAccount:
+                                    type: string
+                                  serviceAccountNamespace:
+                                    type: string
+                                required:
+                                - role
+                                - secret
+                                type: object
+                            type: object
+                        type: object
+                      uri:
+                        properties:
+                          value:
+                            type: string
+                          valueFrom:
+                            properties:
+                              secretKeyRef:
+                                properties:
+                                  key:
+                                    type: string
+                                  name:
+                                    type: string
+                                required:
+                                - key
+                                - name
+                                type: object
+                              ssm:
+                                properties:
+                                  accessKeyId:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  name:
+                                    type: string
+                                  region:
+                                    type: string
+                                  secretAccessKey:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  withDecryption:
+                                    type: boolean
+                                required:
+                                - name
+                                type: object
+                              vault:
+                                properties:
+                                  agentInject:
+                                    type: boolean
+                                  connectionTemplate:
+                                    type: string
+                                  endpoint:
+                                    type: string
+                                  kubernetesAuthEndpoint:
+                                    type: string
+                                  role:
+                                    type: string
+                                  secret:
+                                    type: string
+                                  serviceAccount:
+                                    type: string
+                                  serviceAccountNamespace:
+                                    type: string
+                                required:
+                                - role
+                                - secret
+                                type: object
+                            type: object
+                        type: object
+                      user:
+                        properties:
+                          value:
+                            type: string
+                          valueFrom:
+                            properties:
+                              secretKeyRef:
+                                properties:
+                                  key:
+                                    type: string
+                                  name:
+                                    type: string
+                                required:
+                                - key
+                                - name
+                                type: object
+                              ssm:
+                                properties:
+                                  accessKeyId:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  name:
+                                    type: string
+                                  region:
+                                    type: string
+                                  secretAccessKey:
+                                    properties:
+                                      value:
+                                        type: string
+                                      valueFrom:
+                                        properties:
+                                          secretKeyRef:
+                                            properties:
+                                              key:
+                                                type: string
+                                              name:
+                                                type: string
+                                            required:
+                                            - key
+                                            - name
+                                            type: object
+                                        type: object
+                                    required:
+                                    - value
+                                    type: object
+                                  withDecryption:
+                                    type: boolean
+                                required:
+                                - name
+                                type: object
+                              vault:
+                                properties:
+                                  agentInject:
+                                    type: boolean
+                                  connectionTemplate:
+                                    type: string
+                                  endpoint:
+                                    type: string
+                                  kubernetesAuthEndpoint:
+                                    type: string
+                                  role:
+                                    type: string
+                                  secret:
+                                    type: string
+                                  serviceAccount:
+                                    type: string
+                                  serviceAccountNamespace:
+                                    type: string
+                                required:
+                                - role
+                                - secret
+                                type: object
+                            type: object
+                        type: object
+                    type: object
                 type: object
               deploySeedData:
                 type: boolean
diff --git a/pkg/installer/assets/v1/schemas.schemahero.io_tables.yaml b/pkg/installer/assets/v1/schemas.schemahero.io_tables.yaml
index 4a64e117..ca6d4f8f 100644
--- a/pkg/installer/assets/v1/schemas.schemahero.io_tables.yaml
+++ b/pkg/installer/assets/v1/schemas.schemahero.io_tables.yaml
@@ -586,6 +586,162 @@ spec:
                         items:
                           type: string
                         type: array
+                      strict:
+                        type: boolean
+                    type: object
+                  timescaledb:
+                    properties:
+                      columns:
+                        items:
+                          properties:
+                            attributes:
+                              properties:
+                                autoIncrement:
+                                  type: boolean
+                              type: object
+                            constraints:
+                              properties:
+                                notNull:
+                                  type: boolean
+                              type: object
+                            default:
+                              type: string
+                            name:
+                              type: string
+                            type:
+                              type: string
+                          required:
+                          - name
+                          - type
+                          type: object
+                        type: array
+                      foreignKeys:
+                        items:
+                          properties:
+                            columns:
+                              items:
+                                type: string
+                              type: array
+                            name:
+                              type: string
+                            onDelete:
+                              type: string
+                            references:
+                              properties:
+                                columns:
+                                  items:
+                                    type: string
+                                  type: array
+                                table:
+                                  type: string
+                              required:
+                              - columns
+                              - table
+                              type: object
+                          required:
+                          - columns
+                          - references
+                          type: object
+                        type: array
+                      hypertable:
+                        properties:
+                          associatedSchemaName:
+                            type: string
+                          associatedTablePrefix:
+                            type: string
+                          chunkTimeInterval:
+                            type: string
+                          compression:
+                            properties:
+                              interval:
+                                type: string
+                              segmentBy:
+                                type: string
+                            required:
+                            - interval
+                            - segmentBy
+                            type: object
+                          createDefaultIndexes:
+                            type: boolean
+                          dataNodes:
+                            items:
+                              type: string
+                            type: array
+                          ifNotExists:
+                            type: boolean
+                          migrateData:
+                            type: boolean
+                          numberPartitions:
+                            type: integer
+                          partitioningColumn:
+                            type: string
+                          partitioningFunc:
+                            type: string
+                          replicationFactor:
+                            type: integer
+                          retention:
+                            properties:
+                              interval:
+                                type: string
+                            required:
+                            - interval
+                            type: object
+                          timeColumnName:
+                            type: string
+                          timePartitioningFunc:
+                            type: string
+                        type: object
+                      indexes:
+                        items:
+                          properties:
+                            columns:
+                              items:
+                                type: string
+                              type: array
+                            isUnique:
+                              type: boolean
+                            name:
+                              type: string
+                            type:
+                              type: string
+                          required:
+                          - columns
+                          type: object
+                        type: array
+                      isDeleted:
+                        type: boolean
+                      primaryKey:
+                        items:
+                          type: string
+                        type: array
+                      triggers:
+                        items:
+                          properties:
+                            arguments:
+                              items:
+                                type: string
+                              type: array
+                            condition:
+                              type: string
+                            constraintTrigger:
+                              type: boolean
+                            events:
+                              items:
+                                type: string
+                              type: array
+                            executeProcedure:
+                              type: string
+                            forEachRun:
+                              type: boolean
+                            forEachStatement:
+                              type: boolean
+                            name:
+                              type: string
+                          required:
+                          - events
+                          - executeProcedure
+                          type: object
+                        type: array
                     type: object
                 type: object
               seedData:
diff --git a/pkg/installer/assets/v1/schemas.schemahero.io_views.yaml b/pkg/installer/assets/v1/schemas.schemahero.io_views.yaml
new file mode 100644
index 00000000..19aee101
--- /dev/null
+++ b/pkg/installer/assets/v1/schemas.schemahero.io_views.yaml
@@ -0,0 +1,111 @@
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.7.0
+  creationTimestamp: null
+  name: views.schemas.schemahero.io
+spec:
+  group: schemas.schemahero.io
+  names:
+    kind: View
+    listKind: ViewList
+    plural: views
+    singular: view
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .metadata.namespace
+      name: Namespace
+      priority: 1
+      type: string
+    - jsonPath: .spec.name
+      name: View
+      type: string
+    - jsonPath: .spec.database
+      name: Database
+      type: string
+    - jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
+    name: v1alpha4
+    schema:
+      openAPIV3Schema:
+        description: View is the Schema for the view API
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: ViewSpec defines the desired state of View
+            properties:
+              database:
+                type: string
+              name:
+                type: string
+              requires:
+                items:
+                  type: string
+                type: array
+              schema:
+                properties:
+                  cassandra:
+                    type: object
+                  cockroachdb:
+                    type: object
+                  mysql:
+                    type: object
+                  postgres:
+                    type: object
+                  rqlite:
+                    type: object
+                  sqlite:
+                    type: object
+                  timescaledb:
+                    properties:
+                      isContinuousAggregate:
+                        type: boolean
+                      isDeleted:
+                        type: boolean
+                      query:
+                        type: string
+                      withNoData:
+                        type: boolean
+                    type: object
+                type: object
+            required:
+            - database
+            - name
+            type: object
+          status:
+            description: ViewStatus defines the observed state of View
+            properties:
+              lastPlannedViewSpecSHA:
+                description: We store the SHA of the view spec from the last time
+                  we executed a plan to make startup less noisy by skipping re-planning
+                  objects that have been planned we cannot use the resourceVersion
+                  or generation fields because updating them would cause the object
+                  to be modified again
+                type: string
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources: {}
+status:
+  acceptedNames:
+    kind: ""
+    plural: ""
+  conditions: []
+  storedVersions: []


Test patch:
diff --git a/pkg/controller/migration/reconcile_migration_test.go b/pkg/controller/migration/reconcile_migration_test.go
new file mode 100644
index 00000000..041d3e5a
--- /dev/null
+++ b/pkg/controller/migration/reconcile_migration_test.go
@@ -0,0 +1,90 @@
+package migration
+
+import (
+	"context"
+	"testing"
+
+	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	testclient "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/fake"
+	"github.com/stretchr/testify/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func Test_getDatabaseFromMigration(t *testing.T) {
+	db := &databasesv1alpha4.Database{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "testdb",
+			Namespace: "namespace1",
+		},
+	}
+	table1 := &schemasv1alpha4.Table{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "table1",
+			Namespace: "namespace1",
+		},
+		Spec: schemasv1alpha4.TableSpec{
+			Database: "testdb",
+		},
+	}
+	view1 := &schemasv1alpha4.View{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "view1",
+			Namespace: "namespace1",
+		},
+		Spec: schemasv1alpha4.ViewSpec{
+			Database: "testdb",
+		},
+	}
+
+	schemasClient = testclient.NewSimpleClientset(table1, view1).SchemasV1alpha4()
+	databasesClient = testclient.NewSimpleClientset(db).DatabasesV1alpha4()
+
+	tests := []struct {
+		name      string
+		migration *schemasv1alpha4.Migration
+		want      *databasesv1alpha4.Database
+	}{
+		{
+			name: "db from table",
+			migration: &schemasv1alpha4.Migration{
+				Spec: schemasv1alpha4.MigrationSpec{
+					TableNamespace: "namespace1",
+					TableName:      "table1",
+				},
+			},
+			want: db,
+		},
+		{
+			name: "db from view",
+			migration: &schemasv1alpha4.Migration{
+				Spec: schemasv1alpha4.MigrationSpec{
+					TableNamespace: "namespace1",
+					TableName:      "view1",
+				},
+			},
+			want: db,
+		},
+		{
+			name: "unknown db",
+			migration: &schemasv1alpha4.Migration{
+				Spec: schemasv1alpha4.MigrationSpec{
+					TableNamespace: "namespace1",
+					TableName:      "unknown",
+				},
+			},
+			want: nil,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ctx := context.Background()
+			got, err := getDatabaseFromMigration(ctx, tt.migration)
+			if tt.want != nil {
+				assert.Equal(t, tt.want, got)
+			} else {
+				assert.Error(t, err)
+			}
+		})
+	}
+}