Monday, 23 May 2016

Custom Groovy Changes In Grails DbMigration (Part II)






Check out the Part I first.
In my previous post for Custom Groovy Changes In Grails DbMigration, you have seen a various example of writing custom changes for your migrations. Here are some more cases with example:

Case 1: Changing a field’s data type

databaseChangeLog = {

    changeSet(author: 'John', id: 'content.body-1') {
        comment { 'change type to text' }
        modifyDataType(tableName: 'content', columnName: 'body', newDataType: 'longtext')
        // or
        // modifyDataType(tableName: 'content', columnName: 'body', newDataType: 'text')
    }
}

Case 2: Custom Id generation to auto increment id generation.

By default, Grails add an id field to domain class which is marked to auto increment. But grails also provide configuration to generate custom ids (see here). When changing the id generator from custom id generator to an autoincrement field (default of grails), the migration plugin does not generate the changeset and it has to be manually added.
databaseChangeLog = {
    
    changeSet(author: "John", id: "dummy-10182013-1") {
        addAutoIncrement(columnName: "id", tableName: "expired_invite", columnDataType: "bigint")
    }
 
}

Case 3: Deleting records

   changeSet(author: "John", id: "content-plugin-upgrade-09-20-2013-1") {
       delete(tableName: "cc_content_menu_item", whereClause: "id < 7")
   }
    

Case 4: Adding unique constraint

   changeSet(author: "John", id: "10182013-1") {
       addUniqueConstraint(columnNames: "user_id", tableName: "user_profile")
   }
    

Case 5: Adding a foreign key

DbMigration generates both a changeset for adding a column and an index for a foreign key but MySql creates an index by default for a Foreign Key. Which makes the second changeset generated by DB migration for creating an index useless. You will have to remove the generated code which adds the index on foreign keys for the migration to run error free.
    changeSet(author: "Donald (generated)", id: "1317282537891-3") {
        createIndex(indexName: "FK7268FEF5EE5762B4", tableName: "user_profile") {
            column(name: "user_id")
        }
    }
    

Friday, 6 May 2016

Custom Groovy Changes In Grails DbMigration (Part I)




Grails has a wonderful plugin to manage database changes & state in production server, known by  Grails Database Migration Plugin. This plugin helps by managing database changes while developing a grails application. The plugin uses the Liquibase library.
This plugin manages changes of the database structure in a very consistent manner, avoiding inconsistencies, communication issues, and other problems by representing database migration scripts in text format, either using groovy DSL or liquibase XML. This also helps to maintain the change log migration files in source control like Git.
You can read more here in the documentation of DB migration plugin. If you are new to database migration plugin & don’t know how to use the migration plugin and write basic migration script please read Getting Started. (See this blog post for quick start.)
This plugin can generate most of the database change logs automatically by using liquibase build-in changes. But there are situations & changes where this plugin can’t help you out to write the changes.
For example: To rename a column ( a field in grails domain class), this plugin generates a script which removes the column with old name & creates a new column, which is not acceptable when there are records in the production database.
Another example can be where you have to migrate some data from one table to another table without repeating the code & in the proper way. Similarly, there are many more situations where you could not depend on this plugin’s auto generated code.
These situations can be handled by writing database changes using Groovy code (as long as you are using Groovy DSL file format). These changes use the grailsChange closure name and are contained in a changeSet closure like standard built-in tags. See Groovy Changes to read more about this.
Here I’m providing some scenarios where you can easily migrate your data & database structure without data redundancy & loss of data by writing custom grails DB migration scripts which the plugin will not generate for you.

Case 1: Updating Some Records

Suppose you have added a not null field in a domain class & want to add default value or add/update data to the other fields based on some conditions. Here you can write:

1.1) Using Liquibase classes:

import liquibase.statement.core.UpdateStatement

databaseChangeLog = {

    // Some other changeSets

    changeSet(author: "Shashank (generated)", id: "1381499382647-13") {
        addColumn(tableName: "ufile") {
            column(name: "file_group", type: "varchar(255)") {
                constraints(nullable: "false")
            }
        }
    }

    changeSet(author: 'Shashank Agrawal', id: 'file-uploader-10112013-1')  {
        grailsChange {
            change {
                def statements = []

                // Example 1: Updating file_group column with some value based on where clause
                statements << new UpdateStatement('my_database_name', 'ufile')
                        .addNewColumnValue('file_group', "customerLogo")
                        .setWhereClause(" id < 8")

                statements << new UpdateStatement('my_database_name', 'ufile')
                        .addNewColumnValue('file_group', "blogLogo")
                        .setWhereClause(" id in (13,14)")

                statements << new UpdateStatement('my_database_name', 'ufile')
                        .addNewColumnValue('type', "LOCAL")

                statements << new UpdateStatement('my_database_name', 'ufile')
                        .addNewColumnValue('date_created', new Date())

                sqlStatements(statements)   // Executing all statements at once

                confirm 'Initializing default value'    // Optional method call to display message

            }
        }
    }

}

1.2) Using SQL Query:

databaseChangeLog = {

    // Other changesets

    changeSet(author: "Shashank Agrawal", id: "07-13-2013-01") {
        grailsChange {
            change {
                sql.execute("update ufile set type = ‘LOCAL’ where id > 14")
                confirm 'Updating records.'
            }
        }
    }

}

Case 2: Inserting records to a table

Suppose you need to migrate some data from one table to another table or you simply need to add data to the table.
import liquibase.statement.core.InsertStatement


databaseChangeLog = {

    changeSet(author: "Shashank Agrawal", id: "my-custom-unique-id-for-each-changeSet-11212013-1") {
        grailsChange {
            change {
                def statements = []

                // Iterating through each row in table.
                sql.eachRow('select * from old_table_name') {

                    def insertStatement = new InsertStatement("my_database_name", "new_table_name")
                            .addColumnValue("id", it.id)
                            .addColumnValue("version", 0)
                            .addColumnValue("date_created", it.date_created)
                            .addColumnValue("last_updated", new Date())
                            .addColumnValue("some_other_field", it.id)
                            .addColumnValue("user_id", 0)

                    statements.add(insertStatement)

                }

                sqlStatements(statements)   // Executing every statement at one shot

                confirm 'Adding records to new table' // Optional line. Just for message.
            }
        }
    }

}

Case 3: Using Grails domain directly to update

There are situations where when we add some new data fields in a domain class table which already has records.
For example: If you added a date field. In this case, grails add the column with type datetime but its default value is initialized to 0000-00-00 00:00:00. Due to which grails throws an exception on server startup. So these fields need to be initialized before server startup. Here is an example:
import liquibase.statement.core.*
import com.some.domain.Post

databaseChangeLog = {

    // Auto generated change sets which adds column to the post table.

    grailsChange {
        change {
            Post.list().each { postInstance ->
                postInstance.dateCreated = new Date()
                postInstance.lastUpdated = new Date()
                postInstance.isActive = true
                postInstance.save(failOnError: true, flush: true)
            }
        }
    }

}

Case 4: Increasing field size for a text field

databaseChangeLog = {

    changeSet(author: 'Shashank', id: 'post.description.051020123') {
        comment { 'increase description to 1400 chars' }
        modifyDataType(tableName: 'post', columnName: 'description', newDataType: 'varchar(1400)')
    }

}

Case 5: Dropping a column

databaseChangeLog = {

    changeSet(author: "Shashank", id: "1319193290536-6") {
        dropColumn(columnName: "canPublish", tableName: "post")
    }

}
Check out the 2nd part.