diff --git a/lib/column.js b/lib/column.js index 7a80e90..ba315cf 100644 --- a/lib/column.js +++ b/lib/column.js @@ -8,6 +8,7 @@ Column.iface = [ 'getDataType', 'getMaxLength', 'isPrimaryKey', + 'isForeignKey', 'getDefaultValue', 'isUnique', 'isAutoIncrementing' diff --git a/lib/mysql/column.js b/lib/mysql/column.js index 499e912..4673ebf 100644 --- a/lib/mysql/column.js +++ b/lib/mysql/column.js @@ -29,6 +29,10 @@ Column.prototype.isPrimaryKey = function () { return this.meta.column_key === 'PRI'; }; +Column.prototype.isForeignKey = function () { + return this.meta.foreign_key === true; +}; + Column.prototype.getDefaultValue = function () { return this.meta.column_default; }; diff --git a/lib/mysql/driver.js b/lib/mysql/driver.js index 403c53f..a0eedb7 100644 --- a/lib/mysql/driver.js +++ b/lib/mysql/driver.js @@ -2,10 +2,18 @@ var mysql = require('mysql'); var Table = require('../table'); var Column = require('./column'); var Index = require('./index'); +var metaUtils = require('../util'); +var arrayContains = metaUtils.arrayContains; exports.connect = function (options, callback) { try { - var client = mysql.createClient(options); + var client; + if (typeof(mysql.createConnection) === 'undefined') { + client = new mysql.createClient(options); + } else { + client = new mysql.createConnection(options); + } + callback(null, new Driver(client)); } catch (err) { callback(err); @@ -39,9 +47,37 @@ Driver.prototype.getTables = function (callback) { }; Driver.prototype.getColumns = function (tableName, callback) { - var handler = handleResults.bind(this, Column, callback); + var handler = handleResults.bind(this, Column, findForeignKeys.bind(this)); var db = getClientDatabase(this.client); this.client.query("select * from information_schema.columns where table_schema = ? and table_name = ?", [db, tableName], handler); + + function findForeignKeys(err, columns) { + if(err) { + return callback(err); + } + + this.client.query("select * from information_schema.key_column_usage " + + "WHERE table_schema = ? AND table_name = ? AND referenced_column_name IS NOT NULL", + [db, tableName], onForeignKeysResult.bind(this, columns, this.client)); + } + + function onForeignKeysResult(columns, client, err, result) { + if(err) { + return callback(err); + } + + var foreignKeys = result.map(function (row) { + return row.COLUMN_NAME; + }); + + columns.forEach(function (column) { + if (arrayContains(foreignKeys, column.getName())) { + column.meta.foreign_key = true; + } + }); + + callback(null, columns); + } }; Driver.prototype.getIndexes = function(tableName, callback) { diff --git a/lib/pg/column.js b/lib/pg/column.js index 5c48a52..f36aff6 100644 --- a/lib/pg/column.js +++ b/lib/pg/column.js @@ -29,6 +29,10 @@ Column.prototype.isPrimaryKey = function () { return this.meta.primary_key === true; }; +Column.prototype.isForeignKey = function () { + return this.meta.foreign_key === true; +}; + Column.prototype.getDefaultValue = function () { return this.meta.column_default; }; diff --git a/lib/pg/driver.js b/lib/pg/driver.js index fba3297..dee1215 100644 --- a/lib/pg/driver.js +++ b/lib/pg/driver.js @@ -2,6 +2,7 @@ var pg = require('pg'); var Table = require('../table'); var Column = require('./column'); var Index = require('./index'); +var arrayContains = require('../util').arrayContains; exports.connect = function (options, callback) { var client = new pg.Client(options); @@ -12,7 +13,6 @@ exports.connect = function (options, callback) { }; exports.connectToExistingConnection = function(client, callback){ - // console.log("connecting to existing connection"); callback(null, new Driver(client)); }; @@ -105,10 +105,45 @@ Driver.prototype.getColumns = function (tableName, callback) { column.meta.unique = true; } }); + findForeignKeys(null, columns, client); + } + + function findForeignKeys(err, columns, client) { + if(err) { + return callback(err); + } + + client.query("select cu.column_name " + + "from information_schema.key_column_usage cu, " + + "information_schema.table_constraints tc " + + "where tc.table_name = cu.table_name " + + "and tc.constraint_type = 'FOREIGN KEY' " + + "and tc.constraint_name = cu.constraint_name " + + "and tc.table_name = $1", + [tableName], + onForeignKeysResult.bind(this, columns, client) + ); + } + + function onForeignKeysResult(columns, client, err, result) { + if(err) { + return callbacke(err); + } + + var foreignKeys = result.rows.map(function (row) { + return row.column_name; + }); + + columns.forEach(function (column) { + if (arrayContains(foreignKeys, column.getName())) { + column.meta.foreign_key = true; + } + }); + checkAutoincrement(null, columns, client); } -function checkAutoincrement(err, columns, client) { + function checkAutoincrement(err, columns, client) { if (err) { return callback(err); } @@ -135,6 +170,7 @@ function checkAutoincrement(err, columns, client) { column.meta.autoincrement = true; } }); + callback(null, columns); } }; @@ -171,12 +207,3 @@ function handleResults(obj, callback, err, result) { callback(null, objects); } - -function arrayContains(arr, item) { - for (var i = 0; i < arr.length; i++) { - if (arr[i] === item) { - return true; - } - } - return false; -} diff --git a/lib/sqlite3/column.js b/lib/sqlite3/column.js index 45b193f..d3e1669 100644 --- a/lib/sqlite3/column.js +++ b/lib/sqlite3/column.js @@ -19,10 +19,10 @@ Column.prototype.isNullable = function () { Column.prototype.getMaxLength = function () { var match = this.getDataType().match(/\((\d+)\)$/); - if (match[1]) { + if (match && match[1]) { return parseInt(match[1]); } - return -1; + return null; }; Column.prototype.getDataType = function() { @@ -33,6 +33,10 @@ Column.prototype.isPrimaryKey = function () { return this.meta.pk === 1; }; +Column.prototype.isForeignKey = function () { + return this.meta.fk === 1; +}; + Column.prototype.getDefaultValue = function () { return this.meta.dflt_value; }; diff --git a/lib/sqlite3/driver.js b/lib/sqlite3/driver.js index 03a1efc..936a9d8 100644 --- a/lib/sqlite3/driver.js +++ b/lib/sqlite3/driver.js @@ -2,6 +2,7 @@ var sqlite3 = require('sqlite3'); var Table = require('../table'); var Column = require('./column'); var Index = require('./index'); +var arrayContains = require('../util').arrayContains; exports.connect = function (options, callback) { try { @@ -71,7 +72,31 @@ Driver.prototype.getColumns = function (tableName, callback) { } colNumber++; }); - callback(err,previous); + + findForeignKeys(previous, this.client); + } + + function findForeignKeys(columns, client) { + client.all('PRAGMA foreign_key_list(' + tableName + ');', + onForeignKeysResult.bind(this, columns, client)); + } + + function onForeignKeysResult(columns, client, err, result) { + if(err) { + return callback(err); + } + + var foreignKeys = result.map(function (row) { + return row.from; + }); + + columns.forEach(function (column) { + if (arrayContains(foreignKeys, column.getName())) { + column.meta.fk = 1; + } + }); + + callback(err, columns); } }; diff --git a/lib/util.js b/lib/util.js index f72b469..3c1f2e1 100644 --- a/lib/util.js +++ b/lib/util.js @@ -4,4 +4,13 @@ exports.lowercaseKeys = function (obj) { newObj[key.toLowerCase()] = obj[key]; }); return newObj; -}; \ No newline at end of file +}; + +exports.arrayContains = function(arr, item) { + for (var i = 0; i < arr.length; i++) { + if (arr[i] === item) { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/test/mysql/column-test.js b/test/mysql/column-test.js index 85f0732..31151f5 100644 --- a/test/mysql/column-test.js +++ b/test/mysql/column-test.js @@ -2,7 +2,7 @@ var expect = require('chai').expect; var Column = require('../../lib/mysql/column'); var iface = require('../../lib/column').iface; -describe('pg column', function () { +describe('mysql column', function () { it('should implement all the methods defined in the base column interface', function (done) { var c = new Column({ column_name: 'col', data_type: 'integer' }); iface.forEach(function (method) { @@ -52,6 +52,14 @@ describe('pg column', function () { expect(c.isPrimaryKey()).to.be.false; done(); }); + + it('should implement the isForeignKey method', function(done) { + var c = new Column({ column_name: 'col', foreign_key: true }); + expect(c.isForeignKey()).to.be.true; + var c = new Column({ column_name: 'col', foreign_key: false }); + expect(c.isForeignKey()).to.be.false; + done(); + }); it('should implement the getDefaultValue method', function(done) { var c = new Column({ column_name: 'col', column_default: '30' }); diff --git a/test/mysql/driver-test.js b/test/mysql/driver-test.js index 97fa249..45f3551 100644 --- a/test/mysql/driver-test.js +++ b/test/mysql/driver-test.js @@ -8,11 +8,19 @@ describe('mysql driver', function() { mysql.connect({ database: 'db_meta_test' }, onConnect); function onConnect(err, dbDriver) { + if(err) { + return done(err); + } + driver = dbDriver; - driver.client.query('CREATE TABLE person (id INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(100) UNIQUE, age INTEGER DEFAULT 30);', createIndex); + driver.client.query('CREATE TABLE person (id INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(100) UNIQUE, age INTEGER DEFAULT 30, ref INTEGER, CONSTRAINT FOREIGN KEY (ref) REFERENCES person(id));', createIndex); } function createIndex(err) { + if(err) { + return done(err); + } + driver.client.query('CREATE INDEX person_name_idx ON person (name, age)', done); } }); @@ -50,7 +58,7 @@ describe('mysql driver', function() { function onResult(err, columns) { expect(err).to.be.null; expect(columns).not.to.be.empty; - expect(columns.length).to.equal(4); + expect(columns.length).to.equal(5); var idColumn = getColumnByName(columns, 'id'); expect(idColumn).not.to.be.null; @@ -59,6 +67,7 @@ describe('mysql driver', function() { expect(idColumn.getMaxLength()).to.be.null; expect(idColumn.getDataType()).to.equal('INT'); expect(idColumn.isPrimaryKey()).to.be.true; + expect(idColumn.isForeignKey()).to.be.false; expect(idColumn.isUnique()).to.be.true; expect(idColumn.isAutoIncrementing()).to.be.true; @@ -87,6 +96,15 @@ describe('mysql driver', function() { expect(ageColumn.isUnique()).to.be.false; expect(ageColumn.isAutoIncrementing()).to.be.false; + var refColumn = getColumnByName(columns, 'ref'); + expect(refColumn).to.be.not.null; + expect(refColumn.meta).not.to.be.empty; + expect(refColumn.isForeignKey(), 'FK').to.be.true; + expect(refColumn.isPrimaryKey()).to.be.false; + expect(refColumn.getDataType()).to.equal('INT'); + expect(refColumn.getMaxLength()).to.be.null; + expect(refColumn.isAutoIncrementing()).to.be.false; + done(); } }); @@ -95,19 +113,19 @@ describe('mysql driver', function() { driver.getIndexes('person', onResult); function onResult(err, indexes) { - expect(indexes.length).to.equal(4); + expect(indexes.length).to.equal(5); expect(indexes[0].getName()).to.equal('PRIMARY'); expect(indexes[0].getTableName()).to.equal('person'); expect(indexes[0].getColumnName()).to.equal('id'); expect(indexes[1].getName()).to.equal('email'); expect(indexes[1].getTableName()).to.equal('person'); expect(indexes[1].getColumnName()).to.equal('email'); - expect(indexes[2].getName()).to.equal('person_name_idx'); - expect(indexes[2].getTableName()).to.equal('person'); - expect(indexes[2].getColumnName()).to.equal('name'); expect(indexes[3].getName()).to.equal('person_name_idx'); expect(indexes[3].getTableName()).to.equal('person'); - expect(indexes[3].getColumnName()).to.equal('age'); + expect(indexes[3].getColumnName()).to.equal('name'); + expect(indexes[4].getName()).to.equal('person_name_idx'); + expect(indexes[4].getTableName()).to.equal('person'); + expect(indexes[4].getColumnName()).to.equal('age'); done(); } }); diff --git a/test/pg/column-test.js b/test/pg/column-test.js index 7da23a9..1f02adf 100644 --- a/test/pg/column-test.js +++ b/test/pg/column-test.js @@ -53,6 +53,14 @@ describe('pg column', function () { done(); }); + it('should implement the isForeignKey method', function(done) { + var c = new Column({ column_name: 'col', foreign_key: true }); + expect(c.isForeignKey()).to.be.true; + c = new Column({ column_name: 'col', foreign_key: false }); + expect(c.isForeignKey()).to.be.false; + done(); + }); + it('should implement the getDefaultValue method', function(done) { var c = new Column({ column_name: 'col', column_default: '30' }); expect(c.getDefaultValue()).to.equal('30'); diff --git a/test/pg/driver-test.js b/test/pg/driver-test.js index 5527e81..ff355f5 100644 --- a/test/pg/driver-test.js +++ b/test/pg/driver-test.js @@ -9,7 +9,7 @@ describe('pg driver', function() { function onConnect(err, dbDriver) { driver = dbDriver; - driver.client.query('CREATE TABLE person (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(100), age INTEGER DEFAULT 30,number SERIAL NOT NULL, CONSTRAINT person_email_key UNIQUE (email))', createIndex); + driver.client.query('CREATE TABLE person (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(100), age INTEGER DEFAULT 30,number SERIAL NOT NULL, ref INTEGER REFERENCES person(id), CONSTRAINT person_email_key UNIQUE (email))', createIndex); } function createIndex(err) { @@ -50,7 +50,7 @@ describe('pg driver', function() { function onResult(err, columns) { expect(err).to.be.null; expect(columns).not.to.be.empty; - expect(columns.length).to.equal(5); + expect(columns.length).to.equal(6); var idColumn = getColumnByName(columns, 'id'); expect(idColumn).not.to.be.null; @@ -59,6 +59,7 @@ describe('pg driver', function() { expect(idColumn.getDataType()).to.equal('INTEGER'); expect(idColumn.getMaxLength()).to.be.null; expect(idColumn.isPrimaryKey()).to.be.true; + expect(idColumn.isForeignKey()).to.be.false; expect(idColumn.isUnique()).to.be.true; expect(idColumn.isAutoIncrementing()).to.be.false; @@ -96,6 +97,15 @@ describe('pg driver', function() { expect(numberColumn.isUnique()).to.be.false; expect(numberColumn.isAutoIncrementing()).to.be.true; + var refColumn = getColumnByName(columns, 'ref'); + expect(refColumn).to.be.not.null; + expect(refColumn.meta).not.to.be.empty; + expect(refColumn.isForeignKey()).to.be.true; + expect(refColumn.isPrimaryKey()).to.be.false; + expect(refColumn.getDataType()).to.equal('INTEGER'); + expect(refColumn.getMaxLength()).to.be.null; + expect(refColumn.isAutoIncrementing()).to.be.false; + done(); } }); @@ -110,10 +120,10 @@ describe('pg driver', function() { expect(indexes[1].getColumnName()).to.equal('email'); expect(indexes[2].getName()).to.equal('person_name_idx'); expect(indexes[2].getTableName()).to.equal('person'); - expect(indexes[2].getColumnName()).to.equal('name'); + expect(indexes[2].getColumnName()).to.equal('age'); expect(indexes[3].getName()).to.equal('person_name_idx'); expect(indexes[3].getTableName()).to.equal('person'); - expect(indexes[3].getColumnName()).to.equal('age'); + expect(indexes[3].getColumnName()).to.equal('name'); done(); } }); diff --git a/test/sqlite3/column-test.js b/test/sqlite3/column-test.js index d1a340b..fc23569 100644 --- a/test/sqlite3/column-test.js +++ b/test/sqlite3/column-test.js @@ -2,7 +2,7 @@ var expect = require('chai').expect; var Column = require('../../lib/sqlite3/column'); var iface = require('../../lib/column').iface; -describe('pg column', function () { +describe('sqlite3 column', function () { it('should implement all the methods defined in the base column interface', function (done) { var c = new Column({ name: 'col', type: 'varchar(255)' }); iface.forEach(function (method) { @@ -53,6 +53,14 @@ describe('pg column', function () { done(); }); + it('should implement the isForeignKey method', function(done) { + var c = new Column({ column_name: 'col', fk: 1 }); + expect(c.isForeignKey()).to.be.true; + var c = new Column({ column_name: 'col', fk: 0 }); + expect(c.isForeignKey()).to.be.false; + done(); + }); + it('should implement the getDefaultValue method', function(done) { var c = new Column({ column_name: 'col', dflt_value: '30' }); expect(c.getDefaultValue()).to.equal('30'); diff --git a/test/sqlite3/driver-test.js b/test/sqlite3/driver-test.js index 320a893..2af7d30 100644 --- a/test/sqlite3/driver-test.js +++ b/test/sqlite3/driver-test.js @@ -9,7 +9,7 @@ describe('sqlite3 driver', function() { function onConnect(err, dbDriver) { driver = dbDriver; - driver.client.run('CREATE TABLE person (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(100) UNIQUE, age INTEGER DEFAULT 30)', createIndex); + driver.client.run('CREATE TABLE person (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(100) UNIQUE, age INTEGER DEFAULT 30, ref INTEGER, FOREIGN KEY (ref) REFERENCES person(id))', createIndex); } function createIndex(err) { @@ -50,7 +50,7 @@ describe('sqlite3 driver', function() { function onResult(err, columns) { expect(err).to.be.null; expect(columns).not.to.be.empty; - expect(columns.length).to.equal(4); + expect(columns.length).to.equal(5); var idColumn = getColumnByName(columns, 'id'); expect(idColumn).not.to.be.null; @@ -58,6 +58,7 @@ describe('sqlite3 driver', function() { expect(idColumn.isNullable()).to.be.false; expect(idColumn.getDataType()).to.equal('INTEGER'); expect(idColumn.isPrimaryKey()).to.be.true; + expect(idColumn.isForeignKey(), 'FK').to.be.false; expect(idColumn.isUnique()).to.be.true; expect(idColumn.isAutoIncrementing()).to.be.true; @@ -86,6 +87,15 @@ describe('sqlite3 driver', function() { expect(ageColumn.isUnique()).to.be.false; expect(ageColumn.isAutoIncrementing()).to.be.false; + var refColumn = getColumnByName(columns, 'ref'); + expect(refColumn).to.be.not.null; + expect(refColumn.meta).not.to.be.empty; + expect(refColumn.isForeignKey(), 'FK').to.be.true; + expect(refColumn.isPrimaryKey()).to.be.false; + expect(refColumn.getDataType()).to.equal('INTEGER'); + expect(refColumn.getMaxLength()).to.be.null; + expect(refColumn.isAutoIncrementing()).to.be.false; + done(); } });