setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); echo "✅ Database connection established\n\n"; $compiler = new PostgreSQLSchemaCompiler(); // Cleanup echo "Cleanup...\n"; $pdo->exec("DROP TABLE IF EXISTS test_ranges CASCADE"); echo "✅ Cleanup complete\n\n"; // Test 1: Create table with all range types echo "Test 1: Create Table With Range Types\n"; echo "======================================\n"; $blueprint = new Blueprint('test_ranges'); $blueprint->bigIncrements('id'); $blueprint->string('description'); // Numeric ranges $blueprint->int4range('age_range')->nullable(); $blueprint->int8range('population_range')->nullable(); $blueprint->numrange('price_range')->nullable(); // Timestamp ranges $blueprint->tsrange('event_period')->nullable(); $blueprint->tstzrange('booking_period')->nullable(); // Date ranges $blueprint->daterange('valid_dates')->nullable(); $createCommand = new CreateTableCommand('test_ranges', $blueprint); $sql = $compiler->compile($createCommand); echo "Creating table with range columns:\n"; echo $sql . "\n\n"; $pdo->exec($sql); echo "✅ Table 'test_ranges' created with all range types\n\n"; // Test 2: Insert data with range values echo "Test 2: Insert Range Data\n"; echo "==========================\n"; $pdo->exec(" INSERT INTO test_ranges ( description, age_range, population_range, price_range, event_period, booking_period, valid_dates ) VALUES ( 'Hotel Booking', '[18,65)', -- Age 18 to 64 (inclusive start, exclusive end) '[1000000,5000000)', -- Population range '[99.99,499.99]', -- Price range (both inclusive) '[2024-06-01 10:00:00, 2024-06-01 18:00:00)', -- Event period '[2024-06-01 10:00:00+02, 2024-06-01 18:00:00+02)', -- Booking with timezone '[2024-01-01, 2024-12-31]' -- Valid date range ) "); echo "✅ Range data inserted\n\n"; // Test 3: Query range data echo "Test 3: Query Range Data\n"; echo "=========================\n"; $stmt = $pdo->query("SELECT * FROM test_ranges WHERE id = 1"); $result = $stmt->fetch(PDO::FETCH_ASSOC); echo "Retrieved data:\n"; echo " - Description: {$result['description']}\n"; echo " - Age Range: {$result['age_range']}\n"; echo " - Population Range: {$result['population_range']}\n"; echo " - Price Range: {$result['price_range']}\n"; echo " - Event Period: {$result['event_period']}\n"; echo " - Booking Period: {$result['booking_period']}\n"; echo " - Valid Dates: {$result['valid_dates']}\n"; echo "✅ Range data successfully retrieved\n\n"; // Test 4: Range operators echo "Test 4: Range Operators\n"; echo "========================\n"; // Insert test data for operator tests $pdo->exec(" INSERT INTO test_ranges (description, age_range, price_range, valid_dates) VALUES ('Product A', '[25,50)', '[100.00,200.00]', '[2024-01-01,2024-06-30]'), ('Product B', '[30,60)', '[150.00,300.00]', '[2024-03-01,2024-09-30]'), ('Product C', '[40,70)', '[250.00,500.00]', '[2024-06-01,2024-12-31]') "); echo "✅ Test data inserted\n\n"; // Test @> (contains) operator echo "Test @> (contains) operator:\n"; $stmt = $pdo->query("SELECT description FROM test_ranges WHERE age_range @> 35"); $results = $stmt->fetchAll(PDO::FETCH_COLUMN); echo " Products with age range containing 35: " . implode(', ', $results) . "\n"; echo (count($results) == 3) ? "✅ Contains operator works\n\n" : "❌ Should find 3 products\n\n"; // Test <@ (is contained by) operator echo "Test <@ (is contained by) operator:\n"; $stmt = $pdo->query("SELECT description FROM test_ranges WHERE '[100,150]'::numrange <@ price_range"); $results = $stmt->fetchAll(PDO::FETCH_COLUMN); echo " Products containing price range [100,150]: " . implode(', ', $results) . "\n"; echo (count($results) >= 1) ? "✅ Is contained by operator works\n\n" : "❌ Should find products\n\n"; // Test && (overlap) operator echo "Test && (overlap) operator:\n"; $stmt = $pdo->query("SELECT description FROM test_ranges WHERE valid_dates && '[2024-05-01,2024-07-31]'::daterange"); $results = $stmt->fetchAll(PDO::FETCH_COLUMN); echo " Products with overlapping date range: " . implode(', ', $results) . "\n"; echo (count($results) >= 2) ? "✅ Overlap operator works\n\n" : "❌ Should find overlapping products\n\n"; // Test 5: Range functions echo "Test 5: Range Functions\n"; echo "========================\n"; // lower() and upper() functions $stmt = $pdo->query(" SELECT description, lower(age_range) as min_age, upper(age_range) as max_age, lower(price_range) as min_price, upper(price_range) as max_price FROM test_ranges WHERE description = 'Product A' "); $result = $stmt->fetch(PDO::FETCH_ASSOC); echo "Product A bounds:\n"; echo " - Min Age: {$result['min_age']}\n"; echo " - Max Age: {$result['max_age']}\n"; echo " - Min Price: {$result['min_price']}\n"; echo " - Max Price: {$result['max_price']}\n"; echo "✅ Range boundary functions work\n\n"; // isempty() function echo "Test isempty() function:\n"; $stmt = $pdo->query("SELECT COUNT(*) FROM test_ranges WHERE isempty(age_range)"); $emptyCount = $stmt->fetchColumn(); echo " - Empty ranges: {$emptyCount}\n"; echo ($emptyCount == 0) ? "✅ No empty ranges found (as expected)\n\n" : "✅ isempty() function works\n\n"; // Test 6: Range intersection echo "Test 6: Range Intersection\n"; echo "===========================\n"; // Test range intersection $stmt = $pdo->query(" SELECT '[25,60)'::int4range * '[30,50)'::int4range as intersection, '[100.00,200.00]'::numrange * '[150.00,300.00]'::numrange as price_intersection "); $result = $stmt->fetch(PDO::FETCH_ASSOC); echo "Range intersections:\n"; echo " - Age range [25,60) * [30,50) = {$result['intersection']}\n"; echo " - Price range [100,200] * [150,300] = {$result['price_intersection']}\n"; echo "✅ Range intersection operator works\n\n"; // Test 7: GiST index on range column echo "Test 7: GiST Index on Range Column\n"; echo "===================================\n"; $pdo->exec("CREATE INDEX idx_test_ranges_age ON test_ranges USING GIST (age_range)"); echo "✅ GiST index created on age_range column\n"; $pdo->exec("CREATE INDEX idx_test_ranges_price ON test_ranges USING GIST (price_range)"); echo "✅ GiST index created on price_range column\n"; $pdo->exec("CREATE INDEX idx_test_ranges_dates ON test_ranges USING GIST (valid_dates)"); echo "✅ GiST index created on valid_dates column\n\n"; // Test 8: Verify indexes are used echo "Test 8: Verify Index Usage\n"; echo "===========================\n"; $stmt = $pdo->query(" SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'test_ranges' AND indexname LIKE 'idx_test_ranges_%' ORDER BY indexname "); $indexes = $stmt->fetchAll(PDO::FETCH_ASSOC); echo "Found " . count($indexes) . " GiST indexes:\n"; foreach ($indexes as $index) { echo " - {$index['indexname']}\n"; } echo (count($indexes) == 3) ? "✅ All GiST indexes created\n\n" : "❌ Expected 3 indexes\n\n"; // Cleanup echo "Cleanup...\n"; $pdo->exec("DROP TABLE IF EXISTS test_ranges CASCADE"); echo "✅ Test data cleaned up\n"; echo "\n✅ All Range Types tests passed!\n"; echo "\nSummary:\n"; echo "========\n"; echo "✅ INT4RANGE (integer ranges) works\n"; echo "✅ INT8RANGE (bigint ranges) works\n"; echo "✅ NUMRANGE (numeric ranges) works\n"; echo "✅ TSRANGE (timestamp ranges) works\n"; echo "✅ TSTZRANGE (timestamp with timezone ranges) works\n"; echo "✅ DATERANGE (date ranges) works\n"; echo "✅ Range operators (@>, <@, &&) work\n"; echo "✅ Range functions (lower, upper, isempty) work\n"; echo "✅ Range intersection operator (*) works\n"; echo "✅ GiST indexes on range columns work\n"; } catch (\Exception $e) { echo "❌ Error: " . $e->getMessage() . "\n"; echo "Stack trace:\n" . $e->getTraceAsString() . "\n"; exit(1); }