Skip to content

Commit

Permalink
🐛 added match support to nested relations
Browse files Browse the repository at this point in the history
  • Loading branch information
vikaspotluri123 committed Aug 18, 2024
1 parent 6f4ddce commit cc25cfc
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 19 deletions.
55 changes: 36 additions & 19 deletions packages/mongo-knex/lib/convertor.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const isLogicOp = key => isOp(key) && _.includes(logicOps, key);
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);
const isLikeOp = key => isOp(key) && _.includes(['$regex', '$not'], key);

/**
* JSON Stringify with RegExp support
Expand Down Expand Up @@ -280,7 +281,7 @@ class MongoToKnex {
const whereType = ['whereNull', 'whereNotNull'].includes(reference.whereType) ? 'andWhere' : (['orWhereNull', 'orWhereNotNull'].includes(reference.whereType) ? 'orWhere' : reference.whereType);

// CASE: WHERE resource.id (IN | NOT IN) (SELECT ...)
qb[whereType](`${this.tableName}.id`, comp, function () {
qb[whereType](`${this.tableName}.id`, comp, (whereQb) => {
const joinFilterStatements = groupedRelations[key].joinFilterStatements;

let innerJoinValue = reference.config.tableName;
Expand All @@ -294,7 +295,7 @@ class MongoToKnex {

const joinType = reference.config.joinType || 'innerJoin';

const innerQB = this
const innerQB = whereQb
.select(`${reference.config.joinTable}.${reference.config.joinFrom}`)
.from(`${reference.config.joinTable}`)[joinType](innerJoinValue, function () {
this.on(innerJoinOn, '=', `${reference.config.joinTable}.${reference.config.joinTo}`);
Expand All @@ -312,10 +313,15 @@ class MongoToKnex {

_.each(statements, (statement, _key) => {
debug(`(buildRelationQuery) build relation where statements for ${_key}`);

const statementColumn = `${statement.joinTable || statement.table}.${statement.column}`;
let statementOp;

// CASE: LIKE query --> use the existing builder
if (isLikeOp(statement.operator)) {
this.buildLikeComparison(innerQB, {...statement, column: statementColumn}, statement.whereType);
return;
}

if (negateGroup) {
statementOp = compOps.$in;
} else {
Expand Down Expand Up @@ -359,7 +365,7 @@ class MongoToKnex {
const tableName = this.tableName;

const where = reference.whereType === 'orWhere' ? 'orWhere' : 'where';
qb[where](`${this.tableName}.id`, comp, function () {
qb[where](`${this.tableName}.id`, comp, (whereQb) => {
const joinFilterStatements = groupedRelations[key].joinFilterStatements;

let innerJoinValue = reference.config.tableName;
Expand All @@ -371,7 +377,7 @@ class MongoToKnex {
innerJoinOn = `${reference.config.tableNameAs}.${reference.config.joinFrom}`;
}

const innerQB = this
const innerQB = whereQb
.select(`${tableName}.id`)
.from(`${tableName}`)
.leftJoin(innerJoinValue, function () {
Expand All @@ -390,6 +396,12 @@ class MongoToKnex {
const statementColumn = `${statement.table}.${statement.column}`;
let statementOp;

// CASE: LIKE query --> use the existing builder
if (isLikeOp(statement.operator)) {
this.buildLikeComparison(innerQB, {...statement, column: statementColumn}, statement.whereType);
return;
}

// NOTE: this negation is here to ensure records with no relation are
// include in negation (e.g. `relation.columnName: {$ne: null})
if (negateGroup) {
Expand Down Expand Up @@ -469,27 +481,32 @@ class MongoToKnex {
op = processedStatement.operator;
value = processedStatement.value;

if (op === '$regex' || op === '$not') {
const {source, ignoreCase} = processRegExp(value);
value = source;

// CASE: regex with i flag needs whereRaw to wrap column in lower() else fall through
if (ignoreCase) {
whereType += 'Raw';
debug(`(buildComparison) whereType: ${whereType}, statement: ${statement}, op: ${op}, comp: ${comp}, value: ${value} (REGEX/i)`);
qb[whereType](`lower(??) ${comp} ? ESCAPE ?`, [column, value, likeEscapeCharacter]);
return;
}
whereType += 'Raw';
debug(`(buildComparison) whereType: ${whereType}, statement: ${statement}, op: ${op}, comp: ${comp}, value: ${value} (REGEX)`);
qb[whereType](`?? ${comp} ? ESCAPE ?`, [column, value, likeEscapeCharacter]);
if (isLikeOp(op)) {
this.buildLikeComparison(qb, processedStatement, whereType);
return;
}

debug(`(buildComparison) whereType: ${whereType}, statement: ${statement}, op: ${op}, comp: ${comp}, value: ${value}`);
qb[whereType](column, comp, value);
}

buildLikeComparison(qb, {column, operator: op, value}, whereType) {
const comp = compOps[op] || '=';
const {source, ignoreCase} = processRegExp(value);
value = source;

// CASE: regex with i flag needs whereRaw to wrap column in lower() else fall through
if (ignoreCase) {
whereType += 'Raw';
debug(`(buildLikeComparison) whereType: ${whereType}, op: ${op}, comp: ${comp}, value: ${value} (REGEX/i)`);
qb[whereType](`lower(??) ${comp} ? ESCAPE ?`, [column, value, likeEscapeCharacter]);
return;
}
whereType += 'Raw';
debug(`(buildLikeComparison) whereType: ${whereType}, op: ${op}, comp: ${comp}, value: ${value} (REGEX)`);
qb[whereType](`?? ${comp} ? ESCAPE ?`, [column, value, likeEscapeCharacter]);
}

/**
* {author: 'carl'}
*/
Expand Down
24 changes: 24 additions & 0 deletions packages/mongo-knex/test/unit/convertor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,30 @@ describe('Relations', 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\'))');
});

