Source code for operators.bedrock.safe_sql_executor

"""
SafeSQLExecutorOperator - Execute SQL with validation and limits.

This operator provides secure SQL execution with:
- SQL validation before execution (DDL/DML rejection)
- LIMIT clause enforcement
- Configurable return format (dataframe, dict, records)
- Security event logging

Usage:
    from operators.bedrock import SafeSQLExecutorOperator

    execute_query = SafeSQLExecutorOperator(
        task_id="execute_query",
        sql="SELECT * FROM my_table WHERE status = 'active'",
        database="my_database",
        max_limit=100,
    )

Author: yuan@ready.net
Date: 2026-01-29
"""

from typing import List, Optional, Tuple

from airflow.models import BaseOperator
from airflow.utils.decorators import apply_defaults


[docs] class SafeSQLExecutorOperator(BaseOperator): """ Execute SQL query with safety validation. This operator validates SQL queries before execution to prevent dangerous operations (DDL/DML) and enforces query limits. :param sql: SQL query to execute (can be templated) :param database: Athena database name :param max_limit: Maximum allowed LIMIT value (default 1000) :param default_limit: Default LIMIT if none specified (default 100) :param dangerous_patterns: Custom SQL patterns to block (optional) :param return_format: Output format - 'dataframe', 'dict', or 'records' (default) :param require_limit: Whether to require a LIMIT clause (default True) """ # Operator UI color (light blue for data operations) ui_color = "#87CEEB" # Templatable fields template_fields = ["sql", "database"] @apply_defaults def __init__( self, sql: str, database: str, max_limit: int = 1000, default_limit: int = 100, dangerous_patterns: Optional[List[Tuple[str, str]]] = None, return_format: str = "records", require_limit: bool = True, *args, **kwargs, ): super(SafeSQLExecutorOperator, self).__init__(*args, **kwargs) self.sql = sql self.database = database self.max_limit = max_limit self.default_limit = default_limit self.dangerous_patterns = dangerous_patterns self.return_format = return_format self.require_limit = require_limit
[docs] def execute(self, context): """Execute the SQL query with validation.""" import awswrangler as wr from util.bedrock.safe_sql import enforce_limit, validate_sql # Layer 3: Validate SQL is_valid, error_msg = validate_sql( self.sql, dangerous_patterns=self.dangerous_patterns, require_limit=self.require_limit, ) if not is_valid: self.log.error(f"SQL validation failed: {error_msg}") raise ValueError(f"SQL validation failed: {error_msg}") # Layer 4: Enforce limit safe_sql = enforce_limit( self.sql, default_limit=self.default_limit, max_limit=self.max_limit, ) self.log.info(f"Executing safe SQL: {safe_sql[:200]}...") # Execute query try: df = wr.athena.read_sql_query(safe_sql, database=self.database) self.log.info(f"Query returned {len(df)} rows") except Exception as e: self.log.error(f"Query execution failed: {str(e)}") raise # Return in requested format if self.return_format == "dataframe": return df elif self.return_format == "dict": return df.to_dict() else: # records (default) return df.to_dict("records")