}
sub castdate {
- my ($value, $gte, $lte) = @_;
+ my ($value, $gte, $lte, $between) = @_;
my $op = "=";
$op = ">=" if $gte;
$op = "<=" if $lte;
+ $op = "between" if $between;
# avoid transforming a date if the match value is NULL.
return {'=' => undef} if $op eq '=' and not $value;
};
# castdate not supported for acqlia fields: they're all type text
- my ($k, $v, $fuzzy, $between, $not) = breakdown_term($unit);
+ my ($k, $v, $fuzzy, $between, $not, $starts, $ends) = breakdown_term($unit);
my $point = $subquery->{"where"}->{"-and"};
my $term_clause;
if ($fuzzy and not ref $v) {
push @$point, {"attr_value" => {"ilike" => "%" . $v . "%"}};
+ } elsif ($starts and not ref $v) {
+ push @$point, {"attr_value" => {"ilike" => $v . "%"}};
+ } elsif ($ends and not ref $v) {
+ push @$point, {"attr_value" => {"ilike" => "%" . $v}};
} elsif ($between and could_be_range($v)) {
push @$point, {"attr_value" => {"between" => $v}};
} elsif (check_1d_max($v)) {
foreach my $unit (@$acqlia) {
# castdate not supported for acqlia fields: they're all type text
- my ($k, $v, $fuzzy, $between, $not) = breakdown_term($unit);
+ my ($k, $v, $fuzzy, $between, $not, $starts, $ends) = breakdown_term($unit);
my $term_clause;
if ($fuzzy and not ref $v) {
$term_clause = {
"attr_value" => {"ilike" => "%" . $v . "%"}
}
};
+ } elsif ($starts and not ref $v) {
+ $term_clause = {
+ "-and" => {
+ "definition" => $k,
+ "attr_value" => {"ilike" => $v . "%"}
+ }
+ };
+ } elsif ($ends and not ref $v) {
+ $term_clause = {
+ "-and" => {
+ "definition" => $k,
+ "attr_value" => {"ilike" => "%" . $v}
+ }
+ };
} elsif ($between and could_be_range($v)) {
$term_clause = {
"-and" => {
$term->{"__fuzzy"} ? 1 : 0,
$term->{"__between"} ? 1 : 0,
$term->{"__not"} ? 1 : 0,
+ $term->{"__starts"} ? 1 : 0,
+ $term->{"__ends"} ? 1 : 0,
$term->{"__castdate"} ? 1 : 0,
$term->{"__gte"} ? 1 : 0,
- $term->{"__lte"} ? 1 : 0
+ $term->{"__lte"} ? 1 : 0,
+ $term->{"__age"} ? 1 : 0,
);
}
@joins;
}
+sub gen_acqpro_term {
+ my ($value, $n) = @_;
+ my $lc_value = {
+ "=" => { transform => "lowercase", value => lc($value) }
+ };
+
+ +{
+ "-or" => [
+ {"+acqpro$n" => {"name" => $value}},
+ {"+acqpro$n" => {"code" => $lc_value}},
+ ]
+ };
+}
+
+# go through the terms hash, find keys that correspond to fields links
+# to actor.usr, and rewrite the search as one that searches not by
+# actor.usr.id but by any of these user properties: card barcode, username,
+# given names and family name.
+sub prepare_acqpro_terms {
+ my ($terms, $join_num) = @_;
+
+ my @joins = ();
+ my $nots = 0;
+ $join_num ||= 0;
+
+ foreach my $conj (qw/-and -or/) {
+ next unless exists $terms->{$conj};
+
+ my @new_outer_terms = ();
+ HINT_UNIT: foreach my $hint_unit (@{$terms->{$conj}}) {
+ my $hint = (keys %$hint_unit)[0];
+ (my $plain_hint = $hint) =~ y/+//d;
+ if ($hint eq "-not") {
+ $hint_unit = $hint_unit->{$hint};
+ $nots++;
+ redo HINT_UNIT;
+ }
+
+ if (my $links = get_fm_links_by_hint($plain_hint) and
+ $plain_hint ne "acqlia") {
+ my @new_terms = ();
+ my ($attr, $value) = breakdown_term($hint_unit->{$hint});
+ my $is_fuzzy = ref($value) eq 'HASH' && exists($value->{'ilike'});
+ if ($links->{$attr} and
+ $is_fuzzy and
+ $links->{$attr}->{"class"} eq "acqpro") {
+ push @joins, [$plain_hint, $attr, $join_num];
+ my $acqpro_term = gen_acqpro_term($value, $join_num);
+ if ($nots > 0) {
+ $acqpro_term = {"-not" => $acqpro_term};
+ $nots--;
+ }
+ push @new_outer_terms, $acqpro_term;
+ $join_num++;
+ delete $hint_unit->{$hint};
+ }
+ }
+ if ($nots > 0) {
+ $hint_unit = {"-not" => $hint_unit};
+ $nots--;
+ }
+ push @new_outer_terms, $hint_unit if scalar keys %$hint_unit;
+ }
+ $terms->{$conj} = [ @new_outer_terms ];
+ }
+ @joins;
+}
+
sub prepare_terms {
my ($terms, $is_and) = @_;
$outer_clause->{$conj} = [] unless $outer_clause->{$conj};
foreach my $unit (@{$terms->{$class}}) {
my $special_clause;
- my ($k, $v, $fuzzy, $between, $not, $castdate, $gte, $lte) =
+ my ($k, $v, $fuzzy, $between, $not, $starts, $ends, $castdate, $gte, $lte, $age) =
breakdown_term($unit);
my $term_clause;
- if ($fuzzy and not ref $v) {
+ if ($age and not ref $v) { # $v is expected to be parsed as an interval
+ $v =~ s/^\s*//;
+
+ my $op = $gte ? '>=' : '<=';
+ $term_clause = {$k => {$op => {transform => 'age', params => ['now'], value => '-' . $v}}};
+
+ # !!! NOTE: we invert $not because we have to compare to a /negative/
+ # interval, due to json_query restiction on function parameter order for
+ # transformed fields, so we flip the comparison. Alternatively we could
+ # swap the GTE and LTE operators, but that would make the query harder
+ # to read and make diagnosing issues much more difficult.
+ $not = $not ? 0 : 1;
+
+ } elsif ($starts and not ref $v) {
+ $term_clause = {$k => {"ilike" => $v . "%"}};
+ } elsif ($ends and not ref $v) {
+ $term_clause = {$k => {"ilike" => "%" . $v}};
+ } elsif ($fuzzy and not ref $v) {
$term_clause = {$k => {"ilike" => "%" . $v . "%"}};
} elsif ($between and could_be_range($v)) {
- $term_clause = {$k => {"between" => $v}};
+ if ($castdate) {
+ $v = castdate($v, 0, 0, $between);
+ $term_clause = {$k => $v};
+ } else {
+ $term_clause = {$k => {between => $v}};
+ }
} elsif (check_1d_max($v)) {
if ($castdate) {
- $v = castdate($v, $gte, $lte) if $castdate;
+ $v = castdate($v, $gte, $lte);
} elsif ($gte or $lte) {
my $op = $gte ? '>=' : '<=';
$v = {$op => $v};
$n;
}
+sub add_acqpro_joins {
+ my $graft_map = shift;
+ my $core_hint = shift;
+
+ my $n = 0;
+ foreach my $join (@_) {
+ my ($hint, $attr, $num) = @$join;
+ my $start = $graft_map->{$hint};
+ my $clause = {
+ "class" => "acqpro",
+ "type" => "left",
+ "field" => "id",
+ "fkey" => $attr,
+ };
+
+ if ($hint eq $core_hint) {
+ $start->{"acqpro$num"} = $clause;
+ } else {
+ $start->{"join"} ||= {};
+ $start->{"join"}->{"acqpro$num"} = $clause;
+ }
+
+ $n++;
+ }
+ $n;
+}
+
sub build_from_clause_and_joins {
my ($query, $core, $and_terms, $or_terms) = @_;
$and_terms = prepare_terms($and_terms, 1);
$or_terms = prepare_terms($or_terms, 0);
- my $offset = add_au_joins($graft_map, $hint, prepare_au_terms($and_terms));
- add_au_joins($graft_map, $hint, prepare_au_terms($or_terms, $offset));
+ unless ($options->{au_by_id}) {
+ my $offset = add_au_joins($graft_map, $hint, prepare_au_terms($and_terms));
+ add_au_joins($graft_map, $hint, prepare_au_terms($or_terms, $offset));
+ }
+
+ my $offset = add_acqpro_joins($graft_map, $hint, prepare_acqpro_terms($and_terms));
+ add_acqpro_joins($graft_map, $hint, prepare_acqpro_terms($or_terms, $offset));
# The join to acqmapinv needs to be a left join when present.
if ($query->{from}{$hint}{acqmapinv}) {