Query assets from multiple locations in a single query.

This commit is contained in:
paul121 2021-10-27 09:03:53 -07:00 committed by Michael Stenta
parent 50d7f643c2
commit 9937730c14
7 changed files with 67 additions and 49 deletions

View File

@ -38,7 +38,7 @@ asset. Returns a log entity, or `NULL` if no logs were found.
`setIntrinsicGeometry($asset, $wkt)` - Set an asset's intrinsic geometry, given `setIntrinsicGeometry($asset, $wkt)` - Set an asset's intrinsic geometry, given
a string in Well-Known Text format. a string in Well-Known Text format.
`getAssetsByLocation($location)` - Get assets that are in a location. `getAssetsByLocation($locations)` - Get assets that are in locations.
**Example usage**: **Example usage**:

View File

@ -110,11 +110,11 @@ class GroupAssetLocation extends AssetLocation implements AssetLocationInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getAssetsByLocation(AssetInterface $location): array { public function getAssetsByLocation(array $locations): array {
// First delegate to the parent function to get assets in the location. // First delegate to the parent function to get assets in the location.
/** @var \Drupal\asset\Entity\AssetInterface[] $assets */ /** @var \Drupal\asset\Entity\AssetInterface[] $assets */
$assets = parent::getAssetsByLocation($location); $assets = parent::getAssetsByLocation($locations);
// Recursively load all group members and add them to the list of assets. // Recursively load all group members and add them to the list of assets.
$groups = array_filter($assets, function (AssetInterface $asset) { $groups = array_filter($assets, function (AssetInterface $asset) {
@ -123,22 +123,27 @@ class GroupAssetLocation extends AssetLocation implements AssetLocationInterface
$members = $this->groupMembership->getGroupMembers($groups, TRUE); $members = $this->groupMembership->getGroupMembers($groups, TRUE);
$assets = array_merge($assets, $members); $assets = array_merge($assets, $members);
// Get location ids.
$location_ids = array_map(function (AssetInterface $location) {
return $location->id();
}, $locations);
// It is possible for a group member asset to be in a different location // It is possible for a group member asset to be in a different location
// than the group, if it has a movement log that is more recent than the // than the group, if it has a movement log that is more recent than the
// group's. So iterate through all the assets and remove any that are not in // group's. So iterate through all the assets and remove any that are not in
// the location. The asset may be in multiple locations (including this // one of the specified locations. The asset may be in multiple locations
// one), so we only want to remove it if none of its locations match. // (including this one), so we only want to remove it if none of its
foreach ($assets as $key => $asset) { // locations match.
$match = FALSE; $assets = array_filter($assets, function (AssetInterface $asset) use ($location_ids) {
foreach ($this->getLocation($asset) as $asset_location) {
if ($asset_location->id() == $location->id()) { // Get asset location ids.
$match = TRUE; $asset_location_ids = array_map(function (AssetInterface $location) {
} return $location->id();
} }, $this->getLocation($asset));
if (!$match) {
unset($assets[$key]); // Only include the asset if it is in one of the specified locations.
} return !empty(array_intersect($location_ids, $asset_location_ids));
} });
// Return the assets. // Return the assets.
return $assets; return $assets;

View File

@ -307,7 +307,7 @@ class GroupTest extends KernelTestBase {
$second_pasture->save(); $second_pasture->save();
// Confirm that new locations are empty. // Confirm that new locations are empty.
$this->assertEmpty($this->assetLocation->getAssetsByLocation($first_pasture), 'New locations are empty.'); $this->assertEmpty($this->assetLocation->getAssetsByLocation([$first_pasture]), 'New locations are empty.');
// Create a log that moves the animal to the first pasture. // Create a log that moves the animal to the first pasture.
/** @var \Drupal\log\Entity\LogInterface $second_log */ /** @var \Drupal\log\Entity\LogInterface $second_log */
@ -323,8 +323,8 @@ class GroupTest extends KernelTestBase {
// Confirm that the animal is located in the first pasture. // Confirm that the animal is located in the first pasture.
$this->assertEquals($this->logLocation->getLocation($second_log), $this->assetLocation->getLocation($animal), 'Asset location is determined by asset membership log.'); $this->assertEquals($this->logLocation->getLocation($second_log), $this->assetLocation->getLocation($animal), 'Asset location is determined by asset membership log.');
$this->assertEquals($this->logLocation->getGeometry($second_log), $this->assetLocation->getGeometry($animal), 'Asset geometry is determined by asset membership log.'); $this->assertEquals($this->logLocation->getGeometry($second_log), $this->assetLocation->getGeometry($animal), 'Asset geometry is determined by asset membership log.');
$this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($first_pasture)), 'Locations have assets that are moved to them.'); $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$first_pasture])), 'Locations have assets that are moved to them.');
$this->assertEmpty($this->assetLocation->getAssetsByLocation($second_pasture), 'Locations that do not have assets moved to them are unaffected.'); $this->assertEmpty($this->assetLocation->getAssetsByLocation([$second_pasture]), 'Locations that do not have assets moved to them are unaffected.');
// Assert that the animal's cache tags were invalidated. // Assert that the animal's cache tags were invalidated.
$this->assertEntityTestCache($animal, FALSE); $this->assertEntityTestCache($animal, FALSE);
@ -346,8 +346,8 @@ class GroupTest extends KernelTestBase {
// Confirm that the animal is located in the second pasture. // Confirm that the animal is located in the second pasture.
$this->assertEquals($this->logLocation->getLocation($third_log), $this->assetLocation->getLocation($animal), 'Asset location is determined by group membership log.'); $this->assertEquals($this->logLocation->getLocation($third_log), $this->assetLocation->getLocation($animal), 'Asset location is determined by group membership log.');
$this->assertEquals($this->logLocation->getGeometry($third_log), $this->assetLocation->getGeometry($animal), 'Asset geometry is determined by group membership log.'); $this->assertEquals($this->logLocation->getGeometry($third_log), $this->assetLocation->getGeometry($animal), 'Asset geometry is determined by group membership log.');
$this->assertEmpty($this->assetLocation->getAssetsByLocation($first_pasture), 'A group movement removes assets from their previous location.'); $this->assertEmpty($this->assetLocation->getAssetsByLocation([$first_pasture]), 'A group movement removes assets from their previous location.');
$this->assertEquals(2, count($this->assetLocation->getAssetsByLocation($second_pasture)), 'A group movement adds assets to their new location.'); $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$second_pasture])), 'A group movement adds assets to their new location.');
// Assert that the animal's cache tags were invalidated. // Assert that the animal's cache tags were invalidated.
$this->assertEntityTestCache($animal, FALSE); $this->assertEntityTestCache($animal, FALSE);
@ -369,8 +369,8 @@ class GroupTest extends KernelTestBase {
// Confirm that the animal location was unset. // Confirm that the animal location was unset.
$this->assertEquals($this->logLocation->getLocation($fourth_log), $this->assetLocation->getLocation($animal), 'Asset location can be unset by group membership log.'); $this->assertEquals($this->logLocation->getLocation($fourth_log), $this->assetLocation->getLocation($animal), 'Asset location can be unset by group membership log.');
$this->assertEquals($this->logLocation->getGeometry($fourth_log), $this->assetLocation->getGeometry($animal), 'Asset geometry can be unset by group membership log.'); $this->assertEquals($this->logLocation->getGeometry($fourth_log), $this->assetLocation->getGeometry($animal), 'Asset geometry can be unset by group membership log.');
$this->assertEmpty($this->assetLocation->getAssetsByLocation($first_pasture), 'Unsetting group location removes member assets from all locations.'); $this->assertEmpty($this->assetLocation->getAssetsByLocation([$first_pasture]), 'Unsetting group location removes member assets from all locations.');
$this->assertEmpty($this->assetLocation->getAssetsByLocation($second_pasture), 'Unsetting group location removes member assets from all locations.'); $this->assertEmpty($this->assetLocation->getAssetsByLocation([$second_pasture]), 'Unsetting group location removes member assets from all locations.');
// Assert that the animal's cache tags were invalidated. // Assert that the animal's cache tags were invalidated.
$this->assertEntityTestCache($animal, FALSE); $this->assertEntityTestCache($animal, FALSE);
@ -393,8 +393,8 @@ class GroupTest extends KernelTestBase {
// logs now. // logs now.
$this->assertEquals($this->logLocation->getLocation($second_log), $this->assetLocation->getLocation($animal), 'Asset location is determined by asset membership log.'); $this->assertEquals($this->logLocation->getLocation($second_log), $this->assetLocation->getLocation($animal), 'Asset location is determined by asset membership log.');
$this->assertEquals($this->logLocation->getGeometry($second_log), $this->assetLocation->getGeometry($animal), 'Asset geometry is determined by asset membership log.'); $this->assertEquals($this->logLocation->getGeometry($second_log), $this->assetLocation->getGeometry($animal), 'Asset geometry is determined by asset membership log.');
$this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($first_pasture)), 'Unsetting group membership adds assets to their previous location.'); $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$first_pasture])), 'Unsetting group membership adds assets to their previous location.');
$this->assertEmpty($this->assetLocation->getAssetsByLocation($second_pasture), 'Unsetting group membership removes member assets from the group location.'); $this->assertEmpty($this->assetLocation->getAssetsByLocation([$second_pasture]), 'Unsetting group membership removes member assets from the group location.');
// Assert that the animal's cache tags were invalidated. // Assert that the animal's cache tags were invalidated.
$this->assertEntityTestCache($animal, FALSE); $this->assertEntityTestCache($animal, FALSE);
@ -412,8 +412,8 @@ class GroupTest extends KernelTestBase {
// Confirm that the animal is located in the second pasture. // Confirm that the animal is located in the second pasture.
$this->assertEquals($this->logLocation->getLocation($third_log), $this->assetLocation->getLocation($animal), 'Asset location is determined by group membership log.'); $this->assertEquals($this->logLocation->getLocation($third_log), $this->assetLocation->getLocation($animal), 'Asset location is determined by group membership log.');
$this->assertEquals($this->logLocation->getGeometry($third_log), $this->assetLocation->getGeometry($animal), 'Asset geometry is determined by group membership log.'); $this->assertEquals($this->logLocation->getGeometry($third_log), $this->assetLocation->getGeometry($animal), 'Asset geometry is determined by group membership log.');
$this->assertEmpty($this->assetLocation->getAssetsByLocation($first_pasture), 'A group movement removes assets from their previous location.'); $this->assertEmpty($this->assetLocation->getAssetsByLocation([$first_pasture]), 'A group movement removes assets from their previous location.');
$this->assertEquals(2, count($this->assetLocation->getAssetsByLocation($second_pasture)), 'A group movement adds assets to their new location.'); $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$second_pasture])), 'A group movement adds assets to their new location.');
// Assert that the animal's cache tags were invalidated. // Assert that the animal's cache tags were invalidated.
$this->assertEntityTestCache($animal, FALSE); $this->assertEntityTestCache($animal, FALSE);

