UR::BoolExpr(3pm)

πŸ“¦
version 0.470 Β· 2 versions
, in debian bookworm, ubuntu jammy, ubuntu noble

✨ NAME ✨

UR::BoolExpr(3pm) β€” bestie, this is a "where clause" for objects. That's literally it. That's the whole vibe. πŸŽ€


πŸ’… SYNOPSIS πŸ’…

Okay so here's what it looks like in the wild, and yes we're keeping every line exactly as it is:

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
and yes, payroll_cagtegory is a typo in the original, we are NOT fixing it, it lives here now. πŸ’€

🌸 DESCRIPTION 🌸

So a UR::BoolExpr object captures a set of match criteria for some class of object. That's its whole job. It's the girl who remembers what you're looking for. πŸ’–

Calls to get(), create(), and define_set() all use this internally to objectify their parameters. If you hand them a boolean expression object directly, they'll just use it. Otherwise they construct one from the parameters you gave.

They have a 1:1 correspondence with the WHERE clause in an SQL statement when RDBMS persistence is used. And they also imply the FROM clause in those cases, because the query properties control which joins must be included to return the matching object set. Like, the BoolExpr is out here doing the most and honestly? slay. πŸ‘‘


πŸ¦‹ REFLECTION πŸ¦‹

The data used to create the boolean expression can be re-extracted. Look at her go:

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. All the brains, no notes.

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. iykyk. ✨


🌈 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 a 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 string "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. That's it. Simple. Iconic. βœ…

🌟 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. multi-id girlies rise up 🫢

🌟 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 include '<', '>', '<=', '>=', 'between', 'true', 'false', 'in', 'not <', 'not >', etc.

🌟 normalize

$bx2 = $bx->normalize;

A boolean 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 comparison. no cap, this is the "are we actually the same or just wearing the same outfit" check. πŸ’…

🌟 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. It's giving ✨perspective shift✨.


πŸ‘‘ 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

more parts incoming bestie, don't go anywhere πŸ’œ

🌟 EXAMPLES 🌟

Okay bestie, here's where it all comes together. Real code, real vibes πŸ’–

Evaluate a rule against an object:

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

Make yourself a template with a bunch of params, no big deal:

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

Then actually use it β€” pass the values in the same order as the params, and boom, matching objects:

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

Grab the rule that those values resolve to:

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

Get the template back out of a rule (it's giving very "return to sender" ✨):

my $t = $r->template;

Same results either way β€” through the template, or straight off the rule. Both slay:

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

Peek under the hood and hit every underlying rule with an evaluate, one by one:

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

Template inception moment πŸŒ€ β€” get the template, then get its underlying rule templates:

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

And then pull a rule back out of that rule template for a set of values:

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

Finally, the glow-up move: resolve a whole expression string into a rule, no template juggling required:

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

πŸ‘€ SEE ALSO πŸ‘€


And that's the whole page, bestie!! πŸŽ€ You made it to the end, iconic behavior fr fr. This one came from perl v5.32.1 and was dated 2022-01-17 β€” the UR::BoolExpr(3pm) era, periodt πŸ’…βœ¨

πŸ“¦ where this page comes from