Skip to content

Commit

Permalink
feat(database): add support for table prefixing
Browse files Browse the repository at this point in the history
Closes #58
  • Loading branch information
thetutlage committed Oct 4, 2016
1 parent 908277d commit 22399a0
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 26 deletions.
57 changes: 57 additions & 0 deletions src/Database/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,24 @@ Database.connection = function (connection) {
client.client.QueryBuilder.prototype.forPage = Database.forPage
client.client.QueryBuilder.prototype.paginate = Database.paginate
client.client.QueryBuilder.prototype.chunk = Database.chunk
client.client.QueryBuilder.prototype._originalTable = client.client.QueryBuilder.prototype.table
client.client.QueryBuilder.prototype.table = Database.table
client.client.QueryBuilder.prototype.from = Database.table
client.client.QueryBuilder.prototype.into = Database.table
client.client.QueryBuilder.prototype.withPrefix = Database.withPrefix
client.client.QueryBuilder.prototype.withoutPrefix = Database.withoutPrefix

/**
* Adding methods on the client if withoutPrefix or withPrefix
* is called directly it will return the query builder.
*/
client.withoutPrefix = function () {
return new this.client.QueryBuilder(this.client).withoutPrefix()
}
client.withPrefix = function (prefix) {
return new this.client.QueryBuilder(this.client).withPrefix(prefix)
}

connectionPools[connection] = client
}

Expand Down Expand Up @@ -339,6 +357,45 @@ Database.chunk = function * (limit, cb, page) {
}
}

/**
* Overriding the orginal knex.table method to prefix
* the table name based upon the prefix option
* defined in the config
*
* @param {String} tableName
*
* @return {Object}
*/
Database.table = function (tableName) {
const prefix = this._instancePrefix || this.client.config.prefix
const prefixedTableName = (prefix && !this._skipPrefix) ? `${prefix}${tableName}`: tableName
this._originalTable(prefixedTableName)
return this
}

/**
* Skipping the prefix for a single query
*
* @return {Object}
*/
Database.withoutPrefix = function () {
this._skipPrefix = true
return this
}

/**
* Changing the prefix for a given query
*
* @param {String} prefix
*
* @return {Object}
*/
Database.withPrefix = function (prefix) {
this._instancePrefix = prefix
return this
}


/**
* these methods are not proxied and instead actual implementations
* are returned
Expand Down
22 changes: 22 additions & 0 deletions src/Lucid/Model/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,28 @@ class Model {
return util.makeTableName(this)
}

/**
* Returns a custom prefix to be used for selecting the database
* table for a given model
*
* @return {String}
*
* @public
*/
static get prefix () {
return null
}

/**
* A getter defining whether or not to skip
* table prefixing for this model.
*
* @return {Boolean}
*/
static get skipPrefix () {
return false
}

