001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.directory.server.xdbm.search.impl; 021 022 023import java.io.IOException; 024import java.util.List; 025import java.util.Set; 026import java.util.regex.Pattern; 027 028import org.apache.directory.api.ldap.model.cursor.Cursor; 029import org.apache.directory.api.ldap.model.cursor.CursorException; 030import org.apache.directory.api.ldap.model.entry.Value; 031import org.apache.directory.api.ldap.model.exception.LdapException; 032import org.apache.directory.api.ldap.model.exception.LdapOtherException; 033import org.apache.directory.api.ldap.model.filter.AndNode; 034import org.apache.directory.api.ldap.model.filter.ApproximateNode; 035import org.apache.directory.api.ldap.model.filter.EqualityNode; 036import org.apache.directory.api.ldap.model.filter.ExprNode; 037import org.apache.directory.api.ldap.model.filter.GreaterEqNode; 038import org.apache.directory.api.ldap.model.filter.LessEqNode; 039import org.apache.directory.api.ldap.model.filter.NotNode; 040import org.apache.directory.api.ldap.model.filter.OrNode; 041import org.apache.directory.api.ldap.model.filter.PresenceNode; 042import org.apache.directory.api.ldap.model.filter.ScopeNode; 043import org.apache.directory.api.ldap.model.filter.SubstringNode; 044import org.apache.directory.api.ldap.model.message.SearchScope; 045import org.apache.directory.api.ldap.model.name.Dn; 046import org.apache.directory.api.ldap.model.name.Rdn; 047import org.apache.directory.api.ldap.model.schema.AttributeType; 048import org.apache.directory.api.ldap.model.schema.MatchingRule; 049import org.apache.directory.api.ldap.model.schema.Normalizer; 050import org.apache.directory.api.ldap.model.schema.PrepareString; 051import org.apache.directory.api.ldap.model.schema.normalizers.NoOpNormalizer; 052import org.apache.directory.api.util.exception.NotImplementedException; 053import org.apache.directory.server.core.api.partition.Partition; 054import org.apache.directory.server.core.api.partition.PartitionTxn; 055import org.apache.directory.server.i18n.I18n; 056import org.apache.directory.server.xdbm.Index; 057import org.apache.directory.server.xdbm.IndexEntry; 058import org.apache.directory.server.xdbm.IndexNotFoundException; 059import org.apache.directory.server.xdbm.ParentIdAndRdn; 060import org.apache.directory.server.xdbm.SingletonIndexCursor; 061import org.apache.directory.server.xdbm.Store; 062import org.apache.directory.server.xdbm.search.PartitionSearchResult; 063import org.apache.directory.server.xdbm.search.cursor.ApproximateCursor; 064import org.apache.directory.server.xdbm.search.cursor.ChildrenCursor; 065import org.apache.directory.server.xdbm.search.cursor.DescendantCursor; 066import org.apache.directory.server.xdbm.search.evaluator.ApproximateEvaluator; 067 068 069/** 070 * Builds Cursors over candidates that satisfy a filter expression. 071 * 072 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 073 */ 074public class CursorBuilder 075{ 076 /** The database used by this builder */ 077 private Store db = null; 078 079 /** Evaluator dependency on a EvaluatorBuilder */ 080 private EvaluatorBuilder evaluatorBuilder; 081 082 083 /** 084 * Creates an expression tree enumerator. 085 * 086 * @param db database used by this enumerator 087 * @param evaluatorBuilder the evaluator builder 088 */ 089 public CursorBuilder( Store db, EvaluatorBuilder evaluatorBuilder ) 090 { 091 this.db = db; 092 this.evaluatorBuilder = evaluatorBuilder; 093 } 094 095 096 public <T> long build( PartitionTxn partitionTxn, ExprNode node, PartitionSearchResult searchResult ) throws LdapException 097 { 098 Object count = node.get( DefaultOptimizer.COUNT_ANNOTATION ); 099 100 if ( ( count != null ) && ( ( Long ) count ) == 0L ) 101 { 102 return 0; 103 } 104 105 try 106 { 107 switch ( node.getAssertionType() ) 108 { 109 /* ---------- LEAF NODE HANDLING ---------- */ 110 111 case APPROXIMATE: 112 return computeApproximate( partitionTxn, ( ApproximateNode<T> ) node, searchResult ); 113 114 case EQUALITY: 115 return computeEquality( partitionTxn, ( EqualityNode<T> ) node, searchResult ); 116 117 case GREATEREQ: 118 return computeGreaterEq( partitionTxn, ( GreaterEqNode<T> ) node, searchResult ); 119 120 case LESSEQ: 121 return computeLessEq( partitionTxn, ( LessEqNode<T> ) node, searchResult ); 122 123 case PRESENCE: 124 return computePresence( partitionTxn, ( PresenceNode ) node, searchResult ); 125 126 case SCOPE: 127 if ( ( ( ScopeNode ) node ).getScope() == SearchScope.ONELEVEL ) 128 { 129 return computeOneLevelScope( partitionTxn, ( ScopeNode ) node, searchResult ); 130 } 131 else 132 { 133 return computeSubLevelScope( partitionTxn, ( ScopeNode ) node, searchResult ); 134 } 135 136 case SUBSTRING: 137 return computeSubstring( partitionTxn, ( SubstringNode ) node, searchResult ); 138 139 /* ---------- LOGICAL OPERATORS ---------- */ 140 141 case AND: 142 return computeAnd( partitionTxn, ( AndNode ) node, searchResult ); 143 144 case NOT: 145 // Always return infinite, except if the resulting eva 146 return computeNot( ( NotNode ) node, searchResult ); 147 148 case OR: 149 return computeOr( partitionTxn, ( OrNode ) node, searchResult ); 150 151 /* ---------- NOT IMPLEMENTED ---------- */ 152 153 case ASSERTION: 154 case EXTENSIBLE: 155 throw new NotImplementedException(); 156 157 default: 158 throw new IllegalStateException( I18n.err( I18n.ERR_260, node.getAssertionType() ) ); 159 } 160 } 161 catch ( IndexNotFoundException | CursorException | IOException e ) 162 { 163 throw new LdapOtherException( e.getMessage(), e ); 164 } 165 } 166 167 168 /** 169 * Computes the set of candidates for an Approximate filter. We will feed the set only if 170 * we have an index for the AT. 171 */ 172 173 private <T> long computeApproximate( PartitionTxn partitionTxn, ApproximateNode<T> node, PartitionSearchResult searchResult ) 174 throws LdapException, IndexNotFoundException, CursorException, IOException 175 { 176 ApproximateCursor<T> cursor = new ApproximateCursor<>( partitionTxn, db, 177 ( ApproximateEvaluator<T> ) evaluatorBuilder 178 .build( partitionTxn, node ) ); 179 180 int nbResults = 0; 181 Set<String> uuidSet = searchResult.getCandidateSet(); 182 183 while ( cursor.next() ) 184 { 185 IndexEntry<T, String> indexEntry = cursor.get(); 186 187 String uuid = indexEntry.getId(); 188 boolean added = uuidSet.add( uuid ); 189 190 // if the UUID was added increment the result count 191 if ( added ) 192 { 193 nbResults++; 194 } 195 } 196 197 cursor.close(); 198 199 return nbResults; 200 } 201 202 203 /** 204 * Computes the set of candidates for an Equality filter. We will feed the set only if 205 * we have an index for the AT. 206 */ 207 private <T> long computeEquality( PartitionTxn partitionTxn, EqualityNode<T> node, PartitionSearchResult searchResult ) 208 throws LdapException, IndexNotFoundException, CursorException, IOException 209 { 210 Set<String> thisCandidates = ( Set<String> ) node.get( DefaultOptimizer.CANDIDATES_ANNOTATION_KEY ); 211 212 if ( thisCandidates != null ) 213 { 214 Set<String> candidates = searchResult.getCandidateSet(); 215 216 for ( String candidate : thisCandidates ) 217 { 218 candidates.add( candidate ); 219 } 220 221 return thisCandidates.size(); 222 } 223 224 AttributeType attributeType = node.getAttributeType(); 225 Value value = node.getValue(); 226 int nbResults = 0; 227 228 // Fetch all the UUIDs if we have an index 229 if ( db.hasIndexOn( attributeType ) ) 230 { 231 // Get the cursor using the index 232 Index<T, String> userIndex = ( Index<T, String> ) db.getIndex( attributeType ); 233 Cursor<IndexEntry<T, String>> userIdxCursor = userIndex.forwardCursor( partitionTxn, ( T ) value.getNormalized() ); 234 Set<String> uuidSet = searchResult.getCandidateSet(); 235 236 // And loop on it 237 while ( userIdxCursor.next() ) 238 { 239 IndexEntry<T, String> indexEntry = userIdxCursor.get(); 240 241 String uuid = indexEntry.getId(); 242 boolean added = uuidSet.add( uuid ); 243 244 // if the UUID was added increment the result count 245 if ( added ) 246 { 247 nbResults++; 248 } 249 } 250 251 userIdxCursor.close(); 252 } 253 else 254 { 255 // No index, we will have to do a full scan 256 return Long.MAX_VALUE; 257 } 258 259 return nbResults; 260 } 261 262 263 /** 264 * Computes the set of candidates for an GreateEq filter. We will feed the set only if 265 * we have an index for the AT. 266 */ 267 private <T> long computeGreaterEq( PartitionTxn partitionTxn, GreaterEqNode<T> node, PartitionSearchResult searchResult ) 268 throws LdapException, IndexNotFoundException, CursorException, IOException 269 { 270 AttributeType attributeType = node.getAttributeType(); 271 Value value = node.getValue(); 272 int nbResults = 0; 273 274 // Fetch all the UUIDs if we have an index 275 if ( db.hasIndexOn( attributeType ) ) 276 { 277 // Get the cursor using the index 278 Index<T, String> userIndex = ( Index<T, String> ) db.getIndex( attributeType ); 279 Cursor<IndexEntry<T, String>> userIdxCursor = userIndex.forwardCursor( partitionTxn ); 280 281 // Position the index on the element we should start from 282 IndexEntry<T, String> indexEntry = new IndexEntry<>(); 283 indexEntry.setKey( ( T ) value.getString() ); 284 285 userIdxCursor.before( indexEntry ); 286 Set<String> uuidSet = searchResult.getCandidateSet(); 287 288 // And loop on it 289 while ( userIdxCursor.next() ) 290 { 291 indexEntry = userIdxCursor.get(); 292 293 String uuid = indexEntry.getId(); 294 boolean added = uuidSet.add( uuid ); 295 296 // if the UUID was added increment the result count 297 if ( added ) 298 { 299 nbResults++; 300 } 301 } 302 303 userIdxCursor.close(); 304 } 305 else 306 { 307 // No index, we will have to do a full scan 308 return Long.MAX_VALUE; 309 } 310 311 return nbResults; 312 } 313 314 315 /** 316 * Computes the set of candidates for an LessEq filter. We will feed the set only if 317 * we have an index for the AT. 318 */ 319 private <T> long computeLessEq( PartitionTxn partitionTxn, LessEqNode<T> node, PartitionSearchResult searchResult ) 320 throws LdapException, IndexNotFoundException, CursorException, IOException 321 { 322 AttributeType attributeType = node.getAttributeType(); 323 Value value = node.getValue(); 324 int nbResults = 0; 325 326 // Fetch all the UUIDs if we have an index 327 if ( db.hasIndexOn( attributeType ) ) 328 { 329 // Get the cursor using the index 330 Index<T, String> userIndex = ( Index<T, String> ) db.getIndex( attributeType ); 331 Cursor<IndexEntry<T, String>> userIdxCursor = userIndex.forwardCursor( partitionTxn ); 332 333 // Position the index on the element we should start from 334 IndexEntry<T, String> indexEntry = new IndexEntry<>(); 335 indexEntry.setKey( ( T ) value.getString() ); 336 337 userIdxCursor.after( indexEntry ); 338 Set<String> uuidSet = searchResult.getCandidateSet(); 339 340 // And loop on it 341 while ( userIdxCursor.previous() ) 342 { 343 indexEntry = userIdxCursor.get(); 344 345 String uuid = indexEntry.getId(); 346 boolean added = uuidSet.add( uuid ); 347 348 // if the UUID was added increment the result count 349 if ( added ) 350 { 351 nbResults++; 352 } 353 } 354 355 userIdxCursor.close(); 356 } 357 else 358 { 359 // No index, we will have to do a full scan 360 return Long.MAX_VALUE; 361 } 362 363 return nbResults; 364 } 365 366 367 /** 368 * Computes the set of candidates for a Presence filter. We will feed the set only if 369 * we have an index for the AT. 370 */ 371 private long computePresence( PartitionTxn partitionTxn, PresenceNode node, PartitionSearchResult searchResult ) 372 throws LdapException, CursorException, IOException 373 { 374 AttributeType attributeType = node.getAttributeType(); 375 int nbResults = 0; 376 377 // Fetch all the UUIDs if we have an index 378 if ( db.hasIndexOn( attributeType ) ) 379 { 380 // Get the cursor using the index 381 Cursor<IndexEntry<String, String>> presenceCursor = db.getPresenceIndex().forwardCursor( 382 partitionTxn, attributeType.getOid() ); 383 384 // Position the index on the element we should start from 385 Set<String> uuidSet = searchResult.getCandidateSet(); 386 387 // And loop on it 388 while ( presenceCursor.next() ) 389 { 390 IndexEntry<String, String> indexEntry = presenceCursor.get(); 391 392 String uuid = indexEntry.getId(); 393 boolean added = uuidSet.add( uuid ); 394 395 // if the UUID was added increment the result count 396 if ( added ) 397 { 398 nbResults++; 399 } 400 } 401 402 presenceCursor.close(); 403 } 404 else 405 { 406 // No index, we will have to do a full scan 407 return Long.MAX_VALUE; 408 } 409 410 return nbResults; 411 } 412 413 414 /** 415 * Computes the set of candidates for a OneLevelScope filter. We will feed the set only if 416 * we have an index for the AT. 417 */ 418 private long computeOneLevelScope( PartitionTxn partitionTxn, ScopeNode node, PartitionSearchResult searchResult ) 419 throws LdapException, CursorException, IOException 420 { 421 int nbResults = 0; 422 423 // We use the RdnIndex to get all the entries from a starting point 424 // and below up to the number of children 425 Cursor<IndexEntry<ParentIdAndRdn, String>> rdnCursor = db.getRdnIndex().forwardCursor( partitionTxn ); 426 427 IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>(); 428 startingPos.setKey( new ParentIdAndRdn( node.getBaseId(), ( Rdn[] ) null ) ); 429 rdnCursor.before( startingPos ); 430 431 Cursor<IndexEntry<String, String>> scopeCursor = new ChildrenCursor( partitionTxn, db, node.getBaseId(), rdnCursor ); 432 Set<String> candidateSet = searchResult.getCandidateSet(); 433 434 // Fetch all the UUIDs if we have an index 435 // And loop on it 436 while ( scopeCursor.next() ) 437 { 438 IndexEntry<String, String> indexEntry = scopeCursor.get(); 439 440 String uuid = indexEntry.getId(); 441 442 // If the entry is an alias, and we asked for it to be dereferenced, 443 // we will dereference the alias 444 if ( searchResult.isDerefAlways() || searchResult.isDerefInSearching() ) 445 { 446 Dn aliasedDn = db.getAliasIndex().reverseLookup( partitionTxn, uuid ); 447 448 if ( aliasedDn != null ) 449 { 450 if ( !aliasedDn.isSchemaAware() ) 451 { 452 aliasedDn = new Dn( evaluatorBuilder.getSchemaManager(), aliasedDn ); 453 } 454 455 String aliasedId = db.getEntryId( partitionTxn, aliasedDn ); 456 457 // This is an alias. Add it to the set of candidates to process, if it's not already 458 // present in the candidate set 459 boolean added = candidateSet.add( aliasedId ); 460 461 if ( added ) 462 { 463 nbResults++; 464 } 465 } 466 else 467 { 468 // The UUID is not present in the Set, we add it 469 boolean added = candidateSet.add( uuid ); 470 471 // This is not an alias 472 if ( added ) 473 { 474 nbResults++; 475 } 476 } 477 } 478 else 479 { 480 // The UUID is not present in the Set, we add it 481 boolean added = candidateSet.add( uuid ); 482 483 // This is not an alias 484 if ( added ) 485 { 486 nbResults++; 487 } 488 } 489 } 490 491 scopeCursor.close(); 492 493 return nbResults; 494 } 495 496 497 /** 498 * Computes the set of candidates for a SubLevelScope filter. We will feed the set only if 499 * we have an index for the AT. 500 */ 501 private long computeSubLevelScope( PartitionTxn partitionTxn, ScopeNode node, PartitionSearchResult searchResult ) 502 throws LdapException, IOException, CursorException 503 { 504 // If we are searching from the partition DN, better get out. 505 String contextEntryId = db.getEntryId( partitionTxn, ( ( Partition ) db ).getSuffixDn() ); 506 507 if ( node.getBaseId() == contextEntryId ) 508 { 509 return Long.MAX_VALUE; 510 } 511 512 int nbResults = 0; 513 514 // We use the RdnIndex to get all the entries from a starting point 515 // and below up to the number of descendant 516 String baseId = node.getBaseId(); 517 ParentIdAndRdn parentIdAndRdn = db.getRdnIndex().reverseLookup( partitionTxn, baseId ); 518 IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>(); 519 520 startingPos.setKey( parentIdAndRdn ); 521 startingPos.setId( baseId ); 522 523 Cursor<IndexEntry<ParentIdAndRdn, String>> rdnCursor = new SingletonIndexCursor<>( partitionTxn, 524 startingPos ); 525 String parentId = parentIdAndRdn.getParentId(); 526 527 Cursor<IndexEntry<String, String>> scopeCursor = new DescendantCursor( partitionTxn, db, baseId, parentId, rdnCursor ); 528 Set<String> candidateSet = searchResult.getCandidateSet(); 529 530 // Fetch all the UUIDs if we have an index 531 // And loop on it 532 while ( scopeCursor.next() ) 533 { 534 IndexEntry<String, String> indexEntry = scopeCursor.get(); 535 536 String uuid = indexEntry.getId(); 537 538 // If the entry is an alias, and we asked for it to be dereferenced, 539 // we will dereference the alias 540 if ( searchResult.isDerefAlways() || searchResult.isDerefInSearching() ) 541 { 542 Dn aliasedDn = db.getAliasIndex().reverseLookup( partitionTxn, uuid ); 543 544 if ( aliasedDn != null ) 545 { 546 if ( !aliasedDn.isSchemaAware() ) 547 { 548 aliasedDn = new Dn( evaluatorBuilder.getSchemaManager(), aliasedDn ); 549 } 550 551 String aliasedId = db.getEntryId( partitionTxn, aliasedDn ); 552 553 // This is an alias. Add it to the set of candidates to process, if it's not already 554 // present in the candidate set 555 boolean added = candidateSet.add( aliasedId ); 556 557 if ( added ) 558 { 559 nbResults++; 560 561 ScopeNode newScopeNode = new ScopeNode( 562 node.getDerefAliases(), 563 aliasedDn, 564 aliasedId, 565 node.getScope() ); 566 567 nbResults += computeSubLevelScope( partitionTxn, newScopeNode, searchResult ); 568 } 569 } 570 else 571 { 572 // This is not an alias 573 // The UUID is not present in the Set, we add it 574 boolean added = candidateSet.add( uuid ); 575 576 if ( added ) 577 { 578 nbResults++; 579 } 580 } 581 } 582 else 583 { 584 // The UUID is not present in the Set, we add it 585 boolean added = candidateSet.add( uuid ); 586 587 if ( added ) 588 { 589 nbResults++; 590 } 591 } 592 } 593 594 scopeCursor.close(); 595 596 return nbResults; 597 } 598 599 600 /** 601 * Computes the set of candidates for an Substring filter. We will feed the set only if 602 * we have an index for the AT. 603 */ 604 private long computeSubstring( PartitionTxn partitionTxn, SubstringNode node, PartitionSearchResult searchResult ) 605 throws LdapException, IndexNotFoundException, CursorException, IOException 606 { 607 AttributeType attributeType = node.getAttributeType(); 608 609 // Check if the AttributeType has a SubstringMatchingRule 610 if ( attributeType.getSubstring() == null ) 611 { 612 // No SUBSTRING matching rule : return 0 613 return 0L; 614 } 615 616 // Fetch all the UUIDs if we have an index 617 if ( db.hasIndexOn( attributeType ) ) 618 { 619 Index<String, String> userIndex = ( Index<String, String> ) db.getIndex( attributeType ); 620 Cursor<IndexEntry<String, String>> cursor = userIndex.forwardCursor( partitionTxn ); 621 622 // Position the index on the element we should start from 623 IndexEntry<String, String> indexEntry = new IndexEntry<>(); 624 String initial = node.getInitial(); 625 626 boolean fullIndexScan = false; 627 628 if ( initial == null ) 629 { 630 fullIndexScan = true; 631 cursor.beforeFirst(); 632 } 633 else 634 { 635 indexEntry.setKey( attributeType.getEquality().getNormalizer().normalize( initial, PrepareString.AssertionType.SUBSTRING_INITIAL ) ); 636 637 cursor.before( indexEntry ); 638 } 639 640 int nbResults = 0; 641 642 MatchingRule rule = attributeType.getSubstring(); 643 644 if ( rule == null ) 645 { 646 rule = attributeType.getEquality(); 647 } 648 649 Normalizer normalizer; 650 Pattern regexp; 651 652 if ( rule != null ) 653 { 654 normalizer = rule.getNormalizer(); 655 } 656 else 657 { 658 normalizer = new NoOpNormalizer( attributeType.getSyntaxOid() ); 659 } 660 661 // compile the regular expression to search for a matching attribute 662 // if the attributeType is humanReadable 663 if ( attributeType.getSyntax().isHumanReadable() ) 664 { 665 regexp = node.getRegex( normalizer ); 666 } 667 else 668 { 669 regexp = null; 670 } 671 672 Set<String> uuidSet = searchResult.getCandidateSet(); 673 674 if ( regexp == null ) 675 { 676 return nbResults; 677 } 678 679 // And loop on it 680 while ( cursor.next() ) 681 { 682 indexEntry = cursor.get(); 683 684 String key = indexEntry.getKey(); 685 686 boolean matched = regexp.matcher( key ).matches(); 687 688 if ( !fullIndexScan && !matched ) 689 { 690 cursor.close(); 691 692 return nbResults; 693 } 694 695 if ( !matched ) 696 { 697 continue; 698 } 699 700 String uuid = indexEntry.getId(); 701 702 boolean added = uuidSet.add( uuid ); 703 704 // if the UUID was added increment the result count 705 if ( added ) 706 { 707 nbResults++; 708 } 709 } 710 711 cursor.close(); 712 713 return nbResults; 714 } 715 else 716 { 717 // No index, we will have to do a full scan 718 return Long.MAX_VALUE; 719 } 720 } 721 722 723 /** 724 * Creates a OrCursor over a disjunction expression branch node. 725 * 726 * @param node the disjunction expression branch node 727 * @return Cursor over candidates satisfying disjunction expression 728 * @throws Exception on db access failures 729 */ 730 private long computeOr( PartitionTxn partitionTxn, OrNode node, PartitionSearchResult searchResult ) 731 throws LdapException 732 { 733 List<ExprNode> children = node.getChildren(); 734 735 long nbOrResults = 0; 736 737 // Recursively create Cursors and Evaluators for each child expression node 738 for ( ExprNode child : children ) 739 { 740 Object count = child.get( DefaultOptimizer.COUNT_ANNOTATION ); 741 742 if ( count != null ) 743 { 744 long countLong = ( Long ) count; 745 746 if ( countLong == 0 ) 747 { 748 // We can skip the cursor, it will not return any candidate 749 continue; 750 } 751 else if ( countLong == Long.MAX_VALUE ) 752 { 753 // We can stop here, we will anyway do a full scan 754 return countLong; 755 } 756 } 757 758 long nbResults = build( partitionTxn, child, searchResult ); 759 760 if ( nbResults == Long.MAX_VALUE ) 761 { 762 // We can stop here, we will anyway do a full scan 763 return nbResults; 764 } 765 else 766 { 767 nbOrResults += nbResults; 768 } 769 } 770 771 return nbOrResults; 772 } 773 774 775 /** 776 * Creates an AndCursor over a conjunction expression branch node. 777 * 778 * @param node a conjunction expression branch node 779 * @return Cursor over the conjunction expression 780 * @throws Exception on db access failures 781 */ 782 private long computeAnd( PartitionTxn partitionTxn, AndNode node, PartitionSearchResult searchResult ) 783 throws LdapException 784 { 785 int minIndex = 0; 786 long minValue = Long.MAX_VALUE; 787 long value; 788 789 /* 790 * We scan the child nodes of a branch node searching for the child 791 * expression node with the smallest scan count. This is the child 792 * we will use for iteration 793 */ 794 final List<ExprNode> children = node.getChildren(); 795 796 for ( int i = 0; i < children.size(); i++ ) 797 { 798 ExprNode child = children.get( i ); 799 Object count = child.get( DefaultOptimizer.COUNT_ANNOTATION ); 800 801 if ( count == null ) 802 { 803 continue; 804 } 805 806 value = ( Long ) count; 807 808 if ( value == 0L ) 809 { 810 // No need to go any further : we won't have matching candidates anyway 811 return 0L; 812 } 813 814 if ( value < minValue ) 815 { 816 minValue = value; 817 minIndex = i; 818 } 819 } 820 821 // Once found we return the number of candidates for this child 822 ExprNode minChild = children.get( minIndex ); 823 824 return build( partitionTxn, minChild, searchResult ); 825 } 826 827 828 /** 829 * Creates an AndCursor over a conjunction expression branch node. 830 * 831 * @param node a conjunction expression branch node 832 * @return Cursor over the conjunction expression 833 * @throws Exception on db access failures 834 */ 835 private long computeNot( NotNode node, PartitionSearchResult searchResult ) 836 { 837 final List<ExprNode> children = node.getChildren(); 838 839 ExprNode child = children.get( 0 ); 840 Object count = child.get( DefaultOptimizer.COUNT_ANNOTATION ); 841 842 if ( count == null ) 843 { 844 return Long.MAX_VALUE; 845 } 846 847 long value = ( Long ) count; 848 849 if ( value == Long.MAX_VALUE ) 850 { 851 // No need to go any further : we won't have matching candidates anyway 852 return 0L; 853 } 854 855 return Long.MAX_VALUE; 856 } 857}