krill: simple boolean filter language
Krill provides functions for validating and evaluating boolean filters (also
called predicates) expressed in a simple JSON language that's intended to be
easy to incorporate into JSON APIs.
Synopsis
The basic idea is that you construct a predicate as a boolean expression that
uses variables (called fields). You can then evaluate the predicate with a
particular assignment of variables.
You can specify types for each field, in which case the expression itself will
be type-checked when you create it.
var types = {
'hostname': 'string',
'latency': 'number'
};
var input = {
'or': [
{ 'eq': [ 'hostname', 'spike' ] },
{ 'gt': [ 'latency', 300 ] }
]
};
console.log(input);
var predicate = krill.createPredicate(input, types);
A trivial predicate is one that's just "true":
console.log('trivial? ', predicate.trivial());
You can print out the fields (variables) used in this predicate:
console.log('fields: ', predicate.fields().join(', '));
You can also get access to an object that represents a map between field names
and the lists of values used for each field name in this predicate:
console.log('field names to values: ' + predicate.fieldsAndValues());
You can also print a C-syntax expression for this predicate, which you can
actually plug directly into a C-like language (like JavaScript) to evaluate it:
console.log('DTrace format: ', predicate.toCStyleString());
You can also print a LDAP search filter that represents this predicate:
console.log('LDAP search filter: ', predicate.toLDAPFilterString());
Please note however that without knowing the LDAP object schema, it is not
possible to generate a filter that matches all objects. As a result, trivial
predicates cannot be serialized as LDAP search filters:
var pred = krill.createPredicate({});
pred.toLDAPSearchFilter();
The recommended way to handle this case is to check if the predicate is trivial
before calling toLDAPSearchFilter
:
var pred = krill.createPredicate({});
var ldapSearchFilter;
if (!pred.trivial()) {
ldapSearchFilter = pred.toLDAPFilterString();
} else {
ldapSearchFilter = '(someRDN=*)';
}
You can also evaluate the predicate for a specific set of values:
var value = { 'hostname': 'spike', 'latency': 12 };
console.log(value, predicate.eval(value));
value = { 'hostname': 'sharptooth', 'latency': 400 };
console.log(value, predicate.eval(value));
value = { 'hostname': 'sharptooth', 'latency': 12 };
console.log(value, predicate.eval(value));
Streaming interface
For data processing pipelines, it's useful to treat predicates as a transform
stream that just filters out some results. You can do this with a
PredicateStream. Using the same "types" and "predicate" from above:
var stream = mod_krill.createPredicateStream({ 'predicate': predicate });
stream.write({ 'hostname': 'spike', 'latency': 12 });
stream.write({ 'hostname': 'sharptooth', 'latency': 12 });
stream.write({ 'hostname': 'sharptooth', 'latency': 400 });
stream.on('data', function (c) { console.log(c); });
stream.on('invalid_object', function (obj, err, count) {
console.error('object %d is invalid: %s', count, err.message);
console.error('object was: %s', JSON.stringify(obj));
});
stream.write({ 'hostname': 'invalid' });
stream.on('end', function () { console.log(stream.stats()); });
stream.end();
JSON input format
All predicates can be represented as JSON objects, and you typically pass such
an object into createPredicate
to work with them. The simplest predicate is:
{}
The general pattern for relational operators is:
{ 'OPERATOR': [ 'VARNAME', 'VALUE' ] }
In all of these cases, OPERATOR must be one of the built-in operators, VARNAME
can be any string, and VALUE should be either a specific string or numeric
value.
The built-in operators are:
'eq'
: is-equal-to (strings and numbers)'ne'
: is-not-equal-to (strings and numbers)'lt'
: is-less-than (numbers only)'le'
: is-less-than-or-equal-to (numbers only)'ge'
: is-greater-than-or-equal-to (numbers only)'gt'
: is-greater-than (numbers only)
For examples:
{ 'eq': [ 'hostname', 'spike' ] }
{ 'lt': [ 'count', 15 ] }
You can also use "and" and "or", which have the form:
{ 'or': [ expr1, expr2, ... ] }
{ 'and': [ expr1, expr2, ... ] }
where expr1
, expr2
, and so on are any other predicate. For example:
{
'or': [
{ 'eq': [ 'hostname', 'spike' ] },
{ 'gt': [ 'latency', 300 ] }
]
};
is logically equivalent to the C expression:
hostname == "spike" || latency > 300