fastmssql 0.2.8__cp39-cp39-win_amd64.whl → 0.3.0__cp39-cp39-win_amd64.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 +161 -41
- fastmssql/fastmssql.cp39-win_amd64.pyd +0 -0
- {fastmssql-0.2.8.dist-info → fastmssql-0.3.0.dist-info}/METADATA +166 -50
- fastmssql-0.3.0.dist-info/RECORD +6 -0
- fastmssql-0.3.0.dist-info/licenses/LICENSE +675 -0
- fastmssql-0.2.8.dist-info/RECORD +0 -6
- fastmssql-0.2.8.dist-info/licenses/LICENSE +0 -139
- {fastmssql-0.2.8.dist-info → fastmssql-0.3.0.dist-info}/WHEEL +0 -0
fastmssql/__init__.py
CHANGED
|
@@ -23,18 +23,25 @@ Basic Usage (Async):
|
|
|
23
23
|
>>>
|
|
24
24
|
>>> async def main():
|
|
25
25
|
... async with Connection("Server=localhost;Database=test;Trusted_Connection=yes") as conn:
|
|
26
|
-
... #
|
|
27
|
-
... result = await conn.
|
|
26
|
+
... # SELECT queries - use query() method
|
|
27
|
+
... result = await conn.query("SELECT * FROM users")
|
|
28
28
|
... async for row in result:
|
|
29
29
|
... print(f"User: {row['name']}, Age: {row['age']}")
|
|
30
30
|
...
|
|
31
|
-
... # Parameterized query
|
|
32
|
-
... result = await conn.
|
|
31
|
+
... # Parameterized SELECT query
|
|
32
|
+
... result = await conn.query(
|
|
33
33
|
... "SELECT * FROM users WHERE age > @P1 AND city = @P2",
|
|
34
34
|
... [18, "New York"]
|
|
35
35
|
... )
|
|
36
36
|
... rows = await result.fetchall()
|
|
37
37
|
... print(f"Found {len(rows)} users")
|
|
38
|
+
...
|
|
39
|
+
... # INSERT/UPDATE/DELETE - use execute() method
|
|
40
|
+
... affected = await conn.execute(
|
|
41
|
+
... "INSERT INTO users (name, email, age) VALUES (@P1, @P2, @P3)",
|
|
42
|
+
... ["John Doe", "john@example.com", 30]
|
|
43
|
+
... )
|
|
44
|
+
... print(f"Inserted {affected} rows")
|
|
38
45
|
>>>
|
|
39
46
|
>>> asyncio.run(main())
|
|
40
47
|
|
|
@@ -42,11 +49,9 @@ Basic Usage (Sync):
|
|
|
42
49
|
>>> from fastmssql import Connection
|
|
43
50
|
>>>
|
|
44
51
|
>>> with Connection("Server=localhost;Database=test;Trusted_Connection=yes") as conn:
|
|
45
|
-
...
|
|
46
|
-
...
|
|
47
|
-
...
|
|
48
|
-
... )
|
|
49
|
-
... print(f"Active users: {result[0]['count']}")
|
|
52
|
+
... # For SELECT queries, you would typically use the async API
|
|
53
|
+
... # Sync usage is primarily for simple operations
|
|
54
|
+
... pass # Connection established and will be closed on exit
|
|
50
55
|
|
|
51
56
|
Advanced Configuration:
|
|
52
57
|
>>> from fastmssql import Connection, PoolConfig, SslConfig, EncryptionLevel
|
|
@@ -232,13 +237,19 @@ class Connection:
|
|
|
232
237
|
trusted_connection=trusted_connection
|
|
233
238
|
)
|
|
234
239
|
|
|
235
|
-
async def
|
|
240
|
+
async def query(self, query, parameters=None):
|
|
236
241
|
"""
|
|
237
|
-
Execute a SQL query
|
|
242
|
+
Execute a SQL query that returns rows (SELECT statements) asynchronously.
|
|
243
|
+
|
|
244
|
+
This method is specifically designed for SELECT queries and other statements
|
|
245
|
+
that return result sets. It uses the optimized query() method internally
|
|
246
|
+
for maximum performance when fetching data.
|
|
238
247
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
248
|
+
Use this method for:
|
|
249
|
+
- SELECT statements
|
|
250
|
+
- Stored procedures that return result sets
|
|
251
|
+
- SHOW commands
|
|
252
|
+
- Any query that returns tabular data
|
|
242
253
|
|
|
243
254
|
Parameter Binding:
|
|
244
255
|
Parameters are bound using positional placeholders (@P1, @P2, etc.) in the
|
|
@@ -258,7 +269,7 @@ class Connection:
|
|
|
258
269
|
- uuid.UUID (uniqueidentifier)
|
|
259
270
|
|
|
260
271
|
Args:
|
|
261
|
-
query (str): SQL query to execute. Use @P1, @P2, etc. for parameters.
|
|
272
|
+
query (str): SQL SELECT query to execute. Use @P1, @P2, etc. for parameters.
|
|
262
273
|
Example: "SELECT * FROM users WHERE age > @P1 AND city = @P2"
|
|
263
274
|
|
|
264
275
|
parameters (list, optional): List of parameter values in order.
|
|
@@ -280,56 +291,165 @@ class Connection:
|
|
|
280
291
|
|
|
281
292
|
Examples:
|
|
282
293
|
# Simple SELECT query
|
|
283
|
-
>>> result = await conn.
|
|
294
|
+
>>> result = await conn.query("SELECT * FROM users")
|
|
284
295
|
>>> async for row in result:
|
|
285
296
|
... print(f"User ID: {row['id']}, Name: {row['name']}")
|
|
286
297
|
|
|
287
298
|
# Parameterized query
|
|
288
|
-
>>> result = await conn.
|
|
299
|
+
>>> result = await conn.query(
|
|
289
300
|
... "SELECT * FROM orders WHERE created_date > @P1 AND amount > @P2",
|
|
290
301
|
... [datetime(2023, 1, 1), 100.0]
|
|
291
302
|
... )
|
|
292
303
|
>>> rows = await result.fetchall()
|
|
293
304
|
>>> print(f"Found {len(rows)} orders")
|
|
294
305
|
|
|
295
|
-
#
|
|
296
|
-
>>> result = await conn.
|
|
297
|
-
... "
|
|
298
|
-
...
|
|
306
|
+
# Complex SELECT with joins
|
|
307
|
+
>>> result = await conn.query(
|
|
308
|
+
... \"\"\"SELECT u.name, u.email, COUNT(o.id) as order_count
|
|
309
|
+
... FROM users u
|
|
310
|
+
... LEFT JOIN orders o ON u.id = o.user_id
|
|
311
|
+
... WHERE u.created_date > @P1
|
|
312
|
+
... GROUP BY u.id, u.name, u.email
|
|
313
|
+
... ORDER BY order_count DESC\"\"\",
|
|
314
|
+
... [datetime(2023, 1, 1)]
|
|
299
315
|
... )
|
|
300
|
-
>>>
|
|
316
|
+
>>> async for row in result:
|
|
317
|
+
... print(f"{row['name']}: {row['order_count']} orders")
|
|
301
318
|
|
|
302
|
-
# Stored procedure
|
|
303
|
-
>>> result = await conn.
|
|
319
|
+
# Stored procedure that returns data
|
|
320
|
+
>>> result = await conn.query(
|
|
304
321
|
... "EXEC GetUsersByDepartment @P1, @P2",
|
|
305
322
|
... ["Engineering", True] # department, active_only
|
|
306
323
|
... )
|
|
307
324
|
>>> 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
325
|
|
|
321
326
|
Performance Tips:
|
|
322
|
-
- Use
|
|
327
|
+
- Use this method instead of execute() for SELECT queries for better performance
|
|
323
328
|
- For large result sets, iterate asynchronously rather than calling fetchall()
|
|
324
329
|
- Reuse Connection instances to benefit from connection pooling
|
|
325
|
-
-
|
|
330
|
+
- Use appropriate indexes on filtered columns
|
|
331
|
+
"""
|
|
332
|
+
return await self._conn.query(query, parameters)
|
|
333
|
+
|
|
334
|
+
async def execute(self, query, parameters=None):
|
|
335
|
+
"""
|
|
336
|
+
Execute a SQL command that doesn't return rows (INSERT/UPDATE/DELETE/DDL) asynchronously.
|
|
337
|
+
|
|
338
|
+
This method is specifically designed for SQL commands that modify data or database
|
|
339
|
+
structure but don't return result sets. It uses the optimized execute() method
|
|
340
|
+
internally for maximum performance when performing data modifications.
|
|
341
|
+
|
|
342
|
+
Use this method for:
|
|
343
|
+
- INSERT statements
|
|
344
|
+
- UPDATE statements
|
|
345
|
+
- DELETE statements
|
|
346
|
+
- DDL commands (CREATE, ALTER, DROP)
|
|
347
|
+
- Stored procedures that don't return result sets
|
|
348
|
+
- MERGE statements
|
|
349
|
+
|
|
350
|
+
Parameter Binding:
|
|
351
|
+
Parameters are bound using positional placeholders (@P1, @P2, etc.) in the
|
|
352
|
+
query string. The parameter values are provided as a list in the same order.
|
|
353
|
+
|
|
354
|
+
Supported Parameter Types:
|
|
355
|
+
- None (NULL)
|
|
356
|
+
- bool
|
|
357
|
+
- int (32-bit and 64-bit)
|
|
358
|
+
- float (32-bit and 64-bit)
|
|
359
|
+
- str (varchar, nvarchar, text)
|
|
360
|
+
- bytes (varbinary, image)
|
|
361
|
+
- datetime.datetime (datetime, datetime2)
|
|
362
|
+
- datetime.date (date)
|
|
363
|
+
- datetime.time (time)
|
|
364
|
+
- decimal.Decimal (decimal, money)
|
|
365
|
+
- uuid.UUID (uniqueidentifier)
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
query (str): SQL command to execute. Use @P1, @P2, etc. for parameters.
|
|
369
|
+
Example: "INSERT INTO users (name, email, age) VALUES (@P1, @P2, @P3)"
|
|
370
|
+
|
|
371
|
+
parameters (list, optional): List of parameter values in order.
|
|
372
|
+
Values are automatically converted to appropriate SQL types.
|
|
373
|
+
Example: ["John Doe", "john@example.com", 30]
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
int: Number of rows affected by the command.
|
|
377
|
+
- For INSERT: Number of rows inserted
|
|
378
|
+
- For UPDATE: Number of rows updated
|
|
379
|
+
- For DELETE: Number of rows deleted
|
|
380
|
+
- For DDL: Usually 0 (structure changes don't affect rows)
|
|
381
|
+
|
|
382
|
+
Raises:
|
|
383
|
+
SqlError: If the SQL command contains syntax errors or constraint violations.
|
|
384
|
+
ConnectionError: If the database connection is lost during execution.
|
|
385
|
+
TimeoutError: If the command execution exceeds configured timeouts.
|
|
386
|
+
ParameterError: If parameter types cannot be converted or are invalid.
|
|
387
|
+
|
|
388
|
+
Examples:
|
|
389
|
+
# INSERT with parameters
|
|
390
|
+
>>> affected = await conn.execute(
|
|
391
|
+
... "INSERT INTO users (name, email, age) VALUES (@P1, @P2, @P3)",
|
|
392
|
+
... ["John Doe", "john@example.com", 30]
|
|
393
|
+
... )
|
|
394
|
+
>>> print(f"Inserted {affected} row(s)")
|
|
395
|
+
|
|
396
|
+
# UPDATE with conditions
|
|
397
|
+
>>> affected = await conn.execute(
|
|
398
|
+
... "UPDATE users SET age = @P1, updated_date = @P2 WHERE id = @P3",
|
|
399
|
+
... [31, datetime.now(), 123]
|
|
400
|
+
... )
|
|
401
|
+
>>> print(f"Updated {affected} user(s)")
|
|
402
|
+
|
|
403
|
+
# DELETE with parameters
|
|
404
|
+
>>> affected = await conn.execute(
|
|
405
|
+
... "DELETE FROM users WHERE age < @P1 AND last_login < @P2",
|
|
406
|
+
... [18, datetime(2020, 1, 1)]
|
|
407
|
+
... )
|
|
408
|
+
>>> print(f"Deleted {affected} inactive users")
|
|
409
|
+
|
|
410
|
+
# DDL commands
|
|
411
|
+
>>> affected = await conn.execute(
|
|
412
|
+
... \"\"\"CREATE TABLE temp_data (
|
|
413
|
+
... id INT IDENTITY(1,1) PRIMARY KEY,
|
|
414
|
+
... name NVARCHAR(100) NOT NULL,
|
|
415
|
+
... created_date DATETIME2 DEFAULT GETDATE()
|
|
416
|
+
... )\"\"\"
|
|
417
|
+
... )
|
|
418
|
+
>>> print(f"Table created (affected rows: {affected})")
|
|
419
|
+
|
|
420
|
+
# Stored procedure that modifies data
|
|
421
|
+
>>> affected = await conn.execute(
|
|
422
|
+
... "EXEC UpdateUserPreferences @P1, @P2",
|
|
423
|
+
... [user_id, json.dumps(preferences)]
|
|
424
|
+
... )
|
|
425
|
+
>>> print(f"Updated preferences for {affected} user(s)")
|
|
426
|
+
|
|
427
|
+
# Batch operations
|
|
428
|
+
>>> users_to_insert = [
|
|
429
|
+
... ["Alice Johnson", "alice@example.com", 28],
|
|
430
|
+
... ["Bob Smith", "bob@example.com", 32],
|
|
431
|
+
... ["Carol Davis", "carol@example.com", 25]
|
|
432
|
+
... ]
|
|
433
|
+
>>> total_affected = 0
|
|
434
|
+
>>> for user_data in users_to_insert:
|
|
435
|
+
... affected = await conn.execute(
|
|
436
|
+
... "INSERT INTO users (name, email, age) VALUES (@P1, @P2, @P3)",
|
|
437
|
+
... user_data
|
|
438
|
+
... )
|
|
439
|
+
... total_affected += affected
|
|
440
|
+
>>> print(f"Inserted {total_affected} users total")
|
|
441
|
+
|
|
442
|
+
Performance Tips:
|
|
443
|
+
- Use this method instead of query() for data modification commands
|
|
444
|
+
- For bulk operations, consider using batch processing or table-valued parameters
|
|
445
|
+
- Use transactions for multiple related operations
|
|
446
|
+
- Monitor the returned affected row count for validation
|
|
326
447
|
|
|
327
448
|
Security Notes:
|
|
328
449
|
- Always use parameterized queries to prevent SQL injection attacks
|
|
329
|
-
-
|
|
330
|
-
-
|
|
450
|
+
- Validate affected row counts match expectations
|
|
451
|
+
- Consider using transactions for data consistency
|
|
331
452
|
"""
|
|
332
|
-
# The Rust implementation now returns results directly
|
|
333
453
|
return await self._conn.execute(query, parameters)
|
|
334
454
|
|
|
335
455
|
|
|
Binary file
|
|
@@ -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
|
|
@@ -46,7 +46,23 @@ A Python library for Microsoft SQL Server built with Rust using the [Tiberius](h
|
|
|
46
46
|
- **Type Safety**: Strong typing with automatic Python type conversion
|
|
47
47
|
- **Thread Safety**: Support for concurrent operations
|
|
48
48
|
- **Cross-Platform**: Works on Windows, macOS, and Linux
|
|
49
|
-
- **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
|
|
52
|
+
|
|
53
|
+
FastMSSQL provides two distinct methods for database operations:
|
|
54
|
+
|
|
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()
|
|
62
|
+
|
|
63
|
+
# Use execute() for data modification
|
|
64
|
+
affected = await conn.execute("INSERT INTO users (name) VALUES (@P1)", ["John"])
|
|
65
|
+
```
|
|
50
66
|
|
|
51
67
|
## Performance Highlights
|
|
52
68
|
|
|
@@ -125,7 +141,9 @@ async def main():
|
|
|
125
141
|
|
|
126
142
|
# Automatic connection pool management
|
|
127
143
|
async with Connection(connection_string) as conn:
|
|
128
|
-
|
|
144
|
+
# Use query() for SELECT statements that return rows
|
|
145
|
+
result = await conn.query("SELECT @@VERSION as version")
|
|
146
|
+
rows = result.rows()
|
|
129
147
|
for row in rows:
|
|
130
148
|
print(row['version'])
|
|
131
149
|
|
|
@@ -151,8 +169,10 @@ async def main():
|
|
|
151
169
|
connection_string = "Server=localhost;Database=master;User Id=myuser;Password=mypass"
|
|
152
170
|
|
|
153
171
|
async with Connection(connection_string=connection_string) as conn:
|
|
154
|
-
|
|
155
|
-
|
|
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:
|
|
156
176
|
print(row['version'])
|
|
157
177
|
|
|
158
178
|
asyncio.run(main())
|
|
@@ -174,8 +194,10 @@ async def main():
|
|
|
174
194
|
username="myuser",
|
|
175
195
|
password="mypassword"
|
|
176
196
|
) as conn:
|
|
177
|
-
|
|
178
|
-
|
|
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:
|
|
179
201
|
print(f"Logged in as: {row['login']}")
|
|
180
202
|
|
|
181
203
|
asyncio.run(main())
|
|
@@ -198,8 +220,9 @@ async def high_performance_pattern():
|
|
|
198
220
|
# Each worker gets its own Connection object for maximum throughput
|
|
199
221
|
async with Connection(connection_string, pool_config=config) as conn:
|
|
200
222
|
for _ in range(1000):
|
|
201
|
-
# Use
|
|
202
|
-
result = await conn.
|
|
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()
|
|
203
226
|
# Process results...
|
|
204
227
|
|
|
205
228
|
# Launch multiple workers - each with their own connection
|
|
@@ -234,7 +257,11 @@ async def main():
|
|
|
234
257
|
)
|
|
235
258
|
|
|
236
259
|
async with Connection(connection_string, pool_config) as conn:
|
|
237
|
-
|
|
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']}")
|
|
238
265
|
|
|
239
266
|
# Predefined configurations for different scenarios
|
|
240
267
|
high_throughput_config = PoolConfig.high_throughput() # 100 connections, optimized for high RPS
|
|
@@ -248,7 +275,8 @@ async def main():
|
|
|
248
275
|
# For maximum throughput, use multiple Connection objects:
|
|
249
276
|
async def high_perf_worker():
|
|
250
277
|
async with Connection(connection_string, maximum_performance) as conn:
|
|
251
|
-
|
|
278
|
+
# Use query() for SELECT statements that return rows
|
|
279
|
+
result = await conn.query("SELECT * FROM fast_table")
|
|
252
280
|
return result.rows()
|
|
253
281
|
|
|
254
282
|
# Each worker gets its own connection for optimal performance
|
|
@@ -283,21 +311,37 @@ from fastmssql import Connection
|
|
|
283
311
|
|
|
284
312
|
async def main():
|
|
285
313
|
async with Connection(connection_string) as conn:
|
|
286
|
-
#
|
|
287
|
-
|
|
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()
|
|
288
317
|
|
|
289
318
|
# Iterate through results
|
|
290
|
-
for
|
|
291
|
-
print(f"User {
|
|
319
|
+
for row in rows:
|
|
320
|
+
print(f"User {row['id']}: {row['name']} ({row['email']})")
|
|
292
321
|
|
|
293
|
-
#
|
|
294
|
-
|
|
295
|
-
|
|
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]
|
|
296
327
|
)
|
|
297
|
-
print(f"
|
|
328
|
+
print(f"Inserted {rows_affected} row(s)")
|
|
298
329
|
|
|
299
|
-
#
|
|
300
|
-
|
|
330
|
+
# UPDATE operation
|
|
331
|
+
rows_affected = await conn.execute(
|
|
332
|
+
"UPDATE users SET last_login = GETDATE() WHERE id = @P1",
|
|
333
|
+
[123]
|
|
334
|
+
)
|
|
335
|
+
print(f"Updated {rows_affected} row(s)")
|
|
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("""
|
|
301
345
|
SELECT
|
|
302
346
|
42 as int_value,
|
|
303
347
|
3.14159 as float_value,
|
|
@@ -307,9 +351,11 @@ async def main():
|
|
|
307
351
|
NULL as null_value
|
|
308
352
|
""")
|
|
309
353
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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__})")
|
|
313
359
|
|
|
314
360
|
asyncio.run(main())
|
|
315
361
|
```
|
|
@@ -329,7 +375,9 @@ async def main():
|
|
|
329
375
|
|
|
330
376
|
# Async context manager with automatic pool management
|
|
331
377
|
async with Connection(connection_string) as conn:
|
|
332
|
-
|
|
378
|
+
# Use query() for SELECT statements that return rows
|
|
379
|
+
result = await conn.query("SELECT name FROM sys.databases")
|
|
380
|
+
rows = result.rows()
|
|
333
381
|
for row in rows:
|
|
334
382
|
print(row['name'])
|
|
335
383
|
|
|
@@ -340,7 +388,9 @@ async def main():
|
|
|
340
388
|
# High-performance concurrent operations
|
|
341
389
|
async def fetch_user_data(user_id):
|
|
342
390
|
async with Connection(connection_string) as conn:
|
|
343
|
-
|
|
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()
|
|
344
394
|
|
|
345
395
|
# Execute multiple queries concurrently using the connection pool
|
|
346
396
|
user_ids = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
@@ -407,6 +457,29 @@ python examples/advanced_usage.py
|
|
|
407
457
|
|
|
408
458
|
## API Reference
|
|
409
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
|
+
|
|
410
483
|
### Core Classes
|
|
411
484
|
|
|
412
485
|
#### `Connection`
|
|
@@ -419,21 +492,45 @@ Connection(connection_string: str, pool_config: Optional[PoolConfig] = None)
|
|
|
419
492
|
|
|
420
493
|
**Context Manager Support:**
|
|
421
494
|
```python
|
|
422
|
-
#
|
|
423
|
-
with Connection(conn_str) as conn:
|
|
424
|
-
result = conn.execute("SELECT * FROM table")
|
|
425
|
-
|
|
426
|
-
# Asynchronous
|
|
495
|
+
# Asynchronous (recommended)
|
|
427
496
|
async with Connection(conn_str) as conn:
|
|
428
|
-
|
|
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
|
+
```
|
|
429
504
|
```
|
|
430
505
|
|
|
431
506
|
**Methods:**
|
|
432
|
-
- `
|
|
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
|
|
433
509
|
- `pool_stats() -> dict` - Get connection pool statistics
|
|
434
510
|
- `disconnect()` - Close the connection pool
|
|
435
511
|
- `is_connected() -> bool` - Check if pool is active
|
|
436
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
|
+
|
|
437
534
|
**Pool Statistics:**
|
|
438
535
|
```python
|
|
439
536
|
stats = conn.pool_stats()
|
|
@@ -484,15 +581,17 @@ Represents a database row with column access.
|
|
|
484
581
|
|
|
485
582
|
#### Connection Management
|
|
486
583
|
```python
|
|
487
|
-
# Create connection with
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
#
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
584
|
+
# Create connection with connection pooling
|
|
585
|
+
Connection(connection_string: str, pool_config: Optional[PoolConfig] = None) -> Connection
|
|
586
|
+
|
|
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"])
|
|
496
595
|
```
|
|
497
596
|
|
|
498
597
|
#### Utility Functions
|
|
@@ -515,7 +614,8 @@ The library uses the bb8 connection pool for efficient resource management:
|
|
|
515
614
|
```python
|
|
516
615
|
try:
|
|
517
616
|
async with mssql.connect_async(connection_string) as conn:
|
|
518
|
-
result = await conn.
|
|
617
|
+
result = await conn.query("SELECT * FROM invalid_table")
|
|
618
|
+
rows = result.rows()
|
|
519
619
|
except Exception as e:
|
|
520
620
|
print(f"Database error: {e}")
|
|
521
621
|
# Connection automatically returned to pool even on error
|
|
@@ -529,7 +629,8 @@ This library has been upgraded to use async-only operations with the bb8 connect
|
|
|
529
629
|
```python
|
|
530
630
|
# Async-only with automatic connection pooling
|
|
531
631
|
async with mssql.connect_async(conn_str) as conn:
|
|
532
|
-
result = await conn.
|
|
632
|
+
result = await conn.query("SELECT * FROM table")
|
|
633
|
+
rows = result.rows()
|
|
533
634
|
|
|
534
635
|
# Pool statistics
|
|
535
636
|
stats = conn.pool_stats()
|
|
@@ -601,7 +702,7 @@ async def oltp_operations():
|
|
|
601
702
|
async with mssql.connect_async(conn_str, oltp_config) as conn:
|
|
602
703
|
# Fast, concurrent transactions
|
|
603
704
|
tasks = [
|
|
604
|
-
conn.
|
|
705
|
+
conn.query("SELECT * FROM users WHERE id = @P1", [user_id])
|
|
605
706
|
for user_id in range(1, 101)
|
|
606
707
|
]
|
|
607
708
|
results = await asyncio.gather(*tasks)
|
|
@@ -611,7 +712,7 @@ olap_config = PoolConfig.low_resource()
|
|
|
611
712
|
async def olap_operations():
|
|
612
713
|
async with mssql.connect_async(conn_str, olap_config) as conn:
|
|
613
714
|
# Long-running analytical queries
|
|
614
|
-
|
|
715
|
+
result = await conn.query("""
|
|
615
716
|
SELECT
|
|
616
717
|
DATE_TRUNC('quarter', order_date) as quarter,
|
|
617
718
|
SUM(total_amount) as total_revenue,
|
|
@@ -621,7 +722,7 @@ async def olap_operations():
|
|
|
621
722
|
GROUP BY DATE_TRUNC('quarter', order_date)
|
|
622
723
|
ORDER BY quarter
|
|
623
724
|
""")
|
|
624
|
-
return
|
|
725
|
+
return result.rows()
|
|
625
726
|
```
|
|
626
727
|
|
|
627
728
|
## API Reference
|
|
@@ -641,7 +742,22 @@ Contributions are welcome! Please open an issue or submit a pull request for any
|
|
|
641
742
|
|
|
642
743
|
## License
|
|
643
744
|
|
|
644
|
-
|
|
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.
|
|
645
761
|
|
|
646
762
|
## Third-Party Attributions
|
|
647
763
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
fastmssql-0.3.0.dist-info/METADATA,sha256=djsP1Pqk6bdQpwQ56ZMgXaV99liDhhUZ25an0dzQMSM,30116
|
|
2
|
+
fastmssql-0.3.0.dist-info/WHEEL,sha256=SFq8SBzC81NB5ITKJAU_CjsCJk-sqmjha07boBg64KU,94
|
|
3
|
+
fastmssql-0.3.0.dist-info/licenses/LICENSE,sha256=oCvGJWOz1z4-4sMRHL32UnC0BnRFXmJZYQ0QAsv0Cok,34533
|
|
4
|
+
fastmssql/__init__.py,sha256=DLqdlIrJhYaleMJMfi0rb6wvXwdj-t0MjY3O0f_M1PM,31103
|
|
5
|
+
fastmssql/fastmssql.cp39-win_amd64.pyd,sha256=Xp6uQOkbwJpqbiwPLE3j1PnOGiIJ4C7I5xPcygjzHsE,2151424
|
|
6
|
+
fastmssql-0.3.0.dist-info/RECORD,,
|