diff --git a/src/database/helpers.js b/src/database/helpers.js index 598c1d8812..eb8206f783 100644 --- a/src/database/helpers.js +++ b/src/database/helpers.js @@ -26,3 +26,19 @@ helpers.mergeBatch = function (batchData, start, stop, sort) { } while (item && (result.length < (stop - start + 1) || stop === -1)); return result; }; + +helpers.globToRegex = function (match) { + if (!match) { + return '^.*$'; + } + let _match = match.replace(/[.+^${}()|[\]\\]/g, '\\$&'); + _match = _match.replace(/\*/g, '.*').replace(/\?/g, '.'); + + if (!match.startsWith('*')) { + _match = '^' + _match; + } + if (!match.endsWith('*')) { + _match = _match + '$'; + } + return _match; +}; diff --git a/src/database/mongo/helpers.js b/src/database/mongo/helpers.js index 46ef8b39cb..a087da025b 100644 --- a/src/database/mongo/helpers.js +++ b/src/database/mongo/helpers.js @@ -1,7 +1,6 @@ 'use strict'; const helpers = module.exports; -const utils = require('../../utils'); helpers.noop = function () {}; @@ -50,20 +49,3 @@ helpers.valueToString = function (value) { return String(value); }; -helpers.buildMatchQuery = function (match) { - let _match = match; - if (match.startsWith('*')) { - _match = _match.substring(1); - } - if (match.endsWith('*')) { - _match = _match.substring(0, _match.length - 1); - } - _match = utils.escapeRegexChars(_match); - if (!match.startsWith('*')) { - _match = `^${_match}`; - } - if (!match.endsWith('*')) { - _match += '$'; - } - return _match; -}; diff --git a/src/database/mongo/main.js b/src/database/mongo/main.js index 884be9d2f7..67ff7ac8fa 100644 --- a/src/database/mongo/main.js +++ b/src/database/mongo/main.js @@ -1,7 +1,7 @@ 'use strict'; module.exports = function (module) { - const helpers = require('./helpers'); + const dbHelpers = require('../helpers'); module.flushdb = async function () { await module.client.dropDatabase(); }; @@ -39,7 +39,7 @@ module.exports = function (module) { }; module.scan = async function (params) { - const match = helpers.buildMatchQuery(params.match); + const match = dbHelpers.globToRegex(params.match); return await module.client.collection('objects').distinct( '_key', { _key: { $regex: new RegExp(match) } } ); diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index b24007e98a..eceea5e342 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -543,7 +543,7 @@ module.exports = function (module) { project.score = 1; } - const match = helpers.buildMatchQuery(params.match); + const match = dbHelpers.globToRegex(params.match); let regex; try { regex = new RegExp(match); diff --git a/src/database/postgres/main.js b/src/database/postgres/main.js index f082521a1d..6c397b8879 100644 --- a/src/database/postgres/main.js +++ b/src/database/postgres/main.js @@ -2,6 +2,7 @@ module.exports = function (module) { const helpers = require('./helpers'); + const dbHelpers = require('../helpers'); module.flushdb = async function () { await module.pool.query(`DROP SCHEMA "public" CASCADE`); @@ -84,20 +85,14 @@ module.exports = function (module) { }; module.scan = async function (params) { - let { match } = params; - if (match.startsWith('*')) { - match = `%${match.substring(1)}`; - } - if (match.endsWith('*')) { - match = `${match.substring(0, match.length - 1)}%`; - } + const regex = dbHelpers.globToRegex(params.match); const res = await module.pool.query({ text: ` SELECT o."_key" FROM "legacy_object_live" o - WHERE o."_key" LIKE $1`, - values: [match], + WHERE o."_key" ~ $1`, + values: [regex], }); return res.rows.map(r => r._key); diff --git a/test/database/keys.js b/test/database/keys.js index 71f2aa9baa..673d083f98 100644 --- a/test/database/keys.js +++ b/test/database/keys.js @@ -86,6 +86,27 @@ describe('Key methods', () => { assert(data.includes('ip:124:uid')); assert(data.includes('ip:1:uid')); }); + + it('should scan keys for specific glob pattern', async () => { + const keys = [ + 'tid:00000b86-a333-45ee-ba88-17e78aa6c814:recipients', + 'tid:00001fa5-61e1-4ba5-ac22-572ab98eb76f:recipients', + 'tid:00002ce3-203d-4b14-a201-b3d021f462a5:recipients', + 'other:123:stuff', + 'tid:999:sender', + ]; + await db.sortedSetAddBulk(keys.map(key => [key, 1, 'a'])); + + const data = await db.scan({ match: 'tid:*:recipients' }); + + assert.equal(data.length, 3, 'Should have found exactly 3 recipient keys'); + assert(data.includes('tid:00000b86-a333-45ee-ba88-17e78aa6c814:recipients')); + assert(data.includes('tid:00001fa5-61e1-4ba5-ac22-572ab98eb76f:recipients')); + assert(data.includes('tid:00002ce3-203d-4b14-a201-b3d021f462a5:recipients')); + + assert(!data.includes('other:123:stuff'), 'Should not include unrelated keys'); + assert(!data.includes('tid:999:sender'), 'Should not include sender keys'); + }); }); it('should delete a key without error', (done) => {