View File

@ -227,10 +227,19 @@ class AssetLocation implements AssetLocationInterface {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getAssetsByLocation(AssetInterface $location): array { public function getAssetsByLocation(array $locations): array {
if (empty($location->id())) {
// Get location ids.
$location_ids = array_map(function (AssetInterface $location) {
return $location->id();
}, $locations);
// Bail if there are no location ids.
if (empty($location_ids)) {
return []; return [];
} }
// Build query for assets in locations.
$query = " $query = "
-- Select asset IDs from the asset base table. -- Select asset IDs from the asset base table.
SELECT a.id SELECT a.id
@ -261,13 +270,13 @@ class AssetLocation implements AssetLocationInterface {
-- Limit results to completed movement logs to the desired location that -- Limit results to completed movement logs to the desired location that
-- took place before the given timestamp. -- took place before the given timestamp.
WHERE (lfd.is_movement = 1) AND (lfd.status = 'done') AND (lfd.timestamp <= :timestamp) AND (ll.location_target_id = :location_id) WHERE (lfd.is_movement = 1) AND (lfd.status = 'done') AND (lfd.timestamp <= :timestamp) AND (ll.location_target_id IN (:location_ids[]))
-- Exclude records with future log entries. -- Exclude records with future log entries.
AND lfd2.id IS NULL"; AND lfd2.id IS NULL";
$args = [ $args = [
':timestamp' => $this->time->getRequestTime(), ':timestamp' => $this->time->getRequestTime(),
':location_id' => $location->id(), ':location_ids[]' => $location_ids,
]; ];
$result = $this->database->query($query, $args)->fetchAll(); $result = $this->database->query($query, $args)->fetchAll();
$asset_ids = []; $asset_ids = [];

View File

@ -98,14 +98,14 @@ interface AssetLocationInterface {
public function setIntrinsicGeometry(AssetInterface $asset, string $wkt): void; public function setIntrinsicGeometry(AssetInterface $asset, string $wkt): void;
/** /**
* Get assets that are in a location. * Get assets that are in locations.
* *
* @param \Drupal\asset\Entity\AssetInterface $location * @param \Drupal\asset\Entity\AssetInterface[] $locations
* The location asset entity. * An array of location assets to lookup.
* *
* @return array * @return \Drupal\asset\Entity\AssetInterface[]
* Returns an array of assets. * Returns an array of assets.
*/ */
public function getAssetsByLocation(AssetInterface $location): array; public function getAssetsByLocation(array $locations): array;
} }

View File

@ -81,7 +81,7 @@ class AssetLocation extends ArgumentPluginBase {
// See https://www.drupal.org/project/farm/issues/3217168 for more info. // See https://www.drupal.org/project/farm/issues/3217168 for more info.
/** @var \Drupal\asset\Entity\AssetInterface $location */ /** @var \Drupal\asset\Entity\AssetInterface $location */
$location = $this->entityTypeManager->getStorage('asset')->load($this->argument); $location = $this->entityTypeManager->getStorage('asset')->load($this->argument);
$assets = $this->assetLocation->getAssetsByLocation($location); $assets = $this->assetLocation->getAssetsByLocation([$location]);
$asset_ids = []; $asset_ids = [];
foreach ($assets as $asset) { foreach ($assets as $asset) {
$asset_ids[] = $asset->id(); $asset_ids[] = $asset->id();

View File

@ -348,8 +348,7 @@ class LocationTest extends KernelTestBase {
public function testLocationAssets() { public function testLocationAssets() {
// Locations have no assets. // Locations have no assets.
$this->assertEmpty($this->assetLocation->getAssetsByLocation($this->locations[0])); $this->assertEmpty($this->assetLocation->getAssetsByLocation([$this->locations[0], $this->locations[1]]));
$this->assertEmpty($this->assetLocation->getAssetsByLocation($this->locations[1]));
// Create an asset and move it to the first location. // Create an asset and move it to the first location.
/** @var \Drupal\asset\Entity\AssetInterface $first_asset */ /** @var \Drupal\asset\Entity\AssetInterface $first_asset */
@ -370,8 +369,9 @@ class LocationTest extends KernelTestBase {
$first_log->save(); $first_log->save();
// First location has one asset, second has none. // First location has one asset, second has none.
$this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($this->locations[0]))); $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$this->locations[0]])));
$this->assertEmpty($this->assetLocation->getAssetsByLocation($this->locations[1])); $this->assertEmpty($this->assetLocation->getAssetsByLocation([$this->locations[1]]));
$this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$this->locations[0], $this->locations[1]])));
// Create a second asset and move it to the second location. // Create a second asset and move it to the second location.
/** @var \Drupal\asset\Entity\AssetInterface $second_asset */ /** @var \Drupal\asset\Entity\AssetInterface $second_asset */
@ -392,8 +392,9 @@ class LocationTest extends KernelTestBase {
$second_log->save(); $second_log->save();
// Both locations have one asset. // Both locations have one asset.
$this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($this->locations[0]))); $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$this->locations[0]])));
$this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($this->locations[1]))); $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$this->locations[1]])));
$this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$this->locations[0], $this->locations[1]])));
// Create a third log that moves both assets to the first location. // Create a third log that moves both assets to the first location.
/** @var \Drupal\log\Entity\LogInterface $third_log */ /** @var \Drupal\log\Entity\LogInterface $third_log */
@ -410,8 +411,9 @@ class LocationTest extends KernelTestBase {
$third_log->save(); $third_log->save();
// First location has two assets, second has none. // First location has two assets, second has none.
$this->assertEquals(2, count($this->assetLocation->getAssetsByLocation($this->locations[0]))); $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$this->locations[0]])));
$this->assertEmpty($this->assetLocation->getAssetsByLocation($this->locations[1])); $this->assertEmpty($this->assetLocation->getAssetsByLocation([$this->locations[1]]));
$this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$this->locations[0], $this->locations[1]])));
// Create a fourth log that moves first asset to the second location. // Create a fourth log that moves first asset to the second location.
/** @var \Drupal\log\Entity\LogInterface $fourth_log */ /** @var \Drupal\log\Entity\LogInterface $fourth_log */
@ -427,8 +429,9 @@ class LocationTest extends KernelTestBase {
$fourth_log->save(); $fourth_log->save();
// Both locations have one asset. // Both locations have one asset.
$this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($this->locations[0]))); $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$this->locations[0]])));
$this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($this->locations[1]))); $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$this->locations[1]])));
$this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$this->locations[0], $this->locations[1]])));
// Create a fifth log that moves first asset to the both locations. // Create a fifth log that moves first asset to the both locations.
/** @var \Drupal\log\Entity\LogInterface $fifth_log */ /** @var \Drupal\log\Entity\LogInterface $fifth_log */
@ -447,8 +450,9 @@ class LocationTest extends KernelTestBase {
$fifth_log->save(); $fifth_log->save();
// First location has two asset, second location has one asset. // First location has two asset, second location has one asset.
$this->assertEquals(2, count($this->assetLocation->getAssetsByLocation($this->locations[0]))); $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$this->locations[0]])));
$this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($this->locations[1]))); $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$this->locations[1]])));
$this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$this->locations[0], $this->locations[1]])));
} }
} }