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                case UNDEFINED:
153                    return Long.MAX_VALUE;
154    
155                case ASSERTION:
156                case EXTENSIBLE:
157                    throw new NotImplementedException();
158    
159                default:
160                    throw new IllegalStateException( I18n.err( I18n.ERR_260, node.getAssertionType() ) );
161            }
162        }
163        catch ( IndexNotFoundException | CursorException | IOException e )
164        {
165            throw new LdapOtherException( e.getMessage(), e );
166        }
167    }
168
169
170    /**
171     * Computes the set of candidates for an Approximate filter. We will feed the set only if
172     * we have an index for the AT.
173     */
174
175    private <T> long computeApproximate( PartitionTxn partitionTxn, ApproximateNode<T> node, PartitionSearchResult searchResult )
176        throws LdapException, IndexNotFoundException, CursorException, IOException
177    {
178        ApproximateCursor<T> cursor = new ApproximateCursor<>( partitionTxn, db,
179            ( ApproximateEvaluator<T> ) evaluatorBuilder
180                .build( partitionTxn, node ) );
181
182        int nbResults = 0;
183        Set<String> uuidSet = searchResult.getCandidateSet();
184
185        while ( cursor.next() )
186        {
187            IndexEntry<T, String> indexEntry = cursor.get();
188
189            String uuid = indexEntry.getId();
190            boolean added = uuidSet.add( uuid );
191
192            // if the UUID was added increment the result count
193            if ( added )
194            {
195                nbResults++;
196            }
197        }
198
199        cursor.close();
200
201        return nbResults;
202    }
203
204
205    /**
206     * Computes the set of candidates for an Equality filter. We will feed the set only if
207     * we have an index for the AT.
208     */
209    private <T> long computeEquality( PartitionTxn partitionTxn, EqualityNode<T> node, PartitionSearchResult searchResult )
210        throws LdapException, IndexNotFoundException, CursorException, IOException
211    {
212        Set<String> thisCandidates = ( Set<String> ) node.get( DefaultOptimizer.CANDIDATES_ANNOTATION_KEY );
213
214        if ( thisCandidates != null )
215        {
216            Set<String> candidates = searchResult.getCandidateSet();
217
218            for ( String candidate : thisCandidates )
219            {
220                candidates.add( candidate );
221            }
222
223            return thisCandidates.size();
224        }
225
226        AttributeType attributeType = node.getAttributeType();
227        Value value = node.getValue();
228        int nbResults = 0;
229
230        // Fetch all the UUIDs if we have an index
231        if ( db.hasIndexOn( attributeType ) )
232        {
233            // Get the cursor using the index
234            Index<T, String> userIndex = ( Index<T, String> ) db.getIndex( attributeType );
235            Cursor<IndexEntry<T, String>> userIdxCursor = userIndex.forwardCursor( partitionTxn, ( T ) value.getNormalized() );
236            Set<String> uuidSet = searchResult.getCandidateSet();
237
238            // And loop on it
239            while ( userIdxCursor.next() )
240            {
241                IndexEntry<T, String> indexEntry = userIdxCursor.get();
242
243                String uuid = indexEntry.getId();
244                boolean added = uuidSet.add( uuid );
245                
246                // if the UUID was added increment the result count
247                if ( added )
248                {
249                    nbResults++;
250                }
251            }
252
253            userIdxCursor.close();
254        }
255        else
256        {
257            // No index, we will have to do a full scan
258            return Long.MAX_VALUE;
259        }
260
261        return nbResults;
262    }
263
264
265    /**
266     * Computes the set of candidates for an GreateEq filter. We will feed the set only if
267     * we have an index for the AT.
268     */
269    private <T> long computeGreaterEq( PartitionTxn partitionTxn, GreaterEqNode<T> node, PartitionSearchResult searchResult )
270        throws LdapException, IndexNotFoundException, CursorException, IOException
271    {
272        AttributeType attributeType = node.getAttributeType();
273        Value value = node.getValue();
274        int nbResults = 0;
275
276        // Fetch all the UUIDs if we have an index
277        if ( db.hasIndexOn( attributeType ) )
278        {
279            // Get the cursor using the index
280            Index<T, String> userIndex = ( Index<T, String> ) db.getIndex( attributeType );
281            Cursor<IndexEntry<T, String>> userIdxCursor = userIndex.forwardCursor( partitionTxn );
282
283            // Position the index on the element we should start from
284            IndexEntry<T, String> indexEntry = new IndexEntry<>();
285            indexEntry.setKey( ( T ) value.getString() );
286
287            userIdxCursor.before( indexEntry );
288            Set<String> uuidSet = searchResult.getCandidateSet();
289
290            // And loop on it
291            while ( userIdxCursor.next() )
292            {
293                indexEntry = userIdxCursor.get();
294
295                String uuid = indexEntry.getId();
296                boolean added = uuidSet.add( uuid );
297
298                // if the UUID was added increment the result count
299                if ( added )
300                {
301                    nbResults++;
302                }
303            }
304
305            userIdxCursor.close();
306        }
307        else
308        {
309            // No index, we will have to do a full scan
310            return Long.MAX_VALUE;
311        }
312
313        return nbResults;
314    }
315
316
317    /**
318     * Computes the set of candidates for an LessEq filter. We will feed the set only if
319     * we have an index for the AT.
320     */
321    private <T> long computeLessEq( PartitionTxn partitionTxn, LessEqNode<T> node, PartitionSearchResult searchResult )
322        throws LdapException, IndexNotFoundException, CursorException, IOException
323    {
324        AttributeType attributeType = node.getAttributeType();
325        Value value = node.getValue();
326        int nbResults = 0;
327
328        // Fetch all the UUIDs if we have an index
329        if ( db.hasIndexOn( attributeType ) )
330        {
331            // Get the cursor using the index
332            Index<T, String> userIndex = ( Index<T, String> ) db.getIndex( attributeType );
333            Cursor<IndexEntry<T, String>> userIdxCursor = userIndex.forwardCursor( partitionTxn );
334
335            // Position the index on the element we should start from
336            IndexEntry<T, String> indexEntry = new IndexEntry<>();
337            indexEntry.setKey( ( T ) value.getString() );
338
339            userIdxCursor.after( indexEntry );
340            Set<String> uuidSet = searchResult.getCandidateSet();
341
342            // And loop on it
343            while ( userIdxCursor.previous() )
344            {
345                indexEntry = userIdxCursor.get();
346
347                String uuid = indexEntry.getId();
348                boolean added = uuidSet.add( uuid );
349
350                // if the UUID was added increment the result count
351                if ( added )
352                {
353                    nbResults++;
354                }
355            }
356
357            userIdxCursor.close();
358        }
359        else
360        {
361            // No index, we will have to do a full scan
362            return Long.MAX_VALUE;
363        }
364
365        return nbResults;
366    }
367
368
369    /**
370     * Computes the set of candidates for a Presence filter. We will feed the set only if
371     * we have an index for the AT.
372     */
373    private long computePresence( PartitionTxn partitionTxn, PresenceNode node, PartitionSearchResult searchResult )
374        throws LdapException, CursorException, IOException
375    {
376        AttributeType attributeType = node.getAttributeType();
377        int nbResults = 0;
378
379        // Fetch all the UUIDs if we have an index
380        if ( db.hasIndexOn( attributeType ) )
381        {
382            // Get the cursor using the index
383            Cursor<IndexEntry<String, String>> presenceCursor = db.getPresenceIndex().forwardCursor(
384                partitionTxn, attributeType.getOid() );
385
386            // Position the index on the element we should start from
387            Set<String> uuidSet = searchResult.getCandidateSet();
388
389            // And loop on it
390            while ( presenceCursor.next() )
391            {
392                IndexEntry<String, String> indexEntry = presenceCursor.get();
393
394                String uuid = indexEntry.getId();
395                boolean added = uuidSet.add( uuid );
396
397                // if the UUID was added increment the result count
398                if ( added )
399                {
400                    nbResults++;
401                }
402            }
403
404            presenceCursor.close();
405        }
406        else
407        {
408            // No index, we will have to do a full scan
409            return Long.MAX_VALUE;
410        }
411
412        return nbResults;
413    }
414
415
416    /**
417     * Computes the set of candidates for a OneLevelScope filter. We will feed the set only if
418     * we have an index for the AT.
419     */
420    private long computeOneLevelScope( PartitionTxn partitionTxn, ScopeNode node, PartitionSearchResult searchResult )
421        throws LdapException, CursorException, IOException
422    {
423        int nbResults = 0;
424
425        // We use the RdnIndex to get all the entries from a starting point
426        // and below up to the number of children
427        Cursor<IndexEntry<ParentIdAndRdn, String>> rdnCursor = db.getRdnIndex().forwardCursor( partitionTxn );
428
429        IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>();
430        startingPos.setKey( new ParentIdAndRdn( node.getBaseId(), ( Rdn[] ) null ) );
431        rdnCursor.before( startingPos );
432
433        Cursor<IndexEntry<String, String>> scopeCursor = new ChildrenCursor( partitionTxn, db, node.getBaseId(), rdnCursor );
434        Set<String> candidateSet = searchResult.getCandidateSet();
435
436        // Fetch all the UUIDs if we have an index
437        // And loop on it
438        while ( scopeCursor.next() )
439        {
440            IndexEntry<String, String> indexEntry = scopeCursor.get();
441
442            String uuid = indexEntry.getId();
443
444            // If the entry is an alias, and we asked for it to be dereferenced,
445            // we will dereference the alias
446            if ( searchResult.isDerefAlways() || searchResult.isDerefInSearching() )
447            {
448                Dn aliasedDn = db.getAliasIndex().reverseLookup( partitionTxn, uuid );
449
450                if ( aliasedDn != null )
451                {
452                    if ( !aliasedDn.isSchemaAware() )
453                    {
454                        aliasedDn = new Dn( evaluatorBuilder.getSchemaManager(), aliasedDn );
455                    }
456
457                    String aliasedId = db.getEntryId( partitionTxn, aliasedDn );
458
459                    // This is an alias. Add it to the set of candidates to process, if it's not already
460                    // present in the candidate set 
461                    boolean added = candidateSet.add( aliasedId );
462                    
463                    if ( added )
464                    {
465                        nbResults++;
466                    }
467                }
468                else
469                {
470                    // The UUID is not present in the Set, we add it
471                    boolean added = candidateSet.add( uuid );
472                    
473                    // This is not an alias
474                    if ( added )
475                    {
476                        nbResults++;
477                    }
478                }
479            }
480            else
481            {
482                // The UUID is not present in the Set, we add it
483                boolean added = candidateSet.add( uuid );
484                
485                // This is not an alias
486                if ( added )
487                {
488                    nbResults++;
489                }
490            }
491        }
492
493        scopeCursor.close();
494
495        return nbResults;
496    }
497
498
499    /**
500     * Computes the set of candidates for a SubLevelScope filter. We will feed the set only if
501     * we have an index for the AT.
502     */
503    private long computeSubLevelScope( PartitionTxn partitionTxn, ScopeNode node, PartitionSearchResult searchResult )
504        throws LdapException, IOException, CursorException
505    {
506        // If we are searching from the partition DN, better get out.
507        String contextEntryId = db.getEntryId( partitionTxn, ( ( Partition ) db ).getSuffixDn() );
508
509        if ( node.getBaseId() == contextEntryId )
510        {
511            return Long.MAX_VALUE;
512        }
513
514        long nbResults = 0;
515
516        // We use the RdnIndex to get all the entries from a starting point
517        // and below up to the number of descendant
518        String baseId = node.getBaseId();
519        ParentIdAndRdn parentIdAndRdn = db.getRdnIndex().reverseLookup( partitionTxn, baseId );
520        IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>();
521
522        startingPos.setKey( parentIdAndRdn );
523        startingPos.setId( baseId );
524
525        Cursor<IndexEntry<ParentIdAndRdn, String>> rdnCursor = new SingletonIndexCursor<>( partitionTxn, 
526            startingPos );
527        String parentId = parentIdAndRdn.getParentId();
528
529        Cursor<IndexEntry<String, String>> scopeCursor = new DescendantCursor( partitionTxn, db, baseId, parentId, rdnCursor );
530        Set<String> candidateSet = searchResult.getCandidateSet();
531
532        // Fetch all the UUIDs if we have an index
533        // And loop on it
534        while ( scopeCursor.next() )
535        {
536            IndexEntry<String, String> indexEntry = scopeCursor.get();
537
538            String uuid = indexEntry.getId();
539
540            // If the entry is an alias, and we asked for it to be dereferenced,
541            // we will dereference the alias
542            if ( searchResult.isDerefAlways() || searchResult.isDerefInSearching() )
543            {
544                Dn aliasedDn = db.getAliasIndex().reverseLookup( partitionTxn, uuid );
545
546                if ( aliasedDn != null )
547                {
548                    if ( !aliasedDn.isSchemaAware() )
549                    {
550                        aliasedDn = new Dn( evaluatorBuilder.getSchemaManager(), aliasedDn );
551                    }
552
553                    String aliasedId = db.getEntryId( partitionTxn, aliasedDn );
554
555                    // This is an alias. Add it to the set of candidates to process, if it's not already
556                    // present in the candidate set 
557                    boolean added = candidateSet.add( aliasedId );
558                    
559                    if ( added )
560                    {
561                        nbResults++;
562
563                        ScopeNode newScopeNode = new ScopeNode(
564                            node.getDerefAliases(),
565                            aliasedDn,
566                            aliasedId,
567                            node.getScope() );
568
569                        nbResults += computeSubLevelScope( partitionTxn, newScopeNode, searchResult );
570                    }
571                }
572                else
573                {
574                    // This is not an alias
575                    // The UUID is not present in the Set, we add it
576                    boolean added = candidateSet.add( uuid );
577                    
578                    if ( added )
579                    {
580                        nbResults++;
581                    }
582                }
583            }
584            else
585            {
586                // The UUID is not present in the Set, we add it
587                boolean added = candidateSet.add( uuid );
588                
589                if ( added )
590                {
591                    nbResults++;
592                }
593            }
594        }
595
596        scopeCursor.close();
597
598        return nbResults;
599    }
600
601
602    /**
603     * Computes the set of candidates for an Substring filter. We will feed the set only if
604     * we have an index for the AT.
605     */
606    private long computeSubstring( PartitionTxn partitionTxn, SubstringNode node, PartitionSearchResult searchResult )
607        throws LdapException, IndexNotFoundException, CursorException, IOException
608    {
609        AttributeType attributeType = node.getAttributeType();
610        
611        // Check if the AttributeType has a SubstringMatchingRule
612        if ( attributeType.getSubstring() == null )
613        {
614            // No SUBSTRING matching rule : return 0
615            return 0L;
616        }
617
618        // Fetch all the UUIDs if we have an index
619        if ( db.hasIndexOn( attributeType ) )
620        {
621            Index<String, String> userIndex = ( Index<String, String> ) db.getIndex( attributeType );
622            Cursor<IndexEntry<String, String>> cursor = userIndex.forwardCursor( partitionTxn );
623
624            // Position the index on the element we should start from
625            IndexEntry<String, String> indexEntry = new IndexEntry<>();
626            String initial = node.getInitial();
627            
628            boolean fullIndexScan = false;
629            
630            if ( initial == null )
631            {
632                fullIndexScan = true;
633                cursor.beforeFirst();
634            }
635            else
636            {
637                indexEntry.setKey( attributeType.getEquality().getNormalizer().normalize( initial, PrepareString.AssertionType.SUBSTRING_INITIAL ) );
638                
639                cursor.before( indexEntry );
640            }
641            
642            int nbResults = 0;
643
644            MatchingRule rule = attributeType.getSubstring();
645
646            if ( rule == null )
647            {
648                rule = attributeType.getEquality();
649            }
650
651            Normalizer normalizer;
652            Pattern regexp;
653
654            if ( rule != null )
655            {
656                normalizer = rule.getNormalizer();
657            }
658            else
659            {
660                normalizer = new NoOpNormalizer( attributeType.getSyntaxOid() );
661            }
662
663            // compile the regular expression to search for a matching attribute
664            // if the attributeType is humanReadable
665            if ( attributeType.getSyntax().isHumanReadable() )
666            {
667                regexp = node.getRegex( normalizer );
668            }
669            else
670            {
671                regexp = null;
672            }
673
674            Set<String> uuidSet = searchResult.getCandidateSet();
675
676            if ( regexp == null )
677            {
678                return nbResults;
679            }
680            
681            // And loop on it
682            while ( cursor.next() )
683            {
684                indexEntry = cursor.get();
685
686                String key = indexEntry.getKey();
687
688                boolean matched = regexp.matcher( key ).matches();
689                
690                if ( !fullIndexScan && !matched )
691                {
692                    cursor.close();
693
694                    return nbResults;
695                }
696
697                if ( !matched )
698                {
699                    continue;
700                }
701                
702                String uuid = indexEntry.getId();
703
704                boolean added = uuidSet.add( uuid );
705                
706                // if the UUID was added increment the result count
707                if ( added )
708                {
709                    nbResults++;
710                }
711            }
712
713            cursor.close();
714
715            return nbResults;
716        }
717        else
718        {
719            // No index, we will have to do a full scan
720            return Long.MAX_VALUE;
721        }
722    }
723
724
725    /**
726     * Creates a OrCursor over a disjunction expression branch node.
727     *
728     * @param node the disjunction expression branch node
729     * @return Cursor over candidates satisfying disjunction expression
730     * @throws Exception on db access failures
731     */
732    private long computeOr( PartitionTxn partitionTxn, OrNode node, PartitionSearchResult searchResult ) 
733        throws LdapException
734    {
735        List<ExprNode> children = node.getChildren();
736
737        long nbOrResults = 0;
738
739        // Recursively create Cursors and Evaluators for each child expression node
740        for ( ExprNode child : children )
741        {
742            Object count = child.get( DefaultOptimizer.COUNT_ANNOTATION );
743
744            if ( count != null )
745            {
746                long countLong = ( Long ) count;
747
748                if ( countLong == 0 )
749                {
750                    // We can skip the cursor, it will not return any candidate
751                    continue;
752                }
753                else if ( countLong == Long.MAX_VALUE )
754                {
755                    // We can stop here, we will anyway do a full scan
756                    return countLong;
757                }
758            }
759
760            long nbResults = build( partitionTxn, child, searchResult );
761
762            if ( nbResults == Long.MAX_VALUE )
763            {
764                // We can stop here, we will anyway do a full scan
765                return nbResults;
766            }
767            else
768            {
769                nbOrResults += nbResults;
770            }
771        }
772
773        return nbOrResults;
774    }
775
776
777    /**
778     * Creates an AndCursor over a conjunction expression branch node.
779     *
780     * @param node a conjunction expression branch node
781     * @return Cursor over the conjunction expression
782     * @throws Exception on db access failures
783     */
784    private long computeAnd( PartitionTxn partitionTxn, AndNode node, PartitionSearchResult searchResult ) 
785        throws LdapException
786    {
787        int minIndex = 0;
788        long minValue = Long.MAX_VALUE;
789        long value;
790
791        /*
792         * We scan the child nodes of a branch node searching for the child
793         * expression node with the smallest scan count.  This is the child
794         * we will use for iteration
795         */
796        final List<ExprNode> children = node.getChildren();
797
798        for ( int i = 0; i < children.size(); i++ )
799        {
800            ExprNode child = children.get( i );
801            Object count = child.get( DefaultOptimizer.COUNT_ANNOTATION );
802
803            if ( count == null )
804            {
805                continue;
806            }
807
808            value = ( Long ) count;
809
810            if ( value == 0L )
811            {
812                // No need to go any further : we won't have matching candidates anyway
813                return 0L;
814            }
815
816            if ( value < minValue )
817            {
818                minValue = value;
819                minIndex = i;
820            }
821        }
822
823        // Once found we return the number of candidates for this child
824        ExprNode minChild = children.get( minIndex );
825
826        return build( partitionTxn, minChild, searchResult );
827    }
828
829
830    /**
831     * Creates an AndCursor over a conjunction expression branch node.
832     *
833     * @param node a conjunction expression branch node
834     * @return Cursor over the conjunction expression
835     * @throws Exception on db access failures
836     */
837    private long computeNot( NotNode node, PartitionSearchResult searchResult )
838    {
839        final List<ExprNode> children = node.getChildren();
840
841        ExprNode child = children.get( 0 );
842        Object count = child.get( DefaultOptimizer.COUNT_ANNOTATION );
843
844        if ( count == null )
845        {
846            return Long.MAX_VALUE;
847        }
848
849        long value = ( Long ) count;
850
851        if ( value == Long.MAX_VALUE )
852        {
853            // No need to go any further : we won't have matching candidates anyway
854            return 0L;
855        }
856
857        return Long.MAX_VALUE;
858    }
859}