qodalis-cli-server-plugin-data-explorer-mysql 1.0.0b18__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.
- qodalis_cli_data_explorer_mysql/__init__.py +5 -0
- qodalis_cli_data_explorer_mysql/mysql_provider.py +146 -0
- qodalis_cli_server_plugin_data_explorer_mysql-1.0.0b18.dist-info/METADATA +21 -0
- qodalis_cli_server_plugin_data_explorer_mysql-1.0.0b18.dist-info/RECORD +5 -0
- qodalis_cli_server_plugin_data_explorer_mysql-1.0.0b18.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""MySQL-backed Data Explorer provider."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
import aiomysql
|
|
9
|
+
|
|
10
|
+
from qodalis_cli_server_abstractions import (
|
|
11
|
+
DataExplorerExecutionContext,
|
|
12
|
+
DataExplorerProviderOptions,
|
|
13
|
+
DataExplorerResult,
|
|
14
|
+
DataExplorerSchemaColumn,
|
|
15
|
+
DataExplorerSchemaResult,
|
|
16
|
+
DataExplorerSchemaTable,
|
|
17
|
+
IDataExplorerProvider,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MysqlDataExplorerProvider(IDataExplorerProvider):
|
|
22
|
+
"""Executes SQL queries against a MySQL database."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, connection_string: str) -> None:
|
|
25
|
+
parsed = urlparse(connection_string)
|
|
26
|
+
self._host = parsed.hostname or "localhost"
|
|
27
|
+
self._port = parsed.port or 3306
|
|
28
|
+
self._user = parsed.username or "root"
|
|
29
|
+
self._password = parsed.password or ""
|
|
30
|
+
self._db = parsed.path.lstrip("/") if parsed.path else None
|
|
31
|
+
|
|
32
|
+
async def execute_async(
|
|
33
|
+
self, context: DataExplorerExecutionContext
|
|
34
|
+
) -> DataExplorerResult:
|
|
35
|
+
"""Execute a SQL query against the MySQL database."""
|
|
36
|
+
start = time.monotonic()
|
|
37
|
+
conn: aiomysql.Connection | None = None
|
|
38
|
+
try:
|
|
39
|
+
conn = await aiomysql.connect(
|
|
40
|
+
host=self._host,
|
|
41
|
+
port=self._port,
|
|
42
|
+
user=self._user,
|
|
43
|
+
password=self._password,
|
|
44
|
+
db=self._db,
|
|
45
|
+
)
|
|
46
|
+
async with conn.cursor() as cursor:
|
|
47
|
+
await cursor.execute(context.query, context.parameters or None)
|
|
48
|
+
|
|
49
|
+
if cursor.description:
|
|
50
|
+
columns = [desc[0] for desc in cursor.description]
|
|
51
|
+
rows_raw = await cursor.fetchall()
|
|
52
|
+
rows = [list(row) for row in rows_raw]
|
|
53
|
+
return DataExplorerResult(
|
|
54
|
+
success=True,
|
|
55
|
+
source=context.options.name,
|
|
56
|
+
language=context.options.language,
|
|
57
|
+
default_output_format=context.options.default_output_format,
|
|
58
|
+
execution_time=int((time.monotonic() - start) * 1000),
|
|
59
|
+
columns=columns,
|
|
60
|
+
rows=rows,
|
|
61
|
+
row_count=len(rows),
|
|
62
|
+
truncated=False,
|
|
63
|
+
error=None,
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
await conn.commit()
|
|
67
|
+
return DataExplorerResult(
|
|
68
|
+
success=True,
|
|
69
|
+
source=context.options.name,
|
|
70
|
+
language=context.options.language,
|
|
71
|
+
default_output_format=context.options.default_output_format,
|
|
72
|
+
execution_time=int((time.monotonic() - start) * 1000),
|
|
73
|
+
columns=["changes"],
|
|
74
|
+
rows=[[cursor.rowcount]],
|
|
75
|
+
row_count=1,
|
|
76
|
+
truncated=False,
|
|
77
|
+
error=None,
|
|
78
|
+
)
|
|
79
|
+
except Exception as exc:
|
|
80
|
+
return DataExplorerResult(
|
|
81
|
+
success=False,
|
|
82
|
+
source=context.options.name,
|
|
83
|
+
language=context.options.language,
|
|
84
|
+
default_output_format=context.options.default_output_format,
|
|
85
|
+
execution_time=int((time.monotonic() - start) * 1000),
|
|
86
|
+
columns=None,
|
|
87
|
+
rows=[],
|
|
88
|
+
row_count=0,
|
|
89
|
+
truncated=False,
|
|
90
|
+
error=str(exc),
|
|
91
|
+
)
|
|
92
|
+
finally:
|
|
93
|
+
if conn is not None:
|
|
94
|
+
conn.close()
|
|
95
|
+
|
|
96
|
+
async def get_schema_async(
|
|
97
|
+
self, options: DataExplorerProviderOptions
|
|
98
|
+
) -> DataExplorerSchemaResult | None:
|
|
99
|
+
"""Return the database schema (tables, views, and their columns)."""
|
|
100
|
+
conn: aiomysql.Connection | None = None
|
|
101
|
+
try:
|
|
102
|
+
conn = await aiomysql.connect(
|
|
103
|
+
host=self._host,
|
|
104
|
+
port=self._port,
|
|
105
|
+
user=self._user,
|
|
106
|
+
password=self._password,
|
|
107
|
+
db=self._db,
|
|
108
|
+
)
|
|
109
|
+
async with conn.cursor() as cursor:
|
|
110
|
+
await cursor.execute(
|
|
111
|
+
"SELECT TABLE_NAME, TABLE_TYPE "
|
|
112
|
+
"FROM information_schema.tables "
|
|
113
|
+
"WHERE TABLE_SCHEMA = DATABASE() "
|
|
114
|
+
"ORDER BY TABLE_NAME"
|
|
115
|
+
)
|
|
116
|
+
table_rows = await cursor.fetchall()
|
|
117
|
+
|
|
118
|
+
tables: list[DataExplorerSchemaTable] = []
|
|
119
|
+
for table_name, table_type in table_rows:
|
|
120
|
+
await cursor.execute(
|
|
121
|
+
"SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_KEY "
|
|
122
|
+
"FROM information_schema.columns "
|
|
123
|
+
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s "
|
|
124
|
+
"ORDER BY ORDINAL_POSITION",
|
|
125
|
+
(table_name,),
|
|
126
|
+
)
|
|
127
|
+
col_rows = await cursor.fetchall()
|
|
128
|
+
columns = [
|
|
129
|
+
DataExplorerSchemaColumn(
|
|
130
|
+
name=col_name,
|
|
131
|
+
type=data_type or "TEXT",
|
|
132
|
+
nullable=is_nullable == "YES",
|
|
133
|
+
primary_key=col_key == "PRI",
|
|
134
|
+
)
|
|
135
|
+
for col_name, data_type, is_nullable, col_key in col_rows
|
|
136
|
+
]
|
|
137
|
+
tables.append(DataExplorerSchemaTable(
|
|
138
|
+
name=table_name,
|
|
139
|
+
type=table_type,
|
|
140
|
+
columns=columns,
|
|
141
|
+
))
|
|
142
|
+
|
|
143
|
+
return DataExplorerSchemaResult(source=options.name, tables=tables)
|
|
144
|
+
finally:
|
|
145
|
+
if conn is not None:
|
|
146
|
+
conn.close()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: qodalis-cli-server-plugin-data-explorer-mysql
|
|
3
|
+
Version: 1.0.0b18
|
|
4
|
+
Summary: MySQL Data Explorer provider plugin for Qodalis CLI servers
|
|
5
|
+
Project-URL: Homepage, https://github.com/qodalis-solutions/cli-server-python
|
|
6
|
+
Project-URL: Repository, https://github.com/qodalis-solutions/cli-server-python
|
|
7
|
+
Project-URL: Issues, https://github.com/qodalis-solutions/cli-server-python/issues
|
|
8
|
+
Author-email: Nicolae Lupei <nicolae.lupei@qodalis.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: cli,data-explorer,mysql,plugin,qodalis,sql
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: aiomysql>=0.2.0
|
|
21
|
+
Requires-Dist: qodalis-cli-server-abstractions
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
qodalis_cli_data_explorer_mysql/__init__.py,sha256=xNDWVqaUpfSljB5p1PCkviBuqYWk8gcWPToIQYb8NMM,161
|
|
2
|
+
qodalis_cli_data_explorer_mysql/mysql_provider.py,sha256=3XeX0HxL3LFGTG5WV7Dbrh6WH2biPcawF9Hn9M7EZio,5736
|
|
3
|
+
qodalis_cli_server_plugin_data_explorer_mysql-1.0.0b18.dist-info/METADATA,sha256=B-RXCqn_IvB0_e4V-FZ7RkRR73eKR02kw7KNj3uYktQ,1027
|
|
4
|
+
qodalis_cli_server_plugin_data_explorer_mysql-1.0.0b18.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
5
|
+
qodalis_cli_server_plugin_data_explorer_mysql-1.0.0b18.dist-info/RECORD,,
|