UR::BoolExpr(3) a where clause for objects

SYNOPSIS


my $o = Acme::Employee->create(
ssn => '123-45-6789',
name => 'Pat Jones',
status => 'active',
start_date => UR::Context->current->now,
payroll_category => 'hourly',
boss => $other_employee,
);
my $bx = Acme::Employee->define_boolexpr(
'payroll_category' => 'hourly',
'status' => ['active','terminated'],
'name like' => '%Jones',
'ssn matches' => '\d{3}-\d{2}-\d{4}',
'start_date between' => ['2009-01-01','2009-02-01'],
'boss.name in' => ['Cletus Titus', 'Mitzy Mayhem'],
);
$bx->evaluate($o); # true
$bx->specifies_value_for('payroll_category') # true
$bx->value_for('payroll_cagtegory') # 'hourly'
$o->payroll_category('salary');
$bx->evaluate($o); # false
# these could take either a boolean expression, or a list of params
# from which it will generate one on-the-fly
my $set = Acme::Employee->define_set($bx); # same as listing all of the params
my @matches = Acme::Employee->get($bx); # same as above, but returns the members
my $bx2 = $bx->reframe('boss');
#'employees.payroll_category' => 'hourly',
#'employees.status' => ['active','terminated'],
#'employees.name like' => '%Jones',
#'employees.ssn matches' => '\d{3}-\d{2}-\d{4}',
#'employees.start_date between' => ['2009-01-01','2009-02-01'],
#'name in' => ['Cletus Titus', 'Mitzy Mayhem'],
my $bx3 = $bx->flatten();
# any indirection in the params takes the form a.b.c at the lowest level
# also 'payroll_category' might become 'pay_history.category', and 'pay_history.is_current' => 1 is added to the list
# if this parameter has that as a custom filter

DESCRIPTION

A UR::BoolExpr object captures a set of match criteria for some class of object.

Calls to get(), create(), and define_set() all use this internally to objectify their parameters. If given a boolean expression object directly they will use it. Otherwise they will construct one from the parameters given.

They have a 1:1 correspondence within the WHERE clause in an SQL statement where RDBMS persistance is used. They also imply the FROM clause in these cases, since the query properties control which joins must be included to return the matching object set.

REFLECTION

The data used to create the boolean expression can be re-extracted:

    my $c = $r->subject_class_name;
    # $c eq "GSC::Clone"
    my @p = $r->params_list;
    # @p = four items
    my %p = $r->params_list;
    # %p = two key value pairs

TEMPLATE SUBCLASSES

The template behind the expression can be of type ::Or, ::And or ::PropertyComparison. These classes handle all of the operating logic for the expressions.

Each of those classes incapsulates 0..n of the next type in the list. All templates simplify to this level. See UR::BoolExpr::Template for details.

CONSTRUCTOR

  my $bx = UR::BoolExpr->resolve('Some::Class', property_1 => 'value_1', ... property_n => 'value_n');
  my $bx1 = Some::Class->define_boolexpr(property_1 => value_1, ... property_n => 'value_n');
  my $bx2 = Some::Class->define_boolexpr('property_1 >' => 12345);
  my $bx3 = UR::BoolExpr->resolve_for_string(
                'Some::Class',
                'property_1 = value_1 and ( property_2 < value_2 or property_3 = value_3 )',
            );

Returns a UR::BoolExpr object that can be used to perform tests on the given class and properties. The default comparison for each property is equality. The third example shows using greater-than operator for property_1. The last example shows constructing a UR::BoolExpr from a string containing properties, operators and values joined with 'and' and 'or', with parentheses indicating precedence.

"resolve_for_string()" can parse simple and complicated expressions. A simple expression is a property name followed by an operator followed by a value. The property name can be a series of properties joined by dots (.) to indicate traversal of multiple layers of indirect properties. Values that include spaces, characters that look like operators, commas, or other special characters should be enclosed in quotes.

The parser understands all the same operators the underlying "resolve()" method understands: =, <, >, <=, >=, ``like'', ``between'' and ``in''. Operators may be prefixed by a bang (!) or the word ``not'' to negate the operator. The ``like'' operator understands the SQL wildcards % and _. Values for the ``between'' operator should be separated by a minus (-). Values for the ``in'' operator should begin with a left bracket, end with a right bracket, and have commas between them. For example:
    name_property in [Bob,Fred,Joe]

Simple expressions may be joined together with the words ``and'' and ``or'' to form a more complicated expression. ``and'' has higher precedence than ``or'', and parentheses can surround sub-expressions to indicate the requested precedence. For example:
    ((prop1 = foo or prop2 = 1) and (prop2 > 10 or prop3 like 'Yo%')) or prop4 in [1,2,3]

