Changeset 11899
- Timestamp:
- 08/30/07 18:33:18 (1 year ago)
- Files:
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/midcom/midcom.core/midcom/core/querybuilder.php
r11894 r11899 106 106 * 107 107 * @package midcom 108 * @todo Optimize the limit/offset implementation.109 * @todo Refactor the class to promote code reuse in the execution handlers.110 108 */ 111 109 class midcom_core_querybuilder extends midcom_baseclasses_core_object … … 180 178 * impact. 181 179 * 182 * While on-site, this is enabled by default, in AIS it is disabled by default.183 180 */ 184 181 var $hide_invisible = true; … … 271 268 } 272 269 270 /** 271 * Executes the internal QB and filters objects based on ACLs and metadata 272 * 273 * @param bool $false_on_empty_mgd_resultset used in the moving window loop to get false in stead of empty array back from this method in case the **core** QB returns empty resultset 274 * @return array of objects filtered by ACL and metadata visibility (or false in case of failure) 275 */ 273 276 function _execute_and_check_privileges($false_on_empty_mgd_resultset = false) 274 277 { 275 278 debug_push_class(__CLASS__, __FUNCTION__); 279 // TODO: Remove this silence after all MgdSchemas are fixed 276 280 $result = @$this->_qb->execute(); 277 281 if (!is_array($result)) … … 361 365 362 366 /** 367 * Resets some internal variables for re-execute 368 */ 369 function _reset() 370 { 371 $this->_seen_guids = array(); 372 $this->_qb_error_result = 'UNDEFINED'; 373 $this->count = -1; 374 $this->denied = 0; 375 } 376 377 /** 363 378 * This function will execute the Querybuilder and call the appropriate callbacks from the 364 379 * class it is associated to. This way, class authors have full control over what is actually … … 373 388 * may remove any unwanted entries from the resultset at this point. 374 389 * 375 * If the execution of the query fails for some reason all available error information is logged376 * and a MIDCOM_ERRCRIT level error is triggered, halting execution.377 *378 * @param midgard_query_builder $qb An instance of the Query builder obtained by the new_query_builder379 * function of this class.380 390 * @return Array The result of the query builder or null on any error. Note, that empty resultsets 381 391 * will return an empty array. 382 * @todo Implement proper count / Limit support.383 392 */ 384 393 function execute_windowed() 385 394 { 386 // Reset these two in case someone tries to re-execute this 387 $this->_seen_guids = array(); 388 $this->_qb_error_result = 'UNDEFINED'; 395 $this->_reset(); 389 396 390 397 if (! call_user_func_array(array($this->_real_class, '_on_prepare_exec_query_builder'), array(&$this))) … … 426 433 while (($resultset = $this->_execute_and_check_privileges(true)) !== false) 427 434 { 428 //debug_add("Iteration loop #{$i}");435 debug_add("Iteration loop #{$i}"); 429 436 if ($this->_qb_error_result !== 'UNDEFINED') 430 437 { … … 468 475 call_user_func_array(array($this->_real_class, '_on_process_query_result'), array(&$newresult)); 469 476 470 /*471 // correct record count by the number of limit-skipped objects.472 $this->count = count($newresult) + $skipped_objects;473 */474 477 $this->count = count($newresult); 475 478 … … 539 542 { 540 543 return $this->execute_windowed(); 544 //return $this->execute_notwindowed(); 541 545 } 542 546 … … 554 558 * may remove any unwanted entries from the resultset at this point. 555 559 * 556 * If the execution of the query fails for some reason all available error information is logged557 * and a MIDCOM_ERRCRIT level error is triggered, halting execution.558 *559 * @param midgard_query_builder $qb An instance of the Query builder obtained by the new_query_builder560 * function of this class.561 560 * @return Array The result of the query builder or null on any error. Note, that empty resultsets 562 561 * will return an empty array. 563 * @todo Implement proper count / Limit support.564 562 */ 565 563 function execute_notwindowed() 566 564 { 567 debug_push_class(__CLASS__, __FUNCTION__); 568 565 $this->_reset(); 569 566 if (! call_user_func_array(array($this->_real_class, '_on_prepare_exec_query_builder'), array(&$this))) 570 567 { 568 debug_push_class(__CLASS__, __FUNCTION__); 571 569 debug_add('The _on_prepare_exec_query_builder callback returned false, so we abort now.'); 572 570 debug_pop(); … … 576 574 if ($this->_constraint_count == 0) 577 575 { 578 debug_add('This Query Builder instance has no constraints.', MIDCOM_LOG_WARN); 576 debug_push_class(__CLASS__, __FUNCTION__); 577 debug_add('This Query Builder instance has no constraints, see debug log for stacktrace', MIDCOM_LOG_WARN); 579 578 debug_print_function_stack('We were called from here:'); 580 } 581 582 $result = $this->_qb->execute(); 579 debug_pop(); 580 } 581 582 $result = $this->_execute_and_check_privileges(); 583 583 if (!is_array($result)) 584 584 { 585 debug_print_r('Result was:', $result); 586 debug_add('The querybuilder failed to execute, aborting.', MIDCOM_LOG_ERROR); 587 debug_add('Last Midgard error was: ' . mgd_errstr(), MIDCOM_LOG_ERROR); 588 if (isset($php_errormsg)) 589 { 590 debug_add("Error message was: {$php_errormsg}", MIDCOM_LOG_ERROR); 591 } 592 593 /* 594 $_MIDCOM->generate_error(MIDCOM_ERRCRIT, 595 'The query builder failed to execute, see the log file for more information.'); 596 // This will exit. 597 */ 598 return false; 585 return $result; 599 586 } 600 587 … … 606 593 $skipped_objects = 0; 607 594 $this->denied = 0; 608 // Workaround to ML bug where we get multiple results in non-strict mode 609 $seen_guids = array(); 610 foreach ($result as $key => $value) 595 596 foreach ($result as $key => $object) 611 597 { 612 598 if ( $this->_limit > 0 613 599 && $limit == 0) 614 600 { 615 $skipped_objects++;616 continue;617 }618 619 // Create a new object instance (checks read privilege implicitly) using the copy-constuctor.620 $object = new $classname($value);621 622 if (mgd_errno() == MGD_ERR_ACCESS_DENIED)623 {624 // This is logged by the callers625 $this->denied++;626 $skipped_objects++;627 continue;628 }629 630 if ( ! $object631 || ! is_object($object))632 {633 debug_add("Could not create a MidCOM DBA instance of the {$classname} ID {$value->id}. See debug level log for details.",634 MIDCOM_LOG_INFO);635 601 $skipped_objects++; 636 602 continue; … … 645 611 } 646 612 647 // Check visibility648 if ($this->hide_invisible)649 {650 $metadata =& midcom_helper_metadata::retrieve($object);651 if (! $metadata)652 {653 debug_add("Could not create a MidCOM metadata instance for {$classname} ID {$value->id}, assuming an invisible object.",654 MIDCOM_LOG_INFO);655 $skipped_objects++;656 continue;657 }658 659 if (! $metadata->is_object_visible_onsite())660 {661 debug_add("The {$classname} ID {$value->id} is hidden by metadata.", MIDCOM_LOG_INFO);662 $skipped_objects++;663 continue;664 }665 }666 667 if (isset($seen_guids[$object->guid]))668 {669 debug_push_class(__CLASS__, __FUNCTION__);670 debug_add("The {$classname} object {$object->guid} has already been seen, probably MultiLang bug", MIDCOM_LOG_WARN);671 debug_pop();672 continue;673 }674 675 613 $newresult[] = $object; 676 677 $seen_guids[$object->guid] = true;678 614 679 615 if ($this->_limit > 0) … … 685 621 call_user_func_array(array($this->_real_class, '_on_process_query_result'), array(&$newresult)); 686 622 687 // correct record count by the number of limit-skipped objects. 688 $this->count = count($newresult) + $skipped_objects; 689 690 debug_pop(); 623 $this->count = count($newresult); 624 691 625 return $newresult; 692 626 } … … 707 641 function execute_unchecked() 708 642 { 709 debug_push_class(__CLASS__, __FUNCTION__);643 $this->_reset(); 710 644 711 645 if (! call_user_func_array(array($this->_real_class, '_on_prepare_exec_query_builder'), array(&$this))) 712 646 { 647 debug_push_class(__CLASS__, __FUNCTION__); 713 648 debug_add('The _on_prepare_exec_query_builder callback returned false, so we abort now.'); 714 649 debug_pop(); … … 718 653 if ($this->_constraint_count == 0) 719 654 { 720 debug_add('This Query Builder instance has no constraints.', MIDCOM_LOG_WARN); 655 debug_push_class(__CLASS__, __FUNCTION__); 656 debug_add('This Query Builder instance has no constraints, see debug level log for stacktrace', MIDCOM_LOG_WARN); 721 657 debug_print_function_stack('We were called from here:'); 658 debug_pop(); 722 659 } 723 660 … … 732 669 } 733 670 734 $result = $this->_qb->execute(); 735 if (!is_array($result)) 736 { 737 debug_print_r('Result was:', $result); 738 debug_add('The querybuilder failed to execute, aborting.', MIDCOM_LOG_ERROR); 739 debug_add('Last Midgard error was: ' . mgd_errstr(), MIDCOM_LOG_ERROR); 740 if (isset($php_errormsg)) 741 { 742 debug_add("Error message was: {$php_errormsg}", MIDCOM_LOG_ERROR); 743 } 744 745 /* 746 $_MIDCOM->generate_error(MIDCOM_ERRCRIT, 747 'The query builder failed to execute, see the log file for more information.'); 748 // This will exit. 749 */ 750 return false; 751 } 752 753 // Workaround until the QB returns the correct type, refetch everything 754 $newresult = Array(); 755 $classname = $this->_real_class; 756 $this->denied = 0; 757 foreach ($result as $key => $value) 758 { 759 // Create a new object instance (checks read privilege implicitly) using the copy-constuctor. 760 $object = new $classname($value); 761 762 if (mgd_errno() == MGD_ERR_ACCESS_DENIED) 763 { 764 // This is logged by the callers 765 $this->denied++; 766 continue; 767 } 768 769 if ( ! $object 770 || ! is_object($object)) 771 { 772 debug_add("Could not create a MidCOM DBA instance of the {$classname} ID {$value->id}. See debug level log for details.", 773 MIDCOM_LOG_INFO); 774 continue; 775 } 776 777 // Check visibility 778 if ($this->hide_invisible) 779 { 780 $metadata =& midcom_helper_metadata::retrieve($object); 781 if (! $metadata) 782 { 783 debug_add("Could not create a MidCOM metadata instance for {$classname} ID {$value->id}, assuming an invisible object.", 784 MIDCOM_LOG_INFO); 785 continue; 786 } 787 788 if (! $metadata->is_object_visible_onsite()) 789 { 790 debug_add("The {$classname} ID {$value->id} is hidden by metadata.", MIDCOM_LOG_INFO); 791 continue; 792 } 793 } 794 795 $newresult[$key] = $object; 671 $newresult = $this->_execute_and_check_privileges(); 672 if (!is_array($newresult)) 673 { 674 return $newresult; 796 675 } 797 676 … … 800 679 $this->count = count($newresult); 801 680 802 debug_pop();803 681 return $newresult; 804 682 } … … 817 695 function add_constraint($field, $operator, $value) 818 696 { 697 $this->_reset(); 819 698 // Add check against null values, Core QB is too stupid to get this right. 820 699 if ($value === null) … … 928 807 function set_limit($limit) 929 808 { 809 $this->_reset(); 930 810 $this->_limit = $limit; 931 811 } … … 943 823 function set_offset($offset) 944 824 { 825 $this->_reset(); 945 826 $this->_offset = $offset; 946 827 } … … 955 836 function set_lang($language) 956 837 { 838 $this->_reset(); 957 839 $this->_qb->set_lang($language); 958 840 } … … 960 842 /** 961 843 * Include deleted objects (metadata.deleted is TRUE) in query results. 844 * 845 * Note: this may cause all kinds of weird behaviour with the DBA helpers 962 846 */ 963 847 function include_deleted() 964 848 { 849 $this->_reset(); 965 850 $this->_qb->include_deleted(); 966 851 } … … 969 854 * Returns the number of elements matching the current query. 970 855 * 971 * <i>Developer's note:</i> According to the Midgard core documentation, the count method 972 * does <b>not</b> execute the query using some COUNT() SQL statement. It merely returns 973 * the number of records found and is, thus, mostly useless as you can just count($result) 974 * on the PHP level anyway. 975 * 976 * To match the original inteded behavoir, the class will automatically execute the given 977 * query if it has not yet been executed, thus breaking full API compatibility to the Midgard 978 * core deliberatly on this point. 979 * 980 * Therefore, it is currently <i>strongly discouraged</i> to assume midgard_query_builder::count 981 * to be useful as-is. See http://midgard.tigris.org/issues/show_bug.cgi?id=56 for details. 856 * Due to ACL checking we must first execute the full query to get 982 857 * 983 858 * @return int The number of records found by the last query. … … 1001 876 * mind might nevertheless be able to take advantage of it. 1002 877 * 1003 * @return int The number of records matching the last query without taking access controlinto account.878 * @return int The number of records matching the constraints without taking access control or visibility into account. 1004 879 */ 1005 880 function count_unchecked() 1006 881 { 1007 // TODO: Handle limit and offset 882 if ($this->_limit) 883 { 884 $this->_qb->set_limit($this->_limit); 885 } 886 if ($this->_offset) 887 { 888 $this->_qb->set_offset($this->_offset); 889 } 1008 890 return $this->_qb->count(); 1009 891 } … … 1012 894 1013 895 ?> 896
