qodalis-cli-server-plugin-data-explorer-mssql 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.
@@ -0,0 +1,5 @@
1
+ """Qodalis CLI Data Explorer MSSQL plugin — MS SQL Server provider."""
2
+
3
+ from .mssql_provider import MssqlDataExplorerProvider
4
+
5
+ __all__ = ["MssqlDataExplorerProvider"]
@@ -0,0 +1,209 @@
1
+ """MS SQL Server-backed Data Explorer provider."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import re
7
+ import time
8
+
9
+ import pymssql
10
+
11
+ from qodalis_cli_server_abstractions import (
12
+ DataExplorerExecutionContext,
13
+ DataExplorerProviderOptions,
14
+ DataExplorerResult,
15
+ DataExplorerSchemaColumn,
16
+ DataExplorerSchemaResult,
17
+ DataExplorerSchemaTable,
18
+ IDataExplorerProvider,
19
+ )
20
+
21
+ _CS_RE = re.compile(r"(?P<key>[^=;]+)=(?P<value>[^;]*)", re.IGNORECASE)
22
+
23
+
24
+ def _parse_connection_string(connection_string: str) -> dict[str, str]:
25
+ """Return a lower-cased key dict from an ADO.NET connection string."""
26
+ result: dict[str, str] = {}
27
+ for match in _CS_RE.finditer(connection_string):
28
+ key = match.group("key").strip().lower().replace(" ", "")
29
+ value = match.group("value").strip()
30
+ result[key] = value
31
+ return result
32
+
33
+
34
+ class MssqlDataExplorerProvider(IDataExplorerProvider):
35
+ """Executes SQL queries against a Microsoft SQL Server database.
36
+
37
+ Connection string format (ADO.NET style)::
38
+
39
+ Server=host,1433;Database=mydb;User Id=sa;Password=secret;TrustServerCertificate=true
40
+ """
41
+
42
+ def __init__(self, connection_string: str) -> None:
43
+ self._connection_string = connection_string
44
+ params = _parse_connection_string(connection_string)
45
+
46
+ server_raw = params.get("server", "localhost")
47
+ if "," in server_raw:
48
+ host_part, port_part = server_raw.split(",", 1)
49
+ self._host = host_part.strip()
50
+ try:
51
+ self._port = int(port_part.strip())
52
+ except ValueError:
53
+ self._port = 1433
54
+ else:
55
+ self._host = server_raw
56
+ self._port = 1433
57
+
58
+ self._database = params.get("database", "master")
59
+ self._user = params.get("userid") or params.get("uid", "sa")
60
+ self._password = params.get("password") or params.get("pwd", "")
61
+
62
+ def _connect(self) -> pymssql.Connection:
63
+ """Open a connection to the SQL Server instance."""
64
+ return pymssql.connect(
65
+ server=self._host,
66
+ port=self._port,
67
+ user=self._user,
68
+ password=self._password,
69
+ database=self._database,
70
+ )
71
+
72
+ def _execute_sync(
73
+ self, context: DataExplorerExecutionContext
74
+ ) -> DataExplorerResult:
75
+ """Synchronous query execution, called via ``asyncio.to_thread``."""
76
+ start = time.monotonic()
77
+ conn: pymssql.Connection | None = None
78
+ try:
79
+ conn = self._connect()
80
+ cursor = conn.cursor()
81
+ cursor.execute(context.query, context.parameters or ())
82
+
83
+ if cursor.description:
84
+ columns = [desc[0] for desc in cursor.description]
85
+ rows_raw = cursor.fetchall()
86
+ rows = [list(row) for row in rows_raw]
87
+ return DataExplorerResult(
88
+ success=True,
89
+ source=context.options.name,
90
+ language=context.options.language,
91
+ default_output_format=context.options.default_output_format,
92
+ execution_time=int((time.monotonic() - start) * 1000),
93
+ columns=columns,
94
+ rows=rows,
95
+ row_count=len(rows),
96
+ truncated=False,
97
+ error=None,
98
+ )
99
+ else:
100
+ conn.commit()
101
+ return DataExplorerResult(
102
+ success=True,
103
+ source=context.options.name,
104
+ language=context.options.language,
105
+ default_output_format=context.options.default_output_format,
106
+ execution_time=int((time.monotonic() - start) * 1000),
107
+ columns=["changes"],
108
+ rows=[[cursor.rowcount]],
109
+ row_count=1,
110
+ truncated=False,
111
+ error=None,
112
+ )
113
+ except Exception as exc:
114
+ return DataExplorerResult(
115
+ success=False,
116
+ source=context.options.name,
117
+ language=context.options.language,
118
+ default_output_format=context.options.default_output_format,
119
+ execution_time=int((time.monotonic() - start) * 1000),
120
+ columns=None,
121
+ rows=[],
122
+ row_count=0,
123
+ truncated=False,
124
+ error=str(exc),
125
+ )
126
+ finally:
127
+ if conn is not None:
128
+ conn.close()
129
+
130
+ def _get_schema_sync(
131
+ self, options: DataExplorerProviderOptions
132
+ ) -> DataExplorerSchemaResult | None:
133
+ """Synchronous schema introspection, called via ``asyncio.to_thread``."""
134
+ conn: pymssql.Connection | None = None
135
+ try:
136
+ conn = self._connect()
137
+ cursor = conn.cursor()
138
+
139
+ cursor.execute(
140
+ "SELECT TABLE_NAME, TABLE_TYPE "
141
+ "FROM INFORMATION_SCHEMA.TABLES "
142
+ "WHERE TABLE_SCHEMA = 'dbo' "
143
+ "ORDER BY TABLE_NAME"
144
+ )
145
+ table_rows = cursor.fetchall()
146
+
147
+ tables: list[DataExplorerSchemaTable] = []
148
+ for table_name, table_type in table_rows:
149
+ cursor.execute(
150
+ "SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE "
151
+ "FROM INFORMATION_SCHEMA.COLUMNS "
152
+ "WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = %s "
153
+ "ORDER BY ORDINAL_POSITION",
154
+ (table_name,),
155
+ )
156
+ col_rows = cursor.fetchall()
157
+
158
+ cursor.execute(
159
+ "SELECT kcu.COLUMN_NAME "
160
+ "FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc "
161
+ "JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu "
162
+ " ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME "
163
+ " AND tc.TABLE_SCHEMA = kcu.TABLE_SCHEMA "
164
+ " AND tc.TABLE_NAME = kcu.TABLE_NAME "
165
+ "WHERE tc.CONSTRAINT_TYPE = 'PRIMARY KEY' "
166
+ " AND tc.TABLE_SCHEMA = 'dbo' "
167
+ " AND tc.TABLE_NAME = %s",
168
+ (table_name,),
169
+ )
170
+ pk_rows = cursor.fetchall()
171
+ pk_columns = {row[0] for row in pk_rows}
172
+
173
+ columns = [
174
+ DataExplorerSchemaColumn(
175
+ name=col_name,
176
+ type=data_type or "varchar",
177
+ nullable=is_nullable.upper() == "YES",
178
+ primary_key=col_name in pk_columns,
179
+ )
180
+ for col_name, data_type, is_nullable in col_rows
181
+ ]
182
+
183
+ normalised_type = (
184
+ "view" if table_type.strip().upper() == "VIEW" else "table"
185
+ )
186
+ tables.append(
187
+ DataExplorerSchemaTable(
188
+ name=table_name,
189
+ type=normalised_type,
190
+ columns=columns,
191
+ )
192
+ )
193
+
194
+ return DataExplorerSchemaResult(source=options.name, tables=tables)
195
+ finally:
196
+ if conn is not None:
197
+ conn.close()
198
+
199
+ async def execute_async(
200
+ self, context: DataExplorerExecutionContext
201
+ ) -> DataExplorerResult:
202
+ """Execute a SQL query against the SQL Server database."""
203
+ return await asyncio.to_thread(self._execute_sync, context)
204
+
205
+ async def get_schema_async(
206
+ self, options: DataExplorerProviderOptions
207
+ ) -> DataExplorerSchemaResult | None:
208
+ """Return the dbo schema (tables, views, and their columns)."""
209
+ return await asyncio.to_thread(self._get_schema_sync, options)
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.5
2
+ Name: qodalis-cli-server-plugin-data-explorer-mssql
3
+ Version: 1.0.0b18
4
+ Summary: MS SQL Server 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,mssql,plugin,qodalis,sql-server
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: pymssql>=2.3.0
21
+ Requires-Dist: qodalis-cli-server-abstractions
@@ -0,0 +1,5 @@
1
+ qodalis_cli_data_explorer_mssql/__init__.py,sha256=mDbSMloN-aeqfQy0j_u-dC-XeluO2FrkdVuPN_KVLDc,169
2
+ qodalis_cli_data_explorer_mssql/mssql_provider.py,sha256=uEFZFy3ydH0xAtA5HSXM7SPzzSJsAyzrbw5T_yM4cAE,7822
3
+ qodalis_cli_server_plugin_data_explorer_mssql-1.0.0b18.dist-info/METADATA,sha256=BqmGgNjFQQ98-xzaIcxLtTYzEeRXpiPY-VdxRgoCK6k,1041
4
+ qodalis_cli_server_plugin_data_explorer_mssql-1.0.0b18.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
5
+ qodalis_cli_server_plugin_data_explorer_mssql-1.0.0b18.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any