1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-20 11:41:08 +01:00

Allow filtering of "date" custom fields

Summary: Ref T4663. Ref T4659. Allows "date" fields to be filtered with range parameters.

Test Plan:
  - Added a custom "date" field with "search".
  - Populated some values.
  - Searched for dates using new range filters.
  - Combined date search with other searches.
  - Ran other searches independently.
  - Inspected the generated queries.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: shadowhand, epriestley

Maniphest Tasks: T4659, T4663

Differential Revision: https://secure.phabricator.com/D8598
This commit is contained in:
epriestley 2014-03-25 14:21:32 -07:00
parent 17dee98d32
commit 8e88187835
2 changed files with 190 additions and 27 deletions

View file

@ -81,9 +81,73 @@ final class PhabricatorStandardCustomFieldDate
return $control;
}
// TODO: Support ApplicationSearch for these fields. We build indexes above,
// but don't provide a UI for searching. To do so, we need a reasonable date
// range control and the ability to add a range constraint.
public function readApplicationSearchValueFromRequest(
PhabricatorApplicationSearchEngine $engine,
AphrontRequest $request) {
$key = $this->getFieldKey();
return array(
'min' => $request->getStr($key.'.min'),
'max' => $request->getStr($key.'.max'),
);
}
public function applyApplicationSearchConstraintToQuery(
PhabricatorApplicationSearchEngine $engine,
PhabricatorCursorPagedPolicyAwareQuery $query,
$value) {
$viewer = $this->getViewer();
if (!is_array($value)) {
$value = array();
}
$min_str = idx($value, 'min', '');
if (strlen($min_str)) {
$min = PhabricatorTime::parseLocalTime($min_str, $viewer);
} else {
$min = null;
}
$max_str = idx($value, 'max', '');
if (strlen($max_str)) {
$max = PhabricatorTime::parseLocalTime($max_str, $viewer);
} else {
$max = null;
}
if (($min !== null) || ($max !== null)) {
$query->withApplicationSearchRangeConstraint(
$this->newNumericIndex(null),
$min,
$max);
}
}
public function appendToApplicationSearchForm(
PhabricatorApplicationSearchEngine $engine,
AphrontFormView $form,
$value,
array $handles) {
if (!is_array($value)) {
$value = array();
}
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('%s After', $this->getFieldName()))
->setName($this->getFieldKey().'.min')
->setValue(idx($value, 'min', '')))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('%s Before', $this->getFieldName()))
->setName($this->getFieldKey().'.max')
->setValue(idx($value, 'max', '')));
}
public function getApplicationTransactionTitle(
PhabricatorApplicationTransaction $xaction) {

View file

@ -285,16 +285,17 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
/**
* Constrain the query with an ApplicationSearch index. This adds a constraint
* which requires objects to have one or more corresponding rows in the index
* with one of the given values. Combined with appropriate indexes, it can
* build the most common types of queries, like:
* Constrain the query with an ApplicationSearch index, requiring field values
* contain at least one of the values in a set.
*
* This constraint can build the most common types of queries, like:
*
* - Find users with shirt sizes "X" or "XL".
* - Find shoes with size "13".
*
* @param PhabricatorCustomFieldIndexStorage Table where the index is stored.
* @param string|list<string> One or more values to filter by.
* @return this
* @task appsearch
*/
public function withApplicationSearchContainsConstraint(
@ -313,6 +314,51 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
}
/**
* Constrain the query with an ApplicationSearch index, requiring values
* exist in a given range.
*
* This constraint is useful for expressing date ranges:
*
* - Find events between July 1st and July 7th.
*
* The ends of the range are inclusive, so a `$min` of `3` and a `$max` of
* `5` will match fields with values `3`, `4`, or `5`. Providing `null` for
* either end of the range will leave that end of the constraint open.
*
* @param PhabricatorCustomFieldIndexStorage Table where the index is stored.
* @param int|null Minimum permissible value, inclusive.
* @param int|null Maximum permissible value, inclusive.
* @return this
* @task appsearch
*/
public function withApplicationSearchRangeConstraint(
PhabricatorCustomFieldIndexStorage $index,
$min,
$max) {
$index_type = $index->getIndexValueType();
if ($index_type != 'int') {
throw new Exception(
pht(
'Attempting to apply a range constraint to a field with index type '.
'"%s", expected type "%s".',
$index_type,
'int'));
}
$this->applicationSearchConstraints[] = array(
'type' => $index->getIndexValueType(),
'cond' => 'range',
'table' => $index->getTableName(),
'index' => $index->getIndexKey(),
'value' => array($min, $max),
);
return $this;
}
/**
* Get the name of the query's primary object PHID column, for constructing
* JOIN clauses. Normally (and by default) this is just `"phid"`, but if the
@ -339,16 +385,29 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
foreach ($this->applicationSearchConstraints as $constraint) {
$type = $constraint['type'];
$value = $constraint['value'];
$cond = $constraint['cond'];
switch ($type) {
case 'string':
case 'int':
if (count((array)$value) > 1) {
return true;
switch ($cond) {
case '=':
switch ($type) {
case 'string':
case 'int':
if (count((array)$value) > 1) {
return true;
}
break;
default:
throw new Exception(pht('Unknown index type "%s"!', $type));
}
break;
case 'range':
// NOTE: It's possible to write a custom field where multiple rows
// match a range constraint, but we don't currently ship any in the
// upstream and I can't immediately come up with cases where this
// would make sense.
break;
default:
throw new Exception("Unknown constraint type '{$type}!");
throw new Exception(pht('Unknown constraint condition "%s"!', $cond));
}
}
@ -395,44 +454,84 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
$index = $constraint['index'];
$cond = $constraint['cond'];
$phid_column = $this->getApplicationSearchObjectPHIDColumn();
if ($cond !== '=') {
throw new Exception("Unknown constraint condition '{$cond}'!");
}
switch ($cond) {
case '=':
$type = $constraint['type'];
switch ($type) {
case 'string':
$constraint_clause = qsprintf(
$conn_r,
'%T.indexValue IN (%Ls)',
$alias,
(array)$constraint['value']);
break;
case 'int':
$constraint_clause = qsprintf(
$conn_r,
'%T.indexValue IN (%Ld)',
$alias,
(array)$constraint['value']);
break;
default:
throw new Exception(pht('Unknown index type "%s"!', $type));
}
$type = $constraint['type'];
switch ($type) {
case 'string':
$joins[] = qsprintf(
$conn_r,
'JOIN %T %T ON %T.objectPHID = %Q
AND %T.indexKey = %s
AND %T.indexValue IN (%Ls)',
AND (%Q)',
$table,
$alias,
$alias,
$phid_column,
$alias,
$index,
$alias,
(array)$constraint['value']);
$constraint_clause);
break;
case 'int':
case 'range':
list($min, $max) = $constraint['value'];
if (($min === null) && ($max === null)) {
// If there's no actual range constraint, just move on.
break;
}
if ($min === null) {
$constraint_clause = qsprintf(
$conn_r,
'%T.indexValue <= %d',
$alias,
$max);
} else if ($max === null) {
$constraint_clause = qsprintf(
$conn_r,
'%T.indexValue >= %d',
$alias,
$min);
} else {
$constraint_clause = qsprintf(
$conn_r,
'%T.indexValue BETWEEN %d AND %d',
$alias,
$min,
$max);
}
$joins[] = qsprintf(
$conn_r,
'JOIN %T %T ON %T.objectPHID = %Q
AND %T.indexKey = %s
AND %T.indexValue IN (%Ld)',
AND (%Q)',
$table,
$alias,
$alias,
$phid_column,
$alias,
$index,
$alias,
(array)$constraint['value']);
$constraint_clause);
break;
default:
throw new Exception("Unknown constraint type '{$type}'!");
throw new Exception(pht('Unknown constraint condition "%s"!', $cond));
}
}