Skip to main content
Glama
test_explain_plan_real_db.py8.92 kB
import json import logging import pytest from postgres_mcp.artifacts import ErrorResult from postgres_mcp.artifacts import ExplainPlanArtifact from postgres_mcp.explain import ExplainPlanTool from postgres_mcp.sql import SqlDriver logger = logging.getLogger(__name__) @pytest.fixture def local_sql_driver(test_postgres_connection_string): connection_string, version = test_postgres_connection_string logger.info(f"Using connection string: {connection_string}") logger.info(f"Using version: {version}") return SqlDriver(engine_url=connection_string) async def setup_test_tables(sql_driver): pool_wrapper = sql_driver.connect() conn_pool = await pool_wrapper.pool_connect() async with conn_pool.connection() as conn: # Drop existing tables if they exist await conn.execute("DROP TABLE IF EXISTS test_orders") await conn.execute("DROP TABLE IF EXISTS test_customers") # Create tables with various features for testing explain plan await conn.execute( """ CREATE TABLE test_customers ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE, created_at TIMESTAMP DEFAULT NOW() ) """ ) await conn.execute( """ CREATE TABLE test_orders ( id SERIAL PRIMARY KEY, customer_id INTEGER REFERENCES test_customers(id), total DECIMAL NOT NULL, status TEXT NOT NULL, created_at TIMESTAMP DEFAULT NOW() ) """ ) # Create some indexes to test explain plans await conn.execute( """ CREATE INDEX idx_orders_customer ON test_orders(customer_id) """ ) await conn.execute( """ CREATE INDEX idx_orders_status ON test_orders(status) """ ) # Insert some test data await conn.execute( """ INSERT INTO test_customers (name, email) SELECT 'Customer ' || i, 'customer' || i || '@example.com' FROM generate_series(1, 100) i """ ) await conn.execute( """ INSERT INTO test_orders (customer_id, total, status) SELECT (random() * 99)::int + 1, (random() * 1000)::decimal, CASE (random() * 2)::int WHEN 0 THEN 'pending' WHEN 1 THEN 'completed' ELSE 'cancelled' END FROM generate_series(1, 1000) i """ ) # Run ANALYZE to update statistics await conn.execute("ANALYZE test_customers") await conn.execute("ANALYZE test_orders") async def cleanup_test_tables(sql_driver): pool_wrapper = sql_driver.connect() conn_pool = await pool_wrapper.pool_connect() try: async with conn_pool.connection() as conn: await conn.execute("DROP TABLE IF EXISTS test_orders") await conn.execute("DROP TABLE IF EXISTS test_customers") finally: await conn_pool.close() @pytest.mark.asyncio async def test_explain_with_real_db(local_sql_driver): """Test explain with a real database connection.""" await setup_test_tables(local_sql_driver) try: # Create explain plan tool with real db connection tool = ExplainPlanTool(sql_driver=local_sql_driver) # Test basic explain query = "SELECT * FROM test_customers WHERE id = 1" result = await tool.explain(query) # Verify the result assert isinstance(result, ExplainPlanArtifact) plan_data = json.loads(result.value) assert isinstance(plan_data, dict) assert "Plan" in plan_data assert "Node Type" in plan_data["Plan"] # PostgreSQL may choose different scan types depending on statistics # For small tables, sequential scan may be chosen over index scan node_type = plan_data["Plan"]["Node Type"] assert node_type in ["Index Scan", "Index Only Scan", "Seq Scan"] finally: await cleanup_test_tables(local_sql_driver) @pytest.mark.asyncio async def test_explain_analyze_with_real_db(local_sql_driver): """Test explain analyze with a real database connection.""" await setup_test_tables(local_sql_driver) try: # Create explain plan tool with real db connection tool = ExplainPlanTool(sql_driver=local_sql_driver) # Test explain analyze query = "SELECT * FROM test_customers WHERE id = 1" result = await tool.explain_analyze(query) # Verify the result assert isinstance(result, ExplainPlanArtifact) plan_data = json.loads(result.value) assert isinstance(plan_data, dict) assert "Plan" in plan_data # Check for analyze-specific fields assert "Execution Time" in plan_data assert "Actual Rows" in plan_data["Plan"] assert "Actual Total Time" in plan_data["Plan"] finally: await cleanup_test_tables(local_sql_driver) @pytest.mark.asyncio async def test_explain_join_query_with_real_db(local_sql_driver): """Test explain with a join query.""" await setup_test_tables(local_sql_driver) try: tool = ExplainPlanTool(sql_driver=local_sql_driver) # Test join query explain query = """ SELECT c.name, o.total, o.status FROM test_customers c JOIN test_orders o ON c.id = o.customer_id WHERE o.status = 'completed' """ result = await tool.explain(query) # Verify the result assert isinstance(result, ExplainPlanArtifact) plan_data = json.loads(result.value) assert isinstance(plan_data, dict) assert "Plan" in plan_data # Verify this is a join plan assert "Plans" in plan_data["Plan"] finally: await cleanup_test_tables(local_sql_driver) @pytest.mark.asyncio async def test_explain_with_bind_variables_real_db(local_sql_driver): """Test explain with bind variables on a real database.""" await setup_test_tables(local_sql_driver) try: tool = ExplainPlanTool(sql_driver=local_sql_driver) # Test query with bind variables query = "SELECT * FROM test_customers WHERE id = $1" result = await tool.explain(query) # Verify the result assert isinstance(result, ExplainPlanArtifact) plan_data = json.loads(result.value) assert isinstance(plan_data, dict) finally: await cleanup_test_tables(local_sql_driver) @pytest.mark.asyncio async def test_explain_with_like_expressions_real_db(local_sql_driver): """Test explain with LIKE expressions on a real database.""" await setup_test_tables(local_sql_driver) try: tool = ExplainPlanTool(sql_driver=local_sql_driver) # Test query with LIKE expression query = "SELECT * FROM test_customers WHERE name LIKE 'Customer%'" result = await tool.explain(query) # Verify the result assert isinstance(result, ExplainPlanArtifact) plan_data = json.loads(result.value) assert isinstance(plan_data, dict) assert "Plan" in plan_data # This should be a sequential scan since there's no index on name assert plan_data["Plan"]["Node Type"] == "Seq Scan" finally: await cleanup_test_tables(local_sql_driver) @pytest.mark.asyncio async def test_explain_with_like_and_bind_variables_real_db(local_sql_driver): """Test explain with both LIKE and bind variables on a real database.""" await setup_test_tables(local_sql_driver) try: tool = ExplainPlanTool(sql_driver=local_sql_driver) # Test query with both LIKE and bind variables query = "SELECT * FROM test_customers WHERE name LIKE $1" result = await tool.explain(query) # Verify the result assert isinstance(result, ExplainPlanArtifact) plan_data = json.loads(result.value) assert isinstance(plan_data, dict) finally: await cleanup_test_tables(local_sql_driver) @pytest.mark.asyncio async def test_explain_invalid_query_with_real_db(local_sql_driver): """Test explain with an invalid query.""" await setup_test_tables(local_sql_driver) try: tool = ExplainPlanTool(sql_driver=local_sql_driver) # Test invalid query query = "SELECT * FROM nonexistent_table" result = await tool.explain(query) # Verify error handling assert isinstance(result, ErrorResult) error_msg = result.value.lower() assert "relation" in error_msg and "not exist" in error_msg finally: await cleanup_test_tables(local_sql_driver)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/crystaldba/postgres-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server