Repository: schemahero/schemahero
Pull request #189 (2020-05-24 16:41:25)

Add shell command
This adds a `kubectl schemahero shell` command.

If the configured database is postgres or mysql, this creates a pod and execs in, authenticating using the same connection string as the underlying database driver.

This is a proof-of-concept feature that should be used and documented with pre-alpha reliability. One problem with this feature before recommending it is to think about auditability of commands because any command will be executed as the same user, regardless of who created the shell.



Patch:
diff --git a/.github/workflows/tagged-release.yaml b/.github/workflows/tagged-release.yaml
index b9aac177..2162b9e8 100644
--- a/.github/workflows/tagged-release.yaml
+++ b/.github/workflows/tagged-release.yaml
@@ -9,7 +9,7 @@ jobs:
     steps:
       - uses: actions/setup-go@v1.1.2
         with:
-          go-version: "1.14.1"
+          go-version: "1.14.3"
       - name: setup goenv
         run: |
           echo "::set-env name=GOPATH::$(go env GOPATH)"
@@ -30,7 +30,6 @@ jobs:
           name: kubectl-schemahero
           path: bin/kubectl-schemahero
 
-  ## We list each here so that they all show up in the output of github checks
   test-postgres:
     runs-on: ubuntu-latest
     needs: build
@@ -42,19 +41,8 @@ jobs:
           name: schemahero
           path: bin/
       - run: chmod +x bin/schemahero
-      - run: make -C integration/tests/postgres/column-set-default run
-      - run: make -C integration/tests/postgres/column-unset-default run
-      - run: make -C integration/tests/postgres/create-table run
-      - run: make -C integration/tests/postgres/foreign-key-create run
-      - run: make -C integration/tests/postgres/foreign-key-action run
-      - run: make -C integration/tests/postgres/foreign-key-drop run
-      - run: make -C integration/tests/postgres/foreign-key-alter run
-      - run: make -C integration/tests/postgres/not-null run
-      - run: make -C integration/tests/postgres/index-create run
-      - run: make -C integration/tests/postgres/primary-key-add run
-      - run: make -C integration/tests/postgres/primary-key-drop run
+      - run: make -C integration/tests/postgres run
 
-  ## We list each here so that they all show up in the output of github checks
   test-mysql:
     runs-on: ubuntu-latest
     needs: build
@@ -66,19 +54,8 @@ jobs:
           name: schemahero
           path: bin/
       - run: chmod +x bin/schemahero
-      - run: make -C integration/tests/mysql/column-set-default run
-      - run: make -C integration/tests/mysql/column-unset-default run
-      - run: make -C integration/tests/mysql/create-table run
-      - run: make -C integration/tests/mysql/foreign-key-create run
-      - run: make -C integration/tests/mysql/foreign-key-action run
-      - run: make -C integration/tests/mysql/foreign-key-drop run
-      - run: make -C integration/tests/mysql/foreign-key-alter run
-      - run: make -C integration/tests/mysql/not-null run
-      - run: make -C integration/tests/mysql/index-create run
-      - run: make -C integration/tests/mysql/primary-key-add run
-      - run: make -C integration/tests/mysql/primary-key-drop run
+      - run: make -C integration/tests/mysql run
 
-  ## We list each here so that they all show up in the output of github checks
   test-cockroach:
     runs-on: ubuntu-latest
     needs: build
@@ -90,17 +67,7 @@ jobs:
           name: schemahero
           path: bin/
       - run: chmod +x bin/schemahero
-      - run: make -C integration/tests/cockroach/column-set-default run
-      - run: make -C integration/tests/cockroach/column-unset-default run
-      - run: make -C integration/tests/cockroach/create-table run
-      - run: make -C integration/tests/cockroach/foreign-key-create run
-      - run: make -C integration/tests/cockroach/foreign-key-action run
-      - run: make -C integration/tests/cockroach/foreign-key-drop run
-      - run: make -C integration/tests/cockroach/foreign-key-alter run
-      - run: make -C integration/tests/cockroach/not-null run
-      - run: make -C integration/tests/cockroach/index-create run
-      - run: make -C integration/tests/cockroach/primary-key-add run
-      - run: make -C integration/tests/cockroach/primary-key-drop run
+      - run: make -C integration/tests/cockroach run
 
   goreleaser:
     runs-on: ubuntu-latest
@@ -114,7 +81,7 @@ jobs:
         run: git fetch --prune --unshallow
       - uses: actions/setup-go@v1.1.2
         with:
-          go-version: "1.14.1"
+          go-version: "1.14.3"
       - uses: azure/docker-login@v1
         with:
           username: schemaherodeploy
diff --git a/config/crds/v1/databases.schemahero.io_databases.yaml b/config/crds/v1/databases.schemahero.io_databases.yaml
index 66ecc17e..30eaac7c 100644
--- a/config/crds/v1/databases.schemahero.io_databases.yaml
+++ b/config/crds/v1/databases.schemahero.io_databases.yaml
@@ -16,7 +16,7 @@ spec:
     singular: database
   scope: Namespaced
   versions:
-  - name: v1alpha3
+  - name: v1alpha4
     schema:
       openAPIV3Schema:
         description: Database is the Schema for the databases API
@@ -133,6 +133,8 @@ spec:
                         type: object
                     type: object
                 type: object
+              disableShellCommand:
+                type: boolean
               immediateDeploy:
                 type: boolean
               schemahero:
diff --git a/config/crds/v1beta1/databases.schemahero.io_databases.yaml b/config/crds/v1beta1/databases.schemahero.io_databases.yaml
index 52a20a54..aa69511d 100644
--- a/config/crds/v1beta1/databases.schemahero.io_databases.yaml
+++ b/config/crds/v1beta1/databases.schemahero.io_databases.yaml
@@ -133,6 +133,8 @@ spec:
                       type: object
                   type: object
               type: object
+            disableShellCommand:
+              type: boolean
             immediateDeploy:
               type: boolean
             schemahero:
@@ -159,9 +161,9 @@ spec:
       required:
       - spec
       type: object
-  version: v1alpha3
+  version: v1alpha4
   versions:
-  - name: v1alpha3
+  - name: v1alpha4
     served: true
     storage: true
 status:
diff --git a/config/dev/database/postgres-10.7.yaml b/config/dev/database/postgres-10.7.yaml
index 76a09577..ed69f56c 100644
--- a/config/dev/database/postgres-10.7.yaml
+++ b/config/dev/database/postgres-10.7.yaml
@@ -4,7 +4,7 @@ data:
   uri: cG9zdGdyZXM6Ly90ZXN0dXNlcjpwYXNzd29yZEBwb3N0Z3Jlc3FsOjU0MzIvdGVzdGRiP3NzbG1vZGU9ZGlzYWJsZQ==
 kind: Secret
 metadata:
-  name: postgresql-secret
+  name: postgresql
 type: Opaque
 ---
 apiVersion: v1
diff --git a/config/dev/database/postgres-11.8.0.yaml b/config/dev/database/postgres-11.8.0.yaml
new file mode 100644
index 00000000..194a031c
--- /dev/null
+++ b/config/dev/database/postgres-11.8.0.yaml
@@ -0,0 +1,171 @@
+---
+# Source: postgresql/templates/secrets.yaml
+apiVersion: v1
+kind: Secret
+metadata:
+  name: postgresql
+  labels:
+    app: postgresql
+    chart: postgresql-8.10.0
+    release: "postgresql"
+    heritage: "Helm"
+type: Opaque
+data:
+  postgresql-password: "cGFzc3dvcmQ="
+  uri: cG9zdGdyZXM6Ly90ZXN0dXNlcjpwYXNzd29yZEBwb3N0Z3Jlc3FsOjU0MzIvdGVzdGRiP3NzbG1vZGU9ZGlzYWJsZQ==
+
+---
+# Source: postgresql/templates/svc-headless.yaml
+apiVersion: v1
+kind: Service
+metadata:
+  name: postgresql-headless
+  labels:
+    app: postgresql
+    chart: postgresql-8.10.0
+    release: "postgresql"
+    heritage: "Helm"
+spec:
+  type: ClusterIP
+  clusterIP: None
+  ports:
+    - name: tcp-postgresql
+      port: 5432
+      targetPort: tcp-postgresql
+  selector:
+    app: postgresql
+    release: "postgresql"
+---
+# Source: postgresql/templates/svc.yaml
+apiVersion: v1
+kind: Service
+metadata:
+  name: postgresql
+  labels:
+    app: postgresql
+    chart: postgresql-8.10.0
+    release: "postgresql"
+    heritage: "Helm"
+  annotations:
+spec:
+  type: ClusterIP
+  ports:
+    - name: tcp-postgresql
+      port: 5432
+      targetPort: tcp-postgresql
+  selector:
+    app: postgresql
+    release: "postgresql"
+    role: master
+---
+# Source: postgresql/templates/statefulset.yaml
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  name: postgresql-postgresql
+  labels:
+    app: postgresql
+    chart: postgresql-8.10.0
+    release: "postgresql"
+    heritage: "Helm"
+  annotations:
+spec:
+  serviceName: postgresql-headless
+  replicas: 1
+  updateStrategy:
+    type: RollingUpdate
+  selector:
+    matchLabels:
+      app: postgresql
+      release: "postgresql"
+      role: master
+  template:
+    metadata:
+      name: postgresql
+      labels:
+        app: postgresql
+        chart: postgresql-8.10.0
+        release: "postgresql"
+        heritage: "Helm"
+        role: master
+    spec:      
+      securityContext:
+        fsGroup: 1001
+      containers:
+        - name: postgresql
+          image: docker.io/bitnami/postgresql:11.8.0-debian-10-r5
+          imagePullPolicy: "IfNotPresent"
+          resources:
+            requests:
+              cpu: 250m
+              memory: 256Mi
+          securityContext:
+            runAsUser: 1001
+          env:
+            - name: BITNAMI_DEBUG
+              value: "false"
+            - name: POSTGRESQL_PORT_NUMBER
+              value: "5432"
+            - name: POSTGRESQL_VOLUME_DIR
+              value: "/bitnami/postgresql"
+            - name: PGDATA
+              value: "/bitnami/postgresql/data"
+            - name: POSTGRES_USER
+              value: "testuser"
+            - name: POSTGRES_DB
+              value: testdb
+            - name: POSTGRES_PASSWORD
+              valueFrom:
+                secretKeyRef:
+                  name: postgresql
+                  key: postgresql-password
+            - name: POSTGRESQL_ENABLE_LDAP
+              value: "no"
+          ports:
+            - name: tcp-postgresql
+              containerPort: 5432
+          livenessProbe:
+            exec:
+              command:
+                - /bin/sh
+                - -c
+                - exec pg_isready -U "postgres" -h 127.0.0.1 -p 5432
+            initialDelaySeconds: 30
+            periodSeconds: 10
+            timeoutSeconds: 5
+            successThreshold: 1
+            failureThreshold: 6
+          readinessProbe:
+            exec:
+              command:
+                - /bin/sh
+                - -c
+                - -e
+                - |
+                  exec pg_isready -U "postgres" -h 127.0.0.1 -p 5432
+                  [ -f /opt/bitnami/postgresql/tmp/.initialized ] || [ -f /bitnami/postgresql/.initialized ]
+            initialDelaySeconds: 5
+            periodSeconds: 10
+            timeoutSeconds: 5
+            successThreshold: 1
+            failureThreshold: 6
+          volumeMounts:
+            - name: dshm
+              mountPath: /dev/shm
+            - name: data
+              mountPath: /bitnami/postgresql
+              subPath: 
+      volumes:
+        - name: dshm
+          emptyDir:
+            medium: Memory
+            sizeLimit: 1Gi
+  volumeClaimTemplates:
+    - metadata:
+        name: data
+      spec:
+        accessModes:
+          - "ReadWriteOnce"
+        resources:
+          requests:
+            storage: "8Gi"
\ No newline at end of file
diff --git a/config/dev/github/mysql.yaml b/config/dev/github/mysql.yaml
index 88faa7bc..1678c2bd 100644
--- a/config/dev/github/mysql.yaml
+++ b/config/dev/github/mysql.yaml
@@ -1,4 +1,4 @@
-apiVersion: databases.schemahero.io/v1alpha3
+apiVersion: databases.schemahero.io/v1alpha4
 kind: Database
 metadata:
   name: testdb
diff --git a/config/dev/github/pg.yaml b/config/dev/github/pg.yaml
index ac58f1ca..bf0c6e77 100644
--- a/config/dev/github/pg.yaml
+++ b/config/dev/github/pg.yaml
@@ -1,4 +1,4 @@
-apiVersion: databases.schemahero.io/v1alpha3
+apiVersion: databases.schemahero.io/v1alpha4
 kind: Database
 metadata:
   name: testdb
@@ -11,5 +11,5 @@ spec:
       uri:
         valueFrom:
           secretKeyRef:
-            name: postgresql-secret
+            name: postgresql
             key: uri
diff --git a/config/dev/github/projects-table-pg.yaml b/config/dev/github/projects-table-pg.yaml
index 43f51274..342b8872 100644
--- a/config/dev/github/projects-table-pg.yaml
+++ b/config/dev/github/projects-table-pg.yaml
@@ -1,4 +1,4 @@
-apiVersion: schemas.schemahero.io/v1alpha3
+apiVersion: schemas.schemahero.io/v1alpha4
 kind: Table
 metadata:
   name: projects
diff --git a/config/dev/github/users-table-mysql.yaml b/config/dev/github/users-table-mysql.yaml
index b1d5f55d..8707d4ab 100644
--- a/config/dev/github/users-table-mysql.yaml
+++ b/config/dev/github/users-table-mysql.yaml
@@ -1,4 +1,4 @@
-apiVersion: schemas.schemahero.io/v1alpha3
+apiVersion: schemas.schemahero.io/v1alpha4
 kind: Table
 metadata:
   name: users
diff --git a/config/dev/github/users-table-pg.yaml b/config/dev/github/users-table-pg.yaml
index 6ec30cbd..e744a813 100644
--- a/config/dev/github/users-table-pg.yaml
+++ b/config/dev/github/users-table-pg.yaml
@@ -1,4 +1,4 @@
-apiVersion: schemas.schemahero.io/v1alpha3
+apiVersion: schemas.schemahero.io/v1alpha4
 kind: Table
 metadata:
   name: users
diff --git a/examples/airline/README.md b/examples/airline/README.md
new file mode 100644
index 00000000..05cd1b42
--- /dev/null
+++ b/examples/airline/README.md
@@ -0,0 +1,62 @@
+# Airline
+
+The files in this directory contain a very simple, fictitious airline database schema to manage reservations and flight schedules.
+
+## Installing
+
+To install this example, you'll need a Kubernetes cluster. The YAML in this example has been hard coded to run in a namespace named "airline-demo". The manifests here contain a postgres 11.8.0 engine, and the SchemaHero database and table components. This directory does not contain the SchemaHero Operator; you'll have to install this yourself or already have it in the cluster.
+
+### Database
+
+To install the Postgres 11.8.0 instance, run the following commands from this (examples/airline) directory:
+
+```shell
+kubectl apply -f ./postgres/postgres-11.8.0.yaml
+```
+
+### Schema
+
+After the database engine has been deployed, the schema directory can be deployed and SchemaHero will create the tables. 
+
+#### Database Connection
+
+To start, deploy the database connection:
+
+```
+kubectl apply -f ./schema/airlinedb.yaml
+```
+
+#### Tables
+
+Deplioy the tables:
+
+```
+kubectl apply -f ./schema
+```
+
+### Verifying
+
+You can now connect to the database and explore the schema that was created:
+
+```shell
+kubectl schemahero shell --namespace airline-demo airlinedb
+```
+
+The above command will start a new pod and connect your terminal to it. You'll be authenticated to the database, and the `airlinedb` will already be selected. 
+
+Try viewing the schema of the reservation table:
+
+```
+\d reservation
+                          Table "public.reservation"
+    Column     |            Type             | Collation | Nullable | Default 
+---------------+-----------------------------+-----------+----------+---------
+ id            | character(8)                |           | not null | 
+ created_at    | timestamp without time zone |           | not null | 
+ first_name    | character varying(40)       |           |          | 
+ last_name     | character varying(40)       |           |          | 
+ flight_number | character(4)                |           |          | 
+ flight_date   | timestamp without time zone |           | not null | 
+Indexes:
+    "reservation_pkey" PRIMARY KEY, btree (id)
+```
\ No newline at end of file
diff --git a/examples/airline/postgres/postgres-11.8.0.yaml b/examples/airline/postgres/postgres-11.8.0.yaml
new file mode 100644
index 00000000..2d53da07
--- /dev/null
+++ b/examples/airline/postgres/postgres-11.8.0.yaml
@@ -0,0 +1,181 @@
+---
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: airline-demo
+
+---
+# Source: postgresql/templates/secrets.yaml
+apiVersion: v1
+kind: Secret
+metadata:
+  name: postgresql
+  namespace: airline-demo
+  labels:
+    app: postgresql
+    chart: postgresql-8.10.0
+    release: "postgresql"
+    heritage: "Helm"
+type: Opaque
+stringData:
+  postgresql-password: "airlinedb-password"
+  uri: postgres://airlinedb-user:airlinedb-password@postgresql:5432/airlinedb?sslmode=disable
+
+---
+# Source: postgresql/templates/svc-headless.yaml
+apiVersion: v1
+kind: Service
+metadata:
+  name: postgresql-headless
+  namespace: airline-demo
+  labels:
+    app: postgresql
+    chart: postgresql-8.10.0
+    release: "postgresql"
+    heritage: "Helm"
+spec:
+  type: ClusterIP
+  clusterIP: None
+  ports:
+    - name: tcp-postgresql
+      port: 5432
+      targetPort: tcp-postgresql
+  selector:
+    app: postgresql
+    release: "postgresql"
+---
+# Source: postgresql/templates/svc.yaml
+apiVersion: v1
+kind: Service
+metadata:
+  name: postgresql
+  namespace: airline-demo
+  labels:
+    app: postgresql
+    chart: postgresql-8.10.0
+    release: "postgresql"
+    heritage: "Helm"
+  annotations:
+spec:
+  type: ClusterIP
+  ports:
+    - name: tcp-postgresql
+      port: 5432
+      targetPort: tcp-postgresql
+  selector:
+    app: postgresql
+    release: "postgresql"
+    role: master
+---
+# Source: postgresql/templates/statefulset.yaml
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  name: postgresql-postgresql
+  namespace: airline-demo
+  labels:
+    app: postgresql
+    chart: postgresql-8.10.0
+    release: "postgresql"
+    heritage: "Helm"
+  annotations:
+spec:
+  serviceName: postgresql-headless
+  replicas: 1
+  updateStrategy:
+    type: RollingUpdate
+  selector:
+    matchLabels:
+      app: postgresql
+      release: "postgresql"
+      role: master
+  template:
+    metadata:
+      name: postgresql
+      labels:
+        app: postgresql
+        chart: postgresql-8.10.0
+        release: "postgresql"
+        heritage: "Helm"
+        role: master
+    spec:      
+      securityContext:
+        fsGroup: 1001
+      containers:
+        - name: postgresql
+          image: docker.io/bitnami/postgresql:11.8.0-debian-10-r5
+          imagePullPolicy: "IfNotPresent"
+          resources:
+            requests:
+              cpu: 250m
+              memory: 256Mi
+          securityContext:
+            runAsUser: 1001
+          env:
+            - name: BITNAMI_DEBUG
+              value: "false"
+            - name: POSTGRESQL_PORT_NUMBER
+              value: "5432"
+            - name: POSTGRESQL_VOLUME_DIR
+              value: "/bitnami/postgresql"
+            - name: PGDATA
+              value: "/bitnami/postgresql/data"
+            - name: POSTGRES_USER
+              value: "airlinedb-user"
+            - name: POSTGRES_DB
+              value: airlinedb
+            - name: POSTGRES_PASSWORD
+              valueFrom:
+                secretKeyRef:
+                  name: postgresql
+                  key: postgresql-password
+            - name: POSTGRESQL_ENABLE_LDAP
+              value: "no"
+          ports:
+            - name: tcp-postgresql
+              containerPort: 5432
+          livenessProbe:
+            exec:
+              command:
+                - /bin/sh
+                - -c
+                - exec pg_isready -U "postgres" -h 127.0.0.1 -p 5432
+            initialDelaySeconds: 30
+            periodSeconds: 10
+            timeoutSeconds: 5
+            successThreshold: 1
+            failureThreshold: 6
+          readinessProbe:
+            exec:
+              command:
+                - /bin/sh
+                - -c
+                - -e
+                - |
+                  exec pg_isready -U "postgres" -h 127.0.0.1 -p 5432
+                  [ -f /opt/bitnami/postgresql/tmp/.initialized ] || [ -f /bitnami/postgresql/.initialized ]
+            initialDelaySeconds: 5
+            periodSeconds: 10
+            timeoutSeconds: 5
+            successThreshold: 1
+            failureThreshold: 6
+          volumeMounts:
+            - name: dshm
+              mountPath: /dev/shm
+            - name: data
+              mountPath: /bitnami/postgresql
+              subPath: 
+      volumes:
+        - name: dshm
+          emptyDir:
+            medium: Memory
+            sizeLimit: 1Gi
+  volumeClaimTemplates:
+    - metadata:
+        name: data
+      spec:
+        accessModes:
+          - "ReadWriteOnce"
+        resources:
+          requests:
+            storage: "8Gi"
\ No newline at end of file
diff --git a/examples/airline/schema/airlinedb.yaml b/examples/airline/schema/airlinedb.yaml
new file mode 100644
index 00000000..b4056d73
--- /dev/null
+++ b/examples/airline/schema/airlinedb.yaml
@@ -0,0 +1,16 @@
+apiVersion: databases.schemahero.io/v1alpha4
+kind: Database
+metadata:
+  name: airlinedb
+  namespace: airline-demo
+spec:
+  schemahero:
+    image: "localhost:32000/schemahero/schemahero"
+  immediateDeploy: true
+  connection:
+    postgres:
+      uri:
+        valueFrom:
+          secretKeyRef:
+            name: postgresql
+            key: uri
diff --git a/examples/airline/schema/airport-table.yaml b/examples/airline/schema/airport-table.yaml
new file mode 100644
index 00000000..e0f0b2f8
--- /dev/null
+++ b/examples/airline/schema/airport-table.yaml
@@ -0,0 +1,16 @@
+apiVersion: schemas.schemahero.io/v1alpha4
+kind: Table
+metadata:
+  name: airport
+  namespace: airline-demo
+spec:
+  database: airlinedb
+  name: airport
+  schema:
+    postgres:
+      primaryKey: [code]
+      columns:
+        - name: code
+          type: char(4)
+        - name: name
+          type: varchar(255)
\ No newline at end of file
diff --git a/examples/airline/schema/reservation-table.yaml b/examples/airline/schema/reservation-table.yaml
new file mode 100644
index 00000000..1ac57edc
--- /dev/null
+++ b/examples/airline/schema/reservation-table.yaml
@@ -0,0 +1,28 @@
+apiVersion: schemas.schemahero.io/v1alpha4
+kind: Table
+metadata:
+  name: reservation
+  namespace: airline-demo
+spec:
+  database: airlinedb
+  name: reservation
+  schema:
+    postgres:
+      primaryKey: [id]
+      columns:
+        - name: id
+          type: char(8)
+        - name: created_at
+          type: timestamp
+          constraints:
+            notNull: true
+        - name: first_name
+          type: varchar(40)
+        - name: last_name
+          type: varchar(40)
+        - name: flight_number
+          type: char(4)
+        - name: flight_date
+          type: timestamp
+          constraints:
+            notNull: true
\ No newline at end of file
diff --git a/examples/airline/schema/schedule.yaml b/examples/airline/schema/schedule.yaml
new file mode 100644
index 00000000..ad8e64c4
--- /dev/null
+++ b/examples/airline/schema/schedule.yaml
@@ -0,0 +1,30 @@
+apiVersion: schemas.schemahero.io/v1alpha4
+kind: Table
+metadata:
+  name: schedule
+  namespace: airline-demo
+spec:
+  database: airlinedb
+  name: schedule
+  schema:
+    postgres:
+      primaryKey: [flight_num]
+      columns:
+        - name: flight_num
+          type: int
+        - name: origin
+          type: char(4)
+          constraints:
+            notNull: true
+        - name: destination
+          type: char(4)
+          constraints:
+            notNull: true
+        - name: departure_time
+          type: time
+          constraints:
+            notNull: true
+        - name: arrival_time
+          type: time
+          constraints:
+            notNull: true
\ No newline at end of file
diff --git a/features/kubectl-plugin.md b/features/kubectl-plugin.md
index 1a458a31..a781cdab 100644
--- a/features/kubectl-plugin.md
+++ b/features/kubectl-plugin.md
@@ -16,7 +16,7 @@ Some grouping and ordering can be achieved if manually deploying changes using k
 
 This is the current plan of action; it's based on product feedback received so far since launching SchemaHero.
 
-- Bump the Table and Database CRD versions to v1alpha3
+- Bump the Table and Database CRD versions to v1alpha4
 - Add a top level boolean attribute to the Database kind named `automaticallyDeployMigrations`
 - When a table migration is deployed, if `automaticallyDeployMigrations` is not enabled, the controller should only generate the desired YAML
 - The desired YAML should be stored in the Status field of the Table object
diff --git a/go.sum b/go.sum
index 82fe34e4..ff0754a4 100644
--- a/go.sum
+++ b/go.sum
@@ -62,6 +62,7 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
 github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg=
 github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
diff --git a/integration/Makefile b/integration/Makefile
index 74ed2ad7..95ed6754 100644
--- a/integration/Makefile
+++ b/integration/Makefile
@@ -9,48 +9,15 @@ run: postgres mysql cockroach
 
 .PHONY: postgres
 postgres:
-	make -C tests/postgres/column-set-default run
-	make -C tests/postgres/column-unset-default run
-	make -C tests/postgres/create-table run
-	make -C tests/postgres/foreign-key-create run
-	make -C tests/postgres/foreign-key-action run
-	make -C tests/postgres/foreign-key-drop run
-	make -C tests/postgres/foreign-key-alter run
-	make -C tests/postgres/not-null run
-	make -C tests/postgres/not-null-with-default run
-	make -C tests/postgres/index-create run
-	make -C tests/postgres/primary-key-add run
-	make -C tests/postgres/primary-key-drop run
-	make -C tests/postgres/unique-constraint-add run
-	make -C tests/postgres/unique-constraint-drop run
+	make -C tests/postgres run
 
 .PHONY: mysql
 mysql:
-	make -C tests/mysql/column-set-default run
-	make -C tests/mysql/column-unset-default run
-	make -C tests/mysql/create-table run
-	make -C tests/mysql/foreign-key-create run
-	make -C tests/mysql/foreign-key-action run
-	make -C tests/mysql/foreign-key-drop run
-	make -C tests/mysql/foreign-key-alter run
-	make -C tests/mysql/not-null run
-	make -C tests/mysql/index-create run
-	make -C tests/mysql/primary-key-add run
-	make -C tests/mysql/primary-key-drop run
+	make -C tests/mysql run
 
 .PHONY: cockroach
 cockroach:
-	make -C tests/cockroach/column-set-default run
-	make -C tests/cockroach/column-unset-default run
-	make -C tests/cockroach/create-table run
-	make -C tests/cockroach/foreign-key-create run
-	make -C tests/cockroach/foreign-key-action run
-	make -C tests/cockroach/foreign-key-drop run
-	make -C tests/cockroach/foreign-key-alter run
-	make -C tests/cockroach/not-null run
-	make -C tests/cockroach/index-create run
-	make -C tests/cockroach/primary-key-add run
-	make -C tests/cockroach/primary-key-drop run
+	make -C tests/cockroach run
 
 .PHONY: build
 build: docker-build
diff --git a/pkg/apis/addtoscheme_databases_v1alpha4.go b/pkg/apis/addtoscheme_databases_v1alpha4.go
index a426814c..6ada8273 100644
--- a/pkg/apis/addtoscheme_databases_v1alpha4.go
+++ b/pkg/apis/addtoscheme_databases_v1alpha4.go
@@ -16,7 +16,7 @@ limitations under the License.
 
 package apis
 
