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.
- tree_sitter_analyzer/__init__.py +132 -0
- tree_sitter_analyzer/__main__.py +11 -0
- tree_sitter_analyzer/api.py +853 -0
- tree_sitter_analyzer/cli/__init__.py +39 -0
- tree_sitter_analyzer/cli/__main__.py +12 -0
- tree_sitter_analyzer/cli/argument_validator.py +89 -0
- tree_sitter_analyzer/cli/commands/__init__.py +26 -0
- tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
- tree_sitter_analyzer/cli/commands/base_command.py +181 -0
- tree_sitter_analyzer/cli/commands/default_command.py +18 -0
- tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
- tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
- tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
- tree_sitter_analyzer/cli/commands/query_command.py +109 -0
- tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
- tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
- tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
- tree_sitter_analyzer/cli/commands/table_command.py +414 -0
- tree_sitter_analyzer/cli/info_commands.py +124 -0
- tree_sitter_analyzer/cli_main.py +472 -0
- tree_sitter_analyzer/constants.py +85 -0
- tree_sitter_analyzer/core/__init__.py +15 -0
- tree_sitter_analyzer/core/analysis_engine.py +580 -0
- tree_sitter_analyzer/core/cache_service.py +333 -0
- tree_sitter_analyzer/core/engine.py +585 -0
- tree_sitter_analyzer/core/parser.py +293 -0
- tree_sitter_analyzer/core/query.py +605 -0
- tree_sitter_analyzer/core/query_filter.py +200 -0
- tree_sitter_analyzer/core/query_service.py +340 -0
- tree_sitter_analyzer/encoding_utils.py +530 -0
- tree_sitter_analyzer/exceptions.py +747 -0
- tree_sitter_analyzer/file_handler.py +246 -0
- tree_sitter_analyzer/formatters/__init__.py +1 -0
- tree_sitter_analyzer/formatters/base_formatter.py +201 -0
- tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
- tree_sitter_analyzer/formatters/formatter_config.py +197 -0
- tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
- tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
- tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
- tree_sitter_analyzer/formatters/go_formatter.py +368 -0
- tree_sitter_analyzer/formatters/html_formatter.py +498 -0
- tree_sitter_analyzer/formatters/java_formatter.py +423 -0
- tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
- tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
- tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
- tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
- tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
- tree_sitter_analyzer/formatters/php_formatter.py +301 -0
- tree_sitter_analyzer/formatters/python_formatter.py +830 -0
- tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
- tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
- tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
- tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
- tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
- tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
- tree_sitter_analyzer/interfaces/__init__.py +9 -0
- tree_sitter_analyzer/interfaces/cli.py +535 -0
- tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
- tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
- tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
- tree_sitter_analyzer/language_detector.py +553 -0
- tree_sitter_analyzer/language_loader.py +271 -0
- tree_sitter_analyzer/languages/__init__.py +10 -0
- tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
- tree_sitter_analyzer/languages/css_plugin.py +449 -0
- tree_sitter_analyzer/languages/go_plugin.py +836 -0
- tree_sitter_analyzer/languages/html_plugin.py +496 -0
- tree_sitter_analyzer/languages/java_plugin.py +1299 -0
- tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
- tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
- tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
- tree_sitter_analyzer/languages/php_plugin.py +862 -0
- tree_sitter_analyzer/languages/python_plugin.py +1636 -0
- tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
- tree_sitter_analyzer/languages/rust_plugin.py +673 -0
- tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
- tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
- tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
- tree_sitter_analyzer/legacy_table_formatter.py +860 -0
- tree_sitter_analyzer/mcp/__init__.py +34 -0
- tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
- tree_sitter_analyzer/mcp/server.py +869 -0
- tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
- tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
- tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
- tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
- tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
- tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
- tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
- tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
- tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
- tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
- tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
- tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
- tree_sitter_analyzer/models.py +840 -0
- tree_sitter_analyzer/mypy_current_errors.txt +2 -0
- tree_sitter_analyzer/output_manager.py +255 -0
- tree_sitter_analyzer/platform_compat/__init__.py +3 -0
- tree_sitter_analyzer/platform_compat/adapter.py +324 -0
- tree_sitter_analyzer/platform_compat/compare.py +224 -0
- tree_sitter_analyzer/platform_compat/detector.py +67 -0
- tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
- tree_sitter_analyzer/platform_compat/profiles.py +217 -0
- tree_sitter_analyzer/platform_compat/record.py +55 -0
- tree_sitter_analyzer/platform_compat/recorder.py +155 -0
- tree_sitter_analyzer/platform_compat/report.py +92 -0
- tree_sitter_analyzer/plugins/__init__.py +280 -0
- tree_sitter_analyzer/plugins/base.py +647 -0
- tree_sitter_analyzer/plugins/manager.py +384 -0
- tree_sitter_analyzer/project_detector.py +328 -0
- tree_sitter_analyzer/queries/__init__.py +27 -0
- tree_sitter_analyzer/queries/csharp.py +216 -0
- tree_sitter_analyzer/queries/css.py +615 -0
- tree_sitter_analyzer/queries/go.py +275 -0
- tree_sitter_analyzer/queries/html.py +543 -0
- tree_sitter_analyzer/queries/java.py +402 -0
- tree_sitter_analyzer/queries/javascript.py +724 -0
- tree_sitter_analyzer/queries/kotlin.py +192 -0
- tree_sitter_analyzer/queries/markdown.py +258 -0
- tree_sitter_analyzer/queries/php.py +95 -0
- tree_sitter_analyzer/queries/python.py +859 -0
- tree_sitter_analyzer/queries/ruby.py +92 -0
- tree_sitter_analyzer/queries/rust.py +223 -0
- tree_sitter_analyzer/queries/sql.py +555 -0
- tree_sitter_analyzer/queries/typescript.py +871 -0
- tree_sitter_analyzer/queries/yaml.py +236 -0
- tree_sitter_analyzer/query_loader.py +272 -0
- tree_sitter_analyzer/security/__init__.py +22 -0
- tree_sitter_analyzer/security/boundary_manager.py +277 -0
- tree_sitter_analyzer/security/regex_checker.py +297 -0
- tree_sitter_analyzer/security/validator.py +599 -0
- tree_sitter_analyzer/table_formatter.py +782 -0
- tree_sitter_analyzer/utils/__init__.py +53 -0
- tree_sitter_analyzer/utils/logging.py +433 -0
- tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
- 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}
|