diff --git a/README b/README index ade3034..1ffe1a0 100644 --- a/README +++ b/README @@ -3,6 +3,8 @@ Migrate - A database agnostic migration system for Node.js By Ryan Sandor Richards +SQLite 3 Contribution by Curtis Schlak + Introduction -------------------------------------------------------------------------------- Migrate is a tool that allows you to define database schema migrations with @@ -14,9 +16,10 @@ Requirements -------------------------------------------------------------------------------- 1. Node.js - http://github.com/ry/node 2. node-mysql - https://github.com/felixge/node-mysql +3. node-sqlite3 - https://github.com/developmentseed/node-sqlite3 -Please note that at the current time we only support MySQL but other DBMS' are -on their way (next up: Postgres). +Please note that at the current time we only support MySQL and SQLite 3 but +other DBMS' are on their way (next up: Postgres). Installation -------------------------------------------------------------------------------- @@ -31,12 +34,14 @@ the directory where the migrate.js file resides. The configuration file has the following keys: * `dbms` - The database management system to use (currently we only support a - a value of `'mysql'`). + a value of either `'mysql'` or `'sqlite3'`). * `migration_path` - Relative path to the directory in which migrations are stored. * `mysql` - Just a simple client configuration for mysql. Fill out your username, password, and any other information needed to connect to the database via node-mysql's `Client` class. +* `sqlite3` - Just a simple client configuration for SQLite 3. Fill out the + filename for the database to which you would like to connect. How do I use migrate? -------------------------------------------------------------------------------- @@ -258,8 +263,8 @@ Producing SQL: Outtro -------------------------------------------------------------------------------- So that about sums it up. Simple and easy ;). It's a very early alpha version so -please don't hate on only having MySQL support! If you have a feature request -feel free to send me a message and I'll try to get it in ASAP. +please don't hate on only having MySQL and SQLite 3 support! If you have a +feature request feel free to send me a message and I'll try to get it in ASAP. Thanks! diff --git a/README.markdown b/README.markdown index ade3034..1ffe1a0 100644 --- a/README.markdown +++ b/README.markdown @@ -3,6 +3,8 @@ Migrate - A database agnostic migration system for Node.js By Ryan Sandor Richards +SQLite 3 Contribution by Curtis Schlak + Introduction -------------------------------------------------------------------------------- Migrate is a tool that allows you to define database schema migrations with @@ -14,9 +16,10 @@ Requirements -------------------------------------------------------------------------------- 1. Node.js - http://github.com/ry/node 2. node-mysql - https://github.com/felixge/node-mysql +3. node-sqlite3 - https://github.com/developmentseed/node-sqlite3 -Please note that at the current time we only support MySQL but other DBMS' are -on their way (next up: Postgres). +Please note that at the current time we only support MySQL and SQLite 3 but +other DBMS' are on their way (next up: Postgres). Installation -------------------------------------------------------------------------------- @@ -31,12 +34,14 @@ the directory where the migrate.js file resides. The configuration file has the following keys: * `dbms` - The database management system to use (currently we only support a - a value of `'mysql'`). + a value of either `'mysql'` or `'sqlite3'`). * `migration_path` - Relative path to the directory in which migrations are stored. * `mysql` - Just a simple client configuration for mysql. Fill out your username, password, and any other information needed to connect to the database via node-mysql's `Client` class. +* `sqlite3` - Just a simple client configuration for SQLite 3. Fill out the + filename for the database to which you would like to connect. How do I use migrate? -------------------------------------------------------------------------------- @@ -258,8 +263,8 @@ Producing SQL: Outtro -------------------------------------------------------------------------------- So that about sums it up. Simple and easy ;). It's a very early alpha version so -please don't hate on only having MySQL support! If you have a feature request -feel free to send me a message and I'll try to get it in ASAP. +please don't hate on only having MySQL and SQLite 3 support! If you have a +feature request feel free to send me a message and I'll try to get it in ASAP. Thanks! diff --git a/config.sqlite3.js b/config.sqlite3.js new file mode 100644 index 0000000..11ec6d7 --- /dev/null +++ b/config.sqlite3.js @@ -0,0 +1,34 @@ +/* + * config.sqlite3.js - A database agnostic migration system for Node.js. + * Copyright (c) 2012 Curtis Schlak (https://github.com/realistschuckle) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Path to the migrations directory (relative to migrate) +exports.migration_path = './migrations/'; + +// Which DBMS to use for executing migrations +exports.dbms = 'sqlite3'; + +// Configuration for SQLite3 (filename, verbose) +exports.sqlite3 = { + filename: './another.something.sqlite3', + verbose: true +}; diff --git a/migrate.js b/migrate.js index e0b35d9..ecd1c3d 100644 --- a/migrate.js +++ b/migrate.js @@ -77,18 +77,20 @@ function CreateTable(name) { default_value: null }; - if (arguments.length < 1) + if (arguments.length == 0) { return; + } if (typeof arguments[0] == "string") { - if (arguments.length < 2) - return; - column.name = arguments[0]; - column.type = arguments[1]; - - if (arguments[2] && typeof(arguments[2]) == "object") - column = merge(column, arguments[2]); + + if (arguments.length > 1) { + column.type = arguments[1]; + + if (arguments[2] && typeof(arguments[2]) == "object") { + column = merge(column, arguments[2]); + } + } } else if (typeof arguments[0] == 'object') { column = merge(column, arguments[0]); @@ -100,8 +102,8 @@ function CreateTable(name) { } if (!valid_type(column.type)) - return; - + return column; + this.columns.push(column); }; @@ -165,7 +167,7 @@ function RenameTable(old_name, new_name) { */ function ChangeTable(name) { CreateTable.call(this, name); - + this.remove_columns = []; this.change_columns = []; this.remove_indices = []; @@ -176,8 +178,7 @@ function ChangeTable(name) { * Alters a column and renames it. */ this.rename = function(old_name, new_column) { - this.column(new_column); - this.rename_columns[old_name] = this.columns.pop(); + this.rename_columns[old_name] = this.column(new_column); }; /** @@ -286,7 +287,10 @@ function Migration(opts) { // Encodes an object and appends it to the migrations SQL representation. function encode(o) { - sql += encoder.encode(o); + var encoding = encoder.encode(o); + if(encoding) { + sql += encoding; + } } function reset() { @@ -563,8 +567,183 @@ Encoders['mysql'] = function() { }; }(); +/** + * Translates migrations into valid SQLite 3. + */ +Encoders['sqlite3'] = function() { + // Mapping of abstract migrate types to concrete SQLite 3 types + var types = { + 'integer': 'INT', + 'string': 'VARCHAR', + 'text': 'TEXT', + 'float': 'FLOAT', + 'decimal': 'DECIMAL', + 'datetime': 'DATETIME', + 'timestamp': 'TIMESTAMP', + 'time': 'TIME', + 'date': 'DATE', + 'binary': 'VARBINARY', + 'boolean': 'TINYINT' + }; + + // Intensely helpful function for creating a SQLite type from a column object. + function parse_type(column) { + // type, limit, precision, scale + var type = null; + + if (column.type == 'integer') { + if (column.limit == 1) + type = "TINYINT"; + else if (column.limit == 2) + type = "SMALLINT"; + else if (column.limit == 3) + type = "MEDIUMINT"; + else if (column.limit == 8) + type = "BIGINT"; + else + type = "INT"; + } + else if (column.type == 'string' || column.type == 'binary') { + type = types[column.type]; + if (column.limit) + type += "(" + column.limit + ")"; + else + type += '(255)'; + } + else if (column.type == 'decimal') { + type = types[column.type]; + if (column.precision && column.scale) { + type += "(" + column.precision + "," + column.scale + ")"; + } + else if (column.precision) { + type += "(" + column.precision + ")"; + } + } + else + type = types[column.type]; + + if (column.not_null) + type += ' NOT NULL'; + + if (column.default_value) { + type += ' DEFAULT '; + if (column.type == 'string' || column.type == 'text') + type += "'" + column.default_value + "'"; + else + type += column.default_value; + } + + return type; + } + + /* + * The following functions do the the actual work of generating the SQL for the encode function. + */ + function create_table(table) { + var sql = "CREATE TABLE " + table.name, defs = [], i; + + for (i = 0; i < table.columns.length; i++) + defs.push("\t" + table.columns[i].name + " " + parse_type(table.columns[i])); + + if (table.primary_key_name) + defs.push("\tPRIMARY KEY (" + table.primary_key_name + ")"); + + if (defs.length) + sql += " (\n" + defs.join(",\n") + "\n)"; + + sql += ";\n"; + + for (i = 0; i < table.indices.length; i++) + sql += "\tCREATE INDEX IF NOT EXISTS ix_" + table.name + "_" + table.indices[i] + " ON " + table.name + "(" + table.indices[i] + ");"; + + return sql; + } + + function drop_table(table) { + return "DROP TABLE " + table.name + ";\n"; + } + + function rename_table(table) { + return "ALTER TABLE " + table.old_name + " RENAME TO " + table.new_name + ";\n"; + } + + function change_table(table) { + if(table.primary_key_name || table.remove_columns.length || table.remove_key || table.change_columns.length || table.rename_columns.length) { + return exit('You can only add columns to tables in a change_table migration.'); + } + + var sql = "ALTER TABLE " + table.name, defs = [], i; + + for (i = 0; i < table.columns.length; i++) + defs.push("\tADD COLUMN " + table.columns[i].name + " " + parse_type(table.columns[i])); + + if (defs.length) + sql += "\n" + defs.join(",\n") + ";"; + + for (i = 0; i < table.indices.length; i++) + sql += "\nCREATE INDEX IF NOT EXISTS ix_" + table.name + "_" + table.indices[i] + " ON " + table.name + "(" + table.indices[i] + ");"; + + for (i = 0; i < table.remove_indices.length; i++) + sql += "\nDROP INDEX IF EXISTS ix_" + table.name + "_" + table.remove_indices[i] + " ON " + table.name + "(" + table.remove_indices[i] + ");"; + + return sql; + } + + function add_column(column) { + return "ALTER TABLE " + column.table_name + " ADD COLUMN " + + column.name + " " + parse_type(column) + ";\n"; + } + + function rename_column(column) { + return exit('You can only add columns to tables in a change_table migration.'); + } + + function change_column(column) { + return exit('You can only add columns to tables in a change_table migration.'); + } + + function remove_column(column) { + return exit('You can only add columns to tables in a change_table migration.'); + } + + function add_index(index) { + return "CREATE INDEX IF NOT EXISTS ix_" + index.table_name + "_" + index.name + " ON " + index.table_name + "(" + index.name + ");"; + } + + function remove_index(index) { + return "DROP INDEX IF EXISTS ix_" + index.table_name + "_" + index.name + " ON " + index.table_name + "(" + index.name + ");"; + } + + return { + encode: function(o) { + if (o instanceof ChangeTable) + return change_table(o); + else if (o instanceof CreateTable) + return create_table(o); + else if (o instanceof DropTable) + return drop_table(o); + else if (o instanceof RenameTable) + return rename_table(o); + else if (o instanceof AddColumn) + return add_column(o); + else if (o instanceof RenameColumn) + return rename_column(o); + else if (o instanceof ChangeColumn) + return change_column(o); + else if (o instanceof RemoveColumn) + return remove_column(o); + else if (o instanceof AddIndex) + return add_index(o); + else if (o instanceof RemoveIndex) + return remove_index(o); + else + throw "Error: SQLite 3 Encoder Encountered Unknown Rule Type."; + } + }; +}(); + // The real "beef" is here, this section handles the command-line usage of the module. -var sys = require('sys'), +var sys = require('util'), exec = require('child_process').exec, fs = require('fs'), config = require('./config'); @@ -824,6 +1003,31 @@ var Connect = { client.query("create table schema_migrations (version BIGINT);"); return main(); }); + }, + sqlite3: function() { + var sqlite3 = require("sqlite3"); + if(config.sqlite3.verbose) { + sqlite3.verbose(); + } + client = new sqlite3.Database(config.sqlite3.filename, function(err) { + if(err) { + return exit(err); + } + client.get("select name from sqlite_master where type=? and name=?;", "table", "schema_migrations", function(err, row) { + if(row) { + return main(); + } + sys.puts("Creating migration table."); + client.run("create table schema_migrations(version BIGINT);", function(err) { + if(err) { + return exit(err); + } + return main(); + }); + }); + }); + client.end = function() {client = null;}; + client.query = client.all; } };