|
Project Information
Members
Featured
Downloads
Wiki pages
Links
|
Scala Migrations is a library to manage upgrades and rollbacks to database schemas. Migrations allow a source control system to manage together the database schema and the code using the schema. It is designed to allow multiple developers working on a project with a database backend to design schema modifications independently, apply the migrations to their local database for debugging and when complete, check them into a source control system to manage as one manages normal source code. Other developers then check out the new migrations and apply them to their local database. Finally, the migrations are used to migrate the production databases to the latest schema version. The Scala Migrations library is written in Scala and makes use of the clean Scala language to write easy to understand migrations, which are also written in Scala. Scala Migrations provides a database abstraction layer that allows migrations to target any supported database vendor. HistoryScala Migrations was initially developed at Sony Pictures Imageworks to manage database versioning for a new internal application. The designed is based off Ruby on Rails Migrations and in fact shares the exact same schema_migrations table to manage the list of installed migrations. Sample MigrationHere is a migration used by VnP3, an internal Imageworks project. package com.imageworks.vnp.dao.migrations
import com.imageworks.migration.{Limit,
Migration,
Name,
NotNull,
OnDelete,
Restrict,
Unique}
/**
* Create the 'facility_set_membership' table, which is a many-to-many
* join table between the 'facility' and 'facility_set' tables. It
* represents the sets that a facility is a member of and the
* facilities that are in a set. Rows do not have a their own primary
* key.
*/
class Migrate_20081216235329_FacilitySetMembership
extends Migration
{
def up(): Unit =
{
createTable("facility_set_membership") { t =>
t.varbinary("pk_facility", NotNull, Limit(16))
t.varbinary("pk_facility_set", NotNull, Limit(16))
t.bigint("created_micros", NotNull)
t.bigint("modified_micros", NotNull)
}
// There should only be one pair of (pk_facility_set, pk_facility)
// tuples in the entire table, i.e., for one facility set, the
// facility should only appear once.
addIndex("facility_set_membership",
Array("pk_facility_set", "pk_facility"),
Unique,
Name("idx_fac_set_mmbrshp_uniq_pks"))
addForeignKey(on("facility_set_membership" -> "pk_facility"),
references("facility" -> "pk_facility"),
OnDelete(Restrict),
Name("fk_fac_set_mmbrshp_pk_fac"))
addForeignKey(on("facility_set_membership" -> "pk_facility_set"),
references("facility_set" -> "pk_facility_set"),
OnDelete(Restrict),
Name("fk_fac_set_mmbrshp_pk_fac_set"))
}
def down(): Unit =
{
dropTable("facility_set_membership")
}
}To migrate a database to the latest version requires code similar to: import com.imageworks.migration.{DatabaseAdapter,
InstallAllMigrations,
Vendor}
object Test
{
def main(args: Array[String]): Unit =
{
val driver_class_name = "org.postgresql.Driver"
val vendor = Vendor.forDriver(driver_class_name)
val migration_adapter = DatabaseAdapter.forVendor(vendor, None)
val data_source: javax.sql.DataSource = ...
val migrator = new Migrator(data_source, migration_adapter)
// Now apply all migrations that are in the
// com.imageworks.vnp.dao.migrations package.
migrator.migrate(InstallAllMigrations, "com.imageworks.vnp.dao.migrations", false)
}To rollback a database to its pristine state: migrator.migrate(RemoveAllMigrations, "com.imageworks.vnp.dao.migrations", false) To rollback two migrations: migrator.migrate(RollbackMigration(2), "com.imageworks.vnp.dao.migrations", false) And to migrate to a specific migration, rollbacking back migrations that are newer than the requested migration version and installing migrations older than the requested version. migrator.migrate(MigrateToVersion(20090731), "com.imageworks.vnp.dao.migrations", false) Supported DatabasesScala Migrations currently supports
Patches for other databases are welcome. Dependencies and SetupScala Migrations depends upon:
http://www.slf4j.org/ You will need to choose logging framework that SLF4J will log to. See the SLF4J website for more information on how to set up and use SLF4J.
http://code.google.com/p/log4jdbc/ Since running a migration on a production database is dangerous operation that can leave irreversible damage if anything goes wrong, the JDBC connection given to all migrations is a log4jdbc net.sf.log4jdbc.ConnectionSpy that wraps the real connection. This logs all method calls so that any migration errors can be fully debugged. log4jdbc uses SLF4J; see the log4jdbc website on how to set up the loggers and logging level for log4jdbc messages. Scala Migrations manually wraps the real database connection, so no special work needs to be done by the migration writer to use this feature. Migration NamingIn Scala Migrations, the migrations needs to be compiled and their *.class files need to be made available at runtime; the source files will not be available at runtime. Scala Migrations then needs to know an ordering on the migrations, so the timestamp needs to be in the class name. Scala does not support naming a symbol such as 20080717013526_YourMigrationName because the name begins with a digit (unless one were to quote the name which would look odd), so the Scala Migrations looks for classes named Migrate_(\\d+)_([_a-zA-Z0-9]*) The time stamp can be generated using the following command on Unix systems: $ date -u +%Y%m%d%H%M%S This is different than Ruby on Rails migrations which are in filenames of the form 20080717013526_your_migration_name.rb and have a corresponding class name such as YourMigrationName. Ruby on Rails can find all the migration *.rb files for a project and load them at runtime and from the filename load the correct class name. The ordering to apply the migrations is contained in the filename, not the class name. Unsupported Database FeaturesIt is not a goal of Scala Migrations to check and report on the compatibility of a Scala Migrations specific feature with a database. For example, Oracle does not support the "ON UPDATE SET NULL" clause on a foreign key constraint. If a OnUpdate(SetNull) is specified for a foreign key constraint, then Scala Migrations will generate that clause and ask the database to execute it. If Scala Migrations did attempt to check on the compatibility of each feature, then it would need to grow much larger to know which features worked on which database, and even worse, potentially know which features appear in which database versions. This is not something that the authors of Scala Migrations want to maintain. Data TypesThe following data types are supported listed with their mappings. If a database name is not specified, then the default mapping is used. More information on the mappings is below.
Boolean MappingScala Migrations does not define a mapping for the Boolean datatype in databases that do not have a native Boolean datatype. The reason is that there are many ways of representing a Boolean value database and Scala Migrations is not an ORM layer, so this decision is left to the application developer. Different representations that have been used in schemas include:
Oracle and SMALLINT, INTEGER and BIGINTOracle does not have SMALLINT, INTEGER or BIGINT SQL types comparable to other databases, such such as Derby, MySQL and PostgreSQL. These other databases used a fixed sized signed integer with a limited range of values that can be stored in the column.
Oracle does support an INTEGER column type but it uses a NUMBER(38) to store it. On Oracle, a Scala Migration using any of the SMALLINT, INTEGER and BIGINT types is mapped to a NUMBER with a precision smaller than 38.
This helps ensure the compatibility of any code running against an Oracle database so that it does not assume it can use 38-digit integer values in case the data needs to be exported to another database or if the code needs to work with other databases. Columns wishing to use a NUMBER(38) should use a DecimalType column. NUMERIC and DECIMALThere is a minor difference in the definition of the NUMERIC and DECIMAL types according to the SQL 1992 standard: 17) NUMERIC specifies the data type exact numeric, with the decimal
precision and scale specified by the <precision> and <scale>.
18) DECIMAL specifies the data type exact numeric, with the decimal
scale specified by the <scale> and the implementation-defined
decimal precision equal to or greater than the value of the
specified <precision>.However, in practice, all databases we looked at implement them identically.
"NUMERIC is a synonym for DECIMAL and behaves the same way. See DECIMAL data type." http://db.apache.org/derby/docs/10.4/ref/rrefsqlj12362.html http://db.apache.org/derby/docs/10.4/ref/rrefsqlj15260.html
"NUMERIC implemented as DECIMAL." http://dev.mysql.com/doc/refman/5.1/en/numeric-types.html
Only has the NUMBER type. http://download-west.oracle.com/docs/cd/B19306_01/server.102/b14200/sql_elements001.htm http://download-west.oracle.com/docs/cd/B19306_01/server.102/b14200/sql_elements001.htm#sthref218
"The types decimal and numeric are equivalent. Both types are part of the SQL standard." The documentation uses NUMERIC more and lists DECIMAL as an alias. http://www.postgresql.org/docs/8.3/interactive/datatype-numeric.html http://www.postgresql.org/docs/8.3/interactive/datatype.html#DATATYPE-TABLE Character Set EncodingScala Migrations supports specifying the character set for Char and Varchar columns with the CharacterSet() column option, which takes the name of the character set as an argument. Currently, the only supported character set name is Unicode. Here is how different databases handle character set encoding.
"Character data types are represented as Unicode 2.0 sequences in Derby." So specifying CharacterSet(Unicode) does not change its behavior. Using any character set name besides Unicode as the argument to CharacterSet() raises a warning and is ignored. http://db.apache.org/derby/docs/10.4/devguide/cdevcollation.html
MySQL supports 30+ character sets and and all of them can be simultaneously used; in fact, a table can have multiple character type columns, each with a different character set. See http://dev.mysql.com/doc/refman/5.1/en/charset-database.html for reference. If no CharacterSet is used, then MySQL will use the database's or the server's default character set and the default character set's default collation. If CharacterSet(Unicode) is used, then Scala Migrations uses the utf8 character set without specifying a collation, so MySQL will use utf8's default collation, which is normally utf8_general_ci. Users wishing to have more control on specifying character sets and collations can discuss this on the developers mailing list.
The character set encoding is chosen when a database is created with the "createdb" command line utility or the CREATE DATABASE ENCODING [=] encoding SQL statement. So specifying any CharacterSet has no effect.
Oracle only supports two character sets. The first uses the database character set which was chosen when the database was created. This encoding is used for CHAR, VARCHAR2 and CLOB columns. The second character set is called the national character set and is Unicode, which is used for NCHAR, NVARCHAR2 and NCLOB columns. There are two encodings available for the national character set, AL16UTF16 and UTF8. By default, Oracle uses AL16UTF16. http://download-west.oracle.com/docs/cd/B19306_01/server.102/b14225/ch6unicode.htm Specifying no CharacterSet column option defaults the Char type to CHAR and the Varchar type to VARCHAR2. If CharacterSet(Unicode) is given, then Char uses NCHAR and Varchar uses NVARCHAR2. Using any character set name besides Unicode as the argument to CharacterSet() raises a warning and is ignored, resulting in CHAR and VARCHAR2 column types. Caveats
|