View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.xdbm.search.impl;
21  
22  
23  import java.io.IOException;
24  import java.util.List;
25  import java.util.Set;
26  import java.util.regex.Pattern;
27  
28  import org.apache.directory.api.ldap.model.cursor.Cursor;
29  import org.apache.directory.api.ldap.model.cursor.CursorException;
30  import org.apache.directory.api.ldap.model.entry.Value;
31  import org.apache.directory.api.ldap.model.exception.LdapException;
32  import org.apache.directory.api.ldap.model.exception.LdapOtherException;
33  import org.apache.directory.api.ldap.model.filter.AndNode;
34  import org.apache.directory.api.ldap.model.filter.ApproximateNode;
35  import org.apache.directory.api.ldap.model.filter.EqualityNode;
36  import org.apache.directory.api.ldap.model.filter.ExprNode;
37  import org.apache.directory.api.ldap.model.filter.GreaterEqNode;
38  import org.apache.directory.api.ldap.model.filter.LessEqNode;
39  import org.apache.directory.api.ldap.model.filter.NotNode;
40  import org.apache.directory.api.ldap.model.filter.OrNode;
41  import org.apache.directory.api.ldap.model.filter.PresenceNode;
42  import org.apache.directory.api.ldap.model.filter.ScopeNode;
43  import org.apache.directory.api.ldap.model.filter.SubstringNode;
44  import org.apache.directory.api.ldap.model.message.SearchScope;
45  import org.apache.directory.api.ldap.model.name.Dn;
46  import org.apache.directory.api.ldap.model.name.Rdn;
47  import org.apache.directory.api.ldap.model.schema.AttributeType;
48  import org.apache.directory.api.ldap.model.schema.MatchingRule;
49  import org.apache.directory.api.ldap.model.schema.Normalizer;
50  import org.apache.directory.api.ldap.model.schema.PrepareString;
51  import org.apache.directory.api.ldap.model.schema.normalizers.NoOpNormalizer;
52  import org.apache.directory.api.util.exception.NotImplementedException;
53  import org.apache.directory.server.core.api.partition.Partition;
54  import org.apache.directory.server.core.api.partition.PartitionTxn;
55  import org.apache.directory.server.i18n.I18n;
56  import org.apache.directory.server.xdbm.Index;
57  import org.apache.directory.server.xdbm.IndexEntry;
58  import org.apache.directory.server.xdbm.IndexNotFoundException;
59  import org.apache.directory.server.xdbm.ParentIdAndRdn;
60  import org.apache.directory.server.xdbm.SingletonIndexCursor;
61  import org.apache.directory.server.xdbm.Store;
62  import org.apache.directory.server.xdbm.search.PartitionSearchResult;
63  import org.apache.directory.server.xdbm.search.cursor.ApproximateCursor;
64  import org.apache.directory.server.xdbm.search.cursor.ChildrenCursor;
65  import org.apache.directory.server.xdbm.search.cursor.DescendantCursor;
66  import org.apache.directory.server.xdbm.search.evaluator.ApproximateEvaluator;
67  
68  
69  /**
70   * Builds Cursors over candidates that satisfy a filter expression.
71   * 
72   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
73   */
74  public class CursorBuilder
75  {
76      /** The database used by this builder */
77      private Store db = null;
78  
79      /** Evaluator dependency on a EvaluatorBuilder */
80      private EvaluatorBuilder evaluatorBuilder;
81  
82  
83      /**
84       * Creates an expression tree enumerator.
85       *
86       * @param db database used by this enumerator
87       * @param evaluatorBuilder the evaluator builder
88       */
89      public CursorBuilder( Store db, EvaluatorBuilder evaluatorBuilder )
90      {
91          this.db = db;
92          this.evaluatorBuilder = evaluatorBuilder;
93      }
94  
95  
96      public <T> long build( PartitionTxn partitionTxn, ExprNode node, PartitionSearchResult searchResult ) throws LdapException
97      {
98          Object count = node.get( DefaultOptimizer.COUNT_ANNOTATION );
99  
100         if ( ( count != null ) && ( ( Long ) count ) == 0L )
101         {
102             return 0;
103         }
104 
105         try
106         {
107             switch ( node.getAssertionType() )
108             {
109             /* ---------- LEAF NODE HANDLING ---------- */
110     
111                 case APPROXIMATE:
112                     return computeApproximate( partitionTxn, ( ApproximateNode<T> ) node, searchResult );
113     
114                 case EQUALITY:
115                     return computeEquality( partitionTxn, ( EqualityNode<T> ) node, searchResult );
116     
117                 case GREATEREQ:
118                     return computeGreaterEq( partitionTxn, ( GreaterEqNode<T> ) node, searchResult );
119     
120                 case LESSEQ:
121                     return computeLessEq( partitionTxn, ( LessEqNode<T> ) node, searchResult );
122     
123                 case PRESENCE:
124                     return computePresence( partitionTxn, ( PresenceNode ) node, searchResult );
125     
126                 case SCOPE:
127                     if ( ( ( ScopeNode ) node ).getScope() == SearchScope.ONELEVEL )
128                     {
129                         return computeOneLevelScope( partitionTxn, ( ScopeNode ) node, searchResult );
130                     }
131                     else
132                     {
133                         return computeSubLevelScope( partitionTxn, ( ScopeNode ) node, searchResult );
134                     }
135     
136                 case SUBSTRING:
137                     return computeSubstring( partitionTxn, ( SubstringNode ) node, searchResult );
138     
139                     /* ---------- LOGICAL OPERATORS ---------- */
140     
141                 case AND:
142                     return computeAnd( partitionTxn, ( AndNode ) node, searchResult );
143     
144                 case NOT:
145                     // Always return infinite, except if the resulting eva 
146                     return computeNot( ( NotNode ) node, searchResult );
147     
148                 case OR:
149                     return computeOr( partitionTxn, ( OrNode ) node, searchResult );
150     
151                     /* ----------  NOT IMPLEMENTED  ---------- */
152     
153                 case ASSERTION:
154                 case EXTENSIBLE:
155                     throw new NotImplementedException();
156     
157                 default:
158                     throw new IllegalStateException( I18n.err( I18n.ERR_260, node.getAssertionType() ) );
159             }
160         }
161         catch ( IndexNotFoundException | CursorException | IOException e )
162         {
163             throw new LdapOtherException( e.getMessage(), e );
164         }
165     }
166 
167 
168     /**
169      * Computes the set of candidates for an Approximate filter. We will feed the set only if
170      * we have an index for the AT.
171      */
172 
173     private <T> long computeApproximate( PartitionTxn partitionTxn, ApproximateNode<T> node, PartitionSearchResult searchResult )
174         throws LdapException, IndexNotFoundException, CursorException, IOException
175     {
176         ApproximateCursor<T> cursor = new ApproximateCursor<>( partitionTxn, db,
177             ( ApproximateEvaluator<T> ) evaluatorBuilder
178                 .build( partitionTxn, node ) );
179 
180         int nbResults = 0;
181         Set<String> uuidSet = searchResult.getCandidateSet();
182 
183         while ( cursor.next() )
184         {
185             IndexEntry<T, String> indexEntry = cursor.get();
186 
187             String uuid = indexEntry.getId();
188             boolean added = uuidSet.add( uuid );
189 
190             // if the UUID was added increment the result count
191             if ( added )
192             {
193                 nbResults++;
194             }
195         }
196 
197         cursor.close();
198 
199         return nbResults;
200     }
201 
202 
203     /**
204      * Computes the set of candidates for an Equality filter. We will feed the set only if
205      * we have an index for the AT.
206      */
207     private <T> long computeEquality( PartitionTxn partitionTxn, EqualityNode<T> node, PartitionSearchResult searchResult )
208         throws LdapException, IndexNotFoundException, CursorException, IOException
209     {
210         Set<String> thisCandidates = ( Set<String> ) node.get( DefaultOptimizer.CANDIDATES_ANNOTATION_KEY );
211 
212         if ( thisCandidates != null )
213         {
214             Set<String> candidates = searchResult.getCandidateSet();
215 
216             for ( String candidate : thisCandidates )
217             {
218                 candidates.add( candidate );
219             }
220 
221             return thisCandidates.size();
222         }
223 
224         AttributeType attributeType = node.getAttributeType();
225         Value value = node.getValue();
226         int nbResults = 0;
227 
228         // Fetch all the UUIDs if we have an index
229         if ( db.hasIndexOn( attributeType ) )
230         {
231             // Get the cursor using the index
232             Index<T, String> userIndex = ( Index<T, String> ) db.getIndex( attributeType );
233             Cursor<IndexEntry<T, String>> userIdxCursor = userIndex.forwardCursor( partitionTxn, ( T ) value.getNormalized() );
234             Set<String> uuidSet = searchResult.getCandidateSet();
235 
236             // And loop on it
237             while ( userIdxCursor.next() )
238             {
239                 IndexEntry<T, String> indexEntry = userIdxCursor.get();
240 
241                 String uuid = indexEntry.getId();
242                 boolean added = uuidSet.add( uuid );
243                 
244                 // if the UUID was added increment the result count
245                 if ( added )
246                 {
247                     nbResults++;
248                 }
249             }
250 
251             userIdxCursor.close();
252         }
253         else
254         {
255             // No index, we will have to do a full scan
256             return Long.MAX_VALUE;
257         }
258 
259         return nbResults;
260     }
261 
262 
263     /**
264      * Computes the set of candidates for an GreateEq filter. We will feed the set only if
265      * we have an index for the AT.
266      */
267     private <T> long computeGreaterEq( PartitionTxn partitionTxn, GreaterEqNode<T> node, PartitionSearchResult searchResult )
268         throws LdapException, IndexNotFoundException, CursorException, IOException
269     {
270         AttributeType attributeType = node.getAttributeType();
271         Value value = node.getValue();
272         int nbResults = 0;
273 
274         // Fetch all the UUIDs if we have an index
275         if ( db.hasIndexOn( attributeType ) )
276         {
277             // Get the cursor using the index
278             Index<T, String> userIndex = ( Index<T, String> ) db.getIndex( attributeType );
279             Cursor<IndexEntry<T, String>> userIdxCursor = userIndex.forwardCursor( partitionTxn );
280 
281             // Position the index on the element we should start from
282             IndexEntry<T, String> indexEntry = new IndexEntry<>();
283             indexEntry.setKey( ( T ) value.getString() );
284 
285             userIdxCursor.before( indexEntry );
286             Set<String> uuidSet = searchResult.getCandidateSet();
287 
288             // And loop on it
289             while ( userIdxCursor.next() )
290             {
291                 indexEntry = userIdxCursor.get();
292 
293                 String uuid = indexEntry.getId();
294                 boolean added = uuidSet.add( uuid );
295 
296                 // if the UUID was added increment the result count
297                 if ( added )
298                 {
299                     nbResults++;
300                 }
301             }
302 
303             userIdxCursor.close();
304         }
305         else
306         {
307             // No index, we will have to do a full scan
308             return Long.MAX_VALUE;
309         }
310 
311         return nbResults;
312     }
313 
314 
315     /**
316      * Computes the set of candidates for an LessEq filter. We will feed the set only if
317      * we have an index for the AT.
318      */
319     private <T> long computeLessEq( PartitionTxn partitionTxn, LessEqNode<T> node, PartitionSearchResult searchResult )
320         throws LdapException, IndexNotFoundException, CursorException, IOException
321     {
322         AttributeType attributeType = node.getAttributeType();
323         Value value = node.getValue();
324         int nbResults = 0;
325 
326         // Fetch all the UUIDs if we have an index
327         if ( db.hasIndexOn( attributeType ) )
328         {
329             // Get the cursor using the index
330             Index<T, String> userIndex = ( Index<T, String> ) db.getIndex( attributeType );
331             Cursor<IndexEntry<T, String>> userIdxCursor = userIndex.forwardCursor( partitionTxn );
332 
333             // Position the index on the element we should start from
334             IndexEntry<T, String> indexEntry = new IndexEntry<>();
335             indexEntry.setKey( ( T ) value.getString() );
336 
337             userIdxCursor.after( indexEntry );
338             Set<String> uuidSet = searchResult.getCandidateSet();
339 
340             // And loop on it
341             while ( userIdxCursor.previous() )
342             {
343                 indexEntry = userIdxCursor.get();
344 
345                 String uuid = indexEntry.getId();
346                 boolean added = uuidSet.add( uuid );
347 
348                 // if the UUID was added increment the result count
349                 if ( added )
350                 {
351                     nbResults++;
352                 }
353             }
354 
355             userIdxCursor.close();
356         }
357         else
358         {
359             // No index, we will have to do a full scan
360             return Long.MAX_VALUE;
361         }
362 
363         return nbResults;
364     }
365 
366 
367     /**
368      * Computes the set of candidates for a Presence filter. We will feed the set only if
369      * we have an index for the AT.
370      */
371     private long computePresence( PartitionTxn partitionTxn, PresenceNode node, PartitionSearchResult searchResult )
372         throws LdapException, CursorException, IOException
373     {
374         AttributeType attributeType = node.getAttributeType();
375         int nbResults = 0;
376 
377         // Fetch all the UUIDs if we have an index
378         if ( db.hasIndexOn( attributeType ) )
379         {
380             // Get the cursor using the index
381             Cursor<IndexEntry<String, String>> presenceCursor = db.getPresenceIndex().forwardCursor(
382                 partitionTxn, attributeType.getOid() );
383 
384             // Position the index on the element we should start from
385             Set<String> uuidSet = searchResult.getCandidateSet();
386 
387             // And loop on it
388             while ( presenceCursor.next() )
389             {
390                 IndexEntry<String, String> indexEntry = presenceCursor.get();
391 
392                 String uuid = indexEntry.getId();
393                 boolean added = uuidSet.add( uuid );
394 
395                 // if the UUID was added increment the result count
396                 if ( added )
397                 {
398                     nbResults++;
399                 }
400             }
401 
402             presenceCursor.close();
403         }
404         else
405         {
406             // No index, we will have to do a full scan
407             return Long.MAX_VALUE;
408         }
409 
410         return nbResults;
411     }
412 
413 
414     /**
415      * Computes the set of candidates for a OneLevelScope filter. We will feed the set only if
416      * we have an index for the AT.
417      */
418     private long computeOneLevelScope( PartitionTxn partitionTxn, ScopeNode node, PartitionSearchResult searchResult )
419         throws LdapException, CursorException, IOException
420     {
421         int nbResults = 0;
422 
423         // We use the RdnIndex to get all the entries from a starting point
424         // and below up to the number of children
425         Cursor<IndexEntry<ParentIdAndRdn, String>> rdnCursor = db.getRdnIndex().forwardCursor( partitionTxn );
426 
427         IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>();
428         startingPos.setKey( new ParentIdAndRdn( node.getBaseId(), ( Rdn[] ) null ) );
429         rdnCursor.before( startingPos );
430 
431         Cursor<IndexEntry<String, String>> scopeCursor = new ChildrenCursor( partitionTxn, db, node.getBaseId(), rdnCursor );
432         Set<String> candidateSet = searchResult.getCandidateSet();
433 
434         // Fetch all the UUIDs if we have an index
435         // And loop on it
436         while ( scopeCursor.next() )
437         {
438             IndexEntry<String, String> indexEntry = scopeCursor.get();
439 
440             String uuid = indexEntry.getId();
441 
442             // If the entry is an alias, and we asked for it to be dereferenced,
443             // we will dereference the alias
444             if ( searchResult.isDerefAlways() || searchResult.isDerefInSearching() )
445             {
446                 Dn aliasedDn = db.getAliasIndex().reverseLookup( partitionTxn, uuid );
447 
448                 if ( aliasedDn != null )
449                 {
450                     if ( !aliasedDn.isSchemaAware() )
451                     {
452                         aliasedDn = new Dn( evaluatorBuilder.getSchemaManager(), aliasedDn );
453                     }
454 
455                     String aliasedId = db.getEntryId( partitionTxn, aliasedDn );
456 
457                     // This is an alias. Add it to the set of candidates to process, if it's not already
458                     // present in the candidate set 
459                     boolean added = candidateSet.add( aliasedId );
460                     
461                     if ( added )
462                     {
463                         nbResults++;
464                     }
465                 }
466                 else
467                 {
468                     // The UUID is not present in the Set, we add it
469                     boolean added = candidateSet.add( uuid );
470                     
471                     // This is not an alias
472                     if ( added )
473                     {
474                         nbResults++;
475                     }
476                 }
477             }
478             else
479             {
480                 // The UUID is not present in the Set, we add it
481                 boolean added = candidateSet.add( uuid );
482                 
483                 // This is not an alias
484                 if ( added )
485                 {
486                     nbResults++;
487                 }
488             }
489         }
490 
491         scopeCursor.close();
492 
493         return nbResults;
494     }
495 
496 
497     /**
498      * Computes the set of candidates for a SubLevelScope filter. We will feed the set only if
499      * we have an index for the AT.
500      */
501     private long computeSubLevelScope( PartitionTxn partitionTxn, ScopeNode node, PartitionSearchResult searchResult )
502         throws LdapException, IOException, CursorException
503     {
504         // If we are searching from the partition DN, better get out.
505         String contextEntryId = db.getEntryId( partitionTxn, ( ( Partition ) db ).getSuffixDn() );
506 
507         if ( node.getBaseId() == contextEntryId )
508         {
509             return Long.MAX_VALUE;
510         }
511 
512         int nbResults = 0;
513 
514         // We use the RdnIndex to get all the entries from a starting point
515         // and below up to the number of descendant
516         String baseId = node.getBaseId();
517         ParentIdAndRdn parentIdAndRdn = db.getRdnIndex().reverseLookup( partitionTxn, baseId );
518         IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>();
519 
520         startingPos.setKey( parentIdAndRdn );
521         startingPos.setId( baseId );
522 
523         Cursor<IndexEntry<ParentIdAndRdn, String>> rdnCursor = new SingletonIndexCursor<>( partitionTxn, 
524             startingPos );
525         String parentId = parentIdAndRdn.getParentId();
526 
527         Cursor<IndexEntry<String, String>> scopeCursor = new DescendantCursor( partitionTxn, db, baseId, parentId, rdnCursor );
528         Set<String> candidateSet = searchResult.getCandidateSet();
529 
530         // Fetch all the UUIDs if we have an index
531         // And loop on it
532         while ( scopeCursor.next() )
533         {
534             IndexEntry<String, String> indexEntry = scopeCursor.get();
535 
536             String uuid = indexEntry.getId();
537 
538             // If the entry is an alias, and we asked for it to be dereferenced,
539             // we will dereference the alias
540             if ( searchResult.isDerefAlways() || searchResult.isDerefInSearching() )
541             {
542                 Dn aliasedDn = db.getAliasIndex().reverseLookup( partitionTxn, uuid );
543 
544                 if ( aliasedDn != null )
545                 {
546                     if ( !aliasedDn.isSchemaAware() )
547                     {
548                         aliasedDn = new Dn( evaluatorBuilder.getSchemaManager(), aliasedDn );
549                     }
550 
551                     String aliasedId = db.getEntryId( partitionTxn, aliasedDn );
552 
553                     // This is an alias. Add it to the set of candidates to process, if it's not already
554                     // present in the candidate set 
555                     boolean added = candidateSet.add( aliasedId );
556                     
557                     if ( added )
558                     {
559                         nbResults++;
560 
561                         ScopeNode newScopeNode = new ScopeNode(
562                             node.getDerefAliases(),
563                             aliasedDn,
564                             aliasedId,
565                             node.getScope() );
566 
567                         nbResults += computeSubLevelScope( partitionTxn, newScopeNode, searchResult );
568                     }
569                 }
570                 else
571                 {
572                     // This is not an alias
573                     // The UUID is not present in the Set, we add it
574                     boolean added = candidateSet.add( uuid );
575                     
576                     if ( added )
577                     {
578                         nbResults++;
579                     }
580                 }
581             }
582             else
583             {
584                 // The UUID is not present in the Set, we add it
585                 boolean added = candidateSet.add( uuid );
586                 
587                 if ( added )
588                 {
589                     nbResults++;
590                 }
591             }
592         }
593 
594         scopeCursor.close();
595 
596         return nbResults;
597     }
598 
599 
600     /**
601      * Computes the set of candidates for an Substring filter. We will feed the set only if
602      * we have an index for the AT.
603      */
604     private long computeSubstring( PartitionTxn partitionTxn, SubstringNode node, PartitionSearchResult searchResult )
605         throws LdapException, IndexNotFoundException, CursorException, IOException
606     {
607         AttributeType attributeType = node.getAttributeType();
608         
609         // Check if the AttributeType has a SubstringMatchingRule
610         if ( attributeType.getSubstring() == null )
611         {
612             // No SUBSTRING matching rule : return 0
613             return 0L;
614         }
615 
616         // Fetch all the UUIDs if we have an index
617         if ( db.hasIndexOn( attributeType ) )
618         {
619             Index<String, String> userIndex = ( Index<String, String> ) db.getIndex( attributeType );
620             Cursor<IndexEntry<String, String>> cursor = userIndex.forwardCursor( partitionTxn );
621 
622             // Position the index on the element we should start from
623             IndexEntry<String, String> indexEntry = new IndexEntry<>();
624             String initial = node.getInitial();
625             
626             boolean fullIndexScan = false;
627             
628             if ( initial == null )
629             {
630                 fullIndexScan = true;
631                 cursor.beforeFirst();
632             }
633             else
634             {
635                 indexEntry.setKey( attributeType.getEquality().getNormalizer().normalize( initial, PrepareString.AssertionType.SUBSTRING_INITIAL ) );
636                 
637                 cursor.before( indexEntry );
638             }
639             
640             int nbResults = 0;
641 
642             MatchingRule rule = attributeType.getSubstring();
643 
644             if ( rule == null )
645             {
646                 rule = attributeType.getEquality();
647             }
648 
649             Normalizer normalizer;
650             Pattern regexp;
651 
652             if ( rule != null )
653             {
654                 normalizer = rule.getNormalizer();
655             }
656             else
657             {
658                 normalizer = new NoOpNormalizer( attributeType.getSyntaxOid() );
659             }
660 
661             // compile the regular expression to search for a matching attribute
662             // if the attributeType is humanReadable
663             if ( attributeType.getSyntax().isHumanReadable() )
664             {
665                 regexp = node.getRegex( normalizer );
666             }
667             else
668             {
669                 regexp = null;
670             }
671 
672             Set<String> uuidSet = searchResult.getCandidateSet();
673 
674             if ( regexp == null )
675             {
676                 return nbResults;
677             }
678             
679             // And loop on it
680             while ( cursor.next() )
681             {
682                 indexEntry = cursor.get();
683 
684                 String key = indexEntry.getKey();
685 
686                 boolean matched = regexp.matcher( key ).matches();
687                 
688                 if ( !fullIndexScan && !matched )
689                 {
690                     cursor.close();
691 
692                     return nbResults;
693                 }
694 
695                 if ( !matched )
696                 {
697                     continue;
698                 }
699                 
700                 String uuid = indexEntry.getId();
701 
702                 boolean added = uuidSet.add( uuid );
703                 
704                 // if the UUID was added increment the result count
705                 if ( added )
706                 {
707                     nbResults++;
708                 }
709             }
710 
711             cursor.close();
712 
713             return nbResults;
714         }
715         else
716         {
717             // No index, we will have to do a full scan
718             return Long.MAX_VALUE;
719         }
720     }
721 
722 
723     /**
724      * Creates a OrCursor over a disjunction expression branch node.
725      *
726      * @param node the disjunction expression branch node
727      * @return Cursor over candidates satisfying disjunction expression
728      * @throws Exception on db access failures
729      */
730     private long computeOr( PartitionTxn partitionTxn, OrNode node, PartitionSearchResult searchResult ) 
731         throws LdapException
732     {
733         List<ExprNode> children = node.getChildren();
734 
735         long nbOrResults = 0;
736 
737         // Recursively create Cursors and Evaluators for each child expression node
738         for ( ExprNode child : children )
739         {
740             Object count = child.get( DefaultOptimizer.COUNT_ANNOTATION );
741 
742             if ( count != null )
743             {
744                 long countLong = ( Long ) count;
745 
746                 if ( countLong == 0 )
747                 {
748                     // We can skip the cursor, it will not return any candidate
749                     continue;
750                 }
751                 else if ( countLong == Long.MAX_VALUE )
752                 {
753                     // We can stop here, we will anyway do a full scan
754                     return countLong;
755                 }
756             }
757 
758             long nbResults = build( partitionTxn, child, searchResult );
759 
760             if ( nbResults == Long.MAX_VALUE )
761             {
762                 // We can stop here, we will anyway do a full scan
763                 return nbResults;
764             }
765             else
766             {
767                 nbOrResults += nbResults;
768             }
769         }
770 
771         return nbOrResults;
772     }
773 
774 
775     /**
776      * Creates an AndCursor over a conjunction expression branch node.
777      *
778      * @param node a conjunction expression branch node
779      * @return Cursor over the conjunction expression
780      * @throws Exception on db access failures
781      */
782     private long computeAnd( PartitionTxn partitionTxn, AndNode node, PartitionSearchResult searchResult ) 
783         throws LdapException
784     {
785         int minIndex = 0;
786         long minValue = Long.MAX_VALUE;
787         long value;
788 
789         /*
790          * We scan the child nodes of a branch node searching for the child
791          * expression node with the smallest scan count.  This is the child
792          * we will use for iteration
793          */
794         final List<ExprNode> children = node.getChildren();
795 
796         for ( int i = 0; i < children.size(); i++ )
797         {
798             ExprNode child = children.get( i );
799             Object count = child.get( DefaultOptimizer.COUNT_ANNOTATION );
800 
801             if ( count == null )
802             {
803                 continue;
804             }
805 
806             value = ( Long ) count;
807 
808             if ( value == 0L )
809             {
810                 // No need to go any further : we won't have matching candidates anyway
811                 return 0L;
812             }
813 
814             if ( value < minValue )
815             {
816                 minValue = value;
817                 minIndex = i;
818             }
819         }
820 
821         // Once found we return the number of candidates for this child
822         ExprNode minChild = children.get( minIndex );
823 
824         return build( partitionTxn, minChild, searchResult );
825     }
826 
827 
828     /**
829      * Creates an AndCursor over a conjunction expression branch node.
830      *
831      * @param node a conjunction expression branch node
832      * @return Cursor over the conjunction expression
833      * @throws Exception on db access failures
834      */
835     private long computeNot( NotNode node, PartitionSearchResult searchResult )
836     {
837         final List<ExprNode> children = node.getChildren();
838 
839         ExprNode child = children.get( 0 );
840         Object count = child.get( DefaultOptimizer.COUNT_ANNOTATION );
841 
842         if ( count == null )
843         {
844             return Long.MAX_VALUE;
845         }
846 
847         long value = ( Long ) count;
848 
849         if ( value == Long.MAX_VALUE )
850         {
851             // No need to go any further : we won't have matching candidates anyway
852             return 0L;
853         }
854 
855         return Long.MAX_VALUE;
856     }
857 }