(This may be a duplicate of #312)
A migration file like so:
exports.up = function(knex, Promise) {
var first = knex.schema.createTable('first', function(table) {
table.increments('id');
table.string('name');
});
var second = first.then(function() {
return knex.schema.createTable('second', function(table) {
table.increments('id');
table.string('name');
});
});
return Promise.all([first, second]);
};
exports.down = function(knex, Promise) {
};
gives the following output:
{ __cid: '__cid1',
sql: 'create table "first" ("id" serial primary key, "name" varchar(255))',
bindings: [] }
{ __cid: '__cid2',
sql: 'create table "first" ("id" serial primary key, "name" varchar(255))',
bindings: [] }
{ __cid: '__cid3',
sql: 'create table "second" ("id" serial primary key, "name" varchar(255))',
bindings: [] }
error: duplicate key value violates unique constraint "pg_type_typname_nsp_index"
at Connection.parseE (/home/sohum/node_modules/pg/lib/connection.js:526:11)
at Connection.parseMessage (/home/sohum/node_modules/pg/lib/connection.js:356:17)
at Socket.<anonymous> (/home/sohum/node_modules/pg/lib/connection.js:105:22)
at Socket.EventEmitter.emit (events.js:95:17)
at Socket.<anonymous> (_stream_readable.js:745:14)
at Socket.EventEmitter.emit (events.js:92:17)
at emitReadable_ (_stream_readable.js:407:10)
at emitReadable (_stream_readable.js:403:5)
at readableAddChunk (_stream_readable.js:165:9)
at Socket.Readable.push (_stream_readable.js:127:10)
I don't think that you should return Promise.all([first, second]);
, since you already chained second
to first
. Could you please try to only return first
? Since your second createTable
is inside a then
which is chained off of first
, there is no need to add it to Promise.all
, since it will get executed anyway while first
is being resolved.
The promise spec says that once a promise is fulfilled, it should never be fulfilled again, and all future invocations of then
should return the cached result. If first
and second
are both fulfilled, then Promise.all([first, second])
should be ~ a no-op.
return first
has the same behaviour - i.e., it tries to create the first
, first
, and second
tables, which is _even more wrong_, as there should never be any code that actually evaluates anything inside the function passed to first.then
in that scenario.
Uhm ok that does sound weird. Not using Promise.all
could give a small boost in performance though ;)
But yeah, this does seem to be wrong. I'll mark this as a bug for now and wait until @tgriesser chimes in. Thanks for reporting!
Haha, I don't particularly care about one tick's worth of performance in my migrations :p
Sorry, I made a wrong statement above - return first
should still create the second table, it's just that the result of calling .then
on the result of the function won't wait for it. Which, w/e - it should still definitely not do first
, first
, second
!
Not sure I fully understand: Why wouldn't the 2nd table wait for the creation of the first one? Since you chain off of the first promise using then
they will be effectively run in series. That should not be an issue.
The second table's creation will, yes, but the result of the exports.up
function won't. I.e., if you call exports.up().then
, it's the same as calling first.then
and not second.then
- because it _is_ first.then
!
Ah, misunderstood you there - yeah you're right about that!
So the confusion here is that the createTable
method does not return a promise, but rather it returns a SchemaBuilder
object, which is a "thenable", i.e. calling .then
on the object will return a valid A+ promise, but the object its self is not a promise.
This was done specifically to allow you to use the syntax:
return knex.schema.createTable('first', function(table) {
table.increments('id');
table.string('name');
})
.createTable('second', function(table) {
table.increments('id');
table.string('name');
}).then(function() {
// all done
});
which should run the migrations as expected on the same connection in sequence.
Also, there is no need for Promise.all
here, as this would accomplish the same:
exports.up = function(knex, Promise) {
return knex.schema.createTable('first', function(table) {
table.increments('id');
table.string('name');
}).then(function() {
return knex.schema.createTable('second', function(table) {
table.increments('id');
table.string('name');
});
});
};
though if you wanted, you could do:
exports.up = function(knex, Promise) {
var first = knex.schema.createTable('first', function(table) {
table.increments('id');
table.string('name');
}).then(); // coerce thenable to a promise
var second = first.then(function() {
return knex.schema.createTable('second', function(table) {
table.increments('id');
table.string('name');
});
});
return Promise.all([first, second]);
};
and things would work as defined by the spec.
Ok that explains that. Didn't know that - sorry about the confusion for not being able to give a straight answer right away @SohumB
Yea, we have more complicated dependency graphs than that; this was just a minimal test case! Thanks for the help.
@johanneslumpe
sohum@diurnal ~ % cat test.js
var Promise = require('bluebird');
function promises() {
var first = Promise.resolve('wee');
var second = first.then(function() {
console.log('delayin');
return Promise.delay(1000);
}).then(function() {
console.log('done!');
});
return first;
}
promises().then(function() { console.log('yep, first is finished'); });
sohum@diurnal ~ % node test.js
delayin
yep, first is finished
done!
Most helpful comment
So the confusion here is that the
createTable
method does not return a promise, but rather it returns aSchemaBuilder
object, which is a "thenable", i.e. calling.then
on the object will return a valid A+ promise, but the object its self is not a promise.This was done specifically to allow you to use the syntax:
which should run the migrations as expected on the same connection in sequence.
Also, there is no need for
Promise.all
here, as this would accomplish the same:though if you wanted, you could do:
and things would work as defined by the spec.