Skip to content

Commit a89db31

Browse files
committed
fix: Enhance Query class with lessThanEqual and greaterThanEqual methods; update ClickHouse and Database adapters to support new query types and improve handling of array values
1 parent 3d8adb2 commit a89db31

5 files changed

Lines changed: 166 additions & 22 deletions

File tree

src/Usage/Adapter/ClickHouse.php

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,11 +1017,36 @@ private function parseQueries(array $queries): array
10171017
case Query::TYPE_EQUAL:
10181018
$this->validateAttributeName($attribute);
10191019
$escapedAttr = $this->escapeIdentifier($attribute);
1020-
$paramName = 'param_' . $paramCounter++;
1021-
// Query values are arrays, use first element
1022-
$value = is_array($values) && !empty($values) ? $values[0] : $values;
1023-
$filters[] = "{$escapedAttr} = {{$paramName}:String}";
1024-
$params[$paramName] = $this->formatParamValue($value);
1020+
1021+
// Support arrays of values (produce IN (...) ) or single value equality
1022+
if (is_array($values)) {
1023+
$inParams = [];
1024+
foreach ($values as $value) {
1025+
$paramName = 'param_' . $paramCounter++;
1026+
if ($attribute === 'time') {
1027+
$inParams[] = "{{$paramName}:DateTime64(3)}";
1028+
$params[$paramName] = $this->formatDateTime($value);
1029+
} else {
1030+
$inParams[] = "{{$paramName}:String}";
1031+
$params[$paramName] = $this->formatParamValue($value);
1032+
}
1033+
}
1034+
1035+
if (count($inParams) === 1) {
1036+
$filters[] = "{$escapedAttr} = " . $inParams[0];
1037+
} else {
1038+
$filters[] = "{$escapedAttr} IN (" . implode(', ', $inParams) . ")";
1039+
}
1040+
} else {
1041+
$paramName = 'param_' . $paramCounter++;
1042+
if ($attribute === 'time') {
1043+
$filters[] = "{$escapedAttr} = {{$paramName}:DateTime64(3)}";
1044+
$params[$paramName] = $this->formatDateTime($values);
1045+
} else {
1046+
$filters[] = "{$escapedAttr} = {{$paramName}:String}";
1047+
$params[$paramName] = $this->formatParamValue($values);
1048+
}
1049+
}
10251050
break;
10261051

10271052
case Query::TYPE_LESSER:
@@ -1072,31 +1097,63 @@ private function parseQueries(array $queries): array
10721097
}
10731098
break;
10741099

1075-
case Query::TYPE_IN:
1076-
// SELECT allows selecting multiple columns/values
1100+
1101+
1102+
case Query::TYPE_ORDER_DESC:
1103+
$this->validateAttributeName($attribute);
1104+
$escapedAttr = $this->escapeIdentifier($attribute);
1105+
$orderBy[] = "{$escapedAttr} DESC";
1106+
break;
1107+
1108+
case Query::TYPE_ORDER_ASC:
1109+
$this->validateAttributeName($attribute);
1110+
$escapedAttr = $this->escapeIdentifier($attribute);
1111+
$orderBy[] = "{$escapedAttr} ASC";
1112+
break;
1113+
1114+
case Query::TYPE_CONTAINS:
10771115
$this->validateAttributeName($attribute);
10781116
$escapedAttr = $this->escapeIdentifier($attribute);
10791117
$inParams = [];
10801118
foreach ($values as $value) {
10811119
$paramName = 'param_' . $paramCounter++;
1082-
$inParams[] = "{{$paramName}:String}";
1083-
$params[$paramName] = $this->formatParamValue($value);
1120+
if ($attribute === 'time') {
1121+
$inParams[] = "{{$paramName}:DateTime64(3)}";
1122+
$params[$paramName] = $this->formatDateTime($value);
1123+
} else {
1124+
$inParams[] = "{{$paramName}:String}";
1125+
$params[$paramName] = $this->formatParamValue($value);
1126+
}
10841127
}
10851128
if (!empty($inParams)) {
10861129
$filters[] = "{$escapedAttr} IN (" . implode(', ', $inParams) . ")";
10871130
}
10881131
break;
10891132

