| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.server.xdbm.search.impl; |
| |
| |
| import java.util.List; |
| import java.util.Set; |
| import java.util.regex.Pattern; |
| |
| import org.apache.directory.api.ldap.model.cursor.Cursor; |
| import org.apache.directory.api.ldap.model.entry.Entry; |
| import org.apache.directory.api.ldap.model.entry.Value; |
| import org.apache.directory.api.ldap.model.filter.AndNode; |
| import org.apache.directory.api.ldap.model.filter.ApproximateNode; |
| import org.apache.directory.api.ldap.model.filter.EqualityNode; |
| import org.apache.directory.api.ldap.model.filter.ExprNode; |
| import org.apache.directory.api.ldap.model.filter.GreaterEqNode; |
| import org.apache.directory.api.ldap.model.filter.LessEqNode; |
| import org.apache.directory.api.ldap.model.filter.NotNode; |
| import org.apache.directory.api.ldap.model.filter.OrNode; |
| import org.apache.directory.api.ldap.model.filter.PresenceNode; |
| import org.apache.directory.api.ldap.model.filter.ScopeNode; |
| import org.apache.directory.api.ldap.model.filter.SubstringNode; |
| import org.apache.directory.api.ldap.model.message.SearchScope; |
| import org.apache.directory.api.ldap.model.name.Dn; |
| import org.apache.directory.api.ldap.model.name.Rdn; |
| import org.apache.directory.api.ldap.model.schema.AttributeType; |
| import org.apache.directory.api.ldap.model.schema.MatchingRule; |
| import org.apache.directory.api.ldap.model.schema.Normalizer; |
| import org.apache.directory.api.ldap.model.schema.normalizers.NoOpNormalizer; |
| import org.apache.directory.api.util.exception.NotImplementedException; |
| import org.apache.directory.server.core.api.partition.Partition; |
| import org.apache.directory.server.i18n.I18n; |
| import org.apache.directory.server.xdbm.Index; |
| import org.apache.directory.server.xdbm.IndexEntry; |
| import org.apache.directory.server.xdbm.ParentIdAndRdn; |
| import org.apache.directory.server.xdbm.SingletonIndexCursor; |
| import org.apache.directory.server.xdbm.Store; |
| import org.apache.directory.server.xdbm.search.PartitionSearchResult; |
| import org.apache.directory.server.xdbm.search.cursor.ApproximateCursor; |
| import org.apache.directory.server.xdbm.search.cursor.ChildrenCursor; |
| import org.apache.directory.server.xdbm.search.cursor.DescendantCursor; |
| import org.apache.directory.server.xdbm.search.evaluator.ApproximateEvaluator; |
| |
| |
| /** |
| * Builds Cursors over candidates that satisfy a filter expression. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class CursorBuilder |
| { |
| /** The database used by this builder */ |
| private Store db = null; |
| |
| /** Evaluator dependency on a EvaluatorBuilder */ |
| private EvaluatorBuilder evaluatorBuilder; |
| |
| |
| /** |
| * Creates an expression tree enumerator. |
| * |
| * @param db database used by this enumerator |
| * @param evaluatorBuilder the evaluator builder |
| */ |
| public CursorBuilder( Store db, EvaluatorBuilder evaluatorBuilder ) |
| { |
| this.db = db; |
| this.evaluatorBuilder = evaluatorBuilder; |
| } |
| |
| |
| public <T> long build( ExprNode node, PartitionSearchResult searchResult ) throws Exception |
| { |
| Object count = node.get( "count" ); |
| |
| if ( ( count != null ) && ( ( Long ) count ) == 0L ) |
| { |
| return 0; |
| } |
| |
| switch ( node.getAssertionType() ) |
| { |
| /* ---------- LEAF NODE HANDLING ---------- */ |
| |
| case APPROXIMATE: |
| return computeApproximate( ( ApproximateNode<T> ) node, searchResult ); |
| |
| case EQUALITY: |
| return computeEquality( ( EqualityNode<T> ) node, searchResult ); |
| |
| case GREATEREQ: |
| return computeGreaterEq( ( GreaterEqNode<T> ) node, searchResult ); |
| |
| case LESSEQ: |
| return computeLessEq( ( LessEqNode<T> ) node, searchResult ); |
| |
| case PRESENCE: |
| return computePresence( ( PresenceNode ) node, searchResult ); |
| |
| case SCOPE: |
| if ( ( ( ScopeNode ) node ).getScope() == SearchScope.ONELEVEL ) |
| { |
| return computeOneLevelScope( ( ScopeNode ) node, searchResult ); |
| } |
| else |
| { |
| return computeSubLevelScope( ( ScopeNode ) node, searchResult ); |
| } |
| |
| case SUBSTRING: |
| return computeSubstring( ( SubstringNode ) node, searchResult ); |
| |
| /* ---------- LOGICAL OPERATORS ---------- */ |
| |
| case AND: |
| return computeAnd( ( AndNode ) node, searchResult ); |
| |
| case NOT: |
| // Always return infinite, except if the resulting eva |
| return computeNot( ( NotNode ) node, searchResult ); |
| |
| case OR: |
| return computeOr( ( OrNode ) node, searchResult ); |
| |
| /* ---------- NOT IMPLEMENTED ---------- */ |
| |
| case ASSERTION: |
| case EXTENSIBLE: |
| throw new NotImplementedException(); |
| |
| default: |
| throw new IllegalStateException( I18n.err( I18n.ERR_260, node.getAssertionType() ) ); |
| } |
| } |
| |
| |
| /** |
| * Computes the set of candidates for an Approximate filter. We will feed the set only if |
| * we have an index for the AT. |
| */ |
| |
| private <T> long computeApproximate( ApproximateNode<T> node, PartitionSearchResult searchResult ) |
| throws Exception |
| { |
| ApproximateCursor<T> cursor = new ApproximateCursor<T>( db, |
| ( ApproximateEvaluator<T> ) evaluatorBuilder |
| .build( node ) ); |
| |
| int nbResults = 0; |
| Set<String> uuidSet = searchResult.getCandidateSet(); |
| |
| while ( cursor.next() ) |
| { |
| IndexEntry<T, String> indexEntry = cursor.get(); |
| |
| String uuid = indexEntry.getId(); |
| |
| if ( !uuidSet.contains( uuid ) ) |
| { |
| uuidSet.add( uuid ); |
| nbResults++; |
| } |
| } |
| |
| cursor.close(); |
| |
| return nbResults; |
| } |
| |
| |
| /** |
| * Computes the set of candidates for an Equality filter. We will feed the set only if |
| * we have an index for the AT. |
| */ |
| private <T> long computeEquality( EqualityNode<T> node, PartitionSearchResult searchResult ) |
| throws Exception |
| { |
| Set<String> thisCandidates = ( Set<String> ) node.get( DefaultOptimizer.CANDIDATES_ANNOTATION_KEY ); |
| |
| if ( thisCandidates != null ) |
| { |
| Set<String> candidates = searchResult.getCandidateSet(); |
| |
| for ( String candidate : thisCandidates ) |
| { |
| candidates.add( candidate ); |
| } |
| |
| return thisCandidates.size(); |
| } |
| |
| AttributeType attributeType = node.getAttributeType(); |
| Value<T> value = node.getValue(); |
| int nbResults = 0; |
| |
| // Fetch all the UUIDs if we have an index |
| if ( db.hasIndexOn( attributeType ) ) |
| { |
| // Get the cursor using the index |
| Index<T, String> userIndex = ( Index<T, String> ) db.getIndex( attributeType ); |
| Cursor<IndexEntry<T, String>> userIdxCursor = userIndex.forwardCursor( value.getValue() ); |
| Set<String> uuidSet = searchResult.getCandidateSet(); |
| |
| // And loop on it |
| while ( userIdxCursor.next() ) |
| { |
| IndexEntry<T, String> indexEntry = userIdxCursor.get(); |
| |
| String uuid = indexEntry.getId(); |
| |
| if ( !uuidSet.contains( uuid ) ) |
| { |
| // The UUID is not present in the Set, we add it |
| uuidSet.add( uuid ); |
| nbResults++; |
| } |
| } |
| |
| userIdxCursor.close(); |
| } |
| else |
| { |
| // No index, we will have to do a full scan |
| return Long.MAX_VALUE; |
| } |
| |
| return nbResults; |
| } |
| |
| |
| /** |
| * Computes the set of candidates for an GreateEq filter. We will feed the set only if |
| * we have an index for the AT. |
| */ |
| private <T> long computeGreaterEq( GreaterEqNode<T> node, PartitionSearchResult searchResult ) |
| throws Exception |
| { |
| AttributeType attributeType = node.getAttributeType(); |
| Value<T> value = node.getValue(); |
| int nbResults = 0; |
| |
| // Fetch all the UUIDs if we have an index |
| if ( db.hasIndexOn( attributeType ) ) |
| { |
| // Get the cursor using the index |
| Index<T, String> userIndex = ( Index<T, String> ) db.getIndex( attributeType ); |
| Cursor<IndexEntry<T, String>> userIdxCursor = userIndex.forwardCursor(); |
| |
| // Position the index on the element we should start from |
| IndexEntry<T, String> indexEntry = new IndexEntry<T, String>(); |
| indexEntry.setKey( value.getValue() ); |
| |
| userIdxCursor.before( indexEntry ); |
| Set<String> uuidSet = searchResult.getCandidateSet(); |
| |
| // And loop on it |
| while ( userIdxCursor.next() ) |
| { |
| indexEntry = userIdxCursor.get(); |
| |
| String uuid = indexEntry.getId(); |
| |
| if ( !uuidSet.contains( uuid ) ) |
| { |
| // The UUID is not present in the Set, we add it |
| uuidSet.add( uuid ); |
| nbResults++; |
| } |
| } |
| |
| userIdxCursor.close(); |
| } |
| else |
| { |
| // No index, we will have to do a full scan |
| return Long.MAX_VALUE; |
| } |
| |
| return nbResults; |
| } |
| |
| |
| /** |
| * Computes the set of candidates for an LessEq filter. We will feed the set only if |
| * we have an index for the AT. |
| */ |
| private <T> long computeLessEq( LessEqNode<T> node, PartitionSearchResult searchResult ) |
| throws Exception |
| { |
| AttributeType attributeType = node.getAttributeType(); |
| Value<T> value = node.getValue(); |
| int nbResults = 0; |
| |
| // Fetch all the UUIDs if we have an index |
| if ( db.hasIndexOn( attributeType ) ) |
| { |
| // Get the cursor using the index |
| Index<T, String> userIndex = ( Index<T, String> ) db.getIndex( attributeType ); |
| Cursor<IndexEntry<T, String>> userIdxCursor = userIndex.forwardCursor(); |
| |
| // Position the index on the element we should start from |
| IndexEntry<T, String> indexEntry = new IndexEntry<T, String>(); |
| indexEntry.setKey( value.getValue() ); |
| |
| userIdxCursor.after( indexEntry ); |
| Set<String> uuidSet = searchResult.getCandidateSet(); |
| |
| // And loop on it |
| while ( userIdxCursor.previous() ) |
| { |
| indexEntry = userIdxCursor.get(); |
| |
| String uuid = indexEntry.getId(); |
| |
| if ( !uuidSet.contains( uuid ) ) |
| { |
| // The UUID is not present in the Set, we add it |
| uuidSet.add( uuid ); |
| nbResults++; |
| } |
| } |
| |
| userIdxCursor.close(); |
| } |
| else |
| { |
| // No index, we will have to do a full scan |
| return Long.MAX_VALUE; |
| } |
| |
| return nbResults; |
| } |
| |
| |
| /** |
| * Computes the set of candidates for a Presence filter. We will feed the set only if |
| * we have an index for the AT. |
| */ |
| private <T> long computePresence( PresenceNode node, PartitionSearchResult searchResult ) |
| throws Exception |
| { |
| AttributeType attributeType = node.getAttributeType(); |
| int nbResults = 0; |
| |
| // Fetch all the UUIDs if we have an index |
| if ( db.hasIndexOn( attributeType ) ) |
| { |
| // Get the cursor using the index |
| Cursor<IndexEntry<String, String>> presenceCursor = db.getPresenceIndex().forwardCursor( |
| attributeType.getOid() ); |
| |
| // Position the index on the element we should start from |
| IndexEntry<String, String> indexEntry = new IndexEntry<String, String>(); |
| Set<String> uuidSet = searchResult.getCandidateSet(); |
| |
| // And loop on it |
| while ( presenceCursor.next() ) |
| { |
| indexEntry = presenceCursor.get(); |
| |
| String uuid = indexEntry.getId(); |
| |
| if ( !uuidSet.contains( uuid ) ) |
| { |
| // The UUID is not present in the Set, we add it |
| uuidSet.add( uuid ); |
| nbResults++; |
| } |
| } |
| |
| presenceCursor.close(); |
| } |
| else |
| { |
| // No index, we will have to do a full scan |
| return Long.MAX_VALUE; |
| } |
| |
| return nbResults; |
| } |
| |
| |
| /** |
| * Computes the set of candidates for a OneLevelScope filter. We will feed the set only if |
| * we have an index for the AT. |
| */ |
| private long computeOneLevelScope( ScopeNode node, PartitionSearchResult searchResult ) |
| throws Exception |
| { |
| int nbResults = 0; |
| |
| // We use the RdnIndex to get all the entries from a starting point |
| // and below up to the number of children |
| Cursor<IndexEntry<ParentIdAndRdn, String>> rdnCursor = db.getRdnIndex().forwardCursor(); |
| |
| IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<ParentIdAndRdn, String>(); |
| startingPos.setKey( new ParentIdAndRdn( node.getBaseId(), ( Rdn[] ) null ) ); |
| rdnCursor.before( startingPos ); |
| |
| Cursor<IndexEntry<String, String>> scopeCursor = new ChildrenCursor( db, node.getBaseId(), rdnCursor ); |
| Set<String> candidateSet = searchResult.getCandidateSet(); |
| |
| // Fetch all the UUIDs if we have an index |
| // And loop on it |
| while ( scopeCursor.next() ) |
| { |
| IndexEntry<String, String> indexEntry = scopeCursor.get(); |
| |
| String uuid = indexEntry.getId(); |
| |
| // If the entry is an alias, and we asked for it to be dereferenced, |
| // we will dereference the alias |
| if ( searchResult.isDerefAlways() || searchResult.isDerefInSearching() ) |
| { |
| Dn aliasedDn = db.getAliasIndex().reverseLookup( uuid ); |
| |
| if ( aliasedDn != null ) |
| { |
| aliasedDn.apply( evaluatorBuilder.getSchemaManager() ); |
| String aliasedId = db.getEntryId( aliasedDn ); |
| |
| // This is an alias. Add it to the set of candidates to process, if it's not already |
| // present in the candidate set |
| if ( !candidateSet.contains( aliasedId ) ) |
| { |
| candidateSet.add( aliasedId ); |
| nbResults++; |
| } |
| } |
| else |
| { |
| // This is not an alias |
| if ( !candidateSet.contains( uuid ) ) |
| { |
| // The UUID is not present in the Set, we add it |
| candidateSet.add( uuid ); |
| nbResults++; |
| } |
| } |
| } |
| else |
| { |
| if ( !candidateSet.contains( uuid ) ) |
| { |
| // The UUID is not present in the Set, we add it |
| candidateSet.add( uuid ); |
| nbResults++; |
| } |
| } |
| } |
| |
| scopeCursor.close(); |
| |
| return nbResults; |
| } |
| |
| |
| /** |
| * Computes the set of candidates for a SubLevelScope filter. We will feed the set only if |
| * we have an index for the AT. |
| */ |
| private long computeSubLevelScope( ScopeNode node, PartitionSearchResult searchResult ) |
| throws Exception |
| { |
| // If we are searching from the partition DN, better get out. |
| String contextEntryId = db.getEntryId( ( ( Partition ) db ).getSuffixDn() ); |
| |
| if ( node.getBaseId() == contextEntryId ) |
| { |
| return Long.MAX_VALUE; |
| } |
| |
| int nbResults = 0; |
| |
| // We use the RdnIndex to get all the entries from a starting point |
| // and below up to the number of descendant |
| String baseId = node.getBaseId(); |
| ParentIdAndRdn parentIdAndRdn = db.getRdnIndex().reverseLookup( baseId ); |
| IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<ParentIdAndRdn, String>(); |
| |
| startingPos.setKey( parentIdAndRdn ); |
| startingPos.setId( baseId ); |
| |
| Cursor<IndexEntry<ParentIdAndRdn, String>> rdnCursor = new SingletonIndexCursor<ParentIdAndRdn>( |
| startingPos ); |
| String parentId = parentIdAndRdn.getParentId(); |
| |
| Cursor<IndexEntry<String, String>> scopeCursor = new DescendantCursor( db, baseId, parentId, rdnCursor ); |
| Set<String> candidateSet = searchResult.getCandidateSet(); |
| |
| // Fetch all the UUIDs if we have an index |
| // And loop on it |
| while ( scopeCursor.next() ) |
| { |
| IndexEntry<String, String> indexEntry = scopeCursor.get(); |
| |
| String uuid = indexEntry.getId(); |
| |
| // If the entry is an alias, and we asked for it to be dereferenced, |
| // we will dereference the alias |
| if ( searchResult.isDerefAlways() || searchResult.isDerefInSearching() ) |
| { |
| Dn aliasedDn = db.getAliasIndex().reverseLookup( uuid ); |
| |
| if ( aliasedDn != null ) |
| { |
| aliasedDn.apply( evaluatorBuilder.getSchemaManager() ); |
| String aliasedId = db.getEntryId( aliasedDn ); |
| |
| // This is an alias. Add it to the set of candidates to process, if it's not already |
| // present in the candidate set |
| if ( !candidateSet.contains( aliasedId ) ) |
| { |
| candidateSet.add( aliasedId ); |
| nbResults++; |
| |
| ScopeNode newScopeNode = new ScopeNode( |
| node.getDerefAliases(), |
| aliasedDn, |
| aliasedId, |
| node.getScope() ); |
| |
| nbResults += computeSubLevelScope( newScopeNode, searchResult ); |
| } |
| } |
| else |
| { |
| // This is not an alias |
| if ( !candidateSet.contains( uuid ) ) |
| { |
| // The UUID is not present in the Set, we add it |
| candidateSet.add( uuid ); |
| nbResults++; |
| } |
| } |
| } |
| else |
| { |
| if ( !candidateSet.contains( uuid ) ) |
| { |
| // The UUID is not present in the Set, we add it |
| candidateSet.add( uuid ); |
| nbResults++; |
| } |
| } |
| } |
| |
| scopeCursor.close(); |
| |
| return nbResults; |
| } |
| |
| |
| /** |
| * Computes the set of candidates for an Substring filter. We will feed the set only if |
| * we have an index for the AT. |
| */ |
| private long computeSubstring( SubstringNode node, PartitionSearchResult searchResult ) |
| throws Exception |
| { |
| AttributeType attributeType = node.getAttributeType(); |
| |
| // Fetch all the UUIDs if we have an index |
| if ( db.hasIndexOn( attributeType ) ) |
| { |
| Index<String, String> userIndex = ( ( Index<String, String> ) db.getIndex( attributeType ) ); |
| Cursor<IndexEntry<String, String>> cursor = userIndex.forwardCursor(); |
| |
| // Position the index on the element we should start from |
| IndexEntry<String, String> indexEntry = new IndexEntry<String, String>(); |
| String initial = node.getInitial(); |
| |
| boolean fullIndexScan = false; |
| |
| if ( initial == null ) |
| { |
| fullIndexScan = true; |
| cursor.beforeFirst(); |
| } |
| else |
| { |
| indexEntry.setKey( initial ); |
| |
| cursor.before( indexEntry ); |
| } |
| |
| int nbResults = 0; |
| |
| MatchingRule rule = attributeType.getSubstring(); |
| |
| if ( rule == null ) |
| { |
| rule = attributeType.getEquality(); |
| } |
| |
| Normalizer normalizer; |
| Pattern regexp; |
| |
| if ( rule != null ) |
| { |
| normalizer = rule.getNormalizer(); |
| } |
| else |
| { |
| normalizer = new NoOpNormalizer( attributeType.getSyntaxOid() ); |
| } |
| |
| // compile the regular expression to search for a matching attribute |
| // if the attributeType is humanReadable |
| if ( attributeType.getSyntax().isHumanReadable() ) |
| { |
| regexp = node.getRegex( normalizer ); |
| } |
| else |
| { |
| regexp = null; |
| } |
| |
| Set<String> uuidSet = searchResult.getCandidateSet(); |
| |
| // And loop on it |
| while ( cursor.next() ) |
| { |
| indexEntry = cursor.get(); |
| |
| String key = indexEntry.getKey(); |
| |
| boolean matched = regexp.matcher( key ).matches(); |
| |
| if ( !fullIndexScan & !matched ) |
| { |
| cursor.close(); |
| |
| return nbResults; |
| } |
| |
| if ( !matched ) |
| { |
| continue; |
| } |
| |
| String uuid = indexEntry.getId(); |
| |
| if ( !uuidSet.contains( uuid ) ) |
| { |
| // The UUID is not present in the Set, we add it |
| uuidSet.add( uuid ); |
| nbResults++; |
| } |
| } |
| |
| cursor.close(); |
| |
| return nbResults; |
| } |
| else |
| { |
| // No index, we will have to do a full scan |
| return Long.MAX_VALUE; |
| } |
| } |
| |
| |
| /** |
| * Creates a OrCursor over a disjunction expression branch node. |
| * |
| * @param node the disjunction expression branch node |
| * @return Cursor over candidates satisfying disjunction expression |
| * @throws Exception on db access failures |
| */ |
| private <T> long computeOr( OrNode node, PartitionSearchResult searchResult ) throws Exception |
| { |
| List<ExprNode> children = node.getChildren(); |
| |
| long nbOrResults = 0; |
| |
| // Recursively create Cursors and Evaluators for each child expression node |
| for ( ExprNode child : children ) |
| { |
| Object count = child.get( "count" ); |
| |
| if ( ( count != null ) && ( ( Long ) count == 0L ) ) |
| { |
| long countLong = ( Long ) count; |
| |
| if ( countLong == 0 ) |
| { |
| // We can skip the cursor, it will not return any candidate |
| continue; |
| } |
| else if ( countLong == Long.MAX_VALUE ) |
| { |
| // We can stop here, we will anyway do a full scan |
| return countLong; |
| } |
| } |
| |
| long nbResults = build( child, searchResult ); |
| |
| if ( nbResults == Long.MAX_VALUE ) |
| { |
| // We can stop here, we will anyway do a full scan |
| return nbResults; |
| } |
| else |
| { |
| nbOrResults += nbResults; |
| } |
| } |
| |
| return nbOrResults; |
| } |
| |
| |
| /** |
| * Creates an AndCursor over a conjunction expression branch node. |
| * |
| * @param node a conjunction expression branch node |
| * @return Cursor over the conjunction expression |
| * @throws Exception on db access failures |
| */ |
| private long computeAnd( AndNode node, PartitionSearchResult searchResult ) throws Exception |
| { |
| int minIndex = 0; |
| long minValue = Long.MAX_VALUE; |
| long value = Long.MAX_VALUE; |
| |
| /* |
| * We scan the child nodes of a branch node searching for the child |
| * expression node with the smallest scan count. This is the child |
| * we will use for iteration |
| */ |
| final List<ExprNode> children = node.getChildren(); |
| |
| for ( int i = 0; i < children.size(); i++ ) |
| { |
| ExprNode child = children.get( i ); |
| Object count = child.get( "count" ); |
| |
| if ( count == null ) |
| { |
| continue; |
| } |
| |
| value = ( Long ) count; |
| |
| if ( value == 0L ) |
| { |
| // No need to go any further : we won't have matching candidates anyway |
| return 0L; |
| } |
| |
| if ( value < minValue ) |
| { |
| minValue = value; |
| minIndex = i; |
| } |
| } |
| |
| // Once found we return the number of candidates for this child |
| ExprNode minChild = children.get( minIndex ); |
| long nbResults = build( minChild, searchResult ); |
| |
| return nbResults; |
| } |
| |
| |
| /** |
| * Creates an AndCursor over a conjunction expression branch node. |
| * |
| * @param node a conjunction expression branch node |
| * @return Cursor over the conjunction expression |
| * @throws Exception on db access failures |
| */ |
| private long computeNot( NotNode node, PartitionSearchResult searchResult ) throws Exception |
| { |
| final List<ExprNode> children = node.getChildren(); |
| |
| ExprNode child = children.get( 0 ); |
| Object count = child.get( "count" ); |
| |
| if ( count == null ) |
| { |
| return Long.MAX_VALUE; |
| } |
| |
| long value = ( Long ) count; |
| |
| if ( value == Long.MAX_VALUE ) |
| { |
| // No need to go any further : we won't have matching candidates anyway |
| return 0L; |
| } |
| |
| return Long.MAX_VALUE; |
| } |
| } |