# Phase 1: Foundation - Connection Pool & Schema Registry

**Phase**: 1 of 5
**Effort**: 8 hours
**Status**: Ready to implement
**Prerequisite**: None (independent foundation layer)

---

## Objective

Establish the Rust database foundation layer:
1. Set up tokio-postgres + deadpool connection pool
2. Create schema registry integration
3. Verify connection lifecycle
4. Pass all 5991+ existing tests (backward compatibility)

**Success Criteria**:
- ✅ Connection pool initializes and manages connections
- ✅ Schema registry bridges Python and Rust
- ✅ All existing tests pass (no regressions)
- ✅ Connection pooling benchmarks show stability

---

## Architecture Overview

### Layer 1: Connection Pool (Rust Core)

```rust
// fraiseql_rs/src/db/pool.rs
pub struct DatabasePool {
    pool: deadpool_postgres::Pool,
    config: PoolConfig,
}

impl DatabasePool {
    pub async fn new(url: &str, config: PoolConfig) -> Result<Self>;
    pub async fn get_connection(&self) -> Result<Object>;
    pub async fn health_check(&self) -> Result<()>;
}

pub struct PoolConfig {
    max_size: u32,
    min_idle: u32,
    connection_timeout: Duration,
    idle_timeout: Duration,
}
```

### Layer 2: Python Wrapper

```python
# src/fraiseql/core/database.py (NEW)
from fraiseql._fraiseql_rs import DatabasePool

class RustDatabasePool:
    """Thin Python wrapper around Rust connection pool."""

    def __init__(self, url: str, config: dict) -> None:
        self._pool = DatabasePool(url, config)

    async def acquire(self) -> Connection:
        """Get a connection from the pool."""
        return await self._pool.get_connection()

    async def health_check(self) -> bool:
        """Check pool health."""
        return await self._pool.health_check()
```

### Connection Flow

```
Python: 1. Call Python pool method
         ↓
PyO3:    2. Marshal arguments
         ↓
Rust:    3. Get connection from deadpool
         ↓
Rust:    4. Initialize connection (SET session variables)
         ↓
PyO3:    5. Return connection object to Python
         ↓
Python:  6. Use connection for queries
```

---

## Implementation Steps

### Step 1: Add Cargo Dependencies

**File**: `fraiseql_rs/Cargo.toml`

Add to `[dependencies]` section:

```toml
# Connection pooling
tokio-postgres = "0.7"
deadpool-postgres = "0.14"
deadpool = "0.10"

# URL parsing for connection strings
tokio-postgres-rustls = "0.10"  # TLS support
rustls = "0.23"
rustls-pemfile = "2.0"

# Async utilities
async-trait = "0.1"
```

**Verification**:
```bash
cd fraiseql_rs && cargo check
# Should compile without errors
```

### Step 2: Create Connection Pool Module

**File**: `fraiseql_rs/src/db/mod.rs` (NEW)

```rust
//! Database connection and query execution layer for PostgreSQL.
//!
//! This module provides:
//! - Connection pooling with deadpool-postgres
//! - Query execution with streaming results
//! - Transaction management
//! - Connection lifecycle management

pub mod pool;
pub mod query;
pub mod types;
pub mod where_builder;

pub use pool::{DatabasePool, PoolConfig};
pub use query::QueryExecutor;
pub use types::{QueryParam, QueryResult};
```

**Verification**:
```bash
cargo build -p fraiseql_rs
# Should compile (but pool module will be incomplete)
```

### Step 3: Create Pool Configuration

**File**: `fraiseql_rs/src/db/types.rs` (NEW)

