Coverage for mindsdb / integrations / handlers / hana_handler / hana_handler.py: 0%
101 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-21 00:36 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-21 00:36 +0000
1from typing import Any, Dict, Text
3from hdbcli import dbapi
4from hdbcli.dbapi import Error, ProgrammingError
5from mindsdb_sql_parser.ast.base import ASTNode
6from mindsdb.utilities.render.sqlalchemy_render import SqlalchemyRender
7from pandas import DataFrame
8import sqlalchemy_hana.dialect as hana_dialect
10from mindsdb.integrations.libs.base import DatabaseHandler
11from mindsdb.integrations.libs.response import (
12 HandlerStatusResponse as StatusResponse,
13 HandlerResponse as Response,
14 RESPONSE_TYPE
15)
16from mindsdb.utilities import log
19logger = log.getLogger(__name__)
22class HanaHandler(DatabaseHandler):
23 """
24 This handler handles the connection and execution of SQL statements on SAP HANA.
25 """
27 name = 'hana'
29 def __init__(self, name: Text, connection_data: Dict, **kwargs: Any) -> None:
30 """
31 Initializes the handler.
33 Args:
34 name (Text): The name of the handler instance.
35 connection_data (Dict): The connection data required to connect to the SAP HANA database.
36 kwargs: Arbitrary keyword arguments.
37 """
38 super().__init__(name)
39 self.connection_data = connection_data
40 self.kwargs = kwargs
42 self.connection = None
43 self.is_connected = False
45 def __del__(self):
46 """
47 Closes the connection when the handler instance is deleted.
48 """
49 if self.is_connected is True:
50 self.disconnect()
52 def connect(self) -> dbapi.Connection:
53 """
54 Establishes a connection to the SAP HANA database.
56 Raises:
57 ValueError: If the expected connection parameters are not provided.
58 hdbcli.dbapi.Error: If an error occurs while connecting to the SAP HANA database.
60 Returns:
61 hdbcli.dbapi.Connection: A connection object to the SAP HANA database.
62 """
63 if self.is_connected is True:
64 return self.connection
66 # Mandatory connection parameters.
67 if not all(key in self.connection_data for key in ['address', 'port', 'user', 'password']):
68 raise ValueError('Required parameters (address, port, user, password) must be provided.')
70 config = {
71 'address': self.connection_data['address'],
72 'port': self.connection_data['port'],
73 'user': self.connection_data['user'],
74 'password': self.connection_data['password'],
75 }
77 # Optional connection parameters.
78 if 'database' in self.connection_data:
79 config['databaseName'] = self.connection_data['database']
81 if 'schema' in self.connection_data:
82 config['currentSchema'] = self.connection_data['schema']
84 if 'encrypt' in self.connection_data:
85 config['encrypt'] = self.connection_data['encrypt']
87 try:
88 self.connection = dbapi.connect(
89 **config
90 )
91 self.is_connected = True
92 return self.connection
93 except Error as known_error:
94 logger.error(f'Error connecting to SAP HANA, {known_error}!')
95 raise
96 except Exception as unknown_error:
97 logger.error(f'Unknown error connecting to Teradata, {unknown_error}!')
98 raise
100 def disconnect(self) -> None:
101 """
102 Closes the connection to the SAP HANA database if it's currently open.
103 """
104 if self.is_connected is True:
105 self.connection.close()
106 self.is_connected = False
108 def check_connection(self) -> StatusResponse:
109 """
110 Checks the status of the connection to the SAP HANA database.
112 Returns:
113 StatusResponse: An object containing the success status and an error message if an error occurs.
114 """
115 response = StatusResponse(False)
116 need_to_close = self.is_connected is False
118 try:
119 connection = self.connect()
120 with connection.cursor() as cur:
121 cur.execute('SELECT 1 FROM SYS.DUMMY')
122 response.success = True
123 except (Error, ProgrammingError, ValueError) as known_error:
124 logger.error(f'Connection check to SAP HANA failed, {known_error}!')
125 response.error_message = str(known_error)
126 except Exception as unknown_error:
127 logger.error(f'Connection check to SAP HANA failed due to an unknown error, {unknown_error}!')
128 response.error_message = str(unknown_error)
130 if response.success is True and need_to_close:
131 self.disconnect()
132 if response.success is False and self.is_connected is True:
133 self.is_connected = False
135 return response
137 def native_query(self, query: Text) -> Response:
138 """
139 Executes a native SQL query on the SAP HANA database and returns the result.
141 Args:
142 query (Text): The SQL query to be executed.
144 Returns:
145 Response: A response object containing the result of the query or an error message.
146 """
147 need_to_close = self.is_connected is False
149 connection = self.connect()
150 with connection.cursor() as cur:
151 try:
152 cur.execute(query)
153 if not cur.description:
154 response = Response(RESPONSE_TYPE.OK)
155 else:
156 result = cur.fetchall()
157 response = Response(
158 RESPONSE_TYPE.TABLE,
159 DataFrame(
160 result,
161 columns=[x[0] for x in cur.description]
162 )
163 )
164 connection.commit()
165 except ProgrammingError as programming_error:
166 logger.error(f'Error running query: {query} on {self.address}!')
167 response = Response(
168 RESPONSE_TYPE.ERROR,
169 error_code=0,
170 error_message=str(programming_error)
171 )
172 connection.rollback()
173 except Exception as unknown_error:
174 logger.error(f'Unknown error running query: {query} on {self.address}!')
175 response = Response(
176 RESPONSE_TYPE.ERROR,
177 error_code=0,
178 error_message=str(unknown_error)
179 )
180 connection.rollback()
182 if need_to_close is True:
183 self.disconnect()
185 return response
187 def query(self, query: ASTNode) -> Response:
188 """
189 Executes a SQL query represented by an ASTNode on the SAP HANA database and retrieves the data (if any).
191 Args:
192 query (ASTNode): An ASTNode representing the SQL query to be executed.
194 Returns:
195 Response: The response from the `native_query` method, containing the result of the SQL query execution.
196 """
197 renderer = SqlalchemyRender(hana_dialect.HANAHDBCLIDialect)
198 query_str = renderer.get_string(query, with_failback=True)
199 return self.native_query(query_str)
201 def get_tables(self) -> Response:
202 """
203 Retrieves a list of all non-system tables in the SAP HANA database.
205 Returns:
206 Response: A response object containing a list of tables in the SAP HANA database.
207 """
208 query = """
209 SELECT SCHEMA_NAME,
210 TABLE_NAME,
211 'BASE TABLE' AS TABLE_TYPE
212 FROM
213 SYS.TABLES
214 WHERE IS_SYSTEM_TABLE = 'FALSE'
215 AND IS_USER_DEFINED_TYPE = 'FALSE'
216 AND IS_TEMPORARY = 'FALSE'
218 UNION
220 SELECT SCHEMA_NAME,
221 VIEW_NAME AS TABLE_NAME,
222 'VIEW' AS TABLE_TYPE
223 FROM
224 SYS.VIEWS
225 WHERE SCHEMA_NAME <> 'SYS'
226 AND SCHEMA_NAME NOT LIKE '_SYS%'
227 """
228 return self.native_query(query)
230 def get_columns(self, table_name: Text) -> Response:
231 """
232 Retrieves column details for a specified table in the SAP HANA database.
234 Args:
235 table_name (Text): The name of the table for which to retrieve column information.
237 Raises:
238 ValueError: If the 'table_name' is not a valid string.
240 Returns:
241 Response: A response object containing the column details.
242 """
243 if not table_name or not isinstance(table_name, str):
244 raise ValueError("Invalid table name provided.")
246 query = f"""
247 SELECT COLUMN_NAME AS Field,
248 DATA_TYPE_NAME AS Type
249 FROM SYS.TABLE_COLUMNS
250 WHERE TABLE_NAME = '{table_name}'
252 UNION ALL
254 SELECT COLUMN_NAME AS Field,
255 DATA_TYPE_NAME AS Type
256 FROM SYS.VIEW_COLUMNS
257 WHERE VIEW_NAME = '{table_name}'
258 """
259 return self.native_query(query)