File size: 7,777 Bytes
7814c1f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
"""
SQL Test Runner using Modal (SECONDARY SPONSOR)
Provides serverless SQL execution for testing queries
"""

import os
from typing import Dict, Any

# Try to import Modal, but don't fail if not available
try:
    import modal
    MODAL_AVAILABLE = True
except ImportError:
    MODAL_AVAILABLE = False
    print("⚠️ Modal not available. Install with: pip install modal")

# Try to import DuckDB for local fallback
try:
    import duckdb
    DUCKDB_AVAILABLE = True
except ImportError:
    DUCKDB_AVAILABLE = False
    print("⚠️ DuckDB not available for local testing fallback")


class SQLTestRunner:
    """
    Test runner for SQL queries
    Uses Modal for serverless execution, with DuckDB fallback
    """
    
    def __init__(self):
        self.modal_available = MODAL_AVAILABLE
        self.duckdb_available = DUCKDB_AVAILABLE
        
        # Initialize Modal app if available
        if self.modal_available:
            try:
                self.app = modal.App("codeflow-sql-tester")
                self._setup_modal()
            except Exception as e:
                print(f"⚠️ Modal setup failed: {e}")
                self.modal_available = False
    
    def _setup_modal(self):
        """Setup Modal function for SQL execution"""
        if not self.modal_available:
            return
        
        try:
            # Define Modal image with DuckDB
            image = modal.Image.debian_slim().pip_install("duckdb==0.9.2")
            
            # Define Modal function
            @self.app.function(image=image, timeout=60)
            def execute_sql_modal(sql: str, sample_data: dict = None) -> dict:
                """Execute SQL in Modal sandbox"""
                import duckdb
                
                try:
                    # Create in-memory DuckDB database
                    conn = duckdb.connect(':memory:')
                    
                    # If sample data provided, create tables
                    if sample_data:
                        for table_name, data in sample_data.items():
                            conn.execute(f"CREATE TABLE {table_name} AS SELECT * FROM ?", [data])
                    
                    # Execute the SQL
                    result = conn.execute(sql).fetchall()
                    columns = [desc[0] for desc in conn.description] if conn.description else []
                    
                    conn.close()
                    
                    return {
                        "success": True,
                        "rows": result[:100],  # Limit to 100 rows
                        "row_count": len(result),
                        "columns": columns,
                        "error": None
                    }
                    
                except Exception as e:
                    return {
                        "success": False,
                        "rows": [],
                        "row_count": 0,
                        "columns": [],
                        "error": str(e)
                    }
            
            self.execute_sql_modal = execute_sql_modal
            
        except Exception as e:
            print(f"⚠️ Modal function setup failed: {e}")
            self.modal_available = False
    
    def test_sql(self, sql: str, sample_data: Dict = None) -> Dict[str, Any]:
        """
        Test SQL query execution
        
        Args:
            sql: SQL query to test
            sample_data: Optional sample data as dict of table_name -> rows
            
        Returns:
            Dict with test results
        """
        
        # Try Modal first if available
        if self.modal_available:
            try:
                result = self.execute_sql_modal.remote(sql, sample_data)
                result["execution_method"] = "Modal (Serverless)"
                return result
            except Exception as e:
                print(f"⚠️ Modal execution failed, falling back to local: {e}")
        
        # Fallback to local DuckDB
        if self.duckdb_available:
            return self._test_sql_local(sql, sample_data)
        
        # No execution method available
        return {
            "success": False,
            "rows": [],
            "row_count": 0,
            "columns": [],
            "error": "No SQL execution method available. Install Modal or DuckDB.",
            "execution_method": "None"
        }
    
    def _test_sql_local(self, sql: str, sample_data: Dict = None) -> Dict[str, Any]:
        """Execute SQL locally using DuckDB"""
        try:
            conn = duckdb.connect(':memory:')
            
            # Create sample tables if provided
            if sample_data:
                for table_name, data in sample_data.items():
                    conn.execute(f"CREATE TABLE {table_name} AS SELECT * FROM ?", [data])
            
            # Execute SQL
            result = conn.execute(sql).fetchall()
            columns = [desc[0] for desc in conn.description] if conn.description else []
            
            conn.close()
            
            return {
                "success": True,
                "rows": result[:100],  # Limit to 100 rows
                "row_count": len(result),
                "columns": columns,
                "error": None,
                "execution_method": "DuckDB (Local)"
            }
            
        except Exception as e:
            return {
                "success": False,
                "rows": [],
                "row_count": 0,
                "columns": [],
                "error": str(e),
                "execution_method": "DuckDB (Local)"
            }
    
    def validate_syntax(self, sql: str) -> Dict[str, Any]:
        """
        Quick syntax validation without execution
        
        Args:
            sql: SQL query to validate
            
        Returns:
            Dict with validation results
        """
        if not self.duckdb_available:
            return {
                "valid": None,
                "error": "DuckDB not available for syntax validation"
            }
        
        try:
            conn = duckdb.connect(':memory:')
            # Try to prepare the query (doesn't execute, just validates syntax)
            conn.execute(f"EXPLAIN {sql}")
            conn.close()
            
            return {
                "valid": True,
                "error": None
            }
            
        except Exception as e:
            return {
                "valid": False,
                "error": str(e)
            }
    
    def get_execution_plan(self, sql: str) -> Dict[str, Any]:
        """
        Get query execution plan
        
        Args:
            sql: SQL query
            
        Returns:
            Dict with execution plan
        """
        if not self.duckdb_available:
            return {
                "success": False,
                "plan": None,
                "error": "DuckDB not available"
            }
        
        try:
            conn = duckdb.connect(':memory:')
            plan = conn.execute(f"EXPLAIN {sql}").fetchall()
            conn.close()
            
            return {
                "success": True,
                "plan": "\n".join([str(row[0]) for row in plan]),
                "error": None
            }
            
        except Exception as e:
            return {
                "success": False,
                "plan": None,
                "error": str(e)
            }


# Singleton instance
_test_runner = None

def get_test_runner() -> SQLTestRunner:
    """Get or create the global test runner instance"""
    global _test_runner
    if _test_runner is None:
        _test_runner = SQLTestRunner()
    return _test_runner