Improved boolean queries by sharing optimization code
diff --git a/lib/Krawfish/Koral/Corpus.pm b/lib/Krawfish/Koral/Corpus.pm
index d1fcd7b..d5ca75e 100644
--- a/lib/Krawfish/Koral/Corpus.pm
+++ b/lib/Krawfish/Koral/Corpus.pm
@@ -80,7 +80,7 @@
print_log('kq_corpus', 'Do an "andNot" on any') if DEBUG;
- return $self->builder->field_and_not(
+ return $self->builder->bool_and_not(
$self->builder->any,
$self
);
@@ -88,7 +88,7 @@
print_log('kq_corpus', 'Do an "and" on any') if DEBUG;
- return $self->builder->field_and(
+ return $self->builder->bool_and(
$self->builder->any,
$self
);
diff --git a/lib/Krawfish/Koral/Corpus/Builder.pm b/lib/Krawfish/Koral/Corpus/Builder.pm
index 0de264f..ea84ecf 100644
--- a/lib/Krawfish/Koral/Corpus/Builder.pm
+++ b/lib/Krawfish/Koral/Corpus/Builder.pm
@@ -14,20 +14,20 @@
};
# Create 'and' group
-sub field_and {
+sub bool_and {
shift;
return Krawfish::Koral::Corpus::FieldGroup->new('and', @_);
};
# Create 'or' group
-sub field_or {
+sub bool_or {
shift;
return Krawfish::Koral::Corpus::FieldGroup->new('or', @_);
};
# Create 'and' group
-sub field_and_not {
+sub bool_and_not {
shift;
return Krawfish::Koral::Corpus::AndNot->new(@_);
};
@@ -57,18 +57,21 @@
};
# Create 'string' field
+# May be renamed to 'field'
sub string {
shift;
return Krawfish::Koral::Corpus::Field->new('string', @_);
};
# Create 'date' field
+# May be renamed to 'field_date'
sub date {
shift;
return Krawfish::Koral::Corpus::Field->new('date', @_);
};
-# Create 'integer' field
+# Create 'regex' field
+# May be renamed to 'field_re'
sub regex {
shift;
return Krawfish::Koral::Corpus::Field->new('regex', @_);
diff --git a/lib/Krawfish/Koral/Corpus/FieldGroup.pm b/lib/Krawfish/Koral/Corpus/FieldGroup.pm
index 3eaebf9..975854e 100644
--- a/lib/Krawfish/Koral/Corpus/FieldGroup.pm
+++ b/lib/Krawfish/Koral/Corpus/FieldGroup.pm
@@ -49,36 +49,6 @@
};
-sub new_or {
- shift;
- __PACKAGE__->new('or',@_);
-};
-
-
-sub new_and {
- shift;
- __PACKAGE__->new('and', @_);
-};
-
-
-# Build AndNot group
-sub new_and_not {
- shift;
- Krawfish::Koral::Corpus::AndNot->new(@_);
-};
-
-
-sub new_any {
- shift;
- Krawfish::Koral::Corpus::Any->new;
-
- # TODO: May as well be
- # my $any = Krawfish::Koral::Corpus::FieldGroup->new;
- # $any->is_any(1);
- # return $any;
-};
-
-
sub operands {
my $self = shift;
if (@_) {
@@ -89,96 +59,26 @@
};
-# Create operands in order
-sub operands_in_order {
+# normalize() is provided by Boolean
+
+# optimize() is provided by Boolean
+
+sub bool_and_query {
my $self = shift;
- my $ops = $self->{operands};
- return [ sort { ($a && $b) ? ($a->to_string cmp $b->to_string) : 1 } @$ops ];
+ Krawfish::Corpus::And->new(
+ $_[0],
+ $_[1]
+ );
};
-
-# normalize() is provided by BooleanTree
-
-# Optimize for an index
-sub optimize {
- my ($self, $index) = @_;
-
- # Get operands in alphabetical order
- my $ops = $self->operands_in_order;
-
- # Check the frequency of all operands
- # Start with a query != null
- my $i = 0;
- my $first = $ops->[$i];
-
- print_log('kq_fgroup', 'Initial query is ' . $self->to_string) if DEBUG;
-
- my $query = $first->optimize($index);
- $i++;
-
- # Check unless
- while ($query->max_freq == 0 && $i < @$ops) {
- $first = $ops->[$i++];
- $query = $first->optimize($index);
- $i++;
- };
-
- if ($self->operation eq 'or') {
- print_log('kq_fgroup', 'Prepare or-group') if DEBUG;
-
- # Filter out all terms that do not occur
- for (; $i < @$ops; $i++) {
-
- # Get query operation for next operand
- # TODO: Check for negation!
- my $next = $ops->[$i]->optimize($index);
-
- if ($next->max_freq != 0) {
-
- # TODO: Distinguish here between classes and non-classes!
- $query = Krawfish::Corpus::Or->new(
- $query,
- $next
- );
- };
- };
- }
- elsif ($self->operation eq 'and') {
- print_log('kq_fgroup', 'Prepare and-group') if DEBUG;
-
- # Filter out all terms that do not occur
- for (; $i < @$ops; $i++) {
-
- # Get query operation for next operand
- my $next = $ops->[$i]->optimize($index);
-
- if ($next->max_freq != 0) {
-
- # TODO: Distinguish here between classes and non-classes!
- $query = Krawfish::Corpus::And->new(
- $query,
- $next
- );
- }
-
- # One operand is not existing
- else {
- return Krawfish::Query::Nothing->new;
- };
- };
- }
- else {
- warn 'Should never happen!';
- };
-
- if ($query->max_freq == 0) {
- return Krawfish::Query::Nothing->new;
- };
-
- return $query;
+sub bool_or_query {
+ my $self = shift;
+ Krawfish::Corpus::Or->new(
+ $_[0],
+ $_[1]
+ );
};
-
#sub is_any {
# my $self = shift;
# return 0 if $self->is_nothing;
diff --git a/lib/Krawfish/Koral/Query/Builder.pm b/lib/Krawfish/Koral/Query/Builder.pm
index 84da940..876e11e 100644
--- a/lib/Krawfish/Koral/Query/Builder.pm
+++ b/lib/Krawfish/Koral/Query/Builder.pm
@@ -22,6 +22,8 @@
use Krawfish::Koral::Query::Constraint::NotBetween;
use Krawfish::Koral::Query::Constraint::InBetween;
+use Scalar::Util qw/blessed/;
+
sub new {
my $class = shift;
my $text_span = shift // 'base/s=t';
@@ -54,31 +56,49 @@
Krawfish::Koral::Query::Token->new(@_);
};
-sub term_re {
- shift;
- Krawfish::Koral::Query::Term->new(@_)->match('~');
-}
-
sub term {
shift;
Krawfish::Koral::Query::Term->new(@_);
};
-sub term_and {
- shift;
- Krawfish::Koral::Query::TermGroup->new('and' => @_);
-};
-
-sub term_or {
- shift;
- Krawfish::Koral::Query::TermGroup->new('or' => @_);
-};
-
sub term_neg {
shift;
Krawfish::Koral::Query::Term->new(@_)->match('!=');
};
+sub term_re {
+ shift;
+ Krawfish::Koral::Query::Term->new(@_)->match('~');
+};
+
+
+sub bool_and {
+ shift;
+ Krawfish::Koral::Query::TermGroup->new('and' => @_);
+};
+
+sub bool_and_not {
+ shift;
+ my ($pos, $neg) = @_;
+ Krawfish::Koral::Query::Exclusion->new(['matches'], $pos, $neg);
+};
+
+
+sub bool_or {
+ my $self = shift;
+ my $first_type = blessed $_[0] ? $_[0]->type : 'term';
+ my $second_type = blessed $_[1] ? $_[1]->type : 'term';
+ if (
+ ($first_type eq 'term' || $first_type eq 'termGroup') &&
+ ($second_type eq 'term' || $second_type eq 'termGroup')
+ ) {
+ return Krawfish::Koral::Query::TermGroup->new('or' => @_);
+ };
+
+ return Krawfish::Koral::Query::Or->new(@_);
+};
+
+
# Span construct
sub span {
shift;
@@ -86,13 +106,6 @@
};
-# Or on spans
-sub span_or {
- shift;
- Krawfish::Koral::Query::Or->new(@_);
-};
-
-
# Create an in-text construct
sub in_text {
my $self = shift;
diff --git a/lib/Krawfish/Koral/Query/Or.pm b/lib/Krawfish/Koral/Query/Or.pm
index fd8a153..0fbf238 100644
--- a/lib/Krawfish/Koral/Query/Or.pm
+++ b/lib/Krawfish/Koral/Query/Or.pm
@@ -24,58 +24,20 @@
'or'
};
-
-# Optimize Or-operand sequence
-sub optimize {
- my ($self, $index) = @_;
-
- # Get operands in alphabetical order
- my $ops = $self->operands_in_order;
-
- my $i = 0;
- my $first = $ops->[$i];
-
- print_log('kq_or', 'Initial query is ' . $self->to_string) if DEBUG;
-
- my $query = $first->optimize($index);
- $i++;
-
- # Check to get a valid first query
- while ($query->max_freq == 0 && $i < @$ops) {
- $first = $ops->[$i++];
- $query = $first->optimize($index);
- $i++;
- };
-
- for (; $i < @$ops; $i++) {
- # Get query operation for next operand
- # TODO: Check for negation!
- my $next = $ops->[$i]->optimize($index);
-
- if ($next->max_freq != 0) {
- $query = Krawfish::Query::Or->new(
- $query,
- $next
- );
- };
- };
-
- if ($query->max_freq == 0) {
- return Krawfish::Query::Nothing->new;
- };
-
- return $query;
-};
-
-
-# Create operands in order
-sub operands_in_order {
+sub bool_or_query {
my $self = shift;
- my $ops = $self->{operands};
- return [ sort { $a->to_string cmp $b->to_string } @$ops ];
+ Krawfish::Query::Or->new(
+ $_[0],
+ $_[1]
+ );
};
+# Can't occur per definition
+sub bool_and_query {
+ return;
+};
+
# Stringification
sub to_string {
my $self = shift;
diff --git a/lib/Krawfish/Koral/Query/Term.pm b/lib/Krawfish/Koral/Query/Term.pm
index 4444b68..fa89873 100644
--- a/lib/Krawfish/Koral/Query/Term.pm
+++ b/lib/Krawfish/Koral/Query/Term.pm
@@ -344,7 +344,7 @@
# TODO:
# Use refer?
- return $self->builder->term_or(@terms)->normalize;
+ return $self->builder->bool_or(@terms)->normalize;
};
diff --git a/lib/Krawfish/Koral/Query/TermGroup.pm b/lib/Krawfish/Koral/Query/TermGroup.pm
index 2aebf42..c55c55b 100644
--- a/lib/Krawfish/Koral/Query/TermGroup.pm
+++ b/lib/Krawfish/Koral/Query/TermGroup.pm
@@ -17,88 +17,6 @@
# -> memoize(cache)
# -> optimize(index)
-use constant DEBUG => 1;
-
-sub new {
- my $class = shift;
- my $operation = shift;
-
- # Make all operands, terms
- my @operands = map {
- blessed $_ ? $_ : Krawfish::Koral::Query::Term->new($_)
- } @_;
-
- bless {
- operation => $operation,
- operands => [@operands]
- }
-};
-
-
-# Query type
-sub type {
- 'termGroup'
-};
-
-
-# Build helper for or-relations
-sub new_or {
- shift;
- __PACKAGE__->new('or',@_);
-};
-
-
-# Build helper for and-relations
-sub new_and {
- shift;
- __PACKAGE__->new('and', @_);
-};
-
-
-# Build helper for andNot-relations
-sub new_and_not {
- my ($self, $pos, $neg) = @_;
- my $query = $self->builder->exclusion(['matches'], $pos, $neg);
- print_log('kq_tgroup', 'Create andNot: ' . $query->to_string) if DEBUG;
- $query;
-};
-
-
-# Build helper for any match
-sub new_any {
- shift;
- my $any = Krawfish::Koral::Query::TermGroup->new;
- $any->is_any(1);
- return $any;
-};
-
-
-# Get or set the group operation
-sub operation {
- my $self = shift;
- if (@_) {
- $self->{operation} = shift;
- return $self;
- };
- $self->{operation};
-};
-
-
-# There are no classes allowed in term groups
-sub remove_classes {
- $_[0];
-};
-
-
-# Create operands in order
-sub operands_in_order {
- my $self = shift;
- my $ops = $self->{operands};
- return [ sort { $a->to_string cmp $b->to_string } @$ops ];
-};
-
-
-
# TODO: Flatten or groups in a first pass!
# TODO: In case, the group is 'and' and there is at
@@ -125,7 +43,6 @@
# )
# )
-
# TODO:
# IMPORTANT:
# for example in an annotation like
@@ -144,105 +61,66 @@
# so - if classes in tokens are needed, they need to be reformulated
# as token-groups, e.g.
# {1:[marmot/m=case:dat]}|{2:[marmot/m=gender:masc]}|{3:[marmot/m=number:sg]}
-#
-# This is rather identical to FieldGroup
-sub optimize {
- my ($self, $index) = @_;
- # Get operands
- my $ops = $self->operands;
+use constant DEBUG => 1;
- # Check the frequency of all operands
+sub new {
+ my $class = shift;
+ my $operation = shift;
- my @freq;
- my $query;
+ # Make all operands, terms
+ my @operands = map {
+ blessed $_ ? $_ : Krawfish::Koral::Query::Term->new($_)
+ } @_;
- # Filter out all terms that do not occur
- for (my $i = 0; $i < @$ops; $i++) {
-
- # Get query operation for next operand
- my $next = $ops->[$i]->optimize($index);
-
- # Get maximum frequency
- my $freq = $next->max_freq;
-
- # Push to frequency list
- push @freq, [$next, $freq];
- };
-
- # Sort operands based on ascending frequency
- @freq = sort {
- ($a->[1] < $b->[1]) ? -1 : (($a->[1] > $b->[1]) ? 1 : ($a->[0]->to_string cmp $b->[0]->to_string))
- } @freq;
-
- if ($self->operation eq 'or') {
- print_log('kq_tgroup', 'Prepare or-group') if DEBUG;
-
- # Ignore non-existing terms
- while (@freq && $freq[0]->[1] == 0) {
- shift @freq;
- };
-
- # No valid operands exist
- if (@freq == 0) {
- return Krawfish::Query::Nothing->new;
- };
-
- # Get the first operand
- $query = shift(@freq)->[0];
-
- # For all further queries, create a query tree
- while (@freq) {
- my $next = shift(@freq)->[0];
-
- # TODO: Distinguish here between classes and non-classes!
- $query = Krawfish::Query::Or->new(
- $query,
- $next
- );
- };
+ bless {
+ operation => $operation,
+ operands => [@operands]
}
-
- elsif ($self->operation eq 'and') {
- print_log('kq_tgroup', 'Prepare and-group') if DEBUG;
-
- # If the least frequent operand does not exist,
- # the whole group can't exist
- if ($freq[0]->[1] == 0) {
-
- # One operand is not existing
- return Krawfish::Query::Nothing->new;
- };
-
- # Get the first operand
- $query = shift(@freq)->[0];
-
- # Make the least frequent terms come first in constraint
- while (@freq) {
- my $next = shift(@freq)->[0];
-
- # Create constraint with the least frequent as second (buffered) operand
- $query = Krawfish::Query::Constraints->new(
- [Krawfish::Query::Constraint::Position->new(MATCHES)],
- $next,
- $query
- );
- };
- }
-
- else {
- warn 'Should never happen!';
- };
-
- if ($query->max_freq == 0) {
- return Krawfish::Query::Nothing->new;
- };
-
- return $query;
};
+# Query type
+sub type {
+ 'termGroup'
+};
+
+
+# Get or set the group operation
+sub operation {
+ my $self = shift;
+ if (@_) {
+ $self->{operation} = shift;
+ return $self;
+ };
+ $self->{operation};
+};
+
+
+# There are no classes allowed in term groups
+sub remove_classes {
+ $_[0];
+};
+
+
+sub bool_and_query {
+ my $self = shift;
+ Krawfish::Query::Constraints->new(
+ [Krawfish::Query::Constraint::Position->new(MATCHES)],
+ $_[0],
+ $_[1]
+ );
+};
+
+sub bool_or_query {
+ my $self = shift;
+ Krawfish::Query::Or->new(
+ $_[0],
+ $_[1]
+ );
+};
+
sub maybe_unsorted { 0 };
#sub is_any {
diff --git a/lib/Krawfish/Koral/Util/Boolean.pm b/lib/Krawfish/Koral/Util/Boolean.pm
index e912156..550b6c6 100644
--- a/lib/Krawfish/Koral/Util/Boolean.pm
+++ b/lib/Krawfish/Koral/Util/Boolean.pm
@@ -4,30 +4,27 @@
use strict;
use warnings;
-# This can be used by Koral::FieldGroup and Koral::TermGroup
+# Base class for boolean group queries.
+# Used by Koral::Corpus::FieldGroup, Koral::Query::TermGroup, and Koral::Query::Or
use constant DEBUG => 0;
# TODO:
-# Probably use builder->any etc. to trim codebase.
-
-# TODO:
-# Change andNot([1],X) to not(x),
-# so this can be used for tokenGroups without a change
-
-# TODO:
# To simplify this, it may be useful to use Negation instead of is_negative().
# This means, fields with "ne" won't be "ne"-fields, but become not(term).
# It's also easier to detect double negation.
# TODO:
+# Let normalize return a cloned query instead of in-place creation
+
+# TODO:
# - Deal with classes:
# (A | !A) -> 1, aber ({1:A} | {2:!A}) -> ({1:A} | {2:!A})
# (A & !A) -> 0, und ({1:A} & {2:!A}) -> 0
-# Check https://de.wikipedia.org/wiki/Boolesche_Algebra
-# for optimizations
# TODO:
+# Check https://de.wikipedia.org/wiki/Boolesche_Algebra
+# for optimizations
# or(and(a,b),and(a,c)) -> and(a,or(b,c))
# and(or(a,b),or(a,c)) -> or(a,and(b,c))
# not(not(a)) -> a
@@ -42,7 +39,7 @@
# from managing gigabytes bool_optimiser.c
#/* =========================================================================
# * Function: OptimiseBoolTree
-# * Description:
+# * Description:
# * For case 2:
# * Do three major steps:
# * (i) put into standard form
@@ -54,73 +51,70 @@
# * convert &! to diff nodes, order terms by frequency,...
# * Could also do the matching idempotency laws i.e. ...
# * (A | A), (A | !A), (A & !A), (A & A), (A & (A | B)), (A | (A & B))
-# * Job for future.... ;-)
-# * Input:
-# * Output:
+# * Job for future.... ;-)
# * ========================================================================= */
#/* =========================================================================
# * Function: DoubleNeg
-# * Description:
+# * Description:
# * !(!(a) = a
# * Assumes binary tree.
-# * Input:
-# * Output:
+# * Input:
+# * Output:
# * ========================================================================= */
#/* =========================================================================
# * Function: AndDeMorgan
-# * Description:
+# * Description:
# * DeMorgan's rule for 'not' of an 'and' i.e. !(a & b) <=> (!a) | (!b)
# * Assumes Binary Tree
-# * Input:
+# * Input:
# * not of and tree
-# * Output:
+# * Output:
# * or of not trees
# * ========================================================================= */
#/* =========================================================================
# * Function: OrDeMorgan
-# * Description:
+# * Description:
# * DeMorgan's rule for 'not' of an 'or' i.e. !(a | b) <=> (!a) & (!b)
# * Assumes Binary Tree
-# * Input:
+# * Input:
# * not of and tree
-# * Output:
+# * Output:
# * or of not trees
# * ========================================================================= */
#/* =========================================================================
# * Function: PermeateNots
-# * Description:
+# * Description:
# * Use DeMorgan's and Double-negative
# * Assumes tree in binary form (i.e. No ands/ors collapsed)
-# * Input:
-# * Output:
+# * Input:
+# * Output:
# * ========================================================================= */
#/* =========================================================================
# * Function: AndDistribute
-# * Description:
+# * Description:
# * (a | b) & A <=> (a & A) | (b & A)
-# * Input:
+# * Input:
# * binary tree of "AND" , "OR"s.
-# * Output:
+# * Output:
# * return 1 if changed the tree
# * return 0 if there was NO change (no distributive rule to apply)
# * ========================================================================= */
#/* =========================================================================
#/* =========================================================================
# * Function: AndSort
-# * Description:
+# * Description:
# * Sort the list of nodes by increasing doc_count
# * Using some Neil Sharman code - pretty straight forward.
# * Note: not-terms are sent to the end of the list
-# * Input:
-# * Output:
+# * Input:
+# * Output:
# * ========================================================================= */
# From managing gigabytes bool_optimiser.c
# - function: TF_Idempotent -> DONE
-# TODO:
-# This should return a cloned query instead of in-place creation
+# Normalize boolean query
sub normalize {
my $self = shift;
@@ -572,13 +566,13 @@
# Get reverted DeMorgan group
if ($self->operation eq 'and') {
- $new_group = $self->new_or(@new_group);
+ $new_group = $self->builder->bool_or(@new_group);
# Create an andNot group in the next step
}
# For 'or' operation
else {
- $new_group = $self->new_and(@new_group);
+ $new_group = $self->builder->bool_and(@new_group);
};
# Set group to negative
@@ -635,8 +629,8 @@
# return !a -> andNot(any,a)
if ($self->is_negative) {
$self->is_negative(0);
- return $self->new_and_not(
- $self->new_any,
+ return $self->builder->bool_and_not(
+ $self->builder->any,
$self
)->normalize;
};
@@ -663,7 +657,9 @@
# Switch negativity
$neg->is_negative(0);
- print_log('kq_bool', 'Negative operand is removed and reversed: ' . $neg->to_string) if DEBUG;
+ if (DEBUG) {
+ print_log('kq_bool', 'Negative operand is removed and reversed: ' . $neg->to_string);
+ };
# Deal with operations differently
if ($self->operation eq 'and') {
@@ -674,7 +670,7 @@
if (@$ops == 1) {
print_log('kq_bool', 'Operation on a single operand') if DEBUG;
- my $and_not = $self->new_and_not($ops->[0], $neg)->normalize;
+ my $and_not = $self->builder->bool_and_not($ops->[0], $neg)->normalize;
print_log('kq_bool', 'Created ' . $and_not->to_string) if DEBUG;
return $and_not;
@@ -683,15 +679,15 @@
print_log('kq_bool', 'Operation on multiple operands') if DEBUG;
# There are multiple positive operands - create a new group
- return $self->new_and_not($self, $neg)->normalize;
+ return $self->builder->bool_and_not($self, $neg)->normalize;
}
elsif ($self->operation eq 'or') {
print_log('kq_bool', 'Operation is "or"') if DEBUG;
- push @$ops, $self->new_and_not(
- $self->new_any,
+ push @$ops, $self->builder->bool_and_not(
+ $self->builder->any,
$neg
)->normalize;
return $self;
@@ -701,6 +697,104 @@
};
+# Optimize boolean queries based on their frequencies
+sub optimize {
+ my ($self, $index) = @_;
+
+ # Get operands
+ my $ops = $self->operands;
+
+ # Check the frequency of all operands
+ my (@freq, $query);
+
+ # Filter out all terms that do not occur
+ for (my $i = 0; $i < @$ops; $i++) {
+
+ # Get query operation for next operand
+ my $next = $ops->[$i]->optimize($index);
+
+ # Get maximum frequency
+ my $freq = $next->max_freq;
+
+ # Push to frequency list
+ push @freq, [$next, $freq];
+ };
+
+ # Sort operands based on ascending frequency
+ @freq = sort {
+ ($a->[1] < $b->[1]) ? -1 :
+ (($a->[1] > $b->[1]) ? 1 :
+ ($a->[0]->to_string cmp $b->[0]->to_string))
+ } @freq;
+
+
+ # Operation is 'or'
+ if ($self->operation eq 'or') {
+ print_log('kq_bool', 'Prepare or-group') if DEBUG;
+
+ # Ignore non-existing terms
+ while (@freq && $freq[0]->[1] == 0) {
+ shift @freq;
+ };
+
+ # No valid operands exist
+ if (@freq == 0) {
+ return Krawfish::Query::Nothing->new;
+ };
+
+ # Get the first operand
+ $query = shift(@freq)->[0];
+
+ # For all further queries, create a query tree
+ while (@freq) {
+ my $next = shift(@freq)->[0];
+
+ # TODO: Distinguish here between classes and non-classes!
+ $query = $self->bool_or_query(
+ $query,
+ $next
+ );
+ };
+ }
+
+ # Operation is 'and'
+ elsif ($self->operation eq 'and') {
+ print_log('kq_bool', 'Prepare and-group') if DEBUG;
+
+ # If the least frequent operand does not exist,
+ # the whole group can't exist
+ if ($freq[0]->[1] == 0) {
+
+ # One operand is not existing
+ return Krawfish::Query::Nothing->new;
+ };
+
+ # Get the first operand
+ $query = shift(@freq)->[0];
+
+ # Make the least frequent terms come first in constraint
+ while (@freq) {
+ my $next = shift(@freq)->[0];
+
+ # Create constraint with the least frequent as second (buffered) operand
+ $query = $self->bool_and_query($next, $query);
+ };
+ }
+
+ # Operation is unknown!
+ else {
+ warn 'Should never happen!';
+ };
+
+ # Return nothing if nothing matches!
+ if ($query->max_freq == 0) {
+ return Krawfish::Query::Nothing->new;
+ };
+
+ return $query;
+};
+
+
# Toggle the operation
sub toggle_operation {
my $self = shift;
@@ -713,6 +807,14 @@
};
+# Create operands in order
+sub operands_in_order {
+ my $self = shift;
+ my $ops = $self->{operands};
+ return [ sort { ($a && $b) ? ($a->to_string cmp $b->to_string) : 1 } @$ops ];
+};
+
+
1;