- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
235 lines
7.7 KiB
PHP
235 lines
7.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
echo "Testing PostgreSQL JSONB with GIN Index Support\n";
|
|
echo "===============================================\n\n";
|
|
|
|
try {
|
|
// Create PDO connection
|
|
$pdo = new PDO(
|
|
'pgsql:host=db;dbname=michaelschiemer',
|
|
'postgres',
|
|
'StartSimple2024!'
|
|
);
|
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
echo "✅ Database connection established\n\n";
|
|
|
|
// Drop test table if exists
|
|
echo "Dropping test table if exists...\n";
|
|
$pdo->exec("DROP TABLE IF EXISTS test_jsonb_table");
|
|
echo "✅ Cleanup complete\n\n";
|
|
|
|
// Create test table with JSONB column
|
|
echo "Creating test table with JSONB column...\n";
|
|
$pdo->exec("
|
|
CREATE TABLE test_jsonb_table (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(100) NOT NULL,
|
|
metadata JSONB NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
");
|
|
echo "✅ Table created successfully\n\n";
|
|
|
|
// Create GIN index on JSONB column
|
|
echo "Creating GIN index on JSONB column...\n";
|
|
$pdo->exec("CREATE INDEX idx_metadata_gin ON test_jsonb_table USING GIN (metadata)");
|
|
echo "✅ GIN index created successfully\n\n";
|
|
|
|
// Verify index was created with correct method
|
|
echo "Verifying index method...\n";
|
|
$stmt = $pdo->query("
|
|
SELECT
|
|
indexname,
|
|
indexdef
|
|
FROM pg_indexes
|
|
WHERE tablename = 'test_jsonb_table'
|
|
AND indexname = 'idx_metadata_gin'
|
|
");
|
|
$indexInfo = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if ($indexInfo && str_contains($indexInfo['indexdef'], 'USING gin')) {
|
|
echo "✅ Index confirmed to use GIN method\n";
|
|
echo " Definition: {$indexInfo['indexdef']}\n\n";
|
|
} else {
|
|
echo "❌ Index not found or not using GIN method\n\n";
|
|
exit(1);
|
|
}
|
|
|
|
// Insert test data with JSONB
|
|
echo "Inserting test data...\n";
|
|
$testData = [
|
|
[
|
|
'name' => 'Product A',
|
|
'metadata' => json_encode([
|
|
'category' => 'electronics',
|
|
'price' => 299.99,
|
|
'tags' => ['new', 'sale', 'featured'],
|
|
'specs' => [
|
|
'cpu' => 'Intel i7',
|
|
'ram' => '16GB',
|
|
'storage' => '512GB SSD'
|
|
]
|
|
])
|
|
],
|
|
[
|
|
'name' => 'Product B',
|
|
'metadata' => json_encode([
|
|
'category' => 'electronics',
|
|
'price' => 499.99,
|
|
'tags' => ['premium', 'featured'],
|
|
'specs' => [
|
|
'cpu' => 'AMD Ryzen 9',
|
|
'ram' => '32GB',
|
|
'storage' => '1TB SSD'
|
|
]
|
|
])
|
|
],
|
|
[
|
|
'name' => 'Product C',
|
|
'metadata' => json_encode([
|
|
'category' => 'books',
|
|
'price' => 19.99,
|
|
'tags' => ['bestseller'],
|
|
'author' => 'John Doe',
|
|
'isbn' => '978-3-16-148410-0'
|
|
])
|
|
]
|
|
];
|
|
|
|
$insertStmt = $pdo->prepare("
|
|
INSERT INTO test_jsonb_table (name, metadata)
|
|
VALUES (:name, :metadata)
|
|
");
|
|
|
|
foreach ($testData as $data) {
|
|
$insertStmt->execute([
|
|
'name' => $data['name'],
|
|
'metadata' => $data['metadata']
|
|
]);
|
|
}
|
|
echo "✅ Test data inserted (3 products)\n\n";
|
|
|
|
// Test JSONB queries with different operators
|
|
echo "Testing JSONB queries:\n";
|
|
echo "=====================\n\n";
|
|
|
|
// Test 1: Contains operator (@>)
|
|
echo "1. Query: Find all electronics\n";
|
|
$stmt = $pdo->query("
|
|
SELECT name, metadata->>'category' as category, metadata->>'price' as price
|
|
FROM test_jsonb_table
|
|
WHERE metadata @> '{\"category\": \"electronics\"}'
|
|
");
|
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
echo " Found " . count($results) . " products:\n";
|
|
foreach ($results as $row) {
|
|
echo " - {$row['name']}: {$row['category']} (\${$row['price']})\n";
|
|
}
|
|
echo "\n";
|
|
|
|
// Test 2: JSON path query (using jsonb_exists to avoid PDO parameter conflict with ?)
|
|
echo "2. Query: Find products with 'featured' tag\n";
|
|
$stmt = $pdo->query("
|
|
SELECT name, metadata->'tags' as tags
|
|
FROM test_jsonb_table
|
|
WHERE jsonb_exists(metadata->'tags', 'featured')
|
|
");
|
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
echo " Found " . count($results) . " products:\n";
|
|
foreach ($results as $row) {
|
|
echo " - {$row['name']}: " . $row['tags'] . "\n";
|
|
}
|
|
echo "\n";
|
|
|
|
// Test 3: Nested JSON query
|
|
echo "3. Query: Find products with specific CPU\n";
|
|
$stmt = $pdo->query("
|
|
SELECT name, metadata->'specs'->>'cpu' as cpu, metadata->'specs'->>'ram' as ram
|
|
FROM test_jsonb_table
|
|
WHERE metadata->'specs'->>'cpu' LIKE '%Intel%'
|
|
");
|
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
echo " Found " . count($results) . " products:\n";
|
|
foreach ($results as $row) {
|
|
echo " - {$row['name']}: {$row['cpu']}, {$row['ram']} RAM\n";
|
|
}
|
|
echo "\n";
|
|
|
|
// Test 4: Query by price range
|
|
echo "4. Query: Find products under $100\n";
|
|
$stmt = $pdo->query("
|
|
SELECT name, (metadata->>'price')::numeric as price
|
|
FROM test_jsonb_table
|
|
WHERE (metadata->>'price')::numeric < 100
|
|
");
|
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
echo " Found " . count($results) . " products:\n";
|
|
foreach ($results as $row) {
|
|
echo " - {$row['name']}: \${$row['price']}\n";
|
|
}
|
|
echo "\n";
|
|
|
|
// Test 5: Existence check (using jsonb_exists to avoid PDO parameter conflict)
|
|
echo "5. Query: Find products with author field\n";
|
|
$stmt = $pdo->query("
|
|
SELECT name, metadata->>'author' as author
|
|
FROM test_jsonb_table
|
|
WHERE jsonb_exists(metadata, 'author')
|
|
");
|
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
echo " Found " . count($results) . " products:\n";
|
|
foreach ($results as $row) {
|
|
echo " - {$row['name']}: by {$row['author']}\n";
|
|
}
|
|
echo "\n";
|
|
|
|
// Verify GIN index is being used via EXPLAIN
|
|
echo "Verifying GIN index usage with EXPLAIN:\n";
|
|
$stmt = $pdo->query("
|
|
EXPLAIN (FORMAT JSON)
|
|
SELECT name FROM test_jsonb_table
|
|
WHERE metadata @> '{\"category\": \"electronics\"}'
|
|
");
|
|
$explainResult = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
$explainJson = json_decode($explainResult['QUERY PLAN'], true);
|
|
|
|
$usesGinIndex = false;
|
|
$planStr = json_encode($explainJson, JSON_PRETTY_PRINT);
|
|
|
|
if (str_contains($planStr, 'idx_metadata_gin') || str_contains($planStr, 'Bitmap Index Scan')) {
|
|
echo "✅ Query optimizer is using GIN index\n";
|
|
$usesGinIndex = true;
|
|
} else {
|
|
echo "⚠️ GIN index may not be used (table might be too small for index to be beneficial)\n";
|
|
}
|
|
echo "\n";
|
|
|
|
// Cleanup
|
|
echo "Cleaning up...\n";
|
|
$pdo->exec("DROP TABLE IF EXISTS test_jsonb_table");
|
|
echo "✅ Test table dropped\n";
|
|
|
|
echo "\n✅ All JSONB with GIN Index tests passed!\n";
|
|
echo "\nSummary:\n";
|
|
echo "========\n";
|
|
echo "✅ JSONB column type supported\n";
|
|
echo "✅ GIN index created successfully\n";
|
|
echo "✅ Contains operator (@>) works\n";
|
|
echo "✅ Existence check (jsonb_exists) works\n";
|
|
echo "✅ JSON path queries work\n";
|
|
echo "✅ Nested JSON queries work\n";
|
|
echo "✅ Type casting works\n";
|
|
if ($usesGinIndex) {
|
|
echo "✅ Query optimizer uses GIN index\n";
|
|
}
|
|
echo "\nNote: Using jsonb_exists() instead of ? operator due to PDO parameter placeholder conflict\n";
|
|
|
|
} catch (\Exception $e) {
|
|
echo "❌ Error: " . $e->getMessage() . "\n";
|
|
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
|
|
exit(1);
|
|
}
|