-import "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+import "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
 
 func init() {
 	// Register the types with the Scheme so the components can map objects to GroupVersionKinds and back
diff --git a/pkg/apis/databases/v1alpha4/cockroachdb_types.go b/pkg/apis/databases/v1alpha4/cockroachdb_types.go
index 2816cfc8..933b6753 100644
--- a/pkg/apis/databases/v1alpha4/cockroachdb_types.go
+++ b/pkg/apis/databases/v1alpha4/cockroachdb_types.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v1alpha3
+package v1alpha4
 
 type CockroachDBConnection struct {
 	URI ValueOrValueFrom `json:"uri,omitempty"`
diff --git a/pkg/apis/databases/v1alpha4/database_types.go b/pkg/apis/databases/v1alpha4/database_types.go
index f5f5f5cf..78729a77 100644
--- a/pkg/apis/databases/v1alpha4/database_types.go
+++ b/pkg/apis/databases/v1alpha4/database_types.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v1alpha3
+package v1alpha4
 
 import (
 	"fmt"
@@ -35,9 +35,10 @@ type SchemaHero struct {
 }
 
 type DatabaseSpec struct {
-	Connection      DatabaseConnection `json:"connection,omitempty"`
-	ImmediateDeploy bool               `json:"immediateDeploy,omitempty"`
-	SchemaHero      *SchemaHero        `json:"schemahero,omitempty"`
+	Connection          DatabaseConnection `json:"connection,omitempty"`
+	DisableShellCommand bool               `json:"disableShellCommand,omitempty"`
+	ImmediateDeploy     bool               `json:"immediateDeploy,omitempty"`
+	SchemaHero          *SchemaHero        `json:"schemahero,omitempty"`
 }
 
 // DatabaseStatus defines the observed state of Database
diff --git a/pkg/apis/databases/v1alpha4/doc.go b/pkg/apis/databases/v1alpha4/doc.go
index 01f098b2..d90bc260 100644
--- a/pkg/apis/databases/v1alpha4/doc.go
+++ b/pkg/apis/databases/v1alpha4/doc.go
@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-// Package v1alpha3 contains API Schema definitions for the databases v1alpha3 API group
+// package v1alpha4 contains API Schema definitions for the databases v1alpha4 API group
 // +k8s:openapi-gen=true
 // +k8s:deepcopy-gen=package,register
 // +k8s:conversion-gen=github.com/schemahero/schemahero/pkg/apis/databases
 // +k8s:defaulter-gen=TypeMeta
 // +groupName=databases.schemahero.io
-package v1alpha3
+package v1alpha4
diff --git a/pkg/apis/databases/v1alpha4/mysql_types.go b/pkg/apis/databases/v1alpha4/mysql_types.go
index 6649a527..23dd9dbd 100644
--- a/pkg/apis/databases/v1alpha4/mysql_types.go
+++ b/pkg/apis/databases/v1alpha4/mysql_types.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v1alpha3
+package v1alpha4
 
 type MysqlConnection struct {
 	URI ValueOrValueFrom `json:"uri,omitempty"`
diff --git a/pkg/apis/databases/v1alpha4/postgres_types.go b/pkg/apis/databases/v1alpha4/postgres_types.go
index e699311c..10dd8b00 100644
--- a/pkg/apis/databases/v1alpha4/postgres_types.go
+++ b/pkg/apis/databases/v1alpha4/postgres_types.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v1alpha3
+package v1alpha4
 
 type PostgresConnection struct {
 	URI ValueOrValueFrom `json:"uri,omitempty"`
diff --git a/pkg/apis/databases/v1alpha4/register.go b/pkg/apis/databases/v1alpha4/register.go
index 50b38463..71798284 100644
--- a/pkg/apis/databases/v1alpha4/register.go
+++ b/pkg/apis/databases/v1alpha4/register.go
@@ -16,13 +16,13 @@ limitations under the License.
 
 // NOTE: Boilerplate only.  Ignore this file.
 
-// Package v1alpha3 contains API Schema definitions for the databases v1alpha3 API group
+// package v1alpha4 contains API Schema definitions for the databases v1alpha4 API group
 // +k8s:openapi-gen=true
 // +k8s:deepcopy-gen=package,register
 // +k8s:conversion-gen=github.com/schemahero/schemahero/pkg/apis/databases
 // +k8s:defaulter-gen=TypeMeta
 // +groupName=databases.schemahero.io
-package v1alpha3
+package v1alpha4
 
 import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -31,7 +31,7 @@ import (
 
 var (
 	// SchemeGroupVersion is group version used to register these objects
-	SchemeGroupVersion = schema.GroupVersion{Group: "databases.schemahero.io", Version: "v1alpha3"}
+	SchemeGroupVersion = schema.GroupVersion{Group: "databases.schemahero.io", Version: "v1alpha4"}
 
 	// SchemeBuilder is used to add go types to the GroupVersionKind scheme
 	SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
diff --git a/pkg/apis/databases/v1alpha4/string_or_value.go b/pkg/apis/databases/v1alpha4/string_or_value.go
index cfd3474b..f19be242 100644
--- a/pkg/apis/databases/v1alpha4/string_or_value.go
+++ b/pkg/apis/databases/v1alpha4/string_or_value.go
@@ -1,4 +1,4 @@
-package v1alpha3
+package v1alpha4
 
 import "fmt"
 
diff --git a/pkg/apis/databases/v1alpha4/value_or_value_from.go b/pkg/apis/databases/v1alpha4/value_or_value_from.go
index 89493d78..23ed2eb2 100644
--- a/pkg/apis/databases/v1alpha4/value_or_value_from.go
+++ b/pkg/apis/databases/v1alpha4/value_or_value_from.go
@@ -1,6 +1,12 @@
-package v1alpha3
+package v1alpha4
 
-import "errors"
+import (
+	"context"
+
+	"github.com/pkg/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes"
+)
 
 type ValueOrValueFrom struct {
 	Value     string     `json:"value,omitempty" yaml:"value,omitempty"`
@@ -25,3 +31,30 @@ func (v *ValueOrValueFrom) GetVaultDetails() (*Vault, error) {
 
 	return nil, errors.New("No Vault secret configured")
 }
+
+func (v *ValueOrValueFrom) Read(clientset *kubernetes.Clientset, namespace string) (string, error) {
+	if v.Value != "" {
+		return v.Value, nil
+	}
+
+	if v.ValueFrom == nil {
+		return "", errors.New("value and valueFrom cannot both be nil/empty")
+	}
+
+	if v.ValueFrom.SecretKeyRef != nil {
+		secret, err := clientset.CoreV1().Secrets(namespace).Get(context.Background(), v.ValueFrom.SecretKeyRef.Name, metav1.GetOptions{})
+		if err != nil {
+			return "", errors.Wrap(err, "failed to get secret")
+		}
+
+		return string(secret.Data[v.ValueFrom.SecretKeyRef.Key]), nil
+	}
+
+	if v.ValueFrom.Vault != nil {
+		// this feels wrong, but also doesn't make sense to return a
+		// a URI ref as a connection URI?
+		return "", nil
+	}
+
+	return "", errors.New("unable to find supported valueFrom")
+}
diff --git a/pkg/apis/databases/v1alpha4/zz_generated.deepcopy.go b/pkg/apis/databases/v1alpha4/zz_generated.deepcopy.go
index 759701e5..a72aff27 100644
--- a/pkg/apis/databases/v1alpha4/zz_generated.deepcopy.go
+++ b/pkg/apis/databases/v1alpha4/zz_generated.deepcopy.go
@@ -18,7 +18,7 @@ limitations under the License.
 
 // Code generated by controller-gen. DO NOT EDIT.
 
-package v1alpha3
+package v1alpha4
 
 import (
 	runtime "k8s.io/apimachinery/pkg/runtime"
diff --git a/pkg/apis/schemas/v1alpha4/doc.go b/pkg/apis/schemas/v1alpha4/doc.go
index a47d9ae4..0cb7fd88 100644
--- a/pkg/apis/schemas/v1alpha4/doc.go
+++ b/pkg/apis/schemas/v1alpha4/doc.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-// package v1alpha4 contains API Schema definitions for the schemas v1alpha3 API group
+// package v1alpha4 contains API Schema definitions for the schemas v1alpha4 API group
 // +k8s:openapi-gen=true
 // +k8s:deepcopy-gen=package,register
 // +k8s:conversion-gen=github.com/schemahero/schemahero/pkg/apis/schemas
diff --git a/pkg/apis/schemas/v1alpha4/register.go b/pkg/apis/schemas/v1alpha4/register.go
index d4619dd8..308ff657 100644
--- a/pkg/apis/schemas/v1alpha4/register.go
+++ b/pkg/apis/schemas/v1alpha4/register.go
@@ -16,7 +16,7 @@ limitations under the License.
 
 // NOTE: Boilerplate only.  Ignore this file.
 
-// package v1alpha4 contains API Schema definitions for the schemas v1alpha3 API group
+// package v1alpha4 contains API Schema definitions for the schemas v1alpha4 API group
 // +k8s:openapi-gen=true
 // +k8s:deepcopy-gen=package,register
 // +k8s:conversion-gen=github.com/schemahero/schemahero/pkg/apis/schemas
@@ -31,7 +31,7 @@ import (
 
 var (
 	// SchemeGroupVersion is group version used to register these objects
-	SchemeGroupVersion = schema.GroupVersion{Group: "schemas.schemahero.io", Version: "v1alpha3"}
+	SchemeGroupVersion = schema.GroupVersion{Group: "schemas.schemahero.io", Version: "v1alpha4"}
 
 	// SchemeBuilder is used to add go types to the GroupVersionKind scheme
 	SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
diff --git a/pkg/cli/schemaherokubectlcli/approve_migration.go b/pkg/cli/schemaherokubectlcli/approve_migration.go
index faedcb90..fe71c137 100644
--- a/pkg/cli/schemaherokubectlcli/approve_migration.go
+++ b/pkg/cli/schemaherokubectlcli/approve_migration.go
@@ -6,7 +6,7 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
-	schemasclientv1alpha3 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
+	schemasclientv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
 	kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
@@ -41,7 +41,7 @@ func ApproveMigrationCmd() *cobra.Command {
 				return err
 			}
 
-			schemasClient, err := schemasclientv1alpha3.NewForConfig(cfg)
+			schemasClient, err := schemasclientv1alpha4.NewForConfig(cfg)
 			if err != nil {
 				return err
 			}
diff --git a/pkg/cli/schemaherokubectlcli/describe_migration.go b/pkg/cli/schemaherokubectlcli/describe_migration.go
index 6f007dce..1439ba82 100644
--- a/pkg/cli/schemaherokubectlcli/describe_migration.go
+++ b/pkg/cli/schemaherokubectlcli/describe_migration.go
@@ -6,7 +6,7 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
-	schemasclientv1alpha3 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
+	schemasclientv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
 	kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
@@ -41,7 +41,7 @@ func DescribeMigrationCmd() *cobra.Command {
 				return err
 			}
 
-			schemasClient, err := schemasclientv1alpha3.NewForConfig(cfg)
+			schemasClient, err := schemasclientv1alpha4.NewForConfig(cfg)
 			if err != nil {
 				return err
 			}
diff --git a/pkg/cli/schemaherokubectlcli/get_databases.go b/pkg/cli/schemaherokubectlcli/get_databases.go
index 52f65f6e..91ec27ca 100644
--- a/pkg/cli/schemaherokubectlcli/get_databases.go
+++ b/pkg/cli/schemaherokubectlcli/get_databases.go
@@ -6,8 +6,8 @@ import (
 	"os"
 	"text/tabwriter"
 
-	databasesv1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
-	databasesclientv1alpha3 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
+	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	databasesclientv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -37,7 +37,7 @@ func GetDatabasesCmd() *cobra.Command {
 				return err
 			}
 
-			databasesClient, err := databasesclientv1alpha3.NewForConfig(cfg)
+			databasesClient, err := databasesclientv1alpha4.NewForConfig(cfg)
 			if err != nil {
 				return err
 			}
@@ -47,7 +47,7 @@ func GetDatabasesCmd() *cobra.Command {
 				return err
 			}
 
-			matchingDatabases := []databasesv1alpha3.Database{}
+			matchingDatabases := []databasesv1alpha4.Database{}
 
 			for _, namespace := range namespaces.Items {
 				databases, err := databasesClient.Databases(namespace.Name).List(ctx, metav1.ListOptions{})
diff --git a/pkg/cli/schemaherokubectlcli/get_migrations.go b/pkg/cli/schemaherokubectlcli/get_migrations.go
index cb34248c..95887bd9 100644
--- a/pkg/cli/schemaherokubectlcli/get_migrations.go
+++ b/pkg/cli/schemaherokubectlcli/get_migrations.go
@@ -7,8 +7,8 @@ import (
 	"text/tabwriter"
 	"time"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
-	schemasclientv1alpha3 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasclientv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/controller/migration"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
@@ -42,7 +42,7 @@ func GetMigrationsCmd() *cobra.Command {
 				return err
 			}
 
-			schemasClient, err := schemasclientv1alpha3.NewForConfig(cfg)
+			schemasClient, err := schemasclientv1alpha4.NewForConfig(cfg)
 			if err != nil {
 				return err
 			}
@@ -66,7 +66,7 @@ func GetMigrationsCmd() *cobra.Command {
 				}
 			}
 
-			matchingMigrations := []schemasv1alpha3.Migration{}
+			matchingMigrations := []schemasv1alpha4.Migration{}
 			for _, namespaceName := range namespaceNames {
 				migrations, err := schemasClient.Migrations(namespaceName).List(ctx, metav1.ListOptions{})
 				if err != nil {
diff --git a/pkg/cli/schemaherokubectlcli/get_tables.go b/pkg/cli/schemaherokubectlcli/get_tables.go
index 77ddd98e..dc530b67 100644
--- a/pkg/cli/schemaherokubectlcli/get_tables.go
+++ b/pkg/cli/schemaherokubectlcli/get_tables.go
@@ -6,8 +6,8 @@ import (
 	"os"
 	"text/tabwriter"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
-	schemasclientv1alpha3 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasclientv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -40,7 +40,7 @@ func GetTablesCmd() *cobra.Command {
 				return err
 			}
 
-			schemasClient, err := schemasclientv1alpha3.NewForConfig(cfg)
+			schemasClient, err := schemasclientv1alpha4.NewForConfig(cfg)
 			if err != nil {
 				return err
 			}
@@ -50,7 +50,7 @@ func GetTablesCmd() *cobra.Command {
 				return err
 			}
 
-			matchingTables := []schemasv1alpha3.Table{}
+			matchingTables := []schemasv1alpha4.Table{}
 
 			for _, namespace := range namespaces.Items {
 				tables, err := schemasClient.Tables(namespace.Name).List(ctx, metav1.ListOptions{})
diff --git a/pkg/cli/schemaherokubectlcli/root.go b/pkg/cli/schemaherokubectlcli/root.go
index b54b59df..cc8aa100 100644
--- a/pkg/cli/schemaherokubectlcli/root.go
+++ b/pkg/cli/schemaherokubectlcli/root.go
@@ -11,7 +11,7 @@ import (
 )
 
 var (
-	KubernetesConfigFlags *genericclioptions.ConfigFlags
+	kubernetesConfigFlags *genericclioptions.ConfigFlags
 )
 
 func RootCmd() *cobra.Command {
@@ -30,17 +30,20 @@ func RootCmd() *cobra.Command {
 
 	cobra.OnInitialize(initConfig)
 
+	kubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
+	kubernetesConfigFlags.AddFlags(cmd.PersistentFlags())
+
 	cmd.AddCommand(Version())
 	cmd.AddCommand(InstallCmd())
+	cmd.AddCommand(ShellCmd())
 	cmd.AddCommand(GetCmd())
 	cmd.AddCommand(DescribeCmd())
 	cmd.AddCommand(UpdateCmd())
 	cmd.AddCommand(ApproveCmd())
 
-	viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
+	viper.BindPFlags(cmd.Flags())
 
-	KubernetesConfigFlags = genericclioptions.NewConfigFlags(true)
-	KubernetesConfigFlags.AddFlags(cmd.PersistentFlags())
+	viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
 
 	return cmd
 }
diff --git a/pkg/cli/schemaherokubectlcli/shell.go b/pkg/cli/schemaherokubectlcli/shell.go
new file mode 100644
index 00000000..5e40f008
--- /dev/null
+++ b/pkg/cli/schemaherokubectlcli/shell.go
@@ -0,0 +1,198 @@
+package schemaherokubectlcli
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"os"
+
+	databasesclientv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
+	"github.com/schemahero/schemahero/pkg/database/mysql"
+	"github.com/schemahero/schemahero/pkg/shell"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+	corev1 "k8s.io/api/core/v1"
+	kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/kubernetes/scheme"
+	"k8s.io/client-go/tools/remotecommand"
+	"sigs.k8s.io/controller-runtime/pkg/client/config"
+)
+
+func ShellCmd() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:           "shell",
+		Short:         "create a new shell to the database",
+		Long:          `...`,
+		SilenceErrors: true,
+		SilenceUsage:  true,
+		PreRun: func(cmd *cobra.Command, args []string) {
+			viper.BindPFlags(cmd.Flags())
+		},
+		RunE: func(cmd *cobra.Command, args []string) error {
+			ctx := context.Background()
+			v := viper.GetViper()
+
+			if len(args) == 0 {
+				return errors.New("database name required")
+			}
+
+			databaseName := args[0]
+			namespace := v.GetString("namespace")
+
+			if namespace == "" {
+				namespace = corev1.NamespaceDefault
+			}
+
+			cfg, err := config.GetConfig()
+			if err != nil {
+				return err
+			}
+
+			clientset, err := kubernetes.NewForConfig(cfg)
+			if err != nil {
+				return err
+			}
+
+			databasesClient, err := databasesclientv1alpha4.NewForConfig(cfg)
+			if err != nil {
+				return err
+			}
+
+			database, err := databasesClient.Databases(namespace).Get(ctx, databaseName, metav1.GetOptions{})
+			if kuberneteserrors.IsNotFound(err) {
+				return fmt.Errorf("database %q not found in %q namespace", databaseName, namespace)
+			}
+			if err != nil {
+				return err
+			}
+
+			if database.Spec.DisableShellCommand {
+				return fmt.Errorf("shell command is not allowed for database %q", databaseName)
+			}
+
+			podImage := v.GetString("image")
+			podCommand := []string{}
+			// podArgs := []string{}
+
+			if database.Spec.Connection.Postgres != nil {
+				// TODO versions
+				podImage = "postgres:11"
+
+				connectionURI, err := database.Spec.Connection.Postgres.URI.Read(clientset, namespace)
+				if err != nil {
+					return err
+				}
+				podCommand = []string{
+					"psql",
+					connectionURI,
+				}
+			} else if database.Spec.Connection.Mysql != nil {
+				// TODO versions
+				podImage = "mysql:latest"
+
+				connectionURI, err := database.Spec.Connection.Mysql.URI.Read(clientset, namespace)
+				if err != nil {
+					return err
+				}
+
+				username, err := mysql.UsernameFromURI(connectionURI)
+				if err != nil {
+					return err
+				}
+
+				password, err := mysql.PasswordFromURI(connectionURI)
+				if err != nil {
+					return err
+				}
+
+				hostname, err := mysql.HostnameFromURI(connectionURI)
+				if err != nil {
+					return err
+				}
+
+				database, err := mysql.DatabaseNameFromURI(connectionURI)
+				if err != nil {
+					return err
+				}
+
+				// TODO add port
+				podCommand = []string{
+					"mysql",
+					"-u",
+					username,
+					fmt.Sprintf("-p%s", password),
+					"-h",
+					hostname,
+					"-D",
+					database,
+				}
+			}
+
+			if podImage == "" {
+				return errors.New("unable to determine image for shell -- consider specifying it with --image tag")
+			}
+
+			podName, err := shell.StartShellPod(ctx, namespace, podImage)
+			if err != nil {
+				return err
+			}
+
+			defer func() error {
+				// a lot of time could pass, so we need to get a
+				// new clientset here to re-authc
+				cfg, err := config.GetConfig()
+				if err != nil {
+					return err
+				}
+
+				clientset, err := kubernetes.NewForConfig(cfg)
+				if err != nil {
+					return err
+				}
+
+				fmt.Println("deleting pod...")
+				err = clientset.CoreV1().Pods(namespace).Delete(ctx, podName, metav1.DeleteOptions{})
+				if err != nil {
+					return err
+				}
+
+				fmt.Println("pod deleted.")
+				return nil
+			}()
+
+			// exec and pipe the stdin/out/err
+			req := clientset.CoreV1().RESTClient().Post().Resource("pods").Name(podName).Namespace(namespace).SubResource("exec")
+
+			req.VersionedParams(&corev1.PodExecOptions{
+				Container: "shell",
+				Stdin:     true,
+				Stdout:    true,
+				Stderr:    true,
+				TTY:       true,
+				Command:   podCommand,
+			}, scheme.ParameterCodec)
+
+			exec, err := remotecommand.NewSPDYExecutor(cfg, "POST", req.URL())
+			if err != nil {
+				return err
+			}
+			err = exec.Stream(remotecommand.StreamOptions{
+				Stdin:  os.Stdin,
+				Stdout: os.Stdout,
+				Stderr: os.Stderr,
+				Tty:    true,
+			})
+			if err != nil {
+				return err
+			}
+
+			return nil
+		},
+	}
+
+	cmd.Flags().String("image", "", "the image to use when executing the shell. if not provided, one will be selected based on the database provider")
+
+	return cmd
+}
diff --git a/pkg/client/schemaheroclientset/typed/databases/v1alpha3/database.go b/pkg/client/schemaheroclientset/typed/databases/v1alpha3/database.go
index dad00634..599441af 100644
--- a/pkg/client/schemaheroclientset/typed/databases/v1alpha3/database.go
+++ b/pkg/client/schemaheroclientset/typed/databases/v1alpha3/database.go
@@ -16,13 +16,13 @@ limitations under the License.
 
 // Code generated by client-gen. DO NOT EDIT.
 
-package v1alpha3
+package v1alpha4
 
 import (
 	"context"
 	"time"
 
-	v1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	v1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
 	scheme "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/scheme"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	types "k8s.io/apimachinery/pkg/types"
@@ -38,15 +38,15 @@ type DatabasesGetter interface {
 
 // DatabaseInterface has methods to work with Database resources.
 type DatabaseInterface interface {
-	Create(ctx context.Context, database *v1alpha3.Database, opts v1.CreateOptions) (*v1alpha3.Database, error)
-	Update(ctx context.Context, database *v1alpha3.Database, opts v1.UpdateOptions) (*v1alpha3.Database, error)
-	UpdateStatus(ctx context.Context, database *v1alpha3.Database, opts v1.UpdateOptions) (*v1alpha3.Database, error)
+	Create(ctx context.Context, database *v1alpha4.Database, opts v1.CreateOptions) (*v1alpha4.Database, error)
+	Update(ctx context.Context, database *v1alpha4.Database, opts v1.UpdateOptions) (*v1alpha4.Database, error)
+	UpdateStatus(ctx context.Context, database *v1alpha4.Database, opts v1.UpdateOptions) (*v1alpha4.Database, error)
 	Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
 	DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
-	Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha3.Database, error)
-	List(ctx context.Context, opts v1.ListOptions) (*v1alpha3.DatabaseList, error)
+	Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha4.Database, error)
+	List(ctx context.Context, opts v1.ListOptions) (*v1alpha4.DatabaseList, error)
 	Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
-	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Database, err error)
+	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha4.Database, err error)
 	DatabaseExpansion
 }
 
@@ -57,7 +57,7 @@ type databases struct {
 }
 
 // newDatabases returns a Databases
-func newDatabases(c *DatabasesV1alpha3Client, namespace string) *databases {
+func newDatabases(c *Databasesv1alpha4Client, namespace string) *databases {
 	return &databases{
 		client: c.RESTClient(),
 		ns:     namespace,
@@ -65,8 +65,8 @@ func newDatabases(c *DatabasesV1alpha3Client, namespace string) *databases {
 }
 
 // Get takes name of the database, and returns the corresponding database object, and an error if there is any.
-func (c *databases) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha3.Database, err error) {
-	result = &v1alpha3.Database{}
+func (c *databases) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha4.Database, err error) {
+	result = &v1alpha4.Database{}
 	err = c.client.Get().
 		Namespace(c.ns).
 		Resource("databases").
@@ -78,12 +78,12 @@ func (c *databases) Get(ctx context.Context, name string, options v1.GetOptions)
 }
 
 // List takes label and field selectors, and returns the list of Databases that match those selectors.
-func (c *databases) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha3.DatabaseList, err error) {
+func (c *databases) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha4.DatabaseList, err error) {
 	var timeout time.Duration
 	if opts.TimeoutSeconds != nil {
 		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
 	}
-	result = &v1alpha3.DatabaseList{}
+	result = &v1alpha4.DatabaseList{}
 	err = c.client.Get().
 		Namespace(c.ns).
 		Resource("databases").
@@ -110,8 +110,8 @@ func (c *databases) Watch(ctx context.Context, opts v1.ListOptions) (watch.Inter
 }
 
 // Create takes the representation of a database and creates it.  Returns the server's representation of the database, and an error, if there is any.
-func (c *databases) Create(ctx context.Context, database *v1alpha3.Database, opts v1.CreateOptions) (result *v1alpha3.Database, err error) {
-	result = &v1alpha3.Database{}
+func (c *databases) Create(ctx context.Context, database *v1alpha4.Database, opts v1.CreateOptions) (result *v1alpha4.Database, err error) {
+	result = &v1alpha4.Database{}
 	err = c.client.Post().
 		Namespace(c.ns).
 		Resource("databases").
@@ -123,8 +123,8 @@ func (c *databases) Create(ctx context.Context, database *v1alpha3.Database, opt
 }
 
 // Update takes the representation of a database and updates it. Returns the server's representation of the database, and an error, if there is any.
-func (c *databases) Update(ctx context.Context, database *v1alpha3.Database, opts v1.UpdateOptions) (result *v1alpha3.Database, err error) {
-	result = &v1alpha3.Database{}
+func (c *databases) Update(ctx context.Context, database *v1alpha4.Database, opts v1.UpdateOptions) (result *v1alpha4.Database, err error) {
+	result = &v1alpha4.Database{}
 	err = c.client.Put().
 		Namespace(c.ns).
 		Resource("databases").
@@ -138,8 +138,8 @@ func (c *databases) Update(ctx context.Context, database *v1alpha3.Database, opt
 
 // UpdateStatus was generated because the type contains a Status member.
 // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
-func (c *databases) UpdateStatus(ctx context.Context, database *v1alpha3.Database, opts v1.UpdateOptions) (result *v1alpha3.Database, err error) {
-	result = &v1alpha3.Database{}
+func (c *databases) UpdateStatus(ctx context.Context, database *v1alpha4.Database, opts v1.UpdateOptions) (result *v1alpha4.Database, err error) {
+	result = &v1alpha4.Database{}
 	err = c.client.Put().
 		Namespace(c.ns).
 		Resource("databases").
@@ -180,8 +180,8 @@ func (c *databases) DeleteCollection(ctx context.Context, opts v1.DeleteOptions,
 }
 
 // Patch applies the patch and returns the patched database.
-func (c *databases) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Database, err error) {
-	result = &v1alpha3.Database{}
+func (c *databases) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha4.Database, err error) {
+	result = &v1alpha4.Database{}
 	err = c.client.Patch(pt).
 		Namespace(c.ns).
 		Resource("databases").
diff --git a/pkg/client/schemaheroclientset/typed/databases/v1alpha3/databases_client.go b/pkg/client/schemaheroclientset/typed/databases/v1alpha3/databases_client.go
index 1c64bf7d..f52d26fd 100644
--- a/pkg/client/schemaheroclientset/typed/databases/v1alpha3/databases_client.go
+++ b/pkg/client/schemaheroclientset/typed/databases/v1alpha3/databases_client.go
@@ -16,30 +16,30 @@ limitations under the License.
 
 // Code generated by client-gen. DO NOT EDIT.
 
-package v1alpha3
+package v1alpha4
 
 import (
-	v1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	v1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/client/schemaheroclientset/scheme"
 	rest "k8s.io/client-go/rest"
 )
 
-type DatabasesV1alpha3Interface interface {
+type Databasesv1alpha4Interface interface {
 	RESTClient() rest.Interface
 	DatabasesGetter
 }
 
-// DatabasesV1alpha3Client is used to interact with features provided by the databases.schemahero.io group.
-type DatabasesV1alpha3Client struct {
+// Databasesv1alpha4Client is used to interact with features provided by the databases.schemahero.io group.
+type Databasesv1alpha4Client struct {
 	restClient rest.Interface
 }
 
-func (c *DatabasesV1alpha3Client) Databases(namespace string) DatabaseInterface {
+func (c *Databasesv1alpha4Client) Databases(namespace string) DatabaseInterface {
 	return newDatabases(c, namespace)
 }
 
-// NewForConfig creates a new DatabasesV1alpha3Client for the given config.
-func NewForConfig(c *rest.Config) (*DatabasesV1alpha3Client, error) {
+// NewForConfig creates a new Databasesv1alpha4Client for the given config.
+func NewForConfig(c *rest.Config) (*Databasesv1alpha4Client, error) {
 	config := *c
 	if err := setConfigDefaults(&config); err != nil {
 		return nil, err
@@ -48,12 +48,12 @@ func NewForConfig(c *rest.Config) (*DatabasesV1alpha3Client, error) {
 	if err != nil {
 		return nil, err
 	}
-	return &DatabasesV1alpha3Client{client}, nil
+	return &Databasesv1alpha4Client{client}, nil
 }
 
-// NewForConfigOrDie creates a new DatabasesV1alpha3Client for the given config and
+// NewForConfigOrDie creates a new Databasesv1alpha4Client for the given config and
 // panics if there is an error in the config.
-func NewForConfigOrDie(c *rest.Config) *DatabasesV1alpha3Client {
+func NewForConfigOrDie(c *rest.Config) *Databasesv1alpha4Client {
 	client, err := NewForConfig(c)
 	if err != nil {
 		panic(err)
@@ -61,13 +61,13 @@ func NewForConfigOrDie(c *rest.Config) *DatabasesV1alpha3Client {
 	return client
 }
 
-// New creates a new DatabasesV1alpha3Client for the given RESTClient.
-func New(c rest.Interface) *DatabasesV1alpha3Client {
-	return &DatabasesV1alpha3Client{c}
+// New creates a new Databasesv1alpha4Client for the given RESTClient.
+func New(c rest.Interface) *Databasesv1alpha4Client {
+	return &Databasesv1alpha4Client{c}
 }
 
 func setConfigDefaults(config *rest.Config) error {
-	gv := v1alpha3.SchemeGroupVersion
+	gv := v1alpha4.SchemeGroupVersion
 	config.GroupVersion = &gv
 	config.APIPath = "/apis"
 	config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
@@ -81,7 +81,7 @@ func setConfigDefaults(config *rest.Config) error {
 
 // RESTClient returns a RESTClient that is used to communicate
 // with API server by this client implementation.
-func (c *DatabasesV1alpha3Client) RESTClient() rest.Interface {
+func (c *Databasesv1alpha4Client) RESTClient() rest.Interface {
 	if c == nil {
 		return nil
 	}
diff --git a/pkg/client/schemaheroclientset/typed/databases/v1alpha3/doc.go b/pkg/client/schemaheroclientset/typed/databases/v1alpha3/doc.go
index 289258d1..6db43c0d 100644
--- a/pkg/client/schemaheroclientset/typed/databases/v1alpha3/doc.go
+++ b/pkg/client/schemaheroclientset/typed/databases/v1alpha3/doc.go
@@ -17,4 +17,4 @@ limitations under the License.
 // Code generated by client-gen. DO NOT EDIT.
 
 // This package has the automatically generated typed clients.
-package v1alpha3
+package v1alpha4
diff --git a/pkg/client/schemaheroclientset/typed/databases/v1alpha3/fake/fake_database.go b/pkg/client/schemaheroclientset/typed/databases/v1alpha3/fake/fake_database.go
index d3c2d1f0..85178f6f 100644
--- a/pkg/client/schemaheroclientset/typed/databases/v1alpha3/fake/fake_database.go
+++ b/pkg/client/schemaheroclientset/typed/databases/v1alpha3/fake/fake_database.go
@@ -21,7 +21,7 @@ package fake
 import (
 	"context"
 
-	v1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	v1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	labels "k8s.io/apimachinery/pkg/labels"
 	schema "k8s.io/apimachinery/pkg/runtime/schema"
@@ -32,29 +32,29 @@ import (
 
 // FakeDatabases implements DatabaseInterface
 type FakeDatabases struct {
-	Fake *FakeDatabasesV1alpha3
+	Fake *FakeDatabasesv1alpha4
 	ns   string
 }
 
-var databasesResource = schema.GroupVersionResource{Group: "databases.schemahero.io", Version: "v1alpha3", Resource: "databases"}
+var databasesResource = schema.GroupVersionResource{Group: "databases.schemahero.io", Version: "v1alpha4", Resource: "databases"}
 
-var databasesKind = schema.GroupVersionKind{Group: "databases.schemahero.io", Version: "v1alpha3", Kind: "Database"}
+var databasesKind = schema.GroupVersionKind{Group: "databases.schemahero.io", Version: "v1alpha4", Kind: "Database"}
 
 // Get takes name of the database, and returns the corresponding database object, and an error if there is any.
-func (c *FakeDatabases) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha3.Database, err error) {
+func (c *FakeDatabases) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha4.Database, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewGetAction(databasesResource, c.ns, name), &v1alpha3.Database{})
+		Invokes(testing.NewGetAction(databasesResource, c.ns, name), &v1alpha4.Database{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Database), err
+	return obj.(*v1alpha4.Database), err
 }
 
 // List takes label and field selectors, and returns the list of Databases that match those selectors.
-func (c *FakeDatabases) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha3.DatabaseList, err error) {
+func (c *FakeDatabases) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha4.DatabaseList, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewListAction(databasesResource, databasesKind, c.ns, opts), &v1alpha3.DatabaseList{})
+		Invokes(testing.NewListAction(databasesResource, databasesKind, c.ns, opts), &v1alpha4.DatabaseList{})
 
 	if obj == nil {
 		return nil, err
@@ -64,8 +64,8 @@ func (c *FakeDatabases) List(ctx context.Context, opts v1.ListOptions) (result *
 	if label == nil {
 		label = labels.Everything()
 	}
-	list := &v1alpha3.DatabaseList{ListMeta: obj.(*v1alpha3.DatabaseList).ListMeta}
-	for _, item := range obj.(*v1alpha3.DatabaseList).Items {
+	list := &v1alpha4.DatabaseList{ListMeta: obj.(*v1alpha4.DatabaseList).ListMeta}
+	for _, item := range obj.(*v1alpha4.DatabaseList).Items {
 		if label.Matches(labels.Set(item.Labels)) {
 			list.Items = append(list.Items, item)
 		}
@@ -81,43 +81,43 @@ func (c *FakeDatabases) Watch(ctx context.Context, opts v1.ListOptions) (watch.I
 }
 
 // Create takes the representation of a database and creates it.  Returns the server's representation of the database, and an error, if there is any.
-func (c *FakeDatabases) Create(ctx context.Context, database *v1alpha3.Database, opts v1.CreateOptions) (result *v1alpha3.Database, err error) {
+func (c *FakeDatabases) Create(ctx context.Context, database *v1alpha4.Database, opts v1.CreateOptions) (result *v1alpha4.Database, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewCreateAction(databasesResource, c.ns, database), &v1alpha3.Database{})
+		Invokes(testing.NewCreateAction(databasesResource, c.ns, database), &v1alpha4.Database{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Database), err
+	return obj.(*v1alpha4.Database), err
 }
 
 // Update takes the representation of a database and updates it. Returns the server's representation of the database, and an error, if there is any.
-func (c *FakeDatabases) Update(ctx context.Context, database *v1alpha3.Database, opts v1.UpdateOptions) (result *v1alpha3.Database, err error) {
+func (c *FakeDatabases) Update(ctx context.Context, database *v1alpha4.Database, opts v1.UpdateOptions) (result *v1alpha4.Database, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewUpdateAction(databasesResource, c.ns, database), &v1alpha3.Database{})
+		Invokes(testing.NewUpdateAction(databasesResource, c.ns, database), &v1alpha4.Database{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Database), err
+	return obj.(*v1alpha4.Database), err
 }
 
 // UpdateStatus was generated because the type contains a Status member.
 // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
-func (c *FakeDatabases) UpdateStatus(ctx context.Context, database *v1alpha3.Database, opts v1.UpdateOptions) (*v1alpha3.Database, error) {
+func (c *FakeDatabases) UpdateStatus(ctx context.Context, database *v1alpha4.Database, opts v1.UpdateOptions) (*v1alpha4.Database, error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewUpdateSubresourceAction(databasesResource, "status", c.ns, database), &v1alpha3.Database{})
+		Invokes(testing.NewUpdateSubresourceAction(databasesResource, "status", c.ns, database), &v1alpha4.Database{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Database), err
+	return obj.(*v1alpha4.Database), err
 }
 
 // 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), &v1alpha3.Database{})
+		Invokes(testing.NewDeleteAction(databasesResource, c.ns, name), &v1alpha4.Database{})
 
 	return err
 }
@@ -126,17 +126,17 @@ func (c *FakeDatabases) Delete(ctx context.Context, name string, opts v1.DeleteO
 func (c *FakeDatabases) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
 	action := testing.NewDeleteCollectionAction(databasesResource, c.ns, listOpts)
 
-	_, err := c.Fake.Invokes(action, &v1alpha3.DatabaseList{})
+	_, err := c.Fake.Invokes(action, &v1alpha4.DatabaseList{})
 	return err
 }
 
 // Patch applies the patch and returns the patched database.
-func (c *FakeDatabases) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Database, err error) {
+func (c *FakeDatabases) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha4.Database, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewPatchSubresourceAction(databasesResource, c.ns, name, pt, data, subresources...), &v1alpha3.Database{})
+		Invokes(testing.NewPatchSubresourceAction(databasesResource, c.ns, name, pt, data, subresources...), &v1alpha4.Database{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Database), err
+	return obj.(*v1alpha4.Database), err
 }
diff --git a/pkg/client/schemaheroclientset/typed/databases/v1alpha3/fake/fake_databases_client.go b/pkg/client/schemaheroclientset/typed/databases/v1alpha3/fake/fake_databases_client.go
index 5eb9ab91..1422f394 100644
--- a/pkg/client/schemaheroclientset/typed/databases/v1alpha3/fake/fake_databases_client.go
+++ b/pkg/client/schemaheroclientset/typed/databases/v1alpha3/fake/fake_databases_client.go
@@ -19,22 +19,22 @@ limitations under the License.
 package fake
 
 import (
-	v1alpha3 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
+	v1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
 	rest "k8s.io/client-go/rest"
 	testing "k8s.io/client-go/testing"
 )
 
-type FakeDatabasesV1alpha3 struct {
+type FakeDatabasesv1alpha4 struct {
 	*testing.Fake
 }
 
-func (c *FakeDatabasesV1alpha3) Databases(namespace string) v1alpha3.DatabaseInterface {
+func (c *FakeDatabasesv1alpha4) Databases(namespace string) v1alpha4.DatabaseInterface {
 	return &FakeDatabases{c, namespace}
 }
 
 // RESTClient returns a RESTClient that is used to communicate
 // with API server by this client implementation.
-func (c *FakeDatabasesV1alpha3) RESTClient() rest.Interface {
+func (c *FakeDatabasesv1alpha4) RESTClient() rest.Interface {
 	var ret *rest.RESTClient
 	return ret
 }
diff --git a/pkg/client/schemaheroclientset/typed/databases/v1alpha3/generated_expansion.go b/pkg/client/schemaheroclientset/typed/databases/v1alpha3/generated_expansion.go
index 1d9c4ccc..8d3525ed 100644
--- a/pkg/client/schemaheroclientset/typed/databases/v1alpha3/generated_expansion.go
+++ b/pkg/client/schemaheroclientset/typed/databases/v1alpha3/generated_expansion.go
@@ -16,6 +16,6 @@ limitations under the License.
 
 // Code generated by client-gen. DO NOT EDIT.
 
-package v1alpha3
+package v1alpha4
 
 type DatabaseExpansion interface{}
diff --git a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/doc.go b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/doc.go
index 289258d1..6db43c0d 100644
--- a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/doc.go
+++ b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/doc.go
@@ -17,4 +17,4 @@ limitations under the License.
 // Code generated by client-gen. DO NOT EDIT.
 
 // This package has the automatically generated typed clients.
-package v1alpha3
+package v1alpha4
diff --git a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/fake/fake_migration.go b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/fake/fake_migration.go
index a1302def..be760133 100644
--- a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/fake/fake_migration.go
+++ b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/fake/fake_migration.go
@@ -21,7 +21,7 @@ package fake
 import (
 	"context"
 
-	v1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	v1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	labels "k8s.io/apimachinery/pkg/labels"
 	schema "k8s.io/apimachinery/pkg/runtime/schema"
@@ -32,29 +32,29 @@ import (
 
 // FakeMigrations implements MigrationInterface
 type FakeMigrations struct {
-	Fake *FakeSchemasV1alpha3
+	Fake *FakeSchemasv1alpha4
 	ns   string
 }
 
-var migrationsResource = schema.GroupVersionResource{Group: "schemas.schemahero.io", Version: "v1alpha3", Resource: "migrations"}
+var migrationsResource = schema.GroupVersionResource{Group: "schemas.schemahero.io", Version: "v1alpha4", Resource: "migrations"}
 
-var migrationsKind = schema.GroupVersionKind{Group: "schemas.schemahero.io", Version: "v1alpha3", Kind: "Migration"}
+var migrationsKind = schema.GroupVersionKind{Group: "schemas.schemahero.io", Version: "v1alpha4", Kind: "Migration"}
 
 // Get takes name of the migration, and returns the corresponding migration object, and an error if there is any.
-func (c *FakeMigrations) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha3.Migration, err error) {
+func (c *FakeMigrations) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha4.Migration, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewGetAction(migrationsResource, c.ns, name), &v1alpha3.Migration{})
+		Invokes(testing.NewGetAction(migrationsResource, c.ns, name), &v1alpha4.Migration{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Migration), err
+	return obj.(*v1alpha4.Migration), err
 }
 
 // List takes label and field selectors, and returns the list of Migrations that match those selectors.
-func (c *FakeMigrations) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha3.MigrationList, err error) {
+func (c *FakeMigrations) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha4.MigrationList, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewListAction(migrationsResource, migrationsKind, c.ns, opts), &v1alpha3.MigrationList{})
+		Invokes(testing.NewListAction(migrationsResource, migrationsKind, c.ns, opts), &v1alpha4.MigrationList{})
 
 	if obj == nil {
 		return nil, err
@@ -64,8 +64,8 @@ func (c *FakeMigrations) List(ctx context.Context, opts v1.ListOptions) (result
 	if label == nil {
 		label = labels.Everything()
 	}
-	list := &v1alpha3.MigrationList{ListMeta: obj.(*v1alpha3.MigrationList).ListMeta}
-	for _, item := range obj.(*v1alpha3.MigrationList).Items {
+	list := &v1alpha4.MigrationList{ListMeta: obj.(*v1alpha4.MigrationList).ListMeta}
+	for _, item := range obj.(*v1alpha4.MigrationList).Items {
 		if label.Matches(labels.Set(item.Labels)) {
 			list.Items = append(list.Items, item)
 		}
@@ -81,43 +81,43 @@ func (c *FakeMigrations) Watch(ctx context.Context, opts v1.ListOptions) (watch.
 }
 
 // Create takes the representation of a migration and creates it.  Returns the server's representation of the migration, and an error, if there is any.
-func (c *FakeMigrations) Create(ctx context.Context, migration *v1alpha3.Migration, opts v1.CreateOptions) (result *v1alpha3.Migration, err error) {
+func (c *FakeMigrations) Create(ctx context.Context, migration *v1alpha4.Migration, opts v1.CreateOptions) (result *v1alpha4.Migration, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewCreateAction(migrationsResource, c.ns, migration), &v1alpha3.Migration{})
+		Invokes(testing.NewCreateAction(migrationsResource, c.ns, migration), &v1alpha4.Migration{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Migration), err
+	return obj.(*v1alpha4.Migration), err
 }
 
 // Update takes the representation of a migration and updates it. Returns the server's representation of the migration, and an error, if there is any.
-func (c *FakeMigrations) Update(ctx context.Context, migration *v1alpha3.Migration, opts v1.UpdateOptions) (result *v1alpha3.Migration, err error) {
+func (c *FakeMigrations) Update(ctx context.Context, migration *v1alpha4.Migration, opts v1.UpdateOptions) (result *v1alpha4.Migration, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewUpdateAction(migrationsResource, c.ns, migration), &v1alpha3.Migration{})
+		Invokes(testing.NewUpdateAction(migrationsResource, c.ns, migration), &v1alpha4.Migration{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Migration), err
+	return obj.(*v1alpha4.Migration), err
 }
 
 // UpdateStatus was generated because the type contains a Status member.
 // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
-func (c *FakeMigrations) UpdateStatus(ctx context.Context, migration *v1alpha3.Migration, opts v1.UpdateOptions) (*v1alpha3.Migration, error) {
+func (c *FakeMigrations) UpdateStatus(ctx context.Context, migration *v1alpha4.Migration, opts v1.UpdateOptions) (*v1alpha4.Migration, error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewUpdateSubresourceAction(migrationsResource, "status", c.ns, migration), &v1alpha3.Migration{})
+		Invokes(testing.NewUpdateSubresourceAction(migrationsResource, "status", c.ns, migration), &v1alpha4.Migration{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Migration), err
+	return obj.(*v1alpha4.Migration), err
 }
 
 // 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), &v1alpha3.Migration{})
+		Invokes(testing.NewDeleteAction(migrationsResource, c.ns, name), &v1alpha4.Migration{})
 
 	return err
 }
@@ -126,17 +126,17 @@ func (c *FakeMigrations) Delete(ctx context.Context, name string, opts v1.Delete
 func (c *FakeMigrations) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
 	action := testing.NewDeleteCollectionAction(migrationsResource, c.ns, listOpts)
 
-	_, err := c.Fake.Invokes(action, &v1alpha3.MigrationList{})
+	_, err := c.Fake.Invokes(action, &v1alpha4.MigrationList{})
 	return err
 }
 
 // Patch applies the patch and returns the patched migration.
-func (c *FakeMigrations) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Migration, err error) {
+func (c *FakeMigrations) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha4.Migration, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewPatchSubresourceAction(migrationsResource, c.ns, name, pt, data, subresources...), &v1alpha3.Migration{})
+		Invokes(testing.NewPatchSubresourceAction(migrationsResource, c.ns, name, pt, data, subresources...), &v1alpha4.Migration{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Migration), err
+	return obj.(*v1alpha4.Migration), err
 }
diff --git a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/fake/fake_schemas_client.go b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/fake/fake_schemas_client.go
index 2dea4038..5dd3ce9f 100644
--- a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/fake/fake_schemas_client.go
+++ b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/fake/fake_schemas_client.go
@@ -19,26 +19,26 @@ limitations under the License.
 package fake
 
 import (
-	v1alpha3 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
+	v1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
 	rest "k8s.io/client-go/rest"
 	testing "k8s.io/client-go/testing"
 )
 
-type FakeSchemasV1alpha3 struct {
+type FakeSchemasv1alpha4 struct {
 	*testing.Fake
 }
 
-func (c *FakeSchemasV1alpha3) Migrations(namespace string) v1alpha3.MigrationInterface {
+func (c *FakeSchemasv1alpha4) Migrations(namespace string) v1alpha4.MigrationInterface {
 	return &FakeMigrations{c, namespace}
 }
 
-func (c *FakeSchemasV1alpha3) Tables(namespace string) v1alpha3.TableInterface {
+func (c *FakeSchemasv1alpha4) Tables(namespace string) v1alpha4.TableInterface {
 	return &FakeTables{c, namespace}
 }
 
 // RESTClient returns a RESTClient that is used to communicate
 // with API server by this client implementation.
-func (c *FakeSchemasV1alpha3) RESTClient() rest.Interface {
+func (c *FakeSchemasv1alpha4) RESTClient() rest.Interface {
 	var ret *rest.RESTClient
 	return ret
 }
diff --git a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/fake/fake_table.go b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/fake/fake_table.go
index 5bd7f789..3d6182f7 100644
--- a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/fake/fake_table.go
+++ b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/fake/fake_table.go
@@ -21,7 +21,7 @@ package fake
 import (
 	"context"
 
-	v1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	v1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	labels "k8s.io/apimachinery/pkg/labels"
 	schema "k8s.io/apimachinery/pkg/runtime/schema"
@@ -32,29 +32,29 @@ import (
 
 // FakeTables implements TableInterface
 type FakeTables struct {
-	Fake *FakeSchemasV1alpha3
+	Fake *FakeSchemasv1alpha4
 	ns   string
 }
 
-var tablesResource = schema.GroupVersionResource{Group: "schemas.schemahero.io", Version: "v1alpha3", Resource: "tables"}
+var tablesResource = schema.GroupVersionResource{Group: "schemas.schemahero.io", Version: "v1alpha4", Resource: "tables"}
 
-var tablesKind = schema.GroupVersionKind{Group: "schemas.schemahero.io", Version: "v1alpha3", Kind: "Table"}
+var tablesKind = schema.GroupVersionKind{Group: "schemas.schemahero.io", Version: "v1alpha4", Kind: "Table"}
 
 // Get takes name of the table, and returns the corresponding table object, and an error if there is any.
-func (c *FakeTables) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha3.Table, err error) {
+func (c *FakeTables) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha4.Table, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewGetAction(tablesResource, c.ns, name), &v1alpha3.Table{})
+		Invokes(testing.NewGetAction(tablesResource, c.ns, name), &v1alpha4.Table{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Table), err
+	return obj.(*v1alpha4.Table), err
 }
 
 // List takes label and field selectors, and returns the list of Tables that match those selectors.
-func (c *FakeTables) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha3.TableList, err error) {
+func (c *FakeTables) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha4.TableList, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewListAction(tablesResource, tablesKind, c.ns, opts), &v1alpha3.TableList{})
+		Invokes(testing.NewListAction(tablesResource, tablesKind, c.ns, opts), &v1alpha4.TableList{})
 
 	if obj == nil {
 		return nil, err
@@ -64,8 +64,8 @@ func (c *FakeTables) List(ctx context.Context, opts v1.ListOptions) (result *v1a
 	if label == nil {
 		label = labels.Everything()
 	}
-	list := &v1alpha3.TableList{ListMeta: obj.(*v1alpha3.TableList).ListMeta}
-	for _, item := range obj.(*v1alpha3.TableList).Items {
+	list := &v1alpha4.TableList{ListMeta: obj.(*v1alpha4.TableList).ListMeta}
+	for _, item := range obj.(*v1alpha4.TableList).Items {
 		if label.Matches(labels.Set(item.Labels)) {
 			list.Items = append(list.Items, item)
 		}
@@ -81,43 +81,43 @@ func (c *FakeTables) Watch(ctx context.Context, opts v1.ListOptions) (watch.Inte
 }
 
 // Create takes the representation of a table and creates it.  Returns the server's representation of the table, and an error, if there is any.
-func (c *FakeTables) Create(ctx context.Context, table *v1alpha3.Table, opts v1.CreateOptions) (result *v1alpha3.Table, err error) {
+func (c *FakeTables) Create(ctx context.Context, table *v1alpha4.Table, opts v1.CreateOptions) (result *v1alpha4.Table, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewCreateAction(tablesResource, c.ns, table), &v1alpha3.Table{})
+		Invokes(testing.NewCreateAction(tablesResource, c.ns, table), &v1alpha4.Table{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Table), err
+	return obj.(*v1alpha4.Table), err
 }
 
 // Update takes the representation of a table and updates it. Returns the server's representation of the table, and an error, if there is any.
-func (c *FakeTables) Update(ctx context.Context, table *v1alpha3.Table, opts v1.UpdateOptions) (result *v1alpha3.Table, err error) {
+func (c *FakeTables) Update(ctx context.Context, table *v1alpha4.Table, opts v1.UpdateOptions) (result *v1alpha4.Table, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewUpdateAction(tablesResource, c.ns, table), &v1alpha3.Table{})
+		Invokes(testing.NewUpdateAction(tablesResource, c.ns, table), &v1alpha4.Table{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Table), err
+	return obj.(*v1alpha4.Table), err
 }
 
 // UpdateStatus was generated because the type contains a Status member.
 // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
-func (c *FakeTables) UpdateStatus(ctx context.Context, table *v1alpha3.Table, opts v1.UpdateOptions) (*v1alpha3.Table, error) {
+func (c *FakeTables) UpdateStatus(ctx context.Context, table *v1alpha4.Table, opts v1.UpdateOptions) (*v1alpha4.Table, error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewUpdateSubresourceAction(tablesResource, "status", c.ns, table), &v1alpha3.Table{})
+		Invokes(testing.NewUpdateSubresourceAction(tablesResource, "status", c.ns, table), &v1alpha4.Table{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Table), err
+	return obj.(*v1alpha4.Table), err
 }
 
 // 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), &v1alpha3.Table{})
+		Invokes(testing.NewDeleteAction(tablesResource, c.ns, name), &v1alpha4.Table{})
 
 	return err
 }
@@ -126,17 +126,17 @@ func (c *FakeTables) Delete(ctx context.Context, name string, opts v1.DeleteOpti
 func (c *FakeTables) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
 	action := testing.NewDeleteCollectionAction(tablesResource, c.ns, listOpts)
 
-	_, err := c.Fake.Invokes(action, &v1alpha3.TableList{})
+	_, err := c.Fake.Invokes(action, &v1alpha4.TableList{})
 	return err
 }
 
 // Patch applies the patch and returns the patched table.
-func (c *FakeTables) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Table, err error) {
+func (c *FakeTables) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha4.Table, err error) {
 	obj, err := c.Fake.
-		Invokes(testing.NewPatchSubresourceAction(tablesResource, c.ns, name, pt, data, subresources...), &v1alpha3.Table{})
+		Invokes(testing.NewPatchSubresourceAction(tablesResource, c.ns, name, pt, data, subresources...), &v1alpha4.Table{})
 
 	if obj == nil {
 		return nil, err
 	}
-	return obj.(*v1alpha3.Table), err
+	return obj.(*v1alpha4.Table), err
 }
diff --git a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/generated_expansion.go b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/generated_expansion.go
index fb4d5b2c..e46c385a 100644
--- a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/generated_expansion.go
+++ b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/generated_expansion.go
@@ -16,7 +16,7 @@ limitations under the License.
 
 // Code generated by client-gen. DO NOT EDIT.
 
-package v1alpha3
+package v1alpha4
 
 type MigrationExpansion interface{}
 
diff --git a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/migration.go b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/migration.go
index 78e8070a..7f1fe598 100644
--- a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/migration.go
+++ b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/migration.go
@@ -16,13 +16,13 @@ limitations under the License.
 
 // Code generated by client-gen. DO NOT EDIT.
 
-package v1alpha3
+package v1alpha4
 
 import (
 	"context"
 	"time"
 
-	v1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	v1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	scheme "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/scheme"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	types "k8s.io/apimachinery/pkg/types"
@@ -38,15 +38,15 @@ type MigrationsGetter interface {
 
 // MigrationInterface has methods to work with Migration resources.
 type MigrationInterface interface {
-	Create(ctx context.Context, migration *v1alpha3.Migration, opts v1.CreateOptions) (*v1alpha3.Migration, error)
-	Update(ctx context.Context, migration *v1alpha3.Migration, opts v1.UpdateOptions) (*v1alpha3.Migration, error)
-	UpdateStatus(ctx context.Context, migration *v1alpha3.Migration, opts v1.UpdateOptions) (*v1alpha3.Migration, error)
+	Create(ctx context.Context, migration *v1alpha4.Migration, opts v1.CreateOptions) (*v1alpha4.Migration, error)
+	Update(ctx context.Context, migration *v1alpha4.Migration, opts v1.UpdateOptions) (*v1alpha4.Migration, error)
+	UpdateStatus(ctx context.Context, migration *v1alpha4.Migration, opts v1.UpdateOptions) (*v1alpha4.Migration, error)
 	Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
 	DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
-	Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha3.Migration, error)
-	List(ctx context.Context, opts v1.ListOptions) (*v1alpha3.MigrationList, error)
+	Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha4.Migration, error)
+	List(ctx context.Context, opts v1.ListOptions) (*v1alpha4.MigrationList, error)
 	Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
-	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Migration, err error)
+	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha4.Migration, err error)
 	MigrationExpansion
 }
 
@@ -57,7 +57,7 @@ type migrations struct {
 }
 
 // newMigrations returns a Migrations
-func newMigrations(c *SchemasV1alpha3Client, namespace string) *migrations {
+func newMigrations(c *Schemasv1alpha4Client, namespace string) *migrations {
 	return &migrations{
 		client: c.RESTClient(),
 		ns:     namespace,
@@ -65,8 +65,8 @@ func newMigrations(c *SchemasV1alpha3Client, namespace string) *migrations {
 }
 
 // Get takes name of the migration, and returns the corresponding migration object, and an error if there is any.
-func (c *migrations) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha3.Migration, err error) {
-	result = &v1alpha3.Migration{}
+func (c *migrations) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha4.Migration, err error) {
+	result = &v1alpha4.Migration{}
 	err = c.client.Get().
 		Namespace(c.ns).
 		Resource("migrations").
@@ -78,12 +78,12 @@ func (c *migrations) Get(ctx context.Context, name string, options v1.GetOptions
 }
 
 // List takes label and field selectors, and returns the list of Migrations that match those selectors.
-func (c *migrations) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha3.MigrationList, err error) {
+func (c *migrations) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha4.MigrationList, err error) {
 	var timeout time.Duration
 	if opts.TimeoutSeconds != nil {
 		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
 	}
-	result = &v1alpha3.MigrationList{}
+	result = &v1alpha4.MigrationList{}
 	err = c.client.Get().
 		Namespace(c.ns).
 		Resource("migrations").
@@ -110,8 +110,8 @@ func (c *migrations) Watch(ctx context.Context, opts v1.ListOptions) (watch.Inte
 }
 
 // Create takes the representation of a migration and creates it.  Returns the server's representation of the migration, and an error, if there is any.
-func (c *migrations) Create(ctx context.Context, migration *v1alpha3.Migration, opts v1.CreateOptions) (result *v1alpha3.Migration, err error) {
-	result = &v1alpha3.Migration{}
+func (c *migrations) Create(ctx context.Context, migration *v1alpha4.Migration, opts v1.CreateOptions) (result *v1alpha4.Migration, err error) {
+	result = &v1alpha4.Migration{}
 	err = c.client.Post().
 		Namespace(c.ns).
 		Resource("migrations").
@@ -123,8 +123,8 @@ func (c *migrations) Create(ctx context.Context, migration *v1alpha3.Migration,
 }
 
 // Update takes the representation of a migration and updates it. Returns the server's representation of the migration, and an error, if there is any.
-func (c *migrations) Update(ctx context.Context, migration *v1alpha3.Migration, opts v1.UpdateOptions) (result *v1alpha3.Migration, err error) {
-	result = &v1alpha3.Migration{}
+func (c *migrations) Update(ctx context.Context, migration *v1alpha4.Migration, opts v1.UpdateOptions) (result *v1alpha4.Migration, err error) {
+	result = &v1alpha4.Migration{}
 	err = c.client.Put().
 		Namespace(c.ns).
 		Resource("migrations").
@@ -138,8 +138,8 @@ func (c *migrations) Update(ctx context.Context, migration *v1alpha3.Migration,
 
 // UpdateStatus was generated because the type contains a Status member.
 // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
-func (c *migrations) UpdateStatus(ctx context.Context, migration *v1alpha3.Migration, opts v1.UpdateOptions) (result *v1alpha3.Migration, err error) {
-	result = &v1alpha3.Migration{}
+func (c *migrations) UpdateStatus(ctx context.Context, migration *v1alpha4.Migration, opts v1.UpdateOptions) (result *v1alpha4.Migration, err error) {
+	result = &v1alpha4.Migration{}
 	err = c.client.Put().
 		Namespace(c.ns).
 		Resource("migrations").
@@ -180,8 +180,8 @@ func (c *migrations) DeleteCollection(ctx context.Context, opts v1.DeleteOptions
 }
 
 // Patch applies the patch and returns the patched migration.
-func (c *migrations) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Migration, err error) {
-	result = &v1alpha3.Migration{}
+func (c *migrations) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha4.Migration, err error) {
+	result = &v1alpha4.Migration{}
 	err = c.client.Patch(pt).
 		Namespace(c.ns).
 		Resource("migrations").
diff --git a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/schemas_client.go b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/schemas_client.go
index 750c93ba..ecc55359 100644
--- a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/schemas_client.go
+++ b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/schemas_client.go
@@ -16,35 +16,35 @@ limitations under the License.
 
 // Code generated by client-gen. DO NOT EDIT.
 
-package v1alpha3
+package v1alpha4
 
 import (
-	v1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	v1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/client/schemaheroclientset/scheme"
 	rest "k8s.io/client-go/rest"
 )
 
-type SchemasV1alpha3Interface interface {
+type Schemasv1alpha4Interface interface {
 	RESTClient() rest.Interface
 	MigrationsGetter
 	TablesGetter
 }
 
-// SchemasV1alpha3Client is used to interact with features provided by the schemas.schemahero.io group.
-type SchemasV1alpha3Client struct {
+// Schemasv1alpha4Client is used to interact with features provided by the schemas.schemahero.io group.
+type Schemasv1alpha4Client struct {
 	restClient rest.Interface
 }
 
-func (c *SchemasV1alpha3Client) Migrations(namespace string) MigrationInterface {
+func (c *Schemasv1alpha4Client) Migrations(namespace string) MigrationInterface {
 	return newMigrations(c, namespace)
 }
 
-func (c *SchemasV1alpha3Client) Tables(namespace string) TableInterface {
+func (c *Schemasv1alpha4Client) Tables(namespace string) TableInterface {
 	return newTables(c, namespace)
 }
 
-// NewForConfig creates a new SchemasV1alpha3Client for the given config.
-func NewForConfig(c *rest.Config) (*SchemasV1alpha3Client, error) {
+// NewForConfig creates a new Schemasv1alpha4Client for the given config.
+func NewForConfig(c *rest.Config) (*Schemasv1alpha4Client, error) {
 	config := *c
 	if err := setConfigDefaults(&config); err != nil {
 		return nil, err
@@ -53,12 +53,12 @@ func NewForConfig(c *rest.Config) (*SchemasV1alpha3Client, error) {
 	if err != nil {
 		return nil, err
 	}
-	return &SchemasV1alpha3Client{client}, nil
+	return &Schemasv1alpha4Client{client}, nil
 }
 
-// NewForConfigOrDie creates a new SchemasV1alpha3Client for the given config and
+// NewForConfigOrDie creates a new Schemasv1alpha4Client for the given config and
 // panics if there is an error in the config.
-func NewForConfigOrDie(c *rest.Config) *SchemasV1alpha3Client {
+func NewForConfigOrDie(c *rest.Config) *Schemasv1alpha4Client {
 	client, err := NewForConfig(c)
 	if err != nil {
 		panic(err)
@@ -66,13 +66,13 @@ func NewForConfigOrDie(c *rest.Config) *SchemasV1alpha3Client {
 	return client
 }
 
-// New creates a new SchemasV1alpha3Client for the given RESTClient.
-func New(c rest.Interface) *SchemasV1alpha3Client {
-	return &SchemasV1alpha3Client{c}
+// New creates a new Schemasv1alpha4Client for the given RESTClient.
+func New(c rest.Interface) *Schemasv1alpha4Client {
+	return &Schemasv1alpha4Client{c}
 }
 
 func setConfigDefaults(config *rest.Config) error {
-	gv := v1alpha3.SchemeGroupVersion
+	gv := v1alpha4.SchemeGroupVersion
 	config.GroupVersion = &gv
 	config.APIPath = "/apis"
 	config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
@@ -86,7 +86,7 @@ func setConfigDefaults(config *rest.Config) error {
 
 // RESTClient returns a RESTClient that is used to communicate
 // with API server by this client implementation.
-func (c *SchemasV1alpha3Client) RESTClient() rest.Interface {
+func (c *Schemasv1alpha4Client) RESTClient() rest.Interface {
 	if c == nil {
 		return nil
 	}
diff --git a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/table.go b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/table.go
index 3223d134..42ee9056 100644
--- a/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/table.go
+++ b/pkg/client/schemaheroclientset/typed/schemas/v1alpha3/table.go
@@ -16,13 +16,13 @@ limitations under the License.
 
 // Code generated by client-gen. DO NOT EDIT.
 
-package v1alpha3
+package v1alpha4
 
 import (
 	"context"
 	"time"
 
-	v1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	v1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	scheme "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/scheme"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	types "k8s.io/apimachinery/pkg/types"
@@ -38,15 +38,15 @@ type TablesGetter interface {
 
 // TableInterface has methods to work with Table resources.
 type TableInterface interface {
-	Create(ctx context.Context, table *v1alpha3.Table, opts v1.CreateOptions) (*v1alpha3.Table, error)
-	Update(ctx context.Context, table *v1alpha3.Table, opts v1.UpdateOptions) (*v1alpha3.Table, error)
-	UpdateStatus(ctx context.Context, table *v1alpha3.Table, opts v1.UpdateOptions) (*v1alpha3.Table, error)
+	Create(ctx context.Context, table *v1alpha4.Table, opts v1.CreateOptions) (*v1alpha4.Table, error)
+	Update(ctx context.Context, table *v1alpha4.Table, opts v1.UpdateOptions) (*v1alpha4.Table, error)
+	UpdateStatus(ctx context.Context, table *v1alpha4.Table, opts v1.UpdateOptions) (*v1alpha4.Table, error)
 	Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
 	DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
-	Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha3.Table, error)
-	List(ctx context.Context, opts v1.ListOptions) (*v1alpha3.TableList, error)
+	Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha4.Table, error)
+	List(ctx context.Context, opts v1.ListOptions) (*v1alpha4.TableList, error)
 	Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
-	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Table, err error)
+	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha4.Table, err error)
 	TableExpansion
 }
 
@@ -57,7 +57,7 @@ type tables struct {
 }
 
 // newTables returns a Tables
-func newTables(c *SchemasV1alpha3Client, namespace string) *tables {
+func newTables(c *Schemasv1alpha4Client, namespace string) *tables {
 	return &tables{
 		client: c.RESTClient(),
 		ns:     namespace,
@@ -65,8 +65,8 @@ func newTables(c *SchemasV1alpha3Client, namespace string) *tables {
 }
 
 // Get takes name of the table, and returns the corresponding table object, and an error if there is any.
-func (c *tables) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha3.Table, err error) {
-	result = &v1alpha3.Table{}
+func (c *tables) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha4.Table, err error) {
+	result = &v1alpha4.Table{}
 	err = c.client.Get().
 		Namespace(c.ns).
 		Resource("tables").
@@ -78,12 +78,12 @@ func (c *tables) Get(ctx context.Context, name string, options v1.GetOptions) (r
 }
 
 // List takes label and field selectors, and returns the list of Tables that match those selectors.
-func (c *tables) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha3.TableList, err error) {
+func (c *tables) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha4.TableList, err error) {
 	var timeout time.Duration
 	if opts.TimeoutSeconds != nil {
 		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
 	}
-	result = &v1alpha3.TableList{}
+	result = &v1alpha4.TableList{}
 	err = c.client.Get().
 		Namespace(c.ns).
 		Resource("tables").
@@ -110,8 +110,8 @@ func (c *tables) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interfac
 }
 
 // Create takes the representation of a table and creates it.  Returns the server's representation of the table, and an error, if there is any.
-func (c *tables) Create(ctx context.Context, table *v1alpha3.Table, opts v1.CreateOptions) (result *v1alpha3.Table, err error) {
-	result = &v1alpha3.Table{}
+func (c *tables) Create(ctx context.Context, table *v1alpha4.Table, opts v1.CreateOptions) (result *v1alpha4.Table, err error) {
+	result = &v1alpha4.Table{}
 	err = c.client.Post().
 		Namespace(c.ns).
 		Resource("tables").
@@ -123,8 +123,8 @@ func (c *tables) Create(ctx context.Context, table *v1alpha3.Table, opts v1.Crea
 }
 
 // Update takes the representation of a table and updates it. Returns the server's representation of the table, and an error, if there is any.
-func (c *tables) Update(ctx context.Context, table *v1alpha3.Table, opts v1.UpdateOptions) (result *v1alpha3.Table, err error) {
-	result = &v1alpha3.Table{}
+func (c *tables) Update(ctx context.Context, table *v1alpha4.Table, opts v1.UpdateOptions) (result *v1alpha4.Table, err error) {
+	result = &v1alpha4.Table{}
 	err = c.client.Put().
 		Namespace(c.ns).
 		Resource("tables").
@@ -138,8 +138,8 @@ func (c *tables) Update(ctx context.Context, table *v1alpha3.Table, opts v1.Upda
 
 // UpdateStatus was generated because the type contains a Status member.
 // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
-func (c *tables) UpdateStatus(ctx context.Context, table *v1alpha3.Table, opts v1.UpdateOptions) (result *v1alpha3.Table, err error) {
-	result = &v1alpha3.Table{}
+func (c *tables) UpdateStatus(ctx context.Context, table *v1alpha4.Table, opts v1.UpdateOptions) (result *v1alpha4.Table, err error) {
+	result = &v1alpha4.Table{}
 	err = c.client.Put().
 		Namespace(c.ns).
 		Resource("tables").
@@ -180,8 +180,8 @@ func (c *tables) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, li
 }
 
 // Patch applies the patch and returns the patched table.
-func (c *tables) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.Table, err error) {
-	result = &v1alpha3.Table{}
+func (c *tables) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha4.Table, err error) {
+	result = &v1alpha4.Table{}
 	err = c.client.Patch(pt).
 		Namespace(c.ns).
 		Resource("tables").
diff --git a/pkg/controller/database/database_controller.go b/pkg/controller/database/database_controller.go
index c3ed29a5..91fcfef7 100644
--- a/pkg/controller/database/database_controller.go
+++ b/pkg/controller/database/database_controller.go
@@ -20,7 +20,7 @@ import (
 	"context"
 
 	"github.com/pkg/errors"
-	databasesv1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/logger"
 	"go.uber.org/zap"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -52,7 +52,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	}
 
 	// Watch for changes to Database kinds
-	err = c.Watch(&source.Kind{Type: &databasesv1alpha3.Database{}}, &handler.EnqueueRequestForObject{})
+	err = c.Watch(&source.Kind{Type: &databasesv1alpha4.Database{}}, &handler.EnqueueRequestForObject{})
 	if err != nil {
 		return err
 	}
@@ -92,11 +92,11 @@ func (r *ReconcileDatabase) Reconcile(request reconcile.Request) (reconcile.Resu
 	return reconcile.Result{}, nil
 }
 
-func (r *ReconcileDatabase) getInstance(request reconcile.Request) (*databasesv1alpha3.Database, error) {
-	instance := &databasesv1alpha3.Database{}
+func (r *ReconcileDatabase) getInstance(request reconcile.Request) (*databasesv1alpha4.Database, error) {
+	instance := &databasesv1alpha4.Database{}
 	err := r.Get(context.Background(), request.NamespacedName, instance)
 	if err != nil {
-		return nil, errors.Wrap(err, "failed to get databasesv1alpha3 instance")
+		return nil, errors.Wrap(err, "failed to get databasesv1alpha4 instance")
 	}
 
 	return instance, nil
diff --git a/pkg/controller/migration/lookup.go b/pkg/controller/migration/lookup.go
index a3dad5cf..4c8550d3 100644
--- a/pkg/controller/migration/lookup.go
+++ b/pkg/controller/migration/lookup.go
@@ -4,21 +4,21 @@ import (
 	"context"
 
 	"github.com/pkg/errors"
-	databasesv1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
-	databasesclientv1alpha3 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
-	schemasclientv1alpha3 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
+	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	databasesclientv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
+	schemasclientv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client/config"
 )
 
-func TableFromMigration(ctx context.Context, migration *schemasv1alpha3.Migration) (*schemasv1alpha3.Table, error) {
+func TableFromMigration(ctx context.Context, migration *schemasv1alpha4.Migration) (*schemasv1alpha4.Table, error) {
 	cfg, err := config.GetConfig()
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to get config")
 	}
 
-	schemasClient, err := schemasclientv1alpha3.NewForConfig(cfg)
+	schemasClient, err := schemasclientv1alpha4.NewForConfig(cfg)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to create schemas client")
 	}
@@ -31,13 +31,13 @@ func TableFromMigration(ctx context.Context, migration *schemasv1alpha3.Migratio
 	return table, nil
 }
 
-func DatabaseFromTable(ctx context.Context, table *schemasv1alpha3.Table) (*databasesv1alpha3.Database, error) {
+func DatabaseFromTable(ctx context.Context, table *schemasv1alpha4.Table) (*databasesv1alpha4.Database, error) {
 	cfg, err := config.GetConfig()
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to get config")
 	}
 
-	databasesClient, err := databasesclientv1alpha3.NewForConfig(cfg)
+	databasesClient, err := databasesclientv1alpha4.NewForConfig(cfg)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to create databases client")
 	}
diff --git a/pkg/controller/migration/migration_controller.go b/pkg/controller/migration/migration_controller.go
index 64b5ac02..55f4809e 100644
--- a/pkg/controller/migration/migration_controller.go
+++ b/pkg/controller/migration/migration_controller.go
@@ -21,8 +21,8 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
-	databasesv1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/logger"
 	corev1 "k8s.io/api/core/v1"
 	kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
@@ -58,7 +58,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	}
 
 	// Watch for changes to Migration
-	err = c.Watch(&source.Kind{Type: &schemasv1alpha3.Migration{}}, &handler.EnqueueRequestForObject{})
+	err = c.Watch(&source.Kind{Type: &schemasv1alpha4.Migration{}}, &handler.EnqueueRequestForObject{})
 	if err != nil {
 		return errors.Wrap(err, "failed to start watch on migrations")
 	}
@@ -127,18 +127,18 @@ func (r *ReconcileMigration) Reconcile(request reconcile.Request) (reconcile.Res
 	return reconcile.Result{}, errors.New("unknown error in migration reconciler")
 }
 
-func (r *ReconcileMigration) getInstance(request reconcile.Request) (*schemasv1alpha3.Migration, error) {
-	v1alpha3instance := &schemasv1alpha3.Migration{}
-	err := r.Get(context.Background(), request.NamespacedName, v1alpha3instance)
+func (r *ReconcileMigration) getInstance(request reconcile.Request) (*schemasv1alpha4.Migration, error) {
+	v1alpha4instance := &schemasv1alpha4.Migration{}
+	err := r.Get(context.Background(), request.NamespacedName, v1alpha4instance)
 	if err != nil {
 		return nil, err // don't wrap
 	}
 
-	return v1alpha3instance, nil
+	return v1alpha4instance, nil
 }
 
-func (r *ReconcileMigration) readConnectionURI(database *databasesv1alpha3.Database) (string, error) {
-	var valueOrValueFrom *databasesv1alpha3.ValueOrValueFrom
+func (r *ReconcileMigration) readConnectionURI(database *databasesv1alpha4.Database) (string, error) {
+	var valueOrValueFrom *databasesv1alpha4.ValueOrValueFrom
 
 	if database.Spec.Connection.Postgres != nil {
 		valueOrValueFrom = &database.Spec.Connection.Postgres.URI
diff --git a/pkg/controller/migration/objects.go b/pkg/controller/migration/objects.go
index 7f9a1888..881633ad 100644
--- a/pkg/controller/migration/objects.go
+++ b/pkg/controller/migration/objects.go
@@ -4,8 +4,8 @@ import (
 	"fmt"
 	"os"
 
-	databasesv1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	corev1 "k8s.io/api/core/v1"
 	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -54,7 +54,7 @@ func podNameForMigrationApply(databaseName string, tableName string, migrationID
 	return podName
 }
 
-func getApplyPod(migrationID string, namespace string, connectionURI string, database *databasesv1alpha3.Database, table *schemasv1alpha3.Table) (*corev1.Pod, error) {
+func getApplyPod(migrationID string, namespace string, connectionURI string, database *databasesv1alpha4.Database, table *schemasv1alpha4.Table) (*corev1.Pod, error) {
 	imageName := "schemahero/schemahero:alpha"
 	if os.Getenv("SCHEMAHERO_IMAGE_NAME") != "" {
 		imageName = os.Getenv("SCHEMAHERO_IMAGE_NAME")
@@ -115,9 +115,8 @@ func getApplyPod(migrationID string, namespace string, connectionURI string, dat
 			Kind:       "Pod",
 		},
 		Spec: corev1.PodSpec{
-			NodeSelector:       nodeSelector,
-			ServiceAccountName: database.Name,
-			RestartPolicy:      corev1.RestartPolicyOnFailure,
+			NodeSelector:  nodeSelector,
+			RestartPolicy: corev1.RestartPolicyOnFailure,
 			Containers: []corev1.Container{
 				{
 					Image:           imageName,
diff --git a/pkg/controller/migration/reconcile_migration.go b/pkg/controller/migration/reconcile_migration.go
index 5cfeb002..b2b912aa 100644
--- a/pkg/controller/migration/reconcile_migration.go
+++ b/pkg/controller/migration/reconcile_migration.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/pkg/errors"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/logger"
 	"go.uber.org/zap"
 	corev1 "k8s.io/api/core/v1"
@@ -14,7 +14,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 )
 
-func (r *ReconcileMigration) reconcileMigration(ctx context.Context, instance *schemasv1alpha3.Migration) (reconcile.Result, error) {
+func (r *ReconcileMigration) reconcileMigration(ctx context.Context, instance *schemasv1alpha4.Migration) (reconcile.Result, error) {
 	logger.Debug("reconciling migration",
 		zap.String("kind", instance.Kind),
 		zap.String("name", instance.Name),
diff --git a/pkg/controller/migration/reconcile_pod.go b/pkg/controller/migration/reconcile_pod.go
index ade68634..00916e31 100644
--- a/pkg/controller/migration/reconcile_pod.go
+++ b/pkg/controller/migration/reconcile_pod.go
@@ -5,7 +5,7 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/logger"
 	"go.uber.org/zap"
 	corev1 "k8s.io/api/core/v1"
@@ -42,7 +42,7 @@ func (r *ReconcileMigration) reconcilePod(ctx context.Context, pod *corev1.Pod)
 
 	// the migration has completed
 	// so lets store the executed at timestamp on the status
-	var instance schemasv1alpha3.Migration
+	var instance schemasv1alpha4.Migration
 	err := r.Get(ctx, types.NamespacedName{
 		Name:      migrationID,
 		Namespace: pod.Namespace,
diff --git a/pkg/controller/table/objects.go b/pkg/controller/table/objects.go
index 7a07f16b..2c2365a5 100644
--- a/pkg/controller/table/objects.go
+++ b/pkg/controller/table/objects.go
@@ -6,15 +6,15 @@ import (
 	"os"
 
 	"github.com/pkg/errors"
-	databasesv1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"gopkg.in/yaml.v2"
 	corev1 "k8s.io/api/core/v1"
 	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
-func configMapNameForPlan(database *databasesv1alpha3.Database, table *schemasv1alpha3.Table) string {
+func configMapNameForPlan(database *databasesv1alpha4.Database, table *schemasv1alpha4.Table) string {
 	shortID, err := getShortIDForTableSpec(table)
 	if err != nil {
 		return table.Name
@@ -30,7 +30,7 @@ func configMapNameForPlan(database *databasesv1alpha3.Database, table *schemasv1
 	return configMapName
 }
 
-func podNameForPlan(database *databasesv1alpha3.Database, table *schemasv1alpha3.Table) string {
+func podNameForPlan(database *databasesv1alpha4.Database, table *schemasv1alpha4.Table) string {
 	shortID, err := getShortIDForTableSpec(table)
 	if err != nil {
 		return table.Name
@@ -46,7 +46,7 @@ func podNameForPlan(database *databasesv1alpha3.Database, table *schemasv1alpha3
 	return podName
 }
 
-func getShortIDForTableSpec(table *schemasv1alpha3.Table) (string, error) {
+func getShortIDForTableSpec(table *schemasv1alpha4.Table) (string, error) {
 	b, err := yaml.Marshal(table.Spec)
 	if err != nil {
 		return "", errors.Wrap(err, "failed to marshal yaml spec")
@@ -56,7 +56,7 @@ func getShortIDForTableSpec(table *schemasv1alpha3.Table) (string, error) {
 	return fmt.Sprintf("%x", sum)[:7], nil
 }
 
-func getPlanConfigMap(database *databasesv1alpha3.Database, table *schemasv1alpha3.Table) (*corev1.ConfigMap, error) {
+func getPlanConfigMap(database *databasesv1alpha4.Database, table *schemasv1alpha4.Table) (*corev1.ConfigMap, error) {
 	b, err := yaml.Marshal(table.Spec)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to marshal yaml spec")
@@ -80,7 +80,7 @@ func getPlanConfigMap(database *databasesv1alpha3.Database, table *schemasv1alph
 	return configMap, nil
 }
 
-func getPlanServiceAccount(database *databasesv1alpha3.Database) *corev1.ServiceAccount {
+func getPlanServiceAccount(database *databasesv1alpha4.Database) *corev1.ServiceAccount {
 	b := true
 	sa := &corev1.ServiceAccount{
 		ObjectMeta: metav1.ObjectMeta{
@@ -97,7 +97,7 @@ func getPlanServiceAccount(database *databasesv1alpha3.Database) *corev1.Service
 	return sa
 }
 
-func (r *ReconcileTable) getPlanPod(database *databasesv1alpha3.Database, table *schemasv1alpha3.Table) (*corev1.Pod, error) {
+func (r *ReconcileTable) getPlanPod(database *databasesv1alpha4.Database, table *schemasv1alpha4.Table) (*corev1.Pod, error) {
 	imageName := "schemahero/schemahero:alpha"
 	if os.Getenv("SCHEMAHERO_IMAGE_NAME") != "" {
 		imageName = os.Getenv("SCHEMAHERO_IMAGE_NAME")
@@ -179,9 +179,8 @@ func (r *ReconcileTable) getPlanPod(database *databasesv1alpha3.Database, table
 			Kind:       "Pod",
 		},
 		Spec: corev1.PodSpec{
-			NodeSelector:       nodeSelector,
-			ServiceAccountName: database.Name,
-			RestartPolicy:      corev1.RestartPolicyOnFailure,
+			NodeSelector:  nodeSelector,
+			RestartPolicy: corev1.RestartPolicyOnFailure,
 			Containers: []corev1.Container{
 				{
 					Image:           imageName,
diff --git a/pkg/controller/table/reconcile_pod.go b/pkg/controller/table/reconcile_pod.go
index 9da5b1b3..cf95a19a 100644
--- a/pkg/controller/table/reconcile_pod.go
+++ b/pkg/controller/table/reconcile_pod.go
@@ -8,10 +8,10 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
-	databasesv1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
-	databasesclientv1alpha3 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
-	schemasclientv1alpha3 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
+	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	databasesclientv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
+	schemasclientv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/logger"
 	"go.uber.org/zap"
 	corev1 "k8s.io/api/core/v1"
@@ -91,7 +91,7 @@ func (r *ReconcileTable) reconcilePod(ctx context.Context, pod *corev1.Pod) (rec
 		return reconcile.Result{}, nil
 	}
 
-	schemasClient, err := schemasclientv1alpha3.NewForConfig(cfg)
+	schemasClient, err := schemasclientv1alpha4.NewForConfig(cfg)
 	if err != nil {
 		return reconcile.Result{}, errors.Wrap(err, "failed to create schema client")
 	}
@@ -111,27 +111,27 @@ func (r *ReconcileTable) reconcilePod(ctx context.Context, pod *corev1.Pod) (rec
 		return reconcile.Result{}, errors.Wrap(err, "failed to get sha of table")
 	}
 
-	desiredMigration := schemasv1alpha3.Migration{
+	desiredMigration := schemasv1alpha4.Migration{
 		TypeMeta: metav1.TypeMeta{
-			APIVersion: "schemas.schemahero.io/v1alpha3",
+			APIVersion: "schemas.schemahero.io/v1alpha4",
 			Kind:       "Migration",
 		},
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      tableSHA,
 			Namespace: table.Namespace,
 		},
-		Spec: schemasv1alpha3.MigrationSpec{
+		Spec: schemasv1alpha4.MigrationSpec{
 			GeneratedDDL:   out,
 			TableName:      table.Name,
 			TableNamespace: table.Namespace,
 		},
-		Status: schemasv1alpha3.MigrationStatus{
+		Status: schemasv1alpha4.MigrationStatus{
 			PlannedAt: time.Now().Unix(),
 		},
 	}
 
 	// If the database is set to immediate deploy, then set it as approved also
-	databasesClient, err := databasesclientv1alpha3.NewForConfig(cfg)
+	databasesClient, err := databasesclientv1alpha4.NewForConfig(cfg)
 	if err != nil {
 		return reconcile.Result{}, errors.Wrap(err, "Failed to create database client")
 	}
@@ -145,7 +145,7 @@ func (r *ReconcileTable) reconcilePod(ctx context.Context, pod *corev1.Pod) (rec
 		desiredMigration.Status.ApprovedAt = time.Now().Unix()
 	}
 
-	var existingMigration schemasv1alpha3.Migration
+	var existingMigration schemasv1alpha4.Migration
 	err = r.Get(ctx, types.NamespacedName{
 		Name:      desiredMigration.Name,
 		Namespace: desiredMigration.Namespace,
@@ -194,7 +194,7 @@ func (r *ReconcileTable) reconcilePod(ctx context.Context, pod *corev1.Pod) (rec
 	return reconcile.Result{}, nil
 }
 
-func (r *ReconcileTable) readConnectionURI(namespace string, valueOrValueFrom databasesv1alpha3.ValueOrValueFrom) (string, error) {
+func (r *ReconcileTable) readConnectionURI(namespace string, valueOrValueFrom databasesv1alpha4.ValueOrValueFrom) (string, error) {
 	if valueOrValueFrom.Value != "" {
 		return valueOrValueFrom.Value, nil
 	}
diff --git a/pkg/controller/table/reconile_table.go b/pkg/controller/table/reconile_table.go
index dce7355d..c144b256 100644
--- a/pkg/controller/table/reconile_table.go
+++ b/pkg/controller/table/reconile_table.go
@@ -5,9 +5,9 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
-	databasesv1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
-	databasesclientv1alpha3 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
+	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	databasesclientv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/logger"
 	"go.uber.org/zap"
 	corev1 "k8s.io/api/core/v1"
@@ -19,7 +19,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 )
 
-func (r *ReconcileTable) reconcileTable(ctx context.Context, instance *schemasv1alpha3.Table) (reconcile.Result, error) {
+func (r *ReconcileTable) reconcileTable(ctx context.Context, instance *schemasv1alpha4.Table) (reconcile.Result, error) {
 	logger.Debug("reconciling table",
 		zap.String("kind", instance.Kind),
 		zap.String("name", instance.Name),
@@ -73,17 +73,17 @@ func (r *ReconcileTable) reconcileTable(ctx context.Context, instance *schemasv1
 	return r.deployMigrationPlanPhase(ctx, database, instance)
 }
 
-func (r *ReconcileTable) getInstance(request reconcile.Request) (*schemasv1alpha3.Table, error) {
-	v1alpha3instance := &schemasv1alpha3.Table{}
-	err := r.Get(context.Background(), request.NamespacedName, v1alpha3instance)
+func (r *ReconcileTable) getInstance(request reconcile.Request) (*schemasv1alpha4.Table, error) {
+	v1alpha4instance := &schemasv1alpha4.Table{}
+	err := r.Get(context.Background(), request.NamespacedName, v1alpha4instance)
 	if err != nil {
 		return nil, err // don't wrap
 	}
 
-	return v1alpha3instance, nil
+	return v1alpha4instance, nil
 }
 
-func (r *ReconcileTable) getMigrationSpec(namespace string, name string) (*schemasv1alpha3.Migration, error) {
+func (r *ReconcileTable) getMigrationSpec(namespace string, name string) (*schemasv1alpha4.Migration, error) {
 	logger.Debug("getting migration spec",
 		zap.String("namespace", namespace),
 		zap.String("name", name))
@@ -91,18 +91,18 @@ func (r *ReconcileTable) getMigrationSpec(namespace string, name string) (*schem
 	return nil, nil
 }
 
-func (r *ReconcileTable) getDatabaseSpec(ctx context.Context, namespace string, name string) (*databasesv1alpha3.Database, error) {
+func (r *ReconcileTable) getDatabaseSpec(ctx context.Context, namespace string, name string) (*databasesv1alpha4.Database, error) {
 	logger.Debug("getting database spec",
 		zap.String("namespace", namespace),
 		zap.String("name", name))
 
 	cfg, err := config.GetConfig()
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "failed to get config")
 	}
-	databasesClient, err := databasesclientv1alpha3.NewForConfig(cfg)
+	databasesClient, err := databasesclientv1alpha4.NewForConfig(cfg)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "failed to get databasesclient")
 	}
 
 	database, err := databasesClient.Databases(namespace).Get(ctx, name, metav1.GetOptions{})
@@ -112,7 +112,8 @@ func (r *ReconcileTable) getDatabaseSpec(ctx context.Context, namespace string,
 		if kuberneteserrors.IsNotFound(err) {
 			return nil, nil
 		}
-		return nil, err
+
+		return nil, errors.Wrap(err, "failed to get database object")
 	}
 
 	// try to parse the secret too, the database may be deployed, but that doesn't mean it's ready
@@ -138,7 +139,7 @@ func (r *ReconcileTable) getDatabaseSpec(ctx context.Context, namespace string,
 	return database, nil
 }
 
-func checkDatabaseTypeMatches(connection *databasesv1alpha3.DatabaseConnection, tableSchema *schemasv1alpha3.TableSchema) bool {
+func checkDatabaseTypeMatches(connection *databasesv1alpha4.DatabaseConnection, tableSchema *schemasv1alpha4.TableSchema) bool {
 	if connection.Postgres != nil {
 		return tableSchema.Postgres != nil
 	} else if connection.Mysql != nil {
@@ -150,7 +151,7 @@ func checkDatabaseTypeMatches(connection *databasesv1alpha3.DatabaseConnection,
 	return false
 }
 
-func (r *ReconcileTable) deployMigrationPlanPhase(ctx context.Context, database *databasesv1alpha3.Database, table *schemasv1alpha3.Table) (reconcile.Result, error) {
+func (r *ReconcileTable) deployMigrationPlanPhase(ctx context.Context, database *databasesv1alpha4.Database, table *schemasv1alpha4.Table) (reconcile.Result, error) {
 	logger.Debug("deploying plan phase of migration",
 		zap.String("databaseName", database.Name),
 		zap.String("tableName", table.Name))
diff --git a/pkg/controller/table/table_controller.go b/pkg/controller/table/table_controller.go
index 9981ff05..6b2dc1dd 100644
--- a/pkg/controller/table/table_controller.go
+++ b/pkg/controller/table/table_controller.go
@@ -21,7 +21,7 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/logger"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -55,7 +55,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 	}
 
 	// Watch for changes to Table
-	err = c.Watch(&source.Kind{Type: &schemasv1alpha3.Table{}}, &handler.EnqueueRequestForObject{})
+	err = c.Watch(&source.Kind{Type: &schemasv1alpha4.Table{}}, &handler.EnqueueRequestForObject{})
 	if err != nil {
 		return errors.Wrap(err, "failed to start watch on tables")
 	}
diff --git a/pkg/database/database.go b/pkg/database/database.go
index 62cb2548..c9ffcbd9 100644
--- a/pkg/database/database.go
+++ b/pkg/database/database.go
@@ -8,7 +8,7 @@ import (
 	"strings"
 
 	"github.com/pkg/errors"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/mysql"
 	"github.com/schemahero/schemahero/pkg/database/postgres"
 	"github.com/schemahero/schemahero/pkg/logger"
@@ -46,9 +46,9 @@ func (d *Database) CreateFixturesSync() error {
 			return err
 		}
 
-		var spec *schemasv1alpha3.TableSpec
+		var spec *schemasv1alpha4.TableSpec
 
-		parsedK8sObject := schemasv1alpha3.Table{}
+		parsedK8sObject := schemasv1alpha4.Table{}
 		if err := yaml.Unmarshal(fileData, &parsedK8sObject); err == nil {
 			if parsedK8sObject.Spec.Schema != nil {
 				spec = &parsedK8sObject.Spec
@@ -56,7 +56,7 @@ func (d *Database) CreateFixturesSync() error {
 		}
 
 		if spec == nil {
-			plainSpec := schemasv1alpha3.TableSpec{}
+			plainSpec := schemasv1alpha4.TableSpec{}
 			if err := yaml.Unmarshal(fileData, &plainSpec); err != nil {
 				return err
 			}
@@ -134,8 +134,8 @@ func (d *Database) PlanSync(filename string) ([]string, error) {
 		return nil, errors.Wrap(err, "failed to read file")
 	}
 
-	var spec *schemasv1alpha3.TableSpec
-	parsedK8sObject := schemasv1alpha3.Table{}
+	var spec *schemasv1alpha4.TableSpec
+	parsedK8sObject := schemasv1alpha4.Table{}
 	if err := yaml.Unmarshal(specContents, &parsedK8sObject); err == nil {
 		if parsedK8sObject.Spec.Schema != nil {
 			spec = &parsedK8sObject.Spec
@@ -143,7 +143,7 @@ func (d *Database) PlanSync(filename string) ([]string, error) {
 	}
 
 	if spec == nil {
-		plainSpec := schemasv1alpha3.TableSpec{}
+		plainSpec := schemasv1alpha4.TableSpec{}
 		if err := yaml.Unmarshal(specContents, &plainSpec); err != nil {
 			return nil, errors.Wrap(err, "failed to unmarshal spec")
 		}
diff --git a/pkg/database/mysql/alter.go b/pkg/database/mysql/alter.go
index 184fb65e..c02d9e1c 100644
--- a/pkg/database/mysql/alter.go
+++ b/pkg/database/mysql/alter.go
@@ -1,11 +1,11 @@
 package mysql
 
 import (
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 )
 
-func AlterColumnStatements(tableName string, primaryKeys []string, desiredColumns []*schemasv1alpha3.SQLTableColumn, existingColumn *types.Column) ([]string, error) {
+func AlterColumnStatements(tableName string, primaryKeys []string, desiredColumns []*schemasv1alpha4.SQLTableColumn, existingColumn *types.Column) ([]string, error) {
 	// this could be an alter or a drop column command
 	for _, desiredColumn := range desiredColumns {
 		if desiredColumn.Name == existingColumn.Name {
diff --git a/pkg/database/mysql/column.go b/pkg/database/mysql/column.go
index 83c075ec..68b3c9cf 100644
--- a/pkg/database/mysql/column.go
+++ b/pkg/database/mysql/column.go
@@ -3,11 +3,11 @@ package mysql
 import (
 	"fmt"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 )
 
-func schemaColumnToColumn(schemaColumn *schemasv1alpha3.SQLTableColumn) (*types.Column, error) {
+func schemaColumnToColumn(schemaColumn *schemasv1alpha4.SQLTableColumn) (*types.Column, error) {
 	column := &types.Column{
 		Name:          schemaColumn.Name,
 		ColumnDefault: schemaColumn.Default,
@@ -48,7 +48,7 @@ func schemaColumnToColumn(schemaColumn *schemasv1alpha3.SQLTableColumn) (*types.
 	return nil, fmt.Errorf("unknown column type. cannot validate column type %q", schemaColumn.Type)
 }
 
-func mysqlColumnAsInsert(column *schemasv1alpha3.SQLTableColumn) (string, error) {
+func mysqlColumnAsInsert(column *schemasv1alpha4.SQLTableColumn) (string, error) {
 	mysqlColumn, err := schemaColumnToColumn(column)
 	if err != nil {
 		return "", err
@@ -71,7 +71,7 @@ func mysqlColumnAsInsert(column *schemasv1alpha3.SQLTableColumn) (string, error)
 	return formatted, nil
 }
 
-func InsertColumnStatement(tableName string, desiredColumn *schemasv1alpha3.SQLTableColumn) (string, error) {
+func InsertColumnStatement(tableName string, desiredColumn *schemasv1alpha4.SQLTableColumn) (string, error) {
 	columnFields, err := mysqlColumnAsInsert(desiredColumn)
 	if err != nil {
 		return "", err
diff --git a/pkg/database/mysql/connection.go b/pkg/database/mysql/connection.go
index 31215023..61ec7b43 100644
--- a/pkg/database/mysql/connection.go
+++ b/pkg/database/mysql/connection.go
@@ -2,9 +2,11 @@ package mysql
 
 import (
 	"database/sql"
+	"strings"
 
 	// import the mysql driver
 	mysqldriver "github.com/go-sql-driver/mysql"
+	"github.com/pkg/errors"
 )
 
 type MysqlConnection struct {
@@ -59,8 +61,37 @@ func Connect(uri string) (*MysqlConnection, error) {
 func DatabaseNameFromURI(uri string) (string, error) {
 	cfg, err := mysqldriver.ParseDSN(uri)
 	if err != nil {
-		return "", err
+		return "", errors.Wrap(err, "failed to parse database uri")
 	}
 
 	return cfg.DBName, nil
 }
+
+func UsernameFromURI(uri string) (string, error) {
+	cfg, err := mysqldriver.ParseDSN(uri)
+	if err != nil {
+		return "", errors.Wrap(err, "failed to parse database uri")
+	}
+
+	return cfg.User, nil
+}
+
+func PasswordFromURI(uri string) (string, error) {
+	cfg, err := mysqldriver.ParseDSN(uri)
+	if err != nil {
+		return "", errors.Wrap(err, "failed to parse database uri")
+	}
+
+	return cfg.Passwd, nil
+}
+
+func HostnameFromURI(uri string) (string, error) {
+	cfg, err := mysqldriver.ParseDSN(uri)
+	if err != nil {
+		return "", errors.Wrap(err, "failed to parse database uri")
+	}
+
+	// TODO this is very happy path
+	addrAndPort := strings.Split(cfg.Addr, ":")
+	return addrAndPort[0], nil
+}
diff --git a/pkg/database/mysql/create.go b/pkg/database/mysql/create.go
index d5bd085a..08b67748 100644
--- a/pkg/database/mysql/create.go
+++ b/pkg/database/mysql/create.go
@@ -4,10 +4,10 @@ import (
 	"fmt"
 	"strings"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 )
 
-func CreateTableStatement(tableName string, tableSchema *schemasv1alpha3.SQLTableSchema) (string, error) {
+func CreateTableStatement(tableName string, tableSchema *schemasv1alpha4.SQLTableSchema) (string, error) {
 	columns := []string{}
 	for _, desiredColumn := range tableSchema.Columns {
 		columnFields, err := mysqlColumnAsInsert(desiredColumn)
diff --git a/pkg/database/mysql/deploy.go b/pkg/database/mysql/deploy.go
index 2bdbd81c..9f135591 100644
--- a/pkg/database/mysql/deploy.go
+++ b/pkg/database/mysql/deploy.go
@@ -6,11 +6,11 @@ import (
 	"fmt"
 
 	"github.com/pkg/errors"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 )
 
-func PlanMysqlTable(uri string, tableName string, mysqlTableSchema *schemasv1alpha3.SQLTableSchema) ([]string, error) {
+func PlanMysqlTable(uri string, tableName string, mysqlTableSchema *schemasv1alpha4.SQLTableSchema) ([]string, error) {
 	m, err := Connect(uri)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to connect to mysql")
@@ -105,7 +105,7 @@ func executeStatements(m *MysqlConnection, statements []string) error {
 	return nil
 }
 
-func buildColumnStatements(m *MysqlConnection, tableName string, mysqlTableSchema *schemasv1alpha3.SQLTableSchema) ([]string, error) {
+func buildColumnStatements(m *MysqlConnection, tableName string, mysqlTableSchema *schemasv1alpha4.SQLTableSchema) ([]string, error) {
 	query := `select
 COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH
 from information_schema.COLUMNS
@@ -183,7 +183,7 @@ where TABLE_NAME = ?`
 	return alterAndDropStatements, nil
 }
 
-func buildPrimaryKeyStatements(m *MysqlConnection, tableName string, mysqlTableSchema *schemasv1alpha3.SQLTableSchema) ([]string, error) {
+func buildPrimaryKeyStatements(m *MysqlConnection, tableName string, mysqlTableSchema *schemasv1alpha4.SQLTableSchema) ([]string, error) {
 	currentPrimaryKey, err := m.GetTablePrimaryKey(tableName)
 	if err != nil {
 		return nil, err
@@ -218,7 +218,7 @@ func buildPrimaryKeyStatements(m *MysqlConnection, tableName string, mysqlTableS
 	return statements, nil
 }
 
-func buildForeignKeyStatements(m *MysqlConnection, tableName string, mysqlTableSchema *schemasv1alpha3.SQLTableSchema) ([]string, error) {
+func buildForeignKeyStatements(m *MysqlConnection, tableName string, mysqlTableSchema *schemasv1alpha4.SQLTableSchema) ([]string, error) {
 	foreignKeyStatements := []string{}
 	currentForeignKeys, err := m.ListTableForeignKeys(m.databaseName, tableName)
 	if err != nil {
@@ -266,7 +266,7 @@ func buildForeignKeyStatements(m *MysqlConnection, tableName string, mysqlTableS
 	return foreignKeyStatements, nil
 }
 
-func buildIndexStatements(m *MysqlConnection, tableName string, mysqlTableSchema *schemasv1alpha3.SQLTableSchema) ([]string, error) {
+func buildIndexStatements(m *MysqlConnection, tableName string, mysqlTableSchema *schemasv1alpha4.SQLTableSchema) ([]string, error) {
 	indexStatements := []string{}
 	currentIndexes, err := m.ListTableIndexes(m.databaseName, tableName)
 	if err != nil {
diff --git a/pkg/database/mysql/foreignkey.go b/pkg/database/mysql/foreignkey.go
index fb9d1e74..60f00741 100644
--- a/pkg/database/mysql/foreignkey.go
+++ b/pkg/database/mysql/foreignkey.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"strings"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 )
 
@@ -12,11 +12,11 @@ func RemoveForeignKeyStatement(tableName string, foreignKey *types.ForeignKey) s
 	return fmt.Sprintf("alter table %s drop constraint %s", tableName, foreignKey.Name)
 }
 
-func AddForeignKeyStatement(tableName string, schemaForeignKey *schemasv1alpha3.SQLTableForeignKey) string {
+func AddForeignKeyStatement(tableName string, schemaForeignKey *schemasv1alpha4.SQLTableForeignKey) string {
 	return fmt.Sprintf("alter table %s add %s", tableName, foreignKeyConstraintClause(tableName, schemaForeignKey))
 }
 
-func foreignKeyConstraintClause(tableName string, schemaForeignKey *schemasv1alpha3.SQLTableForeignKey) string {
+func foreignKeyConstraintClause(tableName string, schemaForeignKey *schemasv1alpha4.SQLTableForeignKey) string {
 	onDelete := ""
 	if schemaForeignKey.OnDelete != "" {
 		onDelete = fmt.Sprintf(" on delete %s", schemaForeignKey.OnDelete)
diff --git a/pkg/database/mysql/index.go b/pkg/database/mysql/index.go
index d43bf985..24d4c0de 100644
--- a/pkg/database/mysql/index.go
+++ b/pkg/database/mysql/index.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"strings"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 )
 
@@ -12,7 +12,7 @@ func RemoveIndexStatement(tableName string, index *types.Index) string {
 	return fmt.Sprintf("alter table %q drop index %q", tableName, index.Name)
 }
 
-func AddIndexStatement(tableName string, schemaIndex *schemasv1alpha3.SQLTableIndex) string {
+func AddIndexStatement(tableName string, schemaIndex *schemasv1alpha4.SQLTableIndex) string {
 	unique := ""
 	if schemaIndex.IsUnique {
 		unique = "unique "
@@ -26,6 +26,6 @@ func AddIndexStatement(tableName string, schemaIndex *schemasv1alpha3.SQLTableIn
 	return fmt.Sprintf("create %sindex %s on %s (%s)", unique, name, tableName, strings.Join(schemaIndex.Columns, ", "))
 }
 
-func RenameIndexStatement(tableName string, index *types.Index, schemaIndex *schemasv1alpha3.SQLTableIndex) string {
+func RenameIndexStatement(tableName string, index *types.Index, schemaIndex *schemasv1alpha4.SQLTableIndex) string {
 	return fmt.Sprintf("alter index %s rename to %s", index.Name, schemaIndex.Name)
 }
diff --git a/pkg/database/mysql/ping.go b/pkg/database/mysql/ping.go
index 8f15f37b..af881b83 100644
--- a/pkg/database/mysql/ping.go
+++ b/pkg/database/mysql/ping.go
@@ -4,7 +4,7 @@ import (
 	"context"
 	"time"
 
-	databasesclientv1alpha3 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
+	databasesclientv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client/config"
 )
@@ -14,7 +14,7 @@ func (m *MysqlConnection) CheckAlive(ctx context.Context, namespace string, inst
 	if err != nil {
 		return false, err
 	}
-	databasesClient, err := databasesclientv1alpha3.NewForConfig(cfg)
+	databasesClient, err := databasesclientv1alpha4.NewForConfig(cfg)
 	if err != nil {
 		return false, err
 	}
diff --git a/pkg/database/postgres/alter.go b/pkg/database/postgres/alter.go
index 6e6f2c4a..2a063cf6 100644
--- a/pkg/database/postgres/alter.go
+++ b/pkg/database/postgres/alter.go
@@ -6,11 +6,11 @@ import (
 
 	"github.com/lib/pq"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 )
 
-func AlterColumnStatements(tableName string, primaryKeys []string, desiredColumns []*schemasv1alpha3.SQLTableColumn, existingColumn *types.Column) ([]string, error) {
+func AlterColumnStatements(tableName string, primaryKeys []string, desiredColumns []*schemasv1alpha4.SQLTableColumn, existingColumn *types.Column) ([]string, error) {
 	alterStatement := fmt.Sprintf("alter column %s", pq.QuoteIdentifier(existingColumn.Name))
 
 	// this could be an alter or a drop column command
diff --git a/pkg/database/postgres/column.go b/pkg/database/postgres/column.go
index d7fb914b..f1f1c975 100644
--- a/pkg/database/postgres/column.go
+++ b/pkg/database/postgres/column.go
@@ -5,11 +5,11 @@ import (
 	"strings"
 
 	"github.com/lib/pq"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 )
 
-func schemaColumnToColumn(schemaColumn *schemasv1alpha3.SQLTableColumn) (*types.Column, error) {
+func schemaColumnToColumn(schemaColumn *schemasv1alpha4.SQLTableColumn) (*types.Column, error) {
 	column := &types.Column{
 		Name:          schemaColumn.Name,
 		ColumnDefault: schemaColumn.Default,
@@ -56,7 +56,7 @@ func schemaColumnToColumn(schemaColumn *schemasv1alpha3.SQLTableColumn) (*types.
 	return nil, fmt.Errorf("unknown column type. cannot validate column type %q", schemaColumn.Type)
 }
 
-func postgresColumnAsInsert(column *schemasv1alpha3.SQLTableColumn) (string, error) {
+func postgresColumnAsInsert(column *schemasv1alpha4.SQLTableColumn) (string, error) {
 	// Note, we don't always quote the column type because of how pg handles these two statement very differently:
 
 	// 1. create table "users" ("id" "bigint","login" "varchar(255)","name" "varchar(255)")
@@ -91,7 +91,7 @@ func postgresColumnAsInsert(column *schemasv1alpha3.SQLTableColumn) (string, err
 	return formatted, nil
 }
 
-func InsertColumnStatement(tableName string, desiredColumn *schemasv1alpha3.SQLTableColumn) (string, error) {
+func InsertColumnStatement(tableName string, desiredColumn *schemasv1alpha4.SQLTableColumn) (string, error) {
 	columnFields, err := postgresColumnAsInsert(desiredColumn)
 	if err != nil {
 		return "", err
diff --git a/pkg/database/postgres/create.go b/pkg/database/postgres/create.go
index c3757819..1d176a12 100644
--- a/pkg/database/postgres/create.go
+++ b/pkg/database/postgres/create.go
@@ -6,11 +6,11 @@ import (
 
 	"github.com/lib/pq"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 )
 
-func CreateTableStatement(tableName string, tableSchema *schemasv1alpha3.SQLTableSchema) (string, error) {
+func CreateTableStatement(tableName string, tableSchema *schemasv1alpha4.SQLTableSchema) (string, error) {
 	columns := []string{}
 	for _, desiredColumn := range tableSchema.Columns {
 		columnFields, err := postgresColumnAsInsert(desiredColumn)
diff --git a/pkg/database/postgres/deploy.go b/pkg/database/postgres/deploy.go
index 7e8636c4..4b9c723c 100644
--- a/pkg/database/postgres/deploy.go
+++ b/pkg/database/postgres/deploy.go
@@ -7,11 +7,11 @@ import (
 
 	"github.com/lib/pq"
 	"github.com/pkg/errors"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 )
 
-func PlanPostgresTable(uri string, tableName string, postgresTableSchema *schemasv1alpha3.SQLTableSchema) ([]string, error) {
+func PlanPostgresTable(uri string, tableName string, postgresTableSchema *schemasv1alpha4.SQLTableSchema) ([]string, error) {
 	p, err := Connect(uri)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to connect to postgres")
@@ -106,7 +106,7 @@ func executeStatements(p *PostgresConnection, statements []string) error {
 	return nil
 }
 
-func buildColumnStatements(p *PostgresConnection, tableName string, postgresTableSchema *schemasv1alpha3.SQLTableSchema) ([]string, error) {
+func buildColumnStatements(p *PostgresConnection, tableName string, postgresTableSchema *schemasv1alpha4.SQLTableSchema) ([]string, error) {
 	query := `select
 column_name, column_default, is_nullable, data_type, udt_name, character_maximum_length
 from information_schema.columns
@@ -183,7 +183,7 @@ where table_name = $1`
 	return alterAndDropStatements, nil
 }
 
-func buildPrimaryKeyStatements(p *PostgresConnection, tableName string, postgresTableSchema *schemasv1alpha3.SQLTableSchema) ([]string, error) {
+func buildPrimaryKeyStatements(p *PostgresConnection, tableName string, postgresTableSchema *schemasv1alpha4.SQLTableSchema) ([]string, error) {
 	currentPrimaryKey, err := p.GetTablePrimaryKey(tableName)
 	if err != nil {
 		return nil, err
@@ -212,7 +212,7 @@ func buildPrimaryKeyStatements(p *PostgresConnection, tableName string, postgres
 	return statements, nil
 }
 
-func buildForeignKeyStatements(p *PostgresConnection, tableName string, postgresTableSchema *schemasv1alpha3.SQLTableSchema) ([]string, error) {
+func buildForeignKeyStatements(p *PostgresConnection, tableName string, postgresTableSchema *schemasv1alpha4.SQLTableSchema) ([]string, error) {
 	foreignKeyStatements := []string{}
 	droppedKeys := []string{}
 	currentForeignKeys, err := p.ListTableForeignKeys(p.databaseName, tableName)
@@ -268,7 +268,7 @@ func buildForeignKeyStatements(p *PostgresConnection, tableName string, postgres
 	return foreignKeyStatements, nil
 }
 
-func buildIndexStatements(p *PostgresConnection, tableName string, postgresTableSchema *schemasv1alpha3.SQLTableSchema) ([]string, error) {
+func buildIndexStatements(p *PostgresConnection, tableName string, postgresTableSchema *schemasv1alpha4.SQLTableSchema) ([]string, error) {
 	indexStatements := []string{}
 	droppedIndexes := []string{}
 	currentIndexes, err := p.ListTableIndexes(p.databaseName, tableName)
diff --git a/pkg/database/postgres/foreignkey.go b/pkg/database/postgres/foreignkey.go
index 590a8fea..f18afb4a 100644
--- a/pkg/database/postgres/foreignkey.go
+++ b/pkg/database/postgres/foreignkey.go
@@ -5,7 +5,7 @@ import (
 	"strings"
 
 	"github.com/lib/pq"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 )
 
@@ -13,11 +13,11 @@ func RemoveForeignKeyStatement(tableName string, foreignKey *types.ForeignKey) s
 	return fmt.Sprintf("alter table %s drop constraint %s", tableName, pq.QuoteIdentifier(foreignKey.Name))
 }
 
-func AddForeignKeyStatement(tableName string, schemaForeignKey *schemasv1alpha3.SQLTableForeignKey) string {
+func AddForeignKeyStatement(tableName string, schemaForeignKey *schemasv1alpha4.SQLTableForeignKey) string {
 	return fmt.Sprintf("alter table %s add %s", tableName, foreignKeyConstraintClause(tableName, schemaForeignKey))
 }
 
-func foreignKeyConstraintClause(tableName string, schemaForeignKey *schemasv1alpha3.SQLTableForeignKey) string {
+func foreignKeyConstraintClause(tableName string, schemaForeignKey *schemasv1alpha4.SQLTableForeignKey) string {
 	onDelete := ""
 	if schemaForeignKey.OnDelete != "" {
 		onDelete = fmt.Sprintf(" on delete %s", schemaForeignKey.OnDelete)
diff --git a/pkg/database/postgres/index.go b/pkg/database/postgres/index.go
index 0a911e49..7eb666ca 100644
--- a/pkg/database/postgres/index.go
+++ b/pkg/database/postgres/index.go
@@ -5,7 +5,7 @@ import (
 	"strings"
 
 	"github.com/lib/pq"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 )
 
@@ -20,7 +20,7 @@ func RemoveIndexStatement(tableName string, index *types.Index) string {
 	return fmt.Sprintf("drop index %s", pq.QuoteIdentifier(index.Name))
 }
 
-func AddIndexStatement(tableName string, schemaIndex *schemasv1alpha3.SQLTableIndex) string {
+func AddIndexStatement(tableName string, schemaIndex *schemasv1alpha4.SQLTableIndex) string {
 	unique := ""
 	if schemaIndex.IsUnique {
 		unique = "unique "
@@ -38,6 +38,6 @@ func AddIndexStatement(tableName string, schemaIndex *schemasv1alpha3.SQLTableIn
 		strings.Join(schemaIndex.Columns, ", "))
 }
 
-func RenameIndexStatement(tableName string, index *types.Index, schemaIndex *schemasv1alpha3.SQLTableIndex) string {
+func RenameIndexStatement(tableName string, index *types.Index, schemaIndex *schemasv1alpha4.SQLTableIndex) string {
 	return fmt.Sprintf("alter index %s rename to %s", pq.QuoteIdentifier(index.Name), pq.QuoteIdentifier(schemaIndex.Name))
 }
diff --git a/pkg/database/postgres/parameterized.go b/pkg/database/postgres/parameterized.go
index d9c24b28..55fe5481 100644
--- a/pkg/database/postgres/parameterized.go
+++ b/pkg/database/postgres/parameterized.go
@@ -130,6 +130,28 @@ func maybeParseParameterizedColumnType(requestedType string) (string, error) {
 		} else if len(withoutPrecisionMatchGroups) == 1 {
 			columnType = "timestamp"
 		}
+	} else if strings.HasPrefix(requestedType, "time") {
+		columnType = "time"
+
+		withPrecisionWithoutTimeZone := regexp.MustCompile(`time\s*\(\s*(?P<precision>.*)\s*\)\s*without time zone`)
+		withPrecision := regexp.MustCompile(`time\s*\(\s*(?P<precision>.*)\s*\)`)
+		withoutPrecisionWithoutTimeZone := regexp.MustCompile(`time\s*without time zone`)
+		withoutPrecision := regexp.MustCompile(`time\s*`)
+
+		withPrecisionMatchGroups := withPrecision.FindStringSubmatch(requestedType)
+		withPrecisionWithoutTimeZoneMatchGroups := withPrecisionWithoutTimeZone.FindStringSubmatch(requestedType)
+		withoutPrecisionMatchGroups := withoutPrecision.FindStringSubmatch(requestedType)
+		withoutPrecisionWithoutTimeZoneMatchGroups := withoutPrecisionWithoutTimeZone.FindStringSubmatch(requestedType)
+
+		if len(withPrecisionWithoutTimeZoneMatchGroups) == 2 {
+			columnType = fmt.Sprintf("time (%s) without time zone", withPrecisionWithoutTimeZoneMatchGroups[1])
+		} else if len(withoutPrecisionWithoutTimeZoneMatchGroups) == 1 {
+			columnType = "time without time zone"
+		} else if len(withPrecisionMatchGroups) == 2 {
+			columnType = fmt.Sprintf("time (%s)", withPrecisionMatchGroups[1])
+		} else if len(withoutPrecisionMatchGroups) == 1 {
+			columnType = "time"
+		}
 	} else if strings.HasPrefix(requestedType, "numeric") {
 		columnType = "numeric"
 
diff --git a/pkg/database/postgres/ping.go b/pkg/database/postgres/ping.go
index 11676297..0b6c1cbd 100644
--- a/pkg/database/postgres/ping.go
+++ b/pkg/database/postgres/ping.go
@@ -5,7 +5,7 @@ import (
 	"time"
 
 	_ "github.com/lib/pq"
-	databasesclientv1alpha3 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
+	databasesclientv1alpha4 "github.com/schemahero/schemahero/pkg/client/schemaheroclientset/typed/databases/v1alpha4"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client/config"
 )
@@ -15,7 +15,7 @@ func (p *PostgresConnection) CheckAlive(ctx context.Context, namespace string, i
 	if err != nil {
 		return false, err
 	}
-	databasesClient, err := databasesclientv1alpha3.NewForConfig(cfg)
+	databasesClient, err := databasesclientv1alpha4.NewForConfig(cfg)
 	if err != nil {
 		return false, err
 	}
diff --git a/pkg/database/types/column.go b/pkg/database/types/column.go
index 641fb1b8..050d70ff 100644
--- a/pkg/database/types/column.go
+++ b/pkg/database/types/column.go
@@ -1,7 +1,7 @@
 package types
 
 import (
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 )
 
 type ColumnConstraints struct {
@@ -23,14 +23,14 @@ type Column struct {
 	IsArray       bool
 }
 
-func ColumnToSchemaColumn(column *Column) (*schemasv1alpha3.SQLTableColumn, error) {
-	schemaColumn := &schemasv1alpha3.SQLTableColumn{
+func ColumnToSchemaColumn(column *Column) (*schemasv1alpha4.SQLTableColumn, error) {
+	schemaColumn := &schemasv1alpha4.SQLTableColumn{
 		Name: column.Name,
 		Type: column.DataType,
 	}
 
 	if column.Constraints != nil {
-		schemaColumn.Constraints = &schemasv1alpha3.SQLTableColumnConstraints{
+		schemaColumn.Constraints = &schemasv1alpha4.SQLTableColumnConstraints{
 			NotNull: column.Constraints.NotNull,
 		}
 	}
diff --git a/pkg/database/types/foreignkey.go b/pkg/database/types/foreignkey.go
index 50ec94ed..32b60ae9 100644
--- a/pkg/database/types/foreignkey.go
+++ b/pkg/database/types/foreignkey.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"strings"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 )
 
 type ForeignKey struct {
@@ -21,10 +21,10 @@ func (fk *ForeignKey) Equals(other *ForeignKey) bool {
 	return false
 }
 
-func ForeignKeyToSchemaForeignKey(foreignKey *ForeignKey) *schemasv1alpha3.SQLTableForeignKey {
-	schemaForeignKey := schemasv1alpha3.SQLTableForeignKey{
+func ForeignKeyToSchemaForeignKey(foreignKey *ForeignKey) *schemasv1alpha4.SQLTableForeignKey {
+	schemaForeignKey := schemasv1alpha4.SQLTableForeignKey{
 		Columns: foreignKey.ChildColumns,
-		References: schemasv1alpha3.SQLTableForeignKeyReferences{
+		References: schemasv1alpha4.SQLTableForeignKeyReferences{
 			Table:   foreignKey.ParentTable,
 			Columns: foreignKey.ParentColumns,
 		},
@@ -35,7 +35,7 @@ func ForeignKeyToSchemaForeignKey(foreignKey *ForeignKey) *schemasv1alpha3.SQLTa
 	return &schemaForeignKey
 }
 
-func SchemaForeignKeyToForeignKey(schemaForeignKey *schemasv1alpha3.SQLTableForeignKey) *ForeignKey {
+func SchemaForeignKeyToForeignKey(schemaForeignKey *schemasv1alpha4.SQLTableForeignKey) *ForeignKey {
 	foreignKey := ForeignKey{
 		ChildColumns:  schemaForeignKey.Columns,
 		ParentTable:   schemaForeignKey.References.Table,
@@ -47,7 +47,7 @@ func SchemaForeignKeyToForeignKey(schemaForeignKey *schemasv1alpha3.SQLTableFore
 	return &foreignKey
 }
 
-func GenerateFKName(tableName string, schemaForeignKey *schemasv1alpha3.SQLTableForeignKey) string {
+func GenerateFKName(tableName string, schemaForeignKey *schemasv1alpha4.SQLTableForeignKey) string {
 	if schemaForeignKey.Name != "" {
 		return schemaForeignKey.Name
 	}
diff --git a/pkg/database/types/index.go b/pkg/database/types/index.go
index 94c62ec4..8f098432 100644
--- a/pkg/database/types/index.go
+++ b/pkg/database/types/index.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"strings"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 )
 
 type Index struct {
@@ -41,8 +41,8 @@ func (idx *Index) Equals(other *Index) bool {
 	return true
 }
 
-func IndexToSchemaIndex(index *Index) *schemasv1alpha3.SQLTableIndex {
-	schemaIndex := schemasv1alpha3.SQLTableIndex{
+func IndexToSchemaIndex(index *Index) *schemasv1alpha4.SQLTableIndex {
+	schemaIndex := schemasv1alpha4.SQLTableIndex{
 		Columns:  index.Columns,
 		Name:     index.Name,
 		IsUnique: index.IsUnique,
@@ -51,7 +51,7 @@ func IndexToSchemaIndex(index *Index) *schemasv1alpha3.SQLTableIndex {
 	return &schemaIndex
 }
 
-func SchemaIndexToIndex(schemaIndex *schemasv1alpha3.SQLTableIndex) *Index {
+func SchemaIndexToIndex(schemaIndex *schemasv1alpha4.SQLTableIndex) *Index {
 	index := Index{
 		Columns:  schemaIndex.Columns,
 		Name:     schemaIndex.Name,
@@ -61,6 +61,6 @@ func SchemaIndexToIndex(schemaIndex *schemasv1alpha3.SQLTableIndex) *Index {
 	return &index
 }
 
-func GenerateIndexName(tableName string, schemaIndex *schemasv1alpha3.SQLTableIndex) string {
+func GenerateIndexName(tableName string, schemaIndex *schemasv1alpha4.SQLTableIndex) string {
 	return fmt.Sprintf("idx_%s_%s", tableName, strings.Join(schemaIndex.Columns, "_"))
 }
diff --git a/pkg/generate/generate.go b/pkg/generate/generate.go
index bcf557d2..fc2352b7 100644
--- a/pkg/generate/generate.go
+++ b/pkg/generate/generate.go
@@ -7,7 +7,7 @@ import (
 	"strings"
 
 	"github.com/pkg/errors"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/interfaces"
 	"github.com/schemahero/schemahero/pkg/database/mysql"
 	"github.com/schemahero/schemahero/pkg/database/postgres"
@@ -115,19 +115,19 @@ func (g *Generator) RunSync() error {
 }
 
 func generateTableYAML(driver string, dbName string, tableName string, primaryKey []string, foreignKeys []*types.ForeignKey, indexes []*types.Index, columns []*types.Column) (string, error) {
-	schemaForeignKeys := make([]*schemasv1alpha3.SQLTableForeignKey, 0, 0)
+	schemaForeignKeys := make([]*schemasv1alpha4.SQLTableForeignKey, 0, 0)
 	for _, foreignKey := range foreignKeys {
 		schemaForeignKey := types.ForeignKeyToSchemaForeignKey(foreignKey)
 		schemaForeignKeys = append(schemaForeignKeys, schemaForeignKey)
 	}
 
-	schemaIndexes := make([]*schemasv1alpha3.SQLTableIndex, 0, 0)
+	schemaIndexes := make([]*schemasv1alpha4.SQLTableIndex, 0, 0)
 	for _, index := range indexes {
 		schemaIndex := types.IndexToSchemaIndex(index)
 		schemaIndexes = append(schemaIndexes, schemaIndex)
 	}
 
-	schemaTableColumns := make([]*schemasv1alpha3.SQLTableColumn, 0, 0)
+	schemaTableColumns := make([]*schemasv1alpha4.SQLTableColumn, 0, 0)
 	for _, column := range columns {
 		schemaTableColumn, err := types.ColumnToSchemaColumn(column)
 		if err != nil {
@@ -138,14 +138,14 @@ func generateTableYAML(driver string, dbName string, tableName string, primaryKe
 		schemaTableColumns = append(schemaTableColumns, schemaTableColumn)
 	}
 
-	tableSchema := &schemasv1alpha3.SQLTableSchema{
+	tableSchema := &schemasv1alpha4.SQLTableSchema{
 		PrimaryKey:  primaryKey,
 		Columns:     schemaTableColumns,
 		ForeignKeys: schemaForeignKeys,
 		Indexes:     schemaIndexes,
 	}
 
-	schema := &schemasv1alpha3.TableSchema{}
+	schema := &schemasv1alpha4.TableSchema{}
 
 	if driver == "postgres" {
 		schema.Postgres = tableSchema
@@ -153,7 +153,7 @@ func generateTableYAML(driver string, dbName string, tableName string, primaryKe
 		schema.Mysql = tableSchema
 	}
 
-	schemaHeroResource := schemasv1alpha3.TableSpec{
+	schemaHeroResource := schemasv1alpha4.TableSpec{
 		Database: dbName,
 		Name:     tableName,
 		Requires: []string{},
@@ -161,7 +161,7 @@ func generateTableYAML(driver string, dbName string, tableName string, primaryKe
 	}
 
 	specDoc := struct {
-		Spec schemasv1alpha3.TableSpec `yaml:"spec"`
+		Spec schemasv1alpha4.TableSpec `yaml:"spec"`
 	}{
 		schemaHeroResource,
 	}
@@ -173,7 +173,7 @@ func generateTableYAML(driver string, dbName string, tableName string, primaryKe
 	}
 
 	// TODO consider marshaling this instead of inline
-	tableDoc := fmt.Sprintf(`apiVersion: schemas.schemahero.io/v1alpha3
+	tableDoc := fmt.Sprintf(`apiVersion: schemas.schemahero.io/v1alpha4
 kind: Table
 metadata:
   name: %s
diff --git a/pkg/installer/database_objects.go b/pkg/installer/database_objects.go
index f268094b..cf492607 100644
--- a/pkg/installer/database_objects.go
+++ b/pkg/installer/database_objects.go
@@ -135,6 +135,8 @@ spec:
                       type: object
                   type: object
               type: object
+            disableShellCommand:
+              type: boolean
             immediateDeploy:
               type: boolean
             schemahero:
@@ -161,9 +163,9 @@ spec:
       required:
       - spec
       type: object
-  version: v1alpha3
+  version: v1alpha4
   versions:
-  - name: v1alpha3
+  - name: v1alpha4
     served: true
     storage: true
 status:
@@ -191,7 +193,7 @@ spec:
     singular: database
   scope: Namespaced
   versions:
-  - name: v1alpha3
+  - name: v1alpha4
     schema:
       openAPIV3Schema:
         description: Database is the Schema for the databases API
@@ -308,6 +310,8 @@ spec:
                         type: object
                     type: object
                 type: object
+              disableShellCommand:
+                type: boolean
               immediateDeploy:
                 type: boolean
               schemahero:
diff --git a/pkg/shell/shell.go b/pkg/shell/shell.go
new file mode 100644
index 00000000..eb34e47c
--- /dev/null
+++ b/pkg/shell/shell.go
@@ -0,0 +1,98 @@
+package shell
+
+import (
+	"context"
+	"time"
+
+	"github.com/pkg/errors"
+	corev1 "k8s.io/api/core/v1"
+	kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes"
+	"sigs.k8s.io/controller-runtime/pkg/client/config"
+)
+
+// StartShellPod will start a new pod in the namespace using the imagename
+// The command will be sleep infinity, so the exec will have to start the
+// destired command
+// the caller is respondible for cleaning up this pod
+func StartShellPod(ctx context.Context, namespace string, imageName string) (string, error) {
+	pod := &corev1.Pod{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "v1",
+			Kind:       "Pod",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace:    namespace,
+			GenerateName: "schemahero-shell-",
+		},
+		Spec: corev1.PodSpec{
+			// NodeSelector,
+			// ServiceAccount
+			RestartPolicy: corev1.RestartPolicyNever,
+			Containers: []corev1.Container{
+				{
+					Image:           imageName,
+					ImagePullPolicy: corev1.PullIfNotPresent,
+					Name:            "shell",
+					Command: []string{
+						"sleep",
+						"infinity",
+					},
+				},
+			},
+		},
+	}
+
+	cfg, err := config.GetConfig()
+	if err != nil {
+		return "", errors.Wrap(err, "failed to get config")
+	}
+
+	clientset, err := kubernetes.NewForConfig(cfg)
+	if err != nil {
+		return "", errors.Wrap(err, "failed to get clientset")
+	}
+
+	createdPod, err := clientset.CoreV1().Pods(namespace).Create(ctx, pod, metav1.CreateOptions{})
+	if err != nil {
+		return "", errors.Wrap(err, "failed to create pod")
+	}
+
+	// wait for the pod to start
+	startedAt := time.Now()
+	abortAt := startedAt.Add(time.Minute)
+	pollInterval := time.Millisecond * 200
+
+	for {
+		if abortAt.Before(time.Now()) {
+			return "", errors.New("shell pod did not start")
+		}
+
+		currentPod, err := clientset.CoreV1().Pods(namespace).Get(ctx, createdPod.Name, metav1.GetOptions{})
+		if kuberneteserrors.IsNotFound(err) {
+			time.Sleep(pollInterval)
+			continue
+		}
+
+		if err != nil {
+			return "", errors.Wrap(err, "failed to get pod")
+		}
+
+		numNotReady := 0
+		numReady := 0
+		for _, podCondition := range currentPod.Status.Conditions {
+			if podCondition.Type == corev1.ContainersReady {
+				if podCondition.Status == corev1.ConditionTrue {
+					numReady++
+				} else {
+					numNotReady++
+				}
+			}
+		}
+
+		if numNotReady == 0 && numReady > 0 {
+			return createdPod.Name, nil
+		}
+	}
+}


Test patch:
diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml
index 18b45eb0..bff97caf 100644
--- a/.github/workflows/build-and-test.yaml
+++ b/.github/workflows/build-and-test.yaml
@@ -10,7 +10,7 @@ jobs:
     steps:
       - uses: actions/setup-go@v1.1.2
         with:
-          go-version: "1.14.1"
+          go-version: "1.14.3"
       - name: setup goenv
         run: |
           echo "::set-env name=GOPATH::$(go env GOPATH)"
@@ -31,7 +31,6 @@ jobs:
           name: kubectl-schemahero
           path: bin/kubectl-schemahero
 
-  ## We list each here so that they all show up in the output of github checks
   test-postgres:
     runs-on: ubuntu-latest
     needs: build
@@ -43,19 +42,8 @@ jobs:
           name: schemahero
           path: bin/
       - run: chmod +x bin/schemahero
-      - run: make -C integration/tests/postgres/column-set-default run
-      - run: make -C integration/tests/postgres/column-unset-default run
-      - run: make -C integration/tests/postgres/create-table run
-      - run: make -C integration/tests/postgres/foreign-key-create run
-      - run: make -C integration/tests/postgres/foreign-key-action run
-      - run: make -C integration/tests/postgres/foreign-key-drop run
-      - run: make -C integration/tests/postgres/foreign-key-alter run
-      - run: make -C integration/tests/postgres/not-null run
-      - run: make -C integration/tests/postgres/index-create run
-      - run: make -C integration/tests/postgres/primary-key-add run
-      - run: make -C integration/tests/postgres/primary-key-drop run
+      - run: make -C integration/tests/postgres run
 
-  ## We list each here so that they all show up in the output of github checks
   test-mysql:
     runs-on: ubuntu-latest
     needs: build
@@ -67,19 +55,8 @@ jobs:
           name: schemahero
           path: bin/
       - run: chmod +x bin/schemahero
-      - run: make -C integration/tests/mysql/column-set-default run
-      - run: make -C integration/tests/mysql/column-unset-default run
-      - run: make -C integration/tests/mysql/create-table run
-      - run: make -C integration/tests/mysql/foreign-key-create run
-      - run: make -C integration/tests/mysql/foreign-key-action run
-      - run: make -C integration/tests/mysql/foreign-key-drop run
-      - run: make -C integration/tests/mysql/foreign-key-alter run
-      - run: make -C integration/tests/mysql/not-null run
-      - run: make -C integration/tests/mysql/index-create run
-      - run: make -C integration/tests/mysql/primary-key-add run
-      - run: make -C integration/tests/mysql/primary-key-drop run
+      - run: make -C integration/tests/mysql run
 
-  ## We list each here so that they all show up in the output of github checks
   test-cockroach:
     runs-on: ubuntu-latest
     needs: build
@@ -91,17 +68,7 @@ jobs:
           name: schemahero
           path: bin/
       - run: chmod +x bin/schemahero
-      - run: make -C integration/tests/cockroach/column-set-default run
-      - run: make -C integration/tests/cockroach/column-unset-default run
-      - run: make -C integration/tests/cockroach/create-table run
-      - run: make -C integration/tests/cockroach/foreign-key-create run
-      - run: make -C integration/tests/cockroach/foreign-key-action run
-      - run: make -C integration/tests/cockroach/foreign-key-drop run
-      - run: make -C integration/tests/cockroach/foreign-key-alter run
-      - run: make -C integration/tests/cockroach/not-null run
-      - run: make -C integration/tests/cockroach/index-create run
-      - run: make -C integration/tests/cockroach/primary-key-add run
-      - run: make -C integration/tests/cockroach/primary-key-drop run
+      - run: make -C integration/tests/cockroach run
 
   kots:
     runs-on: ubuntu-latest
diff --git a/integration/tests/cockroach/Makefile b/integration/tests/cockroach/Makefile
new file mode 100644
index 00000000..08a3148d
--- /dev/null
+++ b/integration/tests/cockroach/Makefile
@@ -0,0 +1,48 @@
+
+SHELL := /bin/bash
+
+export IMAGE
+export GO111MODULE=on
+
+.PHONY: run
+run: v19.2.7 v20.1.0
+
+.PHONY: v19.2.7
+v19.2.7: export COCKROACHDB_VBERSION = v19.2.7
+v19.2.7:
+	make -C column-set-default run
+	make -C column-unset-default run
+	make -C create-table run
+	make -C foreign-key-create run
+	make -C foreign-key-action run
+	make -C foreign-key-drop run
+	make -C foreign-key-alter run
+	make -C not-null run
+	make -C index-create run
+	make -C primary-key-add run
+	make -C primary-key-drop run
+
+.PHONY: v20.1.0
+v20.1.0: export COCKROACHDB_VBERSION = v20.1.0
+v20.1.0:
+	make -C column-set-default run
+	make -C column-unset-default run
+	make -C create-table run
+	make -C foreign-key-create run
+	make -C foreign-key-action run
+	make -C foreign-key-drop run
+	make -C foreign-key-alter run
+	make -C not-null run
+	make -C index-create run
+	make -C primary-key-add run
+	make -C primary-key-drop run
+
+
+.PHONY: build
+build: docker-build
+	docker push $(IMAGE)
+
+.PHONY: docker-build
+docker-build:
+	docker build -t $(IMAGE) -f ../Dockerfile.schemahero ..
+	@echo "export IMAGE=$(IMAGE)"
diff --git a/integration/tests/cockroach/common.mk b/integration/tests/cockroach/common.mk
index 75cee40a..15ea23fb 100644
--- a/integration/tests/cockroach/common.mk
+++ b/integration/tests/cockroach/common.mk
@@ -7,8 +7,8 @@ URI := postgres://schemahero@127.0.0.1:26257/schemahero?sslmode=disable
 .PHONY: run
 run:
 	# Fixtures
-	docker pull cockroachdb/cockroach:v19.2.5
-    docker tag cockroachdb/cockroach:v19.2.5 $(DATABASE_IMAGE_NAME)
+	docker pull cockroachdb/cockroach:$(COCKROACHDB_VBERSION)
+    docker tag cockroachdb/cockroach:$(COCKROACHDB_VBERSION) $(DATABASE_IMAGE_NAME)
 	@-docker rm -f $(DATABASE_CONTAINER_NAME) > /dev/null 2>&1 ||:
 	docker run -p 26257:26257 --rm -d \
 		--name $(DATABASE_CONTAINER_NAME) \
diff --git a/integration/tests/mysql/Makefile b/integration/tests/mysql/Makefile
new file mode 100644
index 00000000..7a56a007
--- /dev/null
+++ b/integration/tests/mysql/Makefile
@@ -0,0 +1,78 @@
+
+SHELL := /bin/bash
+
+export IMAGE
+export GO111MODULE=on
+
+.PHONY: run
+run: 5.6 5.7 8.0
+
+.PHONY: 5.6
+5.6: export MYSQL_VERSION = 5.6
+5.6:
+	make -C column-set-default run
+	make -C column-unset-default run
+	make -C create-table run
+	make -C foreign-key-create run
+	make -C foreign-key-action run
+	make -C foreign-key-drop run
+	make -C foreign-key-alter run
+	make -C not-null run
+	make -C index-create run
+	make -C primary-key-add run
+	make -C primary-key-drop run
+
+.PHONY: 5.7
+5.7: export MYSQL_VERSION = 5.7
+5.7:
+	make -C column-set-default run
+	make -C column-unset-default run
+	make -C create-table run
+	make -C foreign-key-create run
+	make -C foreign-key-action run
+	make -C foreign-key-drop run
+	make -C foreign-key-alter run
+	make -C not-null run
+	make -C index-create run
+	make -C primary-key-add run
+	make -C primary-key-drop run
+
+.PHONY: 8.0
+8.0: export MYSQL_VERSION = 8.0
+8.0:
+	make -C column-set-default run
+	make -C column-unset-default run
+	make -C create-table run
+	make -C foreign-key-create run
+	make -C foreign-key-action run
+	make -C foreign-key-drop run
+	make -C foreign-key-alter run
+	make -C not-null run
+	make -C index-create run
+	make -C primary-key-add run
+	make -C primary-key-drop run
+
+
+.PHONY: 8.0
+8.0: export MYSQL_VERSION = 8.0
+8.0:
+	make -C column-set-default run
+	make -C column-unset-default run
+	make -C create-table run
+	make -C foreign-key-create run
+	make -C foreign-key-action run
+	make -C foreign-key-drop run
+	make -C foreign-key-alter run
+	make -C not-null run
+	make -C index-create run
+	make -C primary-key-add run
+	make -C primary-key-drop run
+
+.PHONY: build
+build: docker-build
+	docker push $(IMAGE)
+
+.PHONY: docker-build
+docker-build:
+	docker build -t $(IMAGE) -f ../Dockerfile.schemahero ..
+	@echo "export IMAGE=$(IMAGE)"
diff --git a/integration/tests/mysql/common.mk b/integration/tests/mysql/common.mk
index 556c3c58..6811df96 100644
--- a/integration/tests/mysql/common.mk
+++ b/integration/tests/mysql/common.mk
@@ -7,7 +7,7 @@ URI := schemahero:password@tcp(localhost:13306)/schemahero?tls=false
 .PHONY: run
 run:
 	# Fixtures
-	docker pull mysql:8.0
+	docker pull mysql:$(MYSQL_VERSION)
 	docker build -t $(DATABASE_IMAGE_NAME) .
 	@-docker rm -f $(DATABASE_CONTAINER_NAME) > /dev/null 2>&1 ||:
 	docker run -p 13306:3306 --rm -d --name $(DATABASE_CONTAINER_NAME) $(DATABASE_IMAGE_NAME)
diff --git a/integration/tests/postgres/Makefile b/integration/tests/postgres/Makefile
new file mode 100644
index 00000000..475a44f7
--- /dev/null
+++ b/integration/tests/postgres/Makefile
@@ -0,0 +1,71 @@
+
+SHELL := /bin/bash
+
+export IMAGE
+export GO111MODULE=on
+
+.PHONY: run
+run: 10.13 11.8 12.3
+
+.PHONY: 10.13
+10.13: export PG_VERSION = 10.13
+10.13:
+	make -C column-set-default run
+	make -C column-unset-default run
+	make -C create-table run
+	make -C foreign-key-create run
+	make -C foreign-key-action run
+	make -C foreign-key-drop run
+	make -C foreign-key-alter run
+	make -C not-null run
+	make -C not-null-with-default run
+	make -C index-create run
+	make -C primary-key-add run
+	make -C primary-key-drop run
+	make -C unique-constraint-add run
+	make -C unique-constraint-drop run
+
+.PHONY: 11.8
+11.8: export PG_VERSION = 11.8
+11.8:
+	make -C column-set-default run
+	make -C column-unset-default run
+	make -C create-table run
+	make -C foreign-key-create run
+	make -C foreign-key-action run
+	make -C foreign-key-drop run
+	make -C foreign-key-alter run
+	make -C not-null run
+	make -C not-null-with-default run
+	make -C index-create run
+	make -C primary-key-add run
+	make -C primary-key-drop run
+	make -C unique-constraint-add run
+	make -C unique-constraint-drop run
+
+.PHONY: 12.3
+12.3: export PG_VERSION = 12.3
+12.3:
+	make -C column-set-default run
+	make -C column-unset-default run
+	make -C create-table run
+	make -C foreign-key-create run
+	make -C foreign-key-action run
+	make -C foreign-key-drop run
+	make -C foreign-key-alter run
+	make -C not-null run
+	make -C not-null-with-default run
+	make -C index-create run
+	make -C primary-key-add run
+	make -C primary-key-drop run
+	make -C unique-constraint-add run
+	make -C unique-constraint-drop run
+
+.PHONY: build
+build: docker-build
+	docker push $(IMAGE)
+
+.PHONY: docker-build
+docker-build:
+	docker build -t $(IMAGE) -f ../Dockerfile.schemahero ..
+	@echo "export IMAGE=$(IMAGE)"
diff --git a/integration/tests/postgres/common.mk b/integration/tests/postgres/common.mk
index fe4e0a87..79b0c441 100644
--- a/integration/tests/postgres/common.mk
+++ b/integration/tests/postgres/common.mk
@@ -7,7 +7,7 @@ URI := postgres://schemahero:password@127.0.0.1:15432/schemahero?sslmode=disable
 .PHONY: run
 run:
 	# Fixtures
-	docker pull postgres:10.7
+	docker pull postgres:$(PG_VERSION)
 	docker build -t $(DATABASE_IMAGE_NAME) .
 	@-docker rm -f $(DATABASE_CONTAINER_NAME) > /dev/null 2>&1 ||:
 	docker run -p 15432:5432 --rm -d --name $(DATABASE_CONTAINER_NAME) $(DATABASE_IMAGE_NAME)
diff --git a/integration/tests/postgres/unique-constraint-add/expect.sql b/integration/tests/postgres/unique-constraint-add/expect.sql
index e69de29b..9dc85041 100644
--- a/integration/tests/postgres/unique-constraint-add/expect.sql
+++ b/integration/tests/postgres/unique-constraint-add/expect.sql
@@ -0,0 +1,1 @@
+create unique index idx_projects_name on projects (name);
diff --git a/integration/tests/postgres/unique-constraint-drop/expect.sql b/integration/tests/postgres/unique-constraint-drop/expect.sql
index e69de29b..9a597ad3 100644
--- a/integration/tests/postgres/unique-constraint-drop/expect.sql
+++ b/integration/tests/postgres/unique-constraint-drop/expect.sql
@@ -0,0 +1,1 @@
+alter table "projects" drop constraint "ukey_projects_name";
diff --git a/pkg/apis/databases/v1alpha4/database_types_test.go b/pkg/apis/databases/v1alpha4/database_types_test.go
index bb17c930..a00e5f2e 100644
--- a/pkg/apis/databases/v1alpha4/database_types_test.go
+++ b/pkg/apis/databases/v1alpha4/database_types_test.go
@@ -1,4 +1,4 @@
-package v1alpha3
+package v1alpha4
 
 import (
 	"testing"
diff --git a/pkg/apis/databases/v1alpha4/mysql_types_test.go b/pkg/apis/databases/v1alpha4/mysql_types_test.go
index 26450b68..54a0fac9 100644
--- a/pkg/apis/databases/v1alpha4/mysql_types_test.go
+++ b/pkg/apis/databases/v1alpha4/mysql_types_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v1alpha3
+package v1alpha4
 
 import (
 	"testing"
diff --git a/pkg/apis/databases/v1alpha4/postgres_types_test.go b/pkg/apis/databases/v1alpha4/postgres_types_test.go
index 761a51d6..13364d45 100644
--- a/pkg/apis/databases/v1alpha4/postgres_types_test.go
+++ b/pkg/apis/databases/v1alpha4/postgres_types_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v1alpha3
+package v1alpha4
 
 import (
 	"testing"
diff --git a/pkg/apis/databases/v1alpha4/string_or_value_test.go b/pkg/apis/databases/v1alpha4/string_or_value_test.go
index 07a5c320..6a3bb1f7 100644
--- a/pkg/apis/databases/v1alpha4/string_or_value_test.go
+++ b/pkg/apis/databases/v1alpha4/string_or_value_test.go
@@ -1,4 +1,4 @@
-package v1alpha3
+package v1alpha4
 
 import (
 	"testing"
diff --git a/pkg/controller/migration/objects_test.go b/pkg/controller/migration/objects_test.go
index ae68b9bd..c88fe698 100644
--- a/pkg/controller/migration/objects_test.go
+++ b/pkg/controller/migration/objects_test.go
@@ -3,8 +3,8 @@ package migration
 import (
 	"testing"
 
-	databasesv1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/stretchr/testify/assert"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
@@ -46,8 +46,8 @@ func Test_vaultAnnotations(t *testing.T) {
 		name                string
 		expectedAnnotations map[string]string
 		expectedArgs        []string
-		database            *databasesv1alpha3.Database
-		table               *schemasv1alpha3.Table
+		database            *databasesv1alpha4.Database
+		table               *schemasv1alpha4.Table
 	}{
 		{
 			name: "Adds correct annotations for postgres",
@@ -68,15 +68,15 @@ postgres://{{ .Data.username }}:{{ .Data.password }}@postgres:5432/my-database{{
 				"--vault-uri-ref",
 				"/vault/secrets/schemaherouri",
 			},
-			database: &databasesv1alpha3.Database{
-				TypeMeta:   v1.TypeMeta{APIVersion: "databases.schemahero.io/v1alpha3", Kind: "Database"},
+			database: &databasesv1alpha4.Database{
+				TypeMeta:   v1.TypeMeta{APIVersion: "databases.schemahero.io/v1alpha4", Kind: "Database"},
 				ObjectMeta: v1.ObjectMeta{Name: "my-database"},
-				Spec: databasesv1alpha3.DatabaseSpec{
-					Connection: databasesv1alpha3.DatabaseConnection{
-						Postgres: &databasesv1alpha3.PostgresConnection{
-							URI: databasesv1alpha3.ValueOrValueFrom{
-								ValueFrom: &databasesv1alpha3.ValueFrom{
-									Vault: &databasesv1alpha3.Vault{
+				Spec: databasesv1alpha4.DatabaseSpec{
+					Connection: databasesv1alpha4.DatabaseConnection{
+						Postgres: &databasesv1alpha4.PostgresConnection{
+							URI: databasesv1alpha4.ValueOrValueFrom{
+								ValueFrom: &databasesv1alpha4.ValueFrom{
+									Vault: &databasesv1alpha4.Vault{
 										Secret: "database/creds/schemahero",
 										Role:   "schemahero-plan",
 									},
@@ -85,29 +85,29 @@ postgres://{{ .Data.username }}:{{ .Data.password }}@postgres:5432/my-database{{
 						},
 					},
 				},
-				Status: databasesv1alpha3.DatabaseStatus{},
+				Status: databasesv1alpha4.DatabaseStatus{},
 			},
-			table: &schemasv1alpha3.Table{
-				TypeMeta:   v1.TypeMeta{APIVersion: "schemas.schemahero.io/v1alpha3", Kind: "Table"},
+			table: &schemasv1alpha4.Table{
+				TypeMeta:   v1.TypeMeta{APIVersion: "schemas.schemahero.io/v1alpha4", Kind: "Table"},
 				ObjectMeta: v1.ObjectMeta{Name: "my-table"},
-				Spec: schemasv1alpha3.TableSpec{
+				Spec: schemasv1alpha4.TableSpec{
 					Database: "my-database",
 					Name:     "my-table",
-					Schema: &schemasv1alpha3.TableSchema{
-						Postgres: &schemasv1alpha3.SQLTableSchema{
+					Schema: &schemasv1alpha4.TableSchema{
+						Postgres: &schemasv1alpha4.SQLTableSchema{
 							PrimaryKey: []string{"id"},
-							Columns: []*schemasv1alpha3.SQLTableColumn{
+							Columns: []*schemasv1alpha4.SQLTableColumn{
 								{
 									Name: "id",
 									Type: "text",
-									Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+									Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 										NotNull: new(bool),
 									},
 								},
 								{
 									Name: "name",
 									Type: "text",
-									Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+									Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 										NotNull: new(bool),
 									},
 								},
@@ -115,7 +115,7 @@ postgres://{{ .Data.username }}:{{ .Data.password }}@postgres:5432/my-database{{
 						},
 					},
 				},
-				Status: schemasv1alpha3.TableStatus{},
+				Status: schemasv1alpha4.TableStatus{},
 			},
 		},
 		{
@@ -130,41 +130,41 @@ postgres://{{ .Data.username }}:{{ .Data.password }}@postgres:5432/my-database{{
 				"--uri",
 				"",
 			},
-			database: &databasesv1alpha3.Database{
-				TypeMeta:   v1.TypeMeta{APIVersion: "databases.schemahero.io/v1alpha3", Kind: "Database"},
+			database: &databasesv1alpha4.Database{
+				TypeMeta:   v1.TypeMeta{APIVersion: "databases.schemahero.io/v1alpha4", Kind: "Database"},
 				ObjectMeta: v1.ObjectMeta{Name: "my-database"},
-				Spec: databasesv1alpha3.DatabaseSpec{
-					Connection: databasesv1alpha3.DatabaseConnection{
-						Postgres: &databasesv1alpha3.PostgresConnection{
-							URI: databasesv1alpha3.ValueOrValueFrom{
+				Spec: databasesv1alpha4.DatabaseSpec{
+					Connection: databasesv1alpha4.DatabaseConnection{
+						Postgres: &databasesv1alpha4.PostgresConnection{
+							URI: databasesv1alpha4.ValueOrValueFrom{
 								Value: "postgres://user:password@postgres:5432/my-database",
 							},
 						},
 					},
 				},
-				Status: databasesv1alpha3.DatabaseStatus{},
+				Status: databasesv1alpha4.DatabaseStatus{},
 			},
-			table: &schemasv1alpha3.Table{
-				TypeMeta:   v1.TypeMeta{APIVersion: "schemas.schemahero.io/v1alpha3", Kind: "Table"},
+			table: &schemasv1alpha4.Table{
+				TypeMeta:   v1.TypeMeta{APIVersion: "schemas.schemahero.io/v1alpha4", Kind: "Table"},
 				ObjectMeta: v1.ObjectMeta{Name: "my-table"},
-				Spec: schemasv1alpha3.TableSpec{
+				Spec: schemasv1alpha4.TableSpec{
 					Database: "my-database",
 					Name:     "my-table",
-					Schema: &schemasv1alpha3.TableSchema{
-						Postgres: &schemasv1alpha3.SQLTableSchema{
+					Schema: &schemasv1alpha4.TableSchema{
+						Postgres: &schemasv1alpha4.SQLTableSchema{
 							PrimaryKey: []string{"id"},
-							Columns: []*schemasv1alpha3.SQLTableColumn{
+							Columns: []*schemasv1alpha4.SQLTableColumn{
 								{
 									Name: "id",
 									Type: "text",
-									Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+									Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 										NotNull: new(bool),
 									},
 								},
 								{
 									Name: "name",
 									Type: "text",
-									Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+									Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 										NotNull: new(bool),
 									},
 								},
@@ -172,7 +172,7 @@ postgres://{{ .Data.username }}:{{ .Data.password }}@postgres:5432/my-database{{
 						},
 					},
 				},
-				Status: schemasv1alpha3.TableStatus{},
+				Status: schemasv1alpha4.TableStatus{},
 			},
 		},
 	}
diff --git a/pkg/controller/table/objects_test.go b/pkg/controller/table/objects_test.go
index b35a7d8f..545ab93b 100644
--- a/pkg/controller/table/objects_test.go
+++ b/pkg/controller/table/objects_test.go
@@ -3,8 +3,8 @@ package table
 import (
 	"testing"
 
-	databasesv1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -13,22 +13,22 @@ import (
 func Test_planConfigMap(t *testing.T) {
 	tests := []struct {
 		name     string
-		table    schemasv1alpha3.Table
-		database databasesv1alpha3.Database
+		table    schemasv1alpha4.Table
+		database databasesv1alpha4.Database
 		expect   string
 	}{
 		{
 			name: "basic test",
-			table: schemasv1alpha3.Table{
-				Spec: schemasv1alpha3.TableSpec{
+			table: schemasv1alpha4.Table{
+				Spec: schemasv1alpha4.TableSpec{
 					Database: "db",
 					Name:     "name",
-					Schema: &schemasv1alpha3.TableSchema{
-						Postgres: &schemasv1alpha3.SQLTableSchema{},
+					Schema: &schemasv1alpha4.TableSchema{
+						Postgres: &schemasv1alpha4.SQLTableSchema{},
 					},
 				},
 			},
-			database: databasesv1alpha3.Database{},
+			database: databasesv1alpha4.Database{},
 			expect: `database: db
 name: name
 schema:
@@ -55,8 +55,8 @@ func Test_vaultAnnotations(t *testing.T) {
 		name                string
 		expectedAnnotations map[string]string
 		expectedArgs        []string
-		database            *databasesv1alpha3.Database
-		table               *schemasv1alpha3.Table
+		database            *databasesv1alpha4.Database
+		table               *schemasv1alpha4.Table
 	}{
 		{
 			name: "Configures correctly when using Vault",
@@ -77,15 +77,15 @@ postgres://{{ .Data.username }}:{{ .Data.password }}@postgres:5432/my-database{{
 				"--vault-uri-ref",
 				"/vault/secrets/schemaherouri",
 			},
-			database: &databasesv1alpha3.Database{
-				TypeMeta:   v1.TypeMeta{APIVersion: "databases.schemahero.io/v1alpha3", Kind: "Database"},
+			database: &databasesv1alpha4.Database{
+				TypeMeta:   v1.TypeMeta{APIVersion: "databases.schemahero.io/v1alpha4", Kind: "Database"},
 				ObjectMeta: v1.ObjectMeta{Name: "my-database"},
-				Spec: databasesv1alpha3.DatabaseSpec{
-					Connection: databasesv1alpha3.DatabaseConnection{
-						Postgres: &databasesv1alpha3.PostgresConnection{
-							URI: databasesv1alpha3.ValueOrValueFrom{
-								ValueFrom: &databasesv1alpha3.ValueFrom{
-									Vault: &databasesv1alpha3.Vault{
+				Spec: databasesv1alpha4.DatabaseSpec{
+					Connection: databasesv1alpha4.DatabaseConnection{
+						Postgres: &databasesv1alpha4.PostgresConnection{
+							URI: databasesv1alpha4.ValueOrValueFrom{
+								ValueFrom: &databasesv1alpha4.ValueFrom{
+									Vault: &databasesv1alpha4.Vault{
 										Secret: "database/creds/schemahero",
 										Role:   "schemahero-plan",
 									},
@@ -94,29 +94,29 @@ postgres://{{ .Data.username }}:{{ .Data.password }}@postgres:5432/my-database{{
 						},
 					},
 				},
-				Status: databasesv1alpha3.DatabaseStatus{},
+				Status: databasesv1alpha4.DatabaseStatus{},
 			},
-			table: &schemasv1alpha3.Table{
-				TypeMeta:   v1.TypeMeta{APIVersion: "schemas.schemahero.io/v1alpha3", Kind: "Table"},
+			table: &schemasv1alpha4.Table{
+				TypeMeta:   v1.TypeMeta{APIVersion: "schemas.schemahero.io/v1alpha4", Kind: "Table"},
 				ObjectMeta: v1.ObjectMeta{Name: "my-table"},
-				Spec: schemasv1alpha3.TableSpec{
+				Spec: schemasv1alpha4.TableSpec{
 					Database: "my-database",
 					Name:     "my-table",
-					Schema: &schemasv1alpha3.TableSchema{
-						Postgres: &schemasv1alpha3.SQLTableSchema{
+					Schema: &schemasv1alpha4.TableSchema{
+						Postgres: &schemasv1alpha4.SQLTableSchema{
 							PrimaryKey: []string{"id"},
-							Columns: []*schemasv1alpha3.SQLTableColumn{
+							Columns: []*schemasv1alpha4.SQLTableColumn{
 								{
 									Name: "id",
 									Type: "text",
-									Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+									Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 										NotNull: new(bool),
 									},
 								},
 								{
 									Name: "name",
 									Type: "text",
-									Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+									Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 										NotNull: new(bool),
 									},
 								},
@@ -124,7 +124,7 @@ postgres://{{ .Data.username }}:{{ .Data.password }}@postgres:5432/my-database{{
 						},
 					},
 				},
-				Status: schemasv1alpha3.TableStatus{},
+				Status: schemasv1alpha4.TableStatus{},
 			},
 		},
 		{
@@ -139,41 +139,41 @@ postgres://{{ .Data.username }}:{{ .Data.password }}@postgres:5432/my-database{{
 				"--uri",
 				"postgres://user:password@postgres:5432/my-database",
 			},
-			database: &databasesv1alpha3.Database{
-				TypeMeta:   v1.TypeMeta{APIVersion: "databases.schemahero.io/v1alpha3", Kind: "Database"},
+			database: &databasesv1alpha4.Database{
+				TypeMeta:   v1.TypeMeta{APIVersion: "databases.schemahero.io/v1alpha4", Kind: "Database"},
 				ObjectMeta: v1.ObjectMeta{Name: "my-database"},
-				Spec: databasesv1alpha3.DatabaseSpec{
-					Connection: databasesv1alpha3.DatabaseConnection{
-						Postgres: &databasesv1alpha3.PostgresConnection{
-							URI: databasesv1alpha3.ValueOrValueFrom{
+				Spec: databasesv1alpha4.DatabaseSpec{
+					Connection: databasesv1alpha4.DatabaseConnection{
+						Postgres: &databasesv1alpha4.PostgresConnection{
+							URI: databasesv1alpha4.ValueOrValueFrom{
 								Value: "postgres://user:password@postgres:5432/my-database",
 							},
 						},
 					},
 				},
-				Status: databasesv1alpha3.DatabaseStatus{},
+				Status: databasesv1alpha4.DatabaseStatus{},
 			},
-			table: &schemasv1alpha3.Table{
-				TypeMeta:   v1.TypeMeta{APIVersion: "schemas.schemahero.io/v1alpha3", Kind: "Table"},
+			table: &schemasv1alpha4.Table{
+				TypeMeta:   v1.TypeMeta{APIVersion: "schemas.schemahero.io/v1alpha4", Kind: "Table"},
 				ObjectMeta: v1.ObjectMeta{Name: "my-table"},
-				Spec: schemasv1alpha3.TableSpec{
+				Spec: schemasv1alpha4.TableSpec{
 					Database: "my-database",
 					Name:     "my-table",
-					Schema: &schemasv1alpha3.TableSchema{
-						Postgres: &schemasv1alpha3.SQLTableSchema{
+					Schema: &schemasv1alpha4.TableSchema{
+						Postgres: &schemasv1alpha4.SQLTableSchema{
 							PrimaryKey: []string{"id"},
-							Columns: []*schemasv1alpha3.SQLTableColumn{
+							Columns: []*schemasv1alpha4.SQLTableColumn{
 								{
 									Name: "id",
 									Type: "text",
-									Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+									Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 										NotNull: new(bool),
 									},
 								},
 								{
 									Name: "name",
 									Type: "text",
-									Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+									Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 										NotNull: new(bool),
 									},
 								},
@@ -181,7 +181,7 @@ postgres://{{ .Data.username }}:{{ .Data.password }}@postgres:5432/my-database{{
 						},
 					},
 				},
-				Status: schemasv1alpha3.TableStatus{},
+				Status: schemasv1alpha4.TableStatus{},
 			},
 		},
 	}
diff --git a/pkg/controller/table/reconcile_pod_test.go b/pkg/controller/table/reconcile_pod_test.go
index f596a227..a4f0db01 100644
--- a/pkg/controller/table/reconcile_pod_test.go
+++ b/pkg/controller/table/reconcile_pod_test.go
@@ -6,7 +6,7 @@ import (
 	"fmt"
 	"testing"
 
-	databasesv1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
 	corev1 "k8s.io/api/core/v1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -17,22 +17,22 @@ import (
 func TestReadConnectionURI(t *testing.T) {
 	tests := []struct {
 		name     string
-		value    databasesv1alpha3.ValueOrValueFrom
+		value    databasesv1alpha4.ValueOrValueFrom
 		secret   *corev1.Secret
 		expected string
 	}{
 		{
 			name: "Gets URI string",
-			value: databasesv1alpha3.ValueOrValueFrom{
+			value: databasesv1alpha4.ValueOrValueFrom{
 				Value: "postgresql://username:password@postgrs:5433/database",
 			},
 			expected: "postgresql://username:password@postgrs:5433/database",
 		},
 		{
 			name: "Gets URI from k8s secret",
-			value: databasesv1alpha3.ValueOrValueFrom{
-				ValueFrom: &databasesv1alpha3.ValueFrom{
-					SecretKeyRef: &databasesv1alpha3.SecretKeyRef{
+			value: databasesv1alpha4.ValueOrValueFrom{
+				ValueFrom: &databasesv1alpha4.ValueFrom{
+					SecretKeyRef: &databasesv1alpha4.SecretKeyRef{
 						Name: "postgresql-secret",
 						Key:  "uri",
 					},
@@ -48,9 +48,9 @@ func TestReadConnectionURI(t *testing.T) {
 		},
 		{
 			name: "Gets URI from Vault volume",
-			value: databasesv1alpha3.ValueOrValueFrom{
-				ValueFrom: &databasesv1alpha3.ValueFrom{
-					Vault: &databasesv1alpha3.Vault{
+			value: databasesv1alpha4.ValueOrValueFrom{
+				ValueFrom: &databasesv1alpha4.ValueFrom{
+					Vault: &databasesv1alpha4.Vault{
 						Secret: "/vault/creds/schemahero",
 						Role:   "databaseName",
 					},
diff --git a/pkg/controller/table/reconcile_table_test.go b/pkg/controller/table/reconcile_table_test.go
index 9bd62413..165b46cf 100644
--- a/pkg/controller/table/reconcile_table_test.go
+++ b/pkg/controller/table/reconcile_table_test.go
@@ -3,71 +3,71 @@ package table
 import (
 	"testing"
 
-	databasesv1alpha3 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	databasesv1alpha4 "github.com/schemahero/schemahero/pkg/apis/databases/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/stretchr/testify/assert"
 )
 
 func Test_checkDatabaseTypeMatches(t *testing.T) {
 	tests := []struct {
 		name        string
-		connection  databasesv1alpha3.DatabaseConnection
-		tableSchema schemasv1alpha3.TableSchema
+		connection  databasesv1alpha4.DatabaseConnection
+		tableSchema schemasv1alpha4.TableSchema
 		expect      bool
 	}{
 		{
 			name: "is postgres",
-			connection: databasesv1alpha3.DatabaseConnection{
-				Postgres: &databasesv1alpha3.PostgresConnection{
-					URI: databasesv1alpha3.ValueOrValueFrom{
+			connection: databasesv1alpha4.DatabaseConnection{
+				Postgres: &databasesv1alpha4.PostgresConnection{
+					URI: databasesv1alpha4.ValueOrValueFrom{
 						Value: "test",
 					},
 				},
 			},
-			tableSchema: schemasv1alpha3.TableSchema{
-				Postgres: &schemasv1alpha3.SQLTableSchema{},
+			tableSchema: schemasv1alpha4.TableSchema{
+				Postgres: &schemasv1alpha4.SQLTableSchema{},
 			},
 			expect: true,
 		},
 		{
 			name: "is mysql",
-			connection: databasesv1alpha3.DatabaseConnection{
-				Mysql: &databasesv1alpha3.MysqlConnection{
-					URI: databasesv1alpha3.ValueOrValueFrom{
+			connection: databasesv1alpha4.DatabaseConnection{
+				Mysql: &databasesv1alpha4.MysqlConnection{
+					URI: databasesv1alpha4.ValueOrValueFrom{
 						Value: "test",
 					},
 				},
 			},
-			tableSchema: schemasv1alpha3.TableSchema{
-				Mysql: &schemasv1alpha3.SQLTableSchema{},
+			tableSchema: schemasv1alpha4.TableSchema{
+				Mysql: &schemasv1alpha4.SQLTableSchema{},
 			},
 			expect: true,
 		},
 		{
 			name: "is mysql, expect postgres",
-			connection: databasesv1alpha3.DatabaseConnection{
-				Mysql: &databasesv1alpha3.MysqlConnection{
-					URI: databasesv1alpha3.ValueOrValueFrom{
+			connection: databasesv1alpha4.DatabaseConnection{
+				Mysql: &databasesv1alpha4.MysqlConnection{
+					URI: databasesv1alpha4.ValueOrValueFrom{
 						Value: "test",
 					},
 				},
 			},
-			tableSchema: schemasv1alpha3.TableSchema{
-				Postgres: &schemasv1alpha3.SQLTableSchema{},
+			tableSchema: schemasv1alpha4.TableSchema{
+				Postgres: &schemasv1alpha4.SQLTableSchema{},
 			},
 			expect: false,
 		},
 		{
 			name: "is postgres, expect mysql",
-			connection: databasesv1alpha3.DatabaseConnection{
-				Postgres: &databasesv1alpha3.PostgresConnection{
-					URI: databasesv1alpha3.ValueOrValueFrom{
+			connection: databasesv1alpha4.DatabaseConnection{
+				Postgres: &databasesv1alpha4.PostgresConnection{
+					URI: databasesv1alpha4.ValueOrValueFrom{
 						Value: "test",
 					},
 				},
 			},
-			tableSchema: schemasv1alpha3.TableSchema{
-				Mysql: &schemasv1alpha3.SQLTableSchema{},
+			tableSchema: schemasv1alpha4.TableSchema{
+				Mysql: &schemasv1alpha4.SQLTableSchema{},
 			},
 			expect: false,
 		},
diff --git a/pkg/database/mysql/alter_test.go b/pkg/database/mysql/alter_test.go
index b4715c90..b4fb6ba0 100644
--- a/pkg/database/mysql/alter_test.go
+++ b/pkg/database/mysql/alter_test.go
@@ -3,7 +3,7 @@ package mysql
 import (
 	"testing"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 
 	"github.com/stretchr/testify/assert"
@@ -17,14 +17,14 @@ func Test_AlterColumnStatment(t *testing.T) {
 	tests := []struct {
 		name               string
 		tableName          string
-		desiredColumns     []*schemasv1alpha3.SQLTableColumn
+		desiredColumns     []*schemasv1alpha4.SQLTableColumn
 		existingColumn     *types.Column
 		expectedStatements []string
 	}{
 		{
 			name:      "no change",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
 				{
 					Name: "a",
 					Type: "integer",
@@ -44,7 +44,7 @@ func Test_AlterColumnStatment(t *testing.T) {
 		{
 			name:      "change data type",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
 				{
 					Name: "a",
 					Type: "integer",
@@ -64,7 +64,7 @@ func Test_AlterColumnStatment(t *testing.T) {
 		{
 			name:      "drop column",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
 				{
 					Name: "a",
 					Type: "integer",
@@ -80,11 +80,11 @@ func Test_AlterColumnStatment(t *testing.T) {
 		{
 			name:      "add not null constraint",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
 				{
 					Name: "a",
 					Type: "integer",
-					Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+					Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 						NotNull: &trueValue,
 					},
 				},
@@ -102,11 +102,11 @@ func Test_AlterColumnStatment(t *testing.T) {
 		{
 			name:      "drop not null constraint",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
 				{
 					Name: "a",
 					Type: "integer",
-					Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+					Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 						NotNull: &falseValue,
 					},
 				},
@@ -124,7 +124,7 @@ func Test_AlterColumnStatment(t *testing.T) {
 		{
 			name:      "no change to not null constraint",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
 				{
 					Name: "t",
 					Type: "text",
@@ -143,11 +143,11 @@ func Test_AlterColumnStatment(t *testing.T) {
 		{
 			name:      "type change, constraint no change",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
 				{
 					Name: "a",
 					Type: "integer",
-					Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+					Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 						NotNull: &trueValue,
 					},
 				},
@@ -165,7 +165,7 @@ func Test_AlterColumnStatment(t *testing.T) {
 		{
 			name:      "default set",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
 				{
 					Name:    "a",
 					Type:    "integer",
@@ -181,7 +181,7 @@ func Test_AlterColumnStatment(t *testing.T) {
 		{
 			name:      "default unset",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
 				{
 					Name: "a",
 					Type: "integer",
@@ -197,7 +197,7 @@ func Test_AlterColumnStatment(t *testing.T) {
 		{
 			name:      "default empty string",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
 				{
 					Name:    "a",
 					Type:    "varchar (32)",
@@ -214,11 +214,11 @@ func Test_AlterColumnStatment(t *testing.T) {
 		// {
 		// 	name:      "no change to not nullable timestamp using short column type",
 		// 	tableName: "ts",
-		// 	desiredColumns: []*schemasv1alpha3.SQLTableColumn{
-		// 		&schemasv1alpha3.SQLTableColumn{
+		// 	desiredColumns: []*schemasv1alpha4.SQLTableColumn{
+		// 		&schemasv1alpha4.SQLTableColumn{
 		// 			Name: "ts",
 		// 			Type: "timestamp",
-		// 			Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+		// 			Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 		// 				NotNull: &trueValue,
 		// 			},
 		// 		},
@@ -236,11 +236,11 @@ func Test_AlterColumnStatment(t *testing.T) {
 		// {
 		// 	name:      "no change to not nullable timestamp",
 		// 	tableName: "ts",
-		// 	desiredColumns: []*schemasv1alpha3.SQLTableColumn{
-		// 		&schemasv1alpha3.SQLTableColumn{
+		// 	desiredColumns: []*schemasv1alpha4.SQLTableColumn{
+		// 		&schemasv1alpha4.SQLTableColumn{
 		// 			Name: "ts",
 		// 			Type: "timestamp with time zone",
-		// 			Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+		// 			Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 		// 				NotNull: &trueValue,
 		// 			},
 		// 		},
@@ -258,12 +258,12 @@ func Test_AlterColumnStatment(t *testing.T) {
 		{
 			name:      "add null and default",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
 				{
 					Name:    "a",
 					Type:    "varchar (255)",
 					Default: &defaultEleven,
-					Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+					Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 						NotNull: &trueValue,
 					},
 				},
diff --git a/pkg/database/mysql/column_test.go b/pkg/database/mysql/column_test.go
index e6750b40..a9b1c06e 100644
--- a/pkg/database/mysql/column_test.go
+++ b/pkg/database/mysql/column_test.go
@@ -3,7 +3,7 @@ package mysql
 import (
 	"testing"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 
 	"github.com/stretchr/testify/assert"
@@ -14,15 +14,15 @@ func Test_mysqlColumnAsInsert(t *testing.T) {
 	default11 := "11"
 	tests := []struct {
 		name              string
-		column            *schemasv1alpha3.SQLTableColumn
+		column            *schemasv1alpha4.SQLTableColumn
 		expectedStatement string
 	}{
 		{
 			name: "simple",
-			column: &schemasv1alpha3.SQLTableColumn{
+			column: &schemasv1alpha4.SQLTableColumn{
 				Name: "c",
 				Type: "integer",
-				Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+				Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 					NotNull: &trueValue,
 				},
 				Default: &default11,
@@ -46,13 +46,13 @@ func Test_InsertColumnStatement(t *testing.T) {
 	tests := []struct {
 		name              string
 		tableName         string
-		desiredColumn     *schemasv1alpha3.SQLTableColumn
+		desiredColumn     *schemasv1alpha4.SQLTableColumn
 		expectedStatement string
 	}{
 		{
 			name:      "add column",
 			tableName: "t",
-			desiredColumn: &schemasv1alpha3.SQLTableColumn{
+			desiredColumn: &schemasv1alpha4.SQLTableColumn{
 				Name: "a",
 				Type: "integer",
 			},
@@ -61,10 +61,10 @@ func Test_InsertColumnStatement(t *testing.T) {
 		{
 			name:      "add not null column",
 			tableName: "t",
-			desiredColumn: &schemasv1alpha3.SQLTableColumn{
+			desiredColumn: &schemasv1alpha4.SQLTableColumn{
 				Name: "a",
 				Type: "integer",
-				Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+				Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 					NotNull: &trueValue,
 				},
 			},
@@ -73,10 +73,10 @@ func Test_InsertColumnStatement(t *testing.T) {
 		{
 			name:      "add null column",
 			tableName: "t",
-			desiredColumn: &schemasv1alpha3.SQLTableColumn{
+			desiredColumn: &schemasv1alpha4.SQLTableColumn{
 				Name: "a",
 				Type: "integer",
-				Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+				Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 					NotNull: &falseValue,
 				},
 			},
@@ -98,12 +98,12 @@ func Test_InsertColumnStatement(t *testing.T) {
 func Test_schemaColumnToMysqlColumn(t *testing.T) {
 	tests := []struct {
 		name           string
-		schemaColumn   *schemasv1alpha3.SQLTableColumn
+		schemaColumn   *schemasv1alpha4.SQLTableColumn
 		expectedColumn *types.Column
 	}{
 		{
 			name: "varchar (10)",
-			schemaColumn: &schemasv1alpha3.SQLTableColumn{
+			schemaColumn: &schemasv1alpha4.SQLTableColumn{
 				Name: "vc",
 				Type: "varchar (10)",
 			},
@@ -115,7 +115,7 @@ func Test_schemaColumnToMysqlColumn(t *testing.T) {
 		},
 		{
 			name: "bool",
-			schemaColumn: &schemasv1alpha3.SQLTableColumn{
+			schemaColumn: &schemasv1alpha4.SQLTableColumn{
 				Name: "b",
 				Type: "bool",
 			},
diff --git a/pkg/database/mysql/create_test.go b/pkg/database/mysql/create_test.go
index 1b5734ee..6fa491ad 100644
--- a/pkg/database/mysql/create_test.go
+++ b/pkg/database/mysql/create_test.go
@@ -3,7 +3,7 @@ package mysql
 import (
 	"testing"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -12,18 +12,18 @@ import (
 func Test_CreateTableStatement(t *testing.T) {
 	tests := []struct {
 		name              string
-		tableSchema       *schemasv1alpha3.SQLTableSchema
+		tableSchema       *schemasv1alpha4.SQLTableSchema
 		tableName         string
 		expectedStatement string
 	}{
 		{
 			name: "simple",
-			tableSchema: &schemasv1alpha3.SQLTableSchema{
+			tableSchema: &schemasv1alpha4.SQLTableSchema{
 				PrimaryKey: []string{
 					"id",
 				},
-				Columns: []*schemasv1alpha3.SQLTableColumn{
-					&schemasv1alpha3.SQLTableColumn{
+				Columns: []*schemasv1alpha4.SQLTableColumn{
+					&schemasv1alpha4.SQLTableColumn{
 						Name: "id",
 						Type: "integer",
 					},
@@ -34,21 +34,21 @@ func Test_CreateTableStatement(t *testing.T) {
 		},
 		{
 			name: "composite primary key",
-			tableSchema: &schemasv1alpha3.SQLTableSchema{
+			tableSchema: &schemasv1alpha4.SQLTableSchema{
 				PrimaryKey: []string{
 					"one",
 					"two",
 				},
-				Columns: []*schemasv1alpha3.SQLTableColumn{
-					&schemasv1alpha3.SQLTableColumn{
+				Columns: []*schemasv1alpha4.SQLTableColumn{
+					&schemasv1alpha4.SQLTableColumn{
 						Name: "one",
 						Type: "integer",
 					},
-					&schemasv1alpha3.SQLTableColumn{
+					&schemasv1alpha4.SQLTableColumn{
 						Name: "two",
 						Type: "integer",
 					},
-					&schemasv1alpha3.SQLTableColumn{
+					&schemasv1alpha4.SQLTableColumn{
 						Name: "three",
 						Type: "varchar(255)",
 					},
diff --git a/pkg/database/mysql/foreignkey_test.go b/pkg/database/mysql/foreignkey_test.go
index 642f2a3a..a92100bb 100644
--- a/pkg/database/mysql/foreignkey_test.go
+++ b/pkg/database/mysql/foreignkey_test.go
@@ -3,7 +3,7 @@ package mysql
 import (
 	"testing"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -12,17 +12,17 @@ func Test_AddForeignKeyStatement(t *testing.T) {
 	tests := []struct {
 		name              string
 		tableName         string
-		schemaForeignKey  *schemasv1alpha3.SQLTableForeignKey
+		schemaForeignKey  *schemasv1alpha4.SQLTableForeignKey
 		expectedStatement string
 	}{
 		{
 			name:      "no name, one column, no on delete",
 			tableName: "t2",
-			schemaForeignKey: &schemasv1alpha3.SQLTableForeignKey{
+			schemaForeignKey: &schemasv1alpha4.SQLTableForeignKey{
 				Columns: []string{
 					"c2",
 				},
-				References: schemasv1alpha3.SQLTableForeignKeyReferences{
+				References: schemasv1alpha4.SQLTableForeignKeyReferences{
 					Table: "t1",
 					Columns: []string{
 						"c1",
@@ -34,12 +34,12 @@ func Test_AddForeignKeyStatement(t *testing.T) {
 		{
 			name:      "named, one column, no on delete",
 			tableName: "t2",
-			schemaForeignKey: &schemasv1alpha3.SQLTableForeignKey{
+			schemaForeignKey: &schemasv1alpha4.SQLTableForeignKey{
 				Name: "hi_i_am_a_fkey",
 				Columns: []string{
 					"c2",
 				},
-				References: schemasv1alpha3.SQLTableForeignKeyReferences{
+				References: schemasv1alpha4.SQLTableForeignKeyReferences{
 					Table: "t1",
 					Columns: []string{
 						"c1",
@@ -51,12 +51,12 @@ func Test_AddForeignKeyStatement(t *testing.T) {
 		{
 			name:      "no name, two columns, no on delete",
 			tableName: "t2",
-			schemaForeignKey: &schemasv1alpha3.SQLTableForeignKey{
+			schemaForeignKey: &schemasv1alpha4.SQLTableForeignKey{
 				Columns: []string{
 					"c2",
 					"c22",
 				},
-				References: schemasv1alpha3.SQLTableForeignKeyReferences{
+				References: schemasv1alpha4.SQLTableForeignKeyReferences{
 					Table: "t1",
 					Columns: []string{
 						"c1",
@@ -69,12 +69,12 @@ func Test_AddForeignKeyStatement(t *testing.T) {
 		{
 			name:      "no name, one column, on delete cascade",
 			tableName: "t2",
-			schemaForeignKey: &schemasv1alpha3.SQLTableForeignKey{
+			schemaForeignKey: &schemasv1alpha4.SQLTableForeignKey{
 				OnDelete: "cascade",
 				Columns: []string{
 					"c2",
 				},
-				References: schemasv1alpha3.SQLTableForeignKeyReferences{
+				References: schemasv1alpha4.SQLTableForeignKeyReferences{
 					Table: "t1",
 					Columns: []string{
 						"c1",
diff --git a/pkg/database/postgres/alter_test.go b/pkg/database/postgres/alter_test.go
index 3b6e718f..a9a8247a 100644
--- a/pkg/database/postgres/alter_test.go
+++ b/pkg/database/postgres/alter_test.go
@@ -3,7 +3,7 @@ package postgres
 import (
 	"testing"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 
 	"github.com/stretchr/testify/assert"
@@ -17,19 +17,19 @@ func Test_AlterColumnStatments(t *testing.T) {
 	tests := []struct {
 		name               string
 		tableName          string
-		desiredColumns     []*schemasv1alpha3.SQLTableColumn
+		desiredColumns     []*schemasv1alpha4.SQLTableColumn
 		existingColumn     *types.Column
 		expectedStatements []string
 	}{
 		{
 			name:      "no change",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
-				&schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
+				&schemasv1alpha4.SQLTableColumn{
 					Name: "a",
 					Type: "integer",
 				},
-				&schemasv1alpha3.SQLTableColumn{
+				&schemasv1alpha4.SQLTableColumn{
 					Name: "b",
 					Type: "integer",
 				},
@@ -44,8 +44,8 @@ func Test_AlterColumnStatments(t *testing.T) {
 		{
 			name:      "no change varchar",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
-				&schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
+				&schemasv1alpha4.SQLTableColumn{
 					Name: "a",
 					Type: "varchar(32)",
 				},
@@ -59,12 +59,12 @@ func Test_AlterColumnStatments(t *testing.T) {
 		{
 			name:      "change data type",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
-				&schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
+				&schemasv1alpha4.SQLTableColumn{
 					Name: "a",
 					Type: "integer",
 				},
-				&schemasv1alpha3.SQLTableColumn{
+				&schemasv1alpha4.SQLTableColumn{
 					Name: "b",
 					Type: "integer",
 				},
@@ -79,8 +79,8 @@ func Test_AlterColumnStatments(t *testing.T) {
 		{
 			name:      "drop column",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
-				&schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
+				&schemasv1alpha4.SQLTableColumn{
 					Name: "a",
 					Type: "integer",
 				},
@@ -95,11 +95,11 @@ func Test_AlterColumnStatments(t *testing.T) {
 		{
 			name:      "add not null constraint",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
 				{
 					Name: "a",
 					Type: "integer",
-					Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+					Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 						NotNull: &trueValue,
 					},
 				},
@@ -117,11 +117,11 @@ func Test_AlterColumnStatments(t *testing.T) {
 		{
 			name:      "drop not null constraint",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
-				&schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
+				&schemasv1alpha4.SQLTableColumn{
 					Name: "a",
 					Type: "integer",
-					Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+					Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 						NotNull: &falseValue,
 					},
 				},
@@ -139,8 +139,8 @@ func Test_AlterColumnStatments(t *testing.T) {
 		{
 			name:      "no change to not null constraint",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
-				&schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
+				&schemasv1alpha4.SQLTableColumn{
 					Name: "t",
 					Type: "text",
 				},
@@ -158,11 +158,11 @@ func Test_AlterColumnStatments(t *testing.T) {
 		{
 			name:      "no change to not nullable timestamp using short column type",
 			tableName: "ts",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
-				&schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
+				&schemasv1alpha4.SQLTableColumn{
 					Name: "ts",
 					Type: "timestamp",
-					Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+					Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 						NotNull: &trueValue,
 					},
 				},
@@ -180,11 +180,11 @@ func Test_AlterColumnStatments(t *testing.T) {
 		{
 			name:      "no change to not nullable timestamp",
 			tableName: "ts",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
-				&schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
+				&schemasv1alpha4.SQLTableColumn{
 					Name: "ts",
 					Type: "timestamp with time zone",
-					Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+					Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 						NotNull: &trueValue,
 					},
 				},
@@ -202,8 +202,8 @@ func Test_AlterColumnStatments(t *testing.T) {
 		{
 			name:      "default set",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
-				&schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
+				&schemasv1alpha4.SQLTableColumn{
 					Name:    "a",
 					Type:    "integer",
 					Default: &defaultEleven,
@@ -218,8 +218,8 @@ func Test_AlterColumnStatments(t *testing.T) {
 		{
 			name:      "default unset",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
-				&schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
+				&schemasv1alpha4.SQLTableColumn{
 					Name: "a",
 					Type: "integer",
 				},
@@ -234,8 +234,8 @@ func Test_AlterColumnStatments(t *testing.T) {
 		{
 			name:      "default empty string",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
-				&schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
+				&schemasv1alpha4.SQLTableColumn{
 					Name:    "a",
 					Type:    "varchar (32)",
 					Default: &defaultEmpty,
@@ -250,12 +250,12 @@ func Test_AlterColumnStatments(t *testing.T) {
 		{
 			name:      "add null and default",
 			tableName: "t",
-			desiredColumns: []*schemasv1alpha3.SQLTableColumn{
+			desiredColumns: []*schemasv1alpha4.SQLTableColumn{
 				{
 					Name:    "a",
 					Type:    "varchar (32)",
 					Default: &defaultEleven,
-					Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+					Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 						NotNull: &trueValue,
 					},
 				},
diff --git a/pkg/database/postgres/column_test.go b/pkg/database/postgres/column_test.go
index 4c1e386b..6d4d793a 100644
--- a/pkg/database/postgres/column_test.go
+++ b/pkg/database/postgres/column_test.go
@@ -3,7 +3,7 @@ package postgres
 import (
 	"testing"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 	"github.com/schemahero/schemahero/pkg/database/types"
 
 	"github.com/stretchr/testify/assert"
@@ -14,12 +14,12 @@ func Test_postgresColumnAsInsert(t *testing.T) {
 	default11 := "11"
 	tests := []struct {
 		name              string
-		column            *schemasv1alpha3.SQLTableColumn
+		column            *schemasv1alpha4.SQLTableColumn
 		expectedStatement string
 	}{
 		{
 			name: "simple",
-			column: &schemasv1alpha3.SQLTableColumn{
+			column: &schemasv1alpha4.SQLTableColumn{
 				Name: "c",
 				Type: "integer",
 			},
@@ -27,7 +27,7 @@ func Test_postgresColumnAsInsert(t *testing.T) {
 		},
 		{
 			name: "text",
-			column: &schemasv1alpha3.SQLTableColumn{
+			column: &schemasv1alpha4.SQLTableColumn{
 				Name: "t",
 				Type: "text",
 			},
@@ -35,7 +35,7 @@ func Test_postgresColumnAsInsert(t *testing.T) {
 		},
 		{
 			name: "timestamp without time zone",
-			column: &schemasv1alpha3.SQLTableColumn{
+			column: &schemasv1alpha4.SQLTableColumn{
 				Name: "t",
 				Type: "timestamp without time zone",
 			},
@@ -43,7 +43,7 @@ func Test_postgresColumnAsInsert(t *testing.T) {
 		},
 		{
 			name: "character varying (4)",
-			column: &schemasv1alpha3.SQLTableColumn{
+			column: &schemasv1alpha4.SQLTableColumn{
 				Name: "c",
 				Type: "character varying (4)",
 			},
@@ -51,10 +51,10 @@ func Test_postgresColumnAsInsert(t *testing.T) {
 		},
 		{
 			name: "constraint not null",
-			column: &schemasv1alpha3.SQLTableColumn{
+			column: &schemasv1alpha4.SQLTableColumn{
 				Name: "c",
 				Type: "integer",
-				Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+				Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 					NotNull: &trueValue,
 				},
 			},
@@ -62,7 +62,7 @@ func Test_postgresColumnAsInsert(t *testing.T) {
 		},
 		{
 			name: "default",
-			column: &schemasv1alpha3.SQLTableColumn{
+			column: &schemasv1alpha4.SQLTableColumn{
 				Name:    "c",
 				Type:    "integer",
 				Default: &default11,
@@ -71,7 +71,7 @@ func Test_postgresColumnAsInsert(t *testing.T) {
 		},
 		{
 			name: "text[]",
-			column: &schemasv1alpha3.SQLTableColumn{
+			column: &schemasv1alpha4.SQLTableColumn{
 				Name: "c",
 				Type: "text[]",
 			},
@@ -94,13 +94,13 @@ func Test_InsertColumnStatement(t *testing.T) {
 	tests := []struct {
 		name              string
 		tableName         string
-		desiredColumn     *schemasv1alpha3.SQLTableColumn
+		desiredColumn     *schemasv1alpha4.SQLTableColumn
 		expectedStatement string
 	}{
 		{
 			name:      "add column",
 			tableName: "t",
-			desiredColumn: &schemasv1alpha3.SQLTableColumn{
+			desiredColumn: &schemasv1alpha4.SQLTableColumn{
 				Name: "a",
 				Type: "integer",
 			},
@@ -109,10 +109,10 @@ func Test_InsertColumnStatement(t *testing.T) {
 		{
 			name:      "add not null column",
 			tableName: "t",
-			desiredColumn: &schemasv1alpha3.SQLTableColumn{
+			desiredColumn: &schemasv1alpha4.SQLTableColumn{
 				Name: "a",
 				Type: "integer",
-				Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+				Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 					NotNull: &trueValue,
 				},
 			},
@@ -121,10 +121,10 @@ func Test_InsertColumnStatement(t *testing.T) {
 		{
 			name:      "add null column",
 			tableName: "t",
-			desiredColumn: &schemasv1alpha3.SQLTableColumn{
+			desiredColumn: &schemasv1alpha4.SQLTableColumn{
 				Name: "a",
 				Type: "integer",
-				Constraints: &schemasv1alpha3.SQLTableColumnConstraints{
+				Constraints: &schemasv1alpha4.SQLTableColumnConstraints{
 					NotNull: &falseValue,
 				},
 			},
@@ -146,12 +146,12 @@ func Test_InsertColumnStatement(t *testing.T) {
 func Test_schemaColumnToPostgresColumn(t *testing.T) {
 	tests := []struct {
 		name           string
-		schemaColumn   *schemasv1alpha3.SQLTableColumn
+		schemaColumn   *schemasv1alpha4.SQLTableColumn
 		expectedColumn *types.Column
 	}{
 		{
 			name: "text",
-			schemaColumn: &schemasv1alpha3.SQLTableColumn{
+			schemaColumn: &schemasv1alpha4.SQLTableColumn{
 				Name: "t",
 				Type: "text",
 			},
@@ -164,7 +164,7 @@ func Test_schemaColumnToPostgresColumn(t *testing.T) {
 		},
 		{
 			name: "character varying (10)",
-			schemaColumn: &schemasv1alpha3.SQLTableColumn{
+			schemaColumn: &schemasv1alpha4.SQLTableColumn{
 				Name: "c",
 				Type: "character varying (10)",
 			},
@@ -176,7 +176,7 @@ func Test_schemaColumnToPostgresColumn(t *testing.T) {
 		},
 		{
 			name: "varchar (10)",
-			schemaColumn: &schemasv1alpha3.SQLTableColumn{
+			schemaColumn: &schemasv1alpha4.SQLTableColumn{
 				Name: "vc",
 				Type: "varchar (10)",
 			},
@@ -188,7 +188,7 @@ func Test_schemaColumnToPostgresColumn(t *testing.T) {
 		},
 		{
 			name: "cidr",
-			schemaColumn: &schemasv1alpha3.SQLTableColumn{
+			schemaColumn: &schemasv1alpha4.SQLTableColumn{
 				Name: "ip",
 				Type: "cidr",
 			},
diff --git a/pkg/database/postgres/create_test.go b/pkg/database/postgres/create_test.go
index d7b4c6db..4b93dd9f 100644
--- a/pkg/database/postgres/create_test.go
+++ b/pkg/database/postgres/create_test.go
@@ -3,7 +3,7 @@ package postgres
 import (
 	"testing"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -12,18 +12,18 @@ import (
 func Test_CreateTableStatement(t *testing.T) {
 	tests := []struct {
 		name              string
-		tableSchema       *schemasv1alpha3.SQLTableSchema
+		tableSchema       *schemasv1alpha4.SQLTableSchema
 		tableName         string
 		expectedStatement string
 	}{
 		{
 			name: "simple",
-			tableSchema: &schemasv1alpha3.SQLTableSchema{
+			tableSchema: &schemasv1alpha4.SQLTableSchema{
 				PrimaryKey: []string{
 					"id",
 				},
-				Columns: []*schemasv1alpha3.SQLTableColumn{
-					&schemasv1alpha3.SQLTableColumn{
+				Columns: []*schemasv1alpha4.SQLTableColumn{
+					&schemasv1alpha4.SQLTableColumn{
 						Name: "id",
 						Type: "integer",
 					},
@@ -34,21 +34,21 @@ func Test_CreateTableStatement(t *testing.T) {
 		},
 		{
 			name: "composite primary key",
-			tableSchema: &schemasv1alpha3.SQLTableSchema{
+			tableSchema: &schemasv1alpha4.SQLTableSchema{
 				PrimaryKey: []string{
 					"one",
 					"two",
 				},
-				Columns: []*schemasv1alpha3.SQLTableColumn{
-					&schemasv1alpha3.SQLTableColumn{
+				Columns: []*schemasv1alpha4.SQLTableColumn{
+					&schemasv1alpha4.SQLTableColumn{
 						Name: "one",
 						Type: "integer",
 					},
-					&schemasv1alpha3.SQLTableColumn{
+					&schemasv1alpha4.SQLTableColumn{
 						Name: "two",
 						Type: "integer",
 					},
-					&schemasv1alpha3.SQLTableColumn{
+					&schemasv1alpha4.SQLTableColumn{
 						Name: "three",
 						Type: "varchar(255)",
 					},
@@ -59,26 +59,26 @@ func Test_CreateTableStatement(t *testing.T) {
 		},
 		{
 			name: "composite unique index",
-			tableSchema: &schemasv1alpha3.SQLTableSchema{
+			tableSchema: &schemasv1alpha4.SQLTableSchema{
 				PrimaryKey: []string{
 					"one",
 				},
-				Indexes: []*schemasv1alpha3.SQLTableIndex{
-					&schemasv1alpha3.SQLTableIndex{
+				Indexes: []*schemasv1alpha4.SQLTableIndex{
+					&schemasv1alpha4.SQLTableIndex{
 						Columns:  []string{"two", "three"},
 						IsUnique: true,
 					},
 				},
-				Columns: []*schemasv1alpha3.SQLTableColumn{
-					&schemasv1alpha3.SQLTableColumn{
+				Columns: []*schemasv1alpha4.SQLTableColumn{
+					&schemasv1alpha4.SQLTableColumn{
 						Name: "one",
 						Type: "integer",
 					},
-					&schemasv1alpha3.SQLTableColumn{
+					&schemasv1alpha4.SQLTableColumn{
 						Name: "two",
 						Type: "integer",
 					},
-					&schemasv1alpha3.SQLTableColumn{
+					&schemasv1alpha4.SQLTableColumn{
 						Name: "three",
 						Type: "varchar(255)",
 					},
diff --git a/pkg/database/postgres/foreignkey_test.go b/pkg/database/postgres/foreignkey_test.go
index c82499c4..e5aee508 100644
--- a/pkg/database/postgres/foreignkey_test.go
+++ b/pkg/database/postgres/foreignkey_test.go
@@ -3,7 +3,7 @@ package postgres
 import (
 	"testing"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -12,17 +12,17 @@ func Test_AddForeignKeyStatement(t *testing.T) {
 	tests := []struct {
 		name              string
 		tableName         string
-		schemaForeignKey  *schemasv1alpha3.SQLTableForeignKey
+		schemaForeignKey  *schemasv1alpha4.SQLTableForeignKey
 		expectedStatement string
 	}{
 		{
 			name:      "no name, one column, no on delete",
 			tableName: "t2",
-			schemaForeignKey: &schemasv1alpha3.SQLTableForeignKey{
+			schemaForeignKey: &schemasv1alpha4.SQLTableForeignKey{
 				Columns: []string{
 					"c2",
 				},
-				References: schemasv1alpha3.SQLTableForeignKeyReferences{
+				References: schemasv1alpha4.SQLTableForeignKeyReferences{
 					Table: "t1",
 					Columns: []string{
 						"c1",
@@ -34,12 +34,12 @@ func Test_AddForeignKeyStatement(t *testing.T) {
 		{
 			name:      "named, one column, no on delete",
 			tableName: "t2",
-			schemaForeignKey: &schemasv1alpha3.SQLTableForeignKey{
+			schemaForeignKey: &schemasv1alpha4.SQLTableForeignKey{
 				Name: "hi_i_am_a_fkey",
 				Columns: []string{
 					"c2",
 				},
-				References: schemasv1alpha3.SQLTableForeignKeyReferences{
+				References: schemasv1alpha4.SQLTableForeignKeyReferences{
 					Table: "t1",
 					Columns: []string{
 						"c1",
@@ -51,12 +51,12 @@ func Test_AddForeignKeyStatement(t *testing.T) {
 		{
 			name:      "no name, two columns, no on delete",
 			tableName: "t2",
-			schemaForeignKey: &schemasv1alpha3.SQLTableForeignKey{
+			schemaForeignKey: &schemasv1alpha4.SQLTableForeignKey{
 				Columns: []string{
 					"c2",
 					"c22",
 				},
-				References: schemasv1alpha3.SQLTableForeignKeyReferences{
+				References: schemasv1alpha4.SQLTableForeignKeyReferences{
 					Table: "t1",
 					Columns: []string{
 						"c1",
@@ -69,12 +69,12 @@ func Test_AddForeignKeyStatement(t *testing.T) {
 		{
 			name:      "no name, one column, on delete cascade",
 			tableName: "t2",
-			schemaForeignKey: &schemasv1alpha3.SQLTableForeignKey{
+			schemaForeignKey: &schemasv1alpha4.SQLTableForeignKey{
 				OnDelete: "cascade",
 				Columns: []string{
 					"c2",
 				},
-				References: schemasv1alpha3.SQLTableForeignKeyReferences{
+				References: schemasv1alpha4.SQLTableForeignKeyReferences{
 					Table: "t1",
 					Columns: []string{
 						"c1",
diff --git a/pkg/database/postgres/index_test.go b/pkg/database/postgres/index_test.go
index 684eeae9..2e223d95 100644
--- a/pkg/database/postgres/index_test.go
+++ b/pkg/database/postgres/index_test.go
@@ -3,7 +3,7 @@ package postgres
 import (
 	"testing"
 
-	schemasv1alpha3 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -12,13 +12,13 @@ func Test_AddIndexStatement(t *testing.T) {
 	tests := []struct {
 		name              string
 		tableName         string
-		schemaIndex       *schemasv1alpha3.SQLTableIndex
+		schemaIndex       *schemasv1alpha4.SQLTableIndex
 		expectedStatement string
 	}{
 		{
 			name:      "no name, one column, not specified unique",
 			tableName: "t2",
-			schemaIndex: &schemasv1alpha3.SQLTableIndex{
+			schemaIndex: &schemasv1alpha4.SQLTableIndex{
 				Columns: []string{
 					"c1",
 				},
@@ -28,7 +28,7 @@ func Test_AddIndexStatement(t *testing.T) {
 		{
 			name:      "specified name, one column, not specified unique",
 			tableName: "t2",
-			schemaIndex: &schemasv1alpha3.SQLTableIndex{
+			schemaIndex: &schemasv1alpha4.SQLTableIndex{
 				Columns: []string{
 					"c1",
 				},
@@ -39,7 +39,7 @@ func Test_AddIndexStatement(t *testing.T) {
 		{
 			name:      "no name, two columns, not specified unique",
 			tableName: "t2",
-			schemaIndex: &schemasv1alpha3.SQLTableIndex{
+			schemaIndex: &schemasv1alpha4.SQLTableIndex{
 				Columns: []string{
 					"c1",
 					"c2",
@@ -50,7 +50,7 @@ func Test_AddIndexStatement(t *testing.T) {
 		{
 			name:      "np name, one column, unique",
 			tableName: "t2",
-			schemaIndex: &schemasv1alpha3.SQLTableIndex{
+			schemaIndex: &schemasv1alpha4.SQLTableIndex{
 				Columns: []string{
 					"c1",
 				},
diff --git a/pkg/generate/generate_test.go b/pkg/generate/generate_test.go
index b4cf94af..94e24786 100644
--- a/pkg/generate/generate_test.go
+++ b/pkg/generate/generate_test.go
@@ -52,7 +52,7 @@ func Test_writeTableFile(t *testing.T) {
 					DataType: "integer",
 				},
 			},
-			expectedYAML: `apiVersion: schemas.schemahero.io/v1alpha3
+			expectedYAML: `apiVersion: schemas.schemahero.io/v1alpha4
 kind: Table
 metadata:
   name: simple
@@ -93,7 +93,7 @@ spec:
 					DataType: "integer",
 				},
 			},
-			expectedYAML: `apiVersion: schemas.schemahero.io/v1alpha3
+			expectedYAML: `apiVersion: schemas.schemahero.io/v1alpha4
 kind: Table
 metadata:
   name: withfk
@@ -146,7 +146,7 @@ spec:
 					},
 				},
 			},
-			expectedYAML: `apiVersion: schemas.schemahero.io/v1alpha3
+			expectedYAML: `apiVersion: schemas.schemahero.io/v1alpha4
 kind: Table
 metadata:
   name: simple