tree-sitter-analyzer 1.9.17.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. tree_sitter_analyzer/__init__.py +132 -0
  2. tree_sitter_analyzer/__main__.py +11 -0
  3. tree_sitter_analyzer/api.py +853 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +12 -0
  6. tree_sitter_analyzer/cli/argument_validator.py +89 -0
  7. tree_sitter_analyzer/cli/commands/__init__.py +26 -0
  8. tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
  9. tree_sitter_analyzer/cli/commands/base_command.py +181 -0
  10. tree_sitter_analyzer/cli/commands/default_command.py +18 -0
  11. tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
  12. tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
  13. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
  14. tree_sitter_analyzer/cli/commands/query_command.py +109 -0
  15. tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
  16. tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
  17. tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
  18. tree_sitter_analyzer/cli/commands/table_command.py +414 -0
  19. tree_sitter_analyzer/cli/info_commands.py +124 -0
  20. tree_sitter_analyzer/cli_main.py +472 -0
  21. tree_sitter_analyzer/constants.py +85 -0
  22. tree_sitter_analyzer/core/__init__.py +15 -0
  23. tree_sitter_analyzer/core/analysis_engine.py +580 -0
  24. tree_sitter_analyzer/core/cache_service.py +333 -0
  25. tree_sitter_analyzer/core/engine.py +585 -0
  26. tree_sitter_analyzer/core/parser.py +293 -0
  27. tree_sitter_analyzer/core/query.py +605 -0
  28. tree_sitter_analyzer/core/query_filter.py +200 -0
  29. tree_sitter_analyzer/core/query_service.py +340 -0
  30. tree_sitter_analyzer/encoding_utils.py +530 -0
  31. tree_sitter_analyzer/exceptions.py +747 -0
  32. tree_sitter_analyzer/file_handler.py +246 -0
  33. tree_sitter_analyzer/formatters/__init__.py +1 -0
  34. tree_sitter_analyzer/formatters/base_formatter.py +201 -0
  35. tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
  36. tree_sitter_analyzer/formatters/formatter_config.py +197 -0
  37. tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
  38. tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
  39. tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
  40. tree_sitter_analyzer/formatters/go_formatter.py +368 -0
  41. tree_sitter_analyzer/formatters/html_formatter.py +498 -0
  42. tree_sitter_analyzer/formatters/java_formatter.py +423 -0
  43. tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
  44. tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
  45. tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
  46. tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
  47. tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
  48. tree_sitter_analyzer/formatters/php_formatter.py +301 -0
  49. tree_sitter_analyzer/formatters/python_formatter.py +830 -0
  50. tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
  51. tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
  52. tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
  53. tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
  54. tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
  55. tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
  56. tree_sitter_analyzer/interfaces/__init__.py +9 -0
  57. tree_sitter_analyzer/interfaces/cli.py +535 -0
  58. tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
  59. tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
  60. tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
  61. tree_sitter_analyzer/language_detector.py +553 -0
  62. tree_sitter_analyzer/language_loader.py +271 -0
  63. tree_sitter_analyzer/languages/__init__.py +10 -0
  64. tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
  65. tree_sitter_analyzer/languages/css_plugin.py +449 -0
  66. tree_sitter_analyzer/languages/go_plugin.py +836 -0
  67. tree_sitter_analyzer/languages/html_plugin.py +496 -0
  68. tree_sitter_analyzer/languages/java_plugin.py +1299 -0
  69. tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
  70. tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
  71. tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
  72. tree_sitter_analyzer/languages/php_plugin.py +862 -0
  73. tree_sitter_analyzer/languages/python_plugin.py +1636 -0
  74. tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
  75. tree_sitter_analyzer/languages/rust_plugin.py +673 -0
  76. tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
  77. tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
  78. tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
  79. tree_sitter_analyzer/legacy_table_formatter.py +860 -0
  80. tree_sitter_analyzer/mcp/__init__.py +34 -0
  81. tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
  82. tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
  83. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
  84. tree_sitter_analyzer/mcp/server.py +869 -0
  85. tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
  86. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
  87. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
  88. tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
  89. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
  90. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
  91. tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
  92. tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
  93. tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
  94. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
  95. tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
  96. tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
  97. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
  98. tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
  99. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
  100. tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
  101. tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
  102. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
  103. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
  104. tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
  105. tree_sitter_analyzer/models.py +840 -0
  106. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  107. tree_sitter_analyzer/output_manager.py +255 -0
  108. tree_sitter_analyzer/platform_compat/__init__.py +3 -0
  109. tree_sitter_analyzer/platform_compat/adapter.py +324 -0
  110. tree_sitter_analyzer/platform_compat/compare.py +224 -0
  111. tree_sitter_analyzer/platform_compat/detector.py +67 -0
  112. tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
  113. tree_sitter_analyzer/platform_compat/profiles.py +217 -0
  114. tree_sitter_analyzer/platform_compat/record.py +55 -0
  115. tree_sitter_analyzer/platform_compat/recorder.py +155 -0
  116. tree_sitter_analyzer/platform_compat/report.py +92 -0
  117. tree_sitter_analyzer/plugins/__init__.py +280 -0
  118. tree_sitter_analyzer/plugins/base.py +647 -0
  119. tree_sitter_analyzer/plugins/manager.py +384 -0
  120. tree_sitter_analyzer/project_detector.py +328 -0
  121. tree_sitter_analyzer/queries/__init__.py +27 -0
  122. tree_sitter_analyzer/queries/csharp.py +216 -0
  123. tree_sitter_analyzer/queries/css.py +615 -0
  124. tree_sitter_analyzer/queries/go.py +275 -0
  125. tree_sitter_analyzer/queries/html.py +543 -0
  126. tree_sitter_analyzer/queries/java.py +402 -0
  127. tree_sitter_analyzer/queries/javascript.py +724 -0
  128. tree_sitter_analyzer/queries/kotlin.py +192 -0
  129. tree_sitter_analyzer/queries/markdown.py +258 -0
  130. tree_sitter_analyzer/queries/php.py +95 -0
  131. tree_sitter_analyzer/queries/python.py +859 -0
  132. tree_sitter_analyzer/queries/ruby.py +92 -0
  133. tree_sitter_analyzer/queries/rust.py +223 -0
  134. tree_sitter_analyzer/queries/sql.py +555 -0
  135. tree_sitter_analyzer/queries/typescript.py +871 -0
  136. tree_sitter_analyzer/queries/yaml.py +236 -0
  137. tree_sitter_analyzer/query_loader.py +272 -0
  138. tree_sitter_analyzer/security/__init__.py +22 -0
  139. tree_sitter_analyzer/security/boundary_manager.py +277 -0
  140. tree_sitter_analyzer/security/regex_checker.py +297 -0
  141. tree_sitter_analyzer/security/validator.py +599 -0
  142. tree_sitter_analyzer/table_formatter.py +782 -0
  143. tree_sitter_analyzer/utils/__init__.py +53 -0
  144. tree_sitter_analyzer/utils/logging.py +433 -0
  145. tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
  146. tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
  147. tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
  148. tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
  149. tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
