Skip to content

Commit b2cbdbc

Browse files
committed
fix: round 3 — DB adapter query coverage + null-type cross-type fallback
Database adapter: convertQueriesToDatabase() previously dropped TYPE_NOT_EQUAL, TYPE_NOT_BETWEEN, TYPE_NOT_CONTAINS, TYPE_STARTS_WITH, and TYPE_ENDS_WITH silently — the switch had no case for them so the WHERE fragment was skipped, turning a "not X" or "starts with Y" filter into a full-collection match. Adds all five. ClickHouse adapter: find()/count()/getTimeSeries() with $type=null queried both events and gauges. When the query referenced an event-only attribute like 'path', the gauge iteration would throw "Invalid attribute name: path" via parseQueries(). Adds a private queriesMatchType() helper that pre-checks each filter attribute against the type's schema; skip the table when not satisfied. The caller now gets the events side without the gauge crash, which is what null-type semantics should mean. sum() takes type=TYPE_EVENT as a hard default, no null-type path.
1 parent b90879a commit b2cbdbc

2 files changed

Lines changed: 86 additions & 5 deletions

File tree

src/Usage/Adapter/ClickHouse.php

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1500,9 +1500,14 @@ public function find(array $queries = [], ?string $type = null): array
15001500
// Query both tables and merge. Each side already applied LIMIT, so
15011501
// without a final cap callers asking for `limit(N)` could receive
15021502
// up to 2N rows. Slice the merged result back down to the user's
1503-
// requested limit.
1504-
$events = $this->findFromTable($queries, Usage::TYPE_EVENT);
1505-
$gauges = $this->findFromTable($queries, Usage::TYPE_GAUGE);
1503+
// requested limit. Tables whose schema doesn't support every filter
1504+
// attribute (e.g. `path` on a gauge query) are skipped.
1505+
$events = $this->queriesMatchType($queries, Usage::TYPE_EVENT)
1506+
? $this->findFromTable($queries, Usage::TYPE_EVENT)
1507+
: [];
1508+
$gauges = $this->queriesMatchType($queries, Usage::TYPE_GAUGE)
1509+
? $this->findFromTable($queries, Usage::TYPE_GAUGE)
1510+
: [];
15061511

15071512
$merged = array_merge($events, $gauges);
15081513

@@ -1513,6 +1518,38 @@ public function find(array $queries = [], ?string $type = null): array
15131518
return $merged;
15141519
}
15151520

1521+
/**
1522+
* Check whether every filter attribute in $queries exists on the schema
1523+
* for the given type. Used by the null-$type code paths in find/count/sum
1524+
* so a query with event-only attributes (path/method/status/etc.) silently
1525+
* skips the gauges table instead of throwing "Invalid attribute name".
1526+
*
1527+
* @param array<Query> $queries
1528+
*/
1529+
private function queriesMatchType(array $queries, string $type): bool
1530+
{
1531+
foreach ($queries as $query) {
1532+
$attribute = $query->getAttribute();
1533+
if ($attribute === '' || $attribute === 'id') {
1534+
continue;
1535+
}
1536+
if ($attribute === 'tenant' && $this->sharedTables) {
1537+
continue;
1538+
}
1539+
$matched = false;
1540+
foreach ($this->getAttributes($type) as $schemaAttribute) {
1541+
if ($schemaAttribute['$id'] === $attribute) {
1542+
$matched = true;
1543+
break;
1544+
}
1545+
}
1546+
if (!$matched) {
1547+
return false;
1548+
}
1549+
}
1550+
return true;
1551+
}
1552+
15161553
/**
15171554
* Find metrics from a specific table.
15181555
*
@@ -1752,8 +1789,15 @@ public function count(array $queries = [], ?string $type = null, ?int $max = nul
17521789
// Count from both tables. Each per-table count is independently
17531790
// capped at $max, so naively summing them could yield up to 2*$max.
17541791
// Cap the combined total at $max in PHP to honour the contract.
1755-
$total = $this->countFromTable($queries, Usage::TYPE_EVENT, $max)
1756-
+ $this->countFromTable($queries, Usage::TYPE_GAUGE, $max);
1792+
// Skip a table when its schema can't satisfy every filter attribute.
1793+
$events = $this->queriesMatchType($queries, Usage::TYPE_EVENT)
1794+
? $this->countFromTable($queries, Usage::TYPE_EVENT, $max)
1795+
: 0;
1796+
$gauges = $this->queriesMatchType($queries, Usage::TYPE_GAUGE)
1797+
? $this->countFromTable($queries, Usage::TYPE_GAUGE, $max)
1798+
: 0;
1799+
1800+
$total = $events + $gauges;
17571801

17581802
if ($max !== null && $total > $max) {
17591803
$total = $max;
@@ -2055,6 +2099,13 @@ public function getTimeSeries(array $metrics, string $interval, string $startDat
20552099
}
20562100

20572101
foreach ($typesToQuery as $queryType) {
2102+
// Skip a table when its schema can't satisfy every filter attribute
2103+
// (e.g. `path` on a gauge query); avoids "Invalid attribute name"
2104+
// when the caller leaves $type null and only one side is applicable.
2105+
if (!$this->queriesMatchType($queries, $queryType)) {
2106+
continue;
2107+
}
2108+
20582109
$typeResult = $this->getTimeSeriesFromTable($metrics, $interval, $startDate, $endDate, $queries, $queryType);
20592110

20602111
// Merge results

src/Usage/Adapter/Database.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,36 @@ private function convertQueriesToDatabase(array $queries): array
411411
/** @var array<array<int|string, mixed>|bool|float|int|string> $values */
412412
$dbQueries[] = DatabaseQuery::contains($attribute, $values);
413413
break;
414+
case Query::TYPE_NOT_EQUAL:
415+
if (!empty($values)) {
416+
/** @var bool|float|int|string $value */
417+
$value = $values[0];
418+
$dbQueries[] = DatabaseQuery::notEqual($attribute, $value);
419+
}
420+
break;
421+
case Query::TYPE_NOT_CONTAINS:
422+
/** @var array<array<int|string, mixed>|bool|float|int|string> $values */
423+
$dbQueries[] = DatabaseQuery::notContains($attribute, $values);
424+
break;
425+
case Query::TYPE_NOT_BETWEEN:
426+
if (count($values) >= 2) {
427+
/** @var bool|float|int|string $start */
428+
$start = $values[0];
429+
/** @var bool|float|int|string $end */
430+
$end = $values[1];
431+
$dbQueries[] = DatabaseQuery::notBetween($attribute, $start, $end);
432+
}
433+
break;
434+
case Query::TYPE_STARTS_WITH:
435+
if (!empty($values) && is_string($values[0])) {
436+
$dbQueries[] = DatabaseQuery::startsWith($attribute, $values[0]);
437+
}
438+
break;
439+
case Query::TYPE_ENDS_WITH:
440+
if (!empty($values) && is_string($values[0])) {
441+
$dbQueries[] = DatabaseQuery::endsWith($attribute, $values[0]);
442+
}
443+
break;
414444
case Query::TYPE_LESSER_EQUAL:
415445
if (!empty($values)) {
416446
/** @var bool|float|int|string $value */

0 commit comments

Comments
 (0)