Skip to content

Commit

Permalink
Revamp to ignore the db.version number, which is apparently ignored o…
Browse files Browse the repository at this point in the history
…n Safari.

Instead, a table is created with a single value and that is used instead.  Besides
a slight increase in complexity, there are really no downsides.  Also added debug helpers,
callbacks, updated the example file.  Works on Chrome Linux and Windows Safari, at least.
  • Loading branch information
nanodeath committed Jun 6, 2010
1 parent a16de53 commit de8de4d
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 55 deletions.
148 changes: 125 additions & 23 deletions migrator.js
@@ -1,25 +1,127 @@
function Migrator(db){
var migrations = [];
this.migration = function(number, func){
migrations[number] = func;
};
var doMigration = function(number){
if(migrations[number]){
db.changeVersion(db.version, String(number), function(t){
migrations[number](t);
}, function(err){
if(console.error) console.error("Error!: %o (while upgrading to %d)", err, number);
}, function(){
doMigration(number+1);
});
}
};
this.doIt = function(){
var initialVersion = parseInt(db.version) || 0;
try {
doMigration(initialVersion+1);
} catch(e) {
if(console.error) console.error(e);
}
}
// Pending migrations to run
var migrations = [];
// Callbacks to run when migrations done
var whenDone = [];

var state = 0;

var MIGRATOR_TABLE = "_migrator_schema";

// Use this method to actually add a migration.
// You'll probably want to start with 1 for the migration number.
this.migration = function(number, func){
migrations[number] = func;
};

// Execute a given migration by index
var doMigration = function(number){
if(migrations[number]){
db.transaction(function(t){
t.executeSql("update " + MIGRATOR_TABLE + " set version = ?", [number], function(t){
debug(Migrator.DEBUG_HIGH, "Beginning migration %d", [number]);
migrations[number](t);
debug(Migrator.DEBUG_HIGH, "Completed migration %d", [number]);
doMigration(number+1);
}, function(t, err){
error("Error!: %o (while upgrading to %s from %s)", err, number);
})
});
} else {
debug(Migrator.DEBUG_HIGH, "Migrations complete, executing callbacks.");
state = 2;
executeWhenDoneCallbacks();
}
};

// helper that actually calls doMigration from doIt.
var migrateStartingWith = function(ver){
state = 1;
debug(Migrator.DEBUG_LOW, "Main Migrator starting.");

try {
doMigration(ver+1);
} catch(e) {
error(e);
}
};

this.execute = function(){
if(state > 0){
throw "Migrator is only valid once -- create a new one if you want to do another migration.";
}
db.transaction(function(t){
t.executeSql("select version from _migrator_schema", [], function(t, res){
var rows = res.rows;
var version = rows.item(0).version;
debug(Migrator.DEBUG_HIGH, "Existing database present, migrating from %d", [version]);
migrateStartingWith(version);
}, function(t, err){
if(err.message.match(/no such table/i)){
t.executeSql("create table " + MIGRATOR_TABLE + "(version integer)", [], function(){
t.executeSql("insert into " + MIGRATOR_TABLE + " values(0)", [], function(){
debug(Migrator.DEBUG_HIGH, "New migration database created...");
migrateStartingWith(0);
}, function(t, err){
error("Unrecoverable error inserting initial version into db: %o", err);
});
}, function(t, err){
error("Unrecoverable error creating version table: %o", err);
});
} else {
error("Unrecoverable error resolving schema version: %o", err);
}
});
});

return this;
};

// Called when the migration has completed. If the migration has already completed,
// executes immediately. Otherwise, waits.
this.whenDone = function(func){
if(typeof func !== "array"){
func = [func];
}
for(var f in func){
whenDone.push(func[f]);
}
if(state > 1){
debug(Migrator.DEBUG_LOW, "Executing 'whenDone' tasks immediately as the migrations have already finished.");
executeWhenDoneCallbacks();
}
};

var executeWhenDoneCallbacks = function(){
for(var f in whenDone){
whenDone[f]();
}
debug(Migrator.DEBUG_LOW, "Callbacks complete.");
}

// Debugging stuff.
var log = (window.console && console.log) ? function() { console.log.apply(console, argumentsToArray(arguments)) } : function(){};
var error = (window.console && console.error) ? function() { console.error.apply(console, argumentsToArray(arguments)) } : function(){};

var debugLevel = Migrator.DEBUG_NONE;

var argumentsToArray = function(args) { return Array.prototype.slice.call(args); };
this.setDebugLevel = function(level){
debugLevel = level;
}

var debug = function(minLevel, message, args){
if(debugLevel >= minLevel){
var newArgs = [message];
if(args != null) for(var i in args) newArgs.push(args[i]);

log.apply(null, newArgs);
}
}
}

