Skip to content

Commit

Permalink
Add Database Schema Support, closes #153 (#154)
Browse files Browse the repository at this point in the history
  • Loading branch information
jerfowler authored and daffl committed Apr 20, 2018
1 parent 37a5699 commit ade8941
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 33 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ node_modules
.lock-wscript

dist/
db.sqlite
*.sqlite
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 32 additions & 17 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,34 +43,42 @@ 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) {
return Proto.extend(obj, this);
}

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;
}
});
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -249,7 +260,7 @@ class Service {
query[this.id] = id;
}

let q = this.db(params);
const q = this.db(params);

this.knexify(q, query);

Expand Down Expand Up @@ -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];
Expand Down
85 changes: 70 additions & 15 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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');
Expand All @@ -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');
Expand All @@ -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({
Expand All @@ -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);

Expand Down Expand Up @@ -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()
}
});
});
Expand All @@ -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', () => {
Expand Down

0 comments on commit ade8941

Please sign in to comment.