/**
* primary key to be used for given table. Same key is used for fetching
* associations. Defaults to id
Expand Down
11 changes: 10 additions & 1 deletion src/Lucid/QueryBuilder/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,16 @@ class QueryBuilder {
const Database = Ioc.use('Adonis/Src/Database')
this.HostModel = HostModel
this.queryBuilder = Database.connection(this.HostModel.connection)
this.modelQueryBuilder = this.queryBuilder(this.HostModel.table)
this.modelQueryBuilder = null

if (HostModel.prefix && !HostModel.skipPrefix) {
this.modelQueryBuilder = this.queryBuilder.withPrefix(HostModel.prefix).table(this.HostModel.table)
} else if (HostModel.skipPrefix) {
this.modelQueryBuilder = this.queryBuilder.withoutPrefix().table(this.HostModel.table)
} else {
this.modelQueryBuilder = this.queryBuilder.table(this.HostModel.table)
}

this.avoidTrashed = false
this.eagerLoad = new EagerLoad()
return new Proxy(this, proxyHandler)
Expand Down
65 changes: 65 additions & 0 deletions test/unit/database.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,69 @@ describe('Database provider', function () {
})
expect(callbackCalledForTimes).to.equal(allUsers.length)
})

it('should be able to prefix the database table using a configuration option', function * () {
Database._setConfigProvider(config.withPrefix)
const query = Database.table('users').toSQL()
expect(queryHelpers.formatQuery(query.sql)).to.equal(queryHelpers.formatQuery('select * from "ad_users"'))
})

it('should be able to prefix the database table when table method is called after other methods', function * () {
const query = Database.where('username', 'foo').table('users').toSQL()
expect(queryHelpers.formatQuery(query.sql)).to.equal(queryHelpers.formatQuery('select * from "ad_users" where "username" = ?'))
})

it('should be able to prefix the database table when from method is used', function * () {
const query = Database.from('users').toSQL()
expect(queryHelpers.formatQuery(query.sql)).to.equal(queryHelpers.formatQuery('select * from "ad_users"'))
})

it('should be able to prefix the database table when from method is called after other methods', function * () {
const query = Database.where('username', 'foo').from('users').toSQL()
expect(queryHelpers.formatQuery(query.sql)).to.equal(queryHelpers.formatQuery('select * from "ad_users" where "username" = ?'))
})

it('should be able to prefix the database table when into method is used', function * () {
const query = Database.into('users').toSQL()
expect(queryHelpers.formatQuery(query.sql)).to.equal(queryHelpers.formatQuery('select * from "ad_users"'))
})

it('should be able to prefix the database table when into method is called after other methods', function * () {
const query = Database.where('username', 'foo').into('users').toSQL()
expect(queryHelpers.formatQuery(query.sql)).to.equal(queryHelpers.formatQuery('select * from "ad_users" where "username" = ?'))
})

it('should be able to remove the prefix using the withoutPrefix method', function * () {
const query = Database.withoutPrefix().table('users').toSQL()
expect(queryHelpers.formatQuery(query.sql)).to.equal(queryHelpers.formatQuery('select * from "users"'))
})

it('should be able to remove the prefix when withoutPrefix method is called after other methods', function * () {
const query = Database.where('username', 'foo').withoutPrefix().table('users').toSQL()
expect(queryHelpers.formatQuery(query.sql)).to.equal(queryHelpers.formatQuery('select * from "users" where "username" = ?'))
})

it('should be able to change the prefix using the withPrefix method', function * () {
const query = Database.withPrefix('k_').table('users').toSQL()
expect(queryHelpers.formatQuery(query.sql)).to.equal(queryHelpers.formatQuery('select * from "k_users"'))
})

it('should be able to remove the prefix when withPrefix method is called after other methods', function * () {
const query = Database.where('username', 'foo').withPrefix('k_').table('users').toSQL()
expect(queryHelpers.formatQuery(query.sql)).to.equal(queryHelpers.formatQuery('select * from "k_users" where "username" = ?'))
})

it('should not mess the query builder instance when withPrefix is called on multiple queries at same time', function * () {
const query = Database.where('username', 'foo').withPrefix('k_').table('users')
const query1 = Database.where('username', 'foo').table('users')
expect(queryHelpers.formatQuery(query.toSQL().sql)).to.equal(queryHelpers.formatQuery('select * from "k_users" where "username" = ?'))
expect(queryHelpers.formatQuery(query1.toSQL().sql)).to.equal(queryHelpers.formatQuery('select * from "ad_users" where "username" = ?'))
})

it('should not mess the query builder instance when withoutPrefix is called on multiple queries at same time', function * () {
const query = Database.where('username', 'foo').withoutPrefix().table('users')
const query1 = Database.where('username', 'foo').table('users')
expect(queryHelpers.formatQuery(query.toSQL().sql)).to.equal(queryHelpers.formatQuery('select * from "users" where "username" = ?'))
expect(queryHelpers.formatQuery(query1.toSQL().sql)).to.equal(queryHelpers.formatQuery('select * from "ad_users" where "username" = ?'))
})
})
59 changes: 34 additions & 25 deletions test/unit/helpers/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,47 @@
const mysqlConnections = require('./mysqlConnections')
const postgresConnection = require('./postgresConnection')
const sqliteConnections = require('./sqliteConnections')
const get = function (key, hasPrefix) {
if (key === 'database.migrationsTable') {
return 'adonis_migrations'
}

module.exports = {
get: function (key) {
if (key === 'database.migrationsTable') {
return 'adonis_migrations'
}
if (key === 'database.connection') {
return process.env.DB
}
if (key === 'database.connection') {
return process.env.DB
}

if (key === 'database.sqlite3') {
return sqliteConnections.default
}
if (key === 'database.sqlite3') {
return hasPrefix ? sqliteConnections.defaultPrefix : sqliteConnections.default
}

if (key === 'database.mysql') {
return mysqlConnections.default
}
if (key === 'database.mysql') {
return hasPrefix ? mysqlConnections.defaultPrefix : mysqlConnections.default
}

if (key === 'database.pg') {
return postgresConnection.default
}
if (key === 'database.pg') {
return hasPrefix ? postgresConnection.defaultPrefix : postgresConnection.default
}

if (key === 'database.alternateConnection' && process.env.DB === 'sqlite3') {
return sqliteConnections.alternateConnection
}
if (key === 'database.alternateConnection' && process.env.DB === 'sqlite3') {
return sqliteConnections.alternateConnection
}

if (key === 'database.alternateConnection' && process.env.DB === 'mysql') {
return mysqlConnections.alternateConnection
}
if (key === 'database.alternateConnection' && process.env.DB === 'mysql') {
return mysqlConnections.alternateConnection
}

if (key === 'database.alternateConnection' && process.env.DB === 'pg') {
return postgresConnection.alternateConnection
if (key === 'database.alternateConnection' && process.env.DB === 'pg') {
return postgresConnection.alternateConnection
}
}

module.exports = {
get: function (key) {
return get(key, false)
},
withPrefix: {
get: function (key) {
return get(key, true)
}
}
}
10 changes: 10 additions & 0 deletions test/unit/helpers/mysqlConnections.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,15 @@ module.exports = {
password : '',
database : 'alternate'
}
},

defaultPrefix : {
client: 'mysql',
connection: {
user : 'root',
password : '',
database : 'default'
},
prefix: 'ad_'
}
}
10 changes: 10 additions & 0 deletions test/unit/helpers/postgresConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,15 @@ module.exports = {
password : '',
database : 'alternate'
}
},

defaultPrefix : {
client: 'pg',
connection: {
user: '',
password : '',
database : 'default'
},
prefix: 'ad_'
}
}
10 changes: 10 additions & 0 deletions test/unit/helpers/sqliteConnections.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,15 @@ module.exports = {
filename: path.join(__dirname, '../storage/test2.sqlite3')
},
useNullAsDefault: true
},

defaultPrefix: {
client: 'sqlite3',
connection: {
filename: path.join(__dirname, '../storage/test.sqlite3')
},
useNullAsDefault: true,
debug: false,
prefix: 'ad_'
}
}
53 changes: 53 additions & 0 deletions test/unit/lucid.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,19 @@ describe('Lucid', function () {
})

after(function * () {
Database.close()
Database._setConfigProvider(config)
yield modelFixtures.down(Database)
Database.close()
})

beforeEach(function () {
Database.close()
})

afterEach(function * () {
Database.close()
Database._setConfigProvider(config)
yield Database.table('users').truncate()
yield Database.table('zombies').truncate()
})
Expand Down Expand Up @@ -124,6 +132,51 @@ describe('Lucid', function () {
expect(User.globalScope[0]).to.be.a('function')
expect(Post.globalScope.length).to.equal(1)
})

it('should make use of the prefix when selecting the table @prefix', function () {
Database._setConfigProvider(config.withPrefix)
class User extends Model {
}
const query = User.query().toSQL()
expect(queryHelpers.formatQuery(query.sql)).to.equal(queryHelpers.formatQuery('select * from "ad_users"'))
})

it('should be able to change the prefix for a single model when prefix getter is used', function () {
Database._setConfigProvider(config.withPrefix)
class User extends Model {
static get prefix () {
return 'k_'
}
}
const query = User.query().toSQL()
expect(queryHelpers.formatQuery(query.sql)).to.equal(queryHelpers.formatQuery('select * from "k_users"'))
})

it('should be skip the prefix for a single model when skipPrefix getter is used', function () {
Database._setConfigProvider(config.withPrefix)
class User extends Model {
static get skipPrefix () {
return true
}
}
const query = User.query().toSQL()
expect(queryHelpers.formatQuery(query.sql)).to.equal(queryHelpers.formatQuery('select * from "users"'))
})

it('should be skip the prefix for a single model when skipPrefix and prefix getter both are used', function () {
Database._setConfigProvider(config.withPrefix)
class User extends Model {
static get prefix () {
return 'k_'
}

static get skipPrefix () {
return true
}
}
const query = User.query().toSQL()
expect(queryHelpers.formatQuery(query.sql)).to.equal(queryHelpers.formatQuery('select * from "users"'))
})
})

context('QueryBuilder', function () {
Expand Down

0 comments on commit 22399a0

Please sign in to comment.