Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeff Kunkle committed Jul 2, 2012
0 parents commit 9a757de
Show file tree
Hide file tree
Showing 13 changed files with 406 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
.idea
.DS_Store
node_modules
16 changes: 16 additions & 0 deletions lib/column.js
@@ -0,0 +1,16 @@
module.exports = Column;

function Column() {};

Column.iface = [
'getName',
'isNullable',
'getDataType',
'getMaxLength'
];

Column.iface.forEach(function(method) {
Column.prototype[method] = function() {
throw new Error(method + ' not yet implemented');
};
});
15 changes: 15 additions & 0 deletions lib/data-type.js
@@ -0,0 +1,15 @@
/**
* This file serves as a reference for the strings that each
* driver implementation should return for valid data types.
*/
module.exports = {
integer: 'INTEGER',
boolean: 'BOOLEAN',
date: 'DATE',
text: 'TEXT',
varchar: 'VARCHAR',
float: 'FLOAT',
double: 'DOUBLE',
time: 'TIME',
timestamp: 'TIMESTAMP'
};
16 changes: 16 additions & 0 deletions lib/db-meta.js
@@ -0,0 +1,16 @@
var path = require('path');

module.exports = function (driverName, options, callback) {
if (arguments.length < 3) {
callback = options;
options = {};
}

try {
var driverPath = path.join(__dirname, driverName, 'driver');
var driver = require(driverPath);
driver.connect(options, callback);
} catch (e) {
callback(new Error('Unsupported driver: ' + driverName));
}
};
53 changes: 53 additions & 0 deletions lib/pg/column.js
@@ -0,0 +1,53 @@
module.exports = Column;

var util = require('util');
var BaseColumn = require('../column');
var dataType = require('../data-type');

function Column(props) {
this.meta = props;
}
util.inherits(Column, BaseColumn);

Column.prototype.getName = function () {
return this.meta.column_name;
};

Column.prototype.isNullable = function () {
return this.meta.is_nullable === 'YES';
};

Column.prototype.getMaxLength = function () {
return this.meta.character_maximum_length;
};

Column.prototype.getDataType = function() {
switch (this.meta.data_type) {
case 'integer':
case 'int':
case 'int4':
return dataType.integer;
case 'boolean':
case 'bool':
return dataType.boolean;
case 'text':
return dataType.text;
case 'varchar':
case 'character varying':
return dataType.varchar;
case 'real':
case 'float4':
return dataType.float;
case 'double precision':
case 'float8':
return dataType.double;
case 'time':
case 'timetz':
return dataType.time;
case 'timestamp':
case 'timestamptz':
return dataType.timestamp;
default:
return this.meta.data_type.toUpperCase();
}
};
56 changes: 56 additions & 0 deletions lib/pg/driver.js
@@ -0,0 +1,56 @@
var util = require('util');
var pg = require('pg');
var Table = require('./table');
var Column = require('./column');

exports.connect = function (options, callback) {
var client = new pg.Client(options);
client.connect(onConnect);

function onConnect(err) {
callback(err, new Driver(client));
}
};

function Driver(client) {
this.client = client;
}

Driver.prototype.getVersion = function (callback) {
this.client.query('select version()', onResult);

function onResult(err, result) {
if (err) {
return callback(err);
}

callback(null, result.rows[0].version);
}
};

Driver.prototype.getTables = function (callback) {
var handler = handleResults.bind(this, Table, callback);
this.client.query("SELECT * FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema NOT IN ('pg_catalog', 'information_schema');", handler);
};

Driver.prototype.getColumns = function (tableName, callback) {
var handler = handleResults.bind(this, Column, callback);
this.client.query("SELECT * FROM information_schema.columns WHERE table_name = $1", [tableName], handler);
};

Driver.prototype.close = function(callback) {
this.client.end();
callback();
};

function handleResults(obj, callback, err, result) {
if (err) {
return callback(err);
}

var objects = result.rows.map(function (row) {
return new obj(row);
});

callback(null, objects);
}
13 changes: 13 additions & 0 deletions lib/pg/table.js
@@ -0,0 +1,13 @@
module.exports = Table;

var util = require('util');
var BaseTable = require('../table');

function Table(props) {
this.meta = props;
}
util.inherits(Table, BaseTable);

Table.prototype.getName = function() {
return this.meta.table_name;
};
11 changes: 11 additions & 0 deletions lib/table.js
@@ -0,0 +1,11 @@
module.exports = Table;

function Table() {};

Table.iface = ['getName'];

Table.iface.forEach(function(method) {
Table.prototype[method] = function() {
throw new Error(method + ' not yet implemented');
};
});
33 changes: 33 additions & 0 deletions package.json
@@ -0,0 +1,33 @@
{
"author": "Jeff Kunkle <jkunkle@nearinfinity.com>",
"name": "db-meta",
"keywords": [
"database",
"db",
"metadata",
"postgres"
],
"description": "Relational database metadata extraction library",
"version": "0.1.0",
"license": "MIT",
"main": "./lib/db-meta.js",
"bugs": {
"url": "https://github.com/nearinfinity/node-db-meta/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/nearinfinity/node-db-meta.git"
},
"dependencies": {},
"devDependencies": {
"chai": "~1.1.0",
"mocha": "~1.2.1"
},
"optionalDependencies": {},
"engines": {
"node": ">=0.6"
},
"scripts": {
"test": "mocha"
}
}
17 changes: 17 additions & 0 deletions test/db-meta-test.js
@@ -0,0 +1,17 @@
var expect = require('chai').expect;
var dbmeta = require('../lib/db-meta');

