Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce new method to check if a segment is valid and available for a given site #23034

Merged
merged 2 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 2 additions & 19 deletions core/CronArchive.php
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,8 @@ private function invalidateWithSegments(

foreach ($this->segmentArchiving->getAllSegmentsToArchive($idSite) as $segmentDefinition) {
// check if the segment is available
if (!$this->isSegmentAvailable($segmentDefinition, [$idSite])) {
if (!Segment::isAvailable($segmentDefinition, [$idSite])) {
$this->logger->info("Segment '" . $segmentDefinition . "' is not a supported segment");
continue;
}

Expand Down Expand Up @@ -1059,24 +1060,6 @@ private function invalidateWithSegments(
}
}


/**
* check if segments that contain dimensions that don't exist anymore
* @param $segmentDefinition
* @param $idSites
* @return bool
*/
protected function isSegmentAvailable($segmentDefinition, $idSites): bool
{
try {
new Segment($segmentDefinition, $idSites);
} catch (\Exception $e) {
$this->logger->info("Segment '" . $segmentDefinition . "' is not a supported segment");
return false;
}
return true;
}

private function canWeSkipInvalidatingBecauseInvalidationAlreadyInProgress(int $idSite, Period $period, ?Segment $segment = null): bool
{
$invalidationsInProgress = $this->model->getInvalidationsInProgress($idSite);
Expand Down
6 changes: 3 additions & 3 deletions core/CronArchive/SegmentArchiving.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,13 @@ public function findSegmentForHash($hash, $idSite)
continue;
}

try {
$segmentObj = new Segment($segment['definition'], [$idSite]);
} catch (\Exception $ex) {
if (!Segment::isAvailable($segment['definition'], [$idSite])) {
$this->logger->debug("Could not process segment {$segment['definition']} for site {$idSite}. Segment should not exist for the site, but does.");
continue;
}

$segmentObj = new Segment($segment['definition'], [$idSite]);

if ($segmentObj->getHash() == $hash) {
return $segment;
}
Expand Down
36 changes: 33 additions & 3 deletions core/Segment.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ class Segment
*/
private $isSegmentEncoded;

/**
* @var Exception|null
*/
private $missingDatesException = null;

/**
* Truncate the Segments to 8k
*/
Expand All @@ -125,7 +130,6 @@ class Segment
*/
public function __construct($segmentCondition, $idSites, ?Date $startDate = null, ?Date $endDate = null)
{

$this->segmentQueryBuilder = StaticContainer::get('Piwik\DataAccess\LogQueryBuilder');

$segmentCondition = trim($segmentCondition ?: '');
Expand Down Expand Up @@ -177,6 +181,25 @@ public function __construct($segmentCondition, $idSites, ?Date $startDate = null
}
}

/**
* Checks if the provided segmentCondition is valid and available for the given idSites
*
* @param string $segmentCondition
* @params array $idSites
* @return bool
* @api since Matomo 5.3.0
*/
public static function isAvailable(string $segmentCondition, array $idSites): bool
{
try {
new self($segmentCondition, $idSites);
} catch (Exception $e) {
return false;
}

return true;
}

/**
* Returns the segment expression.
* @return SegmentExpression
Expand Down Expand Up @@ -335,8 +358,7 @@ private function doesSegmentNeedSubquery($operator, $segmentName)

if ($requiresSubQuery && empty($this->startDate) && empty($this->endDate)) {
if (Development::isEnabled()) {
$e = new Exception();
Log::warning("Avoiding segment subquery due to missing start date and/or an end date. Please ensure a start date and/or end date is set when initializing a segment if it's used to build a query. Stacktrace:\n" . $e->getTraceAsString());
$this->missingDatesException = new Exception();
}
return false;
}
Expand Down Expand Up @@ -586,6 +608,14 @@ public static function getSegmentHash($definition)
*/
public function getSelectQuery($select, $from, $where = false, $bind = array(), $orderBy = false, $groupBy = false, $limit = 0, $offset = 0, $forceGroupBy = false)
{
if (Development::isEnabled() && !empty($this->missingDatesException)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, it does make sense to not show the warning whenever a segment is created but then also not used.

However, would it also make sense to include a double stacktrace? One for "segment created at" and one for "segment used at"? Depending on the code it may be difficult to find out where it was used, if you only get the information where it was created, unless you know how to reliably reproduce the error and get a breakpoint triggered 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I'll change that so it will include both stacktraces

$e = new Exception();
Log::warning('Avoiding segment subquery due to missing start date and/or an end date. '
. 'Please ensure a start date and/or end date is set when initializing segment: '
. "\n\nCreation stacktrace:\n" . $this->missingDatesException->getTraceAsString()
. "\n\nUsage stacktrace:\n" . $e->getTraceAsString());
}

$segmentExpression = $this->segmentExpression;

$limitAndOffset = null;
Expand Down
4 changes: 1 addition & 3 deletions plugins/SegmentEditor/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -416,9 +416,7 @@ private function filterSegmentsWithDisabledElements(array $segments, $idSite = f
$idSites = false === $idSite ? [] : [$idSite];

foreach ($segments as $k => $segment) {
try {
new Segment($segment['definition'], $idSites);
} catch (Exception $e) {
if (!Segment::isAvailable($segment['definition'], $idSites)) {
unset($segments[$k]);
}
}
Expand Down
Loading