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.

@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastmssql
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
- Classifier: License :: Other/Proprietary 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: PolyForm Noncommercial License 1.0.0
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 **blazingly fast** Python library for Microsoft SQL Server that delivers **significant performance improvements** over standard libraries. 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.
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
- [![Performance](https://img.shields.io/badge/Performance-3493_RPS-brightgreen)](https://github.com/Rivendael/pymssql-rs)
37
- [![Memory](https://img.shields.io/badge/Memory-0.08_KB/query-blue)](https://github.com/Rivendael/pymssql-rs)
38
- [![Speed](https://img.shields.io/badge/Speed-High_Performance-orange)](https://github.com/Rivendael/pymssql-rs)
39
34
  [![Language](https://img.shields.io/badge/Language-Rust_Backend-red)](https://github.com/Rivendael/pymssql-rs)
40
35
  [![Async](https://img.shields.io/badge/Async-Native_Tokio-blue)](https://github.com/Rivendael/pymssql-rs)
36
+ [![Performance](https://img.shields.io/badge/Performance-17%2C800%2B_RPS-brightgreen)](https://github.com/Rivendael/pymssql-rs)
41
37
 
42
38
  ## Features
43
39
 
44
- - **High Performance**: Built with Rust for memory safety and speed
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**: Advanced bb8-based connection pool for optimal performance
47
- - **Async-Only Design**: Built on Tokio for excellent concurrency with clean async/await API
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**: Full support for concurrent operations
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
- ## Performance
53
+ FastMSSQL provides two distinct methods for database operations:
55
54
 
56
- Fastmssql delivers **excellent performance** that outperforms standard Python SQL Server libraries in our testing:
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
- ### 🚀 Benchmark Results
63
+ # Use execute() for data modification
64
+ affected = await conn.execute("INSERT INTO users (name) VALUES (@P1)", ["John"])
65
+ ```
59
66
 
60
- | Library | RPS (Requests/Second) | Performance vs fastmssql |
61
- |---------|----------------------|--------------------------|
62
- | **fastmssql** | **3,493** | **Baseline** |
63
- | Other Python libraries | ~300-600* | **5-11x slower** |
67
+ ## Performance Highlights
64
68
 
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.*
69
+ Fastmssql delivers exceptional database performance through Rust-powered architecture:
66
70
 
67
- ### 🧠 Memory Efficiency
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
- Fastmssql delivers **exceptional memory efficiency** with minimal overhead:
80
+ ### Performance Benchmarks
70
81
 
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 |
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
- **Memory Efficiency Highlights:**
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
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
- *Traditional Python SQL Server libraries typically use 50-200 KB per operation due to connection overhead and lack of efficient pooling.*
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
- rows = await conn.execute("SELECT @@VERSION as version")
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
- result = await conn.execute("SELECT @@VERSION as version")
159
- for row in result.rows():
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
- result = await conn.execute("SELECT SUSER_NAME() as login")
182
- for row in result.rows():
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
- result = await conn.execute("SELECT * FROM users")
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 common scenarios
210
- high_throughput_config = PoolConfig.high_throughput() # 20 connections, optimized for load
211
- low_resource_config = PoolConfig.low_resource() # 3 connections, minimal resources
212
- dev_config = PoolConfig.development() # 5 connections, shorter timeouts
213
-
214
- asyncio.run(main())
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
- async with Connection(connection_string, extreme_config) as conn:
230
- # This setup achieves 3,493 RPS in benchmarks
231
-
232
- # Concurrent workers for maximum throughput
233
- async def worker():
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
- # Run 20 concurrent workers
238
- tasks = [worker() for _ in range(20)]
239
- results = await asyncio.gather(*tasks)
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
- # Execute queries
323
- users = await conn.execute("SELECT id, name, email FROM users WHERE active = 1")
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 user in users:
327
- print(f"User {user['id']}: {user['name']} ({user['email']})")
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
- # Execute non-query operations
330
- rows_affected = await conn.execute_non_query(
331
- "UPDATE users SET last_login = GETDATE() WHERE id = 123"
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} rows")
335
+ print(f"Updated {rows_affected} row(s)")
334
336
 
335
- # Work with different data types
336
- data = await conn.execute("""
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
- row = data[0]
347
- for column_name, value in row.items():
348
- print(f"{column_name}: {value} (type: {type(value).__name__})")
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
- rows = await conn.execute("SELECT name FROM sys.databases")
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
- return await conn.execute(f"SELECT * FROM users WHERE id = {user_id}")
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
- # Synchronous
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
- result = await conn.execute_async("SELECT * FROM table")
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
- - `execute(sql: str) -> List[Row]` - Execute a query synchronously
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 default pool settings
607
- connect(connection_string: str) -> Connection
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
- # One-liner query execution
613
- execute(connection_string: str, sql: str) -> List[dict]
614
- execute_async(connection_string: str, sql: str) -> List[dict]
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.execute("SELECT * FROM invalid_table")
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.execute("SELECT * FROM table")
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.execute_async("SELECT * FROM users WHERE id = $1", [user_id])
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
- quarterly_report = await conn.execute_async("""
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 quarterly_report
725
+ return result.rows()
757
726
  ```
758
727
 
759
- ## Troubleshooting
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
- This project is licensed under the PolyForm Noncommercial License 1.0.0. See the LICENSE file for details.
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