describe('db-meta', function() {
it('should return an error for an unknown driver', function(done) {
dbmeta('unknown', {}, expectError);

function expectError(err) {
expect(err).to.exist;
done();
}
});

it('should not return an error for a known driver', function(done) {
dbmeta('pg', { database: 'db-meta-test' }, done);
});
});
62 changes: 62 additions & 0 deletions test/pg/column-test.js
@@ -0,0 +1,62 @@
var expect = require('chai').expect;
var Column = require('../../lib/pg/column');
var iface = require('../../lib/column').iface;

describe('pg 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) {
c[method].call(c);
});
done();
});

it('should create an internal meta property for constructor argument', function (done) {
var t = new Column({ column_name: 'col' });
expect(t.meta).not.to.be.null;
expect(t.meta.column_name).to.equal('col');
done();
})

it('should implement the getName method', function (done) {
var c = new Column({ column_name: 'col' });
expect(c.getName()).to.equal('col');
done();
});

it('should implement the isNullable method', function (done) {
var c = new Column({ column_name: 'col', is_nullable: 'YES' });
expect(c.isNullable()).to.be.true;

c = new Column({ column_name: 'col', is_nullable: 'NO' });
expect(c.isNullable()).to.be.false;

done();
});

it('should implement the getMaxLength method', function (done) {
var c = new Column({ column_name: 'col', character_maximum_length: 255 });
expect(c.getMaxLength()).to.equal(255);
done();
});

it('should implement the getDataType method', function(done) {
expectDataType('INTEGER', ['integer', 'int', 'int4']);
expectDataType('BOOLEAN', ['boolean', 'bool']);
expectDataType('TEXT', ['text']);
expectDataType('VARCHAR', ['varchar', 'character varying']);
expectDataType('FLOAT', ['real', 'float4']);
expectDataType('DOUBLE', ['double precision', 'float8']);
expectDataType('TIME', ['time', 'timetz']);
expectDataType('TIMESTAMP', ['timestamp', 'timestamptz']);
expectDataType('FOO', ['foo']);
done();
});
});

function expectDataType(outputType, metaTypes) {
metaTypes.forEach(function (type) {
var c = new Column({ column_name: 'col', data_type: type });
expect(c.getDataType()).to.equal(outputType);
});
}
84 changes: 84 additions & 0 deletions test/pg/driver-test.js
@@ -0,0 +1,84 @@
var expect = require('chai').expect;
var pg = require('../../lib/pg/driver');

var driver = null;

describe('pg driver', function() {
before(function(done) {
pg.connect({ database: 'db-meta-test' }, onConnect);

function onConnect(err, dbDriver) {
driver = dbDriver;
driver.client.query('CREATE TABLE person (id INTEGER PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(100), age INTEGER);', done);
}
});

after(function(done) {
driver.client.query('DROP TABLE person', driver.close.bind(driver, done));
});

it('should return the database version', function(done) {
driver.getVersion(onResult);

function onResult(err, version) {
expect(err).to.be.null;
expect(version).to.be.present;
done();
}
});

it('should return all database tables', function(done) {
driver.getTables(onResult);

function onResult(err, tables) {
expect(err).to.be.null;
expect(tables).not.to.be.empty;
expect(tables.length).to.equal(1);
expect(tables[0].getName()).to.equal('person');
expect(tables[0].meta).not.to.be.empty;
done();
}
});

it('should return all columns for a given table', function(done) {
driver.getColumns('person', onResult);

function onResult(err, columns) {
expect(err).to.be.null;
expect(columns).not.to.be.empty;
expect(columns.length).to.equal(4);

var idColumn = getColumnByName(columns, 'id');
expect(idColumn).not.to.be.null;
expect(idColumn.meta).not.to.be.empty;
expect(idColumn.isNullable()).to.be.false;
expect(idColumn.getDataType()).to.equal('INTEGER');
expect(idColumn.getMaxLength()).to.be.null;

var nameColumn = getColumnByName(columns, 'name');
expect(nameColumn).not.to.be.null;
expect(nameColumn.meta).not.to.be.empty;
expect(nameColumn.isNullable()).to.be.false;
expect(nameColumn.getMaxLength()).to.equal(255);
expect(nameColumn.getDataType()).to.equal('VARCHAR');

var emailColumn = getColumnByName(columns, 'email');
expect(emailColumn).not.to.be.null;
expect(emailColumn.meta).not.to.be.empty;
expect(emailColumn.isNullable()).to.be.true;
expect(emailColumn.getMaxLength()).to.equal(100);
expect(emailColumn.getDataType()).to.equal('VARCHAR');

done();
}
});
});

function getColumnByName(columns, name) {
for (var i = 0; i < columns.length; i++) {
if (columns[i].getName() === name) {
return columns[i];
}
}
return null;
}

0 comments on commit 9a757de

Please sign in to comment.