You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by jo...@apache.org on 2012/06/15 18:58:29 UTC

svn commit: r1350693 [2/2] - in /cayenne/sandbox: ./ cayenne-migrations/ cayenne-migrations/.settings/ cayenne-migrations/src/ cayenne-migrations/src/main/ cayenne-migrations/src/main/java/ cayenne-migrations/src/main/java/org/ cayenne-migrations/src/m...

Added: cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/MigrationTable.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/MigrationTable.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/MigrationTable.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/MigrationTable.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,285 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.migration;
+
+import java.sql.Types;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.merge.MergerFactory;
+import org.apache.cayenne.merge.MergerToken;
+
+/**
+ * Represents a table in the database and provides operations for changing the schema.
+ * 
+ * Internally this holds a DbEntity and uses it along with a MergerFactory to queue up
+ * MergerTokens to perform the schema changes.
+ * 
+ * @author john
+ *
+ */
+public abstract class MigrationTable {
+
+	private MigrationDatabase database;
+	private DbEntity entity;
+	private Map<String, MigrationColumn> columns = new HashMap<String, MigrationColumn>();
+
+	MigrationTable(MigrationDatabase database, String tableName) {
+		this.database = database;
+		
+		this.entity = new DbEntity(tableName);
+		entity.setDataMap(database.getDataMap());
+		database.getDataMap().addDbEntity(entity);
+	}
+
+	MigrationDatabase getDatabase() {
+		return database;
+	}
+	
+	MergerFactory factory() {
+		return database.factory();
+	}
+	
+	DbEntity getEntity() {
+		return entity;
+	}
+    
+    Map<String, MigrationColumn> getColumns() {
+        return columns;
+    }
+	
+	abstract boolean isNew();
+	
+	/**
+	 * Adds a column to a table; works for both new and existing tables.
+	 * @param columnName
+	 * @param jdbcType
+	 * @return
+	 */
+	public MigrationColumnNew addColumn(String columnName, int jdbcType) {
+		return addColumn(columnName, jdbcType, false, null);
+	}
+	public MigrationColumnNew addColumn(String columnName, int jdbcType, boolean isMandatory, Object defaultValue) {
+		return new MigrationColumnNew(this, columnName, jdbcType, -1, -1, -1, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addColumn(String columnName, int jdbcType, int maxLength) {
+		return addColumn(columnName, jdbcType, maxLength, false, null);
+	}
+	public MigrationColumnNew addColumn(String columnName, int jdbcType, int maxLength, boolean isMandatory, Object defaultValue) {
+		return new MigrationColumnNew(this, columnName, jdbcType, maxLength, -1, -1, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addColumn(String columnName, int jdbcType, int precision, int scale) {
+		return addColumn(columnName, jdbcType, precision, scale, false, null);
+	}
+	public MigrationColumnNew addColumn(String columnName, int jdbcType, int precision, int scale, boolean isMandatory, Object defaultValue) {
+		return new MigrationColumnNew(this, columnName, jdbcType, -1, precision, scale, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addArrayColumn(String columnName) {
+		return addArrayColumn(columnName, false);
+	}
+	public MigrationColumnNew addArrayColumn(String columnName, boolean isMandatory) {
+		return addColumn(columnName, Types.ARRAY, isMandatory, null);
+	}
+	
+	public MigrationColumnNew addBigIntColumn(String columnName) {
+		return addBigIntColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addBigIntColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.BIGINT, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addBinaryColumn(String columnName) {
+		return addBinaryColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addBinaryColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.BINARY, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addBitColumn(String columnName) {
+		return addBitColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addBitColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.BIT, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addBlobColumn(String columnName) {
+		return addBlobColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addBlobColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.BLOB, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addCharColumn(String columnName, int maxLength) {
+		return addCharColumn(columnName, maxLength, false, null);
+	}
+	public MigrationColumnNew addCharColumn(String columnName, int maxLength, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.CHAR, maxLength, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addBooleanColumn(String columnName) {
+		return addBooleanColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addBooleanColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.BOOLEAN, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addClobColumn(String columnName) {
+		return addClobColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addClobColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.CLOB, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addDateColumn(String columnName) {
+		return addDateColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addDateColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.DATE, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addDecimalColumn(String columnName, int precision, int scale) {
+		return addDecimalColumn(columnName, precision, scale, false, null);
+	}
+	public MigrationColumnNew addDecimalColumn(String columnName, int precision, int scale, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.DECIMAL, precision, scale, isMandatory, defaultValue);
+	}
+
+	public MigrationColumnNew addDoubleColumn(String columnName) {
+		return addDoubleColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addDoubleColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.DOUBLE, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addFloatColumn(String columnName) {
+		return addFloatColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addFloatColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.FLOAT, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addIntegerColumn(String columnName) {
+		return addIntegerColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addIntegerColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.INTEGER, isMandatory, defaultValue);
+	}
+
+	public MigrationColumnNew addLongVarBinaryColumn(String columnName) {
+		return addLongVarBinaryColumn(columnName, false);
+	}
+	public MigrationColumnNew addLongVarBinaryColumn(String columnName, boolean isMandatory) {
+		return addColumn(columnName, Types.LONGVARBINARY, isMandatory, null);
+	}
+	
+	public MigrationColumnNew addLongVarCharColumn(String columnName) {
+		return addLongVarCharColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addLongVarCharColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.LONGVARCHAR, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addNumericColumn(String columnName, int precision, int scale) {
+		return addNumericColumn(columnName, precision, scale, false, null);
+	}
+	public MigrationColumnNew addNumericColumn(String columnName, int precision, int scale, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.NUMERIC, precision, scale, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addRealColumn(String columnName) {
+		return addRealColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addRealColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.REAL, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addSmallIntColumn(String columnName) {
+		return addSmallIntColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addSmallIntColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.SMALLINT, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addTimeColumn(String columnName) {
+		return addTimeColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addTimeColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.TIME, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addTimestampColumn(String columnName) {
+		return addTimestampColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addTimestampColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.TIMESTAMP, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addTinyIntColumn(String columnName) {
+		return addTinyIntColumn(columnName, false, null);
+	}
+	public MigrationColumnNew addTinyIntColumn(String columnName, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.TINYINT, isMandatory, defaultValue);
+	}
+	
+	public MigrationColumnNew addVarBinaryColumn(String columnName, int maxLength) {
+		return addVarBinaryColumn(columnName, maxLength, false);
+	}
+	public MigrationColumnNew addVarBinaryColumn(String columnName, int maxLength, boolean isMandatory) {
+		return addColumn(columnName, Types.VARBINARY, maxLength, isMandatory, null);
+	}
+	
+	public MigrationColumnNew addVarcharColumn(String columnName, int maxLength) {
+		return addVarcharColumn(columnName, maxLength, false, null);
+	}
+	public MigrationColumnNew addVarcharColumn(String columnName, int maxLength, boolean isMandatory, Object defaultValue) {
+		return addColumn(columnName, Types.VARCHAR, maxLength, isMandatory, defaultValue);
+	}
+
+	/**
+	 * Adds a column to the list of primary keys for this table; works for both new and existing tables.
+	 * @param columnName
+	 */
+	public void addPrimaryKey(String columnName) {
+		((DbAttribute)entity.getAttribute(columnName)).setPrimaryKey(true);
+		if (!isNew()) {
+			MigrationColumn column = columns.get(columnName);
+			MergerToken op = factory().createSetPrimaryKeyToDb(getEntity(), Collections.EMPTY_LIST, Collections.singletonList(column.getAttribute()), null);
+			getDatabase().addOperation(op);
+		}
+	}
+
+	/**
+	 * Adds a new foreign key constraint; works for both new and existing tables.
+	 * @param sourceColumnName
+	 * @param destinationTable
+	 * @param destinationColumnName
+	 */
+	public void addForeignKey(String sourceColumnName, String destinationTable, String destinationColumnName) {
+		MigrationRelationship relationship = new MigrationRelationship(this, sourceColumnName, destinationTable, destinationColumnName);
+		MergerToken op = factory().createAddRelationshipToDb(getEntity(), relationship.getRelationship());
+		getDatabase().addOperation(op);
+	}
+
+}

Added: cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/MigrationTableExisting.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/MigrationTableExisting.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/MigrationTableExisting.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/MigrationTableExisting.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,83 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.migration;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.merge.MergerToken;
+
+/**
+ * Represents an existing table in the database and provides operations for changing the schema.
+ * 
+ * Internally this holds a DbEntity and uses it along with a MergerFactory to queue up
+ * MergerTokens to perform the schema changes.
+ * 
+ * @author john
+ *
+ */
+public class MigrationTableExisting extends MigrationTable {
+
+	MigrationTableExisting(MigrationDatabase database, String tableName) {
+		super(database, tableName);
+	}
+
+	@Override
+	boolean isNew() {
+		return false;
+	}
+	
+    /**
+     * Returns an existing column that can be changed.
+     * @param columnName
+     * @return
+     */
+    public MigrationColumnExisting alterColumn(String columnName) {
+        MigrationColumn result;
+        if (getColumns().get(columnName) != null) {
+            result = getColumns().get(columnName);
+            if (result.isNew()) {
+                throw new IllegalArgumentException(columnName + " is a new column, it cannot be altered.");
+            }
+        } else {
+            result = new MigrationColumnExisting(this, columnName);
+        }
+        
+        return (MigrationColumnExisting) result;
+    }
+    
+	/**
+	 * Drops an existing column.
+	 */
+	public void dropColumn(String columnName) {
+		DbAttribute attribute = alterColumn(columnName).getAttribute();
+		getDatabase().addOperation(factory().createDropColumnToDb(getEntity(), attribute));
+	}
+	
+	/**
+     * Removes an existing foreign key constraint.
+     * @param sourceColumnName
+     * @param destinationTable
+     * @param destinationColumnName
+     */
+    public void dropForeignKey(String sourceColumnName, String destinationTable, String destinationColumnName) {
+        MigrationRelationship relationship = new MigrationRelationship(this, sourceColumnName, destinationTable, destinationColumnName);
+        MergerToken op = factory().createDropRelationshipToDb(getEntity(), relationship.getRelationship());
+        getDatabase().addOperation(op);
+    }
+	
+}

Added: cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/MigrationTableNew.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/MigrationTableNew.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/MigrationTableNew.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/MigrationTableNew.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,49 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.migration;
+
+import org.apache.cayenne.merge.MergerToken;
+
+/**
+ * Represents a new table in the database.
+ * 
+ * Internally this holds a DbEntity and uses it along with a MergerFactory to queue up
+ * MergerTokens to perform the schema changes.
+ * 
+ * @author john
+ *
+ */
+public class MigrationTableNew extends MigrationTable {
+
+	MigrationTableNew(MigrationDatabase database, String tableName) {
+		super(database, tableName);
+		create();
+	}
+
+	@Override
+	boolean isNew() {
+		return true;
+	}
+	
+	private void create() {
+		MergerToken op = factory().createCreateTableToDb(getEntity());
+		getDatabase().addOperation(op);
+	}
+	
+}

Added: cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/Migrator.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/Migrator.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/Migrator.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/Migrator.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,256 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.migration;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.List;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.log.JdbcEventLogger;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.merge.AbstractToDbToken;
+import org.apache.cayenne.merge.MergerToken;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * The Migrator discovers and executes Migration subclasses in order to migrate
+ * a database schema to the latest version.
+ * 
+ * @author john
+ *
+ */
+public class Migrator {
+
+	//private static final Logger log = Logger.getLogger(Migrator.class);
+
+	private final DataNode node;
+	private final String migrationsPackage;
+	private Connection connection;
+	
+	/**
+	 * 
+	 * @param node the node that the schema migrations should be applied to
+	 * @param migrationsPackage the package that your migration subclasses reside in
+	 */
+	public Migrator(DataNode node, String migrationsPackage) {
+		this.node = node;
+		this.migrationsPackage = migrationsPackage;
+	}
+
+	/**
+	 * The name of the table that holds the version and lock information for the Migrator to use.
+	 * @return
+	 */
+	protected String migrationTableName() {
+		return "dbupdater";
+	}
+	
+	void createInternalMigrationSchema() throws SQLException {
+		executeSqlWithUpdateCount("CREATE TABLE " + migrationTableName() + "(dataMap VARCHAR(50) NOT NULL, version INTEGER DEFAULT -1 NOT NULL, locked SMALLINT DEFAULT 0 NOT NULL, PRIMARY KEY(dataMap))");
+		getConnection().commit();
+	}
+	
+	int currentDbVersion(DataMap map) throws SQLException {
+		String sql = String.format("SELECT version FROM %s WHERE dataMap = '%s'", migrationTableName(), map.getName());
+        return executeSqlReturnInt(sql).intValue();
+	}
+	
+	void setDbVersion(DataMap map, int version) throws SQLException {
+		int count = executeSqlWithUpdateCount(String.format("UPDATE %s SET version = %d WHERE version = %d AND dataMap = '%s'", migrationTableName(), version, version-1, map.getName()));
+		if (count == 0) {
+			throw new RuntimeException("Unable to update database version for dataMap: " + map.getName());
+		}
+	}
+	
+	boolean lock(DataMap map) throws SQLException {
+		String sql = String.format("UPDATE %s SET locked = 1 WHERE locked=0 and dataMap='%s'", migrationTableName(), map.getName());
+		int count = 0;
+		try {
+			try {
+				count = executeSqlWithUpdateCount(sql);
+				if (count > 0) {
+					return true; // got the lock
+				}
+			} catch (Exception e) {
+				createInternalMigrationSchema();
+			}
+		
+	        sql = String.format("SELECT locked FROM %s WHERE dataMap='%s'", migrationTableName(), map.getName());
+	        Integer locked = executeSqlReturnInt(sql);
+	        if (locked != null) {
+	        	return false; // row exists and is already locked
+	        } else {
+	        	// row doesn't exist
+	        	sql = String.format("INSERT INTO %s(dataMap, locked) VALUES ('%s', 1)", migrationTableName(), map.getName());
+	        	executeSqlWithUpdateCount(sql);
+	        	return true;
+	        }
+		} finally {
+		    getConnection().commit();
+		}
+	}
+	
+	void unlock(DataMap map) throws SQLException {
+		int count = executeSqlWithUpdateCount(String.format("UPDATE %s SET locked = 0 WHERE locked = 1 AND dataMap = '%s'", migrationTableName(), map.getName()));
+		if (count == 0) {
+			throw new IllegalStateException("Unable to remove migration lock.");
+		}
+		getConnection().commit();
+	}
+	
+	Migration createMigrationClassForVersion(DataMap map, int version) {
+		String className = migrationsPackage + "." + StringUtils.capitalize(map.getName()) + version;
+		
+		Class<?> clazz;
+		try {
+			clazz = Class.forName(className);
+			Migration instance = (Migration) clazz.getConstructor(DataNode.class).newInstance(node);
+			return instance;
+		} catch (Exception e) {
+			//log.debug("Migration class not found: " + className + "; stopping at version " + (version-1) + ".");
+			return null;
+		}
+	}
+	
+	/**
+	 * Discovers and executes the Migrations necessary to update the database schema to the latest version.
+	 * 
+	 * @throws SQLException
+	 */
+	public void migrateToLatest() throws SQLException {
+		synchronized (node) {
+            try {
+				getConnection();
+
+				for (DataMap map : node.getDataMaps()) {
+					
+					while (!lock(map)) {
+						//log.debug("Waiting to obtain migration lock for node: " + node.getName());
+						try {
+							Thread.sleep(200);
+						} catch (InterruptedException e) {
+							return;
+						}
+					}
+					
+					try {
+						int version = currentDbVersion(map)+1;
+						
+						Migration migration;
+						while ((migration = createMigrationClassForVersion(map, version)) != null) {
+							//log.info(String.format("Updating %s to version %d", map.getName(), version));
+							migration.run();
+							try {
+							    executeOperations(migration.getDatabase().getOperations());
+							} catch (Exception e) {
+							    throw new RuntimeException("Failed to migrate database to version " + version + ": " + e.getMessage(), e);
+							}
+							setDbVersion(map, version);
+							getConnection().commit();
+							version++;
+						}
+					} finally {
+						unlock(map);
+					}
+				}
+				
+			} finally {
+	            if (getConnection() != null) {
+	                try {
+	                    getConnection().close();
+	                } catch (SQLException e) {}
+	            }
+	        }
+		}
+	}
+
+	void executeOperations(List<MergerToken> operations) throws SQLException {
+		for (MergerToken token : operations) {
+			AbstractToDbToken dbToken = (AbstractToDbToken)token;
+			executeSqlWithUpdateCount(dbToken.createSql(node.getAdapter()));
+		}
+	}
+	
+	void executeSqlWithUpdateCount(List<String> sqlStatements) throws SQLException {
+    	for (String sql : sqlStatements) {
+			executeSqlWithUpdateCount(sql);
+		}
+    }
+    
+	int executeSqlWithUpdateCount(String sql) throws SQLException {
+        Statement st = null;
+        JdbcEventLogger logger = node.getJdbcEventLogger();
+        try {
+            logger.log(sql);
+            st = getConnection().createStatement();
+            try {
+            	st.execute(sql);
+            } catch (SQLException e) {
+                getConnection().rollback();
+            	throw new RuntimeException("SQL statement failed \"" + sql + "\": " + e.getMessage(), e);
+            }
+            return st.getUpdateCount();
+        } finally {
+            closeStatement(st);
+        }
+    }
+    
+	Integer executeSqlReturnInt(String sql) throws SQLException {
+        Statement st = null;
+        JdbcEventLogger logger = node.getJdbcEventLogger();
+        try {
+            logger.log(sql);
+            st = getConnection().createStatement();
+            try {
+                st.execute(sql);
+                ResultSet rs = st.getResultSet();
+                if (rs != null && rs.next()) {
+                    return rs.getInt(1);
+                } else {
+                	return null;
+                }
+            } catch (SQLException e) {
+                getConnection().rollback();
+                throw new RuntimeException("SQL statement failed \"" + sql + "\": " + e.getMessage(), e);
+            }
+        } finally {
+            closeStatement(st);
+        }
+	}
+
+	private void closeStatement(Statement st) {
+		if (st != null) {
+		    try {
+		        st.close();
+		    } catch (SQLException e) {}
+		}
+	}
+
+    Connection getConnection() throws SQLException {
+        if (connection == null) {
+            connection = node.getDataSource().getConnection();
+            getConnection().setAutoCommit(false);
+        }
+        return connection;
+    }
+	
+}

Added: cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/SqlFileMigration.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/SqlFileMigration.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/SqlFileMigration.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/SqlFileMigration.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,86 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.migration;
+
+import org.apache.cayenne.access.DataNode;
+
+/**
+ * <p>A migration that relies on an SQL file for it's operations.</p>
+ * 
+ * The SQL filename follows these naming conventions:<br>
+ * 1) It is located in the same package as the Migration class it accompanies.<br>
+ * 2) It is named the same as the Migration class is accompanies with a ".sql" extension.<br>
+ * Optionally the class name may be followed by a dash ("-") and the database type it uses to refer to a database-specific file for cases where you want support multiple DB implementations.<br>
+ * The database type is derived by taking from the JdbcAdapter class being used and removing the "Adapter" suffix.<br>
+ * 
+ * <p>So for a Migration class named Tutorial0 the generic (universal) sql file would be named 'Tutorial0.sql'<br>
+ * For a postgresql-specific migration the sql file would be named 'Tutorial0-Postgres.sql'</p>
+ * 
+ * @author john
+ *
+ */
+public class SqlFileMigration extends Migration {
+
+	/**
+	 * 
+	 * @param node the node that you want to apply the migration on
+	 */
+	public SqlFileMigration(DataNode node) {
+		super(node);
+	}
+
+	/**
+	 * Executes the sql script given by 'sqlFilename()'.
+	 */
+	@Override
+	public void upgrade(MigrationDatabase db) {
+		executeSqlScript(sqlFilename());
+	}
+
+	/**
+	 * Returns the filename for the sql script file for this migration, looking first for a database-specific file, then for a generic one.
+	 * @return
+	 */
+	public String sqlFilename() {
+		if (getClass().getResource(databaseSpecificSqlFilename()) != null) {
+			return databaseSpecificSqlFilename();
+		} else {
+			return genericSqlFilename();
+		}
+	}
+	
+	/**
+	 * Returns the filename for a database-specific sql script file with migration commands.
+	 * @return
+	 */
+	protected String databaseSpecificSqlFilename() {
+		String databaseType = getDataNode().getAdapter().getClass().getSimpleName();
+		databaseType = databaseType.replace("Adapter", "");
+		return getClass().getSimpleName() + "-" + databaseType + ".sql";
+	}
+	
+	/**
+	 * Returns the filename for a generic (not database specific) sql script file with migration commands.
+	 * @return
+	 */
+	protected String genericSqlFilename() {
+		return getClass().getSimpleName() + ".sql";
+	}
+	
+}

Added: cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/package.html
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/package.html?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/package.html (added)
+++ cayenne/sandbox/cayenne-migrations/src/main/java/org/apache/cayenne/migration/package.html Fri Jun 15 16:58:25 2012
@@ -0,0 +1,30 @@
+<html>
+<body>
+
+<p>Provides functionality to create and modify database schema / metadata so that it can be evolved over time
+in a recreatable and deterministic way. This is done by versioning the database schema.
+This API gives you database independence for your schema definition since all the SQL is generated by the DbAdapter.</p>
+
+<p>There is also the ability to include raw SQL statements from a file. These files can be defined for each database 
+type in order to preserve database independence.</p>
+
+<p>Getting Started:</p>
+<ul>
+	<li>Run org.apache.cayenne.migration.MigrationGenerator to generate the initial Migration from your Cayenne project file (model). 
+	This class has a main method that takes two arguments: 
+	1) the name or path to your cayenne project file (domain) and 
+	2) the output directory. 
+	It will create a java class with all the calls necessary to create your tables and foreign key constraints, etc. 
+	This java class needs to be moved into your project after being generated.</li>
+	<li>At application startup time create a org.apache.cayenne.migration.Migrator object and call migrateToLatest(). For example:</li>
+</ul>
+<pre>
+try {
+    new Migrator(domain.getDataNode("production"), MyDataMap0.class.getPackage().getName()).migrateToLatest();
+} catch (SQLException e) {
+    throw new RuntimeException("Unable to migrate database to current version: " + e.getMessage(), e);
+}
+</pre>
+
+</body>
+</html>

Added: cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/merge/ArbitrarySqlToDbTest.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/merge/ArbitrarySqlToDbTest.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/merge/ArbitrarySqlToDbTest.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/merge/ArbitrarySqlToDbTest.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,55 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.merge;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.cayenne.map.DbEntity;
+
+public class ArbitrarySqlToDbTest extends TestCase {
+
+	private ArbitrarySqlToDb subject;
+	
+    @Override
+    protected void setUp() throws Exception {
+	    super.setUp();
+	    subject = new ArbitrarySqlToDb("UPDATE x SET y=1");
+	}
+	
+	public void testCreateSql() {
+		List<String> statements = subject.createSql(null);
+		assertEquals(1, statements.size());
+		assertEquals("UPDATE x SET y=1", statements.get(0));
+	}
+
+	public void testGetTokenValue() {
+		assertEquals("UPDATE x SET y=1", subject.getTokenValue());
+	}
+
+	public void testCompareTo() {
+		CreateTableToDb createTableToken = new CreateTableToDb(new DbEntity("test"));
+		assertTrue(subject.compareTo(createTableToken) > 0);
+		
+		ArbitrarySqlToDb arbitrarySqlToken = new ArbitrarySqlToDb("UPDATE z SET y=1");
+		assertEquals(0, subject.compareTo(arbitrarySqlToken));
+	}
+
+}

Added: cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationColumnExistingTest.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationColumnExistingTest.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationColumnExistingTest.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationColumnExistingTest.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,132 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.migration;
+
+import java.sql.Types;
+import java.util.Collections;
+
+import junit.framework.TestCase;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.configuration.DefaultRuntimeProperties;
+import org.apache.cayenne.configuration.RuntimeProperties;
+import org.apache.cayenne.dba.postgres.PostgresAdapter;
+import org.apache.cayenne.merge.SetAllowNullToDb;
+import org.apache.cayenne.merge.SetColumnTypeToDb;
+import org.apache.cayenne.merge.SetNotNullToDb;
+import org.apache.cayenne.merge.SetValueForNullToDb;
+
+public class MigrationColumnExistingTest extends TestCase {
+
+    private DataNode node;
+    private MigrationDatabase db;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        
+        node = new DataNode("node");
+        
+        RuntimeProperties props = new DefaultRuntimeProperties(Collections.EMPTY_MAP);
+        PostgresAdapter adapter = new PostgresAdapter(props, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+        node.setAdapter(adapter);
+        
+        db = new MigrationDatabase(node);
+    }
+
+    public void testIsNew() {
+        MigrationTableExisting table = db.alterTable("table");
+        MigrationColumn column = table.alterColumn("column");
+        assertFalse(column.isNew());
+    }
+
+    public void testAddNotNullConstraint() {
+        MigrationTableExisting table = db.alterTable("table");
+        table.alterColumn("column").addNotNullConstraint();
+
+        assertEquals(1, table.getDatabase().getOperations().size());
+        assertTrue(table.getDatabase().getOperations().get(0) instanceof SetNotNullToDb);
+        
+        SetNotNullToDb operation = (SetNotNullToDb) table.getDatabase().getOperations().get(0);
+        assertEquals("column", operation.getColumn().getName());
+        assertEquals("table", operation.getColumn().getEntity().getName());
+    }
+
+	public void testDropNotNullConstraint() {
+	    MigrationTableExisting table = db.alterTable("table");
+        table.alterColumn("column").dropNotNullConstraint();
+
+        assertEquals(1, table.getDatabase().getOperations().size());
+        assertTrue(table.getDatabase().getOperations().get(0) instanceof SetAllowNullToDb);
+        
+        SetAllowNullToDb operation = (SetAllowNullToDb) table.getDatabase().getOperations().get(0);
+        assertEquals("column", operation.getColumn().getName());
+        assertEquals("table", operation.getColumn().getEntity().getName());
+    }
+
+	public void testSetDataType() {
+	    MigrationTableExisting table = db.alterTable("table");
+	    table.alterColumn("column").setDataType(Types.BIGINT);
+
+	    assertEquals(1, table.getDatabase().getOperations().size());
+	    assertTrue(table.getDatabase().getOperations().get(0) instanceof SetColumnTypeToDb);
+
+	    SetColumnTypeToDb operation = (SetColumnTypeToDb)table.getDatabase().getOperations().get(0);
+	    assertEquals(Types.BIGINT, operation.getColumnNew().getType());
+	    assertTrue(table.getDatabase().getOperations().get(0) instanceof SetColumnTypeToDb);
+	}
+
+	public void testSetDataTypeWithMaxLength() {
+	    MigrationTableExisting table = db.alterTable("table");
+        table.alterColumn("column").setDataType(Types.VARCHAR, 256);
+
+        assertEquals(1, table.getDatabase().getOperations().size());
+        assertTrue(table.getDatabase().getOperations().get(0) instanceof SetColumnTypeToDb);
+        
+        SetColumnTypeToDb operation = (SetColumnTypeToDb)table.getDatabase().getOperations().get(0);
+        assertEquals(Types.VARCHAR, operation.getColumnNew().getType());
+        assertEquals(256, operation.getColumnNew().getMaxLength());
+    }
+
+    public void testSetDataTypeWithPrecisionAndScale() {
+        MigrationTableExisting table = db.alterTable("table");
+        table.alterColumn("column").setDataType(Types.DECIMAL, 38, 4);
+
+        assertEquals(1, table.getDatabase().getOperations().size());
+        assertTrue(table.getDatabase().getOperations().get(0) instanceof SetColumnTypeToDb);
+        
+        SetColumnTypeToDb operation = (SetColumnTypeToDb)table.getDatabase().getOperations().get(0);
+        assertEquals(Types.DECIMAL, operation.getColumnNew().getType());
+        assertEquals(38, operation.getColumnNew().getAttributePrecision());
+        assertEquals(4, operation.getColumnNew().getScale());
+    }
+
+	public void testSetDefaultValue() {
+	    MigrationTableExisting table = db.alterTable("table");
+        table.alterColumn("column").setDefault(1);
+
+        assertEquals(1, table.getDatabase().getOperations().size());
+        assertTrue(table.getDatabase().getOperations().get(0) instanceof SetValueForNullToDb);
+        
+        SetValueForNullToDb operation = (SetValueForNullToDb) table.getDatabase().getOperations().get(0);
+        assertEquals("column", operation.getColumn().getName());
+        assertEquals("table", operation.getColumn().getEntity().getName());
+	}
+
+}

Added: cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationColumnNewTest.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationColumnNewTest.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationColumnNewTest.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationColumnNewTest.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,107 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.migration;
+
+import java.sql.Types;
+import java.util.Collections;
+
+import junit.framework.TestCase;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.configuration.DefaultRuntimeProperties;
+import org.apache.cayenne.configuration.RuntimeProperties;
+import org.apache.cayenne.dba.postgres.PostgresAdapter;
+import org.apache.cayenne.merge.AddColumnToDb;
+import org.apache.cayenne.merge.CreateTableToDb;
+import org.apache.cayenne.merge.SetValueForNullToDb;
+
+public class MigrationColumnNewTest extends TestCase {
+
+	private DataNode node;
+	private MigrationDatabase db;
+	private MigrationColumnNew subject;
+	
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+		
+		node = new DataNode("node");
+		
+		RuntimeProperties props = new DefaultRuntimeProperties(Collections.EMPTY_MAP);
+		PostgresAdapter adapter = new PostgresAdapter(props, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+		node.setAdapter(adapter);
+		
+		db = new MigrationDatabase(node);
+	}
+
+	public void testAddColumnToNewTableNoDefault() {
+		MigrationTable table = db.createTable("table");
+		subject = new MigrationColumnNew(table, "column", Types.INTEGER, 0, 0, 0, false, null);
+
+		assertEquals(1, subject.getTable().getDatabase().getOperations().size());
+		assertTrue(subject.getTable().getDatabase().getOperations().get(0) instanceof CreateTableToDb);
+	}
+
+	public void testAddColumnToNewTableWithDefault() {
+		MigrationTable table = db.createTable("table");
+		subject = new MigrationColumnNew(table, "column", Types.INTEGER, 0, 0, 0, true, 1);
+
+		assertEquals(2, subject.getTable().getDatabase().getOperations().size());
+		assertTrue(subject.getTable().getDatabase().getOperations().get(0) instanceof CreateTableToDb);
+	    assertTrue(subject.getTable().getDatabase().getOperations().get(1) instanceof SetValueForNullToDb);
+
+//		CreateTableToDb op = (CreateTableToDb) subject.getTable().getDatabase().getOperations().get(0);
+//		List<String> statements = op.createSql(node.getAdapter());
+//		for (String statement : statements) {
+//            if (statement.toUpperCase().startsWith("CREATE TABLE")) {
+//                assertTrue(statement.contains(" DEFAULT "));
+//            }
+//        }
+	}
+	
+	public void testAddColumnToExistingTableNoDefault() {
+		MigrationTable table = db.alterTable("table");
+		subject = new MigrationColumnNew(table, "column", Types.INTEGER, 0, 0, 0, false, null);
+
+		assertEquals(1, subject.getTable().getDatabase().getOperations().size());
+		assertTrue(subject.getTable().getDatabase().getOperations().get(0) instanceof AddColumnToDb);
+	}
+	
+	public void testAddColumnToExistingTableWithDefault() {
+		MigrationTable table = db.alterTable("table");
+		subject = new MigrationColumnNew(table, "column", Types.INTEGER, 0, 0, 0, true, 1);
+
+		assertEquals(2, subject.getTable().getDatabase().getOperations().size());
+		assertTrue(subject.getTable().getDatabase().getOperations().get(0) instanceof AddColumnToDb);
+	    assertTrue(subject.getTable().getDatabase().getOperations().get(1) instanceof SetValueForNullToDb);
+
+//		AddColumnToDb op = (AddColumnToDb) subject.getTable().getDatabase().getOperations().get(0);
+//		List<String> statements = op.createSql(node.getAdapter());
+//		for (String statement : statements) {
+//		    assertTrue(statement.contains(" DEFAULT "));
+//		}
+	}
+	
+	public void testIsNew() {
+	    MigrationTable table = db.alterTable("table");
+	    subject = new MigrationColumnNew(table, "column", Types.INTEGER, 0, 0, 0, false, null);
+		assertTrue(subject.isNew());
+	}
+
+}

Added: cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationDatabaseTest.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationDatabaseTest.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationDatabaseTest.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationDatabaseTest.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,107 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.migration;
+
+import java.util.Collections;
+
+import junit.framework.TestCase;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.configuration.DefaultRuntimeProperties;
+import org.apache.cayenne.configuration.RuntimeProperties;
+import org.apache.cayenne.dba.postgres.PostgresAdapter;
+import org.apache.cayenne.merge.ArbitrarySqlToDb;
+import org.apache.cayenne.merge.CreateTableToDb;
+import org.apache.cayenne.merge.DropTableToDb;
+
+public class MigrationDatabaseTest extends TestCase {
+
+    private DataNode node;
+    private MigrationDatabase db;
+    
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        
+        node = new DataNode("node");
+        
+        RuntimeProperties props = new DefaultRuntimeProperties(Collections.EMPTY_MAP);
+        PostgresAdapter adapter = new PostgresAdapter(props, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+        node.setAdapter(adapter);
+        
+        db = new MigrationDatabase(node);
+    }
+
+	public void testAddOperation() {
+	    assertEquals(0, db.getOperations().size());
+	    db.addOperation(new ArbitrarySqlToDb("UPDATE x SET y=1"));
+	    assertEquals(1, db.getOperations().size());
+	}
+
+	public void testCreateTable() {
+		db.createTable("table");
+	    assertEquals(1, db.getOperations().size());
+	    assertTrue(db.getOperations().get(0) instanceof CreateTableToDb);
+	    
+	    CreateTableToDb operation = (CreateTableToDb) db.getOperations().get(0);
+	    assertEquals("table", operation.getEntity().getName());
+	}
+
+	public void testAlterTable() {
+		MigrationTableExisting table = db.alterTable("table");
+		MigrationTableExisting table2 = db.alterTable("table");
+		assertTrue(table == table2);
+	}
+	
+	public void testCantAlterNewTable() {
+		db.createTable("table");
+		try {
+		    db.alterTable("table");
+		    fail("Shouldn't be able to alter a new table.");
+		} catch (Exception e) {}
+	}
+
+	public void testCantCreateAnExistingTable() {
+	    db.alterTable("table");
+	    try {
+	        db.createTable("table");
+	        fail("Shouldn't be able to create an existing table.");
+	    } catch (Exception e) {}
+	}
+	   
+	public void testDropTable() {
+	    db.dropTable("table");
+	    assertEquals(1, db.getOperations().size());
+	    assertTrue(db.getOperations().get(0) instanceof DropTableToDb);
+
+	    DropTableToDb operation = (DropTableToDb) db.getOperations().get(0);
+	    assertEquals("table", operation.getEntity().getName());
+	}
+	
+	public void testExecute() {
+	    db.execute("UPDATE x SET y=1");
+
+	    assertEquals(1, db.getOperations().size());
+	    assertTrue(db.getOperations().get(0) instanceof ArbitrarySqlToDb);
+
+	    ArbitrarySqlToDb operation = (ArbitrarySqlToDb) db.getOperations().get(0);
+	    assertEquals("UPDATE x SET y=1", operation.getTokenValue());
+	}
+
+}

Added: cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationTableExistingTest.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationTableExistingTest.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationTableExistingTest.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationTableExistingTest.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,165 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.migration;
+
+import java.sql.Types;
+import java.util.Collections;
+
+import junit.framework.TestCase;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.configuration.DefaultRuntimeProperties;
+import org.apache.cayenne.configuration.RuntimeProperties;
+import org.apache.cayenne.dba.postgres.PostgresAdapter;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.merge.AddColumnToDb;
+import org.apache.cayenne.merge.AddRelationshipToDb;
+import org.apache.cayenne.merge.DropColumnToDb;
+import org.apache.cayenne.merge.DropRelationshipToDb;
+import org.apache.cayenne.merge.SetPrimaryKeyToDb;
+import org.apache.cayenne.merge.SetValueForNullToDb;
+
+public class MigrationTableExistingTest extends TestCase {
+    
+    private DataNode node;
+    private MigrationDatabase db;
+    
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        
+        node = new DataNode("node");
+        
+        RuntimeProperties props = new DefaultRuntimeProperties(Collections.EMPTY_MAP);
+        PostgresAdapter adapter = new PostgresAdapter(props, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+        node.setAdapter(adapter);
+        
+        db = new MigrationDatabase(node);
+    }
+
+	public void testIsNew() {
+        MigrationTableExisting table = db.alterTable("table");
+        assertFalse(table.isNew());
+	}
+	
+	public void testMigrationTableExisting() {
+        MigrationTableExisting table = db.alterTable("table");
+        assertEquals(0, table.getDatabase().getOperations().size());
+    }
+
+	public void testDropColumn() {
+        MigrationTableExisting table = db.alterTable("table");
+        table.dropColumn("column");
+        assertEquals(1, table.getDatabase().getOperations().size());
+        assertTrue(table.getDatabase().getOperations().get(0) instanceof DropColumnToDb);
+	}
+	
+	public void testAddColumn() {
+        MigrationTableExisting table = db.alterTable("table");
+        table.addColumn("column", Types.INTEGER);
+        assertEquals(1, table.getDatabase().getOperations().size());
+        assertTrue(table.getDatabase().getOperations().get(0) instanceof AddColumnToDb);
+        
+        AddColumnToDb operation = (AddColumnToDb) table.getDatabase().getOperations().get(0);
+        assertEquals("column", operation.getColumn().getName());
+        assertEquals(Types.INTEGER, operation.getColumn().getType());
+        assertEquals("table", operation.getColumn().getEntity().getName());
+        assertFalse(operation.getColumn().isMandatory());
+	}
+	
+	public void testAddColumnWithDefault() {
+	    MigrationTableExisting table = db.alterTable("table");
+	    table.addColumn("column", Types.INTEGER, true, 1);
+	    assertEquals(2, table.getDatabase().getOperations().size());
+	    assertTrue(table.getDatabase().getOperations().get(0) instanceof AddColumnToDb);
+	    assertTrue(table.getDatabase().getOperations().get(1) instanceof SetValueForNullToDb);
+
+        AddColumnToDb operation = (AddColumnToDb) table.getDatabase().getOperations().get(0);
+        assertEquals("column", operation.getColumn().getName());
+        assertEquals(Types.INTEGER, operation.getColumn().getType());
+        assertEquals("table", operation.getColumn().getEntity().getName());
+        assertTrue(operation.getColumn().isMandatory());
+	}
+
+	public void testAddPrimaryKey() {
+        MigrationTableExisting table = db.alterTable("table");
+        table.addIntegerColumn("pk");
+        table.addPrimaryKey("pk");
+        assertEquals(2, table.getDatabase().getOperations().size());
+        assertTrue(table.getDatabase().getOperations().get(0) instanceof AddColumnToDb);
+        assertTrue(table.getDatabase().getOperations().get(1) instanceof SetPrimaryKeyToDb);
+        
+        DbAttribute pk = (DbAttribute) table.getEntity().getAttribute("pk");
+        assertTrue(pk.isPrimaryKey());
+	}
+
+	public void testAddForeignKey() {
+	    MigrationTableExisting table = db.alterTable("table");
+	    table.addForeignKey("fk", "table2", "pk");
+
+	    assertEquals(1, table.getDatabase().getOperations().size());
+	    assertTrue(table.getDatabase().getOperations().get(0) instanceof AddRelationshipToDb);
+
+	    AddRelationshipToDb operation = (AddRelationshipToDb) table.getDatabase().getOperations().get(0);
+	    DbRelationship relationship = operation.getRelationship();
+	    assertEquals("table2", relationship.getTargetEntityName());
+	    assertEquals("fk", relationship.getJoins().get(0).getSource().getName());
+	    assertEquals("pk", relationship.getJoins().get(0).getTarget().getName());
+	}
+	   
+	public void testDropForeignKey() {
+        MigrationTableExisting table = db.alterTable("table");
+        table.dropForeignKey("fk", "table2", "pk");
+        assertEquals(1, table.getDatabase().getOperations().size());
+        assertTrue(table.getDatabase().getOperations().get(0) instanceof DropRelationshipToDb);
+	}
+	
+	public void testAlterColumn() {
+        MigrationTableExisting table = db.alterTable("table");
+        table.alterColumn("column").addNotNullConstraint();
+        
+        // ensure alterColumn can be called multiple times without error
+        table.alterColumn("column").setDefault(1);
+        
+        assertEquals(1, table.getEntity().getAttributes().size());
+        assertEquals(2, table.getDatabase().getOperations().size());
+	}
+	
+	public void testCantAlterNewColumn() {
+	    MigrationTableExisting table = db.alterTable("table");
+	    table.addIntegerColumn("column");
+	    try {
+	        table.alterColumn("column");
+	        fail("New columns should not be allowed to altered.");
+	    } catch (Exception e) {
+	    }
+	}
+	
+	public void testCantCreateAnExistingColumn() {
+	    MigrationTableExisting table = db.alterTable("table");
+	    table.alterColumn("column");
+	    try {
+	        table.addIntegerColumn("column");
+	        fail("Existing columns should not be allowed to created.");
+	    } catch (Exception e) {
+	    }
+	}
+
+}

Added: cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationTableNewTest.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationTableNewTest.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationTableNewTest.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationTableNewTest.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,114 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.migration;
+
+import java.sql.Types;
+import java.util.Collections;
+
+import junit.framework.TestCase;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.configuration.DefaultRuntimeProperties;
+import org.apache.cayenne.configuration.RuntimeProperties;
+import org.apache.cayenne.dba.postgres.PostgresAdapter;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.merge.AddRelationshipToDb;
+import org.apache.cayenne.merge.CreateTableToDb;
+import org.apache.cayenne.merge.SetValueForNullToDb;
+
+public class MigrationTableNewTest extends TestCase {
+
+    private DataNode node;
+    private MigrationDatabase db;
+    
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        
+        node = new DataNode("node");
+        
+        RuntimeProperties props = new DefaultRuntimeProperties(Collections.EMPTY_MAP);
+        PostgresAdapter adapter = new PostgresAdapter(props, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+        node.setAdapter(adapter);
+        
+        db = new MigrationDatabase(node);
+    }
+
+	public void testMigrationTableNew() {
+        MigrationTableNew table = db.createTable("table");
+        assertEquals(1, table.getDatabase().getOperations().size());
+        assertTrue(table.getDatabase().getOperations().get(0) instanceof CreateTableToDb);
+	}
+
+	public void testIsNew() {
+	    MigrationTableNew table = db.createTable("table");
+	    assertTrue(table.isNew());
+	}
+	
+	public void testAddColumn() {
+		MigrationTableNew table = db.createTable("table");
+	    table.addColumn("column", Types.INTEGER);
+	    assertEquals(1, table.getDatabase().getOperations().size());
+	    assertTrue(table.getDatabase().getOperations().get(0) instanceof CreateTableToDb);
+	}
+
+    public void testAddColumnWithDefault() {
+        MigrationTableNew table = db.createTable("table");
+        table.addColumn("column", Types.INTEGER, true, 1);
+        assertEquals(2, table.getDatabase().getOperations().size());
+        assertTrue(table.getDatabase().getOperations().get(0) instanceof CreateTableToDb);
+        assertTrue(table.getDatabase().getOperations().get(1) instanceof SetValueForNullToDb);
+
+//        CreateTableToDb op = (CreateTableToDb) table.getDatabase().getOperations().get(0);
+//        List<String> statements = op.createSql(node.getAdapter());
+//        for (String statement : statements) {
+//            if (statement.toUpperCase().startsWith("CREATE TABLE")) {
+//                assertTrue(statement.contains(" DEFAULT "));
+//            }
+//        }
+    }
+	    
+	public void testAddPrimaryKey() {
+        MigrationTableNew table = db.createTable("table");
+        table.addIntegerColumn("pk");
+        table.addPrimaryKey("pk");
+        
+        assertEquals(1, table.getDatabase().getOperations().size());
+        assertTrue(table.getDatabase().getOperations().get(0) instanceof CreateTableToDb);
+        DbAttribute pk = (DbAttribute) table.getEntity().getAttribute("pk");
+        assertTrue(pk.isPrimaryKey());
+	}
+	
+	public void testAddForeignKey() {
+        MigrationTableNew table = db.createTable("table");
+        table.addForeignKey("fk", "table2", "pk");
+        
+        assertEquals(2, table.getDatabase().getOperations().size());
+        assertTrue(table.getDatabase().getOperations().get(0) instanceof CreateTableToDb);
+        assertTrue(table.getDatabase().getOperations().get(1) instanceof AddRelationshipToDb);
+        
+        AddRelationshipToDb operation = (AddRelationshipToDb) table.getDatabase().getOperations().get(1);
+        DbRelationship relationship = operation.getRelationship();
+        assertEquals("table2", relationship.getTargetEntityName());
+        assertEquals("fk", relationship.getJoins().get(0).getSource().getName());
+        assertEquals("pk", relationship.getJoins().get(0).getTarget().getName());
+	}
+	
+}

Added: cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationTest.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationTest.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationTest.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigrationTest.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,86 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.migration;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import junit.framework.TestCase;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.configuration.DefaultRuntimeProperties;
+import org.apache.cayenne.configuration.RuntimeProperties;
+import org.apache.cayenne.dba.postgres.PostgresAdapter;
+import org.apache.cayenne.merge.ArbitrarySqlToDb;
+
+public class MigrationTest extends TestCase {
+    
+    private DataNode node;
+    
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        
+        node = new DataNode("node");
+        
+        RuntimeProperties props = new DefaultRuntimeProperties(Collections.EMPTY_MAP);
+        PostgresAdapter adapter = new PostgresAdapter(props, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+        node.setAdapter(adapter);
+    }
+    
+    private static class MyMigration extends Migration {
+        public MyMigration(DataNode node) {
+            super(node);
+        }
+        @Override
+        public void upgrade(MigrationDatabase db) {}    
+    }
+    
+	public void testExecuteSqlStatement() {
+	    MyMigration migration = new MyMigration(node);
+        migration.executeSqlStatement("UPDATE x SET y=1");
+
+        assertEquals(1, migration.getDatabase().getOperations().size());
+        assertTrue(migration.getDatabase().getOperations().get(0) instanceof ArbitrarySqlToDb);
+
+        ArbitrarySqlToDb operation = (ArbitrarySqlToDb) migration.getDatabase().getOperations().get(0);
+        assertEquals("UPDATE x SET y=1", operation.getTokenValue());
+    }
+
+	public void testLoadTextResource() {
+	    try {
+            String sql = Migration.loadTextResource("testMigrationScript.sql", getClass());
+            assertEquals("UPDATE x SET y=1;", sql);
+        } catch (IOException e) {
+            fail(e.getMessage());
+        }
+	}
+
+	public void testExecuteSqlScript() {
+        MyMigration migration = new MyMigration(node);
+        migration.executeSqlScript("testMigrationScript.sql");
+
+        assertEquals(1, migration.getDatabase().getOperations().size());
+        assertTrue(migration.getDatabase().getOperations().get(0) instanceof ArbitrarySqlToDb);
+
+        ArbitrarySqlToDb operation = (ArbitrarySqlToDb) migration.getDatabase().getOperations().get(0);
+        assertEquals("UPDATE x SET y=1;", operation.getTokenValue());
+    }
+
+}

Added: cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigratorTest.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigratorTest.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigratorTest.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MigratorTest.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,163 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.migration;
+
+import java.util.Collections;
+
+import junit.framework.TestCase;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.configuration.DefaultRuntimeProperties;
+import org.apache.cayenne.configuration.RuntimeProperties;
+import org.apache.cayenne.dba.postgres.PostgresAdapter;
+import org.apache.cayenne.map.DataMap;
+
+// TODO: implement tests for Migrator - not sure how to connect to a real database
+public class MigratorTest extends TestCase {
+
+    private DataNode node;
+//    private MockConnection connection;
+    
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        
+        node = new DataNode("node");
+        
+        RuntimeProperties props = new DefaultRuntimeProperties(Collections.EMPTY_MAP);
+        PostgresAdapter adapter = new PostgresAdapter(props, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+        node.setAdapter(adapter);
+        
+        DataMap map = new DataMap("MyMap");
+        node.addDataMap(map);
+        
+//        connection = new MockConnection();
+//        
+//        MockDataSource dataSource = new MockDataSource();
+//        dataSource.setupConnection(connection);
+//        node.setDataSource(dataSource);
+    }
+    
+    public void testSomething() {
+    }
+    
+//    
+//    public void testCreateMigrationClassForVersion() {
+//        Migrator migrator = new Migrator(node, getClass().getPackage().getName());
+//        DataMap map = new DataMap("MyMap");
+//        
+//        Migration migration0 = migrator.createMigrationClassForVersion(map, 0);
+//        assertNotNull(migration0);
+//        assertNotNull(migration0 instanceof MyMap0);
+//    }
+//    
+//    public void testExecuteSqlWithUpdateCount() {
+//        Migrator migrator = new Migrator(node, getClass().getPackage().getName());
+//        try {
+//            migrator.executeSqlWithUpdateCount("UPDATE x SET y=1");
+//        } catch (SQLException e) {
+//            fail(e.getMessage());
+//        }
+//    }
+//    
+//    public void testExecuteSqlWithUpdateCountList() {
+//        Migrator migrator = new Migrator(node, getClass().getPackage().getName());
+//        try {
+//            migrator.executeSqlWithUpdateCount(Collections.singletonList("UPDATE x SET y=1"));
+//        } catch (SQLException e) {
+//            fail(e.getMessage());
+//        }
+//    }
+//    
+//    public void testExecuteSqlReturnInt() {
+//        Migrator migrator = new Migrator(node, getClass().getPackage().getName());
+//        try {
+//            migrator.executeSqlReturnInt("SELECT y FROM x");
+//        } catch (SQLException e) {
+//            fail(e.getMessage());
+//        }
+//    }
+//    
+//    public void testExecuteOperations() {
+//        Migrator migrator = new Migrator(node, getClass().getPackage().getName());
+//        try {
+//            MigrationDatabase db = new MigrationDatabase(node);
+//            MigrationTable table = db.createTable("MyTable");
+//            table.addIntegerColumn("pk");
+//            table.addPrimaryKey("pk");
+//            migrator.executeOperations(db.getOperations());
+//        } catch (SQLException e) {
+//            fail(e.getMessage());
+//        } 
+//    }
+//    
+//    public void testCreateInternalMigrationSchema() throws SQLException {
+//        Migrator migrator = new Migrator(node, getClass().getPackage().getName());
+//        migrator.createInternalMigrationSchema();
+//    }
+//    
+//    public void testCurrentDbVersion() throws SQLException {
+//        Migrator migrator = new Migrator(node, getClass().getPackage().getName());
+//        DataMap map = node.getDataMaps().iterator().next();
+//        migrator.currentDbVersion(map);
+//    }
+//    
+//    public void testSetDbVersion() throws SQLException {
+//        Migrator migrator = new Migrator(node, getClass().getPackage().getName());
+//        DataMap map = node.getDataMaps().iterator().next();
+//        migrator.setDbVersion(map, 0);
+//    }
+//    
+//    public void testLock() throws SQLException {
+//        Migrator migrator = new Migrator(node, getClass().getPackage().getName());
+//        DataMap map = node.getDataMaps().iterator().next();
+//        migrator.lock(map);
+//    }
+//    
+//    public void testLockTwiceFails() throws SQLException {
+//        Migrator migrator = new Migrator(node, getClass().getPackage().getName());
+//        DataMap map = node.getDataMaps().iterator().next();
+//        assertTrue(migrator.lock(map));
+//        assertFalse(migrator.lock(map));
+//    }
+//    
+//    public void testUnlock() throws SQLException {
+//        Migrator migrator = new Migrator(node, getClass().getPackage().getName());
+//        DataMap map = node.getDataMaps().iterator().next();
+//        migrator.lock(map);
+//        migrator.unlock(map);
+//    }
+//    
+//    public void testUnlockTwiceFails() throws SQLException {
+//        Migrator migrator = new Migrator(node, getClass().getPackage().getName());
+//        DataMap map = node.getDataMaps().iterator().next();
+//        migrator.lock(map);
+//        migrator.unlock(map);
+//        try {
+//            migrator.unlock(map);
+//            fail("Unlocking twice should fail");
+//        } catch (Exception e) {}
+//    }
+//    
+//    public void testMigrateToLatest() throws SQLException {
+//		Migrator migrator = new Migrator(node, getClass().getPackage().getName());
+//        migrator.migrateToLatest();
+//	}
+
+}

Added: cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MyMap0.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MyMap0.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MyMap0.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/MyMap0.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,34 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.migration;
+
+import org.apache.cayenne.access.DataNode;
+
+
+class MyMap0 extends Migration {
+
+    public MyMap0(DataNode node) {
+        super(node);
+    }
+    
+    @Override
+    public void upgrade(MigrationDatabase db) {
+    }
+    
+}

Added: cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/SqlFileMigrationTest.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/SqlFileMigrationTest.java?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/SqlFileMigrationTest.java (added)
+++ cayenne/sandbox/cayenne-migrations/src/test/java/org/apache/cayenne/migration/SqlFileMigrationTest.java Fri Jun 15 16:58:25 2012
@@ -0,0 +1,87 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.cayenne.migration;
+
+import java.util.Collections;
+
+import junit.framework.TestCase;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.configuration.DefaultRuntimeProperties;
+import org.apache.cayenne.configuration.RuntimeProperties;
+import org.apache.cayenne.dba.postgres.PostgresAdapter;
+import org.apache.cayenne.merge.ArbitrarySqlToDb;
+
+public class SqlFileMigrationTest extends TestCase {
+    
+    private DataNode node;
+    
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        
+        node = new DataNode("node");
+        
+        RuntimeProperties props = new DefaultRuntimeProperties(Collections.EMPTY_MAP);
+        PostgresAdapter adapter = new PostgresAdapter(props, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+        node.setAdapter(adapter);
+    }
+    
+    private static class Node0 extends SqlFileMigration {
+        public Node0(DataNode node) {
+            super(node);
+        }
+    }
+    
+    private static class Node1 extends SqlFileMigration {
+        public Node1(DataNode node) {
+            super(node);
+        }
+    }
+
+	public void testDatabaseSpecificSqlFilename() {
+	    Node0 migration = new Node0(node);
+		assertEquals("Node0-Postgres.sql", migration.databaseSpecificSqlFilename());
+	}
+
+	public void testGenericSqlFilename() {
+	    Node0 migration = new Node0(node);
+        assertEquals("Node0.sql", migration.genericSqlFilename());
+    }
+
+	public void testSqlFilename() {
+        Node0 migration0 = new Node0(node);
+        assertEquals("Node0.sql", migration0.sqlFilename());
+        
+        Node1 migration1 = new Node1(node);
+        assertEquals("Node1-Postgres.sql", migration1.sqlFilename());
+    }
+
+	public void testUpgrade() {
+	    Node0 migration = new Node0(node);
+	    migration.upgrade(migration.getDatabase());
+	    
+        assertEquals(1, migration.getDatabase().getOperations().size());
+        assertTrue(migration.getDatabase().getOperations().get(0) instanceof ArbitrarySqlToDb);
+
+        ArbitrarySqlToDb operation = (ArbitrarySqlToDb) migration.getDatabase().getOperations().get(0);
+        assertEquals("UPDATE x SET y=1;", operation.getTokenValue());
+	}
+
+}

Added: cayenne/sandbox/cayenne-migrations/src/test/resources/org/apache/cayenne/migration/Node0.sql
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/test/resources/org/apache/cayenne/migration/Node0.sql?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/test/resources/org/apache/cayenne/migration/Node0.sql (added)
+++ cayenne/sandbox/cayenne-migrations/src/test/resources/org/apache/cayenne/migration/Node0.sql Fri Jun 15 16:58:25 2012
@@ -0,0 +1 @@
+UPDATE x SET y=1;
\ No newline at end of file

Added: cayenne/sandbox/cayenne-migrations/src/test/resources/org/apache/cayenne/migration/Node1-Postgres.sql
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/test/resources/org/apache/cayenne/migration/Node1-Postgres.sql?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/test/resources/org/apache/cayenne/migration/Node1-Postgres.sql (added)
+++ cayenne/sandbox/cayenne-migrations/src/test/resources/org/apache/cayenne/migration/Node1-Postgres.sql Fri Jun 15 16:58:25 2012
@@ -0,0 +1 @@
+UPDATE x SET y=2;
\ No newline at end of file

Added: cayenne/sandbox/cayenne-migrations/src/test/resources/org/apache/cayenne/migration/testMigrationScript.sql
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-migrations/src/test/resources/org/apache/cayenne/migration/testMigrationScript.sql?rev=1350693&view=auto
==============================================================================
--- cayenne/sandbox/cayenne-migrations/src/test/resources/org/apache/cayenne/migration/testMigrationScript.sql (added)
+++ cayenne/sandbox/cayenne-migrations/src/test/resources/org/apache/cayenne/migration/testMigrationScript.sql Fri Jun 15 16:58:25 2012
@@ -0,0 +1 @@
+UPDATE x SET y=1;
\ No newline at end of file