fastmssql 0.2.4__cp312-cp312-macosx_11_0_arm64.whl → 0.2.6__cp312-cp312-macosx_11_0_arm64.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.
Potentially problematic release.
This version of fastmssql might be problematic. Click here for more details.
- fastmssql/__init__.py +3 -16
- fastmssql/{fastmssql_core.cpython-312-darwin.so → fastmssql.cpython-312-darwin.so} +0 -0
- {fastmssql-0.2.4.dist-info → fastmssql-0.2.6.dist-info}/METADATA +24 -52
- fastmssql-0.2.6.dist-info/RECORD +6 -0
- fastmssql/fastmssql.py +0 -706
- fastmssql-0.2.4.dist-info/RECORD +0 -7
- {fastmssql-0.2.4.dist-info → fastmssql-0.2.6.dist-info}/WHEEL +0 -0
- {fastmssql-0.2.4.dist-info → fastmssql-0.2.6.dist-info}/licenses/LICENSE +0 -0
fastmssql/__init__.py
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
"""
|
|
2
|
-
fastmssql: A high-performance Python library for Microsoft SQL Server
|
|
3
|
-
|
|
4
|
-
This library provides a Python interface to Microsoft SQL Server using the Tiberius
|
|
5
|
-
Rust driver for excellent performance and memory safety.
|
|
6
|
-
|
|
7
|
-
Example (async):
|
|
8
|
-
>>> from fastmssql import Connection
|
|
9
|
-
>>> async with Connection("DATABASE_CONNECTION_STRING") as conn:
|
|
10
|
-
... result = await conn.execute("SELECT * FROM users WHERE age > @P1", [18])
|
|
11
|
-
... for row in result:
|
|
12
|
-
... print(row['name'], row['age'])
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
# Import everything from the main API module
|
|
16
1
|
from .fastmssql import *
|
|
17
2
|
|
|
18
|
-
|
|
3
|
+
__doc__ = fastmssql.__doc__
|
|
4
|
+
if hasattr(fastmssql, "__all__"):
|
|
5
|
+
__all__ = fastmssql.__all__
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmssql
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: Other/Proprietary License
|
|
@@ -29,59 +29,31 @@ Project-URL: Homepage, https://github.com/Rivendael/pymssql-rs
|
|
|
29
29
|
|
|
30
30
|
# Fastmssql ⚡
|
|
31
31
|
|
|
32
|
-
A
|
|
32
|
+
A Python library for Microsoft SQL Server built with Rust using the [Tiberius](https://github.com/prisma/tiberius) driver, [PyO3](https://github.com/PyO3/pyo3), and [bb8](https://github.com/djc/bb8) connection pooling.
|
|
33
33
|
|
|
34
|
-
> **🚀 Performance Highlight**: Achieves **3,493 requests/second** and **0.08 KB memory per query** in our benchmarks
|
|
35
|
-
|
|
36
|
-
[](https://github.com/Rivendael/pymssql-rs)
|
|
37
|
-
[](https://github.com/Rivendael/pymssql-rs)
|
|
38
|
-
[](https://github.com/Rivendael/pymssql-rs)
|
|
39
34
|
[](https://github.com/Rivendael/pymssql-rs)
|
|
40
35
|
[](https://github.com/Rivendael/pymssql-rs)
|
|
41
36
|
|
|
42
37
|
## Features
|
|
43
38
|
|
|
44
|
-
- **
|
|
39
|
+
- **Rust-Powered Backend**: Built with Rust for memory safety and reliability
|
|
45
40
|
- **No ODBC Required**: Direct native SQL Server connection without ODBC drivers
|
|
46
|
-
- **Connection Pooling**:
|
|
47
|
-
- **Async-Only Design**: Built on Tokio
|
|
41
|
+
- **Connection Pooling**: bb8-based connection pool for efficient resource management
|
|
42
|
+
- **Async-Only Design**: Built on Tokio with clean async/await API
|
|
48
43
|
- **Context Managers**: Automatic resource management with `async with`
|
|
49
44
|
- **Type Safety**: Strong typing with automatic Python type conversion
|
|
50
|
-
- **Thread Safety**:
|
|
45
|
+
- **Thread Safety**: Support for concurrent operations
|
|
51
46
|
- **Cross-Platform**: Works on Windows, macOS, and Linux
|
|
52
47
|
- **Simple API**: Clean, intuitive async-only interface
|
|
53
48
|
|
|
54
|
-
##
|
|
55
|
-
|
|
56
|
-
Fastmssql delivers **excellent performance** that outperforms standard Python SQL Server libraries in our testing:
|
|
57
|
-
|
|
58
|
-
### 🚀 Benchmark Results
|
|
59
|
-
|
|
60
|
-
| Library | RPS (Requests/Second) | Performance vs fastmssql |
|
|
61
|
-
|---------|----------------------|--------------------------|
|
|
62
|
-
| **fastmssql** | **3,493** | **Baseline** |
|
|
63
|
-
| Other Python libraries | ~300-600* | **5-11x slower** |
|
|
64
|
-
|
|
65
|
-
*Benchmarks performed with 20 concurrent workers executing `SELECT @@VERSION` queries on our test environment. Performance of other libraries may vary based on configuration, environment, and specific use cases. We recommend conducting your own benchmarks for your specific requirements.*
|
|
66
|
-
|
|
67
|
-
### 🧠 Memory Efficiency
|
|
68
|
-
|
|
69
|
-
Fastmssql delivers **exceptional memory efficiency** with minimal overhead:
|
|
70
|
-
|
|
71
|
-
| Metric | Value | Description |
|
|
72
|
-
|--------|-------|-------------|
|
|
73
|
-
| **Per Query Overhead** | **0.08 KB** | Memory used per database query |
|
|
74
|
-
| **Concurrent Overhead** | **3.52 KB** | Memory per concurrent operation |
|
|
75
|
-
| **Connection Pool** | **5.26 MB** | One-time pool initialization |
|
|
76
|
-
| **Memory Leaks** | **None** | Actually reduces memory over time |
|
|
49
|
+
## Features
|
|
77
50
|
|
|
78
|
-
|
|
79
|
-
- **12,800 queries per MB** of memory overhead
|
|
80
|
-
- **Zero memory leaks** - memory usage decreases over time
|
|
81
|
-
- **100-1000x more efficient** than libraries without connection pooling
|
|
82
|
-
- **Stable memory usage** under high concurrent load
|
|
51
|
+
Fastmssql provides reliable database connectivity with modern Python patterns:
|
|
83
52
|
|
|
84
|
-
|
|
53
|
+
- **Production Ready**: Stable API with comprehensive error handling
|
|
54
|
+
- **Connection Pooling**: Efficient resource management with configurable pools
|
|
55
|
+
- **Type Conversion**: Automatic conversion between SQL Server and Python types
|
|
56
|
+
- **SSL/TLS Support**: Secure connections with flexible encryption options
|
|
85
57
|
|
|
86
58
|
## Installation
|
|
87
59
|
|
|
@@ -214,27 +186,27 @@ async def main():
|
|
|
214
186
|
asyncio.run(main())
|
|
215
187
|
```
|
|
216
188
|
|
|
217
|
-
###
|
|
189
|
+
### Connection Pool Configuration
|
|
218
190
|
|
|
219
|
-
For applications
|
|
191
|
+
For high-throughput applications, you can configure the connection pool:
|
|
220
192
|
|
|
221
193
|
```python
|
|
222
194
|
import asyncio
|
|
223
195
|
from fastmssql import Connection, PoolConfig
|
|
224
196
|
|
|
225
197
|
async def main():
|
|
226
|
-
#
|
|
227
|
-
|
|
198
|
+
# High-throughput pool setup
|
|
199
|
+
config = PoolConfig.high_throughput() # Optimized for concurrent access
|
|
228
200
|
|
|
229
|
-
async with Connection(connection_string,
|
|
230
|
-
#
|
|
201
|
+
async with Connection(connection_string, config) as conn:
|
|
202
|
+
# Pool configured for concurrent operations
|
|
231
203
|
|
|
232
|
-
# Concurrent workers for
|
|
204
|
+
# Concurrent workers for high throughput
|
|
233
205
|
async def worker():
|
|
234
206
|
result = await conn.execute("SELECT @@VERSION")
|
|
235
207
|
return result.rows()
|
|
236
208
|
|
|
237
|
-
# Run
|
|
209
|
+
# Run multiple concurrent workers
|
|
238
210
|
tasks = [worker() for _ in range(20)]
|
|
239
211
|
results = await asyncio.gather(*tasks)
|
|
240
212
|
|
|
@@ -247,7 +219,7 @@ asyncio.run(main())
|
|
|
247
219
|
|
|
248
220
|
**Performance Tips:**
|
|
249
221
|
- **Reuse Connection objects**: Create one `Connection` per database and reuse it across your application
|
|
250
|
-
- Use `PoolConfig.high_throughput()` for
|
|
222
|
+
- Use `PoolConfig.high_throughput()` for high-throughput applications
|
|
251
223
|
- Leverage `asyncio.gather()` for concurrent operations
|
|
252
224
|
- Monitor pool stats to optimize connection count
|
|
253
225
|
- Consider connection lifetime for long-running applications
|
|
@@ -280,7 +252,7 @@ The bb8 connection pool provides significant performance improvements over tradi
|
|
|
280
252
|
| Metric | Traditional Python | fastmssql | Improvement |
|
|
281
253
|
|--------|-------------------|-----------|-------------|
|
|
282
254
|
| **Connection Setup** | 50ms | 0.1ms | **500x faster** |
|
|
283
|
-
| **Memory per Query** | 50-200 KB | 0.
|
|
255
|
+
| **Memory per Query** | 50-200 KB | 0.5 KB | **100-400x less** |
|
|
284
256
|
| **10 Concurrent Queries** | 500ms | 150ms | **3.3x faster** |
|
|
285
257
|
| **100 Concurrent Queries** | 5000ms | 400ms | **12.5x faster** |
|
|
286
258
|
| **1000 Concurrent Queries** | Timeouts/Errors | 2.9s | **Stable** |
|
|
@@ -288,7 +260,7 @@ The bb8 connection pool provides significant performance improvements over tradi
|
|
|
288
260
|
|
|
289
261
|
**Key Benefits:**
|
|
290
262
|
- **Connection Reuse**: Eliminates connection establishment overhead (500x improvement)
|
|
291
|
-
- **Memory Efficiency**: Uses
|
|
263
|
+
- **Memory Efficiency**: Uses 100-400x less memory per operation (0.5 KB vs 50-200 KB)
|
|
292
264
|
- **Zero Memory Leaks**: Automatic cleanup with decreasing memory usage over time
|
|
293
265
|
- **Concurrency**: Safe multi-threaded access with automatic pooling
|
|
294
266
|
- **Resource Management**: Intelligent memory and connection lifecycle management
|
|
@@ -656,7 +628,7 @@ async with mssql.connect_async(conn_str) as conn:
|
|
|
656
628
|
```
|
|
657
629
|
|
|
658
630
|
**Features:**
|
|
659
|
-
- Async-only operations for
|
|
631
|
+
- Async-only operations for optimal concurrency
|
|
660
632
|
- Automatic connection pooling with bb8
|
|
661
633
|
- Configurable pool settings via `PoolConfig`
|
|
662
634
|
- Pool statistics and monitoring
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
fastmssql-0.2.6.dist-info/RECORD,,
|
|
2
|
+
fastmssql-0.2.6.dist-info/WHEEL,sha256=EhaWXx4fd8VOPM6W-6pxsePGk73OLk2gBi7fwS90pc8,104
|
|
3
|
+
fastmssql-0.2.6.dist-info/METADATA,sha256=skDVnYNSe4ny1dYxsHrlehGnRLo8ij-pQFsBGNaJvwg,27674
|
|
4
|
+
fastmssql-0.2.6.dist-info/licenses/LICENSE,sha256=NwufX3BNj7RvVtnrshWhkpFOLvWc_YVpGpr3UZGFz_E,4765
|
|
5
|
+
fastmssql/fastmssql.cpython-312-darwin.so,sha256=Q3mLk39v7HeldBjrxxj_xrFlAP9qFse6y-RalRV8lMc,4128880
|
|
6
|
+
fastmssql/__init__.py,sha256=2I8gj3Ump7-VdoqnLpp4iiZjFrFZeyeG7iwOFJEz544,119
|
fastmssql/fastmssql.py
DELETED
|
@@ -1,706 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
High-level Python API for mssql-python-rust
|
|
3
|
-
|
|
4
|
-
This module provides convenient Python functions that wrap the Rust core functionality.
|
|
5
|
-
Supports asynchronous operations only.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from typing import List, Dict, Any, Optional, Union, Iterable
|
|
9
|
-
|
|
10
|
-
try:
|
|
11
|
-
# Try to import the compiled Rust module from the package
|
|
12
|
-
from . import fastmssql_core as _core
|
|
13
|
-
except ImportError:
|
|
14
|
-
# Fallback for development - try absolute import
|
|
15
|
-
try:
|
|
16
|
-
import fastmssql_core as _core
|
|
17
|
-
except ImportError as e:
|
|
18
|
-
import sys
|
|
19
|
-
print(f"ERROR: fastmssql_core module not found: {e}")
|
|
20
|
-
print("Solution: Build the extension with 'maturin develop' or 'maturin develop --release'")
|
|
21
|
-
print("Make sure you're in the project root directory and have Rust/Maturin installed.")
|
|
22
|
-
sys.exit(1)
|
|
23
|
-
|
|
24
|
-
class Row:
|
|
25
|
-
"""Python wrapper around Row for better type hints and documentation."""
|
|
26
|
-
|
|
27
|
-
def __init__(self, py_row: Any) -> None:
|
|
28
|
-
"""Initialize with a Row instance.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
py_row: The underlying Rust Row instance
|
|
32
|
-
"""
|
|
33
|
-
self._row = py_row
|
|
34
|
-
|
|
35
|
-
def get(self, column: Union[str, int]) -> Any:
|
|
36
|
-
"""Get value by column name or index.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
column: Column name (str) or index (int)
|
|
40
|
-
|
|
41
|
-
Returns:
|
|
42
|
-
The column value
|
|
43
|
-
"""
|
|
44
|
-
return self._row.get(column)
|
|
45
|
-
|
|
46
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
47
|
-
"""Convert row to dictionary.
|
|
48
|
-
|
|
49
|
-
Returns:
|
|
50
|
-
Dictionary mapping column names to values
|
|
51
|
-
"""
|
|
52
|
-
return self._row.to_dict()
|
|
53
|
-
|
|
54
|
-
def to_tuple(self) -> tuple:
|
|
55
|
-
"""Convert row to tuple.
|
|
56
|
-
|
|
57
|
-
Returns:
|
|
58
|
-
Tuple of column values in order
|
|
59
|
-
"""
|
|
60
|
-
return self._row.to_tuple()
|
|
61
|
-
|
|
62
|
-
def __getitem__(self, key: Union[str, int]) -> Any:
|
|
63
|
-
"""Get value by column name or index."""
|
|
64
|
-
return self._row[key]
|
|
65
|
-
|
|
66
|
-
def __len__(self) -> int:
|
|
67
|
-
"""Get number of columns in the row."""
|
|
68
|
-
return len(self._row)
|
|
69
|
-
|
|
70
|
-
def __repr__(self) -> str:
|
|
71
|
-
"""String representation of the row."""
|
|
72
|
-
return f"Row({self.to_dict()})"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class ExecutionResult:
|
|
76
|
-
"""Python wrapper around ExecutionResult for better type hints."""
|
|
77
|
-
|
|
78
|
-
def __init__(self, py_result):
|
|
79
|
-
"""Initialize with a ExecutionResult instance."""
|
|
80
|
-
self._result = py_result
|
|
81
|
-
|
|
82
|
-
def rows(self) -> List[Row]:
|
|
83
|
-
"""Query result rows (for SELECT queries).
|
|
84
|
-
|
|
85
|
-
Returns:
|
|
86
|
-
List of Row objects
|
|
87
|
-
"""
|
|
88
|
-
if self._result.has_rows():
|
|
89
|
-
# Get raw rows - could be property or method
|
|
90
|
-
try:
|
|
91
|
-
if callable(self._result.rows):
|
|
92
|
-
raw_rows = self._result.rows()
|
|
93
|
-
else:
|
|
94
|
-
raw_rows = self._result.rows
|
|
95
|
-
return [Row(py_row) for py_row in raw_rows]
|
|
96
|
-
except Exception:
|
|
97
|
-
return []
|
|
98
|
-
return []
|
|
99
|
-
|
|
100
|
-
@property
|
|
101
|
-
def affected_rows(self) -> Optional[int]:
|
|
102
|
-
"""Number of affected rows (for INSERT/UPDATE/DELETE).
|
|
103
|
-
|
|
104
|
-
Returns:
|
|
105
|
-
Number of affected rows, or None if not applicable
|
|
106
|
-
"""
|
|
107
|
-
return self._result.affected_rows
|
|
108
|
-
|
|
109
|
-
def has_rows(self) -> bool:
|
|
110
|
-
"""Check if result contains rows.
|
|
111
|
-
|
|
112
|
-
Returns:
|
|
113
|
-
True if result has rows (SELECT query), False otherwise
|
|
114
|
-
"""
|
|
115
|
-
return self._result.has_rows()
|
|
116
|
-
|
|
117
|
-
def __len__(self) -> int:
|
|
118
|
-
"""Get number of rows in the result."""
|
|
119
|
-
return len(self.rows())
|
|
120
|
-
|
|
121
|
-
def __iter__(self):
|
|
122
|
-
"""Iterate over rows in the result."""
|
|
123
|
-
return iter(self.rows())
|
|
124
|
-
|
|
125
|
-
def __repr__(self) -> str:
|
|
126
|
-
"""String representation of the result."""
|
|
127
|
-
if self.has_rows():
|
|
128
|
-
return f"ExecutionResult(rows={len(self.rows())})"
|
|
129
|
-
else:
|
|
130
|
-
return f"ExecutionResult(affected_rows={self.affected_rows})"
|
|
131
|
-
|
|
132
|
-
class PoolConfig:
|
|
133
|
-
"""Python wrapper around PoolConfig for better documentation."""
|
|
134
|
-
|
|
135
|
-
def __init__(
|
|
136
|
-
self,
|
|
137
|
-
max_size: int = 10,
|
|
138
|
-
min_idle: Optional[int] = None,
|
|
139
|
-
max_lifetime_secs: Optional[int] = None,
|
|
140
|
-
idle_timeout_secs: Optional[int] = None,
|
|
141
|
-
connection_timeout_secs: Optional[int] = None
|
|
142
|
-
):
|
|
143
|
-
"""Initialize connection pool configuration.
|
|
144
|
-
|
|
145
|
-
Args:
|
|
146
|
-
max_size: Maximum number of connections in pool (default: 10)
|
|
147
|
-
min_idle: Minimum number of idle connections to maintain
|
|
148
|
-
max_lifetime_secs: Maximum lifetime of connections in seconds
|
|
149
|
-
idle_timeout_secs: How long a connection can be idle before being closed (seconds)
|
|
150
|
-
connection_timeout_secs: Timeout for establishing new connections (seconds)
|
|
151
|
-
"""
|
|
152
|
-
self._config = _core.PoolConfig(
|
|
153
|
-
max_size=max_size,
|
|
154
|
-
min_idle=min_idle,
|
|
155
|
-
max_lifetime_secs=max_lifetime_secs,
|
|
156
|
-
idle_timeout_secs=idle_timeout_secs,
|
|
157
|
-
connection_timeout_secs=connection_timeout_secs
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
@property
|
|
161
|
-
def max_size(self) -> int:
|
|
162
|
-
"""Maximum number of connections in pool."""
|
|
163
|
-
return self._config.max_size
|
|
164
|
-
|
|
165
|
-
@property
|
|
166
|
-
def min_idle(self) -> Optional[int]:
|
|
167
|
-
"""Minimum number of idle connections."""
|
|
168
|
-
return self._config.min_idle
|
|
169
|
-
|
|
170
|
-
@property
|
|
171
|
-
def max_lifetime_secs(self) -> Optional[int]:
|
|
172
|
-
"""Maximum lifetime of connections in seconds."""
|
|
173
|
-
return self._config.max_lifetime_secs
|
|
174
|
-
|
|
175
|
-
@property
|
|
176
|
-
def idle_timeout_secs(self) -> Optional[int]:
|
|
177
|
-
"""Idle timeout in seconds."""
|
|
178
|
-
return self._config.idle_timeout_secs
|
|
179
|
-
|
|
180
|
-
@property
|
|
181
|
-
def connection_timeout_secs(self) -> Optional[int]:
|
|
182
|
-
"""Connection timeout in seconds."""
|
|
183
|
-
return self._config.connection_timeout_secs
|
|
184
|
-
|
|
185
|
-
@staticmethod
|
|
186
|
-
def high_throughput() -> 'PoolConfig':
|
|
187
|
-
"""Create configuration for high-throughput scenarios."""
|
|
188
|
-
config = PoolConfig.__new__(PoolConfig)
|
|
189
|
-
config._config = _core.PoolConfig.high_throughput()
|
|
190
|
-
return config
|
|
191
|
-
|
|
192
|
-
@staticmethod
|
|
193
|
-
def low_resource() -> 'PoolConfig':
|
|
194
|
-
"""Create configuration for low-resource scenarios."""
|
|
195
|
-
config = PoolConfig.__new__(PoolConfig)
|
|
196
|
-
config._config = _core.PoolConfig.low_resource()
|
|
197
|
-
return config
|
|
198
|
-
|
|
199
|
-
@staticmethod
|
|
200
|
-
def development() -> 'PoolConfig':
|
|
201
|
-
"""Create configuration for development scenarios."""
|
|
202
|
-
config = PoolConfig.__new__(PoolConfig)
|
|
203
|
-
config._config = _core.PoolConfig.development()
|
|
204
|
-
return config
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
class Parameter:
|
|
208
|
-
"""Represents a SQL parameter with value and optional type information."""
|
|
209
|
-
|
|
210
|
-
# Valid SQL parameter types (base types without parameters)
|
|
211
|
-
VALID_SQL_TYPES = {
|
|
212
|
-
'VARCHAR', 'NVARCHAR', 'CHAR', 'NCHAR', 'TEXT', 'NTEXT',
|
|
213
|
-
'INT', 'BIGINT', 'SMALLINT', 'TINYINT', 'BIT',
|
|
214
|
-
'FLOAT', 'REAL', 'DECIMAL', 'NUMERIC', 'MONEY', 'SMALLMONEY',
|
|
215
|
-
'DATETIME', 'DATETIME2', 'SMALLDATETIME', 'DATE', 'TIME', 'DATETIMEOFFSET',
|
|
216
|
-
'BINARY', 'VARBINARY', 'IMAGE',
|
|
217
|
-
'UNIQUEIDENTIFIER', 'XML', 'JSON'
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
@staticmethod
|
|
221
|
-
def _extract_base_type(sql_type: str) -> str:
|
|
222
|
-
"""Extract the base SQL type from a type specification.
|
|
223
|
-
|
|
224
|
-
Examples:
|
|
225
|
-
VARCHAR(50) -> VARCHAR
|
|
226
|
-
NVARCHAR(MAX) -> NVARCHAR
|
|
227
|
-
DECIMAL(10,2) -> DECIMAL
|
|
228
|
-
INT -> INT
|
|
229
|
-
"""
|
|
230
|
-
# Find the first opening parenthesis and extract everything before it
|
|
231
|
-
paren_pos = sql_type.find('(')
|
|
232
|
-
if paren_pos != -1:
|
|
233
|
-
return sql_type[:paren_pos].strip().upper()
|
|
234
|
-
return sql_type.strip().upper()
|
|
235
|
-
|
|
236
|
-
def __init__(self, value: Any, sql_type: Optional[str] = None) -> None:
|
|
237
|
-
"""Initialize a parameter.
|
|
238
|
-
|
|
239
|
-
Args:
|
|
240
|
-
value: The parameter value (None, bool, int, float, str, bytes, or iterable for IN clauses)
|
|
241
|
-
sql_type: Optional SQL type hint. Can include parameters:
|
|
242
|
-
- 'VARCHAR(50)', 'NVARCHAR(MAX)', 'DECIMAL(10,2)', etc.
|
|
243
|
-
- Base types: 'VARCHAR', 'INT', 'DATETIME', etc.
|
|
244
|
-
|
|
245
|
-
Raises:
|
|
246
|
-
ValueError: If sql_type is provided but the base type is not recognized
|
|
247
|
-
|
|
248
|
-
Note:
|
|
249
|
-
Lists, tuples, sets and other iterables (except strings/bytes) are automatically
|
|
250
|
-
expanded for use in IN clauses. So Parameter([1, 2, 3]) will expand to
|
|
251
|
-
placeholder values for "WHERE id IN (@P1, @P2, @P3)".
|
|
252
|
-
"""
|
|
253
|
-
# Automatically detect iterables for IN clause expansion
|
|
254
|
-
if self._is_iterable_value(value):
|
|
255
|
-
self.value = list(value) # Convert to list for consistency
|
|
256
|
-
self.is_expanded = True
|
|
257
|
-
else:
|
|
258
|
-
self.value = value
|
|
259
|
-
self.is_expanded = False
|
|
260
|
-
|
|
261
|
-
if sql_type is not None:
|
|
262
|
-
# Extract base type and validate it
|
|
263
|
-
base_type = self._extract_base_type(sql_type)
|
|
264
|
-
if base_type not in self.VALID_SQL_TYPES:
|
|
265
|
-
raise ValueError(
|
|
266
|
-
f"Invalid sql_type '{sql_type}'. Base type '{base_type}' not recognized. "
|
|
267
|
-
f"Valid base types: {', '.join(sorted(self.VALID_SQL_TYPES))}"
|
|
268
|
-
)
|
|
269
|
-
# Store the original type specification (including parameters)
|
|
270
|
-
self.sql_type = sql_type.upper()
|
|
271
|
-
else:
|
|
272
|
-
self.sql_type = None
|
|
273
|
-
|
|
274
|
-
@staticmethod
|
|
275
|
-
def _is_iterable_value(value: Any) -> bool:
|
|
276
|
-
"""Check if a value is an iterable that can be expanded for IN clauses.
|
|
277
|
-
|
|
278
|
-
Returns True for lists, tuples, sets, etc., but False for strings and bytes
|
|
279
|
-
which should be treated as single values.
|
|
280
|
-
"""
|
|
281
|
-
return (
|
|
282
|
-
hasattr(value, '__iter__') and
|
|
283
|
-
not isinstance(value, (str, bytes))
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
def __repr__(self) -> str:
|
|
287
|
-
if self.is_expanded:
|
|
288
|
-
type_info = f", type={self.sql_type}" if self.sql_type else ""
|
|
289
|
-
return f"Parameter(IN_values={self.value!r}{type_info})"
|
|
290
|
-
elif self.sql_type:
|
|
291
|
-
return f"Parameter(value={self.value!r}, type={self.sql_type})"
|
|
292
|
-
return f"Parameter(value={self.value!r})"
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
class Parameters:
|
|
296
|
-
"""Container for SQL parameters that can be passed to execute()."""
|
|
297
|
-
|
|
298
|
-
def __init__(self, *args, **kwargs):
|
|
299
|
-
"""Initialize parameters container.
|
|
300
|
-
|
|
301
|
-
Args:
|
|
302
|
-
*args: Positional parameter values. Can be individual values or iterables.
|
|
303
|
-
Iterables (lists, tuples, etc.) will be expanded by Rust for performance.
|
|
304
|
-
Strings and bytes are treated as single values, not expanded.
|
|
305
|
-
**kwargs: Named parameter values (for @name placeholders, if supported)
|
|
306
|
-
|
|
307
|
-
Examples:
|
|
308
|
-
# Individual parameters
|
|
309
|
-
params = Parameters(1, "hello", 3.14)
|
|
310
|
-
|
|
311
|
-
# Mix of individual and iterable parameters (expansion handled in Rust)
|
|
312
|
-
params = Parameters(1, [2, 3, 4], "hello") # Rust expands [2,3,4] automatically
|
|
313
|
-
|
|
314
|
-
# All types of iterables work
|
|
315
|
-
params = Parameters([1, 2], (3, 4), {5, 6}) # All expanded by Rust
|
|
316
|
-
"""
|
|
317
|
-
self._positional = []
|
|
318
|
-
self._named = {}
|
|
319
|
-
|
|
320
|
-
# Handle positional parameters - let Rust handle expansion
|
|
321
|
-
for arg in args:
|
|
322
|
-
if isinstance(arg, Parameter):
|
|
323
|
-
self._positional.append(arg)
|
|
324
|
-
else:
|
|
325
|
-
self._positional.append(Parameter(arg))
|
|
326
|
-
|
|
327
|
-
# Handle named parameters
|
|
328
|
-
for name, value in kwargs.items():
|
|
329
|
-
if isinstance(value, Parameter):
|
|
330
|
-
self._named[name] = value
|
|
331
|
-
else:
|
|
332
|
-
self._named[name] = Parameter(value)
|
|
333
|
-
|
|
334
|
-
def add(self, value: Any, sql_type: Optional[str] = None) -> 'Parameters':
|
|
335
|
-
"""Add a positional parameter and return self for chaining.
|
|
336
|
-
|
|
337
|
-
Args:
|
|
338
|
-
value: Parameter value (can be an iterable for automatic expansion by Rust)
|
|
339
|
-
sql_type: Optional SQL type hint
|
|
340
|
-
|
|
341
|
-
Returns:
|
|
342
|
-
Self for method chaining
|
|
343
|
-
|
|
344
|
-
Examples:
|
|
345
|
-
params = Parameters().add(42).add("hello")
|
|
346
|
-
params = Parameters().add([1, 2, 3]) # Rust expands automatically
|
|
347
|
-
"""
|
|
348
|
-
self._positional.append(Parameter(value, sql_type))
|
|
349
|
-
return self
|
|
350
|
-
|
|
351
|
-
def extend(self, other: Union['Parameters', Iterable[Any]]) -> 'Parameters':
|
|
352
|
-
"""Extend parameters with another Parameters object or iterable.
|
|
353
|
-
|
|
354
|
-
Args:
|
|
355
|
-
other: Another Parameters object or an iterable of values
|
|
356
|
-
|
|
357
|
-
Returns:
|
|
358
|
-
Self for method chaining
|
|
359
|
-
|
|
360
|
-
Examples:
|
|
361
|
-
params1 = Parameters(1, 2)
|
|
362
|
-
params2 = Parameters(3, 4)
|
|
363
|
-
params1.extend(params2) # params1 now has [1, 2, 3, 4]
|
|
364
|
-
|
|
365
|
-
params = Parameters(1, 2)
|
|
366
|
-
params.extend([3, 4, 5]) # params now has [1, 2, [3, 4, 5]] - Rust handles expansion
|
|
367
|
-
"""
|
|
368
|
-
if isinstance(other, Parameters):
|
|
369
|
-
self._positional.extend(other._positional)
|
|
370
|
-
self._named.update(other._named)
|
|
371
|
-
else:
|
|
372
|
-
# Add as single parameter - Rust will expand if it's an iterable
|
|
373
|
-
self._positional.append(Parameter(other))
|
|
374
|
-
return self
|
|
375
|
-
|
|
376
|
-
def set(self, name: str, value: Any, sql_type: Optional[str] = None) -> 'Parameters':
|
|
377
|
-
"""Set a named parameter and return self for chaining.
|
|
378
|
-
|
|
379
|
-
Args:
|
|
380
|
-
name: Parameter name
|
|
381
|
-
value: Parameter value
|
|
382
|
-
sql_type: Optional SQL type hint
|
|
383
|
-
|
|
384
|
-
Returns:
|
|
385
|
-
Self for method chaining
|
|
386
|
-
"""
|
|
387
|
-
self._named[name] = Parameter(value, sql_type)
|
|
388
|
-
return self
|
|
389
|
-
|
|
390
|
-
@property
|
|
391
|
-
def positional(self) -> List[Parameter]:
|
|
392
|
-
"""Get positional parameters."""
|
|
393
|
-
return self._positional.copy()
|
|
394
|
-
|
|
395
|
-
@property
|
|
396
|
-
def named(self) -> Dict[str, Parameter]:
|
|
397
|
-
"""Get named parameters."""
|
|
398
|
-
return self._named.copy()
|
|
399
|
-
|
|
400
|
-
def to_list(self) -> List[Any]:
|
|
401
|
-
"""Convert to simple list of values for compatibility.
|
|
402
|
-
|
|
403
|
-
Note: Iterable expansion is now handled by Rust for performance,
|
|
404
|
-
so this returns the raw values as-is.
|
|
405
|
-
"""
|
|
406
|
-
return [param.value for param in self._positional]
|
|
407
|
-
|
|
408
|
-
def __len__(self) -> int:
|
|
409
|
-
"""Get total number of parameters."""
|
|
410
|
-
return len(self._positional) + len(self._named)
|
|
411
|
-
|
|
412
|
-
def __repr__(self) -> str:
|
|
413
|
-
parts = []
|
|
414
|
-
if self._positional:
|
|
415
|
-
parts.append(f"positional={len(self._positional)}")
|
|
416
|
-
if self._named:
|
|
417
|
-
parts.append(f"named={len(self._named)}")
|
|
418
|
-
return f"Parameters({', '.join(parts)})"
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
class SslConfig:
|
|
422
|
-
"""SSL/TLS configuration for database connections."""
|
|
423
|
-
|
|
424
|
-
def __init__(
|
|
425
|
-
self,
|
|
426
|
-
encryption_level: Optional[str] = None,
|
|
427
|
-
trust_server_certificate: bool = False,
|
|
428
|
-
ca_certificate_path: Optional[str] = None,
|
|
429
|
-
enable_sni: bool = True,
|
|
430
|
-
server_name: Optional[str] = None
|
|
431
|
-
):
|
|
432
|
-
"""Initialize SSL configuration.
|
|
433
|
-
|
|
434
|
-
Args:
|
|
435
|
-
encryption_level: Encryption level - "Required", "LoginOnly", or "Off" (default: "Required")
|
|
436
|
-
trust_server_certificate: Trust server certificate without validation (dangerous in production)
|
|
437
|
-
ca_certificate_path: Path to custom CA certificate file (.pem, .crt, or .der)
|
|
438
|
-
enable_sni: Enable Server Name Indication (default: True)
|
|
439
|
-
server_name: Custom server name for certificate validation
|
|
440
|
-
|
|
441
|
-
Note:
|
|
442
|
-
trust_server_certificate and ca_certificate_path are mutually exclusive.
|
|
443
|
-
For production, either use a valid certificate in the system trust store,
|
|
444
|
-
or provide a ca_certificate_path to a trusted CA certificate.
|
|
445
|
-
"""
|
|
446
|
-
# Set default encryption level
|
|
447
|
-
if encryption_level is None:
|
|
448
|
-
encryption_level = "Required"
|
|
449
|
-
|
|
450
|
-
# Validate encryption level
|
|
451
|
-
valid_levels = ["Required", "LoginOnly", "Off"]
|
|
452
|
-
if encryption_level not in valid_levels:
|
|
453
|
-
raise ValueError(f"Invalid encryption_level. Must be one of: {valid_levels}")
|
|
454
|
-
|
|
455
|
-
# Map string encryption levels to enum values
|
|
456
|
-
encryption_level_map = {
|
|
457
|
-
"Required": _core.EncryptionLevel.REQUIRED,
|
|
458
|
-
"LoginOnly": _core.EncryptionLevel.LOGIN_ONLY,
|
|
459
|
-
"Off": _core.EncryptionLevel.OFF
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
self._config = _core.SslConfig(
|
|
463
|
-
encryption_level=encryption_level_map[encryption_level],
|
|
464
|
-
trust_server_certificate=trust_server_certificate,
|
|
465
|
-
ca_certificate_path=ca_certificate_path,
|
|
466
|
-
enable_sni=enable_sni,
|
|
467
|
-
server_name=server_name
|
|
468
|
-
)
|
|
469
|
-
|
|
470
|
-
@staticmethod
|
|
471
|
-
def development() -> 'SslConfig':
|
|
472
|
-
"""Create SSL config for development (trusts all certificates).
|
|
473
|
-
|
|
474
|
-
Warning: This configuration is insecure and should only be used in development.
|
|
475
|
-
"""
|
|
476
|
-
config = SslConfig.__new__(SslConfig)
|
|
477
|
-
config._config = _core.SslConfig.development()
|
|
478
|
-
return config
|
|
479
|
-
|
|
480
|
-
@staticmethod
|
|
481
|
-
def with_ca_certificate(ca_cert_path: str) -> 'SslConfig':
|
|
482
|
-
"""Create SSL config for production with custom CA certificate.
|
|
483
|
-
|
|
484
|
-
Args:
|
|
485
|
-
ca_cert_path: Path to CA certificate file (.pem, .crt, or .der)
|
|
486
|
-
"""
|
|
487
|
-
config = SslConfig.__new__(SslConfig)
|
|
488
|
-
config._config = _core.SslConfig.with_ca_certificate(ca_cert_path)
|
|
489
|
-
return config
|
|
490
|
-
|
|
491
|
-
@staticmethod
|
|
492
|
-
def login_only() -> 'SslConfig':
|
|
493
|
-
"""Create SSL config that only encrypts login (legacy mode)."""
|
|
494
|
-
config = SslConfig.__new__(SslConfig)
|
|
495
|
-
config._config = _core.SslConfig.login_only()
|
|
496
|
-
return config
|
|
497
|
-
|
|
498
|
-
@staticmethod
|
|
499
|
-
def disabled() -> 'SslConfig':
|
|
500
|
-
"""Create SSL config with no encryption (not recommended)."""
|
|
501
|
-
config = SslConfig.__new__(SslConfig)
|
|
502
|
-
config._config = _core.SslConfig.disabled()
|
|
503
|
-
return config
|
|
504
|
-
|
|
505
|
-
@property
|
|
506
|
-
def encryption_level(self) -> str:
|
|
507
|
-
"""Get the encryption level."""
|
|
508
|
-
return str(self._config.encryption_level)
|
|
509
|
-
|
|
510
|
-
@property
|
|
511
|
-
def trust_server_certificate(self) -> bool:
|
|
512
|
-
"""Get trust server certificate setting."""
|
|
513
|
-
return self._config.trust_server_certificate
|
|
514
|
-
|
|
515
|
-
@property
|
|
516
|
-
def ca_certificate_path(self) -> Optional[str]:
|
|
517
|
-
"""Get CA certificate path."""
|
|
518
|
-
return self._config.ca_certificate_path
|
|
519
|
-
|
|
520
|
-
@property
|
|
521
|
-
def enable_sni(self) -> bool:
|
|
522
|
-
"""Get SNI setting."""
|
|
523
|
-
return self._config.enable_sni
|
|
524
|
-
|
|
525
|
-
@property
|
|
526
|
-
def server_name(self) -> Optional[str]:
|
|
527
|
-
"""Get custom server name."""
|
|
528
|
-
return self._config.server_name
|
|
529
|
-
|
|
530
|
-
def __repr__(self) -> str:
|
|
531
|
-
return f"SslConfig(encryption={self.encryption_level}, trust_cert={self.trust_server_certificate})"
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
class Connection:
|
|
535
|
-
"""Async connection to Microsoft SQL Server database with enhanced type support."""
|
|
536
|
-
|
|
537
|
-
def __init__(
|
|
538
|
-
self,
|
|
539
|
-
connection_string: Optional[str] = None,
|
|
540
|
-
pool_config: Optional[PoolConfig] = None,
|
|
541
|
-
ssl_config: Optional['SslConfig'] = None,
|
|
542
|
-
auto_connect: bool = False,
|
|
543
|
-
server: Optional[str] = None,
|
|
544
|
-
database: Optional[str] = None,
|
|
545
|
-
username: Optional[str] = None,
|
|
546
|
-
password: Optional[str] = None,
|
|
547
|
-
trusted_connection: Optional[bool] = None
|
|
548
|
-
):
|
|
549
|
-
"""Initialize a new async connection.
|
|
550
|
-
|
|
551
|
-
Args:
|
|
552
|
-
connection_string: SQL Server connection string (if not using individual parameters)
|
|
553
|
-
pool_config: Optional connection pool configuration
|
|
554
|
-
ssl_config: Optional SSL/TLS configuration
|
|
555
|
-
auto_connect: If True, automatically connect on creation (not supported for async)
|
|
556
|
-
server: Database server hostname or IP address
|
|
557
|
-
database: Database name to connect to
|
|
558
|
-
username: Username for SQL Server authentication
|
|
559
|
-
password: Password for SQL Server authentication
|
|
560
|
-
trusted_connection: Use Windows integrated authentication (default: True if no username provided)
|
|
561
|
-
|
|
562
|
-
Note:
|
|
563
|
-
Either connection_string OR server must be provided.
|
|
564
|
-
If using individual parameters, server is required.
|
|
565
|
-
If username is provided, password should also be provided for SQL authentication.
|
|
566
|
-
If username is not provided, Windows integrated authentication will be used.
|
|
567
|
-
"""
|
|
568
|
-
py_pool_config = pool_config._config if pool_config else None
|
|
569
|
-
py_ssl_config = ssl_config._config if ssl_config else None
|
|
570
|
-
self._conn = _core.Connection(
|
|
571
|
-
connection_string,
|
|
572
|
-
py_pool_config,
|
|
573
|
-
py_ssl_config,
|
|
574
|
-
server,
|
|
575
|
-
database,
|
|
576
|
-
username,
|
|
577
|
-
password,
|
|
578
|
-
trusted_connection
|
|
579
|
-
)
|
|
580
|
-
self._connected = False
|
|
581
|
-
if auto_connect:
|
|
582
|
-
# Note: Can't await in __init__, so auto_connect won't work for async
|
|
583
|
-
pass
|
|
584
|
-
|
|
585
|
-
async def connect(self) -> None:
|
|
586
|
-
"""Connect to the database asynchronously."""
|
|
587
|
-
await self._conn.connect()
|
|
588
|
-
self._connected = True
|
|
589
|
-
|
|
590
|
-
async def disconnect(self) -> None:
|
|
591
|
-
"""Disconnect from the database asynchronously."""
|
|
592
|
-
await self._conn.disconnect()
|
|
593
|
-
self._connected = False
|
|
594
|
-
|
|
595
|
-
async def is_connected(self) -> bool:
|
|
596
|
-
"""Check if connected to the database."""
|
|
597
|
-
return await self._conn.is_connected()
|
|
598
|
-
|
|
599
|
-
async def execute(self, sql: str, parameters: Optional[Union[List[Any], Parameters, Iterable[Any]]] = None) -> ExecutionResult:
|
|
600
|
-
"""Execute a query asynchronously and return enhanced results.
|
|
601
|
-
|
|
602
|
-
Args:
|
|
603
|
-
sql: SQL query to execute (must be non-empty)
|
|
604
|
-
parameters: Optional parameters - can be:
|
|
605
|
-
- List of values for @P1 placeholders
|
|
606
|
-
- Parameters object for more control
|
|
607
|
-
- Any iterable of values (tuple, set, generator, etc.)
|
|
608
|
-
|
|
609
|
-
Returns:
|
|
610
|
-
ExecutionResult object with rows or affected row count
|
|
611
|
-
|
|
612
|
-
Raises:
|
|
613
|
-
RuntimeError: If not connected to database
|
|
614
|
-
ValueError: If sql is empty or None
|
|
615
|
-
|
|
616
|
-
Examples:
|
|
617
|
-
# Simple list of parameters
|
|
618
|
-
result = await conn.execute("SELECT * FROM users WHERE age > @P1 AND name = @P2", [18, "John"])
|
|
619
|
-
|
|
620
|
-
# Using tuple
|
|
621
|
-
result = await conn.execute("SELECT * FROM users WHERE age > @P1 AND name = @P2", (18, "John"))
|
|
622
|
-
|
|
623
|
-
# Automatic IN clause expansion (handled by Rust for performance)
|
|
624
|
-
result = await conn.execute("SELECT * FROM users WHERE id IN (@P1)", [[1, 2, 3, 4]])
|
|
625
|
-
# Rust automatically expands to: WHERE id IN (@P1, @P2, @P3, @P4)
|
|
626
|
-
|
|
627
|
-
# Using Parameters with automatic IN clause expansion
|
|
628
|
-
params = Parameters([1, 2, 3, 4], "John")
|
|
629
|
-
result = await conn.execute("SELECT * FROM users WHERE id IN (@P1) AND name = @P2", params)
|
|
630
|
-
# Rust expands the list automatically
|
|
631
|
-
|
|
632
|
-
# Using Parameters with type hints
|
|
633
|
-
params = Parameters(Parameter([1, 2, 3, 4], "INT"), "John")
|
|
634
|
-
result = await conn.execute("SELECT * FROM users WHERE id IN (@P1) AND name = @P2", params)
|
|
635
|
-
|
|
636
|
-
# Method chaining with iterables
|
|
637
|
-
params = Parameters().add(18, "INT").add(["admin", "user"])
|
|
638
|
-
result = await conn.execute("SELECT * FROM users WHERE age > @P1 AND role IN (@P2)", params)
|
|
639
|
-
"""
|
|
640
|
-
if not sql or not sql.strip():
|
|
641
|
-
raise ValueError("SQL query cannot be empty or None")
|
|
642
|
-
|
|
643
|
-
if not self._connected:
|
|
644
|
-
raise RuntimeError("Not connected to database. Call await conn.connect() first.")
|
|
645
|
-
|
|
646
|
-
if parameters is None:
|
|
647
|
-
py_result = await self._conn.execute(sql)
|
|
648
|
-
elif isinstance(parameters, Parameters):
|
|
649
|
-
# Convert Parameters object to list of values
|
|
650
|
-
param_values = parameters.to_list()
|
|
651
|
-
py_result = await self._conn.execute_with_python_params(sql, param_values)
|
|
652
|
-
elif hasattr(parameters, '__iter__') and not isinstance(parameters, (str, bytes)):
|
|
653
|
-
# Handle any iterable (list, tuple, set, generator, etc.)
|
|
654
|
-
# Convert to list to ensure we can pass it to the Rust layer
|
|
655
|
-
param_values = list(parameters)
|
|
656
|
-
py_result = await self._conn.execute_with_python_params(sql, param_values)
|
|
657
|
-
else:
|
|
658
|
-
# Single value - wrap in list
|
|
659
|
-
py_result = await self._conn.execute_with_python_params(sql, [parameters])
|
|
660
|
-
|
|
661
|
-
return ExecutionResult(py_result)
|
|
662
|
-
|
|
663
|
-
async def __aenter__(self):
|
|
664
|
-
"""Async context manager entry."""
|
|
665
|
-
await self.connect()
|
|
666
|
-
return self
|
|
667
|
-
|
|
668
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
669
|
-
"""Async context manager exit."""
|
|
670
|
-
await self.disconnect()
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
def version() -> str:
|
|
674
|
-
"""Get the version of the mssql-python-rust library.
|
|
675
|
-
|
|
676
|
-
Returns:
|
|
677
|
-
Version string
|
|
678
|
-
"""
|
|
679
|
-
return _core.version()
|
|
680
|
-
|
|
681
|
-
# Re-export core types for direct access if needed
|
|
682
|
-
RustConnection = _core.Connection # Rename to avoid conflict with our main connect() function
|
|
683
|
-
RustQuery = _core.Query # Rename to avoid conflict with our wrapper
|
|
684
|
-
PyRow = _core.Row
|
|
685
|
-
PyValue = _core.Value
|
|
686
|
-
PyExecutionResult = _core.ExecutionResult
|
|
687
|
-
PyQuery = _core.Query
|
|
688
|
-
|
|
689
|
-
# Export main API
|
|
690
|
-
__all__ = [
|
|
691
|
-
'Connection', # Main connection class
|
|
692
|
-
'Parameter', # Individual parameter with optional type
|
|
693
|
-
'Parameters', # Parameter container for execute()
|
|
694
|
-
'Row',
|
|
695
|
-
'ExecutionResult',
|
|
696
|
-
'PoolConfig',
|
|
697
|
-
'SslConfig', # SSL/TLS configuration
|
|
698
|
-
'version', # Version function
|
|
699
|
-
# Core types for advanced usage
|
|
700
|
-
'RustConnection',
|
|
701
|
-
'RustQuery',
|
|
702
|
-
'PyRow',
|
|
703
|
-
'PyValue',
|
|
704
|
-
'PyExecutionResult',
|
|
705
|
-
'PyQuery'
|
|
706
|
-
]
|
fastmssql-0.2.4.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
fastmssql/fastmssql_core.cpython-312-darwin.so,sha256=Ik79tj1zSuTepsmsk0NdZVrO53s7DcNTgDpvHwWkesU,4059904
|
|
2
|
-
fastmssql/__init__.py,sha256=Sfm-Ven3d_G4dhitAIhWkL9ox-JqXue23nl3-HveA74,607
|
|
3
|
-
fastmssql/fastmssql.py,sha256=kdRlcP-eFWVlcggsmHM33Y7MDZDa8qiIto0r3lsdGr0,26247
|
|
4
|
-
fastmssql-0.2.4.dist-info/RECORD,,
|
|
5
|
-
fastmssql-0.2.4.dist-info/WHEEL,sha256=EhaWXx4fd8VOPM6W-6pxsePGk73OLk2gBi7fwS90pc8,104
|
|
6
|
-
fastmssql-0.2.4.dist-info/METADATA,sha256=XsrLsF1emyatNZss-FjvNMcjPr66bhlOQ-a8Ziq2vRQ,29426
|
|
7
|
-
fastmssql-0.2.4.dist-info/licenses/LICENSE,sha256=NwufX3BNj7RvVtnrshWhkpFOLvWc_YVpGpr3UZGFz_E,4765
|
|
File without changes
|
|
File without changes
|