fastmssql 0.2.2__cp313-cp313-macosx_11_0_arm64.whl → 0.3.0__cp313-cp313-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 +689 -11
- fastmssql/fastmssql.cpython-313-darwin.so +0 -0
- {fastmssql-0.2.2.dist-info → fastmssql-0.3.0.dist-info}/METADATA +243 -259
- fastmssql-0.3.0.dist-info/RECORD +6 -0
- {fastmssql-0.2.2.dist-info → fastmssql-0.3.0.dist-info}/WHEEL +2 -0
- fastmssql-0.3.0.dist-info/licenses/LICENSE +675 -0
- fastmssql/fastmssql.py +0 -706
- fastmssql/fastmssql_core.cpython-313-darwin.so +0 -0
- fastmssql-0.2.2.dist-info/RECORD +0 -7
- fastmssql-0.2.2.dist-info/licenses/LICENSE +0 -139
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmssql
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
|
-
Classifier: License ::
|
|
6
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
7
7
|
Classifier: Programming Language :: Python :: 3
|
|
8
8
|
Classifier: Programming Language :: Python :: 3.8
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -21,7 +21,7 @@ Provides-Extra: dev
|
|
|
21
21
|
License-File: LICENSE
|
|
22
22
|
Summary: A high-performance Python library for Microsoft SQL Server using Rust and Tiberius
|
|
23
23
|
Author-email: Riveranda <riverb514@gmail.com>
|
|
24
|
-
License:
|
|
24
|
+
License: GPL-3.0-or-later OR Commercial
|
|
25
25
|
Requires-Python: >=3.8
|
|
26
26
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
27
27
|
Project-URL: Homepage, https://github.com/Rivendael/pymssql-rs
|
|
@@ -29,59 +29,73 @@ 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)
|
|
36
|
+
[](https://github.com/Rivendael/pymssql-rs)
|
|
41
37
|
|
|
42
38
|
## Features
|
|
43
39
|
|
|
44
|
-
- **High Performance**:
|
|
40
|
+
- **High Performance**: Exceptional throughput with 17,800+ RPS capability
|
|
41
|
+
- **Rust-Powered Backend**: Built with Rust for memory safety and reliability
|
|
45
42
|
- **No ODBC Required**: Direct native SQL Server connection without ODBC drivers
|
|
46
|
-
- **Connection Pooling**:
|
|
47
|
-
- **Async-Only Design**: Built on Tokio
|
|
43
|
+
- **Connection Pooling**: Intelligent bb8-based connection pooling (default: 75 max, 25 min idle)
|
|
44
|
+
- **Async-Only Design**: Built on Tokio with clean async/await API
|
|
48
45
|
- **Context Managers**: Automatic resource management with `async with`
|
|
49
46
|
- **Type Safety**: Strong typing with automatic Python type conversion
|
|
50
|
-
- **Thread Safety**:
|
|
47
|
+
- **Thread Safety**: Support for concurrent operations
|
|
51
48
|
- **Cross-Platform**: Works on Windows, macOS, and Linux
|
|
52
|
-
- **Simple API**: Clean, intuitive async-only interface
|
|
49
|
+
- **Simple API**: Clean, intuitive async-only interface with separate `query()` and `execute()` methods
|
|
50
|
+
|
|
51
|
+
## Key API Methods
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
FastMSSQL provides two distinct methods for database operations:
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
- **`query()`** - For SELECT statements that return rows
|
|
56
|
+
- **`execute()`** - For INSERT/UPDATE/DELETE statements that return affected row count
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# Use query() for SELECT statements
|
|
60
|
+
result = await conn.query("SELECT * FROM users WHERE age > @P1", [25])
|
|
61
|
+
rows = result.rows()
|
|
57
62
|
|
|
58
|
-
|
|
63
|
+
# Use execute() for data modification
|
|
64
|
+
affected = await conn.execute("INSERT INTO users (name) VALUES (@P1)", ["John"])
|
|
65
|
+
```
|
|
59
66
|
|
|
60
|
-
|
|
61
|
-
|---------|----------------------|--------------------------|
|
|
62
|
-
| **fastmssql** | **3,493** | **Baseline** |
|
|
63
|
-
| Other Python libraries | ~300-600* | **5-11x slower** |
|
|
67
|
+
## Performance Highlights
|
|
64
68
|
|
|
65
|
-
|
|
69
|
+
Fastmssql delivers exceptional database performance through Rust-powered architecture:
|
|
66
70
|
|
|
67
|
-
|
|
71
|
+
- **Outstanding Throughput**: Up to **17,800+ RPS** with multiple connection pattern
|
|
72
|
+
- **High Performance**: **5,000+ RPS** with single connection (default usage)
|
|
73
|
+
- **Low Latency**: ~2ms average query latency under high load
|
|
74
|
+
- **Scalable Architecture**: Linear scaling with multiple connection objects
|
|
75
|
+
- **Production Ready**: Stable API with comprehensive error handling
|
|
76
|
+
- **Connection Pooling**: Efficient resource management with configurable pools
|
|
77
|
+
- **Type Conversion**: Automatic conversion between SQL Server and Python types
|
|
78
|
+
- **SSL/TLS Support**: Secure connections with flexible encryption options
|
|
68
79
|
|
|
69
|
-
|
|
80
|
+
### Performance Benchmarks
|
|
70
81
|
|
|
71
|
-
|
|
|
72
|
-
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
| **Memory Leaks** | **None** | Actually reduces memory over time |
|
|
82
|
+
| Pattern | RPS | Configuration | Use Case |
|
|
83
|
+
|---------|-----|---------------|----------|
|
|
84
|
+
| Single Connection (Default) | **5,000+** | Default pool (75/25) | Standard applications |
|
|
85
|
+
| Multiple Connections | **17,800+** | 50 workers, high_throughput() | High-concurrency scenarios |
|
|
86
|
+
| Conservative Load | 3,500+ | Shared connection | Traditional pooling |
|
|
77
87
|
|
|
78
|
-
**
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
88
|
+
**Benchmark Environment:**
|
|
89
|
+
- Database: SQL Server (local instance)
|
|
90
|
+
- Query: `SELECT 1` (minimal overhead)
|
|
91
|
+
- Test Duration: 15-30 seconds per test
|
|
92
|
+
- Hardware: Modern development machine
|
|
83
93
|
|
|
84
|
-
|
|
94
|
+
**Key Performance Insights:**
|
|
95
|
+
1. **Multiple Connection Objects**: Creating separate `Connection()` objects eliminates serialization bottlenecks
|
|
96
|
+
2. **Pool Configuration**: Use `PoolConfig.high_throughput()` for demanding workloads
|
|
97
|
+
3. **Optimal Worker Count**: 30-50 concurrent workers provides best throughput
|
|
98
|
+
4. **Tokio Optimization**: Aggressive threading configuration maximizes async performance
|
|
85
99
|
|
|
86
100
|
## Installation
|
|
87
101
|
|
|
@@ -115,8 +129,6 @@ cd mssql-python-rust
|
|
|
115
129
|
|
|
116
130
|
## Quick Start
|
|
117
131
|
|
|
118
|
-
> **💡 Performance Tip**: Always reuse `Connection` objects! Each `Connection` manages a connection pool, so creating multiple `Connection` instances defeats the purpose of pooling and hurts performance. Create one `Connection` per database and reuse it throughout your application.
|
|
119
|
-
|
|
120
132
|
### Basic Async Usage (Recommended)
|
|
121
133
|
|
|
122
134
|
```python
|
|
@@ -129,7 +141,9 @@ async def main():
|
|
|
129
141
|
|
|
130
142
|
# Automatic connection pool management
|
|
131
143
|
async with Connection(connection_string) as conn:
|
|
132
|
-
|
|
144
|
+
# Use query() for SELECT statements that return rows
|
|
145
|
+
result = await conn.query("SELECT @@VERSION as version")
|
|
146
|
+
rows = result.rows()
|
|
133
147
|
for row in rows:
|
|
134
148
|
print(row['version'])
|
|
135
149
|
|
|
@@ -155,8 +169,10 @@ async def main():
|
|
|
155
169
|
connection_string = "Server=localhost;Database=master;User Id=myuser;Password=mypass"
|
|
156
170
|
|
|
157
171
|
async with Connection(connection_string=connection_string) as conn:
|
|
158
|
-
|
|
159
|
-
|
|
172
|
+
# Use query() for SELECT statements that return rows
|
|
173
|
+
result = await conn.query("SELECT @@VERSION as version")
|
|
174
|
+
rows = result.rows()
|
|
175
|
+
for row in rows:
|
|
160
176
|
print(row['version'])
|
|
161
177
|
|
|
162
178
|
asyncio.run(main())
|
|
@@ -178,13 +194,50 @@ async def main():
|
|
|
178
194
|
username="myuser",
|
|
179
195
|
password="mypassword"
|
|
180
196
|
) as conn:
|
|
181
|
-
|
|
182
|
-
|
|
197
|
+
# Use query() for SELECT statements that return rows
|
|
198
|
+
result = await conn.query("SELECT SUSER_NAME() as login")
|
|
199
|
+
rows = result.rows()
|
|
200
|
+
for row in rows:
|
|
183
201
|
print(f"Logged in as: {row['login']}")
|
|
184
202
|
|
|
185
203
|
asyncio.run(main())
|
|
186
204
|
```
|
|
187
205
|
|
|
206
|
+
### Performance Patterns
|
|
207
|
+
|
|
208
|
+
For maximum performance in high-concurrency scenarios, create multiple Connection objects:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
import asyncio
|
|
212
|
+
from fastmssql import Connection, PoolConfig
|
|
213
|
+
|
|
214
|
+
async def high_performance_pattern():
|
|
215
|
+
"""Optimal pattern for maximum throughput."""
|
|
216
|
+
connection_string = "Server=localhost;Database=master;User Id=myuser;Password=mypass"
|
|
217
|
+
config = PoolConfig.high_throughput() # Optimized settings
|
|
218
|
+
|
|
219
|
+
async def worker():
|
|
220
|
+
# Each worker gets its own Connection object for maximum throughput
|
|
221
|
+
async with Connection(connection_string, pool_config=config) as conn:
|
|
222
|
+
for _ in range(1000):
|
|
223
|
+
# Use query() for SELECT statements that return rows
|
|
224
|
+
result = await conn.query("SELECT data FROM my_table WHERE id = @P1", [123])
|
|
225
|
+
rows = result.rows()
|
|
226
|
+
# Process results...
|
|
227
|
+
|
|
228
|
+
# Launch multiple workers - each with their own connection
|
|
229
|
+
workers = [asyncio.create_task(worker()) for _ in range(50)]
|
|
230
|
+
await asyncio.gather(*workers)
|
|
231
|
+
|
|
232
|
+
# This pattern can achieve 17,800+ RPS
|
|
233
|
+
|
|
234
|
+
asyncio.run(high_performance_pattern())
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Key Performance Insight**: While a single Connection object provides excellent performance (5,000+ RPS),
|
|
238
|
+
creating multiple Connection objects eliminates serialization bottlenecks and can achieve 17,800+ RPS
|
|
239
|
+
for maximum throughput scenarios.
|
|
240
|
+
|
|
188
241
|
### Connection Pool Configuration
|
|
189
242
|
|
|
190
243
|
Configure the connection pool for your specific needs:
|
|
@@ -204,97 +257,36 @@ async def main():
|
|
|
204
257
|
)
|
|
205
258
|
|
|
206
259
|
async with Connection(connection_string, pool_config) as conn:
|
|
207
|
-
|
|
260
|
+
# Use query() for SELECT statements that return rows
|
|
261
|
+
result = await conn.query("SELECT * FROM users")
|
|
262
|
+
rows = result.rows()
|
|
263
|
+
for row in rows:
|
|
264
|
+
print(f"User: {row['name']}")
|
|
208
265
|
|
|
209
|
-
# Predefined configurations for
|
|
210
|
-
high_throughput_config = PoolConfig.high_throughput()
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
### 🔥 Maximum Performance Configuration
|
|
218
|
-
|
|
219
|
-
For applications requiring **extreme performance**, use the high-throughput configuration:
|
|
220
|
-
|
|
221
|
-
```python
|
|
222
|
-
import asyncio
|
|
223
|
-
from fastmssql import Connection, PoolConfig
|
|
224
|
-
|
|
225
|
-
async def main():
|
|
226
|
-
# Maximum performance setup
|
|
227
|
-
extreme_config = PoolConfig.high_throughput() # Optimized for 3000+ RPS
|
|
266
|
+
# Predefined configurations for different scenarios
|
|
267
|
+
high_throughput_config = PoolConfig.high_throughput() # 100 connections, optimized for high RPS
|
|
268
|
+
maximum_performance = PoolConfig.maximum_performance() # 150 connections, optimized for peak load
|
|
269
|
+
low_resource_config = PoolConfig.low_resource() # 3 connections, minimal resources
|
|
270
|
+
dev_config = PoolConfig.development() # 5 connections, shorter timeouts
|
|
271
|
+
|
|
272
|
+
# Default configuration is optimized for high performance
|
|
273
|
+
# Default: max_size=75, min_idle=25 - ready for production workloads
|
|
228
274
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
result = await conn.execute("SELECT @@VERSION")
|
|
275
|
+
# For maximum throughput, use multiple Connection objects:
|
|
276
|
+
async def high_perf_worker():
|
|
277
|
+
async with Connection(connection_string, maximum_performance) as conn:
|
|
278
|
+
# Use query() for SELECT statements that return rows
|
|
279
|
+
result = await conn.query("SELECT * FROM fast_table")
|
|
235
280
|
return result.rows()
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
# Pool monitoring
|
|
242
|
-
stats = await conn.pool_stats()
|
|
243
|
-
print(f"Pool utilization: {stats['active_connections']}/{stats['max_size']}")
|
|
281
|
+
|
|
282
|
+
# Each worker gets its own connection for optimal performance
|
|
283
|
+
tasks = [asyncio.create_task(high_perf_worker()) for _ in range(50)]
|
|
284
|
+
results = await asyncio.gather(*tasks)
|
|
244
285
|
|
|
245
286
|
asyncio.run(main())
|
|
246
287
|
```
|
|
247
288
|
|
|
248
|
-
**Performance Tips:**
|
|
249
|
-
- **Reuse Connection objects**: Create one `Connection` per database and reuse it across your application
|
|
250
|
-
- Use `PoolConfig.high_throughput()` for maximum RPS
|
|
251
|
-
- Leverage `asyncio.gather()` for concurrent operations
|
|
252
|
-
- Monitor pool stats to optimize connection count
|
|
253
|
-
- Consider connection lifetime for long-running applications
|
|
254
289
|
|
|
255
|
-
**⚠️ Performance Anti-Pattern:**
|
|
256
|
-
```python
|
|
257
|
-
# DON'T DO THIS - Creates new pool for each operation
|
|
258
|
-
async def bad_example():
|
|
259
|
-
async with Connection(conn_str) as conn: # New pool created
|
|
260
|
-
return await conn.execute("SELECT 1")
|
|
261
|
-
|
|
262
|
-
async with Connection(conn_str) as conn: # Another new pool created
|
|
263
|
-
return await conn.execute("SELECT 2")
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
**✅ Correct Pattern:**
|
|
267
|
-
```python
|
|
268
|
-
# DO THIS - Reuse the same connection pool
|
|
269
|
-
async def good_example():
|
|
270
|
-
async with Connection(conn_str) as conn: # Single pool created
|
|
271
|
-
result1 = await conn.execute("SELECT 1")
|
|
272
|
-
result2 = await conn.execute("SELECT 2") # Reuses same pool
|
|
273
|
-
return result1, result2
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
### Connection Pool Benefits
|
|
277
|
-
|
|
278
|
-
The bb8 connection pool provides significant performance improvements over traditional Python libraries:
|
|
279
|
-
|
|
280
|
-
| Metric | Traditional Python | fastmssql | Improvement |
|
|
281
|
-
|--------|-------------------|-----------|-------------|
|
|
282
|
-
| **Connection Setup** | 50ms | 0.1ms | **500x faster** |
|
|
283
|
-
| **Memory per Query** | 50-200 KB | 0.08 KB | **625-2500x less** |
|
|
284
|
-
| **10 Concurrent Queries** | 500ms | 150ms | **3.3x faster** |
|
|
285
|
-
| **100 Concurrent Queries** | 5000ms | 400ms | **12.5x faster** |
|
|
286
|
-
| **1000 Concurrent Queries** | Timeouts/Errors | 2.9s | **Stable** |
|
|
287
|
-
| **Memory Leaks** | Common | None | **Zero leaks** |
|
|
288
|
-
|
|
289
|
-
**Key Benefits:**
|
|
290
|
-
- **Connection Reuse**: Eliminates connection establishment overhead (500x improvement)
|
|
291
|
-
- **Memory Efficiency**: Uses 625-2500x less memory per operation (0.08 KB vs 50-200 KB)
|
|
292
|
-
- **Zero Memory Leaks**: Automatic cleanup with decreasing memory usage over time
|
|
293
|
-
- **Concurrency**: Safe multi-threaded access with automatic pooling
|
|
294
|
-
- **Resource Management**: Intelligent memory and connection lifecycle management
|
|
295
|
-
- **Load Balancing**: Intelligent connection distribution across threads
|
|
296
|
-
- **Fault Tolerance**: Built-in retry logic and connection health checks
|
|
297
|
-
- **Timeouts**: Configurable timeouts prevent hanging connections
|
|
298
290
|
|
|
299
291
|
### Connection Strings
|
|
300
292
|
|
|
@@ -319,21 +311,37 @@ from fastmssql import Connection
|
|
|
319
311
|
|
|
320
312
|
async def main():
|
|
321
313
|
async with Connection(connection_string) as conn:
|
|
322
|
-
#
|
|
323
|
-
|
|
314
|
+
# === SELECT QUERIES - Use query() method (returns rows) ===
|
|
315
|
+
result = await conn.query("SELECT id, name, email FROM users WHERE active = 1")
|
|
316
|
+
rows = result.rows()
|
|
324
317
|
|
|
325
318
|
# Iterate through results
|
|
326
|
-
for
|
|
327
|
-
print(f"User {
|
|
319
|
+
for row in rows:
|
|
320
|
+
print(f"User {row['id']}: {row['name']} ({row['email']})")
|
|
321
|
+
|
|
322
|
+
# === DATA MODIFICATION - Use execute() method (returns affected row count) ===
|
|
323
|
+
# INSERT operation
|
|
324
|
+
rows_affected = await conn.execute(
|
|
325
|
+
"INSERT INTO users (name, email, active) VALUES (@P1, @P2, @P3)",
|
|
326
|
+
["John Doe", "john@example.com", 1]
|
|
327
|
+
)
|
|
328
|
+
print(f"Inserted {rows_affected} row(s)")
|
|
328
329
|
|
|
329
|
-
#
|
|
330
|
-
rows_affected = await conn.
|
|
331
|
-
"UPDATE users SET last_login = GETDATE() WHERE id =
|
|
330
|
+
# UPDATE operation
|
|
331
|
+
rows_affected = await conn.execute(
|
|
332
|
+
"UPDATE users SET last_login = GETDATE() WHERE id = @P1",
|
|
333
|
+
[123]
|
|
332
334
|
)
|
|
333
|
-
print(f"Updated {rows_affected}
|
|
335
|
+
print(f"Updated {rows_affected} row(s)")
|
|
334
336
|
|
|
335
|
-
#
|
|
336
|
-
|
|
337
|
+
# DELETE operation
|
|
338
|
+
rows_affected = await conn.execute(
|
|
339
|
+
"DELETE FROM users WHERE active = 0 AND last_login < DATEADD(year, -1, GETDATE())"
|
|
340
|
+
)
|
|
341
|
+
print(f"Deleted {rows_affected} inactive users")
|
|
342
|
+
|
|
343
|
+
# === WORKING WITH DIFFERENT DATA TYPES ===
|
|
344
|
+
result = await conn.query("""
|
|
337
345
|
SELECT
|
|
338
346
|
42 as int_value,
|
|
339
347
|
3.14159 as float_value,
|
|
@@ -343,9 +351,11 @@ async def main():
|
|
|
343
351
|
NULL as null_value
|
|
344
352
|
""")
|
|
345
353
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
354
|
+
rows = result.rows()
|
|
355
|
+
if rows:
|
|
356
|
+
row = rows[0]
|
|
357
|
+
for column_name, value in row.items():
|
|
358
|
+
print(f"{column_name}: {value} (type: {type(value).__name__})")
|
|
349
359
|
|
|
350
360
|
asyncio.run(main())
|
|
351
361
|
```
|
|
@@ -365,7 +375,9 @@ async def main():
|
|
|
365
375
|
|
|
366
376
|
# Async context manager with automatic pool management
|
|
367
377
|
async with Connection(connection_string) as conn:
|
|
368
|
-
|
|
378
|
+
# Use query() for SELECT statements that return rows
|
|
379
|
+
result = await conn.query("SELECT name FROM sys.databases")
|
|
380
|
+
rows = result.rows()
|
|
369
381
|
for row in rows:
|
|
370
382
|
print(row['name'])
|
|
371
383
|
|
|
@@ -376,7 +388,9 @@ async def main():
|
|
|
376
388
|
# High-performance concurrent operations
|
|
377
389
|
async def fetch_user_data(user_id):
|
|
378
390
|
async with Connection(connection_string) as conn:
|
|
379
|
-
|
|
391
|
+
# Use query() for SELECT statements that return rows
|
|
392
|
+
result = await conn.query(f"SELECT * FROM users WHERE id = {user_id}")
|
|
393
|
+
return result.rows()
|
|
380
394
|
|
|
381
395
|
# Execute multiple queries concurrently using the connection pool
|
|
382
396
|
user_ids = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
@@ -390,89 +404,6 @@ async def main():
|
|
|
390
404
|
asyncio.run(main())
|
|
391
405
|
```
|
|
392
406
|
|
|
393
|
-
### Performance Comparison: bb8 Connection Pool
|
|
394
|
-
|
|
395
|
-
The bb8 connection pool dramatically improves performance, especially under load:
|
|
396
|
-
|
|
397
|
-
```python
|
|
398
|
-
import asyncio
|
|
399
|
-
import time
|
|
400
|
-
from fastmssql import Connection
|
|
401
|
-
|
|
402
|
-
async def performance_comparison():
|
|
403
|
-
connection_string = "Server=localhost;Database=test;User Id=myuser;Password=mypass"
|
|
404
|
-
|
|
405
|
-
# Sequential async operations (still efficient with pool reuse)
|
|
406
|
-
start = time.time()
|
|
407
|
-
async with Connection(connection_string) as conn:
|
|
408
|
-
for i in range(10):
|
|
409
|
-
result = await conn.execute("SELECT COUNT(*) FROM users")
|
|
410
|
-
sequential_time = time.time() - start
|
|
411
|
-
|
|
412
|
-
# Concurrent async operations (much faster with bb8 pool)
|
|
413
|
-
start = time.time()
|
|
414
|
-
async def concurrent_queries():
|
|
415
|
-
tasks = []
|
|
416
|
-
for i in range(10):
|
|
417
|
-
async def query():
|
|
418
|
-
async with Connection(connection_string) as conn: # Pool reuse
|
|
419
|
-
return await conn.execute("SELECT COUNT(*) FROM users")
|
|
420
|
-
tasks.append(query())
|
|
421
|
-
return await asyncio.gather(*tasks)
|
|
422
|
-
|
|
423
|
-
await concurrent_queries()
|
|
424
|
-
concurrent_time = time.time() - start
|
|
425
|
-
|
|
426
|
-
print(f"Sequential: {sequential_time:.3f}s")
|
|
427
|
-
print(f"Concurrent: {concurrent_time:.3f}s")
|
|
428
|
-
print(f"Improvement: {sequential_time/concurrent_time:.1f}x faster")
|
|
429
|
-
|
|
430
|
-
asyncio.run(performance_comparison())
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
**Real-world Performance Benefits:**
|
|
434
|
-
- **Web Applications**: Handle 100+ concurrent requests without connection exhaustion
|
|
435
|
-
- **Batch Processing**: Process large datasets with optimal resource usage
|
|
436
|
-
- **Microservices**: Reliable database connections across service boundaries
|
|
437
|
-
- **Data Analytics**: Concurrent query execution for faster insights
|
|
438
|
-
|
|
439
|
-
## Examples
|
|
440
|
-
|
|
441
|
-
Run the provided examples to see async patterns and features:
|
|
442
|
-
|
|
443
|
-
```bash
|
|
444
|
-
# Basic asynchronous usage
|
|
445
|
-
python examples/basic_usage.py
|
|
446
|
-
|
|
447
|
-
# Advanced asynchronous features
|
|
448
|
-
python examples/advanced_usage.py
|
|
449
|
-
|
|
450
|
-
# Asynchronous usage patterns
|
|
451
|
-
python examples/async_usage.py
|
|
452
|
-
|
|
453
|
-
# Advanced pool configuration
|
|
454
|
-
python examples/advanced_pool_config.py
|
|
455
|
-
|
|
456
|
-
# Connection parameters demonstration
|
|
457
|
-
python examples/connection_parameters_demo.py
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
### Key API Improvements
|
|
461
|
-
|
|
462
|
-
Our async-only design provides a clean, intuitive interface:
|
|
463
|
-
|
|
464
|
-
```python
|
|
465
|
-
# ✅ Clean async API (New Design)
|
|
466
|
-
async with Connection(connection_string) as conn:
|
|
467
|
-
result = await conn.execute(sql) # Intuitive!
|
|
468
|
-
rows_affected = await conn.execute_non_query(sql)
|
|
469
|
-
|
|
470
|
-
# ❌ Old confusing API (Removed)
|
|
471
|
-
# async with Connection(connection_string) as conn:
|
|
472
|
-
# result = await conn.execute_async(sql) # Confusing suffixes
|
|
473
|
-
# rows_affected = await conn.execute_non_query_async(sql)
|
|
474
|
-
```
|
|
475
|
-
|
|
476
407
|
## Development
|
|
477
408
|
|
|
478
409
|
### Building from Source
|
|
@@ -526,6 +457,29 @@ python examples/advanced_usage.py
|
|
|
526
457
|
|
|
527
458
|
## API Reference
|
|
528
459
|
|
|
460
|
+
### Query vs Execute Methods
|
|
461
|
+
|
|
462
|
+
FastMSSQL provides two distinct methods for database operations:
|
|
463
|
+
|
|
464
|
+
- **`query()`** - For SELECT statements that return rows
|
|
465
|
+
- Returns a `QueryResult` object with a `rows()` method
|
|
466
|
+
- Use for retrieving data from the database
|
|
467
|
+
|
|
468
|
+
- **`execute()`** - For INSERT, UPDATE, DELETE statements
|
|
469
|
+
- Returns the number of affected rows as an integer
|
|
470
|
+
- Use for modifying data in the database
|
|
471
|
+
|
|
472
|
+
**SQL Server Parameter Syntax**: Use positional parameters `@P1`, `@P2`, `@P3`, etc.
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
# SELECT queries - use query()
|
|
476
|
+
result = await conn.query("SELECT * FROM users WHERE age > @P1 AND city = @P2", [25, "New York"])
|
|
477
|
+
rows = result.rows()
|
|
478
|
+
|
|
479
|
+
# INSERT/UPDATE/DELETE - use execute()
|
|
480
|
+
affected = await conn.execute("INSERT INTO users (name, email) VALUES (@P1, @P2)", ["John", "john@example.com"])
|
|
481
|
+
```
|
|
482
|
+
|
|
529
483
|
### Core Classes
|
|
530
484
|
|
|
531
485
|
#### `Connection`
|
|
@@ -538,21 +492,45 @@ Connection(connection_string: str, pool_config: Optional[PoolConfig] = None)
|
|
|
538
492
|
|
|
539
493
|
**Context Manager Support:**
|
|
540
494
|
```python
|
|
541
|
-
#
|
|
542
|
-
with Connection(conn_str) as conn:
|
|
543
|
-
result = conn.execute("SELECT * FROM table")
|
|
544
|
-
|
|
545
|
-
# Asynchronous
|
|
495
|
+
# Asynchronous (recommended)
|
|
546
496
|
async with Connection(conn_str) as conn:
|
|
547
|
-
|
|
497
|
+
# Use query() for SELECT statements
|
|
498
|
+
result = await conn.query("SELECT * FROM table")
|
|
499
|
+
rows = result.rows()
|
|
500
|
+
|
|
501
|
+
# Use execute() for INSERT/UPDATE/DELETE statements
|
|
502
|
+
affected = await conn.execute("INSERT INTO table (col) VALUES (@P1)", ["value"])
|
|
503
|
+
```
|
|
548
504
|
```
|
|
549
505
|
|
|
550
506
|
**Methods:**
|
|
551
|
-
- `
|
|
507
|
+
- `query(sql: str, params: Optional[List] = None) -> QueryResult` - Execute SELECT queries that return rows
|
|
508
|
+
- `execute(sql: str, params: Optional[List] = None) -> int` - Execute INSERT/UPDATE/DELETE statements, returns affected row count
|
|
552
509
|
- `pool_stats() -> dict` - Get connection pool statistics
|
|
553
510
|
- `disconnect()` - Close the connection pool
|
|
554
511
|
- `is_connected() -> bool` - Check if pool is active
|
|
555
512
|
|
|
513
|
+
**Method Details:**
|
|
514
|
+
|
|
515
|
+
`query()` - For SELECT statements that return data:
|
|
516
|
+
```python
|
|
517
|
+
# Returns a QueryResult object with rows() method
|
|
518
|
+
result = await conn.query("SELECT * FROM users WHERE age > @P1", [21])
|
|
519
|
+
rows = result.rows()
|
|
520
|
+
for row in rows:
|
|
521
|
+
print(row['name'])
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
`execute()` - For INSERT/UPDATE/DELETE statements:
|
|
525
|
+
```python
|
|
526
|
+
# Returns the number of affected rows
|
|
527
|
+
affected = await conn.execute("INSERT INTO users (name) VALUES (@P1)", ["John"])
|
|
528
|
+
print(f"Inserted {affected} row(s)")
|
|
529
|
+
|
|
530
|
+
affected = await conn.execute("UPDATE users SET age = @P1 WHERE name = @P2", [25, "John"])
|
|
531
|
+
print(f"Updated {affected} row(s)")
|
|
532
|
+
```
|
|
533
|
+
|
|
556
534
|
**Pool Statistics:**
|
|
557
535
|
```python
|
|
558
536
|
stats = conn.pool_stats()
|
|
@@ -603,15 +581,17 @@ Represents a database row with column access.
|
|
|
603
581
|
|
|
604
582
|
#### Connection Management
|
|
605
583
|
```python
|
|
606
|
-
# Create connection with
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
# Create async connection with default pool settings
|
|
610
|
-
connect_async(connection_string: str) -> Connection
|
|
584
|
+
# Create connection with connection pooling
|
|
585
|
+
Connection(connection_string: str, pool_config: Optional[PoolConfig] = None) -> Connection
|
|
611
586
|
|
|
612
|
-
#
|
|
613
|
-
|
|
614
|
-
|
|
587
|
+
# Usage with async context manager (recommended)
|
|
588
|
+
async with Connection(connection_string) as conn:
|
|
589
|
+
# Use query() for SELECT statements that return rows
|
|
590
|
+
result = await conn.query("SELECT * FROM users")
|
|
591
|
+
rows = result.rows()
|
|
592
|
+
|
|
593
|
+
# Use execute() for INSERT/UPDATE/DELETE statements that return affected count
|
|
594
|
+
affected = await conn.execute("INSERT INTO users (name) VALUES (@P1)", ["John"])
|
|
615
595
|
```
|
|
616
596
|
|
|
617
597
|
#### Utility Functions
|
|
@@ -634,7 +614,8 @@ The library uses the bb8 connection pool for efficient resource management:
|
|
|
634
614
|
```python
|
|
635
615
|
try:
|
|
636
616
|
async with mssql.connect_async(connection_string) as conn:
|
|
637
|
-
result = await conn.
|
|
617
|
+
result = await conn.query("SELECT * FROM invalid_table")
|
|
618
|
+
rows = result.rows()
|
|
638
619
|
except Exception as e:
|
|
639
620
|
print(f"Database error: {e}")
|
|
640
621
|
# Connection automatically returned to pool even on error
|
|
@@ -648,26 +629,14 @@ This library has been upgraded to use async-only operations with the bb8 connect
|
|
|
648
629
|
```python
|
|
649
630
|
# Async-only with automatic connection pooling
|
|
650
631
|
async with mssql.connect_async(conn_str) as conn:
|
|
651
|
-
result = await conn.
|
|
632
|
+
result = await conn.query("SELECT * FROM table")
|
|
633
|
+
rows = result.rows()
|
|
652
634
|
|
|
653
635
|
# Pool statistics
|
|
654
636
|
stats = conn.pool_stats()
|
|
655
637
|
print(f"Pool utilization: {stats['active_connections']}/{stats['connections']}")
|
|
656
638
|
```
|
|
657
639
|
|
|
658
|
-
**Features:**
|
|
659
|
-
- Async-only operations for maximum performance
|
|
660
|
-
- Automatic connection pooling with bb8
|
|
661
|
-
- Configurable pool settings via `PoolConfig`
|
|
662
|
-
- Pool statistics and monitoring
|
|
663
|
-
- Improved concurrent performance
|
|
664
|
-
- Better resource management
|
|
665
|
-
|
|
666
|
-
**Breaking Changes:**
|
|
667
|
-
- None - the API is fully backward compatible
|
|
668
|
-
- All existing code continues to work without modification
|
|
669
|
-
- Performance improvements are automatic
|
|
670
|
-
|
|
671
640
|
## Advanced Usage Patterns
|
|
672
641
|
|
|
673
642
|
### Custom Pool Configuration for Different Scenarios
|
|
@@ -733,7 +702,7 @@ async def oltp_operations():
|
|
|
733
702
|
async with mssql.connect_async(conn_str, oltp_config) as conn:
|
|
734
703
|
# Fast, concurrent transactions
|
|
735
704
|
tasks = [
|
|
736
|
-
conn.
|
|
705
|
+
conn.query("SELECT * FROM users WHERE id = @P1", [user_id])
|
|
737
706
|
for user_id in range(1, 101)
|
|
738
707
|
]
|
|
739
708
|
results = await asyncio.gather(*tasks)
|
|
@@ -743,7 +712,7 @@ olap_config = PoolConfig.low_resource()
|
|
|
743
712
|
async def olap_operations():
|
|
744
713
|
async with mssql.connect_async(conn_str, olap_config) as conn:
|
|
745
714
|
# Long-running analytical queries
|
|
746
|
-
|
|
715
|
+
result = await conn.query("""
|
|
747
716
|
SELECT
|
|
748
717
|
DATE_TRUNC('quarter', order_date) as quarter,
|
|
749
718
|
SUM(total_amount) as total_revenue,
|
|
@@ -753,10 +722,10 @@ async def olap_operations():
|
|
|
753
722
|
GROUP BY DATE_TRUNC('quarter', order_date)
|
|
754
723
|
ORDER BY quarter
|
|
755
724
|
""")
|
|
756
|
-
return
|
|
725
|
+
return result.rows()
|
|
757
726
|
```
|
|
758
727
|
|
|
759
|
-
##
|
|
728
|
+
## API Reference
|
|
760
729
|
|
|
761
730
|
### Common Issues
|
|
762
731
|
|
|
@@ -773,7 +742,22 @@ Contributions are welcome! Please open an issue or submit a pull request for any
|
|
|
773
742
|
|
|
774
743
|
## License
|
|
775
744
|
|
|
776
|
-
|
|
745
|
+
FastMSSQL is available under your choice of two licenses:
|
|
746
|
+
|
|
747
|
+
### Option 1: GNU General Public License v3.0 (GPL-3.0)
|
|
748
|
+
|
|
749
|
+
**Free for open source projects.** If you distribute your application, you must make your entire application open source under GPL-3.0.
|
|
750
|
+
|
|
751
|
+
### Option 2: Commercial License
|
|
752
|
+
|
|
753
|
+
**Paid license for proprietary applications.** Allows you to keep your application closed source. Contact riverb514@gmail.com for commercial licensing.
|
|
754
|
+
|
|
755
|
+
See the [LICENSE](LICENSE) file for full GPL-3.0 terms and commercial licensing details.
|
|
756
|
+
|
|
757
|
+
### Examples and Benchmarks
|
|
758
|
+
|
|
759
|
+
- **Examples Directory**: All files in the `examples/` directory are licensed under the MIT License. See `examples/LICENSE` for full terms.
|
|
760
|
+
- **Benchmarks Directory**: All files in the `benchmarks/` directory are licensed under the MIT License. See `licenses/MIT_LICENSE.txt` for full terms.
|
|
777
761
|
|
|
778
762
|
## Third-Party Attributions
|
|
779
763
|
|