Comparing version 0.2.0 to 0.3.0
365
index.js
#!/usr/bin/env node | ||
var txain = require('txain') | ||
var multiline = require('multiline') | ||
var _ = require('underscore') | ||
var pg = require('pg') | ||
var util = require('util') | ||
var DbDiff = exports.DbDiff = require('./dbdiff') | ||
var dbdiff = module.exports = {} | ||
exports.describeDatabase = require('./dialects').describeDatabase | ||
dbdiff.log = function() { | ||
var msg = util.format.apply(null, Array.prototype.slice.call(arguments)) | ||
dbdiff.logger(msg) | ||
} | ||
dbdiff.describeDatabase = function(conString, callback) { | ||
var client = new pg.Client(conString) | ||
var schema = { | ||
tables: {}, | ||
} | ||
txain(function(callback) { | ||
client.connect(callback) | ||
}) | ||
.then(function(client, done, callback) { | ||
client.query('SELECT * FROM pg_tables WHERE schemaname NOT IN ($1, $2, $3)', ['temp', 'pg_catalog', 'information_schema'], callback) | ||
}) | ||
.then(function(result, callback) { | ||
callback(null, result.rows) | ||
}) | ||
.map(function(table, callback) { | ||
var query = multiline(function() {;/* | ||
SELECT | ||
table_name, | ||
table_schema, | ||
column_name, | ||
data_type, | ||
udt_name, | ||
character_maximum_length, | ||
is_nullable, | ||
column_default | ||
FROM | ||
INFORMATION_SCHEMA.COLUMNS | ||
WHERE | ||
table_name=$1 AND table_schema=$2; | ||
*/}) | ||
client.query(query, [table.tablename, table.schemaname], callback) | ||
}) | ||
.then(function(descriptions, callback) { | ||
var tables = schema.tables = {} | ||
descriptions.forEach(function(desc) { | ||
desc.rows.forEach(function(row) { | ||
var tableName = util.format('"%s"."%s"', row.table_schema, row.table_name) | ||
var table = tables[tableName] | ||
if (!table) { | ||
tables[tableName] = [] | ||
table = tables[tableName] | ||
} | ||
delete row.table_schema | ||
delete row.table_name | ||
table.push(row) | ||
}) | ||
}) | ||
var query = multiline(function() {;/* | ||
SELECT | ||
i.relname as indname, | ||
i.relowner as indowner, | ||
idx.indrelid::regclass, | ||
am.amname as indam, | ||
idx.indkey, | ||
ARRAY( | ||
SELECT pg_get_indexdef(idx.indexrelid, k + 1, true) | ||
FROM generate_subscripts(idx.indkey, 1) as k | ||
ORDER BY k | ||
) AS indkey_names, | ||
idx.indexprs IS NOT NULL as indexprs, | ||
idx.indpred IS NOT NULL as indpred, | ||
ns.nspname | ||
FROM | ||
pg_index as idx | ||
JOIN pg_class as i | ||
ON i.oid = idx.indexrelid | ||
JOIN pg_am as am | ||
ON i.relam = am.oid | ||
JOIN pg_namespace as ns | ||
ON ns.oid = i.relnamespace | ||
AND ns.nspname NOT IN ('pg_catalog', 'pg_toast'); | ||
*/}) | ||
client.query(query, callback) | ||
}) | ||
.then(function(result, callback) { | ||
schema.indexes = result.rows | ||
client.query('SELECT * FROM information_schema.sequences', callback) | ||
}).then(function(result, callback) { | ||
schema.sequences = result.rows | ||
schema.sequences.forEach(function(sequence) { | ||
sequence.name = util.format('"%s"."%s"', sequence.sequence_schema, sequence.sequence_name) | ||
}) | ||
client.query('SELECT current_schema()', callback) | ||
}) | ||
.end(function(err, result) { | ||
client.end() | ||
if (err) return callback(err) | ||
schema.public_schema = result.rows[0].current_schema | ||
callback(null, schema) | ||
}) | ||
} | ||
function dataType(info) { | ||
var type | ||
if (info.data_type === 'ARRAY') { | ||
type = info.udt_name | ||
if (type.substring(0, 1) === '_') { | ||
type = type.substring(1) | ||
} | ||
type += '[]' | ||
} else if (info.data_type === 'USER-DEFINED') { | ||
type = info.udt_name // hstore for example | ||
} else { | ||
type = info.data_type | ||
} | ||
if (info.character_maximum_length) { | ||
type = type+'('+info.character_maximum_length+')' | ||
} | ||
return type | ||
} | ||
function columnNames(columns) { | ||
return columns.map(function(col) { | ||
return col.column_name | ||
}).sort() | ||
} | ||
function columnDescription(col) { | ||
var desc = dataType(col) | ||
if (col.column_default) { | ||
desc += ' DEFAULT '+col.column_default | ||
} | ||
desc += col.is_nullable === 'NO' ? ' NOT NULL' : ' NULL' | ||
return desc | ||
} | ||
function compareTables(tableName, db1, db2) { | ||
var table1 = db1.tables[tableName] | ||
var table2 = db2.tables[tableName] | ||
var columNames1 = columnNames(table1) | ||
var columNames2 = columnNames(table2) | ||
var diff1 = _.difference(columNames1, columNames2) | ||
var diff2 = _.difference(columNames2, columNames1) | ||
diff1.forEach(function(columnName) { | ||
dbdiff.log('ALTER TABLE %s DROP COLUMN "%s";', tableName, columnName) | ||
dbdiff.log() | ||
}) | ||
diff2.forEach(function(columnName) { | ||
var col = _.findWhere(table2, { column_name: columnName }) | ||
var type = dataType(col) | ||
dbdiff.log('ALTER TABLE %s ADD COLUMN "%s" %s;', tableName, columnName, columnDescription(col)) | ||
dbdiff.log() | ||
}) | ||
var common = _.intersection(columNames1, columNames2) | ||
common.forEach(function(columnName) { | ||
var col1 = _.findWhere(table1, { column_name: columnName }) | ||
var col2 = _.findWhere(table2, { column_name: columnName }) | ||
if (col1.data_type !== col2.data_type | ||
|| col1.udt_name !== col2.udt_name | ||
|| col1.character_maximum_length !== col2.character_maximum_length) { | ||
dbdiff.log('-- Previous data type was %s', dataType(col1)) | ||
dbdiff.log('ALTER TABLE %s ALTER COLUMN "%s" SET DATA TYPE %s;', tableName, columnName, dataType(col2)) | ||
dbdiff.log() | ||
} | ||
if (col1.is_nullable !== col2.is_nullable) { | ||
if (col2.is_nullable === 'YES') { | ||
dbdiff.log('ALTER TABLE %s ALTER COLUMN "%s" DROP NOT NULL;', tableName, columnName) | ||
} else { | ||
dbdiff.log('ALTER TABLE %s ALTER COLUMN "%s" SET NOT NULL;', tableName, columnName) | ||
} | ||
dbdiff.log() | ||
} | ||
}) | ||
} | ||
function indexNames(tableName, indexes) { | ||
return _.filter(indexes, function(index) { | ||
return util.format('"%s"."%s"', index.nspname, index.indrelid) === tableName | ||
}).map(function(index) { | ||
return index.indname | ||
}).sort() | ||
} | ||
function compareIndexes(tableName, db1, db2) { | ||
var indexes1 = db1.indexes | ||
var indexes2 = db2.indexes | ||
var indexNames1 = indexNames(tableName, indexes1) | ||
var indexNames2 = indexNames(tableName, indexes2) | ||
var diff1 = _.difference(indexNames1, indexNames2) | ||
var diff2 = _.difference(indexNames2, indexNames1) | ||
if (diff1.length > 0) { | ||
diff1.forEach(function(indexName) { | ||
var index = _.findWhere(indexes1, { indname: indexName }) | ||
dbdiff.log('DROP INDEX "%s"."%s";', index.nspname, indexName) | ||
}) | ||
} | ||
if (diff2.length > 0) { | ||
diff2.forEach(function(indexName) { | ||
var index = _.findWhere(indexes2, { indname: indexName }) | ||
dbdiff.log('CREATE INDEX "%s" ON %s USING %s (%s);', indexName, index.indrelid, index.indam, index.indkey_names.join(',')) | ||
}) | ||
} | ||
var inter = _.intersection(indexNames1, indexNames2) | ||
inter.forEach(function(indexName) { | ||
var index1 = _.findWhere(indexes1, { indname: indexName }) | ||
var index2 = _.findWhere(indexes2, { indname: indexName }) | ||
if (_.difference(index1.indkey_names, index2.indkey_names).length > 0) { | ||
var index = index2 | ||
dbdiff.log('-- Index "%s"."%s" needs to be changed', index.nspname, index.indname) | ||
dbdiff.log('DROP INDEX "%s"."%s";', index.nspname, index.indname) | ||
dbdiff.log('CREATE INDEX "%s" ON %s USING %s (%s);', index.indname, index.indrelid, index.indam, index.indkey_names.join(',')) | ||
dbdiff.log() | ||
} | ||
}) | ||
} | ||
function isNumber(n) { | ||
return +n == n | ||
} | ||
function sequenceDescription(sequence) { | ||
return util.format('CREATE SEQUENCE %s INCREMENT %s %s %s %s %s CYCLE;', | ||
sequence.name, | ||
sequence.increment, | ||
isNumber(sequence.minimum_value) ? 'MINVALUE '+sequence.minimum_value : 'NO MINVALUE', | ||
isNumber(sequence.maximum_value) ? 'MAXVALUE '+sequence.maximum_value : 'NO MAXVALUE', | ||
isNumber(sequence.start_value) ? 'START '+sequence.start_value : '', | ||
sequence.cycle_option === 'NO' ? 'NO' : '' | ||
) | ||
} | ||
function sequenceNames(db) { | ||
return db.sequences.map(function(sequence) { | ||
return sequence.name | ||
}).sort() | ||
} | ||
function compareSequences(db1, db2) { | ||
var sequenceNames1 = sequenceNames(db1) | ||
var sequenceNames2 = sequenceNames(db2) | ||
var diff1 = _.difference(sequenceNames1, sequenceNames2) | ||
var diff2 = _.difference(sequenceNames2, sequenceNames1) | ||
diff1.forEach(function(sequenceName) { | ||
dbdiff.log('DROP SEQUENCE %s;', sequenceName) | ||
dbdiff.log() | ||
}) | ||
diff2.forEach(function(sequenceName) { | ||
var sequence = _.findWhere(db2.sequences, { name: sequenceName }) | ||
dbdiff.log(sequenceDescription(sequence)) | ||
dbdiff.log() | ||
}) | ||
var inter = _.intersection(sequenceNames1, sequenceNames2) | ||
inter.forEach(function(sequenceName) { | ||
var sequence1 = _.findWhere(db1.sequences, { name: sequenceName }) | ||
var sequence2 = _.findWhere(db2.sequences, { name: sequenceName }) | ||
var desc1 = sequenceDescription(sequence1) | ||
var desc2 = sequenceDescription(sequence2) | ||
if (desc2 !== desc1) { | ||
dbdiff.log('DROP SEQUENCE %s;', sequenceName) | ||
dbdiff.log(desc2) | ||
dbdiff.log() | ||
} | ||
}) | ||
} | ||
dbdiff.compareSchemas = function(db1, db2) { | ||
compareSequences(db1, db2) | ||
var tableNames1 = _.keys(db1.tables).sort() | ||
var tableNames2 = _.keys(db2.tables).sort() | ||
var diff1 = _.difference(tableNames1, tableNames2) | ||
var diff2 = _.difference(tableNames2, tableNames1) | ||
diff1.forEach(function(tableName) { | ||
dbdiff.log('DROP TABLE %s;', tableName) | ||
dbdiff.log() | ||
}) | ||
diff2.forEach(function(tableName) { | ||
var columns = db2.tables[tableName].map(function(col) { | ||
var type = dataType(col) | ||
return '\n "'+col.column_name+'" '+columnDescription(col) | ||
}) | ||
dbdiff.log('CREATE TABLE %s (%s', tableName, columns.join(',')) | ||
dbdiff.log(');') | ||
dbdiff.log() | ||
var indexNames2 = indexNames(tableName, db2.indexes) | ||
indexNames2.forEach(function(indexName) { | ||
var index = _.findWhere(db2.indexes, { indname: indexName }) | ||
dbdiff.log('CREATE INDEX "%s" ON %s USING %s (%s);', index.indname, index.indrelid, index.indam, index.indkey_names.join(',')) | ||
dbdiff.log() | ||
}) | ||
}) | ||
var inter = _.intersection(tableNames1, tableNames2) | ||
inter.forEach(function(tableName) { | ||
compareTables(tableName, db1, db2) | ||
compareIndexes(tableName, db1, db2) | ||
}) | ||
} | ||
dbdiff.compareDatabases = function(conn1, conn2, callback) { | ||
var db1, db2 | ||
txain(function(callback) { | ||
dbdiff.describeDatabase(conn1, callback) | ||
}) | ||
.then(function(db, callback) { | ||
db1 = db | ||
dbdiff.describeDatabase(conn2, callback) | ||
}) | ||
.then(function(db, callback) { | ||
db2 = db | ||
dbdiff.compareSchemas(db1, db2) | ||
callback() | ||
}) | ||
.end(callback) | ||
} | ||
if (module.id === require.main.id) { | ||
@@ -356,2 +17,7 @@ var yargs = require('yargs') | ||
.alias('h', 'help') | ||
.option('level', { | ||
alias: 'l', | ||
describe: 'chooses the safety of the sql', | ||
choices: ['safe', 'warn', 'drop'] | ||
}) | ||
.argv | ||
@@ -361,11 +27,12 @@ | ||
var conn2 = argv._[1] | ||
dbdiff.logger = function(msg) { | ||
console.log(msg) | ||
} | ||
dbdiff.compareDatabases(conn1, conn2, function(err) { | ||
if (err) { | ||
console.error(String(err)) | ||
var dbdiff = new DbDiff() | ||
dbdiff.compare(conn1, conn2) | ||
.then(() => { | ||
console.log(dbdiff.commands(argv.level)) | ||
process.exit(0) | ||
}) | ||
.catch((err) => { | ||
console.error(err.stack) | ||
process.exit(1) | ||
} | ||
}) | ||
}) | ||
} |
{ | ||
"name": "dbdiff", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "Compares two postgresql databases and prints SQL commands to modify the first one in order to match the second one", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "nyc --reporter=lcov ./node_modules/.bin/_mocha" | ||
"test": "mocha", | ||
"coverage": "nyc --reporter=lcov npm test && nyc report" | ||
}, | ||
@@ -18,5 +19,5 @@ "keywords": [ | ||
"dependencies": { | ||
"multiline": "^1.0.2", | ||
"pg": "^4.4.3", | ||
"txain": "^0.4.1", | ||
"dedent": "^0.6.0", | ||
"pg": "^4.5.5", | ||
"pync": "^1.0.1", | ||
"underscore": "^1.8.3", | ||
@@ -26,4 +27,5 @@ "yargs": "^3.29.0" | ||
"devDependencies": { | ||
"mocha": "^2.3.3", | ||
"nyc": "^3.2.2" | ||
"mocha": "^2.4.5", | ||
"nyc": "^3.2.2", | ||
"standard": "^7.0.1" | ||
}, | ||
@@ -30,0 +32,0 @@ "bin": { |
@@ -15,6 +15,7 @@ # dbdiff | ||
# Usage | ||
# CLI Usage | ||
``` | ||
dbdiff \ | ||
-l safe | ||
postgres://user:pass@host[:port]/dbname1 \ | ||
@@ -24,13 +25,17 @@ postgres://user:pass@host[:port]/dbname2 | ||
# Caveats | ||
The flag `-l` or `--level` indicates the safety of the SQL. Allowed values are `safe`, `warn` and `drop` | ||
Some statements may fail or may produce data loss depending on the data stored in the target database. For example: | ||
# Safety level | ||
## Dropping tables and columns | ||
Some statements may fail or may produce data loss depending on the data stored in the target database. | ||
`dbdiff` will generate `DROP TABLE` and `DROP COLUMN` statements. Make sure you want to drop those tables / columns. | ||
- When the `safe` level is specified, only SQL statements that are guaranteed to preserve existing data will be printed. Any other command will be commented out. | ||
- When the `warn` level is specified also SQL statements that *may* fail because of existing data will be printed. These commands are for example: changes in data types or dropping a `NOT NULL` constraint. | ||
- When the `drop` level is specified all SQL statements are printed and this may contain `DROP COLUMN` or `DROP TABLE` statements. | ||
Dropping a sequence or dropping an index is considered safe. | ||
## Changing the data type of existing columns | ||
Postgresql is not able to change the existing data to the new data type. In that case you will get an error similar to this: | ||
Sometimes Postgresql won't be able to change the existing data to the new data type. In that case you will get an error similar to this: | ||
@@ -42,3 +47,3 @@ ``` | ||
So you will need to specify a `USING` expression to perform de conversion. For example to convert text to integers: | ||
You can manually specify a `USING` expression to perform de conversion. For example to convert text to integers: | ||
@@ -50,11 +55,24 @@ ``` | ||
## NOT NULL violations | ||
# Usage as a library | ||
If an existing column needs to be changed from nullable to not nullable the statement may fail if there are existing rows with a `NULL` value in that column. | ||
In that case you will get an error like: | ||
You can use `dbdiff` as a library: | ||
```javascript | ||
var dbdiff = require('dbdiff') | ||
dbdiff.describeDatabase(connString) | ||
.then((schema) => { | ||
// schema is a JSON-serializable object representing the database structure | ||
}) | ||
var diff = new dbdiff.DbDiff() | ||
// Compare two databases passing the connection strings | ||
diff.compare(conn1, conn2) | ||
.then(() => { | ||
console.log(diff.commands('drop')) | ||
}) | ||
// Compare two schemas | ||
diff.compareSchemas(schema1, schema2) | ||
console.log(diff.commands('drop')) | ||
``` | ||
ERROR: column "column_name" contains null values. | ||
``` | ||
You should fill the existing rows with not null values before making the column not nullable. |
268
test/test.js
@@ -1,131 +0,155 @@ | ||
var dbdiff = require('../index') | ||
/* globals describe it */ | ||
var utils = require('./utils') | ||
var assert = require('assert') | ||
var exec = require('child_process').exec | ||
var txain = require('txain') | ||
var childProcess = require('child_process') | ||
var dedent = require('dedent') | ||
describe('dbdiff.compareDatabases', function() { | ||
beforeEach(function(done) { | ||
utils.resetDatabases(done) | ||
const exec = (cmd) => { | ||
return new Promise((resolve, reject) => { | ||
childProcess.exec(cmd, (err, stdout, stderr) => { | ||
err && !stderr ? reject(err) : resolve({ stdout, stderr }) | ||
}) | ||
}) | ||
} | ||
it('should create a table', function(done) { | ||
describe('dbdiff.compareDatabases', () => { | ||
it('should create a table', () => { | ||
var commands1 = [] | ||
var commands2 = ['CREATE TABLE users (email VARCHAR(255), tags varchar(255)[])'] | ||
var expected = [ | ||
'CREATE TABLE "public"."users" (\n "email" character varying(255) NULL,\n \"tags\" varchar[] NULL', | ||
');', | ||
] | ||
utils.runAndCompare(commands1, commands2, expected, done) | ||
var expected = dedent` | ||
CREATE TABLE "public"."users" ( | ||
"email" character varying(255) NULL, | ||
"tags" varchar[] NULL | ||
); | ||
` | ||
return utils.runAndCompare(commands1, commands2, expected) | ||
}) | ||
it('should drop a table', function(done) { | ||
it('should drop a table', () => { | ||
var commands1 = ['CREATE TABLE users (email VARCHAR(255))'] | ||
var commands2 = [] | ||
var expected = [ | ||
'DROP TABLE "public"."users";', | ||
] | ||
utils.runAndCompare(commands1, commands2, expected, done) | ||
return Promise.resolve() | ||
.then(() => { | ||
var expected = 'DROP TABLE "public"."users";' | ||
return utils.runAndCompare(commands1, commands2, expected, ['drop']) | ||
}) | ||
.then(() => { | ||
var expected = '-- DROP TABLE "public"."users";' | ||
return utils.runAndCompare(commands1, commands2, expected, ['safe', 'warn']) | ||
}) | ||
}) | ||
it('should create a table wih an index', function(done) { | ||
it('should create a table wih an index', () => { | ||
var commands1 = [] | ||
var commands2 = ['CREATE TABLE users (id serial)'] | ||
var expected = [ | ||
'CREATE SEQUENCE "public"."users_id_seq" INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 NO CYCLE;', | ||
'CREATE TABLE "public"."users" (\n "id" integer DEFAULT nextval(\'users_id_seq\'::regclass) NOT NULL', | ||
');', | ||
] | ||
utils.runAndCompare(commands1, commands2, expected, done) | ||
var expected = dedent` | ||
CREATE SEQUENCE "public"."users_id_seq" INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 NO CYCLE; | ||
CREATE TABLE "public"."users" ( | ||
"id" integer DEFAULT nextval('users_id_seq'::regclass) NOT NULL | ||
); | ||
` | ||
return utils.runAndCompare(commands1, commands2, expected) | ||
}) | ||
it('should add a column to a table', function(done) { | ||
it('should add a column to a table', () => { | ||
var commands1 = ['CREATE TABLE users (email VARCHAR(255))'] | ||
var commands2 = [ | ||
'CREATE TABLE users (email VARCHAR(255))', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)' | ||
] | ||
var expected = [ | ||
'ALTER TABLE "public"."users" ADD COLUMN "first_name" character varying(255) NULL;', | ||
] | ||
utils.runAndCompare(commands1, commands2, expected, done) | ||
var expected = 'ALTER TABLE "public"."users" ADD COLUMN "first_name" character varying(255) NULL;' | ||
return utils.runAndCompare(commands1, commands2, expected) | ||
}) | ||
it('should drop a column from a table', function(done) { | ||
it('should drop a column from a table', () => { | ||
var commands1 = [ | ||
'CREATE TABLE users (email VARCHAR(255))', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)' | ||
] | ||
var commands2 = [ | ||
'CREATE TABLE users (email VARCHAR(255))', | ||
'CREATE TABLE users (email VARCHAR(255))' | ||
] | ||
var expected = [ | ||
'ALTER TABLE "public"."users" DROP COLUMN "first_name";', | ||
] | ||
utils.runAndCompare(commands1, commands2, expected, done) | ||
return Promise.resolve() | ||
.then(() => { | ||
var expected = 'ALTER TABLE "public"."users" DROP COLUMN "first_name";' | ||
return utils.runAndCompare(commands1, commands2, expected, ['drop']) | ||
}) | ||
.then(() => { | ||
var expected = '-- ALTER TABLE "public"."users" DROP COLUMN "first_name";' | ||
return utils.runAndCompare(commands1, commands2, expected, ['safe', 'warn']) | ||
}) | ||
}) | ||
it('should change the type of a column', function(done) { | ||
it('should change the type of a column', () => { | ||
var commands1 = [ | ||
'CREATE TABLE users (email VARCHAR(255))', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(200)', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(200)' | ||
] | ||
var commands2 = [ | ||
'CREATE TABLE users (email VARCHAR(255))', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)' | ||
] | ||
var expected = [ | ||
'-- Previous data type was character varying(200)', | ||
'ALTER TABLE "public"."users" ALTER COLUMN "first_name" SET DATA TYPE character varying(255);', | ||
] | ||
utils.runAndCompare(commands1, commands2, expected, done) | ||
return Promise.resolve() | ||
.then(() => { | ||
var expected = dedent` | ||
-- Previous data type was character varying(200) | ||
ALTER TABLE "public"."users" ALTER COLUMN "first_name" SET DATA TYPE character varying(255); | ||
` | ||
return utils.runAndCompare(commands1, commands2, expected, ['drop', 'warn']) | ||
}) | ||
.then(() => { | ||
var expected = dedent` | ||
-- Previous data type was character varying(200) | ||
-- ALTER TABLE "public"."users" ALTER COLUMN "first_name" SET DATA TYPE character varying(255); | ||
` | ||
return utils.runAndCompare(commands1, commands2, expected, ['safe']) | ||
}) | ||
}) | ||
it('should change a column to not nullable', function(done) { | ||
it('should change a column to not nullable', () => { | ||
var commands1 = [ | ||
'CREATE TABLE users (email VARCHAR(255))', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)' | ||
] | ||
var commands2 = [ | ||
'CREATE TABLE users (email VARCHAR(255))', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255) NOT NULL', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255) NOT NULL' | ||
] | ||
var expected = [ | ||
'ALTER TABLE "public"."users" ALTER COLUMN "first_name" SET NOT NULL;', | ||
] | ||
utils.runAndCompare(commands1, commands2, expected, done) | ||
return Promise.resolve() | ||
.then(() => { | ||
var expected = 'ALTER TABLE "public"."users" ALTER COLUMN "first_name" SET NOT NULL;' | ||
return utils.runAndCompare(commands1, commands2, expected, ['drop', 'warn']) | ||
}) | ||
.then(() => { | ||
var expected = '-- ALTER TABLE "public"."users" ALTER COLUMN "first_name" SET NOT NULL;' | ||
return utils.runAndCompare(commands1, commands2, expected, ['safe']) | ||
}) | ||
}) | ||
it('should change a column to nullable', function(done) { | ||
it('should change a column to nullable', () => { | ||
var commands1 = [ | ||
'CREATE TABLE users (email VARCHAR(255))', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255) NOT NULL', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255) NOT NULL' | ||
] | ||
var commands2 = [ | ||
'CREATE TABLE users (email VARCHAR(255))', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)' | ||
] | ||
var expected = [ | ||
'ALTER TABLE "public"."users" ALTER COLUMN "first_name" DROP NOT NULL;', | ||
] | ||
utils.runAndCompare(commands1, commands2, expected, done) | ||
var expected = 'ALTER TABLE "public"."users" ALTER COLUMN "first_name" DROP NOT NULL;' | ||
return utils.runAndCompare(commands1, commands2, expected) | ||
}) | ||
it('should create a sequence', function(done) { | ||
it('should create a sequence', () => { | ||
var commands1 = [] | ||
var commands2 = ['CREATE SEQUENCE seq_name'] | ||
var expected = [ | ||
'CREATE SEQUENCE "public"."seq_name" INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 NO CYCLE;', | ||
] | ||
utils.runAndCompare(commands1, commands2, expected, done) | ||
var expected = 'CREATE SEQUENCE "public"."seq_name" INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 NO CYCLE;' | ||
return utils.runAndCompare(commands1, commands2, expected) | ||
}) | ||
it('should drop a sequence', function(done) { | ||
it('should drop a sequence', () => { | ||
var commands1 = ['CREATE SEQUENCE seq_name'] | ||
var commands2 = [] | ||
var expected = [ | ||
'DROP SEQUENCE "public"."seq_name";', | ||
] | ||
utils.runAndCompare(commands1, commands2, expected, done) | ||
var expected = 'DROP SEQUENCE "public"."seq_name";' | ||
return utils.runAndCompare(commands1, commands2, expected) | ||
}) | ||
@@ -135,6 +159,6 @@ | ||
it('should create an index', function(done) { | ||
it('should create an index', () => { | ||
var commands1 = [ | ||
'CREATE TABLE users (email VARCHAR(255))', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)' | ||
] | ||
@@ -144,27 +168,23 @@ var commands2 = [ | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)', | ||
'CREATE INDEX users_email ON users (email)', | ||
'CREATE INDEX users_email ON "users" (email)' | ||
] | ||
var expected = [ | ||
'CREATE INDEX "users_email" ON users USING btree (email);', | ||
] | ||
utils.runAndCompare(commands1, commands2, expected, done) | ||
var expected = 'CREATE INDEX "users_email" ON "public"."users" USING btree (email);' | ||
return utils.runAndCompare(commands1, commands2, expected) | ||
}) | ||
it('should drop an index', function(done) { | ||
it('should drop an index', () => { | ||
var commands1 = [ | ||
'CREATE TABLE users (email VARCHAR(255))', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)', | ||
'CREATE INDEX users_email ON users (email)', | ||
'CREATE INDEX users_email ON users (email)' | ||
] | ||
var commands2 = [ | ||
'CREATE TABLE users (email VARCHAR(255))', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)', | ||
'ALTER TABLE users ADD COLUMN first_name VARCHAR(255)' | ||
] | ||
var expected = [ | ||
'DROP INDEX "public"."users_email";', | ||
] | ||
utils.runAndCompare(commands1, commands2, expected, done) | ||
var expected = 'DROP INDEX "public"."users_email";' | ||
return utils.runAndCompare(commands1, commands2, expected) | ||
}) | ||
it('should recreate an index', function(done) { | ||
it('should recreate an index', () => { | ||
var commands1 = [ | ||
@@ -174,3 +194,3 @@ 'CREATE TABLE users (email VARCHAR(255))', | ||
'ALTER TABLE users ADD COLUMN last_name VARCHAR(255)', | ||
'CREATE INDEX some_index ON users (first_name)', | ||
'CREATE INDEX some_index ON "users" (first_name)' | ||
] | ||
@@ -181,54 +201,64 @@ var commands2 = [ | ||
'ALTER TABLE users ADD COLUMN last_name VARCHAR(255)', | ||
'CREATE INDEX some_index ON users (last_name)', | ||
'CREATE INDEX some_index ON "users" (last_name)' | ||
] | ||
var expected = [ | ||
'-- Index "public"."some_index" needs to be changed', | ||
'DROP INDEX "public"."some_index";', | ||
'CREATE INDEX "some_index" ON users USING btree (last_name);', | ||
] | ||
utils.runAndCompare(commands1, commands2, expected, done) | ||
var expected = dedent` | ||
-- Index "public"."some_index" needs to be changed | ||
DROP INDEX "public"."some_index"; | ||
CREATE INDEX "some_index" ON "public"."users" USING btree (last_name); | ||
` | ||
return utils.runAndCompare(commands1, commands2, expected) | ||
}) | ||
it('should create a table with an index', function(done) { | ||
it('should create a table with an index', () => { | ||
var commands1 = [] | ||
var commands2 = [ | ||
'CREATE TABLE users (email VARCHAR(255))', | ||
'CREATE INDEX users_email ON users (email)', | ||
'CREATE INDEX users_email ON users (email)' | ||
] | ||
var expected = [ | ||
'CREATE TABLE "public"."users" (\n "email" character varying(255) NULL', | ||
');', | ||
'CREATE INDEX "users_email" ON users USING btree (email);', | ||
] | ||
utils.runAndCompare(commands1, commands2, expected, done) | ||
var expected = dedent` | ||
CREATE TABLE "public"."users" ( | ||
"email" character varying(255) NULL | ||
); | ||
CREATE INDEX "users_email" ON "public"."users" USING btree (email); | ||
` | ||
return utils.runAndCompare(commands1, commands2, expected) | ||
}) | ||
it('should run as a cli application', function(done) { | ||
it('should run as a cli application', () => { | ||
var conString1 = 'postgres://postgres:postgres@localhost/db1' | ||
var conString2 = 'postgres://postgres:postgres@localhost/db2' | ||
txain(function(callback) { | ||
utils.runCommands(['CREATE SEQUENCE seq_name'], [], callback) | ||
}) | ||
.then(function(arg, callback) { | ||
exec('node index.js '+conString1+' '+conString2, function(err, stdout, stderr) { | ||
assert.ifError(err) | ||
assert.equal(stdout, 'DROP SEQUENCE "public"."seq_name";\n\n') | ||
done() | ||
return utils.runCommands(['CREATE SEQUENCE seq_name'], []) | ||
.then(() => exec(`node index.js ${conString1} ${conString2}`)) | ||
.then((result) => { | ||
var { stdout } = result | ||
assert.equal(stdout, 'DROP SEQUENCE "public"."seq_name";\n') | ||
}) | ||
}) | ||
.end(done) | ||
}) | ||
it('should fail with an erorr', function(done) { | ||
it('should run as a cli application with level argument', () => { | ||
var conString1 = 'postgres://postgres:postgres@localhost/db1' | ||
var conString2 = 'postgres://postgres:postgres@localhost/db2' | ||
return utils.runCommands(['CREATE TABLE users (email VARCHAR(255))'], []) | ||
.then(() => exec(`node index.js -l safe ${conString1} ${conString2}`)) | ||
.then((result) => { | ||
var { stdout } = result | ||
assert.equal(stdout, '-- DROP TABLE "public"."users";\n') | ||
}) | ||
}) | ||
it('should fail with an erorr', () => { | ||
var conString1 = 'postgres://postgres:postgres@localhost/db1' | ||
var conString2 = 'postgres://postgres:postgres@localhost/none' | ||
require('child_process').exec('node index.js '+conString1+' '+conString2, function(err, stdout, stderr) { | ||
assert.ok(err) | ||
assert.ok(stderr.indexOf('error: database "none" does not exist') >= 0) | ||
done() | ||
}) | ||
return exec(`node index.js ${conString1} ${conString2}`) | ||
.then((result) => { | ||
var { stderr } = result | ||
assert.ok(stderr.indexOf('error: database "none" does not exist') >= 0) | ||
}) | ||
}) | ||
}) |
@@ -1,5 +0,5 @@ | ||
var pg = require('pg') | ||
var txain = require('txain') | ||
var dbdiff = require('../') | ||
var Client = require('../dialects/postgres-client') | ||
var DbDiff = require('../dbdiff') | ||
var assert = require('assert') | ||
var pync = require('pync') | ||
@@ -9,76 +9,37 @@ var conString1 = 'postgres://postgres:postgres@localhost/db1' | ||
var client1, client2 | ||
var client1 = new Client(conString1) | ||
var client2 = new Client(conString2) | ||
exports.connect = function(callback) { | ||
if (client1) return callback() | ||
client1 = new pg.Client(conString1) | ||
client2 = new pg.Client(conString2) | ||
var arr = [client1, client2] | ||
txain(arr) | ||
.each(function(client, callback) { | ||
client.connect(callback) | ||
}) | ||
.end(callback) | ||
exports.resetDatabases = () => { | ||
return Promise.all([ | ||
client1.query('drop schema public cascade; create schema public;'), | ||
client2.query('drop schema public cascade; create schema public;') | ||
]) | ||
} | ||
exports.resetDatabases = function(callback) { | ||
txain(function(callback) { | ||
exports.connect(callback) | ||
}) | ||
.then(function(callback) { | ||
callback(null, [client1, client2]) | ||
}) | ||
.each(function(client, callback) { | ||
client.query('drop schema public cascade; create schema public;', callback) | ||
}) | ||
.end(callback) | ||
exports.runCommands = (commands1, commands2) => { | ||
return exports.resetDatabases() | ||
.then(() => Promise.all([ | ||
pync.series(commands1, (command) => client1.query(command)), | ||
pync.series(commands2, (command) => client2.query(command)) | ||
])) | ||
} | ||
exports.runCommands = function(commands1, commands2, callback) { | ||
txain(function(callback) { | ||
callback(null, commands1) | ||
exports.runAndCompare = (commands1, commands2, expected, levels = ['drop', 'warn', 'safe']) => { | ||
var dbdiff = new DbDiff() | ||
return pync.series(levels, (level) => { | ||
return exports.runCommands(commands1, commands2) | ||
.then(() => dbdiff.compare(conString1, conString2)) | ||
.then(() => assert.equal(dbdiff.commands(level), expected)) | ||
.then(() => client1.query(dbdiff.commands(level))) | ||
.then(() => dbdiff.compare(conString1, conString2)) | ||
.then(() => { | ||
var lines = dbdiff.commands(level).split('\n') | ||
lines.forEach((line) => { | ||
if (line.length > 0 && line.substring(0, 2) !== '--') { | ||
assert.fail(`After running commands there is a change not executed: ${line}`) | ||
} | ||
}) | ||
}) | ||
}) | ||
.each(function(command, callback) { | ||
client1.query(command, callback) | ||
}) | ||
.then(function(callback) { | ||
callback(null, commands2) | ||
}).each(function(command, callback) { | ||
client2.query(command, callback) | ||
}) | ||
.end(callback) | ||
} | ||
exports.runAndCompare = function(commands1, commands2, expected, callback) { | ||
var arr = [] | ||
dbdiff.logger = function(msg) { | ||
if (msg) { | ||
arr.push(msg) | ||
} | ||
} | ||
txain(function(callback) { | ||
exports.runCommands(commands1, commands2, callback) | ||
}) | ||
.then(function(callback) { | ||
dbdiff.compareDatabases(conString1, conString2, callback) | ||
}) | ||
.then(function(callback) { | ||
// run the expected commands | ||
client1.query(arr.join('\n'), callback) | ||
}) | ||
.then(function(callback) { | ||
assert.deepEqual(arr, expected) | ||
// compare again the dbs | ||
arr.splice(0) | ||
dbdiff.compareDatabases(conString1, conString2, callback) | ||
}) | ||
.then(function(callback) { | ||
assert.deepEqual(arr, []) | ||
callback(null, arr) | ||
}) | ||
.end(callback) | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
30631
11
715
75
3
+ Addeddedent@^0.6.0
+ Addedpync@^1.0.1
+ Addeddedent@0.6.0(transitive)
+ Addedpync@1.0.3(transitive)
- Removedmultiline@^1.0.2
- Removedtxain@^0.4.1
- Removedabbrev@1.0.9(transitive)
- Removedamdefine@1.0.1(transitive)
- Removedappend-transform@0.2.2(transitive)
- Removedargparse@1.0.10(transitive)
- Removedarr-diff@1.1.0(transitive)
- Removedarr-flatten@1.1.0(transitive)
- Removedarray-slice@0.2.3(transitive)
- Removedarrify@1.0.1(transitive)
- Removedasync@1.5.2(transitive)
- Removedbalanced-match@1.0.2(transitive)
- Removedbrace-expansion@1.1.11(transitive)
- Removedbraces@1.8.5(transitive)
- Removedcaching-transform@1.0.1(transitive)
- Removedcommander@0.6.12.3.0(transitive)
- Removedcommondir@1.0.1(transitive)
- Removedconcat-map@0.0.1(transitive)
- Removedconvert-source-map@1.9.0(transitive)
- Removedcross-spawn@4.0.2(transitive)
- Removeddebug@2.2.0(transitive)
- Removeddeep-is@0.1.4(transitive)
- Removeddiff@1.4.0(transitive)
- Removederror-ex@1.3.2(transitive)
- Removedescape-string-regexp@1.0.2(transitive)
- Removedescodegen@1.8.1(transitive)
- Removedesprima@2.7.34.0.1(transitive)
- Removedestraverse@1.9.3(transitive)
- Removedesutils@2.0.3(transitive)
- Removedexpand-brackets@0.1.5(transitive)
- Removedexpand-range@1.8.2(transitive)
- Removedfast-levenshtein@2.0.6(transitive)
- Removedfilename-regex@2.0.1(transitive)
- Removedfill-range@2.2.4(transitive)
- Removedfind-cache-dir@0.1.1(transitive)
- Removedfind-up@1.1.2(transitive)
- Removedfor-in@1.0.2(transitive)
- Removedfor-own@0.1.5(transitive)
- Removedforeground-child@1.5.6(transitive)
- Removedfs.realpath@1.0.0(transitive)
- Removedfunction-bind@1.1.2(transitive)
- Removedget-stdin@4.0.1(transitive)
- Removedglob@3.2.115.0.156.0.47.2.3(transitive)
- Removedglob-base@0.3.0(transitive)
- Removedglob-parent@2.0.0(transitive)
- Removedgraceful-fs@4.2.11(transitive)
- Removedgrowl@1.9.2(transitive)
- Removedhandlebars@4.7.8(transitive)
- Removedhas-flag@1.0.0(transitive)
- Removedhasown@2.0.2(transitive)
- Removedhosted-git-info@2.8.9(transitive)
- Removedimurmurhash@0.1.4(transitive)
- Removedinflight@1.0.6(transitive)
- Removedinherits@2.0.4(transitive)
- Removedis-arrayish@0.2.1(transitive)
- Removedis-buffer@1.1.6(transitive)
- Removedis-core-module@2.16.1(transitive)
- Removedis-dotfile@1.0.3(transitive)
- Removedis-equal-shallow@0.1.3(transitive)
- Removedis-extglob@1.0.0(transitive)
- Removedis-glob@1.1.32.0.1(transitive)
- Removedis-number@2.1.04.0.0(transitive)
- Removedis-posix-bracket@0.1.1(transitive)
- Removedis-primitive@2.0.0(transitive)
- Removedis-utf8@0.2.1(transitive)
- Removedisarray@1.0.0(transitive)
- Removedisexe@2.0.0(transitive)
- Removedisobject@0.2.02.1.0(transitive)
- Removedistanbul@0.4.5(transitive)
- Removedjade@0.26.3(transitive)
- Removedjs-yaml@3.14.1(transitive)
- Removedkind-of@1.1.03.2.26.0.3(transitive)
- Removedlevn@0.3.0(transitive)
- Removedload-json-file@1.1.0(transitive)
- Removedlru-cache@2.7.34.1.5(transitive)
- Removedmath-random@1.0.4(transitive)
- Removedmd5-hex@1.3.0(transitive)
- Removedmd5-o-matic@0.1.1(transitive)
- Removedmicromatch@2.1.6(transitive)
- Removedminimatch@0.3.03.1.2(transitive)
- Removedminimist@0.0.81.2.8(transitive)
- Removedmkdirp@0.3.00.5.1(transitive)
- Removedmocha@2.5.3(transitive)
- Removedms@0.7.1(transitive)
- Removedmultiline@1.0.2(transitive)
- Removedneo-async@2.6.2(transitive)
- Removednopt@3.0.6(transitive)
- Removednormalize-package-data@2.5.0(transitive)
- Removednyc@5.6.0(transitive)
- Removedobject.omit@0.2.1(transitive)
- Removedonce@1.4.0(transitive)
- Removedoptionator@0.8.3(transitive)
- Removedos-homedir@1.0.2(transitive)
- Removedparse-glob@3.0.4(transitive)
- Removedparse-json@2.2.0(transitive)
- Removedpath-exists@2.1.0(transitive)
- Removedpath-is-absolute@1.0.1(transitive)
- Removedpath-parse@1.0.7(transitive)
- Removedpath-type@1.1.0(transitive)
- Removedpify@2.3.0(transitive)
- Removedpinkie@2.0.4(transitive)
- Removedpinkie-promise@2.0.1(transitive)
- Removedpkg-dir@1.0.0(transitive)
- Removedpkg-up@1.0.0(transitive)
- Removedprelude-ls@1.1.2(transitive)
- Removedpreserve@0.2.0(transitive)
- Removedpseudomap@1.0.2(transitive)
- Removedrandomatic@3.1.1(transitive)
- Removedread-pkg@1.1.0(transitive)
- Removedregex-cache@0.4.4(transitive)
- Removedrepeat-element@1.1.4(transitive)
- Removedrepeat-string@1.6.1(transitive)
- Removedresolve@1.1.71.22.10(transitive)
- Removedresolve-from@2.0.0(transitive)
- Removedrimraf@2.7.1(transitive)
- Removedsigmund@1.0.1(transitive)
- Removedsignal-exit@2.1.23.0.7(transitive)
- Removedslide@1.1.6(transitive)
- Removedsource-map@0.2.00.5.70.6.1(transitive)
- Removedspawn-wrap@1.4.3(transitive)
- Removedspdx-correct@3.2.0(transitive)
- Removedspdx-exceptions@2.5.0(transitive)
- Removedspdx-expression-parse@3.0.1(transitive)
- Removedspdx-license-ids@3.0.21(transitive)
- Removedsprintf-js@1.0.3(transitive)
- Removedstrip-bom@2.0.0(transitive)
- Removedstrip-indent@1.0.1(transitive)
- Removedsupports-color@1.2.03.2.3(transitive)
- Removedsupports-preserve-symlinks-flag@1.0.0(transitive)
- Removedto-iso-string@0.0.2(transitive)
- Removedtxain@0.4.3(transitive)
- Removedtype-check@0.3.2(transitive)
- Removeduglify-js@3.19.3(transitive)
- Removedvalidate-npm-package-license@3.0.4(transitive)
- Removedwhich@1.3.1(transitive)
- Removedword-wrap@1.2.5(transitive)
- Removedwordwrap@1.0.0(transitive)
- Removedwrappy@1.0.2(transitive)
- Removedwrite-file-atomic@1.3.4(transitive)
- Removedyallist@2.1.2(transitive)
Updatedpg@^4.5.5