package com.raizlabs.android.dbflow.sql.migration;

import android.database.Cursor;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;

import com.raizlabs.android.dbflow.config.FlowManager;
import com.raizlabs.android.dbflow.sql.QueryBuilder;
import com.raizlabs.android.dbflow.sql.SQLiteType;
import com.raizlabs.android.dbflow.sql.language.SQLite;
import com.raizlabs.android.dbflow.structure.Model;
import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper;

import java.util.ArrayList;
import java.util.List;

/**
 * Description: Provides a very nice way to alter a single table quickly and easily.
 */
public class AlterTableMigration<TModel extends Model> extends BaseMigration {

    /**
     * The table to ALTER
     */
    private final Class<TModel> table;

    /**
     * The query we use
     */
    private QueryBuilder query;

    /**
     * The query to rename the table with
     */
    private QueryBuilder renameQuery;

    /**
     * The columns to ALTER within a table
     */
    private List<QueryBuilder> columnDefinitions;

    private List<String> columnNames;

    /**
     * The old name of the table before renaming it
     */
    private String oldTableName;

    public AlterTableMigration(Class<TModel> table) {
        this.table = table;
    }

    @Override
    public final void migrate(DatabaseWrapper database) {
        // "ALTER TABLE "
        String sql = getAlterTableQueryBuilder().getQuery();
        String tableName = FlowManager.getTableName(table);

        // "{oldName}  RENAME TO {newName}"
        // Since the structure has been updated already, the manager knows only the new name.
        if (renameQuery != null) {
            String renameQuery = new QueryBuilder(sql).appendQuotedIfNeeded(oldTableName)
                .append(this.renameQuery.getQuery())
                .append(tableName)
                .toString();
            database.execSQL(renameQuery);
        }

        // We have column definitions to add here
        // ADD COLUMN columnName {type}
        if (columnDefinitions != null) {

            Cursor cursorToCheckColumnFor = SQLite.select().from(table).limit(0).query();
            if (cursorToCheckColumnFor != null) {
                try {
                    sql = new QueryBuilder(sql).append(tableName).toString();
                    for (int i = 0; i < columnDefinitions.size(); i++) {
                        QueryBuilder columnDefinition = columnDefinitions.get(i);
                        String columnName = QueryBuilder.stripQuotes(columnNames.get(i));
                        if (cursorToCheckColumnFor.getColumnIndex(columnName) == -1) {
                            database.execSQL(sql + " ADD COLUMN " + columnDefinition.getQuery());
                        }
                    }
                } finally {
                    cursorToCheckColumnFor.close();
                }
            }
        }
    }

    @CallSuper
    @Override
    public void onPostMigrate() {
        // cleanup and make fields eligible for garbage collection
        query = null;
        renameQuery = null;
        columnDefinitions = null;
        columnNames = null;
    }

    /**
     * Call this to rename a table to a new name, such as changing either the {@link com.raizlabs.android.dbflow.structure.Model} class name
     * or by changing the name through a {@link com.raizlabs.android.dbflow.annotation.Table}
     *
     * @param oldName The new name to call the table.
     * @return This instance
     */
    public AlterTableMigration<TModel> renameFrom(@NonNull String oldName) {
        oldTableName = oldName;
        renameQuery = new QueryBuilder().append(" RENAME").appendSpaceSeparated("TO");
        return this;
    }

    /**
     * Add a column to the DB. This does not necessarily need to be reflected in the {@link TModel},
     * but it is recommended.
     *
     * @param sqLiteType The type of column represented in the DB.
     * @param columnName The name of the column to add. Use the "_Table" class for the specified table.
     * @return This instance
     */
    public AlterTableMigration<TModel> addColumn(@NonNull SQLiteType sqLiteType, @NonNull String columnName) {
        if (columnDefinitions == null) {
            columnDefinitions = new ArrayList<>();
            columnNames = new ArrayList<>();
        }

        QueryBuilder queryBuilder = new QueryBuilder()
            .append(QueryBuilder.quoteIfNeeded(columnName)).appendSpace().appendSQLiteType(sqLiteType);
        columnDefinitions.add(queryBuilder);
        columnNames.add(columnName);

        return this;
    }

    /**
     * Add a column to the DB. This does not necessarily need to be reflected in the {@link TModel},
     * but it is recommended.
     *
     * @param sqLiteType      The type of column that pertains to an {@link SQLiteType}
     * @param columnName      The name of the column to add. Use the "$Table" class for the specified table.
     * @param referenceClause The clause of the references that this foreign key points to.
     * @return This instance
     */
    public AlterTableMigration<TModel> addForeignKeyColumn(SQLiteType sqLiteType, String columnName, String referenceClause) {
        if (columnDefinitions == null) {
            columnDefinitions = new ArrayList<>();
            columnNames = new ArrayList<>();
        }

        QueryBuilder queryBuilder = new QueryBuilder()
            .append(QueryBuilder.quoteIfNeeded(columnName)).appendSpace().appendSQLiteType(sqLiteType)
            .appendSpace().append("REFERENCES ").append(referenceClause);
        columnDefinitions.add(queryBuilder);
        columnNames.add(columnName);

        return this;
    }

    /**
     * @return The query that renames the table.
     */
    public String getRenameQuery() {
        QueryBuilder queryBuilder = new QueryBuilder(getAlterTableQueryBuilder().getQuery()).appendQuotedIfNeeded(oldTableName)
            .append(renameQuery).append(FlowManager.getTableName(table));
        return queryBuilder.getQuery();
    }

    /**
     * @return A List of column definitions that add column to a table in the DB.
     */
    public List<String> getColumnDefinitions() {
        String sql = new QueryBuilder(getAlterTableQueryBuilder()).append(FlowManager.getTableName(table)).toString();
        List<String> columnDefinitions = new ArrayList<>();

        if (this.columnDefinitions != null) {
            for (QueryBuilder columnDefinition : this.columnDefinitions) {
                QueryBuilder queryBuilder = new QueryBuilder(sql).appendSpaceSeparated("ADD COLUMN").append(
                    columnDefinition.getQuery());
                columnDefinitions.add(queryBuilder.getQuery());
            }
        }

        return columnDefinitions;
    }

    public QueryBuilder getAlterTableQueryBuilder() {
        if (query == null) {
            query = new QueryBuilder().append("ALTER").appendSpaceSeparated("TABLE");
        }
        return query;
    }
}

@@ -2191,24 +2191,32 @@
 it(0).query(
+database
 );%0A         

use database wrapepr