bifocal/node_modules/@tryghost/mongo-knex/test/unit/convertor.test.js

424 lines
18 KiB
JavaScript

require('../utils');
const knex = require('knex')({client: 'mysql'});
const convertor = require('../../lib/convertor');
const runQuery = query => convertor(knex('posts'), query, {
relations: {
tags: {
tableName: 'tags',
type: 'manyToMany',
joinTable: 'posts_tags',
joinFrom: 'post_id',
joinTo: 'tag_id'
},
optional_tags: {
tableName: 'optional_tags',
type: 'manyToMany',
joinTable: 'posts_tags',
joinFrom: 'post_id',
joinTo: 'tag_id',
joinType: 'leftJoin'
},
posts_meta: {
tableName: 'posts_meta',
type: 'oneToOne',
joinFrom: 'post_id'
}
}
}).toQuery();
describe('Simple Expressions', function () {
it('should match based on simple id', function () {
runQuery({id: 3})
.should.eql('select * from `posts` where `posts`.`id` = 3');
});
it('should match based on string', function () {
runQuery({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({'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 (number)', function () {
runQuery({'count.posts': '3'})
.should.eql('select * from `posts` where `count`.`posts` = \'3\'');
});
});
describe('Comparison Query Operators', function () {
it('can match equals', function () {
runQuery({id: 2})
.should.eql('select * from `posts` where `posts`.`id` = 2');
});
it('can match not equals', function () {
runQuery({id: {$ne: 2}})
.should.eql('select * from `posts` where `posts`.`id` != 2');
});
it('can match gt', function () {
runQuery({id: {$gt: 2}})
.should.eql('select * from `posts` where `posts`.`id` > 2');
});
it('can match lt', function () {
runQuery({id: {$lt: 2}})
.should.eql('select * from `posts` where `posts`.`id` < 2');
});
it('can match gte', function () {
runQuery({id: {$gte: 2}})
.should.eql('select * from `posts` where `posts`.`id` >= 2');
});
it('can match lte', function () {
runQuery({id: {$lte: 2}})
.should.eql('select * from `posts` where `posts`.`id` <= 2');
});
it('can match simple in (single value)', function () {
runQuery({id: {$in: [2]}})
.should.eql('select * from `posts` where `posts`.`id` in (2)');
});
it('can match simple in (multiple values)', function () {
runQuery({id: {$in: [1, 3]}})
.should.eql('select * from `posts` where `posts`.`id` in (1, 3)');
});
it('can match simple NOT in (single value)', function () {
runQuery({id: {$nin: [2]}})
.should.eql('select * from `posts` where `posts`.`id` not in (2)');
});
it('can match simple NOT in (multiple values)', function () {
runQuery({id: {$nin: [1, 3]}})
.should.eql('select * from `posts` where `posts`.`id` not in (1, 3)');
});
it('can match array in (single value)', function () {
runQuery({tags: {$in: ['video']}})
.should.eql('select * from `posts` where `posts`.`tags` in (\'video\')');
});
it('can match array in (multiple values)', function () {
runQuery({tags: {$in: ['video', 'audio']}})
.should.eql('select * from `posts` where `posts`.`tags` in (\'video\', \'audio\')');
});
it('can match array NOT in (single value)', function () {
runQuery({tags: {$nin: ['video']}})
.should.eql('select * from `posts` where `posts`.`tags` not in (\'video\')');
});
it('can match array NOT in (multiple values)', function () {
runQuery({tags: {$nin: ['video', 'audio']}})
.should.eql('select * from `posts` where `posts`.`tags` not in (\'video\', \'audio\')');
});
it('can match like', function () {
runQuery({email: {$regex: /Gmail\.com/i}})
.should.eql('select * from `posts` where lower(`posts`.`email`) like \'%gmail.com%\' ESCAPE \'*\'');
});
it('can match like with startswith', function () {
runQuery({email: {$regex: /^Gmail\.com/i}})
.should.eql('select * from `posts` where lower(`posts`.`email`) like \'gmail.com%\' ESCAPE \'*\'');
});
it('can match like with startswith containing a slash', function () {
runQuery({email: {$regex: /^https:\/\/www.google.com\//i}})
.should.eql('select * from `posts` where lower(`posts`.`email`) like \'https://www.google.com/%\' ESCAPE \'*\'');
});
it('can match like with endswith', function () {
runQuery({email: {$regex: /Gmail\.com$/i}})
.should.eql('select * from `posts` where lower(`posts`.`email`) like \'%gmail.com\' ESCAPE \'*\'');
});
// % and _ don't have a meaning in regexes, but they do in LIKE, so they should be escaped in the resulting query
it('correctly escapes _ LIKE special character', function () {
// Get all posts that contain __GHOST_URL__
// Since _ is a special character in LIKE, we need to escape it with * (our chosen escape character)
runQuery({url: {$regex: /__GHOST_URL__/}})
.should.eql('select * from `posts` where `posts`.`url` like \'%*_*_GHOST*_URL*_*_%\' ESCAPE \'*\'');
});
it('correctly escapes % LIKE special character', function () {
// Get all posts with titles that contain '100%'
// Since % is a special character in LIKE, we need to escape it with * (our chosen escape character)
runQuery({title: {$regex: /100%/}})
.should.eql('select * from `posts` where `posts`.`title` like \'%100*%%\' ESCAPE \'*\'');
});
it('correctly escapes * LIKE escape character', function () {
// Get all posts with titles that contain '*'
// Since * is the escape character, we need to escape it with itself
runQuery({title: {$regex: /\*/}})
.should.eql('select * from `posts` where `posts`.`title` like \'%**%\' ESCAPE \'*\'');
});
});
describe('Logical Query Operators', function () {
it('$and (different properties)', function () {
runQuery({$and: [{featured: false}, {status: 'published'}]})
.should.eql('select * from `posts` where (`posts`.`featured` = false and `posts`.`status` = \'published\')');
});
it('$and (same properties)', function () {
runQuery({$and: [{featured: false}, {featured: true}]})
.should.eql('select * from `posts` where (`posts`.`featured` = false and `posts`.`featured` = true)');
});
it('$or (different properties)', function () {
runQuery({$or: [{featured: false}, {status: 'published'}]})
.should.eql('select * from `posts` where (`posts`.`featured` = false or `posts`.`status` = \'published\')');
});
it('$or (same properties)', function () {
runQuery({$or: [{featured: true}, {featured: false}]})
.should.eql('select * from `posts` where (`posts`.`featured` = true or `posts`.`featured` = false)');
});
});
describe('Logical Groups', function () {
describe('$or', function () {
it('ungrouped version', function () {
runQuery({
$or:
[{author: {$ne: 'joe'}},
{tags: {$in: ['photo']}},
{image: {$ne: null}},
{featured: true}]
})
.should.eql('select * from `posts` where (`posts`.`author` != \'joe\' or `posts`.`tags` in (\'photo\') or `posts`.`image` is not null or `posts`.`featured` = true)');
});
it('RIGHT grouped version', function () {
runQuery({
$or:
[{author: {$ne: 'joe'}},
{
$or:
[{tags: {$in: ['photo']}},
{image: {$ne: null}},
{featured: true}]
}]
})
.should.eql('select * from `posts` where (`posts`.`author` != \'joe\' or (`posts`.`tags` in (\'photo\') or `posts`.`image` is not null or `posts`.`featured` = true))');
});
it('LEFT grouped version', function () {
runQuery({
$or:
[{
$or:
[
{tags: {$in: ['photo']}},
{image: {$ne: null}},
{featured: true}]
},
{author: {$ne: 'joe'}}]
})
.should.eql('select * from `posts` where ((`posts`.`tags` in (\'photo\') or `posts`.`image` is not null or `posts`.`featured` = true) or `posts`.`author` != \'joe\')');
});
});
describe('$and', function () {
it('ungrouped version', function () {
runQuery({
$and:
[{author: {$ne: 'joe'}},
{tags: {$in: ['photo']}},
{image: {$ne: null}},
{featured: true}]
})
.should.eql('select * from `posts` where (`posts`.`author` != \'joe\' and `posts`.`tags` in (\'photo\') and `posts`.`image` is not null and `posts`.`featured` = true)');
});
it('RIGHT grouped version', function () {
runQuery({
$and:
[{author: {$ne: 'joe'}},
{
$and:
[{tags: {$in: ['photo']}},
{image: {$ne: null}},
{featured: true}]
}]
})
.should.eql('select * from `posts` where (`posts`.`author` != \'joe\' and (`posts`.`tags` in (\'photo\') and `posts`.`image` is not null and `posts`.`featured` = true))');
});
it('LEFT grouped version', function () {
runQuery({
$and:
[{
$and:
[{tags: {$in: ['photo']}},
{image: {$ne: null}},
{featured: true}]
},
{author: {$ne: 'joe'}}]
})
.should.eql('select * from `posts` where ((`posts`.`tags` in (\'photo\') and `posts`.`image` is not null and `posts`.`featured` = true) and `posts`.`author` != \'joe\')');
});
});
describe('$or with $and group', function () {
it('ungrouped version', function () {
runQuery({
$or:
[{author: {$ne: 'joe'}},
{
$and:
[{tags: {$in: ['photo']}},
{image: {$ne: null}},
{featured: true}]
}]
})
.should.eql('select * from `posts` where (`posts`.`author` != \'joe\' or (`posts`.`tags` in (\'photo\') and `posts`.`image` is not null and `posts`.`featured` = true))');
});
it('RIGHT grouped version', function () {
runQuery({
$or:
[{author: {$ne: 'joe'}},
{
$and:
[{tags: {$in: ['photo']}},
{image: {$ne: null}},
{featured: true}]
}]
})
.should.eql('select * from `posts` where (`posts`.`author` != \'joe\' or (`posts`.`tags` in (\'photo\') and `posts`.`image` is not null and `posts`.`featured` = true))');
});
it('LEFT grouped version', function () {
runQuery({
$or:
[{
$and:
[{tags: {$in: ['photo']}},
{image: {$ne: null}},
{featured: true}]
},
{author: {$ne: 'joe'}}]
})
.should.eql('select * from `posts` where ((`posts`.`tags` in (\'photo\') and `posts`.`image` is not null and `posts`.`featured` = true) or `posts`.`author` != \'joe\')');
});
});
describe('$and with $or group', function () {
it('ungrouped version', function () {
runQuery({
$or:
[{
$and:
[{author: {$ne: 'joe'}},
{tags: {$in: ['photo']}}]
},
{image: {$ne: null}},
{featured: true}]
})
.should.eql('select * from `posts` where ((`posts`.`author` != \'joe\' and `posts`.`tags` in (\'photo\')) or `posts`.`image` is not null or `posts`.`featured` = true)');
});
it('RIGHT grouped version', function () {
runQuery({
$and:
[{author: {$ne: 'joe'}},
{
$or:
[{tags: {$in: ['photo']}},
{image: {$ne: null}},
{featured: true}]
}]
})
.should.eql('select * from `posts` where (`posts`.`author` != \'joe\' and (`posts`.`tags` in (\'photo\') or `posts`.`image` is not null or `posts`.`featured` = true))');
});
it('LEFT grouped version', function () {
runQuery({
$and:
[{
$or:
[{tags: {$in: ['photo']}},
{image: {$ne: null}},
{featured: true}]
},
{author: {$ne: 'joe'}}]
})
.should.eql('select * from `posts` where ((`posts`.`tags` in (\'photo\') or `posts`.`image` is not null or `posts`.`featured` = true) and `posts`.`author` != \'joe\')');
});
});
});
describe('Relations', function () {
it('should be able to perform query on a many-to-many relation', function () {
runQuery({'tags.slug': 'fred'})
.should.eql('select * from `posts` where `posts`.`id` in (select `posts_tags`.`post_id` from `posts_tags` inner join `tags` on `tags`.`id` = `posts_tags`.`tag_id` where `tags`.`slug` = \'fred\')');
});
it('should be able to perform NULL query on a many-to-many relation', function () {
runQuery({'tags.slug': null})
.should.eql('select * from `posts` where `posts`.`id` in (select `posts_tags`.`post_id` from `posts_tags` inner join `tags` on `tags`.`id` = `posts_tags`.`tag_id` where `tags`.`slug` is null)');
});
it('should be able to perform NULL query on a many-to-many relation with left join', function () {
runQuery({'optional_tags.slug': null})
.should.eql('select * from `posts` where `posts`.`id` in (select `posts_tags`.`post_id` from `posts_tags` left join `optional_tags` on `optional_tags`.`id` = `posts_tags`.`tag_id` where `optional_tags`.`slug` is null)');
});
it('should be able to perform a negated query on a many-to-many relation (works but is weird)', function () {
runQuery({'tags.slug': {$ne: 'fred'}})
.should.eql('select * from `posts` where `posts`.`id` not in (select `posts_tags`.`post_id` from `posts_tags` inner join `tags` on `tags`.`id` = `posts_tags`.`tag_id` where `tags`.`slug` in (\'fred\'))');
});
// This case doesn't work
it.skip('should be able to perform a query on a many-to-many join table alone', function () {
runQuery({'posts_tags.sort_order': 0});
});
it('should be able to perform a query on a many-to-many join table and its relation', function () {
runQuery({
$and: [
{
'tags.slug': 'cgi'
},
{
'posts_tags.sort_order': 0
}
]
})
.should.eql('select * from `posts` where (`posts`.`id` in (select `posts_tags`.`post_id` from `posts_tags` inner join `tags` on `tags`.`id` = `posts_tags`.`tag_id` and `posts_tags`.`sort_order` = 0 where `tags`.`slug` = \'cgi\'))');
});
it('should be able to perform a query on a one-to-one relation', function () {
runQuery({'posts_meta.meta_title': 'Meta of A Whole New World'})
.should.eql('select * from `posts` where `posts`.`id` in (select `posts`.`id` from `posts` left join `posts_meta` on `posts_meta`.`post_id` = `posts`.`id` where `posts_meta`.`meta_title` = \'Meta of A Whole New World\')');
});
it('should be able to perform a negated query on a one-to-one relation (works but is weird)', function () {
runQuery({'posts_meta.meta_title': {$ne: 'Meta of A Whole New World'}})
.should.eql('select * from `posts` where `posts`.`id` not in (select `posts`.`id` from `posts` left join `posts_meta` on `posts_meta`.`post_id` = `posts`.`id` where `posts_meta`.`meta_title` in (\'Meta of A Whole New World\'))');
});
});
describe('RegExp/Like queries', function () {
it('are well behaved', function () {
runQuery({title: {$regex: /'/i}})
.should.eql('select * from `posts` where lower(`posts`.`title`) like \'%\\\'%\' ESCAPE \'*\'');
runQuery({title: {$regex: /;/i}})
.should.eql('select * from `posts` where lower(`posts`.`title`) like \'%;%\' ESCAPE \'*\'');
runQuery({title: {$regex: /';select * from `settings` where `value` like '/i}})
.should.eql('select * from `posts` where lower(`posts`.`title`) like \'%\\\';select ** from `settings` where `value` like \\\'%\' ESCAPE \'*\'');
});
});