Each driver typically has its own way of dealing with query constraints. A example of a constraint would be f1 < 2 and f2 >= 3. Since the data handler uses a query by example syntax, it’s not immediately obvious how to apply multiple constraints. The data handler provides a mechanism for specific handlers to filter results, even if the handler itself is not capable of these operations – meaning that the syntax is exactly the same regardless of how the underlying driver works.

How to constrain queries.

The handler provides a method, .constraint() that can apply constraints to a query by example. Consider this query –

handler.query ({age:5, height:0.7});

which would return all objects matching the example.  
But say we wanted all heights between .5 and .9 , and all ages less than 7.

var c = enums.CONSTRAINTS;
handler.query ( age: handler.constraints([[c.LTE,.9],[c.GTE,.5]]), age: handler.constraint([c.LT,7]));

This is similar to the ScriptDB and parse.com approach, except of course using the data handler means that the syntax is now the same for all drivers.

Objects deeper than one level

Trying to represent multi depth objects in a 2 dimensional space, such as a spreadsheet can be a challenge. The approach I’m using is laid out in Flattening and unflattening objects to spreadsheets. This means that we can use dot syntax to define queries going beyond 1 level.  
Thus this query

handler.query ({location:{country:'usa',state:'il'}})

can also be represented as 

handler.query ({"location.country":"usa","location.state":"il"});

Combining dot syntax and constraints. This is also possible, as below

var c = enums.CONSTRAINTS;
handler.query ({"location.size":handler.constraints([c.GT,100000])});

var c = enums.CONSTRAINTS;
handler.query ({"location.state":handler.constraints([c.IN,["il","ny"]])});

The code

Drivers that cannot implement these functions natively, can use the DataHandler method below to filter down their results. In any case, drivers should be able to deal with a queryOb that may need flattening or have constraints.

self.processFilters = function (queryOb,inputData) {
    var handleError='',handleCode=enums.CODE.OK;

    if (queryOb) {
      var fob = new cFlatten.Flattener(enums.SETTINGS.CONSTRAINT).flatten(queryOb);
      var f = new cFlatten.Flattener();
      inputData = inputData.filter (function (row) {
        var rd = f.flatten(row.data ? row.data : row);
        return Object.keys(fob).every(function(k) {
          return self.constraintFilter(rd,fob,k);
        });
      });
    }
    return self.makeResults(handleCode,handleError,inputData);
  }

    
  self.constraintFilter = function (rd,qob,k) {
    Logger.log(k);
    if (qob[k].hasOwnProperty(enums.SETTINGS.CONSTRAINT)) {
      return qob[k][enums.SETTINGS.CONSTRAINT].every ( function (c) {
          return ( c.constraint === enums.CONSTRAINTS.LT && rd[k] < c.value ) ||
                 ( c.constraint === enums.CONSTRAINTS.LTE && rd[k] <= c.value ) ||
                 ( c.constraint === enums.CONSTRAINTS.GT && rd[k] > c.value ) ||
                 ( c.constraint === enums.CONSTRAINTS.GTE && rd[k] >= c.value ) ||
                 ( c.constraint === enums.CONSTRAINTS.NE && rd[k] !== c.value ) ||
                 ( c.constraint === enums.CONSTRAINTS.IN && c.value.indexOf(rd[k]) >= 0) ||
                 ( c.constraint === enums.CONSTRAINTS.NIN && c.value.indexOf(rd[k]) < 0) ;
      });
    }
    else if (rd.hasOwnProperty(k)) {
      return rd[k] === qob[k];
    }
    else {
      handleError =  k;
      handleCode = enums.CODE.PROPERTY;
      return false;
    }
  }

See Database abstraction with google apps script for more on this.