Skip to content

Commit 6b28779

Browse files
committed
Add havingRaw method to AsyncQueryBuilder with comprehensive tests
1 parent f82bb3f commit 6b28779

File tree

3 files changed

+111
-116
lines changed

3 files changed

+111
-116
lines changed

src/QueryBuilder/AsyncQueryBuilder.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,22 @@ public function having(string $column, mixed $operator = null, mixed $value = nu
421421
return $this;
422422
}
423423

424+
/**
425+
* Add a raw HAVING condition.
426+
*
427+
* @param string $condition The raw SQL condition.
428+
* @param array<mixed> $bindings Parameter bindings for the condition.
429+
* @return self Returns the query builder instance for method chaining.
430+
*/
431+
public function havingRaw(string $condition, array $bindings = []): self
432+
{
433+
$this->having[] = $condition;
434+
$this->bindings['having'] = array_merge($this->bindings['having'], $bindings);
435+
436+
return $this;
437+
}
438+
439+
424440
/**
425441
* Add an ORDER BY clause to the query.
426442
*
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
use Rcalicdan\FiberAsync\Api\AsyncDB;
4+
use Rcalicdan\FiberAsync\Config\ConfigLoader;
5+
6+
$dbFile = sys_get_temp_dir() . '/advanced_query_test_' . uniqid() . '.sqlite';
7+
8+
beforeAll(function () use ($dbFile) {
9+
run(function () use ($dbFile) {
10+
AsyncDB::reset();
11+
12+
$fileBasedConfig = [
13+
'database' => [
14+
'default' => 'test_file',
15+
'connections' => [
16+
'test_file' => ['driver' => 'sqlite', 'database' => $dbFile]
17+
],
18+
'pool_size' => 5
19+
]
20+
];
21+
22+
$reflection = new \ReflectionClass(ConfigLoader::class);
23+
$configProperty = $reflection->getProperty('config');
24+
$configProperty->setAccessible(true);
25+
$instance = ConfigLoader::getInstance();
26+
$configProperty->setValue($instance, $fileBasedConfig);
27+
28+
await(AsyncDB::rawExecute("
29+
CREATE TABLE sales_data (
30+
id INTEGER PRIMARY KEY AUTOINCREMENT,
31+
product_name VARCHAR(255),
32+
category VARCHAR(100),
33+
quantity_sold INTEGER,
34+
unit_price DECIMAL(10,2),
35+
order_date DATETIME
36+
)
37+
"));
38+
39+
$data = [
40+
['product_name' => 'Laptop', 'category' => 'Electronics', 'quantity_sold' => 5, 'unit_price' => 1200.00, 'order_date' => '2023-01-15'],
41+
['product_name' => 'Mouse', 'category' => 'Electronics', 'quantity_sold' => 20, 'unit_price' => 25.00, 'order_date' => '2023-01-20'],
42+
['product_name' => 'Keyboard', 'category' => 'Electronics', 'quantity_sold' => 15, 'unit_price' => 75.00, 'order_date' => '2023-02-01'],
43+
['product_name' => 'Async PHP', 'category' => 'Books', 'quantity_sold' => 50, 'unit_price' => 40.00, 'order_date' => '2023-01-05'],
44+
['product_name' => 'Pest Cookbook', 'category' => 'Books', 'quantity_sold' => 30, 'unit_price' => 30.00, 'order_date' => '2023-02-10'],
45+
['product_name' => 'Stapler', 'category' => 'Office', 'quantity_sold' => 100, 'unit_price' => 10.00, 'order_date' => '2023-01-25'],
46+
];
47+
48+
await(AsyncDB::table('sales_data')->insertBatch($data));
49+
});
50+
});
51+
52+
afterAll(function () use ($dbFile) {
53+
AsyncDB::reset();
54+
if (file_exists($dbFile)) {
55+
unlink($dbFile);
56+
}
57+
});
58+
59+
describe('AsyncQueryBuilder Advanced Expressions and Aggregates', function () {
60+
it('can use raw expressions in select and order by with a CASE statement', function () {
61+
run(function () {
62+
$revenueExpression = 'quantity_sold * unit_price';
63+
$tierExpression = "CASE WHEN {$revenueExpression} >= 6000 THEN 'Tier A' WHEN {$revenueExpression} >= 2000 THEN 'Tier B' ELSE 'Tier C' END";
64+
$query = AsyncDB::table('sales_data')->select(["product_name", "$revenueExpression as total_revenue", "$tierExpression as performance_tier"])->orderBy('performance_tier')->orderBy('total_revenue', 'DESC');
65+
$result = await($query->get());
66+
expect($result)->toHaveCount(6)->and($result[0]['product_name'])->toBe('Laptop');
67+
});
68+
});
69+
70+
it('can handle complex aggregate queries with multiple HAVING clauses', function () {
71+
run(function () {
72+
$query = AsyncDB::table('sales_data')
73+
->select(['category', 'AVG(unit_price) as avg_price', 'COUNT(id) as item_count'])
74+
->groupBy('category')
75+
->havingRaw('COUNT(id) > 1 AND AVG(unit_price) > 100.0')
76+
->orderBy('category');
77+
78+
$result = await($query->get());
79+
80+
expect($result)->toHaveCount(1);
81+
expect($result[0]['category'])->toBe('Electronics');
82+
});
83+
});
84+
85+
it('can handle scalar subqueries in whereRaw', function () {
86+
run(function () {
87+
$subQuery = AsyncDB::table('sales_data')->select('AVG(unit_price)');
88+
$subSql = $subQuery->toSql();
89+
$query = AsyncDB::table('sales_data')->whereRaw("unit_price > ({$subSql})")->orderBy('unit_price', 'DESC');
90+
$result = await($query->get());
91+
expect($result)->toHaveCount(1)->and($result[0]['product_name'])->toBe('Laptop');
92+
});
93+
});
94+
});

tests/Integration/AsyncDBQueryBuilder.php renamed to tests/Integration/QueryBuilder/AsyncDBQueryBuilderTest.php

Lines changed: 1 addition & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@
77
use Rcalicdan\FiberAsync\Config\ConfigLoader;
88

99
beforeEach(function () {
10-
// Create a temporary config directory and files for testing
1110
$testDir = sys_get_temp_dir() . '/async-db-test-' . uniqid();
1211
mkdir($testDir);
1312
mkdir($testDir . '/config');
14-
mkdir($testDir . '/vendor'); // Required for ConfigLoader to find root
13+
mkdir($testDir . '/vendor');
1514

1615
// Create database config file
1716
$databaseConfig = [
@@ -27,7 +26,6 @@
2726
'<?php return ' . var_export($databaseConfig, true) . ';'
2827
);
2928

30-
// Create .env file
3129
file_put_contents($testDir . '/.env', 'DB_CONNECTION=test');
3230

3331
// Mock the ConfigLoader to use our test directory
@@ -1128,119 +1126,6 @@ function () {
11281126
});
11291127
});
11301128

1131-
describe('AsyncQueryBuilder Advanced Expressions and Aggregates', function () {
1132-
beforeEach(function () {
1133-
run(function () {
1134-
await(AsyncDB::rawExecute("DROP TABLE IF EXISTS sales_data"));
1135-
1136-
await(AsyncDB::rawExecute("
1137-
CREATE TABLE sales_data (
1138-
id INTEGER PRIMARY KEY AUTOINCREMENT,
1139-
product_name VARCHAR(255),
1140-
category VARCHAR(100),
1141-
quantity_sold INTEGER,
1142-
unit_price DECIMAL(10,2),
1143-
order_date DATETIME
1144-
)
1145-
"));
1146-
1147-
$data = [
1148-
['product_name' => 'Laptop', 'category' => 'Electronics', 'quantity_sold' => 5, 'unit_price' => 1200.00, 'order_date' => '2023-01-15'],
1149-
['product_name' => 'Mouse', 'category' => 'Electronics', 'quantity_sold' => 20, 'unit_price' => 25.00, 'order_date' => '2023-01-20'],
1150-
['product_name' => 'Keyboard', 'category' => 'Electronics', 'quantity_sold' => 15, 'unit_price' => 75.00, 'order_date' => '2023-02-01'],
1151-
['product_name' => 'Async PHP', 'category' => 'Books', 'quantity_sold' => 50, 'unit_price' => 40.00, 'order_date' => '2023-01-05'],
1152-
['product_name' => 'Pest Cookbook', 'category' => 'Books', 'quantity_sold' => 30, 'unit_price' => 30.00, 'order_date' => '2023-02-10'],
1153-
['product_name' => 'Stapler', 'category' => 'Office', 'quantity_sold' => 100, 'unit_price' => 10.00, 'order_date' => '2023-01-25'],
1154-
];
1155-
1156-
await(AsyncDB::table('sales_data')->insertBatch($data));
1157-
});
1158-
});
1159-
1160-
it('can use raw expressions in select and order by with a CASE statement', function () {
1161-
run(function () {
1162-
$revenueExpression = 'quantity_sold * unit_price';
1163-
1164-
$tierExpression = "CASE
1165-
WHEN {$revenueExpression} >= 6000 THEN 'Tier A'
1166-
WHEN {$revenueExpression} >= 2000 THEN 'Tier B'
1167-
ELSE 'Tier C'
1168-
END";
1169-
1170-
$query = AsyncDB::table('sales_data')
1171-
->select([
1172-
'product_name',
1173-
"$revenueExpression as total_revenue",
1174-
"$tierExpression as performance_tier"
1175-
])
1176-
->orderBy('performance_tier')
1177-
->orderBy('total_revenue', 'DESC');
1178-
1179-
$result = await($query->get());
1180-
1181-
expect($result)->toHaveCount(6);
1182-
1183-
expect($result[0]['product_name'])->toBe('Laptop');
1184-
expect($result[0]['performance_tier'])->toBe('Tier A');
1185-
1186-
expect($result[1]['product_name'])->toBe('Async PHP');
1187-
expect($result[1]['performance_tier'])->toBe('Tier B');
1188-
1189-
expect($result[5]['product_name'])->toBe('Mouse');
1190-
expect($result[5]['performance_tier'])->toBe('Tier C');
1191-
});
1192-
});
1193-
1194-
it('can handle complex aggregate queries with multiple HAVING clauses', function () {
1195-
run(function () {
1196-
$query = AsyncDB::table('sales_data')
1197-
->select([
1198-
'category',
1199-
'AVG(unit_price) as avg_price',
1200-
'COUNT(id) as item_count'
1201-
])
1202-
->groupBy('category')
1203-
->having('item_count', '>', 1)
1204-
->having('avg_price', '>', 100)
1205-
->orderBy('category');
1206-
1207-
// --- DEBUGGING STATEMENTS ADDED HERE ---
1208-
echo "\n--- DEBUGGING AGGREGATE/HAVING QUERY ---\n";
1209-
echo "Generated SQL: " . $query->toSql() . "\n";
1210-
echo "Bindings: ";
1211-
print_r($query->getBindings());
1212-
echo "\n--------------------------------------\n";
1213-
// --- END DEBUGGING ---
1214-
1215-
$result = await($query->get());
1216-
1217-
// Also output the actual result to be certain
1218-
echo "Actual query result count: " . count($result) . "\n";
1219-
print_r($result);
1220-
echo "\n";
1221-
1222-
expect($result)->toHaveCount(1);
1223-
expect($result[0]['category'])->toBe('Electronics');
1224-
});
1225-
});
1226-
1227-
it('can handle scalar subqueries in whereRaw', function () {
1228-
run(function () {
1229-
$subQuery = AsyncDB::table('sales_data')->select('AVG(unit_price)');
1230-
$subSql = $subQuery->toSql();
1231-
1232-
$query = AsyncDB::table('sales_data')
1233-
->whereRaw("unit_price > ({$subSql})")
1234-
->orderBy('unit_price', 'DESC');
1235-
1236-
$result = await($query->get());
1237-
1238-
expect($result)->toHaveCount(1);
1239-
expect($result[0]['product_name'])->toBe('Laptop');
1240-
});
1241-
});
1242-
});
1243-
12441129
describe('AsyncQueryBuilder Advanced Subqueries', function () {
12451130
beforeEach(function () {
12461131
run(function () {

0 commit comments

Comments
 (0)