Repository: schemahero/schemahero
Pull request #790 (2022-09-21 23:38:28)

sqlite improvements


Patch:
diff --git a/OWNERS.md b/OWNERS.md
index 51f1af13..de78bd5e 100644
--- a/OWNERS.md
+++ b/OWNERS.md
@@ -8,7 +8,4 @@
 - Dmitriy Ivolgin (@divolgin, Replicated)
 - Ethan Mosbaugh (@emosbaugh, Replicated)
 - Andrew Lavery (@laverya, Replicated)
-- Dan Stough (@danstough, Replicated)
 - Salah Aldeen Al Saleh (@sgalsaleh, Replicated)
-- Treva Nichole Williams (@OGtrilliams, Replicated)
-- Sean Rester (@seanrester, Replicated)
diff --git a/go.mod b/go.mod
index 467953f5..73f8b361 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
 	github.com/go-sql-driver/mysql v1.6.0
 	github.com/gocql/gocql v0.0.0-20200815110948-5378c8f664e9
 	github.com/jackc/pgx/v4 v4.14.1
-	github.com/mattn/go-sqlite3 v2.0.3+incompatible
+	github.com/mattn/go-sqlite3 v1.14.15
 	github.com/onsi/gomega v1.17.0
 	github.com/pkg/errors v0.9.1
 	github.com/rqlite/gorqlite v0.0.0-20220917155659-fa5ab9eb60ec
diff --git a/go.sum b/go.sum
index 18ae5079..db749f3e 100644
--- a/go.sum
+++ b/go.sum
@@ -525,8 +525,8 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
 github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
-github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
+github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
diff --git a/pkg/apis/schemas/v1alpha4/sqlite.go b/pkg/apis/schemas/v1alpha4/sqlite.go
index 7de7d72d..739f80de 100644
--- a/pkg/apis/schemas/v1alpha4/sqlite.go
+++ b/pkg/apis/schemas/v1alpha4/sqlite.go
@@ -57,4 +57,5 @@ type SqliteTableSchema struct {
 	Indexes     []*SqliteTableIndex      `json:"indexes,omitempty" yaml:"indexes,omitempty"`
 	Columns     []*SqliteTableColumn     `json:"columns,omitempty" yaml:"columns,omitempty"`
 	IsDeleted   bool                     `json:"isDeleted,omitempty" yaml:"isDeleted,omitempty"`
+	Strict      bool                     `json:"strict,omitempty" yaml:"strict,omitempty"`
 }
diff --git a/pkg/database/database.go b/pkg/database/database.go
index 5d58812c..cd9de9b6 100644
--- a/pkg/database/database.go
+++ b/pkg/database/database.go
@@ -104,6 +104,17 @@ func (d *Database) CreateFixturesSync() error {
 				return err
 			}
 
