Skip to content

Commit

Permalink
Alter table queries.
Browse files Browse the repository at this point in the history
  • Loading branch information
wbyoung committed Apr 3, 2015
1 parent 4fe57b7 commit 458803d
Show file tree
Hide file tree
Showing 9 changed files with 400 additions and 11 deletions.
21 changes: 21 additions & 0 deletions docs/source/guides/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,27 @@ issue</a> or pull request to see this happen sooner.
</div>
</div>

#### `#alterTable`

Alter existing tables. Pass the name of the table you want to alter and a callback
that will receive a table object with which you will be able to create columns
of different [field types](#field-types) as well as drop existing columns.

```js
schema.alterTable('articles', function(table) {
table.string('title'); // add a title column
table.drop('body'); // drop the body column
});
```

- `drop` Drops a table column (**not yet supported in SQLite3**)

```js
schema.alterTable('articles', function(table) {
table.drop('title'); // drop the title column
});
```


#### `#dropTable`

Expand Down
54 changes: 54 additions & 0 deletions lib/grammar/phrasing_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,60 @@ module.exports = Mixin.create(/** @lends Phrasing# */ {
return this._grammar.join(fragments);
},

/**
* Alter table statement.
*
* Subclasses should override to customize this query.
*
* @method
* @public
* @param {Object} data
* @param {String} data.name
* @param {Array.<Column>} data.added
* @param {Array.<String>} data.dropped
* @return {Statement} The statement.
*/
alterTable: function(data) {
var quoteField = this._grammar.field.bind(this._grammar);
var added = data.added.map(this.addColumn, this);
var dropped = data.dropped.map(this.dropColumn, this);
var delimited = this._grammar.delimit([].concat(dropped, added));
var joined = this._grammar.join(delimited);
var fragments = ['ALTER TABLE '];
fragments.push(quoteField(data.name), ' ');
fragments = fragments.concat(joined);
return Statement.create(this._grammar.join(fragments));
},

/**
* Add column fragment.
*
* Subclasses should override to customize this fragment.
*
* @method
* @public
* @param {Object} column
* @return {Fragment} The fragment.
* @see {@link Phrasing#createColumn}
*/
addColumn: function(column) {
return this._grammar.join(['ADD COLUMN ', this.createColumn(column)]);
},

/**
* Rename column fragment.
*
* Subclasses should override to customize this fragment.
*
* @method
* @public
* @param {String} column
* @return {Fragment} The fragment.
*/
dropColumn: function(column) {
return this._grammar.join(['DROP COLUMN ', this._grammar.field(column)]);
},

/**
* Drop table statement.
*
Expand Down
19 changes: 19 additions & 0 deletions lib/schema/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
var _ = require('lodash');
var BaseQuery = require('../query/base');
var CreateTableQuery = require('./table/create');
var AlterTableQuery = require('./table/alter');
var DropTableQuery = require('./table/drop');

/**
Expand Down Expand Up @@ -36,6 +37,24 @@ var Schema = BaseQuery.extend(/** @lends Schema# */ {
*/
createTable: function() { return this._spawn(CreateTableQuery, arguments); },

/**
* @function Schema~AlterTableCallback
* @param {TableAlterer} table The table object on which you can alter a
* table, for instance creating and dropping columns.
*/

/**
* Alter a table.
*
* @method
* @public
* @param {String} name The name of the table to alter.
* @param {Schema~AlterTableCallback} cb A callback that will allow you to
* create and drop table columns.
* @return {CreateTableQuery} A query to execute to alter the table.
*/
alterTable: function() { return this._spawn(AlterTableQuery, arguments); },

/**
* Drop a table.
*
Expand Down
76 changes: 76 additions & 0 deletions lib/schema/table/alter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use strict';

var _ = require('lodash');
var BaseQuery = require('../../query/base');
var ColumnAlterer = require('./column_alterer');

/**
* A query that allows altering tables.
*
* You will not create this query object directly. Instead, you will
* receive it via {@link Schema#alterTable}.
*
* @protected
* @constructor AlterTableQuery
* @extends BaseQuery
*/
var AlterTableQuery = BaseQuery.extend(/** @lends AlterTableQuery# */ {
init: function() { throw new Error('AlterTableQuery must be spawned.'); },

/**
* Override of {@link BaseQuery#_create}.
*
* @method
* @private
* @see {@link BaseQuery#_create}
* @see {@link Schema#alterTable} for parameter details.
*/
_create: function(name, cb) {
if (!cb) { throw new Error('Missing callback to create columns.'); }
this._super();
this._name = name;
this._alterer = ColumnAlterer.create();

cb(this._alterer);

var pkCount = _(this._alterer.added)
.map('options').filter('primaryKey').size();
if (pkCount > 1) {
throw new Error('Table may only have one primary key column.');
}
},

/**
* Duplication implementation.
*
* @method
* @protected
* @see {@link BaseQuery#_take}
*/
_take: function(orig) {
this._super(orig);
this._name = orig._name;
this._alterer = orig._alterer;
},

/**
* Override of {@link BaseQuery#sql}.
*
* @method
* @protected
* @see {@link BaseQuery#sql}
*/
sql: function() {
return this._adapter.phrasing.alterTable({
name: this._name,
added: this._alterer.added,
dropped: this._alterer.dropped,
renamed: this._alterer.renamed
});
}

});

module.exports = AlterTableQuery.reopenClass({
__name__: 'AlterTableQuery'
});
55 changes: 55 additions & 0 deletions lib/schema/table/column_alterer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use strict';

var ColumnCreator = require('./column_creator');
var property = require('../../util/property').fn;

/**
* A class that can create, drop & alter columns for tables.
*
* @protected
* @constructor ColumnAlterer
* @param {Array} columns An empty array that will be mutated to contain
* {@link ColumnAlterer~Column} objects as they are created.
*/
var ColumnAlterer = ColumnCreator.extend({
init: function() {
this._super.apply(this, arguments);
this._dropped = [];
},

/**
* Alias for {@link ColumnCreator#columns}.
*
* @private
* @scope internal
* @type {Array.<Column>}
* @readonly
*/
added: property({ property: 'columns' }),

/**
* Names of dropped columns.
*
* @private
* @scope internal
* @type {Array.<String>}
* @readonly
*/
dropped: property(),

/**
* Drop a column.
*
* @method
* @public
* @param {String} column The column name.
*/
drop: function(column) {
this._dropped.push(column);
}

});

module.exports = ColumnAlterer.reopenClass({
__name__: 'Table~ColumnAlterer'
});
19 changes: 14 additions & 5 deletions lib/schema/table/column_creator.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

var Class = require('../../util/class');
var Column = require('./column');
var property = require('../../util/property').fn;

/**
* Helper method for defining methods that can create attributable columns that
Expand Down Expand Up @@ -31,15 +32,23 @@ var col = function(type) {
*
* @protected
* @constructor ColumnCreator
* @param {Array} columns An empty array that will be mutated to contain
* {@link Column} objects as they are created.
*/
var ColumnCreator = Class.extend({
init: function(columns) {
this._super();
this._columns = columns;
init: function() {
this._super.apply(this, arguments);
this._columns = [];
},

/**
* Created columns.
*
* @private
* @scope internal
* @type {Array.<Column>}
* @readonly
*/
columns: property(),

/**
* Alias for {@link ColumnCreator#serial}.
*
Expand Down
11 changes: 6 additions & 5 deletions lib/schema/table/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ var CreateTableQuery = BaseQuery.extend(/** @lends CreateTableQuery# */ {
if (!cb) { throw new Error('Missing callback to create columns.'); }
this._super();
this._name = name;
this._columns = [];
this._creator = ColumnCreator.create();
this._options = {
ifNotExists: false
};

cb(ColumnCreator.create(this._columns));
cb(this._creator);

var pkCount = _(this._columns).map('options').filter('primaryKey').size();
var pkCount = _(this._creator.columns)
.map('options').filter('primaryKey').size();
if (pkCount > 1) {
throw new Error('Table may only have one primary key column.');
}
Expand All @@ -52,7 +53,7 @@ var CreateTableQuery = BaseQuery.extend(/** @lends CreateTableQuery# */ {
_take: function(orig) {
this._super(orig);
this._name = orig._name;
this._columns = orig._columns;
this._creator = orig._creator;
this._options = _.clone(orig._options);
},

Expand All @@ -66,7 +67,7 @@ var CreateTableQuery = BaseQuery.extend(/** @lends CreateTableQuery# */ {
sql: function() {
return this._adapter.phrasing.createTable({
name: this._name,
columns: this._columns,
columns: this._creator.columns,
options: this._options
});
},
Expand Down
Loading

0 comments on commit 458803d

Please sign in to comment.