@nexes/mongo-knex
Advanced tools
Comparing version 0.1.2 to 0.2.0
@@ -24,2 +24,4 @@ const _ = require('lodash'); | ||
const isCompOp = key => isOp(key) && _.includes(_.keys(compOps), key); | ||
const isNegationOp = key => isOp(key) && _.includes(['$ne', '$nin'], key); | ||
const isStatementGroupOp = key => _.includes([compOps.$in, compOps.$nin], key); | ||
@@ -32,3 +34,3 @@ class MongoToKnex { | ||
// @TODO: https://github.com/NexesJS/mongo-knex/issues/4 | ||
Object.assign(this.config, {relations: {}, aliases: {}}, config); | ||
Object.assign(this.config, {relations: {}}, config); | ||
} | ||
@@ -52,18 +54,9 @@ | ||
processStatement(column, op, value) { | ||
const columnParts = column.split('.'); | ||
const [tableName, columnName] = column.split('.'); | ||
// CASE: `posts.status` -> where "posts" is the parent table | ||
if (columnParts[0] === this.tableName) { | ||
return { | ||
column: column, | ||
value: value, | ||
isRelation: false | ||
}; | ||
} | ||
// CASE: relation? | ||
if (columnParts.length > 1) { | ||
debug(columnParts); | ||
if (columnName) { | ||
debug(tableName, columnName); | ||
const table = columnParts[0]; | ||
const table = tableName; | ||
let relation = this.config.relations[table]; | ||
@@ -77,4 +70,9 @@ | ||
// CASE: assume it's a column on the destination table | ||
if (!relation) { | ||
throw new Error('Can\'t find relation in config object.'); | ||
return { | ||
column: column, | ||
value: value, | ||
isRelation: false | ||
}; | ||
} | ||
@@ -85,3 +83,3 @@ | ||
table: relation.tableName, | ||
column: columnParts[1], | ||
column: columnName, | ||
operator: op, | ||
@@ -95,4 +93,4 @@ value: value, | ||
return { | ||
table: columnParts[0], | ||
column: columnParts[1], | ||
table: tableName, | ||
column: columnName, | ||
operator: op, | ||
@@ -107,3 +105,3 @@ value: value, | ||
return { | ||
column: this.tableName + '.' + column, | ||
column: `${this.tableName}.${column}`, | ||
value: value, | ||
@@ -115,51 +113,86 @@ isRelation: false | ||
/** | ||
* Build queries for relations. | ||
* We group the relations by a unique key. | ||
* Each grouping will create a sub query. | ||
* | ||
* @TODO: choose a different key name (!) | ||
* | ||
* Returns a group structure of following format: | ||
* { | ||
* "groupKey": { | ||
* innerWhereStatements: [], | ||
* joinFilterStatements: [] | ||
* } | ||
* } | ||
*/ | ||
buildRelationQuery(qb, relations) { | ||
debug(`(buildRelationQuery)`); | ||
groupRelationStatements(statements, mode) { | ||
const group = {}; | ||
if (debugExtended.enabled) { | ||
debugExtended(`(buildRelationQuery) ${JSON.stringify(relations)}`); | ||
} | ||
// groups depend on the mode of grouping, if its and $and we need to treat a filter on | ||
// joining table differently than we would with $or | ||
// e.g. for $or we can create a subquery or group that filter, | ||
// for $and we have to include joining table filter in every group | ||
const innerWhereStatements = (mode === '$and') | ||
? statements.filter(r => !(r.join_table)) | ||
: statements; | ||
const groupedRelations = {}; | ||
_.each(innerWhereStatements, (statement) => { | ||
/** | ||
* CASE: | ||
* - we should not use the same sub query if the column name is the same (two sub queries) | ||
* - e.g. $and conjunction requires us to use 2 sub queries, because we have to look at each individual tag | ||
* | ||
* - we should also not use grouping of negated values for the same reasons as above | ||
*/ | ||
let createSubGroup = isNegationOp(statement.operator); | ||
/** | ||
* We group the relations by a unique key. | ||
* Each grouping will create a sub query. | ||
* | ||
* @TODO: choose a different key name (!) | ||
*/ | ||
_.each(relations, (relation) => { | ||
if (!groupedRelations[relation.table]) { | ||
groupedRelations[relation.table] = []; | ||
} | ||
if (groupedRelations[relation.table].length) { | ||
const columnExists = _.find(groupedRelations[relation.table], (statement) => { | ||
/** | ||
* CASE: | ||
* - we should not use the same sub query if the column name is the same (two sub queries) | ||
* - e.g. $and conjunction requires us to use 2 sub queries, because we have to look at each individual tag | ||
*/ | ||
if (statement.column === relation.column) { | ||
if (!createSubGroup && group[statement.table]) { | ||
createSubGroup = _.find(group[statement.table].innerWhereStatements, (innerStatement) => { | ||
if (innerStatement.column === statement.column) { | ||
return true; | ||
} | ||
}); | ||
} | ||
if (columnExists) { | ||
const newKey = `${relation.table}_${Math.floor(Math.random() * 100)}`; | ||
if (!groupedRelations[newKey]) { | ||
groupedRelations[newKey] = []; | ||
} | ||
let groupKey = statement.table; | ||
groupedRelations[newKey].push(relation); | ||
return; | ||
} | ||
if (createSubGroup) { | ||
// TODO: while this is 'good enough' approach this needs to be approached with something | ||
// more stable than random from 1..100 | ||
groupKey = `${statement.table}_${Math.floor(Math.random() * 100)}`; | ||
} | ||
groupedRelations[relation.table].push(relation); | ||
if (!group[groupKey]) { | ||
group[groupKey] = {}; | ||
group[groupKey].innerWhereStatements = []; | ||
} | ||
group[groupKey].innerWhereStatements.push(statement); | ||
}); | ||
// NOTE: filters applied on join level have to be included when they are | ||
// a part of $and group | ||
if (mode === '$and') { | ||
const joinFilterStatements = statements.filter(r => (r.join_table)); | ||
_.each(Object.keys(group), (key) => { | ||
group[key].joinFilterStatements = joinFilterStatements; | ||
}); | ||
} | ||
return group; | ||
} | ||
/** | ||
* Build queries for relations. | ||
*/ | ||
buildRelationQuery(qb, relations, mode) { | ||
debug(`(buildRelationQuery)`); | ||
if (debugExtended.enabled) { | ||
debugExtended(`(buildRelationQuery) ${JSON.stringify(relations)}`); | ||
} | ||
const groupedRelations = this.groupRelationStatements(relations, mode); | ||
if (debugExtended.enabled) { | ||
debugExtended(`(buildRelationQuery) grouped: ${JSON.stringify(groupedRelations)}`); | ||
@@ -172,3 +205,3 @@ } | ||
const statements = groupedRelations[key]; | ||
const statements = groupedRelations[key].innerWhereStatements; | ||
@@ -179,21 +212,77 @@ // CASE: any statement for the same relation should contain the same config | ||
if (reference.config.type === 'manyToMany') { | ||
if (isCompOp(reference.operator)) { | ||
const comp = reference.operator === '$ne' || reference.operator === '$nin' ? 'NOT IN' : 'IN'; | ||
if (_.every(statements.map(s => s.operator), isCompOp)) { | ||
// CASE: only negate whole group when all the operators in the group are negative, | ||
// otherwise we cannot combine groups with negated and regular equation operators | ||
const negateGroup = _.every(statements.map(s => s.operator), (operator) => { | ||
return isNegationOp(operator); | ||
}); | ||
const comp = negateGroup | ||
? compOps.$nin | ||
: compOps.$in; | ||
// CASE: WHERE resource.id (IN | NOT IN) (SELECT ...) | ||
qb[reference.whereType](`${this.tableName}.id`, comp, function () { | ||
const joinFilterStatements = groupedRelations[key].joinFilterStatements; | ||
let innerJoinValue = reference.config.tableName; | ||
let innerJoinOn = `${reference.config.tableName}.id`; | ||
// CASE: you can define a name for the join table | ||
if (reference.config.tableNameAs) { | ||
innerJoinValue = `${reference.config.tableName} as ${reference.config.tableNameAs}`; | ||
innerJoinOn = `${reference.config.tableNameAs}.id`; | ||
} | ||
const innerQB = this | ||
.select(`${reference.config.join_table}.${reference.config.join_from}`) | ||
.from(`${reference.config.join_table}`) | ||
.innerJoin(`${reference.config.tableName}`, `${reference.config.tableName}.id`, '=', `${reference.config.join_table}.${reference.config.join_to}`); | ||
.innerJoin(innerJoinValue, function () { | ||
this.on(innerJoinOn, '=', `${reference.config.join_table}.${reference.config.join_to}`); | ||
_.each(statements, (value, key) => { | ||
// CASE: when applying AND con junction and having multiple groups the filter | ||
// related to joining table has to be applied within each group | ||
_.each(joinFilterStatements, (joinFilter) => { | ||
this.andOn(`${joinFilter.join_table}.${joinFilter.column}`, compOps[joinFilter.operator], joinFilter.value); | ||
}); | ||
}); | ||
if (debugExtended.enabled) { | ||
debug(`(buildRelationQuery) innerQB sql-pre: ${innerQB.toSQL().sql}`); | ||
} | ||
_.each(statements, (statement, key) => { | ||
debug(`(buildRelationQuery) build relation where statements for ${key}`); | ||
innerQB[value.whereType](`${value.join_table || value.table}.${value.column}`, 'IN', !_.isArray(value.value) ? [value.value] : value.value); | ||
const statementColumn = `${statement.join_table || statement.table}.${statement.column}`; | ||
let statementOp; | ||
if (negateGroup) { | ||
statementOp = compOps.$in; | ||
} else { | ||
if (isNegationOp(statement.operator)) { | ||
statementOp = compOps.$nin; | ||
} else { | ||
statementOp = compOps[statement.operator]; | ||
} | ||
} | ||
let statementValue = statement.value; | ||
// CASE: need to normalize value to array when it's a group operation | ||
if (isStatementGroupOp(statementOp)) { | ||
statementValue = !_.isArray(statement.value) ? [statement.value] : statement.value; | ||
} | ||
innerQB[statement.whereType](statementColumn, statementOp, statementValue); | ||
}); | ||
if (debugExtended.enabled) { | ||
debug(`(buildRelationQuery) innerQB sql-post: ${innerQB.toSQL().sql}`); | ||
} | ||
return innerQB; | ||
}); | ||
} else { | ||
debug('unknown operator'); | ||
debug(`one of ${key} group statements contains unknown operator`); | ||
} | ||
@@ -221,6 +310,7 @@ } | ||
if (processedStatement.isRelation) { | ||
processedStatement.whereType = whereType; | ||
// CASE: if the statement is not part of a group, execute the query instantly | ||
if (!group) { | ||
processedStatement.whereType = whereType; | ||
this.buildRelationQuery(qb, [processedStatement]); | ||
this.buildRelationQuery(qb, [processedStatement], mode); | ||
return; | ||
@@ -234,3 +324,2 @@ } | ||
processedStatement.whereType = whereType; | ||
qb.relations.push(processedStatement); | ||
@@ -292,3 +381,3 @@ return; | ||
if (_qb.hasOwnProperty('relations')) { | ||
this.buildRelationQuery(_qb, _qb.relations); | ||
this.buildRelationQuery(_qb, _qb.relations, mode); | ||
delete _qb.relations; | ||
@@ -295,0 +384,0 @@ } |
{ | ||
"name": "@nexes/mongo-knex", | ||
"version": "0.1.2", | ||
"version": "0.2.0", | ||
"description": "tbc", | ||
@@ -5,0 +5,0 @@ "repository": "git@github.com:NexesJS/mongo-knex.git", |
@@ -9,20 +9,28 @@ const utils = require('../utils'); | ||
// @TODO: the config object is not designed yet. | ||
const makeQuery = query => convertor(knex('posts'), query, { | ||
relations: { | ||
tags: { | ||
tableName: 'tags', | ||
type: 'manyToMany', | ||
join_table: 'posts_tags', | ||
join_from: 'post_id', | ||
join_to: 'tag_id', | ||
// @TODO: tag -> tags.slug | ||
aliases: {} | ||
}, | ||
authors: { | ||
type: 'oneToMany', | ||
join_from: 'author_id' | ||
const makeQuery = (mongoJSON) => { | ||
const query = convertor(knex('posts'), mongoJSON, { | ||
relations: { | ||
tags: { | ||
tableName: 'tags', | ||
type: 'manyToMany', | ||
join_table: 'posts_tags', | ||
join_from: 'post_id', | ||
join_to: 'tag_id' | ||
}, | ||
authors: { | ||
tableName: 'users', | ||
tableNameAs: 'authors', | ||
type: 'manyToMany', | ||
join_table: 'posts_authors', | ||
join_from: 'post_id', | ||
join_to: 'author_id' | ||
} | ||
} | ||
} | ||
}); | ||
}); | ||
query.orderBy('id', 'ASC'); | ||
return query; | ||
}; | ||
// Integration tests build a test database and | ||
@@ -35,59 +43,6 @@ // check that we get the exact data we expect from each query | ||
describe.skip('One-to-Many', function () { | ||
beforeEach(utils.db.init('relations1')); | ||
afterEach(utils.db.reset()); | ||
it('can match array in (single value)', function (done) { | ||
const queryJSON = {'authors.slug': {$in: ['sam']}}; | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
// Check any intermediate values | ||
console.log(query.toQuery()); | ||
// Perform the query against the DB | ||
query.select() | ||
.then((result) => { | ||
console.log(result); | ||
result.should.be.an.Array().with.lengthOf(2); | ||
// Check we get the right data | ||
// result.should.do.something; | ||
done(); | ||
}) | ||
.catch(done); | ||
}); | ||
it('can match array in (multiple values)', function (done) { | ||
const queryJSON = {'authors.name': {$in: ['Sam Smith', 'Pat Taylor']}}; | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
// Check any intermediate values | ||
console.log('query', query.toQuery()); | ||
// Perform the query against the DB | ||
query.select() | ||
.then((result) => { | ||
console.log(result); | ||
result.should.be.an.Array().with.lengthOf(5); | ||
// Check we get the right data | ||
// result.should.do.something; | ||
done(); | ||
}) | ||
.catch(done); | ||
}); | ||
}); | ||
describe('Many-to-Many', function () { | ||
before(utils.db.init('relations1')); | ||
before(utils.db.init('many-to-many')); | ||
describe('EQUALS', function () { | ||
describe('EQUALS $eq', function () { | ||
it('tags.slug equals "animal"', function () { | ||
@@ -104,7 +59,23 @@ const mongoJSON = { | ||
result.should.be.an.Array().with.lengthOf(3); | ||
result.should.matchIds([2, 4, 6]); | ||
}); | ||
}); | ||
it('tags.visibility equals "internal"', function () { | ||
const mongoJSON = { | ||
'tags.visibility': 'internal' | ||
}; | ||
const query = makeQuery(mongoJSON); | ||
return query | ||
.select() | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(1); | ||
result.should.matchIds([8]); | ||
}); | ||
}); | ||
}); | ||
describe('NEGATION', function () { | ||
describe('NEGATION $ne', function () { | ||
// should return posts without tags | ||
@@ -114,3 +85,5 @@ // if a post has more than 1 tag, if one tag is animal, do not return | ||
const mongoJSON = { | ||
'tags.slug': {$ne: 'animal'} | ||
'tags.slug': { | ||
$ne: 'animal' | ||
} | ||
}; | ||
@@ -124,25 +97,12 @@ | ||
result.should.be.an.Array().with.lengthOf(5); | ||
result.should.matchIds([1, 3, 5, 7, 8]); | ||
}); | ||
}); | ||
}); | ||
describe('Multiple where clauses for relations', function () { | ||
it('tags.slug equals "animal" and posts_tags.sort_order is 0 and featured is true', function () { | ||
// where primary tag is "animal" | ||
const mongoJSON = {$and: [ | ||
{ | ||
$and: [ | ||
{ | ||
'tags.slug': 'animal' | ||
}, | ||
{ | ||
'posts_tags.sort_order': 0 | ||
} | ||
] | ||
}, | ||
{ | ||
featured: true | ||
it('tags.visibility is NOT "public"', function () { | ||
const mongoJSON = { | ||
'tags.visibility': { | ||
$ne: 'public' | ||
} | ||
]}; | ||
}; | ||
@@ -154,23 +114,13 @@ const query = makeQuery(mongoJSON); | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(0); | ||
result.should.be.an.Array().with.lengthOf(2); | ||
result.should.matchIds([7, 8]); | ||
}); | ||
}); | ||
}); | ||
it('tags.slug equals "animal" and posts_tags.sort_order is 0 and featured is false', function () { | ||
// where primary tag is "animal" | ||
const mongoJSON = {$and: [ | ||
{ | ||
$and: [ | ||
{ | ||
'tags.slug': 'animal' | ||
}, | ||
{ | ||
'posts_tags.sort_order': 0 | ||
} | ||
] | ||
}, | ||
{ | ||
featured: false | ||
} | ||
]}; | ||
describe('COMPARISONS $gt / $gte / $lt / $lte', function () { | ||
it('tags.created_at is > 2015-06-21', function () { | ||
const mongoJSON = {'tags.created_at': { | ||
$gt: '2015-06-21' | ||
}}; | ||
@@ -180,25 +130,12 @@ const query = makeQuery(mongoJSON); | ||
return query | ||
.select() | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(1); | ||
result.length.should.eql(1); | ||
result.should.matchIds([8]); | ||
}); | ||
}); | ||
it('tags.slug equals "animal" and posts_tags.sort_order is 0 OR author_id is 1', function () { | ||
const mongoJSON = {$or: [ | ||
{ | ||
$and: [ | ||
{ | ||
'tags.slug': 'animal' | ||
}, | ||
{ | ||
'posts_tags.sort_order': 0 | ||
}, | ||
{ | ||
featured: false | ||
} | ||
] | ||
}, | ||
{author_id: 1} | ||
]}; | ||
it('tags.created_at is >= 2015-06-21', function () { | ||
const mongoJSON = {'tags.created_at': { | ||
$gte: '2015-06-21' | ||
}}; | ||
@@ -208,10 +145,38 @@ const query = makeQuery(mongoJSON); | ||
return query | ||
.select() | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(6); | ||
result.length.should.eql(2); | ||
result.should.matchIds([3, 8]); | ||
}); | ||
}); | ||
it('tags.created_at is < 2015-01-02', function () { | ||
const mongoJSON = {'tags.created_at': { | ||
$lt: '2015-01-02' | ||
}}; | ||
const query = makeQuery(mongoJSON); | ||
return query | ||
.then((result) => { | ||
result.length.should.eql(4); | ||
result.should.matchIds([1, 4, 5, 6]); | ||
}); | ||
}); | ||
it('tags.created_at is <= 2015-01-02', function () { | ||
const mongoJSON = {'tags.created_at': { | ||
$lte: '2015-01-02' | ||
}}; | ||
const query = makeQuery(mongoJSON); | ||
return query | ||
.then((result) => { | ||
result.length.should.eql(5); | ||
result.should.matchIds([1, 2, 4, 5, 6]); | ||
}); | ||
}); | ||
}); | ||
describe('AND', function () { | ||
describe('AND $and', function () { | ||
it('tags.slug is animal and classic', function () { | ||
@@ -235,8 +200,16 @@ const mongoJSON = { | ||
result.should.be.an.Array().with.lengthOf(2); | ||
result.should.matchIds([4, 6]); | ||
}); | ||
}); | ||
it('tags.slug is animal and tags.slug not in []', function () { | ||
it('tags.slug is hash-internal and tags.visibility is private', function () { | ||
const mongoJSON = { | ||
$and: [{'tags.slug': 'animal'},{'tags.slug': {$nin: ['classic']}}] | ||
$and: [ | ||
{ | ||
'tags.slug': 'hash-internal' | ||
}, | ||
{ | ||
'tags.visibility': 'internal' | ||
} | ||
] | ||
}; | ||
@@ -250,20 +223,16 @@ | ||
result.should.be.an.Array().with.lengthOf(1); | ||
result.should.matchIds([8]); | ||
}); | ||
}); | ||
it('(tags.slug is animal and sort_order is 0) and tags.visibility=public', function () { | ||
it('tags.slug is animal and tags.slug NOT in [classic]', function () { | ||
const mongoJSON = { | ||
$and: [ | ||
{ | ||
$and: [ | ||
{ | ||
'tags.slug': 'animal' | ||
}, | ||
{ | ||
'posts_tags.sort_order': 0 | ||
} | ||
] | ||
'tags.slug': 'animal' | ||
}, | ||
{ | ||
'tags.visibility': 'public' | ||
'tags.slug': { | ||
$nin: ['classic'] | ||
} | ||
} | ||
@@ -279,2 +248,3 @@ ] | ||
result.should.be.an.Array().with.lengthOf(1); | ||
result.should.matchIds([2]); | ||
}); | ||
@@ -291,5 +261,2 @@ }); | ||
'posts_tags.sort_order': 0 | ||
}, | ||
{ | ||
'tags.visibility': 'public' | ||
} | ||
@@ -305,26 +272,9 @@ ] | ||
result.should.be.an.Array().with.lengthOf(1); | ||
result.should.matchIds([2]); | ||
}); | ||
}); | ||
}); | ||
describe('OR', function () { | ||
it('tags.slug IN (animal)', function () { | ||
it('(tags.slug is animal and sort_order is 0) and tags.visibility=public', function () { | ||
const mongoJSON = { | ||
'tags.slug': { | ||
$in: ['animal'] | ||
} | ||
}; | ||
const query = makeQuery(mongoJSON); | ||
return query | ||
.select() | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(3); | ||
}); | ||
}); | ||
it('(tags.slug = animal and sort_order = 0) OR visibility:internal', function () { | ||
const mongoJSON = { | ||
$or: [ | ||
$and: [ | ||
{ | ||
@@ -341,3 +291,3 @@ $and: [ | ||
{ | ||
'tags.visibility': 'internal' | ||
'tags.visibility': 'public' | ||
} | ||
@@ -352,9 +302,10 @@ ] | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(2); | ||
result.should.be.an.Array().with.lengthOf(1); | ||
result.should.matchIds([2]); | ||
}); | ||
}); | ||
it('tags.slug = animal OR sort_order = 0 OR visibility:internal', function () { | ||
it('tags.slug is animal and sort_order is 0 and tags.visibility=public', function () { | ||
const mongoJSON = { | ||
$or: [ | ||
$and: [ | ||
{ | ||
@@ -367,3 +318,3 @@ 'tags.slug': 'animal' | ||
{ | ||
'tags.visibility': 'internal' | ||
'tags.visibility': 'public' | ||
} | ||
@@ -378,15 +329,19 @@ ] | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(7); | ||
result.should.be.an.Array().with.lengthOf(1); | ||
result.should.matchIds([2]); | ||
}); | ||
}); | ||
it('featured:true AND tags.slug IN (animal)', function () { | ||
it('tags.slug is NOT animal and tags.slug is NOT cgi', function () { | ||
// equivalent to $nin: ['animal', 'cgi'] | ||
const mongoJSON = { | ||
$and: [ | ||
{ | ||
featured: true | ||
'tags.slug': { | ||
$ne: 'animal' | ||
} | ||
}, | ||
{ | ||
'tags.slug': { | ||
$in: ['animal'] | ||
$ne: 'cgi' | ||
} | ||
@@ -402,10 +357,12 @@ } | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(2); | ||
result.should.be.an.Array().with.lengthOf(4); | ||
result.should.matchIds([1, 5, 7, 8]); | ||
}); | ||
}); | ||
it('tags.id IN (2,3)', function () { | ||
it('tags.slug NOT equal "classic" and tags.visibility is equal "public"', function () { | ||
const mongoJSON = { | ||
'tags.id': { | ||
$in: [2, 3] | ||
'tags.visibility': 'public', | ||
'tags.slug': { | ||
$ne: 'classic' | ||
} | ||
@@ -419,263 +376,441 @@ }; | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(4); | ||
result.should.be.an.Array().with.lengthOf(2); | ||
result.should.matchIds([2, 3]); | ||
}); | ||
}); | ||
}); | ||
}); | ||
// NOTE: no need to support 1:1 relations just yet | ||
describe.skip('One-to-One: Extended Cases', function () { | ||
beforeEach(() => utils.db.init('suite1', 'one-to-one-extended-cases')); | ||
afterEach(() => utils.db.reset()); | ||
// TODO: should be filled with cases from 'Many-to-Many: Extended Cases' suite | ||
}); | ||
describe.skip('One-to-Many: Extended Cases', function () { | ||
beforeEach(() => utils.db.init('suite1', 'one-to-many-extended-cases')); | ||
afterEach(() => utils.db.reset()); | ||
// TODO: should be filled with cases from 'Many-to-Many: Extended Cases' suite | ||
}); | ||
describe.skip('Many-to-Many: Extended Cases', function () { | ||
before(() => utils.db.init('suite1', 'many-to-many-extended-cases')); | ||
after(() => utils.db.reset()); | ||
describe('negation $ne and $nin', function () { | ||
it('can match $ne (single value)', function () { | ||
const queryJSON = { | ||
it('tags.slug NOT IN ["classic"] and tags.visibility is equal "public"', function () { | ||
const mongoJSON = { | ||
'tags.visibility': 'public', | ||
'tags.slug': { | ||
$ne: 'animal' | ||
$nin: ['classic'] | ||
} | ||
}; | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
const query = makeQuery(mongoJSON); | ||
// Check any intermediate values | ||
console.log(query.toQuery()); | ||
// Perform the query against the DB | ||
return query.select() | ||
return query | ||
.select() | ||
.then((result) => { | ||
console.log(result); | ||
result.should.be.an.Array().with.lengthOf(2); | ||
result.should.matchIds([2, 3]); | ||
}); | ||
}); | ||
// NOTE: make sure to count in posts with no tags | ||
// do not count in posts with multiple tags containing one of the excluded tags | ||
result.should.be.an.Array().with.lengthOf(3); | ||
it('(tags.slug NOT IN "classic" and tags.visibility is equal "public")', function () { | ||
// this case can be generated with: | ||
// 'tags.slug:-classic+tags.visibility:public' | ||
const mongoJSON = { | ||
$and: [ | ||
{ | ||
'tags.visibility': 'public' | ||
}, | ||
{ | ||
'tags.slug': { | ||
$nin: ['classic'] | ||
} | ||
} | ||
] | ||
}; | ||
// Check we get the right data | ||
// result.should.do.something; | ||
const query = makeQuery(mongoJSON); | ||
// NOTE: this query is generating a group, this should be avoided | ||
// as we can't group negated properties with other, unless those | ||
// are going through connecting table | ||
return query | ||
.select() | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(2); | ||
result.should.matchIds([2, 3]); | ||
}); | ||
}); | ||
it('can match aliased $ne (single value)', function () { | ||
// NOTE: makeQuery needs additional configuration to be passed in for aliases | ||
const queryJSON = { | ||
tag: { | ||
$ne: 'animal' | ||
} | ||
it('any author is pat and any tag is classic (query on multiple relations)', function () { | ||
const mongoJSON = { | ||
$and: [ | ||
{ | ||
'authors.slug': 'pat' | ||
}, | ||
{ | ||
'tags.slug': 'classic' | ||
} | ||
] | ||
}; | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
const query = makeQuery(mongoJSON); | ||
// Check any intermediate values | ||
console.log(query.toQuery()); | ||
// Perform the query against the DB | ||
return query.select() | ||
return query | ||
.select() | ||
.then((result) => { | ||
console.log(result); | ||
// NOTE: make sure to count in posts with no tags | ||
// do not count in posts with multiple tags containing one of the excluded tags | ||
result.should.be.an.Array().with.lengthOf(3); | ||
// Check we get the right data | ||
// result.should.do.something; | ||
result.should.be.an.Array().with.lengthOf(4); | ||
result.should.matchIds([1, 4, 5, 6]); | ||
}); | ||
}); | ||
it('can match array $nin (single value)', function () { | ||
const queryJSON = { | ||
'tags.slug': { | ||
$nin: ['animal'] | ||
} | ||
it('first author is pat and first tag is classic (query on multiple relations)', function () { | ||
const mongoJSON = { | ||
$and: [ | ||
{ | ||
$and: [ | ||
{ | ||
'tags.slug': 'classic' | ||
}, | ||
{ | ||
'posts_tags.sort_order': 0 | ||
} | ||
] | ||
}, | ||
{ | ||
$and: [ | ||
{ | ||
'authors.slug': 'pat' | ||
}, | ||
{ | ||
'posts_authors.sort_order': 0 | ||
} | ||
] | ||
} | ||
] | ||
}; | ||
const query = makeQuery(mongoJSON); | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
// Check any intermediate values | ||
console.log(query.toQuery()); | ||
// Perform the query against the DB | ||
return query.select() | ||
return query | ||
.select() | ||
.then((result) => { | ||
console.log(result); | ||
// NOTE: make sure to count in posts with no tags | ||
// do not count in posts with multiple tags containing one of the excluded tags | ||
result.should.be.an.Array().with.lengthOf(3); | ||
// Check we get the right data | ||
// result.should.do.something; | ||
result.should.matchIds([1, 5, 6]); | ||
}); | ||
}); | ||
it('can match array $nin (multiple values)', function () { | ||
const queryJSON = { | ||
'tags.slug': { | ||
$nin: ['animal', 'classic'] | ||
} | ||
}; | ||
describe('Multiple conditions applied to the joining table and to the destination table', function () { | ||
it('tags.slug equals "cgi" and posts_tags.sort_order is 0 and featured is true', function () { | ||
// where primary tag is "cgi" | ||
const mongoJSON = { | ||
$and: [ | ||
{ | ||
$and: [ | ||
{ | ||
'tags.slug': 'cgi' | ||
}, | ||
{ | ||
'posts_tags.sort_order': 0 | ||
} | ||
] | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
}, | ||
{ | ||
featured: true | ||
} | ||
] | ||
}; | ||
// Check any intermediate values | ||
console.log('query', query.toQuery()); | ||
const query = makeQuery(mongoJSON); | ||
// Perform the query against the DB | ||
return query.select() | ||
.then((result) => { | ||
console.log(result); | ||
return query | ||
.select() | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(1); | ||
result.should.matchIds([3]); | ||
}); | ||
}); | ||
// NOTE: make sure to count in posts with no tags | ||
// do not count in posts with multiple tags containing one of the excluded tags | ||
result.should.be.an.Array().with.lengthOf(1); | ||
it('tags.slug equals "animal" and posts_tags.sort_order is 0 and featured is false', function () { | ||
// where primary tag is "animal" | ||
const mongoJSON = { | ||
$and: [ | ||
{ | ||
$and: [ | ||
{ | ||
'tags.slug': 'animal' | ||
}, | ||
{ | ||
'posts_tags.sort_order': 0 | ||
} | ||
] | ||
}, | ||
{ | ||
featured: false | ||
} | ||
] | ||
}; | ||
// Check we get the right data | ||
// result.should.do.something; | ||
}); | ||
const query = makeQuery(mongoJSON); | ||
return query | ||
.select() | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(1); | ||
result.should.matchIds([2]); | ||
}); | ||
}); | ||
it('tags.slug NOT equal "classic" and posts_tags.sort_order is 0 and featured is true', function () { | ||
const mongoJSON = { | ||
$and: [ | ||
{ | ||
$and: [ | ||
{ | ||
'tags.slug': { | ||
$ne: 'classic' | ||
} | ||
}, | ||
{ | ||
'posts_tags.sort_order': 0 | ||
} | ||
] | ||
}, | ||
{ | ||
featured: true | ||
} | ||
] | ||
}; | ||
const query = makeQuery(mongoJSON); | ||
return query | ||
.select() | ||
.then((result) => { | ||
// @NOTE: This should return posts without tags, because a post without tags is not tagged | ||
// with the primary tag "classic". | ||
result.should.be.an.Array().with.lengthOf(3); | ||
result.should.matchIds([3, 7, 8]); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('count', function () { | ||
it('can compare by count $gt', function () { | ||
const queryJSON = { | ||
'authors.count': {$gt: 0} | ||
describe('OR $or', function () { | ||
it('any author is pat or leslie', function () { | ||
const mongoJSON = { | ||
$or: [ | ||
{ | ||
'authors.slug': 'leslie' | ||
}, | ||
{ | ||
'authors.slug': 'pat' | ||
} | ||
] | ||
}; | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
const query = makeQuery(mongoJSON); | ||
// Check any intermediate values | ||
console.log(query.toQuery()); | ||
// Perform the query against the DB | ||
return query.select() | ||
return query | ||
.select() | ||
.then((result) => { | ||
console.log(result); | ||
result.should.be.an.Array().with.lengthOf(7); | ||
result.should.matchIds([1, 3, 4, 5, 6, 7, 8]); | ||
}); | ||
}); | ||
result.should.be.an.Array().with.lengthOf(3); | ||
it('any author is sam or any tag is cgi', function () { | ||
const mongoJSON = { | ||
$or: [ | ||
{ | ||
'authors.slug': 'sam' | ||
}, | ||
{ | ||
'tags.slug': 'cgi' | ||
} | ||
] | ||
}; | ||
// Check we get the right data | ||
// result.should.do.something; | ||
const query = makeQuery(mongoJSON); | ||
return query | ||
.select() | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(4); | ||
result.should.matchIds([2, 3, 4, 8]); | ||
}); | ||
}); | ||
it('can compare by count $lt', function () { | ||
const queryJSON = { | ||
'authors.count': {$lt: 2} | ||
it('any author is not pat or any tag is in [animal]', function () { | ||
const mongoJSON = { | ||
$or: [ | ||
{ | ||
'authors.slug': { | ||
$ne: 'pat' | ||
} | ||
}, | ||
{ | ||
'tags.slug': { | ||
$in: ['animal'] | ||
} | ||
} | ||
] | ||
}; | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
const query = makeQuery(mongoJSON); | ||
// Check any intermediate values | ||
console.log(query.toQuery()); | ||
// Perform the query against the DB | ||
return query.select() | ||
return query | ||
.select() | ||
.then((result) => { | ||
console.log(result); | ||
result.should.be.an.Array().with.lengthOf(4); | ||
result.should.matchIds([2, 4, 6, 8]); | ||
}); | ||
}); | ||
result.should.be.an.Array().with.lengthOf(3); | ||
describe('Multiple conditions applied to the joining table and to the destination table', function () { | ||
it('tags.slug equals "animal" and posts_tags.sort_order is 0 OR author_id is 1', function () { | ||
const mongoJSON = { | ||
$or: [ | ||
{ | ||
$and: [ | ||
{ | ||
'tags.slug': 'animal' | ||
}, | ||
{ | ||
'posts_tags.sort_order': 0 | ||
}, | ||
{ | ||
featured: false | ||
} | ||
] | ||
}, | ||
{ | ||
author_id: 1 | ||
} | ||
] | ||
}; | ||
// Check we get the right data | ||
// result.should.do.something; | ||
}); | ||
const query = makeQuery(mongoJSON); | ||
return query | ||
.select() | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(6); | ||
result.should.matchIds([1, 2, 3, 5, 6, 7]); | ||
}); | ||
}); | ||
it('(tags.slug = animal and sort_order = 0) OR visibility:internal', function () { | ||
const mongoJSON = { | ||
$or: [ | ||
{ | ||
$and: [ | ||
{ | ||
'tags.slug': 'animal' | ||
}, | ||
{ | ||
'posts_tags.sort_order': 0 | ||
} | ||
] | ||
}, | ||
{ | ||
'tags.visibility': 'internal' | ||
} | ||
] | ||
}; | ||
const query = makeQuery(mongoJSON); | ||
return query | ||
.select() | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(2); | ||
result.should.matchIds([2, 8]); | ||
}); | ||
}); | ||
it('tags.slug = animal OR sort_order = 0 OR visibility:internal', function () { | ||
const mongoJSON = { | ||
$or: [ | ||
{ | ||
'tags.slug': 'animal' | ||
}, | ||
{ | ||
'posts_tags.sort_order': 0 | ||
}, | ||
{ | ||
'tags.visibility': 'internal' | ||
} | ||
] | ||
}; | ||
const query = makeQuery(mongoJSON); | ||
return query | ||
.select() | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(7); | ||
result.should.matchIds([1, 2, 3, 4, 5, 6, 8]); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('conjunction $and', function () { | ||
it('can match multiple values of same attribute', function () { | ||
const queryJSON = { | ||
$and: [ | ||
{'author.slug': 'pat'}, | ||
{'authors.slug': 'sam'} | ||
] | ||
describe('IN $in', function () { | ||
it('tags.slug IN (animal)', function () { | ||
const mongoJSON = { | ||
'tags.slug': { | ||
$in: ['animal'] | ||
} | ||
}; | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
const query = makeQuery(mongoJSON); | ||
// Check any intermediate values | ||
console.log(query.toQuery()); | ||
// Perform the query against the DB | ||
return query.select() | ||
return query | ||
.select() | ||
.then((result) => { | ||
console.log(result); | ||
result.should.be.an.Array().with.lengthOf(3); | ||
// Check we get the right data | ||
// result.should.do.something; | ||
result.should.matchIds([2, 4, 6]); | ||
}); | ||
}); | ||
it('can match multiple values of different attributes', function () { | ||
const queryJSON = { | ||
$and: [ | ||
{'authors.slug': 'pat'}, | ||
{'tags.slug': 'classic'} | ||
] | ||
it('tags.slug IN (animal, cgi)', function () { | ||
const mongoJSON = { | ||
'tags.slug': { | ||
$in: ['animal', 'cgi'] | ||
} | ||
}; | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
const query = makeQuery(mongoJSON); | ||
// Check any intermediate values | ||
console.log(query.toQuery()); | ||
// Perform the query against the DB | ||
return query.select() | ||
return query | ||
.select() | ||
.then((result) => { | ||
console.log(result); | ||
result.should.be.an.Array().with.lengthOf(4); | ||
result.should.matchIds([2, 3, 4, 6]); | ||
}); | ||
}); | ||
result.should.be.an.Array().with.lengthOf(3); | ||
it('tags.id IN (2,3)', function () { | ||
const mongoJSON = { | ||
'tags.id': { | ||
$in: [2, 3] | ||
} | ||
}; | ||
// Check we get the right data | ||
// result.should.do.something; | ||
const query = makeQuery(mongoJSON); | ||
return query | ||
.select() | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(4); | ||
result.should.matchIds([2, 3, 4, 6]); | ||
}); | ||
}); | ||
it('can match multiple values of same aliased attribute', function () { | ||
// NOTE: makeQuery needs additional configuration to be passed in for aliases | ||
const queryJSON = { | ||
it('tags.slug IN (animal) AND featured:true', function () { | ||
const mongoJSON = { | ||
$and: [ | ||
{author: 'pat'}, | ||
{author: 'sam'} | ||
{ | ||
'tags.slug': { | ||
$in: ['animal'] | ||
} | ||
}, | ||
{ | ||
featured: true | ||
} | ||
] | ||
}; | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
const query = makeQuery(mongoJSON); | ||
// Check any intermediate values | ||
console.log(query.toQuery()); | ||
// Perform the query against the DB | ||
return query.select() | ||
return query | ||
.select() | ||
.then((result) => { | ||
console.log(result); | ||
result.should.be.an.Array().with.lengthOf(3); | ||
// Check we get the right data | ||
// result.should.do.something; | ||
result.should.be.an.Array().with.lengthOf(2); | ||
result.should.matchIds([4, 6]); | ||
}); | ||
@@ -685,79 +820,75 @@ }); | ||
describe('conjunction $or', function () { | ||
it('can match values of same attributes', function () { | ||
const queryJSON = { | ||
$or: [ | ||
{'authors.slug': 'joe'}, | ||
{'authors.slug': 'pat'} | ||
] | ||
describe('NOT IN $nin', function () { | ||
it('tags.slug NOT IN (animal)', function () { | ||
const mongoJSON = { | ||
'tags.slug': { | ||
$nin: ['animal'] | ||
} | ||
}; | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
const query = makeQuery(mongoJSON); | ||
// Check any intermediate values | ||
console.log(query.toQuery()); | ||
// Perform the query against the DB | ||
return query.select() | ||
return query | ||
.select() | ||
.then((result) => { | ||
console.log(result); | ||
result.should.be.an.Array().with.lengthOf(5); | ||
result.should.matchIds([1, 3, 5, 7, 8]); | ||
}); | ||
}); | ||
result.should.be.an.Array().with.lengthOf(3); | ||
it('tags.slug NOT IN (animal, cgi)', function () { | ||
const mongoJSON = { | ||
'tags.slug': { | ||
$nin: ['animal', 'cgi'] | ||
} | ||
}; | ||
// Check we get the right data | ||
// result.should.do.something; | ||
const query = makeQuery(mongoJSON); | ||
return query | ||
.select() | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(4); | ||
result.should.matchIds([1, 5, 7, 8]); | ||
}); | ||
}); | ||
it('can match values of different attributes', function () { | ||
const queryJSON = { | ||
$or: [ | ||
{'authors.slug': 'joe'}, | ||
{'tags.slug': 'photo'} | ||
] | ||
it('tags.id NOT IN (2,3)', function () { | ||
const mongoJSON = { | ||
'tags.id': { | ||
$nin: [2, 3] | ||
} | ||
}; | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
const query = makeQuery(mongoJSON); | ||
// Check any intermediate values | ||
console.log(query.toQuery()); | ||
// Perform the query against the DB | ||
return query.select() | ||
return query | ||
.select() | ||
.then((result) => { | ||
console.log(result); | ||
result.should.be.an.Array().with.lengthOf(3); | ||
// Check we get the right data | ||
// result.should.do.something; | ||
result.should.be.an.Array().with.lengthOf(4); | ||
result.should.matchIds([1, 5, 7, 8]); | ||
}); | ||
}); | ||
it('can match values of same aliased attributes', function () { | ||
// NOTE: makeQuery needs additional configuration to be passed in for aliases | ||
const queryJSON = { | ||
$or: [ | ||
{author: 'joe'}, | ||
{author: 'pat'} | ||
it('tags.slug NOT IN (classic, animal) AND featured:true', function () { | ||
const mongoJSON = { | ||
$and: [ | ||
{ | ||
'tags.slug': { | ||
$nin: ['classic', 'animal'] | ||
} | ||
}, | ||
{ | ||
featured: true | ||
} | ||
] | ||
}; | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
const query = makeQuery(mongoJSON); | ||
// Check any intermediate values | ||
console.log(query.toQuery()); | ||
// Perform the query against the DB | ||
return query.select() | ||
return query | ||
.select() | ||
.then((result) => { | ||
console.log(result); | ||
result.should.be.an.Array().with.lengthOf(3); | ||
// Check we get the right data | ||
// result.should.do.something; | ||
result.should.matchIds([3, 7, 8]); | ||
}); | ||
@@ -767,29 +898,28 @@ }); | ||
describe('combination of extended cases', function () { | ||
it('should be filled with a mix of all the above cases', function () { | ||
describe('COUNT', function () { | ||
it.skip('can compare by count $gt', function () { | ||
const mongoJSON = { | ||
'authors.count': {$gt: 0} | ||
}; | ||
const query = makeQuery(mongoJSON); | ||
return query | ||
.select() | ||
.then((result) => { | ||
result.should.be.an.Array().with.lengthOf(3); | ||
}); | ||
}); | ||
it('can match values of different attributes combining with negation', function () { | ||
const queryJSON = { | ||
$or: [ | ||
{'authors.slug': {$ne: 'joe'}}, | ||
{'tags.slug': {$in: ['photo']}} | ||
] | ||
it.skip('can compare by count $lt', function () { | ||
const mongoJSON = { | ||
'authors.count': {$lt: 2} | ||
}; | ||
// Use the queryJSON to build a query | ||
const query = makeQuery(queryJSON); | ||
const query = makeQuery(mongoJSON); | ||
// Check any intermediate values | ||
console.log(query.toQuery()); | ||
// Perform the query against the DB | ||
return query.select() | ||
return query | ||
.select() | ||
.then((result) => { | ||
console.log(result); | ||
result.should.be.an.Array().with.lengthOf(3); | ||
// Check we get the right data | ||
// result.should.do.something; | ||
}); | ||
@@ -799,2 +929,5 @@ }); | ||
}); | ||
describe('[NOT IMPLEMENTED] One-to-One', function () {}); | ||
describe('[NOT IMPLEMENTED] One-to-Many', function () {}); | ||
}); |
@@ -26,2 +26,3 @@ { | ||
"status": "published", | ||
"published_at": "2015-01-01", | ||
"author_id": 1 | ||
@@ -35,2 +36,3 @@ }, | ||
"status": "draft", | ||
"published_at": null, | ||
"author_id": 2 | ||
@@ -44,2 +46,3 @@ }, | ||
"status": "published", | ||
"published_at": "2015-01-03", | ||
"author_id": 1 | ||
@@ -53,2 +56,3 @@ }, | ||
"status": "published", | ||
"published_at": "2015-06-01", | ||
"author_id": 2 | ||
@@ -62,2 +66,3 @@ }, | ||
"status": "published", | ||
"published_at": "2015-06-02", | ||
"author_id": 1 | ||
@@ -71,2 +76,3 @@ }, | ||
"status": "published", | ||
"published_at": "2015-06-03", | ||
"author_id": 1 | ||
@@ -80,2 +86,3 @@ }, | ||
"status": "published", | ||
"published_at": "2015-06-04", | ||
"author_id": 1 | ||
@@ -89,2 +96,3 @@ }, | ||
"status": "published", | ||
"published_at": "2015-06-05", | ||
"author_id": 2 | ||
@@ -98,3 +106,4 @@ } | ||
"slug": "classic", | ||
"visibility": "public" | ||
"visibility": "public", | ||
"created_at": "2015-01-01" | ||
}, | ||
@@ -105,3 +114,4 @@ { | ||
"slug": "animal", | ||
"visibility": "public" | ||
"visibility": "public", | ||
"created_at": "2015-01-02" | ||
}, | ||
@@ -112,3 +122,4 @@ { | ||
"slug": "cgi", | ||
"visibility": "public" | ||
"visibility": "public", | ||
"created_at": "2015-06-21" | ||
}, | ||
@@ -119,5 +130,6 @@ { | ||
"slug": "hash-internal", | ||
"visibility": "internal" | ||
"visibility": "internal", | ||
"created_at": "2015-06-22" | ||
} | ||
] | ||
} |
const Promise = require('bluebird'); | ||
const TABLES = ['posts_tags', 'posts', 'tags', 'users']; | ||
const TABLES = ['posts_tags', 'posts_authors', 'posts', 'tags', 'users']; | ||
@@ -18,2 +18,3 @@ module.exports.up = function (knex) { | ||
table.string('status', 191).nullable(); | ||
table.dateTime('published_at').nullable(); | ||
table.integer('author_id').unsigned().references('users.id'); | ||
@@ -26,2 +27,3 @@ })) | ||
table.string('visibility', 191).defaultTo('public'); | ||
table.dateTime('created_at'); | ||
})) | ||
@@ -33,2 +35,8 @@ .then(() => knex.schema.createTable('posts_tags', (table) => { | ||
table.integer('sort_order').defaultTo(0); | ||
})) | ||
.then(() => knex.schema.createTable('posts_authors', (table) => { | ||
table.increments('id').primary(); | ||
table.integer('post_id').unsigned().references('posts.id'); | ||
table.integer('author_id').unsigned().references('users.id'); | ||
table.integer('sort_order').defaultTo(0); | ||
})); | ||
@@ -35,0 +43,0 @@ }; |
@@ -17,2 +17,12 @@ require('../utils'); | ||
}); | ||
it('should accept any table input and interprets it as destination where clause', function () { | ||
runQuery({'posts.title': 'Second post'}) | ||
.should.eql('select * from `posts` where `posts`.`title` = \'Second post\''); | ||
}); | ||
it('should accept any table input and interprets it as destination where clause', function () { | ||
runQuery({'count.posts': '3'}) | ||
.should.eql('select * from `posts` where `count`.`posts` = \'3\''); | ||
}); | ||
}); | ||
@@ -19,0 +29,0 @@ |
@@ -15,2 +15,5 @@ /** | ||
// "should" overrides | ||
require('./assertions'); | ||
module.exports.db = require('./db'); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
77931
1897
0
25