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( "count" );
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<T>( 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.getValue() );
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.getValue() );
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            IndexEntry<String, String> indexEntry = new IndexEntry<>();
386            Set<String> uuidSet = searchResult.getCandidateSet();
387
388            // And loop on it
389            while ( presenceCursor.next() )
390            {
391                indexEntry = presenceCursor.get();
392
393                String uuid = indexEntry.getId();
394                boolean added = uuidSet.add( uuid );
395
396                // if the UUID was added increment the result count
397                if ( added )
398                {
399                    nbResults++;
400                }
401            }
402
403            presenceCursor.close();
404        }
405        else
406        {
407            // No index, we will have to do a full scan
408            return Long.MAX_VALUE;
409        }
410
411        return nbResults;
412    }
413
414
415    /**
416     * Computes the set of candidates for a OneLevelScope filter. We will feed the set only if
417     * we have an index for the AT.
418     */
419    private long computeOneLevelScope( PartitionTxn partitionTxn, ScopeNode node, PartitionSearchResult searchResult )
420        throws LdapException, IndexNotFoundException, CursorException, IOException
421    {
422        int nbResults = 0;
423
424        // We use the RdnIndex to get all the entries from a starting point
425        // and below up to the number of children
426        Cursor<IndexEntry<ParentIdAndRdn, String>> rdnCursor = db.getRdnIndex().forwardCursor( partitionTxn );
427
428        IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>();
429        startingPos.setKey( new ParentIdAndRdn( node.getBaseId(), ( Rdn[] ) null ) );
430        rdnCursor.before( startingPos );
431
432        Cursor<IndexEntry<String, String>> scopeCursor = new ChildrenCursor( partitionTxn, db, node.getBaseId(), rdnCursor );
433        Set<String> candidateSet = searchResult.getCandidateSet();
434
435        // Fetch all the UUIDs if we have an index
436        // And loop on it
437        while ( scopeCursor.next() )
438        {
439            IndexEntry<String, String> indexEntry = scopeCursor.get();
440
441            String uuid = indexEntry.getId();
442
443            // If the entry is an alias, and we asked for it to be dereferenced,
444            // we will dereference the alias
445            if ( searchResult.isDerefAlways() || searchResult.isDerefInSearching() )
446            {
447                Dn aliasedDn = db.getAliasIndex().reverseLookup( partitionTxn, uuid );
448
449                if ( aliasedDn != null )
450                {
451                    if ( !aliasedDn.isSchemaAware() )
452                    {
453                        aliasedDn = new Dn( evaluatorBuilder.getSchemaManager(), aliasedDn );
454                    }
455
456                    String aliasedId = db.getEntryId( partitionTxn, aliasedDn );
457
458                    // This is an alias. Add it to the set of candidates to process, if it's not already
459                    // present in the candidate set 
460                    boolean added = candidateSet.add( aliasedId );
461                    
462                    if ( added )
463                    {
464                        nbResults++;
465                    }
466                }
467                else
468                {
469                    // The UUID is not present in the Set, we add it
470                    boolean added = candidateSet.add( uuid );
471                    
472                    // This is not an alias
473                    if ( added )
474                    {
475                        nbResults++;
476                    }
477                }
478            }
479            else
480            {
481                // The UUID is not present in the Set, we add it
482                boolean added = candidateSet.add( uuid );
483                
484                // This is not an alias
485                if ( added )
486                {
487                    nbResults++;
488                }
489            }
490        }
491
492        scopeCursor.close();
493
494        return nbResults;
495    }
496
497
498    /**
499     * Computes the set of candidates for a SubLevelScope filter. We will feed the set only if
500     * we have an index for the AT.
501     */
502    private long computeSubLevelScope( PartitionTxn partitionTxn, ScopeNode node, PartitionSearchResult searchResult )
503        throws LdapException, IOException, CursorException
504    {
505        // If we are searching from the partition DN, better get out.
506        String contextEntryId = db.getEntryId( partitionTxn, ( ( Partition ) db ).getSuffixDn() );
507
508        if ( node.getBaseId() == contextEntryId )
509        {
510            return Long.MAX_VALUE;
511        }
512
513        int nbResults = 0;
514
515        // We use the RdnIndex to get all the entries from a starting point
516        // and below up to the number of descendant
517        String baseId = node.getBaseId();
518        ParentIdAndRdn parentIdAndRdn = db.getRdnIndex().reverseLookup( partitionTxn, baseId );
519        IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>();
520
521        startingPos.setKey( parentIdAndRdn );
522        startingPos.setId( baseId );
523
524        Cursor<IndexEntry<ParentIdAndRdn, String>> rdnCursor = new SingletonIndexCursor<>( partitionTxn, 
525            startingPos );
526        String parentId = parentIdAndRdn.getParentId();
527
528        Cursor<IndexEntry<String, String>> scopeCursor = new DescendantCursor( partitionTxn, db, baseId, parentId, rdnCursor );
529        Set<String> candidateSet = searchResult.getCandidateSet();
530
531        // Fetch all the UUIDs if we have an index
532        // And loop on it
533        while ( scopeCursor.next() )
534        {
535            IndexEntry<String, String> indexEntry = scopeCursor.get();
536
537            String uuid = indexEntry.getId();
538
539            // If the entry is an alias, and we asked for it to be dereferenced,
540            // we will dereference the alias
541            if ( searchResult.isDerefAlways() || searchResult.isDerefInSearching() )
542            {
543                Dn aliasedDn = db.getAliasIndex().reverseLookup( partitionTxn, uuid );
544
545                if ( aliasedDn != null )
546                {
547                    if ( !aliasedDn.isSchemaAware() )
548                    {
549                        aliasedDn = new Dn( evaluatorBuilder.getSchemaManager(), aliasedDn );
550                    }
551
552                    String aliasedId = db.getEntryId( partitionTxn, aliasedDn );
553
554                    // This is an alias. Add it to the set of candidates to process, if it's not already
555                    // present in the candidate set 
556                    boolean added = candidateSet.add( aliasedId );
557                    
558                    if ( added )
559                    {
560                        nbResults++;
561
562                        ScopeNode newScopeNode = new ScopeNode(
563                            node.getDerefAliases(),
564                            aliasedDn,
565                            aliasedId,
566                            node.getScope() );
567
568                        nbResults += computeSubLevelScope( partitionTxn, newScopeNode, searchResult );
569                    }
570                }
571                else
572                {
573                    // This is not an alias
574                    // The UUID is not present in the Set, we add it
575                    boolean added = candidateSet.add( uuid );
576                    
577                    if ( added )
578                    {
579                        nbResults++;
580                    }
581                }
582            }
583            else
584            {
585                // The UUID is not present in the Set, we add it
586                boolean added = candidateSet.add( uuid );
587                
588                if ( added )
589                {
590                    nbResults++;
591                }
592            }
593        }
594
595        scopeCursor.close();
596
597        return nbResults;
598    }
599
600
601    /**
602     * Computes the set of candidates for an Substring filter. We will feed the set only if
603     * we have an index for the AT.
604     */
605    private long computeSubstring( PartitionTxn partitionTxn, SubstringNode node, PartitionSearchResult searchResult )
606        throws LdapException, IndexNotFoundException, CursorException, IOException
607    {
608        AttributeType attributeType = node.getAttributeType();
609        
610        // Check if the AttributeType has a SubstringMatchingRule
611        if ( attributeType.getSubstring() == null )
612        {
613            // No SUBSTRING matching rule : return 0
614            return 0L;
615        }
616
617        // Fetch all the UUIDs if we have an index
618        if ( db.hasIndexOn( attributeType ) )
619        {
620            Index<String, String> userIndex = ( Index<String, String> ) db.getIndex( attributeType );
621            Cursor<IndexEntry<String, String>> cursor = userIndex.forwardCursor( partitionTxn );
622
623            // Position the index on the element we should start from
624            IndexEntry<String, String> indexEntry = new IndexEntry<>();
625            String initial = node.getInitial();
626            
627            boolean fullIndexScan = false;
628            
629            if ( initial == null )
630            {
631                fullIndexScan = true;
632                cursor.beforeFirst();
633            }
634            else
635            {
636                indexEntry.setKey( attributeType.getEquality().getNormalizer().normalize( initial, PrepareString.AssertionType.SUBSTRING_INITIAL ) );
637                
638                cursor.before( indexEntry );
639            }
640            
641            int nbResults = 0;
642
643            MatchingRule rule = attributeType.getSubstring();
644
645            if ( rule == null )
646            {
647                rule = attributeType.getEquality();
648            }
649
650            Normalizer normalizer;
651            Pattern regexp;
652
653            if ( rule != null )
654            {
655                normalizer = rule.getNormalizer();
656            }
657            else
658            {
659                normalizer = new NoOpNormalizer( attributeType.getSyntaxOid() );
660            }
661
662            // compile the regular expression to search for a matching attribute
663            // if the attributeType is humanReadable
664            if ( attributeType.getSyntax().isHumanReadable() )
665            {
666                regexp = node.getRegex( normalizer );
667            }
668            else
669            {
670                regexp = null;
671            }
672
673            Set<String> uuidSet = searchResult.getCandidateSet();
674
675            if ( regexp == null )
676            {
677                return nbResults;
678            }
679            
680            // And loop on it
681            while ( cursor.next() )
682            {
683                indexEntry = cursor.get();
684
685                String key = indexEntry.getKey();
686
687                boolean matched = regexp.matcher( key ).matches();
688                
689                if ( !fullIndexScan && !matched )
690                {
691                    cursor.close();
692
693                    return nbResults;
694                }
695
696                if ( !matched )
697                {
698                    continue;
699                }
700                
701                String uuid = indexEntry.getId();
702
703                boolean added = uuidSet.add( uuid );
704                
705                // if the UUID was added increment the result count
706                if ( added )
707                {
708                    nbResults++;
709                }
710            }
711
712            cursor.close();
713
714            return nbResults;
715        }
716        else
717        {
718            // No index, we will have to do a full scan
719            return Long.MAX_VALUE;
720        }
721    }
722
723
724    /**
725     * Creates a OrCursor over a disjunction expression branch node.
726     *
727     * @param node the disjunction expression branch node
728     * @return Cursor over candidates satisfying disjunction expression
729     * @throws Exception on db access failures
730     */
731    private long computeOr( PartitionTxn partitionTxn, OrNode node, PartitionSearchResult searchResult ) 
732        throws LdapException, IndexNotFoundException, CursorException, IOException
733    {
734        List<ExprNode> children = node.getChildren();
735
736        long nbOrResults = 0;
737
738        // Recursively create Cursors and Evaluators for each child expression node
739        for ( ExprNode child : children )
740        {
741            Object count = child.get( "count" );
742
743            if ( ( count != null ) && ( ( Long ) count == 0L ) )
744            {
745                long countLong = ( Long ) count;
746
747                if ( countLong == 0 )
748                {
749                    // We can skip the cursor, it will not return any candidate
750                    continue;
751                }
752                else if ( countLong == Long.MAX_VALUE )
753                {
754                    // We can stop here, we will anyway do a full scan
755                    return countLong;
756                }
757            }
758
759            long nbResults = build( partitionTxn, child, searchResult );
760
761            if ( nbResults == Long.MAX_VALUE )
762            {
763                // We can stop here, we will anyway do a full scan
764                return nbResults;
765            }
766            else
767            {
768                nbOrResults += nbResults;
769            }
770        }
771
772        return nbOrResults;
773    }
774
775
776    /**
777     * Creates an AndCursor over a conjunction expression branch node.
778     *
779     * @param node a conjunction expression branch node
780     * @return Cursor over the conjunction expression
781     * @throws Exception on db access failures
782     */
783    private long computeAnd( PartitionTxn partitionTxn, AndNode node, PartitionSearchResult searchResult ) 
784        throws LdapException, IndexNotFoundException, CursorException, IOException
785    {
786        int minIndex = 0;
787        long minValue = Long.MAX_VALUE;
788        long value = Long.MAX_VALUE;
789
790        /*
791         * We scan the child nodes of a branch node searching for the child
792         * expression node with the smallest scan count.  This is the child
793         * we will use for iteration
794         */
795        final List<ExprNode> children = node.getChildren();
796
797        for ( int i = 0; i < children.size(); i++ )
798        {
799            ExprNode child = children.get( i );
800            Object count = child.get( "count" );
801
802            if ( count == null )
803            {
804                continue;
805            }
806
807            value = ( Long ) count;
808
809            if ( value == 0L )
810            {
811                // No need to go any further : we won't have matching candidates anyway
812                return 0L;
813            }
814
815            if ( value < minValue )
816            {
817                minValue = value;
818                minIndex = i;
819            }
820        }
821
822        // Once found we return the number of candidates for this child
823        ExprNode minChild = children.get( minIndex );
824
825        return build( partitionTxn, minChild, searchResult );
826    }
827
828
829    /**
830     * Creates an AndCursor over a conjunction expression branch node.
831     *
832     * @param node a conjunction expression branch node
833     * @return Cursor over the conjunction expression
834     * @throws Exception on db access failures
835     */
836    private long computeNot( NotNode node, PartitionSearchResult searchResult )
837    {
838        final List<ExprNode> children = node.getChildren();
839
840        ExprNode child = children.get( 0 );
841        Object count = child.get( "count" );
842
843        if ( count == null )
844        {
845            return Long.MAX_VALUE;
846        }
847
848        long value = ( Long ) count;
849
850        if ( value == Long.MAX_VALUE )
851        {
852            // No need to go any further : we won't have matching candidates anyway
853            return 0L;
854        }
855
856        return Long.MAX_VALUE;
857    }
858}