package UR::BoolExpr::Template::And;
use warnings;
use strict;
require UR;
our $VERSION = "0.43"; # UR $VERSION;;
UR::Object::Type->define(
class_name => __PACKAGE__,
is => ['UR::BoolExpr::Template::Composite'],
);
sub _flatten_bx {
my ($class, $bx) = @_;
my $template = $bx->template;
my ($flattened_template, @extra_values) = $template->_flatten(@_);
my $flattened_bx;
if (not @extra_values) {
# optimized
my $flattened_bx_id = $flattened_template->id . $UR::BoolExpr::Util::id_sep . $bx->value_id;
$flattened_bx = UR::BoolExpr->get($flattened_bx_id);
$flattened_bx->{'values'} = $bx->{'values'} unless $flattened_bx->{'values'};
}
else {
$flattened_bx = $flattened_template->get_rule_for_values($bx->values, @extra_values);
}
return $flattened_bx;
}
sub _reframe_bx {
my ($class, $bx, $in_terms_of_property_name) = @_;
my $template = $bx->template;
my ($reframed_template, @extra_values) = $template->_reframe($in_terms_of_property_name);
my $reframed_bx;
if (@extra_values == 0) {
my $reframed_bx_id = $reframed_template->id . $UR::BoolExpr::Util::id_sep . $bx->value_id;
$reframed_bx = UR::BoolExpr->get($reframed_bx_id);
$reframed_bx->{'values'} = $bx->{'values'} unless $reframed_bx->{'values'};
}
else {
my @values = ($bx->values, @extra_values);
$reframed_bx = $reframed_template->get_rule_for_values(@values);
}
return $reframed_bx;
}
sub _flatten {
my $self = $_[0];
if ($self->{flatten}) {
return @{ $self->{flatten} }
}
my @old_keys = @{ $self->_keys };
my $old_property_meta_hash = $self->_property_meta_hash;
my $class_meta = $self->subject_class_name->__meta__;
my @new_keys;
my @extra_keys;
my @extra_values;
my $old_constant_values;
my @new_constant_values;
my $found_unflattened_params = 0;
while (my $key = shift @old_keys) {
my $name = $key;
$name =~ s/ .*//;
if (substr($name,0,1) ne '-') {
my $mdata = $old_property_meta_hash->{$name};
my ($value_position, $operator) = @$mdata{'value_position','operator'};
my ($flat, $add_keys, $add_values) = $class_meta->_flatten_property_name($name);
$found_unflattened_params = 1 if $flat ne $name or @$add_keys or @$add_values;
$flat .= ' ' . $operator if $operator and $operator ne '=';
push @new_keys, $flat;
push @extra_keys, @$add_keys;
push @extra_values, @$add_values;
}
else {
push @new_keys, $key;
$old_constant_values ||= [ @{ $self->_constant_values } ];
my $old_value = shift @$old_constant_values;
my $new_value = [];
for my $part (@$old_value) {
my ($flat, $add_keys, $add_values) = $class_meta->_flatten_property_name($part);
$found_unflattened_params = 1 if $flat ne $name or @$add_keys or @$add_values;
push @$new_value, $flat;
push @extra_keys, @$add_keys;
push @extra_values, @$add_values;
}
push @new_constant_values, $new_value;
}
}
my $constant_values;
if ($old_constant_values) {
# some -* keys were found above, and we flattened the value internals
$constant_values = \@new_constant_values;
}
else {
# no -* keys, just re-use the empty arrayref
$constant_values = $self->_constant_values;
}
if ($found_unflattened_params or @extra_keys) {
if (@extra_keys) {
# there may be duplication between these and the primary joins
# or each other
my %keys_seen = map { $_ => 1 } @new_keys;
my @nodup_extra_keys;
my @nodup_extra_values;
while (my $extra_key = shift @extra_keys) {
my $extra_value = shift @extra_values;
unless ($keys_seen{$extra_key}) {
push @nodup_extra_keys, $extra_key;
push @nodup_extra_values, $extra_value;
$keys_seen{$extra_key} = 1;
}
}
push @new_keys, @nodup_extra_keys;
@extra_values = @nodup_extra_values
}
my $flat = UR::BoolExpr::Template::And->_fast_construct(
$self->subject_class_name,
\@new_keys,
$constant_values,
);
$self->{flatten} = [$flat,@extra_values];
return ($flat, @extra_values);
}
else {
# everything was already flat, just remember this so you DRY
$self->{flatten} = [$self];
Scalar::Util::weaken($self->{flatten}[0]);
return $self
}
}
sub _reframe {
my $self = shift;
my $in_terms_of_property_name = shift;
# determine the from_class, to_class, and path_back
my $from_class = $self->subject_class_name;
my $cmeta = $self->subject_class_name->__meta__;
my @pmeta = $cmeta->property_meta_for_name($in_terms_of_property_name);
unless (@pmeta) {
Carp::confess("Failed to find property $in_terms_of_property_name on $from_class. Cannot reframe $self!");
}
my @reframe_path_forward = map { $_->_resolve_join_chain($in_terms_of_property_name) } @pmeta;
my $to_class = $reframe_path_forward[-1]{foreign_class};
# translate all of the old properties to use the path back to the original class
my ($flat,@extra_values) = $self->_flatten;
my @old_keys = @{ $flat->_keys };
my $old_property_meta_hash = $flat->_property_meta_hash;
my %sub_group_label_used;
my $reframer = sub {
my $old_name = $_[0];
# uses: @reframe_path_forward from above in this closure
# get back to the original object
my @reframe_path_back = reverse @reframe_path_forward;
# then forward to the property related to it
my @filter_path_forward = split('\.',$old_name);
# if the end of the path back matches the beginning of the path
# to the property in the expression unneeded steps (beyond 1)
my $new_key;
while (1) {
unless (@reframe_path_back) {
last;
}
unless (@filter_path_forward) {
last;
}
my $last_name_back = $reframe_path_back[-1]{source_name_for_foreign};
my $first_name_forward = $filter_path_forward[0];
my $turnaround_match = 0;
if ($last_name_back eq $first_name_forward) {
# complete overlap
$turnaround_match = 1; # safe
}
else {
# see if stripping off any labels makes them match
my $last_name_back_base = $last_name_back;
$last_name_back_base =~ s/-.*//;
my $first_name_forward_base = $first_name_forward;
$first_name_forward_base =~ s/-.*//;
if ($last_name_back_base eq $first_name_forward_base) {
# removing the grouping label causes a match
# possible overlap
for my $pair (
[$first_name_forward_base, $last_name_back],
[$last_name_back_base, $first_name_forward],
) {
my ($partial, $full) = @$pair;
if (index($full, $partial) == 0) {
#print "$partial is part of $full\n";
if (my $prev_full = $sub_group_label_used{$partial}) {
# we've tracked back through this $partially specified relationship once
# see if we did it the same way
if ($prev_full eq $full) {
$turnaround_match = 1;
}
else {
#print "previously used $prev_full for $partial: cannot use $full\n";
next;
}
}
else {
# this relationship has not been seen
#print "using $full for $partial\n";
$sub_group_label_used{$partial} = $full;
$turnaround_match = 1;
}
}
}
}
}
if ($turnaround_match == 0) {
# found a difference: no shortcut
# we have to trek all the way back to the original subject before
# moving forward to this property
last;
}
else {
# the last step back matches the first step to the property
if (@reframe_path_back == 1 and @filter_path_forward == 1) {
# just keep one of the identical pair
shift @filter_path_forward;
}
else {
# remove both (if one is empty this is no problem)
pop @reframe_path_back;
shift @filter_path_forward;
}
}
}
$new_key = join('.', map { $_->{foreign_name_for_source} } @reframe_path_back);
$new_key = join('.', ($new_key ? $new_key : ()), @filter_path_forward);
return $new_key;
};
# this is only set below if we find any -* keys
my $old_constant_values;
my @new_keys;
my @new_constant_values;
while (@old_keys) {
my $old_key = shift @old_keys;
if (substr($old_key,0,1) ne '-') {
# a regular property
my $old_name = $old_key;
$old_name =~ s/ .*//;
my $mdata = $old_property_meta_hash->{$old_name};
my ($value_position, $operator) = @$mdata{'value_position','operator'};
my $new_key = $reframer->($old_name);
$new_key .= ' ' . $operator if $operator and $operator ne '=';
push @new_keys, $new_key;
}
else {
# this key is not a property, it's a special key like -order_by or -group_by
unless ($old_key eq '-order_by'
or $old_key eq '-group_by'
or $old_key eq '-hints'
or $old_key eq '-recurse'
) {
Carp::confess("no support yet for $old_key in bx reframe()!");
}
push @new_keys, $old_key;
unless ($old_constant_values) {
$old_constant_values = [ @{ $flat->_constant_values } ];
}
my $old_value = shift @$old_constant_values;
my $new_value = [];
for my $part (@$old_value) {
my $reframed_part = $reframer->($part);
push @$new_value, $reframed_part;
}
push @new_constant_values, $new_value;
}
}
my $constant_values;
if (@new_constant_values) {
$constant_values = \@new_constant_values;
}
else {
$constant_values = $flat->_constant_values; # re-use empty immutable arrayref
}
my $reframed = UR::BoolExpr::Template::And->_fast_construct(
$to_class,
\@new_keys,
$constant_values,
);
return $reframed, @extra_values;
}
sub _template_for_grouped_subsets {
my $self = shift;
my $group_by = $self->group_by;
die "rule template $self->{id} has no -group_by!?!?" unless $group_by;
my @base_property_names = $self->_property_names;
for (my $i = 0; $i < @base_property_names; $i++) {
my $operator = $self->operator_for($base_property_names[$i]);
if ($operator ne '=') {
$base_property_names[$i] .= " $operator";
}
}
my $template = UR::BoolExpr::Template->get_by_subject_class_name_logic_type_and_logic_detail(
$self->subject_class_name,
'And',
join(",", @base_property_names, @$group_by),
);
return $template;
}
sub _variable_value_count {
my $self = shift;
my $k = $self->_underlying_keys;
my $v = $self->_constant_values;
if ($v) {
$v = scalar(@$v);
}
else {
$v = 0;
}
return $k-$v;
}
sub _underlying_keys {
my $self = shift;
my $logic_detail = $self->logic_detail;
return unless $logic_detail;
my @underlying_keys = split(",",$logic_detail);
return @underlying_keys;
}
sub get_underlying_rule_templates {
my $self = shift;
my @underlying_keys = grep { substr($_,0,1) eq '-' ? () : ($_) } $self->_underlying_keys();
my $subject_class_name = $self->subject_class_name;
return map {
UR::BoolExpr::Template::PropertyComparison
->_get_for_subject_class_name_and_logic_detail(
$subject_class_name,
$_
);
} @underlying_keys;
}
sub specifies_value_for {
my ($self, $property_name) = @_;
Carp::confess('Missing required parameter property_name for specifies_value_for()') if not defined $property_name;
my @underlying_templates = $self->get_underlying_rule_templates();
foreach ( @underlying_templates ) {
return 1 if $property_name eq $_->property_name;
}
return;
}
sub _filter_breakdown {
my $self = $_[0];
my $filter_breakdown = $self->{_filter_breakdown} ||= do {
my @underlying = $self->get_underlying_rule_templates;
my @primary;
my %sub_group_filters;
my %sub_group_sub_filters;
for (my $n = 0; $n < @underlying; $n++) {
my $underlying = $underlying[$n];
my $sub_group = $underlying->sub_group;
if ($sub_group) {
if (substr($sub_group,-1) ne '?') {
# control restruct the subject based on the sub-group properties
my $list = $sub_group_filters{$sub_group} ||= [];
push @$list, $underlying, $n;
}
else {
# control what is IN a sub-group (effectively define it with these)
chop($sub_group);
my $list = $sub_group_sub_filters{$sub_group} ||= [];
push @$list, $underlying, $n;
}
}
else {
push @primary, $underlying, $n;
}
}
{
primary => \@primary,
sub_group_filters => \%sub_group_filters,
sub_group_sub_filters => \%sub_group_sub_filters,
};
};
return $filter_breakdown;
}
sub evaluate_subject_and_values {
my $self = shift;
my $subject = shift;
return unless (ref($subject) && $subject->isa($self->subject_class_name));
my $filter_breakdown = $self->_filter_breakdown;
my ($primary,$sub_group_filters,$sub_group_sub_filters)
= @$filter_breakdown{"primary","sub_group_filters","sub_group_sub_filters"};
# flattening expresions now requires that we re-group them :(
# these effectively are subqueries where they occur
# check the ungrouped comparisons first since they are simpler
for (my $n = 0; $n < @$primary; $n+=2) {
my $underlying = $primary->[$n];
my $pos = $primary->[$n+1];
my $value = $_[$pos];
unless ($underlying->evaluate_subject_and_values($subject, $value)) {
return;
}
}
# only check the complicated rules if none of the above failed
if (%$sub_group_filters) {
#$DB::single = 1;
for my $sub_group (keys %$sub_group_filters) {
my $filters = $sub_group_filters->{$sub_group};
my $sub_filters = $sub_group_sub_filters->{$sub_group};
print "FILTERING $sub_group: " . Data::Dumper::Dumper($filters, $sub_filters);
}
}
return 1;
}
sub params_list_for_values {
# This is the reverse of the bulk of resolve.
# It returns the params in list form, directly coercable into a hash if necessary.
# $r = UR::BoolExpr->resolve($c1,@p1);
# ($c2, @p2) = ($r->subject_class_name, $r->params_list);
my $rule_template = shift;
my @values_sorted = @_;
my @keys_sorted = $rule_template->_underlying_keys;
my $constant_values = $rule_template->_constant_values;
my @params;
my ($v,$c) = (0,0);
for (my $k=0; $k<@keys_sorted; $k++) {
my $key = $keys_sorted[$k];
#if (substr($key,0,1) eq "_") {
# next;
#}
#elsif (substr($key,0,1) eq '-') {
if (substr($key,0,1) eq '-') {
my $value = $constant_values->[$c];
push @params, $key, $value;
$c++;
}
else {
my ($property, $op) = ($key =~ /^(\-*[\w\.]+)\s*(.*)$/);
unless ($property) {
$DB::single = 1;
Carp::confess("bad key $key in @keys_sorted");
}
my $value = $values_sorted[$v];
if ($op) {
if ($op ne "in") {
if ($op =~ /^(.+)-(.+)$/) {
$value = { operator => $1, value => $value, escape => $2 };
}
else {
$value = { operator => $op, value => $value };
}
}
}
push @params, $property, $value;
$v++;
}
}
return @params;
}
sub _fast_construct {
my ($class,
$subject_class_name, # produces subject class meta
$keys, # produces logic detail
$constant_values, # produces constant value id
$logic_detail, # optional, passed by get
$constant_value_id, # optional, passed by get
$subject_class_meta, # optional, passed by bx
) = @_;
my $logic_type = 'And';
$logic_detail ||= join(",",@$keys);
$constant_value_id ||= UR::BoolExpr::Util->values_to_value_id(@$constant_values);
my $id = join('/',$subject_class_name,$logic_type,$logic_detail,$constant_value_id);
my $self = $UR::Object::rule_templates->{$id};
return $self if $self;
$subject_class_meta ||= $subject_class_name->__meta__;
# See what properties are id-related for the class
my $cache = $subject_class_meta->{cache}{'UR::BoolExpr::Template::get'} ||= do {
my $id_related = {};
my $id_translations = [];
my $id_pos = {};
my $id_prop_is_real; # true if there's a property called 'id' that's a real property, not from UR::Object
for my $iclass ($subject_class_name, $subject_class_meta->ancestry_class_names) {
last if $iclass eq "UR::Object";
next unless $iclass->isa("UR::Object");
my $iclass_meta = $iclass->__meta__;
my @id_props = $iclass_meta->id_property_names;
next unless @id_props;
$id_prop_is_real = 1 if (grep { $_ eq 'id'} @id_props);
next if @id_props == 1 and $id_props[0] eq "id" and !$id_prop_is_real;
@$id_related{@id_props} = @id_props;
push @$id_translations, \@id_props;
@$id_pos{@id_props} = (0..$#id_props) unless @id_props == 1 and $id_props[0] eq 'id';
}
[$id_related,$id_translations,$id_pos];
};
my ($id_related,$id_translations,$id_pos) = @$cache;
my @keys = @$keys;
my @constant_values = @$constant_values;
# Make a hash to quick-validate the params for duplication
no warnings;
my %check_for_duplicate_rules;
for (my $n=0; $n < @keys; $n++) {
next if (substr($keys[$n],0,1) eq '-');
my $pos = index($keys[$n],' ');
if ($pos != -1) {
my $property = substr($keys[$n],0,$pos);
$check_for_duplicate_rules{$property}++;
}
else {
$check_for_duplicate_rules{$keys[$n]}++;
}
}
# each item in this list mutates the initial set of key-value pairs
my $extenders = [];
# add new @$extenders for class-specific characteristics
# add new @keys at the same time
# flag keys as removed also at the same time
# note the positions for each key in the "original" rule
# by original, we mean the original plus the extensions from above
#
my $id_position = undef;
my $var_pos = 0;
my $const_pos = 0;
my $property_meta_hash = {};
my $property_names = [];
for my $key (@keys) {
if (substr($key,0,1) eq '-') {
$property_meta_hash->{$key} = {
name => $key,
value_position => $const_pos
};
$const_pos++;
}
else {
my ($name, $op) = ($key =~ /^(.+?)\s+(.*)$/);
$name ||= $key;
if ($name eq 'id') {
$id_position = $var_pos;
}
$property_meta_hash->{$name} = {
name => $name,
operator => $op,
value_position => $var_pos
};
$var_pos++;
push @$property_names, $name;
}
}
# Note whether there are properties not involved in the ID
# Add value extenders for any cases of id-related properties,
# or aliases.
my $original_key_count = @keys;
my $id_only = 1;
my $partial_id = 0;
my $key_op_hash = {};
if (@$id_translations and @{$id_translations->[0]} == 1) {
# single-property ID
## use Data::Dumper;
## print "single property id\n". Dumper($id_translations);
my ($property, $op);
# Presume we are only getting id properties until another is found.
# If a multi-property is partially specified, we'll zero this out too.
my $values_index = -1; # -1 so we can bump it at start of loop
for (my $key_pos = 0; $key_pos < $original_key_count; $key_pos++) {
my $key = $keys[$key_pos];
if (substr($key, 0, 1) eq '-') {
# -* are constant value keys and do not need to be changed
next;
} else {
$values_index++;
}
my ($property, $op) = ($key =~ /^(.+?)\s+(.*)$/);
$property ||= $key;
$op ||= "";
$op =~ s/\s+//;
$key_op_hash->{$property} ||= {};
$key_op_hash->{$property}{$op}++;
if ($property eq "id" or $id_related->{$property}) {
# Put an id key into the key list.
for my $alias (["id"], @$id_translations) {
next if $alias->[0] eq $property;
next if $check_for_duplicate_rules{$alias->[0]};
$op ||= "";
push @keys, $alias->[0] . ($op ? " $op" : "");
push @$extenders, [ [$values_index], undef, $keys[-1] ];
$key_op_hash->{$alias->[0]} ||= {};
$key_op_hash->{$alias->[0]}{$op}++;
## print ">> extend for @$alias with op $op.\n";
}
unless ($op =~ m/^(=|eq|in|\[\]|)$/) {
$id_only = 0;
}
}
elsif (substr($key,0,1) ne '-') {
$id_only = 0;
## print "non id single property $property on $subject_class\n";
}
}
}
else {
# multi-property ID
## print "multi property id\n". Dumper($id_translations);
my ($property, $op);
my %id_parts;
my $values_index = -1; # -1 so we can bump it at start of loop
my $id_op;
for (my $key_pos = 0; $key_pos < $original_key_count; $key_pos++) {
my $key = $keys[$key_pos];
if (substr($key, 0, 1) eq '-') {
# -* are constant value keys and do not need to be changed
next;
} else {
$values_index++;
}
next if substr($key,0,1) eq '-';
my ($property, $op) = ($key =~ /^(.+?)\s+(.*)$/);
$property ||= $key;
$op ||= '';
$op =~ s/^\s+// if $op;
$key_op_hash->{$property} ||= {};
$key_op_hash->{$property}{$op}++;
if ($property eq "id") {
$id_op = $op;
$key_op_hash->{id} ||= {};
$key_op_hash->{id}{$op}++;
# Put an id-breakdown key into the key list.
for my $alias (@$id_translations) {
my @new_keys = map { $_ . ($op ? " $op" : "") } @$alias;
if (grep { $check_for_duplicate_rules{$_} } @$alias) {
#print "up @new_keys with @$alias\n";
}
else {
push @keys, @new_keys;
push @$extenders, [ [$values_index], "resolve_ordered_values_from_composite_id", @new_keys ];
for (@$alias) {
$key_op_hash->{$_} ||= {};
$key_op_hash->{$_}{$op}++;
}
# print ">> extend for @$alias with op $op.\n";
}
}
}
elsif ($id_related->{$property}) {
$id_op ||= $op;
if ($op eq "" or $op eq "eq" or $op eq "=" or $op eq 'in') {
$id_parts{$id_pos->{$property}} = $values_index;
}
else {
# We're doing some sort of gray-area comparison on an ID
# field, and though we could possibly resolve an ID
# from things like an 'in' op, it's more than we've done
# before.
$id_only = 0;
}
}
else {
## print "non id multi property $property on class $subject_class\n";
$id_only = 0;
}
}
if (my $parts = (scalar(keys(%id_parts)))) {
# some parts are id-related
if ($parts == @{$id_translations->[0]}) {
# all parts are of the id are there
if (@$id_translations) {
if (grep { $_ eq 'id' } @keys) {
#print "found id already\n";
}
else {
#print "no id\n";
# we have translations of that ID into underlying properties
#print "ADDING ID for " . join(",",keys %id_parts) . "\n";
my @id_pos = sort { $a <=> $b } keys %id_parts;
push @$extenders, [ [@id_parts{@id_pos}], "resolve_composite_id_from_ordered_values", 'id' ]; #TODO was this correct?
$key_op_hash->{id} ||= {};
$key_op_hash->{id}{$id_op}++;
push @keys, "id";
}
}
}
else {
# not all parts of the id are there
## print "partial id property $property on class $subject_class\n";
$id_only = 0;
$partial_id = 1;
}
} else {
$id_only = 0;
$partial_id = 0;
}
}
# Determine the positions of each key in the parameter list.
# In actuality, the position of the key's value in the @values or @constant_values array,
# depending on whether it is a -* key or not.
my %key_positions;
my $vpos = 0;
my $cpos = 0;
for my $key (@keys) {
$key_positions{$key} ||= [];
if (substr($key,0,1) eq '-') {
push @{ $key_positions{$key} }, $cpos++;
}
else {
push @{ $key_positions{$key} }, $vpos++;
}
}
# Sort the keys, and make an arrayref which will
# re-order the values to match.
my $last_key = '';
my @keys_sorted = map { $_ eq $last_key ? () : ($last_key = $_) } sort @keys;
my $normalized_positions_arrayref = [];
my $constant_value_normalized_positions = [];
my $recursion_desc = undef;
my $hints = undef;
my $order_by = undef;
my $group_by = undef;
my $page = undef;
my $limit = undef;
my $offset = undef;
my $aggregate = undef;
my @constant_values_sorted;
for my $key (@keys_sorted) {
my $pos_list = $key_positions{$key};
my $pos = pop @$pos_list;
if (substr($key,0,1) eq '-') {
push @$constant_value_normalized_positions, $pos;
my $constant_value = $constant_values[$pos];
if ($key eq '-recurse') {
$constant_value = [$constant_value] if (!ref $constant_value);
$recursion_desc = $constant_value;
}
elsif ($key eq '-hints' or $key eq '-hint') {
$constant_value = [$constant_value] if (!ref $constant_value);
$hints = $constant_value;
}
elsif ($key eq '-order_by' or $key eq '-order') {
$constant_value = [$constant_value] if (!ref $constant_value);
$order_by = $constant_value;
}
elsif ($key eq '-group_by' or $key eq '-group') {
$constant_value = [$constant_value] if (!ref $constant_value);
$group_by = $constant_value;
}
elsif ($key eq '-page') {
$constant_value = [$constant_value] if (!ref $constant_value);
$page = $constant_value;
}
elsif ($key eq '-limit') {
$limit = $constant_value;
}
elsif ($key eq '-offset') {
$offset = $constant_value;
}
elsif ($key eq '-aggregate') {
$constant_value = [$constant_value] if (!ref $constant_value);
$aggregate = $constant_value;
}
else {
Carp::croak("Unknown special param '$key'. Expected one of: @UR::BoolExpr::Template::meta_param_names");
}
push @constant_values_sorted, $constant_value;
}
else {
push @$normalized_positions_arrayref, $pos;
}
}
if ($page) {
if (defined($limit) || defined($offset)) {
Carp::croak("-page and -limit/-offset are mutually exclusive when defining a BoolExpr");
}
if (ref($page) and ref($page) eq 'ARRAY') {
if (@$page == 2) {
$limit = $page->[1];
$offset = ($page->[0] - 1) * $limit;
} elsif (@$page) {
Carp::croak('-page must be an arrayref of two integers: -page => [$page_number, $page_size]');
}
} else {
Carp::croak('-page must be an arrayref of two integers: -page => [$page_number, $page_size]');
}
}
if (defined($hints) and ref($hints) ne 'ARRAY') {
if (! ref($hints)) {
$hints = [$hints]; # convert it to a list of one item
} else {
Carp::croak('-hints of a rule must be an arrayref of property names');
}
}
my $matches_all = scalar(@keys_sorted) == scalar(@constant_values);
$id_only = 0 if ($matches_all);
# these are used to rapidly turn a bx used for querying into one
# suitable for object construction
my @ambiguous_keys;
my @ambiguous_property_names;
for (my $n=0; $n < @keys; $n++) {
next if substr($keys[$n],0,1) eq '-';
my ($property, $op) = ($keys[$n] =~ /^(.+?)\s+(.*)$/);
$property ||= $keys[$n];
$op ||= '';
$op =~ s/^\s+// if $op;
if ($op and $op ne 'eq' and $op ne '==' and $op ne '=') {
push @ambiguous_keys, $keys[$n];
push @ambiguous_property_names, $property;
}
}
# Determine the rule template's ID.
# The normalizer will store this. Below, we'll
# find or create the template for this ID.
my $normalized_constant_value_id = (scalar(@constant_values_sorted) ? UR::BoolExpr::Util->values_to_value_id(@constant_values_sorted) : $constant_value_id);
my @keys_unaliased = $UR::Object::Type::bootstrapping
? @keys_sorted
: map { $_->[0] = substr($_->[0], 0, 1) eq '-' ? $_->[0] : $subject_class_meta->resolve_property_aliases($_->[0]);
join(' ',@$_); }
map { [ split(' ') ] }
@keys_sorted;
my $normalized_id = UR::BoolExpr::Template->__meta__->resolve_composite_id_from_ordered_values($subject_class_name, "And", join(",",@keys_unaliased), $normalized_constant_value_id);
$self = bless {
id => $id,
subject_class_name => $subject_class_name,
logic_type => $logic_type,
logic_detail => $logic_detail,
constant_value_id => $constant_value_id,
normalized_id => $normalized_id,
# subclass specific
id_position => $id_position,
is_id_only => $id_only,
is_partial_id => $partial_id,
is_unique => undef, # assigned on first use
matches_all => $matches_all,
key_op_hash => $key_op_hash,
_property_names_arrayref => $property_names,
_property_meta_hash => $property_meta_hash,
recursion_desc => $recursion_desc,
hints => $hints,
order_by => $order_by,
group_by => $group_by,
limit => $limit,
offset => $offset,
aggregate => $aggregate,
is_normalized => ($id eq $normalized_id ? 1 : 0),
normalized_positions_arrayref => $normalized_positions_arrayref,
constant_value_normalized_positions_arrayref => $constant_value_normalized_positions,
normalization_extender_arrayref => $extenders,
num_values => scalar(@$keys),
_keys => \@keys,
_constant_values => $constant_values,
_ambiguous_keys => (@ambiguous_keys ? \@ambiguous_keys : undef),
_ambiguous_property_names => (@ambiguous_property_names ? \@ambiguous_property_names : undef),
}, 'UR::BoolExpr::Template::And';
$UR::Object::rule_templates->{$id} = $self;
return $self;
}
1;
=pod
=head1 NAME
UR::BoolExpr::And - a rule which is true if ALL the underlying conditions are true
=head1 SEE ALSO
UR::BoolExpr;(3)
=cut