001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *  
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *  
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License. 
018 *  
019 */
020package org.apache.directory.server.xdbm.search.impl;
021
022
023import java.io.IOException;
024import java.util.List;
025import java.util.Set;
026import java.util.regex.Pattern;
027
028import org.apache.directory.api.ldap.model.cursor.Cursor;
029import org.apache.directory.api.ldap.model.cursor.CursorException;
030import org.apache.directory.api.ldap.model.entry.Value;
031import org.apache.directory.api.ldap.model.exception.LdapException;
032import org.apache.directory.api.ldap.model.exception.LdapOtherException;
033import org.apache.directory.api.ldap.model.filter.AndNode;
034import org.apache.directory.api.ldap.model.filter.ApproximateNode;
035import org.apache.directory.api.ldap.model.filter.EqualityNode;
036import org.apache.directory.api.ldap.model.filter.ExprNode;
037import org.apache.directory.api.ldap.model.filter.GreaterEqNode;
038import org.apache.directory.api.ldap.model.filter.LessEqNode;
039import org.apache.directory.api.ldap.model.filter.NotNode;
040import org.apache.directory.api.ldap.model.filter.OrNode;
041import org.apache.directory.api.ldap.model.filter.PresenceNode;
042import org.apache.directory.api.ldap.model.filter.ScopeNode;
043import org.apache.directory.api.ldap.model.filter.SubstringNode;
044import org.apache.directory.api.ldap.model.message.SearchScope;
045import org.apache.directory.api.ldap.model.name.Dn;
046import org.apache.directory.api.ldap.model.name.Rdn;
047import org.apache.directory.api.ldap.model.schema.AttributeType;
048import org.apache.directory.api.ldap.model.schema.MatchingRule;
049import org.apache.directory.api.ldap.model.schema.Normalizer;
050import org.apache.directory.api.ldap.model.schema.PrepareString;
051import org.apache.directory.api.ldap.model.schema.normalizers.NoOpNormalizer;
052import org.apache.directory.api.util.exception.NotImplementedException;
053import org.apache.directory.server.core.api.partition.Partition;
054import org.apache.directory.server.core.api.partition.PartitionTxn;
055import org.apache.directory.server.i18n.I18n;
056import org.apache.directory.server.xdbm.Index;
057import org.apache.directory.server.xdbm.IndexEntry;
058import org.apache.directory.server.xdbm.IndexNotFoundException;
059import org.apache.directory.server.xdbm.ParentIdAndRdn;
060import org.apache.directory.server.xdbm.SingletonIndexCursor;
061import org.apache.directory.server.xdbm.Store;
062import org.apache.directory.server.xdbm.search.PartitionSearchResult;
063import org.apache.directory.server.xdbm.search.cursor.ApproximateCursor;
064import org.apache.directory.server.xdbm.search.cursor.ChildrenCursor;
065import org.apache.directory.server.xdbm.search.cursor.DescendantCursor;
066import org.apache.directory.server.xdbm.search.evaluator.ApproximateEvaluator;
067
068
069/**
070 * Builds Cursors over candidates that satisfy a filter expression.
071 * 
072 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
073 */
074public class CursorBuilder
075{
076    /** The database used by this builder */
077    private Store db = null;
078
079    /** Evaluator dependency on a EvaluatorBuilder */
080    private EvaluatorBuilder evaluatorBuilder;
081
082
083    /**
084     * Creates an expression tree enumerator.
085     *
086     * @param db database used by this enumerator
087     * @param evaluatorBuilder the evaluator builder
088     */
089    public CursorBuilder( Store db, EvaluatorBuilder evaluatorBuilder )
090    {
091        this.db = db;
092        this.evaluatorBuilder = evaluatorBuilder;
093    }
094
095
096    public <T> long build( PartitionTxn partitionTxn, ExprNode node, PartitionSearchResult searchResult ) throws LdapException
097    {
098        Object count = node.get( DefaultOptimizer.COUNT_ANNOTATION );
099
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}