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

1from typing import Any, Dict, Text 

2 

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 

9 

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 

17 

18 

19logger = log.getLogger(__name__) 

20 

21 

22class HanaHandler(DatabaseHandler): 

23 """ 

24 This handler handles the connection and execution of SQL statements on SAP HANA. 

25 """ 

26 

27 name = 'hana' 

28 

29 def __init__(self, name: Text, connection_data: Dict, **kwargs: Any) -> None: 

30 """ 

31 Initializes the handler. 

32 

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 

41 

42 self.connection = None 

43 self.is_connected = False 

44 

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() 

51 

52 def connect(self) -> dbapi.Connection: 

53 """ 

54 Establishes a connection to the SAP HANA database. 

55 

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. 

59 

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 

65 

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.') 

69 

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 } 

76 

77 # Optional connection parameters. 

78 if 'database' in self.connection_data: 

79 config['databaseName'] = self.connection_data['database'] 

80 

81 if 'schema' in self.connection_data: 

82 config['currentSchema'] = self.connection_data['schema'] 

83 

84 if 'encrypt' in self.connection_data: 

85 config['encrypt'] = self.connection_data['encrypt'] 

86 

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 

99 

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 

107 

108 def check_connection(self) -> StatusResponse: 

109 """ 

110 Checks the status of the connection to the SAP HANA database. 

111 

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 

117 

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) 

129 

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 

134 

135 return response 

136 

137 def native_query(self, query: Text) -> Response: 

138 """ 

139 Executes a native SQL query on the SAP HANA database and returns the result. 

140 

141 Args: 

142 query (Text): The SQL query to be executed. 

143 

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 

148 

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() 

181 

182 if need_to_close is True: 

183 self.disconnect() 

184 

185 return response 

186 

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). 

190 

191 Args: 

192 query (ASTNode): An ASTNode representing the SQL query to be executed. 

193 

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) 

200 

201 def get_tables(self) -> Response: 

202 """ 

203 Retrieves a list of all non-system tables in the SAP HANA database. 

204 

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' 

217 

218 UNION 

219 

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) 

229 

230 def get_columns(self, table_name: Text) -> Response: 

231 """ 

232 Retrieves column details for a specified table in the SAP HANA database. 

233 

234 Args: 

235 table_name (Text): The name of the table for which to retrieve column information. 

236 

237 Raises: 

238 ValueError: If the 'table_name' is not a valid string. 

239 

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.") 

245 

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}' 

251 

252 UNION ALL 

253 

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)