# Database Performance Rules
# ==========================

database_rules:
  - id: "PERF-001"
    name: "N+1 query detection"
    severity: warning
    description: |
      Accessing relationships in loops causes N+1 queries.
      For each item in a list, a separate query is executed.

      Solutions:
      - joinedload(): Eager load with JOIN
      - selectinload(): Eager load with IN clause
      - subqueryload(): Eager load with subquery
    file_pattern: "**/service*.py|**/api/**/*.py"
    anti_patterns:
      - 'for\s+\w+\s+in\s+\w+\.all\(\):\s*\n[^}]*\.\w+\.\w+'
    suggested_patterns:
      - "joinedload|selectinload|subqueryload"
    example_bad: |
      orders = db.query(Order).all()
      for order in orders:
          customer_name = order.customer.name  # N+1 query!
    example_good: |
      orders = db.query(Order).options(
          joinedload(Order.customer)
      ).all()
      for order in orders:
          customer_name = order.customer.name  # Already loaded

  - id: "PERF-002"
    name: "Eager loading for known relationships"
    severity: info
    description: |
      When you always need related data, use eager loading
      to reduce the number of database round trips.
    file_pattern: "**/service*.py"
    suggested_patterns:
      - "joinedload|selectinload|subqueryload"

  - id: "PERF-003"
    name: "Query result limiting"
    severity: warning
    description: |
      All list queries should have pagination or limits.
      Unbounded queries can cause memory issues and slow responses.
    file_pattern: "**/service*.py|**/api/**/*.py"
    anti_patterns:
      - '\\.all\\(\\)(?![^\\n]*limit|[^\\n]*\\[:)'
    exclude_patterns:
      - "# noqa: PERF-003"
      - "# bounded query"
      - ".filter("
    suggested_patterns:
      - "limit|offset|skip|paginate"
    example_bad: |
      all_products = db.query(Product).all()
    example_good: |
      products = db.query(Product).limit(100).all()
      # Or with pagination
      products = db.query(Product).offset(skip).limit(limit).all()

  - id: "PERF-004"
    name: "Index usage for filtered columns"
    severity: info
    description: |
      Columns frequently used in WHERE clauses should have indexes:
      - Foreign keys (vendor_id, customer_id)
      - Status fields
      - Date fields used for filtering
      - Boolean flags used for filtering
    file_pattern: "**/models/database/*.py"
    suggested_patterns:
      - "index=True|Index\\("

  - id: "PERF-005"
    name: "Select only needed columns"
    severity: info
    description: |
      For large tables, select only the columns you need.
      Use .with_entities() or load_only() to reduce data transfer.
    file_pattern: "**/service*.py"
    suggested_patterns:
      - "with_entities|load_only|defer"
    example_good: |
      # Only load id and name columns
      products = db.query(Product).options(
          load_only(Product.id, Product.name)
      ).all()

  - id: "PERF-006"
    name: "Bulk operations for multiple records"
    severity: warning
    description: |
      Use bulk operations instead of individual operations in loops:
      - bulk_insert_mappings() for inserts
      - bulk_update_mappings() for updates
      - add_all() for ORM inserts
    file_pattern: "**/service*.py"
    anti_patterns:
      - 'for\\s+\\w+\\s+in\\s+\\w+:\\s*\\n[^}]*db\\.add\\s*\\('
      - 'for\\s+\\w+\\s+in\\s+\\w+:\\s*\\n[^}]*\\.save\\s*\\('
    suggested_patterns:
      - "bulk_insert_mappings|bulk_update_mappings|add_all"
    example_bad: |
      for item in items:
          product = Product(**item)
          db.add(product)
    example_good: |
      products = [Product(**item) for item in items]
      db.add_all(products)

  - id: "PERF-007"
    name: "Connection pool configuration"
    severity: info
    description: |
      Configure database connection pool for optimal performance:
      - pool_size: Number of persistent connections
      - max_overflow: Additional connections allowed
      - pool_pre_ping: Check connection health
      - pool_recycle: Recycle connections periodically
    file_pattern: "**/database.py|**/config*.py"
    suggested_patterns:
      - "pool_size|pool_pre_ping|pool_recycle|max_overflow"

  - id: "PERF-008"
    name: "Use EXISTS for existence checks"
    severity: info
    description: |
      Use EXISTS or .first() is not None instead of count() > 0.
      EXISTS stops at first match, count() scans all matches.
    file_pattern: "**/service*.py"
    anti_patterns:
      - '\\.count\\(\\)\\s*>\\s*0'
      - '\\.count\\(\\)\\s*>=\\s*1'
      - '\\.count\\(\\)\\s*!=\\s*0'
    suggested_patterns:
      - "exists\\(\\)|scalar\\(exists"
    example_bad: |
      if db.query(Order).filter_by(customer_id=id).count() > 0:
    example_good: |
      exists_query = db.query(exists().where(Order.customer_id == id))
      if db.scalar(exists_query):

  - id: "PERF-009"
    name: "Batch updates instead of loops"
    severity: warning
    description: |
      Use .update() with filters instead of updating in a loop.
      One UPDATE statement is faster than N individual updates.
    file_pattern: "**/service*.py"
    anti_patterns:
      - 'for\\s+\\w+\\s+in\\s+\\w+:\\s*\\n[^}]*\\w+\\.\\w+\\s*='
    suggested_patterns:
      - "\\.update\\(\\{"
    example_bad: |
      for product in products:
          product.is_active = False
          db.add(product)
    example_good: |
      db.query(Product).filter(
          Product.id.in_(product_ids)
      ).update({"is_active": False}, synchronize_session=False)

  - id: "PERF-010"
    name: "Avoid SELECT * patterns"
    severity: info
    description: |
      When you only need specific columns, don't load entire rows.
      This reduces memory usage and network transfer.
    file_pattern: "**/service*.py"

  - id: "PERF-011"
    name: "Use appropriate join strategies"
    severity: info
    description: |
      Choose the right join strategy:
      - joinedload: Few related items, always needed
      - selectinload: Many related items, always needed
      - subqueryload: Complex queries, many related items
      - lazyload: Rarely accessed relationships
    file_pattern: "**/service*.py"

  - id: "PERF-012"
    name: "Transaction scope optimization"
    severity: warning
    description: |
      Keep transactions short and focused:
      - Don't hold transactions during I/O
      - Commit after bulk operations
      - Use read-only transactions when possible
    file_pattern: "**/service*.py"

  - id: "PERF-013"
    name: "Query result caching"
    severity: info
    description: |
      Consider caching for:
      - Frequently accessed, rarely changed data
      - Configuration tables
      - Reference data (categories, statuses)
    file_pattern: "**/service*.py"
    suggested_patterns:
      - "@cache|@lru_cache|redis|memcache"

  - id: "PERF-014"
    name: "Composite indexes for multi-column filters"
    severity: info
    description: |
      Queries filtering on multiple columns benefit from composite indexes.
      Order columns by selectivity (most selective first).
    file_pattern: "**/models/database/*.py"
    suggested_patterns:
      - "Index\\([^)]*,[^)]*\\)"

  - id: "PERF-015"
    name: "Avoid correlated subqueries"
    severity: info
    description: |
      Correlated subqueries execute once per row.
      Use JOINs or window functions instead when possible.
    file_pattern: "**/service*.py"