1090-
case Query::TYPE_ORDER_DESC:
1133+
case Query::TYPE_LESSER_EQUAL:
10911134
$this->validateAttributeName($attribute);
10921135
$escapedAttr = $this->escapeIdentifier($attribute);
1093-
$orderBy[] = "{$escapedAttr} DESC";
1136+
$paramName = 'param_' . $paramCounter++;
1137+
if ($attribute === 'time') {
1138+
$filters[] = "{$escapedAttr} <= {{$paramName}:DateTime64(3)}";
1139+
$params[$paramName] = $this->formatDateTime(is_array($values) ? ($values[0] ?? null) : $values);
1140+
} else {
1141+
$filters[] = "{$escapedAttr} <= {{$paramName}:String}";
1142+
$params[$paramName] = $this->formatParamValue(is_array($values) ? ($values[0] ?? null) : $values);
1143+
}
10941144
break;
10951145

1096-
case Query::TYPE_ORDER_ASC:
1146+
case Query::TYPE_GREATER_EQUAL:
10971147
$this->validateAttributeName($attribute);
10981148
$escapedAttr = $this->escapeIdentifier($attribute);
1099-
$orderBy[] = "{$escapedAttr} ASC";
1149+
$paramName = 'param_' . $paramCounter++;
1150+
if ($attribute === 'time') {
1151+
$filters[] = "{$escapedAttr} >= {{$paramName}:DateTime64(3)}";
1152+
$params[$paramName] = $this->formatDateTime(is_array($values) ? ($values[0] ?? null) : $values);
1153+
} else {
1154+
$filters[] = "{$escapedAttr} >= {{$paramName}:String}";
1155+
$params[$paramName] = $this->formatParamValue(is_array($values) ? ($values[0] ?? null) : $values);
1156+
}
11001157
break;
11011158

11021159
case Query::TYPE_LIMIT:

src/Usage/Adapter/Database.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,23 @@ private function convertQueriesToDatabase(array $queries): array
181181
$dbQueries[] = DatabaseQuery::between($attribute, $start, $end);
182182
}
183183
break;
184-
case Query::TYPE_IN:
185-
// For IN queries, the values are the items to match
186-
// Create using equal with array values for compatibility
184+
case Query::TYPE_CONTAINS:
185+
// For contains queries, the values are the items to match
187186
/** @var array<array<int|string, mixed>|bool|float|int|string> $values */
188187
$dbQueries[] = DatabaseQuery::contains($attribute, $values);
189188
break;
189+
case Query::TYPE_LESSER_EQUAL:
190+
if (!empty($values)) {
191+
$value = $values[0];
192+
$dbQueries[] = DatabaseQuery::lessThanEqual($attribute, $value);
193+
}
194+
break;
195+
case Query::TYPE_GREATER_EQUAL:
196+
if (!empty($values)) {
197+
$value = $values[0];
198+
$dbQueries[] = DatabaseQuery::greaterThanEqual($attribute, $value);
199+
}
200+
break;
190201
case Query::TYPE_ORDER_DESC:
191202
$dbQueries[] = DatabaseQuery::orderDesc($attribute);
192203
break;

src/Usage/Query.php

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ class Query
1515
public const TYPE_GREATER = 'greaterThan';
1616
public const TYPE_LESSER = 'lessThan';
1717
public const TYPE_BETWEEN = 'between';
18-
public const TYPE_IN = 'contains';
18+
public const TYPE_LESSER_EQUAL = 'lessThanEqual';
19+
public const TYPE_GREATER_EQUAL = 'greaterThanEqual';
20+
public const TYPE_CONTAINS = 'contains';
1921

2022
// Order methods
2123
public const TYPE_ORDER_DESC = 'orderDesc';
@@ -104,6 +106,14 @@ public static function lessThan(string $attribute, mixed $value): self
104106
return new self(self::TYPE_LESSER, $attribute, [$value]);
105107
}
106108

109+
/**
110+
* Filter by less than or equal condition
111+
*/
112+
public static function lessThanEqual(string $attribute, mixed $value): self
113+
{
114+
return new self(self::TYPE_LESSER_EQUAL, $attribute, [$value]);
115+
}
116+
107117
/**
108118
* Filter by greater than condition
109119
*
@@ -116,6 +126,14 @@ public static function greaterThan(string $attribute, mixed $value): self
116126
return new self(self::TYPE_GREATER, $attribute, [$value]);
117127
}
118128

129+
/**
130+
* Filter by greater than or equal condition
131+
*/
132+
public static function greaterThanEqual(string $attribute, mixed $value): self
133+
{
134+
return new self(self::TYPE_GREATER_EQUAL, $attribute, [$value]);
135+
}
136+
119137
/**
120138
* Filter by BETWEEN condition
121139
*
@@ -136,9 +154,9 @@ public static function between(string $attribute, mixed $start, mixed $end): sel
136154
* @param array<mixed> $values
137155
* @return self
138156
*/
139-
public static function in(string $attribute, array $values): self
157+
public static function contains(string $attribute, array $values): self
140158
{
141-
return new self(self::TYPE_IN, $attribute, $values);
159+
return new self(self::TYPE_CONTAINS, $attribute, $values);
142160
}
143161