```rust
//! Type definitions for database layer.

use serde::{Deserialize, Serialize};
use std::time::Duration;
use thiserror::Error;

/// Connection pool configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoolConfig {
    /// Maximum number of connections in the pool.
    pub max_size: u32,

    /// Minimum number of idle connections to maintain.
    pub min_idle: u32,

    /// Timeout for acquiring a connection from the pool.
    pub connection_timeout: u64,  // milliseconds

    /// Timeout for idle connections (0 = no timeout).
    pub idle_timeout: u64,  // milliseconds

    /// Maximum lifetime of a connection (0 = no limit).
    pub max_lifetime: u64,  // milliseconds
}

impl Default for PoolConfig {
    fn default() -> Self {
        Self {
            max_size: 20,
            min_idle: 2,
            connection_timeout: 30_000,  // 30 seconds
            idle_timeout: 600_000,       // 10 minutes
            max_lifetime: 1_800_000,     // 30 minutes
        }
    }
}

/// Query parameter.
#[derive(Debug, Clone)]
pub enum QueryParam {
    String(String),
    Int(i64),
    Float(f64),
    Bool(bool),
    Null,
    Json(String),
}

/// Query result row.
#[derive(Debug)]
pub struct QueryResult {
    pub columns: Vec<String>,
    pub rows: Vec<Vec<QueryParam>>,
}

/// Database errors.
#[derive(Error, Debug)]
pub enum DatabaseError {
    #[error("Connection pool error: {0}")]
    PoolError(String),

    #[error("Query error: {0}")]
    QueryError(String),

    #[error("Connection error: {0}")]
    ConnectionError(String),

    #[error("Timeout: {0}")]
    Timeout(String),

    #[error("Configuration error: {0}")]
    ConfigError(String),
}

impl From<tokio_postgres::Error> for DatabaseError {
    fn from(err: tokio_postgres::Error) -> Self {
        DatabaseError::QueryError(err.to_string())
    }
}
```

**Verification**:
```bash
cargo test -p fraiseql_rs --lib db::types
# Should pass (simple type tests)
```

### Step 4: Create Connection Pool Implementation

**File**: `fraiseql_rs/src/db/pool.rs` (NEW)

**CRITICAL**: This step implements the async/PyO3 bridge. Study this carefully.

