blob: 8cda4bbd3c6e5ccf21c4dbc8e19d32274054b936 [file] [log] [blame]
/*
* 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 org.apache.directory.shared.ldap.filter.AndNode;
import org.apache.directory.shared.ldap.filter.ApproximateNode;
import org.apache.directory.shared.ldap.filter.AssertionNode;
import org.apache.directory.shared.ldap.filter.BranchNode;
import org.apache.directory.shared.ldap.filter.EqualityNode;
import org.apache.directory.shared.ldap.filter.ExprNode;
import org.apache.directory.shared.ldap.filter.ExtensibleNode;
import org.apache.directory.shared.ldap.filter.GreaterEqNode;
import org.apache.directory.shared.ldap.filter.LeafNode;
import org.apache.directory.shared.ldap.filter.LessEqNode;
import org.apache.directory.shared.ldap.filter.NotNode;
import org.apache.directory.shared.ldap.filter.OrNode;
import org.apache.directory.shared.ldap.filter.PresenceNode;
import org.apache.directory.shared.ldap.filter.ScopeNode;
import org.apache.directory.shared.ldap.filter.SimpleNode;
import org.apache.directory.shared.ldap.filter.SubstringNode;
import org.apache.directory.server.xdbm.Index;
import org.apache.directory.server.xdbm.search.Optimizer;
import org.apache.directory.server.xdbm.Store;
/**
* Optimizer that annotates the filter using scan counts.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$
*/
public class DefaultOptimizer<E> implements Optimizer
{
/** the database this optimizer operates on */
private final Store<E> db;
private final Long contextEntryId;
/**
* Creates an optimizer on a database.
*
* @param db the database this optimizer works for.
*/
public DefaultOptimizer( Store<E> db )
{
this.db = db;
this.contextEntryId = db.getContextEntryId();
}
/**
* Annotates the expression tree to determine optimal evaluation order based
* on the scan count for indices that exist for each expression node. If an
* index on the attribute does not exist an IndexNotFoundException will be
* thrown.
*
* @see org.apache.directory.server.xdbm.search.Optimizer#annotate(ExprNode)
*/
public Long annotate( ExprNode node ) throws Exception
{
// Start off with the worst case unless scan count says otherwise.
Long count = Long.MAX_VALUE;
/* --------------------------------------------------------------------
* H A N D L E L E A F N O D E S
* --------------------------------------------------------------------
*
* Each leaf node is based on an attribute and it represents a condition
* that needs to be statisfied. We ask the index (if one exists) for
* the attribute to give us a scan count of all the candidates that
* would satisfy the attribute assertion represented by the leaf node.
*
* This is conducted differently based on the type of the leaf node.
* Comments on each node type explain how each scan count is arrived at.
*/
if ( node instanceof ScopeNode )
{
count = getScopeScan( ( ScopeNode ) node );
}
else if ( node instanceof AssertionNode )
{
/*
* Leave it up to the assertion node to determine just how much it
* will cost us. Anyway it defaults to a maximum scan count if a
* scan count is not specified by the implementation.
*/
}
else if ( node.isLeaf() )
{
LeafNode leaf = ( LeafNode ) node;
if ( node instanceof PresenceNode )
{
count = getPresenceScan( ( PresenceNode ) leaf );
}
else if ( node instanceof EqualityNode )
{
count = getEqualityScan( ( EqualityNode ) leaf );
}
else if ( node instanceof GreaterEqNode )
{
count = getGreaterLessScan( ( GreaterEqNode ) leaf, SimpleNode.EVAL_GREATER );
}
else if ( node instanceof LessEqNode )
{
count = getGreaterLessScan( ( SimpleNode ) leaf, SimpleNode.EVAL_LESSER );
}
else if ( node instanceof SubstringNode )
{
/** Cannot really say so we presume the total index count */
count = getFullScan( leaf );
}
else if ( node instanceof ExtensibleNode )
{
/** Cannot really say so we presume the total index count */
count = getFullScan( leaf );
}
else if ( node instanceof ApproximateNode )
{
/** Feature not implemented so we just use equality matching */
count = getEqualityScan( ( ApproximateNode ) leaf );
}
else
{
throw new IllegalArgumentException( "Unrecognized leaf node" );
}
}
// --------------------------------------------------------------------
// H A N D L E B R A N C H N O D E S
// --------------------------------------------------------------------
else
{
if ( node instanceof AndNode )
{
count = getConjunctionScan( (AndNode)node );
}
else if ( node instanceof OrNode )
{
count = getDisjunctionScan( (OrNode)node );
}
else if ( node instanceof NotNode )
{
annotate( ( ( NotNode ) node ).getFirstChild() );
/*
* A negation filter is always worst case since we will have
* to retrieve all entries from the master table then test
* each one against the negated child filter. There is no way
* to use the indices.
*/
count = Long.MAX_VALUE;
}
else
{
throw new IllegalArgumentException( "Unrecognized branch node type" );
}
}
// Protect against overflow when counting.
if ( count < 0L )
{
count = Long.MAX_VALUE;
}
node.set( "count", count );
return count;
}
/**
* ANDs or Conjunctions take the count of the smallest child as their count.
* This is the best that a conjunction can do and should be used rather than
* the worst case. Notice that we annotate the child node with a recursive
* call before accessing its count parameter making the chain recursion
* depth first.
*
* @param node a AND (Conjunction) BranchNode
* @return the calculated scan count
* @throws Exception if there is an error
*/
private long getConjunctionScan( BranchNode node ) throws Exception
{
long count = Long.MAX_VALUE;
List<ExprNode> children = node.getChildren();
for ( ExprNode child : children )
{
annotate( child );
count = Math.min( ( ( Long ) child.get( "count" ) ), count );
}
return count;
}
/**
* Disjunctions (OR) are the union of candidates across all subexpressions
* so we add all the counts of the child nodes. Notice that we annotate the
* child node with a recursive call.
*
* @param node the OR branch node
* @return the scan count on the OR node
* @throws Exception if there is an error
*/
private long getDisjunctionScan( BranchNode node ) throws Exception
{
List<ExprNode> children = node.getChildren();
long total = 0L;
for ( ExprNode child : children )
{
annotate( child );
total += ( Long ) child.get( "count" );
}
return total;
}
/**
* Gets the worst case scan count for all entries that satisfy the equality
* assertion in the SimpleNode argument.
*
* @param node the node to get a scan count for
* @return the worst case
* @throws Exception if there is an error accessing an index
*/
private<V> long getEqualityScan( SimpleNode<V> node ) throws Exception
{
if ( db.hasUserIndexOn( node.getAttribute() ) )
{
//noinspection unchecked
Index<V,E> idx = ( Index<V, E> ) db.getUserIndex( node.getAttribute() );
return idx.count( node.getValue().get() );
}
// count for non-indexed attribute is unknown so we presume da worst
return Long.MAX_VALUE;
}
/**
* Gets a scan count of the nodes that satisfy the greater or less than test
* specified by the node.
*
* @param node the greater or less than node to get a count for
* @param isGreaterThan if true test is for >=, otherwise <=
* @return the scan count of all nodes satisfying the AVA
* @throws Exception if there is an error accessing an index
*/
private<V> long getGreaterLessScan( SimpleNode<V> node, boolean isGreaterThan ) throws Exception
{
if ( db.hasUserIndexOn( node.getAttribute() ) )
{
//noinspection unchecked
Index<V, E> idx = ( Index<V, E> ) db.getUserIndex( node.getAttribute() );
if ( isGreaterThan )
{
return idx.greaterThanCount( node.getValue().get() );
}
else
{
return idx.lessThanCount( node.getValue().get() );
}
}
// count for non-indexed attribute is unknown so we presume da worst
return Long.MAX_VALUE;
}
/**
* Gets the total number of entries within the database index if one is
* available otherwise the count of all the entries within the database is
* returned.
*
* @param node the leaf node to get a full scan count for
* @return the worst case full scan count
* @throws Exception if there is an error access database indices
*/
private long getFullScan( LeafNode node ) throws Exception
{
if ( db.hasUserIndexOn( node.getAttribute() ) )
{
Index idx = db.getUserIndex( node.getAttribute() );
return idx.count();
}
return Long.MAX_VALUE;
}
/**
* Gets the number of entries that would be returned by a presence node
* assertion. Leverages the existance system index for scan counts.
*
* @param node the presence node
* @return the number of entries matched for the presence of an attribute
* @throws Exception if errors result
*/
private long getPresenceScan( PresenceNode node ) throws Exception
{
if ( db.hasUserIndexOn( node.getAttribute() ) )
{
Index<String,E> idx = db.getPresenceIndex();
return idx.count( node.getAttribute() );
}
return Long.MAX_VALUE;
}
/**
* Gets the scan count for the scope node attached to this filter.
*
* @param node the ScopeNode
* @return the scan count for scope
* @throws Exception if any errors result
*/
private long getScopeScan( ScopeNode node ) throws Exception
{
Long id = db.getEntryId( node.getBaseDn() );
switch ( node.getScope() )
{
case OBJECT:
return 1L;
case ONELEVEL:
return db.getChildCount( id );
case SUBTREE:
if ( id.longValue() == contextEntryId.longValue() )
{
return db.count();
}
else
{
return db.getSubLevelIndex().count( id );
}
default:
throw new IllegalArgumentException( "Unrecognized search scope " + "value for filter scope node" );
}
}
}