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;