fastmssql 0.2.6__cp38-cp38-macosx_11_0_arm64.whl → 0.2.7__cp38-cp38-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 CHANGED
@@ -1,5 +1,661 @@
1
- from .fastmssql import *
1
+ """
2
+ FastMSSQL - High-Performance Microsoft SQL Server Driver for Python
3
+ ===================================================================
2
4
 
3
- __doc__ = fastmssql.__doc__
4
- if hasattr(fastmssql, "__all__"):
5
- __all__ = fastmssql.__all__
5
+ This library provides direct access to high-performance Rust implementations
6
+ with minimal Python overhead for maximum performance. Built on top of the
7
+ tiberius crate, it offers both synchronous and asynchronous database operations
8
+ with advanced features like connection pooling, SSL/TLS configuration, and
9
+ efficient parameter handling.
10
+
11
+ Key Features:
12
+ - High-performance Rust backend with Python bindings
13
+ - Async/await support for non-blocking operations
14
+ - Connection pooling with configurable parameters
15
+ - SSL/TLS encryption with certificate validation
16
+ - Parameterized queries with automatic type conversion
17
+ - Memory-efficient result iteration
18
+ - Comprehensive error handling and logging
19
+
20
+ Basic Usage (Async):
21
+ >>> import asyncio
22
+ >>> from fastmssql import Connection
23
+ >>>
24
+ >>> async def main():
25
+ ... async with Connection("Server=localhost;Database=test;Trusted_Connection=yes") as conn:
26
+ ... # Simple query
27
+ ... result = await conn.execute("SELECT * FROM users")
28
+ ... async for row in result:
29
+ ... print(f"User: {row['name']}, Age: {row['age']}")
30
+ ...
31
+ ... # Parameterized query
32
+ ... result = await conn.execute(
33
+ ... "SELECT * FROM users WHERE age > @P1 AND city = @P2",
34
+ ... [18, "New York"]
35
+ ... )
36
+ ... rows = await result.fetchall()
37
+ ... print(f"Found {len(rows)} users")
38
+ >>>
39
+ >>> asyncio.run(main())
40
+
41
+ Basic Usage (Sync):
42
+ >>> from fastmssql import Connection
43
+ >>>
44
+ >>> with Connection("Server=localhost;Database=test;Trusted_Connection=yes") as conn:
45
+ ... result = conn.execute_with_python_params(
46
+ ... "SELECT COUNT(*) as count FROM users WHERE active = ?",
47
+ ... [True]
48
+ ... )
49
+ ... print(f"Active users: {result[0]['count']}")
50
+
51
+ Advanced Configuration:
52
+ >>> from fastmssql import Connection, PoolConfig, SslConfig, EncryptionLevel
53
+ >>>
54
+ >>> # Configure connection pool
55
+ >>> pool_config = PoolConfig(
56
+ ... max_connections=20,
57
+ ... min_connections=2,
58
+ ... acquire_timeout_seconds=30,
59
+ ... idle_timeout_seconds=600
60
+ ... )
61
+ >>>
62
+ >>> # Configure SSL/TLS
63
+ >>> ssl_config = SslConfig(
64
+ ... encryption_level=EncryptionLevel.Required,
65
+ ... trust_server_certificate=False,
66
+ ... certificate_path="/path/to/cert.pem"
67
+ ... )
68
+ >>>
69
+ >>> conn = Connection(
70
+ ... server="myserver.database.windows.net",
71
+ ... database="mydatabase",
72
+ ... username="myuser",
73
+ ... password="mypassword",
74
+ ... pool_config=pool_config,
75
+ ... ssl_config=ssl_config
76
+ ... )
77
+
78
+ Performance Considerations:
79
+ - Use parameterized queries to prevent SQL injection and improve performance
80
+ - Leverage connection pooling for applications with multiple concurrent operations
81
+ - Use async methods for I/O-bound applications to improve throughput
82
+ - Consider batch operations for bulk data manipulation
83
+ - Monitor connection pool statistics to optimize pool configuration
84
+
85
+ Thread Safety:
86
+ This library is thread-safe and can be used in multi-threaded applications.
87
+ Each Connection instance maintains its own connection pool and can be safely
88
+ shared across threads when using async methods.
89
+ """
90
+
91
+ # Import from the maturin-generated module
92
+ from .fastmssql import Connection as _RustConnection
93
+ from .fastmssql import PoolConfig
94
+ from .fastmssql import SslConfig
95
+ from .fastmssql import FastExecutionResult
96
+ from .fastmssql import version, EncryptionLevel, Parameter, Parameters
97
+
98
+ # Wrapper class to handle async execution result conversion
99
+ class Connection:
100
+ """
101
+ High-performance connection to Microsoft SQL Server.
102
+
103
+ This class provides a Python wrapper around the Rust-based connection implementation,
104
+ offering both synchronous and asynchronous database operations with advanced features
105
+ like connection pooling, SSL/TLS configuration, and efficient parameter handling.
106
+
107
+ The Connection class supports multiple initialization patterns:
108
+ 1. Connection string-based initialization
109
+ 2. Individual parameter initialization
110
+ 3. Advanced configuration with pool and SSL settings
111
+
112
+ Connection Patterns:
113
+ # Using connection string
114
+ conn = Connection("Server=localhost;Database=test;Trusted_Connection=yes")
115
+
116
+ # Using individual parameters
117
+ conn = Connection(
118
+ server="localhost",
119
+ database="test",
120
+ trusted_connection=True
121
+ )
122
+
123
+ # Using username/password authentication
124
+ conn = Connection(
125
+ server="myserver.database.windows.net",
126
+ database="mydatabase",
127
+ username="myuser",
128
+ password="mypassword"
129
+ )
130
+
131
+ Thread Safety:
132
+ This class is thread-safe and maintains an internal connection pool that can
133
+ be safely accessed from multiple threads when using async methods.
134
+
135
+ Performance Notes:
136
+ - Async methods are recommended for I/O-bound applications
137
+ - Connection pooling is automatically managed for optimal resource usage
138
+ - Parameterized queries provide better performance and security
139
+ - Results are streamed efficiently to minimize memory usage
140
+
141
+ Attributes:
142
+ _conn: The underlying Rust connection implementation
143
+ """
144
+
145
+ def __init__(
146
+ self,
147
+ connection_string=None,
148
+ pool_config=None,
149
+ ssl_config=None,
150
+ server=None,
151
+ database=None,
152
+ username=None,
153
+ password=None,
154
+ trusted_connection=None
155
+ ):
156
+ """
157
+ Initialize a new SQL Server connection.
158
+
159
+ Args:
160
+ connection_string (str, optional): Complete ADO.NET-style connection string.
161
+ Takes precedence over individual parameters if provided.
162
+ Example: "Server=localhost;Database=test;Trusted_Connection=yes"
163
+
164
+ pool_config (PoolConfig, optional): Configuration for the connection pool.
165
+ Allows customization of pool size, timeouts, and behavior.
166
+
167
+ ssl_config (SslConfig, optional): SSL/TLS configuration for secure connections.
168
+ Required for encrypted connections to Azure SQL Database and other
169
+ secure SQL Server instances.
170
+
171
+ server (str, optional): SQL Server hostname or IP address.
172
+ Can include instance name (e.g., "localhost\\SQLEXPRESS") or port
173
+ (e.g., "localhost:1433").
174
+
175
+ database (str, optional): Name of the database to connect to.
176
+ If not specified, connects to the default database for the user.
177
+
178
+ username (str, optional): Username for SQL Server authentication.
179
+ Required when not using Windows Authentication.
180
+
181
+ password (str, optional): Password for SQL Server authentication.
182
+ Required when username is provided.
183
+
184
+ trusted_connection (bool, optional): Whether to use Windows Authentication.
185
+ When True, uses the current Windows user's credentials.
186
+ Mutually exclusive with username/password.
187
+
188
+ Raises:
189
+ ValueError: If connection parameters are invalid or conflicting.
190
+ ConnectionError: If unable to establish initial connection pool.
191
+
192
+ Examples:
193
+ # Connection string approach
194
+ >>> conn = Connection("Server=localhost;Database=AdventureWorks;Trusted_Connection=yes")
195
+
196
+ # Individual parameters
197
+ >>> conn = Connection(
198
+ ... server="localhost",
199
+ ... database="AdventureWorks",
200
+ ... trusted_connection=True
201
+ ... )
202
+
203
+ # SQL Server authentication
204
+ >>> conn = Connection(
205
+ ... server="myserver.database.windows.net",
206
+ ... database="mydatabase",
207
+ ... username="myuser@mydomain.com",
208
+ ... password="SecurePassword123!"
209
+ ... )
210
+
211
+ # With advanced configuration
212
+ >>> from fastmssql import PoolConfig, SslConfig, EncryptionLevel
213
+ >>> pool_config = PoolConfig(max_connections=10, min_connections=2)
214
+ >>> ssl_config = SslConfig(encryption_level=EncryptionLevel.Required)
215
+ >>> conn = Connection(
216
+ ... server="secure-server.example.com",
217
+ ... database="production_db",
218
+ ... username="app_user",
219
+ ... password="app_password",
220
+ ... pool_config=pool_config,
221
+ ... ssl_config=ssl_config
222
+ ... )
223
+ """
224
+ self._conn = _RustConnection(
225
+ connection_string=connection_string,
226
+ pool_config=pool_config,
227
+ ssl_config=ssl_config,
228
+ server=server,
229
+ database=database,
230
+ username=username,
231
+ password=password,
232
+ trusted_connection=trusted_connection
233
+ )
234
+
235
+ async def execute(self, query, parameters=None):
236
+ """
237
+ Execute a SQL query asynchronously and return a FastExecutionResult.
238
+
239
+ This method executes SQL queries with optional parameterization for security
240
+ and performance. It supports all types of SQL statements including SELECT,
241
+ INSERT, UPDATE, DELETE, and stored procedure calls.
242
+
243
+ Parameter Binding:
244
+ Parameters are bound using positional placeholders (@P1, @P2, etc.) in the
245
+ query string. The parameter values are provided as a list in the same order.
246
+
247
+ Supported Parameter Types:
248
+ - None (NULL)
249
+ - bool
250
+ - int (32-bit and 64-bit)
251
+ - float (32-bit and 64-bit)
252
+ - str (varchar, nvarchar, text)
253
+ - bytes (varbinary, image)
254
+ - datetime.datetime (datetime, datetime2)
255
+ - datetime.date (date)
256
+ - datetime.time (time)
257
+ - decimal.Decimal (decimal, money)
258
+ - uuid.UUID (uniqueidentifier)
259
+
260
+ Args:
261
+ query (str): SQL query to execute. Use @P1, @P2, etc. for parameters.
262
+ Example: "SELECT * FROM users WHERE age > @P1 AND city = @P2"
263
+
264
+ parameters (list, optional): List of parameter values in order.
265
+ Values are automatically converted to appropriate SQL types.
266
+ Example: [18, "New York"]
267
+
268
+ Returns:
269
+ FastExecutionResult: An async iterable result object that provides:
270
+ - Async iteration over result rows
271
+ - fetchone(), fetchmany(), fetchall() methods
272
+ - Row count and column metadata
273
+ - Efficient memory usage for large result sets
274
+
275
+ Raises:
276
+ SqlError: If the SQL query contains syntax errors or constraint violations.
277
+ ConnectionError: If the database connection is lost during execution.
278
+ TimeoutError: If the query execution exceeds configured timeouts.
279
+ ParameterError: If parameter types cannot be converted or are invalid.
280
+
281
+ Examples:
282
+ # Simple SELECT query
283
+ >>> result = await conn.execute("SELECT * FROM users")
284
+ >>> async for row in result:
285
+ ... print(f"User ID: {row['id']}, Name: {row['name']}")
286
+
287
+ # Parameterized query
288
+ >>> result = await conn.execute(
289
+ ... "SELECT * FROM orders WHERE created_date > @P1 AND amount > @P2",
290
+ ... [datetime(2023, 1, 1), 100.0]
291
+ ... )
292
+ >>> rows = await result.fetchall()
293
+ >>> print(f"Found {len(rows)} orders")
294
+
295
+ # INSERT with parameters
296
+ >>> result = await conn.execute(
297
+ ... "INSERT INTO users (name, email, age) VALUES (@P1, @P2, @P3)",
298
+ ... ["John Doe", "john@example.com", 30]
299
+ ... )
300
+ >>> print(f"Inserted {result.rowcount} row(s)")
301
+
302
+ # Stored procedure call
303
+ >>> result = await conn.execute(
304
+ ... "EXEC GetUsersByDepartment @P1, @P2",
305
+ ... ["Engineering", True] # department, active_only
306
+ ... )
307
+ >>> users = await result.fetchall()
308
+
309
+ # Complex query with multiple data types
310
+ >>> from decimal import Decimal
311
+ >>> from datetime import datetime
312
+ >>> import uuid
313
+ >>>
314
+ >>> result = await conn.execute(
315
+ ... \"\"\"UPDATE products
316
+ ... SET price = @P1, updated_date = @P2, active = @P3
317
+ ... WHERE product_id = @P4\"\"\",
318
+ ... [Decimal('29.99'), datetime.now(), True, uuid.uuid4()]
319
+ ... )
320
+
321
+ Performance Tips:
322
+ - Use parameterized queries instead of string formatting for better performance
323
+ - For large result sets, iterate asynchronously rather than calling fetchall()
324
+ - Reuse Connection instances to benefit from connection pooling
325
+ - Consider batch operations for bulk data manipulation
326
+
327
+ Security Notes:
328
+ - Always use parameterized queries to prevent SQL injection attacks
329
+ - Parameter values are automatically escaped and type-checked
330
+ - Never concatenate user input directly into SQL query strings
331
+ """
332
+ # The Rust implementation now returns results directly
333
+ return await self._conn.execute(query, parameters)
334
+
335
+ def execute_with_python_params(self, query, params):
336
+ """
337
+ Execute a SQL query synchronously with Python-style parameter substitution.
338
+
339
+ This method provides a synchronous interface for executing SQL queries with
340
+ parameter substitution using Python's DB-API 2.0 style placeholders (?).
341
+ It's designed for compatibility with existing Python database code and
342
+ simple synchronous operations.
343
+
344
+ Parameter Binding:
345
+ Uses question mark (?) placeholders in the query string, with parameters
346
+ provided as a list in the same order as the placeholders appear.
347
+
348
+ Note:
349
+ This is a synchronous method that blocks until the query completes.
350
+ For better performance in async applications, prefer the async execute() method.
351
+
352
+ Args:
353
+ query (str): SQL query with ? placeholders for parameters.
354
+ Example: "SELECT * FROM users WHERE age > ? AND city = ?"
355
+
356
+ params (list): List of parameter values in order of appearance.
357
+ Values are automatically converted to appropriate SQL types.
358
+ Example: [18, "New York"]
359
+
360
+ Returns:
361
+ list: A list of dictionaries representing the result rows.
362
+ Each dictionary maps column names to values.
363
+ For non-SELECT queries, returns an empty list.
364
+
365
+ Raises:
366
+ SqlError: If the SQL query contains syntax errors or constraint violations.
367
+ ConnectionError: If the database connection is not available.
368
+ ParameterError: If parameter types cannot be converted or are invalid.
369
+
370
+ Examples:
371
+ # Simple SELECT query
372
+ >>> rows = conn.execute_with_python_params(
373
+ ... "SELECT id, name, email FROM users WHERE active = ?",
374
+ ... [True]
375
+ ... )
376
+ >>> for row in rows:
377
+ ... print(f"User: {row['name']} ({row['email']})")
378
+
379
+ # INSERT operation
380
+ >>> conn.execute_with_python_params(
381
+ ... "INSERT INTO logs (message, level, timestamp) VALUES (?, ?, ?)",
382
+ ... ["Application started", "INFO", datetime.now()]
383
+ ... )
384
+
385
+ # UPDATE with multiple parameters
386
+ >>> conn.execute_with_python_params(
387
+ ... "UPDATE users SET last_login = ?, login_count = login_count + 1 WHERE id = ?",
388
+ ... [datetime.now(), 12345]
389
+ ... )
390
+
391
+ # Complex query with various data types
392
+ >>> from decimal import Decimal
393
+ >>> rows = conn.execute_with_python_params(
394
+ ... \"\"\"SELECT p.name, p.price, c.name as category
395
+ ... FROM products p
396
+ ... JOIN categories c ON p.category_id = c.id
397
+ ... WHERE p.price BETWEEN ? AND ? AND p.discontinued = ?\"\"\",
398
+ ... [Decimal('10.00'), Decimal('100.00'), False]
399
+ ... )
400
+
401
+ Migration from DB-API 2.0:
402
+ This method is designed to be compatible with Python's DB-API 2.0
403
+ specification, making it easier to migrate from libraries like pyodbc
404
+ or pymssql with minimal code changes.
405
+
406
+ # Old pyodbc code:
407
+ # cursor.execute("SELECT * FROM users WHERE id = ?", [user_id])
408
+ # rows = cursor.fetchall()
409
+
410
+ # New fastmssql code:
411
+ # rows = conn.execute_with_python_params("SELECT * FROM users WHERE id = ?", [user_id])
412
+
413
+ Performance Considerations:
414
+ - This method is synchronous and will block the calling thread
415
+ - For high-throughput applications, use the async execute() method instead
416
+ - Connection pooling is still utilized for efficient resource management
417
+ - Results are loaded entirely into memory, so be cautious with large result sets
418
+ """
419
+ return self._conn.execute_with_python_params(query, params)
420
+
421
+ async def is_connected(self):
422
+ """
423
+ Check if the connection is active and available for queries.
424
+
425
+ This method performs a lightweight check to determine if the underlying
426
+ connection pool has active connections and can accept new queries.
427
+ It's useful for health checks and connection validation in long-running
428
+ applications.
429
+
430
+ The check verifies:
431
+ - Connection pool is initialized and operational
432
+ - At least one connection in the pool is active
433
+ - Network connectivity to the SQL Server instance
434
+ - Authentication credentials are still valid
435
+
436
+ Returns:
437
+ bool: True if the connection is active and ready for queries,
438
+ False if the connection is closed, failed, or unavailable.
439
+
440
+ Raises:
441
+ ConnectionError: If there's an unexpected error checking connection status.
442
+
443
+ Examples:
444
+ # Basic connection check
445
+ >>> if await conn.is_connected():
446
+ ... result = await conn.execute("SELECT COUNT(*) FROM users")
447
+ ... else:
448
+ ... await conn.connect() # Reconnect if needed
449
+
450
+ # Health check in a web application
451
+ >>> async def health_check():
452
+ ... try:
453
+ ... if await conn.is_connected():
454
+ ... return {"database": "healthy", "status": "connected"}
455
+ ... else:
456
+ ... return {"database": "unhealthy", "status": "disconnected"}
457
+ ... except Exception as e:
458
+ ... return {"database": "error", "status": str(e)}
459
+
460
+ # Periodic connection monitoring
461
+ >>> import asyncio
462
+ >>>
463
+ >>> async def monitor_connection():
464
+ ... while True:
465
+ ... if await conn.is_connected():
466
+ ... print(f"{datetime.now()}: Database connection is healthy")
467
+ ... else:
468
+ ... print(f"{datetime.now()}: Database connection is down!")
469
+ ... # Attempt to reconnect
470
+ ... try:
471
+ ... await conn.connect()
472
+ ... print("Reconnection successful")
473
+ ... except Exception as e:
474
+ ... print(f"Reconnection failed: {e}")
475
+ ...
476
+ ... await asyncio.sleep(60) # Check every minute
477
+
478
+ Performance Notes:
479
+ - This is a lightweight operation that doesn't execute actual SQL
480
+ - The check uses connection pool metadata and cached connection state
481
+ - Suitable for frequent health checks without performance impact
482
+ - Does not count against connection pool limits
483
+
484
+ Use Cases:
485
+ - Application startup validation
486
+ - Periodic health monitoring
487
+ - Circuit breaker pattern implementation
488
+ - Load balancer health checks
489
+ - Graceful degradation in microservices
490
+ """
491
+ return await self._conn.is_connected()
492
+
493
+ async def pool_stats(self):
494
+ """
495
+ Get comprehensive connection pool statistics and health metrics.
496
+
497
+ This method provides detailed information about the current state of the
498
+ connection pool, including active connections, idle connections, and
499
+ configuration parameters. It's essential for monitoring, debugging, and
500
+ optimizing connection pool performance in production environments.
501
+
502
+ The statistics help identify:
503
+ - Connection pool utilization patterns
504
+ - Potential connection leaks
505
+ - Optimal pool sizing configuration
506
+ - Performance bottlenecks
507
+ - Resource contention issues
508
+
509
+ Returns:
510
+ dict: A dictionary containing pool statistics with the following keys:
511
+
512
+ When connected:
513
+ - 'connections' (int): Total number of connections in the pool
514
+ - 'idle_connections' (int): Number of idle connections available
515
+ - 'active_connections' (int): Number of connections currently in use
516
+ - 'max_size' (int): Maximum allowed connections in the pool
517
+ - 'min_idle' (int): Minimum idle connections maintained
518
+
519
+ When disconnected:
520
+ - 'connected' (bool): False, indicating no active pool
521
+
522
+ Raises:
523
+ ConnectionError: If unable to retrieve pool statistics due to connection issues.
524
+
525
+ Examples:
526
+ # Basic pool monitoring
527
+ >>> stats = await conn.pool_stats()
528
+ >>> if stats.get('connected', True): # Handle disconnected case
529
+ ... print(f"Active connections: {stats['active_connections']}")
530
+ ... print(f"Idle connections: {stats['idle_connections']}")
531
+ ... print(f"Pool utilization: {stats['active_connections']/stats['max_size']*100:.1f}%")
532
+
533
+ # Comprehensive pool monitoring
534
+ >>> async def monitor_pool():
535
+ ... stats = await conn.pool_stats()
536
+ ...
537
+ ... if not stats.get('connected', True):
538
+ ... print("❌ Connection pool is not active")
539
+ ... return
540
+ ...
541
+ ... total = stats['connections']
542
+ ... active = stats['active_connections']
543
+ ... idle = stats['idle_connections']
544
+ ... max_size = stats['max_size']
545
+ ... min_idle = stats['min_idle']
546
+ ...
547
+ ... utilization = (active / max_size) * 100
548
+ ...
549
+ ... print(f"📊 Connection Pool Statistics:")
550
+ ... print(f" Total connections: {total}")
551
+ ... print(f" Active connections: {active}")
552
+ ... print(f" Idle connections: {idle}")
553
+ ... print(f" Max pool size: {max_size}")
554
+ ... print(f" Min idle: {min_idle}")
555
+ ... print(f" Utilization: {utilization:.1f}%")
556
+ ...
557
+ ... # Health assessment
558
+ ... if utilization > 90:
559
+ ... print("⚠️ High pool utilization - consider increasing max_size")
560
+ ... elif idle < min_idle:
561
+ ... print("⚠️ Low idle connections - pool may be under pressure")
562
+ ... elif utilization < 10 and total > min_idle * 2:
563
+ ... print("ℹ️ Low utilization - consider reducing max_size")
564
+ ... else:
565
+ ... print("✅ Pool appears healthy")
566
+
567
+ # Pool statistics for alerting
568
+ >>> async def check_pool_health():
569
+ ... stats = await conn.pool_stats()
570
+ ...
571
+ ... if not stats.get('connected', True):
572
+ ... return {"status": "critical", "message": "Pool disconnected"}
573
+ ...
574
+ ... utilization = stats['active_connections'] / stats['max_size']
575
+ ... idle_ratio = stats['idle_connections'] / stats['max_size']
576
+ ...
577
+ ... if utilization > 0.9:
578
+ ... return {
579
+ ... "status": "warning",
580
+ ... "message": f"High utilization: {utilization:.1%}",
581
+ ... "stats": stats
582
+ ... }
583
+ ... elif idle_ratio < 0.1:
584
+ ... return {
585
+ ... "status": "warning",
586
+ ... "message": f"Low idle connections: {stats['idle_connections']}",
587
+ ... "stats": stats
588
+ ... }
589
+ ... else:
590
+ ... return {"status": "healthy", "stats": stats}
591
+
592
+ # Logging pool metrics
593
+ >>> import logging
594
+ >>>
595
+ >>> async def log_pool_metrics():
596
+ ... stats = await conn.pool_stats()
597
+ ... if stats.get('connected', True):
598
+ ... logging.info(
599
+ ... "Pool metrics: active=%d, idle=%d, total=%d, utilization=%.1f%%",
600
+ ... stats['active_connections'],
601
+ ... stats['idle_connections'],
602
+ ... stats['connections'],
603
+ ... (stats['active_connections'] / stats['max_size']) * 100
604
+ ... )
605
+
606
+ Monitoring Best Practices:
607
+ - Monitor pool utilization during peak load periods
608
+ - Set up alerts for utilization > 80% or idle connections < min_idle
609
+ - Track connection acquisition times and pool exhaustion events
610
+ - Use metrics for capacity planning and performance optimization
611
+ - Log pool statistics periodically for historical analysis
612
+
613
+ Performance Impact:
614
+ - This operation has minimal performance overhead
615
+ - Safe to call frequently for monitoring purposes
616
+ - Does not affect active connections or pool operation
617
+ - Recommended for inclusion in health check endpoints
618
+ """
619
+ result_tuple = await self._conn.pool_stats()
620
+
621
+ # Convert tuple to dictionary
622
+ connected, connections, idle_connections, max_size, min_idle = result_tuple
623
+
624
+ if connected:
625
+ return {
626
+ 'connections': connections,
627
+ 'idle_connections': idle_connections,
628
+ 'max_size': max_size,
629
+ 'min_idle': min_idle,
630
+ 'active_connections': connections - idle_connections,
631
+ }
632
+ else:
633
+ return {'connected': False}
634
+
635
+ async def connect(self):
636
+ """Explicitly connect to the database."""
637
+ return await self._conn.connect()
638
+
639
+ async def disconnect(self):
640
+ """Explicitly disconnect from the database."""
641
+ return await self._conn.disconnect()
642
+
643
+ async def __aenter__(self):
644
+ await self._conn.__aenter__()
645
+ return self
646
+
647
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
648
+ await self._conn.__aexit__(exc_type, exc_val, exc_tb)
649
+ return None
650
+
651
+ def __enter__(self):
652
+ return self._conn.__enter__()
653
+
654
+ def __exit__(self, exc_type, exc_val, exc_tb):
655
+ return self._conn.__exit__(exc_type, exc_val, exc_tb)
656
+
657
+ # Preserve module documentation
658
+ if hasattr(_RustConnection, "__doc__"):
659
+ __doc__ = _RustConnection.__doc__
660
+
661
+ __all__ = ["Connection", "PoolConfig", "SslConfig", "FastExecutionResult", "version", "EncryptionLevel", "Parameter", "Parameters"]
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastmssql
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: Other/Proprietary License
@@ -33,12 +33,14 @@ A Python library for Microsoft SQL Server built with Rust using the [Tiberius](h
33
33
 
34
34
  [![Language](https://img.shields.io/badge/Language-Rust_Backend-red)](https://github.com/Rivendael/pymssql-rs)
35
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)
36
37
 
37
38
  ## Features
38
39
 
40
+ - **High Performance**: Exceptional throughput with 17,800+ RPS capability
39
41
  - **Rust-Powered Backend**: Built with Rust for memory safety and reliability
40
42
  - **No ODBC Required**: Direct native SQL Server connection without ODBC drivers
41
- - **Connection Pooling**: bb8-based connection pool for efficient resource management
43
+ - **Connection Pooling**: Intelligent bb8-based connection pooling (default: 75 max, 25 min idle)
42
44
  - **Async-Only Design**: Built on Tokio with clean async/await API
43
45
  - **Context Managers**: Automatic resource management with `async with`
44
46
  - **Type Safety**: Strong typing with automatic Python type conversion
@@ -46,15 +48,39 @@ A Python library for Microsoft SQL Server built with Rust using the [Tiberius](h
46
48
  - **Cross-Platform**: Works on Windows, macOS, and Linux
47
49
  - **Simple API**: Clean, intuitive async-only interface
48
50
 
49
- ## Features
51
+ ## Performance Highlights
50
52
 
51
- Fastmssql provides reliable database connectivity with modern Python patterns:
53
+ Fastmssql delivers exceptional database performance through Rust-powered architecture:
52
54
 
55
+ - **Outstanding Throughput**: Up to **17,800+ RPS** with multiple connection pattern
56
+ - **High Performance**: **5,000+ RPS** with single connection (default usage)
57
+ - **Low Latency**: ~2ms average query latency under high load
58
+ - **Scalable Architecture**: Linear scaling with multiple connection objects
53
59
  - **Production Ready**: Stable API with comprehensive error handling
54
60
  - **Connection Pooling**: Efficient resource management with configurable pools
55
61
  - **Type Conversion**: Automatic conversion between SQL Server and Python types
56
62
  - **SSL/TLS Support**: Secure connections with flexible encryption options
57
63
 
64
+ ### Performance Benchmarks
65
+
66
+ | Pattern | RPS | Configuration | Use Case |
67
+ |---------|-----|---------------|----------|
68
+ | Single Connection (Default) | **5,000+** | Default pool (75/25) | Standard applications |
69
+ | Multiple Connections | **17,800+** | 50 workers, high_throughput() | High-concurrency scenarios |
70
+ | Conservative Load | 3,500+ | Shared connection | Traditional pooling |
71
+
72
+ **Benchmark Environment:**
73
+ - Database: SQL Server (local instance)
74
+ - Query: `SELECT 1` (minimal overhead)
75
+ - Test Duration: 15-30 seconds per test
76
+ - Hardware: Modern development machine
77
+
78
+ **Key Performance Insights:**
79
+ 1. **Multiple Connection Objects**: Creating separate `Connection()` objects eliminates serialization bottlenecks
80
+ 2. **Pool Configuration**: Use `PoolConfig.high_throughput()` for demanding workloads
81
+ 3. **Optimal Worker Count**: 30-50 concurrent workers provides best throughput
82
+ 4. **Tokio Optimization**: Aggressive threading configuration maximizes async performance
83
+
58
84
  ## Installation
59
85
 
60
86
  ### From PyPI (Recommended)
@@ -87,8 +113,6 @@ cd mssql-python-rust
87
113
 
88
114
  ## Quick Start
89
115
 
90
- > **💡 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.
91
-
92
116
  ### Basic Async Usage (Recommended)
93
117
 
94
118
  ```python
@@ -157,6 +181,40 @@ async def main():
157
181
  asyncio.run(main())
158
182
  ```
159
183
 
184
+ ### Performance Patterns
185
+
186
+ For maximum performance in high-concurrency scenarios, create multiple Connection objects:
187
+
188
+ ```python
189
+ import asyncio
190
+ from fastmssql import Connection, PoolConfig
191
+
192
+ async def high_performance_pattern():
193
+ """Optimal pattern for maximum throughput."""
194
+ connection_string = "Server=localhost;Database=master;User Id=myuser;Password=mypass"
195
+ config = PoolConfig.high_throughput() # Optimized settings
196
+
197
+ async def worker():
198
+ # Each worker gets its own Connection object for maximum throughput
199
+ async with Connection(connection_string, pool_config=config) as conn:
200
+ for _ in range(1000):
201
+ # Use proper SQL Server parameterization with @param syntax
202
+ result = await conn.execute("SELECT data FROM my_table WHERE id = @id", {"id": 123})
203
+ # Process results...
204
+
205
+ # Launch multiple workers - each with their own connection
206
+ workers = [asyncio.create_task(worker()) for _ in range(50)]
207
+ await asyncio.gather(*workers)
208
+
209
+ # This pattern can achieve 17,800+ RPS
210
+
211
+ asyncio.run(high_performance_pattern())
212
+ ```
213
+
214
+ **Key Performance Insight**: While a single Connection object provides excellent performance (5,000+ RPS),
215
+ creating multiple Connection objects eliminates serialization bottlenecks and can achieve 17,800+ RPS
216
+ for maximum throughput scenarios.
217
+
160
218
  ### Connection Pool Configuration
161
219
 
162
220
  Configure the connection pool for your specific needs:
@@ -178,95 +236,29 @@ async def main():
178
236
  async with Connection(connection_string, pool_config) as conn:
179
237
  result = await conn.execute("SELECT * FROM users")
180
238
 
181
- # Predefined configurations for common scenarios
182
- high_throughput_config = PoolConfig.high_throughput() # 20 connections, optimized for load
183
- low_resource_config = PoolConfig.low_resource() # 3 connections, minimal resources
184
- dev_config = PoolConfig.development() # 5 connections, shorter timeouts
185
-
186
- asyncio.run(main())
187
- ```
188
-
189
- ### Connection Pool Configuration
190
-
191
- For high-throughput applications, you can configure the connection pool:
192
-
193
- ```python
194
- import asyncio
195
- from fastmssql import Connection, PoolConfig
196
-
197
- async def main():
198
- # High-throughput pool setup
199
- config = PoolConfig.high_throughput() # Optimized for concurrent access
239
+ # Predefined configurations for different scenarios
240
+ high_throughput_config = PoolConfig.high_throughput() # 100 connections, optimized for high RPS
241
+ maximum_performance = PoolConfig.maximum_performance() # 150 connections, optimized for peak load
242
+ low_resource_config = PoolConfig.low_resource() # 3 connections, minimal resources
243
+ dev_config = PoolConfig.development() # 5 connections, shorter timeouts
244
+
245
+ # Default configuration is optimized for high performance
246
+ # Default: max_size=75, min_idle=25 - ready for production workloads
200
247
 
201
- async with Connection(connection_string, config) as conn:
202
- # Pool configured for concurrent operations
203
-
204
- # Concurrent workers for high throughput
205
- async def worker():
206
- result = await conn.execute("SELECT @@VERSION")
248
+ # For maximum throughput, use multiple Connection objects:
249
+ async def high_perf_worker():
250
+ async with Connection(connection_string, maximum_performance) as conn:
251
+ result = await conn.execute("SELECT * FROM fast_table")
207
252
  return result.rows()
208
-
209
- # Run multiple concurrent workers
210
- tasks = [worker() for _ in range(20)]
211
- results = await asyncio.gather(*tasks)
212
-
213
- # Pool monitoring
214
- stats = await conn.pool_stats()
215
- print(f"Pool utilization: {stats['active_connections']}/{stats['max_size']}")
253
+
254
+ # Each worker gets its own connection for optimal performance
255
+ tasks = [asyncio.create_task(high_perf_worker()) for _ in range(50)]
256
+ results = await asyncio.gather(*tasks)
216
257
 
217
258
  asyncio.run(main())
218
259
  ```
219
260
 
220
- **Performance Tips:**
221
- - **Reuse Connection objects**: Create one `Connection` per database and reuse it across your application
222
- - Use `PoolConfig.high_throughput()` for high-throughput applications
223
- - Leverage `asyncio.gather()` for concurrent operations
224
- - Monitor pool stats to optimize connection count
225
- - Consider connection lifetime for long-running applications
226
261
 
227
- **⚠️ Performance Anti-Pattern:**
228
- ```python
229
- # DON'T DO THIS - Creates new pool for each operation
230
- async def bad_example():
231
- async with Connection(conn_str) as conn: # New pool created
232
- return await conn.execute("SELECT 1")
233
-
234
- async with Connection(conn_str) as conn: # Another new pool created
235
- return await conn.execute("SELECT 2")
236
- ```
237
-
238
- **✅ Correct Pattern:**
239
- ```python
240
- # DO THIS - Reuse the same connection pool
241
- async def good_example():
242
- async with Connection(conn_str) as conn: # Single pool created
243
- result1 = await conn.execute("SELECT 1")
244
- result2 = await conn.execute("SELECT 2") # Reuses same pool
245
- return result1, result2
246
- ```
247
-
248
- ### Connection Pool Benefits
249
-
250
- The bb8 connection pool provides significant performance improvements over traditional Python libraries:
251
-
252
- | Metric | Traditional Python | fastmssql | Improvement |
253
- |--------|-------------------|-----------|-------------|
254
- | **Connection Setup** | 50ms | 0.1ms | **500x faster** |
255
- | **Memory per Query** | 50-200 KB | 0.5 KB | **100-400x less** |
256
- | **10 Concurrent Queries** | 500ms | 150ms | **3.3x faster** |
257
- | **100 Concurrent Queries** | 5000ms | 400ms | **12.5x faster** |
258
- | **1000 Concurrent Queries** | Timeouts/Errors | 2.9s | **Stable** |
259
- | **Memory Leaks** | Common | None | **Zero leaks** |
260
-
261
- **Key Benefits:**
262
- - **Connection Reuse**: Eliminates connection establishment overhead (500x improvement)
263
- - **Memory Efficiency**: Uses 100-400x less memory per operation (0.5 KB vs 50-200 KB)
264
- - **Zero Memory Leaks**: Automatic cleanup with decreasing memory usage over time
265
- - **Concurrency**: Safe multi-threaded access with automatic pooling
266
- - **Resource Management**: Intelligent memory and connection lifecycle management
267
- - **Load Balancing**: Intelligent connection distribution across threads
268
- - **Fault Tolerance**: Built-in retry logic and connection health checks
269
- - **Timeouts**: Configurable timeouts prevent hanging connections
270
262
 
271
263
  ### Connection Strings
272
264
 
@@ -362,89 +354,6 @@ async def main():
362
354
  asyncio.run(main())
363
355
  ```
364
356
 
365
- ### Performance Comparison: bb8 Connection Pool
366
-
367
- The bb8 connection pool dramatically improves performance, especially under load:
368
-
369
- ```python
370
- import asyncio
371
- import time
372
- from fastmssql import Connection
373
-
374
- async def performance_comparison():
375
- connection_string = "Server=localhost;Database=test;User Id=myuser;Password=mypass"
376
-
377
- # Sequential async operations (still efficient with pool reuse)
378
- start = time.time()
379
- async with Connection(connection_string) as conn:
380
- for i in range(10):
381
- result = await conn.execute("SELECT COUNT(*) FROM users")
382
- sequential_time = time.time() - start
383
-
384
- # Concurrent async operations (much faster with bb8 pool)
385
- start = time.time()
386
- async def concurrent_queries():
387
- tasks = []
388
- for i in range(10):
389
- async def query():
390
- async with Connection(connection_string) as conn: # Pool reuse
391
- return await conn.execute("SELECT COUNT(*) FROM users")
392
- tasks.append(query())
393
- return await asyncio.gather(*tasks)
394
-
395
- await concurrent_queries()
396
- concurrent_time = time.time() - start
397
-
398
- print(f"Sequential: {sequential_time:.3f}s")
399
- print(f"Concurrent: {concurrent_time:.3f}s")
400
- print(f"Improvement: {sequential_time/concurrent_time:.1f}x faster")
401
-
402
- asyncio.run(performance_comparison())
403
- ```
404
-
405
- **Real-world Performance Benefits:**
406
- - **Web Applications**: Handle 100+ concurrent requests without connection exhaustion
407
- - **Batch Processing**: Process large datasets with optimal resource usage
408
- - **Microservices**: Reliable database connections across service boundaries
409
- - **Data Analytics**: Concurrent query execution for faster insights
410
-
411
- ## Examples
412
-
413
- Run the provided examples to see async patterns and features:
414
-
415
- ```bash
416
- # Basic asynchronous usage
417
- python examples/basic_usage.py
418
-
419
- # Advanced asynchronous features
420
- python examples/advanced_usage.py
421
-
422
- # Asynchronous usage patterns
423
- python examples/async_usage.py
424
-
425
- # Advanced pool configuration
426
- python examples/advanced_pool_config.py
427
-
428
- # Connection parameters demonstration
429
- python examples/connection_parameters_demo.py
430
- ```
431
-
432
- ### Key API Improvements
433
-
434
- Our async-only design provides a clean, intuitive interface:
435
-
436
- ```python
437
- # ✅ Clean async API (New Design)
438
- async with Connection(connection_string) as conn:
439
- result = await conn.execute(sql) # Intuitive!
440
- rows_affected = await conn.execute_non_query(sql)
441
-
442
- # ❌ Old confusing API (Removed)
443
- # async with Connection(connection_string) as conn:
444
- # result = await conn.execute_async(sql) # Confusing suffixes
445
- # rows_affected = await conn.execute_non_query_async(sql)
446
- ```
447
-
448
357
  ## Development
449
358
 
450
359
  ### Building from Source
@@ -627,19 +536,6 @@ async with mssql.connect_async(conn_str) as conn:
627
536
  print(f"Pool utilization: {stats['active_connections']}/{stats['connections']}")
628
537
  ```
629
538
 
630
- **Features:**
631
- - Async-only operations for optimal concurrency
632
- - Automatic connection pooling with bb8
633
- - Configurable pool settings via `PoolConfig`
634
- - Pool statistics and monitoring
635
- - Improved concurrent performance
636
- - Better resource management
637
-
638
- **Breaking Changes:**
639
- - None - the API is fully backward compatible
640
- - All existing code continues to work without modification
641
- - Performance improvements are automatic
642
-
643
539
  ## Advanced Usage Patterns
644
540
 
645
541
  ### Custom Pool Configuration for Different Scenarios
@@ -728,7 +624,7 @@ async def olap_operations():
728
624
  return quarterly_report
729
625
  ```
730
626
 
731
- ## Troubleshooting
627
+ ## API Reference
732
628
 
733
629
  ### Common Issues
734
630
 
@@ -0,0 +1,6 @@
1
+ fastmssql-0.2.7.dist-info/RECORD,,
2
+ fastmssql-0.2.7.dist-info/WHEEL,sha256=uCxAYGR8mh7ybiMWkA6Fu-BNUURLCTLWDXY7SKDjVCA,102
3
+ fastmssql-0.2.7.dist-info/METADATA,sha256=pF7dtgu6k6kSZfz-_kLln9dlcjYgWi5yKzbu-L02TU0,24664
4
+ fastmssql-0.2.7.dist-info/licenses/LICENSE,sha256=NwufX3BNj7RvVtnrshWhkpFOLvWc_YVpGpr3UZGFz_E,4765
5
+ fastmssql/__init__.py,sha256=1DZiNk-l6MFhxGVWikdxDQiaXh_6FyhQWtz6UxyxO9s,29041
6
+ fastmssql/fastmssql.cpython-38-darwin.so,sha256=RJkHfHiNbD64UjGIRYoWLhCIOkdU9b_jUYl5nBsDXYQ,3838720
@@ -1,6 +0,0 @@
1
- fastmssql-0.2.6.dist-info/RECORD,,
2
- fastmssql-0.2.6.dist-info/WHEEL,sha256=uCxAYGR8mh7ybiMWkA6Fu-BNUURLCTLWDXY7SKDjVCA,102
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/__init__.py,sha256=2I8gj3Ump7-VdoqnLpp4iiZjFrFZeyeG7iwOFJEz544,119
6
- fastmssql/fastmssql.cpython-38-darwin.so,sha256=Gd34lEX_dJykh-tMeFhjEUU5l9x-JHXCAMHybmSCNwU,4163776