// no output, low threshold (lots of output), or high threshold (just log the weird stuff)
// these might be a little, uh, backwards
Migrator.DEBUG_NONE = 0;
Migrator.DEBUG_LOW = 1;
Migrator.DEBUG_HIGH = 2;
71 changes: 39 additions & 32 deletions test.html
Expand Up @@ -9,26 +9,31 @@
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.js"></script>
<script type="text/javascript" src="migrator.js"></script>
<script type="text/javascript">
var db = openDatabase("example_db", "", "Example Database", 100000);
// now db is a database object: http://dev.w3.org/html5/webdatabase/#database
if(window.openDatabase){
var db = openDatabase("example_db", "", "Example Database", 100000);
// now db is a database object: http://dev.w3.org/html5/webdatabase/#database

var M = new Migrator(db);
M.migration(1, function(t){
// t here is a transaction object: http://dev.w3.org/html5/webdatabase/#sqltransaction
t.executeSql("create table user(id integer primary key, name text)");
t.executeSql("insert into user(name) values('max')");
});
M.migration(2, function(t){
t.executeSql("alter table user add column phone text");
t.executeSql("update user set phone = '555-5555' where name == 'max'");
});
M.migration(3, function(t){
t.executeSql("insert into user(name, phone) values('jeremy', '555-1234')");
t.executeSql("update user set phone = '555-5556' where name == 'max'");
});
var M = new Migrator(db);
M.setDebugLevel(Migrator.DEBUG_HIGH);
M.migration(1, function(t){
// t here is a transaction object: http://dev.w3.org/html5/webdatabase/#sqltransaction
t.executeSql("create table user(id integer primary key, name text)");
t.executeSql("insert into user(name) values('max')");
});
M.migration(2, function(t){
t.executeSql("alter table user add column phone text");
t.executeSql("update user set phone = '555-5555' where name == 'max'");
});
M.migration(3, function(t){
t.executeSql("insert into user(name, phone) values('jeremy', '555-1234')");
t.executeSql("update user set phone = '555-5556' where name == 'max'");
});

// This executes the applicable transactions
M.doIt();
// This executes the applicable transactions
M.execute();
} else {
$("p:eq(0)").text("Oops! HTML5 databases are not supported with your browser.");
}
</script>
<h1>Address Book</h1>
<p>Here are all the numbers in the address book...</p>
Expand All @@ -39,18 +44,20 @@ <h1>Address Book</h1>
<th>Phone</th>
</tr>
</table>
<script type="text/javascript">
db.readTransaction(function(t){
t.executeSql("select id, name, phone from user", [], function(t, res){
var rows = res.rows;
var addressTable = $("table.address_book");
for(var i = 0; i < rows.length; i++){
var row = rows.item(i);
var domRow = $("<tr><td>"+row.id+"</td><td>"+row.name+"</td><td>"+row.phone+"</td></tr>");
domRow.appendTo(addressTable);
}
});
});
</script>
<script type="text/javascript">
M.whenDone(function(){
db.transaction(function(t){
t.executeSql("select id, name, phone from user", [], function(t, res){
var rows = res.rows;
var addressTable = $("table.address_book");
for(var i = 0; i < rows.length; i++){
var row = rows.item(i);
var domRow = $("<tr><td>"+row.id+"</td><td>"+row.name+"</td><td>"+row.phone+"</td></tr>");
domRow.appendTo(addressTable);
}
});
});
});
</script>
</body>
</html>
</html>

0 comments on commit de8de4d

Please sign in to comment.