@@ -0,0 +1,689 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SQL Formatter Wrapper
4
+
5
+ Wraps SQL-specific formatters to conform to the BaseFormatter interface
6
+ for integration with the CLI and MCP tools.
7
+ """
8
+
9
+ from typing import Any
10
+
11
+ from ..models import SQLElement
12
+ from .base_formatter import BaseFormatter
13
+ from .sql_formatters import SQLCompactFormatter, SQLCSVFormatter, SQLFullFormatter
14
+
15
+
16
+ class SQLFormatterWrapper(BaseFormatter):
17
+ """
18
+ Wrapper for SQL-specific formatters to conform to BaseFormatter interface.
19
+
20
+ This class bridges the gap between the generic BaseFormatter interface
21
+ and the SQL-specific formatters, enabling seamless integration with
22
+ the existing CLI and MCP infrastructure.
23
+ """
24
+
25
+ def __init__(self) -> None:
26
+ """Initialize the SQL formatter wrapper."""
27
+ super().__init__()
28
+ self._formatters = {
29
+ "full": SQLFullFormatter(),
30
+ "compact": SQLCompactFormatter(),
31
+ "csv": SQLCSVFormatter(),
32
+ }
33
+
34
+ def format_table(self, data: dict[str, Any], table_type: str = "full") -> str:
35
+ """
36
+ Format analysis data as table using SQL-specific formatters.
37
+
38
+ Args:
39
+ data: Analysis data containing SQL elements
40
+ table_type: Type of table format (full, compact, csv)
41
+
42
+ Returns:
43
+ Formatted table string
44
+
45
+ Raises:
46
+ ValueError: If table_type is not supported
47
+ """
48
+ if table_type not in self._formatters:
49
+ raise ValueError(
50
+ f"Unsupported table type: {table_type}. Supported types: {list(self._formatters.keys())}"
51
+ )
52
+
53
+ # Convert generic analysis data to SQL elements
54
+ sql_elements = self._convert_to_sql_elements(data)
55
+
56
+ # Get the appropriate formatter
57
+ formatter = self._formatters[table_type]
58
+
59
+ # Format using SQL-specific formatter
60
+ file_path = data.get("file_path", "unknown.sql")
61
+ return formatter.format_elements(sql_elements, file_path)
62
+
63
+ def format_analysis_result(
64
+ self, analysis_result: Any, table_type: str = "full"
65
+ ) -> str:
66
+ """Format AnalysisResult directly for SQL files - prevents degradation"""
67
+ # Convert AnalysisResult to SQL elements directly
68
+ sql_elements = self._convert_analysis_result_to_sql_elements(analysis_result)
69
+
70
+ # Get the appropriate formatter
71
+ if table_type not in self._formatters:
72
+ table_type = "full" # Default fallback
73
+
74
+ formatter = self._formatters[table_type]
75
+ return formatter.format_elements(sql_elements, analysis_result.file_path)
76
+
77
+ def _convert_analysis_result_to_sql_elements(
78
+ self, analysis_result: Any
79
+ ) -> list[SQLElement]:
80
+ """Convert AnalysisResult directly to SQL elements"""
81
+ from ..constants import (
82
+ ELEMENT_TYPE_SQL_FUNCTION,
83
+ ELEMENT_TYPE_SQL_INDEX,
84
+ ELEMENT_TYPE_SQL_PROCEDURE,
85
+ ELEMENT_TYPE_SQL_TABLE,
86
+ ELEMENT_TYPE_SQL_TRIGGER,
87
+ ELEMENT_TYPE_SQL_VIEW,
88
+ get_element_type,
89
+ )
90
+ from ..models import (
91
+ SQLFunction,
92
+ SQLIndex,
93
+ SQLProcedure,
94
+ SQLTable,
95
+ SQLTrigger,
96
+ SQLView,
97
+ )
98
+
99
+ sql_elements = []
100
+
101
+ for element in analysis_result.elements:
102
+ # Check if element is already a SQL element
103
+ if isinstance(element, SQLElement):
104
+ sql_elements.append(element)
105
+ continue
106
+
107
+ element_type = get_element_type(element)
108
+ name = getattr(element, "name", "unknown")
109
+ start_line = getattr(element, "start_line", 0)
110
+ end_line = getattr(element, "end_line", 0)
111
+ raw_text = getattr(element, "raw_text", "")
112
+ language = getattr(element, "language", "sql")
113
+
114
+ # Create appropriate SQL element based on type with enhanced information extraction
115
+ if element_type == ELEMENT_TYPE_SQL_TABLE:
116
+ # Extract table information from raw_text
117
+ columns_info = self._extract_table_columns(raw_text, name)
118
+ sql_element = SQLTable(
119
+ name=name,
120
+ start_line=start_line,
121
+ end_line=end_line,
122
+ raw_text=raw_text,
123
+ language=language,
124
+ columns=columns_info.get("columns", []),
125
+ constraints=columns_info.get("constraints", []),
126
+ dependencies=getattr(element, "dependencies", []),
127
+ )
128
+ elif element_type == ELEMENT_TYPE_SQL_VIEW:
129
+ # Extract view information from raw_text
130
+ view_info = self._extract_view_info(raw_text, name)
131
+ sql_element = SQLView(
132
+ name=name,
133
+ start_line=start_line,
134
+ end_line=end_line,
135
+ raw_text=raw_text,
136
+ language=language,
137
+ source_tables=view_info.get("source_tables", []),
138
+ columns=view_info.get("columns", []),
139
+ dependencies=view_info.get("source_tables", []),
140
+ )
141
+ elif element_type == ELEMENT_TYPE_SQL_PROCEDURE:
142
+ # Extract procedure information from raw_text
143
+ proc_info = self._extract_procedure_info(raw_text, name)
144
+ sql_element = SQLProcedure(
145
+ name=name,
146
+ start_line=start_line,
147
+ end_line=end_line,
148
+ raw_text=raw_text,
149
+ language=language,
150
+ parameters=proc_info.get("parameters", []),
151
+ dependencies=proc_info.get("dependencies", []),
152
+ )
153
+ elif element_type == ELEMENT_TYPE_SQL_FUNCTION:
154
+ # Extract function information from raw_text
155
+ func_info = self._extract_function_info(raw_text, name)
156
+ sql_element = SQLFunction(
157
+ name=name,
158
+ start_line=start_line,
159
+ end_line=end_line,
160
+ raw_text=raw_text,
161
+ language=language,
162
+ parameters=func_info.get("parameters", []),
163
+ return_type=func_info.get("return_type", ""),
164
+ dependencies=func_info.get("dependencies", []),
165
+ )
166
+ elif element_type == ELEMENT_TYPE_SQL_TRIGGER:
167
+ # Extract trigger information from raw_text
168
+ trigger_info = self._extract_trigger_info(raw_text, name)
169
+ sql_element = SQLTrigger(
170
+ name=name,
171
+ start_line=start_line,
172
+ end_line=end_line,
173
+ raw_text=raw_text,
174
+ language=language,
175
+ trigger_timing=trigger_info.get("timing", ""),
176
+ trigger_event=trigger_info.get("event", ""),
177
+ table_name=trigger_info.get("table_name", ""),
178
+ dependencies=trigger_info.get("dependencies", []),
179
+ )
180
+ elif element_type == ELEMENT_TYPE_SQL_INDEX:
181
+ # Extract index information from raw_text
182
+ index_info = self._extract_index_info(raw_text, name)
183
+ sql_element = SQLIndex(
184
+ name=name,
185
+ start_line=start_line,
186
+ end_line=end_line,
187
+ raw_text=raw_text,
188
+ language=language,
189
+ table_name=index_info.get("table_name", ""),
190
+ indexed_columns=index_info.get("columns", []),
191
+ is_unique=index_info.get("is_unique", False),
192
+ dependencies=(
193
+ [index_info.get("table_name", "")]
194
+ if index_info.get("table_name")
195
+ else []
196
+ ),
197
+ )
198
+ else:
199
+ # Skip non-SQL elements
200
+ continue
201
+
202
+ sql_elements.append(sql_element)
203
+
204
+ return sql_elements
205
+
206
+ def _convert_to_sql_elements(self, data: dict[str, Any]) -> list[SQLElement]:
207
+ """
208
+ Convert generic analysis data to SQL elements.
209
+
210
+ Args:
211
+ data: Analysis data from the analysis engine
212
+
213
+ Returns:
214
+ List of SQL elements
215
+ """
216
+ sql_elements = []
217
+ # Check both 'elements' and 'methods' for SQL elements
218
+ elements = data.get("elements", [])
219
+ methods = data.get("methods", [])
220
+
221
+ # Combine elements and methods for processing
222
+ all_elements = elements + methods
223
+
224
+ for element in all_elements:
225
+ # Check if element is already a SQL element
226
+ if isinstance(element, SQLElement):
227
+ sql_elements.append(element)
228
+ continue
229
+
230
+ # For non-SQL elements, convert them but preserve any existing metadata
231
+ element_dict = (
232
+ element if isinstance(element, dict) else self._element_to_dict(element)
233
+ )
234
+ sql_element = self._create_sql_element_from_dict(element_dict)
235
+
236
+ if sql_element:
237
+ sql_elements.append(sql_element)
238
+
239
+ return sql_elements
240
+
241
+ def _element_to_dict(self, element: Any) -> dict[str, Any]:
242
+ """
243
+ Convert element object to dictionary.
244
+
245
+ Args:
246
+ element: Element object from analysis
247
+
248
+ Returns:
249
+ Dictionary representation of element
250
+ """
251
+ return {
252
+ "name": getattr(element, "name", str(element)),
253
+ "type": getattr(element, "type", getattr(element, "sql_type", "unknown")),
254
+ "start_line": getattr(element, "start_line", 0),
255
+ "end_line": getattr(element, "end_line", 0),
256
+ "raw_text": getattr(element, "raw_text", ""),
257
+ "language": getattr(element, "language", "sql"),
258
+ }
259
+
260
+ def _create_sql_element_from_dict(
261
+ self, element_dict: dict[str, Any]
262
+ ) -> SQLElement | None:
263
+ """
264
+ Create SQL element from dictionary data.
265
+
266
+ Args:
267
+ element_dict: Dictionary containing element data
268
+
269
+ Returns:
270
+ SQL element or None if conversion fails
271
+ """
272
+ try:
273
+ from ..models import (
274
+ SQLFunction,
275
+ SQLIndex,
276
+ SQLProcedure,
277
+ SQLTable,
278
+ SQLTrigger,
279
+ SQLView,
280
+ )
281
+
282
+ element_type = element_dict.get("type", "").lower()
283
+ name = element_dict.get("name", "unknown")
284
+ start_line = element_dict.get("start_line", 0)
285
+ end_line = element_dict.get("end_line", 0)
286
+ raw_text = element_dict.get("raw_text", "")
287
+ language = element_dict.get("language", "sql")
288
+
289
+ # Create appropriate SQL element based on type
290
+ if element_type in ["table", "create_table"]:
291
+ return SQLTable(
292
+ name=name,
293
+ start_line=start_line,
294
+ end_line=end_line,
295
+ raw_text=raw_text,
296
+ language=language,
297
+ columns=[], # Will be populated by enhanced extraction
298
+ constraints=[],
299
+ dependencies=[],
300
+ )
301
+ elif element_type in ["view", "create_view"]:
302
+ return SQLView(
303
+ name=name,
304
+ start_line=start_line,
305
+ end_line=end_line,
306
+ raw_text=raw_text,
307
+ language=language,
308
+ source_tables=[],
309
+ columns=[],
310
+ dependencies=[],
311
+ )
312
+ elif element_type in ["procedure", "create_procedure"]:
313
+ return SQLProcedure(
314
+ name=name,
315
+ start_line=start_line,
316
+ end_line=end_line,
317
+ raw_text=raw_text,
318
+ language=language,
319
+ parameters=[],
320
+ dependencies=[],
321
+ )
322
+ elif element_type in ["function", "create_function"]:
323
+ return SQLFunction(
324
+ name=name,
325
+ start_line=start_line,
326
+ end_line=end_line,
327
+ raw_text=raw_text,
328
+ language=language,
329
+ parameters=[],
330
+ return_type="",
331
+ dependencies=[],
332
+ )
333
+ elif element_type in ["trigger", "create_trigger"]:
334
+ return SQLTrigger(
335
+ name=name,
336
+ start_line=start_line,
337
+ end_line=end_line,
338
+ raw_text=raw_text,
339
+ language=language,
340
+ trigger_timing="",
341
+ trigger_event="",
342
+ table_name="",
343
+ dependencies=[],
344
+ )
345
+ elif element_type in ["index", "create_index"]:
346
+ return SQLIndex(
347
+ name=name,
348
+ start_line=start_line,
349
+ end_line=end_line,
350
+ raw_text=raw_text,
351
+ language=language,
352
+ table_name="",
353
+ indexed_columns=[],
354
+ is_unique=False,
355
+ dependencies=[],
356
+ )
357
+ else:
358
+ # Create a generic SQL element for unknown types
359
+ return SQLTable( # Use SQLTable as fallback
360
+ name=name,
361
+ start_line=start_line,
362
+ end_line=end_line,
363
+ raw_text=raw_text,
364
+ language=language,
365
+ columns=[],
366
+ constraints=[],
367
+ dependencies=[],
368
+ )
369
+
370
+ except Exception as e:
371
+ # Log error but don't fail the entire formatting process
372
+ import logging
373
+
374
+ logger = logging.getLogger(__name__)
375
+ logger.warning(f"Failed to create SQL element from dict: {e}")
376
+ return None
377
+
378
+ def format_elements(self, elements: list[Any], format_type: str = "full") -> str:
379
+ """
380
+ Format elements using SQL-specific formatters.
381
+
382
+ Args:
383
+ elements: List of elements to format
384
+ format_type: Format type (full, compact, csv)
385
+
386
+ Returns:
387
+ Formatted string
388
+ """
389
+ # Convert to SQL elements if needed
390
+ sql_elements = []
391
+ for element in elements:
392
+ if isinstance(element, SQLElement):
393
+ sql_elements.append(element)
394
+ else:
395
+ element_dict = (
396
+ element
397
+ if isinstance(element, dict)
398
+ else self._element_to_dict(element)
399
+ )
400
+ sql_element = self._create_sql_element_from_dict(element_dict)
401
+ if sql_element:
402
+ sql_elements.append(sql_element)
403
+
404
+ # Get the appropriate formatter
405
+ if format_type not in self._formatters:
406
+ format_type = "full" # Default fallback
407
+
408
+ formatter = self._formatters[format_type]
409
+ return formatter.format_elements(sql_elements, "analysis.sql")
410
+
411
+ def supports_language(self, language: str) -> bool:
412
+ """
413
+ Check if this formatter supports the given language.
414
+
415
+ Args:
416
+ language: Programming language name
417
+
418
+ Returns:
419
+ True if language is supported
420
+ """
421
+ return language.lower() == "sql"
422
+
423
+ def format_summary(self, analysis_result: dict[str, Any]) -> str:
424
+ """
425
+ Format summary output for SQL analysis.
426
+
427
+ Args:
428
+ analysis_result: Analysis result data
429
+
430
+ Returns:
431
+ Formatted summary string
432
+ """
433
+ # Convert to SQL elements and use compact formatter for summary
434
+ return self.format_table(analysis_result, "compact")
435
+
436
+ def format_structure(self, analysis_result: dict[str, Any]) -> str:
437
+ """
438
+ Format structure analysis output for SQL.
439
+
440
+ Args:
441
+ analysis_result: Analysis result data
442
+
443
+ Returns:
444
+ Formatted structure string
445
+ """
446
+ # Use full formatter for detailed structure
447
+ return self.format_table(analysis_result, "full")
448
+
449
+ def format_advanced(
450
+ self, analysis_result: dict[str, Any], output_format: str = "json"
451
+ ) -> str:
452
+ """
453
+ Format advanced analysis output for SQL.
454
+
455
+ Args:
456
+ analysis_result: Analysis result data
457
+ output_format: Output format (json, table, etc.)
458
+
459
+ Returns:
460
+ Formatted advanced analysis string
461
+ """
462
+ if output_format == "json":
463
+ import json
464
+
465
+ return json.dumps(analysis_result, indent=2, ensure_ascii=False)
466
+ else:
467
+ # Default to full table format for other formats
468
+ return self.format_table(analysis_result, "full")
469
+
470
+ def _extract_table_columns(self, raw_text: str, table_name: str) -> dict:
471
+ """Extract column information from CREATE TABLE statement"""
472
+
473
+ # Enhanced column extraction for better accuracy
474
+ columns = []
475
+ constraints = []
476
+
477
+ # Extract column definitions more precisely
478
+ # Look for lines that define columns (not keywords)
479
+ lines = raw_text.split("\n")
480
+ in_table_def = False
481
+
482
+ for line in lines:
483
+ line = line.strip()
484
+ if "CREATE TABLE" in line.upper():
485
+ in_table_def = True
486
+ continue
487
+ if in_table_def and line == ");":
488
+ break
489
+ if in_table_def and line and not line.startswith("--"):
490
+ # Extract column name (first word that's not a keyword)
491
+ words = line.split()
492
+ if words and words[0].upper() not in [
493
+ "PRIMARY",
494
+ "FOREIGN",
495
+ "KEY",
496
+ "CONSTRAINT",
497
+ "INDEX",
498
+ "UNIQUE",
499
+ ]:
500
+ col_name = words[0].rstrip(",")
501
+ if col_name and col_name.upper() not in [
502
+ "PRIMARY",
503
+ "FOREIGN",
504
+ "KEY",
505
+ ]:
506
+ columns.append(col_name)
507
+
508
+ # Extract constraints
509
+ if "PRIMARY KEY" in raw_text.upper():
510
+ constraints.append("PRIMARY KEY")
511
+ if "FOREIGN KEY" in raw_text.upper():
512
+ constraints.append("FOREIGN KEY")
513
+ if "UNIQUE" in raw_text.upper():
514
+ constraints.append("UNIQUE")
515
+ if "NOT NULL" in raw_text.upper():
516
+ constraints.append("NOT NULL")
517
+
518
+ return {"columns": columns, "constraints": constraints}
519
+
520
+ def _extract_view_info(self, raw_text: str, view_name: str) -> dict:
521
+ """Extract view information from CREATE VIEW statement"""
522
+ import re
523
+
524
+ source_tables = []
525
+
526
+ # Extract table names from FROM and JOIN clauses
527
+ from_pattern = r"FROM\s+(\w+)"
528
+ join_pattern = r"JOIN\s+(\w+)"
529
+
530
+ from_matches = re.findall(from_pattern, raw_text, re.IGNORECASE)
531
+ join_matches = re.findall(join_pattern, raw_text, re.IGNORECASE)
532
+
533
+ source_tables.extend(from_matches)
534
+ source_tables.extend(join_matches)
535
+
536
+ return {"source_tables": sorted(set(source_tables)), "columns": []}
537
+
538
+ def _extract_procedure_info(self, raw_text: str, proc_name: str) -> dict:
539
+ """Extract procedure information from CREATE PROCEDURE statement"""
540
+ import re
541
+
542
+ parameters = []
543
+ dependencies = []
544
+
545
+ # Extract parameters from procedure definition more precisely
546
+ # Look for the parameter list in parentheses after procedure name
547
+ param_section_pattern = (
548
+ rf"CREATE\s+PROCEDURE\s+{re.escape(proc_name)}\s*\(([^)]*)\)"
549
+ )
550
+ param_match = re.search(
551
+ param_section_pattern, raw_text, re.IGNORECASE | re.DOTALL
552
+ )
553
+
554
+ if param_match:
555
+ param_text = param_match.group(1).strip()
556
+ if param_text:
557
+ # Split by comma and process each parameter
558
+ param_parts = param_text.split(",")
559
+ for param in param_parts:
560
+ param = param.strip()
561
+ if param:
562
+ # Extract direction, name, and type
563
+ param_pattern = r"(IN|OUT|INOUT)?\s*(\w+)\s+(\w+(?:\([^)]+\))?)"
564
+ match = re.match(param_pattern, param, re.IGNORECASE)
565
+ if match:
566
+ direction = match.group(1) if match.group(1) else "IN"
567
+ param_name = match.group(2)
568
+ param_type = match.group(3)
569
+ parameters.append(f"{direction} {param_name} {param_type}")
570
+
571
+ # Extract table dependencies
572
+ table_pattern = r"FROM\s+(\w+)|UPDATE\s+(\w+)|INSERT\s+INTO\s+(\w+)"
573
+ table_matches = re.findall(table_pattern, raw_text, re.IGNORECASE)
574
+
575
+ for match in table_matches:
576
+ for table in match:
577
+ if table and table not in dependencies:
578
+ dependencies.append(table)
579
+
580
+ return {"parameters": parameters, "dependencies": dependencies}
581
+
582
+ def _extract_function_info(self, raw_text: str, func_name: str) -> dict:
583
+ """Extract function information from CREATE FUNCTION statement"""
584
+ import re
585
+
586
+ parameters = []
587
+ return_type = ""
588
+ dependencies = []
589
+
590
+ # Extract return type
591
+ return_pattern = r"RETURNS\s+(\w+(?:\([^)]+\))?)"
592
+ return_match = re.search(return_pattern, raw_text, re.IGNORECASE)
593
+ if return_match:
594
+ return_type = return_match.group(1)
595
+
596
+ # Extract parameters
597
+ param_pattern = r"(\w+)\s+(\w+(?:\([^)]+\))?)"
598
+ matches = re.findall(param_pattern, raw_text, re.IGNORECASE)
599
+
600
+ for match in matches:
601
+ param_name = match[0]
602
+ param_type = match[1]
603
+
604
+ if param_name.upper() not in [
605
+ "CREATE",
606
+ "FUNCTION",
607
+ "RETURNS",
608
+ "READS",
609
+ "SQL",
610
+ "DATA",
611
+ "DETERMINISTIC",
612
+ "BEGIN",
613
+ "END",
614
+ "DECLARE",
615
+ "SELECT",
616
+ "FROM",
617
+ "WHERE",
618
+ "RETURN",
619
+ ]:
620
+ parameters.append(f"{param_name} {param_type}")
621
+
622
+ # Extract table dependencies
623
+ table_pattern = r"FROM\s+(\w+)"
624
+ table_matches = re.findall(table_pattern, raw_text, re.IGNORECASE)
625
+ dependencies.extend(table_matches)
626
+
627
+ return {
628
+ "parameters": parameters,
629
+ "return_type": return_type,
630
+ "dependencies": sorted(set(dependencies)),
631
+ }
632
+
633
+ def _extract_trigger_info(self, raw_text: str, trigger_name: str) -> dict:
634
+ """Extract trigger information from CREATE TRIGGER statement"""
635
+ import re
636
+
637
+ timing = ""
638
+ event = ""
639
+ table_name = ""
640
+ dependencies = []
641
+
642
+ # Extract trigger timing and event
643
+ trigger_pattern = r"(BEFORE|AFTER)\s+(INSERT|UPDATE|DELETE)\s+ON\s+(\w+)"
644
+ match = re.search(trigger_pattern, raw_text, re.IGNORECASE)
645
+
646
+ if match:
647
+ timing = match.group(1)
648
+ event = match.group(2)
649
+ table_name = match.group(3)
650
+ dependencies.append(table_name)
651
+
652
+ # Extract additional table dependencies
653
+ table_pattern = r"FROM\s+(\w+)|UPDATE\s+(\w+)|INSERT\s+INTO\s+(\w+)"
654
+ table_matches = re.findall(table_pattern, raw_text, re.IGNORECASE)
655
+
656
+ for match in table_matches:
657
+ for table in match:
658
+ if table and table not in dependencies:
659
+ dependencies.append(table)
660
+
661
+ return {
662
+ "timing": timing,
663
+ "event": event,
664
+ "table_name": table_name,
665
+ "dependencies": dependencies,
666
+ }
667
+
668
+ def _extract_index_info(self, raw_text: str, index_name: str) -> dict:
669
+ """Extract index information from CREATE INDEX statement"""
670
+ import re
671
+
672
+ table_name = ""
673
+ columns = []
674
+ is_unique = False
675
+
676
+ # Check if it's a unique index
677
+ if "UNIQUE" in raw_text.upper():
678
+ is_unique = True
679
+
680
+ # Extract table name and columns
681
+ index_pattern = r"ON\s+(\w+)\s*\(([^)]+)\)"
682
+ match = re.search(index_pattern, raw_text, re.IGNORECASE)
683
+
684
+ if match:
685
+ table_name = match.group(1)
686
+ columns_str = match.group(2)
687
+ columns = [col.strip() for col in columns_str.split(",")]
688
+
689
+ return {"table_name": table_name, "columns": columns, "is_unique": is_unique}