```rust
//! PostgreSQL connection pool management with async/PyO3 integration.

use deadpool_postgres::{Config, Runtime, Pool as DeadpoolPool, Object};
use pyo3::prelude::*;
use pyo3_asyncio::tokio;
use std::sync::Arc;

use super::types::{DatabaseError, PoolConfig};

/// PostgreSQL connection pool (wrapped in Arc for thread-safety across FFI).
///
/// CRITICAL: Pool must be created ONCE and shared across all requests.
/// Using Arc<Mutex<>> would add lock contention - deadpool handles this internally.
#[pyclass]
pub struct DatabasePool {
    pool: Arc<DeadpoolPool>,
    config: PoolConfig,
}

#[pymethods]
impl DatabasePool {
    /// Create a new database pool.
    ///
    /// SYNC function (runs on Python thread), returns immediately.
    /// Pool initialization happens asynchronously when first connection is needed.
    ///
    /// # Arguments
    /// * `url` - PostgreSQL connection URL (e.g., "postgres://user:pass@host/db")
    /// * `config_dict` - Python dict with pool configuration
    ///
    /// # Example
    /// ```python
    /// # This is SYNC and returns immediately
    /// pool = DatabasePool(
    ///     "postgres://user:pass@localhost/fraiseql",
    ///     {
    ///         "max_size": 20,
    ///         "min_idle": 2,
    ///         "connection_timeout_ms": 30000,
    ///     }
    /// )
    /// ```
    #[new]
    fn new(py: Python, url: String, config_dict: Option<&PyDict>) -> PyResult<Self> {
        // Parse configuration from Python dict
        let config = parse_config_from_dict(config_dict)?;

        // Parse PostgreSQL connection URL
        let pg_config = url.parse::<tokio_postgres::config::Config>()
            .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(
                format!("Invalid database URL: {}", e)
            ))?;

        // Build deadpool config
        let mut deadpool_cfg = deadpool_postgres::Config {
            dbname: pg_config.get_dbname().map(|s| s.to_string()),
            user: pg_config.get_user().map(|s| s.to_string()),
            password: pg_config.get_password().map(|p| p.to_string()),
            host: pg_config.get_hosts().and_then(|hosts| {
                hosts.first().and_then(|h| h.as_str().map(|s| s.to_string()))
            }),
            port: pg_config.get_ports().and_then(|ports| ports.first().copied()),
            ..Default::default()
        };

        // Set pool size
        deadpool_cfg.pool = Some(deadpool_postgres::PoolConfig {
            max_size: config.max_size as usize,
            timeouts: deadpool_postgres::Timeouts {
                wait: Some(std::time::Duration::from_millis(config.connection_timeout)),
                create: Some(std::time::Duration::from_secs(5)),
                recycle: Some(std::time::Duration::from_secs(5)),
            },
        });

        // Create pool (doesn't connect yet - lazy initialization)
        let pool = deadpool_cfg
            .create_pool(Some(Runtime::Tokio1))
            .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(
                format!("Failed to create pool: {}", e)
            ))?;

        Ok(DatabasePool {
            pool: Arc::new(pool),
            config,
        })
    }

    /// Acquire a connection from the pool (ASYNC).
    ///
    /// CRITICAL IMPLEMENTATION:
    /// - This returns a Python coroutine that Python can await
    /// - The actual async work happens in tokio runtime
    /// - Connection is automatically returned to pool when dropped
    ///
    /// Usage from Python:
    /// ```python
    /// async def my_handler():
    ///     conn = await pool.acquire_connection()
    ///     # Use connection
    ///     # Automatically returned when scope exits
    /// ```
    #[pyo3_asyncio::tokio::main]
    async fn acquire_connection(&self, py: Python) -> PyResult<Py<PyAny>> {
        // Clone arc so we own a reference
        let pool = self.pool.clone();

        // Return Python coroutine wrapping the async work
        pyo3_asyncio::tokio::future_into_py(py, async move {
            // This code runs in tokio runtime
            match tokio::time::timeout(
                std::time::Duration::from_millis(self.config.connection_timeout),
                pool.get(),
            )
            .await
            {
                Ok(Ok(_conn)) => {
                    // Connection acquired successfully
                    // Note: We don't return the connection here - Phase 2 handles this
                    // For now, just confirm success
                    Ok(py.None())
                }
                Ok(Err(e)) => {
                    Err(PyErr::new::<pyo3::exceptions::RuntimeError, _>(
                        format!("Failed to acquire connection: {}", e)
                    ))
                }
                Err(_) => {
                    Err(PyErr::new::<pyo3::exceptions::TimeoutError, _>(
                        format!("Connection acquisition timeout after {}ms", self.config.connection_timeout)
                    ))
                }
            }
        })
    }

    /// Check pool health (ASYNC).
    ///
    /// Tries to acquire and immediately release a connection.
    /// Returns True if successful, False if pool is unhealthy.
    #[pyo3_asyncio::tokio::main]
    async fn health_check(&self, py: Python) -> PyResult<Py<PyAny>> {
        let pool = self.pool.clone();
        let timeout_ms = self.config.connection_timeout;

        pyo3_asyncio::tokio::future_into_py(py, async move {
            match tokio::time::timeout(
                std::time::Duration::from_millis(timeout_ms),
                pool.get(),
            )
            .await
            {
                Ok(Ok(_)) => Ok(true),
                _ => Ok(false),
            }
        })
    }

    /// Get pool statistics (SYNC).
    ///
    /// Returns current pool state. These are approximate values.
    fn get_stats(&self) -> PyResult<PyObject> {
        Python::with_gil(|py| {
            let stats = self.pool.state();

            let dict = pyo3::types::PyDict::new(py);
            dict.set_item("connections", stats.connections)?;
            dict.set_item("idle_connections", stats.idle_connections)?;
            dict.set_item("active_connections", stats.connections - stats.idle_connections)?;

            Ok(dict.into())
        })
    }

    /// Get pool configuration (for debugging).
    fn get_config(&self) -> PyResult<PyObject> {
        Python::with_gil(|py| {
            let dict = pyo3::types::PyDict::new(py);
            dict.set_item("max_size", self.config.max_size)?;
            dict.set_item("min_idle", self.config.min_idle)?;
            dict.set_item("connection_timeout_ms", self.config.connection_timeout)?;
            dict.set_item("idle_timeout_ms", self.config.idle_timeout)?;
            dict.set_item("max_lifetime_ms", self.config.max_lifetime)?;

            Ok(dict.into())
        })
    }
}

