sqlalchemy-jdbcapi 2.0.0.post2__py3-none-any.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.
Files changed (36) hide show
  1. sqlalchemy_jdbcapi/__init__.py +128 -0
  2. sqlalchemy_jdbcapi/_version.py +34 -0
  3. sqlalchemy_jdbcapi/dialects/__init__.py +30 -0
  4. sqlalchemy_jdbcapi/dialects/base.py +879 -0
  5. sqlalchemy_jdbcapi/dialects/db2.py +134 -0
  6. sqlalchemy_jdbcapi/dialects/mssql.py +117 -0
  7. sqlalchemy_jdbcapi/dialects/mysql.py +152 -0
  8. sqlalchemy_jdbcapi/dialects/oceanbase.py +218 -0
  9. sqlalchemy_jdbcapi/dialects/odbc_base.py +389 -0
  10. sqlalchemy_jdbcapi/dialects/odbc_mssql.py +69 -0
  11. sqlalchemy_jdbcapi/dialects/odbc_mysql.py +101 -0
  12. sqlalchemy_jdbcapi/dialects/odbc_oracle.py +80 -0
  13. sqlalchemy_jdbcapi/dialects/odbc_postgresql.py +63 -0
  14. sqlalchemy_jdbcapi/dialects/oracle.py +180 -0
  15. sqlalchemy_jdbcapi/dialects/postgresql.py +110 -0
  16. sqlalchemy_jdbcapi/dialects/sqlite.py +141 -0
  17. sqlalchemy_jdbcapi/jdbc/__init__.py +98 -0
  18. sqlalchemy_jdbcapi/jdbc/connection.py +244 -0
  19. sqlalchemy_jdbcapi/jdbc/cursor.py +329 -0
  20. sqlalchemy_jdbcapi/jdbc/dataframe.py +198 -0
  21. sqlalchemy_jdbcapi/jdbc/driver_manager.py +353 -0
  22. sqlalchemy_jdbcapi/jdbc/exceptions.py +53 -0
  23. sqlalchemy_jdbcapi/jdbc/jvm.py +176 -0
  24. sqlalchemy_jdbcapi/jdbc/type_converter.py +292 -0
  25. sqlalchemy_jdbcapi/jdbc/types.py +72 -0
  26. sqlalchemy_jdbcapi/odbc/__init__.py +46 -0
  27. sqlalchemy_jdbcapi/odbc/connection.py +136 -0
  28. sqlalchemy_jdbcapi/odbc/exceptions.py +48 -0
  29. sqlalchemy_jdbcapi/py.typed +2 -0
  30. sqlalchemy_jdbcapi-2.0.0.post2.dist-info/METADATA +825 -0
  31. sqlalchemy_jdbcapi-2.0.0.post2.dist-info/RECORD +36 -0
  32. sqlalchemy_jdbcapi-2.0.0.post2.dist-info/WHEEL +5 -0
  33. sqlalchemy_jdbcapi-2.0.0.post2.dist-info/entry_points.txt +20 -0
  34. sqlalchemy_jdbcapi-2.0.0.post2.dist-info/licenses/AUTHORS +7 -0
  35. sqlalchemy_jdbcapi-2.0.0.post2.dist-info/licenses/LICENSE +13 -0
  36. sqlalchemy_jdbcapi-2.0.0.post2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,134 @@
