datus-sqlalchemy 0.1.1__tar.gz → 0.1.2__tar.gz
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.
- {datus_sqlalchemy-0.1.1 → datus_sqlalchemy-0.1.2}/PKG-INFO +1 -1
- {datus_sqlalchemy-0.1.1 → datus_sqlalchemy-0.1.2}/datus_sqlalchemy/connector.py +90 -98
- {datus_sqlalchemy-0.1.1 → datus_sqlalchemy-0.1.2}/pyproject.toml +1 -1
- {datus_sqlalchemy-0.1.1 → datus_sqlalchemy-0.1.2}/.gitignore +0 -0
- {datus_sqlalchemy-0.1.1 → datus_sqlalchemy-0.1.2}/README.md +0 -0
- {datus_sqlalchemy-0.1.1 → datus_sqlalchemy-0.1.2}/datus_sqlalchemy/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datus-sqlalchemy
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: SQLAlchemy base connector for Datus database adapters
|
|
5
5
|
Project-URL: Homepage, https://github.com/Datus-ai/datus-db-adapters
|
|
6
6
|
Project-URL: Repository, https://github.com/Datus-ai/datus-db-adapters
|
|
@@ -71,33 +71,33 @@ class SQLAlchemyConnector(BaseSqlConnector):
|
|
|
71
71
|
|
|
72
72
|
@override
|
|
73
73
|
def connect(self):
|
|
74
|
-
"""
|
|
75
|
-
if self.engine and self.
|
|
74
|
+
"""Initialize the connection pool (engine only, no persistent connection)."""
|
|
75
|
+
if self.engine and self._owns_engine:
|
|
76
76
|
return
|
|
77
77
|
|
|
78
78
|
try:
|
|
79
79
|
self._safe_close()
|
|
80
80
|
|
|
81
|
-
# Create engine
|
|
81
|
+
# Create engine with connection pool
|
|
82
82
|
if self.dialect not in (DBType.DUCKDB, DBType.SQLITE):
|
|
83
83
|
self.engine = create_engine(
|
|
84
84
|
self.connection_string,
|
|
85
|
-
pool_size=
|
|
86
|
-
max_overflow=
|
|
85
|
+
pool_size=10, # Increased for parallel execution
|
|
86
|
+
max_overflow=20, # Allow more overflow connections
|
|
87
87
|
pool_timeout=self.timeout_seconds,
|
|
88
88
|
pool_recycle=3600,
|
|
89
|
+
pool_pre_ping=True, # Verify connections before use
|
|
89
90
|
)
|
|
90
91
|
else:
|
|
91
92
|
self.engine = create_engine(self.connection_string)
|
|
92
93
|
|
|
93
|
-
self.connection = self.engine.connect()
|
|
94
94
|
self._owns_engine = True
|
|
95
95
|
|
|
96
96
|
except Exception as e:
|
|
97
97
|
self._force_reset()
|
|
98
98
|
raise self._handle_exception(e, "", "connection") from e
|
|
99
99
|
|
|
100
|
-
if not
|
|
100
|
+
if not self.engine:
|
|
101
101
|
self._force_reset()
|
|
102
102
|
raise DatusException(
|
|
103
103
|
ErrorCode.DB_CONNECTION_FAILED, message_args={"error_message": "Failed to establish connection"}
|
|
@@ -105,16 +105,14 @@ class SQLAlchemyConnector(BaseSqlConnector):
|
|
|
105
105
|
|
|
106
106
|
@override
|
|
107
107
|
def close(self):
|
|
108
|
-
"""
|
|
108
|
+
"""Dispose the connection pool."""
|
|
109
109
|
try:
|
|
110
|
-
if self.connection:
|
|
111
|
-
self.connection.close()
|
|
112
|
-
self.connection = None
|
|
113
110
|
if self.engine:
|
|
114
111
|
self.engine.dispose()
|
|
115
112
|
self.engine = None
|
|
113
|
+
self._owns_engine = False
|
|
116
114
|
except Exception as e:
|
|
117
|
-
logger.warning(f"Error
|
|
115
|
+
logger.warning(f"Error disposing engine: {str(e)}")
|
|
118
116
|
|
|
119
117
|
def _safe_close(self):
|
|
120
118
|
"""Safely close connection, ignoring errors."""
|
|
@@ -124,15 +122,8 @@ class SQLAlchemyConnector(BaseSqlConnector):
|
|
|
124
122
|
pass
|
|
125
123
|
|
|
126
124
|
def _force_reset(self):
|
|
127
|
-
"""Force reset
|
|
125
|
+
"""Force reset engine on error."""
|
|
128
126
|
try:
|
|
129
|
-
self._safe_rollback()
|
|
130
|
-
if self.connection:
|
|
131
|
-
try:
|
|
132
|
-
self.connection.close()
|
|
133
|
-
except Exception:
|
|
134
|
-
pass
|
|
135
|
-
self.connection = None
|
|
136
127
|
if self.engine:
|
|
137
128
|
try:
|
|
138
129
|
self.engine.dispose()
|
|
@@ -141,18 +132,9 @@ class SQLAlchemyConnector(BaseSqlConnector):
|
|
|
141
132
|
self.engine = None
|
|
142
133
|
self._owns_engine = False
|
|
143
134
|
except Exception:
|
|
144
|
-
self.connection = None
|
|
145
135
|
self.engine = None
|
|
146
136
|
self._owns_engine = False
|
|
147
137
|
|
|
148
|
-
def _safe_rollback(self):
|
|
149
|
-
"""Safely rollback transaction."""
|
|
150
|
-
if self.connection:
|
|
151
|
-
try:
|
|
152
|
-
self.connection.rollback()
|
|
153
|
-
except Exception:
|
|
154
|
-
pass
|
|
155
|
-
|
|
156
138
|
# ==================== Error Handling ====================
|
|
157
139
|
|
|
158
140
|
def _handle_exception(self, e: Exception, sql: str = "", operation: str = "SQL execution") -> DatusException:
|
|
@@ -269,9 +251,11 @@ class SQLAlchemyConnector(BaseSqlConnector):
|
|
|
269
251
|
|
|
270
252
|
self.connect()
|
|
271
253
|
try:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
254
|
+
# Get connection from pool for this query
|
|
255
|
+
with self.engine.connect() as conn:
|
|
256
|
+
result = conn.execute(text(sql))
|
|
257
|
+
rows = result.fetchall()
|
|
258
|
+
return [row._asdict() for row in rows]
|
|
275
259
|
except DatusException:
|
|
276
260
|
raise
|
|
277
261
|
except Exception as e:
|
|
@@ -282,23 +266,25 @@ class SQLAlchemyConnector(BaseSqlConnector):
|
|
|
282
266
|
"""Execute INSERT statement."""
|
|
283
267
|
try:
|
|
284
268
|
self.connect()
|
|
285
|
-
|
|
286
|
-
|
|
269
|
+
with self.engine.connect() as conn:
|
|
270
|
+
res = conn.execute(text(sql))
|
|
271
|
+
conn.commit()
|
|
287
272
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
273
|
+
# Get inserted primary key or row count
|
|
274
|
+
inserted_pk = None
|
|
275
|
+
try:
|
|
276
|
+
if hasattr(res, "inserted_primary_key") and res.inserted_primary_key:
|
|
277
|
+
inserted_pk = res.inserted_primary_key
|
|
278
|
+
except Exception:
|
|
279
|
+
pass
|
|
295
280
|
|
|
296
|
-
|
|
297
|
-
|
|
281
|
+
lastrowid = getattr(res, "lastrowid", None)
|
|
282
|
+
return_value = inserted_pk if inserted_pk else (lastrowid if lastrowid else res.rowcount)
|
|
298
283
|
|
|
299
|
-
|
|
284
|
+
return ExecuteSQLResult(
|
|
285
|
+
success=True, sql_query=sql, sql_return=str(return_value), row_count=res.rowcount
|
|
286
|
+
)
|
|
300
287
|
except Exception as e:
|
|
301
|
-
self._safe_rollback()
|
|
302
288
|
ex = e if isinstance(e, DatusException) else self._handle_exception(e, sql)
|
|
303
289
|
return ExecuteSQLResult(success=False, error=str(ex), sql_query=sql, sql_return="", row_count=0)
|
|
304
290
|
|
|
@@ -307,11 +293,13 @@ class SQLAlchemyConnector(BaseSqlConnector):
|
|
|
307
293
|
"""Execute UPDATE statement."""
|
|
308
294
|
try:
|
|
309
295
|
self.connect()
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
296
|
+
with self.engine.connect() as conn:
|
|
297
|
+
res = conn.execute(text(sql))
|
|
298
|
+
conn.commit()
|
|
299
|
+
return ExecuteSQLResult(
|
|
300
|
+
success=True, sql_query=sql, sql_return=str(res.rowcount), row_count=res.rowcount
|
|
301
|
+
)
|
|
313
302
|
except Exception as e:
|
|
314
|
-
self._safe_rollback()
|
|
315
303
|
ex = e if isinstance(e, DatusException) else self._handle_exception(e, sql)
|
|
316
304
|
return ExecuteSQLResult(success=False, error=str(ex), sql_query=sql, sql_return="", row_count=0)
|
|
317
305
|
|
|
@@ -320,11 +308,13 @@ class SQLAlchemyConnector(BaseSqlConnector):
|
|
|
320
308
|
"""Execute DELETE statement."""
|
|
321
309
|
try:
|
|
322
310
|
self.connect()
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
311
|
+
with self.engine.connect() as conn:
|
|
312
|
+
res = conn.execute(text(sql))
|
|
313
|
+
conn.commit()
|
|
314
|
+
return ExecuteSQLResult(
|
|
315
|
+
success=True, sql_query=sql, sql_return=str(res.rowcount), row_count=res.rowcount
|
|
316
|
+
)
|
|
326
317
|
except Exception as e:
|
|
327
|
-
self._safe_rollback()
|
|
328
318
|
ex = e if isinstance(e, DatusException) else self._handle_exception(e, sql)
|
|
329
319
|
return ExecuteSQLResult(success=False, error=str(ex), sql_query=sql, sql_return="", row_count=0)
|
|
330
320
|
|
|
@@ -333,11 +323,13 @@ class SQLAlchemyConnector(BaseSqlConnector):
|
|
|
333
323
|
"""Execute DDL statement (CREATE, ALTER, DROP, etc.)."""
|
|
334
324
|
try:
|
|
335
325
|
self.connect()
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
326
|
+
with self.engine.connect() as conn:
|
|
327
|
+
res = conn.execute(text(sql))
|
|
328
|
+
conn.commit()
|
|
329
|
+
return ExecuteSQLResult(
|
|
330
|
+
success=True, sql_query=sql, sql_return=str(res.rowcount), row_count=res.rowcount
|
|
331
|
+
)
|
|
339
332
|
except Exception as e:
|
|
340
|
-
self._safe_rollback()
|
|
341
333
|
ex = e if isinstance(e, DatusException) else self._handle_exception(e, sql)
|
|
342
334
|
return ExecuteSQLResult(success=False, sql_query=sql, error=str(ex))
|
|
343
335
|
|
|
@@ -395,8 +387,9 @@ class SQLAlchemyConnector(BaseSqlConnector):
|
|
|
395
387
|
"""Execute USE/SET commands."""
|
|
396
388
|
self.connect()
|
|
397
389
|
try:
|
|
398
|
-
self.
|
|
399
|
-
|
|
390
|
+
with self.engine.connect() as conn:
|
|
391
|
+
conn.execute(text(sql))
|
|
392
|
+
conn.commit()
|
|
400
393
|
|
|
401
394
|
# Update context if applicable
|
|
402
395
|
if self.dialect != DBType.SQLITE.value:
|
|
@@ -411,7 +404,6 @@ class SQLAlchemyConnector(BaseSqlConnector):
|
|
|
411
404
|
|
|
412
405
|
return ExecuteSQLResult(success=True, sql_query=sql, sql_return="Successful", row_count=0)
|
|
413
406
|
except Exception as e:
|
|
414
|
-
self._safe_rollback()
|
|
415
407
|
ex = e if isinstance(e, DatusException) else self._handle_exception(e, sql)
|
|
416
408
|
return ExecuteSQLResult(success=False, error=str(ex), sql_query=sql)
|
|
417
409
|
|
|
@@ -420,29 +412,31 @@ class SQLAlchemyConnector(BaseSqlConnector):
|
|
|
420
412
|
results = []
|
|
421
413
|
self.connect()
|
|
422
414
|
try:
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
query_lower = query.strip().lower()
|
|
430
|
-
if query_lower.startswith("insert"):
|
|
431
|
-
inserted_pk = None
|
|
432
|
-
try:
|
|
433
|
-
if hasattr(result, "inserted_primary_key") and result.inserted_primary_key:
|
|
434
|
-
inserted_pk = result.inserted_primary_key
|
|
435
|
-
except Exception:
|
|
436
|
-
pass
|
|
437
|
-
lastrowid = getattr(result, "lastrowid", None)
|
|
438
|
-
results.append(inserted_pk if inserted_pk else (lastrowid if lastrowid else result.rowcount))
|
|
439
|
-
elif query_lower.startswith(("update", "delete")):
|
|
440
|
-
results.append(result.rowcount)
|
|
415
|
+
with self.engine.connect() as conn:
|
|
416
|
+
for query in queries:
|
|
417
|
+
result = conn.execute(text(query))
|
|
418
|
+
if result.returns_rows:
|
|
419
|
+
df = DataFrame(result.fetchall(), columns=list(result.keys()))
|
|
420
|
+
results.append(df.to_dict(orient="records"))
|
|
441
421
|
else:
|
|
442
|
-
|
|
443
|
-
|
|
422
|
+
query_lower = query.strip().lower()
|
|
423
|
+
if query_lower.startswith("insert"):
|
|
424
|
+
inserted_pk = None
|
|
425
|
+
try:
|
|
426
|
+
if hasattr(result, "inserted_primary_key") and result.inserted_primary_key:
|
|
427
|
+
inserted_pk = result.inserted_primary_key
|
|
428
|
+
except Exception:
|
|
429
|
+
pass
|
|
430
|
+
lastrowid = getattr(result, "lastrowid", None)
|
|
431
|
+
results.append(
|
|
432
|
+
inserted_pk if inserted_pk else (lastrowid if lastrowid else result.rowcount)
|
|
433
|
+
)
|
|
434
|
+
elif query_lower.startswith(("update", "delete")):
|
|
435
|
+
results.append(result.rowcount)
|
|
436
|
+
else:
|
|
437
|
+
results.append(None)
|
|
438
|
+
conn.commit()
|
|
444
439
|
except SQLAlchemyError as e:
|
|
445
|
-
self._safe_rollback()
|
|
446
440
|
raise self._handle_exception(e, "\n".join(queries), "batch query") from e
|
|
447
441
|
return results
|
|
448
442
|
|
|
@@ -453,14 +447,11 @@ class SQLAlchemyConnector(BaseSqlConnector):
|
|
|
453
447
|
self._execute_query("SELECT 1")
|
|
454
448
|
return True
|
|
455
449
|
except Exception as e:
|
|
456
|
-
self._safe_close()
|
|
457
450
|
if isinstance(e, DatusException):
|
|
458
451
|
raise
|
|
459
452
|
raise DatusException(
|
|
460
453
|
ErrorCode.DB_CONNECTION_FAILED, message_args={"error_message": "Connection test failed"}
|
|
461
454
|
) from e
|
|
462
|
-
finally:
|
|
463
|
-
self._safe_close()
|
|
464
455
|
|
|
465
456
|
# ==================== Metadata Methods ====================
|
|
466
457
|
|
|
@@ -609,19 +600,20 @@ class SQLAlchemyConnector(BaseSqlConnector):
|
|
|
609
600
|
"""Execute query and return CSV rows in batches."""
|
|
610
601
|
self.connect()
|
|
611
602
|
try:
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
if
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
603
|
+
with self.engine.connect() as conn:
|
|
604
|
+
result = conn.execute(text(sql).execution_options(stream_results=True, max_row_buffer=max_rows))
|
|
605
|
+
if result.returns_rows:
|
|
606
|
+
if with_header:
|
|
607
|
+
yield result.keys()
|
|
608
|
+
while True:
|
|
609
|
+
batch_rows = result.fetchmany(max_rows)
|
|
610
|
+
if not batch_rows:
|
|
611
|
+
break
|
|
612
|
+
for row in batch_rows:
|
|
613
|
+
yield row
|
|
614
|
+
else:
|
|
615
|
+
if with_header:
|
|
616
|
+
yield ()
|
|
617
|
+
yield from []
|
|
626
618
|
except Exception as e:
|
|
627
619
|
raise self._handle_exception(e) from e
|
|
File without changes
|
|
File without changes
|
|
File without changes
|