/// Helper: Parse pool config from Python dict
fn parse_config_from_dict(dict_opt: Option<&PyDict>) -> PyResult<PoolConfig> {
    match dict_opt {
        Some(dict) => {
            Ok(PoolConfig {
                max_size: dict
                    .get_item("max_size")
                    .and_then(|v| v.extract::<u32>().ok())
                    .unwrap_or(20),
                min_idle: dict
                    .get_item("min_idle")
                    .and_then(|v| v.extract::<u32>().ok())
                    .unwrap_or(2),
                connection_timeout: dict
                    .get_item("connection_timeout_ms")
                    .and_then(|v| v.extract::<u64>().ok())
                    .unwrap_or(30_000),
                idle_timeout: dict
                    .get_item("idle_timeout_ms")
                    .and_then(|v| v.extract::<u64>().ok())
                    .unwrap_or(600_000),
                max_lifetime: dict
                    .get_item("max_lifetime_ms")
                    .and_then(|v| v.extract::<u64>().ok())
                    .unwrap_or(1_800_000),
            })
        }
        None => Ok(PoolConfig::default()),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_pool_config_defaults() {
        let config = PoolConfig::default();
        assert_eq!(config.max_size, 20);
        assert_eq!(config.min_idle, 2);
    }

    #[test]
    fn test_pool_config_custom() {
        let config = PoolConfig {
            max_size: 50,
            min_idle: 5,
            ..Default::default()
        };
        assert_eq!(config.max_size, 50);
    }
}
```

**Verification**:
```bash
cargo test -p fraiseql_rs --lib db::pool::tests
# Should pass all tests
```

### Step 5: Create Query Executor Stub

**File**: `fraiseql_rs/src/db/query.rs` (NEW)

```rust
//! Query execution layer.

use super::types::{DatabaseError, QueryParam, QueryResult};
use async_trait::async_trait;

/// Query executor trait.
#[async_trait]
pub trait QueryExecutor {
    async fn execute_raw(
        &self,
        sql: &str,
        params: &[QueryParam],
    ) -> Result<QueryResult, DatabaseError>;

    async fn execute_and_stream(
        &self,
        sql: &str,
        params: &[QueryParam],
    ) -> Result<Vec<String>, DatabaseError>;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_query_param_string() {
        let _param = QueryParam::String("test".to_string());
        // More tests in Phase 2
    }
}
```

### Step 6: Create Where Builder Stub

**File**: `fraiseql_rs/src/db/where_builder.rs` (NEW)

```rust
//! WHERE clause builder for GraphQL queries.

use super::types::QueryParam;

/// Build WHERE clause from GraphQL filters.
pub fn build_where_clause(
    table: &str,
    filters: &[(String, String)],
) -> Result<(String, Vec<QueryParam>), String> {
    // Implementation in Phase 2
    Ok((String::new(), Vec::new()))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_where_clause_simple() {
        // Tests in Phase 2
    }
}
```

**Verification**:
```bash
cargo build -p fraiseql_rs
# Should compile completely
```

### Step 7: Update lib.rs to Export Database Module

**File**: `fraiseql_rs/src/lib.rs`

Find the existing module declarations and add:

```rust
// Add after existing mod declarations (around line 8)
pub mod db;

// In the PyModule initialization (around line 100+), add:
m.add_class::<db::pool::DatabasePool>()?;
```

**Verification**:
```bash
cargo build -p fraiseql_rs
# Should compile
```

### Step 8: Create Python Wrapper

**File**: `src/fraiseql/core/database.py` (NEW)

```python
"""Rust-native database layer wrapper.

This module provides a thin Python wrapper around the Rust database
layer. It handles:
- Connection pool initialization
- Configuration from environment variables
- Health checking
- Graceful degradation to psycopg (fallback)
"""

import logging
import os
from contextlib import asynccontextmanager
from typing import Any, AsyncGenerator, Optional

logger = logging.getLogger(__name__)


class RustDatabasePool:
    """Thin Python wrapper around Rust connection pool."""

    def __init__(
        self,
        url: str,
        config: Optional[dict[str, Any]] = None,
        enabled: bool = True,
    ) -> None:
        """Initialize Rust database pool.

        Args:
            url: PostgreSQL connection URL
            config: Pool configuration dict with keys:
                - max_size: Maximum pool size (default: 20)
                - min_idle: Minimum idle connections (default: 2)
                - connection_timeout: Timeout in ms (default: 30000)
                - idle_timeout: Idle timeout in ms (default: 600000)
                - max_lifetime: Max connection lifetime in ms (default: 1800000)
            enabled: Whether to use Rust backend (default: True)

        Raises:
            ImportError: If Rust extension not available
            ValueError: If URL is invalid
        """
        self.url = url
        self.config = config or {}
        self.enabled = enabled
        self._pool = None

        if enabled:
            self._init_rust_pool()

    def _init_rust_pool(self) -> None:
        """Initialize the Rust connection pool."""
        try:
            from fraiseql._fraiseql_rs import DatabasePool

            self._pool = DatabasePool(self.url, self.config)
            logger.info("✅ Rust database pool initialized")
        except ImportError as e:
            logger.warning(
                f"⚠️  Rust database pool not available: {e}. "
                "Falling back to psycopg."
            )
            self.enabled = False

    async def health_check(self) -> bool:
        """Check pool health."""
        if not self.enabled or self._pool is None:
            return True  # Assume healthy if using psycopg

        try:
            return self._pool.health_check()
        except Exception as e:
            logger.error(f"Health check failed: {e}")
            return False

    def get_stats(self) -> dict[str, Any]:
        """Get pool statistics."""
        if not self.enabled or self._pool is None:
            return {"status": "psycopg", "connections": 0}

        try:
            return self._pool.get_stats()
        except Exception as e:
            logger.error(f"Failed to get stats: {e}")
            return {"error": str(e)}

    @asynccontextmanager
    async def acquire(self) -> AsyncGenerator[Any, None]:
        """Acquire a connection from the pool.

        Usage:
            async with pool.acquire() as conn:
                result = await conn.fetch("SELECT ...")
        """
        if not self.enabled or self._pool is None:
            # Fallback to psycopg (implemented in Phase 2)
            raise NotImplementedError("Fallback to psycopg not yet implemented")

        try:
            conn = await self._pool.acquire_connection()
            yield conn
        finally:
            # Connection automatically returned to pool
            pass


def create_pool_from_env() -> RustDatabasePool:
    """Create a database pool from environment variables.

    Environment variables:
    - DATABASE_URL: PostgreSQL connection URL
    - RUST_DB_ENABLED: Enable Rust backend (default: true)
    - RUST_DB_MAX_SIZE: Pool max size (default: 20)
    - RUST_DB_MIN_IDLE: Pool min idle (default: 2)
    - RUST_DB_CONNECTION_TIMEOUT: Timeout in ms (default: 30000)
    - RUST_DB_IDLE_TIMEOUT: Idle timeout in ms (default: 600000)

    Returns:
        RustDatabasePool configured from environment

    Raises:
        ValueError: If DATABASE_URL not set
    """
    url = os.getenv("DATABASE_URL")
    if not url:
        raise ValueError("DATABASE_URL environment variable not set")

    enabled = os.getenv("RUST_DB_ENABLED", "true").lower() == "true"

    config = {
        "max_size": int(os.getenv("RUST_DB_MAX_SIZE", "20")),
        "min_idle": int(os.getenv("RUST_DB_MIN_IDLE", "2")),
        "connection_timeout": int(os.getenv("RUST_DB_CONNECTION_TIMEOUT", "30000")),
        "idle_timeout": int(os.getenv("RUST_DB_IDLE_TIMEOUT", "600000")),
        "max_lifetime": int(os.getenv("RUST_DB_MAX_LIFETIME", "1800000")),
    }

    return RustDatabasePool(url, config, enabled=enabled)
```

**Verification**:
```bash
uv run python -c "from fraiseql.core.database import RustDatabasePool; print('✅ Import successful')"
# Should print: ✅ Import successful
```

### Step 9: Create Integration Tests

**File**: `tests/integration/db/test_rust_pool.py` (NEW)

```python
"""Integration tests for Rust database pool."""

import pytest
from fraiseql.core.database import RustDatabasePool


class TestDatabasePool:
    """Test Rust connection pool."""

    def test_pool_initialization_disabled(self):
        """Test that pool can be initialized in disabled mode."""
        pool = RustDatabasePool("postgres://localhost/test", enabled=False)
        assert pool.enabled is False

    @pytest.mark.skipif(
        True, reason="Requires Rust extension - implement in Phase 2"
    )
    async def test_pool_initialization_enabled(self):
        """Test pool initialization with Rust backend."""
        pool = RustDatabasePool("postgres://localhost/test", enabled=True)
        assert pool.enabled is True

    def test_pool_stats_disabled(self):
        """Test pool stats when disabled."""
        pool = RustDatabasePool("postgres://localhost/test", enabled=False)
        stats = pool.get_stats()
        assert stats["status"] == "psycopg"

    def test_pool_config_custom(self):
        """Test custom pool configuration."""
        config = {
            "max_size": 50,
            "min_idle": 5,
        }
        pool = RustDatabasePool(
            "postgres://localhost/test",
            config=config,
            enabled=False,
        )
        assert pool.config["max_size"] == 50
        assert pool.config["min_idle"] == 5
```

**Verification**:
```bash
uv run pytest tests/integration/db/test_rust_pool.py -v
# Should pass (skipped tests are OK at this stage)
```

### Step 10: Verify Backward Compatibility

**File**: `tests/regression/test_existing_suite.py` (already exists, just verify)

```bash
# Run existing test suite to ensure no regressions
uv run pytest tests/ -v -k "not rust" --tb=short

# Expected: All 5991+ tests should pass
```

---

## Acceptance Criteria

### Compile & Build
- [ ] `cargo build -p fraiseql_rs` completes without errors
- [ ] `cargo test -p fraiseql_rs --lib db` passes all tests
- [ ] `uv run pytest tests/integration/db/ -v` passes (skipped OK)

### Python Integration
- [ ] `from fraiseql.core.database import RustDatabasePool` succeeds
- [ ] `RustDatabasePool("...", enabled=False)` initializes correctly
- [ ] `pool.get_stats()` returns dict (even when disabled)

### Backward Compatibility
- [ ] All 5991+ existing tests pass
- [ ] No changes to public API
- [ ] psycopg still works (fallback mode)

### Documentation
- [ ] All new Rust code has doc comments
- [ ] All new Python code has docstrings
- [ ] Type hints complete

---

## 🧪 Testing Strategy for Phase 1

**Key Principle**: Don't port existing tests - keep them working, add Rust unit tests.

### What Tests Should Pass

#### ✅ **Existing Python Tests** (~5991 tests)
```bash
# All existing tests continue to pass
# They test through the Python API wrapper
# Backend (Python or Rust) is invisible to them

# Run them with Rust backend:
FRAISEQL_DB_BACKEND=rust uv run pytest tests/ -v
# Expected: All 5991+ tests PASS

# Or with Python backend (fallback):
FRAISEQL_DB_BACKEND=python uv run pytest tests/ -v
# Expected: All 5991+ tests PASS
```

**Why they pass**: Tests call `schema.execute()` or HTTP endpoints. They don't care which backend (Python or Rust) handles the query.

#### ✅ **New Rust Unit Tests** (~50 tests)
```bash
# Add tests for connection pool implementation
# These test Rust code directly

cargo test --lib db::pool
# Expected: 50+ new Rust tests PASS
```

#### ✅ **New Rust Integration Tests** (~20 tests)
```bash
# Test connection pool with actual database

cargo test --test '*pool*'
# Expected: Integration tests PASS
```

#### ✅ **Parity Tests** (~10 tests)
```bash
# Verify Rust pool matches Python pool behavior

FRAISEQL_PARITY_TESTING=true cargo test
# Expected: Rust connections == Python connections
```

### Testing Checklist for Phase 1

- [ ] **Python tests pass with Rust backend**
  ```bash
  FRAISEQL_DB_BACKEND=rust uv run pytest tests/ -v
  # Should see: "5991 passed"
  ```

- [ ] **Rust unit tests for pool pass**
  ```bash
  cargo test --lib db::pool --verbose
  # Should see: "test result: ok. ~50 passed"
  ```

- [ ] **Integration tests pass**
  ```bash
  cargo test --test '*'
  # Should see: all integration tests pass
  ```

- [ ] **Parity tests pass** (both backends match)
  ```bash
  FRAISEQL_PARITY_TESTING=true cargo test regression::parity
  # Should see: "test result: ok"
  ```

- [ ] **No regressions**
  ```bash
  # Verify performance baseline
  cargo bench --benchmark connection_pool -- --save-baseline phase-1
  # Compare against Phase 0 baseline (< 10% overhead acceptable)
  ```

- [ ] **Feature flags work**
  ```bash
  # Test each backend independently
  cargo build --no-default-features --features python-db
  # Test Rust backend
  cargo build --no-default-features --features rust-db
  ```

### What Should NOT Be Changed

❌ **Don't port existing Python tests**
- The 5991 existing Python tests test the Python API layer
- They don't care which backend handles database operations
- Just keep them running as-is
- In Phase 5, remove tests that specifically test psycopg

❌ **Don't remove psycopg yet**
- It's still the fallback in Phase 1
- Feature flags keep both backends active
- We'll remove it in Phase 5 (Deprecation phase)

### Test Count Summary for Phase 1

| Category | Count | Status |
|----------|-------|--------|
| Python API tests (unchanged) | 5991 | ✅ PASS |
| Rust pool unit tests (NEW) | ~50 | ✅ PASS |
| Rust integration tests (NEW) | ~20 | ✅ PASS |
| Parity tests (NEW) | ~10 | ✅ PASS |
| **Total** | **~6071** | **✅ ALL PASS** |

### Verification Command

```bash
# Run this to verify Phase 1 testing is complete:

echo "1. Testing Python API with Rust backend..."
FRAISEQL_DB_BACKEND=rust uv run pytest tests/ -q
# Should see: "5991 passed"

echo ""
echo "2. Testing Rust implementation..."
cargo test --lib db::pool --quiet
# Should see: "test result: ok"

echo ""
echo "3. Testing integration..."
cargo test --test '*' --quiet
# Should see: all integration tests pass

echo ""
echo "4. Testing parity (both backends match)..."
FRAISEQL_PARITY_TESTING=true cargo test regression::parity --quiet
# Should see: parity tests pass

echo ""
echo "5. Checking performance (< 10% overhead)..."
cargo bench --benchmark connection_pool --quiet
# Compare to baseline - should be close

echo ""
echo "✅ Phase 1 Testing Complete!"
```

---

## Troubleshooting

### Issue: `error: could not compile 'fraiseql_rs'`

**Check**:
```bash
cargo update
cargo build -p fraiseql_rs --verbose
```

Look for missing dependencies or Rust version issues.

### Issue: `ImportError: fraiseql._fraiseql_rs not found`

**Expected** at this stage. The module is stubbed out.

**Solution**: Move to Phase 2 to implement async functions.

### Issue: Tests fail with `NotImplementedError`

**Expected** for async tests. Phase 1 is foundation only.

**Solution**: Add `@pytest.mark.skip(reason="Async - Phase 2")` decorators.

---

## Verification Commands

### Quick Check
```bash
# Compile check
cargo check -p fraiseql_rs

# Unit tests
cargo test -p fraiseql_rs --lib db

# Integration tests
uv run pytest tests/integration/db/ -v
```

### Full Verification
```bash
# Build everything
cargo build -p fraiseql_rs
uv run pip install -e .

# Run all tests
uv run pytest tests/ -v --tb=short

# Check for regressions
uv run pytest tests/regression/ -v
```

### Performance Baseline
```bash
# Get current psycopg performance baseline
uv run pytest tests/performance/ -v 2>&1 | tee baseline_phase1.txt
```

---

## 👥 Review Checkpoint for Junior Engineers

**After completing Phase 1, request code review**:

- [ ] Connection pool implementation looks correct?
- [ ] Async/await patterns used properly?
- [ ] Error handling across FFI boundary correct?
- [ ] Schema registry integration makes sense?
- [ ] Tests cover main code paths?
- [ ] No unused .unwrap() or .expect() calls?

**Why**: Phase 1 is foundational. Wrong patterns here cascade through all other phases.

**What to show reviewer**:
```bash
# Run all tests to show they pass
cargo test --lib phase_1

# Show the connection pool code
git diff HEAD~1 fraiseql_rs/src/db/pool.rs

# Show test coverage
cargo test --lib -- --nocapture
```

**What NOT to worry about yet**:
- Performance optimization (comes in Phase 3)
- Full SQL generation (covered in Phase 2)
- Result streaming (Phase 3 focus)

---

## Completion Checklist

- [ ] Step 1-10 completed
- [ ] All compilation errors resolved
- [ ] All unit tests passing
- [ ] Integration tests passing (or skipped)
- [ ] Backward compatibility verified
- [ ] No regressions in existing test suite
- [ ] Documentation complete
- [ ] Branch ready for review

---

## Next Phase

After Phase 1 is complete and verified:

👉 Proceed to **Phase 2: Query Execution**

See: `.phases/rust-postgres-driver/phase-2-query-execution.md`

---

**Status**: ✅ Ready for implementation
**Duration**: 8 hours
**Branch**: `feature/rust-postgres-driver`
