feat: add unified code quality dashboard with multiple validators
- Add validator_type field to scans and violations (architecture, security, performance) - Create security validator with SEC-xxx rules - Create performance validator with PERF-xxx rules - Add base validator class for shared functionality - Add validate_all.py script to run all validators - Update code quality service with validator type filtering - Add validator type tabs to dashboard UI - Add validator type filter to violations list - Update stats response with per-validator breakdown - Add security and performance rules documentation - Add chat-bubble icons to icon library 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
223
.performance-rules/database.yaml
Normal file
223
.performance-rules/database.yaml
Normal file
@@ -0,0 +1,223 @@
|
||||
# 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"
|
||||
Reference in New Issue
Block a user