@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.

Add support for relevance-ranking Ferret engine results

Summary: Ref T12819. "Relevance" here just means "how many of your search terms are present in the title?" but that's about the best we can do anyway.

Test Plan: Indexed tasks "A B", "A Z", "Z B", and "Z Z" (all with "A B" in comments). Searched for "A B". Got results ranked in the listed order, with "A B" as the most relevant hit for query "A B".

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T12819

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

+120 -1
+120 -1
src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
··· 251 251 } 252 252 253 253 $select[] = $this->buildEdgeLogicSelectClause($conn); 254 + $select[] = $this->buildFerretSelectClause($conn); 254 255 255 256 return $select; 256 257 } ··· 769 770 } 770 771 } 771 772 773 + if ($this->supportsFerretEngine()) { 774 + $orders['relevance'] = array( 775 + 'vector' => array('rank', 'id'), 776 + 'name' => pht('Relevence'), 777 + ); 778 + } 779 + 772 780 return $orders; 773 781 } 774 782 ··· 959 967 'customfield.index.key' => $digest, 960 968 ); 961 969 } 970 + } 971 + 972 + if ($this->supportsFerretEngine()) { 973 + $columns['rank'] = array( 974 + 'table' => null, 975 + 'column' => '_ft_rank', 976 + 'type' => 'int', 977 + ); 962 978 } 963 979 964 980 $cache->setKey($cache_key, $columns); ··· 1385 1401 /* -( Ferret )------------------------------------------------------------- */ 1386 1402 1387 1403 1404 + public function supportsFerretEngine() { 1405 + $object = $this->newResultObject(); 1406 + return ($object instanceof PhabricatorFerretInterface); 1407 + } 1408 + 1409 + 1388 1410 public function withFerretConstraint( 1389 1411 PhabricatorFerretEngine $engine, 1390 1412 array $fulltext_tokens) { 1413 + 1414 + if (!$this->supportsFerretEngine()) { 1415 + throw new Exception( 1416 + pht( 1417 + 'Query ("%s") does not support the Ferret fulltext engine.', 1418 + get_class($this))); 1419 + } 1391 1420 1392 1421 if ($this->ferretEngine) { 1393 1422 throw new Exception( ··· 1416 1445 $raw_field = $engine->getFieldForFunction($function); 1417 1446 1418 1447 if (!isset($table_map[$function])) { 1419 - $alias = 'ftfield'.$idx++; 1448 + $alias = 'ftfield_'.$idx++; 1420 1449 $table_map[$function] = array( 1421 1450 'alias' => $alias, 1422 1451 'key' => $raw_field, ··· 1426 1455 $current_function = $function; 1427 1456 } 1428 1457 1458 + // Join the title field separately so we can rank results. 1459 + $table_map['rank'] = array( 1460 + 'alias' => 'ft_rank', 1461 + 'key' => PhabricatorSearchDocumentFieldType::FIELD_TITLE, 1462 + ); 1463 + 1429 1464 $this->ferretTables = $table_map; 1430 1465 1431 1466 return $this; 1467 + } 1468 + 1469 + protected function buildFerretSelectClause(AphrontDatabaseConnection $conn) { 1470 + $select = array(); 1471 + 1472 + if (!$this->supportsFerretEngine()) { 1473 + return $select; 1474 + } 1475 + 1476 + if (!$this->ferretEngine) { 1477 + $select[] = '0 _ft_rank'; 1478 + return $select; 1479 + } 1480 + 1481 + $engine = $this->ferretEngine; 1482 + $stemmer = $engine->newStemmer(); 1483 + 1484 + $op_sub = PhutilSearchQueryCompiler::OPERATOR_SUBSTRING; 1485 + $op_not = PhutilSearchQueryCompiler::OPERATOR_NOT; 1486 + $table_alias = 'ft_rank'; 1487 + 1488 + $parts = array(); 1489 + foreach ($this->ferretTokens as $fulltext_token) { 1490 + $raw_token = $fulltext_token->getToken(); 1491 + $value = $raw_token->getValue(); 1492 + 1493 + if ($raw_token->getOperator() == $op_not) { 1494 + // Ignore "not" terms when ranking, since they aren't useful. 1495 + continue; 1496 + } 1497 + 1498 + if ($raw_token->getOperator() == $op_sub) { 1499 + $is_substring = true; 1500 + } else { 1501 + $is_substring = false; 1502 + } 1503 + 1504 + if ($is_substring) { 1505 + $parts[] = qsprintf( 1506 + $conn, 1507 + 'IF(%T.rawCorpus LIKE %~, 2, 0)', 1508 + $table_alias, 1509 + $value); 1510 + continue; 1511 + } 1512 + 1513 + if ($raw_token->isQuoted()) { 1514 + $is_quoted = true; 1515 + $is_stemmed = false; 1516 + } else { 1517 + $is_quoted = false; 1518 + $is_stemmed = true; 1519 + } 1520 + 1521 + $term_constraints = array(); 1522 + 1523 + $term_value = $engine->newTermsCorpus($value); 1524 + 1525 + $parts[] = qsprintf( 1526 + $conn, 1527 + 'IF(%T.termCorpus LIKE %~, 2, 0)', 1528 + $table_alias, 1529 + $term_value); 1530 + 1531 + if ($is_stemmed) { 1532 + $stem_value = $stemmer->stemToken($value); 1533 + $stem_value = $engine->newTermsCorpus($stem_value); 1534 + 1535 + $parts[] = qsprintf( 1536 + $conn, 1537 + 'IF(%T.normalCorpus LIKE %~, 1, 0)', 1538 + $table_alias, 1539 + $stem_value); 1540 + } 1541 + 1542 + $parts[] = '0'; 1543 + } 1544 + 1545 + $select[] = qsprintf( 1546 + $conn, 1547 + '%Q _ft_rank', 1548 + implode(' + ', $parts)); 1549 + 1550 + return $select; 1432 1551 } 1433 1552 1434 1553 protected function buildFerretJoinClause(AphrontDatabaseConnection $conn) {