it('should be able to perform a match query on a one-to-one relation', function () {
const innerQuery = 'select `posts`.`id` from `posts` left join `posts_meta` on `posts_meta`.`post_id` = `posts`.`id` where lower(`posts_meta`.`meta_title`) like \'%world%\' ESCAPE \'*\'';
runQuery({'posts_meta.meta_title': {$regex: /world/i}})
.should.eql(`select * from \`posts\` where \`posts\`.\`id\` in (${innerQuery})`);
});

it('should be able to perform a negated match query on a one-to-one relation', function () {
const innerQuery = 'select `posts`.`id` from `posts` left join `posts_meta` on `posts_meta`.`post_id` = `posts`.`id` where lower(`posts_meta`.`meta_title`) not like \'%world%\' ESCAPE \'*\'';
runQuery({'posts_meta.meta_title': {$not: /world/i}})
.should.eql(`select * from \`posts\` where \`posts\`.\`id\` in (${innerQuery})`);
});

it('should be able to perform a match query on a many-to-many relation', function () {
const innerQuery = 'select `posts_tags`.`post_id` from `posts_tags` inner join `tags` on `tags`.`id` = `posts_tags`.`tag_id` where `tags`.`name` like \'%wor*%ld%\' ESCAPE \'*\'';
runQuery({'tags.name': {$regex: /wor%ld/}})
.should.eql(`select * from \`posts\` where \`posts\`.\`id\` in (${innerQuery})`);
});

it('should be able to perform a negated match query on a many-to-many relation', function () {
const innerQuery = 'select `posts_tags`.`post_id` from `posts_tags` inner join `tags` on `tags`.`id` = `posts_tags`.`tag_id` where `tags`.`name` not like \'%wor*%ld%\' ESCAPE \'*\'';
runQuery({'tags.name': {$not: /wor%ld/}})
.should.eql(`select * from \`posts\` where \`posts\`.\`id\` in (${innerQuery})`);
});
});

describe('RegExp/Like queries', function () {
Expand Down

0 comments on commit cc25cfc

Please sign in to comment.