512 lines
16 KiB
Markdown
512 lines
16 KiB
Markdown
---
|
|
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
|