From 666cb4034c65c8f75ee3f48bac00571afb645a74 Mon Sep 17 00:00:00 2001 From: Jeremy Fowler Date: Tue, 27 Mar 2018 18:43:18 -0500 Subject: [PATCH] Add Database Schema Support, closes #153 --- .gitignore | 2 +- README.md | 1 + lib/index.js | 49 ++++++++++++++++---------- test/index.test.js | 85 ++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 104 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index 993f4d9..2f5fd7a 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,4 @@ node_modules .lock-wscript dist/ -db.sqlite +*.sqlite diff --git a/README.md b/README.md index 61b234a..429bc43 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ __Options:__ - `Model` (**required**) - The KnexJS database instance - `name` (**required**) - The name of the table +- `schema` (*optional*) - The name of the schema table prefix (example: `schema.table`) - `id` (*optional*, default: `'id'`) - The name of the id field property. - `events` (*optional*) - A list of [custom service events](https://docs.feathersjs.com/api/events.html#custom-events) sent by this service - `paginate` (*optional*) - A [pagination object](https://docs.feathersjs.com/api/databases/common.html#pagination) containing a `default` and `max` page size diff --git a/lib/index.js b/lib/index.js index 55414c6..115b54c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -43,18 +43,24 @@ class Service { this.id = options.id || 'id'; this.paginate = options.paginate || {}; this.table = options.name; + this.schema = options.schema; this.events = options.events || []; } + get fullName () { + return (this.schema) ? `${this.schema}.${this.table}` : this.table; + } + // NOTE (EK): We need this method so that we return a new query // instance each time, otherwise it will reuse the same query. db (params = {}) { + const { knex, table, schema, fullName } = this; if (params.transaction) { const { trx, id } = params.transaction; - debug('ran %s with transaction %s', this.table, id); - return trx(this.table); + debug('ran %s with transaction %s', fullName, id); + return (schema) ? trx.withSchema(schema).table(table) : trx(table); } - return this.knex(this.table); + return (schema) ? knex.withSchema(schema).table(table) : knex(table); } extend (obj) { @@ -62,15 +68,17 @@ class Service { } init (opts, cb) { - let k = this.knex; - let table = this.table; + const k = this.knex; + const { table, schema, fullName } = this; - return k.schema.hasTable(table).then(exists => { + return k.schema.hasTable(fullName).then(exists => { if (!exists) { - debug(`creating ${table}`); - return k.schema.createTable(table, cb).then(res => res); + debug(`creating ${fullName}`); + return (schema) + ? k.schema.withSchema(schema).createTable(table, cb).then(res => res) + : k.schema.createTable(table, cb).then(res => res); } else { - debug(`${table} already exists`); + debug(`${fullName} already exists`); return null; } }); @@ -110,13 +118,16 @@ class Service { } createQuery (params = {}) { + const { schema, table, id } = this; const { filters, query } = filterQuery(params.query || {}); - let q = this.db(params).select([`${this.table}.*`]); + let q = this.db(params); - // $select uses a specific find syntax, so it has to come first. - if (filters.$select) { - q = this.db(params).select(...filters.$select.concat(`${this.table}.${this.id}`)); - } + if (schema) { q = q.withSchema(schema).from(`${table} as ${table}`); } + + q = (filters.$select) + // $select uses a specific find syntax, so it has to come first. + ? q.select(...filters.$select.concat(`${table}.${id}`)) + : q.select([`${table}.*`]); // build up the knex query out of the query params this.knexify(q, query); @@ -249,7 +260,7 @@ class Service { query[this.id] = id; } - let q = this.db(params); + const q = this.db(params); this.knexify(q, query); @@ -324,9 +335,13 @@ class Service { return this._find(params).then(page => { const items = page.data; - const query = this.createQuery(params); + const { query } = filterQuery(params.query || {}); + const q = this.db(params); + + // build up the knex query out of the query params + this.knexify(q, query); - return query.del().then(() => { + return q.del().then(() => { if (id !== null) { if (items.length === 1) { return items[0]; diff --git a/test/index.test.js b/test/index.test.js index e9b1ee6..57a1a8e 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -17,6 +17,15 @@ const db = knex({ } }); +// Create a public database to mimic a "schema" +const schemaName = 'public'; +knex({ + client: 'sqlite3', + connection: { + filename: `./${schemaName}.sqlite` + } +}); + const people = service({ Model: db, name: 'people', @@ -30,9 +39,16 @@ const peopleId = service({ events: [ 'testing' ] }); +const users = service({ + Model: db, + schema: schemaName, + name: 'users', + events: [ 'testing' ] +}); + function clean () { return Promise.all([ - db.schema.dropTableIfExists('people').then(() => { + db.schema.dropTableIfExists(people.fullName).then(() => { return people.init({}, (table) => { table.increments('id'); table.string('name'); @@ -42,7 +58,7 @@ function clean () { return table; }); }), - db.schema.dropTableIfExists('people-customid').then(() => { + db.schema.dropTableIfExists(peopleId.fullName).then(() => { return peopleId.init({}, (table) => { table.increments('customid'); table.string('name'); @@ -51,10 +67,39 @@ function clean () { table.boolean('created'); return table; }); + }), + db.schema.dropTableIfExists(users.fullName).then(() => { + return users.init({}, (table) => { + table.increments('id'); + table.string('name'); + table.integer('age'); + table.integer('time'); + table.boolean('created'); + return table; + }); }) ]); } +function attachSchema () { + // Attach the public database to mimic a "schema" + return db.schema.raw(`attach database '${schemaName}.sqlite' as ${schemaName}`); +} + +function customQuery () { + return (context) => { + const { params, service } = context; + const query = service.createQuery(params); + + // do something with query here + query.orderBy('name', 'desc'); + // console.log(query.toSQL().toNative()); + + context.params.knex = query; + return context; + }; +} + describe('Feathers Knex Service', () => { const app = feathers() .hooks({ @@ -63,7 +108,10 @@ describe('Feathers Knex Service', () => { error: transaction.rollback() }) .use('/people', people) - .use('people-customid', peopleId); + .use('people-customid', peopleId) + .use('/users', users); + + before(attachSchema); before(clean); after(clean); @@ -97,23 +145,24 @@ describe('Feathers Knex Service', () => { base(app, errors, 'people'); base(app, errors, 'people-customid', 'customid'); + + describe('database schema support', () => { + base(app, errors, 'users'); + }); }); describe('custom queries', () => { before(clean); before(() => { - app.hooks({}).service('people').hooks({ + app.hooks({}); + app.service('people').hooks({ before: { - find (context) { - const query = this.createQuery(context.params); - - // do something with query here - query.orderBy('name', 'desc'); - // console.log(query.toSQL().toNative()); - - context.params.knex = query; - return context; - } + find: customQuery() + } + }); + app.service('users').hooks({ + before: { + find: customQuery() } }); }); @@ -123,10 +172,16 @@ describe('Feathers Knex Service', () => { before: transaction.start(), after: transaction.end(), error: transaction.rollback() - }).service('people').hooks({}); + }); + app.service('people').hooks({}); + app.service('users').hooks({}); }); base(app, errors, 'people'); + + describe('database schema support', () => { + base(app, errors, 'users'); + }); }); describe('$like method', () => {