Search code examples
javascriptnode.jspostgresqlpg-promise

pg-promise create custom filters for select query


The function that I am working on is getting an input object that has 7 different key-values and each of them could be undefined or not. I want to filter my database based on those key-values that exists in the input. For example if only input.userID exists I want to run this query:

db.query("...WHERE userID = ${userID}", {userID: input.userID});

else if both input.userID and input.startTime exist, I want to do this:

db.query("...WHERE userID = ${userID} and startTime= ${startTime}", {userID: input.userID, startTime: input.startTime});

What I have done is I created a params and keys object like this:

if(input.userID) {
   keys.push('userID');
   params.push(input.userID);
   query = addFilterToTheQuery(query, 'userID', input.userID, filteredItemsCount);
   filteredItemsCount = filteredItemsCount +1;
}

addFilterToTheQuery is a simple function I implemented myself. I basically make 7 if cases. Then I have to use those keys and param values to pass to the query function in a way that might need another huge switch case code.

Is this the only way to do this? Is there a better way to get rid of the redundancies in this code?


Solution

  • Custom Type Formatting is the most suitable here.

    For example, if we want to convert an object with properties - filter values, we could do it like this:

    var pgp = require('pg-promise')(/* initialization options */);
    
    function FilterSet(filters) {
        if (!filters || typeof filters !== 'object') {
            throw new TypeError('Parameter \'filters\' must be an object.');
        }
        this._rawDBType = true; // property renamed later - see UPDATE below
        this.formatDBType = function () {
            var keys = Object.keys(filters);
            var s = keys.map(function (k) {
                return pgp.as.name(k) + ' = ${' + k + '}';
            }).join(' AND ');
            return pgp.as.format(s, filters);
        };
    }
    

    TEST

    var filter = new FilterSet({
        first: 1,
        second: 'two'
    });
    
    var test = pgp.as.format('WHERE $1', filter);
    
    console.log(test);
    

    This outputs:

    WHERE "first" = 1 AND "second" = 'two'
    

    If your filters are to be used as %value% with LIKE or ILIKE, then you would need to change your custom type accordingly.

    See related questions: 42, 49, 89, 90,

    UPDATE

    Below is the same example re-written for the latest pg-promise (version 8.x or newer):

    const pgp = require('pg-promise')(/* initialization options */);
    
    class FilterSet {
        constructor(filters) {
            if (!filters || typeof filters !== 'object') {
                throw new TypeError('Parameter \'filters\' must be an object.');
            }
            this.filters = filters;
            this.rawType = true; // do not escape the result from toPostgres()
        }
    
        toPostgres(/*self*/) {
            // self = this
            const keys = Object.keys(this.filters);
            const s = keys.map(k => pgp.as.name(k) + ' = ${' + k + '}').join(' AND ');
            return pgp.as.format(s, this.filters);
        }
    }
    

    See Custom Type Formatting.