Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15326662
D19738.id47164.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
8 KB
Referenced Files
None
Subscribers
None
D19738.id47164.diff
View Options
diff --git a/src/applications/transactions/bulk/management/PhabricatorBulkManagementExportWorkflow.php b/src/applications/transactions/bulk/management/PhabricatorBulkManagementExportWorkflow.php
--- a/src/applications/transactions/bulk/management/PhabricatorBulkManagementExportWorkflow.php
+++ b/src/applications/transactions/bulk/management/PhabricatorBulkManagementExportWorkflow.php
@@ -26,7 +26,8 @@
'name' => 'query',
'param' => 'key',
'help' => pht(
- 'Export the data selected by this query.'),
+ 'Export the data selected by one or more queries.'),
+ 'repeat' => true,
),
array(
'name' => 'output',
@@ -47,56 +48,7 @@
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
- $class = $args->getArg('class');
-
- if (!strlen($class)) {
- throw new PhutilArgumentUsageException(
- pht(
- 'Specify a search engine class to export data from with '.
- '"--class".'));
- }
-
- if (!is_subclass_of($class, 'PhabricatorApplicationSearchEngine')) {
- throw new PhutilArgumentUsageException(
- pht(
- 'SearchEngine class ("%s") is unknown.',
- $class));
- }
-
- $engine = newv($class, array())
- ->setViewer($viewer);
-
- if (!$engine->canExport()) {
- throw new PhutilArgumentUsageException(
- pht(
- 'SearchEngine class ("%s") does not support data export.',
- $class));
- }
-
- $query_key = $args->getArg('query');
- if (!strlen($query_key)) {
- throw new PhutilArgumentUsageException(
- pht(
- 'Specify a query to export with "--query".'));
- }
-
- if ($engine->isBuiltinQuery($query_key)) {
- $saved_query = $engine->buildSavedQueryFromBuiltin($query_key);
- } else if ($query_key) {
- $saved_query = id(new PhabricatorSavedQueryQuery())
- ->setViewer($viewer)
- ->withQueryKeys(array($query_key))
- ->executeOne();
- } else {
- $saved_query = null;
- }
-
- if (!$saved_query) {
- throw new PhutilArgumentUsageException(
- pht(
- 'Failed to load saved query ("%s").',
- $query_key));
- }
+ list($engine, $queries) = $this->newQueries($args);
$format_key = $args->getArg('format');
if (!strlen($format_key)) {
@@ -140,6 +92,15 @@
}
}
+ // If we have more than one query, execute the queries to figure out which
+ // results they hit, then build a synthetic query for all those results
+ // using the IDs.
+ if (count($queries) > 1) {
+ $saved_query = $this->newUnionQuery($engine, $queries);
+ } else {
+ $saved_query = head($queries);
+ }
+
$export_engine = id(new PhabricatorExportEngine())
->setViewer($viewer)
->setTitle(pht('Export'))
@@ -165,4 +126,179 @@
return 0;
}
+ private function newQueries(PhutilArgumentParser $args) {
+ $viewer = $this->getViewer();
+
+ $query_keys = $args->getArg('query');
+ if (!$query_keys) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Specify one or more queries to export with "--query".'));
+ }
+
+ $engine_classes = id(new PhutilClassMapQuery())
+ ->setAncestorClass('PhabricatorApplicationSearchEngine')
+ ->execute();
+
+ $class = $args->getArg('class');
+ if (strlen($class)) {
+
+ $class_list = array();
+ foreach ($engine_classes as $class_name => $engine_object) {
+ $can_export = id(clone $engine_object)
+ ->setViewer($viewer)
+ ->canExport();
+ if ($can_export) {
+ $class_list[] = $class_name;
+ }
+ }
+
+ sort($class_list);
+ $class_list = implode(', ', $class_list);
+
+ $matches = array();
+ foreach ($engine_classes as $class_name => $engine_object) {
+ if (stripos($class_name, $class) !== false) {
+ if (strtolower($class_name) == strtolower($class)) {
+ $matches = array($class_name);
+ break;
+ } else {
+ $matches[] = $class_name;
+ }
+ }
+ }
+
+ if (!$matches) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'No search engines match "%s". Available engines which support '.
+ 'data export are: %s.',
+ $class,
+ $class_list));
+ } else if (count($matches) > 1) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Multiple search engines match "%s": %s.',
+ $class,
+ implode(', ', $matches)));
+ } else {
+ $class = head($matches);
+ }
+
+ $engine = newv($class, array())
+ ->setViewer($viewer);
+ } else {
+ $engine = null;
+ }
+
+ $queries = array();
+ foreach ($query_keys as $query_key) {
+ if ($engine) {
+ if ($engine->isBuiltinQuery($query_key)) {
+ $queries[$query_key] = $engine->buildSavedQueryFromBuiltin(
+ $query_key);
+ continue;
+ }
+ }
+
+ $saved_query = id(new PhabricatorSavedQueryQuery())
+ ->setViewer($viewer)
+ ->withQueryKeys(array($query_key))
+ ->executeOne();
+ if (!$saved_query) {
+ if (!$engine) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Query "%s" is unknown. To run a builtin query like "all" or '.
+ '"active", also specify the search engine with "--class".',
+ $query_key));
+ } else {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Query "%s" is not a recognized query for class "%s".',
+ $query_key,
+ get_class($engine)));
+ }
+ }
+
+ $queries[$query_key] = $saved_query;
+ }
+
+ // If we don't have an engine from "--class", fill it in by looking at the
+ // class of the first query.
+ if (!$engine) {
+ foreach ($queries as $query) {
+ $engine = newv($query->getEngineClassName(), array())
+ ->setViewer($viewer);
+ break;
+ }
+ }
+
+ $engine_class = get_class($engine);
+
+ foreach ($queries as $query) {
+ $query_class = $query->getEngineClassName();
+ if ($query_class !== $engine_class) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Specified queries use different engines: query "%s" uses '.
+ 'engine "%s", not "%s". All queries must run on the same '.
+ 'engine.',
+ $query->getQueryKey(),
+ $query_class,
+ $engine_class));
+ }
+ }
+
+ if (!$engine->canExport()) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'SearchEngine class ("%s") does not support data export.',
+ $engine_class));
+ }
+
+ return array($engine, $queries);
+ }
+
+ private function newUnionQuery(
+ PhabricatorApplicationSearchEngine $engine,
+ array $queries) {
+
+ assert_instances_of($queries, 'PhabricatorSavedQuery');
+
+ $engine = clone $engine;
+
+ $ids = array();
+ foreach ($queries as $saved_query) {
+ $page_size = 1000;
+ $page_cursor = null;
+ do {
+ $query = $engine->buildQueryFromSavedQuery($saved_query);
+ $pager = $engine->newPagerForSavedQuery($saved_query);
+ $pager->setPageSize($page_size);
+
+ if ($page_cursor !== null) {
+ $pager->setAfterID($page_cursor);
+ }
+
+ $objects = $engine->executeQuery($query, $pager);
+ $page_cursor = $pager->getNextPageID();
+
+ foreach ($objects as $object) {
+ $ids[] = $object->getID();
+ }
+ } while ($pager->getHasMoreResults());
+ }
+
+ // When we're merging multiple different queries, override any query order
+ // and just put the combined result list in ID order. At time of writing,
+ // we can't merge the result sets together while retaining the overall sort
+ // order even if they all used the same order, and it's meaningless to try
+ // to retain orders if the queries had different orders in the first place.
+ rsort($ids);
+
+ return id($engine->newSavedQuery())
+ ->setParameter('ids', $ids);
+ }
+
}
diff --git a/src/infrastructure/export/engine/PhabricatorExportEngine.php b/src/infrastructure/export/engine/PhabricatorExportEngine.php
--- a/src/infrastructure/export/engine/PhabricatorExportEngine.php
+++ b/src/infrastructure/export/engine/PhabricatorExportEngine.php
@@ -125,7 +125,7 @@
$field_list = mpull($field_list, null, 'getKey');
$format->addHeaders($field_list);
- // Iterate over the query results in large page so we don't have to hold
+ // Iterate over the query results in large pages so we don't have to hold
// too much stuff in memory.
$page_size = 1000;
$page_cursor = null;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Mar 8, 3:49 AM (2 w, 23 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7369104
Default Alt Text
D19738.id47164.diff (8 KB)
Attached To
Mode
D19738: Allow "bin/bulk export" to merge multiple queries and accept more flexible flags
Attached
Detach File
Event Timeline
Log In to Comment