| | 273 | function _execute_and_check_privileges($false_on_empty_mgd_resultset = false) |
|---|
| | 274 | { |
|---|
| | 275 | debug_push_class(__CLASS__, __FUNCTION__); |
|---|
| | 276 | $result = $this->_qb->execute(); |
|---|
| | 277 | if (!is_array($result)) |
|---|
| | 278 | { |
|---|
| | 279 | $this->_qb_error_result = $result; |
|---|
| | 280 | debug_print_r('Result was:', $result); |
|---|
| | 281 | debug_add('The querybuilder failed to execute, aborting.', MIDCOM_LOG_ERROR); |
|---|
| | 282 | debug_add('Last Midgard error was: ' . mgd_errstr(), MIDCOM_LOG_ERROR); |
|---|
| | 283 | if (isset($php_errormsg)) |
|---|
| | 284 | { |
|---|
| | 285 | debug_add("Error message was: {$php_errormsg}", MIDCOM_LOG_ERROR); |
|---|
| | 286 | } |
|---|
| | 287 | |
|---|
| | 288 | debug_pop(); |
|---|
| | 289 | return $result; |
|---|
| | 290 | } |
|---|
| | 291 | debug_add('Got ' . count($result) . ' initial results'); |
|---|
| | 292 | if ( empty($result) |
|---|
| | 293 | && $false_on_empty_mgd_resultset) |
|---|
| | 294 | { |
|---|
| | 295 | debug_pop(); |
|---|
| | 296 | return false; |
|---|
| | 297 | } |
|---|
| | 298 | |
|---|
| | 299 | // Workaround until the QB returns the correct type, refetch everything |
|---|
| | 300 | $newresult = Array(); |
|---|
| | 301 | $classname = $this->_real_class; |
|---|
| | 302 | $skipped_objects = 0; |
|---|
| | 303 | $this->denied = 0; |
|---|
| | 304 | foreach ($result as $key => $value) |
|---|
| | 305 | { |
|---|
| | 306 | // Workaround to ML bug where we get multiple results in non-strict mode |
|---|
| | 307 | if (isset($this->_seen_guids[$value->guid])) |
|---|
| | 308 | { |
|---|
| | 309 | debug_add("The {$classname} object {$value->guid} has already been seen, probably MultiLang bug", MIDCOM_LOG_WARN); |
|---|
| | 310 | //debug_add('var_export($seen_guids): ' . var_export($this->_seen_guids, true)); |
|---|
| | 311 | continue; |
|---|
| | 312 | } |
|---|
| | 313 | $this->_seen_guids[$value->guid] = true; |
|---|
| | 314 | |
|---|
| | 315 | // Create a new object instance (checks read privilege implicitly) using the copy-constuctor. |
|---|
| | 316 | $object = new $classname($value); |
|---|
| | 317 | |
|---|
| | 318 | if (mgd_errno() == MGD_ERR_ACCESS_DENIED) |
|---|
| | 319 | { |
|---|
| | 320 | // This is logged by the callers |
|---|
| | 321 | $this->denied++; |
|---|
| | 322 | $skipped_objects++; |
|---|
| | 323 | continue; |
|---|
| | 324 | } |
|---|
| | 325 | |
|---|
| | 326 | if ( ! $object |
|---|
| | 327 | || ! is_object($object)) |
|---|
| | 328 | { |
|---|
| | 329 | debug_add("Could not create a MidCOM DBA instance of the {$classname} ID {$value->id}. See debug level log for details.", |
|---|
| | 330 | MIDCOM_LOG_INFO); |
|---|
| | 331 | $skipped_objects++; |
|---|
| | 332 | continue; |
|---|
| | 333 | } |
|---|
| | 334 | |
|---|
| | 335 | // Check visibility |
|---|
| | 336 | if ($this->hide_invisible) |
|---|
| | 337 | { |
|---|
| | 338 | $metadata =& midcom_helper_metadata::retrieve($object); |
|---|
| | 339 | if (! $metadata) |
|---|
| | 340 | { |
|---|
| | 341 | debug_add("Could not create a MidCOM metadata instance for {$classname} ID {$value->id}, assuming an invisible object.", |
|---|
| | 342 | MIDCOM_LOG_INFO); |
|---|
| | 343 | $skipped_objects++; |
|---|
| | 344 | continue; |
|---|
| | 345 | } |
|---|
| | 346 | |
|---|
| | 347 | if (! $metadata->is_object_visible_onsite()) |
|---|
| | 348 | { |
|---|
| | 349 | debug_add("The {$classname} ID {$value->id} is hidden by metadata.", MIDCOM_LOG_INFO); |
|---|
| | 350 | $skipped_objects++; |
|---|
| | 351 | continue; |
|---|
| | 352 | } |
|---|
| | 353 | } |
|---|
| | 354 | |
|---|
| | 355 | $newresult[] = $object; |
|---|
| | 356 | } |
|---|
| | 357 | debug_add('Returning ' . count($newresult) . ' items'); |
|---|
| | 358 | debug_pop(); |
|---|
| | 359 | return $newresult; |
|---|
| | 360 | } |
|---|
| | 400 | debug_push_class(__CLASS__, __FUNCTION__); |
|---|
| | 401 | debug_add('This Query Builder instance has no constraints (set loglevel to debug to see stack trace)', MIDCOM_LOG_WARN); |
|---|
| | 402 | debug_print_function_stack('We were called from here:'); |
|---|
| | 403 | debug_pop(); |
|---|
| | 404 | } |
|---|
| | 405 | |
|---|
| | 406 | if ( empty($this->_limit) |
|---|
| | 407 | && empty($this->_offset)) |
|---|
| | 408 | { |
|---|
| | 409 | // No point to do windowing |
|---|
| | 410 | $newresult = $this->_execute_and_check_privileges(); |
|---|
| | 411 | if (!is_array($newresult)) |
|---|
| | 412 | { |
|---|
| | 413 | return $newresult; |
|---|
| | 414 | } |
|---|
| | 415 | } |
|---|
| | 416 | else |
|---|
| | 417 | { |
|---|
| | 418 | //debug_push_class(__CLASS__, __FUNCTION__); |
|---|
| | 419 | $newresult = array(); |
|---|
| | 420 | // Must be copies |
|---|
| | 421 | $limit = $this->_limit; |
|---|
| | 422 | $offset = $this->_offset; |
|---|
| | 423 | $i = 0; |
|---|
| | 424 | $this->_set_limit_offset_window($i); |
|---|
| | 425 | |
|---|
| | 426 | while (($resultset = $this->_execute_and_check_privileges(true)) !== false) |
|---|
| | 427 | { |
|---|
| | 428 | //debug_add("Iteration loop #{$i}"); |
|---|
| | 429 | if ($this->_qb_error_result !== 'UNDEFINED') |
|---|
| | 430 | { |
|---|
| | 431 | // QB failed in above method TODO: better catch |
|---|
| | 432 | /* |
|---|
| | 433 | debug_add('_execute_and_check_privileges caught QB error, returning that now', MIDCOM_LOG_WARN); |
|---|
| | 434 | debug_pop(); |
|---|
| | 435 | */ |
|---|
| | 436 | return $this->_qb_error_result; |
|---|
| | 437 | } |
|---|
| | 438 | |
|---|
| | 439 | foreach($resultset as $object) |
|---|
| | 440 | { |
|---|
| | 441 | // We still have offset left to skip |
|---|
| | 442 | if ($offset) |
|---|
| | 443 | { |
|---|
| | 444 | //debug_add("Offset of {$this->_offset} not yet reached, continuing loop"); |
|---|
| | 445 | $offset--; |
|---|
| | 446 | continue; |
|---|
| | 447 | } |
|---|
| | 448 | // We have hit our limit |
|---|
| | 449 | if ( $this->_limit > 0 |
|---|
| | 450 | && $limit == 0) |
|---|
| | 451 | { |
|---|
| | 452 | //debug_add("Limit of {$this->_limit} hit, breaking out of loops"); |
|---|
| | 453 | break 2; |
|---|
| | 454 | } |
|---|
| | 455 | |
|---|
| | 456 | $newresult[] = $object; |
|---|
| | 457 | |
|---|
| | 458 | if ($this->_limit > 0) |
|---|
| | 459 | { |
|---|
| | 460 | $limit--; |
|---|
| | 461 | } |
|---|
| | 462 | } |
|---|
| | 463 | ++$i; |
|---|
| | 464 | $this->_set_limit_offset_window($i); |
|---|
| | 465 | } |
|---|
| | 466 | } |
|---|
| | 467 | |
|---|
| | 468 | call_user_func_array(array($this->_real_class, '_on_process_query_result'), array(&$newresult)); |
|---|
| | 469 | |
|---|
| | 470 | /* |
|---|
| | 471 | // correct record count by the number of limit-skipped objects. |
|---|
| | 472 | $this->count = count($newresult) + $skipped_objects; |
|---|
| | 473 | */ |
|---|
| | 474 | $this->count = count($newresult); |
|---|
| | 475 | |
|---|
| | 476 | //debug_pop(); |
|---|
| | 477 | return $newresult; |
|---|
| | 478 | } |
|---|
| | 479 | |
|---|
| | 480 | function _set_limit_offset_window($iteration) |
|---|
| | 481 | { |
|---|
| | 482 | /* |
|---|
| | 483 | debug_push_class(__CLASS__, __FUNCTION__); |
|---|
| | 484 | debug_add("Called for iteration #{$iteration}"); |
|---|
| | 485 | */ |
|---|
| | 486 | static $window_size = 0; |
|---|
| | 487 | if (!$window_size) |
|---|
| | 488 | { |
|---|
| | 489 | // Try to be smart about the window size |
|---|
| | 490 | switch (true) |
|---|
| | 491 | { |
|---|
| | 492 | case ( empty($this->_offset) |
|---|
| | 493 | && $this->_limit): |
|---|
| | 494 | // Get limited number from start (I supposed generally less than 50% will be unreadable) |
|---|
| | 495 | $window_size = round($this->_limit * 1.5); |
|---|
| | 496 | break; |
|---|
| | 497 | case ( empty($this->_limit) |
|---|
| | 498 | && $this->_offset): |
|---|
| | 499 | // Get rest from offset |
|---|
| | 500 | /* TODO: Somehow factor in that if we have huge number of objects and relatively small offset we want to increase window size |
|---|
| | 501 | $full_object_count = $this->_qb->count(); |
|---|
| | 502 | */ |
|---|
| | 503 | $window_size = round($this->_offset * 2); |
|---|
| | 504 | case ( $this->_offset > $this->_limit): |
|---|
| | 505 | // Offset is greater than limit, basically this is almost the same problem as above |
|---|
| | 506 | $window_size = round($this->_offset * 2); |
|---|
| | 507 | break; |
|---|
| | 508 | case ( $this->_limit > $this->_offset): |
|---|
| | 509 | // Limit is greater than offset, this is probably similar to getting limited number from beginning |
|---|
| | 510 | $window_size = round($this->_limit * 2); |
|---|
| | 511 | break; |
|---|
| | 512 | case ($this->_limit == $this->_offset): |
|---|
| | 513 | $window_size = round($this->_offset * 2); |
|---|
| | 514 | break; |
|---|
| | 515 | } |
|---|
| | 516 | |
|---|
| | 517 | if ($window_size > $this->max_window_size) |
|---|
| | 518 | { |
|---|
| | 519 | $window_size = $this->max_window_size; |
|---|
| | 520 | } |
|---|
| | 521 | if ($window_size < $this->min_window_size) |
|---|
| | 522 | { |
|---|
| | 523 | $window_size = $this->min_window_size; |
|---|
| | 524 | } |
|---|
| | 525 | } |
|---|
| | 526 | //debug_add("Got window size {$window_size}"); |
|---|
| | 527 | $offset = $iteration*$window_size; |
|---|
| | 528 | if ($offset) |
|---|
| | 529 | { |
|---|
| | 530 | //debug_add("Setting offset to {$offset}"); |
|---|
| | 531 | $this->_qb->set_offset($offset); |
|---|
| | 532 | } |
|---|
| | 533 | //debug_add("Setting limit to {$window_size}"); |
|---|
| | 534 | $this->_qb->set_limit($window_size); |
|---|
| | 535 | //debug_pop(); |
|---|
| | 536 | } |
|---|
| | 537 | |
|---|
| | 538 | function execute() |
|---|
| | 539 | { |
|---|
| | 540 | return $this->execute_windowed(); |
|---|
| | 541 | } |
|---|
| | 542 | |
|---|
| | 543 | /** |
|---|
| | 544 | * This function will execute the Querybuilder and call the appropriate callbacks from the |
|---|
| | 545 | * class it is associated to. This way, class authors have full control over what is actually |
|---|
| | 546 | * returned to the application. |
|---|
| | 547 | * |
|---|
| | 548 | * The calling sequence of all event handlers of the associated class is like this: |
|---|
| | 549 | * |
|---|
| | 550 | * 1. bool _on_prepare_exec_query_builder(&$this) is called before the actual query execution. Return false to |
|---|
| | 551 | * abort the operation. |
|---|
| | 552 | * 2. The query is executed. |
|---|
| | 553 | * 3. void _on_process_query_result(&$result) is called after the successful execution of the query. You |
|---|
| | 554 | * may remove any unwanted entries from the resultset at this point. |
|---|
| | 555 | * |
|---|
| | 556 | * If the execution of the query fails for some reason all available error information is logged |
|---|
| | 557 | * 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_builder |
|---|
| | 560 | * function of this class. |
|---|
| | 561 | * @return Array The result of the query builder or null on any error. Note, that empty resultsets |
|---|
| | 562 | * will return an empty array. |
|---|
| | 563 | * @todo Implement proper count / Limit support. |
|---|
| | 564 | */ |
|---|
| | 565 | function execute_notwindowed() |
|---|
| | 566 | { |
|---|
| | 567 | debug_push_class(__CLASS__, __FUNCTION__); |
|---|
| | 568 | |
|---|
| | 569 | if (! call_user_func_array(array($this->_real_class, '_on_prepare_exec_query_builder'), array(&$this))) |
|---|
| | 570 | { |
|---|
| | 571 | debug_add('The _on_prepare_exec_query_builder callback returned false, so we abort now.'); |
|---|
| | 572 | debug_pop(); |
|---|
| | 573 | return null; |
|---|
| | 574 | } |
|---|
| | 575 | |
|---|
| | 576 | if ($this->_constraint_count == 0) |
|---|
| | 577 | { |
|---|
| 300 | | // Workaround until the QB does return empty arrays: All errors are empty resultsets and errors are ignored. |
|---|
| 301 | | $result = @$this->_qb->execute(); |
|---|
| 302 | | if (! is_array($result)) |
|---|
| 303 | | { |
|---|
| 304 | | // Workaround mode for now |
|---|
| 305 | | if (mgd_errno() != MGD_ERR_OK) |
|---|
| 306 | | { |
|---|
| 307 | | debug_print_r('Result was:', $result); |
|---|
| 308 | | debug_add('The querybuilder failed to execute, aborting.', MIDCOM_LOG_ERROR); |
|---|
| 309 | | debug_add('Last Midgard error was: ' . mgd_errstr(), MIDCOM_LOG_ERROR); |
|---|
| 310 | | if (isset($php_errormsg)) |
|---|
| 311 | | { |
|---|
| 312 | | debug_add("Error message was: {$php_errormsg}", MIDCOM_LOG_ERROR); |
|---|
| 313 | | } |
|---|
| 314 | | |
|---|
| 315 | | $_MIDCOM->generate_error(MIDCOM_ERRCRIT, |
|---|
| 316 | | 'The query builder failed to execute, see the log file for more information.'); |
|---|
| 317 | | // This will exit. |
|---|
| 318 | | } |
|---|
| 319 | | |
|---|
| 320 | | $result = Array(); |
|---|
| | 582 | $result = $this->_qb->execute(); |
|---|
| | 583 | if (!is_array($result)) |
|---|
| | 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; |
|---|
| 456 | | // Workaround until the QB does return empty arrays: All errors are empty resultsets and errors are ignored. |
|---|
| 457 | | $result = @$this->_qb->execute(); |
|---|
| 458 | | if (! is_array($result)) |
|---|
| 459 | | { |
|---|
| 460 | | // Workaround mode for now |
|---|
| 461 | | if (mgd_errstr() != 'MGD_ERR_OK') |
|---|
| 462 | | { |
|---|
| 463 | | debug_print_r('Result was:', $result); |
|---|
| 464 | | debug_add('The querybuilder failed to execute, aborting.', MIDCOM_LOG_ERROR); |
|---|
| 465 | | debug_add('Last Midgard error was: ' . mgd_errstr(), MIDCOM_LOG_ERROR); |
|---|
| 466 | | if (isset($php_errormsg)) |
|---|
| 467 | | { |
|---|
| 468 | | debug_add("Error message was: {$php_errormsg}", MIDCOM_LOG_ERROR); |
|---|
| 469 | | } |
|---|
| 470 | | |
|---|
| 471 | | $_MIDCOM->generate_error(MIDCOM_ERRCRIT, |
|---|
| 472 | | 'The query builder failed to execute, see the log file for more information.'); |
|---|
| 473 | | // This will exit. |
|---|
| 474 | | } |
|---|
| 475 | | |
|---|
| 476 | | debug_pop(); |
|---|
| 477 | | $result = Array(); |
|---|
| | 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; |
|---|