In general, whitespace is insignificant. The strings ``prop1 = 1'' is parsed the same as ``prop1=1''. Spaces inside quoted value strings are preserved. For backward compatibility with the deprecated string parser, bare words that appear after the operators =,<,>,<= and >= which are separated by one or more spaces is treated as if it had quotes around the list of words starting with the first character of the first word and ending with the last character of the last word, meaning that spaces at the start and end of the list are trimmed.

Specific ordering may be requested by putting an ``order by'' clause at the end, and is the same as using a -order argument to resolve():
    score > 10 order by name,score.

Likewise, grouping and Set construction is indicated with a ``group by'' clause:
    score > 10 group by color

METHODS

evaluate
    $bx->evaluate($object)

Returns true if the given object satisfies the BoolExpr

template_and_values
  ($template, @values) = $bx->template_and_values();

Returns the UR::BoolExpr::Template and list of the values for the given BoolExpr

is_subset_of
  $bx->is_subset_of($other_bx)

Returns true if the set of objects that matches this BoolExpr is a subset of the set of objects that matches $other_bx. In practice this means:

  * The subject class of $bx isa the subject class of $other_bx
  * all the properties from $bx also appear in $other_bx
  * the operators and values for $bx's properties match $other_bx
values
  @values = $bx->values

Return a list of the values from $bx. The values will be in the same order the BoolExpr was created from

value_for_id
  $id = $bx->value_for_id

If $bx's properties include all the ID properties of its subject class, "value_for_id" returns that value. Otherwise, it returns the empty list. If the subject class has more than one ID property, this returns the value of the composite ID.

specifies_value_for
  $bx->specifies_value_for('property_name');

Returns true if the filter list of $bx includes the given property name

value_for
  my $value = $bx->value_for('property_name');

Return the value for the given property

operator_for
  my $operator = $bx->operator_for('property_name');

Return a string for the operator of the given property. A value of '' (the empty string) means equality (``=''). Other possible values inclue '<', '>', '<=', '>=', 'between', 'true', 'false', 'in', 'not <', 'not >', etc.

normalize
    $bx2 = $bx->normalize;

A boolen expression can be changed in incidental ways and still be equivalent. This method converts the expression into a normalized form so that it can be compared to other normalized expressions without incidental differences affecting the comparision.

flatten
    $bx2 = $bx->flatten();

Transforms a boolean expression into a functional equivalent where indirect properties are turned into property chains.

For instance, in a class with

    a => { is => "A", id_by => "a_id" },
    b => { via => "a", to => "bb" },
    c => { via => "b", to => "cc" },

An expression of:

    c => 1234

Becomes:

    a.bb.cc => 1234

In cases where one of the indirect properties includes a ``where'' clause, the flattened expression would have an additional value for each element:

    a => { is => "A", id_by => "a_id" },
    b => { via => "a", to => "bb" },
    c => { via => "b", where ["xx" => 5678], to => "cc" },

An expression of:

    c => 1234

Becomes:

    a.bb.cc => 1234
    a.bb.xx => 5678
reframe
    $bx  = Acme::Order->define_boolexpr(status => 'active');
    $bx2 = $bx->reframe('customer');

The above will turn a query for orders which are active into a query for customers with active orders, presuming an Acme::Order has a property called ``customer'' with a defined relationship to another class.

INTERNAL STRUCTURE

A boolean expression (or ``rule'') has an ``id'', which completely describes the rule in stringified form, and a method called evaluate($o) which tests the rule on a given object.

The id is composed of two parts: - A template_id. - A value_id.

Nearly all real work delegates to the template to avoid duplication of cached details.

The template_id embeds several other properties, for which the rule delegates to it: - subject_class_name, objects of which the rule can be applied-to - subclass_name, the subclass of rule (property comparison, and, or ``or'') - the body of the rule either key-op-val, or a list of other rules

For example, the rule GSC::Clone name=x,chromosome>y: - the template_id embeds:
    subject_class_name = GSC::Clone
    subclass_name = UR::BoolExpr::And
    and the key-op pairs in sorted order: ``chromosome>,name='' - the value_id embeds the x,y values in a special format

EXAMPLES

my $bool = $x->evaluate($obj);

my $t = GSC::Clone->template_for_params(
    ``status ='',
    ``chromosome []'',
    ``clone_name like'',
    ``clone_size between'' );

my @results = $t->get_matching_objects(
    ``active'',
    [2,4,7],
    ``Foo%'',
    [100000,200000] );

my $r = $t->get_rule($v1,$v2,$v3);

my $t = $r->template;

my @results = $t->get_matching_objects($v1,$v2,$v3); my @results = $r->get_matching_objects();

@r = $r->underlying_rules(); for (@r) {
    print $r->evaluate($c1); }

my $rt = $r->template(); my @rt = $rt->get_underlying_rule_templates();

$r = $rt->get_rule_for_values(@v);

$r = UR::BoolExpr->resolve_for_string(
       'My::Class',
       'name=Bob and (score=10 or score < 5)',
     );