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}