1
+ """
2
+ IBM DB2 JDBC dialect for SQLAlchemy.
3
+
4
+ Provides support for IBM DB2 for Linux, Unix, Windows, and z/OS.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import re
11
+ from typing import Any
12
+
13
+ from sqlalchemy import exc, sql
14
+ from sqlalchemy.engine import Connection, Dialect
15
+
16
+ from .base import BaseJDBCDialect, JDBCDriverConfig
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # Try to import DB2 dialect if available
21
+ try:
22
+ from sqlalchemy.dialects.db2.base import DB2Dialect as BaseDB2Dialect
23
+
24
+ HAS_DB2_DIALECT = True
25
+ except ImportError:
26
+ # Fallback to generic dialect
27
+ BaseDB2Dialect = Dialect # type: ignore
28
+ HAS_DB2_DIALECT = False
29
+ logger.debug("IBM DB2 dialect not available, using generic implementation")
30
+
31
+
32
+ class DB2Dialect(BaseJDBCDialect, BaseDB2Dialect): # type: ignore
33
+ """
34
+ IBM DB2 dialect using JDBC driver.
35
+
36
+ Supports DB2-specific features including:
37
+ - Sequences
38
+ - Identity columns
39
+ - Generated columns
40
+ - Temporal tables (DB2 10+)
41
+ - JSON support (DB2 11+)
42
+
43
+ Connection URL format:
44
+ jdbcapi+db2://user:password@host:50000/database
45
+ """
46
+
47
+ name = "db2"
48
+ driver = "jdbcapi"
49
+
50
+ # DB2 capabilities
51
+ supports_native_boolean = False # DB2 < 11.1 doesn't support BOOLEAN
52
+ supports_sequences = True
53
+ supports_identity_columns = True
54
+ supports_native_enum = False
55
+
56
+ @classmethod
57
+ def get_driver_config(cls) -> JDBCDriverConfig:
58
+ """Get DB2 JDBC driver configuration."""
59
+ return JDBCDriverConfig(
60
+ driver_class="com.ibm.db2.jcc.DB2Driver",
61
+ jdbc_url_template="jdbc:db2://{host}:{port}/{database}",
62
+ default_port=50000,
63
+ supports_transactions=True,
64
+ supports_schemas=True,
65
+ supports_sequences=True,
66
+ )
67
+
68
+ def initialize(self, connection: Connection) -> None:
69
+ """Initialize DB2 connection."""
70
+ if HAS_DB2_DIALECT:
71
+ super().initialize(connection)
72
+ # Basic initialization
73
+ elif not hasattr(self, "_server_version_info"):
74
+ self._server_version_info = self._get_server_version_info(connection)
75
+
76
+ logger.debug("Initialized DB2 JDBC dialect")
77
+
78
+ def _get_server_version_info(self, connection: Connection) -> tuple[int, ...]:
79
+ """
80
+ Get DB2 server version.
81
+
82
+ Returns:
83
+ Tuple of version numbers (e.g., (11, 5, 8))
84
+ """
85
+ try:
86
+ # Try DB2-specific version query
87
+ result = connection.execute(
88
+ sql.text("SELECT SERVICE_LEVEL FROM SYSIBMADM.ENV_INST_INFO")
89
+ ).scalar()
90
+
91
+ if result:
92
+ # Parse version from string like:
93
+ # "DB2 v11.5.8.0"
94
+ match = re.search(r"v(\d+)\.(\d+)\.(\d+)", result)
95
+ if match:
96
+ major = int(match.group(1))
97
+ minor = int(match.group(2))
98
+ patch = int(match.group(3))
99
+ return (major, minor, patch)
100
+
101
+ except exc.DBAPIError as e:
102
+ logger.warning(f"Failed to get DB2 server version: {e}")
103
+
104
+ # Fallback: try alternative query
105
+ try:
106
+ result = connection.execute(
107
+ sql.text("VALUES (SYSPROC.VERSION())")
108
+ ).scalar()
109
+
110
+ if result:
111
+ match = re.search(r"(\d+)\.(\d+)\.(\d+)", result)
112
+ if match:
113
+ return tuple(int(match.group(i)) for i in range(1, 4))
114
+
115
+ except exc.DBAPIError:
116
+ pass
117
+
118
+ # Default fallback
119
+ return (11, 1, 0)
120
+
121
+ def do_ping(self, dbapi_connection: Any) -> bool:
122
+ """Check if DB2 connection is alive."""
123
+ try:
124
+ cursor = dbapi_connection.cursor()
125
+ cursor.execute("SELECT 1 FROM SYSIBM.SYSDUMMY1")
126
+ cursor.close()
127
+ return True
128
+ except Exception as e:
129
+ logger.debug(f"DB2 ping failed: {e}")
130
+ return False
131
+
132
+
133
+ # Export the dialect
134
+ dialect = DB2Dialect
@@ -0,0 +1,117 @@
1
+ """
2
+ Microsoft SQL Server JDBC dialect for SQLAlchemy.
3
+
4
+ Provides full SQL Server support through JDBC.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import re
11
+ from typing import Any
12
+
13
+ from sqlalchemy import exc, sql
14
+ from sqlalchemy.dialects.mssql.base import MSDialect
15
+ from sqlalchemy.engine import Connection
16
+
17
+ from .base import BaseJDBCDialect, JDBCDriverConfig
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class MSSQLDialect(BaseJDBCDialect, MSDialect):
23
+ """
24
+ Microsoft SQL Server dialect using JDBC driver.
25
+
26
+ Supports SQL Server-specific features including:
27
+ - T-SQL extensions
28
+ - TOP clause
29
+ - OUTPUT clause
30
+ - Common Table Expressions (CTEs)
31
+ - Window functions
32
+ - JSON support (SQL Server 2016+)
33
+
34
+ Connection URL formats:
35
+ jdbcapi+mssql://user:password@host:1433/database
36
+ jdbcapi+sqlserver://user:password@host:1433/database # Alias
37
+ """
38
+
39
+ name = "mssql"
40
+ driver = "jdbcapi"
41
+
42
+ # SQL Server capabilities
43
+ supports_native_boolean = False # SQL Server uses BIT
44
+ supports_sequences = True # SQL Server 2012+
45
+ supports_native_enum = False
46
+
47
+ @classmethod
48
+ def get_driver_config(cls) -> JDBCDriverConfig:
49
+ """Get SQL Server JDBC driver configuration."""
50
+ return JDBCDriverConfig(
51
+ driver_class="com.microsoft.sqlserver.jdbc.SQLServerDriver",
52
+ jdbc_url_template="jdbc:sqlserver://{host}:{port};databaseName={database}",
53
+ default_port=1433,
54
+ supports_transactions=True,
55
+ supports_schemas=True,
56
+ supports_sequences=True,
57
+ )
58
+
59
+ def initialize(self, connection: Connection) -> None:
60
+ """Initialize SQL Server connection."""
61
+ super().initialize(connection)
62
+ logger.debug("Initialized SQL Server JDBC dialect")
63
+
64
+ def _get_server_version_info(self, connection: Connection) -> tuple[int, ...]:
65
+ """
66
+ Get SQL Server version.
67
+
68
+ Returns:
69
+ Tuple of version numbers (e.g., (15, 0, 4236))
70
+ """
71
+ try:
72
+ result = connection.execute(sql.text("SELECT @@VERSION")).scalar()
73
+
74
+ if result:
75
+ # Parse version from string like:
76
+ # "Microsoft SQL Server 2019 (RTM-CU15) - 15.0.4236.7 ..."
77
+ match = re.search(r"- (\d+)\.(\d+)\.(\d+)", result)
78
+ if match:
79
+ major = int(match.group(1))
80
+ minor = int(match.group(2))
81
+ build = int(match.group(3))
82
+ return (major, minor, build)
83
+
84
+ # Fallback: try to extract major version from name
85
+ version_names = {
86
+ "2022": (16, 0, 0),
87
+ "2019": (15, 0, 0),
88
+ "2017": (14, 0, 0),
89
+ "2016": (13, 0, 0),
90
+ "2014": (12, 0, 0),
91
+ "2012": (11, 0, 0),
92
+ }
93
+
94
+ for name, version in version_names.items():
95
+ if name in result:
96
+ return version
97
+
98
+ except exc.DBAPIError as e:
99
+ logger.warning(f"Failed to get SQL Server version: {e}")
100
+
101
+ # Default fallback
102
+ return (13, 0, 0)
103
+
104
+ def do_ping(self, dbapi_connection: Any) -> bool:
105
+ """Check if SQL Server connection is alive."""
106
+ try:
107
+ cursor = dbapi_connection.cursor()
108
+ cursor.execute("SELECT 1")
109
+ cursor.close()
110
+ return True
111
+ except Exception as e:
112
+ logger.debug(f"SQL Server ping failed: {e}")
113
+ return False
114
+
115
+
116
+ # Export the dialect
117
+ dialect = MSSQLDialect
@@ -0,0 +1,152 @@
1
+ """
2
+ MySQL and MariaDB JDBC dialects for SQLAlchemy.
3
+
4
+ Provides support for MySQL and MariaDB through JDBC.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import re
11
+ from typing import Any
12
+
13
+ from sqlalchemy import exc, sql
14
+ from sqlalchemy.dialects.mysql.base import MySQLDialect as BaseMySQLDialect
15
+ from sqlalchemy.engine import Connection
16
+
17
+ from .base import BaseJDBCDialect, JDBCDriverConfig
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class MySQLDialect(BaseJDBCDialect, BaseMySQLDialect):
23
+ """
24
+ MySQL dialect using JDBC driver.
25
+
26
+ Supports MySQL-specific features including:
27
+ - AUTO_INCREMENT
28
+ - Full-text indexes
29
+ - JSON columns (MySQL 5.7+)
30
+ - Spatial types
31
+
32
+ Connection URL format:
33
+ jdbcapi+mysql://user:password@host:3306/database
34
+ """
35
+
36
+ name = "mysql"
37
+ driver = "jdbcapi"
38
+
39
+ # MySQL capabilities
40
+ supports_native_boolean = False # MySQL uses TINYINT(1)
41
+ supports_native_enum = True
42
+ supports_sequences = False # MySQL < 8.0 doesn't support sequences
43
+
44
+ @classmethod
45
+ def get_driver_config(cls) -> JDBCDriverConfig:
46
+ """Get MySQL JDBC driver configuration."""
47
+ return JDBCDriverConfig(
48
+ driver_class="com.mysql.cj.jdbc.Driver", # MySQL Connector/J 8.0+
49
+ jdbc_url_template="jdbc:mysql://{host}:{port}/{database}",
50
+ default_port=3306,
51
+ supports_transactions=True,
52
+ supports_schemas=True,
53
+ supports_sequences=False,
54
+ )
55
+
56
+ def initialize(self, connection: Connection) -> None:
57
+ """Initialize MySQL connection."""
58
+ super().initialize(connection)
59
+ logger.debug("Initialized MySQL JDBC dialect")
60
+
61
+ def _get_server_version_info(self, connection: Connection) -> tuple[int, ...]:
62
+ """
63
+ Get MySQL server version.
64
+
65
+ Returns:
66
+ Tuple of version numbers (e.g., (8, 0, 32))
67
+ """
68
+ try:
69
+ result = connection.execute(sql.text("SELECT VERSION()")).scalar()
70
+
71
+ if result:
72
+ # Parse version from string like:
73
+ # "8.0.32" or "5.7.40-log"
74
+ match = re.search(r"(\d+)\.(\d+)\.(\d+)", result)
75
+ if match:
76
+ major = int(match.group(1))
77
+ minor = int(match.group(2))
78
+ patch = int(match.group(3))
79
+ return (major, minor, patch)
80
+
81
+ except exc.DBAPIError as e:
82
+ logger.warning(f"Failed to get MySQL server version: {e}")
83
+
84
+ # Default fallback
85
+ return (5, 7, 0)
86
+
87
+ def do_ping(self, dbapi_connection: Any) -> bool:
88
+ """Check if MySQL connection is alive."""
89
+ try:
90
+ cursor = dbapi_connection.cursor()
91
+ cursor.execute("SELECT 1")
92
+ cursor.close()
93
+ return True
94
+ except Exception as e:
95
+ logger.debug(f"MySQL ping failed: {e}")
96
+ return False
97
+
98
+
99
+ class MariaDBDialect(MySQLDialect):
100
+ """
101
+ MariaDB dialect using JDBC driver.
102
+
103
+ MariaDB is a MySQL fork with additional features.
104
+ This dialect extends MySQL with MariaDB-specific capabilities.
105
+
106
+ Connection URL format:
107
+ jdbcapi+mariadb://user:password@host:3306/database
108
+ """
109
+
110
+ name = "mariadb"
111
+
112
+ @classmethod
113
+ def get_driver_config(cls) -> JDBCDriverConfig:
114
+ """Get MariaDB JDBC driver configuration."""
115
+ return JDBCDriverConfig(
116
+ driver_class="org.mariadb.jdbc.Driver",
117
+ jdbc_url_template="jdbc:mariadb://{host}:{port}/{database}",
118
+ default_port=3306,
119
+ supports_transactions=True,
120
+ supports_schemas=True,
121
+ supports_sequences=True, # MariaDB 10.3+ supports sequences
122
+ )
123
+
124
+ def _get_server_version_info(self, connection: Connection) -> tuple[int, ...]:
125
+ """
126
+ Get MariaDB server version.
127
+
128
+ Returns:
129
+ Tuple of version numbers (e.g., (10, 11, 2))
130
+ """
131
+ try:
132
+ result = connection.execute(sql.text("SELECT VERSION()")).scalar()
133
+
134
+ if result:
135
+ # Parse version from string like:
136
+ # "10.11.2-MariaDB" or "10.6.12-MariaDB-log"
137
+ match = re.search(r"(\d+)\.(\d+)\.(\d+)", result)
138
+ if match:
139
+ major = int(match.group(1))
140
+ minor = int(match.group(2))
141
+ patch = int(match.group(3))
142
+ return (major, minor, patch)
143
+
144
+ except exc.DBAPIError as e:
145
+ logger.warning(f"Failed to get MariaDB server version: {e}")
146
+
147
+ # Default fallback
148
+ return (10, 6, 0)
149
+
150
+
151
+ # Export dialects
152
+ dialect = MySQLDialect
@@ -0,0 +1,218 @@
1
+ """
2
+ OceanBase JDBC dialect for SQLAlchemy.
3
+
4
+ OceanBase is an enterprise distributed relational database with Oracle compatibility mode.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import re
11
+ from datetime import datetime
12
+ from typing import Any
13
+
14
+ from sqlalchemy import TIMESTAMP, TypeDecorator, exc, sql, util
15
+ from sqlalchemy.dialects.oracle.base import OracleDialect
16
+ from sqlalchemy.engine import Connection
17
+ from sqlalchemy.engine.url import URL, make_url
18
+
19
+ from .base import BaseJDBCDialect, JDBCDriverConfig
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class ObTimestamp(TypeDecorator):
25
+ """
26
+ Custom timestamp type for OceanBase.
27
+
28
+ Handles conversion between Python datetime and OceanBase Timestamp.
29
+ """
30
+
31
+ impl = TIMESTAMP
32
+ cache_ok = True
33
+
34
+ def process_bind_param(self, value: Any, dialect: Any) -> Any:
35
+ """Convert Python datetime to OceanBase timestamp."""
36
+ if isinstance(value, datetime):
37
+ try:
38
+ # Try to use JPype to create Java Timestamp
39
+ import jpype
40
+
41
+ if jpype.isJVMStarted():
42
+ Timestamp = jpype.JClass("java.sql.Timestamp")
43
+ return Timestamp.valueOf(
44
+ value.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
45
+ )
46
+ except Exception as e:
47
+ logger.debug(f"Failed to create Java Timestamp: {e}")
48
+ # Fall back to string representation
49
+ return value
50
+ return value
51
+
52
+ def process_result_value(self, value: Any, dialect: Any) -> datetime | None:
53
+ """Convert OceanBase timestamp to Python datetime."""
54
+ if value is not None:
55
+ if isinstance(value, datetime):
56
+ return value
57
+ if isinstance(value, str):
58
+ # Parse string timestamp
59
+ try:
60
+ from dateutil import parser
61
+
62
+ return parser.parse(value)
63
+ except Exception as e:
64
+ logger.warning(f"Failed to parse timestamp: {e}")
65
+ return None
66
+ return None
67
+
68
+
69
+ class OceanBaseDialect(BaseJDBCDialect, OracleDialect): # type: ignore
70
+ """
71
+ OceanBase dialect using JDBC driver (Oracle compatibility mode).
72
+
73
+ OceanBase is a distributed database that supports both MySQL and Oracle
74
+ compatibility modes. This dialect implements Oracle mode.
75
+
76
+ Connection URL format:
77
+ jdbcapi+oceanbase://user@tenant#cluster:password@host:2881/database
78
+ jdbcapi+oceanbasejdbc://user:password@host:2881/database # Alias
79
+
80
+ Note: OceanBase uses a special user format: username@tenant#cluster
81
+ """
82
+
83
+ name = "oceanbase"
84
+ driver = "jdbcapi"
85
+
86
+ # OceanBase capabilities
87
+ supports_native_decimal = True
88
+ supports_sane_rowcount = False
89
+ supports_sane_multi_rowcount = False
90
+ supports_unicode_binds = True
91
+ supports_statement_cache = True
92
+ supports_sequences = True
93
+
94
+ # Custom column specifications
95
+ colspecs = util.update_copy(
96
+ OracleDialect.colspecs, # type: ignore
97
+ {TIMESTAMP: ObTimestamp},
98
+ )
99
+
100
+ @classmethod
101
+ def get_driver_config(cls) -> JDBCDriverConfig:
102
+ """Get OceanBase JDBC driver configuration."""
103
+ return JDBCDriverConfig(
104
+ driver_class="com.oceanbase.jdbc.Driver",
105
+ jdbc_url_template="jdbc:oceanbase://{host}:{port}/{database}",
106
+ default_port=2881,
107
+ supports_transactions=True,
108
+ supports_schemas=True,
109
+ supports_sequences=True,
110
+ )
111
+
112
+ def create_connect_args(self, url: URL | str) -> tuple[list[Any], dict[str, Any]]:
113
+ """
114
+ Create connection arguments for OceanBase.
115
+
116
+ Handles special OceanBase user format and connection properties.
117
+ """
118
+ if isinstance(url, str):
119
+ url = make_url(url)
120
+
121
+ config = self.get_driver_config()
122
+
123
+ # Build JDBC URL
124
+ jdbc_url = config.format_jdbc_url(
125
+ host=url.host or "localhost",
126
+ port=url.port,
127
+ database=url.database,
128
+ )
129
+
130
+ logger.debug(f"Creating OceanBase connection to: {jdbc_url}")
131
+
132
+ # Build connection properties
133
+ connect_args: dict[str, Any] = {}
134
+
135
+ if url.username:
136
+ connect_args["user"] = url.username
137
+ if url.password:
138
+ connect_args["password"] = url.password
139
+
140
+ # Add query parameters as connection properties
141
+ if url.query:
142
+ connect_args.update(url.query)
143
+
144
+ kwargs = {
145
+ "jclassname": config.driver_class,
146
+ "url": jdbc_url,
147
+ "driver_args": connect_args,
148
+ }
149
+
150
+ return ([], kwargs)
151
+
152
+ def initialize(self, connection: Connection) -> None:
153
+ """Initialize OceanBase connection."""
154
+ super().initialize(connection)
155
+ logger.debug("Initialized OceanBase JDBC dialect")
156
+
157
+ def _get_server_version_info(self, connection: Connection) -> tuple[int, ...]:
158
+ """
159
+ Get OceanBase server version.
160
+
161
+ Returns:
162
+ Tuple of version numbers (e.g., (4, 0, 0))
163
+ """
164
+ try:
165
+ ver_sql = sql.text("SELECT BANNER FROM v$version")
166
+ banner = connection.execute(ver_sql).scalar()
167
+
168
+ if banner:
169
+ # Parse version from string like:
170
+ # "OceanBase 4.0.0.0 (r10100032022071511-36b8cb0cebc8c2662e8d0c252603c8f2281bb5cc)"
171
+ match = re.search(r"OceanBase ([\d+\.]+\d+)", banner)
172
+ if match:
173
+ version_str = match.group(1)
174
+ parts = version_str.split(".")
175
+ return tuple(int(p) for p in parts[:3])
176
+
177
+ except exc.DBAPIError as e:
178
+ logger.warning(f"Failed to get OceanBase server version: {e}")
179
+
180
+ # Default fallback
181
+ return (3, 0, 0)
182
+
183
+ @property
184
+ def _is_oracle_8(self) -> bool:
185
+ """OceanBase is never Oracle 8."""
186
+ return False
187
+
188
+ def _check_max_identifier_length(self, connection: Connection) -> int | None:
189
+ """OceanBase uses Oracle-compatible identifier lengths."""
190
+ return 128
191
+
192
+ def do_rollback(self, dbapi_connection: Any) -> None:
193
+ """
194
+ Perform rollback on OceanBase connection.
195
+
196
+ OceanBase sometimes has issues with rollback, this provides
197
+ error handling.
198
+ """
199
+ try:
200
+ if dbapi_connection and not dbapi_connection.closed:
201
+ dbapi_connection.rollback()
202
+ except Exception as e:
203
+ logger.warning(f"OceanBase rollback failed: {e}")
204
+
205
+ def do_ping(self, dbapi_connection: Any) -> bool:
206
+ """Check if OceanBase connection is alive."""
207
+ try:
208
+ cursor = dbapi_connection.cursor()
209
+ cursor.execute("SELECT 1 FROM DUAL")
210
+ cursor.close()
211
+ return True
212
+ except Exception as e:
213
+ logger.debug(f"OceanBase ping failed: {e}")
214
+ return False
215
+
216
+
217
+ # Export the dialect
218
+ dialect = OceanBaseDialect