@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Modernize more paging/order queries

Summary:
Ref T7803. Removes some getReversePaging().

This also fixes `null` column handling, by adding an explicit `'null'` key with possible values "head" (put NULL before other values) or "tail" (put NULL after other values).

Maniphest has some glitchiness in paging through NULLs right now, but I believe it's all pre-existing and will be resolved when it fully converts. Diffusion is fully converted and pages through NULL correctly.

Test Plan:
- Failed to identify any reason for ChangesetQuery to reverse paging.
- Paged thorugh Diffusion.
- Paged through Maniphest.
- Maniphest has some issues when paging inside a NULL section, but these issues are preexisting and will be resolved later in this change sequence.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T7803

Differential Revision: https://secure.phabricator.com/D12371

+228 -211
-4
src/applications/differential/query/DifferentialChangesetQuery.php
··· 150 150 return 'PhabricatorDifferentialApplication'; 151 151 } 152 152 153 - protected function getReversePaging() { 154 - return true; 155 - } 156 - 157 153 }
+24 -33
src/applications/maniphest/query/ManiphestTaskQuery.php
··· 969 969 } 970 970 971 971 protected function buildPagingClause(AphrontDatabaseConnection $conn_r) { 972 - $default = parent::buildPagingClause($conn_r); 973 - 974 972 $before_id = $this->getBeforeID(); 975 973 $after_id = $this->getAfterID(); 976 974 977 975 if (!$before_id && !$after_id) { 978 - return $default; 976 + return ''; 979 977 } 980 978 981 979 $cursor_id = nonempty($before_id, $after_id); ··· 1004 1002 ); 1005 1003 break; 1006 1004 case self::GROUP_OWNER: 1007 - $columns[] = array( 1008 - 'table' => 'task', 1009 - 'column' => 'ownerOrdering', 1010 - 'value' => strlen($group_id), 1011 - 'type' => 'null', 1012 - ); 1005 + $value = null; 1013 1006 if ($group_id) { 1014 1007 $paging_users = id(new PhabricatorPeopleQuery()) 1015 1008 ->setViewer($this->getViewer()) 1016 1009 ->withPHIDs(array($group_id)) 1017 1010 ->execute(); 1018 - if (!$paging_users) { 1019 - return null; 1011 + if ($paging_users) { 1012 + $value = head($paging_users)->getUsername(); 1020 1013 } 1021 - $columns[] = array( 1022 - 'table' => 'task', 1023 - 'column' => 'ownerOrdering', 1024 - 'value' => head($paging_users)->getUsername(), 1025 - 'type' => 'string', 1026 - 'reverse' => true, 1027 - ); 1028 1014 } 1015 + $columns[] = array( 1016 + 'table' => 'task', 1017 + 'column' => 'ownerOrdering', 1018 + 'value' => $value, 1019 + 'type' => 'string', 1020 + 'null' => 'head', 1021 + 'reverse' => true, 1022 + ); 1029 1023 break; 1030 1024 case self::GROUP_STATUS: 1031 1025 $columns[] = array( ··· 1036 1030 ); 1037 1031 break; 1038 1032 case self::GROUP_PROJECT: 1039 - $columns[] = array( 1040 - 'table' => 'projectGroupName', 1041 - 'column' => 'indexedObjectName', 1042 - 'value' => strlen($group_id), 1043 - 'type' => 'null', 1044 - ); 1033 + $value = null; 1045 1034 if ($group_id) { 1046 1035 $paging_projects = id(new PhabricatorProjectQuery()) 1047 1036 ->setViewer($this->getViewer()) 1048 1037 ->withPHIDs(array($group_id)) 1049 1038 ->execute(); 1050 - if (!$paging_projects) { 1051 - return null; 1039 + if ($paging_projects) { 1040 + $value = head($paging_projects)->getName(); 1052 1041 } 1053 - $columns[] = array( 1054 - 'table' => 'projectGroupName', 1055 - 'column' => 'indexedObjectName', 1056 - 'value' => head($paging_projects)->getName(), 1057 - 'type' => 'string', 1058 - 'reverse' => true, 1059 - ); 1060 1042 } 1043 + 1044 + $columns[] = array( 1045 + 'table' => 'projectGroupName', 1046 + 'column' => 'indexedObjectName', 1047 + 'value' => $value, 1048 + 'type' => 'string', 1049 + 'null' => 'head', 1050 + 'reverse' => true, 1051 + ); 1061 1052 break; 1062 1053 default: 1063 1054 throw new Exception("Unknown group query '{$this->groupBy}'!");
+3 -7
src/applications/phrequent/query/PhrequentUserTimeQuery.php
··· 56 56 $this->setOrderVector(array('start', 'id')); 57 57 break; 58 58 case self::ORDER_ENDED_ASC: 59 - $this->setOrderVector(array('-ongoing', '-end', '-id')); 59 + $this->setOrderVector(array('-end', '-id')); 60 60 break; 61 61 case self::ORDER_ENDED_DESC: 62 - $this->setOrderVector(array('ongoing', 'end', 'id')); 62 + $this->setOrderVector(array('end', 'id')); 63 63 break; 64 64 default: 65 65 throw new Exception(pht('Unknown order "%s".', $order)); ··· 125 125 'column' => 'dateStarted', 126 126 'type' => 'int', 127 127 ), 128 - 'ongoing' => array( 129 - 'column' => 'dateEnded', 130 - 'type' => 'null', 131 - ), 132 128 'end' => array( 133 129 'column' => 'dateEnded', 134 130 'type' => 'int', 131 + 'null' => 'head', 135 132 ), 136 133 ); 137 134 } ··· 141 138 return array( 142 139 'id' => $usertime->getID(), 143 140 'start' => $usertime->getDateStarted(), 144 - 'ongoing' => $usertime->getDateEnded(), 145 141 'end' => $usertime->getDateEnded(), 146 142 ); 147 143 }
+90 -136
src/applications/repository/query/PhabricatorRepositoryQuery.php
··· 28 28 const ORDER_CALLSIGN = 'order-callsign'; 29 29 const ORDER_NAME = 'order-name'; 30 30 const ORDER_SIZE = 'order-size'; 31 - private $order = self::ORDER_CREATED; 32 31 33 32 const HOSTED_PHABRICATOR = 'hosted-phab'; 34 33 const HOSTED_REMOTE = 'hosted-remote'; ··· 126 125 } 127 126 128 127 public function setOrder($order) { 129 - $this->order = $order; 128 + switch ($order) { 129 + case self::ORDER_CREATED: 130 + $this->setOrderVector(array('id')); 131 + break; 132 + case self::ORDER_COMMITTED: 133 + $this->setOrderVector(array('committed', 'id')); 134 + break; 135 + case self::ORDER_CALLSIGN: 136 + $this->setOrderVector(array('callsign')); 137 + break; 138 + case self::ORDER_NAME: 139 + $this->setOrderVector(array('name', 'id')); 140 + break; 141 + case self::ORDER_SIZE: 142 + $this->setOrderVector(array('size', 'id')); 143 + break; 144 + default: 145 + throw new Exception(pht('Unknown order "%s".', $order)); 146 + } 130 147 return $this; 131 148 } 132 149 ··· 152 169 $table->getTableName(), 153 170 $this->buildJoinsClause($conn_r), 154 171 $this->buildWhereClause($conn_r), 155 - $this->buildCustomOrderClause($conn_r), 172 + $this->buildOrderClause($conn_r), 156 173 $this->buildLimitClause($conn_r)); 157 174 158 175 $repositories = $table->loadAllFromArray($data); ··· 295 312 return $repositories; 296 313 } 297 314 298 - protected function buildCustomOrderClause(AphrontDatabaseConnection $conn) { 299 - $parts = array(); 315 + public function getPrimaryTableAlias() { 316 + return 'r'; 317 + } 300 318 301 - $order = $this->order; 302 - switch ($order) { 303 - case self::ORDER_CREATED: 304 - break; 305 - case self::ORDER_COMMITTED: 306 - $parts[] = array( 307 - 'table' => 's', 308 - 'column' => 'epoch', 309 - ); 310 - break; 311 - case self::ORDER_CALLSIGN: 312 - $parts[] = array( 313 - 'table' => 'r', 314 - 'column' => 'callsign', 315 - 'reverse' => true, 316 - ); 317 - break; 318 - case self::ORDER_NAME: 319 - $parts[] = array( 320 - 'table' => 'r', 321 - 'column' => 'name', 322 - 'reverse' => true, 323 - ); 324 - break; 325 - case self::ORDER_SIZE: 326 - $parts[] = array( 327 - 'table' => 's', 328 - 'column' => 'size', 329 - ); 330 - break; 331 - default: 332 - throw new Exception("Unknown order '{$order}!'"); 333 - } 334 - 335 - $parts[] = array( 336 - 'table' => 'r', 337 - 'column' => 'id', 319 + public function getOrderableColumns() { 320 + return parent::getOrderableColumns() + array( 321 + 'committed' => array( 322 + 'table' => 's', 323 + 'column' => 'epoch', 324 + 'type' => 'int', 325 + 'null' => 'tail', 326 + ), 327 + 'callsign' => array( 328 + 'table' => 'r', 329 + 'column' => 'callsign', 330 + 'type' => 'string', 331 + 'unique' => true, 332 + 'reverse' => true, 333 + ), 334 + 'name' => array( 335 + 'table' => 'r', 336 + 'column' => 'name', 337 + 'type' => 'string', 338 + 'reverse' => true, 339 + ), 340 + 'size' => array( 341 + 'table' => 's', 342 + 'column' => 'size', 343 + 'type' => 'int', 344 + 'null' => 'tail', 345 + ), 338 346 ); 339 - 340 - return $this->formatOrderClause($conn, $parts); 341 347 } 342 348 343 - protected function loadCursorObject($id) { 344 - $query = id(new PhabricatorRepositoryQuery()) 345 - ->setViewer($this->getPagingViewer()) 346 - ->withIDs(array((int)$id)); 349 + protected function willExecuteCursorQuery( 350 + PhabricatorCursorPagedPolicyAwareQuery $query) { 351 + $vector = $this->getOrderVector(); 347 352 348 - if ($this->order == self::ORDER_COMMITTED) { 353 + if ($vector->containsKey('committed')) { 349 354 $query->needMostRecentCommits(true); 350 355 } 351 356 352 - if ($this->order == self::ORDER_SIZE) { 357 + if ($vector->containsKey('size')) { 353 358 $query->needCommitCounts(true); 354 359 } 355 - 356 - $results = $query->execute(); 357 - return head($results); 358 360 } 359 361 360 - protected function buildPagingClause(AphrontDatabaseConnection $conn_r) { 361 - $default = parent::buildPagingClause($conn_r); 362 - 363 - $before_id = $this->getBeforeID(); 364 - $after_id = $this->getAfterID(); 365 - 366 - if (!$before_id && !$after_id) { 367 - return $default; 368 - } 369 - 370 - $order = $this->order; 371 - if ($order == self::ORDER_CREATED) { 372 - return $default; 373 - } 374 - 375 - if ($before_id) { 376 - $cursor = $this->loadCursorObject($before_id); 377 - } else { 378 - $cursor = $this->loadCursorObject($after_id); 379 - } 380 - 381 - if (!$cursor) { 382 - return null; 383 - } 362 + protected function getPagingValueMap($cursor, array $keys) { 363 + $repository = $this->loadCursorObject($cursor); 384 364 385 - $id_column = array( 386 - 'table' => 'r', 387 - 'column' => 'id', 388 - 'type' => 'int', 389 - 'value' => $cursor->getID(), 365 + $map = array( 366 + 'id' => $repository->getID(), 367 + 'callsign' => $repository->getCallsign(), 368 + 'name' => $repository->getName(), 390 369 ); 391 370 392 - $columns = array(); 393 - switch ($order) { 394 - case self::ORDER_COMMITTED: 395 - $commit = $cursor->getMostRecentCommit(); 396 - if (!$commit) { 397 - return null; 398 - } 399 - $columns[] = array( 400 - 'table' => 's', 401 - 'column' => 'epoch', 402 - 'type' => 'int', 403 - 'value' => $commit->getEpoch(), 404 - ); 405 - $columns[] = $id_column; 406 - break; 407 - case self::ORDER_CALLSIGN: 408 - $columns[] = array( 409 - 'table' => 'r', 410 - 'column' => 'callsign', 411 - 'type' => 'string', 412 - 'value' => $cursor->getCallsign(), 413 - 'reverse' => true, 414 - ); 415 - break; 416 - case self::ORDER_NAME: 417 - $columns[] = array( 418 - 'table' => 'r', 419 - 'column' => 'name', 420 - 'type' => 'string', 421 - 'value' => $cursor->getName(), 422 - 'reverse' => true, 423 - ); 424 - $columns[] = $id_column; 425 - break; 426 - case self::ORDER_SIZE: 427 - $columns[] = array( 428 - 'table' => 's', 429 - 'column' => 'size', 430 - 'type' => 'int', 431 - 'value' => $cursor->getCommitCount(), 432 - ); 433 - $columns[] = $id_column; 434 - break; 435 - default: 436 - throw new Exception("Unknown order '{$order}'!"); 371 + foreach ($keys as $key) { 372 + switch ($key) { 373 + case 'committed': 374 + $commit = $repository->getMostRecentCommit(); 375 + if ($commit) { 376 + $map[$key] = $commit->getEpoch(); 377 + } else { 378 + $map[$key] = null; 379 + } 380 + break; 381 + case 'size': 382 + $count = $repository->getCommitCount(); 383 + if ($count) { 384 + $map[$key] = $count; 385 + } else { 386 + $map[$key] = null; 387 + } 388 + break; 389 + } 437 390 } 438 391 439 - return $this->buildPagingClauseFromMultipleColumns( 440 - $conn_r, 441 - $columns, 442 - array( 443 - 'reversed' => ($this->getReversePaging() xor (bool)($before_id)), 444 - )); 392 + return $map; 445 393 } 446 394 447 395 private function buildJoinsClause(AphrontDatabaseConnection $conn_r) { 448 396 $joins = array(); 449 397 450 398 $join_summary_table = $this->needCommitCounts || 451 - $this->needMostRecentCommits || 452 - ($this->order == self::ORDER_COMMITTED) || 453 - ($this->order == self::ORDER_SIZE); 399 + $this->needMostRecentCommits; 400 + 401 + $vector = $this->getOrderVector(); 402 + if ($vector->containsKey('committed') || 403 + $vector->containsKey('size')) { 404 + $join_summary_table = true; 405 + } 454 406 455 407 if ($join_summary_table) { 456 408 $joins[] = qsprintf( ··· 553 505 554 506 return $this->formatWhereClause($where); 555 507 } 508 + 509 + 556 510 557 511 public function getQueryApplicationClass() { 558 512 return 'PhabricatorDiffusionApplication';
+111 -31
src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
··· 320 320 'type' => 'string', 321 321 'reverse' => 'optional bool', 322 322 'unique' => 'optional bool', 323 + 'null' => 'optional string|null', 323 324 )); 324 325 } 325 326 ··· 336 337 $last_key = last_key($columns); 337 338 foreach ($columns as $key => $column) { 338 339 $type = $column['type']; 339 - switch ($type) { 340 - case 'null': 341 - $value = qsprintf($conn, '%d', ($column['value'] ? 0 : 1)); 342 - break; 343 - case 'int': 344 - $value = qsprintf($conn, '%d', $column['value']); 345 - break; 346 - case 'float': 347 - $value = qsprintf($conn, '%f', $column['value']); 348 - break; 349 - case 'string': 350 - $value = qsprintf($conn, '%s', $column['value']); 351 - break; 352 - default: 353 - throw new Exception("Unknown column type '{$type}'!"); 340 + 341 + $null = idx($column, 'null'); 342 + if ($column['value'] === null) { 343 + if ($null) { 344 + $value = null; 345 + } else { 346 + throw new Exception( 347 + pht( 348 + 'Column "%s" has null value, but does not specify a null '. 349 + 'behavior.', 350 + $key)); 351 + } 352 + } else { 353 + switch ($type) { 354 + case 'int': 355 + $value = qsprintf($conn, '%d', $column['value']); 356 + break; 357 + case 'float': 358 + $value = qsprintf($conn, '%f', $column['value']); 359 + break; 360 + case 'string': 361 + $value = qsprintf($conn, '%s', $column['value']); 362 + break; 363 + default: 364 + throw new Exception( 365 + pht( 366 + 'Column "%s" has unknown column type "%s".', 367 + $column['column'], 368 + $type)); 369 + } 354 370 } 355 371 356 372 $is_column_reversed = idx($column, 'reverse', false); ··· 366 382 $field = qsprintf($conn, '%T', $column_name); 367 383 } 368 384 369 - if ($type == 'null') { 370 - $field = qsprintf($conn, '(%Q IS NULL)', $field); 385 + $parts = array(); 386 + if ($null) { 387 + $can_page_if_null = ($null === 'head'); 388 + $can_page_if_nonnull = ($null === 'tail'); 389 + 390 + if ($reverse) { 391 + $can_page_if_null = !$can_page_if_null; 392 + $can_page_if_nonnull = !$can_page_if_nonnull; 393 + } 394 + 395 + $subclause = null; 396 + if ($can_page_if_null && $value === null) { 397 + $parts[] = qsprintf( 398 + $conn, 399 + '(%Q IS NOT NULL)', 400 + $field); 401 + } else if ($can_page_if_nonnull && $value !== null) { 402 + $parts[] = qsprintf( 403 + $conn, 404 + '(%Q IS NULL)', 405 + $field); 406 + } 371 407 } 372 408 373 - $clause[] = qsprintf( 374 - $conn, 375 - '%Q %Q %Q', 376 - $field, 377 - $reverse ? '>' : '<', 378 - $value); 379 - $clauses[] = '('.implode(') AND (', $clause).')'; 409 + if ($value !== null) { 410 + $parts[] = qsprintf( 411 + $conn, 412 + '%Q %Q %Q', 413 + $field, 414 + $reverse ? '>' : '<', 415 + $value); 416 + } 417 + 418 + if ($parts) { 419 + if (count($parts) > 1) { 420 + $clause[] = '('.implode(') OR (', $parts).')'; 421 + } else { 422 + $clause[] = head($parts); 423 + } 424 + } 425 + 426 + if ($clause) { 427 + if (count($clause) > 1) { 428 + $clauses[] = '('.implode(') AND (', $clause).')'; 429 + } else { 430 + $clauses[] = head($clause); 431 + } 432 + } 380 433 381 - $accumulated[] = qsprintf( 382 - $conn, 383 - '%Q = %Q', 384 - $field, 385 - $value); 434 + if ($value === null) { 435 + $accumulated[] = qsprintf( 436 + $conn, 437 + '%Q IS NULL', 438 + $field); 439 + } else { 440 + $accumulated[] = qsprintf( 441 + $conn, 442 + '%Q = %Q', 443 + $field, 444 + $value); 445 + } 386 446 } 387 447 388 448 return '('.implode(') OR (', $clauses).')'; ··· 576 636 $field = qsprintf($conn, '%T', $column); 577 637 } 578 638 579 - if (idx($part, 'type') === 'null') { 580 - $field = qsprintf($conn, '(%Q IS NULL)', $field); 639 + $null = idx($part, 'null'); 640 + if ($null) { 641 + switch ($null) { 642 + case 'head': 643 + $null_field = qsprintf($conn, '(%Q IS NULL)', $field); 644 + break; 645 + case 'tail': 646 + $null_field = qsprintf($conn, '(%Q IS NOT NULL)', $field); 647 + break; 648 + default: 649 + throw new Exception( 650 + pht( 651 + 'NULL value "%s" is invalid. Valid values are "head" and '. 652 + '"tail".', 653 + $null)); 654 + } 655 + 656 + if ($descending) { 657 + $sql[] = qsprintf($conn, '%Q DESC', $null_field); 658 + } else { 659 + $sql[] = qsprintf($conn, '%Q ASC', $null_field); 660 + } 581 661 } 582 662 583 663 if ($descending) {