+			statements = append(statements, createStatements...)
+		} else if d.Driver == "sqlite" {
+			if spec.Schema.SQLite == nil {
+				return nil
+			}
+
+			createStatements, err := sqlite.CreateTableStatements(spec.Name, spec.Schema.SQLite)
+			if err != nil {
+				return err
+			}
+
 			statements = append(statements, createStatements...)
 		} else if d.Driver == "rqlite" {
 			if spec.Schema.RQLite == nil {
diff --git a/pkg/database/sqlite/alias.go b/pkg/database/sqlite/alias.go
deleted file mode 100644
index dbb67071..00000000
--- a/pkg/database/sqlite/alias.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package sqlite
-
-import (
-	"fmt"
-	"regexp"
-	"strings"
-)
-
-func unaliasUnparameterizedColumnType(requestedType string) string {
-	switch requestedType {
-	case "bool":
-		return "tinyint (1)"
-	case "boolean":
-		return "tinyint (1)"
-	}
-
-	// mysql gives us the length of these text types, but it won't
-	// accept them as create table arguments...
-	if requestedType == "text (65535)" {
-		requestedType = "text"
-	} else if requestedType == "tinytext (255)" {
-		requestedType = "tinytext"
-	} else if requestedType == "mediumtext (16777215)" {
-		requestedType = "mediumtext"
-	} else if requestedType == "longtext (4294967295)" {
-		requestedType = "longtext"
-	} else if requestedType == "blob (65535)" {
-		requestedType = "blob"
-	} else if requestedType == "tinyblob (255)" {
-		requestedType = "tinyblob"
-	} else if requestedType == "mediumblob (16777215)" {
-		requestedType = "mediumblob"
-	} else if requestedType == "longblob (4294967295)" {
-		requestedType = "longblob"
-	}
-
-	for _, unparameterizedColumnType := range unparameterizedColumnTypes {
-		if unparameterizedColumnType == requestedType {
-			return requestedType
-		}
-	}
-
-	return ""
-}
-
-func unaliasParameterizedColumnType(requestedType string) string {
-	if strings.HasPrefix(requestedType, "char ") || strings.HasPrefix(requestedType, "char(") ||
-		requestedType == "character" || requestedType == "char" {
-		r := regexp.MustCompile(`char\s*\((?P<len>\d*)\)`)
-
-		matchGroups := r.FindStringSubmatch(requestedType)
-		if len(matchGroups) == 0 {
-			return "character"
-		}
-
-		return fmt.Sprintf("character (%s)", matchGroups[1])
-	}
-	if strings.HasPrefix(requestedType, "integer") {
-		r := regexp.MustCompile(`integer\s*\(\s*(?P<max>\d*)\s*\)`)
-
-		matchGroups := r.FindStringSubmatch(requestedType)
-		if len(matchGroups) == 0 {
-			return "int (11)"
-		}
-
-		return fmt.Sprintf("int (%s)", matchGroups[1])
-	}
-	if strings.HasPrefix(requestedType, "dec") {
-		if strings.Contains(requestedType, "decimal") {
-			requestedType = strings.Replace(requestedType, "decimal", "dec", -1)
-		}
-
-		precisionAndScale := regexp.MustCompile(`dec\s*\(\s*(?P<precision>\d*),\s*(?P<scale>\d*)\s*\)`)
-		precisionOnly := regexp.MustCompile(`dec\s*\(\s*(?P<precision>\d*)\s*\)`)
-
-		precisionAndScaleMatchGroups := precisionAndScale.FindStringSubmatch(requestedType)
-		precisionOnlyMatchGroups := precisionOnly.FindStringSubmatch(requestedType)
-
-		if len(precisionAndScaleMatchGroups) == 3 {
-			return fmt.Sprintf("decimal (%s, %s)", precisionAndScaleMatchGroups[1], precisionAndScaleMatchGroups[2])
-		} else if len(precisionOnlyMatchGroups) == 2 {
-			return fmt.Sprintf("decimal (%s, 0)", precisionOnlyMatchGroups[1])
-		}
-
-		return "decimal (10, 0)"
-	}
-	if strings.HasPrefix(requestedType, "double precision") {
-		precisionAndScale := regexp.MustCompile(`double precision\s*\(\s*(?P<precision>\d*),\s*(?P<scale>\d*)\s*\)`)
-		precisionAndScaleMatchGroups := precisionAndScale.FindStringSubmatch(requestedType)
-
-		if len(precisionAndScaleMatchGroups) == 3 {
-			return fmt.Sprintf("double (%s, %s)", precisionAndScaleMatchGroups[1], precisionAndScaleMatchGroups[2])
-		}
-
-		return "double"
-	}
-
-	return ""
-}
diff --git a/pkg/database/sqlite/alter.go b/pkg/database/sqlite/alter.go
index 2e76e7f2..9a8b3765 100644
--- a/pkg/database/sqlite/alter.go
+++ b/pkg/database/sqlite/alter.go
@@ -25,7 +25,7 @@ func RecreateTableStatements(tableName string, sqliteTableSchema *schemasv1alpha
 
 	tempTableName := fmt.Sprintf("%s_%x", tableName, sum)
 
-	statements = append(statements, fmt.Sprintf("alter table `%s` rename to `%s`", tableName, tempTableName))
+	statements = append(statements, fmt.Sprintf(`alter table "%s" rename to "%s"`, tableName, tempTableName))
 
 	createTableStatement, err := CreateTableStatements(tableName, sqliteTableSchema)
 	if err != nil {
@@ -47,12 +47,48 @@ func RecreateTableStatements(tableName string, sqliteTableSchema *schemasv1alpha
 	return statements, nil
 }
 
-func columnsMatch(col1 types.Column, col2 types.Column) bool {
-	if col1.DataType != col2.DataType {
-		return false
+func BuildAlterIndexStatements(r *SqliteConnection, tableName string, sqliteTableSchema *schemasv1alpha4.SqliteTableSchema) ([]string, error) {
+	indexStatements := []string{}
+
+	currentIndexes, err := r.ListTableIndexes("", tableName)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to list table indexes")
+	}
+
+desiredIndexesLoop:
+	for _, desiredIndex := range sqliteTableSchema.Indexes {
+		if desiredIndex.Name == "" {
+			desiredIndex.Name = types.GenerateSqliteIndexName(tableName, desiredIndex)
+		}
+		for _, currentIndex := range currentIndexes {
+			if currentIndex.Equals(types.SqliteSchemaIndexToIndex(desiredIndex)) {
+				continue desiredIndexesLoop
+			}
+			if currentIndex.Name == desiredIndex.Name {
+				// we already checked if the index is a constraint when detecting if the table needs to be recreated, so we don't need to check again
+				indexStatements = append(indexStatements, RemoveIndexStatement(tableName, currentIndex))
+			}
+		}
+		indexStatements = append(indexStatements, AddIndexStatement(tableName, desiredIndex))
 	}
 
-	if col1.Charset != col2.Charset {
+currentIndexesLoop:
+	for _, currentIndex := range currentIndexes {
+		for _, desiredIndex := range sqliteTableSchema.Indexes {
+			if currentIndex.Name == desiredIndex.Name {
+				// if index changed, we already handled it above
+				continue currentIndexesLoop
+			}
+		}
+		// we already checked if the index is a constraint when detecting if the table needs to be recreated, so we don't need to check again
+		indexStatements = append(indexStatements, RemoveIndexStatement(tableName, currentIndex))
+	}
+
+	return indexStatements, nil
+}
+
+func columnsMatch(col1 types.Column, col2 types.Column) bool {
+	if !strings.EqualFold(col1.DataType, col2.DataType) {
 		return false
 	}
 
diff --git a/pkg/database/sqlite/alter_statements.go b/pkg/database/sqlite/alter_statements.go
deleted file mode 100644
index 319919cd..00000000
--- a/pkg/database/sqlite/alter_statements.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package sqlite
-
-import (
-	"fmt"
-	"strings"
-
-	"github.com/schemahero/schemahero/pkg/database/types"
-)
-
-type AlterModifyColumnStatement struct {
-	TableName      string
-	ExistingColumn types.Column
-	Column         types.Column
-}
-
-func (s AlterModifyColumnStatement) DDL() []string {
-	isAddingNotNull := false
-	if s.Column.Constraints != nil && s.Column.Constraints.NotNull != nil && *s.Column.Constraints.NotNull {
-		if s.ExistingColumn.Constraints == nil {
-			isAddingNotNull = true
-		} else if s.ExistingColumn.Constraints.NotNull == nil {
-			isAddingNotNull = true
-		} else if !*s.ExistingColumn.Constraints.NotNull {
-			isAddingNotNull = true
-		}
-	}
-
-	if isAddingNotNull {
-		return s.ddlWithNotNull()
-	}
-
-	// we will go ahead and apply the new constraints here because mysql is pretty flexible about
-	// letting you alter a table adding a not null constraint without applying a default
-	// so this will be familiar behavior for mysql users
-	return s.ddl(false)
-}
-
-// ddlIgnoringNotNull will NOT change the "nullability" of the colume
-func (s AlterModifyColumnStatement) ddl(useConstraintsFromExistingColumn bool) []string {
-	stmts := []string{
-		fmt.Sprintf("alter table `%s` rename column `%s` to %s", s.TableName, s.ExistingColumn.Name, s.Column.Name),
-	}
-
-	return []string{strings.Join(stmts, " ")}
-}
-
-func (s AlterModifyColumnStatement) ddlWithNotNull() []string {
-	// 1. update to add the default
-	// 2. update existing values with the default
-	// 3. update to set not null
-
-	statements := []string{}
-
-	// set the default (and change any types as necessary)
-	if s.Column.ColumnDefault != nil {
-		if s.ExistingColumn.ColumnDefault == nil || *s.ExistingColumn.ColumnDefault != *s.Column.ColumnDefault {
-			statements = append(statements, s.ddl(true)...)
-		}
-	}
-
-	// update existing values
-	if s.Column.ColumnDefault != nil {
-		localStatement := fmt.Sprintf("update `%s` set `%s`=%q where `%s` is null",
-			s.TableName,
-			s.Column.Name,
-			*s.Column.ColumnDefault,
-			s.Column.Name)
-		statements = append(statements, localStatement)
-	}
-
-	// update including the not null
-	statements = append(statements, s.ddl(false)...)
-
-	return statements
-}
-
-type AlterDropColumnStatement struct {
-	TableName string
-	Column    types.Column
-}
-
-func (s AlterDropColumnStatement) DDL() []string {
-	return []string{
-		fmt.Sprintf("alter table `%s` drop column `%s`", s.TableName, s.Column.Name),
-	}
-}
-
-type AlterRemoveConstrantStatement struct {
-	TableName  string
-	Constraint types.KeyConstraint
-}
-
-func (s AlterRemoveConstrantStatement) String() string {
-	if s.Constraint.IsPrimary {
-		return fmt.Sprintf("alter table `%s` drop primary key", s.TableName)
-	}
-	return fmt.Sprintf("alter table `%s` drop index `%s`", s.TableName, s.Constraint.Name)
-}
-
-type AlterAddConstrantStatement struct {
-	TableName  string
-	Constraint types.KeyConstraint
-}
-
-func (s AlterAddConstrantStatement) String() string {
-	stmts := []string{
-		fmt.Sprintf("alter table `%s` add constraint `%s`", s.TableName, s.Constraint.GenerateName(s.TableName)),
-	}
-	if s.Constraint.IsPrimary {
-		stmts = append(stmts, "primary key")
-	}
-	stmts = append(stmts, fmt.Sprintf("(`%s`)", strings.Join(s.Constraint.Columns, "`, `")))
-	return strings.Join(stmts, " ")
-}
diff --git a/pkg/database/sqlite/column.go b/pkg/database/sqlite/column.go
index 0275d643..b61a9a0b 100644
--- a/pkg/database/sqlite/column.go
+++ b/pkg/database/sqlite/column.go
@@ -10,6 +10,7 @@ import (
 func schemaColumnToColumn(schemaColumn *schemasv1alpha4.SqliteTableColumn) (*types.Column, error) {
 	column := &types.Column{
 		Name:          schemaColumn.Name,
+		DataType:      schemaColumn.Type,
 		ColumnDefault: schemaColumn.Default,
 	}
 
@@ -25,33 +26,7 @@ func schemaColumnToColumn(schemaColumn *schemasv1alpha4.SqliteTableColumn) (*typ
 		}
 	}
 
-	requestedType := schemaColumn.Type
-	unaliasedColumnType := unaliasUnparameterizedColumnType(requestedType)
-	if unaliasedColumnType != "" {
-		requestedType = unaliasedColumnType
-	}
-
-	unaliasedColumnType = unaliasParameterizedColumnType(requestedType)
-	if unaliasedColumnType != "" {
-		requestedType = unaliasedColumnType
-	}
-
-	if !isParameterizedColumnType(requestedType) {
-		column.DataType = requestedType
-		return column, nil
-	}
-
-	columnType, err := maybeParseParameterizedColumnType(requestedType)
-	if err != nil {
-		return nil, err
-	}
-
-	if columnType != "" {
-		column.DataType = columnType
-		return column, nil
-	}
-
-	return nil, fmt.Errorf("unknown column type. cannot validate column type %q", schemaColumn.Type)
+	return column, nil
 }
 
 func sqliteColumnAsInsert(column *schemasv1alpha4.SqliteTableColumn) (string, error) {
@@ -60,11 +35,8 @@ func sqliteColumnAsInsert(column *schemasv1alpha4.SqliteTableColumn) (string, er
 		return "", err
 	}
 
-	formatted := fmt.Sprintf("`%s` %s", column.Name, sqliteColumn.DataType)
+	formatted := fmt.Sprintf(`"%s" %s`, column.Name, sqliteColumn.DataType)
 
-	if sqliteColumn.Charset != "" {
-		formatted = fmt.Sprintf("%s character set %s", formatted, sqliteColumn.Charset)
-	}
 	if sqliteColumn.Collation != "" {
 		formatted = fmt.Sprintf("%s collate %s", formatted, sqliteColumn.Collation)
 	}
@@ -78,23 +50,11 @@ func sqliteColumnAsInsert(column *schemasv1alpha4.SqliteTableColumn) (string, er
 	}
 
 	if sqliteColumn.Attributes != nil && sqliteColumn.Attributes.AutoIncrement != nil && *sqliteColumn.Attributes.AutoIncrement {
-		formatted = fmt.Sprintf("%s auto_increment", formatted)
+		formatted = fmt.Sprintf("%s autoincrement", formatted)
 	}
 
 	if sqliteColumn.ColumnDefault != nil {
-		quoteDefaultValue := true
-
-		if sqliteColumn.DataType == "datetime" || sqliteColumn.DataType == "timestamp" {
-			if *sqliteColumn.ColumnDefault == "CURRENT_TIMESTAMP" {
-				quoteDefaultValue = false
-			}
-		}
-
-		if quoteDefaultValue {
-			formatted = fmt.Sprintf("%s default '%s'", formatted, *sqliteColumn.ColumnDefault)
-		} else {
-			formatted = fmt.Sprintf("%s default %s", formatted, *sqliteColumn.ColumnDefault)
-		}
+		formatted = fmt.Sprintf("%s default '%s'", formatted, *sqliteColumn.ColumnDefault)
 	}
 
 	return formatted, nil
@@ -106,7 +66,12 @@ func InsertColumnStatement(tableName string, desiredColumn *schemasv1alpha4.Sqli
 		return "", err
 	}
 
-	statement := fmt.Sprintf("alter table `%s` add column %s", tableName, columnFields)
+	statement := fmt.Sprintf(`alter table "%s" add column %s`, tableName, columnFields)
+
+	return statement, nil
+}
 
+func DropColumnStatement(tableName string, existingColumn types.Column) (string, error) {
+	statement := fmt.Sprintf(`alter table "%s" drop column "%s"`, tableName, existingColumn.Name)
 	return statement, nil
 }
diff --git a/pkg/database/sqlite/create.go b/pkg/database/sqlite/create.go
index 1917a1dc..5eeb0abf 100644
--- a/pkg/database/sqlite/create.go
+++ b/pkg/database/sqlite/create.go
@@ -43,7 +43,7 @@ func CreateTableStatements(tableName string, tableSchema *schemasv1alpha4.Sqlite
 	if len(tableSchema.PrimaryKey) > 0 {
 		primaryKeyColumns := []string{}
 		for _, primaryKeyColumn := range tableSchema.PrimaryKey {
-			primaryKeyColumns = append(primaryKeyColumns, fmt.Sprintf("`%s`", primaryKeyColumn))
+			primaryKeyColumns = append(primaryKeyColumns, fmt.Sprintf(`"%s"`, primaryKeyColumn))
 		}
 
 		columns = append(columns, fmt.Sprintf("primary key (%s)", strings.Join(primaryKeyColumns, ", ")))
@@ -53,10 +53,17 @@ func CreateTableStatements(tableName string, tableSchema *schemasv1alpha4.Sqlite
 		for _, foreignKey := range tableSchema.ForeignKeys {
 			columns = append(columns, foreignKeyConstraintClause(tableName, foreignKey))
 		}
+	}
 
+	query := fmt.Sprintf(`create table "%s" (%s)`, tableName, strings.Join(columns, ", "))
+	if tableSchema.Strict {
+		query = fmt.Sprintf("%s strict", query)
 	}
 
-	query := fmt.Sprintf("create table `%s` (%s)", tableName, strings.Join(columns, ", "))
+	statements := []string{query}
+	for _, index := range tableSchema.Indexes {
+		statements = append(statements, AddIndexStatement(tableName, index))
+	}
 
-	return []string{query}, nil
+	return statements, nil
 }
diff --git a/pkg/database/sqlite/deploy.go b/pkg/database/sqlite/deploy.go
index 6cda90a3..582a5b13 100644
--- a/pkg/database/sqlite/deploy.go
+++ b/pkg/database/sqlite/deploy.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"database/sql"
 	"fmt"
+	"strings"
 
 	"github.com/pkg/errors"
 	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
@@ -27,7 +28,7 @@ func PlanSqliteTable(dsn string, tableName string, sqliteTableSchema *schemasv1a
 		return []string{}, nil
 	} else if tableExists > 0 && sqliteTableSchema.IsDeleted {
 		return []string{
-			fmt.Sprintf("drop table `%s`", tableName),
+			fmt.Sprintf(`drop table "%s"`, tableName),
 		}, nil
 	}
 
@@ -49,44 +50,16 @@ func PlanSqliteTable(dsn string, tableName string, sqliteTableSchema *schemasv1a
 		return append(queries, seedDataStatements...), nil
 	}
 
-	statements := []string{}
-
-	// table needs to be altered?
-	columnStatements, err := buildColumnStatements(s, tableName, sqliteTableSchema)
+	statements, err := buildStatements(s, tableName, sqliteTableSchema)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to build column statements")
 	}
-	statements = append(statements, columnStatements...)
-
-	// primary key changes
-	// primaryKeyStatements, err := buildPrimaryKeyStatements(m, tableName, mysqlTableSchema)
-	// if err != nil {
-	// 	return nil, errors.Wrap(err, "failed to build primary key statements")
-	// }
-	// statements = append(statements, primaryKeyStatements...)
-
-	// // foreign key changes
-	// foreignKeyStatements, err := buildForeignKeyStatements(m, tableName, mysqlTableSchema)
-	// if err != nil {
-	// 	return nil, errors.Wrap(err, "failed to build foreign key statements")
-	// }
-	// statements = append(statements, foreignKeyStatements...)
-
-	// // index changes
-	// indexStatements, err := buildIndexStatements(m, tableName, mysqlTableSchema)
-	// if err != nil {
-	// 	return nil, errors.Wrap(err, "failed to build index statements")
-	// }
-	// statements = append(statements, indexStatements...)
-
 	statements = append(statements, seedDataStatements...)
 
 	return statements, nil
 }
 
-func buildColumnStatements(s *SqliteConnection, tableName string, sqliteTableSchema *schemasv1alpha4.SqliteTableSchema) ([]string, error) {
-	tableNeedsRecreate := false
-
+func buildStatements(s *SqliteConnection, tableName string, sqliteTableSchema *schemasv1alpha4.SqliteTableSchema) ([]string, error) {
 	query := `SELECT
 p.name AS col_name,
 p.type AS col_type,
@@ -97,14 +70,16 @@ FROM sqlite_master m
 LEFT OUTER JOIN pragma_table_info((m.name)) p
 WHERE m.type = 'table'
 AND m.name = ?`
+
 	rows, err := s.db.Query(query, tableName)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to query from sqlite_master")
 	}
 	defer rows.Close()
 
-	alterAndDropStatements := []string{}
-	foundColumnNames := []string{}
+	statements := []string{}
+	existingColumns := []types.Column{}
+
 	for rows.Next() {
 		var columnName, dataType string
 		var columnDefault sql.NullString
@@ -114,15 +89,6 @@ AND m.name = ?`
 			return nil, errors.Wrap(err, "failed to scan")
 		}
 
-		foundColumnNames = append(foundColumnNames, columnName)
-
-		if isParameterizedColumnType(dataType) {
-			dataType, err = maybeParseParameterizedColumnType(dataType)
-			if err != nil {
-				return nil, errors.Wrap(err, "failed to parse parameterized column type")
-			}
-		}
-
 		existingColumn := types.Column{
 			Name:        columnName,
 			DataType:    dataType,
@@ -137,58 +103,184 @@ AND m.name = ?`
 		}
 
 		if columnDefault.Valid {
-			existingColumn.ColumnDefault = &columnDefault.String
+			// sqlite stores quotes as well, strip them.
+			// e.g. 'sometext' is stored as 'sometext' instead of sometext.
+			v := columnDefault.String
+			if len(v) > 0 && v[0] == '"' {
+				v = strings.Trim(v, `"`)
+			} else if len(v) > 0 && v[0] == '\'' {
+				v = strings.Trim(v, `'`)
+			} else if len(v) > 0 && v[0] == '`' {
+				v = strings.Trim(v, "`")
+			}
+			existingColumn.ColumnDefault = &v
+		}
+
+		existingColumns = append(existingColumns, existingColumn)
+	}
+
+	tableNeedsRecreate, err := checkTableNeedsRecreate(s, tableName, sqliteTableSchema, existingColumns)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to check if table needs recreate")
+	}
+
+	if tableNeedsRecreate {
+		hardWayStatements, err := RecreateTableStatements(tableName, sqliteTableSchema)
+		if err != nil {
+			return nil, errors.Wrap(err, "failed to create recreate table statements")
 		}
 
-		// if the column exists in the desired also, we need to recreate if it different
-		needsRecreate := false
+		statements = append(statements, hardWayStatements...)
+	} else {
+		// add new columns
 		for _, desiredColumn := range sqliteTableSchema.Columns {
-			if desiredColumn.Name == existingColumn.Name {
-				colA, err := schemaColumnToColumn(desiredColumn)
-				if err != nil {
-					return nil, errors.Wrap(err, "failed to convert desired column")
+			isColumnPresent := false
+			for _, existingColumn := range existingColumns {
+				if existingColumn.Name == desiredColumn.Name {
+					isColumnPresent = true
+					break
 				}
-				if !columnsMatch(*colA, existingColumn) {
-					needsRecreate = true
+			}
+			if !isColumnPresent {
+				statement, err := InsertColumnStatement(tableName, desiredColumn)
+				if err != nil {
+					return nil, errors.Wrap(err, "failed to create insert column statement")
 				}
+
+				statements = append(statements, statement)
 			}
 		}
 
-		if needsRecreate {
-			tableNeedsRecreate = true
+		// if there are unique indexes, they'll have to be removed before dropping their columns
+		indexStatements, err := BuildAlterIndexStatements(s, tableName, sqliteTableSchema)
+		if err != nil {
+			return nil, errors.Wrap(err, "failed to build alter index statements")
 		}
-	}
+		statements = append(statements, indexStatements...)
 
-	if !tableNeedsRecreate {
-		for _, desiredColumn := range sqliteTableSchema.Columns {
+		// drop removed columns
+		for _, existingColumn := range existingColumns {
 			isColumnPresent := false
-			for _, foundColumn := range foundColumnNames {
-				if foundColumn == desiredColumn.Name {
+			for _, desiredColumn := range sqliteTableSchema.Columns {
+				if existingColumn.Name == desiredColumn.Name {
 					isColumnPresent = true
+					break
 				}
 			}
-
 			if !isColumnPresent {
-				statement, err := InsertColumnStatement(tableName, desiredColumn)
+				statement, err := DropColumnStatement(tableName, existingColumn)
 				if err != nil {
-					return nil, errors.Wrap(err, "failed to create insert column statement")
+					return nil, errors.Wrap(err, "failed to create drop column statement")
 				}
 
-				alterAndDropStatements = append(alterAndDropStatements, statement)
+				statements = append(statements, statement)
 			}
 		}
 	}
 
-	if tableNeedsRecreate {
-		hardWayStatements, err := RecreateTableStatements(tableName, sqliteTableSchema)
-		if err != nil {
-			return nil, errors.Wrap(err, "failed to create recreate table statements")
+	return statements, nil
+}
+
+func checkTableNeedsRecreate(s *SqliteConnection, tableName string, sqliteTableSchema *schemasv1alpha4.SqliteTableSchema, existingColumns []types.Column) (bool, error) {
+	// check if primary keys match
+	existingPrimaryKey, err := s.GetTablePrimaryKeyColumns(tableName)
+	if err != nil {
+		return false, errors.Wrap(err, "failed to get table primary key")
+	}
+	if len(existingPrimaryKey) != len(sqliteTableSchema.PrimaryKey) {
+		return true, nil
+	}
+nextPrimaryKeyColumn:
+	for _, desiredColumn := range sqliteTableSchema.PrimaryKey {
+		for _, existingColumn := range existingPrimaryKey {
+			if existingColumn == desiredColumn {
+				continue nextPrimaryKeyColumn
+			}
 		}
+		return true, nil
+	}
 
-		alterAndDropStatements = append(alterAndDropStatements, hardWayStatements...)
+	// check if foreign keys match
+	existingForeignKeys, err := s.ListTableForeignKeys("", tableName)
+	if err != nil {
+		return false, errors.Wrap(err, "failed to list table foreign keys")
+	}
+	if len(existingForeignKeys) != len(sqliteTableSchema.ForeignKeys) {
+		return true, nil
+	}
+nextForeignKey:
+	for _, desiredForeignKey := range sqliteTableSchema.ForeignKeys {
+		for _, existingForeignKey := range existingForeignKeys {
+			if existingForeignKey.Equals(types.SqliteSchemaForeignKeyToForeignKey(desiredForeignKey)) {
+				continue nextForeignKey
+			}
+		}
+		return true, nil
+	}
+
+	// check if columns were modified (ok if added or removed)
+	for _, existingColumn := range existingColumns {
+		for _, desiredColumn := range sqliteTableSchema.Columns {
+			if existingColumn.Name == desiredColumn.Name {
+				col, err := schemaColumnToColumn(desiredColumn)
+				if err != nil {
+					return false, errors.Wrap(err, "failed to convert desired column")
+				}
+				if !columnsMatch(*col, existingColumn) {
+					return true, nil
+				}
+				break
+			}
+		}
 	}
 
-	return alterAndDropStatements, nil
+	currentIndexes, err := s.ListTableIndexes("", tableName)
+	if err != nil {
+		return false, errors.Wrap(err, "failed to list table indexes")
+	}
+
+desiredIndexesLoop:
+	for _, desiredIndex := range sqliteTableSchema.Indexes {
+		if desiredIndex.Name == "" {
+			desiredIndex.Name = types.GenerateSqliteIndexName(tableName, desiredIndex)
+		}
+		for _, currentIndex := range currentIndexes {
+			if currentIndex.Equals(types.SqliteSchemaIndexToIndex(desiredIndex)) {
+				continue desiredIndexesLoop
+			}
+			if currentIndex.Name == desiredIndex.Name {
+				// index already exists but it changed, check if it's a constraint
+				isConstraint, err := s.IndexIsAConstraint(tableName, currentIndex.Name)
+				if err != nil {
+					return false, errors.Wrap(err, "failed to check if index is a constraint")
+				}
+				if isConstraint {
+					// sqlite doesn't support dropping/adding constraints, so we have to recreate the table
+					return true, nil
+				}
+			}
+		}
+	}
+
+currentIndexesLoop:
+	for _, currentIndex := range currentIndexes {
+		for _, desiredIndex := range sqliteTableSchema.Indexes {
+			if desiredIndex.Name == currentIndex.Name {
+				// if index changed, we already checked if it's a constraint above
+				continue currentIndexesLoop
+			}
+		}
+		isConstraint, err := s.IndexIsAConstraint(tableName, currentIndex.Name)
+		if err != nil {
+			return false, errors.Wrap(err, "failed to check if index is a constraint")
+		}
+		if isConstraint {
+			// sqlite doesn't support dropping/adding constraints, so we have to recreate the table
+			return true, nil
+		}
+	}
+
+	return false, nil
 }
 
 func DeploySqliteStatements(dsn string, statements []string) error {
@@ -200,7 +292,7 @@ func DeploySqliteStatements(dsn string, statements []string) error {
 
 	// execute
 	if err := executeStatements(s, statements); err != nil {
-		return err
+		return errors.Wrap(err, "failed to execute statements")
 	}
 
 	return nil
@@ -211,9 +303,9 @@ func executeStatements(s *SqliteConnection, statements []string) error {
 		if statement == "" {
 			continue
 		}
-		fmt.Printf("Executing query %q\n", statement)
+		fmt.Printf("Executing query %s\n", statement)
 		if _, err := s.db.ExecContext(context.Background(), statement); err != nil {
-			return err
+			return errors.Wrap(err, "failed to execute")
 		}
 	}
 
diff --git a/pkg/database/sqlite/foreignkey.go b/pkg/database/sqlite/foreignkey.go
index a3f7b5f2..d504982e 100644
--- a/pkg/database/sqlite/foreignkey.go
+++ b/pkg/database/sqlite/foreignkey.go
@@ -8,14 +8,6 @@ import (
 	"github.com/schemahero/schemahero/pkg/database/types"
 )
 
-func RemoveForeignKeyStatement(tableName string, foreignKey *types.ForeignKey) string {
-	return fmt.Sprintf("alter table %s drop constraint %s", tableName, foreignKey.Name)
-}
-
-func AddForeignKeyStatement(tableName string, schemaForeignKey *schemasv1alpha4.SqliteTableForeignKey) string {
-	return fmt.Sprintf("alter table %s add %s", tableName, foreignKeyConstraintClause(tableName, schemaForeignKey))
-}
-
 func foreignKeyConstraintClause(tableName string, schemaForeignKey *schemasv1alpha4.SqliteTableForeignKey) string {
 	onDelete := ""
 	if schemaForeignKey.OnDelete != "" {
diff --git a/pkg/database/sqlite/index.go b/pkg/database/sqlite/index.go
new file mode 100644
index 00000000..07b5d42d
--- /dev/null
+++ b/pkg/database/sqlite/index.go
@@ -0,0 +1,34 @@
+package sqlite
+
+import (
+	"fmt"
+	"strings"
+
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	"github.com/schemahero/schemahero/pkg/database/types"
+)
+
+func RemoveIndexStatement(tableName string, index *types.Index) string {
+	if index.IsUnique {
+		return fmt.Sprintf("drop index if exists %s", index.Name)
+	}
+	return fmt.Sprintf("drop index %s", index.Name)
+}
+
+func AddIndexStatement(tableName string, schemaIndex *schemasv1alpha4.SqliteTableIndex) string {
+	unique := ""
+	if schemaIndex.IsUnique {
+		unique = "unique "
+	}
+
+	name := schemaIndex.Name
+	if name == "" {
+		name = types.GenerateSqliteIndexName(tableName, schemaIndex)
+	}
+
+	return fmt.Sprintf("create %sindex %s on %s (%s)",
+		unique,
+		name,
+		tableName,
+		strings.Join(schemaIndex.Columns, ", "))
+}
diff --git a/pkg/database/sqlite/parameterized.go b/pkg/database/sqlite/parameterized.go
deleted file mode 100644
index c4b3b4bd..00000000
--- a/pkg/database/sqlite/parameterized.go
+++ /dev/null
@@ -1,283 +0,0 @@
-package sqlite
-
-import (
-	"fmt"
-	"regexp"
-	"strconv"
-	"strings"
-)
-
-var unparameterizedColumnTypes = []string{
-	"date",
-	"datetime",
-	"timestamp",
-	"tinyblob",
-	"tinytext",
-	"mediumblob",
-	"mediumtext",
-	"longblob",
-	"longtext",
-	"blob",
-	"text",
-}
-
-func isParameterizedColumnType(requestedType string) bool {
-	for _, unparameterizedColumnType := range unparameterizedColumnTypes {
-		if unparameterizedColumnType == requestedType {
-			return false
-		}
-	}
-
-	return true
-}
-
-func maybeParseParameterizedColumnType(requestedType string) (string, error) {
-	columnType := ""
-
-	if strings.HasPrefix(requestedType, "varchar") {
-		columnType = "varchar"
-
-		r := regexp.MustCompile(`varchar\s*\((?P<max>\d*)\)`)
-
-		matchGroups := r.FindStringSubmatch(requestedType)
-		if len(matchGroups) == 0 {
-			columnType = "varchar (1)"
-		} else {
-			maxStr := matchGroups[1]
-			max, err := strconv.Atoi(maxStr)
-			if err != nil {
-				return "", err
-			}
-			columnType = fmt.Sprintf("varchar (%d)", max)
-		}
-	} else if strings.HasPrefix(requestedType, "char") {
-		columnType = "char"
-
-		if strings.Contains(requestedType, "character") {
-			requestedType = strings.Replace(requestedType, "character", "char", -1)
-		}
-
-		r := regexp.MustCompile(`char\s*\((?P<max>\d*)\)`)
-
-		matchGroups := r.FindStringSubmatch(requestedType)
-
-		if len(matchGroups) == 0 {
-			columnType = "char (11)"
-		} else {
-			maxStr := matchGroups[1]
-			max, err := strconv.Atoi(maxStr)
-			if err != nil {
-				return "", err
-			}
-			columnType = fmt.Sprintf("char (%d)", max)
-		}
-	} else if strings.HasPrefix(requestedType, "tinyint") {
-		columnType = "tinyint"
-
-		r := regexp.MustCompile(`tinyint\s*\((?P<max>\d*)\)`)
-
-		matchGroups := r.FindStringSubmatch(requestedType)
-		if len(matchGroups) == 0 {
-			columnType = "tinyint (1)"
-		} else {
-			maxStr := matchGroups[1]
-			max, err := strconv.Atoi(maxStr)
-			if err != nil {
-				return "", err
-			}
-			columnType = fmt.Sprintf("tinyint (%d)", max)
-		}
-	} else if strings.HasPrefix(requestedType, "bit") {
-		columnType = "bit"
-
-		r := regexp.MustCompile(`bit\s*\((?P<max>\d*)\)`)
-
-		matchGroups := r.FindStringSubmatch(requestedType)
-		if len(matchGroups) == 0 {
-			columnType = "bit (1)"
-		} else {
-			maxStr := matchGroups[1]
-			max, err := strconv.Atoi(maxStr)
-			if err != nil {
-				return "", err
-			}
-			columnType = fmt.Sprintf("bit (%d)", max)
-		}
-	} else if strings.HasPrefix(requestedType, "smallint") {
-		columnType = "smallint"
-
-		r := regexp.MustCompile(`smallint\s*\((?P<max>\d*)\)`)
-
-		matchGroups := r.FindStringSubmatch(requestedType)
-		if len(matchGroups) == 0 {
-			columnType = "smallint (5)"
-		} else {
-			maxStr := matchGroups[1]
-			max, err := strconv.Atoi(maxStr)
-			if err != nil {
-				return "", err
-			}
-			columnType = fmt.Sprintf("smallint (%d)", max)
-		}
-	} else if strings.HasPrefix(requestedType, "mediumint") {
-		columnType = "mediumint"
-
-		r := regexp.MustCompile(`mediumint\s*\((?P<max>\d*)\)`)
-
-		matchGroups := r.FindStringSubmatch(requestedType)
-		if len(matchGroups) == 0 {
-			columnType = "mediumint (9)"
-		} else {
-			maxStr := matchGroups[1]
-			max, err := strconv.Atoi(maxStr)
-			if err != nil {
-				return "", err
-			}
-			columnType = fmt.Sprintf("mediumint (%d)", max)
-		}
-	} else if strings.HasPrefix(requestedType, "int") {
-		columnType = "int"
-
-		r := regexp.MustCompile(`int\s*\((?P<max>\d*)\)`)
-
-		matchGroups := r.FindStringSubmatch(requestedType)
-		if len(matchGroups) == 0 {
-			columnType = "int (11)"
-		} else {
-			maxStr := matchGroups[1]
-			max, err := strconv.Atoi(maxStr)
-			if err != nil {
-				return "", err
-			}
-			columnType = fmt.Sprintf("int (%d)", max)
-		}
-	} else if strings.HasPrefix(requestedType, "bigint") {
-		columnType = "bigint"
-
-		r := regexp.MustCompile(`bigint\s*\((?P<max>\d*)\)`)
-
-		matchGroups := r.FindStringSubmatch(requestedType)
-		if len(matchGroups) == 0 {
-			columnType = "bigint (20)"
-		} else {
-			maxStr := matchGroups[1]
-			max, err := strconv.Atoi(maxStr)
-			if err != nil {
-				return "", err
-			}
-			columnType = fmt.Sprintf("bigint (%d)", max)
-		}
-	} else if strings.HasPrefix(requestedType, "decimal") {
-		columnType = "decimal"
-
-		precisionAndScale := regexp.MustCompile(`decimal\s*\(\s*(?P<precision>\d*),\s*(?P<scale>\d*)\s*\)`)
-		precisionOnly := regexp.MustCompile(`decimal\s*\(\s*(?P<precision>\d*)\s*\)`)
-
-		precisionAndScaleMatchGroups := precisionAndScale.FindStringSubmatch(requestedType)
-		precisionOnlyMatchGroups := precisionOnly.FindStringSubmatch(requestedType)
-
-		if len(precisionAndScaleMatchGroups) == 3 {
-			columnType = fmt.Sprintf("decimal (%s, %s)", precisionAndScaleMatchGroups[1], precisionAndScaleMatchGroups[2])
-		} else if len(precisionOnlyMatchGroups) == 2 {
-			columnType = fmt.Sprintf("decimal (%s, 0)", precisionOnlyMatchGroups[1])
-		} else {
-			columnType = "decimal (10, 0)"
-		}
-	} else if strings.HasPrefix(requestedType, "float") {
-		columnType = "float"
-
-		precisionAndScale := regexp.MustCompile(`float\s*\(\s*(?P<precision>\d*),\s*(?P<scale>\d*)\s*\)`)
-		precisionOnly := regexp.MustCompile(`float\s*\(\s*(?P<precision>\d*)\s*\)`)
-
-		precisionAndScaleMatchGroups := precisionAndScale.FindStringSubmatch(requestedType)
-		precisionOnlyMatchGroups := precisionOnly.FindStringSubmatch(requestedType)
-
-		if len(precisionAndScaleMatchGroups) == 3 {
-			columnType = fmt.Sprintf("float (%s, %s)", precisionAndScaleMatchGroups[1], precisionAndScaleMatchGroups[2])
-		} else if len(precisionOnlyMatchGroups) == 2 {
-			precision, err := strconv.Atoi(precisionOnlyMatchGroups[1])
-			if err != nil {
-				return "", err
-			}
-
-			if precision > 24 {
-				columnType = "double"
-			}
-		} else {
-			columnType = "float"
-		}
-	} else if strings.HasPrefix(requestedType, "double") {
-		columnType = "double"
-
-		precisionAndScale := regexp.MustCompile(`double\s*\(\s*(?P<precision>\d*),\s*(?P<scale>\d*)\s*\)`)
-		precisionAndScaleMatchGroups := precisionAndScale.FindStringSubmatch(requestedType)
-
-		if len(precisionAndScaleMatchGroups) == 3 {
-			columnType = fmt.Sprintf("double (%s, %s)", precisionAndScaleMatchGroups[1], precisionAndScaleMatchGroups[2])
-		} else {
-			columnType = "double"
-		}
-	} else if strings.HasPrefix(requestedType, "binary") {
-		columnType = "binary"
-
-		r := regexp.MustCompile(`binary\s*\((?P<max>\d*)\)`)
-
-		matchGroups := r.FindStringSubmatch(requestedType)
-		if len(matchGroups) == 0 {
-			columnType = "binary (1)"
-		} else {
-			maxStr := matchGroups[1]
-			max, err := strconv.Atoi(maxStr)
-			if err != nil {
-				return "", err
-			}
-			columnType = fmt.Sprintf("binary (%d)", max)
-		}
-	} else if strings.HasPrefix(requestedType, "varbinary") {
-		columnType = "varbinary"
-
-		r := regexp.MustCompile(`varbinary\s*\((?P<max>\d*)\)`)
-
-		matchGroups := r.FindStringSubmatch(requestedType)
-		if len(matchGroups) == 0 {
-			columnType = "varbinary (1)"
-		} else {
-			maxStr := matchGroups[1]
-			max, err := strconv.Atoi(maxStr)
-			if err != nil {
-				return "", err
-			}
-			columnType = fmt.Sprintf("varbinary (%d)", max)
-		}
-	} else if strings.HasPrefix(requestedType, "blob") {
-		columnType = "blob"
-
-		r := regexp.MustCompile(`blob\s*\((?P<max>\d*)\)`)
-
-		matchGroups := r.FindStringSubmatch(requestedType)
-		if len(matchGroups) > 0 {
-			maxStr := matchGroups[1]
-			max, err := strconv.Atoi(maxStr)
-			if err != nil {
-				return "", err
-			}
-			columnType = fmt.Sprintf("blob (%d)", max)
-		}
-	} else if strings.HasPrefix(requestedType, "text") {
-		columnType = "text"
-
-		r := regexp.MustCompile(`text\s*\((?P<max>\d*)\)`)
-
-		matchGroups := r.FindStringSubmatch(requestedType)
-		if len(matchGroups) > 0 {
-			maxStr := matchGroups[1]
-			max, err := strconv.Atoi(maxStr)
-			if err != nil {
-				return "", err
-			}
-			columnType = fmt.Sprintf("text (%d)", max)
-		}
-	}
-
-	return columnType, nil
-}
diff --git a/pkg/database/sqlite/tables.go b/pkg/database/sqlite/tables.go
new file mode 100644
index 00000000..7c8dd39f
--- /dev/null
+++ b/pkg/database/sqlite/tables.go
@@ -0,0 +1,221 @@
+package sqlite
+
+import (
+	"database/sql"
+
+	"github.com/pkg/errors"
+	"github.com/schemahero/schemahero/pkg/database/types"
+)
+
+func (s *SqliteConnection) ListTables() ([]*types.Table, error) {
+	query := "SELECT name FROM sqlite_master WHERE type='table'"
+
+	rows, err := s.db.Query(query)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to query")
+	}
+	defer rows.Close()
+
+	tables := []*types.Table{}
+	for rows.Next() {
+		var tableName string
+		if err := rows.Scan(&tableName); err != nil {
+			return nil, errors.Wrap(err, "failed to scan row")
+		}
+
+		tables = append(tables, &types.Table{
+			Name: tableName,
+		})
+	}
+
+	return tables, nil
+}
+
+func (s *SqliteConnection) ListTableIndexes(_ string, tableName string) ([]*types.Index, error) {
+	query := `
+SELECT DISTINCT il.name AS index_name, il.'unique', ii.name AS column_name
+FROM
+	sqlite_master AS m,
+	pragma_index_list(m.name) AS il,
+	pragma_index_info(il.name) AS ii
+WHERE m.type='table' AND m.name=? AND il.origin!='pk'
+`
+	rows, err := s.db.Query(query, tableName)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to query indexes")
+	}
+	defer rows.Close()
+
+	indexesMap := map[string]*types.Index{}
+
+	for rows.Next() {
+		var name string
+		var isUnique bool
+		var column string
+
+		if err := rows.Scan(&name, &isUnique, &column); err != nil {
+			return nil, err
+		}
+
+		var index *types.Index
+		if i, ok := indexesMap[name]; ok {
+			index = i
+		} else {
+			index = &types.Index{
+				Name:     name,
+				IsUnique: isUnique,
+			}
+			indexesMap[name] = index
+		}
+
+		index.Columns = append(index.Columns, column)
+	}
+
+	indexes := []*types.Index{}
+	for _, index := range indexesMap {
+		indexes = append(indexes, index)
+	}
+
+	return indexes, nil
+}
+
+func (s *SqliteConnection) IndexIsAConstraint(tableName string, indexName string) (bool, error) {
+	query := `
+SELECT DISTINCT il.origin
+FROM
+	sqlite_master AS m,
+	pragma_index_list(m.name) AS il
+WHERE m.type='table' AND m.name=? AND il.name=? AND il.origin!='pk'
+`
+
+	var origin string
+	row := s.db.QueryRow(query, tableName, indexName)
+	if err := row.Scan(&origin); err != nil {
+		return false, errors.Wrap(err, "failed to scan")
+	}
+
+	return origin != "c", nil
+}
+
+func (s *SqliteConnection) ListTableForeignKeys(_ string, tableName string) ([]*types.ForeignKey, error) {
+	query := `SELECT id, 'from' as child_column, 'table' as parent_table, 'to' as parent_column, 'on_delete' FROM pragma_foreign_key_list(?)`
+	rows, err := s.db.Query(query, tableName)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to query foreign keys")
+	}
+	defer rows.Close()
+
+	foreignKeysMap := map[int]*types.ForeignKey{}
+
+	for rows.Next() {
+		var id int
+		var childColumn, parentColumn, parentTable, deleteRule string
+
+		if err := rows.Scan(&id, &childColumn, &parentTable, &parentColumn, &deleteRule); err != nil {
+			return nil, err
+		}
+
+		var foreignKey *types.ForeignKey
+		if fk, ok := foreignKeysMap[id]; ok {
+			foreignKey = fk
+		} else {
+			foreignKey = &types.ForeignKey{
+				Name:        "", // TODO: find a way to get the name of the foreign key
+				ParentTable: parentTable,
+				OnDelete:    deleteRule,
+			}
+			foreignKeysMap[id] = foreignKey
+		}
+
+		foreignKey.ChildColumns = append(foreignKey.ChildColumns, childColumn)
+		foreignKey.ParentColumns = append(foreignKey.ParentColumns, parentColumn)
+	}
+
+	foreignKeys := []*types.ForeignKey{}
+	for _, fk := range foreignKeysMap {
+		foreignKeys = append(foreignKeys, fk)
+	}
+
+	return foreignKeys, nil
+}
+
+func (s *SqliteConnection) GetTablePrimaryKey(tableName string) (*types.KeyConstraint, error) {
+	query := `SELECT name FROM pragma_table_info(?) WHERE pk > 0`
+
+	rows, err := s.db.Query(query, tableName)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to query primary keys")
+	}
+	defer rows.Close()
+
+	var hasKey bool
+
+	key := types.KeyConstraint{
+		Name:      "", // TODO: find a way to get the name of the primary key
+		IsPrimary: true,
+	}
+	for rows.Next() {
+		hasKey = true
+		var columnName string
+		if err := rows.Scan(&columnName); err != nil {
+			return nil, errors.Wrap(err, "failed to scan")
+		}
+		key.Columns = append(key.Columns, columnName)
+	}
+	if !hasKey {
+		return nil, nil
+	}
+
+	return &key, nil
+}
+
+func (s *SqliteConnection) GetTablePrimaryKeyColumns(tableName string) ([]string, error) {
+	primaryKey, err := s.GetTablePrimaryKey(tableName)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get primary key")
+	}
+	if primaryKey == nil {
+		return nil, nil
+	}
+	return primaryKey.Columns, nil
+}
+
+func (s *SqliteConnection) GetTableSchema(tableName string) ([]*types.Column, error) {
+	query := "select name, type, dflt_value, notnull from pragma_table_info(?)"
+
+	rows, err := s.db.Query(query, tableName)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to query table schema")
+	}
+	defer rows.Close()
+
+	columns := make([]*types.Column, 0)
+	for rows.Next() {
+		column := types.Column{}
+
+		var notNull bool
+		var columnDefault sql.NullString
+
+		if err := rows.Scan(&column.Name, &column.DataType, &columnDefault, &notNull); err != nil {
+			return nil, err
+		}
+
+		if notNull {
+			column.Constraints = &types.ColumnConstraints{
+				NotNull: &trueValue,
+			}
+		} else {
+			column.Constraints = &types.ColumnConstraints{
+				NotNull: &falseValue,
+			}
+		}
+
+		if columnDefault.Valid {
+			column.ColumnDefault = &columnDefault.String
+		}
+
+		columns = append(columns, &column)
+	}
+
+	return columns, nil
+}
diff --git a/pkg/database/types/foreignkey.go b/pkg/database/types/foreignkey.go
index cedba3a0..8694da68 100644
--- a/pkg/database/types/foreignkey.go
+++ b/pkg/database/types/foreignkey.go
@@ -124,6 +124,18 @@ func PostgresqlSchemaForeignKeyToForeignKey(schemaForeignKey *schemasv1alpha4.Po
 	return &foreignKey
 }
 
+func SqliteSchemaForeignKeyToForeignKey(schemaForeignKey *schemasv1alpha4.SqliteTableForeignKey) *ForeignKey {
+	foreignKey := ForeignKey{
+		ChildColumns:  schemaForeignKey.Columns,
+		ParentTable:   schemaForeignKey.References.Table,
+		ParentColumns: schemaForeignKey.References.Columns,
+		Name:          schemaForeignKey.Name,
+		OnDelete:      schemaForeignKey.OnDelete,
+	}
+
+	return &foreignKey
+}
+
 func RqliteSchemaForeignKeyToForeignKey(schemaForeignKey *schemasv1alpha4.RqliteTableForeignKey) *ForeignKey {
 	foreignKey := ForeignKey{
 		ChildColumns:  schemaForeignKey.Columns,
diff --git a/pkg/database/types/index.go b/pkg/database/types/index.go
index fb120d96..95fb04d8 100644
--- a/pkg/database/types/index.go
+++ b/pkg/database/types/index.go
@@ -91,6 +91,16 @@ func PostgresqlSchemaIndexToIndex(schemaIndex *schemasv1alpha4.PostgresqlTableIn
 	return &index
 }
 
+func SqliteSchemaIndexToIndex(schemaIndex *schemasv1alpha4.SqliteTableIndex) *Index {
+	index := Index{
+		Columns:  schemaIndex.Columns,
+		Name:     schemaIndex.Name,
+		IsUnique: schemaIndex.IsUnique,
+	}
+
+	return &index
+}
+
 func RqliteSchemaIndexToIndex(schemaIndex *schemasv1alpha4.RqliteTableIndex) *Index {
 	index := Index{
 		Columns:  schemaIndex.Columns,
@@ -113,6 +123,10 @@ func GeneratePostgresqlIndexName(tableName string, schemaIndex *schemasv1alpha4.
 	return fmt.Sprintf("idx_%s_%s", tableName, strings.Join(schemaIndex.Columns, "_"))
 }
 
+func GenerateSqliteIndexName(tableName string, schemaIndex *schemasv1alpha4.SqliteTableIndex) string {
+	return fmt.Sprintf("idx_%s_%s", tableName, strings.Join(schemaIndex.Columns, "_"))
+}
+
 func GenerateRqliteIndexName(tableName string, schemaIndex *schemasv1alpha4.RqliteTableIndex) string {
 	return fmt.Sprintf("idx_%s_%s", tableName, strings.Join(schemaIndex.Columns, "_"))
 }


Test patch:
diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml
index cdba81c2..8d5bc008 100644
--- a/.github/workflows/build-and-test.yaml
+++ b/.github/workflows/build-and-test.yaml
@@ -122,7 +122,7 @@ jobs:
     needs: build
     strategy:
       matrix:
-        sqlite_version: ["3.33.0"]
+        sqlite_version: ["3.39.3"]
     steps:
       - uses: actions/checkout@v2
       - name: Download kubectl-schemahero binary
diff --git a/integration/tests/rqlite/Makefile b/integration/tests/rqlite/Makefile
index 70e292de..35dc9291 100644
--- a/integration/tests/rqlite/Makefile
+++ b/integration/tests/rqlite/Makefile
@@ -4,6 +4,9 @@ SHELL := /bin/bash
 export IMAGE
 export GO111MODULE=on
 
+.PHONY: run
+run: 7.6.1 6.10.2
+
 .PHONY: 7.6.1
 7.6.1: export RQLITE_VERSION = 7.6.1
 7.6.1:
diff --git a/integration/tests/sqlite/Makefile b/integration/tests/sqlite/Makefile
index 86023732..6a25c137 100644
--- a/integration/tests/sqlite/Makefile
+++ b/integration/tests/sqlite/Makefile
@@ -5,17 +5,47 @@ export IMAGE
 export GO111MODULE=on
 
 .PHONY: run
-run: 3.33.0
+run: 3.39.3
 
-.PHONY: 3.33.0
-3.33.0: export SQLITE_VERSION = 3.33.0
-3.33.0:
-	make -C create-table run
+.PHONY: 3.39.3
+3.39.3: export SQLITE_VERSION = 3.39.3
+3.39.3:
+	make -C basic-seed run
+	make -C column-add run
+	make -C column-alter run
+	make -C column-drop run
+	make -C column-set-default run
+	make -C column-unset-default run
+	make -C drop-indexed-column run
+	make -C drop-unique-constraint-column run
+	make -C drop-unique-indexed-column run
+	make -C foreign-key-action run
+	make -C foreign-key-alter run
+	make -C foreign-key-create run
+	make -C foreign-key-drop run
+	make -C index-alter run
+	make -C index-create run
+	make -C index-drop run
+	make -C index-named-no-change run
+	make -C index-no-change run
 	make -C not-null run
 	make -C not-null-with-default run
-	# make -C primary-key-add run
-	# make -C primary-key-drop run
-	make -C table-column-name run
+	make -C primary-key-add run
+	make -C primary-key-alter run
+	make -C primary-key-drop run
+	make -C seed-with-many-rows run
+	make -C table-create run
+	make -C table-create-strict run
+	make -C table-create-with-index run
+	make -C table-create-with-unique-index run
+	make -C unique-constraint-add run
+	make -C unique-constraint-alter run
+	make -C unique-constraint-drop run
+	make -C unique-index-alter run
+	make -C unique-index-create run
+	make -C unique-index-drop run
+	make -C unique-index-named-no-change run
+	make -C unique-index-no-change run
 		
 .PHONY: build
 build: docker-build
diff --git a/integration/tests/sqlite/create-table/.gitignore b/integration/tests/sqlite/basic-seed/.gitignore
similarity index 100%
rename from integration/tests/sqlite/create-table/.gitignore
rename to integration/tests/sqlite/basic-seed/.gitignore
diff --git a/integration/tests/sqlite/basic-seed/Makefile b/integration/tests/sqlite/basic-seed/Makefile
new file mode 100644
index 00000000..915390d6
--- /dev/null
+++ b/integration/tests/sqlite/basic-seed/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqite-basic-seed
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/basic-seed/expect.sql b/integration/tests/sqlite/basic-seed/expect.sql
new file mode 100644
index 00000000..fa0ba84e
--- /dev/null
+++ b/integration/tests/sqlite/basic-seed/expect.sql
@@ -0,0 +1,2 @@
+create table "users" ("id" integer not null, "login" text, "name" text not null, primary key ("id"));
+replace into users (id, login, name) values (1, 'test', 'test2');
diff --git a/integration/tests/sqlite/basic-seed/fixtures.sql b/integration/tests/sqlite/basic-seed/fixtures.sql
new file mode 100644
index 00000000..87633745
--- /dev/null
+++ b/integration/tests/sqlite/basic-seed/fixtures.sql
@@ -0,0 +1,1 @@
+create table other (id integer primary key not null, something text not null);
diff --git a/integration/tests/sqlite/basic-seed/specs/users.yaml b/integration/tests/sqlite/basic-seed/specs/users.yaml
new file mode 100644
index 00000000..d27df497
--- /dev/null
+++ b/integration/tests/sqlite/basic-seed/specs/users.yaml
@@ -0,0 +1,29 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: login
+        type: text
+      - name: name
+        type: text
+        constraints:
+          notNull: true
+seedData:
+  rows:
+    - columns:
+      - column: id
+        value:
+          int: 1
+      - column: login
+        value:
+          str: test
+      - column: name
+        value:
+          str: test2
diff --git a/integration/tests/sqlite/table-column-name/.gitignore b/integration/tests/sqlite/column-add/.gitignore
similarity index 100%
rename from integration/tests/sqlite/table-column-name/.gitignore
rename to integration/tests/sqlite/column-add/.gitignore
diff --git a/integration/tests/sqlite/column-add/Makefile b/integration/tests/sqlite/column-add/Makefile
new file mode 100644
index 00000000..e8f6812c
--- /dev/null
+++ b/integration/tests/sqlite/column-add/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-column-add
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/column-add/expect.sql b/integration/tests/sqlite/column-add/expect.sql
new file mode 100644
index 00000000..ae1c6e62
--- /dev/null
+++ b/integration/tests/sqlite/column-add/expect.sql
@@ -0,0 +1,1 @@
+alter table "users" add column "age" integer;
diff --git a/integration/tests/sqlite/column-add/fixtures.sql b/integration/tests/sqlite/column-add/fixtures.sql
new file mode 100644
index 00000000..47cb7e97
--- /dev/null
+++ b/integration/tests/sqlite/column-add/fixtures.sql
@@ -0,0 +1,1 @@
+create table users (id integer primary key not null, name text);
diff --git a/integration/tests/sqlite/create-table/specs/users.yaml b/integration/tests/sqlite/column-add/specs/users.yaml
similarity index 67%
rename from integration/tests/sqlite/create-table/specs/users.yaml
rename to integration/tests/sqlite/column-add/specs/users.yaml
index 1a6e1276..eb0b7276 100644
--- a/integration/tests/sqlite/create-table/specs/users.yaml
+++ b/integration/tests/sqlite/column-add/specs/users.yaml
@@ -7,10 +7,9 @@ schema:
     columns:
       - name: id
         type: integer
-      - name: login
-        type: varchar(255)
-      - name: name
-        type: varchar(255)
         constraints:
           notNull: true
-        default: ethan
+      - name: name
+        type: text
+      - name: age
+        type: integer
diff --git a/integration/tests/sqlite/column-alter/.gitignore b/integration/tests/sqlite/column-alter/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/column-alter/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/column-alter/Makefile b/integration/tests/sqlite/column-alter/Makefile
new file mode 100644
index 00000000..19b8f99d
--- /dev/null
+++ b/integration/tests/sqlite/column-alter/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-column-alter
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/column-alter/expect.sql b/integration/tests/sqlite/column-alter/expect.sql
new file mode 100644
index 00000000..74256007
--- /dev/null
+++ b/integration/tests/sqlite/column-alter/expect.sql
@@ -0,0 +1,6 @@
+begin transaction;
+alter table "users" rename to "users_b2c1a24f693cd984c516dc5a970c88b6ea2b08b29dd73c45ea98111c7ea82eee";
+create table "users" ("id" integer not null, "name" text, "age" integer, primary key ("id"));
+insert into users (id, name, age) select id, name, age from users_b2c1a24f693cd984c516dc5a970c88b6ea2b08b29dd73c45ea98111c7ea82eee;
+drop table users_b2c1a24f693cd984c516dc5a970c88b6ea2b08b29dd73c45ea98111c7ea82eee;
+commit;
diff --git a/integration/tests/sqlite/column-alter/fixtures.sql b/integration/tests/sqlite/column-alter/fixtures.sql
new file mode 100644
index 00000000..0b008d74
--- /dev/null
+++ b/integration/tests/sqlite/column-alter/fixtures.sql
@@ -0,0 +1,1 @@
+create table users (id integer primary key not null, name text, age real);
diff --git a/integration/tests/sqlite/column-alter/specs/users.yaml b/integration/tests/sqlite/column-alter/specs/users.yaml
new file mode 100644
index 00000000..eb0b7276
--- /dev/null
+++ b/integration/tests/sqlite/column-alter/specs/users.yaml
@@ -0,0 +1,15 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: name
+        type: text
+      - name: age
+        type: integer
diff --git a/integration/tests/sqlite/column-drop/.gitignore b/integration/tests/sqlite/column-drop/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/column-drop/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/column-drop/Makefile b/integration/tests/sqlite/column-drop/Makefile
new file mode 100644
index 00000000..4fa00d40
--- /dev/null
+++ b/integration/tests/sqlite/column-drop/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-column-drop
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/column-drop/expect.sql b/integration/tests/sqlite/column-drop/expect.sql
new file mode 100644
index 00000000..3ca19b61
--- /dev/null
+++ b/integration/tests/sqlite/column-drop/expect.sql
@@ -0,0 +1,1 @@
+alter table "users" drop column "age";
diff --git a/integration/tests/sqlite/column-drop/fixtures.sql b/integration/tests/sqlite/column-drop/fixtures.sql
new file mode 100644
index 00000000..f29fca0f
--- /dev/null
+++ b/integration/tests/sqlite/column-drop/fixtures.sql
@@ -0,0 +1,1 @@
+create table users (id integer primary key not null, name text, age integer);
diff --git a/integration/tests/sqlite/column-drop/specs/users.yaml b/integration/tests/sqlite/column-drop/specs/users.yaml
new file mode 100644
index 00000000..6d759504
--- /dev/null
+++ b/integration/tests/sqlite/column-drop/specs/users.yaml
@@ -0,0 +1,13 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: name
+        type: text
diff --git a/integration/tests/sqlite/column-set-default/.gitignore b/integration/tests/sqlite/column-set-default/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/column-set-default/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/column-set-default/Makefile b/integration/tests/sqlite/column-set-default/Makefile
new file mode 100644
index 00000000..30ea5ebe
--- /dev/null
+++ b/integration/tests/sqlite/column-set-default/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-column-set-default
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/column-set-default/expect.sql b/integration/tests/sqlite/column-set-default/expect.sql
new file mode 100644
index 00000000..88162438
--- /dev/null
+++ b/integration/tests/sqlite/column-set-default/expect.sql
@@ -0,0 +1,6 @@
+begin transaction;
+alter table "users" rename to "users_177853ca2be414e548038166010e689fe31ede3391cd16bcd1baf7dbbc719bb7";
+create table "users" ("id" integer not null, "email" text not null, "account_type" text default 'trial', "num_seats" integer default '5', primary key ("id"));
+insert into users (id, email, account_type, num_seats) select id, email, account_type, num_seats from users_177853ca2be414e548038166010e689fe31ede3391cd16bcd1baf7dbbc719bb7;
+drop table users_177853ca2be414e548038166010e689fe31ede3391cd16bcd1baf7dbbc719bb7;
+commit;
diff --git a/integration/tests/sqlite/column-set-default/fixtures.sql b/integration/tests/sqlite/column-set-default/fixtures.sql
new file mode 100644
index 00000000..1ee2ce61
--- /dev/null
+++ b/integration/tests/sqlite/column-set-default/fixtures.sql
@@ -0,0 +1,1 @@
+create table users (id integer primary key not null, email text not null, account_type text, num_seats integer);
diff --git a/integration/tests/sqlite/column-set-default/specs/users.yaml b/integration/tests/sqlite/column-set-default/specs/users.yaml
new file mode 100644
index 00000000..11d66e57
--- /dev/null
+++ b/integration/tests/sqlite/column-set-default/specs/users.yaml
@@ -0,0 +1,21 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: email
+        type: text
+        constraints:
+          notNull: true
+      - name: account_type
+        type: text
+        default: trial
+      - name: num_seats
+        type: integer
+        default: "5"
diff --git a/integration/tests/sqlite/column-unset-default/.gitignore b/integration/tests/sqlite/column-unset-default/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/column-unset-default/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/column-unset-default/Makefile b/integration/tests/sqlite/column-unset-default/Makefile
new file mode 100644
index 00000000..59d3ca23
--- /dev/null
+++ b/integration/tests/sqlite/column-unset-default/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-column-unset-default
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/column-unset-default/expect.sql b/integration/tests/sqlite/column-unset-default/expect.sql
new file mode 100644
index 00000000..32dbda3e
--- /dev/null
+++ b/integration/tests/sqlite/column-unset-default/expect.sql
@@ -0,0 +1,6 @@
+begin transaction;
+alter table "users" rename to "users_e0b541947f3a981629f9b7b96ff2f2ce47b11d6cf5d2770fd705855a5f9b26f3";
+create table "users" ("id" integer not null, "email" text not null, "account_type" text, "num_seats" integer, primary key ("id"));
+insert into users (id, email, account_type, num_seats) select id, email, account_type, num_seats from users_e0b541947f3a981629f9b7b96ff2f2ce47b11d6cf5d2770fd705855a5f9b26f3;
+drop table users_e0b541947f3a981629f9b7b96ff2f2ce47b11d6cf5d2770fd705855a5f9b26f3;
+commit;
diff --git a/integration/tests/sqlite/column-unset-default/fixtures.sql b/integration/tests/sqlite/column-unset-default/fixtures.sql
new file mode 100644
index 00000000..8812a442
--- /dev/null
+++ b/integration/tests/sqlite/column-unset-default/fixtures.sql
@@ -0,0 +1,1 @@
+create table users (id integer primary key not null, email text not null, account_type text not null default 'trial', num_seats integer not null default 5);
diff --git a/integration/tests/sqlite/column-unset-default/specs/users.yaml b/integration/tests/sqlite/column-unset-default/specs/users.yaml
new file mode 100644
index 00000000..5bc85dba
--- /dev/null
+++ b/integration/tests/sqlite/column-unset-default/specs/users.yaml
@@ -0,0 +1,19 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: email
+        type: text
+        constraints:
+          notNull: true
+      - name: account_type
+        type: text
+      - name: num_seats
+        type: integer
diff --git a/integration/tests/sqlite/common.mk b/integration/tests/sqlite/common.mk
index 0fc73c12..9e2ea559 100644
--- a/integration/tests/sqlite/common.mk
+++ b/integration/tests/sqlite/common.mk
@@ -10,11 +10,11 @@ run:
 	rm -rf ./db/db.db
 	mkdir -p db
 	touch ./db/db.db
-	docker pull keinos/sqlite3:latest
-	docker run --rm -v `pwd`/db:/db --name $(DATABASE_CONTAINER_NAME) keinos/sqlite3:latest sqlite3 /db/db.db
+	docker pull keinos/sqlite3:$(SQLITE_VERSION)
+	docker run --rm -v `pwd`/db:/db --name $(DATABASE_CONTAINER_NAME) keinos/sqlite3:$(SQLITE_VERSION) sqlite3 /db/db.db
 	chmod a+w db   # give the "sqlite" user in the container permissions
 	chmod a+w db/db.db   # and permission to the file
-	docker run --rm -v `pwd`/db:/db -v `pwd`/fixtures.sql:/fixtures.sql --name $(DATABASE_CONTAINER_NAME) keinos/sqlite3:latest sqlite3 /db/db.db ".read /fixtures.sql"
+	docker run --rm -v `pwd`/db:/db -v `pwd`/fixtures.sql:/fixtures.sql --name $(DATABASE_CONTAINER_NAME) keinos/sqlite3:$(SQLITE_VERSION) sqlite3 /db/db.db ".read /fixtures.sql"
 
 	# Plan
 	../../../../bin/kubectl-schemahero plan --seed-data --driver=$(DRIVER) --uri="$(URI)" --spec-file $(SPEC_FILE) > out.sql
@@ -27,4 +27,5 @@ run:
 	../../../../bin/kubectl-schemahero apply --driver=$(DRIVER) --uri="$(URI)" --ddl out.sql
 
 	# Cleanup
-	# rm ./out.sql
+	rm -rf ./db
+	rm ./out.sql
diff --git a/integration/tests/sqlite/create-table/expect.sql b/integration/tests/sqlite/create-table/expect.sql
deleted file mode 100644
index bc489e40..00000000
--- a/integration/tests/sqlite/create-table/expect.sql
+++ /dev/null
@@ -1,9 +0,0 @@
-begin transaction;
-alter table `other` rename to `other_ffb36fbcac4bec40514212401762afb5a139e8371ddd8ca0f996b29bfd6ccc27`;
-create table `other` (`id` int (11), `something` varchar (255) not null, primary key (`id`));
-insert into other (id, something) select id, something from other_ffb36fbcac4bec40514212401762afb5a139e8371ddd8ca0f996b29bfd6ccc27;
-drop table other_ffb36fbcac4bec40514212401762afb5a139e8371ddd8ca0f996b29bfd6ccc27;
-commit;
-replace into other (id, something) values (1, 'one');
-replace into other (id, something) values (2, 'two');
-create table `users` (`id` int (11), `login` varchar (255), `name` varchar (255) not null default 'ethan', primary key (`id`));
diff --git a/integration/tests/sqlite/create-table/fixtures.sql b/integration/tests/sqlite/create-table/fixtures.sql
deleted file mode 100644
index a8cac7fb..00000000
--- a/integration/tests/sqlite/create-table/fixtures.sql
+++ /dev/null
@@ -1,7 +0,0 @@
-create table other (
-  id integer primary key not null,
-  something varchar(255) not null
-);
-
-insert into other (id, something) values (1, 'one hundred');
-insert into other (id, something) values (2, 'two hundred');
\ No newline at end of file
diff --git a/integration/tests/sqlite/drop-indexed-column/.gitignore b/integration/tests/sqlite/drop-indexed-column/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/drop-indexed-column/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/drop-indexed-column/Makefile b/integration/tests/sqlite/drop-indexed-column/Makefile
new file mode 100644
index 00000000..d5452952
--- /dev/null
+++ b/integration/tests/sqlite/drop-indexed-column/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-drop-indexed-column
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/drop-indexed-column/expect.sql b/integration/tests/sqlite/drop-indexed-column/expect.sql
new file mode 100644
index 00000000..63853a69
--- /dev/null
+++ b/integration/tests/sqlite/drop-indexed-column/expect.sql
@@ -0,0 +1,2 @@
+drop index idx_users_email;
+alter table "users" drop column "email";
diff --git a/integration/tests/sqlite/drop-indexed-column/fixtures.sql b/integration/tests/sqlite/drop-indexed-column/fixtures.sql
new file mode 100644
index 00000000..985f0d80
--- /dev/null
+++ b/integration/tests/sqlite/drop-indexed-column/fixtures.sql
@@ -0,0 +1,2 @@
+create table users (id integer primary key not null, email text not null);
+create index idx_users_email on users (email);
diff --git a/integration/tests/sqlite/drop-indexed-column/specs/users.yaml b/integration/tests/sqlite/drop-indexed-column/specs/users.yaml
new file mode 100644
index 00000000..d9dee822
--- /dev/null
+++ b/integration/tests/sqlite/drop-indexed-column/specs/users.yaml
@@ -0,0 +1,11 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
diff --git a/integration/tests/sqlite/drop-unique-constraint-column/.gitignore b/integration/tests/sqlite/drop-unique-constraint-column/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/drop-unique-constraint-column/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/drop-unique-constraint-column/Makefile b/integration/tests/sqlite/drop-unique-constraint-column/Makefile
new file mode 100644
index 00000000..7dc1eca2
--- /dev/null
+++ b/integration/tests/sqlite/drop-unique-constraint-column/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-drop-unique-constraint-column
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/drop-unique-constraint-column/expect.sql b/integration/tests/sqlite/drop-unique-constraint-column/expect.sql
new file mode 100644
index 00000000..501c32cb
--- /dev/null
+++ b/integration/tests/sqlite/drop-unique-constraint-column/expect.sql
@@ -0,0 +1,6 @@
+begin transaction;
+alter table "users" rename to "users_662b1fbe4ce8b8e73cc961d3400d5dbcce5105681d2d5857568bfab4fdafb6b6";
+create table "users" ("id" integer not null, primary key ("id"));
+insert into users (id) select id from users_662b1fbe4ce8b8e73cc961d3400d5dbcce5105681d2d5857568bfab4fdafb6b6;
+drop table users_662b1fbe4ce8b8e73cc961d3400d5dbcce5105681d2d5857568bfab4fdafb6b6;
+commit;
diff --git a/integration/tests/sqlite/drop-unique-constraint-column/fixtures.sql b/integration/tests/sqlite/drop-unique-constraint-column/fixtures.sql
new file mode 100644
index 00000000..7e959319
--- /dev/null
+++ b/integration/tests/sqlite/drop-unique-constraint-column/fixtures.sql
@@ -0,0 +1,1 @@
+create table users (id integer primary key not null, email text not null unique);
diff --git a/integration/tests/sqlite/drop-unique-constraint-column/specs/users.yaml b/integration/tests/sqlite/drop-unique-constraint-column/specs/users.yaml
new file mode 100644
index 00000000..d9dee822
--- /dev/null
+++ b/integration/tests/sqlite/drop-unique-constraint-column/specs/users.yaml
@@ -0,0 +1,11 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
diff --git a/integration/tests/sqlite/drop-unique-indexed-column/.gitignore b/integration/tests/sqlite/drop-unique-indexed-column/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/drop-unique-indexed-column/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/drop-unique-indexed-column/Makefile b/integration/tests/sqlite/drop-unique-indexed-column/Makefile
new file mode 100644
index 00000000..d7deafe7
--- /dev/null
+++ b/integration/tests/sqlite/drop-unique-indexed-column/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-drop-unique-indexed-column
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/drop-unique-indexed-column/expect.sql b/integration/tests/sqlite/drop-unique-indexed-column/expect.sql
new file mode 100644
index 00000000..94e2fe86
--- /dev/null
+++ b/integration/tests/sqlite/drop-unique-indexed-column/expect.sql
@@ -0,0 +1,2 @@
+drop index if exists idx_users_email;
+alter table "users" drop column "email";
diff --git a/integration/tests/sqlite/drop-unique-indexed-column/fixtures.sql b/integration/tests/sqlite/drop-unique-indexed-column/fixtures.sql
new file mode 100644
index 00000000..926c8760
--- /dev/null
+++ b/integration/tests/sqlite/drop-unique-indexed-column/fixtures.sql
@@ -0,0 +1,2 @@
+create table users (id integer primary key not null, email text not null);
+create unique index idx_users_email on users (email);
diff --git a/integration/tests/sqlite/drop-unique-indexed-column/specs/users.yaml b/integration/tests/sqlite/drop-unique-indexed-column/specs/users.yaml
new file mode 100644
index 00000000..d9dee822
--- /dev/null
+++ b/integration/tests/sqlite/drop-unique-indexed-column/specs/users.yaml
@@ -0,0 +1,11 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
diff --git a/integration/tests/sqlite/foreign-key-action/.gitignore b/integration/tests/sqlite/foreign-key-action/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-action/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/foreign-key-action/Makefile b/integration/tests/sqlite/foreign-key-action/Makefile
new file mode 100644
index 00000000..ffb24050
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-action/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-foreign-key-action
+SPEC_FILE := ./specs/user-project.yaml
diff --git a/integration/tests/sqlite/foreign-key-action/expect.sql b/integration/tests/sqlite/foreign-key-action/expect.sql
new file mode 100644
index 00000000..fc2bbb90
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-action/expect.sql
@@ -0,0 +1,2 @@
+create table "user_project" ("user_id" integer, "project_id" integer, "misc_id" text not null, primary key ("user_id", "project_id"), constraint user_project_user_id_fkey foreign key (user_id) references users (id), constraint user_project_project_id_fkey foreign key (project_id) references projects (id), constraint misc_named_fk foreign key (misc_id) references misc (pk) on delete cascade);
+
diff --git a/integration/tests/sqlite/foreign-key-action/fixtures.sql b/integration/tests/sqlite/foreign-key-action/fixtures.sql
new file mode 100644
index 00000000..96186110
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-action/fixtures.sql
@@ -0,0 +1,6 @@
+create table users (id integer primary key not null, email text not null);
+
+create table projects (id integer primary key not null, name text not null);
+
+create table misc (pk text primary key not null);
+
diff --git a/integration/tests/sqlite/foreign-key-action/specs/user-project.yaml b/integration/tests/sqlite/foreign-key-action/specs/user-project.yaml
new file mode 100644
index 00000000..a470c876
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-action/specs/user-project.yaml
@@ -0,0 +1,35 @@
+database: schemahero
+name: user_project
+schema:
+  sqlite:
+    primaryKey: [user_id, project_id]
+    foreignKeys:
+      - columns:
+          - user_id
+        references:
+          table: users
+          columns:
+            - id
+      - columns:
+          - project_id
+        references:
+          table: projects
+          columns:
+            - id
+      - columns:
+          - misc_id
+        references:
+          table: misc
+          columns:
+            - pk
+        name: misc_named_fk
+        onDelete: cascade
+    columns:
+      - name: user_id
+        type: integer
+      - name: project_id
+        type: integer
+      - name: misc_id
+        type: text
+        constraints:
+          notNull: true
diff --git a/integration/tests/sqlite/foreign-key-alter/.gitignore b/integration/tests/sqlite/foreign-key-alter/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-alter/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/foreign-key-alter/Makefile b/integration/tests/sqlite/foreign-key-alter/Makefile
new file mode 100644
index 00000000..83e791ec
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-alter/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-foreign-key-alter
+SPEC_FILE := ./specs/issues.yaml
diff --git a/integration/tests/sqlite/foreign-key-alter/expect.sql b/integration/tests/sqlite/foreign-key-alter/expect.sql
new file mode 100644
index 00000000..24482cfd
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-alter/expect.sql
@@ -0,0 +1,6 @@
+begin transaction;
+alter table "issues" rename to "issues_36a17f93aaada01c0b438606c254940bbe48a88c1bc192feacfb363b0640107b";
+create table "issues" ("id" integer not null, "project_id" integer, primary key ("id"), constraint renamed_fkey foreign key (project_id) references projects (id));
+insert into issues (id, project_id) select id, project_id from issues_36a17f93aaada01c0b438606c254940bbe48a88c1bc192feacfb363b0640107b;
+drop table issues_36a17f93aaada01c0b438606c254940bbe48a88c1bc192feacfb363b0640107b;
+commit;
diff --git a/integration/tests/sqlite/foreign-key-alter/fixtures.sql b/integration/tests/sqlite/foreign-key-alter/fixtures.sql
new file mode 100644
index 00000000..1c4558b0
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-alter/fixtures.sql
@@ -0,0 +1,7 @@
+create table users (id integer primary key not null, email text not null);
+
+create table projects (id integer primary key not null, name text not null);
+
+create table issues (id integer primary key not null, project_id integer references users(id));
+
+create table other (id integer primary key not null, project_id integer references users(id) on delete cascade);
diff --git a/integration/tests/sqlite/foreign-key-alter/specs/issues.yaml b/integration/tests/sqlite/foreign-key-alter/specs/issues.yaml
new file mode 100644
index 00000000..881b8e3c
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-alter/specs/issues.yaml
@@ -0,0 +1,20 @@
+database: schemahero
+name: issues
+schema:
+  sqlite:
+    primaryKey: [id]
+    foreignKeys:
+      - columns:
+          - project_id
+        references:
+          table: projects
+          columns:
+            - id
+        name: renamed_fkey
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: project_id
+        type: integer
diff --git a/integration/tests/sqlite/foreign-key-create/.gitignore b/integration/tests/sqlite/foreign-key-create/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-create/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/foreign-key-create/Makefile b/integration/tests/sqlite/foreign-key-create/Makefile
new file mode 100644
index 00000000..b0c2fb4c
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-create/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-foreign-key-create
+SPEC_FILE := ./specs/user-project.yaml
diff --git a/integration/tests/sqlite/foreign-key-create/expect.sql b/integration/tests/sqlite/foreign-key-create/expect.sql
new file mode 100644
index 00000000..51b6a9d7
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-create/expect.sql
@@ -0,0 +1,1 @@
+create table "user_project" ("user_id" integer, "project_id" integer, "misc_id" text, primary key ("user_id", "project_id"), constraint user_project_user_id_fkey foreign key (user_id) references users (id), constraint user_project_project_id_fkey foreign key (project_id) references projects (id), constraint misc_named_fk foreign key (misc_id) references misc (pk));
diff --git a/integration/tests/sqlite/foreign-key-create/fixtures.sql b/integration/tests/sqlite/foreign-key-create/fixtures.sql
new file mode 100644
index 00000000..dfc00f97
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-create/fixtures.sql
@@ -0,0 +1,5 @@
+create table users (id integer primary key not null, email text not null);
+
+create table projects (id integer primary key not null, name text not null);
+
+create table misc (pk text primary key not null);
diff --git a/integration/tests/sqlite/foreign-key-create/specs/user-project.yaml b/integration/tests/sqlite/foreign-key-create/specs/user-project.yaml
new file mode 100644
index 00000000..6cea4427
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-create/specs/user-project.yaml
@@ -0,0 +1,33 @@
+database: schemahero
+name: user_project
+schema:
+  sqlite:
+    primaryKey: [user_id, project_id]
+    foreignKeys:
+      - columns:
+          - user_id
+        references:
+          table: users
+          columns:
+            - id
+      - columns:
+          - project_id
+        references:
+          table: projects
+          columns:
+            - id
+      - columns:
+          - misc_id
+        references:
+          table: misc
+          columns:
+            - pk
+        name: misc_named_fk
+    columns:
+      - name: user_id
+        type: integer
+      - name: project_id
+        type: integer
+      - name: misc_id
+        type: text
+        constraints:
diff --git a/integration/tests/sqlite/foreign-key-drop/.gitignore b/integration/tests/sqlite/foreign-key-drop/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-drop/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/foreign-key-drop/Makefile b/integration/tests/sqlite/foreign-key-drop/Makefile
new file mode 100644
index 00000000..0cc80b95
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-drop/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-foreign-key-drop
+SPEC_FILE := ./specs/org.yaml
diff --git a/integration/tests/sqlite/foreign-key-drop/expect.sql b/integration/tests/sqlite/foreign-key-drop/expect.sql
new file mode 100644
index 00000000..ca72c067
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-drop/expect.sql
@@ -0,0 +1,6 @@
+begin transaction;
+alter table "org" rename to "org_e14ee3e9b88284bec448aaf9d2272daff9d0057110f42e1a83ac5bc1ca0d30b0";
+create table "org" ("id" integer not null, "project_id" integer, primary key ("id"));
+insert into org (id, project_id) select id, project_id from org_e14ee3e9b88284bec448aaf9d2272daff9d0057110f42e1a83ac5bc1ca0d30b0;
+drop table org_e14ee3e9b88284bec448aaf9d2272daff9d0057110f42e1a83ac5bc1ca0d30b0;
+commit;
diff --git a/integration/tests/sqlite/foreign-key-drop/fixtures.sql b/integration/tests/sqlite/foreign-key-drop/fixtures.sql
new file mode 100644
index 00000000..d4e4389e
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-drop/fixtures.sql
@@ -0,0 +1,3 @@
+create table projects (id integer primary key not null, name text not null);
+
+create table org (id integer primary key not null, project_id integer references projects(id));
diff --git a/integration/tests/sqlite/foreign-key-drop/specs/org.yaml b/integration/tests/sqlite/foreign-key-drop/specs/org.yaml
new file mode 100644
index 00000000..f7495372
--- /dev/null
+++ b/integration/tests/sqlite/foreign-key-drop/specs/org.yaml
@@ -0,0 +1,12 @@
+database: schemahero
+name: org
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+    - name: id
+      type: integer
+      constraints:
+        notNull: true
+    - name: project_id
+      type: integer
diff --git a/integration/tests/sqlite/index-alter/.gitignore b/integration/tests/sqlite/index-alter/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/index-alter/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/index-alter/Makefile b/integration/tests/sqlite/index-alter/Makefile
new file mode 100644
index 00000000..d64607d0
--- /dev/null
+++ b/integration/tests/sqlite/index-alter/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-index-alter
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/index-alter/expect.sql b/integration/tests/sqlite/index-alter/expect.sql
new file mode 100644
index 00000000..2776daf7
--- /dev/null
+++ b/integration/tests/sqlite/index-alter/expect.sql
@@ -0,0 +1,2 @@
+drop index idx_users_email;
+create index idx_users_email on users (email);
diff --git a/integration/tests/sqlite/index-alter/fixtures.sql b/integration/tests/sqlite/index-alter/fixtures.sql
new file mode 100644
index 00000000..73e9db51
--- /dev/null
+++ b/integration/tests/sqlite/index-alter/fixtures.sql
@@ -0,0 +1,2 @@
+create table users (id integer primary key not null, email text not null, phone text not null default '');
+create index idx_users_email on users (phone);
diff --git a/integration/tests/sqlite/index-alter/specs/users.yaml b/integration/tests/sqlite/index-alter/specs/users.yaml
new file mode 100644
index 00000000..0f5108bb
--- /dev/null
+++ b/integration/tests/sqlite/index-alter/specs/users.yaml
@@ -0,0 +1,22 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    indexes:
+      - columns: [email]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: email
+        type: text
+        constraints:
+          notNull: true
+      - name: phone
+        type: text
+        constraints:
+          notNull: true
+        default: ""
diff --git a/integration/tests/sqlite/index-create/.gitignore b/integration/tests/sqlite/index-create/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/index-create/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/index-create/Makefile b/integration/tests/sqlite/index-create/Makefile
new file mode 100644
index 00000000..a1cbb30c
--- /dev/null
+++ b/integration/tests/sqlite/index-create/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-index-create
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/index-create/expect.sql b/integration/tests/sqlite/index-create/expect.sql
new file mode 100644
index 00000000..51b79e5e
--- /dev/null
+++ b/integration/tests/sqlite/index-create/expect.sql
@@ -0,0 +1,1 @@
+create index idx_users_email on users (email);
diff --git a/integration/tests/sqlite/index-create/fixtures.sql b/integration/tests/sqlite/index-create/fixtures.sql
new file mode 100644
index 00000000..880cef41
--- /dev/null
+++ b/integration/tests/sqlite/index-create/fixtures.sql
@@ -0,0 +1,1 @@
+create table users (id integer primary key not null, email text not null, phone text not null default '');
diff --git a/integration/tests/sqlite/index-create/specs/users.yaml b/integration/tests/sqlite/index-create/specs/users.yaml
new file mode 100644
index 00000000..0f5108bb
--- /dev/null
+++ b/integration/tests/sqlite/index-create/specs/users.yaml
@@ -0,0 +1,22 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    indexes:
+      - columns: [email]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: email
+        type: text
+        constraints:
+          notNull: true
+      - name: phone
+        type: text
+        constraints:
+          notNull: true
+        default: ""
diff --git a/integration/tests/sqlite/index-drop/.gitignore b/integration/tests/sqlite/index-drop/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/index-drop/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/index-drop/Makefile b/integration/tests/sqlite/index-drop/Makefile
new file mode 100644
index 00000000..8e4e4dfe
--- /dev/null
+++ b/integration/tests/sqlite/index-drop/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-index-drop
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/index-drop/expect.sql b/integration/tests/sqlite/index-drop/expect.sql
new file mode 100644
index 00000000..391aa800
--- /dev/null
+++ b/integration/tests/sqlite/index-drop/expect.sql
@@ -0,0 +1,1 @@
+drop index idx_users_email;
diff --git a/integration/tests/sqlite/index-drop/fixtures.sql b/integration/tests/sqlite/index-drop/fixtures.sql
new file mode 100644
index 00000000..ab282d80
--- /dev/null
+++ b/integration/tests/sqlite/index-drop/fixtures.sql
@@ -0,0 +1,2 @@
+create table users (id integer primary key not null, email text not null, phone text not null default '');
+create index idx_users_email on users (email);
diff --git a/integration/tests/sqlite/index-drop/specs/users.yaml b/integration/tests/sqlite/index-drop/specs/users.yaml
new file mode 100644
index 00000000..c1f24f76
--- /dev/null
+++ b/integration/tests/sqlite/index-drop/specs/users.yaml
@@ -0,0 +1,20 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: email
+        type: text
+        constraints:
+          notNull: true
+      - name: phone
+        type: text
+        constraints:
+          notNull: true
+        default: ""
diff --git a/integration/tests/sqlite/index-named-no-change/.gitignore b/integration/tests/sqlite/index-named-no-change/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/index-named-no-change/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/index-named-no-change/Makefile b/integration/tests/sqlite/index-named-no-change/Makefile
new file mode 100644
index 00000000..3681c7b2
--- /dev/null
+++ b/integration/tests/sqlite/index-named-no-change/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-index-named-no-change
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/index-named-no-change/expect.sql b/integration/tests/sqlite/index-named-no-change/expect.sql
new file mode 100644
index 00000000..f08cc726
--- /dev/null
+++ b/integration/tests/sqlite/index-named-no-change/expect.sql
@@ -0,0 +1,1 @@
+replace into users (id, email) values (1, 'salah@replicated.com');
diff --git a/integration/tests/sqlite/index-named-no-change/fixtures.sql b/integration/tests/sqlite/index-named-no-change/fixtures.sql
new file mode 100644
index 00000000..20f8a08c
--- /dev/null
+++ b/integration/tests/sqlite/index-named-no-change/fixtures.sql
@@ -0,0 +1,2 @@
+create table users (id integer primary key not null, email text not null, phone text not null default '');
+create index custom_idx_users_email on users (email);
diff --git a/integration/tests/sqlite/index-named-no-change/specs/users.yaml b/integration/tests/sqlite/index-named-no-change/specs/users.yaml
new file mode 100644
index 00000000..2ac7b64b
--- /dev/null
+++ b/integration/tests/sqlite/index-named-no-change/specs/users.yaml
@@ -0,0 +1,32 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    indexes:
+      - columns: [email]
+        name: custom_idx_users_email
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: email
+        type: text
+        constraints:
+          notNull: true
+      - name: phone
+        type: text
+        constraints:
+          notNull: true
+        default: ""
+seedData:
+  rows:
+    - columns:
+      - column: id
+        value:
+          int: 1
+      - column: email
+        value:
+          str: salah@replicated.com
diff --git a/integration/tests/sqlite/index-no-change/.gitignore b/integration/tests/sqlite/index-no-change/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/index-no-change/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/index-no-change/Makefile b/integration/tests/sqlite/index-no-change/Makefile
new file mode 100644
index 00000000..30e9bb12
--- /dev/null
+++ b/integration/tests/sqlite/index-no-change/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-index-no-change
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/index-no-change/expect.sql b/integration/tests/sqlite/index-no-change/expect.sql
new file mode 100644
index 00000000..f08cc726
--- /dev/null
+++ b/integration/tests/sqlite/index-no-change/expect.sql
@@ -0,0 +1,1 @@
+replace into users (id, email) values (1, 'salah@replicated.com');
diff --git a/integration/tests/sqlite/index-no-change/fixtures.sql b/integration/tests/sqlite/index-no-change/fixtures.sql
new file mode 100644
index 00000000..ab282d80
--- /dev/null
+++ b/integration/tests/sqlite/index-no-change/fixtures.sql
@@ -0,0 +1,2 @@
+create table users (id integer primary key not null, email text not null, phone text not null default '');
+create index idx_users_email on users (email);
diff --git a/integration/tests/sqlite/index-no-change/specs/users.yaml b/integration/tests/sqlite/index-no-change/specs/users.yaml
new file mode 100644
index 00000000..4fae418e
--- /dev/null
+++ b/integration/tests/sqlite/index-no-change/specs/users.yaml
@@ -0,0 +1,31 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    indexes:
+      - columns: [email]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: email
+        type: text
+        constraints:
+          notNull: true
+      - name: phone
+        type: text
+        constraints:
+          notNull: true
+        default: ""
+seedData:
+  rows:
+    - columns:
+      - column: id
+        value:
+          int: 1
+      - column: email
+        value:
+          str: salah@replicated.com
diff --git a/integration/tests/sqlite/not-null-with-default/Makefile b/integration/tests/sqlite/not-null-with-default/Makefile
index d6ba94a2..d22693b3 100644
--- a/integration/tests/sqlite/not-null-with-default/Makefile
+++ b/integration/tests/sqlite/not-null-with-default/Makefile
@@ -1,4 +1,4 @@
 include ../common.mk
 
-TEST_NAME := sqlite-not-null
+TEST_NAME := sqlite-not-null-with-default
 SPEC_FILE := ./specs/projects.yaml
diff --git a/integration/tests/sqlite/not-null-with-default/expect.sql b/integration/tests/sqlite/not-null-with-default/expect.sql
index 5d054976..f38fe655 100644
--- a/integration/tests/sqlite/not-null-with-default/expect.sql
+++ b/integration/tests/sqlite/not-null-with-default/expect.sql
@@ -1,6 +1,6 @@
 begin transaction;
-alter table `projects` rename to `projects_4af4d804ad6edd0ca84e50bf32fa5413e3542e2a26d6076851ef1fee7318aadf`;
-create table `projects` (`id` int (11), `name` varchar (255) not null default 'name me', `icon_uri` varchar (255), primary key (`id`));
-insert into projects (id, name, icon_uri) select id, name, icon_uri from projects_4af4d804ad6edd0ca84e50bf32fa5413e3542e2a26d6076851ef1fee7318aadf;
-drop table projects_4af4d804ad6edd0ca84e50bf32fa5413e3542e2a26d6076851ef1fee7318aadf;
+alter table "projects" rename to "projects_af391bf9bfeed3d6608216e29e9b14f7ad57b637cb9de9598ed4135294b7fe99";
+create table "projects" ("id" integer not null, "name" text not null default 'unnamed', "icon_uri" text, primary key ("id"));
+insert into projects (id, name, icon_uri) select id, name, icon_uri from projects_af391bf9bfeed3d6608216e29e9b14f7ad57b637cb9de9598ed4135294b7fe99;
+drop table projects_af391bf9bfeed3d6608216e29e9b14f7ad57b637cb9de9598ed4135294b7fe99;
 commit;
diff --git a/integration/tests/sqlite/not-null-with-default/fixtures.sql b/integration/tests/sqlite/not-null-with-default/fixtures.sql
index 7bc4dfda..3c1822ac 100644
--- a/integration/tests/sqlite/not-null-with-default/fixtures.sql
+++ b/integration/tests/sqlite/not-null-with-default/fixtures.sql
@@ -1,10 +1,3 @@
-create table users (
-  id integer primary key not null,
-  email varchar(255) not null
-);
+create table users (id integer primary key not null, email text not null);
 
-create table projects (
-  id integer primary key not null,
-  name varchar(255),
-  icon_uri varchar(255)
-);
+create table projects (id integer primary key not null, name text, icon_uri text);
diff --git a/integration/tests/sqlite/not-null-with-default/specs/projects.yaml b/integration/tests/sqlite/not-null-with-default/specs/projects.yaml
index d659bd68..6a3ed2e8 100644
--- a/integration/tests/sqlite/not-null-with-default/specs/projects.yaml
+++ b/integration/tests/sqlite/not-null-with-default/specs/projects.yaml
@@ -6,11 +6,13 @@ schema:
     columns:
       - name: id
         type: integer
+        constraints:
+          notNull: true
       - name: name
-        type: varchar(255)
-        default: "name me"
+        type: text
+        default: "unnamed"
         constraints:
           notNull: true
       - name: icon_uri
-        type: varchar(255)
+        type: text
 
diff --git a/integration/tests/sqlite/not-null/expect.sql b/integration/tests/sqlite/not-null/expect.sql
index beec6154..c26f8c3d 100644
--- a/integration/tests/sqlite/not-null/expect.sql
+++ b/integration/tests/sqlite/not-null/expect.sql
@@ -1,6 +1,6 @@
 begin transaction;
-alter table `projects` rename to `projects_dbb570ff3221477e0d90f5b378ea3402bb752984d035a84ba65589f5a96aeda6`;
-create table `projects` (`id` int (11) not null, `name` varchar (255) not null, `icon_uri` varchar (255), primary key (`id`));
-insert into projects (id, name, icon_uri) select id, name, icon_uri from projects_dbb570ff3221477e0d90f5b378ea3402bb752984d035a84ba65589f5a96aeda6;
-drop table projects_dbb570ff3221477e0d90f5b378ea3402bb752984d035a84ba65589f5a96aeda6;
+alter table "projects" rename to "projects_c1372ea07c32b6c87d5e43d073ec5c3852c079e383666cf9c203fcb13dda0d39";
+create table "projects" ("id" integer not null, "name" text not null, "icon_uri" text, primary key ("id"));
+insert into projects (id, name, icon_uri) select id, name, icon_uri from projects_c1372ea07c32b6c87d5e43d073ec5c3852c079e383666cf9c203fcb13dda0d39;
+drop table projects_c1372ea07c32b6c87d5e43d073ec5c3852c079e383666cf9c203fcb13dda0d39;
 commit;
diff --git a/integration/tests/sqlite/not-null/fixtures.sql b/integration/tests/sqlite/not-null/fixtures.sql
index a01a571e..f9700a49 100644
--- a/integration/tests/sqlite/not-null/fixtures.sql
+++ b/integration/tests/sqlite/not-null/fixtures.sql
@@ -1,10 +1,3 @@
-create table users (
-  id integer primary key not null,
-  email varchar(255) not null
-);
+create table users (id integer primary key not null, email text not null);
 
-create table projects (
-  id integer primary key not null,
-  name varchar(255) not null,
-  icon_uri varchar(255) not null
-);
+create table projects (id integer primary key not null, name text not null, icon_uri text not null);
diff --git a/integration/tests/sqlite/not-null/specs/projects.yaml b/integration/tests/sqlite/not-null/specs/projects.yaml
index 13acbec8..049da79c 100644
--- a/integration/tests/sqlite/not-null/specs/projects.yaml
+++ b/integration/tests/sqlite/not-null/specs/projects.yaml
@@ -9,9 +9,8 @@ schema:
         constraints:
           notNull: true
       - name: name
-        type: varchar(255)
+        type: text
         constraints:
           notNull: true
       - name: icon_uri
-        type: varchar(255)
-
+        type: text
diff --git a/integration/tests/sqlite/primary-key-add/expect.sql b/integration/tests/sqlite/primary-key-add/expect.sql
index 774e410a..b22f8a0d 100644
--- a/integration/tests/sqlite/primary-key-add/expect.sql
+++ b/integration/tests/sqlite/primary-key-add/expect.sql
@@ -1,1 +1,6 @@
-alter table `user_projects` add constraint `user_projects_pkey` primary key (`user_id`, `project_id`);
+begin transaction;
+alter table "user_projects" rename to "user_projects_a0870d4dbd1f49995bcf4142bed53756cfb850fa68e9ae0dfdd29980264e9793";
+create table "user_projects" ("user_id" integer not null, "project_id" integer not null, primary key ("user_id", "project_id"));
+insert into user_projects (user_id, project_id) select user_id, project_id from user_projects_a0870d4dbd1f49995bcf4142bed53756cfb850fa68e9ae0dfdd29980264e9793;
+drop table user_projects_a0870d4dbd1f49995bcf4142bed53756cfb850fa68e9ae0dfdd29980264e9793;
+commit;
diff --git a/integration/tests/sqlite/primary-key-add/fixtures.sql b/integration/tests/sqlite/primary-key-add/fixtures.sql
index 9dcb19f1..c7647f3f 100644
--- a/integration/tests/sqlite/primary-key-add/fixtures.sql
+++ b/integration/tests/sqlite/primary-key-add/fixtures.sql
@@ -1,4 +1,1 @@
-create table user_projects (
-  user_id integer not null,
-  project_id integer not null
-);
+create table user_projects (user_id integer not null, project_id integer not null);
diff --git a/integration/tests/sqlite/primary-key-alter/.gitignore b/integration/tests/sqlite/primary-key-alter/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/primary-key-alter/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/primary-key-alter/Makefile b/integration/tests/sqlite/primary-key-alter/Makefile
new file mode 100644
index 00000000..18a1d680
--- /dev/null
+++ b/integration/tests/sqlite/primary-key-alter/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-primary-key-alter
+SPEC_FILE := ./specs/user_projects.yaml
diff --git a/integration/tests/sqlite/primary-key-alter/expect.sql b/integration/tests/sqlite/primary-key-alter/expect.sql
new file mode 100644
index 00000000..b22f8a0d
--- /dev/null
+++ b/integration/tests/sqlite/primary-key-alter/expect.sql
@@ -0,0 +1,6 @@
+begin transaction;
+alter table "user_projects" rename to "user_projects_a0870d4dbd1f49995bcf4142bed53756cfb850fa68e9ae0dfdd29980264e9793";
+create table "user_projects" ("user_id" integer not null, "project_id" integer not null, primary key ("user_id", "project_id"));
+insert into user_projects (user_id, project_id) select user_id, project_id from user_projects_a0870d4dbd1f49995bcf4142bed53756cfb850fa68e9ae0dfdd29980264e9793;
+drop table user_projects_a0870d4dbd1f49995bcf4142bed53756cfb850fa68e9ae0dfdd29980264e9793;
+commit;
diff --git a/integration/tests/sqlite/primary-key-alter/fixtures.sql b/integration/tests/sqlite/primary-key-alter/fixtures.sql
new file mode 100644
index 00000000..6a231ad3
--- /dev/null
+++ b/integration/tests/sqlite/primary-key-alter/fixtures.sql
@@ -0,0 +1,1 @@
+create table user_projects (user_id integer not null, project_id integer not null, primary key (user_id));
diff --git a/integration/tests/sqlite/primary-key-alter/specs/user_projects.yaml b/integration/tests/sqlite/primary-key-alter/specs/user_projects.yaml
new file mode 100644
index 00000000..374b5abf
--- /dev/null
+++ b/integration/tests/sqlite/primary-key-alter/specs/user_projects.yaml
@@ -0,0 +1,16 @@
+database: schemahero
+name: user_projects
+schema:
+  sqlite:
+    primaryKey:
+    - user_id
+    - project_id
+    columns:
+      - name: user_id
+        type: integer
+        constraints:
+          notNull: true
+      - name: project_id
+        type: integer
+        constraints:
+          notNull: true
diff --git a/integration/tests/sqlite/primary-key-drop/expect.sql b/integration/tests/sqlite/primary-key-drop/expect.sql
index 87c3d7ac..d7ad0b6b 100644
--- a/integration/tests/sqlite/primary-key-drop/expect.sql
+++ b/integration/tests/sqlite/primary-key-drop/expect.sql
@@ -1,1 +1,6 @@
-alter table `user_projects` drop primary key;
+begin transaction;
+alter table "user_projects" rename to "user_projects_e9b9cedb5139e31de07edaaf8e769c995e6ad4d33e44dc1bc6e647823ff82692";
+create table "user_projects" ("user_id" integer not null, "project_id" integer not null);
+insert into user_projects (user_id, project_id) select user_id, project_id from user_projects_e9b9cedb5139e31de07edaaf8e769c995e6ad4d33e44dc1bc6e647823ff82692;
+drop table user_projects_e9b9cedb5139e31de07edaaf8e769c995e6ad4d33e44dc1bc6e647823ff82692;
+commit;
diff --git a/integration/tests/sqlite/primary-key-drop/fixtures.sql b/integration/tests/sqlite/primary-key-drop/fixtures.sql
index ab543965..e35e175f 100644
--- a/integration/tests/sqlite/primary-key-drop/fixtures.sql
+++ b/integration/tests/sqlite/primary-key-drop/fixtures.sql
@@ -1,5 +1,1 @@
-create table user_projects (
-  user_id integer not null,
-  project_id integer not null,
-  primary key (user_id, project_id)
-);
+create table user_projects (user_id integer not null, project_id integer not null, primary key (user_id, project_id));
diff --git a/integration/tests/sqlite/primary-key-drop/specs/user_projects.yaml b/integration/tests/sqlite/primary-key-drop/specs/user_projects.yaml
index 89c210fa..855577d7 100644
--- a/integration/tests/sqlite/primary-key-drop/specs/user_projects.yaml
+++ b/integration/tests/sqlite/primary-key-drop/specs/user_projects.yaml
@@ -4,10 +4,10 @@ schema:
   sqlite:
     columns:
       - name: user_id
-        type: int
+        type: integer
         constraints:
           notNull: true
       - name: project_id
-        type: int
+        type: integer
         constraints:
           notNull: true
diff --git a/integration/tests/sqlite/seed-with-many-rows/.gitignore b/integration/tests/sqlite/seed-with-many-rows/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/seed-with-many-rows/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/seed-with-many-rows/Makefile b/integration/tests/sqlite/seed-with-many-rows/Makefile
new file mode 100644
index 00000000..96bbb614
--- /dev/null
+++ b/integration/tests/sqlite/seed-with-many-rows/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-seed-with-many-rows
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/seed-with-many-rows/expect.sql b/integration/tests/sqlite/seed-with-many-rows/expect.sql
new file mode 100644
index 00000000..59e74918
--- /dev/null
+++ b/integration/tests/sqlite/seed-with-many-rows/expect.sql
@@ -0,0 +1,5 @@
+create table "users" ("id" integer not null, "login" text, "name" text not null, primary key ("id"));
+replace into users (id, login, name) values (1, 'test', 'test2');
+replace into users (id, login, name) values (2, 'other', 'test2');
+replace into users (id, login, name) values (3, 'yet', 'someone');
+replace into users (id, login, name) values (4, 'another', 'more');
diff --git a/integration/tests/sqlite/seed-with-many-rows/fixtures.sql b/integration/tests/sqlite/seed-with-many-rows/fixtures.sql
new file mode 100644
index 00000000..87633745
--- /dev/null
+++ b/integration/tests/sqlite/seed-with-many-rows/fixtures.sql
@@ -0,0 +1,1 @@
+create table other (id integer primary key not null, something text not null);
diff --git a/integration/tests/sqlite/seed-with-many-rows/specs/users.yaml b/integration/tests/sqlite/seed-with-many-rows/specs/users.yaml
new file mode 100644
index 00000000..77d7cf6c
--- /dev/null
+++ b/integration/tests/sqlite/seed-with-many-rows/specs/users.yaml
@@ -0,0 +1,59 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: login
+        type: text
+      - name: name
+        type: text
+        constraints:
+          notNull: true
+seedData:
+  rows:
+    - columns:
+      - column: id
+        value:
+          int: 1
+      - column: login
+        value:
+          str: test
+      - column: name
+        value:
+          str: test2
+    - columns:
+      - column: id
+        value:
+          int: 2
+      - column: login
+        value:
+          str: other
+      - column: name
+        value:
+          str: test2
+    - columns:
+      - column: id
+        value:
+          int: 3
+      - column: login
+        value:
+          str: yet
+      - column: name
+        value:
+          str: someone
+    - columns:
+      - column: id
+        value:
+          int: 4
+      - column: login
+        value:
+          str: another
+      - column: name
+        value:
+          str: more
diff --git a/integration/tests/sqlite/table-column-name/Makefile b/integration/tests/sqlite/table-column-name/Makefile
deleted file mode 100644
index 9ecd7c9d..00000000
--- a/integration/tests/sqlite/table-column-name/Makefile
+++ /dev/null
@@ -1,4 +0,0 @@
-include ../common.mk
-
-TEST_NAME := sqlite-table-column-name
-SPEC_FILE := ./specs/table_a.yaml
diff --git a/integration/tests/sqlite/table-column-name/expect.sql b/integration/tests/sqlite/table-column-name/expect.sql
deleted file mode 100644
index 8b137891..00000000
--- a/integration/tests/sqlite/table-column-name/expect.sql
+++ /dev/null
@@ -1,1 +0,0 @@
-
diff --git a/integration/tests/sqlite/table-column-name/fixtures.sql b/integration/tests/sqlite/table-column-name/fixtures.sql
deleted file mode 100644
index a0dbe1e5..00000000
--- a/integration/tests/sqlite/table-column-name/fixtures.sql
+++ /dev/null
@@ -1,1 +0,0 @@
-CREATE TABLE `abcdefg` (`source_hash` text not null, `abcdefg` timestamp not null, primary key (`source_hash`));
\ No newline at end of file
diff --git a/integration/tests/sqlite/table-column-name/specs/table_a.yaml b/integration/tests/sqlite/table-column-name/specs/table_a.yaml
deleted file mode 100644
index 1922cbf8..00000000
--- a/integration/tests/sqlite/table-column-name/specs/table_a.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-database: schemahero
-name: abcdefg
-schema:
-  sqlite:
-    primaryKey:
-    - source_hash
-    columns:
-    - name: source_hash
-      type: text
-      constraints:
-        notNull: true
-    - name: abcdefg
-      type: timestamp
-      constraints:
-        notNull: true
\ No newline at end of file
diff --git a/integration/tests/sqlite/table-create-strict/.gitignore b/integration/tests/sqlite/table-create-strict/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/table-create-strict/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/table-create-strict/Makefile b/integration/tests/sqlite/table-create-strict/Makefile
new file mode 100644
index 00000000..fbd815f8
--- /dev/null
+++ b/integration/tests/sqlite/table-create-strict/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-table-create-strict
+SPEC_FILE := ./specs
diff --git a/integration/tests/sqlite/table-create-strict/expect.sql b/integration/tests/sqlite/table-create-strict/expect.sql
new file mode 100644
index 00000000..0b3746f8
--- /dev/null
+++ b/integration/tests/sqlite/table-create-strict/expect.sql
@@ -0,0 +1,1 @@
+create table "users" ("id" integer not null, "login" text, "name" text not null default 'salah', "ts_1" integer, "ts_2" real, "ts_3" text, primary key ("id")) strict;
diff --git a/integration/tests/sqlite/table-create-strict/fixtures.sql b/integration/tests/sqlite/table-create-strict/fixtures.sql
new file mode 100644
index 00000000..87633745
--- /dev/null
+++ b/integration/tests/sqlite/table-create-strict/fixtures.sql
@@ -0,0 +1,1 @@
+create table other (id integer primary key not null, something text not null);
diff --git a/integration/tests/sqlite/table-create-strict/specs/other.yaml b/integration/tests/sqlite/table-create-strict/specs/other.yaml
new file mode 100644
index 00000000..9fa6231f
--- /dev/null
+++ b/integration/tests/sqlite/table-create-strict/specs/other.yaml
@@ -0,0 +1,15 @@
+database: schemahero
+name: other
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: something
+        type: text
+        constraints:
+          notNull: true
diff --git a/integration/tests/sqlite/table-create-strict/specs/users.yaml b/integration/tests/sqlite/table-create-strict/specs/users.yaml
new file mode 100644
index 00000000..fcdf42ae
--- /dev/null
+++ b/integration/tests/sqlite/table-create-strict/specs/users.yaml
@@ -0,0 +1,25 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    strict: true
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: login
+        type: text
+      - name: name
+        type: text
+        constraints:
+          notNull: true
+        default: salah
+      - name: ts_1
+        type: integer
+      - name: ts_2
+        type: real
+      - name: ts_3
+        type: text
diff --git a/integration/tests/sqlite/table-create-with-index/.gitignore b/integration/tests/sqlite/table-create-with-index/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/table-create-with-index/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/table-create-with-index/Makefile b/integration/tests/sqlite/table-create-with-index/Makefile
new file mode 100644
index 00000000..0471f253
--- /dev/null
+++ b/integration/tests/sqlite/table-create-with-index/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-table-create-with-index
+SPEC_FILE := ./specs
diff --git a/integration/tests/sqlite/table-create-with-index/expect.sql b/integration/tests/sqlite/table-create-with-index/expect.sql
new file mode 100644
index 00000000..8bf36676
--- /dev/null
+++ b/integration/tests/sqlite/table-create-with-index/expect.sql
@@ -0,0 +1,2 @@
+create table "users" ("id" integer not null, "email" text not null, "name" text not null default 'salah', "ts_1" integer, "ts_2" real, "ts_3" text, primary key ("id"));
+create index idx_users_email on users (email);
diff --git a/integration/tests/sqlite/table-create-with-index/fixtures.sql b/integration/tests/sqlite/table-create-with-index/fixtures.sql
new file mode 100644
index 00000000..87633745
--- /dev/null
+++ b/integration/tests/sqlite/table-create-with-index/fixtures.sql
@@ -0,0 +1,1 @@
+create table other (id integer primary key not null, something text not null);
diff --git a/integration/tests/sqlite/table-create-with-index/specs/other.yaml b/integration/tests/sqlite/table-create-with-index/specs/other.yaml
new file mode 100644
index 00000000..9fa6231f
--- /dev/null
+++ b/integration/tests/sqlite/table-create-with-index/specs/other.yaml
@@ -0,0 +1,15 @@
+database: schemahero
+name: other
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: something
+        type: text
+        constraints:
+          notNull: true
diff --git a/integration/tests/sqlite/table-create-with-index/specs/users.yaml b/integration/tests/sqlite/table-create-with-index/specs/users.yaml
new file mode 100644
index 00000000..95bfa33c
--- /dev/null
+++ b/integration/tests/sqlite/table-create-with-index/specs/users.yaml
@@ -0,0 +1,28 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    indexes:
+      - columns: [email]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: email
+        type: text
+        constraints:
+          notNull: true
+      - name: name
+        type: text
+        constraints:
+          notNull: true
+        default: salah
+      - name: ts_1
+        type: integer
+      - name: ts_2
+        type: real
+      - name: ts_3
+        type: text
diff --git a/integration/tests/sqlite/table-create-with-unique-index/.gitignore b/integration/tests/sqlite/table-create-with-unique-index/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/table-create-with-unique-index/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/table-create-with-unique-index/Makefile b/integration/tests/sqlite/table-create-with-unique-index/Makefile
new file mode 100644
index 00000000..6dbf01fb
--- /dev/null
+++ b/integration/tests/sqlite/table-create-with-unique-index/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-table-create-with-unique-index
+SPEC_FILE := ./specs
diff --git a/integration/tests/sqlite/table-create-with-unique-index/expect.sql b/integration/tests/sqlite/table-create-with-unique-index/expect.sql
new file mode 100644
index 00000000..e9e3401b
--- /dev/null
+++ b/integration/tests/sqlite/table-create-with-unique-index/expect.sql
@@ -0,0 +1,2 @@
+create table "users" ("id" integer not null, "email" text not null, "name" text not null default 'salah', "ts_1" integer, "ts_2" real, "ts_3" text, primary key ("id"));
+create unique index idx_users_email on users (email);
diff --git a/integration/tests/sqlite/table-create-with-unique-index/fixtures.sql b/integration/tests/sqlite/table-create-with-unique-index/fixtures.sql
new file mode 100644
index 00000000..87633745
--- /dev/null
+++ b/integration/tests/sqlite/table-create-with-unique-index/fixtures.sql
@@ -0,0 +1,1 @@
+create table other (id integer primary key not null, something text not null);
diff --git a/integration/tests/sqlite/table-create-with-unique-index/specs/other.yaml b/integration/tests/sqlite/table-create-with-unique-index/specs/other.yaml
new file mode 100644
index 00000000..9fa6231f
--- /dev/null
+++ b/integration/tests/sqlite/table-create-with-unique-index/specs/other.yaml
@@ -0,0 +1,15 @@
+database: schemahero
+name: other
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: something
+        type: text
+        constraints:
+          notNull: true
diff --git a/integration/tests/sqlite/table-create-with-unique-index/specs/users.yaml b/integration/tests/sqlite/table-create-with-unique-index/specs/users.yaml
new file mode 100644
index 00000000..e282bf7a
--- /dev/null
+++ b/integration/tests/sqlite/table-create-with-unique-index/specs/users.yaml
@@ -0,0 +1,29 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    indexes:
+      - columns: [email]
+        isUnique: true
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: email
+        type: text
+        constraints:
+          notNull: true
+      - name: name
+        type: text
+        constraints:
+          notNull: true
+        default: salah
+      - name: ts_1
+        type: integer
+      - name: ts_2
+        type: real
+      - name: ts_3
+        type: text
diff --git a/integration/tests/sqlite/table-create/.gitignore b/integration/tests/sqlite/table-create/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/table-create/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/create-table/Makefile b/integration/tests/sqlite/table-create/Makefile
similarity index 56%
rename from integration/tests/sqlite/create-table/Makefile
rename to integration/tests/sqlite/table-create/Makefile
index 67b65feb..da0ebc0f 100644
--- a/integration/tests/sqlite/create-table/Makefile
+++ b/integration/tests/sqlite/table-create/Makefile
@@ -1,4 +1,4 @@
 include ../common.mk
 
-TEST_NAME := sqlite-create-table
+TEST_NAME := sqlite-table-create
 SPEC_FILE := ./specs
diff --git a/integration/tests/sqlite/table-create/expect.sql b/integration/tests/sqlite/table-create/expect.sql
new file mode 100644
index 00000000..227f0b13
--- /dev/null
+++ b/integration/tests/sqlite/table-create/expect.sql
@@ -0,0 +1,3 @@
+replace into other (id, something) values (1, 'one');
+replace into other (id, something) values (2, 'two');
+create table "users" ("id" integer not null, "login" text, "name" text not null default 'salah', "ts_1" integer, "ts_2" real, "ts_3" text, primary key ("id"));
diff --git a/integration/tests/sqlite/table-create/fixtures.sql b/integration/tests/sqlite/table-create/fixtures.sql
new file mode 100644
index 00000000..38b19c1e
--- /dev/null
+++ b/integration/tests/sqlite/table-create/fixtures.sql
@@ -0,0 +1,4 @@
+create table other (id integer primary key not null, something text not null);
+
+insert into other (id, something) values (1, 'one hundred');
+insert into other (id, something) values (2, 'two hundred');
\ No newline at end of file
diff --git a/integration/tests/sqlite/create-table/specs/other.yaml b/integration/tests/sqlite/table-create/specs/other.yaml
similarity index 88%
rename from integration/tests/sqlite/create-table/specs/other.yaml
rename to integration/tests/sqlite/table-create/specs/other.yaml
index dcfd437f..86470e54 100644
--- a/integration/tests/sqlite/create-table/specs/other.yaml
+++ b/integration/tests/sqlite/table-create/specs/other.yaml
@@ -7,8 +7,10 @@ schema:
     columns:
       - name: id
         type: integer
+        constraints:
+          notNull: true
       - name: something
-        type: varchar(255)
+        type: text
         constraints:
           notNull: true
 seedData:
diff --git a/integration/tests/sqlite/table-create/specs/users.yaml b/integration/tests/sqlite/table-create/specs/users.yaml
new file mode 100644
index 00000000..675d593b
--- /dev/null
+++ b/integration/tests/sqlite/table-create/specs/users.yaml
@@ -0,0 +1,24 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: login
+        type: text
+      - name: name
+        type: text
+        constraints:
+          notNull: true
+        default: salah
+      - name: ts_1
+        type: integer
+      - name: ts_2
+        type: real
+      - name: ts_3
+        type: text
diff --git a/integration/tests/sqlite/unique-constraint-add/.gitignore b/integration/tests/sqlite/unique-constraint-add/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-add/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/unique-constraint-add/Makefile b/integration/tests/sqlite/unique-constraint-add/Makefile
new file mode 100644
index 00000000..fc7acdab
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-add/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-unique-constraint-add
+SPEC_FILE := ./specs/projects.yaml
diff --git a/integration/tests/sqlite/unique-constraint-add/expect.sql b/integration/tests/sqlite/unique-constraint-add/expect.sql
new file mode 100644
index 00000000..9dc85041
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-add/expect.sql
@@ -0,0 +1,1 @@
+create unique index idx_projects_name on projects (name);
diff --git a/integration/tests/sqlite/unique-constraint-add/fixtures.sql b/integration/tests/sqlite/unique-constraint-add/fixtures.sql
new file mode 100644
index 00000000..fa4fc416
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-add/fixtures.sql
@@ -0,0 +1,1 @@
+create table projects (id integer primary key not null, name text not null);
diff --git a/integration/tests/sqlite/unique-constraint-add/specs/projects.yaml b/integration/tests/sqlite/unique-constraint-add/specs/projects.yaml
new file mode 100644
index 00000000..152bc4e4
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-add/specs/projects.yaml
@@ -0,0 +1,18 @@
+database: schemahero
+name: projects
+schema:
+  sqlite:
+    primaryKey:
+    - id
+    indexes:
+    - columns: [name]
+      isUnique: true
+    columns:
+    - name: id
+      type: integer
+      constraints:
+        notNull: true
+    - name: name
+      type: text
+      constraints:
+        notNull: true
diff --git a/integration/tests/sqlite/unique-constraint-alter/.gitignore b/integration/tests/sqlite/unique-constraint-alter/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-alter/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/unique-constraint-alter/Makefile b/integration/tests/sqlite/unique-constraint-alter/Makefile
new file mode 100644
index 00000000..c0deb17b
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-alter/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-unique-constraint-alter
+SPEC_FILE := ./specs/projects.yaml
diff --git a/integration/tests/sqlite/unique-constraint-alter/expect.sql b/integration/tests/sqlite/unique-constraint-alter/expect.sql
new file mode 100644
index 00000000..46cd8902
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-alter/expect.sql
@@ -0,0 +1,7 @@
+begin transaction;
+alter table "projects" rename to "projects_0342a1ab69fe0887ec4f83d0a5c1a3678e4b4e2c9d631b82549f55cbaea90b75";
+create table "projects" ("id" integer not null, "email" text not null, primary key ("id"));
+create unique index idx_projects_email on projects (email);
+insert into projects (id, email) select id, email from projects_0342a1ab69fe0887ec4f83d0a5c1a3678e4b4e2c9d631b82549f55cbaea90b75;
+drop table projects_0342a1ab69fe0887ec4f83d0a5c1a3678e4b4e2c9d631b82549f55cbaea90b75;
+commit;
diff --git a/integration/tests/sqlite/unique-constraint-alter/fixtures.sql b/integration/tests/sqlite/unique-constraint-alter/fixtures.sql
new file mode 100644
index 00000000..6342411d
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-alter/fixtures.sql
@@ -0,0 +1,1 @@
+create table projects (id integer primary key not null, email text not null, UNIQUE (email));
diff --git a/integration/tests/sqlite/unique-constraint-alter/specs/projects.yaml b/integration/tests/sqlite/unique-constraint-alter/specs/projects.yaml
new file mode 100644
index 00000000..a90ca1ef
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-alter/specs/projects.yaml
@@ -0,0 +1,17 @@
+database: schemahero
+name: projects
+schema:
+  sqlite:
+    primaryKey: [id]
+    indexes:
+    - columns: [email]
+      isUnique: true
+    columns:
+    - name: id
+      type: integer
+      constraints:
+        notNull: true
+    - name: email
+      type: text
+      constraints:
+        notNull: true
diff --git a/integration/tests/sqlite/unique-constraint-drop/.gitignore b/integration/tests/sqlite/unique-constraint-drop/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-drop/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/unique-constraint-drop/Makefile b/integration/tests/sqlite/unique-constraint-drop/Makefile
new file mode 100644
index 00000000..6bdb6d4c
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-drop/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-unique-constraint-drop
+SPEC_FILE := ./specs/projects.yaml
diff --git a/integration/tests/sqlite/unique-constraint-drop/expect.sql b/integration/tests/sqlite/unique-constraint-drop/expect.sql
new file mode 100644
index 00000000..12b46b9e
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-drop/expect.sql
@@ -0,0 +1,6 @@
+begin transaction;
+alter table "projects" rename to "projects_8c15d51ea0c81cfa97744f5daa3e4e60238741787765a30e610359e566dbfa1c";
+create table "projects" ("id" integer not null, "name" text not null, primary key ("id"));
+insert into projects (id, name) select id, name from projects_8c15d51ea0c81cfa97744f5daa3e4e60238741787765a30e610359e566dbfa1c;
+drop table projects_8c15d51ea0c81cfa97744f5daa3e4e60238741787765a30e610359e566dbfa1c;
+commit;
diff --git a/integration/tests/sqlite/unique-constraint-drop/fixtures.sql b/integration/tests/sqlite/unique-constraint-drop/fixtures.sql
new file mode 100644
index 00000000..b24096fc
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-drop/fixtures.sql
@@ -0,0 +1,1 @@
+create table projects (id integer primary key not null, name text not null, UNIQUE (name));
diff --git a/integration/tests/sqlite/unique-constraint-drop/specs/projects.yaml b/integration/tests/sqlite/unique-constraint-drop/specs/projects.yaml
new file mode 100644
index 00000000..f18b6c85
--- /dev/null
+++ b/integration/tests/sqlite/unique-constraint-drop/specs/projects.yaml
@@ -0,0 +1,14 @@
+database: schemahero
+name: projects
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+    - name: id
+      type: integer
+      constraints:
+        notNull: true
+    - name: name
+      type: text
+      constraints:
+        notNull: true
diff --git a/integration/tests/sqlite/unique-index-alter/.gitignore b/integration/tests/sqlite/unique-index-alter/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-alter/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/unique-index-alter/Makefile b/integration/tests/sqlite/unique-index-alter/Makefile
new file mode 100644
index 00000000..45f36188
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-alter/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-unique-index-alter
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/unique-index-alter/expect.sql b/integration/tests/sqlite/unique-index-alter/expect.sql
new file mode 100644
index 00000000..f2b27cbf
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-alter/expect.sql
@@ -0,0 +1,2 @@
+drop index if exists idx_users_email;
+create unique index idx_users_email on users (email);
diff --git a/integration/tests/sqlite/unique-index-alter/fixtures.sql b/integration/tests/sqlite/unique-index-alter/fixtures.sql
new file mode 100644
index 00000000..52107ceb
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-alter/fixtures.sql
@@ -0,0 +1,2 @@
+create table users (id integer primary key not null, email text not null, phone text not null default '');
+create unique index idx_users_email on users (phone);
diff --git a/integration/tests/sqlite/unique-index-alter/specs/users.yaml b/integration/tests/sqlite/unique-index-alter/specs/users.yaml
new file mode 100644
index 00000000..57c2d7fc
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-alter/specs/users.yaml
@@ -0,0 +1,23 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    indexes:
+      - columns: [email]
+        isUnique: true
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: email
+        type: text
+        constraints:
+          notNull: true
+      - name: phone
+        type: text
+        constraints:
+          notNull: true
+        default: ""
diff --git a/integration/tests/sqlite/unique-index-create/.gitignore b/integration/tests/sqlite/unique-index-create/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-create/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/unique-index-create/Makefile b/integration/tests/sqlite/unique-index-create/Makefile
new file mode 100644
index 00000000..e40201f1
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-create/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-unique-index-create
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/unique-index-create/expect.sql b/integration/tests/sqlite/unique-index-create/expect.sql
new file mode 100644
index 00000000..dfa8b495
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-create/expect.sql
@@ -0,0 +1,1 @@
+create unique index idx_users_email on users (email);
diff --git a/integration/tests/sqlite/unique-index-create/fixtures.sql b/integration/tests/sqlite/unique-index-create/fixtures.sql
new file mode 100644
index 00000000..880cef41
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-create/fixtures.sql
@@ -0,0 +1,1 @@
+create table users (id integer primary key not null, email text not null, phone text not null default '');
diff --git a/integration/tests/sqlite/unique-index-create/specs/users.yaml b/integration/tests/sqlite/unique-index-create/specs/users.yaml
new file mode 100644
index 00000000..57c2d7fc
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-create/specs/users.yaml
@@ -0,0 +1,23 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    indexes:
+      - columns: [email]
+        isUnique: true
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: email
+        type: text
+        constraints:
+          notNull: true
+      - name: phone
+        type: text
+        constraints:
+          notNull: true
+        default: ""
diff --git a/integration/tests/sqlite/unique-index-drop/.gitignore b/integration/tests/sqlite/unique-index-drop/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-drop/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/unique-index-drop/Makefile b/integration/tests/sqlite/unique-index-drop/Makefile
new file mode 100644
index 00000000..c61139d0
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-drop/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-unique-index-drop
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/unique-index-drop/expect.sql b/integration/tests/sqlite/unique-index-drop/expect.sql
new file mode 100644
index 00000000..001fe0f2
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-drop/expect.sql
@@ -0,0 +1,1 @@
+drop index if exists idx_users_email;
diff --git a/integration/tests/sqlite/unique-index-drop/fixtures.sql b/integration/tests/sqlite/unique-index-drop/fixtures.sql
new file mode 100644
index 00000000..17b6cf16
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-drop/fixtures.sql
@@ -0,0 +1,2 @@
+create table users (id integer primary key not null, email text not null, phone text not null default '');
+create unique index idx_users_email on users (email);
diff --git a/integration/tests/sqlite/unique-index-drop/specs/users.yaml b/integration/tests/sqlite/unique-index-drop/specs/users.yaml
new file mode 100644
index 00000000..c1f24f76
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-drop/specs/users.yaml
@@ -0,0 +1,20 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: email
+        type: text
+        constraints:
+          notNull: true
+      - name: phone
+        type: text
+        constraints:
+          notNull: true
+        default: ""
diff --git a/integration/tests/sqlite/unique-index-named-no-change/.gitignore b/integration/tests/sqlite/unique-index-named-no-change/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-named-no-change/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/unique-index-named-no-change/Makefile b/integration/tests/sqlite/unique-index-named-no-change/Makefile
new file mode 100644
index 00000000..1420ae7e
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-named-no-change/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-unique-index-named-no-change
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/unique-index-named-no-change/expect.sql b/integration/tests/sqlite/unique-index-named-no-change/expect.sql
new file mode 100644
index 00000000..f08cc726
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-named-no-change/expect.sql
@@ -0,0 +1,1 @@
+replace into users (id, email) values (1, 'salah@replicated.com');
diff --git a/integration/tests/sqlite/unique-index-named-no-change/fixtures.sql b/integration/tests/sqlite/unique-index-named-no-change/fixtures.sql
new file mode 100644
index 00000000..c995c792
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-named-no-change/fixtures.sql
@@ -0,0 +1,2 @@
+create table users (id integer primary key not null, email text not null, phone text not null default '');
+create unique index custom_idx_users_email on users (email);
diff --git a/integration/tests/sqlite/unique-index-named-no-change/specs/users.yaml b/integration/tests/sqlite/unique-index-named-no-change/specs/users.yaml
new file mode 100644
index 00000000..23b65a58
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-named-no-change/specs/users.yaml
@@ -0,0 +1,33 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    indexes:
+      - columns: [email]
+        isUnique: true
+        name: custom_idx_users_email
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: email
+        type: text
+        constraints:
+          notNull: true
+      - name: phone
+        type: text
+        constraints:
+          notNull: true
+        default: ""
+seedData:
+  rows:
+    - columns:
+      - column: id
+        value:
+          int: 1
+      - column: email
+        value:
+          str: salah@replicated.com
diff --git a/integration/tests/sqlite/unique-index-no-change/.gitignore b/integration/tests/sqlite/unique-index-no-change/.gitignore
new file mode 100644
index 00000000..5baa59d6
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-no-change/.gitignore
@@ -0,0 +1,1 @@
+db
\ No newline at end of file
diff --git a/integration/tests/sqlite/unique-index-no-change/Makefile b/integration/tests/sqlite/unique-index-no-change/Makefile
new file mode 100644
index 00000000..76184e93
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-no-change/Makefile
@@ -0,0 +1,4 @@
+include ../common.mk
+
+TEST_NAME := sqlite-unique-index-no-change
+SPEC_FILE := ./specs/users.yaml
diff --git a/integration/tests/sqlite/unique-index-no-change/expect.sql b/integration/tests/sqlite/unique-index-no-change/expect.sql
new file mode 100644
index 00000000..f08cc726
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-no-change/expect.sql
@@ -0,0 +1,1 @@
+replace into users (id, email) values (1, 'salah@replicated.com');
diff --git a/integration/tests/sqlite/unique-index-no-change/fixtures.sql b/integration/tests/sqlite/unique-index-no-change/fixtures.sql
new file mode 100644
index 00000000..17b6cf16
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-no-change/fixtures.sql
@@ -0,0 +1,2 @@
+create table users (id integer primary key not null, email text not null, phone text not null default '');
+create unique index idx_users_email on users (email);
diff --git a/integration/tests/sqlite/unique-index-no-change/specs/users.yaml b/integration/tests/sqlite/unique-index-no-change/specs/users.yaml
new file mode 100644
index 00000000..e6619311
--- /dev/null
+++ b/integration/tests/sqlite/unique-index-no-change/specs/users.yaml
@@ -0,0 +1,32 @@
+database: schemahero
+name: users
+requires: []
+schema:
+  sqlite:
+    primaryKey: [id]
+    indexes:
+      - columns: [email]
+        isUnique: true
+    columns:
+      - name: id
+        type: integer
+        constraints:
+          notNull: true
+      - name: email
+        type: text
+        constraints:
+          notNull: true
+      - name: phone
+        type: text
+        constraints:
+          notNull: true
+        default: ""
+seedData:
+  rows:
+    - columns:
+      - column: id
+        value:
+          int: 1
+      - column: email
+        value:
+          str: salah@replicated.com
diff --git a/pkg/apis/schemas/v1alpha4/sqlite_types_test.go b/pkg/apis/schemas/v1alpha4/sqlite_types_test.go
new file mode 100644
index 00000000..29009521
--- /dev/null
+++ b/pkg/apis/schemas/v1alpha4/sqlite_types_test.go
@@ -0,0 +1,59 @@
+/*
+Copyright 2019 The SchemaHero Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha4
+
+import (
+	"testing"
+
+	"github.com/onsi/gomega"
+	"gopkg.in/yaml.v2"
+)
+
+func Test_SqliteTypes(t *testing.T) {
+	const fk = `
+isDeleted: false
+columns:
+  - name: id
+    type: integer
+  - name: order_id
+    type: integer
+primaryKey:
+  - id
+foreignKeys:
+  - columns:
+      - order_id
+    references:
+      table: order
+      columns:
+        - id
+`
+
+	g := gomega.NewGomegaWithT(t)
+
+	table := SqliteTableSchema{}
+	err := yaml.Unmarshal([]byte(fk), &table)
+	g.Expect(err).NotTo(gomega.HaveOccurred())
+	g.Expect(table.Columns).To(gomega.HaveLen(2))
+	g.Expect(table.ForeignKeys).To(gomega.HaveLen(1))
+
+	g.Expect(table.ForeignKeys[0].Columns).To(gomega.HaveLen(1))
+	g.Expect(table.ForeignKeys[0].Columns[0]).To(gomega.Equal("order_id"))
+
+	g.Expect(table.ForeignKeys[0].References.Table).To(gomega.Equal("order"))
+	g.Expect(table.ForeignKeys[0].References.Columns).To(gomega.HaveLen(1))
+	g.Expect(table.ForeignKeys[0].References.Columns[0]).To(gomega.Equal("id"))
+}
diff --git a/pkg/database/sqlite/alter_test.go b/pkg/database/sqlite/alter_test.go
new file mode 100644
index 00000000..19926a0a
--- /dev/null
+++ b/pkg/database/sqlite/alter_test.go
@@ -0,0 +1,62 @@
+package sqlite
+
+import (
+	"testing"
+
+	"github.com/schemahero/schemahero/pkg/database/types"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_ColumnsMatch(t *testing.T) {
+	tests := []struct {
+		name   string
+		col1   types.Column
+		col2   types.Column
+		expect bool
+	}{
+		{
+			name: "integers",
+			col1: types.Column{
+				Name:     "a",
+				DataType: "integer",
+			},
+			col2: types.Column{
+				Name:     "a",
+				DataType: "integer",
+			},
+			expect: true,
+		},
+		{
+			name: "reals",
+			col1: types.Column{
+				Name:     "a",
+				DataType: "real",
+			},
+			col2: types.Column{
+				Name:     "a",
+				DataType: "real",
+			},
+			expect: true,
+		},
+		{
+			name: "string and integer",
+			col1: types.Column{
+				Name:     "a",
+				DataType: "string",
+			},
+			col2: types.Column{
+				Name:     "a",
+				DataType: "integer",
+			},
+			expect: false,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			actual := columnsMatch(test.col1, test.col2)
+			assert.Equal(t, test.expect, actual)
+		})
+	}
+}
diff --git a/pkg/database/sqlite/column_test.go b/pkg/database/sqlite/column_test.go
new file mode 100644
index 00000000..cb61d881
--- /dev/null
+++ b/pkg/database/sqlite/column_test.go
@@ -0,0 +1,181 @@
+package sqlite
+
+import (
+	"testing"
+
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+	"github.com/schemahero/schemahero/pkg/database/types"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_columnAsInsert(t *testing.T) {
+	default11 := "11"
+	tests := []struct {
+		name              string
+		column            *schemasv1alpha4.SqliteTableColumn
+		expectedStatement string
+	}{
+		{
+			name: "simple",
+			column: &schemasv1alpha4.SqliteTableColumn{
+				Name: "c",
+				Type: "integer",
+			},
+			expectedStatement: `"c" integer`,
+		},
+		{
+			name: "text",
+			column: &schemasv1alpha4.SqliteTableColumn{
+				Name: "t",
+				Type: "text",
+			},
+			expectedStatement: `"t" text`,
+		},
+		{
+			name: "constraint not null",
+			column: &schemasv1alpha4.SqliteTableColumn{
+				Name: "c",
+				Type: "integer",
+				Constraints: &schemasv1alpha4.SqliteTableColumnConstraints{
+					NotNull: &trueValue,
+				},
+			},
+			expectedStatement: `"c" integer not null`,
+		},
+		{
+			name: "default",
+			column: &schemasv1alpha4.SqliteTableColumn{
+				Name:    "c",
+				Type:    "integer",
+				Default: &default11,
+			},
+			expectedStatement: `"c" integer default '11'`,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			req := require.New(t)
+
+			generatedStatement, err := sqliteColumnAsInsert(test.column)
+			req.NoError(err)
+			assert.Equal(t, test.expectedStatement, generatedStatement)
+		})
+	}
+}
+
+func Test_InsertColumnStatement(t *testing.T) {
+	tests := []struct {
+		name              string
+		tableName         string
+		desiredColumn     *schemasv1alpha4.SqliteTableColumn
+		expectedStatement string
+	}{
+		{
+			name:      "add column",
+			tableName: "t",
+			desiredColumn: &schemasv1alpha4.SqliteTableColumn{
+				Name: "a",
+				Type: "integer",
+			},
+			expectedStatement: `alter table "t" add column "a" integer`,
+		},
+		{
+			name:      "add not null column",
+			tableName: "t",
+			desiredColumn: &schemasv1alpha4.SqliteTableColumn{
+				Name: "a",
+				Type: "integer",
+				Constraints: &schemasv1alpha4.SqliteTableColumnConstraints{
+					NotNull: &trueValue,
+				},
+			},
+			expectedStatement: `alter table "t" add column "a" integer not null`,
+		},
+		{
+			name:      "add null column",
+			tableName: "t",
+			desiredColumn: &schemasv1alpha4.SqliteTableColumn{
+				Name: "a",
+				Type: "integer",
+				Constraints: &schemasv1alpha4.SqliteTableColumnConstraints{
+					NotNull: &falseValue,
+				},
+			},
+			expectedStatement: `alter table "t" add column "a" integer null`,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			req := require.New(t)
+
+			generatedStatement, err := InsertColumnStatement(test.tableName, test.desiredColumn)
+			req.NoError(err)
+			assert.Equal(t, test.expectedStatement, generatedStatement)
+		})
+	}
+}
+
+func Test_schemaColumnToSqliteColumn(t *testing.T) {
+	defaultText := "sometext"
+	tests := []struct {
+		name           string
+		schemaColumn   *schemasv1alpha4.SqliteTableColumn
+		expectedColumn *types.Column
+	}{
+		{
+			name: "text",
+			schemaColumn: &schemasv1alpha4.SqliteTableColumn{
+				Name: "t",
+				Type: "text",
+			},
+			expectedColumn: &types.Column{
+				Name:          "t",
+				DataType:      "text",
+				ColumnDefault: nil,
+				Constraints:   nil,
+			},
+		},
+		{
+			name: "integer",
+			schemaColumn: &schemasv1alpha4.SqliteTableColumn{
+				Name: "t",
+				Type: "integer",
+			},
+			expectedColumn: &types.Column{
+				Name:          "t",
+				DataType:      "integer",
+				ColumnDefault: nil,
+				Constraints:   nil,
+			},
+		},
+		{
+			name: "text default",
+			schemaColumn: &schemasv1alpha4.SqliteTableColumn{
+				Name:    "t",
+				Type:    "text",
+				Default: &defaultText,
+			},
+			expectedColumn: &types.Column{
+				Name:          "t",
+				DataType:      "text",
+				ColumnDefault: &defaultText,
+				Constraints:   nil,
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			req := require.New(t)
+
+			column, err := schemaColumnToColumn(test.schemaColumn)
+			req.NoError(err)
+			assert.Equal(t, test.expectedColumn, column)
+		})
+	}
+
+}
diff --git a/pkg/database/sqlite/create_test.go b/pkg/database/sqlite/create_test.go
new file mode 100644
index 00000000..4e2bdc3e
--- /dev/null
+++ b/pkg/database/sqlite/create_test.go
@@ -0,0 +1,138 @@
+package sqlite
+
+import (
+	"testing"
+
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_CreateTableStatement(t *testing.T) {
+	tests := []struct {
+		name               string
+		tableSchema        *schemasv1alpha4.SqliteTableSchema
+		tableName          string
+		expectedStatements []string
+	}{
+		{
+			name: "simple",
+			tableSchema: &schemasv1alpha4.SqliteTableSchema{
+				PrimaryKey: []string{
+					"id",
+				},
+				Columns: []*schemasv1alpha4.SqliteTableColumn{
+					{
+						Name: "id",
+						Type: "integer",
+					},
+				},
+			},
+			tableName: "simple",
+			expectedStatements: []string{
+				`create table "simple" ("id" integer, primary key ("id"))`,
+			},
+		},
+		{
+			name: "composite primary key",
+			tableSchema: &schemasv1alpha4.SqliteTableSchema{
+				PrimaryKey: []string{
+					"one",
+					"two",
+				},
+				Columns: []*schemasv1alpha4.SqliteTableColumn{
+					{
+						Name: "one",
+						Type: "integer",
+					},
+					{
+						Name: "two",
+						Type: "integer",
+					},
+					{
+						Name: "three",
+						Type: "text",
+					},
+				},
+			},
+			tableName: "composite_primary_key",
+			expectedStatements: []string{
+				`create table "composite_primary_key" ("one" integer, "two" integer, "three" text, primary key ("one", "two"))`,
+			},
+		},
+		{
+			name: "composite unique index",
+			tableSchema: &schemasv1alpha4.SqliteTableSchema{
+				PrimaryKey: []string{
+					"one",
+				},
+				Indexes: []*schemasv1alpha4.SqliteTableIndex{
+					{
+						Columns:  []string{"two", "three"},
+						IsUnique: true,
+					},
+				},
+				Columns: []*schemasv1alpha4.SqliteTableColumn{
+					{
+						Name: "one",
+						Type: "integer",
+					},
+					{
+						Name: "two",
+						Type: "integer",
+					},
+					{
+						Name: "three",
+						Type: "text",
+					},
+				},
+			},
+			tableName: "composite_unique_index",
+			expectedStatements: []string{
+				`create table "composite_unique_index" ("one" integer, "two" integer, "three" text, primary key ("one"))`,
+				`create unique index idx_composite_unique_index_two_three on composite_unique_index (two, three)`,
+			},
+		},
+		{
+			name: "simple with index",
+			tableSchema: &schemasv1alpha4.SqliteTableSchema{
+				PrimaryKey: []string{
+					"id",
+				},
+				Columns: []*schemasv1alpha4.SqliteTableColumn{
+					{
+						Name: "id",
+						Type: "integer",
+					},
+					{
+						Name: "email",
+						Type: "text",
+					},
+				},
+				Indexes: []*schemasv1alpha4.SqliteTableIndex{
+					{
+						Name:    "idx_email",
+						Columns: []string{"email"},
+					},
+				},
+			},
+			tableName: "simple",
+			expectedStatements: []string{
+				`create table "simple" ("id" integer, "email" text, primary key ("id"))`,
+				`create index idx_email on simple (email)`,
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			req := require.New(t)
+
+			createTableStatements, err := CreateTableStatements(test.tableName, test.tableSchema)
+			req.NoError(err)
+
+			assert.Equal(t, test.expectedStatements, createTableStatements)
+		})
+	}
+}
diff --git a/pkg/database/sqlite/index_test.go b/pkg/database/sqlite/index_test.go
new file mode 100644
index 00000000..af59b5c6
--- /dev/null
+++ b/pkg/database/sqlite/index_test.go
@@ -0,0 +1,70 @@
+package sqlite
+
+import (
+	"testing"
+
+	schemasv1alpha4 "github.com/schemahero/schemahero/pkg/apis/schemas/v1alpha4"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_AddIndexStatement(t *testing.T) {
+	tests := []struct {
+		name              string
+		tableName         string
+		schemaIndex       *schemasv1alpha4.SqliteTableIndex
+		expectedStatement string
+	}{
+		{
+			name:      "no name, one column, not specified unique",
+			tableName: "t2",
+			schemaIndex: &schemasv1alpha4.SqliteTableIndex{
+				Columns: []string{
+					"c1",
+				},
+			},
+			expectedStatement: "create index idx_t2_c1 on t2 (c1)",
+		},
+		{
+			name:      "specified name, one column, not specified unique",
+			tableName: "t2",
+			schemaIndex: &schemasv1alpha4.SqliteTableIndex{
+				Columns: []string{
+					"c1",
+				},
+				Name: "idx_name",
+			},
+			expectedStatement: "create index idx_name on t2 (c1)",
+		},
+		{
+			name:      "no name, two columns, not specified unique",
+			tableName: "t2",
+			schemaIndex: &schemasv1alpha4.SqliteTableIndex{
+				Columns: []string{
+					"c1",
+					"c2",
+				},
+			},
+			expectedStatement: "create index idx_t2_c1_c2 on t2 (c1, c2)",
+		},
+		{
+			name:      "np name, one column, unique",
+			tableName: "t2",
+			schemaIndex: &schemasv1alpha4.SqliteTableIndex{
+				Columns: []string{
+					"c1",
+				},
+				IsUnique: true,
+			},
+			expectedStatement: "create unique index idx_t2_c1 on t2 (c1)",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			addIndexStatement := AddIndexStatement(test.tableName, test.schemaIndex)
+
+			assert.Equal(t, test.expectedStatement, addIndexStatement)
+		})
+	}
+}