144162
/**

tests/Usage/QueryTest.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,27 @@ public function testQueryStaticFactoryMethods(): void
3030
$this->assertEquals('time', $query->getAttribute());
3131
$this->assertEquals(['2023-01-01'], $query->getValues());
3232

33+
// Test greaterThanEqual
34+
$query = Query::greaterThanEqual('time', '2023-01-01');
35+
$this->assertEquals(Query::TYPE_GREATER_EQUAL, $query->getMethod());
36+
$this->assertEquals('time', $query->getAttribute());
37+
$this->assertEquals(['2023-01-01'], $query->getValues());
38+
39+
// Test lessThanEqual
40+
$query = Query::lessThanEqual('time', '2024-01-01');
41+
$this->assertEquals(Query::TYPE_LESSER_EQUAL, $query->getMethod());
42+
$this->assertEquals('time', $query->getAttribute());
43+
$this->assertEquals(['2024-01-01'], $query->getValues());
44+
3345
// Test between
3446
$query = Query::between('time', '2023-01-01', '2024-01-01');
3547
$this->assertEquals(Query::TYPE_BETWEEN, $query->getMethod());
3648
$this->assertEquals('time', $query->getAttribute());
3749
$this->assertEquals(['2023-01-01', '2024-01-01'], $query->getValues());
3850

39-
// Test in
40-
$query = Query::in('event', ['create', 'update', 'delete']);
41-
$this->assertEquals(Query::TYPE_IN, $query->getMethod());
51+
// Test contains
52+
$query = Query::contains('event', ['create', 'update', 'delete']);
53+
$this->assertEquals(Query::TYPE_CONTAINS, $query->getMethod());
4254
$this->assertEquals('event', $query->getAttribute());
4355
$this->assertEquals(['create', 'update', 'delete'], $query->getValues());
4456

tests/Usage/UsageBase.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,52 @@ public function testWithQueries(): void
147147
$this->assertEquals(0, count($results2));
148148
}
149149

150+
public function testEqualWithArrayValues(): void
151+
{
152+
// Test equal query with array of values (IN clause)
153+
$results = $this->usage->find([
154+
Query::equal('metric', ['requests', 'bandwidth']),
155+
]);
156+
157+
// Should find all metrics matching either 'requests' or 'bandwidth'
158+
$this->assertGreaterThanOrEqual(2, count($results));
159+
}
160+
161+
public function testContainsQuery(): void
162+
{
163+
// Test contains query with multiple values
164+
$results = $this->usage->find([
165+
Query::contains('metric', ['requests', 'storage']),
166+
]);
167+
168+
// Should find all metrics matching either 'requests' or 'storage'
169+
$this->assertGreaterThanOrEqual(2, count($results));
170+
}
171+
172+
public function testLessThanEqualQuery(): void
173+
{
174+
// Get current time and subtract some time to test lessThanEqual
175+
$now = (new \DateTime())->format('Y-m-d\TH:i:s');
176+
$results = $this->usage->find([
177+
Query::lessThanEqual('time', $now),
178+
]);
179+
180+
// Should find all metrics with time <= now
181+
$this->assertGreaterThanOrEqual(0, count($results));
182+
}
183+
184+
public function testGreaterThanEqualQuery(): void
185+
{
186+
// Get a time in the past (formatted as ISO 8601 string)
187+
$past = (new \DateTime())->modify('-24 hours')->format('Y-m-d\TH:i:s');
188+
$results = $this->usage->find([
189+
Query::greaterThanEqual('time', $past),
190+
]);
191+
192+
// Should find all metrics with time >= past (most recent metrics)
193+
$this->assertGreaterThanOrEqual(0, count($results));
194+
}
195+
150196
public function testPurge(): void
151197
{
152198
sleep(2);

0 commit comments

Comments
 (0)