Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:18:52 +08:00
commit 4257712eac
8 changed files with 667 additions and 0 deletions

511
commands/stored-proc.md Normal file
View File

@@ -0,0 +1,511 @@
---
description: Generate production-ready stored procedures and database functions
shortcut: stored-proc
---
# Stored Procedure Generator
Generate production-ready stored procedures, functions, triggers, and custom database logic for complex business rules, performance optimization, and transaction safety across PostgreSQL, MySQL, and SQL Server.
## When to Use This Command
Use `/stored-proc` when you need to:
- Implement complex business logic close to the data
- Enforce data integrity constraints beyond foreign keys
- Optimize performance by reducing network round trips
- Implement atomic multi-step operations with transaction safety
- Create reusable database functions for reporting and analytics
- Build database triggers for audit logging and data synchronization
DON'T use this when:
- Business logic frequently changes (better in application layer)
- Logic requires external API calls or file I/O
- Team lacks database development expertise
- Migrating between database systems (vendor lock-in risk)
- Simple CRUD operations sufficient (ORMs handle this)
## Design Decisions
This command implements **comprehensive stored procedure generation** because:
- Encapsulates business logic at database level for data integrity
- Reduces network latency by executing multiple queries in single call
- Provides transaction safety for complex multi-step operations
- Enables code reuse across multiple applications
- Leverages database-specific optimizations (compiled execution plans)
**Alternative considered: Application-layer logic**
- More portable across database systems
- Easier to test and debug
- Better for frequently changing logic
- Recommended for API-heavy applications
**Alternative considered: Database views**
- Read-only, no data modification
- Cannot contain procedural logic
- Better query optimizer hints
- Recommended for read-heavy reporting
## Prerequisites
Before running this command:
1. Understanding of target database's procedural language (PL/pgSQL, MySQL, T-SQL)
2. Knowledge of business logic requirements and edge cases
3. Database permissions to create procedures/functions
4. Testing framework for stored procedure validation
5. Documentation of expected inputs/outputs and error handling
## Implementation Process
### Step 1: Analyze Business Logic Requirements
Define inputs, outputs, error conditions, and transaction boundaries.
### Step 2: Choose Procedure Type
Select function (returns value), procedure (performs action), or trigger (automated).
### Step 3: Implement Core Logic
Write procedural code with proper error handling and transaction management.
### Step 4: Add Validation and Security
Implement input validation, SQL injection prevention, and permission checks.
### Step 5: Test and Optimize
Test edge cases, measure performance, and optimize execution plans.
## Output Format
The command generates:
- `procedures/business_logic.sql` - Production-ready stored procedures
- `functions/calculations.sql` - Reusable database functions
- `triggers/audit_triggers.sql` - Automated data tracking triggers
- `tests/procedure_tests.sql` - Unit tests for validation
- `docs/procedure_api.md` - Documentation with usage examples
## Code Examples
### Example 1: PostgreSQL Complex Business Logic Function
```sql
-- Order processing with inventory management and audit logging
CREATE OR REPLACE FUNCTION process_order(
p_customer_id INTEGER,
p_order_items JSONB,
p_shipping_address JSONB
) RETURNS TABLE (
order_id INTEGER,
total_amount DECIMAL(10,2),
status VARCHAR(50),
estimated_delivery DATE
) AS $$
DECLARE
v_order_id INTEGER;
v_total DECIMAL(10,2) := 0;
v_item JSONB;
v_product_id INTEGER;
v_quantity INTEGER;
v_price DECIMAL(10,2);
v_available_stock INTEGER;
BEGIN
-- Validate customer exists and is active
IF NOT EXISTS (
SELECT 1 FROM customers
WHERE customer_id = p_customer_id AND status = 'active'
) THEN
RAISE EXCEPTION 'Customer % not found or inactive', p_customer_id
USING HINT = 'Check customer_id and status';
END IF;
-- Create order record
INSERT INTO orders (customer_id, order_date, status, shipping_address)
VALUES (
p_customer_id,
CURRENT_TIMESTAMP,
'pending',
p_shipping_address
)
RETURNING order_id INTO v_order_id;
-- Process each order item
FOR v_item IN SELECT * FROM jsonb_array_elements(p_order_items)
LOOP
v_product_id := (v_item->>'product_id')::INTEGER;
v_quantity := (v_item->>'quantity')::INTEGER;
-- Validate product exists
SELECT price INTO v_price
FROM products
WHERE product_id = v_product_id AND active = true;
IF v_price IS NULL THEN
RAISE EXCEPTION 'Product % not found or inactive', v_product_id;
END IF;
-- Check inventory availability
SELECT stock_quantity INTO v_available_stock
FROM inventory
WHERE product_id = v_product_id
FOR UPDATE; -- Lock row for update
IF v_available_stock < v_quantity THEN
RAISE EXCEPTION 'Insufficient stock for product %. Available: %, Requested: %',
v_product_id, v_available_stock, v_quantity
USING HINT = 'Reduce quantity or remove from order';
END IF;
-- Create order item
INSERT INTO order_items (order_id, product_id, quantity, unit_price, subtotal)
VALUES (
v_order_id,
v_product_id,
v_quantity,
v_price,
v_quantity * v_price
);
-- Update inventory
UPDATE inventory
SET stock_quantity = stock_quantity - v_quantity,
last_updated = CURRENT_TIMESTAMP
WHERE product_id = v_product_id;
-- Add to total
v_total := v_total + (v_quantity * v_price);
END LOOP;
-- Update order total
UPDATE orders
SET total_amount = v_total,
status = 'confirmed'
WHERE order_id = v_order_id;
-- Calculate estimated delivery (3-5 business days)
DECLARE
v_delivery_date DATE;
BEGIN
v_delivery_date := CURRENT_DATE + INTERVAL '3 days';
-- Skip weekends
WHILE EXTRACT(DOW FROM v_delivery_date) IN (0, 6) LOOP
v_delivery_date := v_delivery_date + INTERVAL '1 day';
END LOOP;
END;
-- Log audit trail
INSERT INTO order_audit_log (order_id, action, user_id, timestamp, details)
VALUES (
v_order_id,
'order_created',
CURRENT_USER,
CURRENT_TIMESTAMP,
jsonb_build_object(
'customer_id', p_customer_id,
'total_amount', v_total,
'item_count', jsonb_array_length(p_order_items)
)
);
-- Return order details
RETURN QUERY
SELECT
v_order_id,
v_total,
'confirmed'::VARCHAR(50),
v_delivery_date;
EXCEPTION
WHEN OTHERS THEN
-- Log error
INSERT INTO error_log (function_name, error_message, error_detail, timestamp)
VALUES (
'process_order',
SQLERRM,
SQLSTATE,
CURRENT_TIMESTAMP
);
-- Re-raise exception
RAISE;
END;
$$ LANGUAGE plpgsql;
-- Usage example
SELECT * FROM process_order(
123, -- customer_id
'[
{"product_id": 456, "quantity": 2},
{"product_id": 789, "quantity": 1}
]'::JSONB,
'{"street": "123 Main St", "city": "Portland", "state": "OR", "zip": "97201"}'::JSONB
);
```
### Example 2: MySQL Stored Procedure with Cursors and Error Handling
```sql
-- User activity report generator with aggregations
DELIMITER $$
CREATE PROCEDURE generate_user_activity_report(
IN p_start_date DATE,
IN p_end_date DATE,
IN p_user_type VARCHAR(50)
)
BEGIN
DECLARE v_user_id INT;
DECLARE v_username VARCHAR(255);
DECLARE v_total_logins INT;
DECLARE v_total_transactions DECIMAL(10,2);
DECLARE v_done INT DEFAULT FALSE;
-- Cursor to iterate over users
DECLARE user_cursor CURSOR FOR
SELECT user_id, username
FROM users
WHERE user_type = p_user_type
AND created_at <= p_end_date
ORDER BY username;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = TRUE;
-- Create temporary results table
DROP TEMPORARY TABLE IF EXISTS temp_user_report;
CREATE TEMPORARY TABLE temp_user_report (
user_id INT,
username VARCHAR(255),
total_logins INT,
total_transactions DECIMAL(10,2),
avg_transaction_value DECIMAL(10,2),
last_activity_date DATETIME,
activity_status VARCHAR(20)
);
-- Start transaction
START TRANSACTION;
-- Open cursor
OPEN user_cursor;
user_loop: LOOP
FETCH user_cursor INTO v_user_id, v_username;
IF v_done THEN
LEAVE user_loop;
END IF;
-- Count login attempts
SELECT COUNT(*) INTO v_total_logins
FROM login_history
WHERE user_id = v_user_id
AND login_date BETWEEN p_start_date AND p_end_date
AND status = 'success';
-- Sum transaction amounts
SELECT COALESCE(SUM(amount), 0) INTO v_total_transactions
FROM transactions
WHERE user_id = v_user_id
AND transaction_date BETWEEN p_start_date AND p_end_date
AND status = 'completed';
-- Insert aggregated data
INSERT INTO temp_user_report (
user_id,
username,
total_logins,
total_transactions,
avg_transaction_value,
last_activity_date,
activity_status
)
SELECT
v_user_id,
v_username,
v_total_logins,
v_total_transactions,
CASE
WHEN v_total_logins > 0 THEN v_total_transactions / v_total_logins
ELSE 0
END,
(SELECT MAX(activity_date)
FROM (
SELECT MAX(login_date) AS activity_date FROM login_history WHERE user_id = v_user_id
UNION ALL
SELECT MAX(transaction_date) FROM transactions WHERE user_id = v_user_id
) activities),
CASE
WHEN v_total_logins >= 20 THEN 'highly_active'
WHEN v_total_logins >= 5 THEN 'active'
WHEN v_total_logins > 0 THEN 'low_activity'
ELSE 'inactive'
END;
END LOOP;
CLOSE user_cursor;
-- Commit transaction
COMMIT;
-- Return final report
SELECT
user_id,
username,
total_logins,
FORMAT(total_transactions, 2) AS total_transactions,
FORMAT(avg_transaction_value, 2) AS avg_transaction_value,
DATE_FORMAT(last_activity_date, '%Y-%m-%d %H:%i:%s') AS last_activity,
activity_status
FROM temp_user_report
ORDER BY total_transactions DESC;
END$$
DELIMITER ;
-- Usage
CALL generate_user_activity_report('2024-01-01', '2024-12-31', 'premium');
```
### Example 3: Database Triggers for Audit Logging
```sql
-- PostgreSQL audit trigger for tracking all table changes
CREATE OR REPLACE FUNCTION audit_trigger_function()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO audit_log (
table_name,
operation,
row_id,
new_data,
changed_by,
changed_at
) VALUES (
TG_TABLE_NAME,
'INSERT',
NEW.id,
row_to_json(NEW),
CURRENT_USER,
CURRENT_TIMESTAMP
);
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
INSERT INTO audit_log (
table_name,
operation,
row_id,
old_data,
new_data,
changed_by,
changed_at
) VALUES (
TG_TABLE_NAME,
'UPDATE',
NEW.id,
row_to_json(OLD),
row_to_json(NEW),
CURRENT_USER,
CURRENT_TIMESTAMP
);
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO audit_log (
table_name,
operation,
row_id,
old_data,
changed_by,
changed_at
) VALUES (
TG_TABLE_NAME,
'DELETE',
OLD.id,
row_to_json(OLD),
CURRENT_USER,
CURRENT_TIMESTAMP
);
RETURN OLD;
END IF;
END;
$$ LANGUAGE plpgsql;
-- Apply audit trigger to sensitive tables
CREATE TRIGGER users_audit_trigger
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW EXECUTE FUNCTION audit_trigger_function();
CREATE TRIGGER orders_audit_trigger
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW EXECUTE FUNCTION audit_trigger_function();
CREATE TRIGGER transactions_audit_trigger
AFTER INSERT OR UPDATE OR DELETE ON transactions
FOR EACH ROW EXECUTE FUNCTION audit_trigger_function();
```
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| "Function does not exist" | Missing or misspelled function name | Check function signature and schema |
| "Deadlock detected" | Concurrent transactions locking same rows | Use `FOR UPDATE SKIP LOCKED` or retry logic |
| "Stack depth limit exceeded" | Infinite recursion in function | Add recursion depth limit checks |
| "Out of shared memory" | Too many cursors or temp tables | Close cursors explicitly, limit temp table size |
| "Division by zero" | Unhandled edge case | Add NULL/zero checks before calculations |
## Configuration Options
**Function Types**
- **RETURNS TABLE**: Multi-row result sets
- **RETURNS SETOF**: Dynamic result sets
- **RETURNS VOID**: No return value (procedures)
- **RETURNS TRIGGER**: Trigger functions
**Volatility Categories**
- `IMMUTABLE`: Pure function, same inputs = same outputs
- `STABLE`: Results consistent within transaction
- `VOLATILE`: May change even with same inputs (default)
**Security Options**
- `SECURITY DEFINER`: Runs with creator's permissions
- `SECURITY INVOKER`: Runs with caller's permissions (default)
## Best Practices
DO:
- Use explicit parameter names (p_customer_id, not just id)
- Handle all possible error conditions with meaningful messages
- Use transactions for multi-step operations
- Close cursors explicitly to free resources
- Add input validation at function start
- Document parameters and return values
DON'T:
- Use `SELECT *` in production functions (specify columns)
- Perform network I/O or file operations in functions
- Create overly complex logic (split into multiple functions)
- Ignore SQL injection risks in dynamic SQL
- Forget to handle NULL values
- Use SECURITY DEFINER without careful permission checks
## Performance Considerations
- Functions add ~1-5ms overhead per call (acceptable for complex logic)
- Cached execution plans provide 10-50% speedup on repeated calls
- Triggers add overhead to every INSERT/UPDATE/DELETE (use sparingly)
- Use `FOR UPDATE SKIP LOCKED` to avoid blocking in high-concurrency scenarios
- Avoid cursors when set-based operations possible (cursors 10-100x slower)
- Index columns used in function WHERE clauses
## Related Commands
- `/database-migration-manager` - Deploy stored procedures across environments
- `/sql-query-optimizer` - Optimize queries within procedures
- `/database-transaction-monitor` - Monitor procedure execution and locks
- `/database-security-scanner` - Audit procedure permissions and SQL injection risks
## Version History
- v1.0.0 (2024-10): Initial implementation with PostgreSQL, MySQL, SQL Server support
- Planned v1.1.0: Add automated procedure testing framework and performance profiling