In this section we will discuss how migrations work, how each of the provided migration classes work, and how to create your own custom one.
There are two kinds of migrations that DBFlow supports: Script-based SQL files and class annotation-based migrations.
In SQL databases, migrations are used to modify or change existing database schema to adapt to changing format or nature of stored data. In SQLite we have a limited ability compared to SQL to modify tables and columns of an existing database. There are only two kinds of modifications that exist: rename table and add a new column.
In DBFlow, migrations are not only used to modify the structure of the database, but also other operations such as insert data into a database (for pre-population), or add an index on a specific table.
Migrations are only run on an existing database except for the "0th" migration. Read initial database setup
We recommend placing any Migration
inside an associated @Database
class so it's apparent the migration is tied to it. An example migration class:
@Database(version = 2)
object AppDatabase {
@Migration(version = 2, database = AppDatabase::class, priority = 1)
class Migration2 : BaseMigration() {
override fun migrate(database: DatabaseWrapper) {
// run some code here
(update<Employee>()
set Employee_Table.status.eq("Invalid")
where Employee_Table.job.eq("Laid Off"))
.execute(database) // required to pass wrapper in migration
}
}
}
The classes provide the ability to set a priority
on the Migration
so that an order is established if more than one migration is to run on the same DB version upgrade. They are in reverse order - lower the priority, that one will execute first.
Migration
have three methods:
-
onPreMigrate()
- called first, do setup, and construction here. -
migrate()
-> called with theDatabaseWrapper
specified, this is where the actual migration code should execute. -
onPostMigrate()
-> perform some cleanup, or any notifications that it was executed.
DBFlow also supports .sql
migration files. The rules on these follows must be followed:
-
Place them in
assets/migrations/{DATABASE_NAME}/{versionNumber}.sql
. So that an exampleAppDatabase
migration for version 2 resides inassets/migrations/AppDatabase/2.sql
-
The file can contain any number of SQL statements - they are executed in order. Each statement must be on a single line or multiline and must end with
;
-
Comments are allowed as long as they appear on an individual file with standard SQLite comment syntax
--
Since Migration
occur when the database is opening, we cannot recursively access the database object in our models, SQLite wrapper statements, and other classes in DBFlow that are inside a Migration
.
To remedy that, DBFlow comes with support to pass the DatabaseWrapper
into almost all places that require it:
-
All query language
BaseQueriable
objects such asSelect
,Insert
,Update
,Delete
, etc have methods that take in theDatabaseWrapper
-
Any subclass of
BaseModel
(Model
does not provide the methods for simplicity)
Note: As of 5.0.0-alpha2, all DB methods require the DatabaseWrapper
object to be passed into their operations. This is for explicit safety.
DBFlow supports Migration
that run on version "0" of a database. When Android opens a SQLiteDatabase
object, if the database is created, DBFlow calls on a Migration
of -1 to 0th version. In this case, any Migration
run at version = 0
will get called. Once a database is created, this migration will not run again. So if you had an existent database at version 1, and changed version to 2, the "0th" Migration
is not run because the old version the database would have been 1.
In DBFlow we provide a few helper Migration
subclasses to provide default and easier implementation: 1. AlterTableMigration
-
IndexMigration/IndexPropertyMigration
-
UpdateTableMigration
The structural modification of a table is brought to a handy Migration
subclass.
It performs both of SQLite supported operations:
-
Rename tables
-
Add columns.
For renaming tables, you should rename the Model
class' @Table(name = "{newName}")
before running this Migration
. The reason is that DBFlow will know the new name only and the existing database will get caught up on it through this migration. Any new database created on a device will automatically have the new table name.
For adding columns, we only support SQLiteType
(all supported ones here) operations to add or remove columns. This is to enforce that the columns are created properly. If a column needs to be a TypeConverter
column, use the database value from it. We map the associated type of the database field to a SQLiteType
in SQLiteType.kt. So if you have a DateConverter
that specifies a Date
column converted to Long
, then you should look up Long
in the Map
. In this case Long
converts to INTEGER
.
@Migration(version = 2, database = AppDatabase::class)
class Migration2 : AlterTableMigration<AModel>(AModel::class) {
override fun onPreMigrate() {
addColumn(SQLiteType.TEXT, "myColumn")
addColumn(SQLiteType.REAL, "anotherColumn")
}
}
An IndexMigration
(and IndexPropertyMigration
) is used to structurally activate an Index
on the database at a specific version. See here for information on creating them.
IndexMigration
does not require an IndexProperty
to run, while IndexPropertyMigration
makes use of the property to run.
An IndexMigration
:
@Migration(version = 2, priority = 0, database = MigrationDatabase::class)
class IndexMigration2(override val name = "TestIndex") : IndexMigration<MigrationModel>(MigrationModel::class)
An IndexPropertyMigration
:
@Migration(version = 2, priority = 1, database = MigrationDatabase.class)
class IndexPropertyMigration2(override val indexProperty = IndexModel_Table.index_customIndex) : IndexPropertyMigration
A simple wrapper around Update
, provides simply a default way to update data during a migration.
@Migration(version = 2, priority = 2, database = MigrationDatabase.class)
class UpdateMigration2 extends UpdateTableMigration<MigrationModel>(MigrationModel::class) {
init {
// preselect update query set.
set(MigrationModel_Table.name.eq("New Name"));
}
}