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}